2 * MACDRV Cocoa event queue code
4 * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #include <sys/types.h>
22 #include <sys/event.h>
24 #include <libkern/OSAtomic.h>
25 #import <Carbon/Carbon.h>
27 #include "macdrv_cocoa.h"
28 #import "cocoa_event.h"
30 #import "cocoa_window.h"
33 static NSString* const WineEventQueueThreadDictionaryKey = @"WineEventQueueThreadDictionaryKey";
35 static NSString* const WineHotKeyMacIDKey = @"macID";
36 static NSString* const WineHotKeyVkeyKey = @"vkey";
37 static NSString* const WineHotKeyModFlagsKey = @"modFlags";
38 static NSString* const WineHotKeyKeyCodeKey = @"keyCode";
39 static NSString* const WineHotKeyCarbonRefKey = @"hotKeyRef";
40 static const OSType WineHotKeySignature = 'Wine';
43 @implementation NSEvent (WineExtensions)
45 static BOOL wine_commandKeyDown(NSUInteger flags)
47 return ((flags & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask)) == NSCommandKeyMask);
50 + (BOOL) wine_commandKeyDown
52 return wine_commandKeyDown([self modifierFlags]);
55 - (BOOL) wine_commandKeyDown
57 return wine_commandKeyDown([self modifierFlags]);
63 @interface MacDrvEvent : NSObject
69 - (id) initWithEvent:(macdrv_event*)event;
73 @implementation MacDrvEvent
75 - (id) initWithEvent:(macdrv_event*)inEvent
80 event = macdrv_retain_event(inEvent);
87 if (event) macdrv_release_event(event);
94 @implementation WineEventQueue
98 [self doesNotRecognizeSelector:_cmd];
103 - (id) initWithEventHandler:(macdrv_event_handler)handler
105 NSParameterAssert(handler != nil);
113 fds[0] = fds[1] = kq = -1;
115 event_handler = handler;
116 events = [[NSMutableArray alloc] init];
117 eventsLock = [[NSLock alloc] init];
119 if (!events || !eventsLock)
126 fcntl(fds[0], F_SETFD, 1) == -1 ||
127 fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
128 fcntl(fds[1], F_SETFD, 1) == -1 ||
129 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1)
142 EV_SET(&kev, fds[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
145 rc = kevent(kq, &kev, 1, NULL, 0, NULL);
146 } while (rc == -1 && errno == EINTR);
158 NSNumber* hotKeyMacID;
160 for (hotKeyMacID in hotKeysByMacID)
162 NSDictionary* hotKeyDict = [hotKeysByMacID objectForKey:hotKeyMacID];
163 EventHotKeyRef hotKeyRef = [[hotKeyDict objectForKey:WineHotKeyCarbonRefKey] pointerValue];
164 UnregisterEventHotKey(hotKeyRef);
166 [hotKeysByMacID release];
167 [hotKeysByWinID release];
169 [eventsLock release];
171 if (kq != -1) close(kq);
172 if (fds[0] != -1) close(fds[0]);
173 if (fds[1] != -1) close(fds[1]);
178 - (void) signalEventAvailable
185 rc = write(fds[1], &junk, 1);
186 } while (rc < 0 && errno == EINTR);
188 if (rc < 0 && errno != EAGAIN)
189 ERR(@"%@: got error writing to event queue signaling pipe: %s\n", self, strerror(errno));
192 - (void) postEventObject:(MacDrvEvent*)event
195 MacDrvEvent* lastEvent;
199 indexes = [events indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){
200 return ((MacDrvEvent*)obj)->event->deliver <= 0;
202 [events removeObjectsAtIndexes:indexes];
204 if ((event->event->type == MOUSE_MOVED ||
205 event->event->type == MOUSE_MOVED_ABSOLUTE) &&
206 event->event->deliver == INT_MAX &&
207 (lastEvent = [events lastObject]) &&
208 (lastEvent->event->type == MOUSE_MOVED ||
209 lastEvent->event->type == MOUSE_MOVED_ABSOLUTE) &&
210 lastEvent->event->deliver == INT_MAX &&
211 lastEvent->event->window == event->event->window &&
212 lastEvent->event->mouse_moved.drag == event->event->mouse_moved.drag)
214 if (event->event->type == MOUSE_MOVED)
216 lastEvent->event->mouse_moved.x += event->event->mouse_moved.x;
217 lastEvent->event->mouse_moved.y += event->event->mouse_moved.y;
221 lastEvent->event->type = MOUSE_MOVED_ABSOLUTE;
222 lastEvent->event->mouse_moved.x = event->event->mouse_moved.x;
223 lastEvent->event->mouse_moved.y = event->event->mouse_moved.y;
226 lastEvent->event->mouse_moved.time_ms = event->event->mouse_moved.time_ms;
229 [events addObject:event];
233 [self signalEventAvailable];
236 - (void) postEvent:(macdrv_event*)inEvent
238 MacDrvEvent* event = [[MacDrvEvent alloc] initWithEvent:inEvent];
239 [self postEventObject:event];
243 - (MacDrvEvent*) getEventMatchingMask:(macdrv_event_mask)mask
248 MacDrvEvent* ret = nil;
250 /* Clear the pipe which signals there are pending events. */
253 rc = read(fds[0], buf, sizeof(buf));
254 } while (rc > 0 || (rc < 0 && errno == EINTR));
255 if (rc == 0 || (rc < 0 && errno != EAGAIN))
258 ERR(@"%@: event queue signaling pipe unexpectedly closed\n", self);
260 ERR(@"%@: got error reading from event queue signaling pipe: %s\n", self, strerror(errno));
267 while (index < [events count])
269 MacDrvEvent* event = [events objectAtIndex:index];
270 if (event_mask_for_type(event->event->type) & mask)
272 [[event retain] autorelease];
273 [events removeObjectAtIndex:index];
275 if (event->event->deliver == INT_MAX ||
276 OSAtomicDecrement32Barrier(&event->event->deliver) >= 0)
290 - (void) discardEventsPassingTest:(BOOL (^)(macdrv_event* event))block
292 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
297 indexes = [events indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){
298 MacDrvEvent* event = obj;
299 return block(event->event);
302 [events removeObjectsAtIndexes:indexes];
309 - (void) discardEventsMatchingMask:(macdrv_event_mask)mask forWindow:(NSWindow*)window
311 [self discardEventsPassingTest:^BOOL (macdrv_event* event){
312 return ((event_mask_for_type(event->type) & mask) &&
313 (!window || event->window == (macdrv_window)window));
317 - (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout flags:(NSUInteger)flags
321 NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
324 type = (flags & WineQueryNoPreemptWait) ? QUERY_EVENT_NO_PREEMPT_WAIT : QUERY_EVENT;
325 event = macdrv_create_event(type, (WineWindow*)query->window);
326 event->query_event.query = macdrv_retain_query(query);
329 [self postEvent:event];
330 macdrv_release_event(event);
331 timedout = ![[WineApplicationController sharedController] waitUntilQueryDone:&query->done
333 processEvents:(flags & WineQueryProcessEvents) != 0];
334 return !timedout && query->status;
337 - (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout
339 return [self query:query timeout:timeout flags:0];
342 - (void) resetMouseEventPositions:(CGPoint)pos
346 pos = cgpoint_win_from_mac(pos);
350 for (event in events)
352 if (event->event->type == MOUSE_BUTTON)
354 event->event->mouse_button.x = pos.x;
355 event->event->mouse_button.y = pos.y;
357 else if (event->event->type == MOUSE_SCROLL)
359 event->event->mouse_scroll.x = pos.x;
360 event->event->mouse_scroll.y = pos.y;
367 - (BOOL) postHotKeyEvent:(UInt32)hotKeyNumber time:(double)time
369 NSDictionary* hotKeyDict = [hotKeysByMacID objectForKey:[NSNumber numberWithUnsignedInt:hotKeyNumber]];
374 event = macdrv_create_event(HOTKEY_PRESS, nil);
375 event->hotkey_press.vkey = [[hotKeyDict objectForKey:WineHotKeyVkeyKey] unsignedIntValue];
376 event->hotkey_press.mod_flags = [[hotKeyDict objectForKey:WineHotKeyModFlagsKey] unsignedIntValue];
377 event->hotkey_press.keycode = [[hotKeyDict objectForKey:WineHotKeyKeyCodeKey] unsignedIntValue];
378 event->hotkey_press.time_ms = [[WineApplicationController sharedController] ticksForEventTime:time];
380 [self postEvent:event];
382 macdrv_release_event(event);
385 return hotKeyDict != nil;
388 static OSStatus HotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData)
390 WineEventQueue* self = userData;
392 EventHotKeyID hotKeyID;
394 status = GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL,
395 sizeof(hotKeyID), NULL, &hotKeyID);
398 if (hotKeyID.signature != WineHotKeySignature ||
399 ![self postHotKeyEvent:hotKeyID.id time:GetEventTime(theEvent)])
400 status = eventNotHandledErr;
406 - (void) unregisterHotKey:(unsigned int)vkey modFlags:(unsigned int)modFlags
408 NSNumber* vkeyNumber = [NSNumber numberWithUnsignedInt:vkey];
409 NSNumber* modFlagsNumber = [NSNumber numberWithUnsignedInt:modFlags];
410 NSArray* winIDPair = [NSArray arrayWithObjects:vkeyNumber, modFlagsNumber, nil];
411 NSDictionary* hotKeyDict = [hotKeysByWinID objectForKey:winIDPair];
414 EventHotKeyRef hotKeyRef = [[hotKeyDict objectForKey:WineHotKeyCarbonRefKey] pointerValue];
415 NSNumber* macID = [hotKeyDict objectForKey:WineHotKeyMacIDKey];
417 UnregisterEventHotKey(hotKeyRef);
418 [hotKeysByMacID removeObjectForKey:macID];
419 [hotKeysByWinID removeObjectForKey:winIDPair];
423 - (int) registerHotKey:(UInt32)keyCode modifiers:(UInt32)modifiers vkey:(unsigned int)vkey modFlags:(unsigned int)modFlags
425 static EventHandlerRef handler;
426 static UInt32 hotKeyNumber;
428 NSNumber* vkeyNumber;
429 NSNumber* modFlagsNumber;
431 EventHotKeyID hotKeyID;
432 EventHotKeyRef hotKeyRef;
433 NSNumber* macIDNumber;
434 NSDictionary* hotKeyDict;
438 EventTypeSpec eventType = { kEventClassKeyboard, kEventHotKeyPressed };
439 status = InstallApplicationEventHandler(HotKeyHandler, 1, &eventType, self, &handler);
442 ERR(@"InstallApplicationEventHandler() failed: %d\n", status);
444 return MACDRV_HOTKEY_FAILURE;
448 if (!hotKeysByMacID && !(hotKeysByMacID = [[NSMutableDictionary alloc] init]))
449 return MACDRV_HOTKEY_FAILURE;
450 if (!hotKeysByWinID && !(hotKeysByWinID = [[NSMutableDictionary alloc] init]))
451 return MACDRV_HOTKEY_FAILURE;
453 vkeyNumber = [NSNumber numberWithUnsignedInt:vkey];
454 modFlagsNumber = [NSNumber numberWithUnsignedInt:modFlags];
455 winIDPair = [NSArray arrayWithObjects:vkeyNumber, modFlagsNumber, nil];
456 if ([hotKeysByWinID objectForKey:winIDPair])
457 return MACDRV_HOTKEY_ALREADY_REGISTERED;
459 hotKeyID.signature = WineHotKeySignature;
460 hotKeyID.id = hotKeyNumber++;
462 status = RegisterEventHotKey(keyCode, modifiers, hotKeyID, GetApplicationEventTarget(),
463 kEventHotKeyExclusive, &hotKeyRef);
464 if (status == eventHotKeyExistsErr)
465 return MACDRV_HOTKEY_ALREADY_REGISTERED;
468 ERR(@"RegisterEventHotKey() failed: %d\n", status);
469 return MACDRV_HOTKEY_FAILURE;
472 macIDNumber = [NSNumber numberWithUnsignedInt:hotKeyID.id];
473 hotKeyDict = [NSDictionary dictionaryWithObjectsAndKeys:
474 macIDNumber, WineHotKeyMacIDKey,
475 vkeyNumber, WineHotKeyVkeyKey,
476 modFlagsNumber, WineHotKeyModFlagsKey,
477 [NSNumber numberWithUnsignedInt:keyCode], WineHotKeyKeyCodeKey,
478 [NSValue valueWithPointer:hotKeyRef], WineHotKeyCarbonRefKey,
480 [hotKeysByMacID setObject:hotKeyDict forKey:macIDNumber];
481 [hotKeysByWinID setObject:hotKeyDict forKey:winIDPair];
483 return MACDRV_HOTKEY_SUCCESS;
487 /***********************************************************************
490 * Run a block on the main thread synchronously.
492 void OnMainThread(dispatch_block_t block)
494 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
495 NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
496 WineEventQueue* queue = [threadDict objectForKey:WineEventQueueThreadDictionaryKey];
497 dispatch_semaphore_t semaphore = NULL;
498 __block BOOL finished;
502 semaphore = dispatch_semaphore_create(0);
503 dispatch_retain(semaphore);
511 [queue signalEventAvailable];
514 dispatch_semaphore_signal(semaphore);
515 dispatch_release(semaphore);
523 MacDrvEvent* macDrvEvent;
527 (macDrvEvent = [queue getEventMatchingMask:event_mask_for_type(QUERY_EVENT)]))
529 queue->event_handler(macDrvEvent->event);
535 pool = [[NSAutoreleasePool alloc] init];
537 kevent(queue->kq, NULL, 0, &kev, 1, NULL);
543 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
544 dispatch_release(semaphore);
551 /***********************************************************************
552 * macdrv_create_event_queue
554 * Register this thread with the application on the main thread, and set
555 * up an event queue on which it can deliver events to this thread.
557 macdrv_event_queue macdrv_create_event_queue(macdrv_event_handler handler)
559 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
560 NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
562 WineEventQueue* queue = [threadDict objectForKey:WineEventQueueThreadDictionaryKey];
565 queue = [[[WineEventQueue alloc] initWithEventHandler:handler] autorelease];
568 if ([[WineApplicationController sharedController] registerEventQueue:queue])
569 [threadDict setObject:queue forKey:WineEventQueueThreadDictionaryKey];
576 return (macdrv_event_queue)queue;
579 /***********************************************************************
580 * macdrv_destroy_event_queue
582 * Tell the application that this thread is exiting and destroy the
583 * associated event queue.
585 void macdrv_destroy_event_queue(macdrv_event_queue queue)
587 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
588 WineEventQueue* q = (WineEventQueue*)queue;
589 NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
591 [[WineApplicationController sharedController] unregisterEventQueue:q];
592 [threadDict removeObjectForKey:WineEventQueueThreadDictionaryKey];
597 /***********************************************************************
598 * macdrv_get_event_queue_fd
600 * Get the file descriptor whose readability signals that there are
601 * events on the event queue.
603 int macdrv_get_event_queue_fd(macdrv_event_queue queue)
605 WineEventQueue* q = (WineEventQueue*)queue;
609 /***********************************************************************
610 * macdrv_copy_event_from_queue
612 * Pull an event matching the event mask from the event queue and store
613 * it in the event record pointed to by the event parameter. If a
614 * matching event was found, return non-zero; otherwise, return 0.
616 * The caller is responsible for calling macdrv_release_event on any
617 * event returned by this function.
619 int macdrv_copy_event_from_queue(macdrv_event_queue queue,
620 macdrv_event_mask mask, macdrv_event **event)
622 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
623 WineEventQueue* q = (WineEventQueue*)queue;
625 MacDrvEvent* macDrvEvent = [q getEventMatchingMask:mask];
627 *event = macdrv_retain_event(macDrvEvent->event);
630 return (macDrvEvent != nil);
633 /***********************************************************************
634 * macdrv_create_event
636 macdrv_event* macdrv_create_event(int type, WineWindow* window)
640 event = calloc(1, sizeof(*event));
642 event->deliver = INT_MAX;
644 event->window = (macdrv_window)[window retain];
648 /***********************************************************************
649 * macdrv_retain_event
651 macdrv_event* macdrv_retain_event(macdrv_event *event)
653 OSAtomicIncrement32Barrier(&event->refs);
657 /***********************************************************************
658 * macdrv_release_event
660 * Decrements the reference count of an event. If the count falls to
661 * zero, cleans up any resources, such as allocated memory or retained
662 * objects, held by the event and deallocates it
664 void macdrv_release_event(macdrv_event *event)
666 if (OSAtomicDecrement32Barrier(&event->refs) <= 0)
668 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
673 if (event->im_set_text.text)
674 CFRelease(event->im_set_text.text);
676 case KEYBOARD_CHANGED:
677 CFRelease(event->keyboard_changed.uchr);
678 CFRelease(event->keyboard_changed.input_source);
681 case QUERY_EVENT_NO_PREEMPT_WAIT:
682 macdrv_release_query(event->query_event.query);
684 case WINDOW_GOT_FOCUS:
685 [(NSMutableSet*)event->window_got_focus.tried_windows release];
689 [(WineWindow*)event->window release];
696 /***********************************************************************
697 * macdrv_create_query
699 macdrv_query* macdrv_create_query(void)
703 query = calloc(1, sizeof(*query));
708 /***********************************************************************
709 * macdrv_retain_query
711 macdrv_query* macdrv_retain_query(macdrv_query *query)
713 OSAtomicIncrement32Barrier(&query->refs);
717 /***********************************************************************
718 * macdrv_release_query
720 void macdrv_release_query(macdrv_query *query)
722 if (OSAtomicDecrement32Barrier(&query->refs) <= 0)
726 case QUERY_DRAG_OPERATION:
727 if (query->drag_operation.pasteboard)
728 CFRelease(query->drag_operation.pasteboard);
730 case QUERY_DRAG_DROP:
731 if (query->drag_drop.pasteboard)
732 CFRelease(query->drag_drop.pasteboard);
734 case QUERY_PASTEBOARD_DATA:
735 if (query->pasteboard_data.type)
736 CFRelease(query->pasteboard_data.type);
739 [(WineWindow*)query->window release];
744 /***********************************************************************
745 * macdrv_set_query_done
747 void macdrv_set_query_done(macdrv_query *query)
749 macdrv_retain_query(query);
755 macdrv_release_query(query);
757 event = [NSEvent otherEventWithType:NSApplicationDefined
760 timestamp:[[NSProcessInfo processInfo] systemUptime]
763 subtype:WineApplicationEventWakeQuery
766 [NSApp postEvent:event atStart:TRUE];
773 /***********************************************************************
774 * macdrv_register_hot_key
776 int macdrv_register_hot_key(macdrv_event_queue q, unsigned int vkey, unsigned int mod_flags,
777 unsigned int keycode, unsigned int modifiers)
779 WineEventQueue* queue = (WineEventQueue*)q;
783 ret = [queue registerHotKey:keycode modifiers:modifiers vkey:vkey modFlags:mod_flags];
790 /***********************************************************************
791 * macdrv_unregister_hot_key
793 void macdrv_unregister_hot_key(macdrv_event_queue q, unsigned int vkey, unsigned int mod_flags)
795 WineEventQueue* queue = (WineEventQueue*)q;
798 [queue unregisterHotKey:vkey modFlags:mod_flags];