1 /*
   2  * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #ifdef HEADLESS
  27     #error This file should not be included in headless library
  28 #endif
  29 
  30 #include <dlfcn.h>
  31 #include "jvm_md.h"
  32 #include <setjmp.h>
  33 #include <string.h>
  34 
  35 #include "jni_util.h"
  36 #include "awt_Taskbar.h"
  37 
  38 
  39 extern JavaVM *jvm;
  40 
  41 #define NO_SYMBOL_EXCEPTION 1
  42 
  43 #define UNITY_LIB_VERSIONED VERSIONED_JNI_LIB_NAME("unity", "9")
  44 #define UNITY_LIB JNI_LIB_NAME("unity")
  45 
  46 static jmp_buf j;
  47 
  48 static void *unity_libhandle = NULL;
  49 
  50 static DbusmenuMenuitem* menu = NULL;
  51 UnityLauncherEntry* entry = NULL;
  52 
  53 static jclass jTaskbarCls = NULL;
  54 static jmethodID jTaskbarCallback = NULL;
  55 static jmethodID jMenuItemGetLabel = NULL;
  56 
  57 GList* globalRefs = NULL;
  58 
  59 static void* dl_symbol(const char* name) {
  60     void* result = dlsym(unity_libhandle, name);
  61     if (!result)
  62         longjmp(j, NO_SYMBOL_EXCEPTION);
  63 
  64     return result;
  65 }
  66 
  67 static gboolean unity_load() {
  68     unity_libhandle = dlopen(UNITY_LIB_VERSIONED, RTLD_LAZY | RTLD_LOCAL);
  69     if (unity_libhandle == NULL) {
  70         unity_libhandle = dlopen(UNITY_LIB, RTLD_LAZY | RTLD_LOCAL);
  71         if (unity_libhandle == NULL) {
  72             return FALSE;
  73         }
  74     }
  75     if (setjmp(j) == 0) {
  76         fp_unity_launcher_entry_get_for_desktop_file = dl_symbol("unity_launcher_entry_get_for_desktop_file");
  77         fp_unity_launcher_entry_set_count = dl_symbol("unity_launcher_entry_set_count");
  78         fp_unity_launcher_entry_set_count_visible = dl_symbol("unity_launcher_entry_set_count_visible");
  79         fp_unity_launcher_entry_set_urgent = dl_symbol("unity_launcher_entry_set_urgent");
  80         fp_unity_launcher_entry_set_progress = dl_symbol("unity_launcher_entry_set_progress");
  81         fp_unity_launcher_entry_set_progress_visible = dl_symbol("unity_launcher_entry_set_progress_visible");
  82 
  83         fp_dbusmenu_menuitem_new = dl_symbol("dbusmenu_menuitem_new");
  84         fp_dbusmenu_menuitem_property_set = dl_symbol("dbusmenu_menuitem_property_set");
  85         fp_dbusmenu_menuitem_property_set_int = dl_symbol("dbusmenu_menuitem_property_set_int");
  86         fp_dbusmenu_menuitem_property_get_int = dl_symbol("dbusmenu_menuitem_property_get_int");
  87         fp_dbusmenu_menuitem_property_set = dl_symbol("dbusmenu_menuitem_property_set");
  88         fp_dbusmenu_menuitem_child_append = dl_symbol("dbusmenu_menuitem_child_append");
  89         fp_dbusmenu_menuitem_child_delete = dl_symbol("dbusmenu_menuitem_child_delete");
  90         fp_dbusmenu_menuitem_take_children = dl_symbol("dbusmenu_menuitem_take_children");
  91         fp_dbusmenu_menuitem_foreach = dl_symbol("dbusmenu_menuitem_foreach");
  92         fp_unity_launcher_entry_set_quicklist = dl_symbol("unity_launcher_entry_set_quicklist");
  93         fp_unity_launcher_entry_get_quicklist = dl_symbol("unity_launcher_entry_get_quicklist");
  94     } else {
  95         dlclose(unity_libhandle);
  96         unity_libhandle = NULL;
  97         return FALSE;
  98     }
  99     return TRUE;
 100 }
 101 
 102 void callback(DbusmenuMenuitem* mi, guint ts, jobject data) {
 103     JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
 104     (*env)->CallStaticVoidMethod(env, jTaskbarCls, jTaskbarCallback, data);
 105 }
 106 
 107 /*
 108  * Class:     sun_awt_X11_XTaskbarPeer
 109  * Method:    init
 110  * Signature: (Ljava/lang/String;)Z
 111  */
 112 JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XTaskbarPeer_init
 113 (JNIEnv *env, jclass cls, jstring jname, jint version, jboolean verbose) {
 114     jclass clazz;
 115 
 116     jTaskbarCls = (*env)->NewGlobalRef(env, cls);
 117 
 118     CHECK_NULL_RETURN(jTaskbarCallback =
 119             (*env)->GetStaticMethodID(env, cls, "menuItemCallback", "(Ljava/awt/MenuItem;)V"), JNI_FALSE);
 120     CHECK_NULL_RETURN(
 121             clazz = (*env)->FindClass(env, "java/awt/MenuItem"), JNI_FALSE);
 122     CHECK_NULL_RETURN(
 123             jMenuItemGetLabel = (*env)->GetMethodID(env, clazz, "getLabel", "()Ljava/lang/String;"), JNI_FALSE);
 124 
 125     if (gtk_load(env, version, verbose) && unity_load()) {
 126         const gchar* name = (*env)->GetStringUTFChars(env, jname, NULL);
 127         if (name) {
 128             entry = fp_unity_launcher_entry_get_for_desktop_file(name);
 129             (*env)->ReleaseStringUTFChars(env, jname, name);
 130             return JNI_TRUE;
 131         }
 132     }
 133     return JNI_FALSE;
 134 }
 135 
 136 /*
 137  * Class:     sun_awt_X11_XTaskbarPeer
 138  * Method:    runloop
 139  * Signature: ()V
 140  */
 141 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_runloop
 142 (JNIEnv *env, jclass cls) {
 143     gtk->gdk_threads_enter();
 144     gtk->gtk_main();
 145     gtk->gdk_threads_leave();
 146 }
 147 
 148 /*
 149  * Class:     sun_awt_X11_XTaskbarPeer
 150  * Method:    setBadge
 151  * Signature: (JZ)V
 152  */
 153 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setBadge
 154 (JNIEnv *env, jobject obj, jlong value, jboolean visible) {
 155     gtk->gdk_threads_enter();
 156     fp_unity_launcher_entry_set_count(entry, value);
 157     fp_unity_launcher_entry_set_count_visible(entry, visible);
 158     DbusmenuMenuitem* m;
 159     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 160         fp_unity_launcher_entry_set_quicklist(entry, m);
 161     }
 162     gtk->gdk_threads_leave();
 163 }
 164 
 165 /*
 166  * Class:     sun_awt_X11_XTaskbarPeer
 167  * Method:    setUrgent
 168  * Signature: (Z)V
 169  */
 170 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setUrgent
 171 (JNIEnv *env, jobject obj, jboolean urgent) {
 172     gtk->gdk_threads_enter();
 173     fp_unity_launcher_entry_set_urgent(entry, urgent);
 174     DbusmenuMenuitem* m;
 175     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 176         fp_unity_launcher_entry_set_quicklist(entry, m);
 177     }
 178     gtk->gdk_threads_leave();
 179 }
 180 
 181 /*
 182  * Class:     sun_awt_X11_XTaskbarPeer
 183  * Method:    updateProgress
 184  * Signature: (DZ)V
 185  */
 186 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_updateProgress
 187 (JNIEnv *env, jobject obj, jdouble value, jboolean visible) {
 188     gtk->gdk_threads_enter();
 189     fp_unity_launcher_entry_set_progress(entry, value);
 190     fp_unity_launcher_entry_set_progress_visible(entry, visible);
 191     DbusmenuMenuitem* m;
 192     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 193         fp_unity_launcher_entry_set_quicklist(entry, m);
 194     }
 195     gtk->gdk_threads_leave();
 196 }
 197 
 198 void deleteGlobalRef(gpointer data) {
 199     JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
 200     (*env)->DeleteGlobalRef(env, data);
 201 }
 202 
 203 void fill_menu(JNIEnv *env, jobjectArray items) {
 204     int index;
 205     jsize length = (*env)->GetArrayLength(env, items);
 206     for (index = 0; index < length; index++) {
 207         jobject elem = (*env)->GetObjectArrayElement(env, items, index);
 208         if ((*env)->ExceptionCheck(env)) {
 209             break;
 210         }
 211         elem = (*env)->NewGlobalRef(env, elem);
 212 
 213         globalRefs = gtk->g_list_append(globalRefs, elem);
 214 
 215         jstring jlabel = (jstring) (*env)->CallObjectMethod(env, elem, jMenuItemGetLabel);
 216         if (!(*env)->ExceptionCheck(env) && jlabel) {
 217             const gchar* label = (*env)->GetStringUTFChars(env, jlabel, NULL);
 218             if (label) {
 219                 DbusmenuMenuitem* mi = fp_dbusmenu_menuitem_new();
 220                 if (!strcmp(label, "-")) {
 221                     fp_dbusmenu_menuitem_property_set(mi, "type", "separator");
 222                 } else {
 223                     fp_dbusmenu_menuitem_property_set(mi, "label", label);
 224                 }
 225 
 226                 (*env)->ReleaseStringUTFChars(env, jlabel, label);
 227                 fp_dbusmenu_menuitem_child_append(menu, mi);
 228                 gtk->g_signal_connect_data(mi, "item_activated",
 229                                            G_CALLBACK(callback), elem, NULL, 0);
 230             }
 231         }
 232     }
 233 }
 234 
 235 /*
 236  * Class:     sun_awt_X11_XTaskbarPeer
 237  * Method:    setNativeMenu
 238  * Signature: ([Ljava/awt/MenuItem;)V
 239  */
 240 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setNativeMenu
 241 (JNIEnv *env, jobject obj, jobjectArray items) {
 242 
 243     gtk->gdk_threads_enter();
 244 
 245     if (!menu) {
 246         menu = fp_dbusmenu_menuitem_new();
 247         fp_unity_launcher_entry_set_quicklist(entry, menu);
 248     }
 249 
 250     GList* list = fp_dbusmenu_menuitem_take_children(menu);
 251     gtk->g_list_free_full(list, gtk->g_object_unref);
 252 
 253     gtk->g_list_free_full(globalRefs, deleteGlobalRef);
 254     globalRefs = NULL;
 255 
 256     if (items) {
 257         fill_menu(env, items);
 258     }
 259 
 260     gtk->gdk_threads_leave();
 261 }