diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h index 36c45e73d6a..2ccaec9552a 100644 --- a/dlls/winemac.drv/cocoa_app.h +++ b/dlls/winemac.drv/cocoa_app.h @@ -42,9 +42,11 @@ @interface WineApplication : NSApplication unsigned long windowFocusSerial; CGEventSourceKeyboardType keyboardType; + NSEvent* lastFlagsChanged; } @property (nonatomic) CGEventSourceKeyboardType keyboardType; +@property (readonly, copy, nonatomic) NSEvent* lastFlagsChanged; - (void) transformProcessToForeground; @@ -56,6 +58,8 @@ - (double) ticksForEventTime:(NSTimeInterval)eventTime; - (void) windowGotFocus:(WineWindow*)window; + - (void) keyboardSelectionDidChange; + @end void OnMainThread(dispatch_block_t block); diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m index a3a8cb58f40..cd09f95f5f9 100644 --- a/dlls/winemac.drv/cocoa_app.m +++ b/dlls/winemac.drv/cocoa_app.m @@ -28,9 +28,16 @@ int macdrv_err_on; +@interface WineApplication () + +@property (readwrite, copy, nonatomic) NSEvent* lastFlagsChanged; + +@end + + @implementation WineApplication - @synthesize keyboardType; + @synthesize keyboardType, lastFlagsChanged; - (id) init { @@ -219,6 +226,18 @@ - (void) keyboardSelectionDidChange } + /* + * ---------- NSApplication method overrides ---------- + */ + - (void) sendEvent:(NSEvent*)anEvent + { + if ([anEvent type] == NSFlagsChanged) + self.lastFlagsChanged = anEvent; + + [super sendEvent:anEvent]; + } + + /* * ---------- NSApplicationDelegate methods ---------- */ diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h index 0aa31399609..a4e4db3c17a 100644 --- a/dlls/winemac.drv/cocoa_window.h +++ b/dlls/winemac.drv/cocoa_window.h @@ -46,6 +46,8 @@ @interface WineWindow : NSPanel BOOL usePerPixelAlpha; + NSUInteger lastModifierFlags; + BOOL causing_becomeKeyWindow; BOOL ignore_windowMiniaturize; BOOL ignore_windowDeminiaturize; diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m index 5d912212c40..38bc1766741 100644 --- a/dlls/winemac.drv/cocoa_window.m +++ b/dlls/winemac.drv/cocoa_window.m @@ -18,6 +18,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#import + #import "cocoa_window.h" #include "macdrv_cocoa.h" @@ -25,6 +27,12 @@ #import "cocoa_event.h" +/* Additional Mac virtual keycode, to complement those in Carbon's . */ +enum { + kVK_RightCommand = 0x36, /* Invented for Wine; was unused */ +}; + + static NSUInteger style_mask_for_features(const struct macdrv_window_features* wf) { NSUInteger style_mask; @@ -55,6 +63,49 @@ static BOOL frame_intersects_screens(NSRect frame, NSArray* screens) } +/* We rely on the supposedly device-dependent modifier flags to distinguish the + keys on the left side of the keyboard from those on the right. Some event + sources don't set those device-depdendent flags. If we see a device-independent + flag for a modifier without either corresponding device-dependent flag, assume + the left one. */ +static inline void fix_device_modifiers_by_generic(NSUInteger* modifiers) +{ + if ((*modifiers & (NX_COMMANDMASK | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) == NX_COMMANDMASK) + *modifiers |= NX_DEVICELCMDKEYMASK; + if ((*modifiers & (NX_SHIFTMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) == NX_SHIFTMASK) + *modifiers |= NX_DEVICELSHIFTKEYMASK; + if ((*modifiers & (NX_CONTROLMASK | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) == NX_CONTROLMASK) + *modifiers |= NX_DEVICELCTLKEYMASK; + if ((*modifiers & (NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) == NX_ALTERNATEMASK) + *modifiers |= NX_DEVICELALTKEYMASK; +} + +/* As we manipulate individual bits of a modifier mask, we can end up with + inconsistent sets of flags. In particular, we might set or clear one of the + left/right-specific bits, but not the corresponding non-side-specific bit. + Fix that. If either side-specific bit is set, set the non-side-specific bit, + otherwise clear it. */ +static inline void fix_generic_modifiers_by_device(NSUInteger* modifiers) +{ + if (*modifiers & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) + *modifiers |= NX_COMMANDMASK; + else + *modifiers &= ~NX_COMMANDMASK; + if (*modifiers & (NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) + *modifiers |= NX_SHIFTMASK; + else + *modifiers &= ~NX_SHIFTMASK; + if (*modifiers & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) + *modifiers |= NX_CONTROLMASK; + else + *modifiers &= ~NX_CONTROLMASK; + if (*modifiers & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) + *modifiers |= NX_ALTERNATEMASK; + else + *modifiers &= ~NX_ALTERNATEMASK; +} + + @interface WineContentView : NSView @end @@ -493,6 +544,44 @@ - (void) makeFocused [self windowDidResize:nil]; } + - (void) postKey:(uint16_t)keyCode + pressed:(BOOL)pressed + modifiers:(NSUInteger)modifiers + event:(NSEvent*)theEvent + { + macdrv_event event; + CGEventRef cgevent; + WineApplication* app = (WineApplication*)NSApp; + + event.type = pressed ? KEY_PRESS : KEY_RELEASE; + event.window = (macdrv_window)[self retain]; + event.key.keycode = keyCode; + event.key.modifiers = modifiers; + event.key.time_ms = [app ticksForEventTime:[theEvent timestamp]]; + + if ((cgevent = [theEvent CGEvent])) + { + CGEventSourceKeyboardType keyboardType = CGEventGetIntegerValueField(cgevent, + kCGKeyboardEventKeyboardType); + if (keyboardType != app.keyboardType) + { + app.keyboardType = keyboardType; + [app keyboardSelectionDidChange]; + } + } + + [queue postEvent:&event]; + } + + - (void) postKeyEvent:(NSEvent *)theEvent + { + [self flagsChanged:theEvent]; + [self postKey:[theEvent keyCode] + pressed:[theEvent type] == NSKeyDown + modifiers:[theEvent modifierFlags] + event:theEvent]; + } + /* * ---------- NSWindow method overrides ---------- @@ -556,12 +645,81 @@ - (void) mouseUp:(NSEvent *)theEvent { [self postMouseButtonEvent:theEvent press - (void) rightMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; } - (void) otherMouseUp:(NSEvent *)theEvent { [self mouseUp:theEvent]; } + - (void) keyDown:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; } + - (void) keyUp:(NSEvent *)theEvent { [self postKeyEvent:theEvent]; } + + - (void) flagsChanged:(NSEvent *)theEvent + { + static const struct { + NSUInteger mask; + uint16_t keycode; + } modifiers[] = { + { NX_ALPHASHIFTMASK, kVK_CapsLock }, + { NX_DEVICELSHIFTKEYMASK, kVK_Shift }, + { NX_DEVICERSHIFTKEYMASK, kVK_RightShift }, + { NX_DEVICELCTLKEYMASK, kVK_Control }, + { NX_DEVICERCTLKEYMASK, kVK_RightControl }, + { NX_DEVICELALTKEYMASK, kVK_Option }, + { NX_DEVICERALTKEYMASK, kVK_RightOption }, + { NX_DEVICELCMDKEYMASK, kVK_Command }, + { NX_DEVICERCMDKEYMASK, kVK_RightCommand }, + }; + + NSUInteger modifierFlags = [theEvent modifierFlags]; + NSUInteger changed; + int i, last_changed; + + fix_device_modifiers_by_generic(&modifierFlags); + changed = modifierFlags ^ lastModifierFlags; + + last_changed = -1; + for (i = 0; i < sizeof(modifiers)/sizeof(modifiers[0]); i++) + if (changed & modifiers[i].mask) + last_changed = i; + + for (i = 0; i <= last_changed; i++) + { + if (changed & modifiers[i].mask) + { + BOOL pressed = (modifierFlags & modifiers[i].mask) != 0; + + if (i == last_changed) + lastModifierFlags = modifierFlags; + else + { + lastModifierFlags ^= modifiers[i].mask; + fix_generic_modifiers_by_device(&lastModifierFlags); + } + + // Caps lock generates one event for each press-release action. + // We need to simulate a pair of events for each actual event. + if (modifiers[i].mask == NX_ALPHASHIFTMASK) + { + [self postKey:modifiers[i].keycode + pressed:TRUE + modifiers:lastModifierFlags + event:(NSEvent*)theEvent]; + pressed = FALSE; + } + + [self postKey:modifiers[i].keycode + pressed:pressed + modifiers:lastModifierFlags + event:(NSEvent*)theEvent]; + } + } + } + /* * ---------- NSWindowDelegate methods ---------- */ - (void)windowDidBecomeKey:(NSNotification *)notification { + NSEvent* event = [NSApp lastFlagsChanged]; + if (event) + [self flagsChanged:event]; + if (causing_becomeKeyWindow) return; [NSApp windowGotFocus:self]; diff --git a/dlls/winemac.drv/event.c b/dlls/winemac.drv/event.c index fb00260304f..5d8d260b791 100644 --- a/dlls/winemac.drv/event.c +++ b/dlls/winemac.drv/event.c @@ -33,6 +33,8 @@ static const char *dbgstr_event(int type) { static const char * const event_names[] = { "APP_DEACTIVATED", + "KEY_PRESS", + "KEY_RELEASE", "KEYBOARD_CHANGED", "MOUSE_BUTTON", "WINDOW_CLOSE_REQUESTED", @@ -58,7 +60,11 @@ static macdrv_event_mask get_event_mask(DWORD mask) if ((mask & QS_ALLINPUT) == QS_ALLINPUT) return -1; if (mask & QS_KEY) + { + event_mask |= event_mask_for_type(KEY_PRESS); + event_mask |= event_mask_for_type(KEY_RELEASE); event_mask |= event_mask_for_type(KEYBOARD_CHANGED); + } if (mask & QS_MOUSEBUTTON) event_mask |= event_mask_for_type(MOUSE_BUTTON); @@ -98,6 +104,10 @@ void macdrv_handle_event(macdrv_event *event) case APP_DEACTIVATED: macdrv_app_deactivated(); break; + case KEY_PRESS: + case KEY_RELEASE: + macdrv_key_event(hwnd, event); + break; case KEYBOARD_CHANGED: macdrv_keyboard_changed(event); break; diff --git a/dlls/winemac.drv/keyboard.c b/dlls/winemac.drv/keyboard.c index 3591521df8b..46a03bcec4f 100644 --- a/dlls/winemac.drv/keyboard.c +++ b/dlls/winemac.drv/keyboard.c @@ -31,6 +31,7 @@ #include "wine/unicode.h" WINE_DEFAULT_DEBUG_CHANNEL(keyboard); +WINE_DECLARE_DEBUG_CHANNEL(key); /* Carbon-style modifier mask definitions from . */ @@ -663,6 +664,62 @@ void macdrv_compute_keyboard_layout(struct macdrv_thread_data *thread_data) } +/*********************************************************************** + * macdrv_send_keyboard_input + */ +static void macdrv_send_keyboard_input(HWND hwnd, WORD vkey, WORD scan, DWORD flags, DWORD time) +{ + INPUT input; + + TRACE_(key)("hwnd %p vkey=%04x scan=%04x flags=%04x\n", hwnd, vkey, scan, flags); + + input.type = INPUT_KEYBOARD; + input.ki.wVk = vkey; + input.ki.wScan = scan; + input.ki.dwFlags = flags; + input.ki.time = time; + input.ki.dwExtraInfo = 0; + + __wine_send_input(hwnd, &input); +} + + +/*********************************************************************** + * macdrv_key_event + * + * Handler for KEY_PRESS and KEY_RELEASE events. + */ +void macdrv_key_event(HWND hwnd, const macdrv_event *event) +{ + struct macdrv_thread_data *thread_data = macdrv_thread_data(); + WORD vkey, scan; + DWORD flags; + + TRACE_(key)("win %p/%p key %s keycode %hu modifiers 0x%08llx\n", + hwnd, event->window, (event->type == KEY_PRESS ? "press" : "release"), + event->key.keycode, event->key.modifiers); + + if (event->key.keycode < sizeof(thread_data->keyc2vkey)/sizeof(thread_data->keyc2vkey[0])) + { + vkey = thread_data->keyc2vkey[event->key.keycode]; + scan = thread_data->keyc2scan[event->key.keycode]; + } + else + vkey = scan = 0; + + TRACE_(key)("keycode %hu converted to vkey 0x%X scan 0x%02x\n", + event->key.keycode, vkey, scan); + + if (!vkey) return; + + flags = 0; + if (event->type == KEY_RELEASE) flags |= KEYEVENTF_KEYUP; + if (scan & 0x100) flags |= KEYEVENTF_EXTENDEDKEY; + + macdrv_send_keyboard_input(hwnd, vkey, scan & 0xff, flags, event->key.time_ms); +} + + /*********************************************************************** * macdrv_keyboard_changed * diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index d30c7ae1d4e..d6e6fd4034e 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -135,5 +135,6 @@ static inline RECT rect_from_cgrect(CGRect cgrect) extern void macdrv_compute_keyboard_layout(struct macdrv_thread_data *thread_data) DECLSPEC_HIDDEN; extern void macdrv_keyboard_changed(const macdrv_event *event) DECLSPEC_HIDDEN; +extern void macdrv_key_event(HWND hwnd, const macdrv_event *event) DECLSPEC_HIDDEN; #endif /* __WINE_MACDRV_H */ diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index d49992f1356..62bc996a415 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -125,6 +125,8 @@ /* event */ enum { APP_DEACTIVATED, + KEY_PRESS, + KEY_RELEASE, KEYBOARD_CHANGED, MOUSE_BUTTON, WINDOW_CLOSE_REQUESTED, @@ -142,6 +144,11 @@ int type; macdrv_window window; union { + struct { + CGKeyCode keycode; + CGEventFlags modifiers; + unsigned long time_ms; + } key; struct { CFDataRef uchr; CGEventSourceKeyboardType keyboard_type;