/* * Copyright 2014 dorkbox, llc * * 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.linux; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.linux.jna.GEventCallback; import dorkbox.systemTray.linux.jna.GdkEventButton; import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; import javafx.application.Platform; /** * Class for handling all system tray interactions via GTK. *

* This is the "old" way to do it, and does not work with some desktop environments. */ public class GtkSystemTray extends GtkTypeSystemTray { private volatile Pointer trayIcon; // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) private final List gtkCallbacks = new ArrayList(); // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) private AtomicBoolean shuttingDown = new AtomicBoolean(); private volatile boolean isActive = false; public GtkSystemTray() { super(); Gtk.startGui(); final CountDownLatch blockUntilStarted = new CountDownLatch(1); dispatch(new Runnable() { @Override public void run() { final Pointer trayIcon_ = Gtk.gtk_status_icon_new(); trayIcon = trayIcon_; // necessary for gnome icon detection/placement because we move tray icons around by name. The name is hardcoded // in extension.js, so don't change it Gtk.gtk_status_icon_set_title(trayIcon_, "SystemTray"); final GEventCallback gtkCallback2 = new GEventCallback() { @Override public void callback(Pointer notUsed, final GdkEventButton event) { // BUTTON_PRESS only (any mouse click) if (event.type == 4) { Gtk.gtk_menu_popup(getMenu(), null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); } } }; final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon_, "button_press_event", gtkCallback2, null, 0); // have to do this to prevent GC on these objects gtkCallbacks.add(gtkCallback2); gtkCallbacks.add(button_press_event); blockUntilStarted.countDown(); } }); if (SystemTray.isJavaFxLoaded) { if (!Platform.isFxApplicationThread()) { try { blockUntilStarted.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } else if (SystemTray.isSwtLoaded) { if (SystemTray.FORCE_LINUX_TYPE != SystemTray.LINUX_GTK) { // GTK system tray has threading issues if we block here (because it is likely in the event thread) // AppIndicator version doesn't have this problem try { blockUntilStarted.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } else { try { blockUntilStarted.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } @SuppressWarnings("FieldRepeatedlyAccessedInMethod") @Override public void shutdown() { if (!shuttingDown.getAndSet(true)) { dispatch(new Runnable() { @Override public void run() { // this hides the indicator Gtk.gtk_status_icon_set_visible(trayIcon, false); Gobject.g_object_unref(trayIcon); // mark for GC trayIcon = null; gtkCallbacks.clear(); } }); super.shutdown(); } } @Override protected void setIcon_(final String iconPath) { dispatch(new Runnable() { @Override public void run() { Gtk.gtk_status_icon_set_from_file(trayIcon, iconPath); if (!isActive) { isActive = true; // can cause // Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed // Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed // it THIS PLACE IN CODE, these errors don't appear to happen. Nobody knows wtf why this is... This was trial and error Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray"); Gtk.gtk_status_icon_set_visible(trayIcon, true); } } }); } }