forked from Sneeds-Feed-and-Seed/sneedacity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathKeyboardCapture.cpp
430 lines (359 loc) · 12.3 KB
/
KeyboardCapture.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/**********************************************************************
Sneedacity: A Digital Audio Editor
KeyboardCapture.cpp
Paul Licameli split this from Project.cpp
**********************************************************************/
#include "KeyboardCapture.h"
#if defined(__WXMAC__)
#include <wx/textctrl.h>
#include <AppKit/AppKit.h>
#include <wx/osx/core/private.h>
#include <wx/osx/cocoa/private.h>
#elif defined(__WXGTK__)
#include <gtk/gtk.h>
#endif
#include <wx/button.h>
#include <wx/eventfilter.h>
#include <wx/toplevel.h>
#include <wx/weakref.h>
#include <wx/window.h>
#include "SneedacityException.h"
////////////////////////////////////////////////////////////
/// Custom events
////////////////////////////////////////////////////////////
DEFINE_EVENT_TYPE(EVT_CAPTURE_KEY);
namespace {
wxWindowRef &sHandler()
{
static wxWindowRef theHandler;
return theHandler;
}
KeyboardCapture::FilterFunction &sPreFilter()
{
static KeyboardCapture::FilterFunction theFilter;
return theFilter;
}
KeyboardCapture::FilterFunction &sPostFilter()
{
static KeyboardCapture::FilterFunction theFilter;
return theFilter;
}
}
namespace KeyboardCapture
{
// Keyboard capture
bool IsHandler(const wxWindow *handler)
{
return GetHandler() == handler;
}
wxWindow *GetHandler()
{
return sHandler();
}
void Capture(wxWindow *handler)
{
sHandler() = handler;
}
void Release(wxWindow *handler)
{
// wxASSERT(sHandler() == handler);
sHandler() = nullptr;
}
FilterFunction SetPreFilter( const FilterFunction &function )
{
auto result = sPreFilter();
sPreFilter() = function;
return result;
}
FilterFunction SetPostFilter( const FilterFunction &function )
{
auto result = sPostFilter();
sPostFilter() = function;
return result;
}
void OnFocus( wxWindow &window, wxFocusEvent &event )
{
if (event.GetEventType() == wxEVT_KILL_FOCUS)
KeyboardCapture::Release( &window );
else
KeyboardCapture::Capture( &window );
window.Refresh( false );
event.Skip();
}
}
// Shared by all projects
static class EventMonitor final : public wxEventFilter
{
public:
EventMonitor()
: wxEventFilter()
{
#if defined(__WXMAC__)
// In wx3, the menu accelerators take precedence over key event processing
// so we won't get wxEVT_CHAR_HOOK events for combinations assigned to menus.
// Since we only support OS X 10.6 or greater, we can use an event monitor
// to capture the key event before it gets to the normal wx3 processing.
// The documentation for addLocalMonitorForEventsMatchingMask implies that
// NSKeyUpMask can't be used in 10.6, but testing shows that it can.
NSEventMask mask = NSKeyDownMask | NSKeyUpMask;
mHandler =
[
NSEvent addLocalMonitorForEventsMatchingMask:mask handler:^NSEvent *(NSEvent *event)
{
WXWidget widget = (WXWidget) [ [event window] firstResponder];
if (widget)
{
wxWidgetCocoaImpl *impl = (wxWidgetCocoaImpl *)
wxWidgetImpl::FindFromWXWidget(widget);
if (impl)
{
mEvent = event;
wxKeyEvent wxevent([event type] == NSKeyDown ? wxEVT_KEY_DOWN : wxEVT_KEY_UP);
impl->SetupKeyEvent(wxevent, event);
NSEvent *result;
if ([event type] == NSKeyDown)
{
wxKeyEvent eventHook(wxEVT_CHAR_HOOK, wxevent);
result = FilterEvent(eventHook) == Event_Processed ? nil : event;
}
else
{
result = FilterEvent(wxevent) == Event_Processed ? nil : event;
}
mEvent = nullptr;
return result;
}
}
return event;
}
];
// Bug1252: must also install this filter with wxWidgets, else
// we don't intercept command keys when focus is in a combo box.
wxEvtHandler::AddFilter(this);
#else
wxEvtHandler::AddFilter(this);
#endif
}
~EventMonitor() override
{
#if defined(__WXMAC__)
wxEvtHandler::RemoveFilter(this);
[NSEvent removeMonitor:mHandler];
#else
wxEvtHandler::RemoveFilter(this);
#endif
}
int FilterEvent(wxEvent& event) override
{
// Unguarded exception propagation may crash the program, at least
// on Mac while in the objective-C closure above
return GuardedCall< int > ( [&] {
// Quickly bail if this isn't something we want.
wxEventType type = event.GetEventType();
if (type != wxEVT_CHAR_HOOK && type != wxEVT_KEY_UP)
{
return Event_Skip;
}
wxKeyEvent key = static_cast<wxKeyEvent &>( event );
if ( !( sPreFilter() && sPreFilter()( key ) ) )
return Event_Skip;
#ifdef __WXMAC__
// Bugs 1329 and 2107 (Mac only)
// wxButton::SetDefault() alone doesn't cause correct event routing
// of key-down to the button when a text entry or combo has focus,
// but we can intercept wxEVT_CHAR_HOOK here and do it
if ( type == wxEVT_CHAR_HOOK &&
key.GetKeyCode() == WXK_RETURN ) {
const auto focus = wxWindow::FindFocus();
// Bug 2267 (Mac only): don't apply fix for 2107 when a text entry
// needs to allow multiple line input
const auto text = dynamic_cast<wxTextCtrl*>(focus);
if ( !(text && text->IsMultiLine()) ) {
if (auto top =
dynamic_cast< wxTopLevelWindow* >(
wxGetTopLevelParent( focus ) ) ) {
if ( auto button =
dynamic_cast<wxButton*>( top->GetDefaultItem() ) ) {
wxCommandEvent newEvent{ wxEVT_BUTTON, button->GetId() };
button->GetEventHandler()->AddPendingEvent( newEvent );
return Event_Processed;
}
}
}
}
#endif
// Make a copy of the event and (possibly) make it look like a key down
// event.
if (type == wxEVT_CHAR_HOOK)
{
key.SetEventType(wxEVT_KEY_DOWN);
}
// Give the capture handler first dibs at the event.
wxWindow *handler = KeyboardCapture::GetHandler();
if (handler && HandleCapture(handler, key))
{
return Event_Processed;
}
if ( sPostFilter() && sPostFilter()( key ) )
return Event_Processed;
// Give it back to WX for normal processing.
return Event_Skip;
},
// Immediate handler invokes the same high level catch-all as for
// unhandled exceptions, which will also do its own delayed handling
[](SneedacityException *pEx){
if (pEx)
wxTheApp->OnExceptionInMainLoop();
else
throw;
return Event_Processed;
},
// So don't duplicate delayed handling:
[](auto){}
);
}
private:
// Returns true if the event was captured and processed
bool HandleCapture(wxWindow *target, const wxKeyEvent & event)
{
if (wxGetTopLevelParent(target) != wxGetTopLevelParent(wxWindow::FindFocus()))
{
return false;
}
wxEvtHandler *handler = target->GetEventHandler();
// We make a copy of the event because the capture handler may modify it.
wxKeyEvent temp = event;
#if defined(__WXGTK__)
// wxGTK uses the control and alt modifiers to represent ALTGR,
// so remove it as it might confuse the capture handlers.
if (temp.GetModifiers() == (wxMOD_CONTROL | wxMOD_ALT))
{
temp.SetControlDown(false);
temp.SetAltDown(false);
}
#endif
// Ask the capture handler if the key down/up event is something it
// might be interested in handling.
wxCommandEvent e(EVT_CAPTURE_KEY);
e.SetEventObject(&temp);
e.StopPropagation();
if (!handler->ProcessEvent(e))
{
return false;
}
// Now, let the handler process the normal key event.
bool keyDown = temp.GetEventType() == wxEVT_KEY_DOWN;
temp.WasProcessed();
temp.StopPropagation();
wxEventProcessInHandlerOnly onlyDown(temp, handler);
bool processed = handler->ProcessEvent(temp);
// Don't go any further if the capture handler didn't process
// the key down event.
if (!processed && keyDown)
{
return false;
}
// At this point the capture handler has either processed a key down event
// or we're dealing with a key up event.
//
// So, only generate the char events for key down events.
if (keyDown)
{
wxString chars = GetUnicodeString(temp);
for (size_t i = 0, cnt = chars.length(); i < cnt; i++)
{
temp = event;
temp.SetEventType(wxEVT_CHAR);
temp.WasProcessed();
temp.StopPropagation();
temp.m_uniChar = chars[i];
wxEventProcessInHandlerOnly onlyChar(temp, handler);
handler->ProcessEvent(temp);
}
}
// We get here for processed key down events or for key up events, whether
// processed or not.
return true;
}
// Convert the key down event to a unicode string.
wxString GetUnicodeString(const wxKeyEvent & event)
{
wxString chars;
#if defined(__WXMSW__)
BYTE ks[256];
GetKeyboardState(ks);
WCHAR ucode[256];
int res = ToUnicode(event.GetRawKeyCode(), 0, ks, ucode, 256, 0);
if (res >= 1)
{
chars.Append(ucode, res);
}
#elif defined(__WXGTK__)
chars.Append((wxChar) gdk_keyval_to_unicode(event.GetRawKeyCode()));
#elif defined(__WXMAC__)
if (!mEvent) {
// TODO: we got here without getting the NSEvent pointer,
// as in the combo box case of bug 1252. We can't compute it!
// This makes a difference only when there is a capture handler.
// It's never the case yet that there is one.
// Return just a one-character string.
return event.GetUnicodeKey();
}
NSString *c = [mEvent charactersIgnoringModifiers];
if ([c length] == 1)
{
unichar codepoint = [c characterAtIndex:0];
if ((codepoint >= 0xF700 && codepoint <= 0xF8FF) || codepoint == 0x7F)
{
return chars;
}
}
c = [mEvent characters];
chars = [c UTF8String];
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
CFRelease(currentKeyboard);
if (uchr == NULL)
{
return chars;
}
const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if (keyboardLayout == NULL)
{
return chars;
}
const UniCharCount maxStringLength = 255;
UniCharCount actualStringLength = 0;
UniChar unicodeString[maxStringLength];
UInt32 nsflags = [mEvent modifierFlags];
UInt16 modifiers = (nsflags & NSAlphaShiftKeyMask ? alphaLock : 0) |
(nsflags & NSShiftKeyMask ? shiftKey : 0) |
(nsflags & NSControlKeyMask ? controlKey : 0) |
(nsflags & NSAlternateKeyMask ? optionKey : 0) |
(nsflags & NSCommandKeyMask ? cmdKey : 0);
OSStatus status = UCKeyTranslate(keyboardLayout,
[mEvent keyCode],
kUCKeyActionDown,
(modifiers >> 8) & 0xff,
LMGetKbdType(),
0,
&mDeadKeyState,
maxStringLength,
&actualStringLength,
unicodeString);
if (status != noErr)
{
return chars;
}
chars = [ [NSString stringWithCharacters:unicodeString
length:(NSInteger)actualStringLength] UTF8String];
#endif
return chars;
}
private:
#if defined(__WXMAC__)
id mHandler;
NSEvent *mEvent {};
UInt32 mDeadKeyState;
#endif
} monitor;