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