-
Notifications
You must be signed in to change notification settings - Fork 0
PR-04: KWin Event Monitor (JavaScript Integration) #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
01e72e4
a3442c7
0702640
20c2c42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # Shortcut Sage Configuration | ||
| # Place this file in your config directory (default: ~/.config/shortcut-sage/) | ||
|
|
||
| [general] | ||
| # Enable debug logging (verbose output) | ||
| debug = false | ||
|
|
||
| [privacy] | ||
| # Capture and use window titles (captions) in suggestion logic | ||
| # - true: Full window titles sent to daemon (e.g., "Mozilla Firefox - Stack Overflow") | ||
| # RECOMMENDED for better, more context-aware suggestions | ||
| # - false: Only application resource class sent (e.g., "firefox") | ||
| # Limits suggestion accuracy for privacy | ||
| # | ||
| # Window titles enable more targeted suggestions based on: | ||
| # - Document names (suggest Kate shortcuts when editing *.py files) | ||
| # - Web page context (suggest browser shortcuts on specific sites) | ||
| # - More nuanced workflow understanding | ||
| capture_window_titles = false | ||
|
|
||
| # Redact window titles from persistent logs (even if capture_window_titles = true) | ||
| # Set to false if you want full audit trails for debugging/improvement | ||
| redact_titles_from_logs = true | ||
|
|
||
| [engine] | ||
| # Ring buffer size in seconds (how far back to look for patterns) | ||
| buffer_size_seconds = 3 | ||
|
|
||
| # Default cooldown in seconds (can be overridden per-rule in rules.yaml) | ||
| default_cooldown_seconds = 300 | ||
|
|
||
| # Maximum number of suggestions to show simultaneously | ||
| max_suggestions = 3 | ||
|
|
||
| [overlay] | ||
| # Show the suggestion overlay | ||
| enabled = true | ||
|
|
||
| # Overlay position (top-left, top-right, bottom-left, bottom-right) | ||
| position = "top-left" | ||
|
|
||
| # Overlay offset from screen edge in pixels | ||
| offset_x = 20 | ||
| offset_y = 20 | ||
|
|
||
| # Auto-hide overlay after N seconds (0 = never auto-hide) | ||
| auto_hide_seconds = 0 | ||
|
|
||
| # Overlay transparency (0.0 = invisible, 1.0 = opaque) | ||
| opacity = 0.9 | ||
|
|
||
| [logging] | ||
| # Log format: "json" or "ndjson" | ||
| format = "ndjson" | ||
|
|
||
| # Log rotation: maximum file size in MB before rotation | ||
| max_log_size_mb = 10 | ||
|
|
||
| # Number of rotated log files to keep | ||
| max_log_files = 5 | ||
|
|
||
| # Log level: "debug", "info", "warning", "error" | ||
| level = "info" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,125 +1,176 @@ | ||
| /* | ||
| * Shortcut Sage Event Monitor - KWin Script | ||
| * Monitors KDE Plasma events and sends them to Shortcut Sage daemon | ||
| /** | ||
| * Shortcut Sage - KWin Event Monitor | ||
| * | ||
| * Monitors desktop events and sends them to the Shortcut Sage daemon via DBus. | ||
| * | ||
| * Events monitored: | ||
| * - Desktop/workspace switches | ||
| * - Window focus changes (including window titles for better context) | ||
| * - Show desktop state changes | ||
| * | ||
| * Dev shortcut: Meta+Shift+S sends a test event | ||
| * | ||
| * Window titles are sent to improve suggestion accuracy. The daemon's | ||
| * config.toml controls whether titles are used for suggestions and/or | ||
| * logged. Configure privacy.capture_window_titles to balance accuracy | ||
| * vs. privacy based on your preferences. | ||
| */ | ||
|
|
||
| // Configuration | ||
| const DAEMON_SERVICE = "org.shortcutsage.Daemon"; | ||
| const DAEMON_PATH = "/org/shortcutsage/Daemon"; | ||
| // DBus connection to Shortcut Sage daemon | ||
| const BUS_NAME = "org.shortcutsage.Daemon"; | ||
| const OBJECT_PATH = "/org/shortcutsage/Daemon"; | ||
| const INTERFACE = "org.shortcutsage.Daemon"; | ||
|
|
||
| // Initialize DBus interface | ||
| function initDBus() { | ||
| try { | ||
| var dbusInterface = workspace.knownInterfaces[DAEMON_SERVICE]; | ||
| if (dbusInterface) { | ||
| print("Found Shortcut Sage daemon interface"); | ||
| return true; | ||
| } else { | ||
| print("Shortcut Sage daemon not available"); | ||
| return false; | ||
| } | ||
| } catch (error) { | ||
| print("Failed to connect to Shortcut Sage daemon: " + error); | ||
| return false; | ||
| // Logging configuration | ||
| const DEBUG = false; // Set to true for verbose logging during development | ||
| const LOG_PREFIX = "[ShortcutSage]"; | ||
|
|
||
| // Helper function for logging | ||
| function log(message) { | ||
| if (DEBUG) { | ||
| console.log(LOG_PREFIX + " " + message); | ||
| } | ||
| } | ||
|
|
||
| // Function to send event to daemon via DBus | ||
| function logError(message) { | ||
| console.error(LOG_PREFIX + " ERROR: " + message); | ||
| } | ||
|
|
||
| // Initialize the script | ||
| log("Initializing KWin Event Monitor"); | ||
|
|
||
| /** | ||
| * Send an event to the daemon via DBus | ||
| * @param {string} type - Event type (e.g., "window_focus", "desktop_switch") | ||
| * @param {string} action - Action name (e.g., "show_desktop", "tile_left") | ||
| * @param {Object} metadata - Additional metadata (optional) | ||
| */ | ||
| function sendEvent(type, action, metadata) { | ||
| // Using DBus to call the daemon's SendEvent method | ||
| callDBus( | ||
| DAEMON_SERVICE, | ||
| DAEMON_PATH, | ||
| DAEMON_SERVICE, | ||
| "SendEvent", | ||
| JSON.stringify({ | ||
| try { | ||
| // Build event object | ||
| const event = { | ||
| timestamp: new Date().toISOString(), | ||
| type: type, | ||
| action: action, | ||
| metadata: metadata || {} | ||
| }) | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| // Monitor workspace events | ||
| function setupEventListeners() { | ||
| // Desktop switch events | ||
| workspace.clientDesktopChanged.connect(function(client, desktop) { | ||
| sendEvent("desktop_switch", "switch_desktop", { | ||
| window: client ? client.caption : "unknown", | ||
| desktop: desktop | ||
| }); | ||
| }); | ||
|
|
||
| // Window focus events | ||
| workspace.clientActivated.connect(function(client) { | ||
| if (client) { | ||
| sendEvent("window_focus", "window_focus", { | ||
| window: client.caption, | ||
| app: client.resourceClass ? client.resourceClass.toString() : "unknown" | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // Screen edge activation (overview, etc.) | ||
| workspace.screenEdgeActivated.connect(function(edge, desktop) { | ||
| var action = "unknown"; | ||
| if (edge === 0) action = "overview"; // Top edge usually shows overview | ||
| else if (edge === 2) action = "application_launcher"; // Bottom edge | ||
| else action = "screen_edge"; | ||
|
|
||
| sendEvent("desktop_state", action, { | ||
| edge: edge, | ||
| desktop: desktop | ||
| }); | ||
| }); | ||
|
|
||
| // Window geometry changes (for tiling, maximizing, etc.) | ||
| workspace.clientStepUserMovedResized.connect(function(client, step) { | ||
| if (client && step) { | ||
| var action = "window_move"; | ||
| if (client.maximizedHorizontally && client.maximizedVertically) { | ||
| action = "maximize"; | ||
| } else if (!client.maximizedHorizontally && !client.maximizedVertically) { | ||
| action = "window_move"; | ||
| } | ||
|
|
||
| sendEvent("window_state", action, { | ||
| window: client.caption, | ||
| maximized: client.maximizedHorizontally && client.maximizedVertically | ||
| }); | ||
| } | ||
| }); | ||
| const eventJson = JSON.stringify(event); | ||
| log("Sending event: " + eventJson); | ||
|
|
||
| // Call DBus method | ||
| callDBus( | ||
| BUS_NAME, | ||
| OBJECT_PATH, | ||
| INTERFACE, | ||
| "SendEvent", | ||
| eventJson | ||
| ); | ||
| } catch (error) { | ||
| logError("Failed to send event: " + error); | ||
| } | ||
| } | ||
|
|
||
| // Register a test shortcut for development | ||
| function setupTestShortcut() { | ||
| registerShortcut( | ||
| "Shortcut Sage Test", | ||
| "Test shortcut for Shortcut Sage development", | ||
| "Ctrl+Alt+S", | ||
| function() { | ||
| sendEvent("test", "test_shortcut", { | ||
| source: "kwin_script" | ||
| }); | ||
| } | ||
| ); | ||
| /** | ||
| * Ping the daemon to check if it's alive | ||
| */ | ||
| function pingDaemon() { | ||
| try { | ||
| const result = callDBus( | ||
| BUS_NAME, | ||
| OBJECT_PATH, | ||
| INTERFACE, | ||
| "Ping" | ||
| ); | ||
| log("Ping result: " + result); | ||
| return result === "pong"; | ||
| } catch (error) { | ||
| logError("Daemon not responding to ping: " + error); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // Initialize when script loads | ||
| function init() { | ||
| print("Shortcut Sage KWin script initializing..."); | ||
|
|
||
| if (initDBus()) { | ||
| setupEventListeners(); | ||
| setupTestShortcut(); | ||
| print("Shortcut Sage KWin script initialized successfully"); | ||
| } else { | ||
| print("Shortcut Sage KWin script initialized in fallback mode - daemon not available"); | ||
| // Still set up events but with fallback behavior if needed | ||
| setupTestShortcut(); | ||
| // Track previous state to detect changes | ||
| // These variables must be mutable (using `let`) because they are updated | ||
| // in event handlers to compare with new states | ||
| let previousDesktop = workspace.currentDesktop; | ||
| let showingDesktop = workspace.showingDesktop; | ||
|
|
||
| /** | ||
| * Monitor desktop/workspace switches | ||
| */ | ||
| workspace.currentDesktopChanged.connect(function(desktop, client) { | ||
| if (desktop !== previousDesktop) { | ||
| log("Desktop switched: " + previousDesktop + " -> " + desktop); | ||
| sendEvent( | ||
| "desktop_switch", | ||
| "switch_desktop", | ||
| { | ||
| from: previousDesktop, | ||
| to: desktop | ||
| } | ||
| ); | ||
| previousDesktop = desktop; | ||
|
Comment on lines
+102
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The handler treats the Useful? React with 👍 / 👎. |
||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * Monitor "Show Desktop" state changes | ||
| */ | ||
| workspace.showingDesktopChanged.connect(function(showing) { | ||
| if (showing !== showingDesktop) { | ||
| log("Show desktop changed: " + showing); | ||
| const action = showing ? "show_desktop" : "hide_desktop"; | ||
| sendEvent( | ||
| "show_desktop", | ||
| action, | ||
| { showing: showing } | ||
| ); | ||
| showingDesktop = showing; | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * Monitor active window (focus) changes | ||
| */ | ||
| workspace.clientActivated.connect(function(client) { | ||
| if (client) { | ||
| log("Window activated: " + client.caption); | ||
| sendEvent( | ||
| "window_focus", | ||
| "window_activated", | ||
| { | ||
| caption: client.caption, | ||
| resourceClass: client.resourceClass || "unknown" | ||
| } | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * Dev shortcut: Meta+Shift+S to send a test event | ||
| */ | ||
| registerShortcut( | ||
| "ShortcutSage: Test Event", | ||
| "ShortcutSage: Send Test Event (Meta+Shift+S)", | ||
| "Meta+Shift+S", | ||
| function() { | ||
| log("Test event triggered"); | ||
| sendEvent( | ||
| "test", | ||
| "test_event", | ||
| { source: "dev_shortcut" } | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| // Ping daemon on startup to verify connection | ||
| log("Pinging daemon..."); | ||
| if (pingDaemon()) { | ||
| log("Successfully connected to daemon"); | ||
| } else { | ||
| logError("Could not connect to daemon - is it running?"); | ||
| logError("Start the daemon with: shortcut-sage daemon <config_dir>"); | ||
| } | ||
|
|
||
| // Run initialization | ||
| init(); | ||
| log("KWin Event Monitor initialized successfully"); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Variables
previousDesktopandshowingDesktopare declared withletat the script level. Consider usingconstfor values that represent state, or add comments explaining why mutable global state is necessary for tracking desktop changes.