linux & mac support, refactoring on minimize
This commit is contained in:
+81
-43
@@ -10,60 +10,79 @@
|
||||
|
||||
@defmodule[racket-tray]
|
||||
|
||||
Racket Tray provides a small API for adding a system tray icon to a Racket GUI
|
||||
application. Version 0.1.1 supports Windows. The Windows implementation uses
|
||||
the native @tt{HWND} of an existing Racket top-level window and requires no
|
||||
additional native library.
|
||||
Racket Tray provides a small cross-platform system tray API for Racket GUI
|
||||
applications. Version 0.1.1 contains native backends for Windows, Linux and
|
||||
macOS. The public API uses symbolic actions so application code does not depend
|
||||
on a platform-specific tray menu implementation.
|
||||
|
||||
@section{Creating a Tray Icon}
|
||||
|
||||
@defproc[(mk-tray [frame (is-a?/c top-level-window<%>)]
|
||||
[icon-file path-string?]
|
||||
[on-click-cb (or/c #f (-> any))]
|
||||
[action-spec list?]
|
||||
[#:hide-on-minimize? hide-on-minimize? boolean? #f])
|
||||
any/c]{
|
||||
Creates a tray icon associated with @racket[frame]. The frame must already
|
||||
have a native window handle. A @racket[frame%] or @racket[dialog%] is suitable.
|
||||
Creates a tray icon associated with @racket[frame]. A @racket[frame%] or
|
||||
@racket[dialog%] is suitable.
|
||||
|
||||
@racket[icon-file] can be a Windows @tt{.ico} file or a @tt{.png} file. PNG
|
||||
alpha transparency is preserved when the image is converted to a native
|
||||
Windows icon.
|
||||
@racket[action-spec] is mandatory and must contain exactly two values:
|
||||
@racket[(list callback default-action-id)]. @racket[callback] must accept one
|
||||
argument and @racket[default-action-id] must be a symbol. Every action generated
|
||||
by the tray is passed to @racket[callback] as a symbol.
|
||||
|
||||
When the tray icon is activated, @racket[on-click-cb] is queued in the
|
||||
eventspace of @racket[frame]. Use @racket[#f] when no activation callback is
|
||||
needed.
|
||||
The default action must occur in the menu later installed with
|
||||
@racket[tray-set-menu!]. Windows maps normal tray activation to this action.
|
||||
Ayatana AppIndicator 0.6 or newer can do the same on Linux. Older AppIndicator
|
||||
0.5.x implementations open the menu on primary activation instead. A native
|
||||
macOS status item with an attached menu also opens its menu instead of invoking
|
||||
the default action directly.
|
||||
|
||||
When @racket[hide-on-minimize?] is true, minimizing the associated window
|
||||
hides it after Windows reports @tt{SIZE_MINIMIZED}. Hiding the window removes
|
||||
it from the taskbar while leaving its native handle alive for the tray icon.
|
||||
When showing such a window again, an application can restore it with
|
||||
@racket[(send frame iconize #f)] when @racket[(send frame is-iconized?)] is
|
||||
true.
|
||||
When @racket[hide-on-minimize?] is true, Racket Tray periodically checks
|
||||
@method[frame% is-iconized?]. When the frame changes to the iconized state it
|
||||
is hidden with @racket[(send frame show #f)]. This implementation is entirely
|
||||
platform independent and does not use a Win32, GTK or AppKit minimize hook.
|
||||
|
||||
The returned value represents the tray icon and is accepted by the other
|
||||
procedures in this library.
|
||||
}
|
||||
|
||||
@section{Tray Actions and Menus}
|
||||
|
||||
@defproc[(tray-set-menu! [tray any/c]
|
||||
[menu-spec list?]) void?]{
|
||||
Sets the menu for @racket[tray]. An actionable entry has the form
|
||||
@racket[(list action-id label)], where @racket[action-id] is a symbol and
|
||||
@racket[label] is a string. @racket['separator] or @racket[#f] creates a
|
||||
separator. Action identifiers must be unique and the menu must contain the
|
||||
default action supplied to @racket[mk-tray].
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(tray-set-menu!
|
||||
tray
|
||||
(list
|
||||
(list 'open "Open")
|
||||
'separator
|
||||
(list 'exit "Exit")))
|
||||
]
|
||||
|
||||
Choosing @racket["Open"] invokes the callback as @racket[(callback 'open)]. The
|
||||
same callback receives every other menu action.
|
||||
}
|
||||
|
||||
@section{Changing and Closing a Tray Icon}
|
||||
|
||||
@defproc[(tray-set-icon! [tray any/c]
|
||||
[icon-file path-string?]) void?]{
|
||||
Replaces the image of @racket[tray]. Both @tt{.ico} and @tt{.png} files are
|
||||
accepted.
|
||||
}
|
||||
|
||||
@defproc[(tray-set-menu! [tray any/c]
|
||||
[menu any/c]) void?]{
|
||||
Sets the context menu for @racket[tray]. @racket[menu] can be a
|
||||
@racket[popup-menu%], @racket[#f], or a list. A list entry of the form
|
||||
@racket[(list label callback)] creates a menu item. @racket['separator] and
|
||||
@racket[#f] create a separator. Each callback must accept zero arguments.
|
||||
Replaces the image of @racket[tray]. PNG files can be used on all supported
|
||||
platforms. The Windows backend also accepts ICO files.
|
||||
}
|
||||
|
||||
@defproc[(tray-close [tray any/c]) void?]{
|
||||
Removes @racket[tray], detaches its native window hook, and releases the
|
||||
Windows icon resources owned by the tray object. Calling this procedure does
|
||||
not close the associated Racket window.
|
||||
Removes @racket[tray], stops its optional portable minimize watcher and releases
|
||||
the native resources owned by the active platform backend. Calling this
|
||||
procedure does not close the associated Racket window.
|
||||
}
|
||||
|
||||
@section{Closing a Window to the Tray}
|
||||
@@ -81,17 +100,36 @@ of a frame. A tray application normally overrides it and hides the frame:
|
||||
]
|
||||
|
||||
This is intentionally separate from @racket[#:hide-on-minimize?]. Closing a
|
||||
window is already represented by a public Racket GUI callback, while Windows
|
||||
does not expose minimizing through a corresponding public Racket callback.
|
||||
window already has a portable public Racket callback; minimizing does not.
|
||||
|
||||
@section{Platform Behaviour}
|
||||
|
||||
On Windows, the backend uses the native HWND returned by Racket GUI,
|
||||
@tt{Shell_NotifyIconW} and @tt{SetWindowSubclass}. A normal tray activation
|
||||
invokes the configured default action. No additional native library is needed.
|
||||
|
||||
On Linux, the backend uses Ayatana AppIndicator and GTK3. AppIndicator 0.6 or
|
||||
newer provides primary activation, which Racket Tray maps to the default action.
|
||||
With AppIndicator 0.5.x, primary activation opens the menu instead. Menu choices
|
||||
have the same symbolic callback behaviour on both library versions.
|
||||
|
||||
On macOS, the backend uses AppKit @tt{NSStatusItem}, @tt{NSStatusBarButton} and
|
||||
@tt{NSMenu} through Racket's Objective-C FFI. A status item with an attached
|
||||
menu opens that menu when activated. Menu choices invoke the common symbolic
|
||||
callback. No additional native library is needed.
|
||||
|
||||
@section{Linux Runtime Dependency}
|
||||
|
||||
The Linux backend requires the Ayatana AppIndicator GTK3 runtime library. If it
|
||||
cannot be loaded, @racket[mk-tray] reports the missing dependency and suggests
|
||||
the native package to install.
|
||||
|
||||
Debian and Ubuntu use @tt{libayatana-appindicator3-1}. Fedora uses
|
||||
@tt{libayatana-appindicator-gtk3}. Arch Linux uses
|
||||
@tt{libayatana-appindicator}.
|
||||
|
||||
@section{Example}
|
||||
|
||||
The package contains @filepath{examples/simple.rkt}. It demonstrates hiding a
|
||||
frame on both minimize and close, restoring it from the tray, and terminating
|
||||
the application only through the tray menu.
|
||||
|
||||
@section{Platform Support}
|
||||
|
||||
Version 0.1.1 implements Windows. Requiring @racketmodname[racket-tray] is safe
|
||||
on other operating systems, but calling its tray operations reports that the
|
||||
platform is not yet supported.
|
||||
The package contains @filepath{examples/simple.rkt}. It demonstrates symbolic
|
||||
tray actions, hiding a frame on minimize and close, restoring it from the tray,
|
||||
and terminating the application only through the @racket['exit] action.
|
||||
|
||||
Reference in New Issue
Block a user