Files
2026-06-03 21:26:54 +02:00

1489 lines
60 KiB
Python

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="<b>Action</b>", h_align="start", style="margin-bottom: 5px;"
)
modifier_label = Label(
markup="<b>Modifier</b>", h_align="start", style="margin-bottom: 5px;"
)
separator_label = Label(
label="+", h_align="center", style="margin-bottom: 5px;"
)
key_label = Label(
markup="<b>Key</b>", 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="<b>Wallpapers</b>", 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="<b>Profile Icon</b>", 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="<b>Date & Time Format</b>", 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="<b>Layout Options</b>", 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="<b>Modules</b>", 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="<b>Monitor Selection</b>", 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="<small>Leave all unchecked to show on all monitors</small>",
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="<b>Terminal Settings</b>", 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="<small>Examples: 'kitty -e', 'alacritty -e', 'foot -e'</small>",
h_align="start",
)
system_grid.attach(hint_label, 0, 5, 2, 1)
hypr_header = Label(markup="<b>Hyprland Integration</b>", 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="<small>Existing configs will be backed up</small>",
h_align="start",
)
system_grid.attach(note_label, 2, row, 2, 1)
# Notifications app lists 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 = 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='<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 = 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='<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)
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 = {}
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"<b>{APP_NAME_CAP}</b>",
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='<a href="https://github.com/Axenide/Ax-Shell">https://github.com/Axenide/Ax-Shell</a>'
)
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()