aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeddy Wing2018-10-05 13:31:26 +0200
committerTeddy Wing2018-10-05 13:31:26 +0200
commit4b482ff81bd094966af282b8ffd0a09ad1b18d78 (patch)
tree58b438aa5ea3366cdac79c0e03b5b8c62b65e518
parentfb46a8b01ccf243b0caa360e360c43f1fc2df7de (diff)
downloadDomeKey-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.h10
-rw-r--r--DomeKey/Mappings.m52
-rw-r--r--DomeKey/main.m9
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;