Files
Modus/mine/modules/launcher/plugins/settings.py
T
2026-07-12 18:56:10 +05:45

445 lines
15 KiB
Python

from fabric.utils import Gtk, idle_add, remove_handler
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.label import Label
from config import data as config_data
from modules.launcher.base import LauncherPlugin
class SettingsPlugin(LauncherPlugin):
@property
def name(self) -> str:
return "settings"
@property
def icon(self) -> str:
return "emblem-system-symbolic"
@property
def keywords(self) -> list[str]:
return ["settings", "set"]
def __init__(self, handler):
super().__init__(handler)
self._query_handler: int = 0
self._settings_items: list[dict] = []
def on_search(self, text: str) -> None:
self.query_settings(text)
def stop(self):
if self._query_handler:
remove_handler(self._query_handler)
self._query_handler = 0
def query_settings(self, text: str) -> None:
self.handler.start("settings")
self._settings_items = self._get_all_settings()
filtered = (
self._settings_items
if not text.strip()
else [i for i in self._settings_items if text.lower() in i["name"].lower()]
)
if not filtered:
return
self._query_handler = idle_add(self._bake_settings, iter(filtered), pin=True)
def _get_all_settings(self) -> list[dict]:
config = config_data.DATA()
systray_hide_icons = config.get("systray_hide_icons", [])
# Convert list to comma-separated string for display
systray_hide_icons_str = (
", ".join(systray_hide_icons)
if isinstance(systray_hide_icons, list)
else str(systray_hide_icons)
)
return [
{
"name": "OSD",
"key": "osd",
"type": "switch",
"value": config.get("osd", True),
},
{
"name": "OSD Position",
"key": "osd_position",
"type": "select",
"options": ["top", "bottom"],
"value": config.get("osd_position", "bottom"),
},
{
"name": "OSD Audio",
"key": "osd_audio",
"type": "switch",
"value": config.get("osd_audio", True),
},
{
"name": "OSD Brightness",
"key": "osd_brightness",
"type": "switch",
"value": config.get("osd_brightness", True),
},
{
"name": "OSD Capslock",
"key": "osd_capslock",
"type": "switch",
"value": config.get("osd_capslock", True),
},
{
"name": "OSD Keyboard Layout",
"key": "osd_kb_layout",
"type": "switch",
"value": config.get("osd_kb_layout", True),
},
{
"name": "OSD Network",
"key": "osd_network",
"type": "switch",
"value": config.get("osd_network", True),
},
{
"name": "OSD Microphone",
"key": "osd_microphone",
"type": "switch",
"value": config.get("osd_microphone", True),
},
{
"name": "OSD Battery",
"key": "osd_battery",
"type": "switch",
"value": config.get("osd_battery", True),
},
{
"name": "Panel",
"key": "panel_enabled",
"type": "switch",
"value": config.get("panel_enabled", True),
},
{
"name": "Panel Position",
"key": "panel_position",
"type": "select",
"options": ["top", "bottom"],
"value": config.get("panel_position", "top"),
},
{
"name": "Panel Autohide",
"key": "panel_autohide",
"type": "switch",
"value": config.get("panel_autohide", True),
},
{
"name": "Systray",
"key": "systray",
"type": "switch",
"value": config.get("systray", True),
},
{
"name": "Network",
"key": "network",
"type": "switch",
"value": config.get("network", True),
},
{
"name": "Bluetooth",
"key": "bluetooth",
"type": "switch",
"value": config.get("bluetooth", True),
},
{
"name": "DateTime",
"key": "datetime",
"type": "switch",
"value": config.get("datetime", True),
},
{
"name": "DateTime 12hrs",
"key": "datetime_12hrs",
"type": "switch",
"value": config.get("datetime_12hrs", True),
},
{
"name": "Battery",
"key": "battery",
"type": "switch",
"value": config.get("battery", True),
},
{
"name": "Battery Label",
"key": "battery_label",
"type": "switch",
"value": config.get("battery_label", True),
},
{
"name": "Battery Hide Full",
"key": "battery_hide_on_full",
"type": "switch",
"value": config.get("battery_hide_on_full", True),
},
{
"name": "Workspace",
"key": "workspace",
"type": "switch",
"value": config.get("workspace", True),
},
{
"name": "Workspace Hide Special",
"key": "workspace_hide_special",
"type": "switch",
"value": config.get("workspace_hide_special", True),
},
{
"name": "Taskbar",
"key": "taskbar",
"type": "switch",
"value": config.get("taskbar", True),
},
{
"name": "Taskbar Hide Empty",
"key": "taskbar_hide_empty",
"type": "switch",
"value": config.get("taskbar_hide_empty", True),
},
{
"name": "Notifications",
"key": "notifications",
"type": "switch",
"value": config.get("notifications", True),
},
{
"name": "Desktop Widget",
"key": "desktop_widget_enabled",
"type": "switch",
"value": config.get("desktop_widget_enabled", True),
},
{
"name": "Desktop Widget Time",
"key": "desktop_widget_time_format",
"type": "select",
"options": ["12 hrs", "24 hrs"],
"value": config.get("desktop_widget_time_format", "12 hrs"),
},
{
"name": "Corners",
"key": "corners",
"type": "switch",
"value": config.get("corners", True),
},
{
"name": "Debug",
"key": "debug",
"type": "switch",
"value": config.get("debug", True),
},
{
"name": "Wallpaper Path",
"key": "wallpaper_dir",
"type": "entry",
"value": config.get("wallpaper_dir", ""),
},
{
"name": "Systray Hide Items",
"key": "systray_hide_icons",
"type": "entry",
"value": systray_hide_icons_str,
},
]
def _bake_settings(self, iterator) -> bool:
item = next(iterator, None)
if item is None:
self.handler.done()
return False
self._create_setting_slot(item)
return True
def _create_setting_slot(self, item: dict) -> None:
# For all types, put label on left and control on right
if item["type"] == "switch":
# Left side: label
name_label = Label(label=item["name"])
# Right side: switch
switch = Gtk.Switch()
switch.set_active(item["value"])
switch.connect("state-set", self._on_switch_toggled, item)
switch.show()
content = Box(
orientation="h",
spacing=12,
children=[
name_label,
switch,
],
)
slot = Button(
style_classes="app-slot settings-slot",
child=content,
on_clicked=lambda *_: switch.set_active(not switch.get_active()),
)
elif item["type"] == "select":
# Left side: label with colon
name_label = Label(label=f"{item['name']}: ")
# Right side: entry for editing (always visible)
value_text = item["options"][item["options"].index(item["value"])]
entry = Entry(
h_expand=True,
stop_propagation=False,
)
entry.set_text(value_text)
entry.connect("focus-out-event", self._on_entry_focus_out, item)
entry.connect("activate", self._on_entry_activate, item)
entry.connect("key-press-event", self._on_entry_key_press, item)
# Store reference for updating
item["_entry_ref"] = entry
content = Box(
orientation="h",
spacing=12,
children=[
name_label,
entry,
],
)
# Create button that focuses the entry when clicked
def _focus_entry(button):
entry.grab_focus()
entry.select_region(0, -1) # Select all text
slot = Button(
style_classes="app-slot settings-slot",
child=content,
on_clicked=_focus_entry,
)
else: # entry type (text input)
# Left side: label with colon
name_label = Label(label=f"{item['name']}: ")
# Right side: entry for editing (always visible)
entry = Entry(
h_expand=True,
stop_propagation=False,
)
entry.set_text(item["value"])
entry.connect("focus-out-event", self._on_entry_focus_out, item)
entry.connect("activate", self._on_entry_activate, item)
entry.connect("key-press-event", self._on_entry_key_press, item)
# Store reference for updating
item["_entry_ref"] = entry
content = Box(
orientation="h",
spacing=12,
children=[
name_label,
entry,
],
)
# Create button that focuses the entry when clicked
def _focus_entry(button):
entry.grab_focus()
entry.select_region(0, -1) # Select all text
slot = Button(
style_classes="app-slot settings-slot",
child=content,
on_clicked=_focus_entry,
)
self.handler.slot_ready(slot, "settings")
def _on_switch_toggled(self, switch: Gtk.Switch, state, item: dict) -> None:
new_value = switch.get_active()
item["value"] = new_value
def do_update():
config_data.update_setting(item["key"], new_value)
return False
idle_add(do_update)
def _on_entry_activate(self, entry, item: dict) -> None:
"""Handle Enter key press in entry"""
new_value = entry.get_text().strip()
# For select type, validate against options
if item["type"] == "select" and new_value not in item["options"]:
# Reset to current value if invalid
entry.set_text(item["value"])
return
# For systray_hide_icons, convert comma-separated string to list
if item["key"] == "systray_hide_icons":
# Split by comma and strip whitespace from each item
new_value = [item.strip() for item in new_value.split(",") if item.strip()]
else:
item["value"] = new_value
item["value"] = new_value
def do_update():
config_data.update_setting(item["key"], new_value)
return False
idle_add(do_update)
# Remove focus from entry after saving by focusing parent button
def unfocus_entry():
try:
parent_box = entry.get_parent()
if parent_box:
parent_button = parent_box.get_parent()
if parent_button and hasattr(parent_button, "grab_focus"):
parent_button.grab_focus()
except Exception:
pass # Ignore errors in unfocusing
return False
idle_add(unfocus_entry)
def _on_entry_focus_out(self, entry, event, item: dict) -> None:
"""Handle focus out event (when user clicks away)"""
new_value = entry.get_text().strip()
# For select type, validate against options
if item["type"] == "select" and new_value not in item["options"]:
# Reset to current value if invalid
entry.set_text(item["value"])
return
# For systray_hide_icons, convert comma-separated string to list
if item["key"] == "systray_hide_icons":
# Split by comma and strip whitespace from each item
new_value = [item.strip() for item in new_value.split(",") if item.strip()]
else:
item["value"] = new_value
item["value"] = new_value
def do_update():
config_data.update_setting(item["key"], new_value)
return False
idle_add(do_update)
def _on_entry_key_press(self, entry, event, item: dict) -> None:
"""Handle key press events - Escape to cancel"""
# Escape key resets to current value
if event.keyval == 65307: # GDK_KEY_Escape
entry.set_text(item["value"])
return True # Prevent further processing
return False # Allow other keys to be processed
def handle_external(self, command: str, args: str) -> None:
pass