hammerspoon/extensions/keycodes/libkeycodes.m

657 lines
24 KiB
Objective-C

@import Cocoa ;
@import Carbon ;
@import LuaSkin ;
static char *USERDATA_TAG = "hs.keycodes.callback" ;
static LSRefTable refTable;
static void pushkeycode(lua_State* L, int code, const char* key) {
//NSLog(@"PUSHKEYCODE: %d::%s", code, key);
// t[key] = code
lua_pushinteger(L, code);
lua_setfield(L, -2, key);
// t[code] = key
lua_pushstring(L, key);
lua_rawseti(L, -2, code);
}
int keycodes_cachemap(lua_State* L) {
lua_newtable(L);
UInt16 relocatableKeyCodes[] = {
kVK_ANSI_A, kVK_ANSI_B, kVK_ANSI_C, kVK_ANSI_D, kVK_ANSI_E, kVK_ANSI_F,
kVK_ANSI_G, kVK_ANSI_H, kVK_ANSI_I, kVK_ANSI_J, kVK_ANSI_K, kVK_ANSI_L,
kVK_ANSI_M, kVK_ANSI_N, kVK_ANSI_O, kVK_ANSI_P, kVK_ANSI_Q, kVK_ANSI_R,
kVK_ANSI_S, kVK_ANSI_T, kVK_ANSI_U, kVK_ANSI_V, kVK_ANSI_W, kVK_ANSI_X,
kVK_ANSI_Y, kVK_ANSI_Z, kVK_ANSI_0, kVK_ANSI_1, kVK_ANSI_2, kVK_ANSI_3,
kVK_ANSI_4, kVK_ANSI_5, kVK_ANSI_6, kVK_ANSI_7, kVK_ANSI_8, kVK_ANSI_9,
kVK_ANSI_Grave, kVK_ANSI_Equal, kVK_ANSI_Minus, kVK_ANSI_RightBracket,
kVK_ANSI_LeftBracket, kVK_ANSI_Quote, kVK_ANSI_Semicolon, kVK_ANSI_Backslash,
kVK_ANSI_Comma, kVK_ANSI_Slash, kVK_ANSI_Period, kVK_ISO_Section,
kVK_JIS_Yen, kVK_JIS_Underscore, kVK_JIS_KeypadComma, kVK_JIS_Eisu, kVK_JIS_Kana,
};
// NOTE: It appears that TISCopyCurrentKeyboardInputSources() can return NULL
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
if (layoutData) {
const UCKeyboardLayout *keyboardLayout = (const void *)CFDataGetBytePtr(layoutData);
UInt32 keysDown = 0;
UniChar chars[4];
UniCharCount realLength;
for (int i = 0 ; i < (int)(sizeof(relocatableKeyCodes)/sizeof(relocatableKeyCodes[0])) ; i++) {
if (UCKeyTranslate(keyboardLayout,
relocatableKeyCodes[i],
kUCKeyActionDown,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask,
&keysDown,
sizeof(chars) / sizeof(chars[0]),
&realLength,
chars) == noErr && realLength > 0) {
const char* name = [[NSString stringWithCharacters:chars length:1] UTF8String];
// This is an ugly hack to work around an unexplained change in macOS12, where keycodes 93 and 94 are now included in kTISPropertyUnicodeKeyLayoutData
if (relocatableKeyCodes[i] != 93 && relocatableKeyCodes[i] != 94) {
pushkeycode(L, relocatableKeyCodes[i], name);
}
}
}
}
else {
pushkeycode(L, kVK_ANSI_A, "a");
pushkeycode(L, kVK_ANSI_B, "b");
pushkeycode(L, kVK_ANSI_C, "c");
pushkeycode(L, kVK_ANSI_D, "d");
pushkeycode(L, kVK_ANSI_E, "e");
pushkeycode(L, kVK_ANSI_F, "f");
pushkeycode(L, kVK_ANSI_G, "g");
pushkeycode(L, kVK_ANSI_H, "h");
pushkeycode(L, kVK_ANSI_I, "i");
pushkeycode(L, kVK_ANSI_J, "j");
pushkeycode(L, kVK_ANSI_K, "k");
pushkeycode(L, kVK_ANSI_L, "l");
pushkeycode(L, kVK_ANSI_M, "m");
pushkeycode(L, kVK_ANSI_N, "n");
pushkeycode(L, kVK_ANSI_O, "o");
pushkeycode(L, kVK_ANSI_P, "p");
pushkeycode(L, kVK_ANSI_Q, "q");
pushkeycode(L, kVK_ANSI_R, "r");
pushkeycode(L, kVK_ANSI_S, "s");
pushkeycode(L, kVK_ANSI_T, "t");
pushkeycode(L, kVK_ANSI_U, "u");
pushkeycode(L, kVK_ANSI_V, "v");
pushkeycode(L, kVK_ANSI_W, "w");
pushkeycode(L, kVK_ANSI_X, "x");
pushkeycode(L, kVK_ANSI_Y, "y");
pushkeycode(L, kVK_ANSI_Z, "z");
pushkeycode(L, kVK_ANSI_0, "0");
pushkeycode(L, kVK_ANSI_1, "1");
pushkeycode(L, kVK_ANSI_2, "2");
pushkeycode(L, kVK_ANSI_3, "3");
pushkeycode(L, kVK_ANSI_4, "4");
pushkeycode(L, kVK_ANSI_5, "5");
pushkeycode(L, kVK_ANSI_6, "6");
pushkeycode(L, kVK_ANSI_7, "7");
pushkeycode(L, kVK_ANSI_8, "8");
pushkeycode(L, kVK_ANSI_9, "9");
pushkeycode(L, kVK_ANSI_Grave, "`");
pushkeycode(L, kVK_ANSI_Equal, "=");
pushkeycode(L, kVK_ANSI_Minus, "-");
pushkeycode(L, kVK_ANSI_RightBracket, "]");
pushkeycode(L, kVK_ANSI_LeftBracket, "[");
pushkeycode(L, kVK_ANSI_Quote, "'");
pushkeycode(L, kVK_ANSI_Semicolon, ";");
pushkeycode(L, kVK_ANSI_Backslash, "\\");
pushkeycode(L, kVK_ANSI_Comma, ",");
pushkeycode(L, kVK_ANSI_Slash, "/");
pushkeycode(L, kVK_ANSI_Period, ".");
pushkeycode(L, kVK_ISO_Section, "§");
}
if (currentKeyboard) {
CFRelease(currentKeyboard);
}
//NSLog(@"PUSHKEYCODE: ********* PART 2 *******");
pushkeycode(L, kVK_F1, "f1");
pushkeycode(L, kVK_F2, "f2");
pushkeycode(L, kVK_F3, "f3");
pushkeycode(L, kVK_F4, "f4");
pushkeycode(L, kVK_F5, "f5");
pushkeycode(L, kVK_F6, "f6");
pushkeycode(L, kVK_F7, "f7");
pushkeycode(L, kVK_F8, "f8");
pushkeycode(L, kVK_F9, "f9");
pushkeycode(L, kVK_F10, "f10");
pushkeycode(L, kVK_F11, "f11");
pushkeycode(L, kVK_F12, "f12");
pushkeycode(L, kVK_F13, "f13");
pushkeycode(L, kVK_F14, "f14");
pushkeycode(L, kVK_F15, "f15");
pushkeycode(L, kVK_F16, "f16");
pushkeycode(L, kVK_F17, "f17");
pushkeycode(L, kVK_F18, "f18");
pushkeycode(L, kVK_F19, "f19");
pushkeycode(L, kVK_F20, "f20");
pushkeycode(L, kVK_ANSI_KeypadDecimal, "pad.");
pushkeycode(L, kVK_ANSI_KeypadMultiply, "pad*");
pushkeycode(L, kVK_ANSI_KeypadPlus, "pad+");
pushkeycode(L, kVK_ANSI_KeypadDivide, "pad/");
pushkeycode(L, kVK_ANSI_KeypadMinus, "pad-");
pushkeycode(L, kVK_ANSI_KeypadEquals, "pad=");
pushkeycode(L, kVK_ANSI_Keypad0, "pad0");
pushkeycode(L, kVK_ANSI_Keypad1, "pad1");
pushkeycode(L, kVK_ANSI_Keypad2, "pad2");
pushkeycode(L, kVK_ANSI_Keypad3, "pad3");
pushkeycode(L, kVK_ANSI_Keypad4, "pad4");
pushkeycode(L, kVK_ANSI_Keypad5, "pad5");
pushkeycode(L, kVK_ANSI_Keypad6, "pad6");
pushkeycode(L, kVK_ANSI_Keypad7, "pad7");
pushkeycode(L, kVK_ANSI_Keypad8, "pad8");
pushkeycode(L, kVK_ANSI_Keypad9, "pad9");
pushkeycode(L, kVK_ANSI_KeypadClear, "padclear");
pushkeycode(L, kVK_ANSI_KeypadEnter, "padenter");
pushkeycode(L, kVK_Return, "return");
pushkeycode(L, kVK_Tab, "tab");
pushkeycode(L, kVK_Space, "space");
pushkeycode(L, kVK_Delete, "delete");
pushkeycode(L, kVK_Escape, "escape");
pushkeycode(L, kVK_Help, "help");
pushkeycode(L, kVK_Home, "home");
pushkeycode(L, kVK_PageUp, "pageup");
pushkeycode(L, kVK_ForwardDelete, "forwarddelete");
pushkeycode(L, kVK_End, "end");
pushkeycode(L, kVK_PageDown, "pagedown");
pushkeycode(L, kVK_LeftArrow, "left");
pushkeycode(L, kVK_RightArrow, "right");
pushkeycode(L, kVK_DownArrow, "down");
pushkeycode(L, kVK_UpArrow, "up");
pushkeycode(L, kVK_Command, "cmd");
pushkeycode(L, kVK_RightCommand, "rightcmd");
pushkeycode(L, kVK_Shift, "shift");
pushkeycode(L, kVK_CapsLock, "capslock");
pushkeycode(L, kVK_Option, "alt");
pushkeycode(L, kVK_Control, "ctrl");
pushkeycode(L, kVK_RightShift, "rightshift");
pushkeycode(L, kVK_RightOption, "rightalt");
pushkeycode(L, kVK_RightControl, "rightctrl");
pushkeycode(L, kVK_Function, "fn");
pushkeycode(L, kVK_JIS_Yen, "yen");
pushkeycode(L, kVK_JIS_Underscore, "underscore");
pushkeycode(L, kVK_JIS_KeypadComma, "pad,");
pushkeycode(L, kVK_JIS_Eisu, "eisu");
pushkeycode(L, kVK_JIS_Kana, "kana");
return 1;
}
@interface MJKeycodesObserver : NSObject
@property int ref;
@property LSGCCanary lsCanary;
@end
@implementation MJKeycodesObserver
- (instancetype)init {
self = [super init] ;
if (self) {
_ref = LUA_NOREF ;
}
return self ;
}
- (void) inputSourceChanged:(NSNotification*)__unused note {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.ref != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
if (![skin checkGCCanary:self.lsCanary]) {
return;
}
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:self.ref];
[skin protectedCallAndError:@"hs.keycodes.inputSourceChanged" nargs:0 nresults:0];
_lua_stackguard_exit(skin.L);
}
});
}
- (void) start {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(inputSourceChanged:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
/* This should have made things better, but it seems to cause crashes for some, possibly because the paired removeObserver call is wrong?
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:@selector(_inputSourceChanged:)
name:(__bridge NSString *)kTISNotifySelectedKeyboardInputSourceChanged
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
*/
}
- (void) stop {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
/*
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self
name:(__bridge NSString *)kTISNotifyEnabledKeyboardInputSourcesChanged
object:nil];
*/
}
@end
static int keycodes_newcallback(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_pushvalue(L, 1);
int ref = [skin luaRef:refTable];
MJKeycodesObserver* observer = [[MJKeycodesObserver alloc] init];
observer.ref = ref;
observer.lsCanary = [skin createGCCanary];
[observer start];
void** ud = lua_newuserdata(L, sizeof(id));
*ud = (__bridge_retained void*)observer;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
return 1;
}
static int userdata_tostring(lua_State* L) {
lua_pushstring(L, [[NSString stringWithFormat:@"%s: (%p)", USERDATA_TAG, lua_topointer(L, 1)] UTF8String]) ;
return 1 ;
}
static int keycodes_callback_gc(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
MJKeycodesObserver* observer = (__bridge_transfer MJKeycodesObserver*)*(void**)luaL_checkudata(L, 1, USERDATA_TAG);
LSGCCanary tmplsCanary = observer.lsCanary;
[skin destroyGCCanary:&tmplsCanary];
observer.lsCanary = tmplsCanary;
[observer stop];
observer.ref = [skin luaUnref:refTable ref:observer.ref];
observer = nil;
return 0;
}
static int keycodes_callback_stop(lua_State* L) {
MJKeycodesObserver* observer = (__bridge MJKeycodesObserver*)*(void**)luaL_checkudata(L, 1, USERDATA_TAG);
[observer stop];
return 0;
}
NSString *getLayoutName(TISInputSourceRef layout) {
return (__bridge NSString *)TISGetInputSourceProperty(layout, kTISPropertyLocalizedName);
}
void pushSourceIcon(lua_State *L, TISInputSourceRef source) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
IconRef icon = TISGetInputSourceProperty(source, kTISPropertyIconRef);
if (icon) {
[skin pushNSObject:[[NSImage alloc] initWithIconRef:icon]];
} else {
lua_pushnil(L);
}
}
CFArrayRef getAllLayouts(void) {
NSDictionary *properties = @{
(__bridge NSString *)kTISPropertyInputSourceType : (__bridge NSString *)kTISTypeKeyboardLayout,
(__bridge NSString *)kTISPropertyInputSourceIsSelectCapable: @true
};
return TISCreateInputSourceList((__bridge CFDictionaryRef)properties, false);
}
CFArrayRef getAllInputMethods(void) {
NSDictionary *properties = @{
(__bridge NSString *)kTISPropertyInputSourceType : (__bridge NSString *)kTISTypeKeyboardInputMode,
(__bridge NSString *)kTISPropertyInputSourceIsSelectCapable: @true
};
return TISCreateInputSourceList((__bridge CFDictionaryRef)properties, false);
}
/// hs.keycodes.currentSourceID([sourceID]) -> string | boolean
/// Function
/// Get or set the source id for the keyboard input source
///
/// Parameters:
/// * sourceID - an optional string specifying the input source to set for keyboard input
///
/// Returns:
/// * If no parameter is provided, returns a string containing the source id for the current keyboard layout or input method; if a parameter is provided, returns true or false specifying whether or not the input source was able to be changed.
static int keycodes_sourceID(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING | LS_TOPTIONAL, LS_TBREAK] ;
if (lua_gettop(L) == 0) {
TISInputSourceRef layout = TISCopyCurrentKeyboardInputSource();
[skin pushNSObject:(__bridge id)TISGetInputSourceProperty(layout, kTISPropertyInputSourceID)] ;
CFRelease(layout);
} else {
BOOL found = NO ;
NSString *sourceID = [skin toNSObjectAtIndex:1] ;
NSDictionary *prop = @{
(__bridge NSString *)kTISPropertyInputSourceID : sourceID,
(__bridge NSString *)kTISPropertyInputSourceIsSelectCapable: @true
} ;
CFArrayRef sources = TISCreateInputSourceList((__bridge CFDictionaryRef)prop, false);
if (sources) {
if (CFArrayGetCount(sources) > 0) {
found = (TISSelectInputSource((TISInputSourceRef)CFArrayGetValueAtIndex(sources, 0)) == noErr) ;
}
CFRelease(sources) ;
}
lua_pushboolean(L, found) ;
}
return 1;
}
/// hs.keycodes.currentLayout() -> string
/// Function
/// Gets the name of the current keyboard layout
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the name of the current keyboard layout
static int keycodes_currentLayout(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
TISInputSourceRef layout = TISCopyCurrentKeyboardLayoutInputSource();
[skin pushNSObject:getLayoutName(layout)];
CFRelease(layout);
return 1;
}
/// hs.keycodes.currentLayoutIcon() -> hs.image object
/// Function
/// Gets the icon of the current keyboard layout
///
/// Parameters:
/// * None
///
/// Returns:
/// * An hs.image object containing the icon, if available
static int keycodes_currentLayoutIcon(lua_State* L) {
TISInputSourceRef layout = TISCopyCurrentKeyboardInputSource();
pushSourceIcon(L, layout);
CFRelease(layout);
return 1;
}
/// hs.keycodes.layouts([sourceID]) -> table
/// Function
/// Gets all of the enabled keyboard layouts that the keyboard input source can be switched to
///
/// Parameters:
/// * sourceID - an optional boolean, default false, indicating whether the keyboard layout names should be returned (false) or their source IDs (true).
///
/// Returns:
/// * A table containing a list of keyboard layouts enabled in System Preferences
///
/// Notes:
/// * Only those layouts which can be explicitly switched to will be included in the table. Keyboard layouts which are part of input methods are not included. See `hs.keycodes.methods`.
static int keycodes_layouts(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK] ;
BOOL sourceIDsOnly = lua_gettop(L) == 1 ? (BOOL)lua_toboolean(L, 1) : NO ;
CFArrayRef layoutRefs = getAllLayouts();
lua_newtable(L) ;
if (layoutRefs) {
for (int i = 0; i < CFArrayGetCount(layoutRefs); i++) {
TISInputSourceRef layout = (TISInputSourceRef)(CFArrayGetValueAtIndex(layoutRefs, i));
if (sourceIDsOnly) {
[skin pushNSObject:(__bridge id)TISGetInputSourceProperty(layout, kTISPropertyInputSourceID)] ;
} else {
[skin pushNSObject:getLayoutName(layout)];
}
lua_rawseti(L, -2, luaL_len(L, -2) + 1) ;
}
CFRelease(layoutRefs);
}
return 1;
}
/// hs.keycodes.methods([sourceID]) -> table
/// Function
/// Gets all of the enabled input methods that the keyboard input source can be switched to
///
/// Parameters:
/// * sourceID - an optional boolean, default false, indicating whether the keyboard input method names should be returned (false) or their source IDs (true).
///
/// Returns:
/// * A table containing a list of input methods enabled in System Preferences
///
/// Notes:
/// * Keyboard layouts which are not part of an input method are not included in this table. See `hs.keycodes.layouts`.
static int keycodes_methods(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK] ;
BOOL sourceIDsOnly = lua_gettop(L) == 1 ? (BOOL)lua_toboolean(L, 1) : NO ;
CFArrayRef methodRefs = getAllInputMethods();
lua_newtable(L) ;
if (methodRefs) {
for (int i = 0; i < CFArrayGetCount(methodRefs); i++) {
TISInputSourceRef method = (TISInputSourceRef)(CFArrayGetValueAtIndex(methodRefs, i));
if (sourceIDsOnly) {
[skin pushNSObject:(__bridge id)TISGetInputSourceProperty(method, kTISPropertyInputSourceID)] ;
} else {
[skin pushNSObject:getLayoutName(method)];
}
lua_rawseti(L, -2, luaL_len(L, -2) + 1) ;
}
CFRelease(methodRefs);
}
return 1;
}
/// hs.keycodes.currentMethod() -> string
/// Function
/// Get current input method
///
/// Parameters:
/// * None
///
/// Returns:
/// * Name of current input method, or nil
static int keycodes_currentMethod(__unused lua_State * L) {
LuaSkin * skin = [LuaSkin sharedWithState:L];
CFArrayRef methodRefs = getAllInputMethods();
NSString * currentMethod = nil;
if (methodRefs) {
for (int i = 0 ; i < CFArrayGetCount(methodRefs); i ++ ) {
TISInputSourceRef method = (TISInputSourceRef)(CFArrayGetValueAtIndex(methodRefs, i));
CFBooleanRef selected = TISGetInputSourceProperty(method, kTISPropertyInputSourceIsSelected);
if (CFBooleanGetValue(selected) == YES) {
currentMethod = getLayoutName(method);
break;
}
}
CFRelease(methodRefs);
}
[skin pushNSObject:currentMethod];
return 1;
}
/// hs.keycodes.setLayout(layoutName) -> boolean
/// Function
/// Changes the system keyboard layout
///
/// Parameters:
/// * layoutName - A string containing the name of an enabled keyboard layout
///
/// Returns:
/// * A boolean, true if the layout was successfully changed, otherwise false
static int keycodes_setLayout(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING, LS_TBREAK];
NSString *desiredLayout = [skin toNSObjectAtIndex:1];
CFArrayRef layoutRefs = getAllLayouts();
BOOL found = NO;
if (layoutRefs) {
for (int i = 0; i < CFArrayGetCount(layoutRefs); i++) {
TISInputSourceRef layout = (TISInputSourceRef)(CFArrayGetValueAtIndex(layoutRefs, i));
NSString *layoutName = getLayoutName(layout);
if ([layoutName isEqualToString:desiredLayout] && TISSelectInputSource(layout) == noErr) {
found = YES;
}
}
CFRelease(layoutRefs);
}
lua_pushboolean(L, found);
return 1;
}
/// hs.keycodes.setMethod(methodName) -> boolean
/// Function
/// Changes the system input method
///
/// Parameters:
/// * methodName - A string containing the name of an enabled input method
///
/// Returns:
/// * A boolean, true if the method was successfully changed, otherwise false
static int keycodes_setMethod(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING, LS_TBREAK];
NSString *desiredLayout = [skin toNSObjectAtIndex:1];
CFArrayRef layoutRefs = getAllInputMethods();
BOOL found = NO;
if (layoutRefs) {
for (int i = 0; i < CFArrayGetCount(layoutRefs); i++) {
TISInputSourceRef layout = (TISInputSourceRef)(CFArrayGetValueAtIndex(layoutRefs, i));
NSString *layoutName = getLayoutName(layout);
if ([layoutName isEqualToString:desiredLayout] && TISSelectInputSource(layout) == noErr) {
found = YES;
}
}
CFRelease(layoutRefs);
}
lua_pushboolean(L, found);
return 1;
}
/// hs.keycodes.iconForLayoutOrMethod(sourceName) -> hs.image object
/// Function
/// Gets an hs.image object for a given keyboard layout or input method
///
/// Parameters:
/// * sourceName - A string containing the name of an input method or keyboard layout
///
/// Returns:
/// * An hs.image object, or nil if no image could be found
///
/// Notes:
/// * Not all layouts/methods have icons, so you should assume this will return nil at some point
static int keycodes_getIcon(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING, LS_TBREAK];
NSString *sourceName = [skin toNSObjectAtIndex:1];
CFArrayRef layoutRefs = getAllLayouts();
CFArrayRef methodRefs = getAllInputMethods();
BOOL found = NO;
if (layoutRefs) {
for (int i = 0; i < CFArrayGetCount(layoutRefs); i++) {
TISInputSourceRef layout = (TISInputSourceRef)(CFArrayGetValueAtIndex(layoutRefs, i));
NSString *layoutName = getLayoutName(layout);
if ([layoutName isEqualToString:sourceName]) {
pushSourceIcon(L, layout);
found = YES;
break;
}
}
}
if (!found) {
if (methodRefs) {
for (int i = 0; i < CFArrayGetCount(methodRefs); i++) {
TISInputSourceRef layout = (TISInputSourceRef)(CFArrayGetValueAtIndex(methodRefs, i));
NSString *layoutName = getLayoutName(layout);
if ([layoutName isEqualToString:sourceName]) {
pushSourceIcon(L, layout);
found = YES;
break;
}
}
}
}
if (!found) {
lua_pushnil(L);
}
if (layoutRefs) CFRelease(layoutRefs);
if (methodRefs) CFRelease(methodRefs);
return 1;
}
static const luaL_Reg callbacklib[] = {
// instance methods
{"_stop", keycodes_callback_stop},
// metamethods
{"__tostring", userdata_tostring},
{"__gc", keycodes_callback_gc},
{NULL, NULL}
};
static const luaL_Reg keycodeslib[] = {
// module methods
{"_newcallback", keycodes_newcallback},
{"_cachemap", keycodes_cachemap},
{"currentLayout", keycodes_currentLayout},
{"currentLayoutIcon", keycodes_currentLayoutIcon},
{"currentMethod", keycodes_currentMethod},
{"layouts", keycodes_layouts},
{"methods", keycodes_methods},
{"setLayout", keycodes_setLayout},
{"setMethod", keycodes_setMethod},
{"iconForLayoutOrMethod", keycodes_getIcon},
{"currentSourceID", keycodes_sourceID},
{NULL, NULL}
};
int luaopen_hs_libkeycodes(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
refTable = [skin registerLibraryWithObject:USERDATA_TAG functions:keycodeslib metaFunctions:nil objectFunctions:callbacklib];
return 1;
}