🐛 fix(ui): prevent window resizing in settings app

This commit is contained in:
sankalp committed 2025-07-21 09:16:20 +05:45
1 parent 36904e84e8
commit bb64eb17d7
6 files changed
+279 -258

No files matched your search

+46 -61
View File
@@ -1,12 +1,13 @@
import os
import gi
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.grid import Grid
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.scale import Scale
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GdkPixbuf, Gtk
from config.data import NOTIF_POS_DEFAULT, NOTIF_POS_KEY
@@ -22,7 +23,7 @@ class AppearanceTab:
self.themes = themes
self.parent_window = parent_window
self.selected_face_icon = None
# Widget references
self.wall_dir_chooser = None
self.face_image = None
@@ -60,7 +61,7 @@ class AppearanceTab:
# Create wallpaper and profile icon section
self._create_wallpaper_profile_section(vbox)
# Add separator
separator1 = Box(
style="min-height: 1px; background-color: alpha(@fg_color, 0.2); margin: 5px 0px;",
@@ -85,11 +86,7 @@ class AppearanceTab:
def _create_wallpaper_profile_section(self, vbox):
"""Create wallpaper and profile icon section"""
top_grid = Grid(
column_spacing=20,
row_spacing=5,
style="margin-bottom: 10px;"
)
top_grid = Grid(column_spacing=20, row_spacing=5, style="margin-bottom: 10px;")
vbox.add(top_grid)
# Wallpaper section
@@ -99,9 +96,7 @@ class AppearanceTab:
top_grid.attach(wall_label, 0, 1, 1, 1)
chooser_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.wall_dir_chooser = Gtk.FileChooserButton(
title="Select a folder", action=Gtk.FileChooserAction.SELECT_FOLDER
@@ -135,9 +130,7 @@ class AppearanceTab:
top_grid.attach(face_image_container, 2, 1, 1, 1)
browse_btn_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
face_btn = Button(
label="Browse...",
@@ -156,7 +149,7 @@ class AppearanceTab:
layout_grid = Grid(
column_spacing=20,
row_spacing=10,
style="margin-left: 10px; margin-top: 5px;"
style="margin-left: 10px; margin-top: 5px;",
)
vbox.add(layout_grid)
@@ -165,9 +158,7 @@ class AppearanceTab:
layout_grid.attach(ws_mode_label, 0, 0, 1, 1)
ws_mode_combo_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.ws_mode_combo = Gtk.ComboBoxText()
self.ws_mode_combo.set_tooltip_text("Select how workspaces are displayed")
@@ -200,9 +191,7 @@ class AppearanceTab:
)
layout_grid.attach(ws_chinese_label, 2, 0, 1, 1)
ws_chinese_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.ws_chinese_switch = Gtk.Switch(
active=bind_vars.get("workspace_use_chinese_numerals", False),
@@ -218,9 +207,7 @@ class AppearanceTab:
position_label = Label(label="Dock Position", h_align="start", v_align="center")
layout_grid.attach(position_label, 0, 1, 1, 1)
position_combo_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.position_combo = Gtk.ComboBoxText()
self.position_combo.set_tooltip_text("Select the position of the dock")
@@ -240,9 +227,7 @@ class AppearanceTab:
dock_theme_label = Label(label="Dock Theme", h_align="start", v_align="center")
layout_grid.attach(dock_theme_label, 2, 1, 1, 1)
dock_theme_combo_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_theme_combo = Gtk.ComboBoxText()
self.dock_theme_combo.set_tooltip_text("Select the visual theme for the dock")
@@ -260,9 +245,7 @@ class AppearanceTab:
dock_label = Label(label="Show Dock", h_align="start", v_align="center")
layout_grid.attach(dock_label, 0, 2, 1, 1)
dock_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_switch = Gtk.Switch(active=bind_vars.get("dock_enabled", True))
self.dock_switch.connect("notify::active", self.on_dock_enabled_changed)
@@ -275,9 +258,7 @@ class AppearanceTab:
)
layout_grid.attach(dock_hover_label, 2, 2, 1, 1)
dock_hover_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_hover_switch = Gtk.Switch(
active=bind_vars.get("dock_always_occluded", False),
@@ -292,9 +273,7 @@ class AppearanceTab:
)
layout_grid.attach(dock_auto_hide_label, 0, 3, 1, 1)
dock_auto_hide_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_auto_hide_switch = Gtk.Switch(
active=bind_vars.get("dock_auto_hide", False),
@@ -309,9 +288,7 @@ class AppearanceTab:
)
layout_grid.attach(dock_hide_special_label, 2, 3, 1, 1)
dock_hide_special_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_hide_special_switch = Gtk.Switch(
active=bind_vars.get("dock_hide_special_workspace", True),
@@ -351,7 +328,7 @@ class AppearanceTab:
self.application_switcher_scale = Scale(
min_value=5,
max_value=20,
value=bind_vars.get("window_switcher_items_per_row",13),
value=bind_vars.get("window_switcher_items_per_row", 13),
increments=(2, 4),
draw_value=True,
value_position="right",
@@ -361,7 +338,9 @@ class AppearanceTab:
self.application_switcher_scale.set_tooltip_text(
"Adjust the number of items per row in the application switcher"
)
layout_grid.attach(self.application_switcher_scale, 1, 5, 3, 1) # Span 3 columns
layout_grid.attach(
self.application_switcher_scale, 1, 5, 3, 1
) # Span 3 columns
# Hide Special Workspace Apps
dock_hide_special_apps_label = Label(
@@ -369,9 +348,7 @@ class AppearanceTab:
)
layout_grid.attach(dock_hide_special_apps_label, 0, 6, 1, 1)
dock_hide_special_apps_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.dock_hide_special_apps_switch = Gtk.Switch(
active=bind_vars.get("dock_hide_special_workspace_apps", True),
@@ -390,9 +367,7 @@ class AppearanceTab:
layout_grid.attach(notification_pos_label, 2, 6, 1, 1)
notification_pos_combo_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.notification_pos_combo = Gtk.ComboBoxText()
@@ -426,7 +401,7 @@ class AppearanceTab:
components_grid = Grid(
column_spacing=15,
row_spacing=8,
style="margin-left: 10px; margin-top: 5px;"
style="margin-left: 10px; margin-top: 5px;",
)
vbox.add(components_grid)
@@ -452,9 +427,7 @@ class AppearanceTab:
)
components_grid.attach(corners_label, 0, 0, 1, 1)
switch_container_corners = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
switch_container_corners.add(self.corners_switch)
components_grid.attach(switch_container_corners, 1, 0, 1, 1)
@@ -472,9 +445,7 @@ class AppearanceTab:
components_grid.attach(component_label, col, row, 1, 1)
switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
component_switch = Gtk.Switch(
active=bind_vars.get(f"dock_{name}_visible", True)
@@ -568,10 +539,16 @@ class AppearanceTab:
values["dock_enabled"] = self.dock_switch.get_active()
values["dock_auto_hide"] = self.dock_auto_hide_switch.get_active()
values["dock_always_occluded"] = self.dock_hover_switch.get_active()
values["dock_hide_special_workspace"] = self.dock_hide_special_switch.get_active()
values["dock_hide_special_workspace_apps"] = self.dock_hide_special_apps_switch.get_active()
values["dock_hide_special_workspace"] = (
self.dock_hide_special_switch.get_active()
)
values["dock_hide_special_workspace_apps"] = (
self.dock_hide_special_apps_switch.get_active()
)
values["dock_icon_size"] = int(self.icon_size_scale.get_value())
values["window_switcher_items_per_row"] = int(self.application_switcher_scale.get_value())
values["window_switcher_items_per_row"] = int(
self.application_switcher_scale.get_value()
)
values["corners_visible"] = self.corners_switch.get_active()
values["dock_theme"] = self.dock_theme_combo.get_active_text()
@@ -615,8 +592,12 @@ class AppearanceTab:
self.dock_switch.set_active(defaults.get("dock_enabled", True))
self.dock_auto_hide_switch.set_active(defaults.get("dock_auto_hide", False))
self.dock_hover_switch.set_active(defaults.get("dock_always_occluded", False))
self.dock_hide_special_switch.set_active(defaults.get("dock_hide_special_workspace", True))
self.dock_hide_special_apps_switch.set_active(defaults.get("dock_hide_special_workspace_apps", True))
self.dock_hide_special_switch.set_active(
defaults.get("dock_hide_special_workspace", True)
)
self.dock_hide_special_apps_switch.set_active(
defaults.get("dock_hide_special_workspace_apps", True)
)
# Update sensitivity based on dock enabled state
dock_enabled = self.dock_switch.get_active()
@@ -659,7 +640,9 @@ class AppearanceTab:
except ValueError:
self.ws_mode_combo.set_active(0)
self.ws_chinese_switch.set_active(defaults.get("workspace_use_chinese_numerals", False))
self.ws_chinese_switch.set_active(
defaults.get("workspace_use_chinese_numerals", False)
)
self.ws_chinese_switch.set_sensitive(default_nums)
self.corners_switch.set_active(defaults.get("corners_visible", True))
@@ -679,4 +662,6 @@ class AppearanceTab:
# Reset scales
self.icon_size_scale.set_value(defaults.get("dock_icon_size", 20))
self.application_switcher_scale.set_value(defaults.get("window_switcher_items_per_row", 13))
self.application_switcher_scale.set_value(
defaults.get("window_switcher_items_per_row", 13)
)
+80 -66
View File
@@ -1,3 +1,5 @@
import subprocess
from fabric.bluetooth import BluetoothClient, BluetoothDevice
from fabric.widgets.box import Box
from fabric.widgets.button import Button
@@ -6,7 +8,6 @@ from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GLib, Gtk
import subprocess
import utils.icons as icons
@@ -20,18 +21,20 @@ class BluetoothDeviceSlot(CenterBox):
"notify::closed", lambda *_: self.device.closed and self.destroy()
)
self.connection_label = Label(markup=icons.bluetooth_disconnected, style="font-size: 22px;")
self.connection_label = Label(
markup=icons.bluetooth_disconnected, style="font-size: 22px;"
)
self.connect_button = Button(
label="Connect",
on_clicked=lambda *_: self._handle_device_action(),
)
# Create device name label that we can update
self.device_name_label = Label(
label=device.alias or device.name,
h_expand=True,
h_align="start",
ellipsization="end"
ellipsization="end",
)
self.start_children = [
@@ -50,8 +53,6 @@ class BluetoothDeviceSlot(CenterBox):
self.device.emit("changed")
def _handle_device_action(self):
"""Handle connect/disconnect button click"""
if self.device.connected:
@@ -71,23 +72,31 @@ class BluetoothDeviceSlot(CenterBox):
device_address = self.device.address
# Show pairing notification
subprocess.run([
"notify-send",
"Bluetooth Pairing",
f"Attempting to pair with {device_name}...",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
subprocess.run(
[
"notify-send",
"Bluetooth Pairing",
f"Attempting to pair with {device_name}...",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth",
],
check=False,
)
# Use bluetoothctl to pair the device
# This will trigger the system's pairing agent which should show the pairing dialog
pairing_process = subprocess.Popen([
"bluetoothctl", "pair", device_address
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
pairing_process = subprocess.Popen(
["bluetoothctl", "pair", device_address],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
# Monitor the pairing process in a separate thread
GLib.timeout_add(100, self._monitor_pairing_process, pairing_process, device_name)
GLib.timeout_add(
100, self._monitor_pairing_process, pairing_process, device_name
)
except Exception as e:
print(f"Failed to start pairing process: {e}")
@@ -121,7 +130,7 @@ class BluetoothDeviceSlot(CenterBox):
subprocess.run(["bluetoothctl", "trust", device_address], check=False)
# Connect to the device
GLib.timeout_add(1000, lambda: setattr(self.device, 'connected', True))
GLib.timeout_add(1000, lambda: setattr(self.device, "connected", True))
except Exception as e:
print(f"Failed to trust/connect device: {e}")
@@ -130,14 +139,17 @@ class BluetoothDeviceSlot(CenterBox):
"""Show notification when pairing succeeds"""
try:
device_name = self.device.alias or self.device.name
subprocess.run([
"notify-send",
"Bluetooth Paired",
f"Successfully paired with {device_name}",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
subprocess.run(
[
"notify-send",
"Bluetooth Paired",
f"Successfully paired with {device_name}",
"--icon=bluetooth",
"--urgency=normal",
"--app-name=Bluetooth",
],
check=False,
)
except Exception as e:
print(f"Failed to send pairing success notification: {e}")
@@ -145,28 +157,33 @@ class BluetoothDeviceSlot(CenterBox):
"""Show notification when pairing fails"""
try:
device_name = self.device.alias or self.device.name
subprocess.run([
"notify-send",
"Bluetooth Pairing Failed",
f"Failed to pair with {device_name}",
"--icon=bluetooth-disabled",
"--urgency=normal",
"--app-name=Bluetooth"
], check=False)
subprocess.run(
[
"notify-send",
"Bluetooth Pairing Failed",
f"Failed to pair with {device_name}",
"--icon=bluetooth-disabled",
"--urgency=normal",
"--app-name=Bluetooth",
],
check=False,
)
except Exception as e:
print(f"Failed to send pairing failed notification: {e}")
def on_changed(self, *_):
# Update connection icon
self.connection_label.set_markup(
icons.bluetooth_connected if self.device.connected else icons.bluetooth_disconnected
icons.bluetooth_connected
if self.device.connected
else icons.bluetooth_disconnected
)
# Check for pairing state changes and show notifications
base_name = self.device.alias or self.device.name
# Track pairing success/failure
if hasattr(self, '_was_pairing') and self._was_pairing:
if hasattr(self, "_was_pairing") and self._was_pairing:
if self.device.paired and not self.device.connecting:
# Pairing succeeded
self._show_pairing_success_notification()
@@ -201,7 +218,6 @@ class BluetoothDeviceSlot(CenterBox):
self.device_name_label.set_label(status_text)
class BluetoothTab:
"""Bluetooth device management tab for settings"""
@@ -219,32 +235,27 @@ class BluetoothTab:
"""Set the window size enforcer function from the main GUI"""
self.window_size_enforcer = enforcer_func
def create_bluetooth_tab(self):
"""Create the Bluetooth tab content"""
main_vbox = Box(
orientation="v",
spacing=0,
style="padding: 0; margin: 15px;"
)
main_vbox = Box(orientation="v", spacing=0, style="padding: 0; margin: 15px;")
# Set fixed size for the main container to match tab stack dimensions
main_vbox.set_size_request(580, 580)
# Create widgets first
self.bluetooth_subtitle = Label(
markup="<span>Find and connect to Bluetooth devices</span>",
h_align="start"
markup="<span>Find and connect to Bluetooth devices</span>", h_align="start"
)
self.bluetooth_switch = Gtk.Switch()
self.scan_icon = Label(name="bluetooth-tab-icon", markup=icons.radar, style="font-size: 18px;")
self.scan_icon = Label(
name="bluetooth-tab-icon", markup=icons.radar, style="font-size: 18px;"
)
self.scan_button = Button(
name="bluetooth-scan",
child=self.scan_icon,
tooltip_text="Scan for devices",
on_clicked=lambda *_: self.bluetooth_client.toggle_scan(),
style="margin:10px;"
style="margin:10px;",
)
# Header section with title and controls
@@ -258,10 +269,10 @@ class BluetoothTab:
children=[
Label(
markup="<span size='large'><b>Bluetooth</b></span>",
h_align="start"
h_align="start",
),
self.bluetooth_subtitle,
]
],
)
],
end_children=[
@@ -271,14 +282,16 @@ class BluetoothTab:
children=[
self.bluetooth_switch,
self.scan_button,
]
],
)
]
],
)
# Set up switch
self.bluetooth_switch.set_valign(Gtk.Align.CENTER)
self.bluetooth_switch.connect("notify::active", self._on_bluetooth_switch_toggled)
self.bluetooth_switch.connect(
"notify::active", self._on_bluetooth_switch_toggled
)
main_vbox.add(header_box)
@@ -292,11 +305,19 @@ class BluetoothTab:
spacing=8,
orientation="v",
children=[
Label(name="bluetooth-section", markup="<b>Paired Devices</b>", h_align="start"),
Label(
name="bluetooth-section",
markup="<b>Paired Devices</b>",
h_align="start",
),
self.paired_box,
Label(name="bluetooth-section", markup="<b>Available Devices</b>", h_align="start"),
Label(
name="bluetooth-section",
markup="<b>Available Devices</b>",
h_align="start",
),
self.available_box,
]
],
)
# Set size constraints for content box
content_box.set_size_request(560, -1)
@@ -335,9 +356,7 @@ class BluetoothTab:
self.scan_button.set_sensitive(True)
else:
self.bluetooth_switch.set_active(False)
self.bluetooth_subtitle.set_markup(
"<span>Bluetooth is turned off</span>"
)
self.bluetooth_subtitle.set_markup("<span>Bluetooth is turned off</span>")
self.scan_button.set_sensitive(False)
def _on_bluetooth_changed(self, *args):
@@ -362,9 +381,8 @@ class BluetoothTab:
"""Populate existing devices when tab is created"""
if not self.bluetooth_client or not self.bluetooth_client.enabled:
return
devices = self.bluetooth_client.devices
for device in devices:
self.on_device_added(self.bluetooth_client, device.address)
@@ -430,7 +448,3 @@ class BluetoothTab:
"""Clear and repopulate all devices"""
self._clear_all_devices()
self._populate_existing_devices()
+18 -21
View File
@@ -7,23 +7,19 @@ import time
import gi
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.scrolledwindow import ScrolledWindow
from fabric.widgets.stack import Stack
from fabric.widgets.window import Window
from gi.repository import Gdk, GdkPixbuf, GLib, Gtk
from PIL import Image
from config.data import APP_NAME, APP_NAME_CAP
from config.settings.utils import backup_and_replace, start_config
from config.settings.wifi_tab import WiFiTab
from config.settings.about_tab import AboutTab
from config.settings.appearance_tab import AppearanceTab
from config.settings.bluetooth_tab import BluetoothTab
from config.settings.key_bindings_tab import KeyBindingsTab
from config.settings.appearance_tab import AppearanceTab
from config.settings.system_tab import SystemTab
from config.settings.about_tab import AboutTab
from config.settings.utils import backup_and_replace, start_config
from config.settings.wifi_tab import WiFiTab
gi.require_version("Gtk", "3.0")
@@ -47,8 +43,9 @@ class HyprConfGUI(Window):
geometry.max_width = 640
geometry.min_height = 640
geometry.max_height = 640
self.set_geometry_hints(None, geometry,
Gdk.WindowHints.MIN_SIZE | Gdk.WindowHints.MAX_SIZE)
self.set_geometry_hints(
None, geometry, Gdk.WindowHints.MIN_SIZE | Gdk.WindowHints.MAX_SIZE
)
self.themes = ["Pills", "Dense", "Edge"]
self.selected_face_icon = None
@@ -60,7 +57,9 @@ class HyprConfGUI(Window):
self.bluetooth_tab = BluetoothTab()
self.key_bindings_tab = KeyBindingsTab()
self.appearance_tab = AppearanceTab(self.themes, self)
self.system_tab = SystemTab(show_lock_checkbox, show_idle_checkbox, self.enforce_window_size)
self.system_tab = SystemTab(
show_lock_checkbox, show_idle_checkbox, self.enforce_window_size
)
self.about_tab = AboutTab()
# Pass window size enforcement method to tabs
@@ -72,7 +71,9 @@ class HyprConfGUI(Window):
root_box.set_size_request(620, 620)
self.add(root_box)
main_content_box = Box(orientation="h", spacing=6, v_expand=False, h_expand=False)
main_content_box = Box(
orientation="h", spacing=6, v_expand=False, h_expand=False
)
# Set fixed size for main content to prevent expansion
main_content_box.set_size_request(620, 580)
root_box.add(main_content_box)
@@ -93,12 +94,8 @@ class HyprConfGUI(Window):
self.system_tab_content = self.system_tab.create_system_tab()
self.about_tab_content = self.about_tab.create_about_tab()
self.tab_stack.add_titled(
self.wifi_tab_content, "Wifi", "Wifi"
)
self.tab_stack.add_titled(
self.bluetooth_tab_content, "Bluetooth", "Bluetooth"
)
self.tab_stack.add_titled(self.wifi_tab_content, "Wifi", "Wifi")
self.tab_stack.add_titled(self.bluetooth_tab_content, "Bluetooth", "Bluetooth")
self.tab_stack.add_titled(
self.key_bindings_tab_content, "key_bindings", "Key Bindings"
)
@@ -145,15 +142,15 @@ class HyprConfGUI(Window):
"""Public method to enforce window size - can be called by tabs"""
self._force_window_size(self)
# Event handlers moved to respective tab classes
def on_accept(self, _widget):
current_bind_vars_snapshot = {}
# Get values from key bindings tab
current_bind_vars_snapshot.update(self.key_bindings_tab.get_key_binding_values())
current_bind_vars_snapshot.update(
self.key_bindings_tab.get_key_binding_values()
)
# Get values from appearance tab
current_bind_vars_snapshot.update(self.appearance_tab.get_appearance_values())
+1 -6
View File
@@ -8,7 +8,6 @@ from config.data import APP_NAME_CAP
from config.settings.utils import bind_vars
class KeyBindingsTab:
"""Key bindings management tab for settings"""
@@ -31,11 +30,7 @@ class KeyBindingsTab:
main_vbox = Box(orientation="v", spacing=10, style="margin: 15px;")
scrolled_window.add(main_vbox)
keybind_grid = Grid(
column_spacing=10,
row_spacing=8,
style="margin: 5px;"
)
keybind_grid = Grid(column_spacing=10, row_spacing=8, style="margin: 5px;")
action_label = Label(
markup="<b>Action</b>", h_align="start", style="margin-bottom: 5px;"
+21 -24
View File
@@ -15,11 +15,16 @@ gi.require_version("Gtk", "3.0")
class SystemTab:
"""System settings and metrics management tab for settings"""
def __init__(self, show_lock_checkbox=False, show_idle_checkbox=False, window_size_enforcer=None):
def __init__(
self,
show_lock_checkbox=False,
show_idle_checkbox=False,
window_size_enforcer=None,
):
self.show_lock_checkbox = show_lock_checkbox
self.show_idle_checkbox = show_idle_checkbox
self.window_size_enforcer = window_size_enforcer
# Widget references
self.terminal_entry = None
self.lock_switch = None
@@ -60,9 +65,7 @@ class SystemTab:
def _create_terminal_hyprland_section(self, vbox):
"""Create terminal and Hyprland integration section"""
system_grid = Grid(
column_spacing=20,
row_spacing=10,
style="margin-bottom: 15px;"
column_spacing=20, row_spacing=10, style="margin-bottom: 15px;"
)
vbox.add(system_grid)
@@ -94,9 +97,7 @@ class SystemTab:
)
system_grid.attach(lock_label, 2, row, 1, 1)
lock_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.lock_switch = Gtk.Switch(
tooltip_text="Replace Hyprlock configuration with Modus's custom config"
@@ -111,9 +112,7 @@ class SystemTab:
)
system_grid.attach(idle_label, 2, row, 1, 1)
idle_switch_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
orientation="horizontal", h_align="start", v_align="center"
)
self.idle_switch = Gtk.Switch(
tooltip_text="Replace Hypridle configuration with Modus's custom config"
@@ -138,7 +137,7 @@ class SystemTab:
notif_grid = Grid(
column_spacing=20,
row_spacing=10,
style="margin-left: 10px; margin-top: 5px; margin-bottom: 15px;"
style="margin-left: 10px; margin-top: 5px; margin-bottom: 15px;",
)
vbox.add(notif_grid)
@@ -191,7 +190,7 @@ class SystemTab:
metrics_grid = Grid(
column_spacing=15,
row_spacing=8,
style="margin-left: 10px; margin-top: 5px;"
style="margin-left: 10px; margin-top: 5px;",
)
vbox.add(metrics_grid)
@@ -251,17 +250,15 @@ class SystemTab:
self.disk_entries.set_size_request(530, -1)
disk_entries_scrolled.add(self.disk_entries)
self._create_disk_edit_entry_func = lambda path: self._add_disk_entry_widget(path)
self._create_disk_edit_entry_func = lambda path: self._add_disk_entry_widget(
path
)
for p in bind_vars.get("metrics_disks", ["/"]):
self._create_disk_edit_entry_func(p)
vbox.add(disk_entries_scrolled)
add_container = Box(
orientation="horizontal",
h_align="start",
v_align="center"
)
add_container = Box(orientation="horizontal", h_align="start", v_align="center")
add_btn = Button(
label="Add new disk",
on_clicked=lambda _: self._create_disk_edit_entry_func("/"),
@@ -279,7 +276,9 @@ class SystemTab:
x_btn = Button(label="X")
x_btn.connect(
"clicked",
lambda _, current_bar_to_remove=bar: self._remove_disk_entry(current_bar_to_remove),
lambda _, current_bar_to_remove=bar: self._remove_disk_entry(
current_bar_to_remove
),
)
bar.add(x_btn)
self.disk_entries.add(bar)
@@ -341,7 +340,7 @@ class SystemTab:
"""Get the state of Hyprland integration switches"""
return {
"replace_lock": self.lock_switch and self.lock_switch.get_active(),
"replace_idle": self.idle_switch and self.idle_switch.get_active()
"replace_idle": self.idle_switch and self.idle_switch.get_active(),
}
def reset_to_defaults(self, defaults):
@@ -356,9 +355,7 @@ class SystemTab:
enabled_switches = [s for s in switch_dict.values() if s.get_active()]
can_disable = len(enabled_switches) > 1
for s in switch_dict.values():
s.set_sensitive(
True if can_disable or not s.get_active() else False
)
s.set_sensitive(True if can_disable or not s.get_active() else False)
enforce_minimum_metrics(self.metrics_switches)
+113 -80
View File
@@ -1,3 +1,5 @@
import subprocess
import gi
from fabric.widgets.box import Box
from fabric.widgets.button import Button
@@ -8,7 +10,6 @@ from gi.repository import GLib, Gtk
from services.network import NetworkClient
from utils import icons
import subprocess
gi.require_version("Gtk", "3.0")
@@ -64,11 +65,7 @@ class WiFiTab:
def create_wifi_tab(self):
"""Create the WiFi tab content"""
main_vbox = Box(
orientation="v",
spacing=0,
style="padding: 0; margin: 15px;"
)
main_vbox = Box(orientation="v", spacing=0, style="padding: 0; margin: 15px;")
# Set fixed size for the main container to match tab stack dimensions
main_vbox.set_size_request(580, 580)
@@ -84,15 +81,13 @@ class WiFiTab:
# WiFi title
wifi_title = Label(
markup="<span size='large'><b>Wi-Fi</b></span>",
h_align="start"
markup="<span size='large'><b>Wi-Fi</b></span>", h_align="start"
)
wifi_section.add(wifi_title)
# WiFi subtitle
self.wifi_subtitle = Label(
markup="<span>Find and connect to Wi-Fi networks</span>",
h_align="start"
markup="<span>Find and connect to Wi-Fi networks</span>", h_align="start"
)
wifi_section.add(self.wifi_subtitle)
@@ -114,7 +109,9 @@ class WiFiTab:
# Refresh button
self.refresh_button = Button()
self.refresh_icon = Label(name="wifi-tab-icon", markup=icons.reload, style="font-size: 18px;")
self.refresh_icon = Label(
name="wifi-tab-icon", markup=icons.reload, style="font-size: 18px;"
)
self.refresh_button.add(self.refresh_icon)
self.refresh_button.set_size_request(40, 40)
self.refresh_button.set_tooltip_text("Refresh networks")
@@ -164,8 +161,10 @@ class WiFiTab:
self._update_wifi_status()
# Show available networks if WiFi is enabled
if (self.network_client.wifi_device and
self.network_client.wifi_device.wireless_enabled):
if (
self.network_client.wifi_device
and self.network_client.wifi_device.wireless_enabled
):
self._populate_networks()
self.networks_container.show_all()
@@ -177,9 +176,7 @@ class WiFiTab:
def _update_wifi_status(self):
"""Update WiFi status display"""
if not self.network_client.wifi_device:
self.wifi_subtitle.set_markup(
"<span>No WiFi device available</span>"
)
self.wifi_subtitle.set_markup("<span>No WiFi device available</span>")
self.wifi_switch.set_sensitive(False)
return
@@ -191,9 +188,7 @@ class WiFiTab:
self.refresh_button.set_sensitive(True)
else:
self.wifi_switch.set_active(False)
self.wifi_subtitle.set_markup(
"<span>Wi-Fi is turned off</span>"
)
self.wifi_subtitle.set_markup("<span>Wi-Fi is turned off</span>")
self.refresh_button.set_sensitive(False)
def _populate_networks(self):
@@ -205,15 +200,13 @@ class WiFiTab:
access_points = self.network_client.wifi_device.access_points
if not access_points:
self._show_status_message(
"No networks found\nClick 'Scan for Networks' to refresh",
"info"
"No networks found\nClick 'Scan for Networks' to refresh", "info"
)
return
# Sort access points by signal strength (connected first, then by strength)
sorted_aps = sorted(
access_points,
key=lambda ap: (not ap.is_active, -ap.strength)
access_points, key=lambda ap: (not ap.is_active, -ap.strength)
)
for ap in sorted_aps:
@@ -221,11 +214,7 @@ class WiFiTab:
def _show_status_message(self, message, message_type="info"):
"""Show a status message in the networks container"""
status_box = Box(
orientation="v",
spacing=16,
style="padding: 60px;"
)
status_box = Box(orientation="v", spacing=16, style="padding: 60px;")
status_box.set_halign(Gtk.Align.CENTER)
status_box.set_valign(Gtk.Align.CENTER)
@@ -235,24 +224,26 @@ class WiFiTab:
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--error);"
style="color: var(--error);",
)
elif message_type == "scanning":
icon_markup = icons.loader
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--blue);"
style="color: var(--blue);",
)
else: # info
icon_markup = icons.wifi_off
message_label = Label(
markup=f"<span size='medium'>{message}</span>",
h_align="center",
style="color: var(--outline);"
style="color: var(--outline);",
)
icon_label = Label(name="wifi-tab-icon", markup=icon_markup, style="font-size: 18px;")
icon_label = Label(
name="wifi-tab-icon", markup=icon_markup, style="font-size: 18px;"
)
icon_label.set_halign(Gtk.Align.CENTER)
status_box.add(icon_label)
message_label.set_line_wrap(True)
@@ -271,7 +262,10 @@ class WiFiTab:
network_button = Button(
style="background-color: transparent; border: none; padding: 0; margin: 0;"
)
network_button.connect("clicked", lambda btn, access_point=ap: self._on_network_clicked(access_point))
network_button.connect(
"clicked",
lambda btn, access_point=ap: self._on_network_clicked(access_point),
)
# Network info row
info_box = Box(orientation="h", spacing=12)
@@ -291,7 +285,9 @@ class WiFiTab:
else:
icon_markup = icons.wifi_0
signal_label = Label(name="wifi-tab-icon", markup=icon_markup, style="font-size: 24px;")
signal_label = Label(
name="wifi-tab-icon", markup=icon_markup, style="font-size: 24px;"
)
signal_label.set_size_request(30, -1)
info_box.add(signal_label)
@@ -299,10 +295,18 @@ class WiFiTab:
network_name = ap.ssid
if ap.is_active:
network_markup = f"<b>{network_name}</b>\nConnected"
network_label = Label(markup=network_markup, h_align="start", style="color: var(--foreground);")
network_label = Label(
markup=network_markup,
h_align="start",
style="color: var(--foreground);",
)
else:
network_markup = f"{network_name}"
network_label = Label(markup=network_markup, h_align="start", style="color: var(--foreground);")
network_label = Label(
markup=network_markup,
h_align="start",
style="color: var(--foreground);",
)
network_label.set_line_wrap(True)
info_box.add(network_label)
@@ -313,7 +317,9 @@ class WiFiTab:
# Lock icon for secured networks
if ap.requires_password:
lock_label = Label(name="wifi-tab-icon", markup=icons.lock, style="font-size: 22px;")
lock_label = Label(
name="wifi-tab-icon", markup=icons.lock, style="font-size: 22px;"
)
info_box.add(lock_label)
network_button.add(info_box)
@@ -321,11 +327,11 @@ class WiFiTab:
# Store reference
self.access_point_widgets[ap.ssid] = {
'container': network_container,
'button': network_button,
'info_box': info_box,
'network_label': network_label,
'ap': ap
"container": network_container,
"button": network_button,
"info_box": info_box,
"network_label": network_label,
"ap": ap,
}
self.networks_container.add(network_container)
@@ -353,7 +359,10 @@ class WiFiTab:
def _on_refresh_clicked(self, button):
"""Handle refresh button click"""
if self.network_client.wifi_device and self.network_client.wifi_device.wireless_enabled:
if (
self.network_client.wifi_device
and self.network_client.wifi_device.wireless_enabled
):
self.refresh_icon.set_markup(icons.loader)
button.set_sensitive(False)
@@ -366,11 +375,14 @@ class WiFiTab:
self.network_client.wifi_device.scan()
# Re-enable button after scan
GLib.timeout_add(3000, lambda: [
self.refresh_icon.set_markup(icons.reload),
button.set_sensitive(True),
self._refresh_networks()
])
GLib.timeout_add(
3000,
lambda: [
self.refresh_icon.set_markup(icons.reload),
button.set_sensitive(True),
self._refresh_networks(),
],
)
def _connect_network(self, ap):
"""Connect to a network"""
@@ -380,7 +392,7 @@ class WiFiTab:
if ap.ssid in self.password_entries:
# Get password from stored entry widget
password_widget = self.password_entries[ap.ssid]
if hasattr(password_widget, 'get_text'):
if hasattr(password_widget, "get_text"):
password = password_widget.get_text()
else:
password = str(password_widget) # If it's stored as string
@@ -397,7 +409,7 @@ class WiFiTab:
# Update network widget to show connecting state
if ap.ssid in self.access_point_widgets:
network_label = self.access_point_widgets[ap.ssid]['network_label']
network_label = self.access_point_widgets[ap.ssid]["network_label"]
network_label.set_markup(f"{ap.ssid}\nConnecting...")
network_label.set_style("color: var(--blue);")
@@ -416,24 +428,25 @@ class WiFiTab:
self._reset_connection_state(ap.ssid)
def _show_connection_error(self, ssid, error_message):
try:
subprocess.run([
"notify-send",
"WiFi Connection Failed",
f"Failed to connect to {ssid}\n{error_message}",
"--icon=network-wireless-offline",
"--urgency=normal",
"--app-name=WiFi"
], check=False)
subprocess.run(
[
"notify-send",
"WiFi Connection Failed",
f"Failed to connect to {ssid}\n{error_message}",
"--icon=network-wireless-offline",
"--urgency=normal",
"--app-name=WiFi",
],
check=False,
)
except Exception as e:
print(f"Failed to send connection error notification: {e}")
def _show_password_entry(self, ap):
"""Show password entry for a network"""
if ap.ssid in self.access_point_widgets:
container = self.access_point_widgets[ap.ssid]['container']
container = self.access_point_widgets[ap.ssid]["container"]
# Check if password entry is already shown
if len(container.get_children()) > 1:
@@ -443,42 +456,60 @@ class WiFiTab:
password_box = Box(
orientation="h",
spacing=8,
style="padding: 8px; margin: 4px 16px 8px 16px; border-radius: 8px;"
style="padding: 8px; margin: 4px 16px 8px 16px; border-radius: 8px;",
)
password_entry = Entry(
placeholder_text="Enter network password",
style="background-color: var(--surface-bright); border: 1px solid var(--outline); border-radius: 8px;"
style="background-color: var(--surface-bright); border: 1px solid var(--outline); border-radius: 8px;",
)
password_entry.set_visibility(False) # Hide password characters
password_entry.set_hexpand(True)
password_entry.connect("activate", lambda entry, access_point=ap: self._on_password_entered(access_point, entry))
password_entry.connect(
"activate",
lambda entry, access_point=ap: self._on_password_entered(
access_point, entry
),
)
password_box.add(password_entry)
# Show/Hide password toggle
show_password_btn = Button(
style="border-radius: 8px;"
show_password_btn = Button(style="border-radius: 8px;")
self.show_password_icon = Label(
name="wifi-tab-icon", markup=icons.spy, style="font-size: 18px;"
)
self.show_password_icon = Label(name="wifi-tab-icon", markup=icons.spy,style="font-size: 18px;")
show_password_btn.add(self.show_password_icon)
show_password_btn.set_tooltip_text("Show/Hide password")
show_password_btn.connect("clicked", lambda btn, entry=password_entry: self._toggle_password_visibility(entry, btn))
show_password_btn.connect(
"clicked",
lambda btn, entry=password_entry: self._toggle_password_visibility(
entry, btn
),
)
password_box.add(show_password_btn)
connect_btn = Button(
style="border-radius: 8px;"
connect_btn = Button(style="border-radius: 8px;")
connect_icon = Label(
name="wifi-tab-icon", markup=icons.connect, style="font-size: 18px;"
)
connect_icon = Label(name="wifi-tab-icon", markup=icons.connect,style="font-size: 18px;")
connect_btn.add(connect_icon)
connect_btn.connect("clicked", lambda btn, access_point=ap, entry=password_entry: self._on_password_entered(access_point, entry))
connect_btn.connect(
"clicked",
lambda btn,
access_point=ap,
entry=password_entry: self._on_password_entered(access_point, entry),
)
password_box.add(connect_btn)
cancel_btn = Button(
style="border-radius: 8px;"
cancel_btn = Button(style="border-radius: 8px;")
cancel_icon = Label(
name="wifi-tab-icon", markup=icons.cancel, style="font-size: 18px;"
)
cancel_icon = Label(name="wifi-tab-icon", markup=icons.cancel,style="font-size: 18px;")
cancel_btn.add(cancel_icon)
cancel_btn.connect("clicked", lambda btn, access_point=ap: self._hide_password_entry(access_point))
cancel_btn.connect(
"clicked",
lambda btn, access_point=ap: self._hide_password_entry(access_point),
)
password_box.add(cancel_btn)
container.add(password_box)
@@ -493,7 +524,9 @@ class WiFiTab:
current_visibility = entry.get_visibility()
entry.set_visibility(not current_visibility)
# Use spy icon for hidden, key icon for visible
self.show_password_icon.set_markup(icons.key if not current_visibility else icons.spy)
self.show_password_icon.set_markup(
icons.key if not current_visibility else icons.spy
)
def _on_password_entered(self, ap, entry):
"""Handle password entry"""
@@ -505,7 +538,7 @@ class WiFiTab:
def _hide_password_entry(self, ap):
"""Hide password entry for a network"""
if ap.ssid in self.access_point_widgets:
container = self.access_point_widgets[ap.ssid]['container']
container = self.access_point_widgets[ap.ssid]["container"]
children = container.get_children()
if len(children) > 1: # Remove password entry box
container.remove(children[-1])
@@ -520,8 +553,8 @@ class WiFiTab:
"""Reset connection state for a network"""
if ssid in self.access_point_widgets:
# Reset the network label to normal state
ap = self.access_point_widgets[ssid]['ap']
network_label = self.access_point_widgets[ssid]['network_label']
ap = self.access_point_widgets[ssid]["ap"]
network_label = self.access_point_widgets[ssid]["network_label"]
if ap.is_active:
network_markup = f"<b>{ap.ssid}</b>\nConnected"
network_label.set_style("color: var(--foreground);")