Vuo  2.3.2
VuoKeyboard.m
Go to the documentation of this file.
1 
10 #include "VuoMacOSSDKWorkaround.h"
11 #include <AppKit/AppKit.h>
12 
13 #include "VuoKeyboard.h"
14 #include "VuoApp.h"
15 
16 #ifdef VUO_COMPILER
18  "title" : "VuoKeyboard",
19  "dependencies" : [
20  "VuoKey",
21  "VuoModifierKey",
22  "VuoText",
23  "VuoWindow",
24  "VuoWindowReference",
25  "AppKit.framework"
26  ]
27  });
28 #endif
29 
30 
35 {
36  id monitor;
37 
38  NSMutableString *wordInProgress;
39  NSMutableString *lineInProgress;
40  UInt32 deadKeyState;
41 };
42 
47 {
48  // https://b33p.net/kosada/node/11966
49  // Keyboard events are only received if the process is in app mode.
50  VuoApp_init(true);
51 
52  struct VuoKeyboardContext *context = (struct VuoKeyboardContext *)calloc(1, sizeof(struct VuoKeyboardContext));
53  VuoRegister(context, free);
54  return (VuoKeyboard *)context;
55 }
56 
61 static void VuoKeyboard_fireTypingIfNeeded(NSEvent *event,
62  struct VuoKeyboardContext *context,
63  void (^typedLine) (VuoText),
64  void (^typedWord) (VuoText),
65  void (^typedCharacter) (VuoText, VuoModifierKey),
66  VuoWindowReference windowRef)
67 {
68  NSWindow *targetWindow = (NSWindow *)windowRef;
69  if (! targetWindow || targetWindow == [event window] || [[targetWindow contentView] isInFullScreenMode])
70  {
71  char *unicodeBytes = VuoKey_getCharactersForMacVirtualKeyCode([event keyCode],
72  [event modifierFlags],
73  &context->deadKeyState);
74  if (!unicodeBytes)
75  return;
76 
77  NSString *unicodeString = [NSString stringWithUTF8String:unicodeBytes];
78 
79  unsigned long long flags = [event modifierFlags];
80  VuoModifierKey modifiers = VuoModifierKey_None;
81 
82  if(flags & NSEventModifierFlagCommand) modifiers |= VuoModifierKey_Command;
83  if(flags & NSEventModifierFlagOption) modifiers |= VuoModifierKey_Option;
84  if(flags & NSEventModifierFlagControl) modifiers |= VuoModifierKey_Control;
85  if(flags & NSEventModifierFlagShift) modifiers |= VuoModifierKey_Shift;
86 
87  for (NSUInteger i = 0; i < [unicodeString length]; ++i)
88  {
89  // Typed a character (e.g. Option-E-E for "é" or Option-E-Space for "ˆ", not just Option-E or Option).
90  NSString *characterAsString = [unicodeString substringWithRange:NSMakeRange(i, 1)];
91  VuoText character = VuoText_make([characterAsString UTF8String]);
92  if(typedCharacter) typedCharacter(character, modifiers);
93 
94  unichar characterAsUnichar = [characterAsString characterAtIndex:0];
95  if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:characterAsUnichar])
96  {
97  // Typed a whitespace character...
98  if ([context->wordInProgress length] > 0)
99  {
100  // ... that completes a word.
101  VuoText word = VuoText_make([context->wordInProgress UTF8String]);
102  if(typedWord) typedWord(word);
103  }
104 
105  [context->wordInProgress deleteCharactersInRange:NSMakeRange(0, [context->wordInProgress length])];
106  }
107  else if (character[0] == '\b' && character[1] == 0) // Backspace
108  {
109  NSInteger wordLength = [context->wordInProgress length];
110  if (wordLength > 0)
111  [context->wordInProgress deleteCharactersInRange:NSMakeRange(wordLength - 1, 1)];
112  }
113  else
114  {
115  [context->wordInProgress appendString:characterAsString];
116  }
117 
118  if ([[NSCharacterSet newlineCharacterSet] characterIsMember:characterAsUnichar])
119  {
120  // Typed a newline character that completes a line.
121  VuoText line = VuoText_make([context->lineInProgress UTF8String]);
122  if(typedLine) typedLine(line);
123 
124  [context->lineInProgress deleteCharactersInRange:NSMakeRange(0, [context->lineInProgress length])];
125  }
126  else if (character[0] == '\b' && character[1] == 0) // Backspace
127  {
128  NSInteger lineLength = [context->lineInProgress length];
129  if (lineLength > 0)
130  [context->lineInProgress deleteCharactersInRange:NSMakeRange(lineLength - 1, 1)];
131  }
132  else
133  {
134  [context->lineInProgress appendString:characterAsString];
135  }
136  }
137  }
138 }
139 
143 static void VuoKeyboard_fireButtonsIfNeeded(NSEvent *event,
144  VuoOutputTrigger(pressed, void),
145  VuoOutputTrigger(released, void),
146  VuoWindowReference windowRef,
147  VuoKey key,
148  VuoModifierKey modifierKey,
149  bool shouldFireForRepeat)
150 {
151  NSWindow *targetWindow = (NSWindow *)windowRef;
152  NSEventType type = [event type];
153 
154  bool isARepeat = false;
155  if (type == NSEventTypeKeyDown || type == NSEventTypeKeyUp)
156  isARepeat = [event isARepeat];
157 
158  if ((! targetWindow || targetWindow == [event window] || [[targetWindow contentView] isInFullScreenMode]) &&
159  (VuoKey_doesMacVirtualKeyCodeMatch([event keyCode], key)) &&
160  (shouldFireForRepeat || !isARepeat))
161  {
162  CGEventFlags flags = CGEventGetFlags([event CGEvent]);
163  bool isKeyInFlags = (type == NSEventTypeFlagsChanged &&
164  (((key == VuoKey_Command) && (flags & kCGEventFlagMaskCommand)) ||
165  ((key == VuoKey_CapsLock) && (flags & kCGEventFlagMaskAlphaShift)) ||
166  ((key == VuoKey_Shift || key == VuoKey_RightShift) && (flags & kCGEventFlagMaskShift)) ||
167  ((key == VuoKey_Control || key == VuoKey_RightControl) && (flags & kCGEventFlagMaskControl)) ||
168  ((key == VuoKey_Option || key == VuoKey_RightOption) && (flags & kCGEventFlagMaskAlternate)) ||
169  ((key == VuoKey_Function) && (flags & kCGEventFlagMaskSecondaryFn))));
170 
171  if (VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey) || isKeyInFlags)
172  {
173  if (type == NSEventTypeKeyDown || isKeyInFlags)
174  pressed();
175  else
176  released();
177  }
178  }
179 }
180 
181 
187  void (^typedLine)(VuoText),
188  void (^typedWord)(VuoText),
189  void (^typedCharacter)(VuoText, VuoModifierKey),
190  VuoWindowReference window)
191 {
192  struct VuoKeyboardContext *context = (struct VuoKeyboardContext *)keyboardListener;
193  context->wordInProgress = [NSMutableString new];
194  context->lineInProgress = [NSMutableString new];
195 
196  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent *event) {
197  VuoKeyboard_fireTypingIfNeeded(event, context, typedLine, typedWord, typedCharacter, window);
198  return event;
199  }];
200 
201  context->monitor = monitor;
202 }
203 
208  VuoOutputTrigger(typedLine, VuoText),
209  VuoOutputTrigger(typedWord, VuoText),
210  VuoOutputTrigger(typedCharacter, VuoText),
211  VuoWindowReference window)
212 {
214  ^(VuoText line) { typedLine(line); },
215  ^(VuoText word) { typedWord(word); },
216  ^(VuoText character, VuoModifierKey modifiers) { typedCharacter(character); },
217  window);
218 }
219 
224  VuoOutputTrigger(pressed, void),
225  VuoOutputTrigger(released, void),
226  VuoWindowReference window,
227  VuoKey key,
228  VuoModifierKey modifierKey,
229  bool shouldFireForRepeat)
230 {
231  struct VuoKeyboardContext *context = (struct VuoKeyboardContext *)keyboardListener;
232 
233  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown|NSEventMaskKeyUp|NSEventMaskFlagsChanged handler:^(NSEvent *event) {
234  VuoKeyboard_fireButtonsIfNeeded(event, pressed, released, window, key, modifierKey, shouldFireForRepeat);
235  return event;
236  }];
237 
238  context->monitor = monitor;
239 }
240 
241 
245 void VuoKeyboard_stopListening(VuoKeyboard *keyboardListener)
246 {
247  struct VuoKeyboardContext *context = (struct VuoKeyboardContext *)keyboardListener;
248 
249  VUOLOG_PROFILE_BEGIN(mainQueue);
250  dispatch_sync(dispatch_get_main_queue(), ^{ // wait for any in-progress monitor handlers to complete
251  VUOLOG_PROFILE_END(mainQueue);
252  if (context->monitor)
253  {
254  [NSEvent removeMonitor:context->monitor];
255  context->monitor = nil;
256  }
257  });
258 
259  [context->wordInProgress release];
260  [context->lineInProgress release];
261 }