Files

3.7 KiB

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.

#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:

(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:

(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:

sudo apt install libayatana-appindicator3-1

Fedora:

sudo dnf install libayatana-appindicator-gtk3

Arch Linux:

sudo pacman -S libayatana-appindicator

No additional native dependency is required by the Windows or macOS backend.