feat(update): huge changes
This commit is contained in:
1 parent
2b044338b9
commit
aa9eebb9e7
98 files changed
+9636
-5575
No files matched your search
+2
-23
@@ -1,25 +1,4 @@
|
||||
from .custom_image import *
|
||||
from .icon_resolver import *
|
||||
import json
|
||||
import os
|
||||
from fabric.utils import get_relative_path
|
||||
|
||||
|
||||
def read_config():
|
||||
config_path = get_relative_path("../config/assets/modus.json")
|
||||
|
||||
with open(config_path, "r") as file:
|
||||
# Load JSON data into a Python dictionary
|
||||
data = json.load(file)
|
||||
return data
|
||||
|
||||
|
||||
default_wallpapers_dir = get_relative_path("../assets/wallpaper")
|
||||
json_config_path = get_relative_path("../config/assets/config.json")
|
||||
|
||||
if os.path.exists(json_config_path):
|
||||
with open(json_config_path, "r") as f:
|
||||
config = json.load(f)
|
||||
WALLPAPERS_DIR = config.get("wallpapers_dir", default_wallpapers_dir)
|
||||
else:
|
||||
WALLPAPERS_DIR = default_wallpapers_dir
|
||||
from .occlusion import *
|
||||
from .animator import *
|
||||
@@ -0,0 +1,179 @@
|
||||
from typing import cast
|
||||
|
||||
from fabric import Property, Service, Signal
|
||||
from gi.repository import GLib, Gtk
|
||||
|
||||
|
||||
class Animator(Service):
|
||||
@Signal
|
||||
def finished(self) -> None: ...
|
||||
|
||||
@Property(tuple[float, float, float, float], "read-write")
|
||||
def bezier_curve(self) -> tuple[float, float, float, float]:
|
||||
return self._bezier_curve
|
||||
|
||||
@bezier_curve.setter
|
||||
def bezier_curve(self, value: tuple[float, float, float, float]):
|
||||
self._bezier_curve = value
|
||||
return
|
||||
|
||||
@Property(float, "read-write")
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value: float):
|
||||
self._value = value
|
||||
return
|
||||
|
||||
@Property(float, "read-write")
|
||||
def max_value(self):
|
||||
return self._max_value
|
||||
|
||||
@max_value.setter
|
||||
def max_value(self, value: float):
|
||||
self._max_value = value
|
||||
return
|
||||
|
||||
@Property(float, "read-write")
|
||||
def min_value(self):
|
||||
return self._min_value
|
||||
|
||||
@min_value.setter
|
||||
def min_value(self, value: float):
|
||||
self._min_value = value
|
||||
return
|
||||
|
||||
@Property(bool, "read-write", default_value=False)
|
||||
def playing(self):
|
||||
return self._playing
|
||||
|
||||
@playing.setter
|
||||
def playing(self, value: bool):
|
||||
self._playing = value
|
||||
return
|
||||
|
||||
@Property(bool, "read-write", default_value=False)
|
||||
def repeat(self):
|
||||
return self._repeat
|
||||
|
||||
@repeat.setter
|
||||
def repeat(self, value: bool):
|
||||
self._repeat = value
|
||||
return
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bezier_curve: tuple[float, float, float, float],
|
||||
duration: float,
|
||||
min_value: float = 0.0,
|
||||
max_value: float = 1.0,
|
||||
repeat: bool = False,
|
||||
tick_widget: Gtk.Widget | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self._bezier_curve = (1, 0, 1, 1)
|
||||
self._duration = 5
|
||||
self._value = 0.0
|
||||
self._min_value = 0.0
|
||||
self._max_value = 1.0
|
||||
self._repeat = False
|
||||
|
||||
self.bezier_curve = bezier_curve
|
||||
self.duration = duration
|
||||
self.value = min_value
|
||||
self.min_value = min_value
|
||||
self.max_value = max_value
|
||||
self.repeat = repeat
|
||||
|
||||
self.playing = False
|
||||
self._start_time = None
|
||||
self._tick_handler = None
|
||||
self._timeline_pos = 0
|
||||
self._tick_widget = tick_widget
|
||||
|
||||
def do_get_time_now(self):
|
||||
return GLib.get_monotonic_time() / 1_000_000
|
||||
|
||||
def do_lerp(self, start: float, end: float, time: float) -> float:
|
||||
return start + (end - start) * time
|
||||
|
||||
def do_interpolate_cubic_bezier(self, time: float) -> float:
|
||||
y_points = (0, self.bezier_curve[1], self.bezier_curve[3], 1)
|
||||
return (
|
||||
(1 - time) ** 3 * y_points[0]
|
||||
+ 3 * (1 - time) ** 2 * time * y_points[1]
|
||||
+ 3 * (1 - time) * time**2 * y_points[2]
|
||||
+ time**3 * y_points[3]
|
||||
)
|
||||
|
||||
def do_ease(self, time: float) -> float:
|
||||
return self.do_lerp(
|
||||
self.min_value, self.max_value, self.do_interpolate_cubic_bezier(time)
|
||||
)
|
||||
|
||||
def do_update_value(self, delta_time: float):
|
||||
if not self.playing:
|
||||
return
|
||||
|
||||
elapsed_time = delta_time - cast(float, self._start_time)
|
||||
|
||||
self._timeline_pos = min(1, elapsed_time / self.duration)
|
||||
|
||||
self.value = self.do_ease(self._timeline_pos)
|
||||
|
||||
if not self._timeline_pos >= 1:
|
||||
return
|
||||
|
||||
if not self.repeat:
|
||||
self.value = self.max_value
|
||||
self.finished()
|
||||
self.pause()
|
||||
return
|
||||
|
||||
self._start_time = delta_time
|
||||
self._timeline_pos = 0
|
||||
return
|
||||
|
||||
def do_handle_tick(self, *_):
|
||||
current_time = self.do_get_time_now()
|
||||
self.do_update_value(current_time)
|
||||
return True
|
||||
|
||||
def do_remove_tick_handlers(self):
|
||||
if self._tick_handler:
|
||||
if self._tick_widget:
|
||||
self._tick_widget.remove_tick_callback(self._tick_handler)
|
||||
else:
|
||||
GLib.source_remove(self._tick_handler)
|
||||
self._tick_handler = None
|
||||
return
|
||||
|
||||
def play(self):
|
||||
if self.playing:
|
||||
return
|
||||
|
||||
self._start_time = self.do_get_time_now()
|
||||
|
||||
if not self._tick_handler:
|
||||
if self._tick_widget:
|
||||
self._tick_handler = self._tick_widget.add_tick_callback(
|
||||
self.do_handle_tick
|
||||
)
|
||||
else:
|
||||
self._tick_handler = GLib.timeout_add(16, self.do_handle_tick)
|
||||
|
||||
self.playing = True
|
||||
return
|
||||
|
||||
def pause(self):
|
||||
self.playing = False
|
||||
return self.do_remove_tick_handlers()
|
||||
|
||||
def stop(self):
|
||||
if not self._tick_handler:
|
||||
self._timeline_pos = 0
|
||||
self.playing = False
|
||||
return
|
||||
return self.do_remove_tick_handlers()
|
||||
+39
-40
@@ -8,31 +8,22 @@ gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import GLib, Gtk
|
||||
from loguru import logger
|
||||
|
||||
import config.data as data
|
||||
|
||||
# TODO WIP
|
||||
# Idea: nearest string matching algorithm
|
||||
# if already exists: retrieve from json
|
||||
# if found: stor in json: app_id -> icon_name
|
||||
# if not found: store in json -> misisng-icon
|
||||
|
||||
|
||||
CACHE_DIR = str(GLib.get_user_cache_dir()) + "/modus"
|
||||
ICON_CACHE_FILE = CACHE_DIR + "/icons.json"
|
||||
if not os.path.exists(CACHE_DIR):
|
||||
os.makedirs(CACHE_DIR)
|
||||
ICON_CACHE_FILE = data.CACHE_DIR + "/icons.json"
|
||||
if not os.path.exists(data.CACHE_DIR):
|
||||
os.makedirs(data.CACHE_DIR)
|
||||
|
||||
|
||||
class IconResolver:
|
||||
def __init__(
|
||||
self, default_applicaiton_icon: str = "application-x-executable-symbolic"
|
||||
):
|
||||
def __init__(self, default_applicaiton_icon: str = "application-x-executable-symbolic"):
|
||||
if os.path.exists(ICON_CACHE_FILE):
|
||||
f = open(ICON_CACHE_FILE)
|
||||
try:
|
||||
self._icon_dict = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
logger.info("[ICONS] Cache file does not exist or is corrupted")
|
||||
f.close()
|
||||
with open(ICON_CACHE_FILE) as f:
|
||||
try:
|
||||
self._icon_dict = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
logger.info("[ICONS] Cache file does not exist or is corrupted")
|
||||
self._icon_dict = {}
|
||||
else:
|
||||
self._icon_dict = {}
|
||||
|
||||
@@ -49,20 +40,33 @@ class IconResolver:
|
||||
return new_icon
|
||||
|
||||
def get_icon_pixbuf(self, app_id: str, size: int = 16):
|
||||
return Gtk.IconTheme.get_default().load_icon(
|
||||
self.get_icon_name(app_id),
|
||||
size,
|
||||
Gtk.IconLookupFlags.FORCE_SIZE,
|
||||
)
|
||||
icon_theme = Gtk.IconTheme.get_default()
|
||||
icon_name = self.get_icon_name(app_id)
|
||||
try:
|
||||
# Try to load the resolved icon.
|
||||
return icon_theme.load_icon(icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE)
|
||||
except GLib.Error as primary_error:
|
||||
logger.warning(
|
||||
f"Warning: Icon '{icon_name}' not found in theme. Error: {primary_error}"
|
||||
)
|
||||
try:
|
||||
# Fallback to the default application icon.
|
||||
return icon_theme.load_icon(
|
||||
self.default_applicaiton_icon, size, Gtk.IconLookupFlags.FORCE_SIZE
|
||||
)
|
||||
except GLib.Error as fallback_error:
|
||||
logger.error(
|
||||
f"Error: Fallback icon '{self.default_applicaiton_icon}' also not found. Error: {fallback_error}"
|
||||
)
|
||||
return None
|
||||
|
||||
def _store_new_icon(self, app_id: str, icon: str):
|
||||
self._icon_dict[app_id] = icon
|
||||
with open(ICON_CACHE_FILE, "w") as f:
|
||||
json.dump(self._icon_dict, f)
|
||||
f.close()
|
||||
|
||||
def _get_icon_from_desktop_file(self, desktop_file_path: str):
|
||||
# TODO: get icon in the [Desktop Entry] section only
|
||||
# Retrieve the icon specified in the [Desktop Entry] section.
|
||||
with open(desktop_file_path) as f:
|
||||
for line in f.readlines():
|
||||
if "Icon=" in line:
|
||||
@@ -72,32 +76,27 @@ class IconResolver:
|
||||
def _get_desktop_file(self, app_id: str) -> str | None:
|
||||
data_dirs = GLib.get_system_data_dirs()
|
||||
for data_dir in data_dirs:
|
||||
data_dir = data_dir + "/applications/"
|
||||
data_dir = os.path.join(data_dir, "applications")
|
||||
if os.path.exists(data_dir):
|
||||
# Do name resolving here
|
||||
|
||||
files = os.listdir(data_dir)
|
||||
matching = [
|
||||
s for s in files if "".join(app_id.lower().split()) in s.lower()
|
||||
]
|
||||
matching = [s for s in files if "".join(app_id.lower().split()) in s.lower()]
|
||||
if matching:
|
||||
return data_dir + matching[0]
|
||||
|
||||
return os.path.join(data_dir, matching[0])
|
||||
for word in list(filter(None, re.split(r"-|\.|_|\s", app_id))):
|
||||
matching = [s for s in files if word.lower() in s.lower()]
|
||||
if matching:
|
||||
return data_dir + matching[0]
|
||||
|
||||
return os.path.join(data_dir, matching[0])
|
||||
return None
|
||||
|
||||
def _compositor_find_icon(self, app_id: str):
|
||||
if Gtk.IconTheme.get_default().has_icon(app_id):
|
||||
icon_theme = Gtk.IconTheme.get_default()
|
||||
if icon_theme.has_icon(app_id):
|
||||
return app_id
|
||||
if Gtk.IconTheme.get_default().has_icon(app_id + "-desktop"):
|
||||
if icon_theme.has_icon(app_id + "-desktop"):
|
||||
return app_id + "-desktop"
|
||||
desktop_file = self._get_desktop_file(app_id)
|
||||
return (
|
||||
self._get_icon_from_desktop_file(desktop_file)
|
||||
if desktop_file
|
||||
else self.default_applicaiton_icon
|
||||
)
|
||||
)
|
||||
+52
-11
@@ -5,7 +5,7 @@ font_weight: str = "normal"
|
||||
span: str = f"<span font-family='{font_family}' font-weight='{font_weight}'>"
|
||||
|
||||
# Panels
|
||||
apps: str = ""
|
||||
apps: str = ""
|
||||
dashboard: str = ""
|
||||
chat: str = ""
|
||||
wallpapers: str = ""
|
||||
@@ -23,6 +23,23 @@ memory: str = "流"
|
||||
cpu: str = ""
|
||||
swap: str = ""
|
||||
|
||||
# Toolbox
|
||||
toolbox: str = ""
|
||||
ssfull: str = ""
|
||||
ssregion: str = ""
|
||||
screenshots: str = ""
|
||||
screenrecord: str = ""
|
||||
recordings: str = ""
|
||||
ocr: str = "ﳃ"
|
||||
gamemode: str = ""
|
||||
gamemode_off: str = ""
|
||||
close: str = ""
|
||||
bell: str = ""
|
||||
bell_off: str = ""
|
||||
notifications_on: str = ""
|
||||
notifications_off: str = ""
|
||||
notifications_clear: str = ""
|
||||
notifications: str = ""
|
||||
|
||||
# Wallpapers
|
||||
add: str = ""
|
||||
@@ -55,22 +72,38 @@ power_balanced: str = "勺"
|
||||
power_performance: str = ""
|
||||
charging: str = ""
|
||||
discharging: str = ""
|
||||
alert: str = ""
|
||||
alert: str = ""
|
||||
bat_charging: str = ""
|
||||
bat_discharging: str = ""
|
||||
bat_low: str = "="
|
||||
bat_full: str = ""
|
||||
|
||||
|
||||
moderate_signal: str = ""
|
||||
good_signal: str = ""
|
||||
no_signal: str = ""
|
||||
weak_signal: str = ""
|
||||
# Applets
|
||||
lan: str = ""
|
||||
wifi_0: str = ""
|
||||
wifi_1: str = ""
|
||||
wifi_2: str = ""
|
||||
wifi_3: str = ""
|
||||
wifi: str = ""
|
||||
world: str = ""
|
||||
world_off: str = ""
|
||||
bluetooth: str = ""
|
||||
night: str = ""
|
||||
coffee: str = ""
|
||||
dnd: str = ""
|
||||
darkmode: str = "﹖"
|
||||
world: str = ""
|
||||
|
||||
|
||||
wifi_off: str = ""
|
||||
bluetooth_off: str = ""
|
||||
night_off: str = ""
|
||||
dnd_off: str = ""
|
||||
mic_off: str = ""
|
||||
|
||||
|
||||
# Bluetooth
|
||||
bluetooth_connected: str = ""
|
||||
@@ -97,19 +130,20 @@ vol_mute: str = ""
|
||||
vol_medium: str = ""
|
||||
vol_high: str = ""
|
||||
|
||||
# Network
|
||||
lan: str = ""
|
||||
moderate_signal: str = ""
|
||||
good_signal: str = ""
|
||||
no_signal: str = ""
|
||||
weak_signal: str = ""
|
||||
|
||||
mic: str = ""
|
||||
mic_muted: str = ""
|
||||
|
||||
|
||||
# Overview
|
||||
circle_plus: str = ""
|
||||
|
||||
# Pins
|
||||
copy_plus: str = "ﶮ"
|
||||
paperclip: str = ""
|
||||
|
||||
# Clipboard Manager
|
||||
clipboard: str = ""
|
||||
clip_text: str = ""
|
||||
|
||||
# Confirm
|
||||
accept: str = ""
|
||||
@@ -130,6 +164,13 @@ disc_off: str = ""
|
||||
# Misc
|
||||
dot: str = ""
|
||||
palette: str = ""
|
||||
cloud_off: str = ""
|
||||
loader: str = ""
|
||||
radar: str = ""
|
||||
emoji: str = ""
|
||||
keyboard: str = ""
|
||||
terminal: str = ""
|
||||
|
||||
|
||||
exceptions: list[str] = ["font_family", "font_weight", "span"]
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
import config.data as data
|
||||
|
||||
|
||||
def get_current_workspace():
|
||||
"""
|
||||
Get the current workspace ID using hyprctl.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["hyprctl", "activeworkspace"], capture_output=True, text=True
|
||||
)
|
||||
# Assume the output similar to: "ID <number>"
|
||||
# Extracting the number from the output
|
||||
parts = result.stdout.split()
|
||||
for i, part in enumerate(parts):
|
||||
if part == "ID" and i + 1 < len(parts):
|
||||
return int(parts[i + 1])
|
||||
except Exception as e:
|
||||
print(f"Error getting current workspace: {e}")
|
||||
return -1
|
||||
|
||||
|
||||
def get_screen_dimensions():
|
||||
"""
|
||||
Get screen dimensions from hyprctl.
|
||||
|
||||
Returns:
|
||||
tuple: (width, height) of the monitor containing the current workspace
|
||||
"""
|
||||
try:
|
||||
# Get current workspace
|
||||
workspace_id = get_current_workspace()
|
||||
|
||||
# Get monitor information
|
||||
result = subprocess.run(
|
||||
["hyprctl", "-j", "monitors"], capture_output=True, text=True
|
||||
)
|
||||
monitors = json.loads(result.stdout)
|
||||
|
||||
# Find the monitor containing our workspace
|
||||
for monitor in monitors:
|
||||
if monitor.get("activeWorkspace", {}).get("id") == workspace_id:
|
||||
return monitor.get("width", data.CURRENT_WIDTH), monitor.get(
|
||||
"height", data.CURRENT_HEIGHT
|
||||
)
|
||||
|
||||
# Fallback to first monitor
|
||||
if monitors:
|
||||
return monitors[0].get("width", data.CURRENT_WIDTH), monitors[0].get(
|
||||
"height", data.CURRENT_HEIGHT
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error getting screen dimensions: {e}")
|
||||
|
||||
# Default fallback values
|
||||
return data.CURRENT_WIDTH, data.CURRENT_HEIGHT
|
||||
|
||||
|
||||
def check_occlusion(occlusion_region, workspace=None):
|
||||
"""
|
||||
Check if a region is occupied by any window on a given workspace.
|
||||
|
||||
Parameters:
|
||||
occlusion_region: Can be one of:
|
||||
- tuple (side, size): where side is "top", "bottom", "left", or "right"
|
||||
and size is the pixel width of the region
|
||||
- tuple (x, y, width, height): The full region coordinates (legacy format)
|
||||
workspace (int, optional): The workspace ID to check. If None, the current workspace is used.
|
||||
|
||||
Returns:
|
||||
bool: True if any window overlaps with the occlusion region, False otherwise.
|
||||
"""
|
||||
if workspace is None:
|
||||
workspace = get_current_workspace()
|
||||
|
||||
# Handle simplified side-based format
|
||||
if isinstance(occlusion_region, tuple) and len(occlusion_region) == 2:
|
||||
side, size = occlusion_region
|
||||
if isinstance(side, str):
|
||||
# Convert side-based format to coordinates
|
||||
screen_width, screen_height = get_screen_dimensions()
|
||||
|
||||
if side.lower() == "bottom":
|
||||
occlusion_region = (0, screen_height - size, screen_width, size)
|
||||
elif side.lower() == "top":
|
||||
occlusion_region = (0, 0, screen_width, size)
|
||||
elif side.lower() == "left":
|
||||
occlusion_region = (0, 0, size, screen_height)
|
||||
elif side.lower() == "right":
|
||||
occlusion_region = (screen_width - size, 0, size, screen_height)
|
||||
|
||||
# Ensure occlusion_region is in the correct format (x, y, width, height)
|
||||
if not isinstance(occlusion_region, tuple) or len(occlusion_region) != 4:
|
||||
print(f"Invalid occlusion region format: {occlusion_region}")
|
||||
return False
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["hyprctl", "-j", "clients"], capture_output=True, text=True
|
||||
)
|
||||
clients = json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
print(f"Error retrieving client windows: {e}")
|
||||
return False
|
||||
|
||||
occ_x, occ_y, occ_width, occ_height = occlusion_region
|
||||
occ_x2 = occ_x + occ_width
|
||||
occ_y2 = occ_y + occ_height
|
||||
|
||||
for client in clients:
|
||||
# Check if client is mapped
|
||||
if not client.get("mapped", False):
|
||||
continue
|
||||
|
||||
# Ensure client has proper workspace information and matches the workspace
|
||||
client_workspace = client.get("workspace", {})
|
||||
if client_workspace.get("id") != workspace:
|
||||
continue
|
||||
|
||||
# Ensure client has position and size info
|
||||
position = client.get("at")
|
||||
size = client.get("size")
|
||||
if not position or not size:
|
||||
continue
|
||||
|
||||
x, y = position
|
||||
width, height = size
|
||||
win_x1, win_y1 = x, y
|
||||
win_x2, win_y2 = x + width, y + height
|
||||
|
||||
# Check for intersection between the window and occlusion region
|
||||
if not (
|
||||
win_x2 <= occ_x or win_x1 >= occ_x2 or win_y2 <= occ_y or win_y1 >= occ_y2
|
||||
):
|
||||
return True # Occlusion region is occupied
|
||||
|
||||
return False # No window overlaps the occlusion region
|
||||
|
||||
Reference in new issue
Block a user