wine/dlls/winemac.drv/cocoa_event.m
Ken Thomases 1c94bf396f winemac: Add support for a high-resolution ("Retina") rendering mode.
When this Retina mode is enabled and the primary display is in the user's
default configuration, Wine gets told that screen and window sizes and mouse
coordinates are twice what Cocoa reports them as in its virtual coordinate
system ("points").  The Windows apps then renders at that high resolution and
the Mac driver blits it to screen.  If the screen is actually a Retina display
in a high-DPI mode, then this extra detail will be preserved.  Otherwise, the
rendering will be downsampled and blurry.

This is intended to be combined with increasing the Windows DPI, as via winecfg.
If that is doubled to 192, then, in theory, graphical elements will remain the
same visual size on screen but be rendered with finer detail.  Unfortunately,
many Windows programs don't correctly handle non-standard DPI so the results
are not always perfect.

The registry setting to enable Retina mode is:

[HKEY_CURRENT_USER\Software\Wine\Mac Driver]
"RetinaMode"="y"

Note that this setting is not looked for in the AppDefaults\<exe name> key
because it doesn't make sense for only some processes in a Wine session to see
the high-resolution sizes and coordinates.

Signed-off-by: Ken Thomases <ken@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2016-05-06 11:45:24 +09:00

781 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) 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];
});
}