diff options
author | Teddy Wing | 2018-10-05 13:31:26 +0200 |
---|---|---|
committer | Teddy Wing | 2018-10-05 13:31:26 +0200 |
commit | 4b482ff81bd094966af282b8ffd0a09ad1b18d78 (patch) | |
tree | 58b438aa5ea3366cdac79c0e03b5b8c62b65e518 | |
parent | fb46a8b01ccf243b0caa360e360c43f1fc2df7de (diff) | |
download | DomeKey-4b482ff81bd094966af282b8ffd0a09ad1b18d78.tar.bz2 |
Set up IPC to enable reloading the mappings file
My original plan was to use an `NSXPCConnection` to handle the IPC for
reloading mappings.
For some background, this is what I'm envisioning:
1. DomeKey is running in the background
2. User wants to make a change to their config. Update `mappings.dkmap`.
3. The new mappings file needs to be re-parsed and reloaded into memory.
The simplest thing we could do is just quit and relaunch DomeKey. but
that's kind of a pain. So instead, we run something like this from
the command line:
$ dome-key --reload-mappings
This will tell the running DomeKey process to update its mappings by
reloading the mappings file.
As I said, I was going to use `NSXPCConnection` for the IPC
communication, as it had come up in my research for IPC mechanisms on OS
X. I knew I didn't want to use a TCP socket as that seemed like too much
overhead. The ability to pass messages to classes "directly" using
`NSXPCConnection` was very appealing. However, I got a little lazy
trying to learn it. It has a whole model for how the communication
should work, including various procedures that need to be set up using
its API. It started to feel like a bit of a pain.
At that point I started looking into alternatives. One idea was to move
the IPC into the Rust code and use Unix Domain Sockets
(https://doc.rust-lang.org/std/os/unix/net/struct.UnixListener.html). I
was tempted to go this route, but I wasn't a fan of the need to always
be listening, the potential need to spawn a thread to reload the
mappings to free up the main workload, the need to figure out a way to
pass `State` (which keeps our parsed mappings in memory) around, and the
fact that only plain strings are passed, instead of messages in the case
of `NSXPCConnection`.
I came across
https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/OSX_Technology_Overview/SystemTechnology/SystemTechnology.html
where I discovered BSD notifications and `notify.3`. This seemed to be
the lightweight easy-to-use solution that I was looking for. After
following a little sample code, it works like a charm.
-rw-r--r-- | DomeKey/Mappings.h | 10 | ||||
-rw-r--r-- | DomeKey/Mappings.m | 52 | ||||
-rw-r--r-- | DomeKey/main.m | 9 |
3 files changed, 71 insertions, 0 deletions
diff --git a/DomeKey/Mappings.h b/DomeKey/Mappings.h index ee878f1..2c46dde 100644 --- a/DomeKey/Mappings.h +++ b/DomeKey/Mappings.h @@ -7,7 +7,17 @@ // #import <Foundation/Foundation.h> +#include <notify.h> + +@protocol Reloadable + +- (void)reload; + +@end @interface Mappings : NSObject ++ (void)observeReloadNotification; ++ (void)dispatchReload; + @end diff --git a/DomeKey/Mappings.m b/DomeKey/Mappings.m index bf68829..0d57fa6 100644 --- a/DomeKey/Mappings.m +++ b/DomeKey/Mappings.m @@ -8,6 +8,58 @@ #import "Mappings.h" +#define NOTIFICATION_NAME_RELOAD "com.teddywing.DomeKey.reload_mappings" + +const CFStringRef CFNOTIFICATION_NAME_RELOAD = CFSTR(NOTIFICATION_NAME_RELOAD); + @implementation Mappings +- (void)createXPCConnection +{ + NSXPCInterface *interface = [NSXPCInterface + interfaceWithProtocol:@protocol(Reloadable)]; + NSXPCConnection *connection = [[NSXPCConnection alloc] + initWithServiceName:@"com.teddywing.DomeKey"]; + [connection setRemoteObjectInterface:interface]; + [connection resume]; +} + ++ (void)observeReloadNotification +{ + CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter(); + + if (center) { + CFNotificationCenterAddObserver( + center, + NULL, + reload_mappings, + CFNOTIFICATION_NAME_RELOAD, + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately + ); + } +} + +void reload_mappings( + CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo +) { + if (CFStringCompare(name, CFNOTIFICATION_NAME_RELOAD, 0) == + kCFCompareEqualTo) { + // Reload mappings + NSLog(@"TODO: reload mappings"); + } +} + ++ (void)dispatchReload +{ + if (notify_post(NOTIFICATION_NAME_RELOAD) != 0) { + // Notification failed + NSLog(@"Reload notification failed"); + } +} + @end diff --git a/DomeKey/main.m b/DomeKey/main.m index e61929c..5bda636 100644 --- a/DomeKey/main.m +++ b/DomeKey/main.m @@ -8,8 +8,15 @@ #import <Foundation/Foundation.h> #import "AppDelegate.h" +#import "Mappings.h" int main(int argc, const char * argv[]) { + if (argc == 2 && strcmp(argv[1], "--reload-mappings") == 0) { + [Mappings dispatchReload]; + + return 0; // TODO: Return result of `notify_post`, and still log + } + @autoreleasepool { [NSApplication sharedApplication]; AppDelegate *app = [[AppDelegate alloc] init]; @@ -18,6 +25,8 @@ int main(int argc, const char * argv[]) { // insert code here... NSLog(@"Hello, World!"); + [Mappings observeReloadNotification]; + [NSApp run]; } return 0; |