hammerspoon/extensions/window/libwindow.m

822 lines
26 KiB
Objective-C

#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
#import <LuaSkin/LuaSkin.h>
#import "HSuicore.h"
static const char *USERDATA_TAG = "hs.window";
static LSRefTable refTable = LUA_NOREF;
#define get_objectFromUserdata(objType, L, idx, tag) (objType*)*((void**)luaL_checkudata(L, idx, tag))
#pragma mark - Helper functions
static AXUIElementRef system_wide_element() {
static AXUIElementRef element;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
element = AXUIElementCreateSystemWide();
});
return element;
}
/// hs.window.list(allWindows) -> table
/// Function
/// Gets a table containing all the window data retrieved from `CGWindowListCreate`.
///
/// Parameters:
/// * allWindows - Get all the windows, even those "below" the Dock window.
///
/// Returns:
/// * `true` is succesful otherwise `false` if an error occured.
///
/// Notes:
/// * This allows you to get window information without Accessibility Permissions.
static int window_list(lua_State* L) {
// SOURCE: https://stackoverflow.com/a/15985829/6925202
BOOL allWindows = lua_toboolean(L, 1);
// Fetch all on screen windows
CFArrayRef windowListArray = CGWindowListCreate(kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements, kCGNullWindowID);
NSArray *windows = CFBridgingRelease(CGWindowListCreateDescriptionFromArray(windowListArray));
if (!allWindows) {
// Find window ID of "Dock" window
NSNumber *dockWindowNumber = nil;
for (NSDictionary *window in windows) {
if ([(NSString *)window[(__bridge NSString *)kCGWindowName] isEqualToString:@"Dock"]) {
dockWindowNumber = window[(__bridge NSString *)kCGWindowNumber];
break;
}
}
if (dockWindowNumber) {
// Fetch on screen windows again, filtering to those "below" the Dock window
// This filters out all but the "standard" application windows
CFRelease(windowListArray);
windowListArray = CGWindowListCreate(kCGWindowListOptionOnScreenBelowWindow|kCGWindowListExcludeDesktopElements, [dockWindowNumber unsignedIntValue]);
windows = CFBridgingRelease(CGWindowListCreateDescriptionFromArray(windowListArray));
}
}
CFRelease(windowListArray);
[[LuaSkin sharedWithState:NULL] pushNSObject:windows] ;
return 1 ;
}
/// hs.window.timeout(value) -> boolean
/// Function
/// Sets the timeout value used in the accessibility API.
///
/// Parameters:
/// * value - The number of seconds for the new timeout value.
///
/// Returns:
/// * `true` is succesful otherwise `false` if an error occured.
static int window_timeout(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
[skin checkArgs: LS_TNUMBER, LS_TBREAK] ;
NSNumber *value = [skin toNSObjectAtIndex:1] ;
float fvalue = [value floatValue];
AXError result = AXUIElementSetMessagingTimeout(system_wide_element(), fvalue);
if (result == kAXErrorIllegalArgument) {
[LuaSkin logError:@"hs.window.timeout() - One or more of the arguments is an illegal value (timeout values must be positive)."];
lua_pushboolean(L, false);
return 1;
}
if (result == kAXErrorInvalidUIElement) {
[LuaSkin logError:@"hs.window.timeout() - The AXUIElementRef is invalid."];
lua_pushboolean(L, false);
return 1;
}
lua_pushboolean(L, true);
return 1;
}
/// hs.window.focusedWindow() -> window
/// Constructor
/// Returns the window that has keyboard/mouse focus
///
/// Parameters:
/// * None
///
/// Returns:
/// * An `hs.window` object representing the currently focused window
static int window_focusedwindow(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBREAK];
[skin pushNSObject:[HSwindow focusedWindow]];
return 1;
}
/// hs.window:title() -> string
/// Method
/// Gets the title of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the title of the window
static int window_title(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSObject:win.title];
return 1;
}
/// hs.window:subrole() -> string
/// Method
/// Gets the subrole of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the subrole of the window
///
/// Notes:
/// * This typically helps to determine if a window is a special kind of window - such as a modal window, or a floating window
static int window_subrole(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSObject:win.subRole];
return 1;
}
/// hs.window:role() -> string
/// Method
/// Gets the role of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the role of the window
static int window_role(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSObject:win.role];
return 1;
}
/// hs.window:isStandard() -> bool
/// Method
/// Determines if the window is a standard window
///
/// Parameters:
/// * None
///
/// Returns:
/// * True if the window is standard, otherwise false
///
/// Notes:
/// * "Standard window" means that this is not an unusual popup window, a modal dialog, a floating window, etc.
static int window_isstandard(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushboolean(L, win.isStandard);
return 1;
}
/// hs.window:topLeft() -> point
/// Method
/// Gets the absolute co-ordinates of the top left of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A point-table containing the absolute co-ordinates of the top left corner of the window
static int window__topleft(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSPoint:win.topLeft];
return 1;
}
/// hs.window:size() -> size
/// Method
/// Gets the size of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A size-table containing the width and height of the window
static int window__size(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSSize:win.size];
return 1;
}
/// hs.window:setTopLeft(point) -> window
/// Method
/// Moves the window to a given point
///
/// Parameters:
/// * point - A point-table containing the absolute co-ordinates the window should be moved to
///
/// Returns:
/// * The `hs.window` object
static int window__settopleft(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TTABLE, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
win.topLeft = [skin tableToPointAtIndex:2];
lua_pushvalue(L, 1);
return 1;
}
//TODO window__setframe, but it's Yosemite only :/
//https://developer.apple.com/library/prerelease/mac/documentation/AppKit/Reference/NSAccessibility_Protocol_Reference/index.html#//apple_ref/occ/intfp/NSAccessibility/accessibilityFrame
/// hs.window:setSize(size) -> window
/// Method
/// Resizes the window
///
/// Parameters:
/// * size - A size-table containing the width and height the window should be resized to
///
/// Returns:
/// * The `hs.window` object
static int window__setsize(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TTABLE, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
win.size = [skin tableToSizeAtIndex:2];
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:toggleZoom() -> window
/// Method
/// Toggles the zoom state of the window (this is effectively equivalent to clicking the green maximize/fullscreen button at the top left of a window)
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.window` object
static int window__togglezoom(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[win toggleZoom];
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:zoomButtonRect() -> rect-table or nil
/// Method
/// Gets a rect-table for the location of the zoom button (the green button typically found at the top left of a window)
///
/// Parameters:
/// * None
///
/// Returns:
/// * A rect-table containing the bounding frame of the zoom button, or nil if an error occured
///
/// Notes:
/// * The co-ordinates in the rect-table (i.e. the `x` and `y` values) are in absolute co-ordinates, not relative to the window the button is part of, or the screen the window is on
/// * Although not perfect as such, this method can provide a useful way to find a region of the titlebar suitable for simulating mouse click events on, with `hs.eventtap`
static int window_getZoomButtonRect(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSRect:win.zoomButtonRect];
return 1;
}
/// hs.window:isMaximizable() -> bool or nil
/// Method
/// Determines if a window is maximizable
///
/// Parameters:
/// * None
///
/// Returns:
/// * True if the window is maximizable, False if it isn't, or nil if an error occurred
static int window_isMaximizable(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
AXUIElementRef button = nil;
CFBooleanRef isEnabled;
if (AXUIElementCopyAttributeValue(win.elementRef, kAXZoomButtonAttribute, (CFTypeRef*)&button) != noErr) goto cleanup;
if (AXUIElementCopyAttributeValue(button, kAXEnabledAttribute, (CFTypeRef*)&isEnabled) != noErr) goto cleanup;
lua_pushboolean(L, isEnabled == kCFBooleanTrue ? true : false);
return 1;
cleanup:
lua_pushnil(L);
return 1;
}
/// hs.window:close() -> bool
/// Method
/// Closes the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * True if the operation succeeded, false if not
static int window__close(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushboolean(L, [win close]);
return 1;
}
/// hs.window:focusTab(index) -> bool
/// Method
/// Focuses the tab in the window's tab group at index, or the last tab if index is out of bounds
///
/// Parameters:
/// * index - A number, a 1-based index of a tab to focus
///
/// Returns:
/// * true if the tab was successfully pressed, or false if there was a problem
///
/// Notes:
/// * This method works with document tab groups and some app tabs, like Chrome and Safari.
static int window_focustab(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TINTEGER, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
int tabIndex = (int)lua_tointeger(L, 2);
lua_pushboolean(L, [win focusTab:tabIndex]);
return 1;
}
/// hs.window:tabCount() -> number or nil
/// Method
/// Gets the number of tabs in the window has
///
/// Parameters:
/// * None
///
/// Returns:
/// * A number containing the number of tabs, or nil if an error occurred
///
/// Notes:
/// * Intended for use with the focusTab method, if this returns a number, then focusTab can switch between that many tabs.
static int window_tabcount(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushinteger(L, win.tabCount);
return 1;
}
/// hs.window:setFullScreen(fullscreen) -> window
/// Method
/// Sets the fullscreen state of the window
///
/// Parameters:
/// * fullscreen - A boolean, true if the window should be set fullscreen, false if not
///
/// Returns:
/// * The `hs.window` object
static int window__setfullscreen(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
win.fullscreen = lua_toboolean(L, 2);
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:isFullScreen() -> bool or nil
/// Method
/// Gets the fullscreen state of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * True if the window is fullscreen, false if not. Nil if an error occurred
static int window_isfullscreen(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushboolean(L, win.fullscreen);
return 1;
}
/// hs.window:minimize() -> window
/// Method
/// Minimizes the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.window` object
///
/// Notes:
/// * This method will always animate per your system settings and is not affected by `hs.window.animationDuration`
static int window__minimize(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
win.minimized = YES;
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:unminimize() -> window
/// Method
/// Un-minimizes the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.window` object
static int window__unminimize(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
win.minimized = NO;
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:isMinimized() -> bool
/// Method
/// Gets the minimized state of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * True if the window is minimized, otherwise false
static int window_isminimized(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushboolean(L, win.minimized);
return 1;
}
// hs.window:pid()
static int window_pid(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushinteger(L, win.pid);
return 1;
}
/// hs.window:application() -> app or nil
/// Method
/// Gets the `hs.application` object the window belongs to
///
/// Parameters:
/// * None
///
/// Returns:
/// * An `hs.application` object representing the application that owns the window, or nil if an error occurred
static int window_application(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
HSapplication *app = [[HSapplication alloc] initWithPid:win.pid withState:L];
[skin pushNSObject:app];
return 1;
}
/// hs.window:becomeMain() -> window
/// Method
/// Makes the window the main window of its application
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.window` object
///
/// Notes:
/// * Make a window become the main window does not transfer focus to the application. See `hs.window.focus()`
static int window_becomemain(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[win becomeMain];
lua_pushvalue(L, 1);
return 1;
}
/// hs.window:raise() -> window
/// Method
/// Brings a window to the front of the screen without focussing it
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.window` object
static int window_raise(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[win raise];
lua_pushvalue(L, 1);
return 1;
}
static int window__orderedwinids(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBREAK];
[skin pushNSObject:[HSwindow orderedWindowIDs]];
return 1;
}
/// hs.window:id() -> number or nil
/// Method
/// Gets the unique identifier of the window
///
/// Parameters:
/// * None
///
/// Returns:
/// * A number containing the unique identifier of the window, or nil if an error occurred
static int window_id(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushinteger(L, win.winID);
return 1;
}
/// hs.window.setShadows(shadows)
/// Function
/// Enables/Disables window shadows
///
/// Parameters:
/// * shadows - A boolean, true to show window shadows, false to hide window shadows
///
/// Returns:
/// * None
///
/// Notes:
/// * This function uses a private, undocumented OS X API call, so it is not guaranteed to work in any future OS X release
static int window_setShadows(lua_State* L) {
luaL_checktype(L, 1, LUA_TBOOLEAN);
BOOL shadows = lua_toboolean(L, 1);
// CoreGraphics private API for window shadows
#define kCGSDebugOptionNormal 0
#define kCGSDebugOptionNoShadows 16384
void CGSSetDebugOptions(int);
CGSSetDebugOptions(shadows ? kCGSDebugOptionNormal : kCGSDebugOptionNoShadows);
return 0;
}
/// hs.window.snapshotForID(ID [, keepTransparency]) -> hs.image-object
/// Function
/// Returns a snapshot of the window specified by the ID as an `hs.image` object
///
/// Parameters:
/// * ID - Window ID of the window to take a snapshot of.
/// * keepTransparency - optional boolean value indicating if the windows alpha value (transparency) should be maintained in the resulting image or if it should be fully opaque (default).
///
/// Returns:
/// * `hs.image` object of the window snapshot or nil if unable to create a snapshot
///
/// Notes:
/// * See also method `hs.window:snapshot()`
/// * Because the window ID cannot always be dynamically determined, this function will allow you to provide the ID of a window that was cached earlier.
static int window_snapshotForID(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TNUMBER|LS_TSTRING, LS_TBOOLEAN|LS_TOPTIONAL, LS_TBREAK];
CGWindowID windowID = (CGWindowID)lua_tointeger(L, 1);
[skin pushNSObject:[HSwindow snapshotForID:windowID keepTransparency:lua_toboolean(L, 2)]];
return 1;
}
/// hs.window:snapshot([keepTransparency]) -> hs.image-object
/// Method
/// Returns a snapshot of the window as an `hs.image` object
///
/// Parameters:
/// * keepTransparency - optional boolean value indicating if the windows alpha value (transparency) should be maintained in the resulting image or if it should be fully opaque (default).
///
/// Returns:
/// * `hs.image` object of the window snapshot or nil if unable to create a snapshot
///
/// Notes:
/// * See also function `hs.window.snapshotForID()`
static int window_snapshot(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBOOLEAN|LS_TOPTIONAL, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
[skin pushNSObject:[win snapshot:lua_toboolean(L, 2)]];
return 1;
}
#pragma mark - hs.uielement methods
static int window_uielement_isApplication(lua_State *L) {
// This method is a clone of what happens in hs.uielement:isApplication(), since hs.window objects have to conform to hs.uielement methods
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSapplication *app = [skin toNSObjectAtIndex:1];
HSuielement *uiElement = app.uiElement;
lua_pushboolean(L, [uiElement.role isEqualToString:@"AXApplication"]);
return 1;
}
static int window_uielement_isWindow(lua_State *L) {
// This method is a clone of what happens in hs.uielement:isWindow(), since hs.window objects have to conform to hs.uielement methods
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSapplication *app = [skin toNSObjectAtIndex:1];
HSuielement *uiElement = app.uiElement;
lua_pushboolean(L, uiElement.isWindow);
return 1;
}
static int window_uielement_role(lua_State *L) {
// This method is a clone of what happens in hs.uielement:role(), since hs.window objects have to conform to hs.uielement methods
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSapplication *app = [skin toNSObjectAtIndex:1];
HSuielement *uiElement = app.uiElement;
[skin pushNSObject:uiElement.role];
return 1;
}
static int window_uielement_selectedText(lua_State *L) {
// This method is a clone of what happens in hs.uielement:selectedText(), since hs.window objects have to conform to hs.uielement methods
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSapplication *app = [skin toNSObjectAtIndex:1];
HSuielement *uiElement = app.uiElement;
[skin pushNSObject:uiElement.selectedText];
return 1;
}
static int window_uielement_newWatcher(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION, LS_TANY|LS_TOPTIONAL, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
HSuielement *uiElement = win.uiElement;
HSuielementWatcher *watcher = [uiElement newWatcherAtIndex:2 withUserdataAtIndex:3 withLuaState:L];
[skin pushNSObject:watcher];
return 1;
}
#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 pushHSwindow(lua_State *L, id obj) {
HSwindow *value = obj;
value.selfRefCount++;
void** valuePtr = lua_newuserdata(L, sizeof(HSwindow *));
*valuePtr = (__bridge_retained void *)value;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
return 1;
}
static id toHSwindowFromLua(lua_State *L, int idx) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSwindow *value;
if (luaL_testudata(L, idx, USERDATA_TAG)) {
value = get_objectFromUserdata(__bridge HSwindow, 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 userdata_tostring(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = [skin toNSObjectAtIndex:1];
lua_pushstring(L, [NSString stringWithFormat:@"%s: %@ (%p)", USERDATA_TAG, win.title, lua_topointer(L, 1)].UTF8String);
return 1 ;
}
static int userdata_eq(lua_State *L) {
BOOL isEqual = NO;
if (luaL_testudata(L, 1, USERDATA_TAG) && luaL_testudata(L, 2, USERDATA_TAG)) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSwindow *win1 = [skin toNSObjectAtIndex:1];
HSwindow *win2 = [skin toNSObjectAtIndex:2];
isEqual = CFEqual(win1.elementRef, win2.elementRef);
}
lua_pushboolean(L, isEqual);
return 1;
}
static int userdata_gc(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
HSwindow *win = get_objectFromUserdata(__bridge_transfer HSwindow, L, 1, USERDATA_TAG);
if (win) {
win.selfRefCount--;
if (win.selfRefCount == 0) {
win = nil;
}
}
// Remove the Metatable so future use of the variable in Lua won't think it's valid
lua_pushnil(L);
lua_setmetatable(L, 1);
return 0;
}
// Module functions
static const luaL_Reg moduleLib[] = {
{"focusedWindow", window_focusedwindow},
{"_orderedwinids", window__orderedwinids},
{"setShadows", window_setShadows},
{"snapshotForID", window_snapshotForID},
{"timeout", window_timeout},
{"list", window_list},
{NULL, NULL}
};
static const luaL_Reg module_metaLib[] = {
{NULL, NULL}
};
// Metatable for userdata objects
static const luaL_Reg userdata_metaLib[] = {
{"title", window_title},
{"subrole", window_subrole},
{"role", window_role},
{"isStandard", window_isstandard},
{"_topLeft", window__topleft},
{"_size", window__size},
{"_setTopLeft", window__settopleft},
{"_setSize", window__setsize},
{"_minimize", window__minimize},
{"_unminimize", window__unminimize},
{"isMinimized", window_isminimized},
{"isMaximizable", window_isMaximizable},
{"pid", window_pid},
{"application", window_application},
{"focusTab", window_focustab},
{"tabCount", window_tabcount},
{"becomeMain", window_becomemain},
{"raise", window_raise},
{"id", window_id},
{"_toggleZoom", window__togglezoom},
{"zoomButtonRect", window_getZoomButtonRect},
{"_close", window__close},
{"_setFullScreen", window__setfullscreen},
{"isFullScreen", window_isfullscreen},
{"snapshot", window_snapshot},
// hs.uielement methods
{"isApplication", window_uielement_isApplication},
{"isWindow", window_uielement_isWindow},
{"role", window_uielement_role},
{"selectedText", window_uielement_selectedText},
{"newWatcher", window_uielement_newWatcher},
{"__tostring", userdata_tostring},
{"__eq", userdata_eq},
{"__gc", userdata_gc},
{NULL, NULL}
};
int luaopen_hs_libwindow(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
refTable = [skin registerLibrary:USERDATA_TAG functions:moduleLib metaFunctions:module_metaLib];
[skin registerObject:USERDATA_TAG objectFunctions:userdata_metaLib];
[skin registerPushNSHelper:pushHSwindow forClass:"HSwindow"];
[skin registerLuaObjectHelper:toHSwindowFromLua forClass:"HSwindow"
withUserdataMapping:USERDATA_TAG];
return 1;
}