hammerspoon/extensions/distributednotifications/libdistributednotifications.m

222 lines
7.3 KiB
Objective-C

@import Foundation;
@import Cocoa;
#import <LuaSkin/LuaSkin.h>
#define USERDATA_TAG "hs.distributednotifications"
static LSRefTable refTable = LUA_NOREF;
typedef struct _distnot_t {
void *watcher;
} distnot_t;
#pragma mark - HSDistNotWatcher Definition
@interface HSDistNotWatcher : NSObject
@property int fnRef ;
@property (copy, nonatomic) NSString *object;
@property (copy, nonatomic) NSString *name;
@end
@implementation HSDistNotWatcher
- (void)callback:(NSNotification *)note {
if (self.fnRef != LUA_NOREF && self.fnRef != LUA_REFNIL) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:self.fnRef];
[skin pushNSObject:note.name];
[skin pushNSObject:note.object];
[skin pushNSObject:note.userInfo];
[skin protectedCallAndError:@"hs.distributednotification callback" nargs:3 nresults:0];
_lua_stackguard_exit(skin.L);
}
}
@end
#pragma mark - Module Functions
/// hs.distributednotifications.new(callback[, name[, object]]) -> object
/// Constructor
/// Creates a new NSDistributedNotificationCenter watcher
///
/// Parameters:
/// * callback - A function to be called when a matching notification arrives. The function should accept one argument:
/// * notificationName - A string containing the name of the notification
/// * name - An optional string containing the name of notifications to watch for. A value of `nil` will cause all notifications to be watched on macOS versions earlier than Catalina. Defaults to `nil`.
/// * object - An optional string containing the name of sending objects to watch for. A value of `nil` will cause all sending objects to be watched. Defaults to `nil`.
///
/// Returns:
/// * An `hs.distributednotifications` object
///
/// Notes:
/// * On Catalina and above, it is no longer possible to observe all notifications - the `name` parameter is effectively now required. See https://mjtsai.com/blog/2019/10/04/nsdistributednotificationcenter-no-longer-supports-nil-names/
static int distnot_new(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TFUNCTION, LS_TSTRING|LS_TNIL|LS_TOPTIONAL, LS_TSTRING|LS_TNIL|LS_TOPTIONAL, LS_TBREAK];
NSString *name = lua_isnoneornil(L, 2) ? nil : [skin toNSObjectAtIndex:2];
NSString *obj = lua_isnoneornil(L, 3) ? nil : [skin toNSObjectAtIndex:3];
distnot_t *userData = lua_newuserdata(L, sizeof(distnot_t));
memset(userData, 0, sizeof(distnot_t));
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
HSDistNotWatcher *watcher = [[HSDistNotWatcher alloc] init];
userData->watcher = (__bridge_retained void*)watcher;
lua_pushvalue(L, 1);
watcher.fnRef = [skin luaRef:refTable];
watcher.name = name;
watcher.object = obj;
return 1;
}
#pragma mark - Module Methods
/// hs.distributednotifications.post(name[, sender[, userInfo]])
/// Function
/// Sends a distributed notification
///
/// Parameters:
/// * name - A string containing the name of the notification
/// * sender - An optional string containing the name of the sender of the notification (in the form `com.domain.application.foo`). Defaults to nil.
/// * userInfo - An optional table containing additional information to post with the notification. Defaults to nil.
///
/// Returns:
/// * None
static int distnot_post(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TSTRING, LS_TSTRING | LS_TNIL | LS_TOPTIONAL, LS_TTABLE | LS_TNIL | LS_TOPTIONAL, LS_TBREAK];
NSString *object;
NSDictionary *userInfo;
if (lua_isnoneornil(L, 2)) {
object = nil;
} else {
object = [skin toNSObjectAtIndex:2];
}
if (lua_isnoneornil(L, 3)) {
userInfo = nil;
} else {
userInfo = [skin toNSObjectAtIndex:3];
}
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center postNotificationName:[skin toNSObjectAtIndex:1] object:object userInfo:userInfo deliverImmediately:YES];
return 0;
}
#pragma mark - Module Methods
/// hs.distributednotifications:start() -> object
/// Method
/// Starts a NSDistributedNotificationCenter watcher
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.distributednotifications` object
static int distnot_start(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
distnot_t *userData = lua_touserdata(L, 1);
HSDistNotWatcher *watcher = (__bridge HSDistNotWatcher *)userData->watcher;
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center addObserver:watcher selector:@selector(callback:) name:watcher.name object:watcher.object suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
lua_pushvalue(L, 1);
return 1;
}
/// hs.distributednotifications:stop() -> object
/// Method
/// Stops a NSDistributedNotificationCenter watcher
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.distributednotifications` object
static int distnot_stop(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
distnot_t *userData = lua_touserdata(L, 1);
HSDistNotWatcher *watcher = (__bridge HSDistNotWatcher *)userData->watcher;
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center removeObserver:watcher name:watcher.name object:watcher.object];
lua_pushvalue(L, 1);
return 1;
}
#pragma mark - Hammerspoon Infrastructure
static int userdata_tostring(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
distnot_t *userData = lua_touserdata(L, 1);
HSDistNotWatcher *watcher = (__bridge HSDistNotWatcher *)userData->watcher;
[skin pushNSObject:[NSString stringWithFormat:@"%s: name: %@ object: %@ (%p)", USERDATA_TAG, watcher.name, watcher.object, (void *)watcher]];
return 1;
}
static int userdata_gc(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
distnot_t *userData = lua_touserdata(L, 1);
HSDistNotWatcher *watcher = (__bridge_transfer HSDistNotWatcher *)userData->watcher;
NSDistributedNotificationCenter *center = [NSDistributedNotificationCenter defaultCenter];
[center removeObserver:watcher name:watcher.name object:watcher.object];
watcher.fnRef = [skin luaUnref:refTable ref:watcher.fnRef];
watcher = 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;
}
static const luaL_Reg distributednotificationslib[] = {
{"new", distnot_new},
{"post", distnot_post},
{NULL, NULL}
};
// Metatable for userdata objects
static const luaL_Reg userdata_metaLib[] = {
{"start", distnot_start},
{"stop", distnot_stop},
{"__tostring", userdata_tostring},
{"__gc", userdata_gc},
{NULL, NULL}
};
int luaopen_hs_libdistributednotifications(lua_State* L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
refTable = [skin registerLibrary:USERDATA_TAG functions:distributednotificationslib metaFunctions:nil];
[skin registerObject:USERDATA_TAG objectFunctions:userdata_metaLib];
return 1;
}