151 lines
8.8 KiB
Python
151 lines
8.8 KiB
Python
#!/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 '<br>'.join('<code>' + cell(value) + '</code>' 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 += ['<details>', '<summary>' + cell(title) + f' ({len(disabled)})</summary>', '',
|
||
'| Action | Stored KDE default |', '|---|---|']
|
||
for _, action, label, current, default in disabled:
|
||
lines.append('| ' + cell(label) + ' | ' + keys(default) + ' |')
|
||
lines += ['', '</details>', '']
|
||
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()
|