forked from dorkbox/SystemTray
Code polish/refactor. Removed GTK menu stuff
This commit is contained in:
parent
c2881e54fd
commit
2e9201f692
@ -28,12 +28,12 @@ import java.net.URL;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dorkbox.systemTray.linux.AppIndicatorTray;
|
||||
import dorkbox.systemTray.linux.GnomeShellExtension;
|
||||
import dorkbox.systemTray.linux.GtkSystemTray;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.swing.SwingSystemTray;
|
||||
import dorkbox.systemTray.swing._AppIndicatorTray;
|
||||
import dorkbox.systemTray.swing._GtkStatusIconTray;
|
||||
import dorkbox.systemTray.swing._SwingTray;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.Swt;
|
||||
@ -42,6 +42,7 @@ import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.IO;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.Property;
|
||||
import dorkbox.util.SwingUtil;
|
||||
import dorkbox.util.process.ShellProcessBuilder;
|
||||
|
||||
|
||||
@ -104,7 +105,7 @@ class SystemTray extends Menu {
|
||||
* <p>
|
||||
* This is an advanced feature, and it is recommended to leave at 0.
|
||||
*/
|
||||
public static int FORCE_TRAY_TYPE = 0;
|
||||
public static int FORCE_TRAY_TYPE = 2;
|
||||
|
||||
@Property
|
||||
/**
|
||||
@ -280,19 +281,19 @@ class SystemTray extends Menu {
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e1);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e1);
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,16 +336,26 @@ class SystemTray extends Menu {
|
||||
|
||||
if (DEBUG) {
|
||||
logger.debug("Currently using the '{}' desktop", XDG);
|
||||
|
||||
// Properties properties = System.getProperties();
|
||||
// for (Map.Entry<Object, Object> entry : properties.entrySet()) {
|
||||
// logger.debug(entry.getKey() + " : " + entry.getValue());
|
||||
// }
|
||||
}
|
||||
|
||||
// must always be set in case of forced tray types
|
||||
if ("kde".equalsIgnoreCase(XDG)) {
|
||||
isKDE = true;
|
||||
}
|
||||
|
||||
|
||||
if (trayType == null) {
|
||||
if ("unity".equalsIgnoreCase(XDG)) {
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e);
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -353,75 +364,31 @@ class SystemTray extends Menu {
|
||||
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
|
||||
// XFCE4 is OK to use appindicator, <XFCE4 we use GTKStatusIcon. God i wish there was an easy way to do this.
|
||||
boolean isNewXFCE = false;
|
||||
// so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
// ps aux | grep [x]fce
|
||||
final ShellProcessBuilder shell = new ShellProcessBuilder(outputStream);
|
||||
shell.setExecutable("ps");
|
||||
shell.addArgument("aux");
|
||||
shell.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
// should last us the next 20 years or so. XFCE development is glacially slow.
|
||||
isNewXFCE = output.contains("/xfce4/") || output.contains("/xfce5/") ||
|
||||
output.contains("/xfce6/") || output.contains("/xfce7/");
|
||||
} catch (Throwable e) {
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot detect what version of XFCE is running", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
logger.error("Is 'new' version of XFCE? {}", isNewXFCE);
|
||||
}
|
||||
|
||||
if (isNewXFCE) {
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e);
|
||||
}
|
||||
|
||||
// we can fail on AppIndicator, so this is the fallback
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
} catch (Throwable e1) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e1);
|
||||
}
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("lxde".equalsIgnoreCase(XDG)) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("kde".equalsIgnoreCase(XDG)) {
|
||||
isKDE = true;
|
||||
// kde (at least, plasma 5.5.6) requires appindicator
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize AppIndicatorTray", e);
|
||||
logger.error("Cannot initialize _AppIndicatorTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -435,34 +402,35 @@ class SystemTray extends Menu {
|
||||
|
||||
if ("cinnamon".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("ubuntu".equalsIgnoreCase(GDM)) {
|
||||
// have to install the gnome extension AND customize the restart command
|
||||
trayType = null;
|
||||
// unity panel service??
|
||||
GnomeShellExtension.SHELL_RESTART_COMMAND = "unity --replace &";
|
||||
}
|
||||
}
|
||||
@ -495,7 +463,7 @@ class SystemTray extends Menu {
|
||||
GnomeShellExtension.install(output);
|
||||
// we might be running gnome-shell, we MIGHT NOT. If we are forced to be app-indicator or swing, don't do this.
|
||||
if (trayType == null) {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
@ -533,7 +501,7 @@ class SystemTray extends Menu {
|
||||
if (readLine != null && readLine.contains("indicator-app")) {
|
||||
// make sure we can also load the library (it might be the wrong version)
|
||||
try {
|
||||
trayType = AppIndicatorTray.class;
|
||||
trayType = _AppIndicatorTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e);
|
||||
@ -558,7 +526,7 @@ class SystemTray extends Menu {
|
||||
|
||||
// fallback...
|
||||
if (trayType == null) {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " +
|
||||
"configuration");
|
||||
}
|
||||
@ -575,7 +543,7 @@ class SystemTray extends Menu {
|
||||
if (trayType == null && java.awt.SystemTray.isSupported()) {
|
||||
try {
|
||||
java.awt.SystemTray.getSystemTray();
|
||||
trayType = SwingSystemTray.class;
|
||||
trayType = _SwingTray.class;
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.", e);
|
||||
@ -591,7 +559,7 @@ class SystemTray extends Menu {
|
||||
systemTrayMenu = null;
|
||||
}
|
||||
else {
|
||||
Menu menu_ = null;
|
||||
final Menu[] menuReference = new Menu[1];
|
||||
|
||||
/*
|
||||
* appIndicator/gtk require strings (which is the path)
|
||||
@ -604,17 +572,17 @@ class SystemTray extends Menu {
|
||||
|
||||
try {
|
||||
if (OS.isLinux() &&
|
||||
trayType == AppIndicatorTray.class &&
|
||||
trayType == _AppIndicatorTray.class &&
|
||||
Gtk.isGtk2 &&
|
||||
AppIndicator.isVersion3) {
|
||||
|
||||
try {
|
||||
trayType = GtkSystemTray.class;
|
||||
trayType = _GtkStatusIconTray.class;
|
||||
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
|
||||
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
|
||||
} catch (Throwable e) {
|
||||
if (DEBUG) {
|
||||
logger.error("Cannot initialize GtkSystemTray", e);
|
||||
logger.error("Cannot initialize _GtkStatusIconTray", e);
|
||||
}
|
||||
logger.error("AppIndicator3 detected with GTK2 and unable to fallback to using GTK2 system tray type." +
|
||||
"AppIndicator3 requires GTK3 to be fully functional, and while this will work -- " +
|
||||
@ -623,14 +591,29 @@ class SystemTray extends Menu {
|
||||
}
|
||||
}
|
||||
|
||||
menu_ = (Menu) trayType.getConstructors()[0].newInstance(systemTray);
|
||||
// need to set this
|
||||
Gtk.isKDE = isKDE;
|
||||
|
||||
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
|
||||
// have to construct swing stuff inside the swing EDT
|
||||
// this is the safest way to do this.
|
||||
final Class<? extends Menu> finalTrayType = trayType;
|
||||
SwingUtil.invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
menuReference[0] = (Menu) finalTrayType.getConstructors()[0].newInstance(systemTray);
|
||||
logger.info("Successfully Loaded: {}", finalTrayType.getSimpleName());
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create tray type: '" + finalTrayType.getSimpleName() + "'", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
|
||||
}
|
||||
|
||||
systemTrayMenu = menu_;
|
||||
systemTrayMenu = menuReference[0];
|
||||
|
||||
|
||||
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
|
||||
@ -698,14 +681,14 @@ class SystemTray extends Menu {
|
||||
void shutdown() {
|
||||
final Menu menu = systemTrayMenu;
|
||||
|
||||
if (menu instanceof AppIndicatorTray) {
|
||||
((AppIndicatorTray) menu).shutdown();
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
((_AppIndicatorTray) menu).shutdown();
|
||||
}
|
||||
else if (menu instanceof GtkSystemTray) {
|
||||
((GtkSystemTray) menu).shutdown();
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
((_GtkStatusIconTray) menu).shutdown();
|
||||
} else {
|
||||
// swing
|
||||
((SwingSystemTray) menu).shutdown();
|
||||
((_SwingTray) menu).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,14 +698,14 @@ class SystemTray extends Menu {
|
||||
public
|
||||
String getStatus() {
|
||||
final Menu menu = systemTrayMenu;
|
||||
if (menu instanceof AppIndicatorTray) {
|
||||
return ((AppIndicatorTray) menu).getStatus();
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
return ((_AppIndicatorTray) menu).getStatus();
|
||||
}
|
||||
else if (menu instanceof GtkSystemTray) {
|
||||
return ((GtkSystemTray) menu).getStatus();
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
return ((_GtkStatusIconTray) menu).getStatus();
|
||||
} else {
|
||||
// swing
|
||||
return ((SwingSystemTray) menu).getStatus();
|
||||
return ((_SwingTray) menu).getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -734,28 +717,28 @@ class SystemTray extends Menu {
|
||||
public
|
||||
void setStatus(String statusText) {
|
||||
final Menu menu = systemTrayMenu;
|
||||
if (menu instanceof AppIndicatorTray) {
|
||||
((AppIndicatorTray) menu).setStatus(statusText);
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
((_AppIndicatorTray) menu).setStatus(statusText);
|
||||
}
|
||||
else if (menu instanceof GtkSystemTray) {
|
||||
((GtkSystemTray) menu).setStatus(statusText);
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
((_GtkStatusIconTray) menu).setStatus(statusText);
|
||||
} else {
|
||||
// swing
|
||||
((SwingSystemTray) menu).setStatus(statusText);
|
||||
((_SwingTray) menu).setStatus(statusText);
|
||||
}
|
||||
}
|
||||
|
||||
protected
|
||||
void setImage_(File iconPath) {
|
||||
final Menu menu = systemTrayMenu;
|
||||
if (menu instanceof AppIndicatorTray) {
|
||||
((AppIndicatorTray) menu).setImage_(iconPath);
|
||||
if (menu instanceof _AppIndicatorTray) {
|
||||
((_AppIndicatorTray) menu).setImage_(iconPath);
|
||||
}
|
||||
else if (menu instanceof GtkSystemTray) {
|
||||
((GtkSystemTray) menu).setImage_(iconPath);
|
||||
else if (menu instanceof _GtkStatusIconTray) {
|
||||
((_GtkStatusIconTray) menu).setImage_(iconPath);
|
||||
} else {
|
||||
// swing (windows/mac)
|
||||
((SwingSystemTray) menu).setImage_(iconPath);
|
||||
((_SwingTray) menu).setImage_(iconPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions.
|
||||
* <p/>
|
||||
* specialization for using app indicators in ubuntu unity
|
||||
* <p/>
|
||||
* 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)
|
||||
*
|
||||
* http://unity.ubuntu.com/projects/appindicators/
|
||||
*
|
||||
* http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator
|
||||
*
|
||||
* If we re-implement appindicators dbus functionality, we could POTENTIALLY do whatever we want...
|
||||
*
|
||||
* https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html
|
||||
* 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
|
||||
*/
|
||||
public
|
||||
class AppIndicatorTray extends GtkTypeSystemTray {
|
||||
private AppIndicatorInstanceStruct appIndicator;
|
||||
private boolean isActive = false;
|
||||
|
||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||
|
||||
public
|
||||
AppIndicatorTray(final SystemTray systemTray) {
|
||||
super(systemTray);
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon");
|
||||
}
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
Gtk.startGui();
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// we initialize with a blank image
|
||||
File image = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||
String id = System.nanoTime() + "DBST";
|
||||
appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||
}
|
||||
});
|
||||
|
||||
Gtk.waitForStartup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = appIndicator.getPointer();
|
||||
Gobject.g_object_unref(p);
|
||||
|
||||
appIndicator = null;
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void setImage_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
AppIndicator.app_indicator_set_icon(appIndicator, iconFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
|
||||
*/
|
||||
protected
|
||||
void onMenuAdded(final Pointer menu) {
|
||||
// see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
|
||||
AppIndicator.app_indicator_set_menu(appIndicator, menu);
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
abstract
|
||||
class GtkEntry implements MenuEntry {
|
||||
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final GtkMenu parent;
|
||||
final Pointer _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntry(final GtkMenu parent, final Pointer menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
}
|
||||
|
||||
public
|
||||
Menu getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
|
||||
*
|
||||
* always called on the DISPATCH thread
|
||||
*/
|
||||
abstract
|
||||
void setSpacerImage(final boolean everyoneElseHasImages);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void renderText(final String text);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
abstract
|
||||
void setImage_(final File imageFile);
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
* called when this item is removed. Necessary to cleanup/remove itself
|
||||
*/
|
||||
abstract
|
||||
void removePrivate();
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
if (enabled) {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE);
|
||||
} else {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setText(final String newText) {
|
||||
text = newText;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
if (imageFile == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String imagePath) {
|
||||
if (imagePath == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final URL imageUrl) {
|
||||
if (imageUrl == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void setImage(final InputStream imageStream) {
|
||||
if (imageStream == null) {
|
||||
setImage_(null);
|
||||
}
|
||||
else {
|
||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
||||
}
|
||||
}
|
||||
|
||||
// a child will always remove itself from the parent.
|
||||
@Override
|
||||
public final
|
||||
void remove() {
|
||||
parent.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_container_remove(parent._native, _native);
|
||||
Gtk.gtk_menu_shell_deactivate(parent._native, _native);
|
||||
|
||||
removePrivate();
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
|
||||
// have to rebuild the menu now...
|
||||
parent.deleteMenu();
|
||||
parent.createMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
int hashCode() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkEntry other = (GtkEntry) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.GCallback;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
class GtkEntryItem extends GtkEntry implements GCallback {
|
||||
private static File transparentIcon = null;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final NativeLong nativeLong;
|
||||
|
||||
// these have to be volatile, because they can be changed from any thread
|
||||
private volatile SystemTrayMenuAction callback;
|
||||
private volatile Pointer image;
|
||||
|
||||
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
|
||||
protected volatile boolean hasLegitIcon = true;
|
||||
|
||||
// The mnemonic will ONLY show-up once a menu entry is selected. IT WILL NOT show up before then!
|
||||
// AppIndicators will only show if you use the keyboard to navigate
|
||||
// GtkStatusIconTray will show on mouse+keyboard movement
|
||||
private volatile char mnemonicKey = 0;
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntryItem(final GtkMenu parent, final SystemTrayMenuAction callback) {
|
||||
super(parent, Gtk.gtk_image_menu_item_new_with_mnemonic(""));
|
||||
this.callback = callback;
|
||||
|
||||
// cannot be done in a static initializer, because the tray icon size might not yet have been determined
|
||||
if (transparentIcon == null) {
|
||||
transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE);
|
||||
nativeLong = Gobject.g_signal_connect_object(_native, "activate", this, null, 0);
|
||||
}
|
||||
else {
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
nativeLong = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
this.mnemonicKey = Character.toLowerCase(key);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
renderText(getText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final SystemTrayMenuAction callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
// called by native code
|
||||
@Override
|
||||
public
|
||||
int callback(final Pointer instance, final Pointer data) {
|
||||
final SystemTrayMenuAction cb = this.callback;
|
||||
if (cb != null) {
|
||||
Gtk.proxyClick(getParent(), GtkEntryItem.this, cb);
|
||||
}
|
||||
|
||||
return Gtk.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images.
|
||||
* This is primarily only with AppIndicators, although not always.
|
||||
* <p>
|
||||
* called on the DISPATCH thread
|
||||
*/
|
||||
void setSpacerImage(final boolean everyoneElseHasImages) {
|
||||
if (hasLegitIcon) {
|
||||
// we have a legit icon, so there is nothing else we can do.
|
||||
return;
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
if (everyoneElseHasImages) {
|
||||
image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
|
||||
Gtk.gtk_image_menu_item_set_image(_native, image);
|
||||
|
||||
// must always re-set always-show after setting the image
|
||||
Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE);
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
/**
|
||||
* must always be called in the GTK thread
|
||||
*/
|
||||
void renderText(String text) {
|
||||
if (this.mnemonicKey != 0) {
|
||||
// they are CASE INSENSITIVE!
|
||||
int i = text.toLowerCase()
|
||||
.indexOf(this.mnemonicKey);
|
||||
|
||||
if (i >= 0) {
|
||||
text = text.substring(0, i) + "_" + text.substring(i);
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.gtk_menu_item_set_label(_native, text);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
|
||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||
void setImage_(final File imageFile) {
|
||||
hasLegitIcon = imageFile != null;
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
|
||||
if (imageFile != null) {
|
||||
image = Gtk.gtk_image_new_from_file(imageFile.getAbsolutePath());
|
||||
Gtk.gtk_image_menu_item_set_image(_native, image);
|
||||
|
||||
// must always re-set always-show after setting the image
|
||||
Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE);
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void removePrivate() {
|
||||
callback = null;
|
||||
|
||||
if (image != null) {
|
||||
Gtk.gtk_widget_destroy(image);
|
||||
image = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
|
||||
import dorkbox.systemTray.MenuSpacer;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
class GtkEntrySeparator extends GtkEntry implements MenuSpacer {
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntrySeparator(final GtkMenu parent) {
|
||||
super(parent, Gtk.gtk_separator_menu_item_new());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSpacerImage(final boolean everyoneElseHasImages) {
|
||||
}
|
||||
|
||||
// called in the GTK thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setImage_(final File imageFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
void removePrivate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final SystemTrayMenuAction callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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 dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
// you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else,
|
||||
// where a GtkStatusIconTray + SwingTray will have everything lined up. (with or without icons). This is to normalize how it looks
|
||||
class GtkEntryStatus extends GtkEntryItem {
|
||||
|
||||
/**
|
||||
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
|
||||
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
|
||||
*/
|
||||
GtkEntryStatus(final GtkMenu parent, final String text) {
|
||||
super(parent, null);
|
||||
// need that extra space so it matches windows/mac
|
||||
hasLegitIcon = false;
|
||||
setText(text);
|
||||
}
|
||||
|
||||
// called in the GTK thread
|
||||
@Override
|
||||
void renderText(final String text) {
|
||||
// AppIndicator strips out markup text.
|
||||
// https://mail.gnome.org/archives/commits-list/2016-March/msg05444.html
|
||||
|
||||
Gtk.gtk_menu_item_set_label(_native, text);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
|
||||
Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setCallback(final SystemTrayMenuAction callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
}
|
||||
}
|
@ -1,483 +0,0 @@
|
||||
/*
|
||||
* 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 static dorkbox.systemTray.SystemTray.TIMEOUT;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
class GtkMenu extends Menu {
|
||||
// menu entry that this menu is attached to. Will be NULL when it's the system tray
|
||||
private final GtkEntryItem menuEntry;
|
||||
|
||||
// must ONLY be created at the end of delete!
|
||||
volatile Pointer _native;
|
||||
|
||||
// have to make sure no other methods can call obliterate, delete, or create menu once it's already started
|
||||
private boolean obliterateInProgress = false;
|
||||
|
||||
// called on dispatch
|
||||
GtkMenu(final SystemTray systemTray, final GtkMenu parent) {
|
||||
super(systemTray, parent);
|
||||
|
||||
if (parent != null) {
|
||||
this.menuEntry = new GtkEntryItem(parent, null);
|
||||
// by default, no callback on a menu entry means it's DISABLED. we have to undo that, because we don't have a callback for menus
|
||||
menuEntry.setEnabled(true);
|
||||
} else {
|
||||
this.menuEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to guarantee all updates occur on the dispatch thread
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
void dispatch(final Runnable runnable) {
|
||||
Gtk.dispatch(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Necessary to guarantee all updates occur on the dispatch thread
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
|
||||
// thread occur in REASONABLE time-frames, and alert the user if not.
|
||||
try {
|
||||
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
} else {
|
||||
throw new RuntimeException("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public
|
||||
void shutdown() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
obliterateMenu();
|
||||
|
||||
Gtk.shutdownGui();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables, or disables the sub-menu entry.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
if (enabled) {
|
||||
Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.TRUE);
|
||||
} else {
|
||||
Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void addSeparator() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
synchronized (menuEntries) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
GtkEntry menuEntry = new GtkEntrySeparator(GtkMenu.this);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setShortcut(final char key) {
|
||||
menuEntry.setShortcut(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
String getText() {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setText(final String newText) {
|
||||
menuEntry.setText(newText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final File imageFile) {
|
||||
menuEntry.setImage(imageFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final String imagePath) {
|
||||
menuEntry.setImage(imagePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final URL imageUrl) {
|
||||
menuEntry.setImage(imageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final String cacheName, final InputStream imageStream) {
|
||||
menuEntry.setImage(cacheName, imageStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void setImage(final InputStream imageStream) {
|
||||
menuEntry.setImage(imageStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return menuEntry.hasImage();
|
||||
}
|
||||
|
||||
// NO OP.
|
||||
@Override
|
||||
public
|
||||
void setCallback(final SystemTrayMenuAction callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called inside the gdk_threads block
|
||||
*/
|
||||
protected
|
||||
void onMenuAdded(final Pointer menu) {
|
||||
// only needed for AppIndicator
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
/**
|
||||
* Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object.
|
||||
*/
|
||||
void deleteMenu() {
|
||||
if (obliterateInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_native != null) {
|
||||
// have to remove all other menu entries
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final MenuEntry menuEntry__ = menuEntries.get(i);
|
||||
if (menuEntry__ instanceof GtkEntry) {
|
||||
GtkEntry entry = (GtkEntry) menuEntry__;
|
||||
|
||||
Gobject.g_object_force_floating(entry._native);
|
||||
Gtk.gtk_container_remove(_native, entry._native);
|
||||
}
|
||||
else if (menuEntry__ instanceof GtkMenu) {
|
||||
GtkMenu subMenu = (GtkMenu) menuEntry__;
|
||||
|
||||
Gobject.g_object_force_floating(subMenu.menuEntry._native);
|
||||
Gtk.gtk_container_remove(_native, subMenu.menuEntry._native);
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
}
|
||||
}
|
||||
|
||||
if (getParent() != null) {
|
||||
((GtkMenu) getParent()).deleteMenu();
|
||||
}
|
||||
|
||||
// makes a new one
|
||||
_native = Gtk.gtk_menu_new();
|
||||
|
||||
// binds sub-menu to entry (if it exists! it does not for the root menu)
|
||||
if (menuEntry != null) {
|
||||
Gtk.gtk_menu_item_set_submenu(menuEntry._native, _native);
|
||||
}
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
void createMenu() {
|
||||
if (obliterateInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getParent() != null) {
|
||||
((GtkMenu) getParent()).createMenu();
|
||||
}
|
||||
|
||||
boolean hasImages = false;
|
||||
|
||||
// now add back other menu entries
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final MenuEntry menuEntry__ = menuEntries.get(i);
|
||||
hasImages |= menuEntry__.hasImage();
|
||||
}
|
||||
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final MenuEntry menuEntry__ = menuEntries.get(i);
|
||||
// the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
|
||||
if (menuEntry__ instanceof GtkEntry) {
|
||||
GtkEntry entry = (GtkEntry) menuEntry__;
|
||||
entry.setSpacerImage(hasImages);
|
||||
|
||||
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
|
||||
Gtk.gtk_menu_shell_append(this._native, entry._native);
|
||||
Gobject.g_object_ref_sink(entry._native); // undoes "floating"
|
||||
}
|
||||
else if (menuEntry__ instanceof GtkMenu) {
|
||||
GtkMenu subMenu = (GtkMenu) menuEntry__;
|
||||
|
||||
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
|
||||
Gtk.gtk_menu_shell_append(this._native, subMenu.menuEntry._native);
|
||||
Gobject.g_object_ref_sink(subMenu.menuEntry._native); // undoes "floating"
|
||||
|
||||
if (subMenu.getParent() != GtkMenu.this) {
|
||||
// we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it
|
||||
subMenu.createMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMenuAdded(_native);
|
||||
Gtk.gtk_widget_show_all(_native);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* must be called on the dispatch thread
|
||||
*
|
||||
* Completely obliterates the menu, no possible way to reconstruct it.
|
||||
*/
|
||||
private
|
||||
void obliterateMenu() {
|
||||
if (_native != null && !obliterateInProgress) {
|
||||
obliterateInProgress = true;
|
||||
|
||||
// have to remove all other menu entries
|
||||
synchronized (menuEntries) {
|
||||
// a copy is made because sub-menus remove themselves from parents when .remove() is called. If we don't
|
||||
// do this, errors will be had because indices don't line up anymore.
|
||||
ArrayList<MenuEntry> menuEntriesCopy = new ArrayList<MenuEntry>(this.menuEntries);
|
||||
|
||||
for (int i = 0, menuEntriesSize = menuEntriesCopy.size(); i < menuEntriesSize; i++) {
|
||||
final MenuEntry menuEntry__ = menuEntriesCopy.get(i);
|
||||
menuEntry__.remove();
|
||||
}
|
||||
this.menuEntries.clear();
|
||||
menuEntriesCopy.clear();
|
||||
|
||||
Gtk.gtk_widget_destroy(_native);
|
||||
}
|
||||
|
||||
obliterateInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
*/
|
||||
protected
|
||||
MenuEntry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) {
|
||||
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
|
||||
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
// have to wait for the value
|
||||
final AtomicReference<MenuEntry> value = new AtomicReference<MenuEntry>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = get(menuText);
|
||||
if (menuEntry == null) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
menuEntry = new GtkEntryItem(GtkMenu.this, callback);
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
menuEntries.add(menuEntry);
|
||||
|
||||
createMenu();
|
||||
} else if (menuEntry instanceof GtkEntryItem) {
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
}
|
||||
|
||||
value.set(menuEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a new menu entry, or update one if it already exists
|
||||
*/
|
||||
protected
|
||||
Menu addMenu_(final String menuText, final File imagePath) {
|
||||
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
|
||||
// see: https://bugs.launchpad.net/glipper/+bug/1203888
|
||||
|
||||
if (menuText == null) {
|
||||
throw new NullPointerException("Menu text cannot be null");
|
||||
}
|
||||
|
||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
||||
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = get(menuText);
|
||||
if (menuEntry == null) {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
GtkMenu subMenu = new GtkMenu(getSystemTray(), GtkMenu.this);
|
||||
subMenu.setText(menuText);
|
||||
subMenu.setImage(imagePath);
|
||||
|
||||
menuEntries.add(subMenu);
|
||||
|
||||
value.set(subMenu);
|
||||
|
||||
createMenu();
|
||||
} else if (menuEntry instanceof GtkMenu) {
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
|
||||
value.set(((GtkMenu) menuEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return value.get();
|
||||
}
|
||||
|
||||
|
||||
// a child will always remove itself from the parent.
|
||||
@Override
|
||||
public
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
GtkMenu parent = (GtkMenu) getParent();
|
||||
|
||||
// have to remove from the parent.menuEntries first
|
||||
for (Iterator<MenuEntry> iterator = parent.menuEntries.iterator(); iterator.hasNext(); ) {
|
||||
final MenuEntry entry = iterator.next();
|
||||
if (entry == GtkMenu.this) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cleans up the menu
|
||||
parent.remove__(null);
|
||||
|
||||
// delete all of the children of this submenu (must happen before the menuEntry is removed)
|
||||
obliterateMenu();
|
||||
|
||||
// remove the gtk entry item from our parent menu NATIVE components
|
||||
// NOTE: this will rebuild the parent menu
|
||||
if (menuEntry != null) {
|
||||
menuEntry.remove();
|
||||
} else {
|
||||
// have to rebuild the menu now...
|
||||
parent.deleteMenu();
|
||||
parent.createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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 dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
|
||||
/**
|
||||
* Derived from
|
||||
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
|
||||
*/
|
||||
abstract
|
||||
class GtkTypeSystemTray extends GtkMenu {
|
||||
|
||||
GtkTypeSystemTray(final SystemTray systemTray) {
|
||||
super(systemTray, null);
|
||||
}
|
||||
|
||||
public
|
||||
String getStatus() {
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = menuEntries.get(0);
|
||||
if (menuEntry instanceof GtkEntryStatus) {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public
|
||||
void setStatus(final String statusText) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
GtkEntry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (GtkEntry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof GtkEntryStatus) {
|
||||
// always delete...
|
||||
remove(menuEntry);
|
||||
}
|
||||
|
||||
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
|
||||
// To work around this issue, we destroy then recreate the menu every time something is changed.
|
||||
deleteMenu();
|
||||
|
||||
if (menuEntry == null) {
|
||||
menuEntry = new GtkEntryStatus(GtkTypeSystemTray.this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
else if (menuEntry instanceof GtkEntryStatus) {
|
||||
// change the text?
|
||||
if (statusText != null) {
|
||||
menuEntry = new GtkEntryStatus(GtkTypeSystemTray.this, statusText);
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
||||
}
|
||||
|
||||
createMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ import dorkbox.systemTray.SystemTray;
|
||||
*
|
||||
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@SuppressWarnings({"Duplicates", "SameParameterValue", "DanglingJavadoc"})
|
||||
public
|
||||
class AppIndicator {
|
||||
public static boolean isVersion3 = false;
|
||||
@ -213,14 +213,14 @@ class AppIndicator {
|
||||
// See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
|
||||
|
||||
public static final int CATEGORY_APPLICATION_STATUS = 0;
|
||||
public static final int CATEGORY_COMMUNICATIONS = 1;
|
||||
public static final int CATEGORY_SYSTEM_SERVICES = 2;
|
||||
public static final int CATEGORY_HARDWARE = 3;
|
||||
public static final int CATEGORY_OTHER = 4;
|
||||
// public static final int CATEGORY_COMMUNICATIONS = 1;
|
||||
// public static final int CATEGORY_SYSTEM_SERVICES = 2;
|
||||
// public static final int CATEGORY_HARDWARE = 3;
|
||||
// public static final int CATEGORY_OTHER = 4;
|
||||
|
||||
public static final int STATUS_PASSIVE = 0;
|
||||
public static final int STATUS_ACTIVE = 1;
|
||||
public static final int STATUS_ATTENTION = 2;
|
||||
// public static final int STATUS_ATTENTION = 2;
|
||||
|
||||
|
||||
public static native AppIndicatorInstanceStruct app_indicator_new(String id, String icon_name, int category);
|
||||
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 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.jna;
|
||||
|
||||
import com.sun.jna.Callback;
|
||||
import com.sun.jna.Pointer;
|
||||
import dorkbox.util.Keep;
|
||||
|
||||
@Keep
|
||||
public
|
||||
interface GCallback extends Callback {
|
||||
/**
|
||||
* @return Gtk.TRUE if we handled this event
|
||||
*/
|
||||
int callback(Pointer instance, Pointer data);
|
||||
}
|
@ -35,11 +35,7 @@ class Gobject {
|
||||
|
||||
public static native void g_object_get(Pointer object, String objectName, PointerByReference objectVal, Pointer nullValue);
|
||||
|
||||
public static native void g_free(Pointer object);
|
||||
public static native void g_object_unref(Pointer object);
|
||||
|
||||
public static native void g_object_force_floating(Pointer object);
|
||||
public static native void g_object_ref_sink(Pointer object);
|
||||
|
||||
public static native NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags);
|
||||
}
|
||||
|
@ -24,10 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import com.sun.jna.Function;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.systemTray.util.JavaFX;
|
||||
import dorkbox.systemTray.util.Swt;
|
||||
|
||||
@ -379,23 +376,6 @@ class Gtk {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* required to properly setup the dispatch flag
|
||||
* @param callback will never be null.
|
||||
*/
|
||||
public static
|
||||
void proxyClick(final Menu parent, final MenuEntry menuEntry, final SystemTrayMenuAction callback) {
|
||||
Gtk.isDispatch = true;
|
||||
|
||||
try {
|
||||
callback.onClick(parent.getSystemTray(), parent, menuEntry);
|
||||
} catch (Throwable throwable) {
|
||||
SystemTray.logger.error("Error calling menu entry {} click event.", menuEntry.getText(), throwable);
|
||||
}
|
||||
|
||||
Gtk.isDispatch = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky
|
||||
* enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
|
||||
@ -421,22 +401,10 @@ class Gtk {
|
||||
|
||||
|
||||
public static native Pointer gtk_menu_new();
|
||||
public static native Pointer gtk_menu_item_set_submenu(Pointer menuEntry, Pointer menu);
|
||||
|
||||
|
||||
|
||||
public static native Pointer gtk_separator_menu_item_new();
|
||||
|
||||
// to create a menu entry WITH an icon.
|
||||
public static native Pointer gtk_image_new_from_file(String iconPath);
|
||||
|
||||
// uses '_' to define which key is the mnemonic
|
||||
public static native Pointer gtk_image_menu_item_new_with_mnemonic(String label);
|
||||
|
||||
public static native void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image);
|
||||
|
||||
public static native void gtk_image_menu_item_set_always_show_image(Pointer menu_item, int forceShow);
|
||||
|
||||
public static native Pointer gtk_status_icon_new();
|
||||
|
||||
public static native void gtk_status_icon_set_from_file(Pointer widget, String label);
|
||||
@ -450,18 +418,8 @@ class Gtk {
|
||||
|
||||
public static native void gtk_status_icon_set_name(Pointer widget, String name);
|
||||
|
||||
public static native void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time);
|
||||
|
||||
public static native void gtk_menu_item_set_label(Pointer menu_item, String label);
|
||||
|
||||
public static native void gtk_menu_shell_append(Pointer menu_shell, Pointer child);
|
||||
|
||||
public static native void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child);
|
||||
|
||||
public static native void gtk_widget_set_sensitive(Pointer widget, int sensitive);
|
||||
|
||||
public static native void gtk_container_remove(Pointer menu, Pointer subItem);
|
||||
|
||||
public static native void gtk_widget_show_all(Pointer widget);
|
||||
|
||||
public static native void gtk_widget_destroy(Pointer widget);
|
||||
|
@ -22,7 +22,6 @@ import javax.swing.JPopupMenu;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
class AdjustedJMenu extends JMenu {
|
||||
|
||||
AdjustedJMenu() {
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
abstract
|
||||
class SwingEntry implements MenuEntry {
|
||||
class Entry implements MenuEntry {
|
||||
private final int id = Menu.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
private final SwingMenu parent;
|
||||
@ -40,7 +40,7 @@ class SwingEntry implements MenuEntry {
|
||||
private volatile String text;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingEntry(final SwingMenu parent, final JComponent menuItem) {
|
||||
Entry(final SwingMenu parent, final JComponent menuItem) {
|
||||
this.parent = parent;
|
||||
this._native = menuItem;
|
||||
|
||||
@ -204,7 +204,7 @@ class SwingEntry implements MenuEntry {
|
||||
return false;
|
||||
}
|
||||
|
||||
SwingEntry other = (SwingEntry) obj;
|
||||
Entry other = (Entry) obj;
|
||||
return this.id == other.id;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import javax.swing.JMenuItem;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
class SwingEntryItem extends SwingEntry {
|
||||
class EntryItem extends Entry {
|
||||
|
||||
private final ActionListener swingCallback;
|
||||
|
||||
@ -33,7 +33,7 @@ class SwingEntryItem extends SwingEntry {
|
||||
private volatile SystemTrayMenuAction callback;
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingEntryItem(final SwingMenu parent, final SystemTrayMenuAction callback) {
|
||||
EntryItem(final SwingMenu parent, final SystemTrayMenuAction callback) {
|
||||
super(parent, new AdjustedJMenuItem());
|
||||
this.callback = callback;
|
||||
|
@ -22,10 +22,10 @@ import javax.swing.JSeparator;
|
||||
import dorkbox.systemTray.MenuSpacer;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
|
||||
class SwingEntrySeparator extends SwingEntry implements MenuSpacer {
|
||||
class EntrySeparator extends Entry implements MenuSpacer {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingEntrySeparator(final SwingMenu parent) {
|
||||
EntrySeparator(final SwingMenu parent) {
|
||||
super(parent, new JSeparator(JSeparator.HORIZONTAL));
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ import javax.swing.JMenuItem;
|
||||
import dorkbox.systemTray.MenuStatus;
|
||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||
|
||||
class SwingEntryStatus extends SwingEntry implements MenuStatus {
|
||||
class EntryStatus extends Entry implements MenuStatus {
|
||||
|
||||
// this is ALWAYS called on the EDT.
|
||||
SwingEntryStatus(final SwingMenu parent, final String label) {
|
||||
EntryStatus(final SwingMenu parent, final String label) {
|
||||
super(parent, new JMenuItem());
|
||||
setText(label);
|
||||
}
|
@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 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.swing;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
@ -7,20 +22,12 @@ import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract
|
||||
class SwingGenericTray extends SwingMenu {
|
||||
class GenericTray extends SwingMenu {
|
||||
/**
|
||||
* Called in the EDT
|
||||
*
|
||||
* @param systemTray
|
||||
* the system tray (which is the object that sits in the system tray)
|
||||
* @param parent
|
||||
* @param _native
|
||||
*/
|
||||
SwingGenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||
GenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||
super(systemTray, parent, _native);
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
@ -30,7 +37,7 @@ class SwingGenericTray extends SwingMenu {
|
||||
String getStatus() {
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = menuEntries.get(0);
|
||||
if (menuEntry instanceof SwingEntryStatus) {
|
||||
if (menuEntry instanceof EntryStatus) {
|
||||
return menuEntry.getText();
|
||||
}
|
||||
}
|
||||
@ -47,12 +54,12 @@ class SwingGenericTray extends SwingMenu {
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// status is ALWAYS at 0 index...
|
||||
SwingEntry menuEntry = null;
|
||||
Entry menuEntry = null;
|
||||
if (!menuEntries.isEmpty()) {
|
||||
menuEntry = (SwingEntry) menuEntries.get(0);
|
||||
menuEntry = (Entry) menuEntries.get(0);
|
||||
}
|
||||
|
||||
if (menuEntry instanceof SwingEntryStatus) {
|
||||
if (menuEntry instanceof EntryStatus) {
|
||||
// set the text or delete...
|
||||
|
||||
if (statusText == null) {
|
||||
@ -66,7 +73,7 @@ class SwingGenericTray extends SwingMenu {
|
||||
|
||||
} else {
|
||||
// create a new one
|
||||
menuEntry = new SwingEntryStatus(_this, statusText);
|
||||
menuEntry = new EntryStatus(_this, statusText);
|
||||
// status is ALWAYS at 0 index...
|
||||
menuEntries.add(0, menuEntry);
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
/*
|
||||
* 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.swing;
|
||||
|
||||
import static dorkbox.systemTray.SystemTray.TIMEOUT;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.File;
|
||||
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 javax.swing.JPopupMenu;
|
||||
|
||||
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 dorkbox.util.ScreenUtil;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions via GTK.
|
||||
* <p/>
|
||||
* This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the
|
||||
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
||||
*/
|
||||
public
|
||||
class GtkStatusIconTray extends SwingGenericTray {
|
||||
private volatile Pointer trayIcon;
|
||||
|
||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||
// https://github.com/djdeath/glib/blob/master/gobject/gobject.c
|
||||
|
||||
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
||||
private final List<Object> gtkCallbacks = new ArrayList<Object>();
|
||||
|
||||
// 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;
|
||||
|
||||
// called on the EDT
|
||||
public
|
||||
GtkStatusIconTray(final SystemTray systemTray) {
|
||||
super(systemTray, null, new SwingSystemTrayMenuPopup());
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator");
|
||||
}
|
||||
|
||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||
popupMenu.pack();
|
||||
popupMenu.setFocusable(true);
|
||||
|
||||
final Runnable popupRunnable = new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Dimension size = _native.getPreferredSize();
|
||||
|
||||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
||||
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
|
||||
if (y < bounds.y) {
|
||||
y = bounds.y;
|
||||
}
|
||||
else if (y + size.height > bounds.y + bounds.height) {
|
||||
// our menu cannot have the top-edge snap to the mouse
|
||||
// so we make the bottom-edge snap to the mouse
|
||||
y -= size.height; // snap to edge of mouse
|
||||
}
|
||||
|
||||
if (x < bounds.x) {
|
||||
x = bounds.x;
|
||||
}
|
||||
else if (x + size.width > bounds.x + bounds.width) {
|
||||
// our menu cannot have the left-edge snap to the mouse
|
||||
// so we make the right-edge snap to the mouse
|
||||
x -= size.width; // snap to edge of mouse
|
||||
}
|
||||
|
||||
SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native;
|
||||
popupMenu.doShow(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
||||
|
||||
Gtk.startGui();
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
final Pointer trayIcon_ = Gtk.gtk_status_icon_new();
|
||||
trayIcon = trayIcon_;
|
||||
|
||||
final GEventCallback gtkCallback = new GEventCallback() {
|
||||
@Override
|
||||
public
|
||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||
// show the swing menu on the EDT
|
||||
// BUTTON_PRESS only (any mouse click)
|
||||
if (event.type == 4) {
|
||||
// show the swing menu on the EDT
|
||||
dispatch(popupRunnable);
|
||||
}
|
||||
}
|
||||
};
|
||||
final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback,
|
||||
null, 0);
|
||||
|
||||
// have to do this to prevent GC on these objects
|
||||
gtkCallbacks.add(gtkCallback);
|
||||
gtkCallbacks.add(button_press_event);
|
||||
}
|
||||
});
|
||||
|
||||
Gtk.waitForStartup();
|
||||
|
||||
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// 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
|
||||
|
||||
// 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
|
||||
Gtk.gtk_status_icon_set_title(trayIcon, "SystemTray");
|
||||
|
||||
// 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
|
||||
|
||||
// ... so, bizzaro things going on here. These errors DO NOT happen if JavaFX is dispatching the events.
|
||||
// BUT this is REQUIRED when running JavaFX. For unknown reasons, the title isn't pushed to GTK, so our
|
||||
// gnome-shell extension cannot see our tray icon -- so naturally, it won't move it to the "top" area and
|
||||
// we appear broken.
|
||||
if (SystemTray.isJavaFxLoaded) {
|
||||
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
|
||||
public
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
// this hides the indicator
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
||||
Gobject.g_object_unref(trayIcon);
|
||||
|
||||
// mark for GC
|
||||
trayIcon = null;
|
||||
gtkCallbacks.clear();
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
|
||||
// thread occur in REASONABLE time-frames, and alert the user if not.
|
||||
try {
|
||||
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
|
||||
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to shutdown. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
SystemTray.logger.error("Error waiting for shutdown dispatch to complete.", new Exception());
|
||||
}
|
||||
|
||||
Gtk.shutdownGui();
|
||||
|
||||
// uses EDT
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void setImage_(final File iconFile) {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
|
||||
|
||||
if (!isActive) {
|
||||
isActive = true;
|
||||
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
|
||||
import static dorkbox.systemTray.swing.SwingEntry.getVkKey;
|
||||
import static dorkbox.systemTray.swing.Entry.getVkKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -38,7 +38,7 @@ import dorkbox.util.SwingUtil;
|
||||
class SwingMenu extends Menu {
|
||||
|
||||
// sub-menu = AdjustedJMenu
|
||||
// systemtray = SwingSystemTrayMenuPopup
|
||||
// systemtray = TrayPopup
|
||||
volatile JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
@ -84,7 +84,7 @@ class SwingMenu extends Menu {
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
synchronized (menuEntries) {
|
||||
MenuEntry menuEntry = new SwingEntrySeparator(SwingMenu.this);
|
||||
MenuEntry menuEntry = new EntrySeparator(SwingMenu.this);
|
||||
menuEntries.add(menuEntry);
|
||||
}
|
||||
}
|
||||
@ -121,12 +121,12 @@ class SwingMenu extends Menu {
|
||||
|
||||
if (menuEntry == null) {
|
||||
// must always be called on the EDT
|
||||
menuEntry = new SwingEntryItem(SwingMenu.this, callback);
|
||||
menuEntry = new EntryItem(SwingMenu.this, callback);
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
|
||||
menuEntries.add(menuEntry);
|
||||
} else if (menuEntry instanceof SwingEntryItem) {
|
||||
} else if (menuEntry instanceof EntryItem) {
|
||||
menuEntry.setText(menuText);
|
||||
menuEntry.setImage(imagePath);
|
||||
}
|
||||
@ -317,8 +317,8 @@ class SwingMenu extends Menu {
|
||||
public
|
||||
void run() {
|
||||
_native.setVisible(false);
|
||||
if (_native instanceof SwingSystemTrayMenuPopup) {
|
||||
((SwingSystemTrayMenuPopup) _native).close();
|
||||
if (_native instanceof TrayPopup) {
|
||||
((TrayPopup) _native).close();
|
||||
}
|
||||
|
||||
SwingMenu parent = (SwingMenu) getParent();
|
||||
|
@ -15,8 +15,11 @@
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowFocusListener;
|
||||
import java.io.File;
|
||||
@ -33,23 +36,23 @@ import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.ScreenUtil;
|
||||
|
||||
/**
|
||||
* This custom popup is required if we want to be able to show images on the menu,
|
||||
*
|
||||
* This is our "golden standard" since we have 100% control over it.
|
||||
*/
|
||||
public
|
||||
class SwingSystemTrayMenuPopup extends JPopupMenu {
|
||||
class TrayPopup extends JPopupMenu {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// NOTE: we can use the "hidden dialog" focus window trick...
|
||||
private JDialog hiddenDialog;
|
||||
private volatile File iconFile;
|
||||
private volatile Runnable runnable;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public
|
||||
SwingSystemTrayMenuPopup() {
|
||||
TrayPopup() {
|
||||
super();
|
||||
setFocusable(true);
|
||||
// setBorder(new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0)); // borderUI resource border type will get changed!
|
||||
@ -106,7 +109,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
|
||||
hiddenDialog.addWindowFocusListener(new WindowFocusListener() {
|
||||
@Override
|
||||
public void windowLostFocus (WindowEvent we ) {
|
||||
SwingSystemTrayMenuPopup.this.setVisible(false);
|
||||
TrayPopup.this.setVisible(false);
|
||||
}
|
||||
@Override
|
||||
public void windowGainedFocus (WindowEvent we) {
|
||||
@ -133,8 +136,62 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
|
||||
}
|
||||
}
|
||||
|
||||
void setOnHideRunnable(final Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void doShow(final int x, final int y) {
|
||||
void setVisible(final boolean b) {
|
||||
if (!b) {
|
||||
Runnable r = this.runnable;
|
||||
if (r != null) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
super.setVisible(b);
|
||||
}
|
||||
|
||||
void close() {
|
||||
hiddenDialog.setVisible(false);
|
||||
hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
|
||||
void doShow(final Point point, final int offset) {
|
||||
|
||||
Dimension size = getPreferredSize();
|
||||
|
||||
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
||||
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
|
||||
if (y < bounds.y) {
|
||||
y = bounds.y;
|
||||
}
|
||||
else if (y + size.height > bounds.y + bounds.height) {
|
||||
// our menu cannot have the top-edge snap to the mouse
|
||||
// so we make the bottom-edge snap to the mouse
|
||||
y -= size.height; // snap to edge of mouse
|
||||
}
|
||||
|
||||
if (x < bounds.x) {
|
||||
x = bounds.x;
|
||||
|
||||
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||
}
|
||||
else if (x + size.width > bounds.x + bounds.width) {
|
||||
// our menu cannot have the left-edge snap to the mouse so we make the right-edge snap to the mouse
|
||||
x -= size.width; // snap right edge of menu to mouse
|
||||
|
||||
x += offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||
} else {
|
||||
x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||
}
|
||||
|
||||
System.err.println("SHOWING POPUP @" + x + "," + y);
|
||||
|
||||
|
||||
// critical to get the keyboard listeners working for the popup menu
|
||||
setInvoker(hiddenDialog.getContentPane());
|
||||
|
||||
@ -146,8 +203,5 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
|
||||
requestFocusInWindow();
|
||||
}
|
||||
|
||||
void close() {
|
||||
hiddenDialog.setVisible(false);
|
||||
hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
|
||||
}
|
@ -15,19 +15,11 @@
|
||||
*/
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import static dorkbox.systemTray.SystemTray.TIMEOUT;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.ptr.PointerByReference;
|
||||
@ -40,7 +32,6 @@ import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||
import dorkbox.systemTray.linux.jna.Gobject;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.systemTray.util.ImageUtils;
|
||||
import dorkbox.util.ScreenUtil;
|
||||
import dorkbox.util.SwingUtil;
|
||||
|
||||
/**
|
||||
@ -89,7 +80,7 @@ import dorkbox.util.SwingUtil;
|
||||
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
|
||||
*/
|
||||
public
|
||||
class AppIndicatorTray extends SwingGenericTray {
|
||||
class _AppIndicatorTray extends GenericTray {
|
||||
private AppIndicatorInstanceStruct appIndicator;
|
||||
private boolean isActive = false;
|
||||
|
||||
@ -102,56 +93,21 @@ class AppIndicatorTray extends SwingGenericTray {
|
||||
private final Runnable popupRunnable;
|
||||
|
||||
public
|
||||
AppIndicatorTray(final SystemTray systemTray) {
|
||||
super(systemTray,null, new SwingSystemTrayMenuPopup());
|
||||
_AppIndicatorTray(final SystemTray systemTray) {
|
||||
super(systemTray,null, new TrayPopup());
|
||||
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon");
|
||||
}
|
||||
|
||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
popupMenu.pack();
|
||||
popupMenu.setFocusable(true);
|
||||
|
||||
popupRunnable = new Runnable() {
|
||||
popupMenu.setOnHideRunnable(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Dimension size = _native.getPreferredSize();
|
||||
|
||||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
||||
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
|
||||
if (y < bounds.y) {
|
||||
y = bounds.y;
|
||||
}
|
||||
else if (y + size.height > bounds.y + bounds.height) {
|
||||
// our menu cannot have the top-edge snap to the mouse
|
||||
// so we make the bottom-edge snap to the mouse
|
||||
y -= size.height; // snap to edge of mouse
|
||||
}
|
||||
|
||||
if (x < bounds.x) {
|
||||
x = bounds.x;
|
||||
|
||||
x -= 32; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||
}
|
||||
else if (x + size.width > bounds.x + bounds.width) {
|
||||
// our menu cannot have the left-edge snap to the mouse
|
||||
// so we make the right-edge snap to the mouse
|
||||
x -= size.width; // snap to edge of mouse
|
||||
|
||||
x += 32; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||
}
|
||||
|
||||
SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native;
|
||||
popupMenu.doShow(x, y);
|
||||
|
||||
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
@ -162,11 +118,23 @@ class AppIndicatorTray extends SwingGenericTray {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
popupRunnable = new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
|
||||
}
|
||||
};
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
||||
// trayIcon.setToolTip(_SwingTray.this.appName);
|
||||
|
||||
Gtk.startGui();
|
||||
|
||||
@ -219,36 +187,20 @@ class AppIndicatorTray extends SwingGenericTray {
|
||||
public
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
try {
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = appIndicator.getPointer();
|
||||
Gobject.g_object_unref(p);
|
||||
// STATUS_PASSIVE hides the indicator
|
||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||
Pointer p = appIndicator.getPointer();
|
||||
Gobject.g_object_unref(p);
|
||||
|
||||
appIndicator = null;
|
||||
} finally {
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
appIndicator = null;
|
||||
}
|
||||
});
|
||||
|
||||
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
|
||||
// thread occur in REASONABLE time-frames, and alert the user if not.
|
||||
try {
|
||||
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
|
||||
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to shutdown. Please adjust " +
|
||||
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
SystemTray.logger.error("Error waiting for shutdown dispatch to complete.", new Exception());
|
||||
}
|
||||
|
||||
Gtk.shutdownGui();
|
||||
|
||||
// uses EDT
|
||||
@ -284,7 +236,7 @@ class AppIndicatorTray extends SwingGenericTray {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(imageFile);
|
||||
((TrayPopup) _native).setTitleBarImage(imageFile);
|
||||
}
|
||||
});
|
||||
}
|
@ -13,13 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.systemTray.linux;
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
@ -28,15 +32,15 @@ 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 dorkbox.systemTray.util.ImageUtils;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interactions via GTK.
|
||||
* <p/>
|
||||
* This is the "old" way to do it, and does not work with some desktop environments.
|
||||
* This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the
|
||||
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
||||
*/
|
||||
public
|
||||
class GtkSystemTray extends GtkTypeSystemTray {
|
||||
class _GtkStatusIconTray extends GenericTray {
|
||||
private volatile Pointer trayIcon;
|
||||
|
||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||
@ -50,18 +54,38 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
|
||||
private volatile boolean isActive = false;
|
||||
|
||||
// called on the EDT
|
||||
public
|
||||
GtkSystemTray(final SystemTray systemTray) {
|
||||
super(systemTray);
|
||||
_GtkStatusIconTray(final SystemTray systemTray) {
|
||||
super(systemTray, null, new TrayPopup());
|
||||
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) {
|
||||
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator");
|
||||
}
|
||||
|
||||
ImageUtils.determineIconSize();
|
||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||
popupMenu.pack();
|
||||
popupMenu.setFocusable(true);
|
||||
|
||||
final Runnable popupRunnable = new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
popupMenu.doShow(point, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip(_SwingTray.this.appName);
|
||||
|
||||
Gtk.startGui();
|
||||
|
||||
dispatch(new Runnable() {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
@ -72,14 +96,16 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
@Override
|
||||
public
|
||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||
// show the swing menu on the EDT
|
||||
// BUTTON_PRESS only (any mouse click)
|
||||
if (event.type == 4) {
|
||||
Gtk.gtk_menu_popup(_native, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time);
|
||||
// show the swing menu on the EDT
|
||||
dispatch(popupRunnable);
|
||||
}
|
||||
}
|
||||
};
|
||||
final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback,
|
||||
null, 0);
|
||||
final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event",
|
||||
gtkCallback, null, 0);
|
||||
|
||||
// have to do this to prevent GC on these objects
|
||||
gtkCallbacks.add(gtkCallback);
|
||||
@ -90,7 +116,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
Gtk.waitForStartup();
|
||||
|
||||
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
||||
dispatch(new Runnable() {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
@ -118,11 +144,11 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
|
||||
|
||||
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
|
||||
@Override
|
||||
public
|
||||
void shutdown() {
|
||||
if (!shuttingDown.getAndSet(true)) {
|
||||
dispatchAndWait(new Runnable() {
|
||||
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
@ -136,13 +162,16 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
Gtk.shutdownGui();
|
||||
|
||||
// uses EDT
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public
|
||||
void setImage_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
Gtk.dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
@ -154,5 +183,13 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
((TrayPopup) _native).setTitleBarImage(iconFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -16,10 +16,7 @@
|
||||
package dorkbox.systemTray.swing;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@ -30,7 +27,6 @@ import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import dorkbox.systemTray.MenuEntry;
|
||||
import dorkbox.util.ScreenUtil;
|
||||
|
||||
/**
|
||||
* Class for handling all system tray interaction, via SWING.
|
||||
@ -42,16 +38,16 @@ import dorkbox.util.ScreenUtil;
|
||||
*/
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class SwingSystemTray extends SwingGenericTray {
|
||||
class _SwingTray extends GenericTray {
|
||||
volatile SystemTray tray;
|
||||
volatile TrayIcon trayIcon;
|
||||
|
||||
// Called in the EDT
|
||||
public
|
||||
SwingSystemTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||
super(systemTray, null, new SwingSystemTrayMenuPopup());
|
||||
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||
super(systemTray, null, new TrayPopup());
|
||||
|
||||
SwingSystemTray.this.tray = SystemTray.getSystemTray();
|
||||
_SwingTray.this.tray = SystemTray.getSystemTray();
|
||||
}
|
||||
|
||||
public
|
||||
@ -94,40 +90,14 @@ class SwingSystemTray extends SwingGenericTray {
|
||||
|
||||
// appindicators DO NOT support anything other than PLAIN gtk-menus
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
||||
// trayIcon.setToolTip(_SwingTray.this.appName);
|
||||
|
||||
trayIcon.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public
|
||||
void mousePressed(MouseEvent e) {
|
||||
Dimension size = _native.getPreferredSize();
|
||||
|
||||
Point point = e.getPoint();
|
||||
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
||||
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
|
||||
if (y < bounds.y) {
|
||||
y = bounds.y;
|
||||
}
|
||||
else if (y + size.height > bounds.y + bounds.height) {
|
||||
// our menu cannot have the top-edge snap to the mouse
|
||||
// so we make the bottom-edge snap to the mouse
|
||||
y -= size.height; // snap to edge of mouse
|
||||
}
|
||||
|
||||
if (x < bounds.x) {
|
||||
x = bounds.x;
|
||||
}
|
||||
else if (x + size.width > bounds.x + bounds.width) {
|
||||
// our menu cannot have the left-edge snap to the mouse
|
||||
// so we make the right-edge snap to the mouse
|
||||
x -= size.width; // snap to edge of mouse
|
||||
}
|
||||
|
||||
SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native;
|
||||
popupMenu.doShow(x, y);
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
popupMenu.doShow(e.getPoint(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
@ -140,7 +110,7 @@ class SwingSystemTray extends SwingGenericTray {
|
||||
trayIcon.setImage(trayImage);
|
||||
}
|
||||
|
||||
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile);
|
||||
((TrayPopup) _native).setTitleBarImage(iconFile);
|
||||
}
|
||||
});
|
||||
}
|
@ -36,6 +36,7 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.linux.jna.Gtk;
|
||||
import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.FileUtil;
|
||||
import dorkbox.util.LocationResolver;
|
||||
@ -89,6 +90,11 @@ class ImageUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// 1 = 16
|
||||
// 2 = 32
|
||||
// 4 = 64
|
||||
// 8 = 128
|
||||
|
||||
if (windowsVersion.startsWith("5.1")) {
|
||||
// Windows XP 5.1.2600
|
||||
scalingFactor = 1;
|
||||
@ -139,46 +145,97 @@ class ImageUtils {
|
||||
} else if (OS.isLinux()) {
|
||||
// GtkStatusIcon will USUALLY automatically scale the icon
|
||||
// AppIndicator MIGHT scale the icon (depends on the OS)
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
// gsettings get org.gnome.desktop.interface scaling-factor
|
||||
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||
shellVersion.setExecutable("gsettings");
|
||||
shellVersion.addArgument("get");
|
||||
shellVersion.addArgument("org.gnome.desktop.interface");
|
||||
shellVersion.addArgument("scaling-factor");
|
||||
shellVersion.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
// KDE is bonkers.
|
||||
if (Gtk.isKDE) {
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
if (!output.isEmpty()) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.debug("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output);
|
||||
// plasma-desktop -v
|
||||
// plasmashell --version
|
||||
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||
shellVersion.setExecutable("plasmashell");
|
||||
shellVersion.addArgument("--version");
|
||||
shellVersion.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
|
||||
if (!output.isEmpty()) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.debug("Checking plasma KDE environment, should start with 'plasmashell', value: '{}'", output);
|
||||
}
|
||||
|
||||
// DEFAULT icon size is 16. KDE is bananas on what they did with tray icon scale
|
||||
// should be: plasmashell 5.6.5 or something
|
||||
String s = "plasmashell ";
|
||||
if (output.contains(s)) {
|
||||
String value = output.substring(output.indexOf(s) + s.length(), output.length() - 1);
|
||||
|
||||
// 1 = 16
|
||||
// 2 = 32
|
||||
// 4 = 64
|
||||
// 8 = 128
|
||||
if (value.startsWith("4")) {
|
||||
scalingFactor = 2;
|
||||
} else if (value.startsWith("5")) {
|
||||
scalingFactor = 8; // it is insane how large the icon is
|
||||
} else {
|
||||
// assume very low version of plasmashell, default 32
|
||||
scalingFactor = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well.
|
||||
// should be: uint32 0 or something
|
||||
if (output.contains("uint32")) {
|
||||
String value = output.substring(output.indexOf("uint")+7, output.length()-1);
|
||||
scalingFactor = Integer.parseInt(value);
|
||||
|
||||
// 0 is disabled (no scaling)
|
||||
// 1 is enabled (default scale)
|
||||
// 2 is 2x scale
|
||||
// 3 is 3x scale
|
||||
// etc
|
||||
|
||||
|
||||
// A setting of 2, 3, etc, which is all you can do with scaling-factor
|
||||
// To enable HiDPI, use gsettings:
|
||||
// gsettings set org.gnome.desktop.interface scaling-factor 2
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Cannot check plasmashell version", e);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Cannot check scaling factor", e);
|
||||
} else {
|
||||
// it's a GTK environment or something
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||
|
||||
// gsettings get org.gnome.desktop.interface scaling-factor
|
||||
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||
shellVersion.setExecutable("gsettings");
|
||||
shellVersion.addArgument("get");
|
||||
shellVersion.addArgument("org.gnome.desktop.interface");
|
||||
shellVersion.addArgument("scaling-factor");
|
||||
shellVersion.start();
|
||||
|
||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||
|
||||
if (!output.isEmpty()) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.debug("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output);
|
||||
}
|
||||
|
||||
// DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well.
|
||||
// should be: uint32 0 or something
|
||||
if (output.contains("uint32")) {
|
||||
String value = output.substring(output.indexOf("uint")+7, output.length()-1);
|
||||
scalingFactor = Integer.parseInt(value);
|
||||
|
||||
// 0 is disabled (no scaling)
|
||||
// 1 is enabled (default scale)
|
||||
// 2 is 2x scale
|
||||
// 3 is 3x scale
|
||||
// etc
|
||||
|
||||
|
||||
// A setting of 2, 3, etc, which is all you can do with scaling-factor
|
||||
// To enable HiDPI, use gsettings:
|
||||
// gsettings set org.gnome.desktop.interface scaling-factor 2
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (SystemTray.DEBUG) {
|
||||
SystemTray.logger.error("Cannot check scaling factor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (OS.isMacOsX()) {
|
||||
@ -222,6 +279,7 @@ class ImageUtils {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static
|
||||
File getTransparentImage(final int size) {
|
||||
File newFile = new File(TEMP_DIR, size + "_empty.png").getAbsoluteFile();
|
||||
@ -243,6 +301,7 @@ class ImageUtils {
|
||||
return newFile;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static
|
||||
BufferedImage getTransparentImageAsImage(final int size) {
|
||||
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
|
Loading…
Reference in New Issue
Block a user