feat(desktop): updates

This commit is contained in:
sankalp committed 2026-07-27 14:27:13 +05:45
1 parent fc7f2851c9
commit 29f3519965
13 files changed
+549 -29

No files matched your search

+30 -1
View File
@@ -19,6 +19,7 @@ Entry point is `start.py`, delegates to `src/main.py`. `main.py` instantiates
- **OSD**: Volume/brightness overlays (`src/window/osd/`)
- **Settings**: Configuration GUI (`src/window/settings/`)
- **Wallpaper Picker**: Wallpaper browser and management (`src/window/wallpaper/`)
- **Desktop Widgets**: User-defined widgets on the desktop background (`src/window/desktop/`)
## 2. Spotlight Search Engine
@@ -87,7 +88,35 @@ macOS-style menu bar in the top panel.
- Python daemon (`src/window/globalmenu/service.py`) listens for exported menus and
renders them as Fabric widgets in the panel
## 5. State Management & Services
## 5. Desktop Widgets
Desktop widgets render GTK widgets directly on the wallpaper background.
### Widget System
- **Registry** (`src/window/desktop/registry.py`): Stores widget classes, sizes, and default positions keyed by string id.
- **Built-in widgets** (`src/window/desktop/*.py`): Date, weather, calendar, CPU/RAM info. Each module registers itself at import time.
- **User widgets** (`config/desktop/*.py`): Drop-in `.py` files that call `DesktopWidgetRegistry.register()`. Loaded dynamically at startup and watched for live reload.
- **Position Manager** (`src/window/desktop/main.py`): Reads widget positions from `config/desktop.toml` (per-monitor, fractional coords). Merges with defaults from registry. Drag/drop saves overrides.
- **Desktop Window** (`src/window/desktop/main.py`): `DesktopWidgetWindow` uses a `Gtk.Fixed` container to position widgets at fractional screen coordinates. Handles edit mode (drag and drop), fade animations, and file monitoring.
### Lifecycle
```
Startup:
1. Import built-in widget modules → register widgets
2. Scan config/desktop/*.py → import & register user widgets
3. Read config/desktop.toml → get positions (or populate defaults)
4. Create DesktopWidgetWindow per monitor → render widgets
Runtime:
File monitor on config/desktop/ → detect add/delete
→ unregister stale widgets → re-import new widgets → rebuild
2-second poll fallback for missed file monitor events
Drag/drop in edit mode → save position to desktop.toml
```
## 6. State Management & Services
Services in `src/services/` monitor system state asynchronously and emit
signals:
+58
View File
@@ -9,6 +9,8 @@ Modus is configured via TOML files in the `config/` directory, plus a graphical
| `config.toml` | Main settings (features, widgets, paths) | Restart |
| `mods.toml` | Custom panel buttons (menus, shell commands) | Instant |
| `dock.toml` | Pinned dock apps | Restart |
| `desktop.toml` | Desktop widget positions (per-monitor) | Restart |
| `config/desktop/*.py` | User desktop widgets | Instant (live reload) |
## Settings Window
@@ -156,3 +158,59 @@ pinned = [
```
Use the exact `.desktop` file name or application ID.
---
## Desktop Widgets (`config/desktop/` + `desktop.toml`)
Desktop widgets render directly on your wallpaper. Drop a `.py` file in `config/desktop/` and it appears live.
### Adding a Widget
```bash
cp examples/desktop/example.py config/desktop/example.py
```
The widget file calls `DesktopWidgetRegistry.register()` with a key, class, size, and default position:
```python
DesktopWidgetRegistry.register("my_widget", MyWidget, (174, 174), (0.5, 0.5))
```
### Positioning
Positions live in `config/desktop.toml`, one entry per monitor:
```toml
[[0]]
key = "my_widget"
px = 0.5
py = 0.5
```
- `0` = monitor ID
- `px`, `py` = 0.0–1.0 fractions of screen
Drag widgets in edit mode (right-click desktop → Edit Widgets) to reposition.
### Removing a Widget
Delete the `.py` file from `config/desktop/`:
```bash
rm config/desktop/example.py
```
Widget disappears within 2 seconds. The entry is also removed from `desktop.toml`.
### Built-in Widgets
| Key | Description |
|-----|-------------|
| `date` | Date display |
| `weather` | Weather info |
| `calendar` | Monthly calendar |
| `cpu_info` | CPU usage |
| `ram_info` | RAM usage |
See [Desktop Widgets Guide](desktop_widgets.md) for full documentation and examples.
+210
View File
@@ -0,0 +1,210 @@
# Desktop Widgets Guide
Modus displays widgets directly on your desktop background. Drop a `.py` file in `config/desktop/` and it appears live — no restart needed.
## Quick Start
Copy the example widget:
```bash
cp examples/desktop/example.py config/desktop/example.py
```
The widget appears on your desktop immediately. Edit positions by dragging in edit mode (right-click desktop → Edit Widgets) or by editing `config/desktop.toml`.
## How It Works
1. On startup, Modus scans `config/desktop/` for `.py` files.
2. Each file is imported. It calls `DesktopWidgetRegistry.register()` to register a widget class with a key, size, and default position.
3. `config/desktop.toml` stores per-monitor positions (`px`, `py` as 0.0–1.0 fractions).
4. A file monitor + 2-second poll watches `config/desktop/` — adding or removing files live-reloads widgets.
## Writing a Desktop Widget
### Minimal Example
File: `config/desktop/example.py`
```python
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from window.desktop.registry import DesktopWidgetRegistry
class ExampleClockWidget(Box):
def __init__(self, **kwargs):
super().__init__(
name="example-clock-widget",
orientation="v",
h_expand=True,
v_expand=True,
h_align="center",
v_align="center",
**kwargs,
)
self.label = Label(
name="example-clock-label",
label="Hello Desktop",
justification="center",
)
self.add(self.label)
DesktopWidgetRegistry.register(
"example", # unique key (used in desktop.toml)
ExampleClockWidget, # GTK widget class
(174, 174), # size (width, height) in pixels
(0.45, 0.45), # default position (px, py) as 0.0–1.0 fractions
)
```
### `DesktopWidgetRegistry.register()` Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `key` | `str` | Yes | Unique identifier (e.g., `"my_widget"`). Used in `desktop.toml`. |
| `widget_cls` | `class` | Yes | GTK widget class (must inherit from a Fabric/GTK widget). |
| `size` | `tuple[int, int]` | Yes | `(width, height)` in pixels. |
| `default_position` | `tuple[float, float]` | No | `(px, py)` as 0.0–1.0 screen fractions. Default `(0.0, 0.0)`. |
### Widget Rules
- Widget must be a GTK widget (inherit from `fabric.widgets.box.Box`, `fabric.widgets.label.Label`, etc.)
- Use `name=` for CSS styling (maps to `#example-clock-widget` in CSS)
- Set `h_expand=True` / `v_expand=True` for flexible sizing
- Call `DesktopWidgetRegistry.register()` at module level (not inside a function)
- The `key` must be unique across all widgets
## Built-in Widgets
| Key | Widget | Default Position |
|-----|--------|-----------------|
| `date` | Date display | Top-left `(0.0, 0.0)` |
| `weather` | Weather info | Top-left `(0.097, 0.0)` |
| `calendar` | Monthly calendar | Top-left `(0.191, 0.001)` |
| `cpu_info` | CPU usage | Bottom-right `(0.810, 0.827)` |
| `ram_info` | RAM usage | Bottom-right `(0.905, 0.827)` |
## Positioning
### Edit Mode (Drag and Drop)
1. Right-click desktop → **Edit Widgets**
2. Drag widgets to new positions
3. Right-click → **Done Edit**
4. Positions save to `config/desktop.toml`
### Config File
Edit `config/desktop.toml` directly:
```toml
[[0]]
key = "my_widget"
px = 0.5
py = 0.5
```
- `0` = monitor ID (0 for primary)
- `px`, `py` = position as 0.0–1.0 fractions of screen
### Default Position
Set in your widget file's `register()` call. Used when no entry exists in `desktop.toml`:
```python
DesktopWidgetRegistry.register("my_widget", MyWidget, (174, 174), (0.3, 0.7))
```
## Lifecycle
| Action | What Happens |
|--------|-------------|
| Add `.py` file to `config/desktop/` | Widget appears on desktop (live) |
| Delete `.py` file from `config/desktop/` | Widget disappears (within 2s) |
| Edit `.py` file in `config/desktop/` | Widget rebuilds on next file change |
| Drag widget in edit mode | Position saves to `desktop.toml` |
| Edit `desktop.toml` manually | Position updates on restart |
## Styling Widgets
Use the widget's `name` parameter for CSS targeting:
```python
self.label = Label(name="my-widget-label", label="Hello")
```
In your CSS (`src/styles/`), target with:
```css
#my-widget-label {
font-size: 24px;
color: @theme_text_color;
}
#my-widget-label:selected {
color: @accent_color;
}
```
## Advanced: Using Services
Widgets can access Modus services for live data:
```python
from fabric.utils import GLib, invoke_repeater
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from window.desktop.registry import DesktopWidgetRegistry
class LiveClockWidget(Box):
def __init__(self, **kwargs):
super().__init__(
name="live-clock",
orientation="v",
h_align="center",
v_align="center",
**kwargs,
)
self.label = Label(name="clock-label", label="--:--")
self.add(self.label)
self._timer_id = invoke_repeater(1000, self._update)
def _update(self) -> bool:
import datetime
now = datetime.datetime.now()
self.label.set_label(now.strftime("%H:%M"))
return True
def destroy(self):
if hasattr(self, "_timer_id") and self._timer_id:
GLib.source_remove(self._timer_id)
super().destroy()
DesktopWidgetRegistry.register("live_clock", LiveClockWidget, (174, 174), (0.5, 0.0))
```
## Troubleshooting
### Widget not appearing?
- Check the file is in `config/desktop/` (not a subdirectory)
- Check `journalctl --user -f -t modus` for `[DesktopWidgets]` errors
- Ensure the file calls `DesktopWidgetRegistry.register()` at module level
- Ensure the `key` is unique (not conflicting with built-in widgets)
### Widget still showing after deleting the file?
The file monitor + poll should clean up within 2 seconds. If not:
1. Check `journalctl --user -f -t modus` for `[DesktopWidgets] poll found stale:` messages
2. Restart Modus: `uv run start`
### Position resets after restart?
- `desktop.toml` stores your custom positions
- If you delete `desktop.toml`, positions revert to defaults from `register()` calls
- Ensure `config/desktop.toml` is writable
+23
View File
@@ -49,6 +49,25 @@ Update `weather_location` in `config.toml` (e.g., `"London, UK"` or
- The venv lives at `config/plugins/.venvs/<plugin_name>/`
- Delete that directory to force re-install on next load
### 8. Desktop widget not appearing?
- Check the file is in `config/desktop/` (not a subdirectory)
- Ensure it calls `DesktopWidgetRegistry.register()` at module level
- Check `journalctl --user -f -t modus` for `[DesktopWidgets]` errors
- Ensure the widget `key` is unique (not `"date"`, `"weather"`, etc.)
### 9. Desktop widget still showing after deleting the file?
The cleanup runs within 2 seconds via file monitor + poll fallback. If it persists:
- Check `journalctl --user -f -t modus` for stale widget messages
- Restart Modus: `uv run start`
### 10. Desktop widget position resets after restart?
- Positions are stored in `config/desktop.toml`
- If you delete `desktop.toml`, positions revert to defaults from `register()` calls
- Ensure `config/desktop.toml` is writable
---
## Pro Tips
@@ -56,6 +75,10 @@ Update `weather_location` in `config.toml` (e.g., `"London, UK"` or
- **Live Reload for Mods**: `config/mods.toml` is monitored for changes. Edits
apply instantly — no restart needed
- **Desktop Widgets**: Drop `.py` files in `config/desktop/` for instant widgets.
Edit `config/desktop.toml` or drag in edit mode to reposition.
Remove by deleting the `.py` file — disappears within 2 seconds.
- **Pinning Apps**: Use exact `.desktop` filename in `dock.toml` (e.g.,
`"org.gnome.Nautilus"`, not `"Files"`)