keyboard_cgo.go RawHistoryFolder
1//go:build webkit_cgo
2
3package webkit
4
5/*
6#cgo pkg-config: gtk+-3.0
7#include <gtk/gtk.h>
8#include <gdk/gdk.h>
9*/
10import "C"
11
12import (
13 "fmt"
14 "log"
15 "sync/atomic"
16 "sync"
17 "time"
18)
19
20// Registry of accelerators per view id.
21type shortcutRegistry map[string]func()
22
23var (
24 regMu sync.RWMutex
25 viewShortcuts = make(map[uintptr]shortcutRegistry)
26 viewIDCounter uint64
27 viewByID = make(map[uintptr]*WebView)
28 lastKeyTime = make(map[uintptr]map[string]time.Time)
29)
30
31// RegisterKeyboardShortcut registers a callback under an accelerator string.
32func (w *WebView) RegisterKeyboardShortcut(accel string, callback func()) error {
33 if w == nil || w.destroyed {
34 return ErrNotImplemented
35 }
36 if accel == "" || callback == nil {
37 return fmt.Errorf("invalid shortcut registration")
38 }
39 regMu.Lock()
40 if _, ok := viewShortcuts[w.id]; !ok {
41 viewShortcuts[w.id] = make(shortcutRegistry)
42 }
43 viewShortcuts[w.id][accel] = callback
44 regMu.Unlock()
45 return nil
46}
47
48//export goOnKeyPress
49func goOnKeyPress(id C.ulong, keyval C.guint, state C.GdkModifierType) {
50 // Normalize and dispatch
51 uid := uintptr(id)
52 kv := uint(keyval)
53 st := uint(state)
54 dispatchAccelerator(uid, kv, st)
55}
56
57func dispatchAccelerator(uid uintptr, keyval uint, state uint) {
58 ctrl := (state & uint(C.GDK_CONTROL_MASK)) != 0
59 // Map keyval to string names
60 var keyName string
61 switch keyval {
62 case uint(C.GDK_KEY_plus), uint(C.GDK_KEY_KP_Add):
63 keyName = "plus"
64 case uint(C.GDK_KEY_equal):
65 keyName = "="
66 case uint(C.GDK_KEY_minus), uint(C.GDK_KEY_KP_Subtract):
67 keyName = "-"
68 case uint(C.GDK_KEY_0), uint(C.GDK_KEY_KP_0):
69 keyName = "0"
70 case uint(C.GDK_KEY_F12):
71 keyName = "F12"
72 default:
73 return
74 }
75
76 // Candidate accelerator strings in order
77 candidates := []string{keyName}
78 if ctrl {
79 candidates = append([]string{"cmdorctrl+" + keyName, "ctrl+" + keyName}, candidates...)
80 }
81
82 regMu.RLock()
83 reg := viewShortcuts[uid]
84 regMu.RUnlock()
85 if reg == nil {
86 return
87 }
88 for _, name := range candidates {
89 if cb, ok := reg[name]; ok {
90 // Debounce duplicates within 120ms for the same view+key
91 regMu.Lock()
92 if _, ok := lastKeyTime[uid]; !ok { lastKeyTime[uid] = make(map[string]time.Time) }
93 last := lastKeyTime[uid][name]
94 now := time.Now()
95 if now.Sub(last) < 120*time.Millisecond {
96 // Skip duplicate
97 regMu.Unlock()
98 return
99 }
100 lastKeyTime[uid][name] = now
101 regMu.Unlock()
102 log.Printf("[accelerator] %s", name)
103 cb()
104 break
105 }
106 }
107}
108
109func nextViewID() uintptr {
110 return uintptr(atomic.AddUint64(&viewIDCounter, 1))
111}
112
113func registerView(id uintptr, w *WebView) {
114 regMu.Lock()
115 viewByID[id] = w
116 regMu.Unlock()
117}
118
119func unregisterView(id uintptr) {
120 regMu.Lock()
121 delete(viewByID, id)
122 delete(viewShortcuts, id)
123 regMu.Unlock()
124}
125
126//export goOnUcmMessage
127func goOnUcmMessage(id C.ulong, json *C.char) {
128 uid := uintptr(id)
129 regMu.RLock()
130 vw := viewByID[uid]
131 regMu.RUnlock()
132 if vw == nil || json == nil { return }
133 goPayload := C.GoString(json)
134 vw.dispatchScriptMessage(goPayload)
135}
136
137//export goOnTitleChanged
138func goOnTitleChanged(id C.ulong, ctitle *C.char) {
139 uid := uintptr(id)
140 regMu.RLock()
141 vw := viewByID[uid]
142 regMu.RUnlock()
143 if vw == nil || ctitle == nil { return }
144 title := C.GoString(ctitle)
145 vw.dispatchTitleChanged(title)
146}
147
148//export goOnURIChanged
149func goOnURIChanged(id C.ulong, curi *C.char) {
150 uid := uintptr(id)
151 regMu.RLock()
152 vw := viewByID[uid]
153 regMu.RUnlock()
154 if vw == nil || curi == nil { return }
155 uri := C.GoString(curi)
156 vw.dispatchURIChanged(uri)
157}
158
159//export goOnButtonPress
160func goOnButtonPress(id C.ulong, button C.guint, state C.GdkModifierType) {
161 uid := uintptr(id)
162 btn := uint(button)
163 log.Printf("[mouse] button=%d state=0x%x", btn, uint(state))
164 regMu.RLock()
165 vw := viewByID[uid]
166 regMu.RUnlock()
167 if vw == nil {
168 return
169 }
170 switch btn {
171 case 8: // Back button on many mice
172 _ = vw.GoBack()
173 case 9: // Forward button on many mice
174 _ = vw.GoForward()
175 }
176}