Updated to better support gnome-shell desktop environments.
This commit is contained in:
parent
ac74d8bdd7
commit
c8fac55967
46
README.md
46
README.md
|
@ -8,24 +8,44 @@ This libraries only purpose is to show *reasonably* decent system-tray icons and
|
||||||
There are a number of problems on Linux with the Swing (and SWT) system-tray icons, namely that:
|
There are a number of problems on Linux with the Swing (and SWT) system-tray icons, namely that:
|
||||||
|
|
||||||
1. Swing system-tray icons on linux **do not** support transparent backgrounds (they have a white background)
|
1. Swing system-tray icons on linux **do not** support transparent backgrounds (they have a white background)
|
||||||
2. Swing/SWT **do not** support app-indicators, which are necessary on more recent versions of linux
|
2. Swing/SWT **do not** support app-indicators, which are necessary on more recent versions of gnu/linux distros.
|
||||||
3. Swing popup menus look like crap
|
3. Swing popup menus look like crap
|
||||||
- swing-based system-tray uses a JMenuPopup, which looks nicer than the java 'regular' one.
|
- swing-based system-tray uses a JMenuPopup, which looks nicer than the java 'regular' one.
|
||||||
- app-indicators use native popups (a system limitation).
|
- app-indicators use native popups.
|
||||||
|
- gtk-indicators use native popups.
|
||||||
|
|
||||||
|
|
||||||
This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windows 32/64. Java 6+
|
This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windows 32/64. Java 6+
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
To customize the delay (for hiding the popup) when the cursor is "moused out" of the
|
Customization parameters:
|
||||||
popup menu, change the value of 'SystemTrayMenuPopup.hidePopupDelay'
|
|
||||||
|
SystemTrayMenuPopup.hidePopupDelay (type long, default value '1000L')
|
||||||
|
- Allows you to customize the delay (for hiding the popup) when the cursor is "moused out" of the popup menu
|
||||||
|
|
||||||
|
GnomeShellExtension.ENABLE_SHELL_RESTART (type boolean, default value 'true')
|
||||||
|
- Permit the gnome-shell to be restarted when the extension is installed.
|
||||||
|
|
||||||
|
|
||||||
|
GnomeShellExtension.SHELL_RESTART_TIMEOUT_MILLIS (type long, default value '5000L')
|
||||||
|
- Default timeout to wait for the gnome-shell to completely restart. This is a best-guess estimate.
|
||||||
|
|
||||||
|
|
||||||
|
GnomeShellExtension.SHELL_RESTART_COMMAND (type String, default value 'gnome-shell --replace &')
|
||||||
|
- Command to restart the gnome-shell. It is recommended to start it in the background (hence '&')
|
||||||
|
|
||||||
|
|
||||||
|
SystemTray.TRAY_SIZE (type int, default value '24')
|
||||||
|
- Size of the tray, so that the icon can properly scale based on OS. (if it's not exact)
|
||||||
|
- NOTE: Must be set after any other customization options, as a static call to SystemTray will cause initialization of the library.
|
||||||
|
|
||||||
|
|
||||||
|
SystemTray.ICON_PATH (type String, default value '')
|
||||||
|
- Location of the icon (to make it easier when specifying icons)
|
||||||
|
- NOTE: Must be set after any other customization options, as a static call to SystemTray will cause initialization of the library.
|
||||||
|
|
||||||
Not all system tray icons are the same size (default is 22px), so to properly scale the icon
|
|
||||||
to fit, change the value of 'SystemTray.TRAY_SIZE'
|
|
||||||
|
|
||||||
You might want to specify the root location of the icons used (to make it easier when
|
|
||||||
specifying icons), change the value of 'SystemTray.ICON_PATH'
|
|
||||||
|
|
||||||
|
|
||||||
A *simple* example is as follows:
|
A *simple* example is as follows:
|
||||||
|
@ -63,4 +83,14 @@ Note: This project was heavily influence by the excellent Lantern project,
|
||||||
*Many* thanks to them for figuring out AppIndicators via JNA.
|
*Many* thanks to them for figuring out AppIndicators via JNA.
|
||||||
https://github.com/getlantern/lantern
|
https://github.com/getlantern/lantern
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
Note: Gnome-shell users will experience an extension install to also support this
|
||||||
|
functionality. Additionally, a shell restart is necessary for the extension
|
||||||
|
to be noticed by the shell. You can disable the restart behavior if you like,
|
||||||
|
and the 'system tray' functinality will be picked up on log out/in, or a
|
||||||
|
manual restart.
|
||||||
|
|
||||||
|
Also, screw you gnome-project leads, for making it such a pain-in-the-ass
|
||||||
|
to do something so incredibly simple and basic.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,23 @@ import dorkbox.util.NamedThreadFactory;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.jna.linux.AppIndicator;
|
import dorkbox.util.jna.linux.AppIndicator;
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
import dorkbox.util.jna.linux.GtkSupport;
|
||||||
|
import dorkbox.util.process.ShellProcessBuilder;
|
||||||
import dorkbox.util.tray.linux.AppIndicatorTray;
|
import dorkbox.util.tray.linux.AppIndicatorTray;
|
||||||
|
import dorkbox.util.tray.linux.GnomeShellExtension;
|
||||||
import dorkbox.util.tray.linux.GtkSystemTray;
|
import dorkbox.util.tray.linux.GtkSystemTray;
|
||||||
import dorkbox.util.tray.swing.SwingSystemTray;
|
import dorkbox.util.tray.swing.SwingSystemTray;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -54,7 +64,7 @@ class SystemTray {
|
||||||
public static int TRAY_SIZE = 22;
|
public static int TRAY_SIZE = 22;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Location of the icon
|
* Location of the icon (to make it easier when specifying icons)
|
||||||
*/
|
*/
|
||||||
public static String ICON_PATH = "";
|
public static String ICON_PATH = "";
|
||||||
|
|
||||||
|
@ -72,13 +82,38 @@ class SystemTray {
|
||||||
if (GtkSupport.isSupported) {
|
if (GtkSupport.isSupported) {
|
||||||
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
||||||
String getenv = System.getenv("XDG_CURRENT_DESKTOP");
|
String getenv = System.getenv("XDG_CURRENT_DESKTOP");
|
||||||
if (getenv != null && getenv.equals("Unity")) {
|
if ("Unity".equalsIgnoreCase(getenv)) {
|
||||||
try {
|
try {
|
||||||
trayType = AppIndicatorTray.class;
|
trayType = AppIndicatorTray.class;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
|
} else if ("GNOME".equalsIgnoreCase(getenv)) {
|
||||||
|
// if the "topicons" extension is installed, don't install us (because it will override what we do, where ours
|
||||||
|
// is more specialized - so it only modified our tray icon (instead of ALL tray icons)
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||||
|
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||||
|
|
||||||
|
// gnome-shell --version
|
||||||
|
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||||
|
shellVersion.setExecutable("gnome-shell");
|
||||||
|
shellVersion.addArgument("--version");
|
||||||
|
shellVersion.start();
|
||||||
|
|
||||||
|
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||||
|
|
||||||
|
if (!output.isEmpty()) {
|
||||||
|
GnomeShellExtension.install(logger, output);
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
trayType = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
BufferedReader bin = null;
|
BufferedReader bin = null;
|
||||||
try {
|
try {
|
||||||
|
@ -89,6 +124,7 @@ class SystemTray {
|
||||||
if (listFiles != null) {
|
if (listFiles != null) {
|
||||||
for (File procs : listFiles) {
|
for (File procs : listFiles) {
|
||||||
String name = procs.getName();
|
String name = procs.getName();
|
||||||
|
|
||||||
if (!Character.isDigit(name.charAt(0))) {
|
if (!Character.isDigit(name.charAt(0))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +137,7 @@ class SystemTray {
|
||||||
try {
|
try {
|
||||||
bin = new BufferedReader(new FileReader(status));
|
bin = new BufferedReader(new FileReader(status));
|
||||||
String readLine = bin.readLine();
|
String readLine = bin.readLine();
|
||||||
|
|
||||||
if (readLine != null && readLine.contains("indicator-app")) {
|
if (readLine != null && readLine.contains("indicator-app")) {
|
||||||
// make sure we can also load the library (it might be the wrong version)
|
// make sure we can also load the library (it might be the wrong version)
|
||||||
try {
|
try {
|
||||||
|
@ -119,6 +156,16 @@ class SystemTray {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make one last ditch effort
|
||||||
|
if (trayType == null) {
|
||||||
|
try {
|
||||||
|
final AppIndicator instance = AppIndicator.INSTANCE;
|
||||||
|
trayType = AppIndicatorTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
} finally {
|
} finally {
|
||||||
if (bin != null) {
|
if (bin != null) {
|
||||||
|
@ -246,11 +293,15 @@ class SystemTray {
|
||||||
digest.reset();
|
digest.reset();
|
||||||
digest.update(bytes);
|
digest.update(bytes);
|
||||||
|
|
||||||
|
|
||||||
// For KDE4, it must also be unique across runs
|
// For KDE4, it must also be unique across runs
|
||||||
byte[] longBytes = new byte[8];
|
String getenv = System.getenv("XDG_CURRENT_DESKTOP");
|
||||||
ByteBuffer wrap = ByteBuffer.wrap(longBytes);
|
if (getenv != null && getenv.contains("kde")) {
|
||||||
wrap.putLong(runtimeRandom);
|
byte[] longBytes = new byte[8];
|
||||||
digest.update(longBytes);
|
ByteBuffer wrap = ByteBuffer.wrap(longBytes);
|
||||||
|
wrap.putLong(runtimeRandom);
|
||||||
|
digest.update(longBytes);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] hashBytes = digest.digest();
|
byte[] hashBytes = digest.digest();
|
||||||
String hash = new BigInteger(1, hashBytes).toString(32);
|
String hash = new BigInteger(1, hashBytes).toString(32);
|
||||||
|
|
193
src/dorkbox/util/tray/linux/GnomeShellExtension.java
Normal file
193
src/dorkbox/util/tray/linux/GnomeShellExtension.java
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* 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.util.tray.linux;
|
||||||
|
|
||||||
|
import dorkbox.util.process.ShellProcessBuilder;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
public
|
||||||
|
class GnomeShellExtension {
|
||||||
|
private static final String UID = "SystemTray@dorkbox";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permit the gnome-shell to be restarted when the extension is installed.
|
||||||
|
*/
|
||||||
|
public static boolean ENABLE_SHELL_RESTART = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default timeout to wait for the gnome-shell to completely restart. This is a best-guess estimate.
|
||||||
|
*/
|
||||||
|
public static long SHELL_RESTART_TIMEOUT_MILLIS = 5000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to restart the gnome-shell. It is recommended to start it in the background (hence '&')
|
||||||
|
*/
|
||||||
|
public static String SHELL_RESTART_COMMAND = "gnome-shell --replace &";
|
||||||
|
|
||||||
|
public static void install(final Logger logger, final String shellVersionString) throws IOException {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||||
|
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||||
|
|
||||||
|
// gsettings get org.gnome.shell enabled-extensions
|
||||||
|
final ShellProcessBuilder gsettings = new ShellProcessBuilder(outputStream);
|
||||||
|
gsettings.setExecutable("gsettings");
|
||||||
|
gsettings.addArgument("get");
|
||||||
|
gsettings.addArgument("org.gnome.shell");
|
||||||
|
gsettings.addArgument("enabled-extensions");
|
||||||
|
gsettings.start();
|
||||||
|
|
||||||
|
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||||
|
|
||||||
|
boolean hasTopIcons = output.contains("topIcons@adel.gadllah@gmail.com");
|
||||||
|
boolean hasSystemTray = output.contains(UID);
|
||||||
|
|
||||||
|
// topIcons will convert ALL icons to be at the top of the screen, so there is no reason to have both installed
|
||||||
|
if (!hasTopIcons && !hasSystemTray) {
|
||||||
|
// have to copy the extension over and enable it.
|
||||||
|
String userHome = System.getProperty("user.home");
|
||||||
|
|
||||||
|
final File file = new File(userHome + "/.local/share/gnome-shell/extensions/" + UID);
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
final boolean mkdirs = file.mkdirs();
|
||||||
|
if (!mkdirs) {
|
||||||
|
final String msg = "Unable to create extension location: " + file;
|
||||||
|
logger.error(msg);
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream reader = null;
|
||||||
|
FileOutputStream fileOutputStream = null;
|
||||||
|
try {
|
||||||
|
fileOutputStream = new FileOutputStream(new File(file, "extension.js"));
|
||||||
|
reader = GnomeShellExtension.class.getResourceAsStream("extension.js");
|
||||||
|
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = reader.read(buffer)) > 0) {
|
||||||
|
fileOutputStream.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileOutputStream != null) {
|
||||||
|
try {
|
||||||
|
fileOutputStream.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to create the metadata.json file (and make it so that it's **always** current).
|
||||||
|
// we do this via getting the shell version
|
||||||
|
|
||||||
|
|
||||||
|
// GNOME Shell 3.14.1
|
||||||
|
String versionOutput = shellVersionString.replaceAll("[^\\d.]", ""); // should just be 3.14.1
|
||||||
|
|
||||||
|
// now change to major version only (only if applicable)
|
||||||
|
final int indexOf = versionOutput.indexOf('.');
|
||||||
|
final int lastIndexOf = versionOutput.lastIndexOf('.');
|
||||||
|
if (indexOf < lastIndexOf) {
|
||||||
|
versionOutput = versionOutput.substring(0, lastIndexOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
String metadata = "{\n" +
|
||||||
|
" \"description\": \"Shows a java tray icon on the top notification tray\",\n" +
|
||||||
|
" \"name\": \"Dorkbox SystemTray\",\n" +
|
||||||
|
" \"shell-version\": [\n" +
|
||||||
|
" \"" + versionOutput + "\"\n" +
|
||||||
|
" ],\n" +
|
||||||
|
" \"url\": \"https://github.com/dorkbox/SystemTray\",\n" +
|
||||||
|
" \"uuid\": \"SystemTray@dorkbox\",\n" +
|
||||||
|
" \"version\": 1\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
|
||||||
|
BufferedWriter outputWriter = null;
|
||||||
|
try {
|
||||||
|
outputWriter = new BufferedWriter(new FileWriter(new File(file, "metadata.json"), false));
|
||||||
|
// FileWriter always assumes default encoding is OK
|
||||||
|
outputWriter.write(metadata);
|
||||||
|
outputWriter.flush();
|
||||||
|
outputWriter.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (outputWriter != null) {
|
||||||
|
try {
|
||||||
|
outputWriter.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have to enable us
|
||||||
|
final StringBuilder stringBuilder = new StringBuilder(output);
|
||||||
|
stringBuilder.delete(0, 4);
|
||||||
|
stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
|
||||||
|
if (stringBuilder.length() > 2) {
|
||||||
|
stringBuilder.append(", ");
|
||||||
|
}
|
||||||
|
stringBuilder.append("'")
|
||||||
|
.append(UID)
|
||||||
|
.append("'");
|
||||||
|
|
||||||
|
stringBuilder.append("]");
|
||||||
|
|
||||||
|
// gsettings set org.gnome.shell enabled-extensions "['SystemTray@dorkbox']"
|
||||||
|
// gsettings set org.gnome.shell enabled-extensions "['xyz', 'SystemTray@dorkbox']"
|
||||||
|
final ShellProcessBuilder setGsettings = new ShellProcessBuilder(outputStream);
|
||||||
|
setGsettings.setExecutable("gsettings");
|
||||||
|
setGsettings.addArgument("set");
|
||||||
|
setGsettings.addArgument("org.gnome.shell");
|
||||||
|
setGsettings.addArgument("enabled-extensions");
|
||||||
|
setGsettings.addArgument(stringBuilder.toString());
|
||||||
|
setGsettings.start();
|
||||||
|
|
||||||
|
|
||||||
|
if (ENABLE_SHELL_RESTART) {
|
||||||
|
logger.info("Restarting gnome-shell, so tray notification changes can be applied.");
|
||||||
|
|
||||||
|
// now we have to restart the gnome shell via bash
|
||||||
|
final ShellProcessBuilder restartShell = new ShellProcessBuilder();
|
||||||
|
// restart shell in background process
|
||||||
|
restartShell.addArgument(SHELL_RESTART_COMMAND);
|
||||||
|
restartShell.start();
|
||||||
|
|
||||||
|
// have to give the shell time to restart
|
||||||
|
try {
|
||||||
|
Thread.sleep(SHELL_RESTART_TIMEOUT_MILLIS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Shell restarted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
271
src/dorkbox/util/tray/linux/extension.js
Normal file
271
src/dorkbox/util/tray/linux/extension.js
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This is heavily modified from an online email from Vladimir Khrustalev.
|
||||||
|
*
|
||||||
|
* The source material is NOT GPLx/MIT/BSD/Apache/etc, because those licenses
|
||||||
|
* were not specified in accordance with those license requirements (there
|
||||||
|
* was no license specified or implied). As such, this is to be considered as
|
||||||
|
* released by the original sources as public domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Panel = imports.ui.panel;
|
||||||
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
|
const Mainloop = imports.mainloop;
|
||||||
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
|
|
||||||
|
let trayAddedId = 0;
|
||||||
|
let orig_onTrayIconAdded;
|
||||||
|
|
||||||
|
let trayRemovedId = 0;
|
||||||
|
let orig_onTrayIconRemoved;
|
||||||
|
|
||||||
|
let orig_getSource = null;
|
||||||
|
let icons = [];
|
||||||
|
|
||||||
|
let notificationDaemon;
|
||||||
|
|
||||||
|
|
||||||
|
// this value is hardcoded into the display manager
|
||||||
|
const PANEL_ICON_SIZE = 24;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (Main.legacyTray) {
|
||||||
|
notificationDaemon = Main.legacyTray;
|
||||||
|
NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS = imports.ui.legacyTray.STANDARD_TRAY_ICON_IMPLEMENTATIONS;
|
||||||
|
}
|
||||||
|
else if (Main.notificationDaemon._fdoNotificationDaemon) {
|
||||||
|
notificationDaemon = Main.notificationDaemon._fdoNotificationDaemon;
|
||||||
|
orig_getSource = Lang.bind(notificationDaemon, NotificationDaemon.FdoNotificationDaemon.prototype._getSource);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
notificationDaemon = Main.notificationDaemon;
|
||||||
|
orig_getSource = Lang.bind(notificationDaemon, NotificationDaemon.NotificationDaemon.prototype._getSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enable() {
|
||||||
|
GLib.idle_add(GLib.PRIORITY_LOW, installHook);
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable() {
|
||||||
|
if (trayAddedId != 0) {
|
||||||
|
notificationDaemon._trayManager.disconnect(trayAddedId);
|
||||||
|
trayAddedId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trayRemovedId != 0) {
|
||||||
|
notificationDaemon._trayManager.disconnect(trayRemovedId);
|
||||||
|
trayRemovedId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationDaemon._trayIconAddedId = notificationDaemon._trayManager.connect('tray-icon-added', orig_onTrayIconAdded);
|
||||||
|
notificationDaemon._trayIconRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', orig_onTrayIconRemoved);
|
||||||
|
|
||||||
|
notificationDaemon._getSource = orig_getSource;
|
||||||
|
|
||||||
|
for (let i = 0; i < icons.length; i++) {
|
||||||
|
let icon = icons[i];
|
||||||
|
let parent = icon.get_parent();
|
||||||
|
if (icon._clicked) {
|
||||||
|
icon.disconnect(icon._clicked);
|
||||||
|
}
|
||||||
|
icon._clicked = undefined;
|
||||||
|
|
||||||
|
if (icon._proxyAlloc) {
|
||||||
|
Main.panel._rightBox.disconnect(icon._proxyAlloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
icon._clickProxy.destroy();
|
||||||
|
|
||||||
|
parent.remove_actor(icon);
|
||||||
|
parent.destroy();
|
||||||
|
notificationDaemon._onTrayIconAdded(notificationDaemon, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
icons = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function installHook() {
|
||||||
|
//global.log("Installing hook")
|
||||||
|
|
||||||
|
// disable the "normal" method of adding icons
|
||||||
|
notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconAddedId);
|
||||||
|
notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconRemovedId);
|
||||||
|
|
||||||
|
// save the original method
|
||||||
|
orig_onTrayIconAdded = Lang.bind(notificationDaemon, notificationDaemon._onTrayIconAdded);
|
||||||
|
orig_onTrayIconRemoved = Lang.bind(notificationDaemon, notificationDaemon._onTrayIconRemoved)
|
||||||
|
|
||||||
|
// add our hook. If our icon doesn't have our specific title, it calls the original method
|
||||||
|
trayAddedId = notificationDaemon._trayManager.connect('tray-icon-added', onTrayIconAdded);
|
||||||
|
trayRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', onTrayIconRemoved);
|
||||||
|
|
||||||
|
notificationDaemon._getSource = getSourceHook;
|
||||||
|
|
||||||
|
// move icons to top
|
||||||
|
let toDestroy = [];
|
||||||
|
if (notificationDaemon._sources) {
|
||||||
|
for (let i = 0; i < notificationDaemon._sources.length; i++) {
|
||||||
|
let source = notificationDaemon._sources[i];
|
||||||
|
|
||||||
|
if (!source.trayIcon) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = source.trayIcon;
|
||||||
|
|
||||||
|
// we could set the title in java, HOWEVER because of race conditions, it's not consistent. So we check for 'java'
|
||||||
|
if (icon.title !== "java") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = icon.get_parent();
|
||||||
|
parent.remove_actor(icon);
|
||||||
|
|
||||||
|
onTrayIconAdded(this, icon);
|
||||||
|
toDestroy.push(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (let i = 0; i < notificationDaemon._iconBox.get_n_children(); i++) {
|
||||||
|
let button = notificationDaemon._iconBox.get_child_at_index(i);
|
||||||
|
let icon = button.child;
|
||||||
|
|
||||||
|
// we could set the title in java, HOWEVER because of race conditions, it's not consistent. So we check for 'java'
|
||||||
|
if (icon.title !== "java") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.remove_actor(icon);
|
||||||
|
onTrayIconAdded(this, icon);
|
||||||
|
|
||||||
|
toDestroy.push(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < toDestroy.length; i++) {
|
||||||
|
toDestroy[i].destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSourceHook (title, pid, ndata, sender, trayIcon) {
|
||||||
|
// we could set the title in java, HOWEVER because of race conditions, it's not consistent. So we check for 'java'
|
||||||
|
if (trayIcon && title === "java") {
|
||||||
|
//global.log("create source");
|
||||||
|
onTrayIconAdded(this, trayIcon);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSource(title, pid, ndata, sender, trayIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the hook that lets us only add ourselves.
|
||||||
|
function onTrayIconAdded(o, icon) {
|
||||||
|
//global.log("adding tray icon 1 " + icon.title);
|
||||||
|
|
||||||
|
let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : '';
|
||||||
|
if (NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we could set the title in java, HOWEVER because of race conditions, it's not consistent. So we check for 'java'
|
||||||
|
if (icon.title !== "java") {
|
||||||
|
orig_onTrayIconAdded(o, icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//global.log("adding tray icon 2 " + icon.title);
|
||||||
|
|
||||||
|
let buttonBox = new PanelMenu.ButtonBox();
|
||||||
|
let box = buttonBox.actor;
|
||||||
|
let parent = box.get_parent();
|
||||||
|
|
||||||
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||||
|
let iconSize = PANEL_ICON_SIZE * scaleFactor;
|
||||||
|
|
||||||
|
icon.set_size(iconSize, iconSize);
|
||||||
|
box.add_actor(icon);
|
||||||
|
|
||||||
|
// Reactive actors will receive events.
|
||||||
|
icon.set_reactive(true);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
// remove from the (if present) "collapsy tab icon thing"
|
||||||
|
parent.remove_actor(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup proxy for handling click notifications, make it a little larger than the icon
|
||||||
|
let clickProxy = new St.Bin({ width: iconSize + 4, height: iconSize + 4});
|
||||||
|
clickProxy.set_reactive(true);
|
||||||
|
|
||||||
|
icon._proxyAlloc = Main.panel._rightBox.connect('allocation-changed', function() {
|
||||||
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() {
|
||||||
|
let [x, y] = icon.get_transformed_position();
|
||||||
|
// need to offset the proxy, so the icon is centered inside the click handler
|
||||||
|
clickProxy.set_position(x - 2, y);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
icon.connect("destroy", function() {
|
||||||
|
Main.panel._rightBox.disconnect(icon._proxyAlloc);
|
||||||
|
clickProxy.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
clickProxy.connect('button-release-event', function(actor, event) {
|
||||||
|
icon.click(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
icon._clickProxy = clickProxy;
|
||||||
|
|
||||||
|
|
||||||
|
Main.uiGroup.add_actor(clickProxy);
|
||||||
|
|
||||||
|
// add the box to the right panel, always at position 0
|
||||||
|
Main.panel._rightBox.insert_child_at_index(box, 0);
|
||||||
|
|
||||||
|
icons.push(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTrayIconRemoved(o, icon) {
|
||||||
|
//global.log("removing tray icon " + icon.title);
|
||||||
|
|
||||||
|
// we could set the title in java, HOWEVER because of race conditions, it's not consistent. So we check for 'java'
|
||||||
|
if (icon.title !== "java") {
|
||||||
|
orig_onTrayIconRemoved(o, icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = icon.get_parent();
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
icon.destroy();
|
||||||
|
icons.splice(icons.indexOf(icon), 1);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user