1//go:build webkit_cgo
2
3package webkit
4
5import (
6 "log"
7 "strings"
8 "unsafe"
9)
10
11// No C imports needed - using Go wrapper functions from shortcuts_cgo.go
12
13// WindowShortcuts manages global shortcuts at the window level
14type WindowShortcuts struct {
15 window *Window
16 controller uintptr // *C.GtkShortcutController
17 callbacks map[string]uintptr
18}
19
20// InitializeGlobalShortcuts sets up the GTK4 global shortcut controller
21func (w *Window) InitializeGlobalShortcuts() *WindowShortcuts {
22 if w.win == nil {
23 log.Printf("[window-shortcuts] Cannot initialize - invalid window")
24 return nil
25 }
26
27 controller := CreateGlobalShortcutController(uintptr(unsafe.Pointer(w.win)))
28 if controller == 0 {
29 log.Printf("[window-shortcuts] Failed to create shortcut controller")
30 return nil
31 }
32
33 ws := &WindowShortcuts{
34 window: w,
35 controller: controller,
36 callbacks: make(map[string]uintptr),
37 }
38
39 log.Printf("[window-shortcuts] Global shortcut controller initialized")
40 return ws
41}
42
43// RegisterGlobalShortcut registers a shortcut at the window level with global scope
44func (ws *WindowShortcuts) RegisterGlobalShortcut(key string, callback func()) error {
45 if ws == nil || ws.controller == 0 {
46 return ErrNotImplemented
47 }
48
49 // Convert key format (e.g., "ctrl+l" -> "<Control>l")
50 gtkKey := convertToGtkFormat(key)
51 if gtkKey == "" {
52 log.Printf("[window-shortcuts] Invalid key format: %s", key)
53 return ErrNotImplemented
54 }
55
56 // Register callback and get handle
57 handle := registerWindowShortcutCallback(callback)
58 if handle == 0 {
59 log.Printf("[window-shortcuts] Failed to register callback for %s", key)
60 return ErrNotImplemented
61 }
62
63 // Store handle for cleanup
64 ws.callbacks[key] = handle
65
66 // Register with GTK using Go wrapper function
67 if err := AddShortcutToController(ws.controller, gtkKey, handle); err != nil {
68 log.Printf("[window-shortcuts] Failed to add shortcut to controller: %v", err)
69 unregisterWindowShortcutCallback(handle)
70 delete(ws.callbacks, key)
71 return err
72 }
73
74 // Only add Alt+Arrow and Ctrl+W shortcuts to global blocking registry
75 // All other window shortcuts should bubble up to GTK for proper handling
76 if strings.HasPrefix(key, "alt+Arrow") {
77 RegisterGlobalShortcutWithHandle(key, handle)
78 } else if key == "ctrl+w" {
79 // Ctrl+W should also have callback handle for consistent pane closing
80 RegisterGlobalShortcutWithHandle(key, handle)
81 }
82 // Other shortcuts (Ctrl+L, Ctrl+F, zoom) are NOT registered for blocking
83
84 log.Printf("[window-shortcuts] Registered global shortcut: %s -> %s", key, gtkKey)
85 return nil
86}
87
88// Cleanup unregisters all shortcuts and cleans up resources
89func (ws *WindowShortcuts) Cleanup() {
90 if ws == nil {
91 return
92 }
93
94 // Unregister all callbacks
95 for key, handle := range ws.callbacks {
96 unregisterWindowShortcutCallback(handle)
97 log.Printf("[window-shortcuts] Unregistered shortcut: %s", key)
98 }
99
100 ws.callbacks = nil
101 ws.controller = 0
102}
103
104// convertToGtkFormat converts common key formats to GTK shortcut format
105func convertToGtkFormat(key string) string {
106 // Convert from electron/webkit format to GTK format
107 key = strings.ToLower(key)
108
109 // Handle cmdorctrl -> Control
110 key = strings.ReplaceAll(key, "cmdorctrl+", "ctrl+")
111 key = strings.ReplaceAll(key, "cmd+", "ctrl+")
112
113 // Build GTK format
114 var parts []string
115 components := strings.Split(key, "+")
116
117 for i, part := range components {
118 if i == len(components)-1 {
119 // Last part is the key
120 switch part {
121 case "f12":
122 parts = append(parts, "F12")
123 case "f5":
124 parts = append(parts, "F5")
125 case "l":
126 parts = append(parts, "l")
127 case "f":
128 parts = append(parts, "f")
129 case "r":
130 parts = append(parts, "r")
131 case "c":
132 parts = append(parts, "c")
133 case "left", "arrowleft":
134 parts = append(parts, "Left")
135 case "right", "arrowright":
136 parts = append(parts, "Right")
137 case "up", "arrowup":
138 parts = append(parts, "Up")
139 case "down", "arrowdown":
140 parts = append(parts, "Down")
141 default:
142 parts = append(parts, part)
143 }
144 } else {
145 // Modifier
146 switch part {
147 case "ctrl":
148 parts = append(parts, "<Control>")
149 case "shift":
150 parts = append(parts, "<Shift>")
151 case "alt":
152 parts = append(parts, "<Alt>")
153 case "super":
154 parts = append(parts, "<Super>")
155 }
156 }
157 }
158
159 return strings.Join(parts, "")
160}