/* Copyright 2011 Pierre Ossman 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 #endif #include #include #include #import #import #define XK_LATIN1 #define XK_MISCELLANY #define XK_XKB_KEYS #include #include #include "keysym2ucs.h" #define NoSymbol 0 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 != Fl::screen_count()) return 1; #ifdef HAVE_FLTK_FULLSCREEN_SCREENS index = Fl::screen_num(win->x(), win->y(), win->w(), win->h()); #else index = 0; #endif 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. #ifdef HAVE_FLTK_FULLSCREEN if (win->fullscreen_active() && win->contains(Fl::focus())) newlevel = NSStatusWindowLevel; else #endif 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]; } 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] == 0x39) return 1; // These are entirely undocumented, but I cannot find any other way // of differentiating between left and right keys switch ([nsevent keyCode]) { case 0x36: mask = 0x0010; break; case 0x37: mask = 0x0008; break; case 0x38: mask = 0x0002; break; case 0x39: // We don't see any event on release of CapsLock return 1; case 0x3A: mask = 0x0020; break; case 0x3B: mask = 0x0001; break; case 0x3C: mask = 0x0004; break; case 0x3D: mask = 0x0040; break; case 0x3E: 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; #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) TISInputSourceRef keyboard; CFDataRef uchr; keyboard = TISCopyCurrentKeyboardInputSource(); uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); if (uchr == NULL) return nil; layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 KeyboardLayoutRef old_layout; int kind; err = KLGetCurrentKeyboardLayout(&old_layout); if (err != noErr) return nil; err = KLGetKeyboardLayoutProperty(old_layout, kKLKind, (const void**)&kind); if (err != noErr) return nil; // Old, crufty layout format? if (kind == kKLKCHRKind) { void *kchr_layout; UInt32 chars, state; char buf[3]; unichar result[16]; ByteCount in_len, out_len; err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData, (const void**)&kchr_layout); if (err != noErr) return nil; state = 0; keyCode &= 0x7f; modifierFlags &= 0xff00; chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); // Dead key? if (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. chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); } buf[0] = (chars >> 16) & 0xff; buf[1] = chars & 0xff; buf[2] = '\0'; if (buf[0] == '\0') { buf[0] = buf[1]; buf[1] = '\0'; } // The data is now in some layout specific encoding. Need to convert // this to unicode. ScriptCode script; TextEncoding encoding; TECObjectRef converter; script = (ScriptCode)GetScriptManagerVariable(smKeyScript); err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, kTextRegionDontCare, NULL, &encoding); if (err != noErr) return nil; err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0); if (err != noErr) return nil; in_len = strlen(buf); out_len = sizeof(result); err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len, (TextPtr)result, out_len, &out_len); TECDisposeConverter(converter); if (err != noErr) return nil; return [NSString stringWithCharacters:result length:(out_len / sizeof(unichar))]; } if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind)) return nil; err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData, (const void**)&layout); if (err != noErr) return nil; #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 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]; } // FIXME: We use hard coded values here as the constants didn't appear // in the OS X headers until 10.5. static const int kvk_map[][2] = { { 0x24, XK_Return }, { 0x30, XK_Tab }, { 0x31, XK_space }, { 0x33, XK_BackSpace }, { 0x35, XK_Escape }, // This one is undocumented for unknown reasons { 0x36, XK_Super_R }, { 0x37, XK_Super_L }, { 0x38, XK_Shift_L }, { 0x39, XK_Caps_Lock }, { 0x3A, XK_Alt_L }, { 0x3B, XK_Control_L }, { 0x3C, XK_Shift_R }, { 0x3D, XK_Alt_R }, { 0x3E, XK_Control_R }, { 0x40, XK_F17 }, { 0x48, XF86XK_AudioRaiseVolume }, { 0x49, XF86XK_AudioLowerVolume }, { 0x4A, XF86XK_AudioMute }, { 0x4F, XK_F18 }, { 0x50, XK_F19 }, { 0x5A, XK_F20 }, { 0x60, XK_F5 }, { 0x61, XK_F6 }, { 0x62, XK_F7 }, { 0x63, XK_F3 }, { 0x64, XK_F8 }, { 0x65, XK_F9 }, { 0x67, XK_F11 }, { 0x69, XK_F13 }, { 0x6A, XK_F16 }, { 0x6B, XK_F14 }, { 0x6D, XK_F10 }, // Also undocumented { 0x6E, XK_Menu }, { 0x6F, XK_F12 }, { 0x71, XK_F15 }, // Should we send Insert here? { 0x72, XK_Help }, { 0x73, XK_Home }, { 0x74, XK_Page_Up }, { 0x75, XK_Delete }, { 0x76, XK_F4 }, { 0x77, XK_End }, { 0x78, XK_F2 }, { 0x79, XK_Page_Down }, { 0x7A, XK_F1 }, { 0x7B, XK_Left }, { 0x7C, XK_Right }, { 0x7D, XK_Down }, { 0x7E, XK_Up }, // The OS X headers claim these keys are not layout independent. // Could it be because of the state of the decimal key? /* { 0x41, XK_KP_Decimal }, */ // see below { 0x43, XK_KP_Multiply }, { 0x45, XK_KP_Add }, // OS X doesn't have NumLock, so is this really correct? { 0x47, XK_Num_Lock }, { 0x4B, XK_KP_Divide }, { 0x4C, XK_KP_Enter }, { 0x4E, XK_KP_Subtract }, { 0x51, XK_KP_Equal }, { 0x52, XK_KP_0 }, { 0x53, XK_KP_1 }, { 0x54, XK_KP_2 }, { 0x55, XK_KP_3 }, { 0x56, XK_KP_4 }, { 0x57, XK_KP_5 }, { 0x58, XK_KP_6 }, { 0x59, XK_KP_7 }, { 0x5B, XK_KP_8 }, { 0x5C, XK_KP_9 }, }; int cocoa_event_keysym(const void *event) { NSEvent *nsevent; UInt16 key_code; int 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]); }