Post

Implementing Tray Icon for Common Linux Environments

Cryptomator issue #1645

Motivation

I really love Cryptomator. I love the software, that protects my sensitive data in the cloud. And I love the project, that’s Java with state of the art techniques, A code quality and they accept contributions.

Most of my contributions to the project are features I considered missing or nice to have. So is this one.

There was a bug report for this issue already (#1645) and the Cryptomator devs suggest to make the C library libappindicator available to the software. This would improve the tray icon support for Cryptomator. The current tray icon menu on Linux doesn’t look nice and the tray icon isn’t displayed properly.

Requirements

To get libappindicator work with Cryptomator, Java bindings are needed. Their implementation could be done via JNI or JNA, but I decided for the Foreign Function & Memory API (FFI) as a state of the art approach. The Java bindings could be generated with jextract.

Luckily I gained the necessary experience on how callbacks do work with the FFI by writing the Java bindings for WinSparkle.

Design

Analyzing the Cryptomator code base showed, that there is an AwtTrayMenuController, that is responsible to handle the tray icon. This could be supplemented with an AppindicatorTrayMenuController, that also implements the TrayMenuController and talks to libappindicator to use its tray icon functionality.

The code looks quite similar to JNA-Code, as the C library methods are addressed directly (via FFI of course):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void addChildren(MemoryAddress menu, List<TrayMenuItem> items) {
  for (var item : items) {
    // TODO: use Pattern Matching for switch, once available
    if (item instanceof ActionItem a) {
      var gtkMenuItem = gtk_menu_item_new();
      gtk_menu_item_set_label(gtkMenuItem, MemoryAllocator.ALLOCATE_FOR(a.title()));
      g_signal_connect_object(gtkMenuItem,
          MemoryAllocator.ALLOCATE_FOR("activate"),
          MemoryAllocator.ALLOCATE_CALLBACK_FOR(new ActionItemCallback(a), session),
          menu,
          0);
      gtk_menu_shell_append(menu, gtkMenuItem);
    } else if (item instanceof SeparatorItem) {
      var gtkSeparator = gtk_menu_item_new();
      gtk_menu_shell_append(menu, gtkSeparator);
    } else if (item instanceof SubMenuItem s) {
      var gtkMenuItem = gtk_menu_item_new();
      var gtkSubmenu = gtk_menu_new();
      gtk_menu_item_set_label(gtkMenuItem, MemoryAllocator.ALLOCATE_FOR(s.title()));
      addChildren(gtkSubmenu, s.items());
      gtk_menu_item_set_submenu(gtkMenuItem, gtkSubmenu);
      gtk_menu_shell_append(menu, gtkMenuItem);
    }
    gtk_widget_show_all(menu);
  }
}

Implementation part one

The first step was to call libappindicator-methods from the Java code, that show the tray icon menu using D-Bus functionality. It’s very nice, that tray icon menus are displayed with Gtk classes, that look native on a Linux desktop environment like GNOME.

The finished first part does look very neat:

New Gtk tray icon menu New Gtk tray icon menu

You can see it in action in this screen recording.

This post is licensed under CC BY 4.0 by the author.

Trending Tags