2 // EnjoyableApplicationDelegate.m
5 // Created by Sam McCall on 4/05/09.
8 #import <Sparkle/Sparkle.h>
10 #import "EnjoyableApplicationDelegate.h"
13 #import "NJMappingsController.h"
16 @implementation EnjoyableApplicationDelegate {
17 NSStatusItem *statusItem;
20 - (void)didSwitchApplication:(NSNotification *)note {
21 NSRunningApplication *activeApp = note.userInfo[NSWorkspaceApplicationKey];
23 [self.mappingsController activateMappingForProcess:activeApp];
26 - (void)applicationWillFinishLaunching:(NSNotification *)notification {
27 [NSNotificationCenter.defaultCenter
29 selector:@selector(mappingDidChange:)
30 name:NJEventMappingChanged
32 [NSNotificationCenter.defaultCenter
34 selector:@selector(eventSimulationStarted:)
35 name:NJEventSimulationStarted
37 [NSNotificationCenter.defaultCenter
39 selector:@selector(eventSimulationStopped:)
40 name:NJEventSimulationStopped
43 [self.mappingsController load];
45 statusItem = [NSStatusBar.systemStatusBar statusItemWithLength:36];
46 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
47 statusItem.highlightMode = YES;
48 statusItem.menu = statusItemMenu;
49 statusItem.target = self;
52 - (void)applicationDidFinishLaunching:(NSNotification *)notification {
53 if ([NSUserDefaults.standardUserDefaults boolForKey:@"hidden in status item"]
54 && NSRunningApplication.currentApplication.wasLaunchedAsLoginItemOrResume)
55 [self transformIntoElement:nil];
57 [window makeKeyAndOrderFront:nil];
60 - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication
61 hasVisibleWindows:(BOOL)flag {
62 [self restoreToForeground:theApplication];
66 - (void)restoreToForeground:(id)sender {
67 ProcessSerialNumber psn = { 0, kCurrentProcess };
68 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
69 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
70 [window makeKeyAndOrderFront:sender];
71 [NSObject cancelPreviousPerformRequestsWithTarget:self
72 selector:@selector(transformIntoElement:)
74 [NSUserDefaults.standardUserDefaults setBool:NO forKey:@"hidden in status item"];
77 - (void)applicationWillBecomeActive:(NSNotification *)notification {
79 [self restoreToForeground:notification];
82 - (void)transformIntoElement:(id)sender {
83 ProcessSerialNumber psn = { 0, kCurrentProcess };
84 TransformProcessType(&psn, kProcessTransformToUIElementApplication);
85 [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"hidden in status item"];
88 - (void)flashStatusItem {
89 if ([statusItem.image.name isEqualToString:@"Status Menu Icon"]) {
90 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
92 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
97 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
98 [theApplication hide:theApplication];
99 // If we turn into a UIElement right away, the application cancels
100 // the deactivation events. The dock icon disappears, but an
101 // unresponsive menu bar remains until the user clicks somewhere.
102 // So delay just long enough to be past the end handling that.
103 [self performSelector:@selector(transformIntoElement:) withObject:self afterDelay:0.001];
107 - (void)eventSimulationStarted:(NSNotification *)note {
108 statusItem.image = [NSImage imageNamed:@"Status Menu Icon"];
109 [NSWorkspace.sharedWorkspace.notificationCenter
111 selector:@selector(didSwitchApplication:)
112 name:NSWorkspaceDidActivateApplicationNotification
116 - (void)eventSimulationStopped:(NSNotification *)note {
117 statusItem.image = [NSImage imageNamed:@"Status Menu Icon Disabled"];
118 [NSWorkspace.sharedWorkspace.notificationCenter
120 name:NSWorkspaceDidActivateApplicationNotification
124 - (void)mappingDidChange:(NSNotification *)note {
125 if (!window.isVisible)
126 for (int i = 0; i < 4; ++i)
127 [self performSelector:@selector(flashStatusItem)
132 - (NSMenu *)applicationDockMenu:(NSApplication *)sender {
136 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
137 [self restoreToForeground:sender];
138 NSURL *url = [NSURL fileURLWithPath:filename];
139 [self.mappingsController addMappingWithContentsOfURL:url];
143 - (void)mappingWasChosen:(NJMapping *)mapping {
144 [self.mappingsController activateMapping:mapping];
147 - (void)mappingListShouldOpen {
148 [self restoreToForeground:self];
149 [self.mappingsController mappingPressed:self];
152 - (void)loginItemPromptDidEnd:(NSWindow *)sheet
153 returnCode:(int)returnCode
154 contextInfo:(void *)contextInfo {
155 if (returnCode == NSAlertDefaultReturn) {
156 [NSRunningApplication.currentApplication addToLoginItems];
157 // If we're going to automatically start, don't bug the user
158 // about automatic updates next boot - they probably want it,
159 // and if they don't they probably want a prompt for it less.
160 SUUpdater.sharedUpdater.automaticallyChecksForUpdates = YES;
164 - (void)loginItemPromptDidDismiss:(NSWindow *)sheet
165 returnCode:(int)returnCode
166 contextInfo:(void *)contextInfo {
167 [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"explained login items"];
168 [window performClose:sheet];
171 - (BOOL)windowShouldClose:(NSWindow *)sender {
173 || NSRunningApplication.currentApplication.isLoginItem
174 || [NSUserDefaults.standardUserDefaults boolForKey:@"explained login items"])
177 NSLocalizedString(@"login items prompt", @"alert prompt for adding to login items"),
178 NSLocalizedString(@"login items add button", @"button to add to login items"),
179 NSLocalizedString(@"login items don't add button", @"button to not add to login items"),
181 @selector(loginItemPromptDidEnd:returnCode:contextInfo:),
182 @selector(loginItemPromptDidDismiss:returnCode:contextInfo:),
184 NSLocalizedString(@"login items explanation", @"a brief explanation of login items")
186 for (int i = 0; i < 10; ++i)
187 [self performSelector:@selector(flashStatusItem)