#!/usr/bin/env python3
"""Generate a readable reference from the captured KDE global shortcuts."""
from collections import defaultdict
from pathlib import Path
from capture import sections
REPO = Path(__file__).resolve().parents[1]
SOURCE = REPO / 'configs/kde-shortcuts/kconfig/.config/kglobalshortcutsrc'
def bindings(value):
return [key.strip() for key in value.replace(r'\t', '\t').split('\t')
if key.strip() and key.strip().lower() != 'none']
def records(path=SOURCE):
for group, values in sections(path.read_text()).items():
for action, value in values.items():
if action.startswith('_') and action != '_launch':
continue
fields = value.split(',', 2)
current = bindings(fields[0])
default = bindings(fields[1]) if len(fields) > 1 else None
label = fields[2] if len(fields) > 2 else action
yield group, action, label, current, default
def cell(value):
return value.replace('&', '&').replace('<', '<').replace('>', '>').replace('|', '|').replace('`', '`')
def keys(values):
if values is None:
return '—'
return '
'.join('' + cell(value) + '' for value in values) or 'Unassigned'
def generate():
entries = list(records())
active = [r for r in entries if r[3]]
inactive = [r for r in entries if not r[3]]
by_key = defaultdict(list)
for group, action, label, current, default in active:
for key in current:
by_key[key].append(group + ' / ' + action)
duplicates = {key: owners for key, owners in by_key.items() if len(owners) > 1}
lines = ['# KDE shortcuts', '',
'Generated from the repo’s captured `kglobalshortcutsrc`. This describes',
'Fiw’s bindings as configured; applying them is a separate installer action.',
'It covers KDE global shortcuts, not every shortcut inside individual applications.', '',
f'**{len(active)} assigned actions · {len(inactive)} unassigned actions · {len(entries)} total**', '',
'`Meta` is the Super/Windows key. In the full tables, multiple keys for',
'one action are alternatives. Grouped summary keys match the named actions',
'in order. “Unassigned” means explicitly disabled in this capture;',
'a key in the KDE default column is not an active binding. The default',
'column is copied from KDE’s stored values, not inferred from a release.', '',
'## Everyday bindings', '',
'| Keys | Action |', '|---|---|',
'Screenshot bindings use `fiw-shot`: copy to clipboard, then offer Save',
'or Edit in the notification. They need Spectacle, `wl-copy` and',
'`notify-send`; Kitty and KRunner bindings need their respective apps.',
'The native Spectacle bindings are unassigned to avoid competing actions.', '',
'The desktop-move bindings are stored as literal symbols (`!`, `@`, `#`).',
'On a US layout these usually mean Shift+1/2/3; other layouts can differ.',
'The keyboard-layout preset contains US English, Russian and Spanish;',
'`Meta+Alt+Space` cycles through them. Shortcut restoration does not',
'create three virtual desktops or activities on a new installation.', '',
'## Captured choices', '']
lookup = {(r[0], r[1]): r for r in entries}
highlights = [
('Open application launcher', '[plasmashell]', ['activate application launcher']),
('Open KRunner', '[services][org.kde.krunner.desktop]', ['_launch']),
('Open Kitty', '[services][net.local.kitty.desktop]', ['_launch']),
('Select a screenshot region', '[services][net.local.fiw-shot-region.desktop]', ['_launch']),
('Screenshot monitor under pointer', '[services][net.local.fiw-shot-screen.desktop]', ['_launch']),
('Show clipboard history at pointer', '[plasmashell]', ['show-on-mouse-pos']),
('Switch to desktop 1 / 2 / 3', '[kwin]', ['Switch to Desktop 1', 'Switch to Desktop 2', 'Switch to Desktop 3']),
('Move window to desktop 1 / 2 / 3', '[kwin]', ['Window to Desktop 1', 'Window to Desktop 2', 'Window to Desktop 3']),
('Switch desktop left / right / up / down', '[kwin]', ['Switch One Desktop to the Left', 'Switch One Desktop to the Right', 'Switch One Desktop Up', 'Switch One Desktop Down']),
('Tile window left / right / up / down', '[kwin]', ['Window Quick Tile Left', 'Window Quick Tile Right', 'Window Quick Tile Top', 'Window Quick Tile Bottom']),
('Move window to previous / next screen', '[kwin]', ['Window to Previous Screen', 'Window to Next Screen']),
('Maximize window', '[kwin]', ['Window Maximize']),
('Toggle Overview', '[kwin]', ['Overview']),
('Toggle desktop grid', '[kwin]', ['Grid View']),
('Next / previous activity', '[plasmashell]', ['next activity', 'previous activity']),
('Lock session', '[ksmserver]', ['Lock Session']),
('Show logout screen', '[ksmserver]', ['Log Out']),
]
at = lines.index('Screenshot bindings use `fiw-shot`: copy to clipboard, then offer Save')
summary = []
for label, group, actions in highlights:
current = [key for action in actions for key in lookup.get((group, action), (None, None, None, [], None))[3]]
summary.append('| ' + keys(current) + ' | ' + cell(label) + ' |')
lines[at:at] = summary + ['']
forward = lookup[('[kwin]', 'Walk Through Windows')][3]
lines += [('Forward window switching is currently unassigned; reverse switching' if not forward else
'Forward window switching uses ' + keys(forward) + '; reverse switching'),
'uses `Alt+Shift+Tab` and `Meta+Shift+Tab`.', '',
'Other currently unassigned shortcuts include Peek at Desktop (`Meta+D`',
'in the default column), Minimize (`Meta+PgDown`), Restore',
'(`Meta+Backspace`) and the window menu (`Alt+F3`). FiwNode’s four',
'recorded global actions are unassigned. `Meta+1/2/3` are used for',
'desktops, with the corresponding task-manager shortcuts disabled.', '']
if duplicates:
lines += ['Keys assigned to multiple captured actions:', '']
for key, owners in duplicates.items():
lines.append('- ' + keys([key]) + ': ' + cell('; '.join(owners)))
lines.append('')
lines += ['## All assigned actions', '']
grouped = defaultdict(list)
for record in entries:
grouped[record[0]].append(record)
friendly = sections(SOURCE.read_text())
launch_names = {'[services][net.local.fiw-shot-region.desktop]': 'Screenshot region helper',
'[services][net.local.fiw-shot-screen.desktop]': 'Screenshot monitor helper',
'[services][net.local.kitty.desktop]': 'Kitty launcher',
'[services][org.kde.krunner.desktop]': 'KRunner launcher'}
for group, rows in grouped.items():
assigned = [r for r in rows if r[3]]
if not assigned:
continue
title = launch_names.get(group, friendly[group].get('_k_friendly_name', group.strip('[]')))
lines += ['### ' + cell(title), '', '| Action | Current keys | Stored KDE default |', '|---|---|---|']
for _, action, label, current, default in assigned:
label = launch_names.get(group, label) if action == '_launch' else label
lines.append('| ' + cell(label) + ' | ' + keys(current) + ' | ' + keys(default) + ' |')
lines.append('')
lines += ['## All unassigned actions', '',
'These entries are restored as unassigned, including actions with a',
'stored KDE default. Expand each section to review the full capture.', '']
for group, rows in grouped.items():
disabled = [r for r in rows if not r[3]]
if not disabled:
continue
title = friendly[group].get('_k_friendly_name', group.strip('[]'))
lines += ['', '' + cell(title) + f' ({len(disabled)})
', '',
'| Action | Stored KDE default |', '|---|---|']
for _, action, label, current, default in disabled:
lines.append('| ' + cell(label) + ' | ' + keys(default) + ' |')
lines += ['', ' ', '']
lines += ['## Refresh the reference', '', '```sh', 'python3 tools/shortcuts.py', '```', '',
'Run this after reviewing a shortcut capture or changing the preset.',
'The generator reads repo files only and does not change live KDE settings.', '']
dest = REPO / 'docs/shortcuts.md'
dest.write_text('\n'.join(lines))
print(f'Wrote docs/shortcuts.md: {len(active)} assigned, {len(inactive)} unassigned, {len(duplicates)} duplicate keys.')
if __name__ == '__main__':
generate()