import json import os import shutil import subprocess import time from pathlib import Path import gi gi.require_version("Gtk", "3.0") from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.entry import Entry from fabric.widgets.image import Image as FabricImage from fabric.widgets.label import Label from fabric.widgets.scale import Scale from fabric.widgets.scrolledwindow import ScrolledWindow from fabric.widgets.stack import Stack from fabric.widgets.window import Window from gi.repository import GdkPixbuf, GLib, Gtk from PIL import Image from .data import ( APP_NAME, APP_NAME_CAP, ) from .settings_utils import ( backup_and_replace, bind_vars, get_bind_var, get_default, start_config, ) class HyprConfGUI(Window): def __init__(self, show_lock_checkbox: bool, show_idle_checkbox: bool, **kwargs): super().__init__( title="Ax-Shell Settings", name="axshell-settings-window", size=(640, 640), **kwargs, ) self.set_resizable(False) self.selected_face_icon = None self.show_lock_checkbox = show_lock_checkbox self.show_idle_checkbox = show_idle_checkbox root_box = Box(orientation="v", spacing=10, style="margin: 10px;") self.add(root_box) main_content_box = Box(orientation="h", spacing=6, v_expand=True, h_expand=True) root_box.add(main_content_box) self.tab_stack = Stack( transition_type="slide-up-down", transition_duration=250, v_expand=True, h_expand=True, ) self.key_bindings_tab_content = self.create_key_bindings_tab() self.appearance_tab_content = self.create_appearance_tab() self.system_tab_content = self.create_system_tab() self.about_tab_content = self.create_about_tab() self.tab_stack.add_titled( self.key_bindings_tab_content, "key_bindings", "Key Bindings" ) self.tab_stack.add_titled( self.appearance_tab_content, "appearance", "Appearance" ) self.tab_stack.add_titled(self.system_tab_content, "system", "System") self.tab_stack.add_titled(self.about_tab_content, "about", "About") tab_switcher = Gtk.StackSwitcher() tab_switcher.set_stack(self.tab_stack) tab_switcher.set_orientation(Gtk.Orientation.VERTICAL) main_content_box.add(tab_switcher) main_content_box.add(self.tab_stack) button_box = Box(orientation="h", spacing=10, h_align="end") reset_btn = Button(label="Reset to Defaults", on_clicked=self.on_reset) button_box.add(reset_btn) close_btn = Button(label="Close", on_clicked=self.on_close) button_box.add(close_btn) accept_btn = Button(label="Apply & Reload", on_clicked=self.on_accept) button_box.add(accept_btn) root_box.add(button_box) def create_key_bindings_tab(self): scrolled_window = ScrolledWindow( h_scrollbar_policy="never", v_scrollbar_policy="automatic", h_expand=True, v_expand=True, propagate_width=False, propagate_height=False, ) main_vbox = Box(orientation="v", spacing=10, style="margin: 15px;") scrolled_window.add(main_vbox) keybind_grid = Gtk.Grid() keybind_grid.set_column_spacing(10) keybind_grid.set_row_spacing(8) keybind_grid.set_margin_start(5) keybind_grid.set_margin_end(5) keybind_grid.set_margin_top(5) keybind_grid.set_margin_bottom(5) action_label = Label( markup="Action", h_align="start", style="margin-bottom: 5px;" ) modifier_label = Label( markup="Modifier", h_align="start", style="margin-bottom: 5px;" ) separator_label = Label( label="+", h_align="center", style="margin-bottom: 5px;" ) key_label = Label( markup="Key", h_align="start", style="margin-bottom: 5px;" ) keybind_grid.attach(action_label, 0, 0, 1, 1) keybind_grid.attach(modifier_label, 1, 0, 1, 1) keybind_grid.attach(separator_label, 2, 0, 1, 1) keybind_grid.attach(key_label, 3, 0, 1, 1) self.entries = [] bindings = [ (f"Reload {APP_NAME_CAP}", "prefix_restart", "suffix_restart"), ("Message", "prefix_axmsg", "suffix_axmsg"), ("Dashboard", "prefix_dash", "suffix_dash"), ("Bluetooth", "prefix_bluetooth", "suffix_bluetooth"), ("Pins", "prefix_pins", "suffix_pins"), ("Kanban", "prefix_kanban", "suffix_kanban"), ("App Launcher", "prefix_launcher", "suffix_launcher"), ("Tmux", "prefix_tmux", "suffix_tmux"), ("Clipboard History", "prefix_cliphist", "suffix_cliphist"), ("Toolbox", "prefix_toolbox", "suffix_toolbox"), ("Overview", "prefix_overview", "suffix_overview"), ("Wallpapers", "prefix_wallpapers", "suffix_wallpapers"), ("Random Wallpaper", "prefix_randwall", "suffix_randwall"), ("Audio Mixer", "prefix_mixer", "suffix_mixer"), ("Emoji Picker", "prefix_emoji", "suffix_emoji"), ("Power Menu", "prefix_power", "suffix_power"), ("Toggle Caffeine", "prefix_caffeine", "suffix_caffeine"), ("Toggle Bar", "prefix_toggle", "suffix_toggle"), ("Reload CSS", "prefix_css", "suffix_css"), ( "Restart with inspector", "prefix_restart_inspector", "suffix_restart_inspector", ), ] for i, (label_text, prefix_key, suffix_key) in enumerate(bindings): row = i + 1 binding_label = Label(label=label_text, h_align="start") keybind_grid.attach(binding_label, 0, row, 1, 1) prefix_entry = Entry(text=get_bind_var(prefix_key)) keybind_grid.attach(prefix_entry, 1, row, 1, 1) plus_label = Label(label="+", h_align="center") keybind_grid.attach(plus_label, 2, row, 1, 1) suffix_entry = Entry(text=get_bind_var(suffix_key)) keybind_grid.attach(suffix_entry, 3, row, 1, 1) self.entries.append((prefix_key, suffix_key, prefix_entry, suffix_entry)) main_vbox.add(keybind_grid) return scrolled_window def create_appearance_tab(self): scrolled_window = ScrolledWindow( h_scrollbar_policy="never", v_scrollbar_policy="automatic", h_expand=True, v_expand=True, propagate_width=False, propagate_height=False, ) vbox = Box(orientation="v", spacing=15, style="margin: 15px;") scrolled_window.add(vbox) top_grid = Gtk.Grid() top_grid.set_column_spacing(20) top_grid.set_row_spacing(5) top_grid.set_margin_bottom(10) vbox.add(top_grid) wall_header = Label(markup="Wallpapers", h_align="start") top_grid.attach(wall_header, 0, 0, 1, 1) wall_label = Label(label="Directory:", h_align="start", v_align="center") top_grid.attach(wall_label, 0, 1, 1, 1) chooser_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) chooser_container.set_halign(Gtk.Align.START) chooser_container.set_valign(Gtk.Align.CENTER) self.wall_dir_chooser = Gtk.FileChooserButton( title="Select a folder", action=Gtk.FileChooserAction.SELECT_FOLDER ) self.wall_dir_chooser.set_tooltip_text( "Select the directory containing your wallpaper images" ) self.wall_dir_chooser.set_filename(get_bind_var("wallpapers_dir")) self.wall_dir_chooser.set_size_request(180, -1) chooser_container.add(self.wall_dir_chooser) top_grid.attach(chooser_container, 1, 1, 1, 1) face_header = Label(markup="Profile Icon", h_align="start") top_grid.attach(face_header, 2, 0, 2, 1) current_face = os.path.expanduser("~/.face.icon") face_image_container = Box( style_classes=["image-frame"], h_align="center", v_align="center" ) self.face_image = FabricImage(size=64) try: if os.path.exists(current_face): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(current_face, 64, 64) self.face_image.set_from_pixbuf(pixbuf) else: self.face_image.set_from_icon_name("user-info", Gtk.IconSize.DIALOG) except Exception as e: print(f"Error loading face icon: {e}") self.face_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG) face_image_container.add(self.face_image) top_grid.attach(face_image_container, 2, 1, 1, 1) browse_btn_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) browse_btn_container.set_halign(Gtk.Align.START) browse_btn_container.set_valign(Gtk.Align.CENTER) face_btn = Button( label="Browse...", tooltip_text="Select a square image for your profile icon", on_clicked=self.on_select_face_icon, ) browse_btn_container.add(face_btn) top_grid.attach(browse_btn_container, 3, 1, 1, 1) self.face_status_label = Label(label="", h_align="start") top_grid.attach(self.face_status_label, 2, 2, 2, 1) separator1 = Box( style="min-height: 1px; background-color: alpha(@fg_color, 0.2); margin: 5px 0px;", h_expand=True, ) vbox.add(separator1) # START NEW SECTION FOR DATETIME FORMAT datetime_format_header = Label( markup="Date & Time Format", h_align="start" ) vbox.add(datetime_format_header) datetime_grid = Gtk.Grid() datetime_grid.set_column_spacing(20) datetime_grid.set_row_spacing(10) datetime_grid.set_margin_start(10) datetime_grid.set_margin_top(5) datetime_grid.set_margin_bottom(10) # Adds space before the next section vbox.add(datetime_grid) datetime_12h_label = Label( label="Use 12-Hour Clock", h_align="start", v_align="center" ) datetime_grid.attach(datetime_12h_label, 0, 0, 1, 1) datetime_12h_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.datetime_12h_switch = Gtk.Switch( active=get_bind_var("datetime_12h_format") ) datetime_12h_switch_container.add(self.datetime_12h_switch) datetime_grid.attach(datetime_12h_switch_container, 1, 0, 1, 1) # END NEW SECTION FOR DATETIME FORMAT layout_header = Label(markup="Layout Options", h_align="start") vbox.add(layout_header) layout_grid = Gtk.Grid() layout_grid.set_column_spacing(20) layout_grid.set_row_spacing(10) layout_grid.set_margin_start(10) layout_grid.set_margin_top(5) vbox.add(layout_grid) position_label = Label(label="Bar Position", h_align="start", v_align="center") layout_grid.attach(position_label, 0, 0, 1, 1) position_combo_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.position_combo = Gtk.ComboBoxText() self.position_combo.set_tooltip_text("Select the position of the bar") positions = ["Top", "Bottom", "Left", "Right"] for pos in positions: self.position_combo.append_text(pos) current_position = get_bind_var("bar_position") try: self.position_combo.set_active(positions.index(current_position)) except ValueError: self.position_combo.set_active(0) self.position_combo.connect("changed", self.on_position_changed) position_combo_container.add(self.position_combo) layout_grid.attach(position_combo_container, 1, 0, 1, 1) centered_label = Label( label="Centered Bar (Left/Right Only)", h_align="start", v_align="center" ) layout_grid.attach(centered_label, 2, 0, 1, 1) centered_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.centered_switch = Gtk.Switch( active=get_bind_var("centered_bar"), sensitive=get_bind_var("bar_position") in ["Left", "Right"], ) centered_switch_container.add(self.centered_switch) layout_grid.attach(centered_switch_container, 3, 0, 1, 1) dock_label = Label(label="Show Dock", h_align="start", v_align="center") layout_grid.attach(dock_label, 0, 1, 1, 1) dock_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.dock_switch = Gtk.Switch(active=get_bind_var("dock_enabled")) self.dock_switch.connect("notify::active", self.on_dock_enabled_changed) dock_switch_container.add(self.dock_switch) layout_grid.attach(dock_switch_container, 1, 1, 1, 1) dock_hover_label = Label( label="Always Show Dock", h_align="start", v_align="center" ) layout_grid.attach(dock_hover_label, 2, 1, 1, 1) dock_hover_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.dock_hover_switch = Gtk.Switch( active=get_bind_var("dock_always_show"), sensitive=self.dock_switch.get_active(), ) dock_hover_switch_container.add(self.dock_hover_switch) layout_grid.attach(dock_hover_switch_container, 3, 1, 1, 1) dock_size_label = Label( label="Dock Icon Size", h_align="start", v_align="center" ) layout_grid.attach(dock_size_label, 0, 2, 1, 1) self.dock_size_scale = Scale( min_value=16, max_value=48, value=get_bind_var("dock_icon_size"), increments=(2, 4), draw_value=True, value_position="right", digits=0, h_expand=True, ) layout_grid.attach(self.dock_size_scale, 1, 2, 3, 1) ws_num_label = Label( label="Show Workspace Numbers", h_align="start", v_align="center" ) layout_grid.attach(ws_num_label, 0, 3, 1, 1) ws_num_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.ws_num_switch = Gtk.Switch( active=get_bind_var("bar_workspace_show_number") ) self.ws_num_switch.connect("notify::active", self.on_ws_num_changed) ws_num_switch_container.add(self.ws_num_switch) layout_grid.attach(ws_num_switch_container, 1, 3, 1, 1) ws_chinese_label = Label( label="Use Chinese Numerals", h_align="start", v_align="center" ) layout_grid.attach(ws_chinese_label, 2, 3, 1, 1) ws_chinese_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.ws_chinese_switch = Gtk.Switch( active=get_bind_var("bar_workspace_use_chinese_numerals"), sensitive=self.ws_num_switch.get_active(), ) ws_chinese_switch_container.add(self.ws_chinese_switch) layout_grid.attach(ws_chinese_switch_container, 3, 3, 1, 1) special_ws_label = Label( label="Hide Special Workspace", h_align="start", v_align="center" ) layout_grid.attach(special_ws_label, 0, 4, 1, 1) special_ws_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.special_ws_switch = Gtk.Switch( active=get_bind_var("bar_hide_special_workspace") ) special_ws_switch_container.add(self.special_ws_switch) layout_grid.attach(special_ws_switch_container, 1, 4, 1, 1) bar_theme_label = Label(label="Bar Theme", h_align="start", v_align="center") layout_grid.attach(bar_theme_label, 0, 5, 1, 1) bar_theme_combo_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.bar_theme_combo = Gtk.ComboBoxText() self.bar_theme_combo.set_tooltip_text("Select the visual theme for the bar") themes = ["Pills", "Dense", "Edge"] for theme in themes: self.bar_theme_combo.append_text(theme) current_theme = get_bind_var("bar_theme") try: self.bar_theme_combo.set_active(themes.index(current_theme)) except ValueError: self.bar_theme_combo.set_active(0) bar_theme_combo_container.add(self.bar_theme_combo) layout_grid.attach(bar_theme_combo_container, 1, 5, 3, 1) dock_theme_label = Label(label="Dock Theme", h_align="start", v_align="center") layout_grid.attach(dock_theme_label, 0, 6, 1, 1) dock_theme_combo_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.dock_theme_combo = Gtk.ComboBoxText() self.dock_theme_combo.set_tooltip_text("Select the visual theme for the dock") for theme in themes: self.dock_theme_combo.append_text(theme) current_dock_theme = get_bind_var("dock_theme") try: self.dock_theme_combo.set_active(themes.index(current_dock_theme)) except ValueError: self.dock_theme_combo.set_active(0) dock_theme_combo_container.add(self.dock_theme_combo) layout_grid.attach(dock_theme_combo_container, 1, 6, 3, 1) panel_theme_label = Label( label="Panel Theme", h_align="start", v_align="center" ) layout_grid.attach(panel_theme_label, 0, 7, 1, 1) panel_theme_combo_container = Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.panel_theme_combo = Gtk.ComboBoxText() self.panel_theme_combo.set_tooltip_text( "Select the theme/mode for panels like toolbox, clipboard, etc." ) panel_themes = ["Notch", "Panel"] for theme in panel_themes: self.panel_theme_combo.append_text(theme) current_panel_theme = get_bind_var("panel_theme") try: self.panel_theme_combo.set_active(panel_themes.index(current_panel_theme)) except ValueError: self.panel_theme_combo.set_active(0) panel_theme_combo_container.add(self.panel_theme_combo) layout_grid.attach(panel_theme_combo_container, 1, 7, 1, 1) self.panel_theme_combo.connect( "changed", self._on_panel_theme_changed_for_position_sensitivity ) self.panel_position_options = ["Start", "Center", "End"] panel_position_label = Label( label="Panel Position", h_align="start", v_align="center" ) layout_grid.attach(panel_position_label, 2, 7, 1, 1) panel_position_combo_container = Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.panel_position_combo = Gtk.ComboBoxText() self.panel_position_combo.set_tooltip_text( "Select the position for the 'Panel' theme panels" ) for option in self.panel_position_options: self.panel_position_combo.append_text(option) current_panel_position = get_bind_var("panel_position") try: self.panel_position_combo.set_active( self.panel_position_options.index(current_panel_position) ) except ValueError: try: self.panel_position_combo.set_active( self.panel_position_options.index("Center") ) except ValueError: self.panel_position_combo.set_active(0) panel_position_combo_container.add(self.panel_position_combo) layout_grid.attach(panel_position_combo_container, 3, 7, 1, 1) notification_pos_label = Label( label="Notification Position", h_align="start", v_align="center" ) layout_grid.attach(notification_pos_label, 0, 8, 1, 1) notification_pos_combo_container = Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.notification_pos_combo = Gtk.ComboBoxText() self.notification_pos_combo.set_tooltip_text( "Select where notifications appear on the screen." ) notification_positions_list = ["Top", "Bottom"] for pos in notification_positions_list: self.notification_pos_combo.append_text(pos) current_notif_pos = get_bind_var("notif_pos") try: self.notification_pos_combo.set_active( notification_positions_list.index(current_notif_pos) ) except ValueError: self.notification_pos_combo.set_active(0) self.notification_pos_combo.connect( "changed", self.on_notification_position_changed ) notification_pos_combo_container.add(self.notification_pos_combo) layout_grid.attach(notification_pos_combo_container, 1, 8, 3, 1) separator2 = Box( style="min-height: 1px; background-color: alpha(@fg_color, 0.2); margin: 5px 0px;", h_expand=True, ) vbox.add(separator2) components_header = Label(markup="Modules", h_align="start") vbox.add(components_header) components_grid = Gtk.Grid() components_grid.set_column_spacing(15) components_grid.set_row_spacing(8) components_grid.set_margin_start(10) components_grid.set_margin_top(5) vbox.add(components_grid) self.component_switches = {} component_display_names = { "button_apps": "App Launcher Button", "systray": "System Tray", "control": "Control Panel", "network": "Network Applet", "button_tools": "Toolbox Button", "sysprofiles": "Powerprofiles Switcher", "button_overview": "Overview Button", "ws_container": "Workspaces", "weather": "Weather Widget", "battery": "Battery Indicator", "metrics": "System Metrics", "language": "Language Indicator", "date_time": "Date & Time", "button_power": "Power Button", } self.corners_switch = Gtk.Switch(active=get_bind_var("corners_visible")) num_components = len(component_display_names) + 1 rows_per_column = (num_components + 1) // 2 corners_label = Label( label="Rounded Corners", h_align="start", v_align="center" ) components_grid.attach(corners_label, 0, 0, 1, 1) switch_container_corners = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) switch_container_corners.add(self.corners_switch) components_grid.attach(switch_container_corners, 1, 0, 1, 1) current_row = 0 current_col = 0 item_idx = 0 for i, (name, display) in enumerate(component_display_names.items()): if item_idx < (rows_per_column - 1): row = item_idx + 1 col = 0 else: row = item_idx - (rows_per_column - 1) col = 2 component_label = Label(label=display, h_align="start", v_align="center") components_grid.attach(component_label, col, row, 1, 1) switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) component_switch = Gtk.Switch(active=get_bind_var(f"bar_{name}_visible")) switch_container.add(component_switch) components_grid.attach(switch_container, col + 1, row, 1, 1) self.component_switches[name] = component_switch item_idx += 1 self._update_panel_position_sensitivity() return scrolled_window def _on_panel_theme_changed_for_position_sensitivity(self, combo): self._update_panel_position_sensitivity() def _update_panel_position_sensitivity(self): if hasattr(self, "panel_theme_combo") and hasattr(self, "panel_position_combo"): selected_theme = self.panel_theme_combo.get_active_text() is_panel_theme_selected = selected_theme == "Panel" self.panel_position_combo.set_sensitive(is_panel_theme_selected) def on_notification_position_changed(self, combo: Gtk.ComboBoxText): selected_text = combo.get_active_text() if selected_text: bind_vars["notif_pos"] = selected_text print( f"Notification position updated in bind_vars: {bind_vars["notif_pos"]}" ) def create_system_tab(self): scrolled_window = ScrolledWindow( h_scrollbar_policy="never", v_scrollbar_policy="automatic", h_expand=True, v_expand=True, propagate_width=False, propagate_height=False, ) vbox = Box(orientation="v", spacing=15, style="margin: 15px;") scrolled_window.add(vbox) 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) # Auto-append checkbox - first option auto_append_label = Label( label="Auto-append to hyprland.conf", h_align="start", v_align="center" ) system_grid.attach(auto_append_label, 0, 0, 1, 1) auto_append_switch_container = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.START, valign=Gtk.Align.CENTER, ) self.auto_append_switch = Gtk.Switch( active=get_bind_var("auto_append_hyprland"), tooltip_text="Automatically append Ax-Shell source string to hyprland.conf", ) auto_append_switch_container.add(self.auto_append_switch) system_grid.attach(auto_append_switch_container, 1, 0, 1, 1) # Monitor Selection - second option monitor_header = Label(markup="Monitor Selection", h_align="start") system_grid.attach(monitor_header, 0, 1, 2, 1) monitor_label = Label( label="Show Ax-Shell on monitors:", h_align="start", v_align="center" ) system_grid.attach(monitor_label, 0, 2, 1, 1) # Create monitor selection container self.monitor_selection_container = Box( orientation="v", spacing=5, h_align="start" ) self.monitor_checkboxes = {} # Get available monitors try: from utils.monitor_manager import get_monitor_manager monitor_manager = get_monitor_manager() available_monitors = monitor_manager.get_monitors() except (ImportError, Exception) as e: print(f"Could not get monitor info for settings: {e}") available_monitors = [{"id": 0, "name": "default"}] # Get current selection from config current_selection = get_bind_var("selected_monitors") # Create checkboxes for each monitor for monitor in available_monitors: monitor_name = monitor.get("name", f'monitor-{monitor.get("id", 0)}') checkbox_container = Box(orientation="h", spacing=5, h_align="start") checkbox = Gtk.CheckButton(label=monitor_name) # Check if this monitor is selected (empty selection means all selected) is_selected = ( len(current_selection) == 0 or monitor_name in current_selection ) checkbox.set_active(is_selected) checkbox_container.add(checkbox) self.monitor_selection_container.add(checkbox_container) self.monitor_checkboxes[monitor_name] = checkbox # Add hint label hint_label = Label( markup="Leave all unchecked to show on all monitors", h_align="start", ) self.monitor_selection_container.add(hint_label) system_grid.attach(self.monitor_selection_container, 1, 2, 1, 1) terminal_header = Label(markup="Terminal Settings", h_align="start") system_grid.attach(terminal_header, 0, 3, 2, 1) terminal_label = Label(label="Command:", h_align="start", v_align="center") system_grid.attach(terminal_label, 0, 4, 1, 1) self.terminal_entry = Entry( text=get_bind_var("terminal_command"), tooltip_text="Command used to launch terminal apps (e.g., 'kitty -e')", h_expand=True, ) system_grid.attach(self.terminal_entry, 1, 4, 1, 1) hint_label = Label( markup="Examples: 'kitty -e', 'alacritty -e', 'foot -e'", h_align="start", ) system_grid.attach(hint_label, 0, 5, 2, 1) hypr_header = Label(markup="Hyprland Integration", h_align="start") system_grid.attach(hypr_header, 2, 3, 2, 1) row = 4 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 Ax-Shell'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 Ax-Shell'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) # Notifications app lists 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 = get_bind_var("limited_apps_history") 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 = get_bind_var("history_ignored_apps") 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: "Hyprshot", "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) 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 = {} self.metrics_small_switches = {} metric_names = {"cpu": "CPU", "ram": "RAM", "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=get_bind_var("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) metrics_grid.attach( Label(label="Show in Small Metrics", h_align="start"), 2, 0, 1, 1 ) for i, (key, label_text) in enumerate(metric_names.items()): switch = Gtk.Switch( active=get_bind_var("metrics_small_visible").get(key, True) ) self.metrics_small_switches[key] = switch metrics_grid.attach( Label(label=label_text, h_align="start"), 2, i + 1, 1, 1 ) metrics_grid.attach(switch, 3, 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) > 3 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 k_s, s_s in self.metrics_switches.items(): s_s.connect("notify::active", on_metric_toggle, self.metrics_switches) for k_s, s_s in self.metrics_small_switches.items(): s_s.connect("notify::active", on_metric_toggle, self.metrics_small_switches) enforce_minimum_metrics(self.metrics_switches) enforce_minimum_metrics(self.metrics_small_switches) disks_label = Label( label="Disk directories for Metrics", h_align="start", v_align="center" ) vbox.add(disks_label) self.disk_entries = Box(orientation="v", spacing=8, h_align="start") self._create_disk_edit_entry_func = lambda path: self._add_disk_entry_widget( path ) for p in get_bind_var("bar_metrics_disks"): self._create_disk_edit_entry_func(p) vbox.add(self.disk_entries) 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) return scrolled_window def _add_disk_entry_widget(self, path): """Helper para añadir una fila de entrada de disco al Box disk_entries.""" bar = Box(orientation="h", spacing=10, h_align="start") 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.disk_entries.remove( current_bar_to_remove ), ) bar.add(x_btn) self.disk_entries.add(bar) self.disk_entries.show_all() def create_about_tab(self): vbox = Box(orientation="v", spacing=18, style="margin: 30px;") vbox.add( Label( markup=f"{APP_NAME_CAP}", h_align="start", style="font-size: 1.5em; margin-bottom: 8px;", ) ) vbox.add( Label( label="A hackable shell for Hyprland, powered by Fabric.", h_align="start", style="margin-bottom: 12px;", ) ) repo_box = Box(orientation="h", spacing=6, h_align="start") repo_label = Label(label="GitHub:", h_align="start") repo_link = Label( markup='https://github.com/Axenide/Ax-Shell' ) repo_box.add(repo_label) repo_box.add(repo_link) vbox.add(repo_box) def on_kofi_clicked(_): import webbrowser webbrowser.open("https://ko-fi.com/Axenide") kofi_btn = Button( label="Support on Ko-Fi ❤️", on_clicked=on_kofi_clicked, tooltip_text="Support Axenide on Ko-Fi", style="margin-top: 18px; min-width: 160px;", ) vbox.add(kofi_btn) vbox.add(Box(v_expand=True)) return vbox def on_ws_num_changed(self, switch, gparam): is_active = switch.get_active() self.ws_chinese_switch.set_sensitive(is_active) if not is_active: self.ws_chinese_switch.set_active(False) def on_position_changed(self, combo): position = combo.get_active_text() is_vertical = position in ["Left", "Right"] self.centered_switch.set_sensitive(is_vertical) if not is_vertical: self.centered_switch.set_active(False) def on_dock_enabled_changed(self, switch, gparam): is_active = switch.get_active() self.dock_hover_switch.set_sensitive(is_active) if not is_active: self.dock_hover_switch.set_active(False) def on_select_face_icon(self, widget): dialog = Gtk.FileChooserDialog( title="Select Face Icon", transient_for=self.get_toplevel(), action=Gtk.FileChooserAction.OPEN, ) dialog.add_buttons( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, ) image_filter = Gtk.FileFilter() image_filter.set_name("Image files") for mime in ["image/png", "image/jpeg"]: image_filter.add_mime_type(mime) for pattern in ["*.png", "*.jpg", "*.jpeg"]: image_filter.add_pattern(pattern) dialog.add_filter(image_filter) if dialog.run() == Gtk.ResponseType.OK: self.selected_face_icon = dialog.get_filename() self.face_status_label.label = ( f"Selected: {os.path.basename(self.selected_face_icon)}" ) try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( self.selected_face_icon, 64, 64 ) self.face_image.set_from_pixbuf(pixbuf) except Exception as e: print(f"Error loading selected face icon preview: {e}") self.face_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG) dialog.destroy() def on_accept(self, widget): current_bind_vars_snapshot = {} for prefix_key, suffix_key, prefix_entry, suffix_entry in self.entries: current_bind_vars_snapshot[prefix_key] = prefix_entry.get_text() current_bind_vars_snapshot[suffix_key] = suffix_entry.get_text() current_bind_vars_snapshot["wallpapers_dir"] = ( self.wall_dir_chooser.get_filename() ) current_bind_vars_snapshot["bar_position"] = ( self.position_combo.get_active_text() ) current_bind_vars_snapshot["vertical"] = current_bind_vars_snapshot[ "bar_position" ] in ["Left", "Right"] current_bind_vars_snapshot["centered_bar"] = self.centered_switch.get_active() current_bind_vars_snapshot["datetime_12h_format"] = ( self.datetime_12h_switch.get_active() ) current_bind_vars_snapshot["dock_enabled"] = self.dock_switch.get_active() current_bind_vars_snapshot["dock_always_show"] = ( self.dock_hover_switch.get_active() ) current_bind_vars_snapshot["dock_icon_size"] = int(self.dock_size_scale.value) current_bind_vars_snapshot["terminal_command"] = self.terminal_entry.get_text() current_bind_vars_snapshot["auto_append_hyprland"] = ( self.auto_append_switch.get_active() ) current_bind_vars_snapshot["corners_visible"] = self.corners_switch.get_active() current_bind_vars_snapshot["bar_workspace_show_number"] = ( self.ws_num_switch.get_active() ) current_bind_vars_snapshot["bar_workspace_use_chinese_numerals"] = ( self.ws_chinese_switch.get_active() ) current_bind_vars_snapshot["bar_hide_special_workspace"] = ( self.special_ws_switch.get_active() ) current_bind_vars_snapshot["bar_theme"] = self.bar_theme_combo.get_active_text() current_bind_vars_snapshot["dock_theme"] = ( self.dock_theme_combo.get_active_text() ) current_bind_vars_snapshot["panel_theme"] = ( self.panel_theme_combo.get_active_text() ) current_bind_vars_snapshot["panel_position"] = ( self.panel_position_combo.get_active_text() ) selected_notif_pos_text = self.notification_pos_combo.get_active_text() if selected_notif_pos_text: current_bind_vars_snapshot["notif_pos"] = selected_notif_pos_text else: current_bind_vars_snapshot["notif_pos"] = "Top" for component_name, switch in self.component_switches.items(): current_bind_vars_snapshot[f"bar_{component_name}_visible"] = ( switch.get_active() ) current_bind_vars_snapshot["metrics_visible"] = { k: s.get_active() for k, s in self.metrics_switches.items() } current_bind_vars_snapshot["metrics_small_visible"] = { k: s.get_active() for k, s in self.metrics_small_switches.items() } current_bind_vars_snapshot["bar_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 current_bind_vars_snapshot["limited_apps_history"] = parse_app_list( self.limited_apps_entry.get_text() ) current_bind_vars_snapshot["history_ignored_apps"] = parse_app_list( self.ignored_apps_entry.get_text() ) # Save monitor selection selected_monitors = [] any_checked = False for monitor_name, checkbox in self.monitor_checkboxes.items(): if checkbox.get_active(): selected_monitors.append(monitor_name) any_checked = True # If no monitors are checked, use empty array (means show on all monitors) current_bind_vars_snapshot["selected_monitors"] = ( selected_monitors if any_checked else [] ) selected_icon_path = self.selected_face_icon replace_lock = self.lock_switch and self.lock_switch.get_active() replace_idle = self.idle_switch and self.idle_switch.get_active() if self.selected_face_icon: self.selected_face_icon = None self.face_status_label.label = "" def _apply_and_reload_task_thread(user_data): nonlocal current_bind_vars_snapshot from . import settings_utils settings_utils.bind_vars.clear() settings_utils.bind_vars.update(current_bind_vars_snapshot) start_time = time.time() print(f"{start_time:.4f}: Background task started.") config_json = os.path.expanduser( f"~/.config/{APP_NAME_CAP}/config/config.json" ) os.makedirs(os.path.dirname(config_json), exist_ok=True) try: with open(config_json, "w") as f: json.dump(settings_utils.bind_vars, f, indent=4) print(f"{time.time():.4f}: Saved config.json.") except Exception as e: print(f"Error saving config.json: {e}") if selected_icon_path: print(f"{time.time():.4f}: Processing face icon...") try: img = Image.open(selected_icon_path) side = min(img.size) left = (img.width - side) // 2 top = (img.height - side) // 2 cropped_img = img.crop((left, top, left + side, top + side)) face_icon_dest = os.path.expanduser("~/.face.icon") cropped_img.save(face_icon_dest, format="PNG") print(f"{time.time():.4f}: Face icon saved to {face_icon_dest}") GLib.idle_add(self._update_face_image_widget, face_icon_dest) except Exception as e: print(f"Error processing face icon: {e}") print(f"{time.time():.4f}: Finished processing face icon.") if replace_lock: print(f"{time.time():.4f}: Replacing hyprlock config...") src = os.path.expanduser( f"~/.config/{APP_NAME_CAP}/config/hypr/hyprlock.conf" ) dest = os.path.expanduser("~/.config/hypr/hyprlock.conf") if os.path.exists(src): backup_and_replace(src, dest, "Hyprlock") else: print(f"Warning: Source hyprlock config not found at {src}") print(f"{time.time():.4f}: Finished replacing hyprlock config.") if replace_idle: print(f"{time.time():.4f}: Replacing hypridle config...") src = os.path.expanduser( f"~/.config/{APP_NAME_CAP}/config/hypr/hypridle.conf" ) dest = os.path.expanduser("~/.config/hypr/hypridle.conf") if os.path.exists(src): backup_and_replace(src, dest, "Hypridle") else: print(f"Warning: Source hypridle config not found at {src}") print(f"{time.time():.4f}: Finished replacing hypridle config.") print( f"{time.time():.4f}: Checking/Appending hyprland.conf source string..." ) hypr_path = os.path.expanduser("~/.config/hypr/hyprland.conf") try: from .settings_constants import SOURCE_STRING # Check if auto-append is enabled auto_append_enabled = current_bind_vars_snapshot.get( "auto_append_hyprland", True ) if auto_append_enabled: needs_append = True if os.path.exists(hypr_path): with open(hypr_path, "r") as f: if SOURCE_STRING.strip() in f.read(): needs_append = False else: os.makedirs(os.path.dirname(hypr_path), exist_ok=True) if needs_append: with open(hypr_path, "a") as f: f.write("\n" + SOURCE_STRING) print(f"Appended source string to {hypr_path}") else: print("Source string already present in hyprland.conf") else: print("Auto-append to hyprland.conf is disabled") except Exception as e: print(f"Error updating {hypr_path}: {e}") print( f"{time.time():.4f}: Finished checking/appending hyprland.conf source string." ) print(f"{time.time():.4f}: Running start_config()...") start_config() print(f"{time.time():.4f}: Finished start_config().") print(f"{time.time():.4f}: Initiating Ax-Shell restart using Popen...") main_py = os.path.expanduser(f"~/.config/{APP_NAME_CAP}/main.py") kill_cmd = f"killall {APP_NAME}" start_cmd = ["uwsm", "app", "--", "python", main_py] try: kill_proc = subprocess.Popen( kill_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) kill_proc.wait(timeout=2) print(f"{time.time():.4f}: killall process finished (o timed out).") except subprocess.TimeoutExpired: print("Warning: killall command timed out.") except Exception as e: print(f"Error running killall: {e}") try: subprocess.Popen( start_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, ) print(f"{APP_NAME_CAP} restart initiated via Popen.") except FileNotFoundError as e: print(f"Error restarting {APP_NAME_CAP}: Command not found ({e})") except Exception as e: print(f"Error restarting {APP_NAME_CAP} via Popen: {e}") print(f"{time.time():.4f}: Ax-Shell restart commands issued via Popen.") end_time = time.time() print( f"{end_time:.4f}: Background task finished (Total: {end_time - start_time:.4f}s)." ) GLib.Thread.new("apply-reload-task", _apply_and_reload_task_thread, None) print("Configuration apply/reload task started in background.") def _update_face_image_widget(self, icon_path): try: if self.face_image and self.face_image.get_window(): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, 64, 64) self.face_image.set_from_pixbuf(pixbuf) except Exception as e: print(f"Error reloading face icon preview: {e}") if self.face_image and self.face_image.get_window(): self.face_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG) return GLib.SOURCE_REMOVE def on_reset(self, widget): dialog = Gtk.MessageDialog( transient_for=self.get_toplevel(), flags=0, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO, text="Reset all settings to defaults?", ) dialog.format_secondary_text( "This will reset all keybindings and appearance settings to their default values." ) if dialog.run() == Gtk.ResponseType.YES: from . import settings_utils from .settings_constants import DEFAULTS settings_utils.bind_vars.clear() settings_utils.bind_vars.update(DEFAULTS.copy()) for prefix_key, suffix_key, prefix_entry, suffix_entry in self.entries: prefix_entry.set_text(settings_utils.bind_vars[prefix_key]) suffix_entry.set_text(settings_utils.bind_vars[suffix_key]) self.wall_dir_chooser.set_filename( settings_utils.bind_vars["wallpapers_dir"] ) positions = ["Top", "Bottom", "Left", "Right"] default_position = get_default("bar_position") try: self.position_combo.set_active(positions.index(default_position)) except ValueError: self.position_combo.set_active(0) self.centered_switch.set_active(get_bind_var("centered_bar")) self.centered_switch.set_sensitive(default_position in ["Left", "Right"]) self.datetime_12h_switch.set_active(get_bind_var("datetime_12h_format")) self.dock_switch.set_active(get_bind_var("dock_enabled")) self.dock_hover_switch.set_active(get_bind_var("dock_always_show")) self.dock_hover_switch.set_sensitive(self.dock_switch.get_active()) self.dock_size_scale.set_value(get_bind_var("dock_icon_size")) self.terminal_entry.set_text(settings_utils.bind_vars["terminal_command"]) self.auto_append_switch.set_active(get_bind_var("auto_append_hyprland")) self.ws_num_switch.set_active(get_bind_var("bar_workspace_show_number")) self.ws_chinese_switch.set_active( get_bind_var("bar_workspace_use_chinese_numerals") ) self.ws_chinese_switch.set_sensitive(self.ws_num_switch.get_active()) self.special_ws_switch.set_active( get_bind_var("bar_hide_special_workspace") ) default_theme_val = get_default("bar_theme") themes = ["Pills", "Dense", "Edge"] try: self.bar_theme_combo.set_active(themes.index(default_theme_val)) except ValueError: self.bar_theme_combo.set_active(0) default_dock_theme_val = get_default("dock_theme") try: self.dock_theme_combo.set_active(themes.index(default_dock_theme_val)) except ValueError: self.dock_theme_combo.set_active(0) default_panel_theme_val = get_default("panel_theme") panel_themes_options = ["Notch", "Panel"] try: self.panel_theme_combo.set_active( panel_themes_options.index(default_panel_theme_val) ) except ValueError: self.panel_theme_combo.set_active(0) default_panel_position_val = get_default("panel_position") try: self.panel_position_combo.set_active( self.panel_position_options.index(default_panel_position_val) ) except ValueError: try: self.panel_position_combo.set_active( self.panel_position_options.index("Center") ) except ValueError: self.panel_position_combo.set_active(0) default_notif_pos_val = get_default("notif_pos") notification_positions_list = ["Top", "Bottom"] try: self.notification_pos_combo.set_active( notification_positions_list.index(default_notif_pos_val) ) except ValueError: self.notification_pos_combo.set_active(0) for name, switch in self.component_switches.items(): switch.set_active(get_bind_var(f"bar_{name}_visible")) self.corners_switch.set_active(get_bind_var("corners_visible")) metrics_vis_defaults = get_default("metrics_visible") for k, s_widget in self.metrics_switches.items(): s_widget.set_active(metrics_vis_defaults.get(k, True)) metrics_small_vis_defaults = get_default("metrics_small_visible") for k, s_widget in self.metrics_small_switches.items(): s_widget.set_active(metrics_small_vis_defaults.get(k, True)) def enforce_minimum_metrics(switch_dict): enabled_switches = [ s_widget for s_widget in switch_dict.values() if s_widget.get_active() ] can_disable = len(enabled_switches) > 3 for s_widget in switch_dict.values(): s_widget.set_sensitive( True if can_disable or not s_widget.get_active() else False ) enforce_minimum_metrics(self.metrics_switches) enforce_minimum_metrics(self.metrics_small_switches) for child in list(self.disk_entries.get_children()): self.disk_entries.remove(child) for p in get_default("bar_metrics_disks"): self._add_disk_edit_entry_func(p) # Reset notification app lists limited_apps_list = get_default("limited_apps_history") limited_apps_text = ", ".join(f'"{app}"' for app in limited_apps_list) self.limited_apps_entry.set_text(limited_apps_text) ignored_apps_list = get_default("history_ignored_apps") ignored_apps_text = ", ".join(f'"{app}"' for app in ignored_apps_list) self.ignored_apps_entry.set_text(ignored_apps_text) # Reset monitor selection default_monitors = get_default("selected_monitors") for monitor_name, checkbox in self.monitor_checkboxes.items(): # If defaults is empty, check all monitors (show on all) is_selected = ( len(default_monitors) == 0 or monitor_name in default_monitors ) checkbox.set_active(is_selected) self._update_panel_position_sensitivity() self.selected_face_icon = None self.face_status_label.label = "" current_face = os.path.expanduser("~/.face.icon") try: pixbuf = ( GdkPixbuf.Pixbuf.new_from_file_at_size(current_face, 64, 64) if os.path.exists(current_face) else None ) if pixbuf: self.face_image.set_from_pixbuf(pixbuf) else: self.face_image.set_from_icon_name("user-info", Gtk.IconSize.DIALOG) except Exception: self.face_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG) if self.lock_switch: self.lock_switch.set_active(False) if self.idle_switch: self.idle_switch.set_active(False) print("Settings reset to defaults.") dialog.destroy() def on_close(self, widget): if self.application: self.application.quit() else: self.destroy()