feat(desktop): now move widget with drag to any place of window #42

Merged
S4NKALP merged 11 commits from feat/desktop into macos 2026-07-27 12:38:39 +00:00
13 changed files with 549 additions and 29 deletions
Showing only changes of commit 29f3519965 - Show all commits

No files matched your search

+18
View File
@@ -122,6 +122,23 @@ You can also toggle most settings from the **Settings window** (`Super + I`).
See [Configuration Guide](docs/configuration.md) for full reference.
## Desktop Widgets
Drop a `.py` file in `config/desktop/` and it appears on your desktop instantly:
```bash
cp examples/desktop/example.py config/desktop/example.py
```
Edit positions in `config/desktop.toml` or drag widgets in edit mode (right-click → Edit Widgets).
```bash
nvim config/desktop/example.py # live reload, no restart needed
rm config/desktop/example.py # widget disappears within 2s
```
See [Desktop Widgets Guide](docs/desktop_widgets.md) for full docs and examples.
## Documentation
| Doc | What's inside |
@@ -131,6 +148,7 @@ See [Configuration Guide](docs/configuration.md) for full reference.
| [Styling Guide](docs/styling.md) | Matugen colors, CSS customization |
| [Architecture Overview](docs/architecture.md) | App Switcher C-backend, Spotlight engine, Services |
| [Spotlight Plugins](docs/plugins.md) | Install/customize plugins, write your own |
| [Desktop Widgets](docs/desktop_widgets.md) | Custom desktop widgets, user widget system |
| [FAQs & Tips](docs/faqs_tips.md) | Troubleshooting, shortcuts, live reload |
## Custom Mods
+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"`)
+31
View File
@@ -0,0 +1,31 @@
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-widget",
orientation="v",
h_expand=True,
v_expand=True,
h_align="center",
v_align="center",
**kwargs,
)
self.label = Label(
name="examplelabel",
label="Hello Desktop",
justification="center",
)
self.add(self.label)
DesktopWidgetRegistry.register(
"example",
ExampleClockWidget,
(174, 174),
(0.45, 0.45),
)
+1 -3
View File
@@ -73,9 +73,7 @@ class DesktopWidgetService(Service):
return True
def remove(self, monitor_id: int, key: str) -> bool:
removed = position_manager.remove(monitor_id, key)
if not removed:
return False
position_manager.remove_override(monitor_id, key)
win = self._windows.get(monitor_id)
if win:
win.remove_widget(key)
+3 -1
View File
@@ -143,4 +143,6 @@ class DesktopCalendarContainer(Box):
)
DesktopWidgetRegistry.register("calendar", DesktopCalendarContainer, (174, 174))
DesktopWidgetRegistry.register(
"calendar", DesktopCalendarContainer, (174, 174), (0.19115, 0.00095)
)
+1 -1
View File
@@ -49,4 +49,4 @@ class DesktopDateContainer(Box):
)
DesktopWidgetRegistry.register("date", DesktopDateContainer, (174, 174))
DesktopWidgetRegistry.register("date", DesktopDateContainer, (174, 174), (0.0, 0.0))
+138 -19
View File
@@ -1,9 +1,10 @@
import importlib
import os
import pkgutil
import sys
import tomlkit
from fabric.utils import Gdk, GLib, Gtk, logger
from fabric.utils import Gdk, Gio, GLib, Gtk, logger
from fabric.widgets.box import Box
from fabric.widgets.wayland import WaylandWindow
@@ -22,19 +23,54 @@ for _, _name, _ in pkgutil.iter_modules([_dir]):
# Config-backed position manager (stores percentage-based positions) #
_DEFAULT_POSITIONS = [
{"key": "date", "px": 0.0, "py": 0.0},
{"key": "weather", "px": 0.0974, "py": 0.0},
{"key": "calendar", "px": 0.19271, "py": 0.0},
{"key": "cpu_info", "px": 0.81042, "py": 0.82732},
{"key": "ram_info", "px": 0.90521, "py": 0.82732},
]
_CONFIG_DESKTOP_DIR = os.path.join(
os.path.dirname(toml_file("desktop.toml")), "desktop"
)
_TARGET = Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)
_user_widget_modules: dict[str, str] = {}
_user_key_to_file: dict[str, str] = {}
def _load_user_widgets() -> set[str]:
"""Import every .py in config/desktop/. Returns set of loaded keys."""
if not os.path.isdir(_CONFIG_DESKTOP_DIR):
return set()
loaded_keys: set[str] = set()
for fname in os.listdir(_CONFIG_DESKTOP_DIR):
if not fname.endswith(".py") or fname.startswith("_"):
continue
module_name = fname[:-3]
full_path = os.path.join(_CONFIG_DESKTOP_DIR, fname)
try:
before_keys = set(DesktopWidgetRegistry._widgets)
spec = importlib.util.spec_from_file_location(
f"user_widget_{module_name}", full_path
)
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
_user_widget_modules[module_name] = spec.name
new_keys = set(DesktopWidgetRegistry._widgets) - before_keys
for k in new_keys:
_user_key_to_file[k] = module_name
DesktopWidgetRegistry.register_user(k)
loaded_keys.update(new_keys)
except Exception as e:
logger.error(f"[DesktopWidgets] failed to load user widget {fname}: {e}")
return loaded_keys
_load_user_widgets()
class PositionManager:
"""Manages widget positions stored in desktop.toml as fractional coords."""
"""Merges widget defaults from registry with user overrides in desktop.toml.
config/desktop/*.py widgets register themselves with a default position.
desktop.toml stores drag-and-drop overrides per monitor.
"""
_file = toml_file("desktop.toml")
@@ -61,36 +97,49 @@ class PositionManager:
def get_widgets(cls, monitor_id: int) -> list[dict]:
state = cls._read()
mid_str = str(monitor_id)
if mid_str not in state:
state[mid_str] = list(_DEFAULT_POSITIONS)
if mid_str not in state or not state[mid_str]:
state[mid_str] = []
for key in DesktopWidgetRegistry._widgets:
pos = DesktopWidgetRegistry.get_default_position(key)
px, py = pos if pos else (0.0, 0.0)
state[mid_str].append({"key": key, "px": px, "py": py})
cls._write(state)
return state[mid_str]
else:
registered = set(DesktopWidgetRegistry._widgets)
before = len(state[mid_str])
state[mid_str] = [e for e in state[mid_str] if e["key"] in registered]
for key in registered - {e["key"] for e in state[mid_str]}:
pos = DesktopWidgetRegistry.get_default_position(key)
px, py = pos if pos else (0.0, 0.0)
state[mid_str].append({"key": key, "px": px, "py": py})
if len(state[mid_str]) != before:
cls._write(state)
return list(state[mid_str])
@classmethod
def save_position(cls, monitor_id: int, key: str, px: float, py: float) -> None:
state = cls._read()
mid_str = str(monitor_id)
if mid_str not in state:
state[mid_str] = list(_DEFAULT_POSITIONS)
state[mid_str] = []
for entry in state[mid_str]:
if entry["key"] == key:
entry["px"] = round(max(0.0, min(1.0, px)), 5)
entry["py"] = round(max(0.0, min(1.0, py)), 5)
break
else:
state[mid_str].append({"key": key, "px": px, "py": py})
state[mid_str].append({"key": key, "px": round(px, 5), "py": round(py, 5)})
cls._write(state)
@classmethod
def remove(cls, monitor_id: int, key: str) -> bool:
def remove_override(cls, monitor_id: int, key: str) -> None:
"""Remove a drag/drop override so the widget reverts to its default position."""
state = cls._read()
mid_str = str(monitor_id)
if mid_str not in state:
return False
before = len(state[mid_str])
return
state[mid_str] = [e for e in state[mid_str] if e["key"] != key]
cls._write(state)
return len(state[mid_str]) < before
position_manager = PositionManager()
@@ -157,6 +206,9 @@ class DesktopWidgetWindow(WaylandWindow):
self.connect("size-allocate", self._on_size_allocate)
self.connect("button-press-event", self._on_window_button_press)
self._setup_drag_dest()
self._setup_file_monitor()
self._last_user_files: set[str] = set()
GLib.timeout_add(2000, self._poll_user_widgets)
GLib.timeout_add(50, self._initial_build)
@@ -202,6 +254,71 @@ class DesktopWidgetWindow(WaylandWindow):
def _on_fade_out_finished(self, animator) -> None:
self._fixed.set_opacity(0.0)
# fallback poll — catches deletions that Gio file monitor misses
def _poll_user_widgets(self) -> bool:
if not os.path.isdir(_CONFIG_DESKTOP_DIR):
return True
current = {
f[:-3]
for f in os.listdir(_CONFIG_DESKTOP_DIR)
if f.endswith(".py") and not f.startswith("_")
}
if current != self._last_user_files:
self._last_user_files = current
stale = {k for k, f in _user_key_to_file.items() if f not in current}
if stale:
logger.info(f"[DesktopWidgets] poll found stale: {stale}")
for key in stale:
mod_name = _user_key_to_file.pop(key, None)
if mod_name:
sys.modules.pop(f"user_widget_{mod_name}", None)
_user_widget_modules.pop(mod_name, None)
DesktopWidgetRegistry.unregister_user_keys(stale)
_load_user_widgets()
self.rebuild()
return True
# file monitor — live-rebuild when config/desktop/ changes
def _setup_file_monitor(self) -> None:
if not os.path.isdir(_CONFIG_DESKTOP_DIR):
os.makedirs(_CONFIG_DESKTOP_DIR, exist_ok=True)
config_dir = Gio.File.new_for_path(_CONFIG_DESKTOP_DIR)
monitor = config_dir.monitor_directory(Gio.FileMonitorFlags.NONE, None)
monitor.connect("changed", self._on_config_changed)
self._config_monitor = monitor
def _on_config_changed(self, _monitor, _file, _other, event_type) -> None:
logger.info(f"[DesktopWidgets] file monitor event: {event_type}")
if event_type in (
Gio.FileMonitorEvent.CREATED,
Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.CHANGED,
):
GLib.idle_add(self._on_widget_dir_changed)
def _on_widget_dir_changed(self) -> bool:
current_files = {
f[:-3]
for f in os.listdir(_CONFIG_DESKTOP_DIR)
if f.endswith(".py") and not f.startswith("_")
}
stale_keys = {k for k, f in _user_key_to_file.items() if f not in current_files}
logger.info(
f"[DesktopWidgets] dir changed: current={current_files}, "
f"stale={stale_keys}, tracked={set(_user_key_to_file.keys())}"
)
for key in stale_keys:
mod_name = _user_key_to_file.pop(key, None)
if mod_name:
sys.modules.pop(f"user_widget_{mod_name}", None)
_user_widget_modules.pop(mod_name, None)
DesktopWidgetRegistry.unregister_user_keys(stale_keys)
_load_user_widgets()
self.rebuild()
return False
# right-click context menu
def _on_window_button_press(self, widget, event: Gdk.EventButton) -> bool:
@@ -212,11 +329,13 @@ class DesktopWidgetWindow(WaylandWindow):
def _show_context_menu(self, event: Gdk.EventButton) -> None:
menu = Gtk.Menu()
label_text = "Done Edit" if self._edit_mode else "Edit Widgets"
edit_item = Gtk.CheckMenuItem(label=label_text)
edit_item.set_active(self._edit_mode)
edit_item.connect("toggled", self._on_edit_toggled)
menu.append(edit_item)
menu.show_all()
menu.popup_at_pointer(event)
+27 -1
View File
@@ -1,11 +1,33 @@
class DesktopWidgetRegistry:
_widgets = {}
_sizes = {}
_default_positions = {}
_user_keys: set[str] = set()
@classmethod
def register(cls, key: str, widget_cls, size: tuple[int, int]):
def register(
cls,
key: str,
widget_cls,
size: tuple[int, int],
default_position: tuple[float, float] | None = None,
):
cls._widgets[key] = widget_cls
cls._sizes[key] = size
if default_position is not None:
cls._default_positions[key] = default_position
@classmethod
def register_user(cls, key: str) -> None:
cls._user_keys.add(key)
@classmethod
def unregister_user_keys(cls, keys: set[str]) -> None:
for key in keys:
cls._widgets.pop(key, None)
cls._sizes.pop(key, None)
cls._default_positions.pop(key, None)
cls._user_keys.discard(key)
@classmethod
def get_widget_class(cls, key: str):
@@ -14,3 +36,7 @@ class DesktopWidgetRegistry:
@classmethod
def get_widget_size(cls, key: str) -> tuple[int, int]:
return cls._sizes.get(key, (174, 174))
@classmethod
def get_default_position(cls, key: str) -> tuple[float, float] | None:
return cls._default_positions.get(key)
+6 -2
View File
@@ -168,5 +168,9 @@ class DesktopCpuInfoWidget(SystemInfoBase):
return True
DesktopWidgetRegistry.register("ram_info", DesktopRamInfoWidget, (174, 174))
DesktopWidgetRegistry.register("cpu_info", DesktopCpuInfoWidget, (174, 174))
DesktopWidgetRegistry.register(
"ram_info", DesktopRamInfoWidget, (174, 174), (0.90521, 0.82732)
)
DesktopWidgetRegistry.register(
"cpu_info", DesktopCpuInfoWidget, (174, 174), (0.81042, 0.82732)
)
+3 -1
View File
@@ -129,4 +129,6 @@ class DesktopWeatherWidget(Box):
super().destroy()
DesktopWidgetRegistry.register("weather", DesktopWeatherWidget, (174, 174))
DesktopWidgetRegistry.register(
"weather", DesktopWeatherWidget, (174, 174), (0.0974, 0.0)
)