blob: a22937f839071fd2f69ce394041357172c103e1a [file] [log] [blame] [edit]
/* Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/x.H>
#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
#include <IOKit/hidsystem/IOHIDLib.h>
#include <IOKit/hidsystem/IOHIDParameter.h>
#define XK_LATIN1
#define XK_MISCELLANY
#define XK_XKB_KEYS
#include <rfb/keysymdef.h>
#include <rfb/XF86keysym.h>
#include "keysym2ucs.h"
#define NoSymbol 0
// This wasn't added until 10.12
#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
const int kVK_RightCommand = 0x36;
#endif
// And this is still missing
const int kVK_Menu = 0x6E;
static bool captured = false;
int cocoa_capture_display(Fl_Window *win, bool all_displays)
{
NSWindow *nsw;
nsw = (NSWindow*)fl_xid(win);
if (!captured) {
if (all_displays) {
if (CGCaptureAllDisplays() != kCGErrorSuccess)
return 1;
} else {
CGDirectDisplayID displays[16];
CGDisplayCount count;
int index;
if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess)
return 1;
if (count != (unsigned)Fl::screen_count())
return 1;
index = Fl::screen_num(win->x(), win->y(), win->w(), win->h());
if (CGDisplayCapture(displays[index]) != kCGErrorSuccess)
return 1;
}
captured = true;
}
if ([nsw level] == CGShieldingWindowLevel())
return 0;
[nsw setLevel:CGShieldingWindowLevel()];
return 0;
}
void cocoa_release_display(Fl_Window *win)
{
NSWindow *nsw;
int newlevel;
if (captured)
CGReleaseAllDisplays();
captured = false;
nsw = (NSWindow*)fl_xid(win);
// Someone else has already changed the level of this window
if ([nsw level] != CGShieldingWindowLevel())
return;
// FIXME: Store the previous level somewhere so we don't have to hard
// code a level here.
if (win->fullscreen_active() && win->contains(Fl::focus()))
newlevel = NSStatusWindowLevel;
else
newlevel = NSNormalWindowLevel;
// Only change if different as the level change also moves the window
// to the top of that level.
if ([nsw level] != newlevel)
[nsw setLevel:newlevel];
}
CGColorSpaceRef cocoa_win_color_space(Fl_Window *win)
{
NSWindow *nsw;
NSColorSpace *nscs;
nsw = (NSWindow*)fl_xid(win);
nscs = [nsw colorSpace];
if (nscs == nil) {
// Offscreen, so return standard SRGB color space
assert(false);
return CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
}
CGColorSpaceRef lut = [nscs CGColorSpace];
// We want a permanent reference, not an autorelease
CGColorSpaceRetain(lut);
return lut;
}
int cocoa_is_keyboard_event(const void *event)
{
NSEvent *nsevent;
nsevent = (NSEvent*)event;
switch ([nsevent type]) {
case NSKeyDown:
case NSKeyUp:
case NSFlagsChanged:
return 1;
default:
return 0;
}
}
int cocoa_is_key_press(const void *event)
{
NSEvent *nsevent;
nsevent = (NSEvent*)event;
if ([nsevent type] == NSKeyDown)
return 1;
if ([nsevent type] == NSFlagsChanged) {
UInt32 mask;
// We don't see any event on release of CapsLock
if ([nsevent keyCode] == kVK_CapsLock)
return 1;
// These are entirely undocumented, but I cannot find any other way
// of differentiating between left and right keys
switch ([nsevent keyCode]) {
case kVK_RightCommand:
mask = 0x0010;
break;
case kVK_Command:
mask = 0x0008;
break;
case kVK_Shift:
mask = 0x0002;
break;
case kVK_CapsLock:
// We don't see any event on release of CapsLock
return 1;
case kVK_Option:
mask = 0x0020;
break;
case kVK_Control:
mask = 0x0001;
break;
case kVK_RightShift:
mask = 0x0004;
break;
case kVK_RightOption:
mask = 0x0040;
break;
case kVK_RightControl:
mask = 0x2000;
break;
default:
return 0;
}
if ([nsevent modifierFlags] & mask)
return 1;
else
return 0;
}
return 0;
}
int cocoa_event_keycode(const void *event)
{
NSEvent *nsevent;
nsevent = (NSEvent*)event;
return [nsevent keyCode];
}
static NSString *key_translate(UInt16 keyCode, UInt32 modifierFlags)
{
const UCKeyboardLayout *layout;
OSStatus err;
layout = NULL;
TISInputSourceRef keyboard;
CFDataRef uchr;
keyboard = TISCopyCurrentKeyboardLayoutInputSource();
uchr = (CFDataRef)TISGetInputSourceProperty(keyboard,
kTISPropertyUnicodeKeyLayoutData);
if (uchr == NULL)
return nil;
layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
if (layout == NULL)
return nil;
UInt32 dead_state;
UniCharCount max_len, actual_len;
UniChar string[255];
dead_state = 0;
max_len = sizeof(string)/sizeof(*string);
modifierFlags = (modifierFlags >> 8) & 0xff;
err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags,
LMGetKbdType(), 0, &dead_state, max_len, &actual_len,
string);
if (err != noErr)
return nil;
// Dead key?
if (dead_state != 0) {
// We have no fool proof way of asking what dead key this is.
// Assume we get a spacing equivalent if we press the
// same key again, and try to deduce something from that.
err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags,
LMGetKbdType(), 0, &dead_state, max_len, &actual_len,
string);
if (err != noErr)
return nil;
}
return [NSString stringWithCharacters:string length:actual_len];
}
static const int kvk_map[][2] = {
{ kVK_Return, XK_Return },
{ kVK_Tab, XK_Tab },
{ kVK_Space, XK_space },
{ kVK_Delete, XK_BackSpace },
{ kVK_Escape, XK_Escape },
{ kVK_RightCommand, XK_Super_R },
{ kVK_Command, XK_Super_L },
{ kVK_Shift, XK_Shift_L },
{ kVK_CapsLock, XK_Caps_Lock },
{ kVK_Option, XK_Alt_L },
{ kVK_Control, XK_Control_L },
{ kVK_RightShift, XK_Shift_R },
{ kVK_RightOption, XK_Alt_R },
{ kVK_RightControl, XK_Control_R },
{ kVK_F17, XK_F17 },
{ kVK_VolumeUp, XF86XK_AudioRaiseVolume },
{ kVK_VolumeDown, XF86XK_AudioLowerVolume },
{ kVK_Mute, XF86XK_AudioMute },
{ kVK_F18, XK_F18 },
{ kVK_F19, XK_F19 },
{ kVK_F20, XK_F20 },
{ kVK_F5, XK_F5 },
{ kVK_F6, XK_F6 },
{ kVK_F7, XK_F7 },
{ kVK_F3, XK_F3 },
{ kVK_F8, XK_F8 },
{ kVK_F9, XK_F9 },
{ kVK_F11, XK_F11 },
{ kVK_F13, XK_F13 },
{ kVK_F16, XK_F16 },
{ kVK_F14, XK_F14 },
{ kVK_F10, XK_F10 },
{ kVK_Menu, XK_Menu },
{ kVK_F12, XK_F12 },
{ kVK_F15, XK_F15 },
// Should we send Insert here?
{ kVK_Help, XK_Help },
{ kVK_Home, XK_Home },
{ kVK_PageUp, XK_Page_Up },
{ kVK_ForwardDelete, XK_Delete },
{ kVK_F4, XK_F4 },
{ kVK_End, XK_End },
{ kVK_F2, XK_F2 },
{ kVK_PageDown, XK_Page_Down },
{ kVK_F1, XK_F1 },
{ kVK_LeftArrow, XK_Left },
{ kVK_RightArrow, XK_Right },
{ kVK_DownArrow, XK_Down },
{ kVK_UpArrow, XK_Up },
// The OS X headers claim these keys are not layout independent.
// Could it be because of the state of the decimal key?
/* { kVK_ANSI_KeypadDecimal, XK_KP_Decimal }, */ // see below
{ kVK_ANSI_KeypadMultiply, XK_KP_Multiply },
{ kVK_ANSI_KeypadPlus, XK_KP_Add },
// OS X doesn't have NumLock, so is this really correct?
{ kVK_ANSI_KeypadClear, XK_Num_Lock },
{ kVK_ANSI_KeypadDivide, XK_KP_Divide },
{ kVK_ANSI_KeypadEnter, XK_KP_Enter },
{ kVK_ANSI_KeypadMinus, XK_KP_Subtract },
{ kVK_ANSI_KeypadEquals, XK_KP_Equal },
{ kVK_ANSI_Keypad0, XK_KP_0 },
{ kVK_ANSI_Keypad1, XK_KP_1 },
{ kVK_ANSI_Keypad2, XK_KP_2 },
{ kVK_ANSI_Keypad3, XK_KP_3 },
{ kVK_ANSI_Keypad4, XK_KP_4 },
{ kVK_ANSI_Keypad5, XK_KP_5 },
{ kVK_ANSI_Keypad6, XK_KP_6 },
{ kVK_ANSI_Keypad7, XK_KP_7 },
{ kVK_ANSI_Keypad8, XK_KP_8 },
{ kVK_ANSI_Keypad9, XK_KP_9 },
};
int cocoa_event_keysym(const void *event)
{
NSEvent *nsevent;
UInt16 key_code;
size_t i;
NSString *chars;
UInt32 modifiers;
nsevent = (NSEvent*)event;
key_code = [nsevent keyCode];
// Start with keys that either don't generate a symbol, or
// generate the same symbol as some other key.
for (i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) {
if (key_code == kvk_map[i][0])
return kvk_map[i][1];
}
// OS X always sends the same key code for the decimal key on the
// num pad, but X11 wants different keysyms depending on if it should
// be a comma or full stop.
if (key_code == 0x41) {
switch ([[nsevent charactersIgnoringModifiers] UTF8String][0]) {
case ',':
return XK_KP_Separator;
case '.':
return XK_KP_Decimal;
default:
return NoSymbol;
}
}
// We want a "normal" symbol out of the event, which basically means
// we only respect the shift and alt/altgr modifiers. Cocoa can help
// us if we only wanted shift, but as we also want alt/altgr, we'll
// have to do some lookup ourselves. This matches our behaviour on
// other platforms.
modifiers = 0;
if ([nsevent modifierFlags] & NSAlphaShiftKeyMask)
modifiers |= alphaLock;
if ([nsevent modifierFlags] & NSShiftKeyMask)
modifiers |= shiftKey;
if ([nsevent modifierFlags] & NSAlternateKeyMask)
modifiers |= optionKey;
chars = key_translate(key_code, modifiers);
if (chars == nil)
return NoSymbol;
// FIXME: Some dead keys are given as NBSP + combining character
if ([chars length] != 1)
return NoSymbol;
// Dead key?
if ([[nsevent characters] length] == 0)
return ucs2keysym(ucs2combining([chars characterAtIndex:0]));
return ucs2keysym([chars characterAtIndex:0]);
}
static int cocoa_open_hid(io_connect_t *ioc)
{
kern_return_t ret;
io_service_t ios;
CFMutableDictionaryRef mdict;
mdict = IOServiceMatching(kIOHIDSystemClass);
ios = IOServiceGetMatchingService(kIOMasterPortDefault,
(CFDictionaryRef) mdict);
if (!ios)
return KERN_FAILURE;
ret = IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, ioc);
IOObjectRelease(ios);
if (ret != KERN_SUCCESS)
return ret;
return KERN_SUCCESS;
}
static int cocoa_set_modifier_lock_state(int modifier, bool on)
{
kern_return_t ret;
io_connect_t ioc;
ret = cocoa_open_hid(&ioc);
if (ret != KERN_SUCCESS)
return ret;
ret = IOHIDSetModifierLockState(ioc, modifier, on);
IOServiceClose(ioc);
if (ret != KERN_SUCCESS)
return ret;
return KERN_SUCCESS;
}
static int cocoa_get_modifier_lock_state(int modifier, bool *on)
{
kern_return_t ret;
io_connect_t ioc;
ret = cocoa_open_hid(&ioc);
if (ret != KERN_SUCCESS)
return ret;
ret = IOHIDGetModifierLockState(ioc, modifier, on);
IOServiceClose(ioc);
if (ret != KERN_SUCCESS)
return ret;
return KERN_SUCCESS;
}
int cocoa_set_caps_lock_state(bool on)
{
return cocoa_set_modifier_lock_state(kIOHIDCapsLockState, on);
}
int cocoa_set_num_lock_state(bool on)
{
return cocoa_set_modifier_lock_state(kIOHIDNumLockState, on);
}
int cocoa_get_caps_lock_state(bool *on)
{
return cocoa_get_modifier_lock_state(kIOHIDCapsLockState, on);
}
int cocoa_get_num_lock_state(bool *on)
{
return cocoa_get_modifier_lock_state(kIOHIDNumLockState, on);
}