diff --git a/config/scripts/systemupdates.sh b/config/scripts/systemupdates.sh new file mode 100755 index 0000000..e8044eb --- /dev/null +++ b/config/scripts/systemupdates.sh @@ -0,0 +1,164 @@ +#!/bin/bash + +# Word of caution: changing this can break update module, be vigilant + +check_arch_updates() { + official_updates=0 + aur_updates=0 + flatpak_updates=0 + tooltip="" # Initialize tooltip variable + + # Get the number of official updates using 'checkupdates' + official_updates=$(checkupdates 2>/dev/null | wc -l) + + if command -v paru &>/dev/null; then + aur_helper="paru" + else + aur_helper="yay" + fi + + # Get the number of AUR updates using 'yay' + aur_updates=$($aur_helper -Qum 2>/dev/null | wc -l) + + # Calculate total updates + total_updates=$((official_updates + aur_updates + flatpak_updates)) + + # Build the tooltip for official updates + tooltip="󰣇 Official $official_updates\n󰮯 AUR $aur_updates" + + # Check if flatpak is installed + if command -v flatpak &>/dev/null; then + # Get the number of Flatpak updates if flatpak is installed + flatpak_updates=$(flatpak remote-ls --updates | wc -l) + + # Append Flatpak updates to the tooltip + tooltip="$tooltip\n Flatpak $flatpak_updates" + + # Recalculate total updates + total_updates=$((official_updates + aur_updates + flatpak_updates)) + fi + + # Output the result as a JSON object + echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}" +} + +check_ubuntu_updates() { + official_updates=0 + flatpak_updates=0 + + # Check if flatpak is installed and get Flatpak updates if it is + if command -v flatpak &>/dev/null; then + flatpak_updates=$(flatpak remote-ls --updates | wc -l) + # Always show Flatpak updates in the tooltip, even if there are none + tooltip="󰕈 Official $official_updates\n Flatpak $flatpak_updates" + else + # If flatpak is not installed, only show official updates + tooltip="󰕈 Official $official_updates" + fi + + # Get the number of official updates using 'apt-get' + official_updates=$(apt-get -s -o Debug::NoLocking=true upgrade | grep -c ^Inst) + + # Calculate total updates + total_updates=$((official_updates + flatpak_updates)) + + # Output the result as a JSON object + echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}" +} + +check_fedora_updates() { + official_updates=0 + flatpak_updates=0 + + # Get the number of official updates using 'dnf' + official_updates=$(dnf check-update -q | grep -v '^Loaded plugins' | grep -v '^No match for' | wc -l) + + # Calculate total updates + total_updates=$((official_updates + flatpak_updates)) + + # Check if flatpak is installed and get Flatpak updates if it is + if command -v flatpak &>/dev/null; then + flatpak_updates=$(flatpak remote-ls --updates | wc -l) + # Always show Flatpak updates in the tooltip, even if there are none + tooltip="󰣛 Official $official_updates\n Flatpak $flatpak_updates" + else + # If flatpak is not installed, only show official updates + tooltip="󰣛 Official $official_updates" + fi + + # Output the result as a JSON object + echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}" +} + +check_opensuse_updates() { + official_updates=0 + flatpak_updates=0 + + # Check if flatpak is installed and get Flatpak updates if it is + if command -v flatpak &>/dev/null; then + flatpak_updates=$(flatpak remote-ls --updates | wc -l) + fi + + # Get the number of official updates using 'dnf' + official_updates=$(zypper lu | wc -l) + + # Calculate total updates + total_updates=$((official_updates + flatpak_updates)) + + # Check if flatpak is installed and get Flatpak updates if it is + if command -v flatpak &>/dev/null; then + flatpak_updates=$(flatpak remote-ls --updates | wc -l) + # Always show Flatpak updates in the tooltip, even if there are none + tooltip=" Official $official_updates\n Flatpak $flatpak_updates" + else + # If flatpak is not installed, only show official updates + tooltip=" Official $official_updates" + fi + + # Output the result as a JSON object + echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}" +} + +update_arch() { + command=" + fastfetch + yay -Syu + read -n 1 -p 'Press any key to continue...' + " + kitty --title systemupdate sh -c "${command}" +} + +case "$1" in +-arch) + if [ -z "$2" ]; then # If second argument is null (not provided) + check_arch_updates + else + update_arch + fi + ;; +-ubuntu) + if [ -z "$2" ]; then # If second argument is null (not provided) + check_ubuntu_updates + else + update_ubuntu + fi + ;; +-fedora) + if [ -z "$2" ]; then # If second argument is null (not provided) + check_fedora_updates + else + update_fedora + fi + ;; +-suse) + if [ -z "$2" ]; then # If second argument is null (not provided) + check_opensuse_updates + else + update_arch + fi + ;; +*) + echo "Usage: $0 [-arch|-ubuntu|-fedora|-suse] [up (optional)]" + exit 1 + ;; +esac diff --git a/config/scripts/wayland-idle-inhibitor.py b/config/scripts/wayland-idle-inhibitor.py deleted file mode 100755 index c731f67..0000000 --- a/config/scripts/wayland-idle-inhibitor.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python - -import sys -from dataclasses import dataclass -from signal import SIGINT, SIGTERM, signal -from threading import Event -import setproctitle - -from pywayland.client.display import Display # type: ignore -from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import ( # type: ignore - ZwpIdleInhibitManagerV1, -) -from pywayland.protocol.wayland.wl_compositor import WlCompositor # type: ignore -from pywayland.protocol.wayland.wl_registry import WlRegistryProxy # type: ignore -from pywayland.protocol.wayland.wl_surface import WlSurface # type: ignore - - -@dataclass -class GlobalRegistry: - surface: WlSurface | None = None - inhibit_manager: ZwpIdleInhibitManagerV1 | None = None - - -def handle_registry_global( - wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int -) -> None: - global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry() - - if iface_name == "wl_compositor": - compositor = wl_registry.bind(id_num, WlCompositor, version) - global_registry.surface = compositor.create_surface() # type: ignore - elif iface_name == "zwp_idle_inhibit_manager_v1": - global_registry.inhibit_manager = wl_registry.bind( - id_num, ZwpIdleInhibitManagerV1, version - ) - - -def main() -> None: - done = Event() - signal(SIGINT, lambda _, __: done.set()) - signal(SIGTERM, lambda _, __: done.set()) - - global_registry = GlobalRegistry() - - display = Display() - display.connect() - - registry = display.get_registry() # type: ignore - registry.user_data = global_registry - registry.dispatcher["global"] = handle_registry_global - - def shutdown() -> None: - display.dispatch() - display.roundtrip() - display.disconnect() - - display.dispatch() - display.roundtrip() - - if global_registry.surface is None or global_registry.inhibit_manager is None: - print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.") - shutdown() - sys.exit(1) - - inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore - global_registry.surface - ) - - display.dispatch() - display.roundtrip() - - print("Inhibiting idle...") - done.wait() - print("Shutting down...") - - inhibitor.destroy() - - shutdown() - - -if __name__ == "__main__": - setproctitle.setproctitle("wayland-idle-inhibitor.py") - main() diff --git a/modules/bar/bar.py b/modules/bar/bar.py index 62b6132..dc91a6e 100644 --- a/modules/bar/bar.py +++ b/modules/bar/bar.py @@ -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") diff --git a/modules/bar/components/__init__.py b/modules/bar/components/__init__.py index 278c35f..116ed6d 100644 --- a/modules/bar/components/__init__.py +++ b/modules/bar/components/__init__.py @@ -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 * diff --git a/modules/bar/components/battery.py b/modules/bar/components/battery.py index f6ddcac..60440b6 100644 --- a/modules/bar/components/battery.py +++ b/modules/bar/components/battery.py @@ -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") diff --git a/modules/bar/components/bluetooth.py b/modules/bar/components/bluetooth.py deleted file mode 100644 index ffc4fec..0000000 --- a/modules/bar/components/bluetooth.py +++ /dev/null @@ -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) diff --git a/modules/bar/components/indicators.py b/modules/bar/components/indicators.py new file mode 100644 index 0000000..5bf8fe8 --- /dev/null +++ b/modules/bar/components/indicators.py @@ -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) diff --git a/modules/bar/components/microphone.py b/modules/bar/components/microphone.py deleted file mode 100644 index a89baf7..0000000 --- a/modules/bar/components/microphone.py +++ /dev/null @@ -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 diff --git a/modules/bar/components/network.py b/modules/bar/components/network.py deleted file mode 100644 index 3407134..0000000 --- a/modules/bar/components/network.py +++ /dev/null @@ -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 diff --git a/modules/bar/components/stats.py b/modules/bar/components/stats.py index 2b54fa5..9b5c5ab 100644 --- a/modules/bar/components/stats.py +++ b/modules/bar/components/stats.py @@ -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 diff --git a/modules/bar/components/taskbar.py b/modules/bar/components/taskbar.py index cc8af45..b54d0bb 100644 --- a/modules/bar/components/taskbar.py +++ b/modules/bar/components/taskbar.py @@ -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, diff --git a/modules/bar/components/updates.py b/modules/bar/components/updates.py new file mode 100644 index 0000000..0674d3e --- /dev/null +++ b/modules/bar/components/updates.py @@ -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 diff --git a/modules/bar/components/volume.py b/modules/bar/components/volume.py deleted file mode 100644 index 0fed57c..0000000 --- a/modules/bar/components/volume.py +++ /dev/null @@ -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"] diff --git a/modules/launcher/components/applauncher.py b/modules/launcher/components/applauncher.py index 54dd1c1..69669b3 100644 --- a/modules/launcher/components/applauncher.py +++ b/modules/launcher/components/applauncher.py @@ -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() diff --git a/modules/launcher/components/bluetooth.py b/modules/launcher/components/bluetooth.py index 0cf1704..a61849a 100644 --- a/modules/launcher/components/bluetooth.py +++ b/modules/launcher/components/bluetooth.py @@ -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) diff --git a/modules/launcher/components/buttons.py b/modules/launcher/components/buttons.py deleted file mode 100644 index d19fb89..0000000 --- a/modules/launcher/components/buttons.py +++ /dev/null @@ -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() diff --git a/modules/launcher/components/calendar.py b/modules/launcher/components/calendar.py index fdf712e..cdb2562 100644 --- a/modules/launcher/components/calendar.py +++ b/modules/launcher/components/calendar.py @@ -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) diff --git a/modules/launcher/components/cliphist.py b/modules/launcher/components/cliphist.py index 6153fb1..86fe079 100644 --- a/modules/launcher/components/cliphist.py +++ b/modules/launcher/components/cliphist.py @@ -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) diff --git a/modules/launcher/components/dashboard.py b/modules/launcher/components/dashboard.py index 246bfde..da835aa 100644 --- a/modules/launcher/components/dashboard.py +++ b/modules/launcher/components/dashboard.py @@ -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() diff --git a/modules/launcher/components/power.py b/modules/launcher/components/power.py index e00efa1..6964a12 100644 --- a/modules/launcher/components/power.py +++ b/modules/launcher/components/power.py @@ -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() + diff --git a/modules/launcher/components/sh.py b/modules/launcher/components/sh.py index 5992ec8..e9a92de 100644 --- a/modules/launcher/components/sh.py +++ b/modules/launcher/components/sh.py @@ -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) diff --git a/modules/launcher/components/todo.py b/modules/launcher/components/todo.py index 7cc569b..ef81852 100644 --- a/modules/launcher/components/todo.py +++ b/modules/launcher/components/todo.py @@ -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), ) diff --git a/modules/launcher/components/wifi.py b/modules/launcher/components/wifi.py index 675704f..b2f656e 100644 --- a/modules/launcher/components/wifi.py +++ b/modules/launcher/components/wifi.py @@ -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): diff --git a/modules/launcher/launcher.py b/modules/launcher/launcher.py index 6e484f6..97a950e 100644 --- a/modules/launcher/launcher.py +++ b/modules/launcher/launcher.py @@ -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, diff --git a/modules/osd.py b/modules/osd.py index 8092959..37534b7 100644 --- a/modules/osd.py +++ b/modules/osd.py @@ -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): diff --git a/snippets/iconss.py b/snippets/iconss.py new file mode 100644 index 0000000..78eb34d --- /dev/null +++ b/snippets/iconss.py @@ -0,0 +1,142 @@ +# Parameters +font_family: str = "tabler-icons" +font_weight: str = "normal" + +span: str = f"" + +# Panels +apps: str = "" +dashboard: str = "" +chat: str = "" +wallpapers: str = "" +windows: str = "" + +# Bar +colorpicker: str = "" +media: str = "" + +# Circles +temp: str = "" +disk: str = "" +battery: str = "" +memory: str = "流" +cpu: str = "" +swap: str = "" + + +# Wallpapers +add: str = "" +sort: str = "" +circle: str = "" +check_box: str = "" +box: str = "" +scan: str = "" +scan_stop: str = "" +record: str = "" +update: str = "見" +updated: str = "" + +# Chevrons +chevron_up: str = "" +chevron_down: str = "" +chevron_left: str = "" +chevron_right: str = "" + +# Power +lock: str = "" +suspend: str = "" +logout: str = "" +reboot: str = "" +shutdown: str = "" + +# Power Manager +power_saving: str = "" +power_balanced: str = "勺" +power_performance: str = "" +charging: str = "𐁣" +discharging: str = "𐁪" +alert: str = "" + +# Applets +wifi: str = "" +bluetooth: str = "" +night: str = "" +coffee: str = "" +dnd: str = "" +darkmode: str = "﹖" + +wifi_off: str = "" +bluetooth_off: str = "" +night_off: str = "" +dnd_off: str = "" +mic_off: str = "" + +# Bluetooth +bluetooth_connected: str = "" +bluetooth_disconnected: str = "" + + +# Player +pause: str = "" +play: str = "" +stop: str = "" +skip_back: str = "" +skip_forward: str = "" +prev: str = "" +next: str = "" +shuffle: str = "" +repeat: str = "" +music: str = "" +rewind_backward_5: str = "謹" +rewind_forward_5: str = "難" + +# Volume +vol_off: str = "" +vol_mute: str = "" +vol_medium: str = "" +vol_high: str = "" + +# Network +lan: str = "" +moderate_signal: str = "" +good_signal: str = "" +no_signal: str = "" +weak_signal: str = "" + +# Overview +circle_plus: str = "" + +# Pins +copy_plus: str = "ﶮ" +paperclip: str = "" + +# Confirm +accept: str = "" +cancel: str = "" +trash: str = "" +clear: str = "" + +# Config +config: str = "" + +# Icons +firefox: str = "" +chromium: str = "" +spotify: str = "ﺆ" +disc: str = "𐀾" +disc_off: str = "" + +# Misc +dot: str = "" + +exceptions: list[str] = ["font_family", "font_weight", "span"] + + +def apply_span() -> None: + global_dict = globals() + for key in global_dict: + if key not in exceptions and not key.startswith("__"): + global_dict[key] = f"{span}{global_dict[key]}" + + +apply_span() diff --git a/styles/bar.css b/styles/bar.css index f4d1ca6..fe0d1e2 100644 --- a/styles/bar.css +++ b/styles/bar.css @@ -14,6 +14,41 @@ transition: all 0.5s cubic-bezier(0.5, 0.25, 0, 1); } +#bar.visible { + opacity: 1; + padding: 3px; + margin-bottom: 0px; + transition: all 0.5s cubic-bezier(0.5, 0.25, 0, 1); +} + +/* + * ── button bar ──────────────────────────────────────────────────────── +*/ + +#button-bar { + background-color: @surfaceContainer; + min-width: 38px; + border-radius: 100px; + transition: all 0.1s ease; +} + +#button-bar-label { + color: @primary; + font-size: 24px; + border-radius: 100px; + transition: all 0.1s ease; +} + +#button-bar:hover #button-bar-label { + background-color: @surfaceBright; + box-shadow: 0px 3px 3px -2px rgba(0, 0, 0, 0.2); +} + +#button-bar:active #button-bar-label { + color: @shadow; + background-color: @primary; +} + /* * ── bar modules ─────────────────────────────────────────────────────── */ @@ -38,6 +73,36 @@ padding: 0px 10px 0px 10px; } +/* + * ── update ──────────────────────────────────────────────────────────── +*/ + +#update-icon { + color: @primary; + font-size: 22px; + margin-right: 1px; + padding: 2px; + border-radius: 11px; + transition: all 0.1s ease; +} + +#updated-icon { + color: @primary; + font-size: 20px; + padding: 8px; + border-radius: 99px; + transition: all 0.1s ease; +} + +#update-label { + color: @primary; + font-size: 10px; + font-weight: bold; + padding: 0px; + border-radius: 11px; + transition: all 0.1s ease; +} + /* * ── Clock ───────────────────────────────────────────────────────────── */ @@ -51,25 +116,11 @@ /* * ── battery ─────────────────────────────────────────────────────────── */ -/*#battery {*/ -/* background-color: @surfaceContainer;*/ -/* border-radius: 100px;*/ -/* padding: 2px 10px 2px 10px;*/ -/*}*/ -/**/ -/*#battery label {*/ -/* font-weight: 600;*/ -/*}*/ -/**/ -/*#battery-label {*/ -/* margin-right: 5px;*/ -/*}*/ #battery { background-color: @surfaceContainer; - padding: 4px; - border-radius: 16px; - margin-top: 4px; + padding: 2px 10px 2px 10px; + border-radius: 100px; } #battery-circle { @@ -78,7 +129,7 @@ } #battery-circle.alert { - border: 2px solid @error-dim; + border: 2px solid @error; } #battery-icon { @@ -86,22 +137,23 @@ } #battery-icon.alert { - color: @error-dim; + color: @onError; } #battery-save, #battery-balanced, #battery-performance { - border-radius: 12px; - min-width: 28px; + min-width: 30px; min-height: 28px; + margin: 2px 0px; + border-radius: 100px; } #battery-save-label, #battery-balanced-label, #battery-performance-label { color: @outline; - font-size: 20px; + font-size: 24px; } #battery-save:hover, @@ -132,6 +184,17 @@ color: @primary; font-weight: bold; margin: 0 4px; + font-size: 11px; +} + +/* + * ── systeminfo ──────────────────────────────────────────────────────── +*/ + +#system-info { + background-color: @surfaceContainer; + border-radius: 100px; + padding: 2px 10px 0px 10px; } /* @@ -198,15 +261,21 @@ menu > menuitem:hover > label { margin: 0px; } -#applets .icon { - color: @onSurface; - font-weight: 900; +#indicator-icon { + font-size: 24px; } -#applets > button { - color: @onSurface; - margin: 0px; - padding: 0px; +/* + * ── stats i.e cpu,ram,swap,temp ─────────────────────────────────────── +*/ + +#stats-icons { + font-size: 20px; +} + +#stats-progress { + color: @surfaceBright; + border: 3px solid @primary; } /* @@ -241,18 +310,20 @@ menu > menuitem:hover > label { margin-right: 0px; } -#progress { - color: lighter(@outlineVariant); - border: 1px solid @primary; -} +/* + * ── recording indicator ─────────────────────────────────────────────── +*/ #recording-indicator { - padding: 6px 12px 6px 12px; - border-radius: 99px; + min-width: 38px; + border-radius: 100px; } -#recording-indicator { - color: @primary; +#recorder { + color: @error; + border-radius: 100px; + min-width: 25px; + font-size: 20px; animation: error 2s infinite; } diff --git a/styles/dashboard.css b/styles/dashboard.css index 7857729..8792c72 100644 --- a/styles/dashboard.css +++ b/styles/dashboard.css @@ -5,9 +5,8 @@ #network-button, #bluetooth-button, #night-mode-button, -#caffeine-button, #dark-mode-button, -#power-profile-button { +#caffeine-button { border-radius: 20px; } @@ -18,17 +17,15 @@ } #night-mode-button, -#caffeine-button, #dark-mode-button, -#power-profile-button { +#caffeine-button { background-color: @primary; padding: 0 10px; } #night-mode-button.disabled, -#caffeine-button.disabled, #dark-mode-button.disabled, -#power-profile.disabled { +#caffeine-button.disabled { background-color: @surface; } @@ -40,42 +37,48 @@ padding: 0 8px; } +#network-menu-button, +#bluetooth-menu-button { + border-left: 1px solid @outline; + min-width: 24px; +} + #network-status-button:hover, #network-menu-button:hover, #bluetooth-status-button:hover, #bluetooth-menu-button:hover, #bluetooth-button:hover, #night-mode-button:hover, -#caffeine-button:hover, #dark-mode-button:hover, -#power-profile-button:hover { - background-color: mix(@onPrimary, @primary, 0.7); +#caffeine-button:hover { + background-color: @foreground; } +/* Estados Disabled */ #network-status-button.disabled, #network-menu-button.disabled, #bluetooth-status-button.disabled, -#bluetooth-menu-button.disabled, -#dark-mode-button.disabled, -#power-profile-button.disabled { +#bluetooth-menu-button.disabled { background-color: @surface; } +#network-menu-button.disabled, +#bluetooth-menu-button.disabled { + border-left: 1px solid @surfaceBright; +} + #network-status-button.disabled:hover, #network-menu-button.disabled:hover, #bluetooth-status-button.disabled:hover, #bluetooth-menu-button.disabled:hover, #night-mode-button.disabled:hover, -#caffeine-button.disabled:hover, #dark-mode-button.disabled:hover, -#power-profile-button.disabled:hover { +#caffeine-button.disabled:hover { background-color: @surfaceBright; } #network-status-button, -#bluetooth-status-button, -#dark-mode-status-button, -#power-profile-status-button { +#bluetooth-status-button { border-radius: 20px 0 0 20px; } @@ -84,17 +87,31 @@ border-radius: 0 20px 20px 0; } +#network-icon, +#bluetooth-icon, +#night-mode-icon, +#dark-mode-icon, +#caffeine-icon { + color: @shadow; + font-size: 24px; +} + +#network-icon.disabled, +#bluetooth-icon.disabled, +#night-mode-icon.disabled, +#dark-mode-icon.disabled, +#caffeine-icon.disabled { + color: @outline; +} + #network-menu-label, -#bluetooth-menu-label, -#dark-mode-label, -#power-profile-label { +#bluetooth-menu-label { + color: @shadow; font-size: 16px; } #network-menu-label.disabled, -#bluetooth-menu-label.disabled, -#dark-mode-label.disabled, -#power-profile-label.disabled { +#bluetooth-menu-label.disabled { color: @outline; } @@ -102,8 +119,7 @@ #bluetooth-label, #night-mode-label, #caffeine-label, -#dark-mode-label, -#power-profile-label { +#dark-mode-label { color: @shadow; font-size: 14px; font-weight: bold; @@ -113,18 +129,16 @@ #network-label.disabled, #bluetooth-label.disabled, #night-mode-label.disabled, -#caffeine-label.disabled, #dark-mode-label.disabled, -#power-profile-label.disabled { +#caffeine-label.disabled { color: @outline; } #network-ssid, #bluetooth-status, #night-mode-status, -#caffeine-status, #dark-mode-status, -#power-profile-status { +#caffeine-status { color: @surfaceBright; font-size: 12px; } @@ -132,12 +146,7 @@ #network-ssid.disabled, #bluetooth-status.disabled, #night-mode-status.disabled, -#caffeine-status.disabled, #dark-mode-status.disabled, -#power-profile-status.disabled { +#caffeine-status.disabled { color: @outline; } - -#power-profile-button.balanced-profile { - background-color: transparent; -} diff --git a/styles/launcher.css b/styles/launcher.css index 9057c92..0accbe3 100644 --- a/styles/launcher.css +++ b/styles/launcher.css @@ -28,22 +28,26 @@ min-width: 620px; } -#launcer-content.cliphist { - min-height: 420px; +.cliphist { + min-height: 464px; + min-width: 300px; +} + +.applauncher { + min-height: 464px; } #launcher-content.bluetooth { min-height: 464px; - min-width: 485px; } #launcher-content.wifi { min-height: 464px; - min-width: 485px; } -#launcher-content.power { - min-height: 52px; +#launcher-content.todo { + min-width: 460px; + min-height: 300px; } #launcher-content.calendar { @@ -62,25 +66,15 @@ #sh, #cliphist, #emoji, -#powermenu { +#power { transition: all 0.25s cubic-bezier(0.5, 0.25, 0, 1); } -#clear-launcher-button { - background-color: @secondaryContainer; - color: @onSecondaryContainer; - margin: 11px; - margin-left: 0px; - min-width: 44px; - min-height: 44px; - border-radius: 100%; -} - #scrolled-window scrollbar { border-radius: 10px; background-color: @surface; - /*padding: 4px;*/ - /*margin-left: 6px;*/ + padding: 4px; + margin-left: 6px; } #scrolled-window slider { @@ -174,28 +168,41 @@ * ── powermenu ───────────────────────────────────────────────────────── */ -#powermenu-button { +#power-menu-button { border-radius: 20px; + min-width: 52px; + min-height: 52px; padding: 20px; } -#powermenu-button:hover, -#powermenu-button:focus { +#power-menu-button label { + font-size: 49px; + color: @foreground; +} + +#power-menu-button:hover, +#power-menu-button:focus { background-color: @surfaceBright; } -#powermenu-button:active { +#power-menu-button:hover label, +#power-menu-button:focus label { + color: @primary; +} + +#power-menu-button:active { background-color: @primary; } +#power-menu-button:active label { + color: @shadow; +} + /* * ── cliphist ────────────────────────────────────────────────────────── */ -#cliphist-history { - min-height: 520px; -} -#launcher button#clip-img-item { +button#clip-img-item { background-size: cover; background-position: center; background-repeat: no-repeat; @@ -204,12 +211,12 @@ min-height: 70px; } -#launcher button#clip-img-item:hover, -#launcher button#clip-img-item:focus { +button#clip-img-item:hover, +button#clip-img-item:focus { box-shadow: 2px 2px 2px @primary; } -#launcher button#clip-item { +button#clip-item { border-radius: 11px; background-color: transparent; background-image: none; @@ -217,22 +224,34 @@ padding: 6px; } -#launcher button#clip-item { +button#clip-item { font-size: 30px; } -#launcher button#clip-item:hover, -#launcher button#clip-item:focus { +button#clip-item:hover, +button#clip-item:focus { text-shadow: 2px 2px 2px @primary; } +#clear-button { + margin: 11px; + margin-left: 0px; + min-width: 44px; + min-height: 44px; + border-radius: 100%; +} + +#clear-button:hover { + background-color: mix(@onSurface, @surface, 0.92); +} + +#cliphist-clear-button { + font-size: 24px; +} + /* * ── todo ────────────────────────────────────────────────────────────── */ -#todo-content { - min-height: 250px; -} - #todo-launcher { background-color: @surface; padding: 10px; @@ -246,7 +265,7 @@ } #todo-label { - color: @primary; + font-size: 16px; } #todo-label-completed { @@ -254,8 +273,34 @@ text-decoration: line-through; } -#delete-button { - color: @error; +#add-button { + margin: 11px; + margin-left: 0px; + margin-right: 0px; + min-width: 44px; + min-height: 44px; + border-radius: 100%; +} + +#delete-button, +#checkbox { + margin-left: 0px; + margin-right: 0px; + min-width: 44px; + min-height: 44px; + border-radius: 100%; +} + +#add-button:hover, +#delete-button:hover, +#checkbox:hover { + background-color: mix(@onSurface, @surface, 0.92); +} + +#todo-add-button, +#todo-checkbox, +#todo-delete-button { + font-size: 24px; } /* @@ -296,6 +341,11 @@ margin-bottom: 4px; } +#bluetooth-scan-button, +#bluetooth-toggle-button { + font-size: 24px; +} + #bluetooth-scan, #bluetooth-toggle { background-color: @secondaryContainer; @@ -343,7 +393,6 @@ background-color: @surfaceContainer; padding: 4px; border-radius: 20px; - min-height: 200px; } /* @@ -363,6 +412,10 @@ margin-bottom: 4px; } +#wifi-toggle-icon { + font-size: 24px; +} + #wifi-scan, #wifi-toggle { background-color: @secondaryContainer; @@ -409,7 +462,6 @@ background-color: @surfaceContainer; padding: 4px; border-radius: 20px; - min-height: 200px; } /* @@ -434,8 +486,8 @@ #day-label, #day-empty { - min-width: 41px; - min-height: 30px; + min-width: 42px; + min-height: 36px; } #day-empty { @@ -454,7 +506,9 @@ #day-label.current-day { background-color: @primary; color: @onPrimary; - border-radius: 20px; + min-height: 36px; + min-width: 42px; + border-radius: 99px; } #prev-month-button, @@ -483,6 +537,13 @@ color: @surface; } +#day-box { + padding: 5px; + border-radius: 5px; + min-width: 30px; + min-height: 30px; +} + #prev-month-day, #next-month-day { /*color: lighter(@surfaceBright);*/ diff --git a/styles/main.css b/styles/main.css index 7703a10..a0cd973 100644 --- a/styles/main.css +++ b/styles/main.css @@ -9,8 +9,7 @@ all: unset; color: @foreground; font-size: 16px; - font-family: "Product Sans", Roboto, "Font Awesome 6 Free", Helvetica, Arial, - sans-serif; + font-family: "Product Sans", sans-serif; } /* @@ -19,7 +18,7 @@ #corner { background-color: @surface; - border-radius: 0; + border-radius: 0px; } #corner-container {