SystemTray/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java

239 lines
9.3 KiB
Java
Raw Normal View History

2016-10-09 20:20:23 +02:00
/*
2021-01-31 22:28:55 +01:00
* Copyright 2021 dorkbox, llc
2016-10-09 20:20:23 +02:00
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.ui.gtk;
2016-10-09 20:20:23 +02:00
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.jna.Pointer;
2020-08-19 13:06:49 +02:00
import dorkbox.jna.linux.AppIndicator;
import dorkbox.jna.linux.GObject;
import dorkbox.jna.linux.GtkEventDispatch;
import dorkbox.jna.linux.structs.AppIndicatorInstanceStruct;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.systemTray.util.SizeAndScalingUtil;
2016-10-09 20:20:23 +02:00
/**
* Class for handling all system tray interactions.
* specialization for using app indicators in ubuntu unity
*
* Derived from
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*
* AppIndicators DO NOT support anything other than plain gtk-menus, because of how they use dbus so no tooltips AND no custom widgets
*
*
*
* As a result of this decision by Canonical, we have to resort to hacks to get it to do what we want. BY NO MEANS IS THIS PERFECT.
*
*
* We still cannot have tooltips, but we *CAN* have custom widgets in the menu (because it's our swing menu now...)
*
*
* It would be too much work to re-implement AppIndicators, or even to use LD_PRELOAD + restart service to do what we want.
*
* As a result, we have some wicked little hacks which are rather effective (but have a small side-effect of very briefly
* showing a blank menu)
*
* // What are AppIndicators?
* http://unity.ubuntu.com/projects/appindicators/
*
*
* // Entry-point into appindicators
* http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/services/panel-main.c
*
*
* // The idiocy of appindicators
* https://bugs.launchpad.net/screenlets/+bug/522152
*
* // Code of how the dbus menus work
* http://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu/trunk.16.10/view/head:/libdbusmenu-gtk/client.c
* https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html
*
* // more info about trying to put widgets into GTK menus
* http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator
*
* // possible idea on how to get GTK widgets into GTK menus
* https://launchpad.net/ido
* http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
*/
2016-10-11 15:23:58 +02:00
@SuppressWarnings("Duplicates")
2017-06-15 00:45:06 +02:00
public final
class _AppIndicatorNativeTray extends Tray {
public static boolean isLoaded = false;
2016-10-10 00:26:52 +02:00
private volatile AppIndicatorInstanceStruct appIndicator;
2016-10-09 20:20:23 +02:00
private boolean isActive = false;
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
2021-01-31 20:05:40 +01:00
private final AtomicBoolean shuttingDown = new AtomicBoolean();
2016-10-09 20:20:23 +02:00
// is the system tray visible or not.
private volatile boolean visible = true;
private volatile File imageFile;
// has the name already been set for the indicator?
private volatile boolean setName = false;
2017-12-01 22:46:19 +01:00
// appindicators DO NOT support anything other than PLAIN gtk-menus
// they ALSO do not support tooltips!!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
2016-10-09 20:20:23 +02:00
public
_AppIndicatorNativeTray(final String trayName, final ImageResizeUtil imageResizeUtil, final Runnable onRemoveEvent) {
super(onRemoveEvent);
2016-10-09 20:20:23 +02:00
isLoaded = true;
// setup some GTK menu bits...
GtkBaseMenuItem.transparentIcon = imageResizeUtil.getTransparentImage();
GtkMenuItemCheckbox.uncheckedFile = imageResizeUtil.getTransparentImage().getAbsolutePath();
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
final GtkMenu gtkMenu = new GtkMenu() {
/**
* MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
2016-12-19 11:58:59 +01:00
*
* ALWAYS CALLED ON THE EDT
*/
@Override
protected final
void onMenuAdded(final Pointer menu) {
// see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
2017-12-01 22:46:19 +01:00
appIndicator.app_indicator_set_menu(menu);
if (!setName) {
setName = true;
// in GNOME by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
// can cause (potentially)
// GLib-GIO-CRITICAL **: g_dbus_connection_emit_signal: assertion 'object_path != NULL && g_variant_is_object_path (object_path)' failed
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
// in extension.js, so don't change it
// additionally, this is required to be set HERE (not somewhere else)
appIndicator.app_indicator_set_title(trayName);
}
}
2016-10-09 20:20:23 +02:00
@Override
public
void setEnabled(final MenuItem menuItem) {
2021-01-31 20:05:40 +01:00
GtkEventDispatch.dispatch(()->{
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
// STATUS_PASSIVE hides the indicator
appIndicator.app_indicator_set_status(AppIndicator.STATUS_PASSIVE);
visible = false;
}
else if (!visible && enabled) {
appIndicator.app_indicator_set_status(AppIndicator.STATUS_ACTIVE);
visible = true;
}
});
2016-10-09 20:20:23 +02:00
}
@Override
public
void setImage(final MenuItem menuItem) {
imageFile = menuItem.getImage();
if (imageFile == null) {
return;
2016-10-09 20:20:23 +02:00
}
2021-01-31 20:05:40 +01:00
GtkEventDispatch.dispatch(()->{
appIndicator.app_indicator_set_icon(imageFile.getAbsolutePath());
2016-10-09 20:20:23 +02:00
2021-01-31 20:05:40 +01:00
if (!isActive) {
isActive = true;
appIndicator.app_indicator_set_status(AppIndicator.STATUS_ACTIVE);
}
});
}
2016-10-11 15:23:58 +02:00
2016-10-09 20:20:23 +02:00
@Override
public
void setText(final MenuItem menuItem) {
// no op.
}
2016-10-09 20:20:23 +02:00
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op.
}
2016-10-09 20:20:23 +02:00
@Override
public
void setTooltip(final MenuItem menuItem) {
// no op. see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
}
@Override
public
void remove() {
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
if (!shuttingDown.getAndSet(true)) {
super.remove();
2021-01-31 20:05:40 +01:00
GtkEventDispatch.dispatch(()->{
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
// STATUS_PASSIVE hides the indicator
savedAppIndicator.app_indicator_set_status(AppIndicator.STATUS_PASSIVE);
Pointer p = savedAppIndicator.getPointer();
GObject.g_object_unref(p);
GtkEventDispatch.shutdownGui();
});
2016-10-09 20:20:23 +02:00
}
}
};
2021-01-31 20:05:40 +01:00
GtkEventDispatch.dispatchAndWait(()->{
String id = "DBST" + System.nanoTime();
// we initialize with a blank image. Throws RuntimeException if not possible (this should never happen!)
// Ubuntu 17.10 REQUIRES this to be the correct tray image size, otherwise we get the error:
// GLib-GIO-CRITICAL **: g_dbus_proxy_new: assertion 'G_IS_DBUS_CONNECTION (connection)' failed
File image = imageResizeUtil.getTransparentImage(SizeAndScalingUtil.TRAY_MENU_SIZE);
appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS);
});
GtkEventDispatch.waitForEventsToComplete();
bind(gtkMenu, null, imageResizeUtil);
}
2016-10-11 15:23:58 +02:00
@Override
2017-06-15 00:45:06 +02:00
public
boolean hasImage() {
return imageFile != null;
2016-10-11 15:23:58 +02:00
}
2016-10-09 20:20:23 +02:00
}