Huge Updates
This commit is contained in:
1 parent
28f47aed3c
commit
1885a2dc30
30 files changed
+1421
-1260
No files matched your search
+26
-22
@@ -1,6 +1,11 @@
|
||||
from fabric.hyprland.widgets import Language
|
||||
from fabric.system_tray.widgets import SystemTray
|
||||
from fabric.utils import FormattedString, bulk_replace
|
||||
from fabric.utils import (
|
||||
FormattedString,
|
||||
bulk_replace,
|
||||
exec_shell_command_async,
|
||||
get_relative_path,
|
||||
)
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
@@ -10,16 +15,14 @@ from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from gi.repository import GLib
|
||||
from modules.bar.components import (
|
||||
BatteryLabel,
|
||||
BluetoothIndicator,
|
||||
MicrophoneIndicator,
|
||||
NetworkIndicator,
|
||||
SystemInfo,
|
||||
TaskBar,
|
||||
VolumeIndicator,
|
||||
workspace,
|
||||
Indicators,
|
||||
UpdatesWidget,
|
||||
)
|
||||
from snippets import MaterialIcon
|
||||
from services import sc
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class Bar(Window):
|
||||
@@ -39,7 +42,7 @@ class Bar(Window):
|
||||
|
||||
self.recording_indicator = Button(
|
||||
name="recording-indicator",
|
||||
child=MaterialIcon("stop_circle", 16),
|
||||
child=Label(name="recorder", markup=icons.record),
|
||||
visible=False,
|
||||
on_clicked=lambda *_: sc.screencast_stop(),
|
||||
)
|
||||
@@ -50,13 +53,9 @@ class Bar(Window):
|
||||
|
||||
self.date_time = DateTime(formatters=["%-I:%M %a %d %b"], name="datetime")
|
||||
|
||||
self.battery = BatteryLabel(name="battery")
|
||||
self.volume = VolumeIndicator()
|
||||
self.bluetooth = BluetoothIndicator()
|
||||
self.network = NetworkIndicator()
|
||||
self.microphone = MicrophoneIndicator()
|
||||
self.battery = BatteryLabel()
|
||||
self.taskbar = TaskBar()
|
||||
self.info = SystemInfo()
|
||||
self.stats = SystemInfo()
|
||||
self.tray = SystemTray(name="tray", icon_size=16, spacing=4)
|
||||
self.launcher = Button(
|
||||
name="logo",
|
||||
@@ -65,6 +64,15 @@ class Bar(Window):
|
||||
"fabric-cli exec modus 'launcher.open(\"launcher\")'"
|
||||
),
|
||||
)
|
||||
self.updates = UpdatesWidget()
|
||||
self.indicators = Indicators()
|
||||
self.button_config = Button(
|
||||
name="button-bar",
|
||||
on_clicked=lambda *_: exec_shell_command_async(
|
||||
f"python {get_relative_path('../../config/config.py')}"
|
||||
),
|
||||
child=Label(name="button-bar-label", markup=icons.config),
|
||||
)
|
||||
|
||||
self.applets = Box(
|
||||
name="applets",
|
||||
@@ -72,10 +80,7 @@ class Bar(Window):
|
||||
orientation="h",
|
||||
children=[
|
||||
self.language,
|
||||
self.bluetooth,
|
||||
self.network,
|
||||
self.volume,
|
||||
self.microphone,
|
||||
self.indicators,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -85,11 +90,7 @@ class Bar(Window):
|
||||
name="start-container",
|
||||
spacing=8,
|
||||
orientation="h",
|
||||
children=[
|
||||
self.launcher,
|
||||
self.workspaces,
|
||||
self.info,
|
||||
],
|
||||
children=[self.launcher, self.workspaces, self.stats, self.updates],
|
||||
),
|
||||
center_children=Box(
|
||||
name="center-container",
|
||||
@@ -107,6 +108,7 @@ class Bar(Window):
|
||||
self.battery,
|
||||
self.applets,
|
||||
self.date_time,
|
||||
self.button_config,
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -127,6 +129,8 @@ class Bar(Window):
|
||||
def toggle_hidden(self):
|
||||
self.hidden = not self.hidden
|
||||
if self.hidden:
|
||||
self.bar.remove_style_class("visible")
|
||||
self.bar.add_style_class("hidden")
|
||||
else:
|
||||
self.bar.remove_style_class("hidden")
|
||||
self.bar.add_style_class("visible")
|
||||
@@ -1,10 +1,6 @@
|
||||
from .battery import *
|
||||
from .bluetooth import *
|
||||
from .idle import *
|
||||
from .stats import *
|
||||
from .microphone import *
|
||||
from .network import *
|
||||
from .powerprofile import *
|
||||
from .taskbar import *
|
||||
from .volume import *
|
||||
from .workspace import *
|
||||
from .indicators import *
|
||||
from .updates import *
|
||||
@@ -1,70 +1,237 @@
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
import subprocess
|
||||
import re
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from snippets import MaterialIcon
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from fabric.core.fabricator import Fabricator
|
||||
from fabric.utils.helpers import exec_shell_command_async
|
||||
from gi.repository import GLib
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class BatteryLabel(Box):
|
||||
ICONS_CHARGING = [
|
||||
"battery_charging_20",
|
||||
"battery_charging_20",
|
||||
"battery_charging_20",
|
||||
"battery_charging_30",
|
||||
"battery_charging_30",
|
||||
"battery_charging_50",
|
||||
"battery_charging_60",
|
||||
"battery_charging_80",
|
||||
"battery_charging_80",
|
||||
"battery_charging_90",
|
||||
"battery_charging_full",
|
||||
]
|
||||
ICONS_NOT_CHARGING = [
|
||||
"battery_alert",
|
||||
"battery_1_bar",
|
||||
"battery_1_bar",
|
||||
"battery_2_bar",
|
||||
"battery_2_bar",
|
||||
"battery_3_bar",
|
||||
"battery_4_bar",
|
||||
"battery_4_bar",
|
||||
"battery_5_bar",
|
||||
"battery_6_bar",
|
||||
"battery_full",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Fabricator(interval=1000, poll_from=self.update_battery_status)
|
||||
|
||||
def update_battery_status(self, *_):
|
||||
battery = psutil.sensors_battery()
|
||||
if battery is None:
|
||||
self.hide()
|
||||
return
|
||||
is_charging = battery.power_plugged if battery else False
|
||||
icons = self.ICONS_CHARGING if is_charging else self.ICONS_NOT_CHARGING
|
||||
battery_percent = round(battery.percent)
|
||||
index = min(max(battery_percent // 10, 0), 10)
|
||||
battery_icon = MaterialIcon(icons[index], size=16)
|
||||
|
||||
self.progress_bar = CircularProgressBar(
|
||||
name="progress", line_style="round", line_width=2, size=24
|
||||
super().__init__(
|
||||
name="battery",
|
||||
orientation="h",
|
||||
spacing=0,
|
||||
)
|
||||
|
||||
self.battery_overlay = Overlay(child=self.progress_bar, overlays=battery_icon)
|
||||
self.battery_label = Label(label=f"{battery_percent}%", name="battery-label")
|
||||
|
||||
self.children = self.battery_label, self.battery_overlay
|
||||
|
||||
battery_percent = round(battery.percent)
|
||||
self.progress_bar.value = battery_percent / 100
|
||||
self.show() if battery_percent < 100 else self.hide()
|
||||
|
||||
self.set_tooltip_text(
|
||||
f"Battery: {battery_percent}%\nStatus: {'Charging' if is_charging else 'Discharging'}"
|
||||
self.bat_save = Button(
|
||||
name="battery-save",
|
||||
child=Label(name="battery-save-label", markup=icons.power_saving),
|
||||
on_clicked=lambda *_: self.set_power_mode("powersave"),
|
||||
)
|
||||
self.bat_balanced = Button(
|
||||
name="battery-balanced",
|
||||
child=Label(name="battery-balanced-label", markup=icons.power_balanced),
|
||||
on_clicked=lambda *_: self.set_power_mode("balanced"),
|
||||
)
|
||||
self.bat_perf = Button(
|
||||
name="battery-performance",
|
||||
child=Label(
|
||||
name="battery-performance-label", markup=icons.power_performance
|
||||
),
|
||||
on_clicked=lambda *_: self.set_power_mode("performance"),
|
||||
)
|
||||
|
||||
return True
|
||||
# Attach mouse enter/leave events to buttons as well
|
||||
for btn in [self.bat_save, self.bat_balanced, self.bat_perf]:
|
||||
btn.connect("enter-notify-event", self.on_mouse_enter)
|
||||
btn.connect("leave-notify-event", self.on_mouse_leave)
|
||||
|
||||
# Group the mode buttons into a container.
|
||||
self.mode_switcher = Box(
|
||||
name="power-mode-switcher",
|
||||
orientation="h",
|
||||
spacing=4,
|
||||
children=[self.bat_save, self.bat_balanced, self.bat_perf],
|
||||
)
|
||||
|
||||
self.bat_icon = Label(name="battery-icon", markup=icons.battery)
|
||||
|
||||
self.bat_circle = CircularProgressBar(
|
||||
name="battery-circle",
|
||||
value=0,
|
||||
size=28,
|
||||
line_width=2,
|
||||
start_angle=180,
|
||||
end_angle=360,
|
||||
)
|
||||
|
||||
self.bat_overlay = Overlay(
|
||||
name="battery-overlay",
|
||||
visible=False,
|
||||
child=self.bat_circle,
|
||||
overlays=[self.bat_icon],
|
||||
)
|
||||
|
||||
self.bat_level = Label(
|
||||
name="battery-level",
|
||||
label="100%",
|
||||
)
|
||||
|
||||
self.bat_revealer = Revealer(
|
||||
name="battery-revealer",
|
||||
transition_duration=250,
|
||||
transition_type="slide-left",
|
||||
child=self.bat_level,
|
||||
child_revealed=False,
|
||||
)
|
||||
|
||||
# Create an inner container to hold the battery elements.
|
||||
inner_container = Box(orientation="h", spacing=3)
|
||||
inner_container.add(self.bat_overlay)
|
||||
inner_container.add(self.bat_revealer)
|
||||
inner_container.add(self.mode_switcher)
|
||||
|
||||
# Wrap the inner container in an EventBox.
|
||||
self.event_box = EventBox(
|
||||
events=["enter-notify-event", "leave-notify-event"],
|
||||
name="battery-eventbox",
|
||||
)
|
||||
self.event_box.connect("enter-notify-event", self.on_mouse_enter)
|
||||
self.event_box.connect("leave-notify-event", self.on_mouse_leave)
|
||||
self.event_box.add(inner_container)
|
||||
|
||||
# Add the EventBox to the Battery widget.
|
||||
self.add(self.event_box)
|
||||
|
||||
self.current_mode = None
|
||||
# Initialize hide_timer for delayed hiding.
|
||||
self.hide_timer = None
|
||||
# Initialize counter to track hover status across all related widgets.
|
||||
self.hover_counter = 0
|
||||
|
||||
# Initialize Fabricator for battery polling every 5 seconds.
|
||||
self.batt_fabricator = Fabricator(
|
||||
lambda *args, **kwargs: self.poll_battery(),
|
||||
interval=1000,
|
||||
stream=False,
|
||||
default_value=0,
|
||||
)
|
||||
self.batt_fabricator.changed.connect(self.update_battery)
|
||||
|
||||
# Run update_battery immediately at startup.
|
||||
GLib.idle_add(self.update_battery, None, self.poll_battery())
|
||||
self.set_power_mode("balanced")
|
||||
|
||||
def on_mouse_enter(self, widget, event):
|
||||
"""Reveal battery level on hover."""
|
||||
self.hover_counter += 1
|
||||
if self.hide_timer is not None:
|
||||
GLib.source_remove(self.hide_timer)
|
||||
self.hide_timer = None
|
||||
self.bat_revealer.set_reveal_child(True)
|
||||
return False
|
||||
|
||||
def on_mouse_leave(self, widget, event):
|
||||
"""Schedule hiding the battery level after a 0.5s delay only if not hovering any element."""
|
||||
if self.hover_counter > 0:
|
||||
self.hover_counter -= 1
|
||||
if self.hover_counter == 0:
|
||||
if self.hide_timer is not None:
|
||||
GLib.source_remove(self.hide_timer)
|
||||
self.hide_timer = GLib.timeout_add(500, self.hide_revealer)
|
||||
return False
|
||||
|
||||
def hide_revealer(self):
|
||||
self.bat_revealer.set_reveal_child(False)
|
||||
self.hide_timer = None
|
||||
return False
|
||||
|
||||
def poll_battery(self):
|
||||
"""
|
||||
Polls the battery status by running the "acpi -b" command.
|
||||
If no battery information is found, returns (0, None).
|
||||
Otherwise, returns a tuple: (battery percentage as a float between 0 and 1, battery status string)
|
||||
"""
|
||||
try:
|
||||
output = subprocess.check_output(["acpi", "-b"]).decode("utf-8").strip()
|
||||
if "Battery" not in output:
|
||||
return (0, None)
|
||||
match_percent = re.search(r"(\d+)%", output)
|
||||
match_status = re.search(r"Battery \d+: (\w+)", output)
|
||||
if match_percent:
|
||||
percent = int(match_percent.group(1))
|
||||
status = match_status.group(1) if match_status else None
|
||||
return (percent / 100.0, status)
|
||||
except Exception:
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
def update_battery(self, sender, battery_data):
|
||||
"""
|
||||
Updates the battery widget...
|
||||
"""
|
||||
value, status = battery_data
|
||||
if value == 0:
|
||||
self.bat_overlay.set_visible(False)
|
||||
self.bat_revealer.set_visible(False)
|
||||
else:
|
||||
self.bat_overlay.set_visible(True)
|
||||
self.bat_revealer.set_visible(True)
|
||||
self.bat_circle.set_value(value)
|
||||
|
||||
percentage = int(value * 100)
|
||||
self.bat_level.set_label(f"{percentage}%")
|
||||
|
||||
# Actualizar el icono de la batería basado en el nivel y el estado de carga.
|
||||
if percentage <= 15:
|
||||
self.bat_icon.set_markup(icons.alert)
|
||||
self.bat_icon.add_style_class("alert")
|
||||
self.bat_circle.add_style_class("alert")
|
||||
else:
|
||||
self.bat_icon.remove_style_class("alert")
|
||||
self.bat_circle.remove_style_class("alert")
|
||||
if (
|
||||
status == "Discharging"
|
||||
): # Muestra discharging en cualquier nivel, incluso al 100%.
|
||||
self.bat_icon.set_markup(icons.discharging)
|
||||
elif percentage == 100:
|
||||
self.bat_icon.set_markup(icons.battery)
|
||||
elif status == "Charging":
|
||||
self.bat_icon.set_markup(icons.charging)
|
||||
else:
|
||||
self.bat_icon.set_markup(icons.battery)
|
||||
|
||||
def set_power_mode(self, mode):
|
||||
"""
|
||||
Switches power mode by running the corresponding auto-cpufreq command.
|
||||
mode: one of 'powersave', 'balanced', or 'performance'
|
||||
"""
|
||||
commands = {
|
||||
"powersave": "sudo auto-cpufreq --force powersave",
|
||||
"balanced": "sudo auto-cpufreq --force reset",
|
||||
"performance": "sudo auto-cpufreq --force performance",
|
||||
}
|
||||
if mode in commands:
|
||||
try:
|
||||
exec_shell_command_async(commands[mode])
|
||||
self.current_mode = mode
|
||||
self.update_button_styles()
|
||||
except Exception as err:
|
||||
# Optionally, handle errors or display a notification.
|
||||
print(f"Error setting power mode: {err}")
|
||||
|
||||
def update_button_styles(self):
|
||||
"""
|
||||
Optionally updates button styles to reflect the current mode.
|
||||
Adjust the styling method based on your toolkit's capabilities.
|
||||
"""
|
||||
if self.current_mode == "powersave":
|
||||
self.bat_save.add_style_class("active")
|
||||
self.bat_balanced.remove_style_class("active")
|
||||
self.bat_perf.remove_style_class("active")
|
||||
elif self.current_mode == "balanced":
|
||||
self.bat_save.remove_style_class("active")
|
||||
self.bat_balanced.add_style_class("active")
|
||||
self.bat_perf.remove_style_class("active")
|
||||
elif self.current_mode == "performance":
|
||||
self.bat_save.remove_style_class("active")
|
||||
self.bat_balanced.remove_style_class("active")
|
||||
self.bat_perf.add_style_class("active")
|
||||
@@ -1,34 +0,0 @@
|
||||
from fabric.bluetooth import BluetoothClient
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class BluetoothIndicator(Box):
|
||||
ICON_ENABLED = "bluetooth"
|
||||
ICON_DISABLED = "bluetooth_disabled"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bluetooth_client = BluetoothClient()
|
||||
|
||||
self.bluetooth_icon = None
|
||||
|
||||
self.bluetooth_client.connect("changed", self.update_bluetooth_status)
|
||||
|
||||
self.update_bluetooth_status()
|
||||
|
||||
def update_bluetooth_status(self, *_):
|
||||
icon_label = (
|
||||
self.ICON_ENABLED if self.bluetooth_client.enabled else self.ICON_DISABLED
|
||||
)
|
||||
|
||||
new_icon = MaterialIcon(icon_label, size=16)
|
||||
|
||||
if self.bluetooth_icon:
|
||||
self.remove(self.bluetooth_icon)
|
||||
|
||||
self.bluetooth_icon = new_icon
|
||||
self.add(self.bluetooth_icon)
|
||||
|
||||
def on_destroy(self):
|
||||
self.bluetooth_client.disconnect("changed", self.update_bluetooth_status)
|
||||
@@ -0,0 +1,134 @@
|
||||
import subprocess
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.bluetooth import BluetoothClient
|
||||
from services import NetworkClient, audio
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class Indicators(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(orientation="h", spacing=2, **kwargs)
|
||||
|
||||
self.bluetooth_icon = Label(name="indicator-icon")
|
||||
self.wifi_icon = Label(name="indicator-icon")
|
||||
self.volume_icon_button = Label(name="indicator-icon")
|
||||
self.microphone_icon = Label(name="indicator-icon")
|
||||
self.idle_label = Label(name="indicator-icon")
|
||||
self.night_label = Label(name="indicator-icon")
|
||||
|
||||
for widget in [
|
||||
self.bluetooth_icon,
|
||||
self.wifi_icon,
|
||||
self.volume_icon_button,
|
||||
self.microphone_icon,
|
||||
self.idle_label,
|
||||
self.night_label,
|
||||
]:
|
||||
self.add(widget)
|
||||
|
||||
self.audio_service = audio
|
||||
self.bluetooth_client = BluetoothClient()
|
||||
self.network_client = NetworkClient()
|
||||
|
||||
# Connect signals
|
||||
self.bluetooth_client.connect("changed", self.update_bluetooth_status)
|
||||
self.audio_service.connect("microphone_changed", self.update_mic_status)
|
||||
self.audio_service.connect("changed", self.update_volume_status)
|
||||
self.network_client.connect("device_ready", self.update_network_status)
|
||||
|
||||
if self.network_client.wifi_device:
|
||||
self.network_client.wifi_device.connect(
|
||||
"changed", self.update_network_status
|
||||
)
|
||||
|
||||
if self.network_client.ethernet_device:
|
||||
self.network_client.ethernet_device.connect(
|
||||
"changed", self.update_network_status
|
||||
)
|
||||
|
||||
Fabricator(interval=1000, poll_from=self.update_all_statuses)
|
||||
|
||||
self.update_bluetooth_status()
|
||||
|
||||
def update_all_statuses(self, *_args):
|
||||
self.update_idle_night_status()
|
||||
self.update_network_status()
|
||||
|
||||
def update_idle_night_status(self, *_):
|
||||
"""Updates the idle and night mode status icons."""
|
||||
self.idle_label.set_markup(
|
||||
icons.coffee
|
||||
if subprocess.run(
|
||||
["pgrep", "-x", "wlinhibit"], capture_output=True
|
||||
).returncode
|
||||
== 0
|
||||
else ""
|
||||
)
|
||||
|
||||
self.night_label.set_markup(
|
||||
icons.night
|
||||
if subprocess.run(
|
||||
["pgrep", "-x", "hyprsunset"], capture_output=True
|
||||
).returncode
|
||||
== 0
|
||||
else ""
|
||||
)
|
||||
|
||||
def update_volume_status(self, *_):
|
||||
"""Updates the volume icon based on speaker status."""
|
||||
stream = self.audio_service.speaker
|
||||
if stream:
|
||||
volume_level = stream.volume
|
||||
self.volume_icon_button.set_markup(
|
||||
icons.vol_off
|
||||
if stream.muted
|
||||
else (
|
||||
icons.vol_mute
|
||||
if volume_level == 0
|
||||
else (icons.vol_medium if volume_level <= 50 else icons.vol_high)
|
||||
)
|
||||
)
|
||||
|
||||
def update_mic_status(self, *_):
|
||||
"""Updates the microphone icon based on mute status."""
|
||||
mic = self.audio_service.microphone
|
||||
self.microphone_icon.set_markup(icons.mic_off if mic and mic.muted else "")
|
||||
self.microphone_icon.set_visible(bool(mic))
|
||||
|
||||
def update_bluetooth_status(self, *_):
|
||||
"""Updates the Bluetooth icon based on enabled status."""
|
||||
self.bluetooth_icon.set_markup(
|
||||
icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off
|
||||
)
|
||||
|
||||
def update_network_status(self, *_):
|
||||
primary_device = self.network_client.primary_device
|
||||
|
||||
if primary_device == "wifi" and self.network_client.wifi_device:
|
||||
wifi_device = self.network_client.wifi_device
|
||||
self.set_tooltip_text(self.network_client.wifi_device.ssid)
|
||||
|
||||
if wifi_device.enabled:
|
||||
wifi_strength = wifi_device.strength
|
||||
|
||||
if wifi_strength == -1: # No active connection
|
||||
icon_label = icons.wifi_off
|
||||
else:
|
||||
icon_label = {
|
||||
80: icons.wifi,
|
||||
60: icons.good_signal,
|
||||
40: icons.moderate_signal,
|
||||
20: icons.weak_signal,
|
||||
0: icons.no_signal,
|
||||
}.get(min(80, 20 * round(wifi_strength / 20)), icons.wifi_off)
|
||||
else:
|
||||
icon_label = icons.wifi_off
|
||||
|
||||
elif primary_device == "wired":
|
||||
icon_label = icons.lan
|
||||
else:
|
||||
icon_label = icons.wifi_off
|
||||
|
||||
self.wifi_icon.set_markup(icon_label)
|
||||
@@ -1,33 +0,0 @@
|
||||
from fabric.audio.service import Audio
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class MicrophoneIndicator(Box):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.audio_service = Audio()
|
||||
self.microphone_icon = self.create_microphone_icon()
|
||||
|
||||
self.audio_service.connect("microphone_changed", self.update_microphone_status)
|
||||
self.update_microphone_status()
|
||||
|
||||
self.children = (self.microphone_icon,)
|
||||
|
||||
def create_microphone_icon(self):
|
||||
return MaterialIcon("mic_off", size=16)
|
||||
|
||||
def update_microphone_status(self, *args):
|
||||
current_microphone = self.audio_service.microphone
|
||||
|
||||
if current_microphone:
|
||||
is_muted = current_microphone.muted
|
||||
icon_name = "mic_off" if is_muted else "mic"
|
||||
|
||||
self.microphone_icon.set_label(icon_name)
|
||||
self.microphone_icon.set_visible(is_muted)
|
||||
else:
|
||||
self.microphone_icon.set_visible(False)
|
||||
|
||||
return True
|
||||
@@ -1,84 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class NetworkIndicator(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.update_interval = 5000 # Increased interval to 5 seconds
|
||||
self.last_status = None
|
||||
self.wifi_strength_cache = None
|
||||
self.fabricator = Fabricator(
|
||||
interval=self.update_interval,
|
||||
poll_from=self.update_network_status,
|
||||
)
|
||||
|
||||
def update_network_status(self, *_):
|
||||
current_status = self.get_network_status()
|
||||
if current_status != self.last_status:
|
||||
self.update_icon(current_status)
|
||||
self.last_status = current_status
|
||||
return True
|
||||
|
||||
def update_icon(self, current_status):
|
||||
self.icon_label = MaterialIcon(current_status, size=16)
|
||||
self.children = (self.icon_label,)
|
||||
|
||||
def get_network_status(self):
|
||||
if self.is_ethernet_connected():
|
||||
return "lan"
|
||||
if self.is_wifi_connected():
|
||||
return self.get_wifi_strength_icon()
|
||||
return "signal_wifi_off"
|
||||
|
||||
def get_wifi_strength_icon(self):
|
||||
wifi_strength = self.check_wifi_strength()
|
||||
if wifi_strength is not None:
|
||||
self.wifi_strength_cache = wifi_strength # Cache the result
|
||||
else:
|
||||
wifi_strength = self.wifi_strength_cache # Use cached value if available
|
||||
|
||||
return self.wifi_signal_icon(wifi_strength)
|
||||
|
||||
def wifi_signal_icon(self, strength):
|
||||
if strength is None or strength <= -100:
|
||||
return "signal_wifi_0_bar"
|
||||
if strength > -50:
|
||||
return "signal_wifi_4_bar"
|
||||
if strength > -60:
|
||||
return "network_wifi_3_bar"
|
||||
if strength > -70:
|
||||
return "network_wifi_2_bar"
|
||||
return "network_wifi_1_bar"
|
||||
|
||||
def check_wifi_strength(self):
|
||||
try:
|
||||
result = subprocess.run(["iwconfig"], capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if "Signal level" in line:
|
||||
signal_level = line.split("Signal level=")[1].split(" ")[0]
|
||||
return int(signal_level.strip().replace("dBm", ""))
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def is_wifi_connected(self):
|
||||
return any(
|
||||
self._is_interface_up(interface, ["wlan", "wifi"])
|
||||
for interface in psutil.net_if_addrs()
|
||||
)
|
||||
|
||||
def is_ethernet_connected(self):
|
||||
return any(
|
||||
self._is_interface_up(interface, ["enp", "eth"])
|
||||
for interface in psutil.net_if_addrs()
|
||||
)
|
||||
|
||||
def _is_interface_up(self, interface, keywords):
|
||||
if any(substring in interface.lower() for substring in keywords):
|
||||
return psutil.net_if_stats().get(interface, {}).isup
|
||||
return False
|
||||
@@ -1,88 +1,83 @@
|
||||
from time import sleep
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from gi.repository import GLib
|
||||
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from snippets import MaterialIcon
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class SystemInfo(Button):
|
||||
ICONS = [
|
||||
("CPU", "memory"),
|
||||
("Swap", "swap_horiz"),
|
||||
("RAM", "memory_alt"),
|
||||
("Temp", "thermometer"),
|
||||
]
|
||||
class SystemInfo(Box):
|
||||
ICONS = {
|
||||
"CPU": icons.cpu,
|
||||
"RAM": icons.memory,
|
||||
"Swap": icons.swap,
|
||||
"Disk": icons.disk,
|
||||
"Temp": icons.temp,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(child=self._initialize_content())
|
||||
|
||||
Fabricator(
|
||||
poll_from=self._refresh_system_info,
|
||||
stream=True,
|
||||
on_changed=lambda fab, data: self._update_ui(data),
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
name="system-info",
|
||||
spacing=8,
|
||||
h_align="center",
|
||||
v_align="fill",
|
||||
visible=True,
|
||||
all_visible=True,
|
||||
)
|
||||
|
||||
def _initialize_content(self):
|
||||
self.box = Box(orientation="h", name="tray")
|
||||
self.progress_bars = {}
|
||||
self.icons = {}
|
||||
self.progress_bars = {
|
||||
system: CircularProgressBar(
|
||||
name="stats-progress",
|
||||
line_style="round",
|
||||
line_width=3,
|
||||
size=28,
|
||||
start_at=0,
|
||||
end_at=0.5,
|
||||
start_angle=0,
|
||||
end_angle=180,
|
||||
)
|
||||
for system in self.ICONS
|
||||
}
|
||||
|
||||
for system, icon_name in self.ICONS:
|
||||
self._create_system_item(system, icon_name)
|
||||
for system, icon_name in self.ICONS.items():
|
||||
overlay = Overlay(
|
||||
child=self.progress_bars[system],
|
||||
overlays=[Label(name="stats-icons", markup=icon_name)],
|
||||
)
|
||||
self.add(overlay)
|
||||
|
||||
return self.box
|
||||
GLib.timeout_add_seconds(1, self._update_system_info)
|
||||
|
||||
def _create_system_item(self, system, icon_name):
|
||||
self.progress_bars[system] = self._create_progress_bar()
|
||||
self.icons[system] = self._create_icon(icon_name)
|
||||
def _update_system_info(self):
|
||||
ram_usage = psutil.virtual_memory().percent
|
||||
swap_usage = psutil.swap_memory().percent
|
||||
disk_usage = psutil.disk_usage("/").percent
|
||||
cpu = psutil.cpu_percent(interval=0)
|
||||
|
||||
overlay = Overlay(
|
||||
child=self.progress_bars[system], overlays=[self.icons[system]]
|
||||
)
|
||||
self.box.pack_start(overlay, False, False, 4)
|
||||
usages = {
|
||||
"CPU": cpu,
|
||||
"RAM": ram_usage,
|
||||
"Swap": swap_usage,
|
||||
"Disk": disk_usage,
|
||||
"Temp": self._get_device_temperature(),
|
||||
}
|
||||
|
||||
def _create_progress_bar(self):
|
||||
return CircularProgressBar(
|
||||
name="progress",
|
||||
line_style="round",
|
||||
line_width=3,
|
||||
size=28,
|
||||
start_at=0,
|
||||
end_at=0.5,
|
||||
# start_angle=180,
|
||||
end_angle=180,
|
||||
)
|
||||
for system, usage in usages.items():
|
||||
self.progress_bars[system].value = usage / 100.0
|
||||
self.progress_bars[system].set_tooltip_text(f"{system} {usage:.1f}%")
|
||||
|
||||
def _create_icon(self, icon_name):
|
||||
return MaterialIcon(icon_name, 16)
|
||||
|
||||
def _refresh_system_info(self, fab: Fabricator):
|
||||
while True:
|
||||
yield {
|
||||
"CPU": round(psutil.cpu_percent()),
|
||||
"RAM": round(psutil.virtual_memory().percent),
|
||||
"Swap": round(psutil.swap_memory().percent),
|
||||
"Temp": self._get_device_temperature(),
|
||||
}
|
||||
sleep(1)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_device_temperature():
|
||||
try:
|
||||
temps = psutil.sensors_temperatures()
|
||||
if "coretemp" in temps:
|
||||
return round(temps["coretemp"][0].current, 1)
|
||||
elif "cpu_thermal" in temps:
|
||||
return round(temps["cpu_thermal"][0].current, 1)
|
||||
return None
|
||||
for key in ("coretemp", "cpu_thermal"):
|
||||
if key in temps:
|
||||
return round(temps[key][0].current, 1)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _update_ui(self, usages):
|
||||
for system, usage in usages.items():
|
||||
self.progress_bars[system].value = usage / 100
|
||||
self.progress_bars[system].set_tooltip_text(f"{system} {usage}%")
|
||||
pass
|
||||
return None
|
||||
@@ -20,7 +20,7 @@ class PagerClient(TypedDict):
|
||||
|
||||
|
||||
class TaskBar(Box):
|
||||
def __init__(self, icon_size: int = 20, **kwargs):
|
||||
def __init__(self, icon_size: int = 22, **kwargs):
|
||||
super().__init__(
|
||||
orientation="h",
|
||||
spacing=7,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import json
|
||||
from fabric.utils import exec_shell_command_async, get_relative_path, invoke_repeater
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.stack import Stack
|
||||
import snippets.iconss as icons
|
||||
from loguru import logger
|
||||
from gi.repository import Gtk
|
||||
from fabric.widgets.button import Button
|
||||
|
||||
|
||||
class UpdatesWidget(Button):
|
||||
"""A widget to display the number of available updates."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
# Initialize the button with specific name and style
|
||||
super().__init__(name="button-bar", **kwargs)
|
||||
|
||||
self.script_file = get_relative_path("../../../config/scripts/systemupdates.sh")
|
||||
|
||||
self.update_level_label = Label(
|
||||
name="update-label",
|
||||
label="0",
|
||||
visible=False, # Initially hidden
|
||||
)
|
||||
|
||||
self.update_icon = Label(name="update-icon", markup=icons.update)
|
||||
self.updated_icon = Label(name="updated-icon", markup=icons.updated)
|
||||
|
||||
self.update_box = CenterBox(
|
||||
center_children=[self.update_icon, self.update_level_label],
|
||||
)
|
||||
|
||||
self.updated_box = CenterBox(
|
||||
center_children=[self.updated_icon],
|
||||
)
|
||||
|
||||
self.stack = Stack()
|
||||
self.stack.add_named(self.update_box, "update_box")
|
||||
self.stack.add_named(self.updated_box, "updated_box")
|
||||
self.stack.set_visible_child_name("update_box")
|
||||
|
||||
self.children = Box(
|
||||
children=self.stack,
|
||||
)
|
||||
|
||||
# Ensure the stack resizes to the current visible child
|
||||
self.stack.set_homogeneous(False)
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
|
||||
|
||||
# Connect the button press event
|
||||
self.connect("button-press-event", self.on_button_press)
|
||||
|
||||
# Set up a repeater to call the update method at specified intervals
|
||||
invoke_repeater(60000, self.update, initial_call=True)
|
||||
|
||||
def update_values(self, value: str):
|
||||
logger.info(f"[Updates] Received update JSON: {value}")
|
||||
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
logger.error("[Updates] Failed to parse update JSON")
|
||||
return False
|
||||
|
||||
update_count = str(value["total"])
|
||||
self.update_level_label.set_label(update_count)
|
||||
|
||||
if update_count == "0":
|
||||
self.stack.set_visible_child_name("updated_box")
|
||||
self.update_level_label.set_visible(False)
|
||||
else:
|
||||
self.stack.set_visible_child_name("update_box")
|
||||
self.update_level_label.set_visible(True)
|
||||
|
||||
self.set_tooltip_text(value.get("tooltip", ""))
|
||||
return True
|
||||
|
||||
def on_button_press(self, _, event):
|
||||
if event.button == 1:
|
||||
exec_shell_command_async(
|
||||
f"{self.script_file} -arch -up",
|
||||
lambda _: None,
|
||||
)
|
||||
self.update()
|
||||
return True
|
||||
else:
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
logger.info("[Updates] Checking for updates...")
|
||||
|
||||
# Execute the update script asynchronously and update values
|
||||
exec_shell_command_async(
|
||||
f"{self.script_file} -arch",
|
||||
lambda output: self.update_values(output),
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -1,81 +0,0 @@
|
||||
from fabric import Fabricator
|
||||
from fabric.utils import exec_shell_command
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from snippets import MaterialIcon
|
||||
from services import audio
|
||||
|
||||
VOLUME_ICONS = {
|
||||
"muted": "volume_off",
|
||||
"low": "volume_mute",
|
||||
"medium-low": "volume_down",
|
||||
"medium-high": "volume_up",
|
||||
"high": "volume_up",
|
||||
}
|
||||
|
||||
|
||||
class VolumeIndicator(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.audio_service = audio
|
||||
self.volume_icon_button = self.create_volume_icon()
|
||||
|
||||
self.volume_icon_button.connect("button-press-event", self.on_button_press)
|
||||
self.audio_service.connect("changed", self.update_volume_status)
|
||||
|
||||
Fabricator(interval=1000, poll_from=self.update_volume_status)
|
||||
self.children = (self.volume_icon_button,)
|
||||
|
||||
def create_volume_icon(self):
|
||||
icon = MaterialIcon(VOLUME_ICONS["muted"], size=16)
|
||||
button = Button(child=icon)
|
||||
button.icon_widget = icon
|
||||
return button
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
if event.button == 1:
|
||||
self.toggle_mute()
|
||||
elif event.button == 3:
|
||||
self.open_audio_settings()
|
||||
|
||||
def toggle_mute(self):
|
||||
current_stream = self.audio_service.speaker
|
||||
if current_stream:
|
||||
current_stream.muted = not current_stream.muted
|
||||
# self.update_volume_status()
|
||||
|
||||
def open_audio_settings(self):
|
||||
exec_shell_command("pavucontrol")
|
||||
|
||||
def update_volume_status(self, *_):
|
||||
current_stream = self.audio_service.speaker
|
||||
if current_stream:
|
||||
self.update_icon(current_stream)
|
||||
|
||||
return True
|
||||
|
||||
def update_icon(self, stream):
|
||||
if stream.muted:
|
||||
icon_name = VOLUME_ICONS["muted"]
|
||||
volume_percentage = 0
|
||||
tooltip_text = "Volume Muted"
|
||||
else:
|
||||
volume_level = stream.volume
|
||||
icon_name = self.get_volume_icon(volume_level)
|
||||
volume_percentage = int(volume_level)
|
||||
tooltip_text = f"Volume: {volume_percentage}%"
|
||||
|
||||
self.volume_icon_button.icon_widget.set_label(icon_name)
|
||||
|
||||
self.volume_icon_button.set_tooltip_text(tooltip_text)
|
||||
|
||||
def get_volume_icon(self, volume_level):
|
||||
if volume_level == 0:
|
||||
return VOLUME_ICONS["low"]
|
||||
elif volume_level <= 33:
|
||||
return VOLUME_ICONS["medium-low"]
|
||||
elif volume_level <= 66:
|
||||
return VOLUME_ICONS["medium-high"]
|
||||
else:
|
||||
return VOLUME_ICONS["high"]
|
||||
@@ -9,6 +9,12 @@ from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import GLib, Gdk
|
||||
from snippets import read_config
|
||||
from fabric.widgets.image import Image
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
import subprocess
|
||||
import webbrowser
|
||||
|
||||
|
||||
class AppLauncher(Box):
|
||||
@@ -22,11 +28,18 @@ class AppLauncher(Box):
|
||||
|
||||
self.launcher = kwargs["launcher"]
|
||||
self.selected_index = -1
|
||||
|
||||
self._arranger_handler: int = 0
|
||||
self._all_apps = get_desktop_applications()
|
||||
self.config = read_config()
|
||||
|
||||
# Calculator history initialization
|
||||
self.calc_history_path = os.path.expanduser("~/.cache/modus/calc.json")
|
||||
if os.path.exists(self.calc_history_path):
|
||||
with open(self.calc_history_path, "r") as f:
|
||||
self.calc_history = json.load(f)
|
||||
else:
|
||||
self.calc_history = []
|
||||
|
||||
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
||||
self.search_entry = Entry(
|
||||
name="search-entry",
|
||||
@@ -35,18 +48,13 @@ class AppLauncher(Box):
|
||||
on_activate=lambda entry, *_: self.on_search_entry_activate(
|
||||
entry.get_text()
|
||||
),
|
||||
on_key_press_event=self.on_search_entry_key_press, # Handle key presses
|
||||
)
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
h_scrollbar_policy="never",
|
||||
v_scrollbar_policy="never",
|
||||
child=self.viewport,
|
||||
on_key_press_event=self.on_search_entry_key_press,
|
||||
)
|
||||
|
||||
self.scrolled_window = None
|
||||
|
||||
self.header_box = Box(
|
||||
name="header_box",
|
||||
name="header-box",
|
||||
orientation="h",
|
||||
children=[self.search_entry],
|
||||
)
|
||||
@@ -55,41 +63,46 @@ class AppLauncher(Box):
|
||||
name="launcher-box",
|
||||
h_expand=True,
|
||||
orientation="v",
|
||||
children=[
|
||||
self.header_box,
|
||||
self.scrolled_window,
|
||||
],
|
||||
children=[self.header_box],
|
||||
)
|
||||
|
||||
self.resize_viewport()
|
||||
|
||||
self.add(self.launcher_box)
|
||||
self.show_all()
|
||||
|
||||
def close_launcher(self, *_):
|
||||
self.viewport.children = []
|
||||
self.selected_index = -1 # Reset selection
|
||||
self.selected_index = -1
|
||||
self.launcher.close()
|
||||
|
||||
def open_launcher(self):
|
||||
self._all_apps = get_desktop_applications()
|
||||
self.arrange_viewport()
|
||||
self.show_all()
|
||||
self.arrange_viewport("")
|
||||
|
||||
def arrange_viewport(self, query: str = ""):
|
||||
if query.startswith(":"): # Don't show ScrolledWindow for web search
|
||||
self.destroy_scrolled_window()
|
||||
self.launcher.dashboard.hide()
|
||||
return
|
||||
if query.startswith("="):
|
||||
self.show_scrolled_window()
|
||||
# In calculator mode, update history view once (not per keystroke)
|
||||
self.update_calculator_viewport()
|
||||
self.launcher.dashboard.hide()
|
||||
return
|
||||
|
||||
remove_handler(self._arranger_handler) if self._arranger_handler else None
|
||||
self.viewport.children = []
|
||||
self.selected_index = -1
|
||||
|
||||
if query.strip():
|
||||
self.launcher.dashboard.hide()
|
||||
self.show_scrolled_window()
|
||||
else:
|
||||
self.launcher.dashboard.show()
|
||||
|
||||
if not query.strip():
|
||||
self.destroy_scrolled_window()
|
||||
return False
|
||||
|
||||
filtered_apps = iter(
|
||||
filtered_apps_iter = iter(
|
||||
sorted(
|
||||
[
|
||||
app
|
||||
@@ -100,19 +113,41 @@ class AppLauncher(Box):
|
||||
+ (" " + app.name + " ")
|
||||
+ (app.generic_name or "")
|
||||
).casefold()
|
||||
][:6],
|
||||
],
|
||||
key=lambda app: (app.display_name or "").casefold(),
|
||||
)
|
||||
)
|
||||
should_resize = operator.length_hint(filtered_apps) == len(self._all_apps)
|
||||
should_resize = operator.length_hint(filtered_apps_iter) == len(self._all_apps)
|
||||
|
||||
self._arranger_handler = idle_add(
|
||||
lambda apps_iter: self.add_next_application(apps_iter)
|
||||
or self.handle_arrange_complete(should_resize, query),
|
||||
filtered_apps,
|
||||
filtered_apps_iter,
|
||||
pin=True,
|
||||
)
|
||||
|
||||
def show_scrolled_window(self):
|
||||
if self.scrolled_window is None:
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
min_content_size=(-1, -1),
|
||||
h_scrollbar_policy="never",
|
||||
child=self.viewport,
|
||||
v_expand=True,
|
||||
)
|
||||
self.launcher_box.add(self.scrolled_window)
|
||||
self.scrolled_window.show_all()
|
||||
self.get_style_context().add_class("applauncher")
|
||||
|
||||
def destroy_scrolled_window(self):
|
||||
if hasattr(self, "scrolled_window") and self.scrolled_window:
|
||||
# self.scrolled_window.destroy()
|
||||
self.launcher_box.remove(self.scrolled_window)
|
||||
self.scrolled_window = None
|
||||
|
||||
self.get_style_context().remove_class("applauncher")
|
||||
|
||||
def handle_arrange_complete(self, should_resize, query):
|
||||
if should_resize:
|
||||
self.resize_viewport()
|
||||
@@ -128,12 +163,6 @@ class AppLauncher(Box):
|
||||
self.viewport.add(self.bake_application_slot(app))
|
||||
return True
|
||||
|
||||
def resize_viewport(self):
|
||||
self.scrolled_window.set_min_content_width(
|
||||
self.viewport.get_allocation().width # type: ignore
|
||||
)
|
||||
return False
|
||||
|
||||
def bake_application_slot(self, app: DesktopApp, **kwargs) -> Button:
|
||||
button = Button(
|
||||
name="launcher-app",
|
||||
@@ -160,13 +189,12 @@ class AppLauncher(Box):
|
||||
return button
|
||||
|
||||
def update_selection(self, new_index: int):
|
||||
# Unselect current
|
||||
if self.selected_index != -1 and self.selected_index < len(
|
||||
self.viewport.get_children()
|
||||
):
|
||||
current_button = self.viewport.get_children()[self.selected_index]
|
||||
current_button.get_style_context().remove_class("selected")
|
||||
# Select new
|
||||
|
||||
if new_index != -1 and new_index < len(self.viewport.get_children()):
|
||||
new_button = self.viewport.get_children()[new_index]
|
||||
new_button.get_style_context().add_class("selected")
|
||||
@@ -180,41 +208,49 @@ class AppLauncher(Box):
|
||||
adj = self.scrolled_window.get_vadjustment()
|
||||
alloc = button.get_allocation()
|
||||
if alloc.height == 0:
|
||||
return False # Retry if allocation isn't ready
|
||||
return False
|
||||
|
||||
y = alloc.y
|
||||
height = alloc.height
|
||||
page_size = adj.get_page_size()
|
||||
current_value = adj.get_value()
|
||||
|
||||
# Calculate visible boundaries
|
||||
visible_top = current_value
|
||||
visible_bottom = current_value + page_size
|
||||
|
||||
if y < visible_top:
|
||||
# Item above viewport - align to top
|
||||
adj.set_value(y)
|
||||
elif y + height > visible_bottom:
|
||||
# Item below viewport - align to bottom
|
||||
new_value = y + height - page_size
|
||||
adj.set_value(new_value)
|
||||
# No action if already fully visible
|
||||
return False
|
||||
|
||||
GLib.idle_add(scroll)
|
||||
|
||||
def on_search_entry_activate(self, text):
|
||||
commands = self.config.get("commands", {})
|
||||
if text.startswith(":i "): # Detecting ':i' search
|
||||
search_query = text[3:].strip() # Extract query after ':i '
|
||||
if search_query:
|
||||
url = f"https://www.google.com/search?q={search_query}"
|
||||
webbrowser.open(url)
|
||||
self.close_launcher()
|
||||
return
|
||||
|
||||
if text.startswith("="):
|
||||
# If in calculator mode and no history item is selected, evaluate new expression.
|
||||
if self.selected_index == -1:
|
||||
self.evaluate_calculator_expression(text)
|
||||
return
|
||||
|
||||
if text in commands:
|
||||
command = commands[text]
|
||||
eval(command, {"launcher": self.launcher}) # Dynamically execute command
|
||||
exec(command, {"launcher": self.launcher})
|
||||
else:
|
||||
children = self.viewport.get_children()
|
||||
if children:
|
||||
# Only activate if we have selection or non-empty query
|
||||
if text.strip() == "" and self.selected_index == -1:
|
||||
return # Prevent accidental activation when empty
|
||||
return
|
||||
selected_index = self.selected_index if self.selected_index != -1 else 0
|
||||
if 0 <= selected_index < len(children):
|
||||
children[selected_index].clicked()
|
||||
@@ -236,10 +272,81 @@ class AppLauncher(Box):
|
||||
children = self.viewport.get_children()
|
||||
if not children:
|
||||
return
|
||||
# Allow starting selection from nothing when empty
|
||||
if self.selected_index == -1 and delta == 1:
|
||||
new_index = 0
|
||||
else:
|
||||
new_index = self.selected_index + delta
|
||||
new_index = max(0, min(new_index, len(children) - 1))
|
||||
self.update_selection(new_index)
|
||||
|
||||
def save_calc_history(self):
|
||||
with open(self.calc_history_path, "w") as f:
|
||||
json.dump(self.calc_history, f)
|
||||
|
||||
def evaluate_calculator_expression(self, text: str):
|
||||
# Remove the '=' prefix and extra spaces
|
||||
expr = text.lstrip("=").strip()
|
||||
if not expr:
|
||||
return
|
||||
# Replace operators: '^' -> '**', and '×' -> '*'
|
||||
expr = expr.replace("^", "**").replace("×", "*")
|
||||
# Replace factorial: e.g. 5! -> math.factorial(5)
|
||||
expr = re.sub(r"(\d+)!", r"math.factorial(\1)", expr)
|
||||
# Replace brackets: allow [] and {} as ()
|
||||
for old, new in [("[", "("), ("]", ")"), ("{", "("), ("}", ")")]:
|
||||
expr = expr.replace(old, new)
|
||||
try:
|
||||
result = eval(expr, {"__builtins__": None, "math": math})
|
||||
except Exception as e:
|
||||
result = f"Error: {e}"
|
||||
# Prepend to history (newest first)
|
||||
self.calc_history.insert(0, f"{text} => {result}")
|
||||
self.save_calc_history()
|
||||
self.update_calculator_viewport()
|
||||
|
||||
def update_calculator_viewport(self):
|
||||
self.viewport.children = []
|
||||
for item in self.calc_history:
|
||||
btn = self.create_calc_history_button(item)
|
||||
self.viewport.add(btn)
|
||||
# Remove resetting selected_index unconditionally so that a highlighted result isn't lost.
|
||||
# Optionally, only reset if the input is not more than "=".
|
||||
# if self.search_entry.get_text().strip() != "=":
|
||||
# self.selected_index = -1
|
||||
|
||||
def create_calc_history_button(self, text: str) -> Button:
|
||||
btn = Button(
|
||||
name="app-slot-button", # reuse existing CSS styling
|
||||
child=Box(
|
||||
name="calc-slot-box",
|
||||
orientation="h",
|
||||
spacing=10,
|
||||
children=[
|
||||
Label(
|
||||
name="calc-label",
|
||||
label=text,
|
||||
ellipsization="end",
|
||||
v_align="center",
|
||||
h_align="center",
|
||||
),
|
||||
],
|
||||
),
|
||||
tooltip_text=text,
|
||||
on_clicked=lambda *_: self.copy_text_to_clipboard(text),
|
||||
)
|
||||
return btn
|
||||
|
||||
def copy_text_to_clipboard(self, text: str):
|
||||
# Split the text on "=>" and copy only the result part if available
|
||||
parts = text.split("=>", 1)
|
||||
copy_text = parts[1].strip() if len(parts) > 1 else text
|
||||
try:
|
||||
subprocess.run(["wl-copy"], input=copy_text.encode(), check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Clipboard copy failed: {e}")
|
||||
|
||||
def delete_selected_calc_history(self):
|
||||
if self.selected_index != -1 and self.selected_index < len(self.calc_history):
|
||||
del self.calc_history[self.selected_index]
|
||||
self.save_calc_history()
|
||||
self.update_calculator_viewport()
|
||||
@@ -5,8 +5,7 @@ from fabric.widgets.button import Button
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from fabric.bluetooth import BluetoothClient, BluetoothDevice
|
||||
from snippets import MaterialIcon
|
||||
# from .buttons import
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class BluetoothDeviceSlot(CenterBox):
|
||||
@@ -18,7 +17,9 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
)
|
||||
|
||||
self.connection_icon = MaterialIcon("bluetooth_disabled", 12)
|
||||
self.connection_label = Label(
|
||||
name="bluetooth-connection", markup=icons.bluetooth_disconnected
|
||||
)
|
||||
self.connect_button = Button(
|
||||
name="bluetooth-connect",
|
||||
label="Connect",
|
||||
@@ -29,9 +30,9 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
Box(
|
||||
spacing=8,
|
||||
children=[
|
||||
Image(icon_name=device.icon_name + "-symbolic", size=32),
|
||||
Image(icon_name=f"{device.icon_name}-symbolic", size=32),
|
||||
Label(label=device.name),
|
||||
self.connection_icon,
|
||||
self.connection_label,
|
||||
],
|
||||
)
|
||||
]
|
||||
@@ -40,10 +41,11 @@ class BluetoothDeviceSlot(CenterBox):
|
||||
self.device.emit("changed") # to update display status
|
||||
|
||||
def on_changed(self, *_):
|
||||
self.connection_icon.set_label(
|
||||
"bluetooth" if self.device.connected else "bluetooth_disabled"
|
||||
self.connection_label.set_markup(
|
||||
icons.bluetooth_connected
|
||||
if self.device.connected
|
||||
else icons.bluetooth_disconnected
|
||||
)
|
||||
|
||||
if self.device.connecting:
|
||||
self.connect_button.set_label(
|
||||
"Connecting..." if not self.device.connecting else "Disconnecting..."
|
||||
@@ -63,38 +65,37 @@ class BluetoothConnections(Box):
|
||||
orientation="vertical",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self.launcher = kwargs["launcher"]
|
||||
|
||||
self.bluetooth_button = self.launcher.dashboard.bluetooth_button
|
||||
self.bt_status_text = self.bluetooth_button.bluetooth_status_text
|
||||
self.bt_status_button = self.bluetooth_button.bluetooth_status_button
|
||||
self.bt_icon = self.bluetooth_button.bluetooth_icon
|
||||
self.bt_label = self.bluetooth_button.bluetooth_label
|
||||
self.bt_menu_button = self.bluetooth_button.bluetooth_menu_button
|
||||
self.bt_menu_label = self.bluetooth_button.bluetooth_menu_label
|
||||
self.buttons = self.launcher.dashboard.bluetooth_button
|
||||
self.bt_status_text = self.buttons.bluetooth_status_text
|
||||
self.bt_status_button = self.buttons.bluetooth_status_button
|
||||
self.bt_icon = self.buttons.bluetooth_icon
|
||||
self.bt_label = self.buttons.bluetooth_label
|
||||
self.bt_menu_button = self.buttons.bluetooth_menu_button
|
||||
self.bt_menu_label = self.buttons.bluetooth_menu_label
|
||||
|
||||
self.client = BluetoothClient(on_device_added=self.on_device_added)
|
||||
self.toggle_icon = MaterialIcon("bluetooth_disabled")
|
||||
self.scan_icon = MaterialIcon("stop")
|
||||
|
||||
self.scan_button = Button(
|
||||
name="bluetooth-scan",
|
||||
child=self.scan_icon,
|
||||
tooltip_text="scan",
|
||||
child=Label(name="bluetooth-scan-icon", markup=icons.scan_stop),
|
||||
on_clicked=lambda *_: self.client.toggle_scan(),
|
||||
)
|
||||
self.toggle_button = Button(
|
||||
name="bluetooth-toggle",
|
||||
tooltip_text="bluetooth-toggle",
|
||||
child=self.toggle_icon,
|
||||
child=Label(name="bluetooth-toggle-icon", markup=icons.bluetooth_off),
|
||||
on_clicked=lambda *_: self.client.toggle_power(),
|
||||
)
|
||||
|
||||
self.client.connect("notify::enabled", lambda *_: self.status_label())
|
||||
self.client.connect(
|
||||
"notify::enabled",
|
||||
lambda *_: self.status_label(),
|
||||
)
|
||||
self.client.connect(
|
||||
"notify::scanning",
|
||||
lambda *_: self.scan_icon.set_label(
|
||||
"stop" if self.client.scanning else "bluetooth_searching"
|
||||
lambda *_: self.scan_button.get_child().set_markup(
|
||||
icons.scan_stop if self.client.scanning else icons.scan
|
||||
),
|
||||
)
|
||||
|
||||
@@ -111,14 +112,16 @@ class BluetoothConnections(Box):
|
||||
Label(name="bluetooth-section", label="Paired"),
|
||||
ScrolledWindow(
|
||||
name="bluetooth-paired",
|
||||
v_expand=True,
|
||||
min_content_size=(-1, -1),
|
||||
child=self.paired_box,
|
||||
v_expand=True,
|
||||
),
|
||||
Label(name="bluetooth-section", label="Available"),
|
||||
ScrolledWindow(
|
||||
name="bluetooth-paired",
|
||||
v_expand=True,
|
||||
name="bluetooth-available",
|
||||
min_content_size=(-1, -1),
|
||||
child=self.available_box,
|
||||
v_expand=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -130,7 +133,7 @@ class BluetoothConnections(Box):
|
||||
def status_label(self):
|
||||
print(self.client.enabled)
|
||||
if self.client.enabled:
|
||||
self.toggle_icon.set_label("bluetooth")
|
||||
self.toggle_button.get_child().set_markup(icons.bluetooth)
|
||||
self.bt_status_text.set_label("Enabled")
|
||||
for i in [
|
||||
self.bt_status_button,
|
||||
@@ -141,9 +144,9 @@ class BluetoothConnections(Box):
|
||||
self.bt_menu_label,
|
||||
]:
|
||||
i.remove_style_class("disabled")
|
||||
self.bt_icon.set_label("bluetooth")
|
||||
self.bt_icon.set_markup(icons.bluetooth)
|
||||
else:
|
||||
self.toggle_icon.set_label("bluetooth_disabled")
|
||||
self.toggle_button.get_child().set_markup(icons.bluetooth_off)
|
||||
self.bt_status_text.set_label("Disabled")
|
||||
for i in [
|
||||
self.bt_status_button,
|
||||
@@ -154,7 +157,7 @@ class BluetoothConnections(Box):
|
||||
self.bt_menu_label,
|
||||
]:
|
||||
i.add_style_class("disabled")
|
||||
self.bt_icon.set_label("bluetooth_disabled")
|
||||
self.bt_icon.set_markup(icons.bluetooth_off)
|
||||
|
||||
def on_device_added(self, client: BluetoothClient, address: str):
|
||||
if not (device := client.get_device(address)):
|
||||
@@ -162,5 +165,6 @@ class BluetoothConnections(Box):
|
||||
slot = BluetoothDeviceSlot(device)
|
||||
|
||||
if device.paired:
|
||||
return self.paired_box.add(slot)
|
||||
return self.available_box.add(slot)
|
||||
self.paired_box.add(slot)
|
||||
else:
|
||||
self.available_box.add(slot)
|
||||
@@ -1,396 +0,0 @@
|
||||
import subprocess
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.utils.helpers import exec_shell_command_async, exec_shell_command
|
||||
import gi
|
||||
from gi.repository import Gtk, Gdk, GLib # Added Gdk import
|
||||
from fabric.utils import get_relative_path
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
def add_hover_cursor(widget):
|
||||
# Add enter/leave events to change the cursor
|
||||
widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
||||
widget.connect(
|
||||
"enter-notify-event",
|
||||
lambda w, e: w.get_window().set_cursor(
|
||||
Gdk.Cursor.new_from_name(w.get_display(), "pointer")
|
||||
)
|
||||
if w.get_window()
|
||||
else None,
|
||||
)
|
||||
widget.connect(
|
||||
"leave-notify-event",
|
||||
lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None,
|
||||
)
|
||||
|
||||
|
||||
class NetworkButton(Box):
|
||||
def __init__(self):
|
||||
self.network_icon = MaterialIcon(
|
||||
"wifi",
|
||||
)
|
||||
self.network_icon.add_style_class("network-icon")
|
||||
self.network_label = Label(
|
||||
name="network-label",
|
||||
label="Wi-Fi",
|
||||
justification="left",
|
||||
)
|
||||
self.network_label_box = Box(children=[self.network_label, Box(h_expand=True)])
|
||||
self.network_ssid = Label(
|
||||
name="network-ssid",
|
||||
label="Hello_World!!",
|
||||
justification="left",
|
||||
)
|
||||
self.network_ssid_box = Box(children=[self.network_ssid, Box(h_expand=True)])
|
||||
self.network_text = Box(
|
||||
name="network-text",
|
||||
orientation="v",
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
children=[self.network_label_box, self.network_ssid_box],
|
||||
)
|
||||
self.network_status_box = Box(
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
spacing=10,
|
||||
children=[self.network_icon, self.network_text],
|
||||
)
|
||||
self.network_status_button = Button(
|
||||
name="network-status-button",
|
||||
h_expand=True,
|
||||
child=self.network_status_box,
|
||||
)
|
||||
add_hover_cursor(self.network_status_button) # <-- Added hover
|
||||
|
||||
self.network_menu_label = MaterialIcon("chevron_right")
|
||||
self.network_menu_button = Button(
|
||||
name="network-menu-button",
|
||||
child=self.network_menu_label,
|
||||
)
|
||||
add_hover_cursor(self.network_menu_button) # <-- Added hover
|
||||
|
||||
super().__init__(
|
||||
name="network-button",
|
||||
orientation="h",
|
||||
h_align="fill",
|
||||
v_align="fill",
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
spacing=0,
|
||||
children=[self.network_status_button, self.network_menu_button],
|
||||
)
|
||||
|
||||
|
||||
class BluetoothButton(Box):
|
||||
def __init__(self, launcher):
|
||||
super().__init__(
|
||||
name="bluetooth-button",
|
||||
orientation="h",
|
||||
h_align="fill",
|
||||
v_align="fill",
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
)
|
||||
self.launcher = launcher
|
||||
|
||||
self.bluetooth_icon = MaterialIcon("bluetooth")
|
||||
self.bluetooth_label = Label(
|
||||
name="bluetooth-label",
|
||||
label="Bluetooth",
|
||||
justification="left",
|
||||
)
|
||||
self.bluetooth_label_box = Box(
|
||||
children=[self.bluetooth_label, Box(h_expand=True)]
|
||||
)
|
||||
self.bluetooth_status_text = Label(
|
||||
name="bluetooth-status",
|
||||
label="Disabled",
|
||||
justification="left",
|
||||
)
|
||||
self.bluetooth_status_box = Box(
|
||||
children=[self.bluetooth_status_text, Box(h_expand=True)]
|
||||
)
|
||||
self.bluetooth_text = Box(
|
||||
orientation="v",
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
children=[self.bluetooth_label_box, self.bluetooth_status_box],
|
||||
)
|
||||
self.bluetooth_status_container = Box(
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
spacing=10,
|
||||
children=[self.bluetooth_icon, self.bluetooth_text],
|
||||
)
|
||||
self.bluetooth_status_button = Button(
|
||||
name="bluetooth-status-button",
|
||||
h_expand=True,
|
||||
child=self.bluetooth_status_container,
|
||||
on_clicked=lambda *_: self.launcher.bluetooth.client.toggle_power(),
|
||||
)
|
||||
add_hover_cursor(self.bluetooth_status_button) # <-- Added hover
|
||||
|
||||
self.bluetooth_menu_label = MaterialIcon("chevron_right")
|
||||
self.bluetooth_menu_button = Button(
|
||||
name="bluetooth-menu-button",
|
||||
on_clicked=lambda *_: self.launcher.open("bluetooth"),
|
||||
child=self.bluetooth_menu_label,
|
||||
)
|
||||
add_hover_cursor(self.bluetooth_menu_button) # <-- Added hover
|
||||
|
||||
self.add(self.bluetooth_status_button)
|
||||
self.add(self.bluetooth_menu_button)
|
||||
|
||||
|
||||
class NightModeButton(Button):
|
||||
def __init__(self):
|
||||
self.night_mode_icon = MaterialIcon("mode_night")
|
||||
self.night_mode_label = Label(
|
||||
name="night-mode-label",
|
||||
label="Night Mode",
|
||||
justification="left",
|
||||
)
|
||||
self.night_mode_label_box = Box(
|
||||
children=[self.night_mode_label, Box(h_expand=True)]
|
||||
)
|
||||
self.night_mode_status = Label(
|
||||
name="night-mode-status",
|
||||
label="Enabled",
|
||||
justification="left",
|
||||
)
|
||||
self.night_mode_status_box = Box(
|
||||
children=[self.night_mode_status, Box(h_expand=True)]
|
||||
)
|
||||
self.night_mode_text = Box(
|
||||
name="night-mode-text",
|
||||
orientation="v",
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
children=[self.night_mode_label_box, self.night_mode_status_box],
|
||||
)
|
||||
self.night_mode_box = Box(
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
spacing=10,
|
||||
children=[self.night_mode_icon, self.night_mode_text],
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
name="night-mode-button",
|
||||
h_expand=True,
|
||||
child=self.night_mode_box,
|
||||
on_clicked=self.toggle_hyprsunset,
|
||||
)
|
||||
add_hover_cursor(self) # <-- Added hover
|
||||
|
||||
self.widgets = [
|
||||
self,
|
||||
self.night_mode_label,
|
||||
self.night_mode_status,
|
||||
self.night_mode_icon,
|
||||
]
|
||||
self.check_hyprsunset()
|
||||
|
||||
def toggle_hyprsunset(self, *args):
|
||||
"""
|
||||
Toggle the 'hyprsunset' process:
|
||||
- If running, kill it and mark as 'Disabled'.
|
||||
- If not running, start it and mark as 'Enabled'.
|
||||
"""
|
||||
try:
|
||||
subprocess.check_output(["pgrep", "hyprsunset"])
|
||||
exec_shell_command_async("pkill hyprsunset")
|
||||
self.night_mode_status.set_label("Disabled")
|
||||
for widget in self.widgets:
|
||||
widget.add_style_class("disabled")
|
||||
except subprocess.CalledProcessError:
|
||||
exec_shell_command_async("hyprsunset -t 3500")
|
||||
self.night_mode_status.set_label("Enabled")
|
||||
for widget in self.widgets:
|
||||
widget.remove_style_class("disabled")
|
||||
|
||||
def check_hyprsunset(self, *args):
|
||||
"""
|
||||
Update the button state based on whether hyprsunset is running.
|
||||
"""
|
||||
try:
|
||||
subprocess.check_output(["pgrep", "hyprsunset"])
|
||||
self.night_mode_status.set_label("Enabled")
|
||||
for widget in self.widgets:
|
||||
widget.remove_style_class("disabled")
|
||||
except subprocess.CalledProcessError:
|
||||
self.night_mode_status.set_label("Disabled")
|
||||
for widget in self.widgets:
|
||||
widget.add_style_class("disabled")
|
||||
|
||||
|
||||
class CaffeineButton(Button):
|
||||
def __init__(self):
|
||||
self.caffeine_icon = MaterialIcon("coffee")
|
||||
self.caffeine_label = Label(
|
||||
name="caffeine-label",
|
||||
label="Caffeine",
|
||||
justification="left",
|
||||
)
|
||||
self.caffeine_label_box = Box(
|
||||
children=[self.caffeine_label, Box(h_expand=True)]
|
||||
)
|
||||
self.caffeine_status = Label(
|
||||
name="caffeine-status",
|
||||
label="Enabled",
|
||||
justification="left",
|
||||
)
|
||||
self.caffeine_status_box = Box(
|
||||
children=[self.caffeine_status, Box(h_expand=True)]
|
||||
)
|
||||
self.caffeine_text = Box(
|
||||
name="caffeine-text",
|
||||
orientation="v",
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
children=[self.caffeine_label_box, self.caffeine_status_box],
|
||||
)
|
||||
self.caffeine_box = Box(
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
spacing=10,
|
||||
children=[self.caffeine_icon, self.caffeine_text],
|
||||
)
|
||||
super().__init__(
|
||||
name="caffeine-button",
|
||||
h_expand=True,
|
||||
child=self.caffeine_box,
|
||||
on_clicked=self.toggle_wlinhibit,
|
||||
)
|
||||
add_hover_cursor(self) # <-- Added hover
|
||||
|
||||
self.widgets = [
|
||||
self,
|
||||
self.caffeine_label,
|
||||
self.caffeine_status,
|
||||
self.caffeine_icon,
|
||||
]
|
||||
self.check_wlinhibit()
|
||||
|
||||
def toggle_wlinhibit(self, *args):
|
||||
"""
|
||||
Toggle the 'wlinhibit' process:
|
||||
- If running, kill it and mark as 'Disabled' (add 'disabled' class).
|
||||
- If not running, start it and mark as 'Enabled' (remove 'disabled' class).
|
||||
"""
|
||||
try:
|
||||
subprocess.check_output(["pgrep", "wlinhibit"])
|
||||
exec_shell_command_async("pkill wlinhibit")
|
||||
self.caffeine_status.set_label("Disabled")
|
||||
for i in self.widgets:
|
||||
i.add_style_class("disabled")
|
||||
except subprocess.CalledProcessError:
|
||||
exec_shell_command_async("wlinhibit")
|
||||
self.caffeine_status.set_label("Enabled")
|
||||
for i in self.widgets:
|
||||
i.remove_style_class("disabled")
|
||||
|
||||
def check_wlinhibit(self, *args):
|
||||
try:
|
||||
subprocess.check_output(["pgrep", "wlinhibit"])
|
||||
self.caffeine_status.set_label("Enabled")
|
||||
for i in self.widgets:
|
||||
i.remove_style_class("disabled")
|
||||
except subprocess.CalledProcessError:
|
||||
self.caffeine_status.set_label("Disabled")
|
||||
for i in self.widgets:
|
||||
i.add_style_class("disabled")
|
||||
|
||||
|
||||
class DarkModeButton(Button):
|
||||
def __init__(self):
|
||||
self.dark_mode_icon = MaterialIcon("contrast")
|
||||
self.dark_mode_icon.add_style_class("iconss")
|
||||
self.dark_mode_label = Label(
|
||||
name="dark-mode-label", label="Dark Mode", justification="left"
|
||||
)
|
||||
|
||||
self.dark_mode_label_box = Box(
|
||||
children=[self.dark_mode_label, Box(h_expand=True)]
|
||||
)
|
||||
self.dark_mode_status = Label(
|
||||
name="dark-mode-status", label="Enabled", justification="left"
|
||||
)
|
||||
self.dark_mode_status_box = Box(
|
||||
children=[self.dark_mode_status, Box(h_expand=True)]
|
||||
)
|
||||
self.dark_mode_text = Box(
|
||||
name="dark-mode-text",
|
||||
orientation="v",
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
children=[self.dark_mode_label_box, self.dark_mode_status_box],
|
||||
)
|
||||
self.dark_mode_box = Box(
|
||||
h_align="start",
|
||||
v_align="center",
|
||||
spacing=10,
|
||||
children=[self.dark_mode_icon, self.dark_mode_text],
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
name="dark-mode-button",
|
||||
h_expand=True,
|
||||
child=self.dark_mode_box,
|
||||
on_clicked=self.toggle_darkmode,
|
||||
)
|
||||
add_hover_cursor(self)
|
||||
|
||||
self.widgets = [
|
||||
self,
|
||||
self.dark_mode_label,
|
||||
self.dark_mode_status,
|
||||
self.dark_mode_icon,
|
||||
]
|
||||
self.check_darkmode()
|
||||
|
||||
def toggle_darkmode(self, *_):
|
||||
current_state = self.check_darkmode()
|
||||
new_mode = "dark" if not current_state else "light"
|
||||
command = f"bash {get_relative_path('../../../config/scripts/dark-theme.sh')} --set {new_mode}"
|
||||
GLib.spawn_command_line_async(command)
|
||||
self.dark_mode_status.set_label("Enabled" if new_mode == "dark" else "Disabled")
|
||||
|
||||
def check_darkmode(self):
|
||||
result = exec_shell_command(
|
||||
"gsettings get org.gnome.desktop.interface color-scheme"
|
||||
)
|
||||
return result.strip().replace("'", "") == "prefer-dark"
|
||||
|
||||
|
||||
class Buttons(Gtk.Grid):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(name="buttons-grid")
|
||||
self.set_row_homogeneous(True)
|
||||
self.set_column_homogeneous(True)
|
||||
self.set_row_spacing(8)
|
||||
self.set_column_spacing(8)
|
||||
self.set_vexpand(True) # Prevent vertical expansion
|
||||
|
||||
self.launcher = kwargs["launcher"]
|
||||
|
||||
# Instantiate each button
|
||||
self.network_button = NetworkButton()
|
||||
self.bluetooth_button = BluetoothButton(self.launcher)
|
||||
self.night_mode_button = NightModeButton()
|
||||
self.caffeine_button = CaffeineButton()
|
||||
self.dark_mode_button = DarkModeButton()
|
||||
|
||||
# Attach buttons into the grid (one row, four columns)
|
||||
self.attach(self.network_button, 0, 0, 1, 1)
|
||||
self.attach(self.bluetooth_button, 1, 0, 1, 1)
|
||||
self.attach(self.dark_mode_button, 2, 0, 1, 1)
|
||||
self.attach(self.night_mode_button, 0, 1, 1, 1)
|
||||
self.attach(self.caffeine_button, 1, 1, 1, 1)
|
||||
|
||||
self.show_all()
|
||||
@@ -1,11 +1,11 @@
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
import gi
|
||||
from snippets import MaterialIcon
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class Calendar(Gtk.Box):
|
||||
@@ -27,7 +27,7 @@ class Calendar(Gtk.Box):
|
||||
|
||||
self.prev_month_button = Gtk.Button(
|
||||
name="prev-month-button",
|
||||
child=MaterialIcon("chevron_left"),
|
||||
child=Label(name="month-button-label", markup=icons.chevron_left)
|
||||
)
|
||||
self.prev_month_button.connect("clicked", self.on_prev_month_clicked)
|
||||
self.header.pack_start(self.prev_month_button, False, False, 0)
|
||||
@@ -37,7 +37,7 @@ class Calendar(Gtk.Box):
|
||||
|
||||
self.next_month_button = Gtk.Button(
|
||||
name="next-month-button",
|
||||
child=MaterialIcon("chevron_right"),
|
||||
child=Label(name="month-button-label", markup=icons.chevron_right)
|
||||
)
|
||||
self.next_month_button.connect("clicked", self.on_next_month_clicked)
|
||||
self.header.pack_start(self.next_month_button, False, False, 0)
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import os
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from fabric import Fabricator
|
||||
from fabric.utils import remove_handler
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.entry import Entry
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import GLib
|
||||
from snippets import MaterialIcon
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class Cliphist(Box):
|
||||
@@ -25,30 +23,30 @@ class Cliphist(Box):
|
||||
self._arranger_handler = 0
|
||||
self.cliphist_manager = CliphistManager(self)
|
||||
self.viewport = None
|
||||
self.launcher = kwargs["launcher"]
|
||||
|
||||
self.search_entry = Entry(
|
||||
name="search-entry",
|
||||
h_expand=True,
|
||||
notify_text=lambda entry, *_: self.handle_search_input(entry.get_text()),
|
||||
on_activate=lambda entry, *_: self.handle_search_input(entry.get_text()),
|
||||
)
|
||||
|
||||
self.toggle_button = Button(
|
||||
child=MaterialIcon("delete_forever"),
|
||||
name="clear-launcher-button",
|
||||
on_clicked=self.on_button_clicked,
|
||||
on_activate=lambda entry, *_: self.handle_search(entry.get_text()),
|
||||
)
|
||||
|
||||
self.header_box = Box(
|
||||
name="header-box",
|
||||
spacing=10,
|
||||
orientation="h",
|
||||
children=[self.search_entry, self.toggle_button],
|
||||
children=[
|
||||
self.search_entry,
|
||||
Button(
|
||||
name="clear-button",
|
||||
child=Label(name="cliphist-clear-button", markup=icons.clear),
|
||||
tooltip_text="Clear Clipboard History",
|
||||
on_clicked=self.handle_clear_history,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
self.launcher_box = Box(
|
||||
name="cliphist-history",
|
||||
name="cliphist-launcher-box",
|
||||
spacing=10,
|
||||
orientation="v",
|
||||
h_expand=True,
|
||||
@@ -57,32 +55,31 @@ class Cliphist(Box):
|
||||
|
||||
self.add(self.launcher_box)
|
||||
|
||||
def on_button_clicked(self, *_):
|
||||
GLib.spawn_command_line_async("cliphist wipe")
|
||||
self.cliphist_manager.update_cliphist_history()
|
||||
|
||||
def open_launcher(self):
|
||||
if not self.viewport:
|
||||
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
min_content_size=(-1, -1),
|
||||
h_scrollbar_policy="never",
|
||||
# v_scrollbar_policy="never",
|
||||
child=self.viewport,
|
||||
)
|
||||
self.launcher_box.add(self.scrolled_window)
|
||||
|
||||
self.viewport.children = []
|
||||
self.cliphist_manager.arrange_viewport()
|
||||
self.cliphist_manager.update_cliphist_history()
|
||||
|
||||
self.viewport.show()
|
||||
self.search_entry.grab_focus()
|
||||
|
||||
def close_launcher(self):
|
||||
self.launcher.close()
|
||||
def handle_search(self, text: str):
|
||||
self.cliphist_manager.arrange_viewport(query=text)
|
||||
|
||||
def handle_search_input(self, text: str):
|
||||
self.cliphist_manager.arrange_viewport(text)
|
||||
def handle_clear_history(self, *_):
|
||||
GLib.spawn_command_line_async("cliphist wipe")
|
||||
self.cliphist_manager.update_cliphist_history()
|
||||
|
||||
|
||||
class CliphistManager:
|
||||
@@ -121,7 +118,7 @@ class CliphistManager:
|
||||
["cliphist", "decode", clip_id], capture_output=True, check=True
|
||||
)
|
||||
subprocess.run(["wl-copy"], input=decode_result.stdout, check=True)
|
||||
self.launcher.close_launcher()
|
||||
self.launcher.open_launcher()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error copying clip {clip_id}: {e}")
|
||||
|
||||
@@ -133,22 +130,25 @@ class CliphistManager:
|
||||
decode_result = subprocess.run(
|
||||
["cliphist", "decode", clip_id], capture_output=True, check=True
|
||||
)
|
||||
|
||||
with open(output_file, "wb") as f:
|
||||
f.write(decode_result.stdout)
|
||||
|
||||
return output_file
|
||||
except (subprocess.CalledProcessError, IOError) as e:
|
||||
print(f"Error saving image file for clip {clip_id}: {e}")
|
||||
return ""
|
||||
|
||||
def update_cliphist_history(self):
|
||||
self.cliphist_history = self.get_clip_history()
|
||||
self.arrange_viewport()
|
||||
|
||||
def query_clips(self, query: str = "") -> List[dict]:
|
||||
history = self.get_clip_history()
|
||||
|
||||
if not query.strip():
|
||||
return history
|
||||
|
||||
return [item for item in history if query.lower() in item["content"].lower()]
|
||||
return self.cliphist_history
|
||||
return [
|
||||
clip
|
||||
for clip in self.cliphist_history
|
||||
if query.lower() in clip["content"].lower()
|
||||
]
|
||||
|
||||
def bake_clip_slot(self, item: dict) -> Button:
|
||||
if "[[ binary data" in item["content"].lower():
|
||||
@@ -163,19 +163,17 @@ class CliphistManager:
|
||||
name="clip-img-item",
|
||||
on_clicked=lambda _, clip_id=item["id"]: self.copy_clip(clip_id),
|
||||
)
|
||||
|
||||
button.set_style(
|
||||
f"background-image: url('{output_file}'); background-size: cover; background-position: center;"
|
||||
)
|
||||
return button
|
||||
|
||||
return None
|
||||
|
||||
def _create_text_button(self, item: dict) -> Button:
|
||||
return Button(
|
||||
child=Label(
|
||||
label=item["content"][:55]
|
||||
+ ("..." if len(item["content"]) > 55 else ""),
|
||||
label=item["content"][:35]
|
||||
+ ("..." if len(item["content"]) > 35 else ""),
|
||||
h_expand=True,
|
||||
v_align="center",
|
||||
h_align="start",
|
||||
@@ -184,18 +182,10 @@ class CliphistManager:
|
||||
name="clip-item",
|
||||
)
|
||||
|
||||
def update_cliphist_history(self):
|
||||
self.cliphist_history = self.get_clip_history()
|
||||
if self.launcher.viewport:
|
||||
GLib.idle_add(self.arrange_viewport)
|
||||
|
||||
def arrange_viewport(self, query: str = ""):
|
||||
if not self.launcher.viewport:
|
||||
return
|
||||
|
||||
if self.launcher._arranger_handler:
|
||||
remove_handler(self.launcher._arranger_handler)
|
||||
|
||||
self.launcher.viewport.children = []
|
||||
filtered_clips = self.query_clips(query)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from gi.repository import Gtk, Gdk, GLib # Added Gdk import
|
||||
from fabric.utils import get_relative_path
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from snippets import MaterialIcon
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
def add_hover_cursor(widget):
|
||||
@@ -31,8 +31,9 @@ def add_hover_cursor(widget):
|
||||
|
||||
class NetworkButton(Box):
|
||||
def __init__(self):
|
||||
self.network_icon = MaterialIcon(
|
||||
"wifi",
|
||||
self.network_icon = Label(
|
||||
name="network-icon",
|
||||
markup=icons.wifi,
|
||||
)
|
||||
self.network_label = Label(
|
||||
name="network-label",
|
||||
@@ -66,7 +67,10 @@ class NetworkButton(Box):
|
||||
)
|
||||
add_hover_cursor(self.network_status_button) # <-- Added hover
|
||||
|
||||
self.network_menu_label = MaterialIcon("chevron_right")
|
||||
self.network_menu_label = Label(
|
||||
name="network-menu-label",
|
||||
markup=icons.chevron_right,
|
||||
)
|
||||
self.network_menu_button = Button(
|
||||
name="network-menu-button",
|
||||
child=self.network_menu_label,
|
||||
@@ -97,7 +101,10 @@ class BluetoothButton(Box):
|
||||
)
|
||||
self.launcher = launcher
|
||||
|
||||
self.bluetooth_icon = MaterialIcon("bluetooth")
|
||||
self.bluetooth_icon = Label(
|
||||
name="bluetooth-icon",
|
||||
markup=icons.bluetooth,
|
||||
)
|
||||
self.bluetooth_label = Label(
|
||||
name="bluetooth-label",
|
||||
label="Bluetooth",
|
||||
@@ -133,8 +140,10 @@ class BluetoothButton(Box):
|
||||
on_clicked=lambda *_: self.launcher.bluetooth.client.toggle_power(),
|
||||
)
|
||||
add_hover_cursor(self.bluetooth_status_button) # <-- Added hover
|
||||
|
||||
self.bluetooth_menu_label = MaterialIcon("chevron_right")
|
||||
self.bluetooth_menu_label = Label(
|
||||
name="bluetooth-menu-label",
|
||||
markup=icons.chevron_right,
|
||||
)
|
||||
self.bluetooth_menu_button = Button(
|
||||
name="bluetooth-menu-button",
|
||||
on_clicked=lambda *_: self.launcher.open("bluetooth"),
|
||||
@@ -148,7 +157,10 @@ class BluetoothButton(Box):
|
||||
|
||||
class NightModeButton(Button):
|
||||
def __init__(self):
|
||||
self.night_mode_icon = MaterialIcon("mode_night")
|
||||
self.night_mode_icon = Label(
|
||||
name="night-mode-icon",
|
||||
markup=icons.night,
|
||||
)
|
||||
self.night_mode_label = Label(
|
||||
name="night-mode-label",
|
||||
label="Night Mode",
|
||||
@@ -230,7 +242,10 @@ class NightModeButton(Button):
|
||||
|
||||
class CaffeineButton(Button):
|
||||
def __init__(self):
|
||||
self.caffeine_icon = MaterialIcon("coffee")
|
||||
self.caffeine_icon = Label(
|
||||
name="caffeine-icon",
|
||||
markup=icons.coffee,
|
||||
)
|
||||
self.caffeine_label = Label(
|
||||
name="caffeine-label",
|
||||
label="Caffeine",
|
||||
@@ -308,7 +323,10 @@ class CaffeineButton(Button):
|
||||
|
||||
class DarkModeButton(Button):
|
||||
def __init__(self):
|
||||
self.dark_mode_icon = MaterialIcon("contrast")
|
||||
self.dark_mode_icon = Label(
|
||||
name="dark-mode-icon",
|
||||
markup=icons.darkmode,
|
||||
)
|
||||
self.dark_mode_label = Label(
|
||||
name="dark-mode-label", label="Dark Mode", justification="left"
|
||||
)
|
||||
@@ -384,96 +402,6 @@ class DarkModeButton(Button):
|
||||
return is_dark_mode
|
||||
|
||||
|
||||
#
|
||||
# class PowerProfileButton(Button):
|
||||
# def __init__(self):
|
||||
# self.power_profile_icon = MaterialIcon("local_fire_department")
|
||||
# self.power_profile_label = Label(
|
||||
# name="power-profile-label", label="Power Profile", justification="left"
|
||||
# )
|
||||
# self.power_profile_label_box = Box(
|
||||
# children=[self.power_profile_label, Box(h_expand=True)]
|
||||
# )
|
||||
# self.power_profile_status = Label(
|
||||
# name="power-profile-status", label="Loading...", justification="left"
|
||||
# )
|
||||
# self.power_profile_status_box = Box(
|
||||
# children=[self.power_profile_status, Box(h_expand=True)]
|
||||
# )
|
||||
# self.power_profile_text = Box(
|
||||
# name="power-profile-text",
|
||||
# orientation="v",
|
||||
# h_align="start",
|
||||
# v_align="center",
|
||||
# children=[self.power_profile_label_box, self.power_profile_status_box],
|
||||
# )
|
||||
# self.power_profile_box = Box(
|
||||
# h_align="start",
|
||||
# v_align="center",
|
||||
# spacing=10,
|
||||
# children=[self.power_profile_icon, self.power_profile_text],
|
||||
# )
|
||||
#
|
||||
# super().__init__(
|
||||
# name="power-profile-button",
|
||||
# h_expand=True,
|
||||
# child=self.power_profile_box,
|
||||
# on_clicked=self.toggle_power_profile,
|
||||
# )
|
||||
# add_hover_cursor(self)
|
||||
#
|
||||
# self.widgets = [
|
||||
# self,
|
||||
# self.power_profile_label,
|
||||
# self.power_profile_status,
|
||||
# self.power_profile_icon,
|
||||
# ]
|
||||
#
|
||||
# # **Delay the profile check to allow system services to start**
|
||||
# GLib.timeout_add(1000, self.check_power_profile)
|
||||
#
|
||||
# def toggle_power_profile(self, *_):
|
||||
# current_profile = self.check_power_profile()
|
||||
# if current_profile == "balanced":
|
||||
# self.set_power_profile("performance")
|
||||
# elif current_profile == "performance":
|
||||
# self.set_power_profile("power-saver")
|
||||
# else:
|
||||
# self.set_power_profile("balanced")
|
||||
#
|
||||
# def check_power_profile(self):
|
||||
# try:
|
||||
# result = exec_shell_command("powerprofilesctl get")
|
||||
# if isinstance(result, str):
|
||||
# current_profile = result.strip().lower()
|
||||
# self.power_profile_status.set_label(current_profile.capitalize())
|
||||
# self.update_button_style(current_profile)
|
||||
# return current_profile
|
||||
# except Exception as e:
|
||||
# print(f"Error checking power profile: {e}")
|
||||
# self.power_profile_status.set_label("Balanced") # Fallback
|
||||
# return "balanced"
|
||||
#
|
||||
# def set_power_profile(self, profile):
|
||||
# exec_shell_command_async(f"powerprofilesctl set {profile}")
|
||||
# self.power_profile_status.set_label(profile.capitalize())
|
||||
# self.update_button_style(profile)
|
||||
#
|
||||
# def update_button_style(self, profile):
|
||||
# if profile == "balanced":
|
||||
# self.power_profile_icon.set_label("balance")
|
||||
# self.add_style_class("balanced-profile")
|
||||
# self.power_profile_label.add_style_class("disabled")
|
||||
# else:
|
||||
# self.remove_style_class("balanced-profile")
|
||||
#
|
||||
# if profile == "performance":
|
||||
# self.power_profile_icon.set_label("local_fire_department")
|
||||
# elif profile == "power-saver":
|
||||
# self.power_profile_icon.set_label("data_saver_on")
|
||||
#
|
||||
|
||||
|
||||
class Dashboard(Gtk.Grid):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(name="buttons-grid")
|
||||
@@ -491,7 +419,6 @@ class Dashboard(Gtk.Grid):
|
||||
self.night_mode_button = NightModeButton()
|
||||
self.caffeine_button = CaffeineButton()
|
||||
self.dark_mode_button = DarkModeButton()
|
||||
# self.power_profile_button = PowerProfileButton()
|
||||
|
||||
# Attach buttons into the grid (one row, four columns)
|
||||
self.attach(self.network_button, 0, 0, 1, 1)
|
||||
@@ -499,6 +426,5 @@ class Dashboard(Gtk.Grid):
|
||||
self.attach(self.dark_mode_button, 2, 0, 1, 1)
|
||||
self.attach(self.night_mode_button, 0, 1, 1, 1)
|
||||
self.attach(self.caffeine_button, 1, 1, 1, 1)
|
||||
# self.attach(self.power_profile_button, 2, 1, 1, 1)
|
||||
|
||||
self.show_all()
|
||||
@@ -1,14 +1,14 @@
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.button import Button
|
||||
from gi.repository import GLib
|
||||
|
||||
from snippets import MaterialIcon
|
||||
from fabric.utils.helpers import exec_shell_command_async
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class PowerMenu(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
name="powermenu",
|
||||
name="power-menu",
|
||||
orientation="h",
|
||||
spacing=10,
|
||||
v_align="center",
|
||||
@@ -22,32 +22,32 @@ class PowerMenu(Box):
|
||||
self.launcher = kwargs["launcher"]
|
||||
|
||||
self.btn_lock = Button(
|
||||
name="powermenu-button",
|
||||
child=MaterialIcon("lock", 45),
|
||||
name="power-menu-button",
|
||||
child=Label(name="button-label", markup=icons.lock),
|
||||
on_clicked=self.lock,
|
||||
)
|
||||
|
||||
self.btn_suspend = Button(
|
||||
name="powermenu-button",
|
||||
child=MaterialIcon("sleep", 45),
|
||||
name="power-menu-button",
|
||||
child=Label(name="button-label", markup=icons.suspend),
|
||||
on_clicked=self.suspend,
|
||||
)
|
||||
|
||||
self.btn_logout = Button(
|
||||
name="powermenu-button",
|
||||
child=MaterialIcon("logout", 45),
|
||||
name="power-menu-button",
|
||||
child=Label(name="button-label", markup=icons.logout),
|
||||
on_clicked=self.logout,
|
||||
)
|
||||
|
||||
self.btn_reboot = Button(
|
||||
name="powermenu-button",
|
||||
child=MaterialIcon("restart_alt", 45),
|
||||
name="power-menu-button",
|
||||
child=Label(name="button-label", markup=icons.reboot),
|
||||
on_clicked=self.reboot,
|
||||
)
|
||||
|
||||
self.btn_shutdown = Button(
|
||||
name="powermenu-button",
|
||||
child=MaterialIcon("power_settings_new", 45),
|
||||
name="power-menu-button",
|
||||
child=Label(name="button-label", markup=icons.shutdown),
|
||||
on_clicked=self.poweroff,
|
||||
)
|
||||
|
||||
@@ -67,22 +67,23 @@ class PowerMenu(Box):
|
||||
def close_menu(self):
|
||||
self.launcher.close()
|
||||
|
||||
def lock(self, *_):
|
||||
GLib.spawn_command_line_async("hyprlock --immediate")
|
||||
def lock(self, *args):
|
||||
exec_shell_command_async("hyprlock --immediate")
|
||||
self.close_menu()
|
||||
|
||||
def suspend(self, *_):
|
||||
GLib.spawn_command_line_async("systemctl suspend")
|
||||
def suspend(self, *args):
|
||||
exec_shell_command_async("systemctl suspend")
|
||||
self.close_menu()
|
||||
|
||||
def logout(self, *_):
|
||||
GLib.spawn_command_line_async("hyprctl dispatch exit")
|
||||
def logout(self, *args):
|
||||
exec_shell_command_async("hyprctl dispatch exit")
|
||||
self.close_menu()
|
||||
|
||||
def reboot(self, *_):
|
||||
GLib.spawn_command_line_async("systemctl reboot")
|
||||
def reboot(self, *args):
|
||||
exec_shell_command_async("systemctl reboot")
|
||||
self.close_menu()
|
||||
|
||||
def poweroff(self, *_):
|
||||
GLib.spawn_command_line_async("systemctl poweroff")
|
||||
def poweroff(self, *args):
|
||||
exec_shell_command_async("systemctl poweroff")
|
||||
self.close_menu()
|
||||
|
||||
@@ -6,7 +6,7 @@ from fabric.widgets.button import Button
|
||||
from fabric.widgets.entry import Entry
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
CACHE_DIR = os.getenv(
|
||||
"XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache", "fabric")
|
||||
@@ -37,7 +37,7 @@ class Sh(Box):
|
||||
self.header_box = Box(
|
||||
spacing=10,
|
||||
orientation="h",
|
||||
children=[self.search_entry, MaterialIcon("terminal")],
|
||||
children=[self.search_entry],
|
||||
)
|
||||
|
||||
self.launcher_box = Box(
|
||||
@@ -53,9 +53,11 @@ class Sh(Box):
|
||||
if not self.viewport:
|
||||
self.viewport = Box(name="viewport", spacing=10, orientation="v")
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
h_scrollbar_policy="never",
|
||||
v_scrollbar_policy="never",
|
||||
# min_content_size=(-1, -1),
|
||||
child=self.viewport,
|
||||
)
|
||||
self.launcher_box.add(self.scrolled_window)
|
||||
|
||||
@@ -6,7 +6,7 @@ from fabric.widgets.button import Button
|
||||
from fabric.widgets.entry import Entry
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from snippets import MaterialIcon
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class TodoManager(Box):
|
||||
@@ -36,7 +36,7 @@ class TodoManager(Box):
|
||||
self.todo_entry,
|
||||
Button(
|
||||
name="add-button",
|
||||
child=MaterialIcon("add"),
|
||||
child=Label(name="todo-add-button", markup=icons.add),
|
||||
tooltip_text="Add Todo",
|
||||
on_clicked=lambda *_: self.handle_add_todo(
|
||||
self.todo_entry.get_text()
|
||||
@@ -59,8 +59,9 @@ class TodoManager(Box):
|
||||
if not self.viewport:
|
||||
self.viewport = Box(name="viewport", spacing=4, orientation="v")
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="todo-content",
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
min_content_size=(-1, -1),
|
||||
h_scrollbar_policy="never",
|
||||
child=self.viewport,
|
||||
)
|
||||
@@ -82,7 +83,7 @@ class TodoListManager:
|
||||
def __init__(self, launcher):
|
||||
self.launcher = launcher
|
||||
self.todos = []
|
||||
self.todo_file = os.path.expanduser("~/.local/share/fabric/todos.json")
|
||||
self.todo_file = os.path.expanduser("~/.local/share/modus/todos.json")
|
||||
self.load_todos()
|
||||
|
||||
def load_todos(self):
|
||||
@@ -130,9 +131,10 @@ class TodoListManager:
|
||||
)
|
||||
|
||||
checkbox = Button(
|
||||
name="todo-checkbox",
|
||||
child=MaterialIcon(
|
||||
"check_box" if todo["completed"] else "check_box_outline_blank"
|
||||
name="checkbox",
|
||||
child=Label(
|
||||
name="todo-checkbox",
|
||||
markup=icons.check_box if todo["completed"] else icons.box,
|
||||
),
|
||||
on_clicked=lambda _, todo_id=todo["id"]: self.toggle_todo(todo_id),
|
||||
)
|
||||
@@ -147,7 +149,7 @@ class TodoListManager:
|
||||
|
||||
delete_button = Button(
|
||||
name="delete-button",
|
||||
child=MaterialIcon("delete"),
|
||||
child=Label(name="todo-delete-button", markup=icons.trash),
|
||||
on_clicked=lambda _, todo_id=todo["id"]: self.delete_todo(todo_id),
|
||||
)
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ from fabric.widgets.image import Image
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from snippets import MaterialIcon
|
||||
from services import NetworkClient
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class WifiNetworkSlot(CenterBox):
|
||||
@@ -15,7 +15,7 @@ class WifiNetworkSlot(CenterBox):
|
||||
self.client = client
|
||||
self.connecting = False # Tracks if we are connecting/disconnecting
|
||||
|
||||
self.connection_icon = MaterialIcon("signal_wifi_off", 12)
|
||||
self.connection_icon = Label(name="wifi-connection", markup=icons.wifi_off)
|
||||
self.connect_button = Button(
|
||||
name="wifi-connect",
|
||||
label="Connect",
|
||||
@@ -68,9 +68,7 @@ class WifiNetworkSlot(CenterBox):
|
||||
self.connect_button.sensitive = True
|
||||
|
||||
if not self.connecting:
|
||||
self.connection_icon.set_label(
|
||||
"signal_wifi_4_bar" if connected else "signal_wifi_off"
|
||||
)
|
||||
self.connection_icon.set_markup(icons.wifi if connected else icons.wifi_off)
|
||||
self.connect_button.set_label("Disconnect" if connected else "Connect")
|
||||
|
||||
|
||||
@@ -84,8 +82,8 @@ class WifiManager(Box):
|
||||
)
|
||||
|
||||
self.client = NetworkClient()
|
||||
self.scan_icon = MaterialIcon("wifi_find")
|
||||
self.toggle_icon = MaterialIcon("signal_wifi_off")
|
||||
self.scan_icon = Label(name="wifi-scan-icon", markup=icons.scan)
|
||||
self.toggle_icon = Label(name="wifi-toggle-icon", markup=icons.wifi_off)
|
||||
|
||||
self.scan_button = Button(
|
||||
name="wifi-scan",
|
||||
@@ -113,8 +111,10 @@ class WifiManager(Box):
|
||||
end_children=self.toggle_button,
|
||||
),
|
||||
ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
name="wifi-available",
|
||||
v_expand=True,
|
||||
h_scrollbar_policy="never",
|
||||
min_content_size=(-1, -1),
|
||||
child=self.available_box,
|
||||
),
|
||||
]
|
||||
@@ -135,10 +135,8 @@ class WifiManager(Box):
|
||||
self.build_wifi_options()
|
||||
|
||||
def update_toggle_icon(self):
|
||||
self.toggle_icon.set_label(
|
||||
"signal_wifi_4_bar"
|
||||
if self.client.wifi_device.enabled
|
||||
else "signal_wifi_off"
|
||||
self.toggle_icon.set_markup(
|
||||
icons.wifi if self.client.wifi_device.enabled else icons.wifi_off
|
||||
)
|
||||
|
||||
def build_wifi_options(self):
|
||||
|
||||
@@ -2,7 +2,6 @@ from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.stack import Stack
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from modules.launcher.components import (
|
||||
AppLauncher,
|
||||
BluetoothConnections,
|
||||
|
||||
+1
-3
@@ -1,11 +1,9 @@
|
||||
import time
|
||||
from fabric.widgets.wayland import WaylandWindow as Window
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from gi.repository import GLib, GObject
|
||||
from gi.repository import GLib
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from services import brightness, audio
|
||||
from snippets import Animator, MaterialIcon
|
||||
|
||||
|
||||
def create_progress_bar(percentage, width=150):
|
||||
|
||||
Reference in new issue
Block a user