hammerspoon/extensions/mouse/libmouse.m

291 lines
9.8 KiB
Objective-C

#import <Cocoa/Cocoa.h>
#import <LuaSkin/LuaSkin.h>
#include <IOKit/hidsystem/event_status_driver.h>
#import <IOKit/hidsystem/IOHIDParameter.h>
#import <IOKit/hidsystem/IOHIDLib.h>
#import <IOKit/hid/IOHIDLib.h>
// We need a dictionary and a callback outside HSmouse so they can be used at the IOKit level
NSMutableArray<NSString *> *mice = nil;
static void enum_callback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef device) {
if (res != kIOReturnSuccess) {
return;
}
NSString *vendor = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDManufacturerKey));
NSString *product = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
if (vendor == nil) {
vendor = @"Unknown vendor";
}
if (product == nil) {
product = @"Unknown mouse";
}
[mice addObject:[NSString stringWithFormat:@"%@::%@", vendor, product]];
}
// MARK:- Declare HSmouse interface
@interface HSmouse : NSObject
@property (readonly, getter=hasInternalMouse) BOOL hasInternalMouse;
@property (readonly, getter=getCount) int count;
@property (readonly, getter=getNames) NSArray<NSString *> *names;
@property (getter=getAbsolutePosition, setter=setAbsolutePosition:) NSPoint absolutePosition;
@property (readonly, getter=getTrackingSpeed) double trackingSpeed;
@property (readonly, getter=getScrollDirectionNatural) BOOL isScrollDirectionNatural;
-(BOOL)hasInternalMouse;
-(int)getCount;
-(NSArray<NSString *>*)getNames;
-(NSPoint)getAbsolutePosition;
-(void)setAbsolutePosition:(NSPoint)absolutePosition;
-(double)getTrackingSpeed;
-(io_service_t)createIOHIDSystem;
-(NSDictionary *)getIOHIDParamtersFromService:(io_service_t)service;
-(NSDictionary *)getIOHIDParameters;
-(kern_return_t)setTrackingSpeed:(double)trackingSpeed;
@end
@implementation HSmouse
// MARK:- Mouse enumeration
#define RUNLOOPMODE (CFSTR("hs.mouse"))
#define MOUSE_TRACKING_FACTOR 65536
-(NSArray<NSString *>*)getNames {
// This is a highly condensed version of what ManyMouse does to enumerate mice
IOHIDManagerRef hidman = NULL;
NSDictionary *matchingDict = @{
@(kIOHIDDeviceUsagePageKey):@(kHIDPage_GenericDesktop),
@(kIOHIDDeviceUsageKey):@(kHIDUsage_GD_Mouse)
};
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
IOHIDManagerRegisterDeviceMatchingCallback(hidman, enum_callback, NULL);
IOHIDManagerScheduleWithRunLoop(hidman, CFRunLoopGetCurrent(), RUNLOOPMODE);
IOHIDManagerSetDeviceMatching(hidman, (__bridge CFDictionaryRef)matchingDict);
IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone);
mice = [[NSMutableArray alloc] init];
// Run a sub-runloop until the initial enumeration of mice is completed
while (CFRunLoopRunInMode(RUNLOOPMODE, 0, TRUE) == kCFRunLoopRunHandledSource)
// Do nothing
// Remove our callback and unschedule from the runloop
IOHIDManagerRegisterDeviceMatchingCallback(hidman, NULL, NULL);
IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), RUNLOOPMODE);
IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
CFRelease(hidman);
return [mice copy];
}
-(BOOL)hasInternalMouse {
BOOL hasInternal = NO;
for (NSString *name in self.names) {
if ([name containsString:@"Apple Internal"]) {
hasInternal = YES;
}
}
return hasInternal;
}
-(int)getCount {
return (int)self.names.count;
}
// MARK:- Mouse position
-(NSPoint)getAbsolutePosition {
CGEventRef ourEvent = CGEventCreate(NULL);
NSPoint point = CGEventGetLocation(ourEvent);
CFRelease(ourEvent);
return point;
}
-(void)setAbsolutePosition:(NSPoint)absolutePosition {
CGWarpMouseCursorPosition(absolutePosition);
CGAssociateMouseAndMouseCursorPosition(YES);
}
// MARK:- HID parameters
-(BOOL)getScrollDirectionNatural {
return [[[NSUserDefaults standardUserDefaults] objectForKey:@"com.apple.swipescrolldirection"] boolValue];
}
-(io_service_t)createIOHIDSystem {
return IORegistryEntryFromPath(kIOMasterPortDefault, kIOServicePlane ":/IOResources/IOHIDSystem");
}
-(NSDictionary *)getIOHIDParamtersFromService:(io_service_t)service {
return CFBridgingRelease(IORegistryEntryCreateCFProperty(service, CFSTR(kIOHIDParametersKey), kCFAllocatorDefault, kNilOptions));
}
-(NSDictionary *)getIOHIDParameters {
io_service_t service = [self createIOHIDSystem];
NSDictionary *parameters = [self getIOHIDParamtersFromService:service];
IOObjectRelease(service);
return parameters;
}
-(double)getTrackingSpeed {
NSDictionary *parameters = [self getIOHIDParameters];
NSNumber *accel = parameters[@"HIDMouseAcceleration"];
return accel.doubleValue / MOUSE_TRACKING_FACTOR;
}
-(kern_return_t)setTrackingSpeed:(double)trackingSpeed {
io_service_t service = [self createIOHIDSystem];
NSDictionary *parameters = [self getIOHIDParamtersFromService:service];
NSMutableDictionary *newParameters = [parameters mutableCopy];
newParameters[@"HIDMouseAcceleration"] = @(trackingSpeed * MOUSE_TRACKING_FACTOR);
kern_return_t result = IORegistryEntrySetCFProperty(service, CFSTR(kIOHIDParametersKey), (__bridge CFDictionaryRef)newParameters);
IOObjectRelease(service);
return result;
}
@end
/// hs.mouse.count([includeInternal]) -> number
/// Function
/// Gets the total number of mice connected to your system.
///
/// Parameters:
/// * includeInternal - A boolean which sets whether or not you want to include internal Trackpad's in the count. Defaults to false.
///
/// Returns:
/// * The number of mice connected to your system
///
/// Notes:
/// * This function leverages code from [ManyMouse](http://icculus.org/manymouse/).
/// * This function considers any mouse labelled as "Apple Internal Keyboard / Trackpad" to be an internal mouse.
static int mouse_count(lua_State* L) {
LuaSkin *skin = LS_API(LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK);
BOOL includeInternal = lua_toboolean(L, 1);
HSmouse *mouseManager = [[HSmouse alloc] init];
int mouseCount = mouseManager.count;
if (!includeInternal && mouseManager.hasInternalMouse) {
mouseCount--;
}
lua_pushinteger(skin.L, mouseCount);
return 1;
}
/// hs.mouse.names() -> table
/// Function
/// Gets the names of any mice connected to the system.
///
/// Parameters:
/// * None
///
/// Returns:
/// * A table containing strings of all the mice connected to the system.
///
/// Notes:
/// * This function leverages code from [ManyMouse](http://icculus.org/manymouse/).
static int mouse_names(lua_State* L) {
LuaSkin *skin = LS_API(LS_TBREAK);
HSmouse *mouseManager = [[HSmouse alloc] init];
[skin pushNSObject:mouseManager.names];
return 1;
}
/// hs.mouse.absolutePosition([point]) -> point
/// Function
/// Get or set the absolute co-ordinates of the mouse pointer
///
/// Parameters:
/// * An optional point table containing the absolute x and y co-ordinates to move the mouse pointer to
///
/// Returns:
/// * A point table containing the absolute x and y co-ordinates of the mouse pointer
///
/// Notes:
/// * If no parameters are supplied, the current position will be returned. If a point table parameter is supplied, the mouse pointer poisition will be set and the new co-ordinates returned
static int mouse_absolutePosition(lua_State *L) {
LuaSkin *skin = LS_API(LS_TTABLE|LS_TOPTIONAL, LS_TBREAK);
HSmouse *mouseManager = [[HSmouse alloc] init];
if (lua_type(skin.L, 1) == LUA_TTABLE) {
NSPoint point = [skin tableToPointAtIndex:1];
mouseManager.absolutePosition = point;
}
[skin pushNSPoint:mouseManager.absolutePosition];
return 1;
}
/// hs.mouse.trackingSpeed([speed]) -> number
/// Function
/// Gets/Sets the current system mouse tracking speed setting
///
/// Parameters:
/// * speed - An optional number containing the new tracking speed to set. If this is ommitted, the current setting is returned
///
/// Returns:
/// * A number indicating the current tracking speed setting for mice
///
/// Notes:
/// * This is represented in the System Preferences as the "Tracking speed" setting for mice
/// * Note that not all values will work, they should map to the steps defined in the System Preferences app, which are:
/// * 0.0, 0.125, 0.5, 0.6875, 0.875, 1.0, 1.5, 2.0, 2.5, 3.0
/// * Note that changes to this value will not be noticed immedaitely by macOS
static int mouse_mouseAcceleration(lua_State *L) {
LuaSkin *skin = LS_API(LS_TNUMBER | LS_TOPTIONAL, LS_TBREAK);
HSmouse *mouseManager = [[HSmouse alloc] init];
if (lua_type(skin.L, 1) == LUA_TNUMBER) {
kern_return_t result = [mouseManager setTrackingSpeed:lua_tonumber(skin.L, 1)];
if (result != KERN_SUCCESS) {
[skin logError:[NSString stringWithFormat:@"Unable to set mouse tracking speed: %d", result]];
}
}
lua_pushnumber(skin.L, mouseManager.trackingSpeed);
return 1;
}
/// hs.mouse.scrollDirection() -> string
/// Function
/// Gets the system-wide direction of scolling
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string, either "natural" or "normal"
static int mouse_scrollDirection(lua_State *L) {
LuaSkin *skin = LS_API(LS_TBREAK);
HSmouse *mouseManager = [[HSmouse alloc] init];
[skin pushNSObject:mouseManager.isScrollDirectionNatural ? @"natural" : @"normal"];
return 1;
}
//Note to future authors, there is no function to use kIOHIDTrackpadAccelerationType because it doesn't appear to do anything on modern systems.
static const luaL_Reg mouseLib[] = {
{"absolutePosition", mouse_absolutePosition},
{"trackingSpeed", mouse_mouseAcceleration},
{"scrollDirection", mouse_scrollDirection},
{"count", mouse_count},
{"names", mouse_names},
{NULL, NULL}
};
int luaopen_hs_libmouse(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin registerLibrary:"hs.mouse" functions:mouseLib metaFunctions:nil];
return 1;
}