wine/dlls/winemac.drv/cocoa_event.m
Ken Thomases 4f9de6bcdf winemac: More thoroughly discard events which have been obsoleted by subsequent Wine- or program-driven changes.
Among other things, this fixes Syberia 2.  That game shows, hides, and then
shows its window.  Hiding it caused a WINDOW_LOST_FOCUS event to be queued.
By the time it was processed, the window was the foreground window again.
In response to being told it had lost focus, the game minimized its window.

Hiding the window should have prevented or discarded the WINDOW_LOST_FOCUS
event since the change was driven from Wine and the Win32 foreground/active
window state would already be correct.  In addition, when the program
re-showed its window and made it foreground, that should have discarded the
event as being out of date.  Now they do.
2013-12-31 12:31:39 +01:00

769 lines
24 KiB
Objective-C

/*
* 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 <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <libkern/OSAtomic.h>
#import <Carbon/Carbon.h>
#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';
@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) discardEventsMatchingMask:(macdrv_event_mask)mask forWindow:(NSWindow*)window
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSIndexSet* indexes;
[eventsLock lock];
indexes = [events indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop){
MacDrvEvent* event = obj;
return ((event_mask_for_type(event->event->type) & mask) &&
(!window || event->event->window == (macdrv_window)window));
}];
[events removeObjectsAtIndexes:indexes];
[eventsLock unlock];
[pool release];
}
- (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout processEvents:(BOOL)processEvents
{
macdrv_event* event;
NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
BOOL timedout;
event = macdrv_create_event(QUERY_EVENT, (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:processEvents];
return !timedout && query->status;
}
- (BOOL) query:(macdrv_query*)query timeout:(NSTimeInterval)timeout
{
return [self query:query timeout:timeout processEvents:FALSE];
}
- (void) resetMouseEventPositions:(CGPoint)pos
{
MacDrvEvent* event;
[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;
__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:
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];
});
}