This commit is contained in:
sankalp committed 2025-06-26 16:10:17 +05:45
1 parent d684916f14
commit 96434bd036
55 files changed
+724 -543

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
from fabric.widgets.box import Box
from fabric.widgets.shapes import Corner
from utils.wayland import WaylandWindow as Window
+19 -17
View File
@@ -1,21 +1,25 @@
import os
import json
import config.data as data
import os
from fabric.hyprland.service import HyprlandEvent
from fabric.hyprland.widgets import Language, get_hyprland_connection
from fabric.system_tray.widgets import SystemTray
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.datetime import DateTime
from fabric.widgets.label import Label
from fabric.widgets.button import Button
from fabric.system_tray.widgets import SystemTray
from .battery import Battery
from .metrics import Metrics
from .controls import Controls
from .workspaces import workspace
from .indicators import Indicators
from .applications import Applications
from .music_player import MusicPlayer
from fabric.hyprland.widgets import get_hyprland_connection, Language
import config.data as data
import utils.icons as icons
from fabric.hyprland.service import HyprlandEvent
from .applications import Applications
from .battery import Battery
from .controls import Controls
from .indicators import Indicators
from .metrics import Metrics
from .music_player import MusicPlayer
from .workspaces import workspace
class DockComponents(Box):
@@ -114,7 +118,7 @@ class DockComponents(Box):
# Special handling for applications component - let it manage its own visibility
if component_name == "applications":
# Only apply config visibility if the component has content
if hasattr(widget, '_update_visibility_based_on_content'):
if hasattr(widget, "_update_visibility_based_on_content"):
widget._update_visibility_based_on_content()
else:
widget.set_visible(self.component_visibility[component_name])
@@ -143,9 +147,7 @@ class DockComponents(Box):
self.component_visibility[component_name]
)
config_file = os.path.expanduser(
f"~/.config/{data.APP_NAME}/config/config.json"
)
config_file = get_relative_path("../../../config/assets/config.json")
if os.path.exists(config_file):
try:
with open(config_file, "r") as f:
+5 -4
View File
@@ -2,7 +2,6 @@ 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, exec_shell_command_async, get_relative_path
from fabric.utils.helpers import get_desktop_applications
@@ -10,11 +9,13 @@ from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.image import Image
from gi.repository import Gdk, GLib, Gtk
import config.data as data
from utils.icon_resolver import IconResolver
def read_config():
config_path = get_relative_path("../../../config/json/dock.json")
config_path = get_relative_path("../../../config/assets/dock.json")
try:
with open(config_path, "r") as file:
config_data = json.load(file)
@@ -78,7 +79,7 @@ class Applications(Box):
self.conn = get_hyprland_connection()
self.icon_resolver = IconResolver()
self.pinned = self.config.get("pinned_apps", [])
self.config_path = get_relative_path("../../../config/json/dock.json")
self.config_path = get_relative_path("../../../config/assets/dock.json")
self.app_map = {}
self._all_apps = get_desktop_applications()
self.app_identifiers = self._build_app_identifiers_map()
@@ -245,7 +246,7 @@ class Applications(Box):
)
def update_pinned_apps_file(self):
config_path = get_relative_path("../../../config/json/dock.json")
config_path = get_relative_path("../../../config/assets/dock.json")
try:
with open(config_path, "w") as file:
json.dump(self.config, file, indent=4)
+3 -2
View File
@@ -1,10 +1,11 @@
import config.data as data
import utils.icons as icons
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.circularprogressbar import CircularProgressBar
from fabric.widgets.label import Label
from gi.repository import Gdk, GLib
import config.data as data
import utils.icons as icons
from services.battery import Battery as BatteryService
+3 -2
View File
@@ -1,5 +1,3 @@
import config.data as data
import utils.icons as icons
from fabric.audio.service import Audio
from fabric.widgets.box import Box
from fabric.widgets.button import Button
@@ -8,6 +6,9 @@ from fabric.widgets.eventbox import EventBox
from fabric.widgets.label import Label
from fabric.widgets.overlay import Overlay
from gi.repository import Gdk, GLib
import config.data as data
import utils.icons as icons
from services.brightness import Brightness as BrightnessService
+12 -11
View File
@@ -1,14 +1,16 @@
import subprocess
import config.data as data
import utils.icons as icons
from fabric.bluetooth import BluetoothClient
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.circularprogressbar import CircularProgressBar
from fabric.widgets.label import Label
from fabric.widgets.overlay import Overlay
from fabric.utils import get_relative_path
from gi.repository import GLib
import config.data as data
import utils.icons as icons
from services.network import NetworkClient
@@ -188,16 +190,15 @@ class RecordingIndicator(Button):
# Set up periodic checking for recording status
self.check_recording_status()
self.timeout_id = GLib.timeout_add(1000, self.check_recording_status) # Check every second
self.timeout_id = GLib.timeout_add(
1000, self.check_recording_status
) # Check every second
def check_recording_status(self):
"""Check if recording is currently active and update visibility."""
try:
result = subprocess.run(
[self.script_path, "status"],
capture_output=True,
text=True,
timeout=2
[self.script_path, "status"], capture_output=True, text=True, timeout=2
)
is_recording = result.stdout.strip() == "true"
@@ -209,7 +210,7 @@ class RecordingIndicator(Button):
if self.get_visible():
self.hide()
except Exception as e:
except Exception:
# If we can't check status, hide the indicator
if self.get_visible():
self.hide()
@@ -226,7 +227,7 @@ class RecordingIndicator(Button):
def cleanup(self):
"""Clean up the timeout when the indicator is destroyed."""
if hasattr(self, 'timeout_id'):
if hasattr(self, "timeout_id"):
GLib.source_remove(self.timeout_id)
@@ -245,5 +246,5 @@ class Indicators(Box):
def cleanup(self):
"""Clean up all indicators when the dock is destroyed."""
if hasattr(self, 'recording_indicator'):
if hasattr(self, "recording_indicator"):
self.recording_indicator.cleanup()
+3 -2
View File
@@ -1,9 +1,7 @@
import json
import subprocess
import config.data as data
import psutil
import utils.icons as icons
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.circularprogressbar import CircularProgressBar
@@ -11,6 +9,9 @@ from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from gi.repository import GLib
import config.data as data
import utils.icons as icons
class MetricsProvider:
def __init__(self):
+3 -2
View File
@@ -2,9 +2,7 @@ import os
import tempfile
import urllib.parse
import config.data as data
import requests
import utils.icons as icons
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -12,6 +10,9 @@ from fabric.widgets.eventbox import EventBox
from fabric.widgets.label import Label
from fabric.widgets.stack import Stack
from gi.repository import Gdk, GdkPixbuf, GLib
import config.data as data
import utils.icons as icons
from services.mpris import MediaManager
from utils.audio_visualizer import SpectrumRender
from utils.circle_image import CircleImage
+6 -5
View File
@@ -1,12 +1,13 @@
import json
import subprocess
import config.data as data
from fabric.hyprland.service import Hyprland
from fabric.hyprland.widgets import WorkspaceButton, Workspaces
from fabric.widgets.box import Box
from fabric.widgets.button import Button
import config.data as data
CHINESE_NUMERALS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "〇"]
@@ -102,8 +103,8 @@ workspaces = Workspaces(
for i in range(1, 11)
],
buttons_factory=None
if data.DOCK_HIDE_SPECIAL_WORKSPACE
else Workspaces.default_buttons_factory,
if data.DOCK_HIDE_SPECIAL_WORKSPACE
else Workspaces.default_buttons_factory,
)
workspaces_num = Workspaces(
@@ -127,8 +128,8 @@ workspaces_num = Workspaces(
for i in range(1, 11)
],
buttons_factory=None
if data.DOCK_HIDE_SPECIAL_WORKSPACE
else Workspaces.default_buttons_factory,
if data.DOCK_HIDE_SPECIAL_WORKSPACE
else Workspaces.default_buttons_factory,
)
@@ -1,132 +0,0 @@
# Digital Wellbeing Plugin
The Digital Wellbeing plugin provides comprehensive screen time tracking and statistics, inspired by GNOME 48's Digital Wellbeing feature. It monitors application usage and displays beautiful, modern statistics in the Modus launcher.
## Features
- **🎯 GNOME-style Interface**: Modern, clean design matching GNOME 48's Digital Wellbeing
- **📊 Comprehensive Statistics**: Large time display, summary cards, and grid-based app display
- **⏱️ Real-time Tracking**: Monitors active windows using Hyprland's `hyprctl` commands
- **🎯 Real Application Icons**: Shows actual application icons from system icon theme
- **📱 Grid Layout**: Applications displayed in a clean 3-column grid format
- **📅 Weekly Insights**: Compare daily usage with weekly averages and trends
- **🔍 Smart Search**: Find specific app usage with enhanced details and real icons
- **💾 Persistent Data**: Stores usage data in JSON files for historical tracking
- **🎨 Compact Design**:
- Real application icons (32px) optimized for launcher
- Clean 3-column grid layout (540x250px)
- Compact card design (80x70px) with hover effects
- Usage time display (no percentages for cleaner look)
- Optimized for launcher's 550x260 scroll window
## Usage
### Trigger Keywords
- `screentime` - Show full screen time statistics
- `st` - Short alias for screen time
### Commands
- `screentime` or `st` - Display compact Digital Wellbeing summary
- `st list` - Show detailed app usage grid
- `st apps` - Same as list (alternative command)
- `st week` - Display weekly statistics and trends
- `st <app_name>` - Search for specific application with weekly comparison
### Examples
```
screentime # Compact summary (header + cards only)
st # Short alias for compact view
st list # Full app usage grid
st apps # Alternative for app list
st week # Weekly statistics
st firefox # Firefox usage details
st code # VS Code usage details
```
## Interface Components
### 🏠 Main Dashboard (Default View)
- **Large Time Display**: Prominent display of total screen time today
- **Summary Cards**:
- Most Used App with time spent
- Total Apps Used count
- Average Session Length per app
- **Compact Layout**: Clean summary without app grid (540x150px)
### 📱 App List View (`st list`)
- **Full App Grid**: Top 12 applications displayed in a 3-column grid layout
- **Detailed View**: Shows individual app icons, names, and usage times
- **Expanded Layout**: Larger widget (540x250px) to accommodate grid
### 📊 Statistics Display
- **Real Application Icons**: Displays actual application icons from system theme
- **Usage Comparison**: Compare today vs yesterday with difference indicators
- **Weekly Averages**: Show daily averages for each application
- **Compact Grid Layout**:
- 32px application icons optimized for launcher
- 3-column grid layout (80x70px cards)
- App name and usage time display (no percentages)
- Automatic fallback to default application icon
- Icon caching for improved performance
- **Launcher-Optimized Design**:
- Fits perfectly in launcher's 550x260 scroll window
- Compact card layout with minimal spacing
- Hover effects for better interactivity
- Clean, uncluttered information display
## Data Storage
Usage data is stored in `config/json/screentime/` directory:
- `usage_YYYY-MM-DD.json` - Daily usage data files
- Data includes application class names and total seconds used
- Automatic cleanup of old data (configurable retention period)
- Weekly aggregation for trend analysis
## How It Works
1. **Window Tracking**: Uses `hyprctl -j activewindow` to detect the currently focused application
2. **Time Recording**: Tracks time spent in each application by monitoring window focus changes
3. **Data Persistence**: Saves usage data every 30 seconds and on plugin cleanup
4. **Visualization**: Creates custom GTK widgets with progress bars to show usage statistics
## Technical Details
- **Background Monitoring**: Runs a background thread that checks active window every 2 seconds
- **Application Detection**: Uses window class names from Hyprland for application identification
- **Memory Efficient**: Only stores essential data and cleans up old cache entries
- **Error Handling**: Gracefully handles window manager errors and missing data
## Customization
The plugin can be customized by modifying:
- Update frequency (currently 2 seconds)
- Number of top applications shown (currently 10)
- Minimum usage time threshold (currently 1 minute)
- Bar graph colors and styling in `styles/launcher.css`
## CSS Classes
The plugin uses these CSS classes for modern GNOME-style styling:
- `#screentime-widget` - Main widget container with rounded corners
- `.screentime-title` - Main title styling
- `.screentime-time-display` - Large time display with custom font
- `.screentime-card` - Summary cards with hover effects
- `.screentime-app-item` - Individual app items with modern styling
- `.screentime-app-icon` - App icon placeholders with gradients
- `.screentime-progress` - Modern progress bars with gradients
- `.screentime-progress.high-usage` - High usage bars (red gradient)
- `.screentime-progress.medium-usage` - Medium usage bars (yellow gradient)
- `.screentime-progress.low-usage` - Low usage bars (green gradient)
## Requirements
- Hyprland window manager
- `hyprctl` command available in PATH
- GTK 3.0
- Python 3.7+
## Privacy
- All data is stored locally on your system
- No network connections or external data sharing
- Data files can be manually deleted if desired
+2 -1
View File
@@ -4,6 +4,7 @@ from typing import List
from fabric.utils import DesktopApp
from fabric.utils.helpers import get_desktop_applications, get_relative_path
from modules.dock.main import Dock
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
@@ -46,7 +47,7 @@ class ApplicationsPlugin(PluginBase):
if v is not None
}
config_path = get_relative_path("../../../config/json/dock.json")
config_path = get_relative_path("../../../config/assets/dock.json")
try:
with open(config_path, "r") as file:
data = json.load(file)
+2 -1
View File
@@ -1,7 +1,8 @@
from typing import List
import utils.icons as icons
from fabric.bluetooth import BluetoothClient, BluetoothDevice
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+59 -41
View File
@@ -1,5 +1,4 @@
import json
import os
import subprocess
import threading
import time
@@ -7,8 +6,8 @@ from pathlib import Path
from typing import Dict, List, Optional
from urllib.parse import urlparse
from thefuzz import fuzz
from fabric.utils.helpers import get_relative_path
from thefuzz import fuzz
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
@@ -62,7 +61,9 @@ class BookmarkManager:
current_time = time.time()
# Check if cache is still valid
if (current_time - self.last_loaded) < self.cache_ttl and self.last_loaded > 0:
if (
current_time - self.last_loaded
) < self.cache_ttl and self.last_loaded > 0:
return
try:
@@ -84,8 +85,6 @@ class BookmarkManager:
self._load_bookmarks()
return self.bookmarks
def _save_bookmarks_unlocked(self):
"""Save bookmarks to JSON file without acquiring lock."""
try:
@@ -107,7 +106,9 @@ class BookmarkManager:
with self.cache_lock:
self._save_bookmarks_unlocked()
def add_bookmark(self, title: str, url: str, description: str = "", tags: List[str] = None) -> bool:
def add_bookmark(
self, title: str, url: str, description: str = "", tags: List[str] = None
) -> bool:
"""Add a new bookmark."""
try:
url = self._normalize_url(url)
@@ -123,7 +124,7 @@ class BookmarkManager:
"description": description.strip(),
"tags": tags or [],
"created": time.time(),
"accessed": 0
"accessed": 0,
}
self.bookmarks.append(new_bookmark)
@@ -144,10 +145,11 @@ class BookmarkManager:
identifier = identifier.lower().strip()
for i, bookmark in enumerate(self.bookmarks):
if (bookmark["title"].lower() == identifier or
bookmark["url"].lower() == identifier or
self._extract_domain(bookmark["url"]).lower() == identifier):
if (
bookmark["title"].lower() == identifier
or bookmark["url"].lower() == identifier
or self._extract_domain(bookmark["url"]).lower() == identifier
):
self.bookmarks.pop(i)
self._save_bookmarks()
@@ -190,7 +192,9 @@ class BookmarksPlugin(PluginBase):
self.description = "Manage and search your personal bookmarks"
# Initialize bookmark manager with storage file
self.bookmark_file = Path(get_relative_path("../../../config/json/bookmarks.json"))
self.bookmark_file = Path(
get_relative_path("../../../config/assets/bookmarks.json")
)
self.bookmark_manager = BookmarkManager(self.bookmark_file)
self.max_results = 15
@@ -223,7 +227,8 @@ class BookmarksPlugin(PluginBase):
if (
not query_key.startswith(("add ", "remove ", "delete ", "rm "))
and query_key in self._results_cache
and (current_time - self._cache_timestamps.get(query_key, 0)) < self._cache_ttl
and (current_time - self._cache_timestamps.get(query_key, 0))
< self._cache_ttl
):
return self._results_cache[query_key]
@@ -267,7 +272,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "info", "keep_launcher_open": True}
data={"type": "info", "keep_launcher_open": True},
)
]
@@ -282,7 +287,7 @@ class BookmarksPlugin(PluginBase):
# Sort by relevance and limit results
results.sort(key=lambda r: r.relevance, reverse=True)
return results[:self.max_results]
return results[: self.max_results]
def _get_recent_bookmarks(self) -> List[Result]:
"""Get recent/popular bookmarks when no query is provided."""
@@ -297,7 +302,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "help", "keep_launcher_open": True}
data={"type": "help", "keep_launcher_open": True},
),
Result(
title="Example: Add Google",
@@ -306,12 +311,14 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=0.9,
plugin_name=self.display_name,
data={"type": "example", "keep_launcher_open": True}
)
data={"type": "example", "keep_launcher_open": True},
),
]
# Sort by access time (most recent first) and show top 10
sorted_bookmarks = sorted(bookmarks, key=lambda b: b.get("accessed", 0), reverse=True)
sorted_bookmarks = sorted(
bookmarks, key=lambda b: b.get("accessed", 0), reverse=True
)
results = []
for bookmark in sorted_bookmarks[:10]:
result = self._create_bookmark_result(bookmark, 0.8)
@@ -331,7 +338,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "help", "keep_launcher_open": True}
data={"type": "help", "keep_launcher_open": True},
),
Result(
title="Example",
@@ -340,8 +347,8 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=0.9,
plugin_name=self.display_name,
data={"type": "example", "keep_launcher_open": True}
)
data={"type": "example", "keep_launcher_open": True},
),
]
# Parse arguments: title url [description]
@@ -355,7 +362,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "error", "keep_launcher_open": True}
data={"type": "error", "keep_launcher_open": True},
)
]
@@ -366,7 +373,9 @@ class BookmarksPlugin(PluginBase):
# Check if bookmark already exists
normalized_url = self.bookmark_manager._normalize_url(url)
existing_bookmarks = self.bookmark_manager.get_bookmarks()
already_exists = any(bookmark["url"] == normalized_url for bookmark in existing_bookmarks)
already_exists = any(
bookmark["url"] == normalized_url for bookmark in existing_bookmarks
)
if already_exists:
return [
@@ -377,7 +386,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "error", "keep_launcher_open": True}
data={"type": "error", "keep_launcher_open": True},
)
]
@@ -395,7 +404,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: self._add_bookmark_action(title, url, description),
relevance=1.0,
plugin_name=self.display_name,
data={"type": "add", "name": title, "keep_launcher_open": True}
data={"type": "add", "name": title, "keep_launcher_open": True},
)
]
@@ -410,9 +419,12 @@ class BookmarksPlugin(PluginBase):
matching_bookmarks = []
for bookmark in bookmarks:
if (bookmark["title"].lower() == identifier_lower or
bookmark["url"].lower() == identifier_lower or
self.bookmark_manager._extract_domain(bookmark["url"]).lower() == identifier_lower):
if (
bookmark["title"].lower() == identifier_lower
or bookmark["url"].lower() == identifier_lower
or self.bookmark_manager._extract_domain(bookmark["url"]).lower()
== identifier_lower
):
matching_bookmarks.append(bookmark)
if not matching_bookmarks:
@@ -424,7 +436,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "error", "keep_launcher_open": True}
data={"type": "error", "keep_launcher_open": True},
)
]
@@ -441,7 +453,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: self._remove_bookmark_action(identifier),
relevance=1.0,
plugin_name=self.display_name,
data={"type": "remove", "name": title, "keep_launcher_open": True}
data={"type": "remove", "name": title, "keep_launcher_open": True},
)
]
@@ -456,7 +468,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"type": "help", "keep_launcher_open": True}
data={"type": "help", "keep_launcher_open": True},
)
]
@@ -470,7 +482,7 @@ class BookmarksPlugin(PluginBase):
action=lambda: None,
relevance=0.9,
plugin_name=self.display_name,
data={"type": "info", "keep_launcher_open": True}
data={"type": "info", "keep_launcher_open": True},
)
)
@@ -486,7 +498,11 @@ class BookmarksPlugin(PluginBase):
action=lambda t=title: self._remove_bookmark_action(t),
relevance=0.8,
plugin_name=self.display_name,
data={"type": "remove_option", "bookmark": bookmark, "keep_launcher_open": True}
data={
"type": "remove_option",
"bookmark": bookmark,
"keep_launcher_open": True,
},
)
)
@@ -567,7 +583,9 @@ class BookmarksPlugin(PluginBase):
return 0.0
def _create_bookmark_result(self, bookmark: Dict, relevance: float) -> Optional[Result]:
def _create_bookmark_result(
self, bookmark: Dict, relevance: float
) -> Optional[Result]:
"""Create a Result object for a bookmark."""
try:
title = bookmark.get("title", "Untitled")
@@ -598,8 +616,8 @@ class BookmarksPlugin(PluginBase):
"domain": domain,
"description": description,
"keep_launcher_open": False,
"alt_action": lambda t=title: self._remove_bookmark_with_reset(t)
}
"alt_action": lambda t=title: self._remove_bookmark_with_reset(t),
},
)
except Exception as e:
print(f"Error creating bookmark result: {e}")
@@ -625,9 +643,7 @@ class BookmarksPlugin(PluginBase):
# Open URL
subprocess.Popen(
["xdg-open", url],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
["xdg-open", url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
except Exception as e:
print(f"Failed to open bookmark: {e}")
@@ -659,7 +675,9 @@ class BookmarksPlugin(PluginBase):
def _reset_to_trigger(self):
"""Reset launcher to trigger word and refresh."""
try:
if self._launcher_instance and hasattr(self._launcher_instance, "search_entry"):
if self._launcher_instance and hasattr(
self._launcher_instance, "search_entry"
):
# Get the current trigger (bookmark or bm)
current_text = self._launcher_instance.search_entry.get_text()
trigger = "bookmark "
+3 -2
View File
@@ -2,11 +2,12 @@ import subprocess
from threading import Timer
from typing import List
import config.data as data
import gi
import utils.icons as icons
from fabric.utils import get_relative_path
from fabric.utils.helpers import exec_shell_command_async
import config.data as data
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+2 -1
View File
@@ -3,9 +3,10 @@ import datetime
from typing import List
import gi
import utils.icons as icons
from fabric.widgets.label import Label
from gi.repository import Gtk
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+1
View File
@@ -8,6 +8,7 @@ from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Optional
from gi.repository import GdkPixbuf, GLib
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+3 -2
View File
@@ -5,8 +5,9 @@ import time
from collections import OrderedDict
from typing import Dict, List
import config.data as data
from fabric.utils import get_relative_path
import config.data as data
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
@@ -22,7 +23,7 @@ class EmojiPlugin(PluginBase):
self.display_name = "Emoji"
self.description = "Search and copy emojis"
self.emoji_data = {}
self.emoji_path = get_relative_path("../../../config/json/emoji.json")
self.emoji_path = get_relative_path("../../../config/assets/emoji.json")
# Use cache directory for recent emojis (save directly in cache dir)
self.recent_emoji_path = os.path.join(data.CACHE_DIR, "recent_emoji.json")
+2 -1
View File
@@ -5,11 +5,12 @@ from typing import List
import cairo
import gi
import utils.icons as icons
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import Gdk, GLib, GObject, Gtk
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+2 -1
View File
@@ -1,9 +1,10 @@
from typing import List
import utils.icons as icons
from fabric.widgets.box import Box
from fabric.widgets.entry import Entry
from gi.repository import GLib
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from services.network import NetworkClient
+2 -1
View File
@@ -2,11 +2,12 @@ import time
from datetime import datetime
from typing import List, Optional
import utils.icons as icons
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from gi.repository import GdkPixbuf
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from services import notification_service
+3 -2
View File
@@ -6,8 +6,9 @@ import time
from pathlib import Path
from typing import Dict, List, Optional
import utils.icons as icons
from fabric.utils import get_relative_path
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
@@ -28,7 +29,7 @@ class OTPPlugin(PluginBase):
self.description = "Manage TOTP codes and 2FA authentication"
self.secrets_file = Path(
get_relative_path("../../../config/json/accounts.json")
get_relative_path("../../../config/assets/accounts.json")
)
self.secrets: Dict[str, Dict] = {}
self.last_update = 0
+12 -12
View File
@@ -1,16 +1,17 @@
import json
import base64
import json
import subprocess
import threading
import time
from pathlib import Path
from typing import List, Dict, Optional
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
from fabric.utils import get_relative_path
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from fabric.utils import get_relative_path
class PasswordManager:
@@ -166,7 +167,7 @@ class PasswordPlugin(PluginBase):
# Initialize password manager
self.password_file = Path(
get_relative_path("../../../config/json/passwords.json")
get_relative_path("../../../config/assets/passwords.json")
)
self.password_manager = PasswordManager(self.password_file)
@@ -271,10 +272,10 @@ class PasswordPlugin(PluginBase):
# Check if password is revealed
if name in self.revealed_passwords:
title = f"{name}: {self.revealed_passwords[name]}"
subtitle = f"Password revealed - Enter: copy | Shift+Enter: hide"
subtitle = "Password revealed - Enter: copy | Shift+Enter: hide"
else:
title = f"{name}: {'*' * 8}"
subtitle = f"Enter: copy | Shift+Enter: reveal password"
subtitle = "Enter: copy | Shift+Enter: reveal password"
if description:
subtitle += f" | {description}"
@@ -328,7 +329,7 @@ class PasswordPlugin(PluginBase):
results.append(
Result(
title=f"Update password for '{name}'?",
subtitle=f"Password already exists. Click to update it.",
subtitle="Password already exists. Click to update it.",
icon_markup=icons.key,
action=lambda: self._add_password_action(
name, password, description, update=True
@@ -342,7 +343,7 @@ class PasswordPlugin(PluginBase):
results.append(
Result(
title=f"Add password for '{name}'",
subtitle=f"Click to save password"
subtitle="Click to save password"
+ (f" | {description}" if description else ""),
icon_markup=icons.plus,
action=lambda: self._add_password_action(
@@ -449,10 +450,10 @@ class PasswordPlugin(PluginBase):
# Check if password is revealed
if name in self.revealed_passwords:
title = f"{name}: {self.revealed_passwords[name]}"
subtitle = f"Password revealed - Enter: copy | Shift+Enter: hide"
subtitle = "Password revealed - Enter: copy | Shift+Enter: hide"
else:
title = f"{name}: {'*' * 8}"
subtitle = f"Enter: copy | Shift+Enter: reveal password"
subtitle = "Enter: copy | Shift+Enter: reveal password"
if description:
subtitle += f" | {description}"
@@ -510,7 +511,6 @@ class PasswordPlugin(PluginBase):
try:
success = self.password_manager.remove_password(name)
if success:
# Remove from revealed passwords if present
self.revealed_passwords.pop(name, None)
+2 -1
View File
@@ -1,7 +1,8 @@
from typing import List
import utils.icons as icons
from fabric.utils import exec_shell_command_async
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+147 -93
View File
@@ -1,8 +1,9 @@
import time
import threading
import time
from typing import List
import psutil
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
@@ -18,8 +19,6 @@ class ProcessPlugin(PluginBase):
self.display_name = "Process Manager"
self.description = "View and manage running processes"
# Threading for auto-refresh
self.refresh_thread = None
self.stop_refresh = threading.Event()
@@ -39,13 +38,17 @@ class ProcessPlugin(PluginBase):
def _initialize_cpu_monitoring(self):
"""Initialize CPU monitoring for all processes to get meaningful readings immediately."""
try:
for proc in psutil.process_iter(['pid']):
for proc in psutil.process_iter(["pid"]):
try:
pid = proc.info['pid']
pid = proc.info["pid"]
# Initialize CPU monitoring - this establishes baseline for future readings
proc.cpu_percent()
self._cpu_cache[pid] = proc
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
except (
psutil.NoSuchProcess,
psutil.AccessDenied,
psutil.ZombieProcess,
):
continue
self._last_cpu_update = time.time()
except Exception as e:
@@ -67,27 +70,31 @@ class ProcessPlugin(PluginBase):
# Initialize CPU monitoring if this is the first call or enough time has passed
if current_time - self._last_cpu_update > 0.1: # Update every 100ms
# Pre-populate CPU cache for all processes
for proc in psutil.process_iter(['pid']):
for proc in psutil.process_iter(["pid"]):
try:
pid = proc.info['pid']
pid = proc.info["pid"]
# Initialize CPU monitoring - this call establishes baseline
proc.cpu_percent()
self._cpu_cache[pid] = proc
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
except (
psutil.NoSuchProcess,
psutil.AccessDenied,
psutil.ZombieProcess,
):
continue
self._last_cpu_update = current_time
# Group processes by application name
app_groups = {}
for proc in psutil.process_iter(['pid', 'name', 'status', 'ppid']):
for proc in psutil.process_iter(["pid", "name", "status", "ppid"]):
try:
info = proc.info
if info['status'] == psutil.STATUS_ZOMBIE:
if info["status"] == psutil.STATUS_ZOMBIE:
continue
pid = info['pid']
name = info['name'] or 'Unknown'
pid = info["pid"]
name = info["name"] or "Unknown"
# Get CPU percentage - use cached process if available for better readings
if pid in self._cpu_cache:
@@ -110,13 +117,13 @@ class ProcessPlugin(PluginBase):
memory_mb = memory_info.rss / (1024 * 1024) if memory_info else 0
process_data = {
'pid': pid,
'ppid': info['ppid'],
'name': name,
'cpu_percent': cpu_percent,
'memory_percent': memory_percent,
'memory_mb': memory_mb,
'status': info['status']
"pid": pid,
"ppid": info["ppid"],
"name": name,
"cpu_percent": cpu_percent,
"memory_percent": memory_percent,
"memory_mb": memory_mb,
"status": info["status"],
}
# Group processes by application name
@@ -125,7 +132,11 @@ class ProcessPlugin(PluginBase):
app_groups[app_name] = []
app_groups[app_name].append(process_data)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
except (
psutil.NoSuchProcess,
psutil.AccessDenied,
psutil.ZombieProcess,
):
continue
# Create grouped results
@@ -134,37 +145,44 @@ class ProcessPlugin(PluginBase):
if len(app_processes) == 1:
# Single process - show as individual
proc = app_processes[0]
proc['process_count'] = 1
proc["process_count"] = 1
grouped_processes.append(proc)
else:
# Multiple processes - group them and sum resources
total_cpu = sum(p['cpu_percent'] for p in app_processes)
total_memory_mb = sum(p['memory_mb'] for p in app_processes)
total_memory_percent = sum(p['memory_percent'] for p in app_processes)
total_cpu = sum(p["cpu_percent"] for p in app_processes)
total_memory_mb = sum(p["memory_mb"] for p in app_processes)
total_memory_percent = sum(
p["memory_percent"] for p in app_processes
)
# Find the main process (usually the one with lowest PID)
main_process = min(app_processes, key=lambda x: x['pid'])
main_process = min(app_processes, key=lambda x: x["pid"])
grouped_process = {
'pid': main_process['pid'], # Use main process PID for killing
'name': app_name,
'cpu_percent': total_cpu,
'memory_percent': total_memory_percent,
'memory_mb': total_memory_mb,
'status': main_process['status'],
'process_count': len(app_processes),
'child_pids': [p['pid'] for p in app_processes]
# Use main process PID for killing
"pid": main_process["pid"],
"name": app_name,
"cpu_percent": total_cpu,
"memory_percent": total_memory_percent,
"memory_mb": total_memory_mb,
"status": main_process["status"],
"process_count": len(app_processes),
"child_pids": [p["pid"] for p in app_processes],
}
grouped_processes.append(grouped_process)
# Clean up old entries from cache
all_pids = set()
for app_processes in app_groups.values():
all_pids.update(p['pid'] for p in app_processes)
self._cpu_cache = {pid: proc for pid, proc in self._cpu_cache.items() if pid in all_pids}
all_pids.update(p["pid"] for p in app_processes)
self._cpu_cache = {
pid: proc for pid, proc in self._cpu_cache.items() if pid in all_pids
}
# Sort by combined CPU and memory usage for better display
grouped_processes.sort(key=lambda x: (x['cpu_percent'] + x['memory_percent']), reverse=True)
grouped_processes.sort(
key=lambda x: (x["cpu_percent"] + x["memory_percent"]), reverse=True
)
return grouped_processes
except Exception as e:
@@ -177,34 +195,50 @@ class ProcessPlugin(PluginBase):
name = process_name.lower()
# Handle common browser patterns
if 'firefox' in name:
return 'Firefox'
elif 'chrome' in name or 'chromium' in name:
return 'Chrome'
elif 'zen' in name or name in ['socket process', 'privileged cont', 'rdd process',
'isolated web co', 'web content', 'webextensions',
'utility process', 'isolated servic']:
return 'Zen Browser'
elif 'code' in name and ('helper' in name or 'oss' in name or name == 'code'):
return 'VS Code'
elif name.startswith('python') and len(name) > 6:
return 'Python'
elif name.startswith('node') and len(name) > 4:
return 'Node.js'
elif 'electron' in name:
return 'Electron'
elif 'java' in name:
return 'Java'
elif 'gnome' in name:
return 'GNOME'
elif 'gtk' in name:
return 'GTK App'
if "firefox" in name:
return "Firefox"
elif "chrome" in name or "chromium" in name:
return "Chrome"
elif "zen" in name or name in [
"socket process",
"privileged cont",
"rdd process",
"isolated web co",
"web content",
"webextensions",
"utility process",
"isolated servic",
]:
return "Zen Browser"
elif "code" in name and ("helper" in name or "oss" in name or name == "code"):
return "VS Code"
elif name.startswith("python") and len(name) > 6:
return "Python"
elif name.startswith("node") and len(name) > 4:
return "Node.js"
elif "electron" in name:
return "Electron"
elif "java" in name:
return "Java"
elif "gnome" in name:
return "GNOME"
elif "gtk" in name:
return "GTK App"
# Remove common suffixes
suffixes = ['-bin', '-real', '-wrapped', '.bin', '.exe', '-helper', '-gpu', '-renderer']
suffixes = [
"-bin",
"-real",
"-wrapped",
".bin",
".exe",
"-helper",
"-gpu",
"-renderer",
]
for suffix in suffixes:
if name.endswith(suffix):
name = name[:-len(suffix)]
name = name[: -len(suffix)]
break
# Capitalize first letter
@@ -222,8 +256,9 @@ class ProcessPlugin(PluginBase):
filtered_processes = processes
if query:
filtered_processes = [
proc for proc in processes
if query in proc['name'].lower() or query in str(proc['pid'])
proc
for proc in processes
if query in proc["name"].lower() or query in str(proc["pid"])
]
# Limit to top 20 processes to avoid overwhelming the UI
@@ -237,12 +272,12 @@ class ProcessPlugin(PluginBase):
def _create_process_result(self, proc_data: dict) -> Result:
"""Create a Result object for a process or grouped application."""
try:
pid = proc_data['pid']
name = proc_data['name']
cpu_percent = proc_data['cpu_percent']
memory_percent = proc_data['memory_percent']
memory_mb = proc_data['memory_mb']
process_count = proc_data.get('process_count', 1)
pid = proc_data["pid"]
name = proc_data["name"]
cpu_percent = proc_data["cpu_percent"]
memory_percent = proc_data["memory_percent"]
memory_mb = proc_data["memory_mb"]
process_count = proc_data.get("process_count", 1)
# Format the title with process count if grouped
if process_count > 1:
@@ -251,10 +286,14 @@ class ProcessPlugin(PluginBase):
title = f"{name} (PID: {pid})"
# Add visual indicators for high usage
cpu_indicator = "🔥" if cpu_percent > 80 else "⚡" if cpu_percent > 50 else ""
cpu_indicator = (
"🔥" if cpu_percent > 80 else "⚡" if cpu_percent > 50 else ""
)
mem_indicator = "💾" if memory_percent > 80 else ""
subtitle = f"CPU: {cpu_percent:.1f}%{cpu_indicator} | Memory: {memory_mb:.1f}MB ({memory_percent:.1f}%){mem_indicator}"
subtitle = f"CPU: {cpu_percent:.1f}%{cpu_indicator} | Memory: {
memory_mb:.1f
}MB ({memory_percent:.1f}%){mem_indicator}"
# Choose icon based on CPU usage
if cpu_percent > 50:
@@ -279,7 +318,7 @@ class ProcessPlugin(PluginBase):
"memory_percent": memory_percent,
"memory_mb": memory_mb,
"process_count": process_count,
"child_pids": proc_data.get('child_pids', [pid]),
"child_pids": proc_data.get("child_pids", [pid]),
"keep_launcher_open": True, # Keep launcher open to see live updates
"alt_action": lambda data=proc_data: self._kill_process_group(data),
},
@@ -288,18 +327,16 @@ class ProcessPlugin(PluginBase):
print(f"ProcessPlugin: Error creating process result: {e}")
return None
def _kill_process_group(self, proc_data: dict):
"""Kill a process group (application with all its subprocesses)."""
try:
process_count = proc_data.get('process_count', 1)
child_pids = proc_data.get('child_pids', [proc_data['pid']])
app_name = proc_data['name']
process_count = proc_data.get("process_count", 1)
child_pids = proc_data.get("child_pids", [proc_data["pid"]])
app_name = proc_data["name"]
if process_count == 1:
# Single process
self._kill_process(proc_data['pid'])
self._kill_process(proc_data["pid"])
else:
# Multiple processes - kill all
killed_count = 0
@@ -307,7 +344,11 @@ class ProcessPlugin(PluginBase):
if self._kill_process(pid, silent=True):
killed_count += 1
print(f"✓ Terminated {killed_count}/{len(child_pids)} processes for '{app_name}'")
print(
f"✓ Terminated {killed_count}/{len(child_pids)} processes for '{
app_name
}'"
)
except Exception as e:
print(f"✗ Error killing process group: {e}")
@@ -325,7 +366,9 @@ class ProcessPlugin(PluginBase):
try:
proc.wait(timeout=3)
if not silent:
print(f"✓ Successfully terminated process '{proc_name}' (PID: {pid})")
print(
f"✓ Successfully terminated process '{proc_name}' (PID: {pid})"
)
return True
except psutil.TimeoutExpired:
# Force kill if graceful termination failed
@@ -340,7 +383,9 @@ class ProcessPlugin(PluginBase):
return False
except psutil.AccessDenied:
if not silent:
print(f"✗ Access denied - cannot kill process {pid} (insufficient permissions)")
print(
f"✗ Access denied - cannot kill process {pid} (insufficient permissions)"
)
return False
except Exception as e:
if not silent:
@@ -351,7 +396,8 @@ class ProcessPlugin(PluginBase):
"""Start background thread for auto-refreshing process data in milliseconds."""
def refresh_loop():
while not self.stop_refresh.wait(0.5): # Check every 500ms for real-time updates
# Check every 500ms for real-time updates
while not self.stop_refresh.wait(0.5):
current_time = time.time()
if current_time - self.last_update >= 0.5: # Update every 500ms
self.last_update = current_time
@@ -368,6 +414,7 @@ class ProcessPlugin(PluginBase):
"""Update process data in existing result items (like OTP plugin)."""
try:
import gc
from gi.repository import GLib
def do_update():
@@ -408,11 +455,12 @@ class ProcessPlugin(PluginBase):
# Run data collection in background to avoid blocking UI
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(get_data_async)
try:
current_processes = future.result(timeout=1.0) # 1 second timeout
process_dict = {proc['pid']: proc for proc in current_processes}
process_dict = {proc["pid"]: proc for proc in current_processes}
for child in results_box.get_children():
if (
@@ -438,26 +486,32 @@ class ProcessPlugin(PluginBase):
return
proc_data = process_dict[pid]
cpu_percent = proc_data['cpu_percent']
memory_percent = proc_data['memory_percent']
memory_mb = proc_data['memory_mb']
cpu_percent = proc_data["cpu_percent"]
memory_percent = proc_data["memory_percent"]
memory_mb = proc_data["memory_mb"]
# CPU is already normalized in _get_live_process_data, so no need to normalize again
# Update the subtitle with live data
cpu_indicator = "🔥" if cpu_percent > 80 else "⚡" if cpu_percent > 50 else ""
cpu_indicator = (
"🔥" if cpu_percent > 80 else "⚡" if cpu_percent > 50 else ""
)
mem_indicator = "💾" if memory_percent > 80 else ""
new_subtitle = f"CPU: {cpu_percent:.1f}%{cpu_indicator} | Memory: {memory_mb:.1f}MB ({memory_percent:.1f}%){mem_indicator}"
new_subtitle = f"CPU: {cpu_percent:.1f}%{cpu_indicator} | Memory: {
memory_mb:.1f
}MB ({memory_percent:.1f}%){mem_indicator}"
# Find and update the subtitle label widget
self._find_and_update_subtitle_label(result_item, new_subtitle)
# Update the result data for consistency
result_item.result.data.update({
"cpu_percent": cpu_percent,
"memory_percent": memory_percent,
"memory_mb": memory_mb,
})
result_item.result.data.update(
{
"cpu_percent": cpu_percent,
"memory_percent": memory_percent,
"memory_mb": memory_mb,
}
)
except Exception as e:
print(f"Error updating process result item: {e}")
@@ -2,6 +2,7 @@ import subprocess
from typing import List
from fabric.utils import get_relative_path
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from utils.icons import screenrecord, screenshots, ssfull, ssregion, stop
+2 -1
View File
@@ -1,9 +1,10 @@
import os
import time
import shlex
import time
from typing import List, Union
from fabric.utils import exec_shell_command_async
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
+26 -19
View File
@@ -8,13 +8,14 @@ import threading
import time
from typing import Dict, List, Optional
import config.data as data
import utils.icons as icons
from fabric.utils.helpers import exec_shell_command_async
from gi.repository import GdkPixbuf
from PIL import Image
import config.data as data
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from PIL import Image
class WallpaperPlugin(PluginBase):
@@ -319,34 +320,36 @@ class WallpaperPlugin(PluginBase):
wallpaper_path = os.readlink(current_wall)
if os.path.exists(wallpaper_path):
# Apply the new scheme to current wallpaper
exec_shell_command_async(f'matugen image "{wallpaper_path}" -t {scheme}')
exec_shell_command_async(
f'matugen image "{wallpaper_path}" -t {scheme}'
)
# Send notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Applied {scheme_name} scheme' -a '{
data.APP_NAME_CAP
}' -e"
f"notify-send '🎨 Color Scheme' 'Applied {
scheme_name
} scheme' -a '{data.APP_NAME_CAP}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (will apply to next wallpaper)' -a '{
data.APP_NAME_CAP
}' -e"
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (will apply to next wallpaper)' -a '{data.APP_NAME_CAP}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (will apply to next wallpaper)' -a '{
data.APP_NAME_CAP
}' -e"
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (will apply to next wallpaper)' -a '{data.APP_NAME_CAP}' -e"
)
else:
# Matugen is disabled, just save the setting
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {scheme_name} (matugen disabled)' -a '{
data.APP_NAME_CAP
}' -e"
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (matugen disabled)' -a '{data.APP_NAME_CAP}' -e"
)
def _apply_hex_color(self, hex_color: str, scheme: str = None):
@@ -501,7 +504,8 @@ 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={
@@ -541,7 +545,8 @@ 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)
if not matugen_enabled
else None,
relevance=0.9,
@@ -588,7 +593,9 @@ class WallpaperPlugin(PluginBase):
results.append(
Result(
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color with {scheme_name} scheme • {status_text}",
subtitle=f"Generate and apply random color with {
scheme_name
} scheme • {status_text}",
icon_markup=icons.palette,
action=lambda s=scheme: self._apply_hex_color_direct(
self._generate_random_hex_color(), s
+3 -3
View File
@@ -147,8 +147,6 @@ class WebSearchPlugin(PluginBase):
return "", query
def _is_url(self, text: str) -> bool:
"""Check if the text is a URL."""
text = text.strip().lower()
@@ -257,4 +255,6 @@ class WebSearchPlugin(PluginBase):
"""Show help for a specific search engine."""
engine_info = self.search_engines.get(engine_id)
if engine_info:
print(f"Search engine: {engine_info['name']} - {engine_info['description']}")
print(
f"Search engine: {engine_info['name']} - {engine_info['description']}"
)
+3 -2
View File
@@ -2,14 +2,15 @@ import json
import os
from typing import Any, Dict, List
import utils.icons as icons
from fabric.utils import get_relative_path
import utils.icons as icons
class TriggerConfig:
def __init__(self, config_path: str = None):
if config_path is None:
config_path = get_relative_path("../../config/json/launcher.json")
config_path = get_relative_path("../../config/assets/launcher.json")
self.config_path = config_path
+2 -1
View File
@@ -1,6 +1,5 @@
import os
import utils.icons as icons
from fabric.notifications import (
Notification,
NotificationAction,
@@ -15,6 +14,8 @@ from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, GdkPixbuf, GLib
from loguru import logger
import utils.icons as icons
from services import notification_service
from utils.animator import Animator
from utils.custom_image import CustomImage
+2 -1
View File
@@ -1,13 +1,14 @@
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
+2 -1
View File
@@ -1,13 +1,14 @@
import json
import subprocess
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