♻️ refactor(launcher): improve code structure and plugin functionality

This commit is contained in:
sankalp committed 2025-07-30 13:04:22 +05:45
1 parent 19a2b925d5
commit 0f07104205
31 files changed
+536 -618

No files matched your search

+3 -3
View File
@@ -2,6 +2,7 @@ import json
import logging
import cairo
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.utils import (
exec_shell_command,
@@ -17,13 +18,12 @@ from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, GLib, Gtk
import config.data as data
from modules.corners import MyCorner
from utils.icon_resolver import IconResolver
from utils.occlusion import check_occlusion
from utils.wayland import WaylandWindow as Window
from modules.corners import MyCorner
def read_config():
"""Read and return the full configuration from the JSON file, handling missing file."""
-4
View File
@@ -1197,8 +1197,6 @@ class Launcher(Window):
self.focus_mode = "search"
self._focus_search_entry_without_selection()
def _focus_search_entry_without_selection(self):
"""Focus search entry and position cursor at end without selecting text."""
# First grab focus
@@ -1245,8 +1243,6 @@ class Launcher(Window):
# Also try with a small delay as backup
GLib.timeout_add(CURSOR_POSITION_DELAY_MS, clear_selection)
def _navigate_results_forward(self):
"""Navigate to next result or wrap around to first result."""
if self.results and self.selected_index < len(self.results) - 1:
+115 -46
View File
@@ -82,6 +82,7 @@ class BashScriptsPlugin(PluginBase):
"""Get reference to the battery service."""
try:
from services.battery import Battery
self._battery_service = Battery()
except Exception as e:
print(f"BashScriptsPlugin: Error getting battery service: {e}")
@@ -95,7 +96,10 @@ class BashScriptsPlugin(PluginBase):
try:
# Check if profile proxy is available
if not hasattr(self._battery_service, '_profile_proxy') or not self._battery_service._profile_proxy:
if (
not hasattr(self._battery_service, "_profile_proxy")
or not self._battery_service._profile_proxy
):
print("BashScriptsPlugin: Power profile proxy not available")
return False
@@ -114,7 +118,7 @@ class BashScriptsPlugin(PluginBase):
profile_mapping = {
"power-saver": ["power-saver", "powersave", "power_saver"],
"balanced": ["balanced", "balance"],
"performance": ["performance", "performance-mode"]
"performance": ["performance", "performance-mode"],
}
# Find the actual profile name
@@ -129,18 +133,20 @@ class BashScriptsPlugin(PluginBase):
if actual_profile:
self._battery_service._profile_proxy.ActiveProfile = actual_profile
print(f"BashScriptsPlugin: Successfully set power profile to {actual_profile}")
print(
f"BashScriptsPlugin: Successfully set power profile to {actual_profile}"
)
return True
else:
print(f"BashScriptsPlugin: No matching profile found for '{profile}' in available profiles: {available_profiles}")
print(
f"BashScriptsPlugin: No matching profile found for '{profile}' in available profiles: {available_profiles}"
)
return False
except Exception as e:
print(f"BashScriptsPlugin: Error setting power profile: {e}")
return False
def _load_scripts_cache(self):
"""Load scripts cache from JSON file."""
try:
@@ -150,7 +156,9 @@ class BashScriptsPlugin(PluginBase):
self._scripts_cache = cache_data.get("scripts", {})
self._last_cache_update = cache_data.get("last_update", 0)
else:
print("BashScriptsPlugin: No cache file found, will build cache in background")
print(
"BashScriptsPlugin: No cache file found, will build cache in background"
)
except Exception as e:
print(f"BashScriptsPlugin: Error loading scripts cache: {e}")
self._scripts_cache = {}
@@ -172,16 +180,17 @@ class BashScriptsPlugin(PluginBase):
def _start_background_cache_update(self):
"""Start background thread to update scripts cache."""
current_time = time.time()
# Check if cache needs updating
if (current_time - self._last_cache_update > self._cache_update_interval or
not self._scripts_cache):
if (
current_time - self._last_cache_update > self._cache_update_interval
or not self._scripts_cache
):
if not self._cache_building:
self._cache_building = True
self._cache_thread = threading.Thread(
target=self._build_scripts_cache_background,
daemon=True
target=self._build_scripts_cache_background, daemon=True
)
self._cache_thread.start()
@@ -192,11 +201,15 @@ class BashScriptsPlugin(PluginBase):
new_cache = {}
# Scan Modus scripts directory for discovered scripts
if os.path.exists(self.modus_scripts_dir) and os.path.isdir(self.modus_scripts_dir):
if os.path.exists(self.modus_scripts_dir) and os.path.isdir(
self.modus_scripts_dir
):
try:
self._scan_directory_for_scripts(self.modus_scripts_dir, new_cache)
except (PermissionError, FileNotFoundError, OSError) as e:
print(f"BashScriptsPlugin: Error scanning Modus scripts directory: {e}")
print(
f"BashScriptsPlugin: Error scanning Modus scripts directory: {e}"
)
# Update cache atomically
self._scripts_cache = new_cache
@@ -228,11 +241,13 @@ class BashScriptsPlugin(PluginBase):
cache[script_name] = {
"path": script_path,
"name": script_name,
"description": self._get_script_description(script_path),
"description": self._get_script_description(
script_path
),
"type": "discovered",
"executable": os.access(script_path, os.X_OK),
"args": [],
"category": os.path.basename(directory)
"category": os.path.basename(directory),
}
except (PermissionError, FileNotFoundError, OSError) as e:
@@ -244,14 +259,16 @@ class BashScriptsPlugin(PluginBase):
"""Check if a file is a bash script."""
try:
# Check file extension first (most common case)
if file_path.endswith(('.sh', '.bash')):
if file_path.endswith((".sh", ".bash")):
return True
# For files without extension, check shebang
try:
with open(file_path, 'rb') as f:
first_line = f.readline(100).decode('utf-8', errors='ignore')
if first_line.startswith('#!') and ('bash' in first_line or 'sh' in first_line):
with open(file_path, "rb") as f:
first_line = f.readline(100).decode("utf-8", errors="ignore")
if first_line.startswith("#!") and (
"bash" in first_line or "sh" in first_line
):
return True
except (PermissionError, FileNotFoundError, UnicodeDecodeError):
pass
@@ -263,13 +280,13 @@ class BashScriptsPlugin(PluginBase):
def _get_script_description(self, script_path: str) -> str:
"""Extract description from script comments."""
try:
with open(script_path, 'r', encoding='utf-8', errors='ignore') as f:
with open(script_path, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
# Look for description in first few comment lines
for line in lines[:10]:
line = line.strip()
if line.startswith('#') and not line.startswith('#!'):
if line.startswith("#") and not line.startswith("#!"):
# Remove leading # and whitespace
desc = line[1:].strip()
if desc and len(desc) > 5: # Meaningful description
@@ -284,7 +301,9 @@ class BashScriptsPlugin(PluginBase):
query = query_string.strip()
# Start background update if needed (non-blocking)
if not self._scripts_cache or (time.time() - self._last_cache_update > self._cache_update_interval):
if not self._scripts_cache or (
time.time() - self._last_cache_update > self._cache_update_interval
):
self._start_background_cache_update()
results = []
@@ -313,11 +332,15 @@ class BashScriptsPlugin(PluginBase):
max_results = 20
# Sort scripts by name for consistent ordering
sorted_scripts = sorted(self._scripts_cache.items(), key=lambda x: x[1].get("name", ""))
sorted_scripts = sorted(
self._scripts_cache.items(), key=lambda x: x[1].get("name", "")
)
# Add scripts (limit to max_results)
for script_name, script_info in sorted_scripts:
script_results = self._create_script_results_with_args(script_name, script_info, 0.8)
script_results = self._create_script_results_with_args(
script_name, script_info, 0.8
)
for script_result in script_results:
if len(results) < max_results:
results.append(script_result)
@@ -345,7 +368,10 @@ class BashScriptsPlugin(PluginBase):
description_lower = script_info.get("description", "").lower()
# Skip if no match at all
if query_lower not in script_name_lower and query_lower not in description_lower:
if (
query_lower not in script_name_lower
and query_lower not in description_lower
):
continue
# Categorize matches
@@ -359,11 +385,15 @@ class BashScriptsPlugin(PluginBase):
description_matches.append((script_name, script_info, 0.5))
# Combine results in priority order
all_matches = exact_matches + prefix_matches + partial_matches + description_matches
all_matches = (
exact_matches + prefix_matches + partial_matches + description_matches
)
# Convert to Result objects
for script_name, script_info, relevance in all_matches:
script_results = self._create_script_results_with_args(script_name, script_info, relevance)
script_results = self._create_script_results_with_args(
script_name, script_info, relevance
)
for script_result in script_results:
if len(results) < max_results:
results.append(script_result)
@@ -387,10 +417,12 @@ class BashScriptsPlugin(PluginBase):
description_lower = info["description"].lower()
# Check for exact match or partial match
if (query_lower == cmd_lower or
cmd_lower.startswith(query_lower) or
query_lower in cmd_lower or
query_lower in description_lower):
if (
query_lower == cmd_lower
or cmd_lower.startswith(query_lower)
or query_lower in cmd_lower
or query_lower in description_lower
):
relevance = 1.0 if query_lower == cmd_lower else 0.9
@@ -401,7 +433,11 @@ class BashScriptsPlugin(PluginBase):
action=self._create_power_action(info["profile"]),
relevance=relevance,
plugin_name=self.display_name,
data={"command": cmd, "profile": info["profile"], "type": "power_manager"}
data={
"command": cmd,
"profile": info["profile"],
"type": "power_manager",
},
)
results.append(result)
@@ -419,7 +455,11 @@ class BashScriptsPlugin(PluginBase):
action=self._create_power_action(info["profile"]),
relevance=0.8,
plugin_name=self.display_name,
data={"command": cmd, "profile": info["profile"], "type": "power_manager"}
data={
"command": cmd,
"profile": info["profile"],
"type": "power_manager",
},
)
results.append(result)
@@ -427,6 +467,7 @@ class BashScriptsPlugin(PluginBase):
def _create_power_action(self, profile: str):
"""Create an action function for setting power profile."""
def action():
success = self._set_power_profile(profile)
if success:
@@ -439,16 +480,21 @@ class BashScriptsPlugin(PluginBase):
if app and hasattr(app, "launcher"):
launcher = app.launcher
if launcher and hasattr(launcher, "search_entry"):
def clear_search():
launcher.search_entry.set_text("")
return False
# Use a small delay to ensure the action completes first
GLib.timeout_add(50, clear_search)
except Exception as e:
print(f"BashScriptsPlugin: Could not clear search query: {e}")
return action
def _create_script_result(self, script_name: str, script_info: Dict, relevance: float) -> Result:
def _create_script_result(
self, script_name: str, script_info: Dict, relevance: float
) -> Result:
"""Create a Result object for a script."""
script_path = script_info.get("path", "")
description = script_info.get("description", "")
@@ -465,7 +511,9 @@ class BashScriptsPlugin(PluginBase):
if not executable:
subtitle_parts.append("(not executable)")
subtitle = " | ".join(subtitle_parts) if subtitle_parts else f"Execute: {script_name}"
subtitle = (
" | ".join(subtitle_parts) if subtitle_parts else f"Execute: {script_name}"
)
# Choose icon based on script type and status
if not executable:
@@ -482,10 +530,16 @@ class BashScriptsPlugin(PluginBase):
action=self._create_script_action(script_name, script_info),
relevance=relevance,
plugin_name=self.display_name,
data={"script_name": script_name, "script_path": script_path, "type": script_type}
data={
"script_name": script_name,
"script_path": script_path,
"type": script_type,
},
)
def _create_script_results_with_args(self, script_name: str, script_info: Dict, relevance: float) -> List[Result]:
def _create_script_results_with_args(
self, script_name: str, script_info: Dict, relevance: float
) -> List[Result]:
"""Create multiple Result objects for scripts that support arguments."""
results = []
@@ -495,7 +549,7 @@ class BashScriptsPlugin(PluginBase):
variants = [
("-rgb", "Pick RGB color"),
("-hex", "Pick HEX color"),
("-hsv", "Pick HSV color")
("-hsv", "Pick HSV color"),
]
for arg, desc in variants:
@@ -503,32 +557,48 @@ class BashScriptsPlugin(PluginBase):
title=f"{script_name} {arg}",
subtitle=f"{desc} | [scripts]",
icon_markup=icons.terminal,
action=self._create_script_action_with_args(script_name, script_info, [arg]),
relevance=relevance + 0.1, # Slightly higher relevance for specific variants
action=self._create_script_action_with_args(
script_name, script_info, [arg]
),
relevance=relevance
+ 0.1, # Slightly higher relevance for specific variants
plugin_name=self.display_name,
data={"script_name": script_name, "script_path": script_info.get("path", ""), "type": script_info.get("type", "discovered"), "args": [arg]}
data={
"script_name": script_name,
"script_path": script_info.get("path", ""),
"type": script_info.get("type", "discovered"),
"args": [arg],
},
)
results.append(variant_result)
else:
# For other scripts, create the basic result
basic_result = self._create_script_result(script_name, script_info, relevance)
basic_result = self._create_script_result(
script_name, script_info, relevance
)
results.append(basic_result)
return results
def _create_script_action(self, script_name: str, script_info: Dict):
"""Create an action function for executing a script."""
def action():
self._execute_script(script_name, script_info)
return action
def _create_script_action_with_args(self, script_name: str, script_info: Dict, args: List[str]):
def _create_script_action_with_args(
self, script_name: str, script_info: Dict, args: List[str]
):
"""Create an action function for executing a script with specific arguments."""
def action():
# Create a modified script_info with the specific arguments
modified_script_info = script_info.copy()
modified_script_info["args"] = args
self._execute_script(script_name, modified_script_info)
return action
def _execute_script(self, script_name: str, script_info: Dict):
@@ -549,4 +619,3 @@ class BashScriptsPlugin(PluginBase):
except Exception as e:
print(f"BashScriptsPlugin: Error executing script '{script_name}': {e}")
-2
View File
@@ -95,8 +95,6 @@ class CalculatorPlugin(PluginBase):
self._conversion_cache[cache_key] = result
subtitle = f"{value} {from_unit} = {result:.6g} {to_unit}"
return [
Result(
title=f"{result:.6g} {to_unit}",
+3 -3
View File
@@ -380,9 +380,9 @@ class ClipboardPlugin(PluginBase):
result = Result(
title=display_text,
subtitle="Text from clipboard",
description=content
if len(content) <= 100
else content[:97] + "...",
description=(
content if len(content) <= 100 else content[:97] + "..."
),
icon_name="edit-paste",
relevance=relevance,
plugin_name=self.name,
-1
View File
@@ -342,7 +342,6 @@ class ProcessPlugin(PluginBase):
if self._kill_process(pid, silent=True):
killed_count += 1
except Exception as e:
print(f"✗ Error killing process group: {e}")
+3 -2
View File
@@ -408,8 +408,9 @@ class RemindersPlugin(PluginBase):
target_time_str
})",
icon_markup=icons.timer_on,
action=lambda ts=time_str,
msg=message: self._create_and_confirm_reminder(ts, msg),
action=lambda ts=time_str, msg=message: self._create_and_confirm_reminder(
ts, msg
),
relevance=1.0,
plugin_name=self.display_name,
data={
+3 -2
View File
@@ -466,8 +466,9 @@ class ScreencapturePlugin(PluginBase):
title=f"Convert {file_param} to {format_type.upper()}",
subtitle=f"Convert specified file to {format_type} format",
icon_markup=screenrecord,
action=lambda fp=file_param,
ft=format_type: self._run_script_with_file(ft, fp),
action=lambda fp=file_param, ft=format_type: self._run_script_with_file(
ft, fp
),
relevance=1.0,
plugin_name=self.display_name,
)
+27 -16
View File
@@ -57,7 +57,9 @@ class SystemPlugin(PluginBase):
self._bin_cache = set(cache_data.get("binaries", []))
self._last_bin_update = cache_data.get("last_update", 0)
else:
print("SystemPlugin: No cache file found, will build cache in background")
print(
"SystemPlugin: No cache file found, will build cache in background"
)
except Exception as e:
print(f"SystemPlugin: Error loading binary cache: {e}")
self._bin_cache = set()
@@ -72,7 +74,7 @@ class SystemPlugin(PluginBase):
cache_data = {
"binaries": sorted(list(self._bin_cache)),
"last_update": self._last_bin_update,
"cache_version": "1.0"
"cache_version": "1.0",
}
with open(self.bin_cache_file, "w", encoding="utf-8") as f:
@@ -85,14 +87,15 @@ class SystemPlugin(PluginBase):
current_time = time.time()
# Check if cache needs updating
if (current_time - self._last_bin_update > self._bin_update_interval or
not self._bin_cache):
if (
current_time - self._last_bin_update > self._bin_update_interval
or not self._bin_cache
):
if not self._cache_building:
self._cache_building = True
self._cache_thread = threading.Thread(
target=self._build_bin_cache_background,
daemon=True
target=self._build_bin_cache_background, daemon=True
)
self._cache_thread.start()
@@ -113,8 +116,9 @@ class SystemPlugin(PluginBase):
# Use os.scandir for better performance than os.listdir
with os.scandir(path) as entries:
for entry in entries:
if (entry.is_file(follow_symlinks=False) and
os.access(entry.path, os.X_OK)):
if entry.is_file(follow_symlinks=False) and os.access(
entry.path, os.X_OK
):
new_cache.add(entry.name)
except (PermissionError, FileNotFoundError, OSError):
continue
@@ -126,7 +130,6 @@ class SystemPlugin(PluginBase):
# Save to disk
self._save_bin_cache()
except Exception as e:
print(f"SystemPlugin: Error building binary cache: {e}")
finally:
@@ -151,7 +154,9 @@ class SystemPlugin(PluginBase):
# Check system binaries
# Start background update if needed (non-blocking) - but only if cache is empty or very old
if not self._bin_cache or (time.time() - self._last_bin_update > self._bin_update_interval):
if not self._bin_cache or (
time.time() - self._last_bin_update > self._bin_update_interval
):
self._start_background_cache_update()
# Optimize search with early termination and result limiting
@@ -174,19 +179,25 @@ class SystemPlugin(PluginBase):
display_command = full_command
command_to_execute = full_command
relevance = 1.0
exact_matches.append((binary, display_command, command_to_execute, relevance))
exact_matches.append(
(binary, display_command, command_to_execute, relevance)
)
elif binary_lower.startswith(binary_query):
# Prefix match - high priority
display_command = binary
command_to_execute = binary
relevance = 0.9
prefix_matches.append((binary, display_command, command_to_execute, relevance))
prefix_matches.append(
(binary, display_command, command_to_execute, relevance)
)
else:
# Partial match - lower priority
display_command = binary
command_to_execute = binary
relevance = 0.7
partial_matches.append((binary, display_command, command_to_execute, relevance))
partial_matches.append(
(binary, display_command, command_to_execute, relevance)
)
# Early termination if we have enough good matches
if len(exact_matches) + len(prefix_matches) >= max_results:
@@ -196,7 +207,9 @@ class SystemPlugin(PluginBase):
all_matches = exact_matches + prefix_matches + partial_matches
# Convert to Result objects (limit to max_results)
for binary, display_command, command_to_execute, relevance in all_matches[:max_results]:
for binary, display_command, command_to_execute, relevance in all_matches[
:max_results
]:
result = Result(
title=display_command,
subtitle=f"Execute: {display_command}",
@@ -210,8 +223,6 @@ class SystemPlugin(PluginBase):
return results # Already sorted by priority
def _create_action(self, command: Union[str, List[str]]):
"""Create an action function for the given command."""
+72 -46
View File
@@ -46,8 +46,7 @@ class TmuxPlugin(PluginBase):
"""Start background thread to refresh session cache."""
if not self.refresh_thread or not self.refresh_thread.is_alive():
self.refresh_thread = threading.Thread(
target=self._refresh_sessions_background,
daemon=True
target=self._refresh_sessions_background, daemon=True
)
self.refresh_thread.start()
@@ -59,7 +58,7 @@ class TmuxPlugin(PluginBase):
if current_time - self._last_cache_update > self._cache_ttl:
self._sessions_cache = self._get_tmux_sessions()
self._last_cache_update = current_time
# Sleep for a short interval
self.stop_refresh.wait(1)
except Exception as e:
@@ -73,12 +72,18 @@ class TmuxPlugin(PluginBase):
["tmux", "list-sessions", "-F", "#{session_name}"],
capture_output=True,
text=True,
timeout=5
timeout=5,
)
if result.returncode == 0:
return [s.strip() for s in result.stdout.strip().split('\n') if s.strip()]
return [
s.strip() for s in result.stdout.strip().split("\n") if s.strip()
]
return []
except (subprocess.TimeoutExpired, subprocess.CalledProcessError, FileNotFoundError) as e:
except (
subprocess.TimeoutExpired,
subprocess.CalledProcessError,
FileNotFoundError,
) as e:
print(f"TmuxPlugin: Error getting tmux sessions: {e}")
return []
@@ -92,28 +97,32 @@ class TmuxPlugin(PluginBase):
if current_time - self._last_cache_update > self._cache_ttl:
self._sessions_cache = self._get_tmux_sessions()
self._last_cache_update = current_time
sessions = self._sessions_cache
# Handle specific commands
if query.startswith("new ") or query.startswith("create "):
session_name = query.split(" ", 1)[1].strip() if " " in query else ""
results.append(self._create_new_session_result(session_name))
elif query.startswith("kill ") or query.startswith("delete "):
session_name = query.split(" ", 1)[1].strip() if " " in query else ""
if session_name:
matching_sessions = [s for s in sessions if session_name.lower() in s.lower()]
matching_sessions = [
s for s in sessions if session_name.lower() in s.lower()
]
for session in matching_sessions:
results.append(self._create_kill_session_result(session))
elif query.startswith("rename "):
parts = query.split(" ", 2)
if len(parts) >= 3:
old_name, new_name = parts[1], parts[2]
if old_name in sessions:
results.append(self._create_rename_session_result(old_name, new_name))
results.append(
self._create_rename_session_result(old_name, new_name)
)
else:
# Show existing sessions for attachment
if sessions:
@@ -122,13 +131,19 @@ class TmuxPlugin(PluginBase):
filtered_sessions = [s for s in sessions if query in s.lower()]
else:
filtered_sessions = sessions
for session in filtered_sessions:
results.append(self._create_attach_session_result(session))
# Always show option to create new session
if not query or "new" in query or "create" in query:
results.append(self._create_new_session_result(query if query and not any(cmd in query for cmd in ["new", "create"]) else ""))
results.append(
self._create_new_session_result(
query
if query and not any(cmd in query for cmd in ["new", "create"])
else ""
)
)
return results
@@ -140,7 +155,7 @@ class TmuxPlugin(PluginBase):
icon_markup=icons.terminal,
action=lambda: self._attach_to_session(session_name),
relevance=0.9,
data={"type": "attach", "session": session_name}
data={"type": "attach", "session": session_name},
)
def _create_new_session_result(self, session_name: str = "") -> Result:
@@ -152,7 +167,7 @@ class TmuxPlugin(PluginBase):
icon_markup=icons.plus,
action=lambda: self._create_session(session_name),
relevance=0.8,
data={"type": "create", "session": session_name}
data={"type": "create", "session": session_name},
)
def _create_kill_session_result(self, session_name: str) -> Result:
@@ -163,7 +178,7 @@ class TmuxPlugin(PluginBase):
icon_markup=icons.trash,
action=lambda: self._kill_session(session_name),
relevance=0.7,
data={"type": "kill", "session": session_name}
data={"type": "kill", "session": session_name},
)
def _create_rename_session_result(self, old_name: str, new_name: str) -> Result:
@@ -174,14 +189,15 @@ class TmuxPlugin(PluginBase):
icon_markup=icons.config,
action=lambda: self._rename_session(old_name, new_name),
relevance=0.6,
data={"type": "rename", "old_session": old_name, "new_session": new_name}
data={"type": "rename", "old_session": old_name, "new_session": new_name},
)
def _attach_to_session(self, session_name: str):
"""Attach to an existing tmux session."""
try:
terminal_cmd = self._get_terminal_command(f"tmux attach-session -t '{session_name}'")
terminal_cmd = self._get_terminal_command(
f"tmux attach-session -t '{session_name}'"
)
exec_shell_command_async(terminal_cmd)
print(f"TmuxPlugin: Attaching to session '{session_name}'")
except Exception as e:
@@ -197,25 +213,25 @@ class TmuxPlugin(PluginBase):
while str(counter) in existing_sessions:
counter += 1
session_name = str(counter)
# Clean the session name
clean_name = session_name.strip().replace(" ", "_")
# Create session
subprocess.run(
["tmux", "new-session", "-d", "-s", clean_name],
check=True,
timeout=10
["tmux", "new-session", "-d", "-s", clean_name], check=True, timeout=10
)
# Launch terminal and attach
terminal_cmd = self._get_terminal_command(f"tmux attach-session -t '{clean_name}'")
terminal_cmd = self._get_terminal_command(
f"tmux attach-session -t '{clean_name}'"
)
exec_shell_command_async(terminal_cmd)
# Refresh cache
self._sessions_cache = self._get_tmux_sessions()
self._last_cache_update = time.time()
print(f"TmuxPlugin: Created and attached to session '{clean_name}'")
except Exception as e:
print(f"TmuxPlugin: Error creating session '{session_name}': {e}")
@@ -224,15 +240,13 @@ class TmuxPlugin(PluginBase):
"""Kill a tmux session."""
try:
subprocess.run(
["tmux", "kill-session", "-t", session_name],
check=True,
timeout=10
["tmux", "kill-session", "-t", session_name], check=True, timeout=10
)
# Refresh cache
self._sessions_cache = self._get_tmux_sessions()
self._last_cache_update = time.time()
print(f"TmuxPlugin: Killed session '{session_name}'")
except Exception as e:
print(f"TmuxPlugin: Error killing session '{session_name}': {e}")
@@ -244,32 +258,39 @@ class TmuxPlugin(PluginBase):
subprocess.run(
["tmux", "rename-session", "-t", old_name, clean_name],
check=True,
timeout=10
timeout=10,
)
# Refresh cache
self._sessions_cache = self._get_tmux_sessions()
self._last_cache_update = time.time()
print(f"TmuxPlugin: Renamed session '{old_name}' to '{clean_name}'")
except Exception as e:
print(f"TmuxPlugin: Error renaming session '{old_name}' to '{new_name}': {e}")
print(
f"TmuxPlugin: Error renaming session '{old_name}' to '{new_name}': {e}"
)
def _get_terminal_command(self, cmd: str) -> str:
"""Get terminal command based on configured terminal or available terminals."""
# First try to use the configured terminal command
if hasattr(data, 'TERMINAL_COMMAND') and data.TERMINAL_COMMAND:
if hasattr(data, "TERMINAL_COMMAND") and data.TERMINAL_COMMAND:
parts = data.TERMINAL_COMMAND.split()
terminal = parts[0]
try:
# Check if the configured terminal is available
subprocess.run(["which", terminal], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(
["which", terminal],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return f"{data.TERMINAL_COMMAND} {cmd}"
except subprocess.CalledProcessError:
# If configured terminal is not available, fall back to defaults
pass
# Fallback to checking available terminals
terminals = [
("kitty", f"kitty -e {cmd}"),
@@ -279,14 +300,19 @@ class TmuxPlugin(PluginBase):
("konsole", f"konsole -e {cmd}"),
("xfce4-terminal", f"xfce4-terminal -e '{cmd}'"),
]
for term, term_cmd in terminals:
try:
# Check if terminal is available
subprocess.run(["which", term], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(
["which", term],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return term_cmd
except subprocess.CalledProcessError:
continue
# Default fallback
return f"kitty -e {cmd}"
+18 -15
View File
@@ -132,6 +132,7 @@ class WallpaperPlugin(PluginBase):
if app and hasattr(app, "launcher"):
launcher = app.launcher
if launcher and hasattr(launcher, "search_entry"):
def clear_and_refresh():
# Clear the search entry to just the trigger
launcher.search_entry.set_text("wall ")
@@ -150,11 +151,9 @@ class WallpaperPlugin(PluginBase):
import gc
for obj in gc.get_objects():
if (
hasattr(obj, "__class__")
and obj.__class__.__name__ == "Launcher"
):
if hasattr(obj, "__class__") and obj.__class__.__name__ == "Launcher":
if hasattr(obj, "search_entry") and hasattr(obj, "_perform_search"):
def clear_and_refresh():
obj.search_entry.set_text("wall ")
obj.search_entry.set_position(-1)
@@ -554,8 +553,9 @@ class WallpaperPlugin(PluginBase):
status_text
}",
icon_markup=icons.palette,
action=lambda c=hex_color,
s=scheme: self._apply_hex_color_direct(c, s),
action=lambda c=hex_color, s=scheme: self._apply_hex_color_direct(
c, s
),
relevance=1.0,
plugin_name=self.display_name,
data={
@@ -595,10 +595,11 @@ class WallpaperPlugin(PluginBase):
status_text
}",
icon_markup=icons.palette,
action=lambda c=hex_color,
s=scheme: self._apply_hex_color_direct(c, s)
if not matugen_enabled
else None,
action=lambda c=hex_color, s=scheme: (
self._apply_hex_color_direct(c, s)
if not matugen_enabled
else None
),
relevance=0.9,
plugin_name=self.display_name,
data={
@@ -683,11 +684,13 @@ class WallpaperPlugin(PluginBase):
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color • {status_text}",
icon_markup=icons.palette,
action=lambda s=scheme: self._apply_hex_color_direct(
self._generate_random_hex_color(), s
)
if not matugen_enabled
else None,
action=lambda s=scheme: (
self._apply_hex_color_direct(
self._generate_random_hex_color(), s
)
if not matugen_enabled
else None
),
relevance=0.8,
plugin_name=self.display_name,
data={
+3 -1
View File
@@ -25,7 +25,9 @@ class ResultItem(EventBox):
"""Emitted when result is hovered."""
pass
def __init__(self, result: Result, selected: bool = False, index: int = 0, **kwargs):
def __init__(
self, result: Result, selected: bool = False, index: int = 0, **kwargs
):
super().__init__(name="launcher-result-item", **kwargs)
self.result = result
+1 -2
View File
@@ -1,14 +1,13 @@
import time
from typing import ClassVar, Literal
import utils.icons as icons
from fabric.audio import Audio
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.scale import Scale, ScaleMark
from gi.repository import GLib, GObject
import utils.icons as icons
from services.brightness import Brightness
from utils.animator import Animator
from utils.wayland import WaylandWindow as Window
@@ -52,10 +52,10 @@ def patched_do_update_properties(self, *_):
def _try_load_icon_from_path(self, icon_path):
try:
# Check if it's a file path and handle it directly
if os.path.isabs(icon_path) or '/' in icon_path:
if os.path.isabs(icon_path) or "/" in icon_path:
# Try to load as SVG from the original path if it exists
if os.path.exists(icon_path):
if icon_path.lower().endswith('.svg'):
if icon_path.lower().endswith(".svg"):
# Load SVG directly
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
icon_path, self._icon_size, self._icon_size
@@ -80,11 +80,13 @@ def _try_load_icon_from_path(self, icon_path):
# Remove extension for theme lookup
name_without_ext = os.path.splitext(filename)[0]
default_theme = Gtk.IconTheme.get_default()
# Try filename without extension
if default_theme.has_icon(name_without_ext):
pixbuf = default_theme.load_icon(
name_without_ext, self._icon_size, Gtk.IconLookupFlags.FORCE_SIZE
name_without_ext,
self._icon_size,
Gtk.IconLookupFlags.FORCE_SIZE,
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
@@ -109,9 +111,9 @@ def _try_load_icon_from_path(self, icon_path):
"/usr/local/share/icons",
"/usr/local/share/pixmaps",
os.path.expanduser("~/.local/share/icons"),
os.path.expanduser("~/.icons")
os.path.expanduser("~/.icons"),
]
filename = os.path.basename(icon_path)
for icon_dir in common_icon_dirs:
potential_path = os.path.join(icon_dir, filename)
@@ -129,7 +131,7 @@ def _try_load_icon_from_path(self, icon_path):
except Exception:
pass
return False
@@ -146,11 +148,11 @@ def apply_enhanced_system_tray():
# Store original method
global original_do_update_properties
original_do_update_properties = SystemTrayItem.do_update_properties
# Attach helper methods to SystemTrayItem class
SystemTrayItem._try_load_icon_from_path = _try_load_icon_from_path
SystemTrayItem._set_tooltip = _set_tooltip
# Replace the do_update_properties method
SystemTrayItem.do_update_properties = patched_do_update_properties
+40 -15
View File
@@ -54,26 +54,36 @@ class WifiIndicator(Button):
self.network.ethernet_device
and self.network.ethernet_device.state == "activated"
):
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi.svg"))
self.wifi_icon.set_from_file(
get_relative_path("../../../config/assets/icons/wifi.svg")
)
tooltip = f"Connected to Ethernet ({self.network.ethernet_device.speed})"
self.set_tooltip_text(tooltip)
return
if not self.network.wifi_device:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
self.wifi_icon.set_from_file(
get_relative_path("../../../config/assets/icons/wifi-off.svg")
)
tooltip = "No WiFi device found"
elif not self.network.wifi_device.wireless_enabled:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
self.wifi_icon.set_from_file(
get_relative_path("../../../config/assets/icons/wifi-off.svg")
)
tooltip = "WiFi disabled"
else:
active_ap = self.network.wifi_device.active_access_point
if active_ap:
strength = active_ap.strength
# Use wifi.svg for all connected states since we don't have signal strength icons
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi.svg"))
self.wifi_icon.set_from_file(
get_relative_path("../../../config/assets/icons/wifi.svg")
)
tooltip = f"Connected to {active_ap.ssid} ({strength}%)"
else:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
self.wifi_icon.set_from_file(
get_relative_path("../../../config/assets/icons/wifi-off.svg")
)
tooltip = "Not connected"
self.set_tooltip_text(tooltip)
@@ -127,12 +137,16 @@ class BluetoothIndicator(Button):
def update_state(self):
if not self.bluetooth.enabled:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth-off.svg"))
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/bluetooth-off.svg")
)
tooltip = "Bluetooth disabled"
else:
connected_devices = self.bluetooth.connected_devices
if connected_devices:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth.svg"))
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/bluetooth.svg")
)
if len(connected_devices) == 1:
device = connected_devices[0]
tooltip = f"Connected to {device.alias}"
@@ -141,7 +155,9 @@ class BluetoothIndicator(Button):
else:
tooltip = f"Connected to {len(connected_devices)} devices"
else:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth.svg"))
self.bt_icon.set_from_file(
get_relative_path("../../../config/assets/icons/bluetooth.svg")
)
tooltip = "No devices connected"
self.set_tooltip_text(tooltip)
@@ -155,11 +171,10 @@ class BluetoothIndicator(Button):
def on_device_removed(self, _, address):
self.update_state()
class Battery(Box):
def __init__(self, **kwargs):
super().__init__(
orientation="v", visible=True, **kwargs
)
super().__init__(orientation="v", visible=True, **kwargs)
self._battery = BatteryService()
self._battery.changed.connect(self.update_battery)
@@ -167,7 +182,9 @@ class Battery(Box):
self.icon = Svg(
name="indicators-icon",
size=24,
svg_file=get_relative_path("../../../config/assets/icons/battery/battery-100.svg"),
svg_file=get_relative_path(
"../../../config/assets/icons/battery/battery-100.svg"
),
)
self.battery_button = Button(
child=self.icon,
@@ -259,21 +276,29 @@ class Battery(Box):
self.icon.remove_style_class("alert")
if state == "FULLY_CHARGED" or (percentage >= 100 and state == "CHARGING"):
self.icon.set_from_file(get_relative_path("../../../config/assets/icons/battery/battery-100-charging.svg"))
self.icon.set_from_file(
get_relative_path(
"../../../config/assets/icons/battery/battery-100-charging.svg"
)
)
elif state == "CHARGING":
# Calculate battery level and use corresponding charging icon
level = min(100, max(0, int(percentage // 10) * 10))
if level == 0 and percentage > 0:
level = 10
icon_name = f"battery-{level:03d}-charging.svg"
self.icon.set_from_file(get_relative_path(f"../../../config/assets/icons/battery/{icon_name}"))
self.icon.set_from_file(
get_relative_path(f"../../../config/assets/icons/battery/{icon_name}")
)
else:
# Calculate battery level and use corresponding icon
level = min(100, max(0, int(percentage // 10) * 10))
if level == 0 and percentage > 0:
level = 10
icon_name = f"battery-{level:03d}.svg"
self.icon.set_from_file(get_relative_path(f"../../../config/assets/icons/battery/{icon_name}"))
self.icon.set_from_file(
get_relative_path(f"../../../config/assets/icons/battery/{icon_name}")
)
self.set_visible(True)
return True
+6 -8
View File
@@ -8,10 +8,10 @@ from fabric.utils import exec_shell_command
class MenuActionHandler:
def __init__(self):
pass
def execute_action(self, action):
try:
# Window menu actions
@@ -34,16 +34,13 @@ class MenuActionHandler:
elif action == "Quit":
Hyprland.send_command("dispatch killactive")
# Go menu actions
elif action == "Back":
Hyprland.send_command("dispatch workspace e-1")
elif action == "Forward":
Hyprland.send_command("dispatch workspace e+1")
# Edit menu actions
# View menu actions
elif action == "Full Screen":
@@ -63,7 +60,7 @@ class MenuActionHandler:
elif action == "Report a Bug...":
exec_shell_command("xdg-open https://github.com/S4NKALP/Modus/issues")
# System menu actions
# System menu actions
elif action == "About This PC":
exec_shell_command("gnome-system-monitor")
elif action == "Force Quit":
@@ -77,13 +74,14 @@ class MenuActionHandler:
elif action == "Lock":
exec_shell_command("hyprlock")
# App menu actions (for hiding apps)
elif action.startswith("Hide ") and not action == "Hide Others":
action.replace("Hide ", "")
Hyprland.send_command("dispatch movetoworkspacesilent special:hidden")
elif action == "Hide Others":
Hyprland.send_command("dispatch movetoworkspacesilent special:hidden,^(activewindow)")
Hyprland.send_command(
"dispatch movetoworkspacesilent special:hidden,^(activewindow)"
)
elif action == "Show All":
Hyprland.send_command("dispatch movetoworkspace e+0,special:hidden")
elif action.startswith("Quit "):
+14 -26
View File
@@ -6,19 +6,10 @@ This file contains all the menu items and their organization.
def get_default_menu_contents():
return {
"Hyprland": [
],
"File": [
],
"Edit": [
],
"View": [
"Full Screen",
"---",
"Zoom In",
"Zoom Out",
"Actual Size"
],
"Hyprland": [],
"File": [],
"Edit": [],
"View": ["Full Screen", "---", "Zoom In", "Zoom Out", "Actual Size"],
"Go": [
"Back",
"Forward",
@@ -33,7 +24,7 @@ def get_default_menu_contents():
"Center",
"Group",
"Pin",
"Quit"
"Quit",
],
"Help": [
"---",
@@ -41,33 +32,30 @@ def get_default_menu_contents():
"Arch Wiki",
"Keyboard Shortcuts",
"---",
"Report a Bug..."
]
"Report a Bug...",
],
}
def get_app_menu_template():
return [
"About {app_name}",
"---",
"Hide {app_name}",
"---",
"Quit {app_name}"
]
return ["About {app_name}", "---", "Hide {app_name}", "---", "Quit {app_name}"]
def create_app_menu(app_name):
template = get_app_menu_template()
return [item.format(app_name=app_name) if "{app_name}" in item else item for item in template]
return [
item.format(app_name=app_name) if "{app_name}" in item else item
for item in template
]
def get_menu_contents_for_app(app_name):
contents = get_default_menu_contents().copy()
if app_name and app_name != "Hyprland":
# Replace the Hyprland menu with app-specific menu
contents["Hyprland"] = create_app_menu(app_name)
return contents
+46 -44
View File
@@ -13,13 +13,13 @@ class DropdownWindow(WaylandWindow):
super().__init__(
name="dropdown-window",
title="Menu Dropdown",
layer="overlay",
anchor="top left",
exclusivity="none",
margin=f"{y}px 0px 0px {x}px",
visible=False,
all_visible=True,
**kwargs
layer="overlay",
anchor="top left",
exclusivity="none",
margin=f"{y}px 0px 0px {x}px",
visible=False,
all_visible=True,
**kwargs,
)
# Store reference to parent menubar for closing dropdown
@@ -51,14 +51,16 @@ class DropdownWindow(WaylandWindow):
if event.detail != 2: # 2 = Gdk.NotifyType.INFERIOR (child widget)
# Start auto-close timer when mouse leaves
if not self.auto_close_timer:
self.auto_close_timer = GLib.timeout_add(300, self.auto_close_callback) # 300ms delay
self.auto_close_timer = GLib.timeout_add(
300, self.auto_close_callback
) # 300ms delay
return False
def auto_close_callback(self):
if self.parent_menubar:
self.parent_menubar.hide_dropdown()
self.auto_close_timer = None
return False
return False
class MenuBar(Box):
@@ -82,38 +84,32 @@ class MenuBar(Box):
self.action_handler = MenuActionHandler()
# Menu items in macOS style order (excluding the first item which will be dynamic)
self.static_menu_items = [
"File",
"Edit",
"View",
"Go",
"Window",
"Help"
]
self.static_menu_items = ["File", "Edit", "View", "Go", "Window", "Help"]
self.menu_contents = get_default_menu_contents()
self.app_button = Button(
name="menubar-button",
child=Label(
name="activewindow-label",
label="Hyprland"
),
child=Label(name="activewindow-label", label="Hyprland"),
)
self.app_button.connect(
"clicked", lambda widget: self.toggle_dropdown("Hyprland", widget)
)
self.app_button.connect("clicked", lambda widget: self.toggle_dropdown("Hyprland", widget))
self.add(self.app_button)
self.menu_buttons = []
for item in self.static_menu_items:
button = Button(
name="menubar-button",
child=Label(
name="menubar-label",
label=item
),
child=Label(name="menubar-label", label=item),
)
button.connect("clicked", lambda widget, menu_item=item: self.on_menu_button_clicked(menu_item, widget))
button.connect(
"clicked",
lambda widget, menu_item=item: self.on_menu_button_clicked(
menu_item, widget
),
)
self.menu_buttons.append(button)
self.add(button)
@@ -145,9 +141,9 @@ class MenuBar(Box):
if menu_item in context_dependent_menus:
# Only allow if there's an active window (not just showing "Hyprland")
has_active_window = (
self.current_window_title and
self.current_window_title.strip() and
self.app_button.get_child().get_label() != "Hyprland"
self.current_window_title
and self.current_window_title.strip()
and self.app_button.get_child().get_label() != "Hyprland"
)
return has_active_window
@@ -171,9 +167,11 @@ class MenuBar(Box):
def toggle_dropdown(self, menu_item, button):
# If same menu is clicked and dropdown is visible, hide it
if (self.current_dropdown == menu_item and
self.dropdown_window and
self.dropdown_window.get_visible()):
if (
self.current_dropdown == menu_item
and self.dropdown_window
and self.dropdown_window.get_visible()
):
self.hide_dropdown()
return
@@ -209,7 +207,7 @@ class MenuBar(Box):
separator = Box(
name="menu-separator",
size=(150, 1),
style="background-color: #444; margin: 2px 0;"
style="background-color: #444; margin: 2px 0;",
)
self.dropdown_window.content_box.add(separator)
else:
@@ -219,9 +217,9 @@ class MenuBar(Box):
child=Label(
name="dropdown-label",
label=item,
h_align="start" # Align text to the left
h_align="start", # Align text to the left
),
on_clicked=lambda *_, action=item: self.on_menu_action(action)
on_clicked=lambda *_, action=item: self.on_menu_action(action),
)
self.dropdown_window.content_box.add(menu_button)
@@ -236,7 +234,9 @@ class MenuBar(Box):
# Get button allocation
allocation = button.get_allocation()
print(f"Button allocation: x={allocation.x}, y={allocation.y}, width={allocation.width}, height={allocation.height}")
print(
f"Button allocation: x={allocation.x}, y={allocation.y}, width={allocation.width}, height={allocation.height}"
)
# Use the exact button position for perfect alignment
dropdown_x = allocation.x
@@ -291,9 +291,11 @@ class MenuBar(Box):
def show_system_dropdown(self, button):
# If same system dropdown is open, hide it (toggle behavior)
if (self.current_dropdown == "System" and
self.dropdown_window and
self.dropdown_window.get_visible()):
if (
self.current_dropdown == "System"
and self.dropdown_window
and self.dropdown_window.get_visible()
):
self.hide_dropdown()
return
@@ -324,7 +326,7 @@ class MenuBar(Box):
"Shutdown",
"Restart",
"Sleep",
"Lock"
"Lock",
]
# Add menu items to dropdown
@@ -334,7 +336,7 @@ class MenuBar(Box):
separator = Box(
name="menu-separator",
size=(150, 1),
style="background-color: #444; margin: 2px 0;"
style="background-color: #444; margin: 2px 0;",
)
self.dropdown_window.content_box.add(separator)
else:
@@ -344,9 +346,9 @@ class MenuBar(Box):
child=Label(
name="dropdown-label",
label=item,
h_align="start" # Align text to the left
h_align="start", # Align text to the left
),
on_clicked=lambda *_, action=item: self.on_menu_action(action)
on_clicked=lambda *_, action=item: self.on_menu_action(action),
)
self.dropdown_window.content_box.add(menu_button)
@@ -0,0 +1,77 @@
from fabric.widgets.button import Button
from fabric.widgets.label import Label
import time
import subprocess
from fabric.widgets.box import Box
from gi.repository import GLib
from fabric.utils import get_relative_path
from fabric.widgets.svg import Svg
class RecordingIndicator(Button):
def __init__(self, **kwargs):
super().__init__(name="panel-button", visible=False, **kwargs)
self.script_path = get_relative_path("../../../scripts/screen-capture.sh")
self.recording_start_time = None
self.recording_icon = Svg(
name="indicators-icon",
size=24,
svg_file=get_relative_path("../../../config/assets/icons/media-record.svg"),
)
self.time_label = Label(name="recording-time-label", markup="00:00")
self.recording_box = Box(
orientation="h",
spacing=2,
children=self.recording_icon,
)
self.add(self.recording_box)
self.connect("clicked", self.on_stop_recording)
self.hide()
GLib.timeout_add(1, self._delayed_init)
def check_recording_status(self):
try:
result = subprocess.run(
[self.script_path, "status"], capture_output=True, text=True, timeout=2
)
is_recording = result.stdout.strip() == "true"
if is_recording:
if not self.get_visible():
self.show()
# Get the recording start time if we don't have it
if self.recording_start_time is None:
self.recording_start_time = self.get_recording_start_time()
# Update the recording time display
if self.recording_start_time:
elapsed_seconds = int(time.time() - self.recording_start_time)
minutes = elapsed_seconds // 60
seconds = elapsed_seconds % 60
time_text = f"{minutes:02d}:{seconds:02d}"
self.time_label.set_markup(time_text)
self.set_tooltip_text(
f"Recording in progress ({minutes:02d}:{
seconds:02d}) - Click to stop"
)
else:
self.set_tooltip_text("Recording in progress - Click to stop")
else:
if self.get_visible():
self.hide()
self.recording_start_time = None
except Exception:
# If we can't check status, hide the indicator
if self.get_visible():
self.hide()
self.recording_start_time = None
return True # Continue the timeout
+6 -3
View File
@@ -7,10 +7,13 @@ from fabric.widgets.datetime import DateTime
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.svg import Svg
from utils.wayland import WaylandWindow as Window
from modules.panel.components.enhanced_system_tray import (
apply_enhanced_system_tray,
) # ignore
from modules.panel.components.indicators import Indicators
from modules.panel.components.menubar import MenuBar
from modules.panel.components.enhanced_system_tray import apply_enhanced_system_tray
from utils.wayland import WaylandWindow as Window
# Apply enhanced system tray icon handling
apply_enhanced_system_tray()
@@ -131,4 +134,4 @@ class Panel(Window):
else:
self.chevron_button.get_child().set_from_file(
get_relative_path("../../config/assets/icons/chevron-right.svg")
)
)
+5 -3
View File
@@ -1,13 +1,12 @@
import json
import config.data as data
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from gi.repository import Gdk
import config.data as data
from utils.icon_resolver import IconResolver
from utils.wayland import WaylandWindow as Window
@@ -102,7 +101,10 @@ class ApplicationSwitcher(Window):
if c.get("hidden", False):
continue
# Skip clients in special workspaces if the setting is enabled
if data.DOCK_HIDE_SPECIAL_WORKSPACE_APPS and self._is_special_workspace(c):
if (
data.DOCK_HIDE_SPECIAL_WORKSPACE_APPS
and self._is_special_workspace(c)
):
continue
filtered_windows.append(c)