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