-
-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Ralph Plawetzki edited this page Jul 4, 2024
·
8 revisions
These are Java bindings for libayatana-appindicator and libappindicator-gtk3, created with jextract on Java 22. They make it possible to build appindicator tray icons for Java applications.
The icons used for the tray icon itself can be SVG, that scale and should look ok on all desktop environments.
After adding the Maven artifact to your code, you can simply define an appindicator tray icon like this:
A new method allows to set an entry in the tray menu insensitive:
Gtk.widgetSetSensitive(gtkMenuItem, false);
A new public API eases use of the bindings:
package com.purejava.fxdemo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.purejava.appindicator.AppIndicator;
import org.purejava.appindicator.GObject;
import org.purejava.appindicator.Gtk;
import org.purejava.appindicator.GCallback;
import java.io.IOException;
import java.lang.foreign.Arena;
import static org.purejava.appindicator.app_indicator_h.*;
public class HelloApplication extends Application {
private static final Arena ARENA = Arena.global();
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
try (var arena = Arena.ofConfined()) {
arenaAuto = Arena.ofAuto();
var indicator = AppIndicator.newIndicator("example-simple-client",
"indicator-message",
APP_INDICATOR_CATEGORY_APPLICATION_STATUS());
var gtkSeparator = Gtk.newMenuItem();
var gtkMenu = Gtk.newMenu();
var gtkSubmenu = Gtk.newMenu();
var gtkMenuItem = Gtk.newMenuItem();
Gtk.menuItemSetLabel(gtkMenuItem, "More");
var gtkSMenuItem = Gtk.newMenuItem();
Gtk.menuItemSetLabel(gtkSMenuItem, "Change icon");
var gtkSMenuItem1 = Gtk.newMenuItem();
Gtk.menuItemSetLabel(gtkSMenuItem1, "Quit");
Gtk.menuShellAppend(gtkSubmenu, gtkSMenuItem);
Gtk.menuShellAppend(gtkSubmenu, gtkSeparator);
Gtk.menuShellAppend(gtkSubmenu, gtkSMenuItem1);
Gtk.menuItemSetSubmenu(gtkMenuItem, gtkSubmenu);
GObject.signalConnectObject(gtkSMenuItem1, "activate", GCallback.allocate(new QuitCallback(), arenaAuto), gtkMenu, 0);
GObject.signalConnectObject(gtkSMenuItem, "activate", GCallback.allocate(new ChangeIconCallback(indicator), arenaAuto), gtkMenu, 0);
Gtk.menuShellAppend(gtkMenu, gtkMenuItem);
Gtk.widgetShowAll(gtkMenu);
AppIndicator.setMenu(indicator, gtkMenu);
AppIndicator.setAttentionIcon(indicator, "indicator-messages-new");
AppIndicator.setStatus(indicator, APP_INDICATOR_STATUS_ACTIVE());
launch();
}
}
}
package com.purejava.fxdemo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.purejava.appindicator.GCallback;
import java.io.IOException;
import java.lang.foreign.Arena;
import static org.purejava.appindicator.app_indicator_h.*;
public class HelloApplication extends Application {
private static final Arena ARENA = Arena.global();
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
try (var arena = Arena.ofConfined()) {
var indicator = app_indicator_new(arena.allocateUtf8String("example-simple-client"),
arena.allocateUtf8String("indicator-message"),
APP_INDICATOR_CATEGORY_APPLICATION_STATUS());
var gtkSeparator = gtk_menu_item_new();
var gtkMenu = gtk_menu_new();
var gtkSubmenu = gtk_menu_new();
var gtkMenuItem = gtk_menu_item_new();
gtk_menu_item_set_label(gtkMenuItem, arena.allocateUtf8String("More"));
var gtkSMenuItem = gtk_menu_item_new();
gtk_menu_item_set_label(gtkSMenuItem, arena.allocateUtf8String("Change icon"));
var gtkSMenuItem1 = gtk_menu_item_new();
gtk_menu_item_set_label(gtkSMenuItem1, arena.allocateUtf8String("Quit"));
gtk_menu_shell_append(gtkSubmenu, gtkSMenuItem);
gtk_menu_shell_append(gtkSubmenu, gtkSeparator);
gtk_menu_shell_append(gtkSubmenu, gtkSMenuItem1);
gtk_menu_item_set_submenu(gtkMenuItem, gtkSubmenu);
g_signal_connect_object(gtkSMenuItem1, arena.allocateUtf8String("activate"), GCallback.allocate(new QuitCallback(), ARENA), gtkMenu, 0);
g_signal_connect_object(gtkSMenuItem, arena.allocateUtf8String("activate"), GCallback.allocate(new ChangeIconCallback(indicator), ARENA), gtkMenu, 0);
gtk_menu_shell_append(gtkMenu, gtkMenuItem);
gtk_widget_show_all(gtkMenu);
app_indicator_set_menu(indicator, gtkMenu);
app_indicator_set_attention_icon(indicator, arena.allocateUtf8String("indicator-messages-new"));
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE());
launch();
}
}
}
package com.purejava.fxdemo;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.purejava.appindicator.GCallback;
import java.io.IOException;
import java.lang.foreign.Arena;
import java.lang.foreign.SegmentScope;
import static org.purejava.appindicator.app_indicator_h.*;
public class HelloApplication extends Application {
private static final SegmentScope SCOPE = SegmentScope.global();
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
try (var arena = Arena.openConfined()) {
var indicator = app_indicator_new(arena.allocateUtf8String("example-simple-client"),
arena.allocateUtf8String("indicator-message"),
APP_INDICATOR_CATEGORY_APPLICATION_STATUS());
var gtkSeparator = gtk_menu_item_new();
var gtkMenu = gtk_menu_new();
var gtkSubmenu = gtk_menu_new();
var gtkMenuItem = gtk_menu_item_new();
gtk_menu_item_set_label(gtkMenuItem, arena.allocateUtf8String("More"));
var gtkSMenuItem = gtk_menu_item_new();
gtk_menu_item_set_label(gtkSMenuItem, arena.allocateUtf8String("Change icon"));
var gtkSMenuItem1 = gtk_menu_item_new();
gtk_menu_item_set_label(gtkSMenuItem1, arena.allocateUtf8String("Quit"));
gtk_menu_shell_append(gtkSubmenu, gtkSMenuItem);
gtk_menu_shell_append(gtkSubmenu, gtkSeparator);
gtk_menu_shell_append(gtkSubmenu, gtkSMenuItem1);
gtk_menu_item_set_submenu(gtkMenuItem, gtkSubmenu);
g_signal_connect_object(gtkSMenuItem1, arena.allocateUtf8String("activate"), GCallback.allocate(new QuitCallback(), SCOPE), gtkMenu, 0);
g_signal_connect_object(gtkSMenuItem, arena.allocateUtf8String("activate"), GCallback.allocate(new ChangeIconCallback(indicator), SCOPE), gtkMenu, 0);
gtk_menu_shell_append(gtkMenu, gtkMenuItem);
gtk_widget_show_all(gtkMenu);
app_indicator_set_menu(indicator, gtkMenu);
app_indicator_set_attention_icon(indicator, arena.allocateUtf8String("indicator-messages-new"));
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE());
launch();
}
}
}
Don't forget to add the appindicator module to your module-info.java
:
module com.purejava.fxdemo {
requires javafx.controls;
requires javafx.fxml;
requires org.kordamp.bootstrapfx.core;
requires org.slf4j;
requires org.purejava.appindicator;
opens com.purejava.fxdemo to javafx.fxml;
exports com.purejava.fxdemo;
}
For wiring a tray icon menu item entry to an action, use the GCallback
:
package com.purejava.fxdemo;
import org.purejava.appindicator.GCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class QuitCallback implements GCallback.Function {
private static final Logger LOG = LoggerFactory.getLogger(QuitCallback.class);
@Override
public void apply() {
LOG.info("Hit Quit");
System.exit(0);
}
}
Some notes on requirements to get appindicator support:
- libayatana-appindicator is a native library, that needs to be installed on the system. It is on most recent Linux desktop environments though. On Ubuntu, the package is named
libayatana-appindicator3-1
, on Arch linux it's calledlibayatana-appindicator
. - There is another native library, that's used as a fall back, if it is installed on the system: libappindicator. On Ubuntu, the package is named
libappindicator3-1
, on Arch linux it's calledlibappindicator-gtk3
. - Recent GNOME desktop environments by default do not allow the SystemTray icon to be shown. It's necessary to install the Appindicator Support plugin to show an appindicator icon at all