1489 lines
60 KiB
Python
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()
|