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