forked from xilp/systray
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtray_darwin.c
executable file
·239 lines (198 loc) · 8.11 KB
/
tray_darwin.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Obj-C
#import <Cocoa/Cocoa.h>
// C
#include <inttypes.h>
#include <strings.h>
// Generated by cgo
#include "_cgo_export.h"
// A func signature of Go our Go callback
typedef void(*CallbackFunc)(ManagerId, GoInt);
typedef struct {
ManagerId manager; // Ref id to the Go object that will handle the click
unsigned int index; // Representative index of which go-side callback to invoke
CallbackFunc callback;
bool enabled;
bool checked;
} MenuCallbackInfo;
@interface AppDelegate: NSObject <NSApplicationDelegate>
{
NSMutableDictionary *icons;
NSStatusItem *_statusItem;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)showIcon:(NSString*)path hint:(NSString *)hint;
- (void)addMenuItem:(NSString*)item manager:(ManagerId)manager index:(int)index enabled:(bool)enabled checked:(bool)checked callback:(CallbackFunc)callback;
- (void)clearMenuItems;
- (IBAction)clicked:(id)sender;
- (IBAction)menuItem:(id)sender;
@property (readwrite, retain) NSStatusItem *statusItem;
@property (assign) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
NSMenu *m_menu;
ManagerId m_manager;
const char *m_initialIconPath;
const char *m_initialHint;
@synthesize window = _window;
@synthesize statusItem = _statusItem;
- (id)init:(ManagerId)manager iconPath:(const char *)iconPath hint:(const char*)hint{
if ((self = [super init])) {
m_manager = manager;
m_initialIconPath = iconPath;
m_initialHint = hint;
systray_Incref(m_manager);
}
return self;
}
- (void)dealloc {
systray_Decref(m_manager);
[_statusItem release]; _statusItem = nil;
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(@"Setting up menubar");
// Create a menubar item
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
// Useful for debugging if icon loading is broken (e.g., icons don't exist)
//[self.statusItem setTitle:@"SystrayTest"];
// Set up a general click handler - this will happen in addition to any menu
[self.statusItem setAction:@selector(clicked:)];
// Create our menu and add some items
//NSMenu *statusMenu = [[[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Custom"] autorelease];
NSMenu *statusMenu = [[[NSMenu alloc] initWithTitle:@"Custom"] autorelease];
[statusMenu setAutoenablesItems:NO];
// TODO: the app is now solely responsible for managing termination, so we don't add the
// default Quit item - consider whether or not a placeholder or label item should be added
// in its stead
[self.statusItem setMenu:statusMenu];
// Set up an icon cache for loaded image data
icons = [[NSMutableDictionary alloc] init];
// Set our initial icon and hint/tooltip
NSString *nsIcon = [NSString stringWithCString:m_initialIconPath encoding:NSASCIIStringEncoding];
NSString *nsHint = [NSString stringWithCString:m_initialHint encoding:NSASCIIStringEncoding];
[self showIcon:nsIcon hint:nsHint];
// Tell our caller that the menubar item has been created
NSLog(@"AppDelegate setup finished, calling menuCreatedCallback");
menuCreatedCallback(m_manager);
}
// Add a new menu item, with callback and metadata
- (void)addMenuItem:(NSString*)item manager:(ManagerId)manager index:(int)index enabled:(bool)enabled checked:(bool)checked callback:(CallbackFunc)callback {
MenuCallbackInfo callbackInfo;
callbackInfo.manager = manager;
callbackInfo.index = index;
callbackInfo.callback = callback;
callbackInfo.enabled = enabled;
callbackInfo.checked = checked;
//NSMenuItem *newItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:item action:@selector(menuItem:) keyEquivalent:@""] autorelease];
NSMenuItem *newItem = [[[NSMenuItem alloc] initWithTitle:item action:@selector(menuItem:) keyEquivalent:@""] autorelease];
if (enabled) {
[newItem setEnabled:YES];
} else {
[newItem setEnabled:NO];
}
if (checked) {
[newItem setState:NSOnState];
}
[newItem setRepresentedObject:[NSValue value:&callbackInfo withObjCType:@encode(MenuCallbackInfo)]];
[[self.statusItem menu] addItem:newItem];
}
- (void)clearMenuItems {
[[self.statusItem menu] removeAllItems];
}
// Process a previously added menu item, extracting the callback info and invoking
// the callback
- (IBAction)menuItem:(id)sender {
NSLog(@"Menu item!");
NSValue *nsCallbackInfo = [sender representedObject];
MenuCallbackInfo callbackInfo;
[nsCallbackInfo getValue:&callbackInfo];
if (callbackInfo.callback != nil) {
NSLog(@"Issuing callback");
callbackInfo.callback(callbackInfo.manager, callbackInfo.index);
}
}
// Provide any necessary response to a click in addition to the menu
- (IBAction)clicked:(id)sender {
// This could include a generic callback over the fence to Go
// if we want to do something like reset an idle timer.
// Fun fact: this does not get called if a menu is set, so utility is limited
//NSLog(@"clicked");
}
// Set the menubar icon and hint, if any. Either value may
// be nil, in which case no action is taken.
// TBD: decide if nil should mean "remove" instead
- (void)showIcon:(NSString*)path hint:(NSString *)hint {
if (path) {
NSImage *icon = [icons objectForKey:path];
if (!icon) {
NSLog(@"Creating new icon from file");
icon = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
if (icon) {
[icons setValue:icon forKey:path];
}
}
if (icon) {
NSLog(@"Setting icon image");
[self.statusItem setImage:icon];
}
}
if (hint) {
[self.statusItem setToolTip:hint];
}
}
@end
// External API - these are the C functions that are directly callable
// from Go.
// Run the cocoa application's main event loop. This must be run on the main
// thread and will block, so architect accordingly, especially cross-platform.
// TODO: Enforce the main thread restriction earlier, rather than letting some
// NS code assert
// TODO: Consider breaking this into separate setup and run functions, so the app
// can be stopped and restarted
void runApplication(const char *title,
const char *initialIcon,
const char *initialHint,
ManagerId manager) {
[NSAutoreleasePool new];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
AppDelegate *delegate = [[[AppDelegate alloc] init:manager iconPath:initialIcon hint:initialHint] autorelease];
[NSApp setDelegate:delegate];
//[NSApp activateIgnoringOtherApps:YES];
NSLog(@"Running main application");
[NSApp run];
}
// Stop the application
void stopApplication(void) {
[NSApplication sharedApplication];
[NSApp stop:nil];
}
// Set the currently displayed icon
// TODO: figure out how we want to pass unicode
void setIcon(const char *path) {
NSString *nsPath = [NSString stringWithCString:path encoding:NSASCIIStringEncoding];
[[NSApp delegate] showIcon:nsPath hint:nil];
}
// Set the currently displayed hint
void setHint(const char *hint) {
[NSAutoreleasePool new];
NSString *nsHint = [NSString stringWithCString:hint encoding:NSASCIIStringEncoding];
[[NSApp delegate] showIcon:nil hint:nsHint];
}
// Add a new item to the menu, with some (opaque) info on how to process it back on
// the other side
void addSystrayMenuItem(const char *item, ManagerId object, unsigned int index, unsigned char enabled, unsigned char checked) {
[NSAutoreleasePool new];
[[NSApp delegate] addMenuItem:[NSString stringWithCString:item encoding:NSASCIIStringEncoding]
manager:object
index:index
enabled:enabled
checked:checked
callback:&menuClickCallback];
}
// Clear all existing items from the menu
void clearSystrayMenuItems(void) {
[NSAutoreleasePool new];
[[NSApp delegate] clearMenuItems];
}