Huge updates: ControlCenter: Wi-Fi, Bluetooth, Password Entry Dialog, Notification, CustomRevealer for notif

This commit is contained in:
sankalp committed 2025-08-01 07:54:43 +05:45
1 parent 195e2d0f82
commit 10f1b25264
31 files changed
+1418 -349

No files matched your search

+4 -8
View File
@@ -1,13 +1,9 @@
import subprocess
import re
import gi # type: ignore
import subprocess
from fabric.widgets.label import Label
from fabric.widgets.button import Button
from fabric.widgets.box import Box
from widgets.wayland import WaylandWindow as Window
from fabric.utils.helpers import exec_shell_command_async, get_relative_path
from gi.repository import Gtk, GdkPixbuf # type: ignore
from gi.repository import GdkPixbuf, Gtk # type: ignore
from fabric.utils.helpers import get_relative_path
def read_dmi(field):
+15 -27
View File
@@ -1,4 +1,4 @@
from gi.repository import Gtk, Gdk, GLib
from gi.repository import Gdk, GLib, Gtk
from fabric.bluetooth import BluetoothClient, BluetoothDevice
from fabric.widgets.box import Box
@@ -7,7 +7,6 @@ from fabric.widgets.centerbox import CenterBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.eventbox import EventBox
class BluetoothDeviceSlot(CenterBox):
@@ -68,8 +67,6 @@ class BluetoothConnections(Box):
self.client = BluetoothClient(on_device_added=self.on_device_added)
self.title = Box(
orientation="h",
children=[
@@ -95,10 +92,7 @@ class BluetoothConnections(Box):
)
# Connect scanning state changes to update scan button
self.client.connect(
"notify::scanning",
lambda *_: self.update_scan_label()
)
self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
self.not_paired = Box(spacing=2, orientation="vertical")
self.paired = Box(spacing=2, orientation="vertical")
@@ -108,13 +102,13 @@ class BluetoothConnections(Box):
name="bluetooth-refresh-indicator",
label="↓ Pull to scan for devices",
h_align="center",
visible=False
visible=False,
)
self.device_box = Box(
spacing=2,
orientation="vertical",
children=[self.refresh_indicator, self.paired, self.not_paired]
children=[self.refresh_indicator, self.paired, self.not_paired],
)
# Create scrolled window with pull-to-refresh
@@ -143,8 +137,6 @@ class BluetoothConnections(Box):
# Connect cleanup on destroy
self.connect("destroy", self.on_destroy)
def on_destroy(self, widget):
"""Cleanup animations when widget is destroyed"""
self.stop_bounce_animation()
@@ -165,8 +157,6 @@ class BluetoothConnections(Box):
self.bounce_duration = 60 # Total frames for animation
self.bounce_amplitude = 15 # Maximum bounce height in pixels
# Connect to scroll events
self.scrolled_window.connect("scroll-event", self.on_scroll_event)
self.scrolled_window.connect("button-press-event", self.on_button_press)
@@ -175,10 +165,10 @@ class BluetoothConnections(Box):
# Enable events
self.scrolled_window.set_events(
Gdk.EventMask.SCROLL_MASK |
Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
Gdk.EventMask.POINTER_MOTION_MASK
Gdk.EventMask.SCROLL_MASK
| Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
| Gdk.EventMask.POINTER_MOTION_MASK
)
def on_scroll_event(self, widget, event):
@@ -243,22 +233,22 @@ class BluetoothConnections(Box):
def ease_out_bounce(self, t):
"""Smooth bounce easing function (0 to 1)"""
import math
if t < 1/2.75:
if t < 1 / 2.75:
return 7.5625 * t * t
elif t < 2/2.75:
t -= 1.5/2.75
elif t < 2 / 2.75:
t -= 1.5 / 2.75
return 7.5625 * t * t + 0.75
elif t < 2.5/2.75:
t -= 2.25/2.75
elif t < 2.5 / 2.75:
t -= 2.25 / 2.75
return 7.5625 * t * t + 0.9375
else:
t -= 2.625/2.75
t -= 2.625 / 2.75
return 7.5625 * t * t + 0.984375
def ease_out_elastic(self, t):
"""Elastic easing function for smoother bounce"""
import math
if t == 0 or t == 1:
return t
@@ -327,8 +317,6 @@ class BluetoothConnections(Box):
self.bounce_frame += 1
return True
def stop_bounce_animation(self):
"""Stop any ongoing animations"""
if self.bounce_timeout_id:
+31 -9
View File
@@ -1,3 +1,5 @@
from gi.repository import Gdk, GLib
from fabric.utils import idle_add
from fabric.utils.helpers import (
get_relative_path,
@@ -8,9 +10,6 @@ from fabric.widgets.centerbox import CenterBox
from fabric.widgets.label import Label
from fabric.widgets.scale import Scale
from fabric.widgets.svg import Svg
from fabric.widgets.image import Image
from gi.repository import Gdk, GLib
from modules.controlcenter.bluetooth import BluetoothConnections
from modules.controlcenter.wifi import WifiConnections
from services.brightness import Brightness
@@ -37,7 +36,7 @@ class ModusControlCenter(Window):
visible=False,
**kwargs,
)
self.focus_mode = False
self.focus_mode = modus_service.dont_disturb
self._updating_brightness = False
self._updating_volume = False
@@ -50,6 +49,7 @@ class ModusControlCenter(Window):
audio_service.connect("changed", self.audio_changed)
audio_service.connect("changed", self.volume_changed)
modus_service.connect("dont-disturb-changed", self.dnd_changed)
self.wlan_label = Label(wlan, name="wifi-widget-label", h_align="start")
if bluetooth != "disabled":
@@ -178,7 +178,11 @@ class ModusControlCenter(Window):
self.focus_icon = Svg(
name="focus-icon",
svg_file=get_relative_path("../../config/assets/icons/applets/dnd-off.svg"),
svg_file=get_relative_path(
"../../config/assets/icons/applets/dnd.svg"
if self.focus_mode
else "../../config/assets/icons/applets/dnd-off.svg"
),
size=46,
)
@@ -364,9 +368,13 @@ class ModusControlCenter(Window):
parts = wlan.split(":")
if len(parts) >= 2:
wifi_name = parts[1]
GLib.idle_add(lambda: self.wlan_label.set_property("label", wifi_name))
GLib.idle_add(
lambda: self.wlan_label.set_property("label", wifi_name)
)
else:
GLib.idle_add(lambda: self.wlan_label.set_property("label", "Connected"))
GLib.idle_add(
lambda: self.wlan_label.set_property("label", "Connected")
)
else:
GLib.idle_add(lambda: self.wlan_label.set_property("label", wlan))
else:
@@ -385,9 +393,13 @@ class ModusControlCenter(Window):
parts = bluetooth.split(":")
if len(parts) >= 2:
device_name = parts[1]
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", device_name))
GLib.idle_add(
lambda: self.bluetooth_label.set_property("label", device_name)
)
else:
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", "Connected"))
GLib.idle_add(
lambda: self.bluetooth_label.set_property("label", "Connected")
)
elif bluetooth == "enabled":
GLib.idle_add(lambda: self.bluetooth_label.set_property("label", "On"))
else:
@@ -398,6 +410,16 @@ class ModusControlCenter(Window):
def audio_changed(self, *_):
pass
def dnd_changed(self, _, dnd_state):
self.focus_mode = dnd_state
self.focus_icon.set_from_file(
get_relative_path(
"../../config/assets/icons/applets/dnd.svg"
if self.focus_mode
else "../../config/assets/icons/applets/dnd-off.svg"
)
)
def _init_mousecapture(self, mousecapture):
self._mousecapture_parent = mousecapture
+75 -7
View File
@@ -1,19 +1,21 @@
from gi.repository import Gdk, GLib, Gtk
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import Gdk, GLib, Gtk
from services.network import NetworkService
from widgets.wifi_password_dialog import WiFiPasswordDialog
class WifiNetworkSlot(CenterBox):
def __init__(self, network_data: dict, wifi_service, **kwargs):
def __init__(self, network_data: dict, wifi_service, parent=None, **kwargs):
super().__init__(**kwargs)
self.network_data = network_data
self.wifi_service = wifi_service
self.parent = parent # Reference to control center
# Get network info
self.ssid = network_data.get("ssid", "Unknown")
@@ -43,6 +45,9 @@ class WifiNetworkSlot(CenterBox):
style_classes=" ".join(self.styles),
)
# Initialize password dialog
self.password_dialog = None
self.start_children = [
Button(
image=self.dimage,
@@ -79,11 +84,10 @@ class WifiNetworkSlot(CenterBox):
# Reset state if disconnect failed
self._reset_disconnect_state()
else:
# Show connecting state
# Try to connect without password first (for open networks or saved connections)
self.dimage.set_property("icon-name", "network-wireless-acquiring-symbolic")
self.dimage.add_style_class("connecting")
# Connect to network (try without password first, could be enhanced for password input)
success = self.wifi_service.connect_network(self.ssid)
if success:
self.is_connected = True
@@ -92,8 +96,9 @@ class WifiNetworkSlot(CenterBox):
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
# Reset state if connect failed
# Connection failed, likely needs password - show password dialog
self._reset_connect_state()
self._show_password_dialog()
# Update display after connection attempt
self.on_changed()
@@ -133,6 +138,67 @@ class WifiNetworkSlot(CenterBox):
self.dimage.set_property("style-classes", " ".join(self.styles))
return
def _show_password_dialog(self):
"""Show the WiFi password dialog"""
# Close the control center first
if self.parent and hasattr(self.parent, "hide_controlcenter"):
self.parent.hide_controlcenter()
# Create a new dialog each time to ensure clean state
if self.password_dialog:
self.password_dialog.destroy_dialog()
self.password_dialog = WiFiPasswordDialog(
ssid=self.ssid,
on_connect_callback=self._on_password_connect,
on_cancel_callback=self._on_password_cancel,
)
self.password_dialog.show_dialog()
def _on_password_connect(self, ssid, password):
"""Handle password dialog connect action"""
if password.strip():
# Show connecting state
self.dimage.set_property("icon-name", "network-wireless-acquiring-symbolic")
self.dimage.add_style_class("connecting")
# Try to connect with password
try:
success = self.wifi_service.connect_network(ssid, password)
if success:
self.is_connected = True
# Remove connecting state after a short delay
from gi.repository import GLib
GLib.timeout_add(500, lambda: self._reset_connect_state())
else:
# Connection failed - show error in dialog immediately
self._reset_connect_state()
if self.password_dialog:
# Show error immediately without delay
self._show_connection_error()
except Exception:
# Handle any connection errors gracefully
self._reset_connect_state()
if self.password_dialog:
# Show error immediately
self._show_connection_error("Connection failed. Please try again.")
# Update display after connection attempt
self.on_changed()
def _show_connection_error(self, message="Incorrect password. Please try again."):
"""Show connection error in a separate thread to prevent UI blocking"""
if self.password_dialog:
self.password_dialog.show_error(message)
return False # Don't repeat if called from GLib.timeout_add
def _on_password_cancel(self):
"""Handle password dialog cancel action"""
# Reset any connecting state
self._reset_connect_state()
class WifiConnections(Box):
def __init__(self, parent, **kwargs):
@@ -261,7 +327,9 @@ class WifiConnections(Box):
access_points = self.wifi_service.access_points
for ap_data in access_points:
if ap_data.get("ssid") and ap_data.get("ssid") != "Unknown":
network_slot = WifiNetworkSlot(ap_data, self.wifi_service)
network_slot = WifiNetworkSlot(
ap_data, self.wifi_service, parent=self.parent
)
self.available_networks.add(network_slot)
# Update all network connection states
-1
View File
@@ -1,6 +1,5 @@
from fabric.widgets.box import Box
from fabric.widgets.shapes import Corner
from widgets.wayland import WaylandWindow as Window
+3 -3
View File
@@ -2,6 +2,8 @@ import json
import logging
import cairo
from gi.repository import Gdk, GLib, Gtk
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.utils import (
@@ -17,13 +19,11 @@ from fabric.widgets.button import Button
from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, GLib, Gtk
from modules.corners import MyCorner
from utils.icon_resolver import IconResolver
from utils.occlusion import check_occlusion
from widgets.wayland import WaylandWindow as Window
from modules.corners import MyCorner
# TODO: use modus_service
+123 -45
View File
@@ -1,42 +1,35 @@
import os
import sys
from typing import cast
from fabric import Application
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.image import Image
from fabric.widgets.button import Button
from widgets.wayland import WaylandWindow as Window
from fabric.widgets.revealer import Revealer
from gi.repository import GdkPixbuf, GLib # type: ignore
from config.data import NOTIFICATION_TIMEOUT
from fabric.notifications import (
NotificationAction,
Notifications,
Notification,
NotificationAction,
NotificationCloseReason,
NotificationImagePixmap,
Notifications,
)
import os
from fabric.utils import invoke_repeater, get_relative_path
from fabric.utils import invoke_repeater
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.eventbox import EventBox
from fabric.widgets.label import Label
from utils.roam import modus_service
from widgets.custom_image import CustomImage
from widgets.customrevealer import SlideRevealer
from widgets.wayland import WaylandWindow as Window
from utils.custom_image import CustomImage
from gi.repository import GdkPixbuf # type: ignore
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
NOTIFICATION_WIDTH = 360
NOTIFICATION_IMAGE_SIZE = 48
NOTIFICATION_TIMEOUT = 10 * 1000
# Improved animation smoothness and consistent behavior for rapid notifications.
def smooth_revealer_animation(revealer: Revealer, duration: int = 300):
"""
Adjusts the Revealer's animation properties for smoother transitions.
"""
revealer.set_property("transition-duration", duration)
revealer.set_property("transition-type", "slide-left")
def smooth_revealer_animation(revealer: SlideRevealer, duration: int = 250):
revealer.duration = duration
def get_notification_image_pixbuf(
@@ -74,7 +67,6 @@ class NotificationWidget(Box):
self.image = notification.app_icon
body_container = Box(name="noti-image", spacing=4, orientation="h")
# Load and show image (from app_icon, image_file, or pixbuf)
pixbuf = get_notification_image_pixbuf(notification)
image_file = None
@@ -129,7 +121,6 @@ class NotificationWidget(Box):
),
Label(
label=self._notification.body, # type: ignore
# line_wrap="word-char",
v_align="start",
h_align="start",
ellipsization="end",
@@ -187,39 +178,103 @@ class NotificationWidget(Box):
self.destroy()
class NotificationRevealer(Revealer):
class NotificationRevealer(SlideRevealer):
def __init__(self, notification: Notification, on_transition_end=None, **kwargs):
self.notif_box = NotificationWidget(notification)
self.notification = notification
self.on_transition_end = on_transition_end
super().__init__(
child=Box(
children=[self.notif_box],
),
transition_duration=300,
transition_type="slide-left",
# Add swipe detection variables
self._drag_start_x = 0
self._drag_start_y = 0
self._is_dragging = False
self._swipe_threshold = 100 # pixels to trigger swipe dismiss
# Wrap notification in EventBox for swipe detection
self.event_box = EventBox(
events=[
"button-press-event",
"button-release-event",
"motion-notify-event",
],
child=self.notif_box,
)
self.connect(
"notify::child-revealed",
self._on_child_revealed,
self.event_box.connect("button-press-event", self._on_button_press)
self.event_box.connect("button-release-event", self._on_button_release)
self.event_box.connect("motion-notify-event", self._on_motion)
super().__init__(
child=self.event_box,
direction="right",
duration=250,
)
smooth_revealer_animation(self)
# Disconnect the destroy_noti handler from the notification widget
# to prevent immediate destruction that interferes with slide animation
self.notification.disconnect_by_func(self.notif_box.destroy_noti)
# Connect our own handler that manages the slide animation
self.notification.connect("closed", self.on_resolved)
def _on_child_revealed(self, *args):
if not self.get_child_revealed():
def _on_animation_complete(self, is_hiding=False):
if is_hiding:
# Manually destroy the notification widget since we disconnected its handler
self.notif_box.destroy_noti()
if self.on_transition_end:
self.on_transition_end()
self.destroy()
def on_resolved(
self,
notification: Notification,
_notification: Notification,
reason: NotificationCloseReason,
):
self.set_property("transition-type", "slide-right")
self.set_reveal_child(False)
# Use left-to-right slide for auto-dismiss (expired), right-to-left for manual close
if reason == "expired":
# Left-to-right slide for auto-dismiss
self.set_slide_direction("left")
else:
# Right-to-left slide for manual close
self.set_slide_direction("right")
self.hide()
GLib.timeout_add(self.duration + 50, lambda: self._on_animation_complete(True))
def _on_button_press(self, _widget, event):
if event.button == 1:
self._drag_start_x = event.x
self._drag_start_y = event.y
self._is_dragging = True
return False
def _on_button_release(self, _widget, event):
if self._is_dragging and event.button == 1:
self._is_dragging = False
# Calculate swipe distance
dx = event.x - self._drag_start_x
dy = abs(event.y - self._drag_start_y)
if dx > self._swipe_threshold and dy < 50: # 50px vertical tolerance
# Follow the swipe gesture direction
self.set_slide_direction("right")
self.hide()
# Schedule cleanup after animation
GLib.timeout_add(
self.duration + 50, lambda: self._on_animation_complete(True)
)
return False
def _on_motion(self, _widget, event):
if self._is_dragging:
# TODO: Add visual feedback during swipe
# Could add translation or opacity changes here
pass
return False
class ModusNoti(Window):
@@ -244,6 +299,29 @@ class ModusNoti(Window):
def on_new_notification(self, fabric_notif, id):
notification: Notification = fabric_notif.get_notification_from_id(id)
new_box = NotificationRevealer(notification, on_transition_end=lambda: self.notifications.remove(new_box))
self.notifications.add(new_box)
new_box.set_reveal_child(True)
# Check if Do Not Disturb is enabled
if modus_service.dont_disturb:
# Still cache the notification for notification center, but don't show it
modus_service.cache_notification(notification)
# Close the notification immediately to prevent it from showing
notification.close("dismissed-by-user")
return
new_box = NotificationRevealer(
notification, on_transition_end=lambda: self.notifications.remove(new_box)
)
# Insert at the beginning (index 0) to show new notifications at the top
current_children = list(self.notifications.children)
current_children.insert(0, new_box)
self.notifications.children = current_children
# This ensures each notification's container is fully laid out before animation
def start_animation():
if new_box.get_parent(): # Only animate if still in the tree
new_box.reveal()
return False
GLib.timeout_add(32, start_animation)
+5 -5
View File
@@ -1,16 +1,15 @@
import math
import time
from typing import ClassVar, Literal
import utils.icons as icons
from gi.repository import GLib, GObject
from fabric.audio import Audio
from fabric.utils.helpers import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.scale import Scale, ScaleMark
from fabric.widgets.svg import Svg
from fabric.utils.helpers import get_relative_path
from gi.repository import GLib, GObject
import math
from services.brightness import Brightness
from utils.animator import Animator
from widgets.wayland import WaylandWindow as Window
@@ -410,6 +409,7 @@ class OSD(Window):
super().__init__(
layer="overlay",
anchor="bottom",
title="modus",
child=self.main_box,
visible=False,
pass_through=True,
@@ -6,9 +6,10 @@ including fallback mechanisms for file paths and common icon locations.
"""
import os
from gi.repository import Gtk, GdkPixbuf
from fabric.system_tray.widgets import SystemTrayItem
from gi.repository import GdkPixbuf, Gtk
from fabric.system_tray.widgets import SystemTrayItem
# FIX: the tooltip should show application names instead of unknown
+19 -7
View File
@@ -3,8 +3,8 @@ from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.svg import Svg
from services.network import NetworkService
from services.battery import Battery
from services.network import NetworkService
from utils.roam import modus_service
@@ -16,7 +16,9 @@ class BluetoothIndicator(Box):
self.bt_icon = Svg(
name="bt-icon",
size=20,
svg_file=get_relative_path("../../../config/assets/icons/applets/bluetooth-clear.svg"),
svg_file=get_relative_path(
"../../../config/assets/icons/applets/bluetooth-clear.svg"
),
)
self.bt_button = Button(name="bt-button", child=self.bt_icon)
@@ -34,14 +36,18 @@ class BluetoothIndicator(Box):
def update_state(self):
if not self.bluetooth.enabled:
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/applets/bluetooth-off-clear.svg")
get_relative_path(
"../../../config/assets/icons/applets/bluetooth-off-clear.svg"
)
)
tooltip = "Bluetooth disabled"
else:
connected_devices = self.bluetooth.connected_devices
if connected_devices:
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/applets/bluetooth-clear.svg")
get_relative_path(
"../../../config/assets/icons/applets/bluetooth-clear.svg"
)
)
if len(connected_devices) == 1:
device = connected_devices[0]
@@ -52,7 +58,9 @@ class BluetoothIndicator(Box):
tooltip = f"Connected to {len(connected_devices)} devices"
else:
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/applets/bluetooth-clear.svg")
get_relative_path(
"../../../config/assets/icons/applets/bluetooth-clear.svg"
)
)
tooltip = "No devices connected"
@@ -104,7 +112,9 @@ class NetworkIndicator(Box):
self.network_icon = Svg(
name="network-icon",
size=22,
svg_file=get_relative_path("../../../config/assets/icons/applets/wifi-clear.svg"),
svg_file=get_relative_path(
"../../../config/assets/icons/applets/wifi-clear.svg"
),
)
self.network_button = Button(name="network-button", child=self.network_icon)
@@ -227,7 +237,9 @@ class BatteryIndicator(Box):
self.battery_icon = Svg(
name="battery-icon",
size=22,
svg_file=get_relative_path("../../../config/assets/icons/battery/battery-100.svg"),
svg_file=get_relative_path(
"../../../config/assets/icons/battery/battery-100.svg"
),
)
self.battery_button = Button(name="battery-button", child=self.battery_icon)
-1
View File
@@ -7,7 +7,6 @@ from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.label import Label
from modules.about import About
from utils.roam import modus_service
from widgets.dropdown import ModusDropdown, dropdown_divider
+3 -6
View File
@@ -4,22 +4,19 @@ from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.datetime import DateTime
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.svg import Svg
from widgets.wayland import WaylandWindow as Window
from modules.controlcenter.main import ModusControlCenter
from modules.panel.components.enhanced_system_tray import apply_enhanced_system_tray
from modules.panel.components.indicators import (
BatteryIndicator,
BluetoothIndicator,
NetworkIndicator,
BatteryIndicator,
)
from modules.panel.components.menubar import MenuBar
from modules.panel.components.recording_indicator import RecordingIndicator
from modules.controlcenter.main import ModusControlCenter
from widgets.mousecapture import MouseCapture
from widgets.wayland import WaylandWindow as Window
# Apply enhanced system tray icon handling
apply_enhanced_system_tray()
+2 -1
View File
@@ -1,12 +1,13 @@
import json
from gi.repository import Gdk
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from gi.repository import Gdk
from utils.icon_resolver import IconResolver
from widgets.wayland import WaylandWindow as Window