updates
This commit is contained in:
55 files changed
+724
-543
No files matched your search
@@ -1,5 +1,6 @@
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.shapes import Corner
|
||||
|
||||
from utils.wayland import WaylandWindow as Window
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']}"
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user