Files
Modus/config/settings/system_tab.py
T

383 lines
15 KiB
Python

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="<b>Terminal Settings</b>", 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="<small>Examples: 'kitty -e', 'alacritty -e', 'foot -e'</small>",
h_align="start",
)
system_grid.attach(hint_label, 0, 2, 2, 1)
# Hyprland integration
hypr_header = Label(markup="<b>Hyprland Integration</b>", 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="<small>Existing configs will be backed up</small>",
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="<b>Notification Settings</b>", 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='<small>Apps with limited notification history (format: "App1", "App2")</small>',
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='<small>Apps whose notifications are ignored in history (format: "App1", "App2")</small>',
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="<b>System Metrics Options</b>", 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)