diff --git a/README.md b/README.md index 519095e..9df3d10 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ - 一键安装脚本 - `uninstall.sh` - 卸载自动重算脚本与 systemd 用户服务 +- `kwin/manjaro_dock_force_hide` + - 一个 KWin 脚本 + - 用来处理「底部 dock 因通知/attention 弹出后不自动缩回」的问题 + - 逻辑是:dock 弹出后等待 N 秒;如果此时鼠标不在 dock 上,就强制触发一次隐藏 ## 这套美化当前依赖 @@ -66,6 +70,7 @@ Layan 主题请通过 KDE Store 安装,或者使用你自己的 Plasma 6 主 - 安装自动重算脚本到 `~/.local/bin/` - 安装用户级 systemd 服务到 `~/.config/systemd/user/` - 安装 `LogicDock` 预设到 `~/.config/panel-colorizer/presets/LogicDock` +- 安装并启用 `manjaro_dock_force_hide` KWin 脚本 - 启用并立即运行自动重算服务 ### 3. 生效逻辑 @@ -73,6 +78,7 @@ Layan 主题请通过 KDE Store 安装,或者使用你自己的 Plasma 6 主 - 登录后自动执行一次 - 当 KDE 显示器布局配置发生变化时自动执行 - 会自动为每块启用中的屏幕创建/修正底部 dock +- 如果底部 dock 被某些窗口通知/attention 状态顶出来,KWin workaround 会在几秒后强制让它重新隐藏 ## 当前 dock 行为 @@ -83,6 +89,7 @@ Layan 主题请通过 KDE Store 安装,或者使用你自己的 Plasma 6 主 - 长度按内容动态扩展,不固定留大空白 - 每块屏幕都各自有 dock - 显示器变化后自动重算 +- 底部 dock 弹出后,如果鼠标不在 dock 上,会在几秒后自动强制缩回 ## Panel Colorizer 预设说明 @@ -126,6 +133,41 @@ c. 在 KDE 中启用: - 系统设置 → 应用程序样式 → Qt 程序样式 → 选择 `Kvantum` +## Dock 通知弹出后自动缩回 workaround + +这是一个 **KWin 脚本 workaround**,不是 Plasma 当前的原生选项。 + +默认行为: + +- `delaySeconds = 6` +- 当底部 dock 弹出后: + - 如果鼠标正在 dock 上:不强制隐藏 + - 如果鼠标不在 dock 上:6 秒后强制触发一次隐藏 + +### 修改等待秒数 + +例如改成 4 秒: + +```bash +kwriteconfig6 --file ~/.config/kwinrc --group Script-manjaro_dock_force_hide --key delaySeconds 4 +qdbus6 org.kde.KWin /KWin reconfigure +``` + +### 打开调试日志 + +```bash +kwriteconfig6 --file ~/.config/kwinrc --group Script-manjaro_dock_force_hide --key debug true +qdbus6 org.kde.KWin /KWin reconfigure +journalctl --user -f | grep manjaro_dock_force_hide +``` + +### 关闭调试日志 + +```bash +kwriteconfig6 --file ~/.config/kwinrc --group Script-manjaro_dock_force_hide --key debug false +qdbus6 org.kde.KWin /KWin reconfigure +``` + ## 恢复 / 卸载 ```bash diff --git a/install.sh b/install.sh index e03c60e..f7b7480 100755 --- a/install.sh +++ b/install.sh @@ -8,7 +8,21 @@ install -m 644 "$REPO_DIR/systemd/plasma-dock-autofit.service" "$HOME/.config/sy install -m 644 "$REPO_DIR/systemd/plasma-dock-autofit.path" "$HOME/.config/systemd/user/plasma-dock-autofit.path" rm -rf "$HOME/.config/panel-colorizer/presets/LogicDock" cp -a "$REPO_DIR/presets/LogicDock" "$HOME/.config/panel-colorizer/presets/LogicDock" +if kpackagetool6 --type KWin/Script --show manjaro_dock_force_hide >/dev/null 2>&1; then + kpackagetool6 --type KWin/Script --upgrade "$REPO_DIR/kwin/manjaro_dock_force_hide" +else + kpackagetool6 --type KWin/Script --install "$REPO_DIR/kwin/manjaro_dock_force_hide" +fi +kwriteconfig6 --file "$HOME/.config/kwinrc" --group Plugins --key manjaro_dock_force_hideEnabled true +if [[ -z "$(kreadconfig6 --file "$HOME/.config/kwinrc" --group Script-manjaro_dock_force_hide --key delaySeconds)" ]]; then + kwriteconfig6 --file "$HOME/.config/kwinrc" --group Script-manjaro_dock_force_hide --key delaySeconds 6 +fi +if [[ -z "$(kreadconfig6 --file "$HOME/.config/kwinrc" --group Script-manjaro_dock_force_hide --key debug)" ]]; then + kwriteconfig6 --file "$HOME/.config/kwinrc" --group Script-manjaro_dock_force_hide --key debug false +fi systemctl --user daemon-reload systemctl --user enable --now plasma-dock-autofit.service plasma-dock-autofit.path "$HOME/.local/bin/recalc-plasma-bottom-docks.sh" -echo 'Installed. If Panel Colorizer was just updated, log out and back in once.' +qdbus6 org.kde.KWin /KWin reconfigure >/dev/null 2>&1 || true +qdbus6 org.kde.KWin /Scripting org.kde.kwin.Scripting.start >/dev/null 2>&1 || true +echo 'Installed. If Panel Colorizer or KWin scripts were just updated, log out and back in once.' diff --git a/kwin/manjaro_dock_force_hide/contents/code/main.js b/kwin/manjaro_dock_force_hide/contents/code/main.js new file mode 100644 index 0000000..8f2e57e --- /dev/null +++ b/kwin/manjaro_dock_force_hide/contents/code/main.js @@ -0,0 +1,175 @@ +var delaySeconds = parseInt(readConfig("delaySeconds", 6), 10); +if (!(delaySeconds > 0)) { + delaySeconds = 6; +} + +var debugValue = readConfig("debug", false); +var debug = debugValue === true || debugValue === "true" || debugValue === 1 || debugValue === "1"; + +var trackedWindows = []; +var forceHideTimer = new QTimer(); +forceHideTimer.singleShot = true; + +function log(message) { + if (debug) { + print("manjaro_dock_force_hide: " + message); + } +} + +function rectContains(rect, point) { + return point.x >= rect.x && + point.x < rect.x + rect.width && + point.y >= rect.y && + point.y < rect.y + rect.height; +} + +function isBottomPlasmaDock(window) { + if (!window || !window.dock || window.resourceClass !== "plasmashell" || !window.output) { + return false; + } + + var frame = window.frameGeometry; + var output = window.output.geometry; + return (frame.y + frame.height) >= (output.y + output.height - 4); +} + +function visibleBottomDocks() { + var docks = []; + var stack = workspace.stackingOrder; + + for (var i = 0; i < stack.length; ++i) { + var window = stack[i]; + if (isBottomPlasmaDock(window) && !window.hidden) { + docks.push(window); + } + } + + return docks; +} + +function cursorInsideVisibleDock() { + var cursorPos = workspace.cursorPos; + var docks = visibleBottomDocks(); + + for (var i = 0; i < docks.length; ++i) { + if (rectContains(docks[i].frameGeometry, cursorPos)) { + return true; + } + } + + return false; +} + +function restartForceHideTimer(ms) { + if (!(ms > 0)) { + return; + } + + forceHideTimer.stop(); + forceHideTimer.interval = ms; + forceHideTimer.start(); + log("timer armed for " + ms + "ms"); +} + +function forceHideBottomPanels() { + var plasmaScript = + "for (var i = 0; i < panelIds.length; ++i) {" + + " var panel = panelById(panelIds[i]);" + + " if (panel.location == 'bottom' && panel.hiding == 'autohide') {" + + " panel.hiding = 'none';" + + " panel.hiding = 'autohide';" + + " }" + + "}"; + + callDBus( + "org.kde.plasmashell", + "/PlasmaShell", + "org.kde.PlasmaShell", + "evaluateScript", + plasmaScript, + function () { + log("forced bottom dock hide pulse sent"); + } + ); +} + +function onForceHideTimer() { + var docks = visibleBottomDocks(); + if (docks.length === 0) { + log("timer fired but no visible bottom docks"); + return; + } + + if (cursorInsideVisibleDock()) { + log("cursor is on a dock; delaying force-hide recheck"); + restartForceHideTimer(1000); + return; + } + + log("forcing dock hide"); + forceHideBottomPanels(); +} + +function onDockVisibilityMaybeChanged(window) { + if (!isBottomPlasmaDock(window)) { + return; + } + + if (!window.hidden) { + log("bottom dock shown"); + restartForceHideTimer(delaySeconds * 1000); + return; + } + + if (visibleBottomDocks().length === 0) { + log("all bottom docks hidden; stopping timer"); + forceHideTimer.stop(); + } +} + +function trackWindow(window) { + if (!window) { + return; + } + + for (var i = 0; i < trackedWindows.length; ++i) { + if (trackedWindows[i] === window) { + return; + } + } + + trackedWindows.push(window); + + window.hiddenChanged.connect(function () { + onDockVisibilityMaybeChanged(window); + }); + + window.outputChanged.connect(function () { + onDockVisibilityMaybeChanged(window); + }); + + if (isBottomPlasmaDock(window) && !window.hidden) { + restartForceHideTimer(delaySeconds * 1000); + } +} + +function init() { + forceHideTimer.timeout.connect(onForceHideTimer); + + var stack = workspace.stackingOrder; + for (var i = 0; i < stack.length; ++i) { + trackWindow(stack[i]); + } + + workspace.windowAdded.connect(function (window) { + trackWindow(window); + }); + + if (visibleBottomDocks().length > 0) { + restartForceHideTimer(delaySeconds * 1000); + } + + log("loaded with delaySeconds=" + delaySeconds); +} + +init(); diff --git a/kwin/manjaro_dock_force_hide/contents/config/main.xml b/kwin/manjaro_dock_force_hide/contents/config/main.xml new file mode 100644 index 0000000..b0f8205 --- /dev/null +++ b/kwin/manjaro_dock_force_hide/contents/config/main.xml @@ -0,0 +1,18 @@ + + + + + + + + 6 + + + + false + + + diff --git a/kwin/manjaro_dock_force_hide/metadata.json b/kwin/manjaro_dock_force_hide/metadata.json new file mode 100644 index 0000000..74cfd6c --- /dev/null +++ b/kwin/manjaro_dock_force_hide/metadata.json @@ -0,0 +1,12 @@ +{ + "KPackageStructure": "KWin/Script", + "KPlugin": { + "Id": "manjaro_dock_force_hide", + "Name": "Manjaro Dock Force Hide", + "Description": "Force-hide bottom auto-hide Plasma docks a few seconds after they pop up.", + "Version": "1.0.0", + "License": "MIT" + }, + "X-Plasma-API": "javascript", + "X-Plasma-MainScript": "contents/code/main.js" +} diff --git a/scripts/recalc-plasma-bottom-docks.sh b/scripts/recalc-plasma-bottom-docks.sh index fc16173..a40a470 100755 --- a/scripts/recalc-plasma-bottom-docks.sh +++ b/scripts/recalc-plasma-bottom-docks.sh @@ -64,6 +64,35 @@ print(json.dumps(sorted(items, key=lambda x: (x['containment'], x['applet'])))) PY } +get_bottom_taskmanagers() { + python - <<'PY' +import configparser, json, os, pathlib, re +cfg = pathlib.Path(os.path.expanduser('~/.config/plasma-org.kde.plasma.desktop-appletsrc')) +cp = configparser.RawConfigParser(interpolation=None) +cp.optionxform = str +cp.read(cfg) +items = [] +for sec in cp.sections(): + m = re.fullmatch(r'Containments\]\[(\d+)', sec) + if not m: + continue + cid = int(m.group(1)) + if cp.get(sec, 'plugin', fallback='') != 'org.kde.panel': + continue + if cp.get(sec, 'location', fallback='') not in ('4', 'bottom'): + continue + for asec in cp.sections(): + am = re.fullmatch(rf'Containments\]\[{cid}\]\[Applets\]\[(\d+)', asec) + if not am: + continue + if cp.get(asec, 'plugin', fallback='') != 'org.kde.plasma.icontasks': + continue + aid = int(am.group(1)) + items.append({'containment': cid, 'applet': aid}) +print(json.dumps(sorted(items, key=lambda x: (x['containment'], x['applet'])))) +PY +} + ensure_bottom_panels() { local screen_count="$1" local targets @@ -108,6 +137,27 @@ for (var i = 0; i < targetScreens.length; ++i) { " } +apply_taskmanager_behavior() { + local json + json="$(get_bottom_taskmanagers)" + [[ -z "$json" || "$json" == "[]" ]] && return 0 + python - <<'PY' "$json" "$CONFIG_FILE" +import json, subprocess, sys +pairs = json.loads(sys.argv[1]) +config_file = sys.argv[2] +for item in pairs: + cid = item['containment'] + aid = item['applet'] + subprocess.call([ + 'kwriteconfig6', '--file', config_file, + '--group', 'Containments', '--group', str(cid), + '--group', 'Applets', '--group', str(aid), + '--group', 'Configuration', '--group', 'General', + '--key', 'unhideOnAttention', 'false' + ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +PY +} + apply_colorizer_preset() { local json json="$(get_bottom_colorizers)" @@ -142,6 +192,7 @@ main() { local screen_count screen_count="$(get_screen_count)" ensure_bottom_panels "$screen_count" + apply_taskmanager_behavior apply_colorizer_preset } diff --git a/uninstall.sh b/uninstall.sh index d8fcf91..f7a7999 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -4,5 +4,8 @@ systemctl --user disable --now plasma-dock-autofit.path plasma-dock-autofit.serv rm -f "$HOME/.config/systemd/user/plasma-dock-autofit.path" "$HOME/.config/systemd/user/plasma-dock-autofit.service" rm -f "$HOME/.local/bin/recalc-plasma-bottom-docks.sh" rm -rf "$HOME/.config/panel-colorizer/presets/LogicDock" +kwriteconfig6 --file "$HOME/.config/kwinrc" --group Plugins --key manjaro_dock_force_hideEnabled false +kpackagetool6 --type KWin/Script --remove manjaro_dock_force_hide >/dev/null 2>&1 || true systemctl --user daemon-reload -echo 'Removed auto-fit service, script and custom preset.' +qdbus6 org.kde.KWin /KWin reconfigure >/dev/null 2>&1 || true +echo 'Removed auto-fit service, custom preset and KWin dock force-hide workaround.'