mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-22 08:38:33 +02:00
feat(bar): windows systray widget
A new System Tray widget has been added to komorebi-bar, bringing native Windows system tray functionality directly into the bar. Special thanks to the Zebar project and its contributors for developing the systray-util crate library that makes Windows system tray integration possible. The widget intercepts system tray icon data by creating a hidden "spy" window that mimics the Windows taskbar. When applications use the Shell_NotifyIcon API to manage their tray icons, the widget receives the same broadcast messages, allowing it to monitor all system tray activity while forwarding messages to the real taskbar to avoid disruption. Users can configure which icons to hide using flexible rules. A plain string matches by exe name (case-insensitive). A structured object can match on exe, tooltip, and/or GUID fields using AND logic. Each field supports matching strategies from komorebi's window rules (Equals, StartsWith, EndsWith, Contains, Regex, and their negated variants), allowing precise filtering even when multiple icons share the same exe and GUID. An info button opens a floating panel listing all icons with their properties and copy buttons, making it easy to identify which values to use in filter rules. The widget fully supports mouse interactions including left-click, right-click, middle-click, and double-click actions on tray icons. Double-click support uses the LeftDoubleClick action from systray-util 0.2.0, which sends WM_LBUTTONDBLCLK and NIN_SELECT messages. It handles right-aligned placement correctly by adjusting the rendering order and toggle button arrow directions to maintain consistent visual appearance regardless of which panel the widget is placed in. Some system tray icons register a click callback but never actually respond to click messages, effectively becoming "zombie" icons from an interaction standpoint. The widget includes fallback commands for known problematic icons that override the native click action with a direct shell command (e.g. opening Windows Security or volume settings). The implementation uses a background thread with its own tokio runtime to handle the async systray events, communicating with the UI thread through crossbeam channels. Icon images are cached efficiently using hash-based keys that update when icons change. Rapid icon updates are deduplicated to prevent UI freezing, and image conversion (RgbaImage to ColorImage) is performed on the background thread to keep the UI responsive. The widget automatically detects and removes stale icons whose owning process has exited, using the Win32 IsWindow API on a configurable interval. A manual refresh button is also available for immediate cleanup. A shortcuts button can be configured to toggle komorebi-shortcuts by killing the process if running or starting it otherwise. The refresh, info, and shortcuts buttons can each be placed in the main visible area or in the overflow section.
This commit is contained in:
254
schema.bar.json
254
schema.bar.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "KomobarConfig",
|
||||
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.40`",
|
||||
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.41`",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"center_widgets": {
|
||||
@@ -353,7 +353,7 @@
|
||||
},
|
||||
{
|
||||
"title": "CubicBezier",
|
||||
"description": "Custom Cubic Bézier function",
|
||||
"description": "Custom Cubic Bezier function",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"CubicBezier": {
|
||||
@@ -2158,6 +2158,21 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ButtonPosition": {
|
||||
"description": "Where to place a systray button (refresh, info, shortcuts, etc.)",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Show in the main visible area",
|
||||
"type": "string",
|
||||
"const": "Visible"
|
||||
},
|
||||
{
|
||||
"description": "Show in the overflow/hidden section",
|
||||
"type": "string",
|
||||
"const": "Overflow"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Catppuccin": {
|
||||
"description": "Catppuccin palette",
|
||||
"oneOf": [
|
||||
@@ -2573,6 +2588,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FieldMatch": {
|
||||
"description": "A field value with an optional matching strategy.\n\nA plain string uses exact (case-insensitive) matching.\nAn object with `value` and `matching_strategy` uses the specified strategy.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Exact case-insensitive match",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Match using a specific strategy",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matching_strategy": {
|
||||
"description": "How to match (Equals, StartsWith, EndsWith, Contains, Regex, etc.)",
|
||||
"$ref": "#/$defs/MatchingStrategy"
|
||||
},
|
||||
"value": {
|
||||
"description": "The value to match against",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"value",
|
||||
"matching_strategy"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"FocusFollowsMouseImplementation": {
|
||||
"description": "Focus follows mouse implementation",
|
||||
"oneOf": [
|
||||
@@ -2802,6 +2844,54 @@
|
||||
"type": "string",
|
||||
"format": "color-hex"
|
||||
},
|
||||
"HiddenIconRule": {
|
||||
"description": "Rule for matching a systray icon to hide\n\nA plain string matches the exe name (backward compatible).\nAn object with optional `exe`, `tooltip`, and/or `guid` fields\nuses AND logic: all specified fields must match.\nEach field can be a plain string (exact match) or an object with\n`value` and `matching_strategy` for advanced matching.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Match by exe name (case-insensitive, exact)",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Match by one or more properties (all specified fields must match)",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exe": {
|
||||
"description": "Exe name to match",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/FieldMatch"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"guid": {
|
||||
"description": "Icon GUID to match (most stable identifier across restarts)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/FieldMatch"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tooltip": {
|
||||
"description": "Tooltip text to match",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/FieldMatch"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"HidingBehaviour": {
|
||||
"description": "Window hiding behaviour",
|
||||
"oneOf": [
|
||||
@@ -3859,6 +3949,61 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"MatchingStrategy": {
|
||||
"description": "Strategy for matching identifiers",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Should not be used, only kept for backward compatibility",
|
||||
"type": "string",
|
||||
"const": "Legacy"
|
||||
},
|
||||
{
|
||||
"description": "Equals",
|
||||
"type": "string",
|
||||
"const": "Equals"
|
||||
},
|
||||
{
|
||||
"description": "Starts With",
|
||||
"type": "string",
|
||||
"const": "StartsWith"
|
||||
},
|
||||
{
|
||||
"description": "Ends With",
|
||||
"type": "string",
|
||||
"const": "EndsWith"
|
||||
},
|
||||
{
|
||||
"description": "Contains",
|
||||
"type": "string",
|
||||
"const": "Contains"
|
||||
},
|
||||
{
|
||||
"description": "Regex",
|
||||
"type": "string",
|
||||
"const": "Regex"
|
||||
},
|
||||
{
|
||||
"description": "Does not end with",
|
||||
"type": "string",
|
||||
"const": "DoesNotEndWith"
|
||||
},
|
||||
{
|
||||
"description": "Does not start with",
|
||||
"type": "string",
|
||||
"const": "DoesNotStartWith"
|
||||
},
|
||||
{
|
||||
"description": "Does not equal",
|
||||
"type": "string",
|
||||
"const": "DoesNotEqual"
|
||||
},
|
||||
{
|
||||
"description": "Does not contain",
|
||||
"type": "string",
|
||||
"const": "DoesNotContain"
|
||||
}
|
||||
]
|
||||
},
|
||||
"MediaConfig": {
|
||||
"description": "Media widget configuration",
|
||||
"type": "object",
|
||||
@@ -4301,6 +4446,21 @@
|
||||
"Down"
|
||||
]
|
||||
},
|
||||
"OverflowTogglePosition": {
|
||||
"description": "Position of the overflow toggle button",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Toggle button appears on the left side (before visible icons)",
|
||||
"type": "string",
|
||||
"const": "Left"
|
||||
},
|
||||
{
|
||||
"description": "Toggle button appears on the right side (after visible icons)",
|
||||
"type": "string",
|
||||
"const": "Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PathBuf": {
|
||||
"description": "A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved.",
|
||||
"type": "string"
|
||||
@@ -8147,6 +8307,82 @@
|
||||
"filter_state_changes"
|
||||
]
|
||||
},
|
||||
"SystrayConfig": {
|
||||
"description": "System tray widget configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enable": {
|
||||
"description": "Enable the System Tray widget",
|
||||
"type": "boolean"
|
||||
},
|
||||
"hidden_icons": {
|
||||
"description": "A list of rules for icons to hide from the system tray\n\nEach entry can be a plain string (matches exe name, case-insensitive)\nor an object with optional `exe`, `tooltip`, and/or `guid` fields\n(all specified fields must match, AND logic, case-insensitive).\n\nRun komorebi-bar with RUST_LOG=info to see the properties of all\nsystray icons in the log output.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/$defs/HiddenIconRule"
|
||||
}
|
||||
},
|
||||
"info_button": {
|
||||
"description": "Show an info button that opens a floating panel listing all systray icons\nwith their exe, tooltip, and GUID. Set to \"Visible\" to show it in the main\narea, or \"Overflow\" to show it in the hidden/overflow section.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/ButtonPosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overflow_toggle_position": {
|
||||
"description": "Position of the overflow toggle button (default: Right)",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/OverflowTogglePosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"refresh_button": {
|
||||
"description": "Show a refresh button that manually triggers stale icon cleanup.\nSet to \"Visible\" to show it in the main area, or \"Overflow\" to\nshow it in the hidden/overflow section.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/ButtonPosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"shortcuts_button": {
|
||||
"description": "Show a button that toggles komorebi-shortcuts (kills the process if\nrunning, starts it otherwise). Set to \"Visible\" to show it in the main\narea, or \"Overflow\" to show it in the hidden/overflow section.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/ButtonPosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stale_icons_check_interval": {
|
||||
"description": "Interval in seconds to automatically check for and remove stale icons\nwhose owning process has exited. Clamped between 30 and 600 seconds.\nDefaults to 60. Set to 0 to disable.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enable"
|
||||
]
|
||||
},
|
||||
"TimeConfig": {
|
||||
"description": "Time widget configuration",
|
||||
"type": "object",
|
||||
@@ -8416,6 +8652,20 @@
|
||||
"Storage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Systray",
|
||||
"description": "System Tray widget configuration (Windows only)",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Systray": {
|
||||
"$ref": "#/$defs/SystrayConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Systray"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Time",
|
||||
"description": "Time widget configuration",
|
||||
|
||||
Reference in New Issue
Block a user