huge updates
This commit is contained in:
14 files changed
+553
-612
No files matched your search
@@ -37,7 +37,7 @@
|
|||||||
1. Dependencies:
|
1. Dependencies:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fabric fabric-cli gnome-bluetooth-3.0 wf-recorder cliphist wl-clipboard grimblast power-profile-daemon adw-gtk-theme brightnessctl swww hypridle hyprlock imagemagick libnotify swappy libadwaita
|
fabric fabric-cli gnome-bluetooth-3.0 wf-recorder cliphist wl-clipboard grimblast power-profile-daemon adw-gtk-theme brightnessctl swww hypridle hyprlock imagemagick libnotify swappy libadwaita wlinhibit
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -92,7 +92,7 @@ cd Tela-icon-theme
|
|||||||
- [x] Update Screenshot
|
- [x] Update Screenshot
|
||||||
- [x] Installation script
|
- [x] Installation script
|
||||||
- [x] Network Manager
|
- [x] Network Manager
|
||||||
- [ ] Calendar with events
|
- [x] Calendar
|
||||||
- [ ] Power Manager
|
- [ ] Power Manager
|
||||||
- [ ] Notification Panel
|
- [ ] Notification Panel
|
||||||
- [ ] Improve CSS Styling
|
- [ ] Improve CSS Styling
|
||||||
|
|||||||
+2
-1
@@ -33,6 +33,7 @@ PACKAGES=(
|
|||||||
libadwaita
|
libadwaita
|
||||||
adw-gtk-theme
|
adw-gtk-theme
|
||||||
brightnessctl
|
brightnessctl
|
||||||
|
wlinhibit
|
||||||
power-profile-daemon
|
power-profile-daemon
|
||||||
ttf-font-awesome
|
ttf-font-awesome
|
||||||
otf-font-awesome
|
otf-font-awesome
|
||||||
@@ -75,7 +76,7 @@ fi
|
|||||||
|
|
||||||
# Install required packages using paru
|
# Install required packages using paru
|
||||||
echo ":: Installing required packages..."
|
echo ":: Installing required packages..."
|
||||||
paru -S --needed --noconfirm "${PACKAGES[@]}" || true
|
paru -Syy --needed --noconfirm "${PACKAGES[@]}" || true
|
||||||
|
|
||||||
# Backup and replace GTK configs
|
# Backup and replace GTK configs
|
||||||
echo ":: Updating GTK configurations..."
|
echo ":: Updating GTK configurations..."
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ from loguru import logger
|
|||||||
from modules.bar.bar import Bar
|
from modules.bar.bar import Bar
|
||||||
from modules.launcher.launcher import Launcher
|
from modules.launcher.launcher import Launcher
|
||||||
from modules.notification_popup import NotificationPopup
|
from modules.notification_popup import NotificationPopup
|
||||||
from modules.osd import OSDContainer
|
|
||||||
|
from modules.osd import OSD
|
||||||
from modules.corners import Corners
|
from modules.corners import Corners
|
||||||
from services import sc
|
from services import sc
|
||||||
|
from config.config import open_config
|
||||||
|
|
||||||
for log in [
|
for log in [
|
||||||
"fabric.hyprland.widgets",
|
"fabric.hyprland.widgets",
|
||||||
@@ -19,6 +21,8 @@ for log in [
|
|||||||
]:
|
]:
|
||||||
logger.disable(log)
|
logger.disable(log)
|
||||||
|
|
||||||
|
config_path = os.path.expanduser("~/Modus/config/assets/config.json")
|
||||||
|
|
||||||
|
|
||||||
def update_main_css():
|
def update_main_css():
|
||||||
colors_css_path = f"/home/{os.getlogin()}/.cache/material/colors.css"
|
colors_css_path = f"/home/{os.getlogin()}/.cache/material/colors.css"
|
||||||
@@ -42,10 +46,12 @@ def apply_style(app: Application):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
update_main_css()
|
update_main_css()
|
||||||
|
if not os.path.isfile(config_path):
|
||||||
|
open_config()
|
||||||
sc = sc
|
sc = sc
|
||||||
bar = Bar()
|
bar = Bar()
|
||||||
corners = Corners()
|
corners = Corners()
|
||||||
osd = OSDContainer()
|
osd = OSD()
|
||||||
notif = NotificationPopup()
|
notif = NotificationPopup()
|
||||||
launcher = Launcher()
|
launcher = Launcher()
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ from gi.repository import GLib
|
|||||||
from modules.bar.components import (
|
from modules.bar.components import (
|
||||||
BatteryLabel,
|
BatteryLabel,
|
||||||
BluetoothIndicator,
|
BluetoothIndicator,
|
||||||
IdleIndicator,
|
|
||||||
MicrophoneIndicator,
|
MicrophoneIndicator,
|
||||||
NetworkIndicator,
|
NetworkIndicator,
|
||||||
PowerProfileIndicator,
|
|
||||||
SystemInfo,
|
SystemInfo,
|
||||||
TaskBar,
|
TaskBar,
|
||||||
VolumeIndicator,
|
VolumeIndicator,
|
||||||
@@ -57,10 +55,8 @@ class Bar(Window):
|
|||||||
self.bluetooth = BluetoothIndicator()
|
self.bluetooth = BluetoothIndicator()
|
||||||
self.network = NetworkIndicator()
|
self.network = NetworkIndicator()
|
||||||
self.microphone = MicrophoneIndicator()
|
self.microphone = MicrophoneIndicator()
|
||||||
self.idle = IdleIndicator()
|
|
||||||
self.taskbar = TaskBar()
|
self.taskbar = TaskBar()
|
||||||
self.info = SystemInfo()
|
self.info = SystemInfo()
|
||||||
self.powerindicator = PowerProfileIndicator()
|
|
||||||
self.tray = SystemTray(name="tray", icon_size=16, spacing=4)
|
self.tray = SystemTray(name="tray", icon_size=16, spacing=4)
|
||||||
self.launcher = Button(
|
self.launcher = Button(
|
||||||
name="logo",
|
name="logo",
|
||||||
@@ -76,12 +72,10 @@ class Bar(Window):
|
|||||||
orientation="h",
|
orientation="h",
|
||||||
children=[
|
children=[
|
||||||
self.language,
|
self.language,
|
||||||
self.powerindicator,
|
|
||||||
self.bluetooth,
|
self.bluetooth,
|
||||||
self.network,
|
self.network,
|
||||||
self.volume,
|
self.volume,
|
||||||
self.microphone,
|
self.microphone,
|
||||||
self.idle,
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
from fabric import Fabricator
|
|
||||||
from fabric.utils import exec_shell_command
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from snippets import MaterialIcon
|
|
||||||
|
|
||||||
|
|
||||||
class IdleIndicator(Box):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.idle_icon = self.create_idle_icon()
|
|
||||||
self.fabricator = Fabricator(interval=1000, poll_from=self.check_idle_status)
|
|
||||||
|
|
||||||
self.children = [self.idle_icon]
|
|
||||||
|
|
||||||
def create_idle_icon(self):
|
|
||||||
return MaterialIcon("coffee", 16)
|
|
||||||
|
|
||||||
def check_idle_status(self, *_):
|
|
||||||
output = exec_shell_command("pidof wayland-idle-inhibitor.py")
|
|
||||||
is_idle = bool(str(output).strip())
|
|
||||||
self.update_idle_status(is_idle)
|
|
||||||
|
|
||||||
def update_idle_status(self, is_idle):
|
|
||||||
if is_idle:
|
|
||||||
self.idle_icon.set_visible(True)
|
|
||||||
else:
|
|
||||||
self.idle_icon.set_visible(False)
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
from fabric import Fabricator
|
|
||||||
from fabric.utils import exec_shell_command
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from snippets import MaterialIcon
|
|
||||||
|
|
||||||
PROFILE_ICONS = {
|
|
||||||
"power-saver": "data_saver_on",
|
|
||||||
"balanced": None,
|
|
||||||
"performance": "mode_heat",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PowerProfileIndicator(Box):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
self.profile_icon = None
|
|
||||||
|
|
||||||
self.fabricator = Fabricator(interval=1000, poll_from=self.update_power_profile)
|
|
||||||
|
|
||||||
def update_power_profile(self, *_):
|
|
||||||
profile = self.get_power_profile()
|
|
||||||
self.update_icon(profile)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_power_profile(self):
|
|
||||||
result = exec_shell_command("powerprofilesctl get")
|
|
||||||
return result.strip() or "power-saver"
|
|
||||||
|
|
||||||
def update_icon(self, profile):
|
|
||||||
icon_name = PROFILE_ICONS.get(profile)
|
|
||||||
|
|
||||||
new_icon = MaterialIcon(icon_name, 16) if icon_name else None
|
|
||||||
|
|
||||||
if self.profile_icon:
|
|
||||||
self.remove(self.profile_icon)
|
|
||||||
|
|
||||||
if new_icon:
|
|
||||||
self.profile_icon = new_icon
|
|
||||||
self.add(self.profile_icon)
|
|
||||||
else:
|
|
||||||
if self.profile_icon:
|
|
||||||
self.profile_icon.hide()
|
|
||||||
self.profile_icon = None
|
|
||||||
@@ -8,3 +8,4 @@ from .sh import *
|
|||||||
from .todo import *
|
from .todo import *
|
||||||
from .wallpapers import *
|
from .wallpapers import *
|
||||||
from .wifi import *
|
from .wifi import *
|
||||||
|
from .calendar import *
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class Calendar(Gtk.Box):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
orientation=Gtk.Orientation.VERTICAL, spacing=8, name="calendar"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.current_year = datetime.now().year
|
||||||
|
self.current_month = datetime.now().month
|
||||||
|
self.current_day = datetime.now().day
|
||||||
|
self.previous_key = (self.current_year, self.current_month)
|
||||||
|
|
||||||
|
self.cache_threshold = 1 # Allow current month +/- 1 month
|
||||||
|
self.month_views = {}
|
||||||
|
|
||||||
|
self.header = Gtk.Box(spacing=4, name="header")
|
||||||
|
self.pack_start(self.header, False, False, 0)
|
||||||
|
|
||||||
|
self.prev_month_button = Gtk.Button(
|
||||||
|
name="prev-month-button",
|
||||||
|
child=MaterialIcon("chevron_left"),
|
||||||
|
)
|
||||||
|
self.prev_month_button.connect("clicked", self.on_prev_month_clicked)
|
||||||
|
self.header.pack_start(self.prev_month_button, False, False, 0)
|
||||||
|
|
||||||
|
self.month_label = Gtk.Label(name="month-label")
|
||||||
|
self.header.pack_start(self.month_label, True, True, 0)
|
||||||
|
|
||||||
|
self.next_month_button = Gtk.Button(
|
||||||
|
name="next-month-button",
|
||||||
|
child=MaterialIcon("chevron_right"),
|
||||||
|
)
|
||||||
|
self.next_month_button.connect("clicked", self.on_next_month_clicked)
|
||||||
|
self.header.pack_start(self.next_month_button, False, False, 0)
|
||||||
|
|
||||||
|
self.weekday_row = Gtk.Box(spacing=4, name="weekday-row")
|
||||||
|
self.pack_start(self.weekday_row, False, False, 0)
|
||||||
|
|
||||||
|
self.stack = Gtk.Stack(name="calendar-stack")
|
||||||
|
self.stack.set_transition_duration(500)
|
||||||
|
self.pack_start(self.stack, True, True, 0)
|
||||||
|
|
||||||
|
self.update_header()
|
||||||
|
self.update_calendar()
|
||||||
|
GLib.timeout_add_seconds(60, self.check_date_change)
|
||||||
|
|
||||||
|
def update_header(self):
|
||||||
|
self.month_label.set_text(
|
||||||
|
datetime(self.current_year, self.current_month, 1)
|
||||||
|
.strftime("%B %Y")
|
||||||
|
.capitalize()
|
||||||
|
)
|
||||||
|
for child in self.weekday_row.get_children():
|
||||||
|
self.weekday_row.remove(child)
|
||||||
|
|
||||||
|
days = self.get_weekday_initials()
|
||||||
|
for day in days:
|
||||||
|
label = Gtk.Label(label=day.upper(), name="weekday-label")
|
||||||
|
self.weekday_row.pack_start(label, True, True, 0)
|
||||||
|
self.weekday_row.show_all()
|
||||||
|
|
||||||
|
def update_calendar(self):
|
||||||
|
new_key = (self.current_year, self.current_month)
|
||||||
|
|
||||||
|
if new_key > self.previous_key:
|
||||||
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
|
||||||
|
elif new_key < self.previous_key:
|
||||||
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT)
|
||||||
|
|
||||||
|
self.previous_key = new_key
|
||||||
|
|
||||||
|
if new_key not in self.month_views:
|
||||||
|
month_view = self.create_month_view(self.current_year, self.current_month)
|
||||||
|
self.month_views[new_key] = month_view
|
||||||
|
self.stack.add_titled(
|
||||||
|
month_view,
|
||||||
|
f"{self.current_year}_{self.current_month}",
|
||||||
|
f"{self.current_year}_{self.current_month}",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stack.set_visible_child_name(f"{self.current_year}_{self.current_month}")
|
||||||
|
self.update_header()
|
||||||
|
self.stack.show_all()
|
||||||
|
self.prune_cache()
|
||||||
|
|
||||||
|
def prune_cache(self):
|
||||||
|
def month_index(key):
|
||||||
|
year, month = key
|
||||||
|
return year * 12 + (month - 1)
|
||||||
|
|
||||||
|
current_index = month_index((self.current_year, self.current_month))
|
||||||
|
keys_to_remove = [
|
||||||
|
key
|
||||||
|
for key in self.month_views
|
||||||
|
if abs(month_index(key) - current_index) > self.cache_threshold
|
||||||
|
]
|
||||||
|
|
||||||
|
for key in keys_to_remove:
|
||||||
|
widget = self.month_views.pop(key)
|
||||||
|
self.stack.remove(widget)
|
||||||
|
|
||||||
|
def create_month_view(self, year, month):
|
||||||
|
grid = Gtk.Grid(
|
||||||
|
column_homogeneous=True, row_homogeneous=False, name="calendar-grid"
|
||||||
|
)
|
||||||
|
|
||||||
|
cal = calendar.Calendar(firstweekday=6)
|
||||||
|
month_days = cal.monthdayscalendar(year, month)
|
||||||
|
|
||||||
|
prev_month = month - 1 if month > 1 else 12
|
||||||
|
next_month = month + 1 if month < 12 else 1
|
||||||
|
prev_year = year if month > 1 else year - 1
|
||||||
|
next_year = year if month < 12 else year + 1
|
||||||
|
|
||||||
|
prev_month_days_count = calendar.monthrange(prev_year, prev_month)[1]
|
||||||
|
next_month_day = 1
|
||||||
|
|
||||||
|
for row, week in enumerate(month_days):
|
||||||
|
for col, day in enumerate(week):
|
||||||
|
day_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="day-box")
|
||||||
|
top_spacer = Gtk.Box(hexpand=True, vexpand=True)
|
||||||
|
middle_box = Gtk.Box(hexpand=True, vexpand=True)
|
||||||
|
bottom_spacer = Gtk.Box(hexpand=True, vexpand=True)
|
||||||
|
|
||||||
|
if day == 0:
|
||||||
|
if row == 0:
|
||||||
|
day_num = prev_month_days_count - week[:col].count(0)
|
||||||
|
label = Gtk.Label(label=str(day_num), name="prev-month-day")
|
||||||
|
else:
|
||||||
|
label = Gtk.Label(
|
||||||
|
label=str(next_month_day), name="next-month-day"
|
||||||
|
)
|
||||||
|
next_month_day += 1
|
||||||
|
else:
|
||||||
|
label = Gtk.Label(label=str(day), name="day-label")
|
||||||
|
if (
|
||||||
|
day == self.current_day
|
||||||
|
and month == datetime.now().month
|
||||||
|
and year == datetime.now().year
|
||||||
|
):
|
||||||
|
label.get_style_context().add_class("current-day")
|
||||||
|
|
||||||
|
middle_box.pack_start(
|
||||||
|
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
|
||||||
|
)
|
||||||
|
middle_box.pack_start(label, False, False, 0)
|
||||||
|
middle_box.pack_start(
|
||||||
|
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
day_box.pack_start(top_spacer, True, True, 0)
|
||||||
|
day_box.pack_start(middle_box, True, True, 0)
|
||||||
|
day_box.pack_start(bottom_spacer, True, True, 0)
|
||||||
|
|
||||||
|
grid.attach(day_box, col, row, 1, 1)
|
||||||
|
|
||||||
|
grid.show_all()
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def get_weekday_initials(self):
|
||||||
|
return [datetime(2023, 1, i + 1).strftime("%a")[:1] for i in range(7)]
|
||||||
|
|
||||||
|
def on_prev_month_clicked(self, widget):
|
||||||
|
if self.current_month == 1:
|
||||||
|
self.current_month = 12
|
||||||
|
self.current_year -= 1
|
||||||
|
else:
|
||||||
|
self.current_month -= 1
|
||||||
|
self.update_calendar()
|
||||||
|
|
||||||
|
def on_next_month_clicked(self, widget):
|
||||||
|
if self.current_month == 12:
|
||||||
|
self.current_month = 1
|
||||||
|
self.current_year += 1
|
||||||
|
else:
|
||||||
|
self.current_month += 1
|
||||||
|
self.update_calendar()
|
||||||
|
|
||||||
|
def check_date_change(self):
|
||||||
|
now = datetime.now()
|
||||||
|
if (
|
||||||
|
now.day != self.current_day
|
||||||
|
or now.month != self.current_month
|
||||||
|
or now.year != self.current_year
|
||||||
|
):
|
||||||
|
self.current_day = now.day
|
||||||
|
self.current_month = now.month
|
||||||
|
self.current_year = now.year
|
||||||
|
self.update_calendar()
|
||||||
|
return True
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
from fabric.widgets.box import Box
|
|
||||||
from fabric.widgets.button import Button
|
|
||||||
from fabric.widgets.label import Label
|
|
||||||
from snippets import MaterialIcon
|
|
||||||
|
|
||||||
|
|
||||||
class Buttons(Box):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
name="buttons",
|
|
||||||
spacing=4,
|
|
||||||
h_align="center",
|
|
||||||
v_align="start",
|
|
||||||
h_expand=True,
|
|
||||||
v_expand=True,
|
|
||||||
visible=True,
|
|
||||||
all_visible=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.network_status_button = Button(
|
|
||||||
name="network-status-button",
|
|
||||||
h_expand=True,
|
|
||||||
child=Box(
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
spacing=10,
|
|
||||||
children=[
|
|
||||||
MaterialIcon("wifi"),
|
|
||||||
Box(
|
|
||||||
name="network-text",
|
|
||||||
orientation="v",
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
children=[
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="network-label",
|
|
||||||
label="Wi-Fi",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="network-ssid",
|
|
||||||
label="ARGOS_5GHz",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.network_menu_button = Button(
|
|
||||||
name="network-menu-button",
|
|
||||||
child=MaterialIcon("chevron_right"),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.network_button = Box(
|
|
||||||
name="network-button",
|
|
||||||
orientation="h",
|
|
||||||
h_align="fill",
|
|
||||||
v_align="center",
|
|
||||||
h_expand=True,
|
|
||||||
v_expand=True,
|
|
||||||
children=[
|
|
||||||
self.network_status_button,
|
|
||||||
self.network_menu_button,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.bluetooth_status_button = Button(
|
|
||||||
name="bluetooth-status-button",
|
|
||||||
h_expand=True,
|
|
||||||
child=Box(
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
spacing=10,
|
|
||||||
children=[
|
|
||||||
MaterialIcon("bluetooth"),
|
|
||||||
Box(
|
|
||||||
name="bluetooth-text",
|
|
||||||
orientation="v",
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
children=[
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="bluetooth-label",
|
|
||||||
label="Bluetooth",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="bluetooth-status",
|
|
||||||
label="Connected",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.bluetooth_menu_button = Button(
|
|
||||||
name="bluetooth-menu-button",
|
|
||||||
child=MaterialIcon("chevron_right"),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.bluetooth_button = Box(
|
|
||||||
name="bluetooth-button",
|
|
||||||
orientation="h",
|
|
||||||
h_align="fill",
|
|
||||||
v_align="center",
|
|
||||||
h_expand=True,
|
|
||||||
v_expand=True,
|
|
||||||
children=[
|
|
||||||
self.bluetooth_status_button,
|
|
||||||
self.bluetooth_menu_button,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.night_mode_button = Button(
|
|
||||||
name="night-mode-button",
|
|
||||||
child=Box(
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
spacing=10,
|
|
||||||
children=[
|
|
||||||
MaterialIcon("nightlight"),
|
|
||||||
Box(
|
|
||||||
name="night-mode-text",
|
|
||||||
orientation="v",
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
children=[
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="night-mode-label",
|
|
||||||
label="Night Mode",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="night-mode-status",
|
|
||||||
label="Enabled",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.caffeine_button = Button(
|
|
||||||
name="caffeine-button",
|
|
||||||
child=Box(
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
spacing=10,
|
|
||||||
children=[
|
|
||||||
MaterialIcon("coffee"),
|
|
||||||
Box(
|
|
||||||
name="caffeine-text",
|
|
||||||
orientation="v",
|
|
||||||
h_align="start",
|
|
||||||
v_align="center",
|
|
||||||
children=[
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="caffeine-label",
|
|
||||||
label="Caffeine",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Box(
|
|
||||||
children=[
|
|
||||||
Label(
|
|
||||||
name="caffeine-status",
|
|
||||||
label="Enabled",
|
|
||||||
justification="left",
|
|
||||||
),
|
|
||||||
Box(h_expand=True),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add(self.network_button)
|
|
||||||
self.add(self.bluetooth_button)
|
|
||||||
self.add(self.night_mode_button)
|
|
||||||
self.add(self.caffeine_button)
|
|
||||||
|
|
||||||
self.show_all()
|
|
||||||
|
|
||||||
|
|
||||||
class Dashboard(Box):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
name="dashboard",
|
|
||||||
h_align="center",
|
|
||||||
v_align="start",
|
|
||||||
h_expand=True,
|
|
||||||
v_expand=True,
|
|
||||||
visible=True,
|
|
||||||
all_visible=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.buttons = Buttons()
|
|
||||||
|
|
||||||
self.add(self.buttons)
|
|
||||||
|
|
||||||
self.show_all()
|
|
||||||
@@ -3,10 +3,11 @@ import json
|
|||||||
import os
|
import os
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from fabric.widgets.box import Box
|
from fabric.widgets.box import Box
|
||||||
|
from fabric.widgets.centerbox import CenterBox
|
||||||
from fabric.widgets.button import Button
|
from fabric.widgets.button import Button
|
||||||
from fabric.widgets.entry import Entry
|
from fabric.widgets.entry import Entry
|
||||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||||
from gi.repository import GdkPixbuf, GLib, Gtk, Gio
|
from gi.repository import GdkPixbuf, GLib, Gtk, Gio, Gdk
|
||||||
from snippets import MaterialIcon
|
from snippets import MaterialIcon
|
||||||
from fabric.utils import exec_shell_command, get_relative_path
|
from fabric.utils import exec_shell_command, get_relative_path
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
@@ -30,10 +31,16 @@ class WallpaperSelector(Box):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(name="wallpapers", spacing=4, orientation="v", **kwargs)
|
super().__init__(
|
||||||
|
name="wallpapers",
|
||||||
|
spacing=4,
|
||||||
|
orientation="v",
|
||||||
|
h_expand=False,
|
||||||
|
v_expand=False,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
self.launcher = kwargs["launcher"]
|
self.launcher = kwargs["launcher"]
|
||||||
|
|
||||||
# self.wallpapers_dir = os.path.expanduser("~/Pictures/wallpapers")
|
|
||||||
os.makedirs(self.CACHE_DIR, exist_ok=True)
|
os.makedirs(self.CACHE_DIR, exist_ok=True)
|
||||||
|
|
||||||
self.files = [f for f in os.listdir(WALLPAPERS_DIR) if self._is_image(f)]
|
self.files = [f for f in os.listdir(WALLPAPERS_DIR) if self._is_image(f)]
|
||||||
@@ -41,10 +48,13 @@ class WallpaperSelector(Box):
|
|||||||
self.thumbnail_queue = []
|
self.thumbnail_queue = []
|
||||||
self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
|
self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
|
||||||
|
|
||||||
self.viewport = Gtk.IconView()
|
self.selected_index = -1
|
||||||
|
|
||||||
|
# IconView for displaying thumbnails.
|
||||||
|
self.viewport = Gtk.IconView(name="wallpaper-icons")
|
||||||
self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
|
self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
|
||||||
self.viewport.set_pixbuf_column(0)
|
self.viewport.set_pixbuf_column(0)
|
||||||
self.viewport.set_text_column(1)
|
self.viewport.set_text_column(-1)
|
||||||
self.viewport.set_item_width(0)
|
self.viewport.set_item_width(0)
|
||||||
self.viewport.connect("item-activated", self.on_wallpaper_selected)
|
self.viewport.connect("item-activated", self.on_wallpaper_selected)
|
||||||
|
|
||||||
@@ -57,10 +67,12 @@ class WallpaperSelector(Box):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.search_entry = Entry(
|
self.search_entry = Entry(
|
||||||
name="search-entry",
|
name="search-entry-walls",
|
||||||
h_expand=True,
|
h_expand=True,
|
||||||
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
|
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
|
||||||
|
on_key_press_event=self.on_search_entry_key_press,
|
||||||
)
|
)
|
||||||
|
self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
|
||||||
|
|
||||||
self.scheme_dropdown = Gtk.ComboBoxText()
|
self.scheme_dropdown = Gtk.ComboBoxText()
|
||||||
self.scheme_dropdown.set_name("scheme-dropdown")
|
self.scheme_dropdown.set_name("scheme-dropdown")
|
||||||
@@ -72,7 +84,6 @@ class WallpaperSelector(Box):
|
|||||||
self.scheme_dropdown.set_active_id("tonalSpot")
|
self.scheme_dropdown.set_active_id("tonalSpot")
|
||||||
self.scheme_dropdown.connect("changed", self.on_scheme_changed)
|
self.scheme_dropdown.connect("changed", self.on_scheme_changed)
|
||||||
|
|
||||||
# Set the initial icon based on dark mode state
|
|
||||||
initial_icon = "light_mode" if not self.check_dark_mode_state() else "dark_mode"
|
initial_icon = "light_mode" if not self.check_dark_mode_state() else "dark_mode"
|
||||||
self.toggle_button = Button(
|
self.toggle_button = Button(
|
||||||
child=MaterialIcon(initial_icon),
|
child=MaterialIcon(initial_icon),
|
||||||
@@ -82,18 +93,17 @@ class WallpaperSelector(Box):
|
|||||||
|
|
||||||
self.dropdown_box = Box(
|
self.dropdown_box = Box(
|
||||||
name="dropdown-box",
|
name="dropdown-box",
|
||||||
orientation="h",
|
|
||||||
children=[
|
children=[
|
||||||
self.scheme_dropdown,
|
self.scheme_dropdown,
|
||||||
MaterialIcon("keyboard_arrow_down"),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.header_box = Box(
|
self.header_box = CenterBox(
|
||||||
name="header-box",
|
name="header-box",
|
||||||
spacing=10,
|
spacing=10,
|
||||||
orientation="h",
|
orientation="h",
|
||||||
children=[self.search_entry, self.dropdown_box, self.toggle_button],
|
start_children=[self.search_entry],
|
||||||
|
end_children=[self.dropdown_box, self.toggle_button],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.add(self.header_box)
|
self.add(self.header_box)
|
||||||
@@ -101,6 +111,7 @@ class WallpaperSelector(Box):
|
|||||||
self._start_thumbnail_thread()
|
self._start_thumbnail_thread()
|
||||||
self.setup_file_monitor()
|
self.setup_file_monitor()
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
self.search_entry.grab_focus()
|
||||||
|
|
||||||
def setup_file_monitor(self):
|
def setup_file_monitor(self):
|
||||||
gfile = Gio.File.new_for_path(WALLPAPERS_DIR)
|
gfile = Gio.File.new_for_path(WALLPAPERS_DIR)
|
||||||
@@ -135,11 +146,9 @@ class WallpaperSelector(Box):
|
|||||||
print(f"Error deleting cache for changed file {file_name}: {e}")
|
print(f"Error deleting cache for changed file {file_name}: {e}")
|
||||||
self.executor.submit(self._process_file, file_name)
|
self.executor.submit(self._process_file, file_name)
|
||||||
|
|
||||||
def close_selector(self):
|
|
||||||
self.launcher.close()
|
|
||||||
|
|
||||||
def arrange_viewport(self, query: str = ""):
|
def arrange_viewport(self, query: str = ""):
|
||||||
self.viewport.get_model().clear()
|
model = self.viewport.get_model()
|
||||||
|
model.clear()
|
||||||
filtered_thumbnails = [
|
filtered_thumbnails = [
|
||||||
(thumb, name)
|
(thumb, name)
|
||||||
for thumb, name in self.thumbnails
|
for thumb, name in self.thumbnails
|
||||||
@@ -147,32 +156,14 @@ class WallpaperSelector(Box):
|
|||||||
]
|
]
|
||||||
|
|
||||||
filtered_thumbnails.sort(key=lambda x: x[1].lower())
|
filtered_thumbnails.sort(key=lambda x: x[1].lower())
|
||||||
|
|
||||||
for pixbuf, file_name in filtered_thumbnails:
|
for pixbuf, file_name in filtered_thumbnails:
|
||||||
self.viewport.get_model().append([pixbuf, file_name])
|
model.append([pixbuf, file_name])
|
||||||
|
|
||||||
def toggle_dark_mode(self, *_):
|
if query.strip() == "":
|
||||||
current_state = self.check_dark_mode_state()
|
self.viewport.unselect_all()
|
||||||
new_mode = "dark" if not current_state else "light"
|
self.selected_index = -1
|
||||||
|
elif len(model) > 0:
|
||||||
# Call the shell script with the --set argument
|
self.update_selection(0)
|
||||||
command = f"bash {get_relative_path('../../../config/scripts/dark-theme.sh')} --set {new_mode}"
|
|
||||||
GLib.spawn_command_line_async(command)
|
|
||||||
|
|
||||||
# Set the appropriate icon based on the mode
|
|
||||||
icon_name = "dark_mode" if new_mode == "dark" else "light_mode"
|
|
||||||
|
|
||||||
# Update button icon
|
|
||||||
if self.toggle_button.get_child():
|
|
||||||
self.toggle_button.remove(self.toggle_button.get_child())
|
|
||||||
self.toggle_button.add(MaterialIcon(icon_name))
|
|
||||||
self.toggle_button.show_all()
|
|
||||||
|
|
||||||
def check_dark_mode_state(self):
|
|
||||||
result = exec_shell_command(
|
|
||||||
"gsettings get org.gnome.desktop.interface color-scheme"
|
|
||||||
)
|
|
||||||
return result.strip().replace("'", "") == "prefer-dark"
|
|
||||||
|
|
||||||
def on_wallpaper_selected(self, iconview, path):
|
def on_wallpaper_selected(self, iconview, path):
|
||||||
model = iconview.get_model()
|
model = iconview.get_model()
|
||||||
@@ -214,6 +205,76 @@ class WallpaperSelector(Box):
|
|||||||
except Exception as error:
|
except Exception as error:
|
||||||
print(f"Failed to update generation-scheme in settings.json: {error}")
|
print(f"Failed to update generation-scheme in settings.json: {error}")
|
||||||
|
|
||||||
|
def on_search_entry_key_press(self, widget, event):
|
||||||
|
if event.state & Gdk.ModifierType.SHIFT_MASK:
|
||||||
|
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down):
|
||||||
|
schemes_list = list(self.SCHEMES.keys())
|
||||||
|
current_id = self.scheme_dropdown.get_active_id()
|
||||||
|
current_index = (
|
||||||
|
schemes_list.index(current_id) if current_id in schemes_list else 0
|
||||||
|
)
|
||||||
|
if event.keyval == Gdk.KEY_Up:
|
||||||
|
new_index = (current_index - 1) % len(schemes_list)
|
||||||
|
else:
|
||||||
|
new_index = (current_index + 1) % len(schemes_list)
|
||||||
|
self.scheme_dropdown.set_active(new_index)
|
||||||
|
return True
|
||||||
|
elif event.keyval == Gdk.KEY_Right:
|
||||||
|
self.scheme_dropdown.popup()
|
||||||
|
return True
|
||||||
|
|
||||||
|
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
|
||||||
|
self.move_selection_2d(event.keyval)
|
||||||
|
return True
|
||||||
|
elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
|
||||||
|
if self.selected_index != -1:
|
||||||
|
path = Gtk.TreePath.new_from_indices([self.selected_index])
|
||||||
|
self.on_wallpaper_selected(self.viewport, path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def move_selection_2d(self, keyval):
|
||||||
|
model = self.viewport.get_model()
|
||||||
|
total_items = len(model)
|
||||||
|
if total_items == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.selected_index == -1:
|
||||||
|
new_index = (
|
||||||
|
0 if keyval in (Gdk.KEY_Down, Gdk.KEY_Right) else total_items - 1
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
current_index = self.selected_index
|
||||||
|
# Calculate columns based on the allocation width.
|
||||||
|
allocation = self.viewport.get_allocation()
|
||||||
|
item_width = 108
|
||||||
|
columns = max(1, allocation.width // item_width)
|
||||||
|
|
||||||
|
if keyval == Gdk.KEY_Right:
|
||||||
|
new_index = current_index + 1
|
||||||
|
elif keyval == Gdk.KEY_Left:
|
||||||
|
new_index = current_index - 1
|
||||||
|
elif keyval == Gdk.KEY_Down:
|
||||||
|
new_index = current_index + columns
|
||||||
|
elif keyval == Gdk.KEY_Up:
|
||||||
|
new_index = current_index - columns
|
||||||
|
|
||||||
|
# Ensure new_index remains within limits.
|
||||||
|
if new_index < 0:
|
||||||
|
new_index = 0
|
||||||
|
if new_index >= total_items:
|
||||||
|
new_index = total_items - 1
|
||||||
|
|
||||||
|
self.update_selection(new_index)
|
||||||
|
|
||||||
|
def update_selection(self, new_index: int):
|
||||||
|
self.viewport.unselect_all()
|
||||||
|
path = Gtk.TreePath.new_from_indices([new_index])
|
||||||
|
self.viewport.select_path(path)
|
||||||
|
self.viewport.scroll_to_path(path, False, 0.5, 0.5)
|
||||||
|
self.selected_index = new_index
|
||||||
|
|
||||||
def _start_thumbnail_thread(self):
|
def _start_thumbnail_thread(self):
|
||||||
thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
|
thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
|
||||||
|
|
||||||
@@ -231,8 +292,15 @@ class WallpaperSelector(Box):
|
|||||||
if not os.path.exists(cache_path):
|
if not os.path.exists(cache_path):
|
||||||
try:
|
try:
|
||||||
with Image.open(full_path) as img:
|
with Image.open(full_path) as img:
|
||||||
img.thumbnail((96, 96), Image.Resampling.BILINEAR)
|
width, height = img.size
|
||||||
img.save(cache_path, "PNG")
|
side = min(width, height)
|
||||||
|
left = (width - side) // 2
|
||||||
|
top = (height - side) // 2
|
||||||
|
right = left + side
|
||||||
|
bottom = top + side
|
||||||
|
img_cropped = img.crop((left, top, right, bottom))
|
||||||
|
img_cropped.thumbnail((96, 96), Image.Resampling.LANCZOS)
|
||||||
|
img_cropped.save(cache_path, "PNG")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {file_name}: {e}")
|
print(f"Error processing {file_name}: {e}")
|
||||||
return
|
return
|
||||||
@@ -262,3 +330,25 @@ class WallpaperSelector(Box):
|
|||||||
return file_name.lower().endswith(
|
return file_name.lower().endswith(
|
||||||
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
|
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_search_entry_focus_out(self, widget, event):
|
||||||
|
if self.get_mapped():
|
||||||
|
widget.grab_focus()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def toggle_dark_mode(self, *_):
|
||||||
|
current_state = self.check_dark_mode_state()
|
||||||
|
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)
|
||||||
|
icon_name = "dark_mode" if new_mode == "dark" else "light_mode"
|
||||||
|
if self.toggle_button.get_child():
|
||||||
|
self.toggle_button.remove(self.toggle_button.get_child())
|
||||||
|
self.toggle_button.add(MaterialIcon(icon_name))
|
||||||
|
self.toggle_button.show_all()
|
||||||
|
|
||||||
|
def check_dark_mode_state(self):
|
||||||
|
result = exec_shell_command(
|
||||||
|
"gsettings get org.gnome.desktop.interface color-scheme"
|
||||||
|
)
|
||||||
|
return result.strip().replace("'", "") == "prefer-dark"
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
from fabric.widgets.centerbox import CenterBox
|
from fabric.widgets.centerbox import CenterBox
|
||||||
from fabric.widgets.stack import Stack
|
from fabric.widgets.stack import Stack
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from fabric.widgets.wayland import WaylandWindow as Window
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
from gi.repository import Gtk
|
|
||||||
from modules.launcher.components import (
|
from modules.launcher.components import (
|
||||||
AppLauncher,
|
AppLauncher,
|
||||||
BluetoothConnections,
|
BluetoothConnections,
|
||||||
@@ -13,6 +11,7 @@ from modules.launcher.components import (
|
|||||||
TodoManager,
|
TodoManager,
|
||||||
WallpaperSelector,
|
WallpaperSelector,
|
||||||
WifiManager,
|
WifiManager,
|
||||||
|
Calendar,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +34,7 @@ class Launcher(Window):
|
|||||||
self.bluetooth = BluetoothConnections()
|
self.bluetooth = BluetoothConnections()
|
||||||
self.sh = Sh(launcher=self)
|
self.sh = Sh(launcher=self)
|
||||||
self.wifi = WifiManager()
|
self.wifi = WifiManager()
|
||||||
|
self.calendar = Calendar()
|
||||||
|
|
||||||
self.stack = Stack(
|
self.stack = Stack(
|
||||||
name="launcher-content",
|
name="launcher-content",
|
||||||
@@ -52,6 +52,7 @@ class Launcher(Window):
|
|||||||
self.bluetooth,
|
self.bluetooth,
|
||||||
self.sh,
|
self.sh,
|
||||||
self.wifi,
|
self.wifi,
|
||||||
|
self.calendar,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,7 +65,6 @@ class Launcher(Window):
|
|||||||
self.add(self.launcher_box)
|
self.add(self.launcher_box)
|
||||||
self.show_all()
|
self.show_all()
|
||||||
self.hide()
|
self.hide()
|
||||||
self.wallpapers.viewport.hide()
|
|
||||||
self.add_keybinding("Escape", lambda *_: self.close())
|
self.add_keybinding("Escape", lambda *_: self.close())
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@@ -81,11 +81,8 @@ class Launcher(Window):
|
|||||||
self.bluetooth,
|
self.bluetooth,
|
||||||
self.sh,
|
self.sh,
|
||||||
self.wifi,
|
self.wifi,
|
||||||
|
self.calendar,
|
||||||
]:
|
]:
|
||||||
if widget == self.wallpapers:
|
|
||||||
self.wallpapers.viewport.hide()
|
|
||||||
self.wallpapers.viewport.set_property("name", None)
|
|
||||||
|
|
||||||
if hasattr(widget, "viewport") and widget.viewport:
|
if hasattr(widget, "viewport") and widget.viewport:
|
||||||
widget.viewport.hide()
|
widget.viewport.hide()
|
||||||
|
|
||||||
@@ -99,6 +96,7 @@ class Launcher(Window):
|
|||||||
"bluetooth",
|
"bluetooth",
|
||||||
"sh",
|
"sh",
|
||||||
"wifi",
|
"wifi",
|
||||||
|
"calendar",
|
||||||
]:
|
]:
|
||||||
self.stack.remove_style_class(style)
|
self.stack.remove_style_class(style)
|
||||||
|
|
||||||
@@ -118,6 +116,7 @@ class Launcher(Window):
|
|||||||
"bluetooth": self.bluetooth,
|
"bluetooth": self.bluetooth,
|
||||||
"sh": self.sh,
|
"sh": self.sh,
|
||||||
"wifi": self.wifi,
|
"wifi": self.wifi,
|
||||||
|
"calendar": self.calendar,
|
||||||
}
|
}
|
||||||
|
|
||||||
for w in widgets.values():
|
for w in widgets.values():
|
||||||
@@ -130,13 +129,6 @@ class Launcher(Window):
|
|||||||
self.stack.set_visible_child(widgets[widget])
|
self.stack.set_visible_child(widgets[widget])
|
||||||
widgets[widget].show()
|
widgets[widget].show()
|
||||||
|
|
||||||
# if widget != "launcher":
|
|
||||||
# self.launcher.hide()
|
|
||||||
|
|
||||||
if widget != "wallpapers":
|
|
||||||
self.wallpapers.viewport.hide()
|
|
||||||
self.wallpapers.viewport.set_property("name", None)
|
|
||||||
|
|
||||||
if widget == "launcher":
|
if widget == "launcher":
|
||||||
self.launcher.open_launcher()
|
self.launcher.open_launcher()
|
||||||
self.launcher.search_entry.set_text("")
|
self.launcher.search_entry.set_text("")
|
||||||
@@ -146,7 +138,6 @@ class Launcher(Window):
|
|||||||
self.wallpapers.search_entry.set_text("")
|
self.wallpapers.search_entry.set_text("")
|
||||||
self.wallpapers.search_entry.grab_focus()
|
self.wallpapers.search_entry.grab_focus()
|
||||||
self.wallpapers.viewport.show()
|
self.wallpapers.viewport.show()
|
||||||
self.wallpapers.viewport.set_property("name", "wallpaper-icons")
|
|
||||||
|
|
||||||
elif widget == "emoji":
|
elif widget == "emoji":
|
||||||
self.emoji.open_launcher()
|
self.emoji.open_launcher()
|
||||||
|
|||||||
+85
-200
@@ -1,228 +1,113 @@
|
|||||||
import time
|
import time
|
||||||
from typing import ClassVar, Literal
|
|
||||||
from fabric.utils import invoke_repeater
|
|
||||||
from fabric.widgets.box import Box
|
|
||||||
from fabric.widgets.revealer import Revealer
|
|
||||||
from fabric.widgets.scale import Scale, ScaleMark
|
|
||||||
from fabric.widgets.wayland import WaylandWindow as Window
|
from fabric.widgets.wayland import WaylandWindow as Window
|
||||||
from gi.repository import GObject
|
from fabric.widgets.label import Label
|
||||||
from snippets import Animator, MaterialIcon
|
from fabric.widgets.box import Box
|
||||||
|
from gi.repository import GLib, GObject
|
||||||
|
from fabric.widgets.centerbox import CenterBox
|
||||||
from services import brightness, audio
|
from services import brightness, audio
|
||||||
|
from snippets import Animator, MaterialIcon
|
||||||
|
|
||||||
|
|
||||||
class AnimatedScale(Scale):
|
def create_progress_bar(percentage, width=150):
|
||||||
def __init__(self, **kwargs):
|
container = Box(orientation="h", name="progress-container")
|
||||||
super().__init__(**kwargs)
|
progress = Box(
|
||||||
self.animator = None # Lazily initialized
|
name="progress-fill", style=f"min-width: {percentage * width / 100}px;"
|
||||||
|
)
|
||||||
def animate_value(self, value: float):
|
background = Box(name="progress-background", style=f"min-width: {width}px;")
|
||||||
if not self.animator:
|
background.children = [progress]
|
||||||
self.animator = Animator(
|
container.children = [background]
|
||||||
bezier_curve=(0.34, 1.56, 0.64, 1.0),
|
return container
|
||||||
duration=0.8,
|
|
||||||
min_value=self.min_value,
|
|
||||||
max_value=self.value,
|
|
||||||
tick_widget=self,
|
|
||||||
notify_value=lambda p, *_: self.set_value(p.value),
|
|
||||||
)
|
|
||||||
self.animator.pause()
|
|
||||||
self.animator.min_value = self.value
|
|
||||||
self.animator.max_value = value
|
|
||||||
self.animator.play()
|
|
||||||
|
|
||||||
|
|
||||||
class BrightnessOSDContainer(Box):
|
def update_progress_bar(progress_bar, percentage, width=150):
|
||||||
def __init__(self, **kwargs):
|
progress_fill = progress_bar.children[0].children[0]
|
||||||
super().__init__(**kwargs, orientation="h", spacing=12, name="osd")
|
progress_fill.set_style(f"min-width: {percentage * width / 100}px;")
|
||||||
self.brightness_service = brightness
|
|
||||||
self.icon = MaterialIcon(icon_name="brightness_7", size=20)
|
|
||||||
self.scale = AnimatedScale(
|
|
||||||
marks=(ScaleMark(value=i) for i in range(0, 101, 10)),
|
|
||||||
value=70,
|
|
||||||
min_value=0,
|
|
||||||
max_value=100,
|
|
||||||
increments=(1, 1),
|
|
||||||
orientation="h",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add(self.icon)
|
|
||||||
self.add(self.scale)
|
|
||||||
self.update_brightness()
|
|
||||||
|
|
||||||
self.scale.connect("value-changed", lambda *_: self.update_brightness())
|
|
||||||
self.brightness_service.connect("screen", self.on_brightness_changed)
|
|
||||||
|
|
||||||
def update_brightness(self) -> None:
|
|
||||||
current_brightness = self.brightness_service.screen_brightness
|
|
||||||
if current_brightness != 0:
|
|
||||||
normalized_brightness = self._normalize_brightness(current_brightness)
|
|
||||||
self.scale.animate_value(normalized_brightness)
|
|
||||||
self.update_icon(normalized_brightness)
|
|
||||||
|
|
||||||
def update_icon(self, brightness_percentage: float) -> None:
|
|
||||||
icon_name = self.get_brightness_icon(brightness_percentage)
|
|
||||||
self.icon.set_label(icon_name)
|
|
||||||
|
|
||||||
def on_brightness_changed(self, _sender: any, value: float, *_args) -> None:
|
|
||||||
normalized_brightness = self._normalize_brightness(value)
|
|
||||||
self.scale.animate_value(normalized_brightness)
|
|
||||||
self.update_icon(normalized_brightness)
|
|
||||||
|
|
||||||
def _normalize_brightness(self, brightness: float) -> float:
|
|
||||||
return (brightness / self.brightness_service.max_screen) * 100
|
|
||||||
|
|
||||||
def get_brightness_icon(self, brightness_percentage: float) -> str:
|
|
||||||
brightness_icons = [
|
|
||||||
"brightness_1",
|
|
||||||
"brightness_2",
|
|
||||||
"brightness_3",
|
|
||||||
"brightness_4",
|
|
||||||
"brightness_5",
|
|
||||||
"brightness_6",
|
|
||||||
"brightness_7",
|
|
||||||
]
|
|
||||||
thresholds = [
|
|
||||||
100 / (1.6 ** (len(brightness_icons) - i - 1))
|
|
||||||
for i in range(len(brightness_icons))
|
|
||||||
]
|
|
||||||
|
|
||||||
for i, threshold in enumerate(thresholds):
|
|
||||||
if brightness_percentage < threshold:
|
|
||||||
return brightness_icons[i]
|
|
||||||
|
|
||||||
return brightness_icons[-1]
|
|
||||||
|
|
||||||
|
|
||||||
class AudioOSDContainer(Box):
|
def create_labeled_progress(label_text, value_text, percentage):
|
||||||
__gsignals__: ClassVar[dict] = {
|
label = Label(name="osd-label", markup=label_text)
|
||||||
"volume-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()),
|
value = Label(name="osd-value", markup=value_text)
|
||||||
}
|
header = CenterBox(orientation="h", start_children=[label], end_children=[value])
|
||||||
|
progress = create_progress_bar(percentage)
|
||||||
def __init__(self, **kwargs):
|
return Box(
|
||||||
super().__init__(**kwargs, orientation="h", spacing=13, name="osd")
|
name="control-section", orientation="v", spacing=5, children=[header, progress]
|
||||||
self.audio = audio
|
)
|
||||||
self.icon = MaterialIcon(icon_name="volume_up", size=20)
|
|
||||||
self.scale = AnimatedScale(
|
|
||||||
marks=(ScaleMark(value=i) for i in range(1, 100, 10)),
|
|
||||||
value=70,
|
|
||||||
min_value=0,
|
|
||||||
max_value=100,
|
|
||||||
increments=(1, 1),
|
|
||||||
orientation="h",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add(self.icon)
|
|
||||||
self.add(self.scale)
|
|
||||||
self.sync_with_audio()
|
|
||||||
|
|
||||||
self.scale.connect("value-changed", self.on_volume_changed)
|
|
||||||
self.audio.connect("notify::speaker", self.on_audio_speaker_changed)
|
|
||||||
|
|
||||||
def sync_with_audio(self):
|
|
||||||
if self.audio.speaker:
|
|
||||||
volume = round(self.audio.speaker.volume)
|
|
||||||
self.scale.set_value(volume)
|
|
||||||
self.update_icon(volume)
|
|
||||||
|
|
||||||
def on_volume_changed(self, *_):
|
|
||||||
if self.audio.speaker:
|
|
||||||
volume = self.scale.value
|
|
||||||
if 0 <= volume <= 100:
|
|
||||||
self.audio.speaker.set_volume(volume)
|
|
||||||
self.update_icon(volume)
|
|
||||||
self.emit("volume-changed")
|
|
||||||
|
|
||||||
def on_audio_speaker_changed(self, *_):
|
|
||||||
if self.audio.speaker:
|
|
||||||
self.audio.speaker.connect("notify::volume", self.update_volume)
|
|
||||||
self.update_volume()
|
|
||||||
|
|
||||||
def update_volume(self, *_):
|
|
||||||
if self.audio.speaker and not self.is_hovered():
|
|
||||||
volume = round(self.audio.speaker.volume)
|
|
||||||
self.scale.set_value(volume)
|
|
||||||
self.update_icon(volume)
|
|
||||||
|
|
||||||
def update_icon(self, volume):
|
|
||||||
icon_name = self._get_audio_icon_name(volume)
|
|
||||||
self.icon.set_label(icon_name)
|
|
||||||
|
|
||||||
def _get_audio_icon_name(self, volume: int) -> str:
|
|
||||||
if volume == 101:
|
|
||||||
return "sound_detection_loud_sound"
|
|
||||||
elif volume >= 67:
|
|
||||||
return "volume_up"
|
|
||||||
elif volume >= 34:
|
|
||||||
return "volume_down"
|
|
||||||
elif volume >= 1:
|
|
||||||
return "volume_mute"
|
|
||||||
else:
|
|
||||||
return "volume_off"
|
|
||||||
|
|
||||||
|
|
||||||
class OSDContainer(Window):
|
def get_brightness():
|
||||||
def __init__(self, **kwargs):
|
return (
|
||||||
self.audio_container = AudioOSDContainer()
|
round((brightness.screen_brightness / brightness.max_screen) * 100)
|
||||||
self.brightness_container = BrightnessOSDContainer()
|
if brightness.screen_brightness
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
self.timeout = 1000
|
|
||||||
|
|
||||||
self.revealer = Revealer(
|
def get_volume():
|
||||||
transition_type="slide-right",
|
return round(audio.speaker.volume) if audio.speaker else 0
|
||||||
transition_duration=200,
|
|
||||||
child_revealed=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.main_box = Box(
|
|
||||||
orientation="v",
|
|
||||||
spacing=13,
|
|
||||||
children=[self.revealer],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class OSD(Window):
|
||||||
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
name="osd-menu",
|
||||||
layer="overlay",
|
layer="overlay",
|
||||||
anchor="bottom",
|
anchor="bottom",
|
||||||
child=self.main_box,
|
margin="0 0 10px 0",
|
||||||
visible=False,
|
|
||||||
pass_through=True,
|
|
||||||
keyboard_mode="on-demand",
|
keyboard_mode="on-demand",
|
||||||
**kwargs,
|
visible=False,
|
||||||
|
style_classes="osd-panel",
|
||||||
)
|
)
|
||||||
|
self.last_volume = get_volume()
|
||||||
self.last_activity_time = time.time()
|
self.last_brightness = get_brightness()
|
||||||
|
self.volume_container = create_labeled_progress(
|
||||||
self.audio_container.audio.connect("notify::speaker", self.show_audio)
|
"Speaker", str(self.last_volume), self.last_volume
|
||||||
self.brightness_container.brightness_service.connect(
|
|
||||||
"screen", self.show_brightness
|
|
||||||
)
|
)
|
||||||
self.audio_container.connect("volume-changed", self.show_audio)
|
self.brightness_container = create_labeled_progress(
|
||||||
|
"Brightness", str(self.last_brightness), self.last_brightness
|
||||||
|
)
|
||||||
|
self.children = Box(
|
||||||
|
orientation="h",
|
||||||
|
spacing=10,
|
||||||
|
children=[self.brightness_container, self.volume_container],
|
||||||
|
)
|
||||||
|
self.hide_timeout_id = None
|
||||||
|
self._connect_signals()
|
||||||
|
GLib.timeout_add(100, self._check_changes)
|
||||||
|
|
||||||
invoke_repeater(100, self.check_inactivity, initial_call=True)
|
def _connect_signals(self):
|
||||||
|
audio.connect("notify::speaker", self._update_volume)
|
||||||
|
brightness.connect("screen", self._update_brightness)
|
||||||
|
|
||||||
def show_audio(self, *_):
|
def _update_volume(self, *_):
|
||||||
self.show_box(box_to_show="audio")
|
volume = get_volume()
|
||||||
self.reset_inactivity_timer()
|
self.volume_container.children[0].end_children[0].set_markup(str(volume))
|
||||||
|
update_progress_bar(self.volume_container.children[1], volume)
|
||||||
|
self._reset_timeout()
|
||||||
|
|
||||||
def show_brightness(self, *_):
|
def _update_brightness(self, *_):
|
||||||
self.show_box(box_to_show="brightness")
|
brightness_level = get_brightness()
|
||||||
self.reset_inactivity_timer()
|
self.brightness_container.children[0].end_children[0].set_markup(
|
||||||
|
str(brightness_level)
|
||||||
|
)
|
||||||
|
update_progress_bar(self.brightness_container.children[1], brightness_level)
|
||||||
|
self._reset_timeout()
|
||||||
|
|
||||||
def show_box(self, box_to_show: Literal["audio", "brightness"]):
|
def _reset_timeout(self):
|
||||||
self.set_visible(True)
|
if self.hide_timeout_id:
|
||||||
if box_to_show == "audio":
|
GLib.source_remove(self.hide_timeout_id)
|
||||||
self.revealer.children = self.audio_container
|
self.hide_timeout_id = GLib.timeout_add(1500, self._hide_osd)
|
||||||
elif box_to_show == "brightness":
|
|
||||||
self.revealer.children = self.brightness_container
|
|
||||||
self.revealer.set_reveal_child(True)
|
|
||||||
self.reset_inactivity_timer()
|
|
||||||
|
|
||||||
def start_hide_timer(self):
|
def _hide_osd(self):
|
||||||
self.set_visible(False)
|
self.hide()
|
||||||
|
self.hide_timeout_id = None
|
||||||
|
return False
|
||||||
|
|
||||||
def reset_inactivity_timer(self):
|
def _check_changes(self):
|
||||||
self.last_activity_time = time.time()
|
volume = get_volume()
|
||||||
|
brightness_level = get_brightness()
|
||||||
def check_inactivity(self):
|
if volume != self.last_volume or brightness_level != self.last_brightness:
|
||||||
if time.time() - self.last_activity_time >= (self.timeout / 1000):
|
self.last_volume, self.last_brightness = volume, brightness_level
|
||||||
self.start_hide_timer()
|
self.show_all()
|
||||||
|
self._update_volume()
|
||||||
|
self._update_brightness()
|
||||||
return True
|
return True
|
||||||
+86
-1
@@ -43,6 +43,11 @@
|
|||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#launcher-content.calendar {
|
||||||
|
min-height: 420px;
|
||||||
|
min-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
#launcher-content {
|
#launcher-content {
|
||||||
min-width: 420px;
|
min-width: 420px;
|
||||||
}
|
}
|
||||||
@@ -63,7 +68,7 @@
|
|||||||
color: @onSecondaryContainer;
|
color: @onSecondaryContainer;
|
||||||
margin: 11px;
|
margin: 11px;
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
min-width: 54px;
|
min-width: 48px;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
@@ -125,6 +130,9 @@
|
|||||||
/*
|
/*
|
||||||
* ── wallpaper ─────────────────────────────────────────────────────────
|
* ── wallpaper ─────────────────────────────────────────────────────────
|
||||||
*/
|
*/
|
||||||
|
#search-entry-walls {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
#wallpaper-icons:selected {
|
#wallpaper-icons:selected {
|
||||||
background-color: @surfaceBright;
|
background-color: @surfaceBright;
|
||||||
@@ -410,3 +418,80 @@
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ── calendar ──────────────────────────────────────────────────────────
|
||||||
|
*/
|
||||||
|
|
||||||
|
#header {
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#weekday-row {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#month-label,
|
||||||
|
#weekday-label,
|
||||||
|
#day-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#day-label,
|
||||||
|
#day-empty {
|
||||||
|
min-width: 41px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#day-empty {
|
||||||
|
color: @surfaceBright;
|
||||||
|
}
|
||||||
|
|
||||||
|
#weekday-label {
|
||||||
|
color: @primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar-grid {
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#day-label.current-day {
|
||||||
|
background-color: @primary;
|
||||||
|
color: @onPrimary;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prev-month-button,
|
||||||
|
#next-month-button {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prev-month-button:hover,
|
||||||
|
#next-month-button:hover {
|
||||||
|
background-color: @surfaceBright;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prev-month-button:active,
|
||||||
|
#next-month-button:active {
|
||||||
|
background-color: @primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
#month-button-label {
|
||||||
|
color: @primary;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prev-month-button:active #month-button-label,
|
||||||
|
#next-month-button:active #month-button-label {
|
||||||
|
color: @surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
#prev-month-day,
|
||||||
|
#next-month-day {
|
||||||
|
/*color: lighter(@surfaceBright);*/
|
||||||
|
color: mix(@onSurfaceVariant, transparent, 0.6);
|
||||||
|
}
|
||||||
+33
-31
@@ -1,42 +1,44 @@
|
|||||||
#osd {
|
.osd-panel {
|
||||||
background: @surfaceContainer;
|
background-color: transparent;
|
||||||
padding: 10px 4px 10px 4px;
|
|
||||||
border-radius: 14px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
min-width: 190px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#osd scale {
|
#osd-label {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#osd-value {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-container {
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 12px;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#osd slider {
|
#progress-fill {
|
||||||
color: @onPrimary;
|
|
||||||
background-color: @primary;
|
background-color: @primary;
|
||||||
background-size: 20px 20px;
|
border-radius: 6px;
|
||||||
min-width: 5px;
|
min-height: 8px;
|
||||||
min-height: 14px;
|
transition: min-width 0.3s ease-in-out;
|
||||||
border-radius: 100px;
|
|
||||||
padding: 0px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
margin-top: -16px;
|
|
||||||
margin-bottom: -16px;
|
|
||||||
box-shadow:
|
|
||||||
-4px 0px 0px 1px @surfaceContainer,
|
|
||||||
4px 0px 0px 1px @surfaceContainer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#osd trough {
|
#progress-background {
|
||||||
background: @secondaryContainer;
|
background-color: @surfaceContainer;
|
||||||
min-height: 10px;
|
border-radius: 6px;
|
||||||
margin-right: 4px;
|
min-height: 8px;
|
||||||
border-radius: 100px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#osd highlight {
|
#control-section {
|
||||||
border-radius: 100px 0px 0px 100px;
|
padding: 10px;
|
||||||
background: @primary;
|
border-radius: 10px;
|
||||||
|
background-color: @surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
.osd-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user