hammerspoon/extensions/razer/librazer.m

843 lines
27 KiB
Objective-C

@import Cocoa;
@import LuaSkin;
#import "HSRazerManager.h"
#import "HSRazerDevice.h"
#import "razer.h"
#define get_objectFromUserdata(objType, L, idx, tag) (objType*)*((void**)luaL_checkudata(L, idx, tag))
static HSRazerManager *razerManager;
LSRefTable razerRefTable = LUA_NOREF;
#pragma mark - Lua API
static int razer_gc(lua_State *L __unused) {
[razerManager stopHIDManager];
[razerManager doGC];
return 0;
}
#pragma mark - hs.razer: Common Functions
/// hs.razer.init(fn)
/// Function
/// Initialises the Razer driver and sets a discovery callback.
///
/// Parameters:
/// * fn - A function that will be called when a Razer device is connected or disconnected. It should take the following arguments:
/// * A boolean, true if a device was connected, false if a device was disconnected
/// * An hs.razer object, being the device that was connected/disconnected
///
/// Returns:
/// * None
///
/// Notes:
/// * This function must be called before any other parts of this module are used
static int razer_init(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TFUNCTION, LS_TBREAK];
razerManager = [[HSRazerManager alloc] init];
razerManager.discoveryCallbackRef = [skin luaRef:razerRefTable atIndex:1];
[razerManager startHIDManager];
return 0;
}
/// hs.razer.discoveryCallback(fn) -> none
/// Function
/// Sets/clears a callback for reacting to device discovery events
///
/// Parameters:
/// * fn - A function that will be called when a Razer device is connected or disconnected. It should take the following arguments:
/// * A boolean, true if a device was connected, false if a device was disconnected
/// * An hs.razer object, being the device that was connected/disconnected
///
/// Returns:
/// * None
static int razer_discoveryCallback(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TFUNCTION | LS_TNIL, LS_TBREAK];
if (!razerManager) {
razerManager = [[HSRazerManager alloc] init];
}
razerManager.discoveryCallbackRef = [skin luaUnref:razerRefTable ref:razerManager.discoveryCallbackRef];
if (lua_type(skin.L, 1) == LUA_TFUNCTION) {
razerManager.discoveryCallbackRef = [skin luaRef:razerRefTable atIndex:1];
}
return 0;
}
/// hs.razer.numDevices() -> number
/// Function
/// Gets the number of Razer devices connected
///
/// Parameters:
/// * None
///
/// Returns:
/// * A number containing the number of Razer devices attached to the system
static int razer_numDevices(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBREAK];
lua_pushinteger(skin.L, razerManager.devices.count);
return 1;
}
/// hs.razer.getDevice(num) -> razerObject | nil
/// Function
/// Gets an hs.razer object for the specified device
///
/// Parameters:
/// * num - A number that should be within the bounds of the number of connected devices
///
/// Returns:
/// * An hs.razer object or `nil` if something goes wrong
static int razer_getDevice(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TNUMBER, LS_TBREAK];
unsigned long deviceNumber = lua_tointeger(skin.L, 1) - 1;
if (deviceNumber > razerManager.devices.count) {
lua_pushnil(L);
return 1;
}
HSRazerDevice *razer = razerManager.devices[deviceNumber];
if (razer) {
[skin pushNSObject:razer];
} else {
lua_pushnil(L);
}
return 1;
}
#pragma mark - hs.razer: Common Methods
/// hs.razer:name() -> string
/// Method
/// Returns the human readible device name of the Razer device.
///
/// Parameters:
/// * None
///
/// Returns:
/// * The device name as a string.
static int razer_name(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSString *name = razer.name;
[skin pushNSObject:name];
return 1;
}
#pragma mark - hs.razer: Callback Methods
/// hs.razer:callback(callbackFn) -> razerObject
/// Method
/// Sets or removes a callback function for the `hs.razer` object.
///
/// Parameters:
/// * `callbackFn` - a function to set as the callback for this `hs.razer` object. If the value provided is `nil`, any currently existing callback function is removed.
///
/// Returns:
/// * The `hs.razer` object
///
/// Notes:
/// * The callback function should expect 4 arguments and should not return anything:
/// * `razerObject` - The serial port object that triggered the callback.
/// * `buttonName` - The name of the button as a string.
/// * `buttonAction` - A string containing "pressed", "released", "up" or "down".
static int razer_callback(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION | LS_TNIL, LS_TBREAK];
HSRazerDevice *device = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
device.buttonCallbackRef = [skin luaUnref:razerRefTable ref:device.buttonCallbackRef];
if (lua_type(skin.L, 2) == LUA_TFUNCTION) {
device.buttonCallbackRef = [skin luaRef:razerRefTable atIndex:2];
}
lua_pushvalue(skin.L, 1);
return 1;
}
#pragma mark - hs.razer: Private Methods
// hs.razer:_remapping() -> table
// Method
// Returns a table of the remapping data.
//
// Parameters:
// * None
//
// Returns:
// * A table of remapping data used by the `:keyboardDisableDefaults()` and `:keyboardEnableDefaults()` methods.
static int razer_remapping(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSDictionary *remapping = razer.remapping;
[skin pushNSObject:remapping];
return 1;
}
// hs.razer:_productID() -> number
// Method
// Returns the product ID of a `hs.razer` object.
//
// Parameters:
// * None
//
// Returns:
// * The product ID as a number.
static int razer_productID(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSNumber *productID = [NSNumber numberWithInt:razer.productID];
[skin pushNSObject:productID];
return 1;
}
#pragma mark - hs.razer: Brightness Methods
/// hs.razer:brightness(value) -> razerObject, number | nil, string | nil
/// Method
/// Gets or sets the brightness of a Razer keyboard.
///
/// Parameters:
/// * value - The brightness value - a number between 0 (off) and 100 (brightest).
///
/// Returns:
/// * The `hs.razer` object.
/// * The brightness as a number or `nil` if something goes wrong.
/// * A plain text error message if not successful.
static int razer_brightness(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
if (lua_gettop(L) == 1) {
// Getter:
HSRazerResult *result = [razer getBrightness];
if ([result success]) {
lua_pushvalue(L, 1);
[skin pushNSObject:[result brightness]];
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
else {
// Setter:
NSNumber *brightness = [skin toNSObjectAtIndex:2];
if ([brightness intValue] < 0 || [brightness intValue] > 100) {
lua_pushvalue(L, 1);
lua_pushboolean(L, false);
[skin pushNSObject:@"The brightness must be between 0 and 100."];
return 3;
}
HSRazerResult *result = [razer setBrightness:brightness];
if ([result success]){
lua_pushvalue(L, 1);
[skin pushNSObject:[result brightness]];
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
return 3;
}
#pragma mark - hs.razer: Status Light Methods
/// hs.razer:orangeStatusLight(value) -> razerObject, boolean | nil, string | nil
/// Method
/// Gets or sets the orange status light.
///
/// Parameters:
/// * value - `true` for on, `false` for off`
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` for on, `false` for off`, or `nil` if something has gone wrong
/// * A plain text error message if not successful.
static int razer_orangeStatusLight(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
if (lua_gettop(L) == 1) {
// Getter:
HSRazerResult *result = [razer getOrangeStatusLight];
if ([result success]) {
lua_pushvalue(L, 1);
lua_pushboolean(L, [result orangeStatusLight]);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
else {
// Setter:
BOOL active = lua_toboolean(L, 2);
HSRazerResult *result = [razer setOrangeStatusLight:active];
if ([result success]){
lua_pushvalue(L, 1);
lua_pushboolean(L, active);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
return 3;
}
/// hs.razer:greenStatusLight(value) -> razerObject, boolean | nil, string | nil
/// Method
/// Gets or sets the green status light.
///
/// Parameters:
/// * value - `true` for on, `false` for off`
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` for on, `false` for off`, or `nil` if something has gone wrong
/// * A plain text error message if not successful.
static int razer_greenStatusLight(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
if (lua_gettop(L) == 1) {
// Getter:
HSRazerResult *result = [razer getGreenStatusLight];
if ([result success]) {
lua_pushvalue(L, 1);
lua_pushboolean(L, [result greenStatusLight]);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
else {
// Setter:
BOOL active = lua_toboolean(L, 2);
HSRazerResult *result = [razer setGreenStatusLight:active];
if ([result success]){
lua_pushvalue(L, 1);
lua_pushboolean(L, active);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
return 3;
}
/// hs.razer:blueStatusLight(value) -> razerObject, boolean | nil, string | nil
/// Method
/// Gets or sets the blue status light.
///
/// Parameters:
/// * value - `true` for on, `false` for off`
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` for on, `false` for off`, or `nil` if something has gone wrong
/// * A plain text error message if not successful.
static int razer_blueStatusLight(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
if (lua_gettop(L) == 1) {
// Getter:
HSRazerResult *result = [razer getBlueStatusLight];
if ([result success]) {
lua_pushvalue(L, 1);
lua_pushboolean(L, [result blueStatusLight]);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
else {
// Setter:
BOOL active = lua_toboolean(L, 2);
HSRazerResult *result = [razer setBlueStatusLight:active];
if ([result success]){
lua_pushvalue(L, 1);
lua_pushboolean(L, active);
lua_pushnil(L);
} else {
lua_pushvalue(L, 1);
lua_pushnil(L);
[skin pushNSObject:[result errorMessage]];
}
}
return 3;
}
#pragma mark - hs.razer: Backlights Methods
/// hs.razer:backlightsStatic(color) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to a single static color.
///
/// Parameters:
/// * color - A `hs.drawing.color` object.
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`.
/// * A plain text error message if not successful.
static int razer_backlightsStatic(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TTABLE, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSColor *color = [skin luaObjectAtIndex:2 toClass:"NSColor"];
HSRazerResult *result = [razer setBacklightToStaticColor:color];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsOff() -> razerObject, boolean, string | nil
/// Method
/// Turns all the keyboard backlights off.
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`.
/// * A plain text error message if not successful.
static int razer_backlightsOff(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
HSRazerResult *result = [razer setBacklightToOff];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsWave(speed, direction) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to the wave mode.
///
/// Parameters:
/// * speed - A number between 1 (fast) and 255 (slow)
/// * direction - "left" or "right" as a string
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
static int razer_backlightsWave(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER, LS_TSTRING, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSNumber *speed = [skin toNSObjectAtIndex:2];
NSString *direction = [skin toNSObjectAtIndex:3];
if ([speed intValue] < 1 || [speed intValue] > 255) {
lua_pushvalue(L, 1);
lua_pushboolean(L, false);
[skin pushNSObject:@"The speed must be between 1 and 255."];
return 3;
}
if (![direction isEqualToString:@"left"] && ![direction isEqualToString:@"right"]) {
lua_pushvalue(L, 1);
lua_pushboolean(L, false);
[skin pushNSObject:@"The direction must be 'left' or 'right'."];
return 3;
}
HSRazerResult *result = [razer setBacklightToWaveWithSpeed:speed direction:direction];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsSpectrum() -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to the spectrum mode.
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
static int razer_backlightsSpectrum(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
HSRazerResult *result = [razer setBacklightToSpectrum];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsReactive(speed, color) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to the reactive mode.
///
/// Parameters:
/// * speed - A number between 1 (fast) and 4 (slow)
/// * color - A `hs.drawing.color` object
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
static int razer_backlightsReactive(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER, LS_TTABLE, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSNumber *speed = [skin toNSObjectAtIndex:2];
NSColor *color = [skin luaObjectAtIndex:3 toClass:"NSColor"];
if ([speed intValue] < 1 || [speed intValue] > 4) {
lua_pushvalue(L, 1);
lua_pushboolean(L, false);
[skin pushNSObject:@"The speed must be between 1 and 4."];
return 3;
}
HSRazerResult *result = [razer setBacklightToReactiveWithColor:color speed:speed];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsStarlight(speed, [color], [secondaryColor]) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to the Starlight mode.
///
/// Parameters:
/// * speed - A number between 1 (fast) and 3 (slow)
/// * [color] - An optional `hs.drawing.color` value
/// * [secondaryColor] - An optional secondary `hs.drawing.color`
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
///
/// Notes:
/// * If neither `color` nor `secondaryColor` is provided, then random colors will be used.
static int razer_backlightsStarlight(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER, LS_TTABLE | LS_TOPTIONAL | LS_TNIL, LS_TTABLE | LS_TOPTIONAL | LS_TNIL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSNumber *speed = [skin toNSObjectAtIndex:2];
if ([speed intValue] < 1 || [speed intValue] > 3) {
lua_pushvalue(L, 1);
lua_pushboolean(L, false);
[skin pushNSObject:@"The speed must be between 1 and 3."];
return 3;
}
NSColor *color;
NSColor *secondaryColor;
if (lua_type(L, 3) == LUA_TTABLE) {
color = [skin luaObjectAtIndex:3 toClass:"NSColor"];
}
if (lua_type(L, 4) == LUA_TTABLE) {
secondaryColor = [skin luaObjectAtIndex:4 toClass:"NSColor"];
}
HSRazerResult *result = [razer setBacklightToStarlightWithColor:color secondaryColor:secondaryColor speed:speed];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsBreathing([color], [secondaryColor]) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to the breath mode.
///
/// Parameters:
/// * [color] - An optional `hs.drawing.color` value
/// * [secondaryColor] - An optional secondary `hs.drawing.color`
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
///
/// Notes:
/// * If neither `color` nor `secondaryColor` is provided, then random colors will be used.
static int razer_backlightsBreathing(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TTABLE | LS_TOPTIONAL | LS_TNIL, LS_TTABLE | LS_TOPTIONAL | LS_TNIL, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSColor *color;
NSColor *secondaryColor;
if (lua_type(L, 2) == LUA_TTABLE) {
color = [skin luaObjectAtIndex:2 toClass:"NSColor"];
}
if (lua_type(L, 3) == LUA_TTABLE) {
secondaryColor = [skin luaObjectAtIndex:3 toClass:"NSColor"];
}
HSRazerResult *result = [razer setBacklightToBreathingWithColor:color secondaryColor:secondaryColor];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
/// hs.razer:backlightsCustom(colors) -> razerObject, boolean, string | nil
/// Method
/// Changes the keyboard backlights to custom colours.
///
/// Parameters:
/// * colors - A table of `hs.drawing.color` objects for each individual button on your device (i.e. if there's 20 buttons, you should have twenty colors in the table).
///
/// Returns:
/// * The `hs.razer` object.
/// * `true` if successful otherwise `false`
/// * A plain text error message if not successful.
///
/// Notes:
/// * The order is top to bottom, left to right. You can use `nil` for any buttons you don't want to light up.
/// * Example usage: ```lua
/// hs.razer.new(0):backlightsCustom({hs.drawing.color.red, nil, hs.drawing.color.green, hs.drawing.color.blue})
/// ```
static int razer_backlightsCustom(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TTABLE, LS_TBREAK];
HSRazerDevice *razer = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSMutableDictionary *customColors = [NSMutableDictionary dictionary];
lua_pushnil(L); // first key
while (lua_next(L, 2) != 0) {
customColors[@(lua_tonumber(L, -2))] = [skin luaObjectAtIndex:-1 toClass:"NSColor"];
lua_pop(L, 1); // pop value but leave key on stack for `lua_next`
}
HSRazerResult *result = [razer setBacklightToCustomWithColors:customColors];
lua_pushvalue(L, 1);
lua_pushboolean(L, [result success]);
[skin pushNSObject:[result errorMessage]];
return 3;
}
#pragma mark - Lua<->NSObject Conversion Functions
// These must not throw a lua error to ensure LuaSkin can safely be used from Objective-C
// delegates and blocks.
static int pushHSRazerDevice(lua_State *L, id obj) {
HSRazerDevice *value = obj;
value.selfRefCount++;
void** valuePtr = lua_newuserdata(L, sizeof(HSRazerDevice *));
*valuePtr = (__bridge_retained void *)value;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
return 1;
}
static id toHSRazerDeviceFromLua(lua_State *L, int idx) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSRazerDevice *value;
if (luaL_testudata(L, idx, USERDATA_TAG)) {
value = get_objectFromUserdata(__bridge HSRazerDevice, L, idx, USERDATA_TAG);
} else {
[skin logError:[NSString stringWithFormat:@"expected %s object, found %s", USERDATA_TAG,
lua_typename(L, lua_type(L, idx))]];
}
return value;
}
#pragma mark - Hammerspoon/Lua Infrastructure
static int razer_object_tostring(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSRazerDevice *obj = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
NSString *title = [NSString stringWithFormat:@"%@", obj.name];
[skin pushNSObject:[NSString stringWithFormat:@"%s: %@ (%p)", USERDATA_TAG, title, lua_topointer(L, 1)]];
return 1;
}
static int razer_object_eq(lua_State* L) {
// can't get here if at least one of us isn't a userdata type, and we only care if both types are ours,
// so use luaL_testudata before the macro causes a lua error
if (luaL_testudata(L, 1, USERDATA_TAG) && luaL_testudata(L, 2, USERDATA_TAG)) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSRazerDevice *obj1 = [skin luaObjectAtIndex:1 toClass:"HSRazerDevice"];
HSRazerDevice *obj2 = [skin luaObjectAtIndex:2 toClass:"HSRazerDevice"];
lua_pushboolean(L, [obj1 isEqualTo:obj2]);
} else {
lua_pushboolean(L, NO);
}
return 1;
}
static int razer_object_gc(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSRazerDevice *theDevice = get_objectFromUserdata(__bridge_transfer HSRazerDevice, L, 1, USERDATA_TAG);
if (theDevice) {
theDevice.selfRefCount--;
if (theDevice.selfRefCount == 0) {
// Destroy the event tap:
[theDevice destroyEventTap];
theDevice.buttonCallbackRef = [skin luaUnref:razerRefTable ref:theDevice.buttonCallbackRef];
theDevice = nil;
}
}
// Remove the Metatable so future use of the variable in Lua won't think its valid
lua_pushnil(L);
lua_setmetatable(L, 1);
return 0;
}
#pragma mark - Lua Object Function Definitions
static const luaL_Reg userdata_metaLib[] = {
// Common:
{"name", razer_name},
// Callback:
{"callback", razer_callback},
// Brightness:
{"brightness", razer_brightness},
// Backlights:
{"backlightsOff", razer_backlightsOff},
{"backlightsCustom", razer_backlightsCustom},
{"backlightsWave", razer_backlightsWave},
{"backlightsSpectrum", razer_backlightsSpectrum},
{"backlightsReactive", razer_backlightsReactive},
{"backlightsStatic", razer_backlightsStatic},
{"backlightsStarlight", razer_backlightsStarlight},
{"backlightsBreathing", razer_backlightsBreathing},
// Status Lights:
{"orangeStatusLight", razer_orangeStatusLight},
{"greenStatusLight", razer_greenStatusLight},
{"blueStatusLight", razer_blueStatusLight},
// Private Functions:
{"_remapping", razer_remapping},
{"_productID", razer_productID},
// Helpers:
{"__tostring", razer_object_tostring},
{"__eq", razer_object_eq},
{"__gc", razer_object_gc},
{NULL, NULL}
};
#pragma mark - Lua Library Function Definitions
static const luaL_Reg razerlib[] = {
{"init", razer_init},
{"discoveryCallback", razer_discoveryCallback},
{"numDevices", razer_numDevices},
{"getDevice", razer_getDevice},
{NULL, NULL}
};
static const luaL_Reg metalib[] = {
{"__gc", razer_gc},
{NULL, NULL}
};
#pragma mark - Lua Initialiser
int luaopen_hs_librazer(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
razerRefTable = [skin registerLibrary:USERDATA_TAG functions:razerlib metaFunctions:metalib];
[skin registerObject:USERDATA_TAG objectFunctions:userdata_metaLib];
[skin registerPushNSHelper:pushHSRazerDevice forClass:"HSRazerDevice"];
[skin registerLuaObjectHelper:toHSRazerDeviceFromLua forClass:"HSRazerDevice" withTableMapping:USERDATA_TAG];
return 1;
}