From 06b84e5ba9b8c7f064adae0fc71d64efcb17c2e9 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 1 Jul 2026 09:32:26 +0200 Subject: [PATCH] initial commit --- .gitignore | 23 ++++++ contents/code/main.js | 170 +++++++++++++++++++++++++++++++++++++++ contents/config/main.xml | 13 +++ install.sh | 22 +++++ metadata.json | 18 +++++ 5 files changed, 246 insertions(+) create mode 100644 .gitignore create mode 100644 contents/code/main.js create mode 100644 contents/config/main.xml create mode 100755 install.sh create mode 100644 metadata.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4db1d50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Build artifacts +*.kwinscript + +# Editor files +*~ +*.swp +*.swo +.vscode/ +.idea/ +*.kate-swp + +# OS files +.DS_Store +Thumbs.db +.directory + +# Node (if using build tools) +node_modules/ +package-lock.json + +# Temporary files +*.log +*.tmp diff --git a/contents/code/main.js b/contents/code/main.js new file mode 100644 index 0000000..d646883 --- /dev/null +++ b/contents/code/main.js @@ -0,0 +1,170 @@ +/** + * KWin Scratchpad — i3-like hide/show/cycle behavior. + * + * Two shortcuts: + * Meta+Shift+- : Move active window to scratchpad (hide it). + * Meta+- : Show/hide/cycle through scratchpad windows. + * + * Cycle behavior (Meta+-): + * - If a scratchpad window is visible: hide it. + * - If no scratchpad window is visible: show the next one. + * - Wraps around the list. + * + * A window leaves the scratchpad ONLY when: + * - It is closed. + * - It is explicitly moved to a desktop. + */ + +(function () { + "use strict"; + + // --- State --- + var scratchpad = []; // ordered list of windows in the scratchpad + var visibleWindow = null; // the currently-visible scratchpad window, or null + var nextIndex = 0; // which window to show on the next "show" press + + // --- Helpers --- + + function indexInScratchpad(win) { + for (var i = 0; i < scratchpad.length; i++) { + if (scratchpad[i] === win) return i; + } + return -1; + } + + function isScratchpadWindow(win) { + return indexInScratchpad(win) >= 0; + } + + function removeScratchpadWindow(win) { + var i = indexInScratchpad(win); + if (i < 0) return false; + + scratchpad.splice(i, 1); + + if (win === visibleWindow) { + visibleWindow = null; + } + + // Keep nextIndex in bounds + if (scratchpad.length === 0) { + nextIndex = 0; + } else if (nextIndex >= scratchpad.length) { + nextIndex = 0; + } + return true; + } + + function hideWindow(win) { + win.minimized = true; + win.skipTaskbar = true; + win.skipPager = true; + win.skipSwitcher = true; + } + + function showWindow(win) { + win.desktops = [workspace.currentDesktop]; + win.minimized = false; + win.skipTaskbar = true; + win.skipPager = true; + win.skipSwitcher = true; + workspace.activeWindow = win; + } + + function restoreWindow(win) { + win.minimized = false; + win.skipTaskbar = false; + win.skipPager = false; + win.skipSwitcher = false; + } + + // --- Shortcut 1: Add to scratchpad (Meta+Shift+-) --- + + function scratchpadAdd() { + var active = workspace.activeWindow; + if (!active) return; + if (active.desktopWindow || active.dock || active.specialWindow) return; + if (isScratchpadWindow(active)) return; + + scratchpad.push(active); + hideWindow(active); + print("Scratchpad: added — " + active.caption + " (" + scratchpad.length + " total)"); + } + + // --- Shortcut 2: Show/hide/cycle (Meta+-) --- + + function scratchpadCycle() { + if (scratchpad.length === 0) return; + + // If a scratchpad window is currently visible — hide it. Full stop. + if (visibleWindow !== null) { + hideWindow(visibleWindow); + // Advance nextIndex past the one we just hid + var idx = indexInScratchpad(visibleWindow); + if (idx >= 0) { + nextIndex = (idx + 1) % scratchpad.length; + } + visibleWindow = null; + return; + } + + // No scratchpad window is visible — show the next one. + if (nextIndex >= scratchpad.length) { + nextIndex = 0; + } + visibleWindow = scratchpad[nextIndex]; + showWindow(visibleWindow); + } + + // --- Track window closure --- + + workspace.windowRemoved.connect(function (win) { + removeScratchpadWindow(win); + }); + + // --- Track desktop changes (window explicitly moved to a desktop) --- + + function watchDesktopChange(win) { + win.desktopsChanged.connect(function () { + if (!isScratchpadWindow(win)) return; + + // If it's the visible one and not minimized, this is likely our + // own showWindow() call — ignore. + if (win === visibleWindow && !win.minimized) { + return; + } + + // User moved it to a desktop — release from scratchpad. + removeScratchpadWindow(win); + restoreWindow(win); + print("Scratchpad: window released to desktop — " + win.caption); + }); + } + + var clients = workspace.stackingOrder; + for (var i = 0; i < clients.length; i++) { + watchDesktopChange(clients[i]); + } + + workspace.windowAdded.connect(function (win) { + watchDesktopChange(win); + }); + + // --- Register shortcuts --- + + registerShortcut( + "ScratchpadAdd", + "Move Window to Scratchpad", + "Meta+Shift+-", + scratchpadAdd + ); + + registerShortcut( + "ScratchpadCycle", + "Scratchpad Show/Hide/Cycle", + "Meta+-", + scratchpadCycle + ); + + print("KWin Scratchpad loaded"); +})(); diff --git a/contents/config/main.xml b/contents/config/main.xml new file mode 100644 index 0000000..2dbafd0 --- /dev/null +++ b/contents/config/main.xml @@ -0,0 +1,13 @@ + + + + + + + Meta+- + + + diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a351c9b --- /dev/null +++ b/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Install kwin-scratchpad KWin script +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PACKAGE_DIR="$SCRIPT_DIR/" + +echo "Installing kwin-scratchpad..." + +# Remove old version if present +kpackagetool6 --type=KWin/Script -r kwin-scratchpad 2>/dev/null || true + +# Install +kpackagetool6 --type=KWin/Script -i "$PACKAGE_DIR" + +# Enable the script +kwriteconfig6 --file kwinrc --group Plugins --key kwin-scratchpadEnabled true + +# Reload KWin +qdbus6 org.kde.KWin /KWin reconfigure + +echo "Done. Shortcut: Meta+- (configurable in System Settings > Shortcuts)" diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..937bcf2 --- /dev/null +++ b/metadata.json @@ -0,0 +1,18 @@ +{ + "KPlugin": { + "Name": "Scratchpad", + "Description": "i3-like scratchpad: hide the active window, then cycle through hidden scratchpad windows on subsequent presses", + "Icon": "preferences-system-windows", + "Authors": [ + { + "Name": "lkn" + } + ], + "Id": "kwin-scratchpad", + "Version": "1.0", + "License": "GPLv3" + }, + "X-Plasma-API": "javascript", + "X-Plasma-MainScript": "code/main.js", + "KPackageStructure": "KWin/Script" +}