import gi 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 fabric.widgets.scrolledwindow import ScrolledWindow from gi.repository import GLib, Gtk from config.settings.utils import bind_vars gi.require_version("Gtk", "3.0") class SystemTab: """System settings and metrics management tab for settings""" def __init__(self, show_lock_checkbox=False, show_idle_checkbox=False, window_size_enforcer=None): self.show_lock_checkbox = show_lock_checkbox self.show_idle_checkbox = show_idle_checkbox self.window_size_enforcer = window_size_enforcer # Widget references self.terminal_entry = None self.lock_switch = None self.idle_switch = None self.limited_apps_entry = None self.ignored_apps_entry = None self.metrics_switches = {} self.disk_entries = None self._create_disk_edit_entry_func = None def create_system_tab(self): """Create the System tab content""" scrolled_window = ScrolledWindow( h_scrollbar_policy="never", v_scrollbar_policy="automatic", h_expand=False, v_expand=False, propagate_width=False, propagate_height=False, ) # Set fixed size to match tab stack dimensions scrolled_window.set_size_request(580, 580) vbox = Box(orientation="v", spacing=15, style="margin: 15px;") scrolled_window.add(vbox) # Create terminal and hyprland settings section self._create_terminal_hyprland_section(vbox) # Create notification settings section self._create_notification_settings_section(vbox) # Create system metrics section self._create_system_metrics_section(vbox) return scrolled_window def _create_terminal_hyprland_section(self, vbox): """Create terminal and Hyprland integration section""" system_grid = Gtk.Grid() system_grid.set_column_spacing(20) system_grid.set_row_spacing(10) system_grid.set_margin_bottom(15) vbox.add(system_grid) # Terminal settings terminal_header = Label(markup="Terminal Settings", h_align="start") system_grid.attach(terminal_header, 0, 0, 2, 1) terminal_label = Label(label="Command:", h_align="start", v_align="center") system_grid.attach(terminal_label, 0, 1, 1, 1) self.terminal_entry = Entry( text=bind_vars.get("terminal_command", "kitty -e"), tooltip_text="Command used to launch terminal apps (e.g., 'kitty -e')", h_expand=True, ) system_grid.attach(self.terminal_entry, 1, 1, 1, 1) hint_label = Label( markup="Examples: 'kitty -e', 'alacritty -e', 'foot -e'", h_align="start", ) system_grid.attach(hint_label, 0, 2, 2, 1) # Hyprland integration hypr_header = Label(markup="Hyprland Integration", h_align="start") system_grid.attach(hypr_header, 2, 0, 2, 1) row = 1 self.lock_switch = None if self.show_lock_checkbox: lock_label = Label( label="Replace Hyprlock config", h_align="start", v_align="center" ) system_grid.attach(lock_label, 2, row, 1, 1) lock_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.lock_switch = Gtk.Switch( tooltip_text="Replace Hyprlock configuration with Modus's custom config" ) lock_switch_container.add(self.lock_switch) system_grid.attach(lock_switch_container, 3, row, 1, 1) row += 1 self.idle_switch = None if self.show_idle_checkbox: idle_label = Label( label="Replace Hypridle config", h_align="start", v_align="center" ) system_grid.attach(idle_label, 2, row, 1, 1) idle_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.idle_switch = Gtk.Switch( tooltip_text="Replace Hypridle configuration with Modus's custom config" ) idle_switch_container.add(self.idle_switch) system_grid.attach(idle_switch_container, 3, row, 1, 1) row += 1 if self.show_lock_checkbox or self.show_idle_checkbox: note_label = Label( markup="Existing configs will be backed up", h_align="start", ) system_grid.attach(note_label, 2, row, 2, 1) def _create_notification_settings_section(self, vbox): """Create notification settings section""" notifications_header = Label( markup="Notification Settings", h_align="start" ) vbox.add(notifications_header) notif_grid = Gtk.Grid() notif_grid.set_column_spacing(20) notif_grid.set_row_spacing(10) notif_grid.set_margin_start(10) notif_grid.set_margin_top(5) notif_grid.set_margin_bottom(15) vbox.add(notif_grid) # Limited Apps History limited_apps_label = Label( label="Limited Apps History:", h_align="start", v_align="center" ) notif_grid.attach(limited_apps_label, 0, 0, 1, 1) limited_apps_list = bind_vars.get("limited_apps_history", ["Spotify"]) limited_apps_text = ", ".join(f'"{app}"' for app in limited_apps_list) self.limited_apps_entry = Entry( text=limited_apps_text, tooltip_text='Enter app names separated by commas, e.g: "Spotify", "Discord"', h_expand=True, ) notif_grid.attach(self.limited_apps_entry, 1, 0, 1, 1) limited_apps_hint = Label( markup='Apps with limited notification history (format: "App1", "App2")', h_align="start", ) notif_grid.attach(limited_apps_hint, 0, 1, 2, 1) # History Ignored Apps ignored_apps_label = Label( label="History Ignored Apps:", h_align="start", v_align="center" ) notif_grid.attach(ignored_apps_label, 0, 2, 1, 1) ignored_apps_list = bind_vars.get("history_ignored_apps", ["Modus"]) ignored_apps_text = ", ".join(f'"{app}"' for app in ignored_apps_list) self.ignored_apps_entry = Entry( text=ignored_apps_text, tooltip_text='Enter app names separated by commas, e.g: "Modus", "Screenshot"', h_expand=True, ) notif_grid.attach(self.ignored_apps_entry, 1, 2, 1, 1) ignored_apps_hint = Label( markup='Apps whose notifications are ignored in history (format: "App1", "App2")', h_align="start", ) notif_grid.attach(ignored_apps_hint, 0, 3, 2, 1) def _create_system_metrics_section(self, vbox): """Create system metrics section""" metrics_header = Label(markup="System Metrics Options", h_align="start") vbox.add(metrics_header) metrics_grid = Gtk.Grid( column_spacing=15, row_spacing=8, margin_start=10, margin_top=5 ) vbox.add(metrics_grid) self.metrics_switches = {} metric_names = { "cpu": "CPU", "ram": "RAM", "swap": "Swap", "disk": "Disk", "gpu": "GPU", } metrics_grid.attach(Label(label="Show in Metrics", h_align="start"), 0, 0, 1, 1) for i, (key, label_text) in enumerate(metric_names.items()): switch = Gtk.Switch( active=bind_vars.get("metrics_visible", {}).get(key, True) ) self.metrics_switches[key] = switch metrics_grid.attach( Label(label=label_text, h_align="start"), 0, i + 1, 1, 1 ) metrics_grid.attach(switch, 1, i + 1, 1, 1) def enforce_minimum_metrics(switch_dict): enabled_switches = [s for s in switch_dict.values() if s.get_active()] can_disable = len(enabled_switches) > 1 for s in switch_dict.values(): s.set_sensitive(True if can_disable or not s.get_active() else False) def on_metric_toggle(_switch, _gparam, switch_dict): enforce_minimum_metrics(switch_dict) for s_s in self.metrics_switches.values(): s_s.connect("notify::active", on_metric_toggle, self.metrics_switches) enforce_minimum_metrics(self.metrics_switches) # Disk directories section disks_label = Label( label="Disk directories for Metrics", h_align="start", v_align="center" ) vbox.add(disks_label) # Create a scrolled container for disk entries to prevent overflow disk_entries_scrolled = ScrolledWindow( h_scrollbar_policy="never", v_scrollbar_policy="automatic", h_expand=False, v_expand=False, propagate_width=False, propagate_height=False, ) # Set fixed size for disk entries container disk_entries_scrolled.set_size_request(550, 120) self.disk_entries = Box(orientation="v", spacing=8, h_align="start") # Set size constraints for the disk entries box self.disk_entries.set_size_request(530, -1) disk_entries_scrolled.add(self.disk_entries) self._create_disk_edit_entry_func = lambda path: self._add_disk_entry_widget(path) for p in bind_vars.get("metrics_disks", ["/"]): self._create_disk_edit_entry_func(p) vbox.add(disk_entries_scrolled) add_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) add_btn = Button( label="Add new disk", on_clicked=lambda _: self._create_disk_edit_entry_func("/"), ) add_container.add(add_btn) vbox.add(add_container) def _add_disk_entry_widget(self, path): """Helper to add a disk entry row to the disk_entries Box.""" bar = Box(orientation="h", spacing=10, h_align="start") # Set fixed height for disk entry rows bar.set_size_request(-1, 30) entry = Entry(text=path, h_expand=True) bar.add(entry) x_btn = Button(label="X") x_btn.connect( "clicked", lambda _, current_bar_to_remove=bar: self._remove_disk_entry(current_bar_to_remove), ) bar.add(x_btn) self.disk_entries.add(bar) self.disk_entries.show_all() # Enforce window size after adding content if self.window_size_enforcer: GLib.idle_add(self.window_size_enforcer) def _remove_disk_entry(self, bar_to_remove): """Helper to remove a disk entry and enforce window size""" self.disk_entries.remove(bar_to_remove) # Enforce window size after removing content if self.window_size_enforcer: GLib.idle_add(self.window_size_enforcer) def get_system_values(self): """Get current system values from widgets""" values = {} values["terminal_command"] = self.terminal_entry.get_text() values["metrics_visible"] = { k: s.get_active() for k, s in self.metrics_switches.items() } values["metrics_disks"] = [ child.get_children()[0].get_text() for child in self.disk_entries.get_children() if isinstance(child, Gtk.Box) and child.get_children() and isinstance(child.get_children()[0], Entry) ] # Parse notification app lists def parse_app_list(text): """Parse comma-separated app names with quotes""" if not text.strip(): return [] apps = [] for app in text.split(","): app = app.strip() if app.startswith('"') and app.endswith('"'): app = app[1:-1] elif app.startswith("'") and app.endswith("'"): app = app[1:-1] if app: apps.append(app) return apps values["limited_apps_history"] = parse_app_list( self.limited_apps_entry.get_text() ) values["history_ignored_apps"] = parse_app_list( self.ignored_apps_entry.get_text() ) return values def get_hyprland_switches(self): """Get the state of Hyprland integration switches""" return { "replace_lock": self.lock_switch and self.lock_switch.get_active(), "replace_idle": self.idle_switch and self.idle_switch.get_active() } def reset_to_defaults(self, defaults): """Reset system settings to default values""" self.terminal_entry.set_text(defaults.get("terminal_command", "kitty -e")) metrics_vis_defaults = defaults.get("metrics_visible", {}) for k, s_widget in self.metrics_switches.items(): s_widget.set_active(metrics_vis_defaults.get(k, True)) def enforce_minimum_metrics(switch_dict): enabled_switches = [s for s in switch_dict.values() if s.get_active()] can_disable = len(enabled_switches) > 1 for s in switch_dict.values(): s.set_sensitive( True if can_disable or not s.get_active() else False ) enforce_minimum_metrics(self.metrics_switches) # Reset disk entries for child in list(self.disk_entries.get_children()): self.disk_entries.remove(child) for p in defaults.get("metrics_disks", ["/"]): self._create_disk_edit_entry_func(p) # Reset notification app lists limited_apps_list = defaults.get("limited_apps_history", ["Spotify"]) limited_apps_text = ", ".join(f'"{app}"' for app in limited_apps_list) self.limited_apps_entry.set_text(limited_apps_text) ignored_apps_list = defaults.get("history_ignored_apps", ["Modus"]) ignored_apps_text = ", ".join(f'"{app}"' for app in ignored_apps_list) self.ignored_apps_entry.set_text(ignored_apps_text) # Reset Hyprland switches if self.lock_switch: self.lock_switch.set_active(False) if self.idle_switch: self.idle_switch.set_active(False)