Skip to content

Commit

Permalink
examples: add macos_tray example app (#15101)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhomart authored and pull[bot] committed Jun 1, 2024
1 parent f15b808 commit a0799a5
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 0 deletions.
3 changes: 3 additions & 0 deletions cmd/tools/modules/testing/common.v
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
skip_files << 'examples/sokol/sounds/wav_player.v'
skip_files << 'examples/sokol/sounds/simple_sin_tones.v'
}
$if !macos {
skip_files << 'examples/macos_tray/tray.v'
}
}
vargs := _vargs.replace('-progress', '').replace('-progress', '')
vexe := pref.vexe_path()
Expand Down
Binary file added examples/macos_tray/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 131 additions & 0 deletions examples/macos_tray/tray.m
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];
}
72 changes: 72 additions & 0 deletions examples/macos_tray/tray.v
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 added examples/macos_tray/v.mod
Empty file.

0 comments on commit a0799a5

Please sign in to comment.