/* * MACDRV Cocoa event queue code * * Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #import #include "macdrv_cocoa.h" #import "cocoa_event.h" #import "cocoa_app.h" #import "cocoa_window.h" static NSString* const WineEventQueueThreadDictionaryKey = @"WineEventQueueThreadDictionaryKey"; static NSString* const WineHotKeyMacIDKey = @"macID"; static NSString* const WineHotKeyVkeyKey = @"vkey"; static NSString* const WineHotKeyModFlagsKey = @"modFlags"; static NSString* const WineHotKeyKeyCodeKey = @"keyCode"; static NSString* const WineHotKeyCarbonRefKey = @"hotKeyRef"; static const OSType WineHotKeySignature = 'Wine'; @implementation NSEvent (WineExtensions) static BOOL wine_commandKeyDown(NSUInteger flags) { return ((flags & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask)) == NSCommandKeyMask); } + (BOOL) wine_commandKeyDown { return wine_commandKeyDown([self modifierFlags]); } - (BOOL) wine_commandKeyDown { return wine_commandKeyDown([self modifierFlags]); } @end @interface MacDrvEvent : NSObject { @public macdrv_event* event; } - (id) initWithEvent:(macdrv_event*)event; @end @implementation MacDrvEvent - (id) initWithEvent:(macdrv_event*)inEvent { self = [super init]; if (self) { event = macdrv_retain_event(inEvent); } return self; } - (void) dealloc { if (event) macdrv_release_event(event); [super dealloc]; } @end @implementation WineEventQueue - (id) init { [self doesNotRecognizeSelector:_cmd]; [self release]; return nil; } - (id) initWithEventHandler:(macdrv_event_handler)handler { NSParameterAssert(handler != nil); self = [super init]; if (self != nil) { struct kevent kev; int rc; fds[0] = fds[1] = kq = -1; event_handler = handler; events = [[NSMutableArray alloc] init]; eventsLock = [[NSLock alloc] init]; if (!events || !eventsLock) { [self release]; return nil; } if (pipe(fds) || fcntl(fds[0], F_SETFD, 1) == -1 || fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 || fcntl(fds[1], F_SETFD, 1) == -1 || fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1) { [self release]; return nil; } kq = kqueue(); if (kq < 0) { [self release]; return nil; } EV_SET(&kev, fds[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0); do { rc = kevent(kq, &kev, 1, NULL, 0, NULL); } while (rc == -1 && errno == EINTR); if (rc == -1) { [self release]; return nil; } } return self; } - (void) dealloc { NSNumber* hotKeyMacID; for (hotKeyMacID in hotKeysByMacID) { NSDictionary* hotKeyDict = [hotKeysByMacID objectForKey:hotKeyMacID]; EventHotKeyRef hotKeyRef = [[hotKeyDict objectForKey:WineHotKeyCarbonRefKey] pointerValue]; UnregisterEventHotKey(hotKeyRef); } [hotKeysByMacID release]; [hotKeysByWinID release]; [events release]; [eventsLock release]; if (kq != -1) close(kq); if (fds[0] != -1) close(fds[0]); if (fds[1] != -1) close(fds[1]); [super dealloc]; } - (void) signalEventAvailable { char junk = 1; int rc; do { rc = write(fds[1], &junk, 1); } while (rc < 0 && errno == EINTR); if (rc < 0 && errno != EAGAIN) ERR(@"%@: got error writing to event queue signaling pipe: %s\n", self, strerror(errno)); } - (void) postEventObject:(MacDrvEvent*)event { NSIndexSet* indexes; MacDrvEvent* lastEvent; [eventsLock lock]; indexes = [events indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){ return ((MacDrvEvent*)obj)->event->deliver <= 0; }]; [events removeObjectsAtIndexes:indexes]; if ((event->event->type == MOUSE_MOVED || event->event->type == MOUSE_MOVED_ABSOLUTE) && event->event->deliver == INT_MAX && (lastEvent = [events lastObject]) && (lastEvent->event->type == MOUSE_MOVED || lastEvent->event->type == MOUSE_MOVED_ABSOLUTE) && lastEvent->event->deliver == INT_MAX && lastEvent->event->window == event->event->window && lastEvent->event->mouse_moved.drag == event->event->mouse_moved.drag) { if (event->event->type == MOUSE_MOVED) { lastEvent->event->mouse_moved.x += event->event->mouse_moved.x; lastEvent->event->mouse_moved.y += event->event->mouse_moved.y; } else { lastEvent->event->type = MOUSE_MOVED_ABSOLUTE; lastEvent->event->mouse_moved.x = event->event->mouse_moved.x; lastEvent->event->mouse_moved.y = event->event->mouse_moved.y; } lastEvent->event->mouse_moved.time_ms = event->event->mouse_moved.time_ms; } else [events addObject:event]; [eventsLock unlock]; [self signalEventAvailable]; } - (void) postEvent:(macdrv_event*)inEvent { MacDrvEvent* event = [[MacDrvEvent alloc] initWithEvent:inEvent]; [self postEventObject:event]; [event release]; } - (MacDrvEvent*) getEventMatchingMask:(macdrv_event_mask)mask { char buf[512]; int rc; NSUInteger index; MacDrvEvent* ret = nil; /* Clear the pipe which signals there are pending events. */ do { rc = read(fds[0], buf, sizeof(buf)); } while (rc > 0 || (rc < 0 && errno == EINTR)); if (rc == 0 || (rc < 0 && errno != EAGAIN)) { if (rc == 0) ERR(@"%@: event queue signaling pipe unexpectedly closed\n", self); else ERR(@"%@: got error reading from event queue signaling pipe: %s\n", self, strerror(errno)); return nil; } [eventsLock lock]; index = 0; while (index < [events count]) { MacDrvEvent* event = [events objectAtIndex:index]; if (event_mask_for_type(event->event->type) & mask) { [[event retain] autorelease]; [events removeObjectAtIndex:index]; if (event->event->deliver == INT_MAX || OSAtomicDecrement32Barrier(&event->event->deliver) >= 0) { ret = event; break; } } else index++; } [eventsLock unlock]; return ret; } - (void) discardEventsPassingTest:(BOOL (^)(macdrv_event* event))block { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSIndexSet* indexes; [eventsLock lock]; indexes = [events indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){ MacDrvEvent* event = obj; return block(event->event); }]; [events removeObjectsAtIndexes:indexes]; [eventsLock unlock]; [pool release]; } - (void) discardEventsMatchingMask:(macdrv_event_mask)mask forWindow:(NSWindow*)window { [self discardEventsPassingTest:^BOOL (macdrv_event* event){ return ((event_mask_for_type(event->type) & mask) && (!window || event->window == (macdrv_window)window)); }]; } - (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout flags:(NSUInteger)flags { int type; macdrv_event* event; NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; BOOL timedout; type = (flags & WineQueryNoPreemptWait) ? QUERY_EVENT_NO_PREEMPT_WAIT : QUERY_EVENT; event = macdrv_create_event(type, (WineWindow*)query->window); event->query_event.query = macdrv_retain_query(query); query->done = FALSE; [self postEvent:event]; macdrv_release_event(event); timedout = ![[WineApplicationController sharedController] waitUntilQueryDone:&query->done timeout:timeoutDate processEvents:(flags & WineQueryProcessEvents) != 0]; return !timedout && query->status; } - (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout { return [self query:query timeout:timeout flags:0]; } - (void) resetMouseEventPositions:(CGPoint)pos { MacDrvEvent* event; pos = cgpoint_win_from_mac(pos); [eventsLock lock]; for (event in events) { if (event->event->type == MOUSE_BUTTON) { event->event->mouse_button.x = pos.x; event->event->mouse_button.y = pos.y; } else if (event->event->type == MOUSE_SCROLL) { event->event->mouse_scroll.x = pos.x; event->event->mouse_scroll.y = pos.y; } } [eventsLock unlock]; } - (BOOL) postHotKeyEvent:(UInt32)hotKeyNumber time:(double)time { NSDictionary* hotKeyDict = [hotKeysByMacID objectForKey:[NSNumber numberWithUnsignedInt:hotKeyNumber]]; if (hotKeyDict) { macdrv_event* event; event = macdrv_create_event(HOTKEY_PRESS, nil); event->hotkey_press.vkey = [[hotKeyDict objectForKey:WineHotKeyVkeyKey] unsignedIntValue]; event->hotkey_press.mod_flags = [[hotKeyDict objectForKey:WineHotKeyModFlagsKey] unsignedIntValue]; event->hotkey_press.keycode = [[hotKeyDict objectForKey:WineHotKeyKeyCodeKey] unsignedIntValue]; event->hotkey_press.time_ms = [[WineApplicationController sharedController] ticksForEventTime:time]; [self postEvent:event]; macdrv_release_event(event); } return hotKeyDict != nil; } static OSStatus HotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) { WineEventQueue* self = userData; OSStatus status; EventHotKeyID hotKeyID; status = GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); if (status == noErr) { if (hotKeyID.signature != WineHotKeySignature || ![self postHotKeyEvent:hotKeyID.id time:GetEventTime(theEvent)]) status = eventNotHandledErr; } return status; } - (void) unregisterHotKey:(unsigned int)vkey modFlags:(unsigned int)modFlags { NSNumber* vkeyNumber = [NSNumber numberWithUnsignedInt:vkey]; NSNumber* modFlagsNumber = [NSNumber numberWithUnsignedInt:modFlags]; NSArray* winIDPair = [NSArray arrayWithObjects:vkeyNumber, modFlagsNumber, nil]; NSDictionary* hotKeyDict = [hotKeysByWinID objectForKey:winIDPair]; if (hotKeyDict) { EventHotKeyRef hotKeyRef = [[hotKeyDict objectForKey:WineHotKeyCarbonRefKey] pointerValue]; NSNumber* macID = [hotKeyDict objectForKey:WineHotKeyMacIDKey]; UnregisterEventHotKey(hotKeyRef); [hotKeysByMacID removeObjectForKey:macID]; [hotKeysByWinID removeObjectForKey:winIDPair]; } } - (int) registerHotKey:(UInt32)keyCode modifiers:(UInt32)modifiers vkey:(unsigned int)vkey modFlags:(unsigned int)modFlags { static EventHandlerRef handler; static UInt32 hotKeyNumber; OSStatus status; NSNumber* vkeyNumber; NSNumber* modFlagsNumber; NSArray* winIDPair; EventHotKeyID hotKeyID; EventHotKeyRef hotKeyRef; NSNumber* macIDNumber; NSDictionary* hotKeyDict; if (!handler) { EventTypeSpec eventType = { kEventClassKeyboard, kEventHotKeyPressed }; status = InstallApplicationEventHandler(HotKeyHandler, 1, &eventType, self, &handler); if (status != noErr) { ERR(@"InstallApplicationEventHandler() failed: %d\n", status); handler = NULL; return MACDRV_HOTKEY_FAILURE; } } if (!hotKeysByMacID && !(hotKeysByMacID = [[NSMutableDictionary alloc] init])) return MACDRV_HOTKEY_FAILURE; if (!hotKeysByWinID && !(hotKeysByWinID = [[NSMutableDictionary alloc] init])) return MACDRV_HOTKEY_FAILURE; vkeyNumber = [NSNumber numberWithUnsignedInt:vkey]; modFlagsNumber = [NSNumber numberWithUnsignedInt:modFlags]; winIDPair = [NSArray arrayWithObjects:vkeyNumber, modFlagsNumber, nil]; if ([hotKeysByWinID objectForKey:winIDPair]) return MACDRV_HOTKEY_ALREADY_REGISTERED; hotKeyID.signature = WineHotKeySignature; hotKeyID.id = hotKeyNumber++; status = RegisterEventHotKey(keyCode, modifiers, hotKeyID, GetApplicationEventTarget(), kEventHotKeyExclusive, &hotKeyRef); if (status == eventHotKeyExistsErr) return MACDRV_HOTKEY_ALREADY_REGISTERED; if (status != noErr) { ERR(@"RegisterEventHotKey() failed: %d\n", status); return MACDRV_HOTKEY_FAILURE; } macIDNumber = [NSNumber numberWithUnsignedInt:hotKeyID.id]; hotKeyDict = [NSDictionary dictionaryWithObjectsAndKeys: macIDNumber, WineHotKeyMacIDKey, vkeyNumber, WineHotKeyVkeyKey, modFlagsNumber, WineHotKeyModFlagsKey, [NSNumber numberWithUnsignedInt:keyCode], WineHotKeyKeyCodeKey, [NSValue valueWithPointer:hotKeyRef], WineHotKeyCarbonRefKey, nil]; [hotKeysByMacID setObject:hotKeyDict forKey:macIDNumber]; [hotKeysByWinID setObject:hotKeyDict forKey:winIDPair]; return MACDRV_HOTKEY_SUCCESS; } /*********************************************************************** * OnMainThread * * Run a block on the main thread synchronously. */ void OnMainThread(dispatch_block_t block) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary]; WineEventQueue* queue = [threadDict objectForKey:WineEventQueueThreadDictionaryKey]; dispatch_semaphore_t semaphore = NULL; __block BOOL finished; if (!queue) { semaphore = dispatch_semaphore_create(0); dispatch_retain(semaphore); } finished = FALSE; OnMainThreadAsync(^{ block(); finished = TRUE; if (queue) [queue signalEventAvailable]; else { dispatch_semaphore_signal(semaphore); dispatch_release(semaphore); } }); if (queue) { while (!finished) { MacDrvEvent* macDrvEvent; struct kevent kev; while (!finished && (macDrvEvent = [queue getEventMatchingMask:event_mask_for_type(QUERY_EVENT)])) { queue->event_handler(macDrvEvent->event); } if (!finished) { [pool release]; pool = [[NSAutoreleasePool alloc] init]; kevent(queue->kq, NULL, 0, &kev, 1, NULL); } } } else { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_release(semaphore); } [pool release]; } /*********************************************************************** * macdrv_create_event_queue * * Register this thread with the application on the main thread, and set * up an event queue on which it can deliver events to this thread. */ macdrv_event_queue macdrv_create_event_queue(macdrv_event_handler handler) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary]; WineEventQueue* queue = [threadDict objectForKey:WineEventQueueThreadDictionaryKey]; if (!queue) { queue = [[[WineEventQueue alloc] initWithEventHandler:handler] autorelease]; if (queue) { if ([[WineApplicationController sharedController] registerEventQueue:queue]) [threadDict setObject:queue forKey:WineEventQueueThreadDictionaryKey]; else queue = nil; } } [pool release]; return (macdrv_event_queue)queue; } /*********************************************************************** * macdrv_destroy_event_queue * * Tell the application that this thread is exiting and destroy the * associated event queue. */ void macdrv_destroy_event_queue(macdrv_event_queue queue) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineEventQueue* q = (WineEventQueue*)queue; NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary]; [[WineApplicationController sharedController] unregisterEventQueue:q]; [threadDict removeObjectForKey:WineEventQueueThreadDictionaryKey]; [pool release]; } /*********************************************************************** * macdrv_get_event_queue_fd * * Get the file descriptor whose readability signals that there are * events on the event queue. */ int macdrv_get_event_queue_fd(macdrv_event_queue queue) { WineEventQueue* q = (WineEventQueue*)queue; return q->fds[0]; } /*********************************************************************** * macdrv_copy_event_from_queue * * Pull an event matching the event mask from the event queue and store * it in the event record pointed to by the event parameter. If a * matching event was found, return non-zero; otherwise, return 0. * * The caller is responsible for calling macdrv_release_event on any * event returned by this function. */ int macdrv_copy_event_from_queue(macdrv_event_queue queue, macdrv_event_mask mask, macdrv_event **event) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; WineEventQueue* q = (WineEventQueue*)queue; MacDrvEvent* macDrvEvent = [q getEventMatchingMask:mask]; if (macDrvEvent) *event = macdrv_retain_event(macDrvEvent->event); [pool release]; return (macDrvEvent != nil); } /*********************************************************************** * macdrv_create_event */ macdrv_event* macdrv_create_event(int type, WineWindow* window) { macdrv_event *event; event = calloc(1, sizeof(*event)); event->refs = 1; event->deliver = INT_MAX; event->type = type; event->window = (macdrv_window)[window retain]; return event; } /*********************************************************************** * macdrv_retain_event */ macdrv_event* macdrv_retain_event(macdrv_event *event) { OSAtomicIncrement32Barrier(&event->refs); return event; } /*********************************************************************** * macdrv_release_event * * Decrements the reference count of an event. If the count falls to * zero, cleans up any resources, such as allocated memory or retained * objects, held by the event and deallocates it */ void macdrv_release_event(macdrv_event *event) { if (OSAtomicDecrement32Barrier(&event->refs) <= 0) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; switch (event->type) { case IM_SET_TEXT: if (event->im_set_text.text) CFRelease(event->im_set_text.text); break; case KEYBOARD_CHANGED: CFRelease(event->keyboard_changed.uchr); CFRelease(event->keyboard_changed.input_source); break; case QUERY_EVENT: case QUERY_EVENT_NO_PREEMPT_WAIT: macdrv_release_query(event->query_event.query); break; case WINDOW_GOT_FOCUS: [(NSMutableSet*)event->window_got_focus.tried_windows release]; break; } [(WineWindow*)event->window release]; free(event); [pool release]; } } /*********************************************************************** * macdrv_create_query */ macdrv_query* macdrv_create_query(void) { macdrv_query *query; query = calloc(1, sizeof(*query)); query->refs = 1; return query; } /*********************************************************************** * macdrv_retain_query */ macdrv_query* macdrv_retain_query(macdrv_query *query) { OSAtomicIncrement32Barrier(&query->refs); return query; } /*********************************************************************** * macdrv_release_query */ void macdrv_release_query(macdrv_query *query) { if (OSAtomicDecrement32Barrier(&query->refs) <= 0) { switch (query->type) { case QUERY_DRAG_OPERATION: if (query->drag_operation.pasteboard) CFRelease(query->drag_operation.pasteboard); break; case QUERY_DRAG_DROP: if (query->drag_drop.pasteboard) CFRelease(query->drag_drop.pasteboard); break; case QUERY_PASTEBOARD_DATA: if (query->pasteboard_data.type) CFRelease(query->pasteboard_data.type); break; } [(WineWindow*)query->window release]; free(query); } } /*********************************************************************** * macdrv_set_query_done */ void macdrv_set_query_done(macdrv_query *query) { macdrv_retain_query(query); OnMainThreadAsync(^{ NSEvent* event; query->done = TRUE; macdrv_release_query(query); event = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint modifierFlags:0 timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:nil subtype:WineApplicationEventWakeQuery data1:0 data2:0]; [NSApp postEvent:event atStart:TRUE]; }); } @end /*********************************************************************** * macdrv_register_hot_key */ int macdrv_register_hot_key(macdrv_event_queue q, unsigned int vkey, unsigned int mod_flags, unsigned int keycode, unsigned int modifiers) { WineEventQueue* queue = (WineEventQueue*)q; __block int ret; OnMainThread(^{ ret = [queue registerHotKey:keycode modifiers:modifiers vkey:vkey modFlags:mod_flags]; }); return ret; } /*********************************************************************** * macdrv_unregister_hot_key */ void macdrv_unregister_hot_key(macdrv_event_queue q, unsigned int vkey, unsigned int mod_flags) { WineEventQueue* queue = (WineEventQueue*)q; OnMainThreadAsync(^{ [queue unregisterHotKey:vkey modFlags:mod_flags]; }); }