717 lines
28 KiB
Objective-C
717 lines
28 KiB
Objective-C
@import Cocoa ;
|
|
@import LuaSkin ;
|
|
@import SystemConfiguration ;
|
|
@import SystemConfiguration.SCDynamicStoreCopyDHCPInfo ;
|
|
|
|
#define USERDATA_TAG "hs.network.configuration"
|
|
static LSRefTable refTable = LUA_NOREF;
|
|
static dispatch_queue_t dynamicStoreQueue = nil ;
|
|
|
|
#define get_structFromUserdata(objType, L, idx) ((objType *)luaL_checkudata(L, idx, USERDATA_TAG))
|
|
|
|
#pragma mark - Support Functions and Classes
|
|
|
|
typedef struct _dynamicstore_t {
|
|
SCDynamicStoreRef storeObject;
|
|
int callbackRef ;
|
|
int selfRef ;
|
|
BOOL watcherEnabled ;
|
|
LSGCCanary lsCanary;
|
|
} dynamicstore_t;
|
|
|
|
static void doDynamicStoreCallback(__unused SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) {
|
|
dynamicstore_t *thePtr = (dynamicstore_t *)info ;
|
|
NSArray *nsChangedKeys = [(__bridge NSArray *)changedKeys copy];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ((thePtr->callbackRef != LUA_NOREF) && (thePtr->selfRef != LUA_NOREF)) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:NULL] ;
|
|
lua_State *L = [skin L] ;
|
|
if (![skin checkGCCanary:thePtr->lsCanary]) {
|
|
return;
|
|
}
|
|
_lua_stackguard_entry(L);
|
|
[skin pushLuaRef:refTable ref:thePtr->callbackRef] ;
|
|
[skin pushLuaRef:refTable ref:thePtr->selfRef] ;
|
|
if (changedKeys) {
|
|
[skin pushNSObject:nsChangedKeys] ;
|
|
} else {
|
|
lua_pushnil(L) ;
|
|
}
|
|
[skin protectedCallAndError:@"hs.network.configuration callback" nargs:2 nresults:0];
|
|
_lua_stackguard_exit(L);
|
|
}
|
|
}) ;
|
|
}
|
|
|
|
#pragma mark - Module Functions
|
|
|
|
/// hs.network.configuration.open() -> storeObject
|
|
/// Constructor
|
|
/// Opens a session to the dynamic store maintained by the System Configuration server.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * the storeObject
|
|
static int newStoreObject(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TBREAK] ;
|
|
NSString *theName = [[NSUUID UUID] UUIDString] ;
|
|
dynamicstore_t *thePtr = lua_newuserdata(L, sizeof(dynamicstore_t)) ;
|
|
memset(thePtr, 0, sizeof(dynamicstore_t)) ;
|
|
|
|
SCDynamicStoreContext context = { 0, NULL, NULL, NULL, NULL };
|
|
context.info = (void *)thePtr;
|
|
SCDynamicStoreRef theStore = SCDynamicStoreCreate(kCFAllocatorDefault, (__bridge CFStringRef)theName, doDynamicStoreCallback, &context );
|
|
if (theStore) {
|
|
thePtr->storeObject = CFRetain(theStore) ;
|
|
thePtr->callbackRef = LUA_NOREF ;
|
|
thePtr->selfRef = LUA_NOREF ;
|
|
thePtr->watcherEnabled = NO ;
|
|
thePtr->lsCanary = [skin createGCCanary];
|
|
|
|
luaL_getmetatable(L, USERDATA_TAG) ;
|
|
lua_setmetatable(L, -2) ;
|
|
// SCDynamicStoreSetDispatchQueue(thePtr->storeObject, dynamicStoreQueue);
|
|
CFRelease(theStore) ; // we retained it in the structure, so release it here
|
|
} else {
|
|
return luaL_error(L, "** unable to get dynamicStore reference:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
#pragma mark - Module Methods
|
|
|
|
/// hs.network.configuration:contents([keys], [pattern]) -> table
|
|
/// Method
|
|
/// Return the contents of the store for the specified keys or keys matching the specified pattern(s)
|
|
///
|
|
/// Parameters:
|
|
/// * keys - a string or table of strings containing the keys or patterns of keys, if `pattern` is true. Defaults to all keys.
|
|
/// * pattern - a boolean indicating wether or not the string(s) provided are to be considered regular expression patterns (true) or literal strings to match (false). Defaults to false.
|
|
///
|
|
/// Returns:
|
|
/// * a table of key-value pairs from the dynamic store which match the specified keys or key patterns.
|
|
///
|
|
/// Notes:
|
|
/// * if no parameters are provided, then all key-value pairs in the dynamic store are returned.
|
|
static int dynamicStoreContents(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG,
|
|
LS_TSTRING | LS_TTABLE | LS_TOPTIONAL,
|
|
LS_TBOOLEAN | LS_TOPTIONAL,
|
|
LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
NSArray *keys ;
|
|
BOOL keysIsPattern = NO ;
|
|
if (lua_gettop(L) == 1) {
|
|
keys = @[ @".*" ] ;
|
|
keysIsPattern = YES ;
|
|
} else {
|
|
if (lua_type(L, 2) == LUA_TTABLE) {
|
|
keys = [skin toNSObjectAtIndex:2] ;
|
|
} else {
|
|
keys = [NSArray arrayWithObject:[skin toNSObjectAtIndex:2]] ;
|
|
}
|
|
if (lua_gettop(L) == 3) keysIsPattern = (BOOL)lua_toboolean(L, 3) ;
|
|
}
|
|
|
|
CFDictionaryRef results ;
|
|
if (keysIsPattern) {
|
|
results = SCDynamicStoreCopyMultiple(theStore, NULL, (__bridge CFArrayRef)keys);
|
|
} else {
|
|
results = SCDynamicStoreCopyMultiple(theStore, (__bridge CFArrayRef)keys, NULL);
|
|
}
|
|
if (results) {
|
|
[skin pushNSObject:(__bridge NSDictionary *)results withOptions:(LS_NSDescribeUnknownTypes | LS_NSUnsignedLongLongPreserveBits)] ;
|
|
CFRelease(results) ;
|
|
} else {
|
|
return luaL_error(L, "** unable to get dynamicStore contents:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:keys([keypattern]) -> table
|
|
/// Method
|
|
/// Return the keys in the dynamic store which match the specified pattern
|
|
///
|
|
/// Parameters:
|
|
/// * keypattern - a regular expression specifying which keys to return (defaults to ".*", or all keys)
|
|
///
|
|
/// Returns:
|
|
/// * a table of keys from the dynamic store.
|
|
static int dynamicStoreKeys(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
NSString *keys = (lua_gettop(L) == 1) ? @".*" : [skin toNSObjectAtIndex:2] ;
|
|
CFArrayRef results = SCDynamicStoreCopyKeyList(theStore, (__bridge CFStringRef)keys);
|
|
if (results) {
|
|
[skin pushNSObject:(__bridge NSArray *)results withOptions:(LS_NSDescribeUnknownTypes | LS_NSUnsignedLongLongPreserveBits)] ;
|
|
CFRelease(results) ;
|
|
} else {
|
|
return luaL_error(L, "** unable to get dynamicStore keys:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:dhcpInfo([serviceID]) -> table
|
|
/// Method
|
|
/// Return the DHCP information for the specified service or the primary service if no parameter is specified.
|
|
///
|
|
/// Parameters:
|
|
/// * serviceID - an optional string contining the service ID of the interface for which to return DHCP info. If this parameter is not provided, then the default (primary) service is queried.
|
|
///
|
|
/// Returns:
|
|
/// * a table containing DHCP information including lease time and DHCP options
|
|
///
|
|
/// Notes:
|
|
/// * a list of possible Service ID's can be retrieved with `hs.network.configuration:contents("Setup:/Network/Global/IPv4")`
|
|
/// * generates an error if the service ID is invalid or was not assigned an IP address via DHCP.
|
|
static int dynamicStoreDHCPInfo(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TOPTIONAL, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
NSString *serviceID ;
|
|
if (lua_gettop(L) == 2) {
|
|
serviceID = [skin toNSObjectAtIndex:2] ;
|
|
}
|
|
|
|
CFDictionaryRef results = SCDynamicStoreCopyDHCPInfo(theStore, (__bridge CFStringRef)serviceID);
|
|
if (results) {
|
|
[skin pushNSObject:(__bridge NSDictionary *)results withOptions:(LS_NSDescribeUnknownTypes | LS_NSUnsignedLongLongPreserveBits)] ;
|
|
CFRelease(results) ;
|
|
} else {
|
|
return luaL_error(L, "** unable to get DHCP info:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:computerName() -> name, encoding
|
|
/// Method
|
|
/// Returns the name of the computeras specified in the Sharing Preferences, and its string encoding
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * name - the computer name
|
|
/// * encoding - the encoding type
|
|
///
|
|
/// Notes:
|
|
/// * You can also retrieve this information as key-value pairs with `hs.network.configuration:contents("Setup:/System")`
|
|
static int dynamicStoreComputerName(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
CFStringEncoding encoding ;
|
|
CFStringRef computerName = SCDynamicStoreCopyComputerName(theStore, &encoding);
|
|
if (computerName) {
|
|
[skin pushNSObject:(__bridge NSString *)computerName] ;
|
|
switch(encoding) {
|
|
case kCFStringEncodingMacRoman: [skin pushNSObject:@"MacRoman"] ; break ;
|
|
case kCFStringEncodingWindowsLatin1: [skin pushNSObject:@"WindowsLatin1"] ; break ;
|
|
case kCFStringEncodingISOLatin1: [skin pushNSObject:@"ISOLatin1"] ; break ;
|
|
case kCFStringEncodingNextStepLatin: [skin pushNSObject:@"NextStepLatin"] ; break ;
|
|
case kCFStringEncodingASCII: [skin pushNSObject:@"ASCII"] ; break ;
|
|
// alias for kCFStringEncodingUTF16; choose UTF16, since Unicode is not one specific encoding - all UTF
|
|
// types are more accurately a way to encode Unicode
|
|
// case kCFStringEncodingUnicode: [skin pushNSObject:@"Unicode"] ; break ;
|
|
case kCFStringEncodingUTF8: [skin pushNSObject:@"UTF8"] ; break ;
|
|
case kCFStringEncodingNonLossyASCII: [skin pushNSObject:@"NonLossyASCII"] ; break ;
|
|
case kCFStringEncodingUTF16: [skin pushNSObject:@"UTF16"] ; break ;
|
|
case kCFStringEncodingUTF16BE: [skin pushNSObject:@"UTF16BE"] ; break ;
|
|
case kCFStringEncodingUTF16LE: [skin pushNSObject:@"UTF16LE"] ; break ;
|
|
case kCFStringEncodingUTF32: [skin pushNSObject:@"UTF32"] ; break ;
|
|
case kCFStringEncodingUTF32BE: [skin pushNSObject:@"UTF32BE"] ; break ;
|
|
case kCFStringEncodingUTF32LE: [skin pushNSObject:@"UTF32LE"] ; break ;
|
|
case kCFStringEncodingInvalidId: [skin pushNSObject:@"InvalidId"] ; break ;
|
|
default:
|
|
[skin pushNSObject:[NSString stringWithFormat:@"** unrecognized encoding:%d", encoding]] ;
|
|
break ;
|
|
}
|
|
CFRelease(computerName) ;
|
|
} else {
|
|
return luaL_error(L, "** error retrieving computer name:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 2 ;
|
|
}
|
|
|
|
/// hs.network.configuration:consoleUser() -> name, uid, gid
|
|
/// Method
|
|
/// Returns the name of the user currently logged into the system, including the users id and primary group id
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * name - the user name
|
|
/// * uid - the user ID for the user
|
|
/// * gid - the user's primary group ID
|
|
///
|
|
/// Notes:
|
|
/// * You can also retrieve this information as key-value pairs with `hs.network.configuration:contents("State:/Users/ConsoleUser")`
|
|
static int dynamicStoreConsoleUser(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
uid_t uid ;
|
|
gid_t gid ;
|
|
CFStringRef consoleUser = SCDynamicStoreCopyConsoleUser(theStore, &uid, &gid);
|
|
if (consoleUser) {
|
|
[skin pushNSObject:(__bridge NSString *)consoleUser] ;
|
|
lua_pushinteger(L, uid) ;
|
|
lua_pushinteger(L, gid) ;
|
|
CFRelease(consoleUser) ;
|
|
} else {
|
|
return luaL_error(L, "** error retrieving console user:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 3 ;
|
|
}
|
|
|
|
/// hs.network.configuration:hostname() -> name
|
|
/// Method
|
|
/// Returns the current local host name for the computer
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * name - the local host name
|
|
///
|
|
/// Notes:
|
|
/// * You can also retrieve this information as key-value pairs with `hs.network.configuration:contents("Setup:/System")`
|
|
static int dynamicStoreLocalHostName(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
CFStringRef localHostName = SCDynamicStoreCopyLocalHostName(theStore);
|
|
if (localHostName) {
|
|
[skin pushNSObject:(__bridge NSString *)localHostName] ;
|
|
CFRelease(localHostName) ;
|
|
} else {
|
|
return luaL_error(L, "** error retrieving local host name:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
// internal stuff to make setLocation work
|
|
#define kSCPreferencesOptionChangeNetworkSet CFSTR("change-network-set") // CFBooleanRef
|
|
SCPreferencesRef
|
|
SCPreferencesCreateWithOptions (
|
|
CFAllocatorRef allocator,
|
|
CFStringRef name,
|
|
CFStringRef prefsID,
|
|
AuthorizationRef authorization,
|
|
CFDictionaryRef options
|
|
);
|
|
|
|
/// hs.network.configuration:setLocation(location) -> boolean
|
|
/// Method
|
|
/// Switches to a new location
|
|
///
|
|
/// Parameters:
|
|
/// * location - string containing name or UUID of new location
|
|
///
|
|
/// Returns:
|
|
/// * bool - true if the location was successfully changed, false if there was an error
|
|
static int dynamicStoreSetLocation(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK];
|
|
|
|
NSString *target ;
|
|
if (lua_gettop(L) == 2) {
|
|
target = [skin toNSObjectAtIndex:2];
|
|
}
|
|
AuthorizationRef authorization = NULL;
|
|
AuthorizationFlags flags = kAuthorizationFlagDefaults;
|
|
OSStatus status = AuthorizationCreate(NULL,
|
|
kAuthorizationEmptyEnvironment,
|
|
flags,
|
|
&authorization);
|
|
|
|
if (status != errAuthorizationSuccess) {
|
|
lua_pushboolean(L, 0);
|
|
if(authorization) {
|
|
AuthorizationFree(authorization, kAuthorizationFlagDestroyRights);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
CFMutableDictionaryRef options = CFDictionaryCreateMutable(NULL,
|
|
0,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
CFDictionarySetValue(options, kSCPreferencesOptionChangeNetworkSet, kCFBooleanTrue);
|
|
|
|
SCPreferencesRef prefs = SCPreferencesCreateWithOptions(NULL, CFSTR("SystemConfiguration"), NULL, authorization, options);
|
|
if(!prefs) {
|
|
lua_pushboolean(L, 0);
|
|
AuthorizationFree(authorization, kAuthorizationFlagDestroyRights);
|
|
CFRelease(options);
|
|
return 1;
|
|
}
|
|
|
|
CFArrayRef locations = SCNetworkSetCopyAll(prefs);
|
|
if(!locations) {
|
|
lua_pushboolean(L, 0);
|
|
AuthorizationFree(authorization, kAuthorizationFlagDestroyRights);
|
|
CFRelease(options);
|
|
CFRelease(prefs);
|
|
return 1;
|
|
}
|
|
|
|
CFIndex i, c = CFArrayGetCount(locations);
|
|
|
|
bool success=false;
|
|
|
|
for (i=0; i<c; i++) {
|
|
SCNetworkSetRef item = CFArrayGetValueAtIndex(locations, i);
|
|
|
|
CFStringRef name = SCNetworkSetGetName((SCNetworkSetRef)item);
|
|
CFStringRef uuid = SCNetworkSetGetSetID((SCNetworkSetRef)item);
|
|
if ((CFStringCompare(name, (CFStringRef)target, 0) == kCFCompareEqualTo) || (CFStringCompare(uuid,(CFStringRef)target, 0) == kCFCompareEqualTo)) {
|
|
bool res = SCNetworkSetSetCurrent((SCNetworkSetRef)item);
|
|
bool res2 = SCPreferencesCommitChanges(prefs);
|
|
bool res3 = SCPreferencesApplyChanges(prefs);
|
|
success = res || res2 || res3;
|
|
break;
|
|
}
|
|
}
|
|
lua_pushboolean(L, success);
|
|
AuthorizationFree(authorization, kAuthorizationFlagDestroyRights);
|
|
CFRelease(options);
|
|
CFRelease(prefs);
|
|
CFRelease(locations);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// hs.network.configuration:location() -> location
|
|
/// Method
|
|
/// Returns the current location identifier
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * location - the UUID for the currently active network location
|
|
///
|
|
/// Notes:
|
|
/// * You can also retrieve this information as key-value pairs with `hs.network.configuration:contents("Setup:")`
|
|
/// * If you have different locations defined in the Network preferences panel, this can be used to determine the currently active location.
|
|
static int dynamicStoreLocation(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
CFStringRef location = SCDynamicStoreCopyLocation(theStore);
|
|
if (location) {
|
|
[skin pushNSObject:(__bridge NSString *)location] ;
|
|
CFRelease(location) ;
|
|
} else {
|
|
return luaL_error(L, "** error retrieving location:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:locations() -> table
|
|
/// Method
|
|
/// Returns all configured locations
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * a table of key-value pairs mapping location UUIDs to their names
|
|
///
|
|
static int dynamicStoreLocations(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCPreferencesRef prefs = SCPreferencesCreate(NULL, CFSTR("Hammerspoon"), NULL);
|
|
|
|
if(!prefs) { lua_pushnil(L); return 1; }
|
|
|
|
CFArrayRef locations = SCNetworkSetCopyAll(prefs);
|
|
if(!locations) { lua_pushnil(L); CFRelease(prefs); return 1; }
|
|
|
|
CFIndex i, c = CFArrayGetCount(locations);
|
|
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
|
|
|
for (i=0; i<c; i++) {
|
|
SCNetworkSetRef location = CFArrayGetValueAtIndex(locations, i);
|
|
CFStringRef setID = SCNetworkSetGetSetID(location);
|
|
CFStringRef name = SCNetworkSetGetName(location);
|
|
dict[(__bridge NSString*) setID] = (__bridge NSString *)(name);
|
|
}
|
|
[skin pushNSObject:dict];
|
|
|
|
CFRelease(prefs);
|
|
CFRelease(locations);
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:proxies() -> table
|
|
/// Method
|
|
/// Returns information about the currently active proxies, if any
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * a table of key-value pairs describing the current proxies in effect, both globally, and scoped to specific interfaces.
|
|
///
|
|
/// Notes:
|
|
/// * You can also retrieve this information as key-value pairs with `hs.network.configuration:contents("State:/Network/Global/Proxies")`
|
|
static int dynamicStoreProxies(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
CFDictionaryRef proxies = SCDynamicStoreCopyProxies(theStore);
|
|
if (proxies) {
|
|
[skin pushNSObject:(__bridge NSDictionary *)proxies] ;
|
|
CFRelease(proxies) ;
|
|
} else {
|
|
return luaL_error(L, "** error retrieving proxies:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
/// hs.network.configuration:setCallback(function) -> storeObject
|
|
/// Method
|
|
/// Set or remove the callback function for a store object
|
|
///
|
|
/// Parameters:
|
|
/// * a function or nil to set or remove the store object callback function
|
|
///
|
|
/// Returns:
|
|
/// * the store object
|
|
///
|
|
/// Notes:
|
|
/// * The callback function will be invoked each time a monitored key changes value and the callback function should accept two parameters: the storeObject itself, and an array of the keys which contain values that have changed.
|
|
/// * This method just sets the callback function. You specify which keys to watch with [hs.network.configuration:monitorKeys](#monitorKeys) and start or stop the watcher with [hs.network.configuration:start](#start) or [hs.network.configuartion:stop](#stop)
|
|
static int dynamicStoreSetCallback(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TFUNCTION | LS_TNIL, LS_TBREAK];
|
|
dynamicstore_t* thePtr = get_structFromUserdata(dynamicstore_t, L, 1) ;
|
|
|
|
// in either case, we need to remove an existing callback, so...
|
|
thePtr->callbackRef = [skin luaUnref:refTable ref:thePtr->callbackRef];
|
|
if (lua_type(L, 2) == LUA_TFUNCTION) {
|
|
lua_pushvalue(L, 2);
|
|
thePtr->callbackRef = [skin luaRef:refTable];
|
|
if (thePtr->selfRef == LUA_NOREF) { // make sure that we won't be __gc'd if a callback exists
|
|
lua_pushvalue(L, 1) ; // but the user doesn't save us somewhere
|
|
thePtr->selfRef = [skin luaRef:refTable];
|
|
}
|
|
} else {
|
|
thePtr->selfRef = [skin luaUnref:refTable ref:thePtr->selfRef] ;
|
|
}
|
|
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.network.configuration:start() -> storeObject
|
|
/// Method
|
|
/// Starts watching the store object for changes to the monitored keys and invokes the callback function (if any) when a change occurs.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * the store object
|
|
///
|
|
/// Notes:
|
|
/// * The callback function should be specified with [hs.network.configuration:setCallback](#setCallback) and the keys to monitor should be specified with [hs.network.configuration:monitorKeys](#monitorKeys).
|
|
static int dynamicStoreStartWatcher(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
dynamicstore_t* thePtr = get_structFromUserdata(dynamicstore_t, L, 1) ;
|
|
if (!thePtr->watcherEnabled) {
|
|
if (SCDynamicStoreSetDispatchQueue(thePtr->storeObject, dynamicStoreQueue)) {
|
|
thePtr->watcherEnabled = YES ;
|
|
} else {
|
|
return luaL_error(L, "unable to set watcher dispatch queue:%s", SCErrorString(SCError())) ;
|
|
}
|
|
}
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.network.configuration:stop() -> storeObject
|
|
/// Method
|
|
/// Stops watching the store object for changes.
|
|
///
|
|
/// Parameters:
|
|
/// * None
|
|
///
|
|
/// Returns:
|
|
/// * the store object
|
|
static int dynamicStoreStopWatcher(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L];
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TBREAK];
|
|
dynamicstore_t* thePtr = get_structFromUserdata(dynamicstore_t, L, 1) ;
|
|
if (!SCDynamicStoreSetDispatchQueue(thePtr->storeObject, NULL)) {
|
|
[skin logBreadcrumb:[NSString stringWithFormat:@"%s:stop, error removing watcher from dispatch queue:%s",
|
|
USERDATA_TAG, SCErrorString(SCError())]] ;
|
|
}
|
|
thePtr->watcherEnabled = NO ;
|
|
lua_pushvalue(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
/// hs.network.configuration:monitorKeys([keys], [pattern]) -> storeObject
|
|
/// Method
|
|
/// Specify the key(s) or key pattern(s) to monitor for changes.
|
|
///
|
|
/// Parameters:
|
|
/// * keys - a string or table of strings containing the keys or patterns of keys, if `pattern` is true. Defaults to all keys.
|
|
/// * pattern - a boolean indicating wether or not the string(s) provided are to be considered regular expression patterns (true) or literal strings to match (false). Defaults to false.
|
|
///
|
|
/// Returns:
|
|
/// * the store Object
|
|
///
|
|
/// Notes:
|
|
/// * if no parameters are provided, then all key-value pairs in the dynamic store are monitored for changes.
|
|
static int dynamicStoreMonitorKeys(lua_State *L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG,
|
|
LS_TSTRING | LS_TTABLE | LS_TOPTIONAL,
|
|
LS_TBOOLEAN | LS_TOPTIONAL,
|
|
LS_TBREAK] ;
|
|
SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
|
|
NSArray *keys ;
|
|
BOOL keysIsPattern = NO ;
|
|
if (lua_gettop(L) == 1) {
|
|
keys = @[ @".*" ] ;
|
|
keysIsPattern = YES ;
|
|
} else {
|
|
if (lua_type(L, 2) == LUA_TTABLE) {
|
|
keys = [skin toNSObjectAtIndex:2] ;
|
|
} else {
|
|
keys = [NSArray arrayWithObject:[skin toNSObjectAtIndex:2]] ;
|
|
}
|
|
if (lua_gettop(L) == 3) keysIsPattern = (BOOL)lua_toboolean(L, 3) ;
|
|
}
|
|
|
|
Boolean result ;
|
|
if (keysIsPattern) {
|
|
result = SCDynamicStoreSetNotificationKeys(theStore, NULL, (__bridge CFArrayRef)keys);
|
|
} else {
|
|
result = SCDynamicStoreSetNotificationKeys(theStore, (__bridge CFArrayRef)keys, NULL);
|
|
}
|
|
if (result) {
|
|
lua_pushvalue(L, 1) ;
|
|
} else {
|
|
return luaL_error(L, "** unable to set keys to monitor:%s", SCErrorString(SCError())) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
#pragma mark - Module Constants
|
|
|
|
#pragma mark - Hammerspoon/Lua Infrastructure
|
|
|
|
static int userdata_tostring(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
// SCDynamicStoreRef theStore = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
[skin pushNSObject:[NSString stringWithFormat:@"%s: (%p)", USERDATA_TAG, lua_topointer(L, 1)]] ;
|
|
return 1 ;
|
|
}
|
|
|
|
static int userdata_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)) {
|
|
SCDynamicStoreRef theStore1 = get_structFromUserdata(dynamicstore_t, L, 1)->storeObject ;
|
|
SCDynamicStoreRef theStore2 = get_structFromUserdata(dynamicstore_t, L, 2)->storeObject ;
|
|
lua_pushboolean(L, CFEqual(theStore1, theStore2)) ;
|
|
} else {
|
|
lua_pushboolean(L, NO) ;
|
|
}
|
|
return 1 ;
|
|
}
|
|
|
|
static int userdata_gc(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
// [skin logDebug:@"dynamicstore GC"] ;
|
|
dynamicstore_t* thePtr = get_structFromUserdata(dynamicstore_t, L, 1) ;
|
|
if (thePtr->callbackRef != LUA_NOREF) {
|
|
thePtr->callbackRef = [skin luaUnref:refTable ref:thePtr->callbackRef] ;
|
|
if (!SCDynamicStoreSetDispatchQueue(thePtr->storeObject, NULL)) {
|
|
[skin logBreadcrumb:[NSString stringWithFormat:@"%s:__gc, error removing watcher from dispatch queue:%s",
|
|
USERDATA_TAG, SCErrorString(SCError())]] ;
|
|
}
|
|
}
|
|
thePtr->selfRef = [skin luaUnref:refTable ref:thePtr->selfRef] ;
|
|
[skin destroyGCCanary:&(thePtr->lsCanary)];
|
|
|
|
CFRelease(thePtr->storeObject) ;
|
|
lua_pushnil(L) ;
|
|
lua_setmetatable(L, 1) ;
|
|
return 0 ;
|
|
}
|
|
|
|
static int meta_gc(lua_State* __unused L) {
|
|
dynamicStoreQueue = nil ;
|
|
return 0 ;
|
|
}
|
|
|
|
// Metatable for userdata objects
|
|
static const luaL_Reg userdata_metaLib[] = {
|
|
{"contents", dynamicStoreContents},
|
|
{"keys", dynamicStoreKeys},
|
|
{"dhcpInfo", dynamicStoreDHCPInfo},
|
|
{"computerName", dynamicStoreComputerName},
|
|
{"consoleUser", dynamicStoreConsoleUser},
|
|
{"hostname", dynamicStoreLocalHostName},
|
|
{"location", dynamicStoreLocation},
|
|
{"locations", dynamicStoreLocations},
|
|
{"proxies", dynamicStoreProxies},
|
|
{"monitorKeys", dynamicStoreMonitorKeys},
|
|
{"setCallback", dynamicStoreSetCallback},
|
|
{"setLocation", dynamicStoreSetLocation},
|
|
{"start", dynamicStoreStartWatcher},
|
|
{"stop", dynamicStoreStopWatcher},
|
|
|
|
{"__tostring", userdata_tostring},
|
|
{"__eq", userdata_eq},
|
|
{"__gc", userdata_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Functions for returned object when module loads
|
|
static luaL_Reg moduleLib[] = {
|
|
{"open", newStoreObject},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
// Metatable for module, if needed
|
|
static const luaL_Reg module_metaLib[] = {
|
|
{"__gc", meta_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
int luaopen_hs_libnetworkconfiguration(lua_State* L) {
|
|
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
|
|
// Use this some of your functions return or act on a specific object unique to this module
|
|
refTable = [skin registerLibraryWithObject:USERDATA_TAG
|
|
functions:moduleLib
|
|
metaFunctions:module_metaLib
|
|
objectFunctions:userdata_metaLib];
|
|
|
|
dynamicStoreQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
|
|
|
|
return 1;
|
|
}
|