config
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
files = [
|
||||
"core/app_launcher.py",
|
||||
"core/config.py",
|
||||
"core/discord_presence.py",
|
||||
"core/dukto.py",
|
||||
"core/file_search.py",
|
||||
|
||||
@@ -193,14 +193,9 @@ def launch(app: App):
|
||||
return
|
||||
|
||||
try:
|
||||
# Using ShellExecute is more robust for launching various application types on Windows,
|
||||
# as it leverages the Windows shell's own mechanisms. This is particularly helpful for
|
||||
# non-standard executables like PWAs or Microsoft Store apps.
|
||||
command_parts = shlex.split(app.exec, posix=False)
|
||||
target = command_parts[0]
|
||||
|
||||
# Use subprocess.list2cmdline to correctly re-assemble the arguments string,
|
||||
# preserving quotes around arguments with spaces.
|
||||
arguments = subprocess.list2cmdline(command_parts[1:])
|
||||
|
||||
win32api.ShellExecute(
|
||||
|
||||
82
core/config.py
Normal file
82
core/config.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
import platform
|
||||
|
||||
class Config:
|
||||
DEFAULT_CONFIG = {
|
||||
"hotkey": "super",
|
||||
"discord_presence": True,
|
||||
"auto_update": True,
|
||||
"http_share_port": 8080,
|
||||
"dukto_udp_port": 4644,
|
||||
"dukto_tcp_port": 4644,
|
||||
"search_engine": "brave"
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.config_dir = self._get_config_dir()
|
||||
self.config_file = self.config_dir / "config.json"
|
||||
self.config_data = self._load_config()
|
||||
|
||||
def _get_config_dir(self) -> Path:
|
||||
system = platform.system()
|
||||
|
||||
if system == "Windows":
|
||||
base = Path.home() / "AppData" / "Roaming"
|
||||
else:
|
||||
base = Path.home() / ".config"
|
||||
|
||||
config_dir = base / "CLARA"
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
return config_dir
|
||||
|
||||
def _load_config(self) -> Dict[str, Any]:
|
||||
system = platform.system()
|
||||
|
||||
if self.config_file.exists():
|
||||
try:
|
||||
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||
loaded = json.load(f)
|
||||
# Merge with defaults for updates
|
||||
config = self.DEFAULT_CONFIG.copy()
|
||||
config.update(loaded)
|
||||
return config
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Error loading config: {e}. Using defaults.")
|
||||
return self.DEFAULT_CONFIG.copy()
|
||||
else:
|
||||
# default config file
|
||||
conf = self.DEFAULT_CONFIG.copy()
|
||||
if system == "Windows":
|
||||
conf["hotkey"] = "ctrl+space"
|
||||
else:
|
||||
conf["hotkey"] = "super"
|
||||
|
||||
self._save_config(self.DEFAULT_CONFIG)
|
||||
return self.DEFAULT_CONFIG.copy()
|
||||
|
||||
def _save_config(self, config_data: Dict[str, Any]):
|
||||
try:
|
||||
with open(self.config_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
except IOError as e:
|
||||
print(f"Error saving config: {e}")
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
return self.config_data.get(key, default)
|
||||
|
||||
def set(self, key: str, value: Any):
|
||||
self.config_data[key] = value
|
||||
self._save_config(self.config_data)
|
||||
|
||||
def get_all(self) -> Dict[str, Any]:
|
||||
return self.config_data.copy()
|
||||
|
||||
def reset(self):
|
||||
self.config_data = self.DEFAULT_CONFIG.copy()
|
||||
self._save_config(self.config_data)
|
||||
|
||||
|
||||
# Global config instance
|
||||
config = Config()
|
||||
19
main.py
19
main.py
@@ -8,6 +8,7 @@ from core.discord_presence import presence
|
||||
from core.dukto import DuktoProtocol
|
||||
from core.updater import update_repository, is_update_available
|
||||
from core.app_launcher import list_apps
|
||||
from core.config import config
|
||||
|
||||
from windows.main_window import MainWindow
|
||||
|
||||
@@ -40,7 +41,7 @@ def main():
|
||||
super_menu = not "--no-super" in sys.argv and not "--no-start" in sys.argv
|
||||
noupdate = "--no-update" in sys.argv
|
||||
|
||||
if not noupdate:
|
||||
if not noupdate and config.get("auto_update", True):
|
||||
update_available = is_update_available()
|
||||
if update_available:
|
||||
update_repository()
|
||||
@@ -50,10 +51,22 @@ def main():
|
||||
preload_thread.start()
|
||||
|
||||
dukto_handler = DuktoProtocol()
|
||||
dukto_handler.set_ports(
|
||||
udp_port=config.get("dukto_udp_port", 4644),
|
||||
tcp_port=config.get("dukto_tcp_port", 4644)
|
||||
)
|
||||
|
||||
pet = MainWindow(dukto_handler=dukto_handler, strings=strings, restart=restart, no_quit=no_quit, super_menu=super_menu)
|
||||
pet = MainWindow(
|
||||
dukto_handler=dukto_handler,
|
||||
strings=strings,
|
||||
config=config,
|
||||
restart=restart,
|
||||
no_quit=no_quit,
|
||||
super_menu=super_menu
|
||||
)
|
||||
|
||||
presence.start()
|
||||
if config.get("discord_presence", True):
|
||||
presence.start()
|
||||
|
||||
dukto_handler.initialize()
|
||||
dukto_handler.say_hello()
|
||||
|
||||
@@ -23,4 +23,4 @@ class FileSearchResults(QtWidgets.QDialog):
|
||||
if os.path.exists(file_path):
|
||||
directory = os.path.dirname(file_path)
|
||||
url = QtCore.QUrl.fromLocalFile(directory)
|
||||
QtGui.QDesktopServices.openUrl(url)
|
||||
QtGui.QDesktopServices.openUrl(url)
|
||||
@@ -10,6 +10,7 @@ from core.dukto import Peer
|
||||
from core.file_search import find
|
||||
from core.web_search import MullvadLetaWrapper
|
||||
from core.http_share import FileShareServer
|
||||
from core.config import Config
|
||||
|
||||
from windows.app_launcher import AppLauncherDialog
|
||||
from windows.file_search import FileSearchResults
|
||||
@@ -38,10 +39,12 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
http_download_signal = QtCore.Signal(str, str)
|
||||
|
||||
|
||||
def __init__(self, dukto_handler, strings, restart=False, no_quit=False, super_menu=True):
|
||||
def __init__(self, dukto_handler, strings, config: Config, restart=False, no_quit=False, super_menu=True):
|
||||
super().__init__()
|
||||
|
||||
self.strings = strings
|
||||
self.config = config
|
||||
self.listener = None
|
||||
|
||||
flags = (
|
||||
QtCore.Qt.FramelessWindowHint #type: ignore
|
||||
@@ -69,7 +72,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
self.progress_dialog = None
|
||||
|
||||
# HTTP file sharing
|
||||
self.http_share = FileShareServer(port=8080)
|
||||
http_port = self.config.get("http_share_port", 8080)
|
||||
self.http_share = FileShareServer(port=http_port)
|
||||
self.http_share.on_download = lambda filename, ip: self.http_download_signal.emit(filename, ip)
|
||||
|
||||
# Connect Dukto callbacks to emit signals
|
||||
@@ -109,7 +113,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# Super key
|
||||
self.show_menu_signal.connect(self.show_menu)
|
||||
self.start_hotkey_listener()
|
||||
if self.super_menu:
|
||||
self.start_hotkey_listener()
|
||||
|
||||
def build_menus(self):
|
||||
s = self.strings["main_window"]["right_menu"]
|
||||
@@ -200,17 +205,40 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
def show_menu(self):
|
||||
self.left_menu.popup(QtGui.QCursor.pos())
|
||||
|
||||
def on_press(self, key):
|
||||
if self.super_menu:
|
||||
if key == keyboard.Key.cmd:
|
||||
self.show_menu_signal.emit()
|
||||
|
||||
def start_hotkey_listener(self):
|
||||
self.listener = keyboard.Listener(on_press=self.on_press)
|
||||
self.listener.start()
|
||||
hotkey_str = self.config.get("hotkey", "super")
|
||||
|
||||
def on_activate():
|
||||
self.show_menu_signal.emit()
|
||||
|
||||
key_map = {
|
||||
"super": "cmd",
|
||||
"ctrl": "ctrl",
|
||||
"space": "space",
|
||||
}
|
||||
|
||||
try:
|
||||
keys = [key_map.get(k.strip().lower(), k.strip().lower()) for k in hotkey_str.split('+')]
|
||||
formatted_hotkey = '<' + '>+<'.join(keys) + '>' #type: ignore
|
||||
|
||||
hotkey = keyboard.HotKey(
|
||||
keyboard.HotKey.parse(formatted_hotkey),
|
||||
on_activate
|
||||
)
|
||||
|
||||
self.listener = keyboard.Listener(
|
||||
on_press=hotkey.press, #type: ignore
|
||||
on_release=hotkey.release #type: ignore
|
||||
)
|
||||
self.listener.start()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to set hotkey '{hotkey_str}': {e}. Hotkey will be disabled.")
|
||||
self.listener = None
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.listener.stop()
|
||||
if self.listener:
|
||||
self.listener.stop()
|
||||
if self.http_share.is_running():
|
||||
self.http_share.stop()
|
||||
super().closeEvent(event)
|
||||
@@ -518,7 +546,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||
if ok and query:
|
||||
try:
|
||||
QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) #type: ignore
|
||||
leta = MullvadLetaWrapper(engine="brave")
|
||||
search_engine = self.config.get("search_engine", "brave")
|
||||
leta = MullvadLetaWrapper(engine=search_engine)
|
||||
results = leta.search(query)
|
||||
|
||||
if results and results.get('results'):
|
||||
|
||||
Reference in New Issue
Block a user