Files

138 lines
3.7 KiB
Markdown

# racket-tray
A small cross-platform system tray API for Racket GUI applications.
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
(require racket-tray)
(define tray-frame%
(class frame%
(super-new [label "Tray example"]
[width 400]
[height 250])
;; Close [X] to the tray instead of destroying the frame.
(define/override (on-close)
(send this show #f))))
(define frame (new tray-frame%))
(define (show-frame)
(send frame show #t)
(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"
(list tray-action 'open)
#:hide-on-minimize? #t))
(tray-set-menu!
tray
(list
(list 'open "Open")
'separator
(list 'exit "Exit")))
(send frame show #t)
```
## Action and menu model
The third argument of `mk-tray` is mandatory and has the form:
```racket
(list callback default-action-id)
```
`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.