2025-12-28 03:14:31 +01:00
|
|
|
/*
|
|
|
|
|
* DWN - Desktop Window Manager
|
2025-12-28 04:30:10 +01:00
|
|
|
* retoor <retoor@molodetz.nl>
|
2025-12-28 03:14:31 +01:00
|
|
|
* Keyboard shortcut handling implementation
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "keys.h"
|
|
|
|
|
#include "client.h"
|
|
|
|
|
#include "workspace.h"
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
#include "ai.h"
|
|
|
|
|
#include "notifications.h"
|
|
|
|
|
#include "news.h"
|
|
|
|
|
#include "applauncher.h"
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
2025-12-28 04:30:10 +01:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <X11/XKBlib.h>
|
|
|
|
|
|
|
|
|
|
static bool super_pressed = false;
|
|
|
|
|
static bool super_used_in_combo = false;
|
2025-12-28 03:14:31 +01:00
|
|
|
|
|
|
|
|
void key_spawn_terminal(void);
|
|
|
|
|
void key_spawn_launcher(void);
|
|
|
|
|
void key_spawn_file_manager(void);
|
|
|
|
|
void key_spawn_browser(void);
|
|
|
|
|
void key_close_window(void);
|
|
|
|
|
void key_quit_dwn(void);
|
|
|
|
|
void key_cycle_layout(void);
|
|
|
|
|
void key_toggle_floating(void);
|
|
|
|
|
void key_toggle_fullscreen(void);
|
|
|
|
|
void key_toggle_maximize(void);
|
|
|
|
|
void key_focus_next(void);
|
|
|
|
|
void key_focus_prev(void);
|
|
|
|
|
void key_workspace_next(void);
|
|
|
|
|
void key_workspace_prev(void);
|
|
|
|
|
void key_workspace_1(void);
|
|
|
|
|
void key_workspace_2(void);
|
|
|
|
|
void key_workspace_3(void);
|
|
|
|
|
void key_workspace_4(void);
|
|
|
|
|
void key_workspace_5(void);
|
|
|
|
|
void key_workspace_6(void);
|
|
|
|
|
void key_workspace_7(void);
|
|
|
|
|
void key_workspace_8(void);
|
|
|
|
|
void key_workspace_9(void);
|
|
|
|
|
void key_move_to_workspace_1(void);
|
|
|
|
|
void key_move_to_workspace_2(void);
|
|
|
|
|
void key_move_to_workspace_3(void);
|
|
|
|
|
void key_move_to_workspace_4(void);
|
|
|
|
|
void key_move_to_workspace_5(void);
|
|
|
|
|
void key_move_to_workspace_6(void);
|
|
|
|
|
void key_move_to_workspace_7(void);
|
|
|
|
|
void key_move_to_workspace_8(void);
|
|
|
|
|
void key_move_to_workspace_9(void);
|
|
|
|
|
void key_increase_master(void);
|
|
|
|
|
void key_decrease_master(void);
|
|
|
|
|
void key_increase_master_count(void);
|
|
|
|
|
void key_decrease_master_count(void);
|
|
|
|
|
void key_toggle_ai(void);
|
|
|
|
|
void key_ai_command(void);
|
|
|
|
|
void key_exa_search(void);
|
|
|
|
|
void key_news_next(void);
|
|
|
|
|
void key_news_prev(void);
|
|
|
|
|
void key_news_open(void);
|
|
|
|
|
void key_show_shortcuts(void);
|
|
|
|
|
void key_start_tutorial(void);
|
|
|
|
|
void key_screenshot(void);
|
|
|
|
|
|
|
|
|
|
static KeyBinding bindings[MAX_KEYBINDINGS];
|
|
|
|
|
static int binding_count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
const char *title;
|
|
|
|
|
const char *instruction;
|
|
|
|
|
const char *hint;
|
|
|
|
|
unsigned int modifiers;
|
|
|
|
|
KeySym keysym;
|
|
|
|
|
} TutorialStep;
|
|
|
|
|
|
|
|
|
|
static const TutorialStep tutorial_steps[] = {
|
|
|
|
|
{
|
|
|
|
|
"Welcome to DWN!",
|
|
|
|
|
"This tutorial teaches all keyboard shortcuts.\n\n"
|
|
|
|
|
"Press Super+T to continue...",
|
|
|
|
|
"(Super = Windows/Meta key)",
|
|
|
|
|
MOD_SUPER, XK_t
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"1/20: Open Terminal",
|
|
|
|
|
"The terminal is your command center.\n\n"
|
|
|
|
|
"Press: Ctrl + Alt + T",
|
|
|
|
|
"Hold Ctrl and Alt, then press T",
|
|
|
|
|
MOD_CTRL | MOD_ALT, XK_t
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"2/20: App Launcher",
|
|
|
|
|
"Launch any application by name.\n\n"
|
|
|
|
|
"Press: Alt + F2",
|
|
|
|
|
"Type app name and press Enter",
|
|
|
|
|
MOD_ALT, XK_F2
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"3/20: File Manager",
|
|
|
|
|
"Open the file manager.\n\n"
|
|
|
|
|
"Press: Super + E",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_e
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"4/20: Web Browser",
|
|
|
|
|
"Open your default web browser.\n\n"
|
|
|
|
|
"Press: Super + B",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_b
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"5/20: Switch Windows",
|
|
|
|
|
"Cycle through open windows.\n\n"
|
|
|
|
|
"Press: Alt + Tab",
|
|
|
|
|
"Hold Alt, press Tab repeatedly",
|
|
|
|
|
MOD_ALT, XK_Tab
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"6/20: Close Window",
|
|
|
|
|
"Close the focused window.\n\n"
|
|
|
|
|
"Press: Alt + F4",
|
|
|
|
|
"",
|
|
|
|
|
MOD_ALT, XK_F4
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"7/20: Toggle Maximize",
|
|
|
|
|
"Maximize or restore a window.\n\n"
|
|
|
|
|
"Press: Alt + F10",
|
|
|
|
|
"",
|
|
|
|
|
MOD_ALT, XK_F10
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"8/20: Toggle Fullscreen",
|
|
|
|
|
"Make a window fullscreen.\n\n"
|
|
|
|
|
"Press: Alt + F11",
|
|
|
|
|
"Press again to exit",
|
|
|
|
|
MOD_ALT, XK_F11
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"9/20: Toggle Floating",
|
|
|
|
|
"Make window float above tiled windows.\n\n"
|
|
|
|
|
"Press: Super + F9",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_F9
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"10/20: Next Workspace",
|
|
|
|
|
"Switch to the next virtual desktop.\n\n"
|
|
|
|
|
"Press: Ctrl + Alt + Right",
|
|
|
|
|
"",
|
|
|
|
|
MOD_CTRL | MOD_ALT, XK_Right
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"11/20: Previous Workspace",
|
|
|
|
|
"Switch to the previous workspace.\n\n"
|
|
|
|
|
"Press: Ctrl + Alt + Left",
|
|
|
|
|
"",
|
|
|
|
|
MOD_CTRL | MOD_ALT, XK_Left
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"12/20: Go to Workspace",
|
|
|
|
|
"Jump directly to workspace 1.\n\n"
|
|
|
|
|
"Press: F1",
|
|
|
|
|
"Use F1-F9 for workspaces 1-9",
|
|
|
|
|
0, XK_F1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"13/20: Cycle Layout",
|
|
|
|
|
"Switch between tiling, floating, monocle.\n\n"
|
|
|
|
|
"Press: Super + Space",
|
|
|
|
|
"Try it multiple times!",
|
|
|
|
|
MOD_SUPER, XK_space
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"14/20: Expand Master",
|
|
|
|
|
"Make the master area larger.\n\n"
|
|
|
|
|
"Press: Super + L",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_l
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"15/20: Shrink Master",
|
|
|
|
|
"Make the master area smaller.\n\n"
|
|
|
|
|
"Press: Super + H",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_h
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"16/20: Add to Master",
|
|
|
|
|
"Increase windows in master area.\n\n"
|
|
|
|
|
"Press: Super + I",
|
|
|
|
|
"",
|
|
|
|
|
MOD_SUPER, XK_i
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"17/20: AI Context",
|
|
|
|
|
"Show AI analysis of your current task.\n\n"
|
|
|
|
|
"Press: Super + A",
|
|
|
|
|
"Requires OPENROUTER_API_KEY",
|
|
|
|
|
MOD_SUPER, XK_a
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"18/20: AI Command",
|
|
|
|
|
"Ask AI to run commands for you.\n\n"
|
|
|
|
|
"Press: Super + Shift + A",
|
|
|
|
|
"Requires OPENROUTER_API_KEY",
|
|
|
|
|
MOD_SUPER | MOD_SHIFT, XK_a
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"19/20: Exa Search",
|
|
|
|
|
"Semantic web search.\n\n"
|
|
|
|
|
"Press: Super + Shift + E",
|
|
|
|
|
"Requires EXA_API_KEY",
|
|
|
|
|
MOD_SUPER | MOD_SHIFT, XK_e
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"20/20: Show Shortcuts",
|
|
|
|
|
"Display all keyboard shortcuts.\n\n"
|
|
|
|
|
"Press: Super + S",
|
|
|
|
|
"Shows complete reference",
|
|
|
|
|
MOD_SUPER, XK_s
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Tutorial Complete!",
|
|
|
|
|
"You've learned all DWN shortcuts!\n\n"
|
|
|
|
|
"Super+S: Show all shortcuts\n"
|
|
|
|
|
"Super+T: Restart this tutorial\n\n"
|
|
|
|
|
"Press: Super + Backspace to quit DWN",
|
|
|
|
|
"Congratulations!",
|
|
|
|
|
MOD_SUPER, XK_BackSpace
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define TUTORIAL_STEPS (sizeof(tutorial_steps) / sizeof(tutorial_steps[0]))
|
|
|
|
|
|
|
|
|
|
static bool tutorial_active = false;
|
|
|
|
|
static int tutorial_current_step = 0;
|
|
|
|
|
|
|
|
|
|
bool tutorial_is_active(void)
|
|
|
|
|
{
|
|
|
|
|
return tutorial_active;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tutorial_start(void)
|
|
|
|
|
{
|
|
|
|
|
tutorial_active = true;
|
|
|
|
|
tutorial_current_step = 0;
|
|
|
|
|
|
|
|
|
|
const TutorialStep *step = &tutorial_steps[0];
|
|
|
|
|
char msg[512];
|
|
|
|
|
snprintf(msg, sizeof(msg), "%s\n\n%s", step->instruction, step->hint);
|
2025-12-28 05:01:46 +01:00
|
|
|
notification_show("DWN Tutorial", step->title, msg, NULL, 0);
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tutorial_stop(void)
|
|
|
|
|
{
|
|
|
|
|
tutorial_active = false;
|
|
|
|
|
tutorial_current_step = 0;
|
|
|
|
|
notification_show("DWN Tutorial", "Tutorial Stopped",
|
|
|
|
|
"Press Super+T to restart anytime.", NULL, 3000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tutorial_next_step(void)
|
|
|
|
|
{
|
|
|
|
|
tutorial_current_step++;
|
|
|
|
|
|
|
|
|
|
if (tutorial_current_step >= (int)TUTORIAL_STEPS) {
|
|
|
|
|
tutorial_active = false;
|
|
|
|
|
tutorial_current_step = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TutorialStep *step = &tutorial_steps[tutorial_current_step];
|
|
|
|
|
char msg[512];
|
|
|
|
|
snprintf(msg, sizeof(msg), "%s\n\n%s", step->instruction, step->hint);
|
|
|
|
|
notification_show("DWN Tutorial", step->title, msg, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void tutorial_check_key(unsigned int modifiers, KeySym keysym)
|
|
|
|
|
{
|
|
|
|
|
if (!tutorial_active) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TutorialStep *step = &tutorial_steps[tutorial_current_step];
|
|
|
|
|
|
|
|
|
|
if (modifiers == step->modifiers && keysym == step->keysym) {
|
|
|
|
|
tutorial_next_step();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void keys_init(void)
|
|
|
|
|
{
|
|
|
|
|
binding_count = 0;
|
|
|
|
|
memset(bindings, 0, sizeof(bindings));
|
|
|
|
|
|
|
|
|
|
keys_setup_defaults();
|
|
|
|
|
keys_grab_all();
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Keyboard shortcuts initialized (%d bindings)", binding_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_cleanup(void)
|
|
|
|
|
{
|
|
|
|
|
keys_ungrab_all();
|
|
|
|
|
keys_clear_all();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_grab_all(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL || dwn->display == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Display *dpy = dwn->display;
|
|
|
|
|
Window root = dwn->root;
|
|
|
|
|
|
|
|
|
|
XUngrabKey(dpy, AnyKey, AnyModifier, root);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < binding_count; i++) {
|
|
|
|
|
KeyCode code = XKeysymToKeycode(dpy, bindings[i].keysym);
|
|
|
|
|
if (code == 0) {
|
|
|
|
|
LOG_WARN("Could not get keycode for keysym %lu", bindings[i].keysym);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int modifiers[] = {
|
|
|
|
|
bindings[i].modifiers,
|
2025-12-28 05:01:46 +01:00
|
|
|
bindings[i].modifiers | Mod2Mask,
|
|
|
|
|
bindings[i].modifiers | LockMask,
|
2025-12-28 03:14:31 +01:00
|
|
|
bindings[i].modifiers | Mod2Mask | LockMask
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < sizeof(modifiers) / sizeof(modifiers[0]); j++) {
|
|
|
|
|
XGrabKey(dpy, code, modifiers[j], root, True,
|
|
|
|
|
GrabModeAsync, GrabModeAsync);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-28 04:30:10 +01:00
|
|
|
|
|
|
|
|
KeyCode super_l = XKeysymToKeycode(dpy, XK_Super_L);
|
|
|
|
|
KeyCode super_r = XKeysymToKeycode(dpy, XK_Super_R);
|
|
|
|
|
unsigned int lock_mods[] = { 0, Mod2Mask, LockMask, Mod2Mask | LockMask };
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(lock_mods) / sizeof(lock_mods[0]); i++) {
|
|
|
|
|
if (super_l) XGrabKey(dpy, super_l, lock_mods[i], root, True,
|
|
|
|
|
GrabModeAsync, GrabModeAsync);
|
|
|
|
|
if (super_r) XGrabKey(dpy, super_r, lock_mods[i], root, True,
|
|
|
|
|
GrabModeAsync, GrabModeAsync);
|
|
|
|
|
}
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_ungrab_all(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL || dwn->display == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XUngrabKey(dwn->display, AnyKey, AnyModifier, dwn->root);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void keys_bind(unsigned int modifiers, KeySym keysym,
|
|
|
|
|
KeyCallback callback, const char *description)
|
|
|
|
|
{
|
|
|
|
|
if (binding_count >= MAX_KEYBINDINGS) {
|
|
|
|
|
LOG_WARN("Maximum key bindings reached");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bindings[binding_count].modifiers = modifiers;
|
|
|
|
|
bindings[binding_count].keysym = keysym;
|
|
|
|
|
bindings[binding_count].callback = callback;
|
|
|
|
|
bindings[binding_count].description = description;
|
|
|
|
|
binding_count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_unbind(unsigned int modifiers, KeySym keysym)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < binding_count; i++) {
|
|
|
|
|
if (bindings[i].modifiers == modifiers &&
|
|
|
|
|
bindings[i].keysym == keysym) {
|
|
|
|
|
memmove(&bindings[i], &bindings[i + 1],
|
|
|
|
|
(binding_count - i - 1) * sizeof(KeyBinding));
|
|
|
|
|
binding_count--;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_clear_all(void)
|
|
|
|
|
{
|
|
|
|
|
binding_count = 0;
|
|
|
|
|
memset(bindings, 0, sizeof(bindings));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void keys_handle_press(XKeyEvent *ev)
|
|
|
|
|
{
|
|
|
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeySym keysym = XLookupKeysym(ev, 0);
|
|
|
|
|
|
2025-12-28 04:30:10 +01:00
|
|
|
if (keysym == XK_Super_L || keysym == XK_Super_R) {
|
|
|
|
|
super_pressed = true;
|
|
|
|
|
super_used_in_combo = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (super_pressed && (ev->state & Mod4Mask)) {
|
|
|
|
|
super_used_in_combo = true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 03:14:31 +01:00
|
|
|
unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask);
|
|
|
|
|
|
|
|
|
|
if (tutorial_is_active()) {
|
|
|
|
|
tutorial_check_key(clean_mask, keysym);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < binding_count; i++) {
|
|
|
|
|
if (bindings[i].keysym == keysym &&
|
|
|
|
|
bindings[i].modifiers == clean_mask) {
|
|
|
|
|
if (bindings[i].callback != NULL) {
|
|
|
|
|
LOG_DEBUG("Key binding triggered: %s", bindings[i].description);
|
|
|
|
|
bindings[i].callback();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void keys_handle_release(XKeyEvent *ev)
|
|
|
|
|
{
|
2025-12-28 04:30:10 +01:00
|
|
|
if (ev == NULL || dwn == NULL || dwn->display == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeySym keysym = XLookupKeysym(ev, 0);
|
|
|
|
|
|
|
|
|
|
if (keysym == XK_Super_L || keysym == XK_Super_R) {
|
|
|
|
|
if (super_pressed && !super_used_in_combo) {
|
|
|
|
|
key_spawn_launcher();
|
|
|
|
|
}
|
|
|
|
|
super_pressed = false;
|
|
|
|
|
super_used_in_combo = false;
|
|
|
|
|
}
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void keys_setup_defaults(void)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_CTRL | MOD_ALT, XK_t, key_spawn_terminal, "Spawn terminal");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_ALT, XK_F2, key_spawn_launcher, "Application finder");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_e, key_spawn_file_manager, "File manager");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_b, key_spawn_browser, "Web browser");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_ALT, XK_F4, key_close_window, "Close window");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_ALT, XK_F10, key_toggle_maximize, "Toggle maximize");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_ALT, XK_F11, key_toggle_fullscreen, "Toggle fullscreen");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_ALT, XK_Tab, key_focus_next, "Cycle windows");
|
|
|
|
|
keys_bind(MOD_ALT | MOD_SHIFT, XK_Tab, key_focus_prev, "Cycle windows reverse");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_F9, key_toggle_floating, "Toggle floating");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_CTRL | MOD_ALT, XK_Right, key_workspace_next, "Next workspace");
|
|
|
|
|
keys_bind(MOD_CTRL | MOD_ALT, XK_Left, key_workspace_prev, "Previous workspace");
|
|
|
|
|
|
|
|
|
|
keys_bind(0, XK_F1, key_workspace_1, "Switch to workspace 1");
|
|
|
|
|
keys_bind(0, XK_F2, key_workspace_2, "Switch to workspace 2");
|
|
|
|
|
keys_bind(0, XK_F3, key_workspace_3, "Switch to workspace 3");
|
|
|
|
|
keys_bind(0, XK_F4, key_workspace_4, "Switch to workspace 4");
|
|
|
|
|
keys_bind(0, XK_F5, key_workspace_5, "Switch to workspace 5");
|
|
|
|
|
keys_bind(0, XK_F6, key_workspace_6, "Switch to workspace 6");
|
|
|
|
|
keys_bind(0, XK_F7, key_workspace_7, "Switch to workspace 7");
|
|
|
|
|
keys_bind(0, XK_F8, key_workspace_8, "Switch to workspace 8");
|
|
|
|
|
keys_bind(0, XK_F9, key_workspace_9, "Switch to workspace 9");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F1, key_move_to_workspace_1, "Move to workspace 1");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F2, key_move_to_workspace_2, "Move to workspace 2");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F3, key_move_to_workspace_3, "Move to workspace 3");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F4, key_move_to_workspace_4, "Move to workspace 4");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F5, key_move_to_workspace_5, "Move to workspace 5");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F6, key_move_to_workspace_6, "Move to workspace 6");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F7, key_move_to_workspace_7, "Move to workspace 7");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F8, key_move_to_workspace_8, "Move to workspace 8");
|
|
|
|
|
keys_bind(MOD_SHIFT, XK_F9, key_move_to_workspace_9, "Move to workspace 9");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_BackSpace, key_quit_dwn, "Quit DWN");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_space, key_cycle_layout, "Cycle layout");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_l, key_increase_master, "Increase master ratio");
|
|
|
|
|
keys_bind(MOD_SUPER, XK_h, key_decrease_master, "Decrease master ratio");
|
|
|
|
|
keys_bind(MOD_SUPER, XK_i, key_increase_master_count, "Increase master count");
|
|
|
|
|
keys_bind(MOD_SUPER, XK_d, key_decrease_master_count, "Decrease master count");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_a, key_toggle_ai, "Toggle AI context");
|
|
|
|
|
keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_ai_command, "AI command");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER | MOD_SHIFT, XK_e, key_exa_search, "Exa web search");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_s, key_show_shortcuts, "Show shortcuts");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_Down, key_news_next, "Next news article");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_Up, key_news_prev, "Previous news article");
|
|
|
|
|
|
|
|
|
|
keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article");
|
|
|
|
|
|
|
|
|
|
keys_bind(0, XK_Print, key_screenshot, "Take screenshot");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void key_spawn_terminal(void)
|
|
|
|
|
{
|
|
|
|
|
const char *terminal = config_get_terminal();
|
|
|
|
|
spawn_async(terminal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_spawn_launcher(void)
|
|
|
|
|
{
|
|
|
|
|
applauncher_show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_spawn_file_manager(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn != NULL && dwn->config != NULL) {
|
|
|
|
|
spawn_async(dwn->config->file_manager);
|
|
|
|
|
} else {
|
|
|
|
|
spawn_async("thunar");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_spawn_browser(void)
|
|
|
|
|
{
|
|
|
|
|
spawn_async("xdg-open http://");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_close_window(void)
|
|
|
|
|
{
|
|
|
|
|
Workspace *ws = workspace_get_current();
|
|
|
|
|
if (ws != NULL && ws->focused != NULL) {
|
|
|
|
|
client_close(ws->focused);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_quit_dwn(void)
|
|
|
|
|
{
|
|
|
|
|
LOG_INFO("Quit requested via keyboard shortcut");
|
|
|
|
|
dwn_quit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_cycle_layout(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
workspace_cycle_layout(dwn->current_workspace);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_toggle_floating(void)
|
|
|
|
|
{
|
|
|
|
|
Workspace *ws = workspace_get_current();
|
|
|
|
|
if (ws != NULL && ws->focused != NULL) {
|
|
|
|
|
client_toggle_floating(ws->focused);
|
|
|
|
|
workspace_arrange_current();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_toggle_fullscreen(void)
|
|
|
|
|
{
|
|
|
|
|
Workspace *ws = workspace_get_current();
|
|
|
|
|
if (ws != NULL && ws->focused != NULL) {
|
|
|
|
|
client_toggle_fullscreen(ws->focused);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_toggle_maximize(void)
|
|
|
|
|
{
|
|
|
|
|
Workspace *ws = workspace_get_current();
|
|
|
|
|
if (ws != NULL && ws->focused != NULL) {
|
|
|
|
|
client_toggle_fullscreen(ws->focused);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_focus_next(void)
|
|
|
|
|
{
|
|
|
|
|
workspace_focus_next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_focus_prev(void)
|
|
|
|
|
{
|
|
|
|
|
workspace_focus_prev();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_workspace_next(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int next = dwn->current_workspace + 1;
|
|
|
|
|
if (next >= MAX_WORKSPACES) {
|
2025-12-28 05:01:46 +01:00
|
|
|
next = 0;
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
workspace_switch(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_workspace_prev(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int prev = dwn->current_workspace - 1;
|
|
|
|
|
if (prev < 0) {
|
2025-12-28 05:01:46 +01:00
|
|
|
prev = MAX_WORKSPACES - 1;
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
workspace_switch(prev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_workspace_1(void) { workspace_switch(0); }
|
|
|
|
|
void key_workspace_2(void) { workspace_switch(1); }
|
|
|
|
|
void key_workspace_3(void) { workspace_switch(2); }
|
|
|
|
|
void key_workspace_4(void) { workspace_switch(3); }
|
|
|
|
|
void key_workspace_5(void) { workspace_switch(4); }
|
|
|
|
|
void key_workspace_6(void) { workspace_switch(5); }
|
|
|
|
|
void key_workspace_7(void) { workspace_switch(6); }
|
|
|
|
|
void key_workspace_8(void) { workspace_switch(7); }
|
|
|
|
|
void key_workspace_9(void) { workspace_switch(8); }
|
|
|
|
|
|
|
|
|
|
static void move_focused_to_workspace(int ws)
|
|
|
|
|
{
|
|
|
|
|
Workspace *current = workspace_get_current();
|
|
|
|
|
if (current != NULL && current->focused != NULL) {
|
|
|
|
|
workspace_move_client(current->focused, ws);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_move_to_workspace_1(void) { move_focused_to_workspace(0); }
|
|
|
|
|
void key_move_to_workspace_2(void) { move_focused_to_workspace(1); }
|
|
|
|
|
void key_move_to_workspace_3(void) { move_focused_to_workspace(2); }
|
|
|
|
|
void key_move_to_workspace_4(void) { move_focused_to_workspace(3); }
|
|
|
|
|
void key_move_to_workspace_5(void) { move_focused_to_workspace(4); }
|
|
|
|
|
void key_move_to_workspace_6(void) { move_focused_to_workspace(5); }
|
|
|
|
|
void key_move_to_workspace_7(void) { move_focused_to_workspace(6); }
|
|
|
|
|
void key_move_to_workspace_8(void) { move_focused_to_workspace(7); }
|
|
|
|
|
void key_move_to_workspace_9(void) { move_focused_to_workspace(8); }
|
|
|
|
|
|
|
|
|
|
void key_increase_master(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
workspace_adjust_master_ratio(dwn->current_workspace, 0.05f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_decrease_master(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
workspace_adjust_master_ratio(dwn->current_workspace, -0.05f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_increase_master_count(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
workspace_adjust_master_count(dwn->current_workspace, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_decrease_master_count(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
workspace_adjust_master_count(dwn->current_workspace, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_toggle_ai(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dwn->ai_enabled) {
|
|
|
|
|
notification_show("DWN AI", "AI Disabled",
|
|
|
|
|
"Set OPENROUTER_API_KEY to enable AI features",
|
|
|
|
|
NULL, 3000);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ai_update_context();
|
|
|
|
|
const char *task = ai_analyze_task();
|
|
|
|
|
const char *suggestion = ai_suggest_window();
|
|
|
|
|
|
|
|
|
|
char body[512];
|
|
|
|
|
Workspace *ws = workspace_get_current();
|
|
|
|
|
if (ws != NULL && ws->focused != NULL) {
|
|
|
|
|
snprintf(body, sizeof(body),
|
|
|
|
|
"Current task: %s\nFocused: %s\n%s",
|
|
|
|
|
task ? task : "Unknown",
|
|
|
|
|
ws->focused->title[0] ? ws->focused->title : "(unnamed)",
|
|
|
|
|
suggestion ? suggestion : "Press Alt+A to ask AI for help");
|
|
|
|
|
} else {
|
|
|
|
|
snprintf(body, sizeof(body),
|
|
|
|
|
"No window focused.\nPress Alt+A to ask AI for help");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
notification_show("DWN AI", "Context Analysis", body, NULL, 4000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_ai_command(void)
|
|
|
|
|
{
|
|
|
|
|
if (dwn == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dwn->ai_enabled) {
|
|
|
|
|
ai_show_command_palette();
|
|
|
|
|
} else {
|
|
|
|
|
LOG_INFO("AI not enabled (no API key)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_exa_search(void)
|
|
|
|
|
{
|
|
|
|
|
exa_show_app_launcher();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_show_shortcuts(void)
|
|
|
|
|
{
|
|
|
|
|
const char *shortcuts =
|
|
|
|
|
"=== Applications ===\n"
|
|
|
|
|
"Ctrl+Alt+T Terminal\n"
|
2025-12-28 04:30:10 +01:00
|
|
|
"Super / Alt+F2 App launcher\n"
|
2025-12-28 03:14:31 +01:00
|
|
|
"Super+E File manager\n"
|
|
|
|
|
"Super+B Web browser\n"
|
|
|
|
|
"Print Screenshot\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"=== Window Management ===\n"
|
|
|
|
|
"Alt+F4 Close window\n"
|
|
|
|
|
"Alt+Tab Next window\n"
|
|
|
|
|
"Alt+Shift+Tab Previous window\n"
|
|
|
|
|
"Alt+F10 Toggle maximize\n"
|
|
|
|
|
"Alt+F11 Toggle fullscreen\n"
|
|
|
|
|
"Super+F9 Toggle floating\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"=== Workspaces ===\n"
|
|
|
|
|
"F1-F9 Switch to workspace\n"
|
|
|
|
|
"Shift+F1-F9 Move window to workspace\n"
|
|
|
|
|
"Ctrl+Alt+Right Next workspace\n"
|
|
|
|
|
"Ctrl+Alt+Left Previous workspace\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"=== Layout Control ===\n"
|
|
|
|
|
"Super+Space Cycle layout (tile/float/mono)\n"
|
|
|
|
|
"Super+H Shrink master area\n"
|
|
|
|
|
"Super+L Expand master area\n"
|
|
|
|
|
"Super+I Add to master\n"
|
|
|
|
|
"Super+D Remove from master\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"=== AI Features ===\n"
|
|
|
|
|
"Super+A Show AI context\n"
|
|
|
|
|
"Super+Shift+A AI command palette\n"
|
|
|
|
|
"Super+Shift+E Exa semantic search\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"=== Help & System ===\n"
|
|
|
|
|
"Super+S Show shortcuts (this)\n"
|
|
|
|
|
"Super+T Interactive tutorial\n"
|
|
|
|
|
"Super+Backspace Quit DWN";
|
|
|
|
|
|
|
|
|
|
notification_show("DWN Shortcuts", "Complete Reference", shortcuts, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_start_tutorial(void)
|
|
|
|
|
{
|
|
|
|
|
if (tutorial_is_active()) {
|
|
|
|
|
tutorial_next_step();
|
|
|
|
|
} else {
|
|
|
|
|
tutorial_start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void key_news_next(void)
|
|
|
|
|
{
|
|
|
|
|
news_next_article();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_news_prev(void)
|
|
|
|
|
{
|
|
|
|
|
news_prev_article();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_news_open(void)
|
|
|
|
|
{
|
|
|
|
|
news_open_current();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void key_screenshot(void)
|
|
|
|
|
{
|
|
|
|
|
spawn_async("xfce4-screenshooter");
|
|
|
|
|
}
|