-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: add macos_tray example app (#15101)
- Loading branch information
Showing
5 changed files
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// #include <Cocoa/Cocoa.h> // Uncomment for C/C++ intellisense | ||
|
||
// See this tutorial to learn about NSApplicationDelegate: | ||
// https://bumbershootsoft.wordpress.com/2018/11/22/unfiltered-cocoa-completing-the-application/ | ||
|
||
static NSString *nsstring(string s) { | ||
return [[NSString alloc] initWithBytesNoCopy:s.str | ||
length:s.len | ||
encoding:NSUTF8StringEncoding | ||
freeWhenDone:NO]; | ||
} | ||
|
||
// Manages the app lifecycle. | ||
@interface AppDelegate : NSObject <NSApplicationDelegate> { | ||
NSAutoreleasePool *pool; | ||
NSApplication *app; | ||
NSStatusItem *statusItem; // our button | ||
main__TrayParams trayParams; // TrayParams is defined in tray.v | ||
} | ||
@end | ||
|
||
@implementation AppDelegate | ||
- (AppDelegate *)initWithParams:(main__TrayParams)params { | ||
if (self = [super init]) { | ||
trayParams = params; | ||
} | ||
return self; | ||
} | ||
|
||
// Called when NSMenuItem is clicked. | ||
- (void)onAction:(id)sender { | ||
struct main__TrayMenuItem *item = | ||
(struct main__TrayMenuItem *)[[sender representedObject] pointerValue]; | ||
if (item) { | ||
trayParams.on_click(*item); | ||
} | ||
} | ||
|
||
- (NSMenu *)buildMenu { | ||
NSMenu *menu = [NSMenu new]; | ||
[menu autorelease]; | ||
[menu setAutoenablesItems:NO]; | ||
|
||
main__TrayMenuItem *params_items = trayParams.items.data; | ||
for (int i = 0; i < trayParams.items.len; i++) { | ||
NSString *title = nsstring(params_items[i].text); | ||
NSMenuItem *item = [menu addItemWithTitle:title | ||
action:@selector(onAction:) | ||
keyEquivalent:@""]; | ||
NSValue *representedObject = [NSValue valueWithPointer:(params_items + i)]; | ||
[item setRepresentedObject:representedObject]; | ||
[item setTarget:self]; | ||
[item autorelease]; | ||
[item setEnabled:YES]; | ||
} | ||
|
||
return menu; | ||
} | ||
|
||
- (void)initTrayMenuItem { | ||
NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; | ||
statusItem = [statusBar statusItemWithLength:NSSquareStatusItemLength]; | ||
[statusItem retain]; | ||
[statusItem setVisible:YES]; | ||
NSStatusBarButton *statusBarButton = [statusItem button]; | ||
|
||
// Height must be 22px. | ||
NSImage *img = [NSImage imageNamed:@"icon.png"]; | ||
[statusBarButton setImage:img]; | ||
NSMenu *menu = [self buildMenu]; | ||
[statusItem setMenu:menu]; | ||
} | ||
|
||
- (void)applicationWillFinishLaunching:(NSNotification *)notification { | ||
NSLog(@"applicationWillFinishLaunching called"); | ||
} | ||
|
||
- (void)applicationWillTerminate:(NSNotification *)notif; | ||
{ NSLog(@"applicationWillTerminate called"); } | ||
|
||
- (NSApplicationTerminateReply)applicationShouldTerminate: | ||
(NSApplication *)sender { | ||
NSLog(@"applicationShouldTerminate called"); | ||
return NSTerminateNow; | ||
} | ||
@end | ||
|
||
// Initializes NSApplication and NSStatusItem, aka system tray menu item. | ||
main__TrayInfo *tray_app_init(main__TrayParams params) { | ||
NSApplication *app = [NSApplication sharedApplication]; | ||
AppDelegate *appDelegate = [[AppDelegate alloc] initWithParams:params]; | ||
|
||
// Hide icon from the doc. | ||
[app setActivationPolicy:NSApplicationActivationPolicyProhibited]; | ||
[app setDelegate:appDelegate]; | ||
|
||
[appDelegate initTrayMenuItem]; | ||
|
||
main__TrayInfo *tray_info = malloc(sizeof(main__TrayInfo)); | ||
tray_info->app = app; | ||
tray_info->app_delegate = appDelegate; | ||
return tray_info; | ||
} | ||
|
||
// Blocks and runs the application. | ||
void tray_app_run(main__TrayInfo *tray_info) { | ||
NSApplication *app = (NSApplication *)(tray_info->app); | ||
[app run]; | ||
} | ||
|
||
// Processes a single NSEvent while blocking the thread | ||
// until there is an event. | ||
void tray_app_loop(main__TrayInfo *tray_info) { | ||
NSDate *until = [NSDate distantFuture]; | ||
|
||
NSApplication *app = (NSApplication *)(tray_info->app); | ||
NSEvent *event = [app nextEventMatchingMask:ULONG_MAX | ||
untilDate:until | ||
inMode:@"kCFRunLoopDefaultMode" | ||
dequeue:YES]; | ||
|
||
if (event) { | ||
[app sendEvent:event]; | ||
} | ||
} | ||
|
||
// Terminates the app. | ||
void tray_app_exit(main__TrayInfo *tray_info) { | ||
NSApplication *app = (NSApplication *)(tray_info->app); | ||
[app terminate:app]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Simple windows-less application that shows a icon button | ||
// on Mac OS tray. | ||
// | ||
// Tested on Mac OS Monterey (12.3). | ||
|
||
module main | ||
|
||
#include <Cocoa/Cocoa.h> | ||
#flag -framework Cocoa | ||
|
||
#include "@VMODROOT/tray.m" | ||
|
||
fn C.tray_app_init(TrayParams) &TrayInfo | ||
fn C.tray_app_loop(&TrayInfo) | ||
fn C.tray_app_run(&TrayInfo) | ||
fn C.tray_app_exit(&TrayInfo) | ||
|
||
struct TrayMenuItem { | ||
id string [required] // Unique ID. | ||
text string [required] // Text to display. | ||
} | ||
|
||
// Parameters to configure the tray button. | ||
struct TrayParams { | ||
items []TrayMenuItem [required] | ||
on_click fn (item TrayMenuItem) | ||
} | ||
|
||
// Internal Cocoa application state. | ||
struct TrayInfo { | ||
app voidptr // pointer to NSApplication | ||
app_delegate voidptr // pointer to AppDelegate | ||
} | ||
|
||
[heap] | ||
struct MyApp { | ||
mut: | ||
tray_info &TrayInfo | ||
} | ||
|
||
fn (app &MyApp) on_menu_item_click(item TrayMenuItem) { | ||
println('click $item.id') | ||
if item.id == 'quit' { | ||
C.tray_app_exit(app.tray_info) | ||
} | ||
} | ||
|
||
fn main() { | ||
mut my_app := &MyApp{ | ||
tray_info: 0 | ||
} | ||
|
||
my_app.tray_info = C.tray_app_init(TrayParams{ | ||
items: [TrayMenuItem{ | ||
id: 'hello' | ||
text: 'Hello' | ||
}, TrayMenuItem{ | ||
id: 'quit' | ||
text: 'Quit!' | ||
}] | ||
on_click: my_app.on_menu_item_click | ||
}) | ||
|
||
//// Use this: | ||
// for { | ||
// C.tray_app_loop(my_app.tray_info) | ||
// // println("loop") | ||
// } | ||
|
||
//// Or this: | ||
C.tray_app_run(my_app.tray_info) | ||
} |
Empty file.