Skip to content

Commit 7943e81

Browse files
fix(keybind-cheatsheet): v3.4.0 - settings save bug, i18n structure, code quality
1 parent a01b7cd commit 7943e81

26 files changed

Lines changed: 1352 additions & 1492 deletions

keybind-cheatsheet/BarWidget.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ NIconButton {
1717
baseSize: Style.getCapsuleHeightForScreen(screen?.name)
1818
applyUiScale: false
1919
icon: "keyboard"
20-
tooltipText: pluginApi?.tr("keybind-cheatsheet.barwidget.tooltip") || "Keybind Cheatsheet"
20+
tooltipText: pluginApi?.tr("barwidget.tooltip")
2121
tooltipDirection: BarService.getTooltipDirection(screen?.name)
2222
customRadius: Style.radiusL
2323

@@ -36,12 +36,12 @@ NIconButton {
3636

3737
model: [
3838
{
39-
"label": "Open Cheatsheet",
39+
"label": pluginApi?.tr("barwidget.open"),
4040
"action": "open-panel",
4141
"icon": "keyboard"
4242
},
4343
{
44-
"label": "Plugin Settings",
44+
"label": pluginApi?.tr("barwidget.settings"),
4545
"action": "plugin-settings",
4646
"icon": "settings"
4747
},

keybind-cheatsheet/CHANGELOG.md

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,60 @@
11
# Changelog
22

3+
## [3.4.0] - 2026-04-07
4+
5+
### Bug Fixes
6+
7+
**Settings unreachable from panel button**
8+
- Fixed critical bug where clicking the settings button (top-right of the keybind panel) blocked all input in the settings window
9+
- The panel now closes before opening settings, preventing the open panel from intercepting mouse events
10+
- Reported by Discord users: editing width/height was impossible when settings were opened this way
11+
12+
**Settings not saved when opened from panel**
13+
- Fixed settings changes being silently discarded when the settings window was opened via the panel button
14+
- `saveSettings()` was declared on an inner `ColumnLayout` instead of the root `Item`, making it invisible to the Noctalia shell
15+
- With the previous direct-mutation approach this went unnoticed; after switching to the edit-copy pattern saves now work correctly from all entry points
16+
17+
### Code Quality
18+
19+
**Settings: edit-copy pattern**
20+
- Replaced direct `pluginSettings` mutation in `onTextChanged` handlers with proper edit-copy properties (`editWindowWidth`, `editWindowHeight`, `editAutoHeight`, `editColumnCount`, `editModKeyVariable`, `editHyprlandConfigPath`, `editNiriConfigPath`)
21+
- Changes are committed to `pluginSettings` only when the user clicks Save, matching Noctalia plugin conventions
22+
23+
**i18n: corrected structure**
24+
- Removed `"keybind-cheatsheet"` top-level wrapper from all 20 language JSON files
25+
- Removed `"keybind-cheatsheet."` prefix from all 50 `tr()` calls across all QML files
26+
- Structure now matches the Noctalia plugin i18n specification
27+
28+
**Removed dead code**
29+
- Deleted unused `parseNiriConfig()` function (118 lines) superseded by `parseNiriFileContent()` in v3.1.0
30+
31+
**Shell injection hardening**
32+
- Glob expansion in config path resolution now passes user-provided patterns as positional shell arguments (`$1`) instead of string-concatenating them into the shell command, preventing potential injection via crafted config path values
33+
34+
**resizeTimer: semantic popup detection**
35+
- Replaced fragile `toString()` string matching (`"Popup_QMLTYPE"`, `"NPluginSettingsPopup"`) with a semantic check (`typeof obj.modal === "boolean"`) that works with any QML `Popup` subclass
36+
37+
**Named key badge colors**
38+
- Extracted hardcoded hex colors in `getKeyColor()` into named `readonly property color` constants (`keyColorAlt`, `keyColorXF86`, `keyColorPrint`, `keyColorNumeric`, `keyColorMouse`)
39+
40+
### Manifest
41+
42+
- Added missing `repository` field
43+
- Added tags: `System`, `Indicator` (alongside existing `Bar`, `Panel`)
44+
45+
---
46+
347
## [3.2.2] - 2026-02-08
448

5-
### 🐛 Bug Fixes
49+
### Bug Fixes
650

751
**Hyprland Parser - No Category Handling**
852
- Fixed parser crash when Hyprland config has no category headers (`# 1. Category Name`)
953
- Parser now creates default "Keybinds" category for configs without categories
1054
- Default category name is fully translatable via i18n system
1155
- Prevents keybind loss for users who don't organize their configs with categories
1256

13-
### 🌍 Translations
57+
### Translations
1458

1559
**Complete i18n Coverage**
1660
- Added `default-category` translations for all 19 supported languages
@@ -25,7 +69,7 @@
2569
- Turkish (tr), Ukrainian (uk-UA), Swedish (sv), Hungarian (hu)
2670
- Kurdish (ku), Norwegian Nynorsk (nn-NO), Hindi/Nepali (hn)
2771

28-
### 🔧 Code Quality
72+
### Code Quality
2973

3074
**Memory Leak Prevention**
3175
- Added recursion limit tracking for parser (`hasCategories` flag)
@@ -36,21 +80,21 @@
3680

3781
## [3.1.2] - 2026-02-08
3882

39-
### 🌐 Translations
83+
### Translations
4084

4185
- Added german translations for the `error` keys
4286

4387
## [3.1.1] - 2026-02-03
4488

45-
### 🚀 Smart Caching
89+
### Smart Caching
4690

4791
**Compositor Change Detection**
4892
- Plugin now detects when compositor changes (e.g., switching from Hyprland to Niri)
4993
- Automatically re-parses config only when compositor differs from cached data
5094
- Instant panel opening when using same compositor (uses cache)
5195
- Saves detected compositor in settings for comparison
5296

53-
### 🐛 Bug Fixes
97+
### Bug Fixes
5498

5599
**Improved Niri Parser**
56100
- Fixed multiline bind parsing - handles binds that span multiple lines
@@ -63,7 +107,7 @@
63107
- Each compositor shows specific explanation why it's not supported
64108
- All error messages are translatable via i18n
65109

66-
### 📝 Documentation
110+
### Documentation
67111

68112
**README Updates**
69113
- Fixed IPC command syntax in examples
@@ -74,7 +118,7 @@
74118

75119
## [3.1.0] - 2026-02-03
76120

77-
### New Features
121+
### New Features
78122

79123
**Niri Support**
80124
- Full Niri compositor support with KDL config parsing
@@ -87,7 +131,7 @@
87131
- Memory leak prevention with recursion limits
88132
- Glob pattern support for bulk file imports
89133

90-
### 🔧 Improvements
134+
### Improvements
91135

92136
**Better Error Handling**
93137
- Graceful fallback for unsupported compositors
@@ -98,7 +142,7 @@
98142

99143
## [3.0.0] - 2026-01-30
100144

101-
### 🎉 Initial Release
145+
### Initial Release
102146

103147
**Core Features**
104148
- Hyprland keybind parsing

keybind-cheatsheet/Main.qml

Lines changed: 12 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,20 @@ Item {
5353
function getUnsupportedCompositorMessage(compositor) {
5454
var messages = {
5555
"sway": {
56-
short: pluginApi?.tr("keybind-cheatsheet.error.sway-not-supported") || "Sway is not yet supported",
57-
detail: pluginApi?.tr("keybind-cheatsheet.error.sway-detail") || "Sway support may be added in a future update (similar format to Hyprland)"
56+
short: pluginApi?.tr("error.sway-not-supported"),
57+
detail: pluginApi?.tr("error.sway-detail")
5858
},
5959
"labwc": {
60-
short: pluginApi?.tr("keybind-cheatsheet.error.labwc-not-supported") || "LabWC is not supported",
61-
detail: pluginApi?.tr("keybind-cheatsheet.error.labwc-detail") || "LabWC uses XML config format which is incompatible with this plugin"
60+
short: pluginApi?.tr("error.labwc-not-supported"),
61+
detail: pluginApi?.tr("error.labwc-detail")
6262
},
6363
"mango": {
64-
short: pluginApi?.tr("keybind-cheatsheet.error.mango-not-supported") || "MangoWC is not supported",
65-
detail: pluginApi?.tr("keybind-cheatsheet.error.mango-detail") || "MangoWC config format is not compatible with this plugin"
64+
short: pluginApi?.tr("error.mango-not-supported"),
65+
detail: pluginApi?.tr("error.mango-detail")
6666
},
6767
"unknown": {
68-
short: pluginApi?.tr("keybind-cheatsheet.error.unknown-compositor") || "Unknown compositor detected",
69-
detail: pluginApi?.tr("keybind-cheatsheet.error.unknown-detail") || "This plugin only supports Hyprland and Niri compositors"
68+
short: pluginApi?.tr("error.unknown-compositor"),
69+
detail: pluginApi?.tr("error.unknown-detail")
7070
}
7171
};
7272

@@ -144,7 +144,7 @@ Item {
144144

145145
var unsupportedMsg = getUnsupportedCompositorMessage(compositorName);
146146
saveToDb([{
147-
"title": pluginApi?.tr("keybind-cheatsheet.error.unsupported-compositor") || "Unsupported Compositor",
147+
"title": pluginApi?.tr("error.unsupported-compositor"),
148148
"binds": [
149149
{ "keys": compositorName.toUpperCase(), "desc": unsupportedMsg.short },
150150
{ "keys": "INFO", "desc": unsupportedMsg.detail }
@@ -222,7 +222,7 @@ Item {
222222
// Handle glob patterns
223223
if (isGlobPattern(nextFile)) {
224224
niriGlobProcess.expandedFiles = []; // Clear previous results
225-
niriGlobProcess.command = ["sh", "-c", "for f in " + nextFile + "; do [ -f \"$f\" ] && echo \"$f\"; done"];
225+
niriGlobProcess.command = ["sh", "-c", 'for f in $1; do [ -f "$f" ] && echo "$f"; done', "sh", nextFile];
226226
niriGlobProcess.running = true;
227227
return;
228228
}
@@ -499,7 +499,7 @@ Item {
499499
// Handle glob patterns
500500
if (isGlobPattern(nextFile)) {
501501
hyprGlobProcess.expandedFiles = []; // Clear previous results
502-
hyprGlobProcess.command = ["sh", "-c", "for f in " + nextFile + "; do [ -f \"$f\" ] && echo \"$f\"; done"];
502+
hyprGlobProcess.command = ["sh", "-c", 'for f in $1; do [ -f "$f" ] && echo "$f"; done', "sh", nextFile];
503503
hyprGlobProcess.running = true;
504504
return;
505505
}
@@ -588,7 +588,6 @@ Item {
588588
var currentCategory = null;
589589
var hasCategories = false; // Track if we found any category headers
590590

591-
// TUTAJ ZMIANA: Pobierz ustawioną zmienną (domyślnie $mod) i zamień na wielkie litery
592591
var modVar = pluginApi?.pluginSettings?.modKeyVariable || "$mod";
593592
var modVarUpper = modVar.toUpperCase();
594593

@@ -608,7 +607,7 @@ Item {
608607
else if (line.includes("bind") && line.includes('#"')) {
609608
// If no categories found yet, create default category
610609
if (!currentCategory && !hasCategories) {
611-
var defaultCategoryName = pluginApi?.tr("keybind-cheatsheet.default-category") || "Keybinds";
610+
var defaultCategoryName = pluginApi?.tr("default-category");
612611
currentCategory = { "title": defaultCategoryName, "binds": [] };
613612
}
614613

@@ -624,7 +623,6 @@ Item {
624623

625624
// Build modifiers list properly
626625
var mods = [];
627-
// TUTAJ ZMIANA: Sprawdzamy czy to ustawiony mod (np. $MAINMOD) albo SUPER
628626
if (modPart.includes(modVarUpper) || modPart.includes("SUPER")) mods.push("Super");
629627

630628
if (modPart.includes("SHIFT")) mods.push("Shift");
@@ -657,126 +655,6 @@ Item {
657655
clearParsingData();
658656
}
659657

660-
// ========== NIRI PARSER ==========
661-
function parseNiriConfig(text) {
662-
var lines = text.split('\n');
663-
var inBindsBlock = false;
664-
var braceDepth = 0;
665-
var currentCategory = null;
666-
667-
var actionCategories = {
668-
"spawn": "Applications",
669-
"spawn-sh": "Applications",
670-
"focus-column": "Column Navigation",
671-
"focus-window": "Window Focus",
672-
"focus-workspace": "Workspace Navigation",
673-
"move-column": "Move Columns",
674-
"move-column-to-workspace": "Workspace Management",
675-
"move-window": "Move Windows",
676-
"move-window-to-workspace": "Workspace Management",
677-
"consume-window": "Window Management",
678-
"expel-window": "Window Management",
679-
"close-window": "Window Management",
680-
"fullscreen-window": "Window Management",
681-
"maximize-column": "Column Management",
682-
"set-column-width": "Column Width",
683-
"switch-preset-column-width": "Column Width",
684-
"reset-window-height": "Window Size",
685-
"screenshot": "Screenshots",
686-
"power-off-monitors": "Power",
687-
"quit": "System",
688-
"toggle-animation": "Animations"
689-
};
690-
691-
var categorizedBinds = {};
692-
693-
for (var i = 0; i < lines.length; i++) {
694-
var line = lines[i].trim();
695-
696-
// Find binds block
697-
if (line.startsWith("binds") && line.includes("{")) {
698-
inBindsBlock = true;
699-
braceDepth = 1;
700-
continue;
701-
}
702-
703-
if (!inBindsBlock) continue;
704-
705-
// Track brace depth
706-
for (var j = 0; j < line.length; j++) {
707-
if (line[j] === '{') braceDepth++;
708-
else if (line[j] === '}') braceDepth--;
709-
}
710-
711-
if (braceDepth <= 0) {
712-
inBindsBlock = false;
713-
break;
714-
}
715-
716-
// Category markers: // #"Category Name" - only these create categories
717-
if (line.startsWith("//")) {
718-
var categoryMatch = line.match(/\/\/\s*#"([^"]+)"/);
719-
if (categoryMatch) {
720-
currentCategory = categoryMatch[1];
721-
}
722-
continue;
723-
}
724-
725-
if (line.length === 0) continue;
726-
727-
// Parse: Mod+Key { action; }
728-
var bindMatch = line.match(/^([A-Za-z0-9_+]+)\s*(?:[a-z\-]+=\S+\s*)*\{\s*([^}]+)\s*\}/);
729-
730-
if (bindMatch) {
731-
var keyCombo = bindMatch[1];
732-
var action = bindMatch[2].trim().replace(/;$/, '');
733-
734-
var formattedKeys = formatNiriKeyCombo(keyCombo);
735-
var category = currentCategory || getNiriCategory(action, actionCategories);
736-
737-
if (!categorizedBinds[category]) {
738-
categorizedBinds[category] = [];
739-
}
740-
741-
categorizedBinds[category].push({
742-
"keys": formattedKeys,
743-
"desc": formatNiriAction(action)
744-
});
745-
}
746-
}
747-
748-
// Convert to array
749-
var categoryOrder = [
750-
"Noctalia", "Applications", "Window Management", "Column Navigation",
751-
"Window Focus", "Workspace Navigation", "Workspace Management",
752-
"Move Columns", "Move Windows", "Column Management", "Column Width",
753-
"Window Size", "Screenshots", "Power", "System", "Animations"
754-
];
755-
756-
var categories = [];
757-
for (var k = 0; k < categoryOrder.length; k++) {
758-
var catName = categoryOrder[k];
759-
if (categorizedBinds[catName] && categorizedBinds[catName].length > 0) {
760-
categories.push({
761-
"title": catName,
762-
"binds": categorizedBinds[catName]
763-
});
764-
}
765-
}
766-
767-
// Add remaining categories
768-
for (var cat in categorizedBinds) {
769-
if (categoryOrder.indexOf(cat) === -1 && categorizedBinds[cat].length > 0) {
770-
categories.push({
771-
"title": cat,
772-
"binds": categorizedBinds[cat]
773-
});
774-
}
775-
}
776-
777-
saveToDb(categories);
778-
}
779-
780658
function formatSpecialKey(key) {
781659
var keyMap = {
782660
// Audio keys (uppercase for Hyprland)

0 commit comments

Comments
 (0)