341 lines
11 KiB
Objective-C
341 lines
11 KiB
Objective-C
#import <Foundation/Foundation.h>
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <LuaSkin/LuaSkin.h>
|
|
#import "HSuicore.h"
|
|
|
|
/// === hs.application.watcher ===
|
|
///
|
|
/// Watch for application launch/terminate events
|
|
///
|
|
/// This module is based primarily on code from the previous incarnation of Mjolnir by [Markus Engelbrecht](https://github.com/mgee) and [Steven Degutis](https://github.com/sdegutis/).
|
|
|
|
|
|
/// hs.application.watcher.launching
|
|
/// Constant
|
|
/// An application is in the process of being launched
|
|
|
|
/// hs.application.watcher.launched
|
|
/// Constant
|
|
/// An application has been launched
|
|
|
|
/// hs.application.watcher.terminated
|
|
/// Constant
|
|
/// An application has been terminated
|
|
|
|
/// hs.application.watcher.hidden
|
|
/// Constant
|
|
/// An application has been hidden
|
|
|
|
/// hs.application.watcher.unhidden
|
|
/// Constant
|
|
/// An application has been unhidden
|
|
|
|
/// hs.application.watcher.activated
|
|
/// Constant
|
|
/// An application has been activated (i.e. given keyboard/mouse focus)
|
|
|
|
/// hs.application.watcher.deactivated
|
|
/// Constant
|
|
/// An application has been deactivated (i.e. lost keyboard/mouse focus)
|
|
|
|
// Common Code
|
|
|
|
#define USERDATA_TAG "hs.application.watcher"
|
|
static LSRefTable refTable;
|
|
|
|
// Not so common code
|
|
|
|
typedef struct _appwatcher_t {
|
|
bool running;
|
|
int fn;
|
|
void* obj;
|
|
} appwatcher_t;
|
|
|
|
typedef enum _event_t {
|
|
launching = 0,
|
|
launched,
|
|
terminated,
|
|
hidden,
|
|
unhidden,
|
|
activated,
|
|
deactivated
|
|
} event_t;
|
|
|
|
@interface AppWatcher : NSObject
|
|
@property appwatcher_t* object;
|
|
- (id)initWithObject:(appwatcher_t*)object;
|
|
@end
|
|
|
|
@implementation AppWatcher
|
|
- (id)initWithObject:(appwatcher_t*)object {
|
|
if (self = [super init]) {
|
|
self.object = object;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
// Call the lua callback function and pass the application name and event type.
|
|
- (void)callback:(NSDictionary*)dict withEvent:(event_t)event {
|
|
NSRunningApplication* app = [dict objectForKey:@"NSWorkspaceApplicationKey"];
|
|
if (app == nil)
|
|
return;
|
|
if (!self.object->running)
|
|
return;
|
|
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
|
|
lua_State *L = skin.L;
|
|
_lua_stackguard_entry(L);
|
|
|
|
// Depending on the event the name of the NSRunningApplication object may not be available
|
|
// anymore. Fallback to the application name which is provided directly in the notification
|
|
// object.
|
|
NSString* appName = [app localizedName];
|
|
if (appName == nil)
|
|
appName = [dict objectForKey:@"NSApplicationName"];
|
|
|
|
[skin pushLuaRef:refTable ref:self.object->fn];
|
|
|
|
if (appName == nil) {
|
|
lua_pushnil(L);
|
|
} else {
|
|
lua_pushstring(L, [appName UTF8String]); // Parameter 1: application name
|
|
}
|
|
|
|
lua_pushinteger(L, event); // Parameter 2: the event type
|
|
|
|
HSapplication *application = [HSapplication applicationForNSRunningApplication:app withState:L];
|
|
[skin pushNSObject:application];
|
|
|
|
[skin protectedCallAndError:@"hs.application.watcher callback" nargs:3 nresults:0];
|
|
_lua_stackguard_exit(L);
|
|
}
|
|
|
|
- (void)applicationWillLaunch:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:launching];
|
|
}
|
|
|
|
- (void)applicationLaunched:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:launched];
|
|
}
|
|
|
|
- (void)applicationTerminated:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:terminated];
|
|
}
|
|
|
|
- (void)applicationHidden:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:hidden];
|
|
}
|
|
|
|
- (void)applicationUnhidden:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:unhidden];
|
|
}
|
|
|
|
- (void)applicationActivated:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:activated];
|
|
}
|
|
|
|
- (void)applicationDeactivated:(NSNotification*)notification {
|
|
[self callback:[notification userInfo] withEvent:deactivated];
|
|
}
|
|
@end
|
|
|
|
/// hs.application.watcher.new(fn) -> watcher
|
|
/// Constructor
|
|
/// Creates an application event watcher
|
|
///
|
|
/// Parameters:
|
|
/// * fn - A function that will be called when application events happen. It should accept three parameters:
|
|
/// * A string containing the name of the application
|
|
/// * An event type (see the constants defined above)
|
|
/// * An `hs.application` object representing the application, or nil if the application couldn't be found
|
|
///
|
|
/// Returns:
|
|
/// * An `hs.application.watcher` object
|
|
///
|
|
/// Notes:
|
|
/// * If the function is called with an event type of `hs.application.watcher.terminated` then the application name parameter will be `nil` and the `hs.application` parameter, will only be useful for getting the UNIX process ID (i.e. the PID) of the application
|
|
static int app_watcher_new(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
|
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
|
|
appwatcher_t* appWatcher = lua_newuserdata(L, sizeof(appwatcher_t));
|
|
memset(appWatcher, 0, sizeof(appwatcher_t));
|
|
|
|
lua_pushvalue(L, 1);
|
|
appWatcher->fn = [skin luaRef:refTable];
|
|
appWatcher->running = NO;
|
|
appWatcher->obj = (__bridge_retained void*) [[AppWatcher alloc] initWithObject:appWatcher];
|
|
|
|
luaL_getmetatable(L, USERDATA_TAG);
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
|
|
// Register the AppWatcher as observer for application specific events.
|
|
static void register_observer(AppWatcher* observer) {
|
|
// It is crucial to use the shared workspace notification center here.
|
|
// Otherwise the will not receive the events we are interested in.
|
|
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
|
[center addObserver:observer
|
|
selector:@selector(applicationWillLaunch:)
|
|
name:NSWorkspaceWillLaunchApplicationNotification
|
|
object:nil];
|
|
[center addObserver:observer
|
|
selector:@selector(applicationLaunched:)
|
|
name:NSWorkspaceDidLaunchApplicationNotification
|
|
object:nil];
|
|
[center addObserver:observer
|
|
selector:@selector(applicationTerminated:)
|
|
name:NSWorkspaceDidTerminateApplicationNotification
|
|
object:nil];
|
|
|
|
[center addObserver:observer
|
|
selector:@selector(applicationHidden:)
|
|
name:NSWorkspaceDidHideApplicationNotification
|
|
object:nil];
|
|
[center addObserver:observer
|
|
selector:@selector(applicationUnhidden:)
|
|
name:NSWorkspaceDidUnhideApplicationNotification
|
|
object:nil];
|
|
|
|
[center addObserver:observer
|
|
selector:@selector(applicationActivated:)
|
|
name:NSWorkspaceDidActivateApplicationNotification
|
|
object:nil];
|
|
[center addObserver:observer
|
|
selector:@selector(applicationDeactivated:)
|
|
name:NSWorkspaceDidDeactivateApplicationNotification
|
|
object:nil];
|
|
}
|
|
|
|
// Unregister the AppWatcher as observer for all events.
|
|
static void unregister_observer(AppWatcher* observer) {
|
|
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
|
[center removeObserver:observer name:NSWorkspaceWillLaunchApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidLaunchApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidTerminateApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidHideApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidUnhideApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidActivateApplicationNotification object:nil];
|
|
[center removeObserver:observer name:NSWorkspaceDidDeactivateApplicationNotification object:nil];
|
|
}
|
|
|
|
/// hs.application.watcher:start()
|
|
/// Method
|
|
/// Starts the application watcher
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The `hs.application.watcher` object
|
|
static int app_watcher_start(lua_State* L) {
|
|
appwatcher_t* appWatcher = luaL_checkudata(L, 1, USERDATA_TAG);
|
|
lua_settop(L, 1);
|
|
|
|
if (appWatcher->running)
|
|
return 0;
|
|
|
|
appWatcher->running = YES;
|
|
register_observer((__bridge AppWatcher*)appWatcher->obj);
|
|
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.application.watcher:stop()
|
|
/// Method
|
|
/// Stops the application watcher
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * The `hs.application.watcher` object
|
|
static int app_watcher_stop(lua_State* L) {
|
|
appwatcher_t* appWatcher = luaL_checkudata(L, 1, USERDATA_TAG);
|
|
lua_settop(L, 1);
|
|
|
|
if (!appWatcher->running)
|
|
return 0;
|
|
|
|
appWatcher->running = NO;
|
|
unregister_observer((__bridge id)appWatcher->obj);
|
|
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
// Perform cleanup if the AppWatcher is not required anymore.
|
|
static int app_watcher_gc(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
|
|
appwatcher_t* appWatcher = luaL_checkudata(L, 1, USERDATA_TAG);
|
|
|
|
app_watcher_stop(L);
|
|
|
|
appWatcher->fn = [skin luaUnref:refTable ref:appWatcher->fn];
|
|
|
|
AppWatcher* object = (__bridge_transfer AppWatcher*)appWatcher->obj;
|
|
object = nil;
|
|
return 0;
|
|
}
|
|
|
|
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 meta_gc(lua_State* __unused L) {
|
|
return 0;
|
|
}
|
|
|
|
// Add a single event enum value to the lua table.
|
|
static void add_event_value(lua_State* L, event_t value, const char* name) {
|
|
lua_pushinteger(L, value);
|
|
lua_setfield(L, -2, name);
|
|
}
|
|
|
|
// Add the event_t enum to the lua table.
|
|
static void add_event_enum(lua_State* L) {
|
|
add_event_value(L, launching, "launching");
|
|
add_event_value(L, launched, "launched");
|
|
add_event_value(L, terminated, "terminated");
|
|
add_event_value(L, hidden, "hidden");
|
|
add_event_value(L, unhidden, "unhidden");
|
|
add_event_value(L, activated, "activated");
|
|
add_event_value(L, deactivated, "deactivated");
|
|
}
|
|
|
|
// Metatable for created objects when _new invoked
|
|
static const luaL_Reg metaLib[] = {
|
|
{"start", app_watcher_start},
|
|
{"stop", app_watcher_stop},
|
|
{"__tostring", userdata_tostring},
|
|
{"__gc", app_watcher_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Functions for returned object when module loads
|
|
static const luaL_Reg appLib[] = {
|
|
{"new", app_watcher_new},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Metatable for returned object when module loads
|
|
static const luaL_Reg metaGcLib[] = {
|
|
{"__gc", meta_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Called when loading the module. All necessary tables need to be registered here.
|
|
int luaopen_hs_libapplicationwatcher(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
refTable = [skin registerLibraryWithObject:USERDATA_TAG functions:appLib metaFunctions:metaGcLib objectFunctions:metaLib];
|
|
|
|
add_event_enum(L);
|
|
|
|
return 1;
|
|
}
|