v0.9.0 636c44c

shortcuts_cgo.go RawHistoryFolder

1//go:build webkit_cgo
2
3package webkit
4
5/*
6#cgo pkg-config: gtk4 webkitgtk-6.0
7#include <gtk/gtk.h>
8#include <stdint.h>
9#include <stdlib.h>
10
11extern void goHandleWindowShortcut(uintptr_t handle);
12
13static GtkShortcutController* create_global_shortcut_controller(GtkWindow* window) {
14    if (!window) {
15        return NULL;
16    }
17    GtkEventController* event_controller = gtk_shortcut_controller_new();
18    if (!event_controller) {
19        return NULL;
20    }
21    GtkShortcutController* controller = GTK_SHORTCUT_CONTROLLER(event_controller);
22    gtk_shortcut_controller_set_scope(controller, GTK_SHORTCUT_SCOPE_GLOBAL);
23    gtk_widget_add_controller(GTK_WIDGET(window), event_controller);
24    return controller;
25}
26
27static gboolean shortcut_callback_bridge(GtkWidget* widget, GVariant* args, gpointer user_data) {
28    (void)widget;
29    (void)args;
30    uintptr_t handle = (uintptr_t)user_data;
31    if (handle == 0) {
32        return FALSE;
33    }
34    goHandleWindowShortcut(handle);
35    return TRUE;  // Indicate we handled the shortcut
36}
37
38static void add_shortcut_to_controller(GtkShortcutController* controller,
39                                const char* trigger_str,
40                                uintptr_t handle) {
41    if (!controller || !trigger_str || handle == 0) {
42        return;
43    }
44    GtkShortcutTrigger* trigger = gtk_shortcut_trigger_parse_string(trigger_str);
45    if (!trigger) {
46        return;
47    }
48    GtkShortcutAction* action = gtk_callback_action_new((GtkShortcutFunc)shortcut_callback_bridge,
49                                                        (gpointer)handle,
50                                                        NULL);
51    if (!action) {
52        g_object_unref(trigger);
53        return;
54    }
55    GtkShortcut* shortcut = gtk_shortcut_new(trigger, action);
56    gtk_shortcut_controller_add_shortcut(controller, shortcut);
57}
58*/
59import "C"
60
61import (
62	"log"
63	"sync"
64	"sync/atomic"
65	"unsafe"
66)
67
68var (
69	globalShortcutMu        sync.Mutex
70	globalShortcutCounter   uint64
71	globalShortcutCallbacks = make(map[uintptr]func())
72)
73
74func registerWindowShortcutCallback(cb func()) uintptr {
75	if cb == nil {
76		return 0
77	}
78	id := uintptr(atomic.AddUint64(&globalShortcutCounter, 1))
79	if id == 0 {
80		// Should never happen, but guard against wrapping to 0
81		id = uintptr(atomic.AddUint64(&globalShortcutCounter, 1))
82	}
83	globalShortcutMu.Lock()
84	globalShortcutCallbacks[id] = cb
85	globalShortcutMu.Unlock()
86	return id
87}
88
89func unregisterWindowShortcutCallback(id uintptr) {
90	if id == 0 {
91		return
92	}
93	globalShortcutMu.Lock()
94	delete(globalShortcutCallbacks, id)
95	globalShortcutMu.Unlock()
96}
97
98func invokeWindowShortcutCallback(id uintptr) {
99	if id == 0 {
100		return
101	}
102	globalShortcutMu.Lock()
103	cb := globalShortcutCallbacks[id]
104	globalShortcutMu.Unlock()
105	if cb == nil {
106		log.Printf("[shortcuts] no handler for shortcut id=%d", id)
107		return
108	}
109	cb()
110}
111
112//export goHandleWindowShortcut
113func goHandleWindowShortcut(handle C.uintptr_t) {
114	invokeWindowShortcutCallback(uintptr(handle))
115}
116
117// Go wrapper functions for CGO - these can be called from other Go files
118
119// CreateGlobalShortcutController creates a GTK4 global shortcut controller for a window
120func CreateGlobalShortcutController(window uintptr) uintptr {
121	if window == 0 {
122		return 0
123	}
124	controller := C.create_global_shortcut_controller((*C.GtkWindow)(unsafe.Pointer(window)))
125	return uintptr(unsafe.Pointer(controller))
126}
127
128// AddShortcutToController adds a shortcut to the controller with a callback handle
129func AddShortcutToController(controller uintptr, key string, handle uintptr) error {
130	if controller == 0 || key == "" || handle == 0 {
131		return ErrNotImplemented
132	}
133
134	keyStr := C.CString(key)
135	defer C.free(unsafe.Pointer(keyStr))
136
137	C.add_shortcut_to_controller(
138		(*C.GtkShortcutController)(unsafe.Pointer(controller)),
139		keyStr,
140		C.uintptr_t(handle),
141	)
142
143	return nil
144}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close