linux & mac support, refactoring on minimize

This commit is contained in:
2026-08-29 21:13:29 +02:00
parent c7137fc11f
commit 9f87eae8aa
9 changed files with 1760 additions and 535 deletions
+95 -20
View File
@@ -1,10 +1,14 @@
# racket-tray
A small system tray API for Racket.
A small cross-platform system tray API for Racket GUI applications.
Version 0.1.1 implements Windows directly through the Win32 API. It uses the
native `HWND` of an existing Racket `frame%` or `dialog%`, `Shell_NotifyIconW`,
and `SetWindowSubclass`. No additional native DLL is required.
Version 0.1.1 contains native backends for Windows, Linux and macOS:
- Windows uses `Shell_NotifyIconW` and `SetWindowSubclass`.
- Linux uses Ayatana AppIndicator and GTK3.
- macOS uses AppKit `NSStatusItem` and `NSMenu` through Racket's Objective-C FFI.
The public API and symbolic menu actions are the same on every platform.
```racket
#lang racket/gui
@@ -28,35 +32,106 @@ and `SetWindowSubclass`. No additional native DLL is required.
(when (send frame is-iconized?)
(send frame iconize #f)))
(define (tray-action action)
(case action
[(open)
(show-frame)]
[(exit)
(tray-close tray)
(exit)]))
(define tray
(mk-tray frame
"example.png"
show-frame
(list tray-action 'open)
#:hide-on-minimize? #t))
(tray-set-menu!
tray
(list
(list "Open" show-frame)
(list 'open "Open")
'separator
(list "Exit"
(λ ()
(tray-close tray)
(exit)))))
(list 'exit "Exit")))
(send frame show #t)
```
`mk-tray` and `tray-set-icon!` accept both Windows `.ico` files and `.png`
files. PNG transparency is preserved when the image is converted to a native
Windows tray icon.
## Action and menu model
With `#:hide-on-minimize? #t`, minimizing the associated window hides it from
the taskbar while keeping its native window handle alive. Closing a window to
the tray does not need a separate tray API: override `frame%`'s `on-close` and
call `(send this show #f)`.
The third argument of `mk-tray` is mandatory and has the form:
`tray-set-menu!` accepts a `popup-menu%` object directly, a simple menu list,
or `#f` to remove the context menu.
```racket
(list callback default-action-id)
```
At the moment non-Windows platforms report that the operation is unsupported.
`callback` accepts one symbol. `default-action-id` is a symbol that must also
occur in the menu installed by `tray-set-menu!`.
A menu contains `(list action-id label)` entries and separators:
```racket
(list
(list 'open "Open")
'separator
(list 'exit "Exit"))
```
Choosing a menu item calls the callback with its action identifier. In the
example above, choosing `Open` calls `(tray-action 'open)` and choosing `Exit`
calls `(tray-action 'exit)`.
Direct tray activation is platform dependent:
- **Windows:** a normal activation invokes the configured default action.
The context-menu gesture opens the tray menu.
- **Linux with Ayatana AppIndicator 0.6 or newer:** a primary activation can
invoke the configured default action. The context-menu gesture opens the
menu.
- **Linux with Ayatana AppIndicator 0.5.x:** primary activation opens the menu;
the older library has no primary-activation callback.
- **macOS:** the native status item opens its menu. Menu selections invoke the
symbolic callback. The default id remains part of the common API but is not
invoked directly by an `NSStatusItem` with an attached menu.
## Hide on minimize
`#:hide-on-minimize?` is implemented in the platform-independent Racket layer.
It periodically checks `frame%`'s public `is-iconized?` method and hides the
frame when it becomes iconized. No Win32, GTK or AppKit minimize hook is used.
Closing a frame to the tray is separate because Racket already has the public
`on-close` callback. Override it and call `(send this show #f)` as shown above.
## Icons
`example.png` is suitable on all three supported platforms. Windows also
accepts `.ico`; PNG alpha transparency is converted to a native `HICON` by the
Windows backend. Linux passes an absolute image path to AppIndicator. macOS
loads the image with `NSImage`.
## Linux runtime dependency
Linux requires the Ayatana AppIndicator GTK3 runtime library. `racket-tray`
loads the native shared library dynamically. If it is missing, `mk-tray`
reports the dependency and the appropriate package commands instead of
exposing a raw FFI loader error.
Debian/Ubuntu:
```text
sudo apt install libayatana-appindicator3-1
```
Fedora:
```text
sudo dnf install libayatana-appindicator-gtk3
```
Arch Linux:
```text
sudo pacman -S libayatana-appindicator
```
No additional native dependency is required by the Windows or macOS backend.