dock #44

Merged
S4NKALP merged 14 commits from feat/dock into macos 2026-07-30 17:34:30 +00:00
16 changed files with 127 additions and 40 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
* @S4NKALP
+1 -2
View File
@@ -2,9 +2,8 @@ name: CodeQL
on: on:
push: push:
branches: [main, dev] branches: [macos]
pull_request: pull_request:
branches: [main, dev]
schedule: schedule:
- cron: "0 6 * * 1" - cron: "0 6 * * 1"
-1
View File
@@ -2,7 +2,6 @@ name: Commit Lint
on: on:
pull_request: pull_request:
branches: [main, dev]
permissions: permissions:
contents: read contents: read
+1 -2
View File
@@ -2,7 +2,7 @@ name: Install Script Test
on: on:
push: push:
branches: [main, dev] branches: [macos]
paths: paths:
- "install.sh" - "install.sh"
- "src/window/globalmenu/**" - "src/window/globalmenu/**"
@@ -10,7 +10,6 @@ on:
- "pyproject.toml" - "pyproject.toml"
- "uv.lock" - "uv.lock"
pull_request: pull_request:
branches: [main, dev]
paths: paths:
- "install.sh" - "install.sh"
- "src/window/globalmenu/**" - "src/window/globalmenu/**"
+1 -2
View File
@@ -2,9 +2,8 @@ name: Pre-commit
on: on:
push: push:
branches: [main, dev] branches: [macos]
pull_request: pull_request:
branches: [main, dev]
permissions: permissions:
contents: read contents: read
+2
View File
@@ -143,6 +143,8 @@ See [Desktop Widgets Guide](docs/desktop_widgets.md) for full docs and examples.
## Documentation ## Documentation
> **Note:** These docs were written with the assistance of LLM tools. Some phrasing may reflect that.
| Doc | What's inside | | Doc | What's inside |
| --------------------------------------------- | -------------------------------------------------- | | --------------------------------------------- | -------------------------------------------------- |
| [Installation Guide](docs/installation.md) | Automated & manual install, dependencies | | [Installation Guide](docs/installation.md) | Automated & manual install, dependencies |
+1
View File
@@ -0,0 +1 @@
module.exports = { extends: ["@commitlint/config-conventional"] };
+5 -3
View File
@@ -14,6 +14,7 @@ from tomlkit import document as toml_document
from tomlkit import dump as toml_dump from tomlkit import dump as toml_dump
from tomlkit import item as toml_item from tomlkit import item as toml_item
from tomlkit import load as toml_load from tomlkit import load as toml_load
from tomlkit import loads as toml_loads
from utils.gtk_utils import toml_file from utils.gtk_utils import toml_file
@@ -126,12 +127,13 @@ class ConfigService:
self._config = toml_load(f) self._config = toml_load(f)
else: else:
try: try:
from utils.constants import DEFAULT from utils.constants import generate_default_toml
os.makedirs(os.path.dirname(self._config_file), exist_ok=True) os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
self._config = self._dict_to_toml(DEFAULT) raw = generate_default_toml()
self._config = toml_loads(raw)
with open(self._config_file, "w") as f: with open(self._config_file, "w") as f:
toml_dump(self._config, f) f.write(raw)
logger.info( logger.info(
f"[ConfigService] Generated default config at {self._config_file}" f"[ConfigService] Generated default config at {self._config_file}"
) )
+26 -10
View File
@@ -49,19 +49,35 @@ def load_config():
service = start_config_service() service = start_config_service()
return service.get_all() return service.get_all()
except ImportError: except ImportError:
# Fallback to direct file loading pass
config = {}
if os.path.exists(CONFIG_FILE): try:
try: from utils.constants import generate_default_toml
import tomlkit except ImportError:
generate_default_toml = None
with open(CONFIG_FILE) as f: config = {}
config = dict(tomlkit.load(f))
except Exception as e:
logger.error(f"Error loading config: {e}")
return config if os.path.exists(CONFIG_FILE):
try:
import tomlkit
with open(CONFIG_FILE) as f:
config = dict(tomlkit.load(f))
except Exception as e:
logger.error(f"Error loading config: {e}")
elif generate_default_toml:
try:
raw = generate_default_toml()
with open(CONFIG_FILE, "w") as f:
f.write(raw)
import tomlkit
config = dict(tomlkit.loads(raw))
except Exception as e:
logger.error(f"Error generating default config: {e}")
return config
NOTIFICATION_TIMEOUT_STR = "5s" NOTIFICATION_TIMEOUT_STR = "5s"
+63
View File
@@ -1,3 +1,7 @@
from tomlkit import document as _document
from tomlkit import dumps as _dumps
from tomlkit import table as _table
DEFAULT = { DEFAULT = {
"general": { "general": {
"debug": False, "debug": False,
@@ -11,6 +15,7 @@ DEFAULT = {
"always_occluded": False, "always_occluded": False,
"icon_size": 52, "icon_size": 52,
"hide_special_workspace_apps": True, "hide_special_workspace_apps": True,
"hover_scale": 1.6,
}, },
"panel": { "panel": {
"imac_button": True, "imac_button": True,
@@ -42,3 +47,61 @@ DEFAULT = {
"limited_apps_history": ["Spotify"], "limited_apps_history": ["Spotify"],
}, },
} }
def generate_default_toml() -> str:
"""Build default config.toml with inline comments."""
doc = _document()
general = _table()
general.add("debug", False)
general.add("wallpapers_dir", "src/assets/wallpaper_example/")
general.add("keyboard_layouts", ["us"])
general.add("weather_location", "")
doc.add("general", general)
dock = _table()
dock.add("enabled", True)
dock.add("auto_hide", True)
dock.add("always_occluded", False)
dock.add("icon_size", 52)
dock.add("hide_special_workspace_apps", True)
dock.add("hover_scale", 1.6)
dock["hover_scale"].comment(
"1.0 (min, no effect) to 2.0 (max) | >2.0 not recommended — icons get blurry/pixelated on hover"
)
doc.add("dock", dock)
panel = _table()
panel.add("imac_button", True)
panel.add("systray", True)
panel.add("systray_ignore", ["blueman", "network"])
panel.add("control_center", True)
panel.add("search", True)
panel.add("global_menu", True)
panel.add("network", True)
panel.add("battery", True)
panel.add("notification_center", True)
panel.add("workspace_indicator", True)
panel.add("bluetooth", True)
panel.add("date_time", True)
panel.add("night_light_temperature", 4500)
panel.add("notch", True)
panel.add("osd", True)
panel.add("hide_special_workspace", True)
panel.add("custom_mods", True)
doc.add("panel", panel)
switcher = _table()
switcher.add("live_preview", True)
switcher.add("live_preview_delay_ms", 200)
switcher.add("window_switcher", True)
doc.add("switcher", switcher)
notification = _table()
notification.add("timeout", "5s")
notification.add("ignored_apps", ["Hyprshot"])
notification.add("limited_apps_history", ["Spotify"])
doc.add("notification", notification)
return _dumps(doc)
+5 -5
View File
@@ -41,11 +41,11 @@ from .constants import (
INDICATOR_COLOR, INDICATOR_COLOR,
INDICATOR_H, INDICATOR_H,
INDICATOR_RADIUS, INDICATOR_RADIUS,
MAX_SCALE,
MIN_SCALE, MIN_SCALE,
PINNED_APPS_FILE, PINNED_APPS_FILE,
SEPARATOR_COLOR, SEPARATOR_COLOR,
SEPARATOR_WIDTH, SEPARATOR_WIDTH,
max_scale,
) )
from .items import DockHitTest, DockItem, DockModel from .items import DockHitTest, DockItem, DockModel
from .layout import DockLayout from .layout import DockLayout
@@ -126,7 +126,7 @@ class DockCanvas(Gtk.DrawingArea):
return int(config().get("dock.icon_size", 52)) return int(config().get("dock.icon_size", 52))
def _icon_cache_size(self) -> int: def _icon_cache_size(self) -> int:
return int(self._base_icon_size() * MAX_SCALE) return int(self._base_icon_size() * max_scale())
def _get_desktop_apps(self) -> list: def _get_desktop_apps(self) -> list:
if not self._desktop_apps: if not self._desktop_apps:
@@ -140,7 +140,7 @@ class DockCanvas(Gtk.DrawingArea):
def _canvas_height(self) -> int: def _canvas_height(self) -> int:
size = self._base_icon_size() size = self._base_icon_size()
return int( return int(
size * MAX_SCALE + CANVAS_TOP_PAD + 2 * BG_PADDING_V + INDICATOR_H + 4 size * max_scale() + CANVAS_TOP_PAD + 2 * BG_PADDING_V + INDICATOR_H + 4
) )
def _canvas_max_width(self) -> int: def _canvas_max_width(self) -> int:
@@ -149,7 +149,7 @@ class DockCanvas(Gtk.DrawingArea):
return 200 return 200
size = self._base_icon_size() size = self._base_icon_size()
return int( return int(
n * size * MAX_SCALE + max(n - 1, 0) * ICON_GAP + 2 * BG_PADDING_H + 40 n * size * max_scale() + max(n - 1, 0) * ICON_GAP + 2 * BG_PADDING_H + 40
) )
def _canvas_min_width(self) -> int: def _canvas_min_width(self) -> int:
@@ -655,7 +655,7 @@ class DockCanvas(Gtk.DrawingArea):
bg_h = size + 2 * BG_PADDING_V bg_h = size + 2 * BG_PADDING_V
bg_y = h - INDICATOR_H - bg_h bg_y = h - INDICATOR_H - bg_h
baseline_y = bg_y + BG_PADDING_V + size baseline_y = bg_y + BG_PADDING_V + size
icon_top_y = baseline_y - size * MAX_SCALE icon_top_y = baseline_y - size * max_scale()
self._mouse_inside = ( self._mouse_inside = (
base_start_x <= event.x <= base_start_x + total_base base_start_x <= event.x <= base_start_x + total_base
and icon_top_y <= event.y <= baseline_y and icon_top_y <= event.y <= baseline_y
+6 -1
View File
@@ -1,3 +1,4 @@
from services.config import config
from utils.gtk_utils import toml_file from utils.gtk_utils import toml_file
PINNED_APPS_FILE = toml_file("dock.toml") PINNED_APPS_FILE = toml_file("dock.toml")
@@ -7,7 +8,11 @@ IDLE_THRESHOLD = 0.0005
ANIM_FPS = 60 ANIM_FPS = 60
ANIM_INTERVAL_MS = 1000 // ANIM_FPS ANIM_INTERVAL_MS = 1000 // ANIM_FPS
MAX_SCALE = 2.0
def max_scale() -> float:
return float(config().get("dock.hover_scale", 2.0))
MIN_SCALE = 1.0 MIN_SCALE = 1.0
SIGMA_FACTOR = 1.3 SIGMA_FACTOR = 1.3
+2 -2
View File
@@ -5,9 +5,9 @@ from .constants import (
BG_PADDING_V, BG_PADDING_V,
ICON_GAP, ICON_GAP,
INDICATOR_H, INDICATOR_H,
MAX_SCALE,
MIN_SCALE, MIN_SCALE,
SIGMA_FACTOR, SIGMA_FACTOR,
max_scale,
) )
from .items import DockItem from .items import DockItem
@@ -27,7 +27,7 @@ class DockLayout:
return return
sigma = base_icon_size * SIGMA_FACTOR sigma = base_icon_size * SIGMA_FACTOR
amplitude = MAX_SCALE - MIN_SCALE amplitude = max_scale() - MIN_SCALE
n = len(items) n = len(items)
base_w = base_icon_size + ICON_GAP base_w = base_icon_size + ICON_GAP
+3
View File
@@ -135,6 +135,9 @@ class Dock(Window):
if config().has_changed("dock.hide_special_workspace_apps", old_config): if config().has_changed("dock.hide_special_workspace_apps", old_config):
self.canvas._rebuild_model() self.canvas._rebuild_model()
if config().has_changed("dock.hover_scale", old_config):
self.canvas.update_icon_size()
def _update_visibility(self) -> None: def _update_visibility(self) -> None:
if config().get("dock.enabled", True): if config().get("dock.enabled", True):
self.show() self.show()
+10 -4
View File
@@ -1,4 +1,4 @@
from fabric.utils import Gdk, Gtk, logger from fabric.utils import Gdk, Gtk
from fabric.widgets.box import Box from fabric.widgets.box import Box
from fabric.widgets.button import Button from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox from fabric.widgets.centerbox import CenterBox
@@ -90,12 +90,13 @@ class SettingsEntry(Entry):
def on_change(self, entry, *args): def on_change(self, entry, *args):
value = entry.get_text() value = entry.get_text()
# Try to convert to int if possible
try: try:
if value.isdigit(): if value.isdigit():
value = int(value) value = int(value)
except Exception as e: else:
logger.error(f"An error occurred: {e}") value = float(value)
except (ValueError, TypeError):
pass
config().set(self.config_key, value) config().set(self.config_key, value)
config().save() config().save()
@@ -400,6 +401,11 @@ class SettingsWindow(Gtk.Window):
SettingsSwitch("dock.hide_special_workspace_apps"), SettingsSwitch("dock.hide_special_workspace_apps"),
"Hide apps from special workspace in dock", "Hide apps from special workspace in dock",
), ),
SettingsRow(
"Hover Scale",
SettingsEntry("dock.hover_scale"),
"Icon scale on hover (1.0 = off, 1.6 = default, 2.0 = max)",
),
], ],
) )
@@ -99,11 +99,7 @@ class ClipboardPlugin(SpotlightPlugin):
def _load_history(self) -> None: def _load_history(self) -> None:
result = run_command(["cliphist", "list"], timeout=5) result = run_command(["cliphist", "list"], timeout=5)
raw = result.stdout if isinstance(result.stdout, str) else "" raw = result.stdout if isinstance(result.stdout, str) else ""
logger.info(
f"[Clipboard] _load_history: stdout type={type(result.stdout).__name__} len={len(raw)} returncode={result.returncode}"
)
lines = raw.splitlines() lines = raw.splitlines()
logger.info(f"[Clipboard] _load_history: {len(lines)} lines from cliphist")
new_entries = [] new_entries = []
for line in lines[:100]: for line in lines[:100]:
if "\t" not in line: if "\t" not in line:
@@ -122,13 +118,9 @@ class ClipboardPlugin(SpotlightPlugin):
if "binary data" in content: if "binary data" in content:
entry["type"] = "image" entry["type"] = "image"
new_entries.append(entry) new_entries.append(entry)
logger.info(f"[Clipboard] _load_history: parsed {len(new_entries)} entries")
with self._history_lock: with self._history_lock:
self._history.clear() self._history.clear()
self._history.extend(new_entries) self._history.extend(new_entries)
logger.info(
f"[Clipboard] _load_history: history now has {len(self._history)} entries"
)
def _filter_entries(self, query: str) -> list[dict]: def _filter_entries(self, query: str) -> list[dict]:
q = query.strip().lower() q = query.strip().lower()