1 /*
   2  * Copyright (c) 2016, 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 #include <dlfcn.h>
  27 #include "jvm_md.h"
  28 #include <setjmp.h>
  29 #include <string.h>
  30 
  31 #include "jni_util.h"
  32 #include "awt_Taskbar.h"
  33 
  34 
  35 extern JavaVM *jvm;
  36 
  37 #define NO_SYMBOL_EXCEPTION 1
  38 
  39 #define UNITY_LIB_VERSIONED VERSIONED_JNI_LIB_NAME("unity", "9")
  40 #define UNITY_LIB JNI_LIB_NAME("unity")
  41 
  42 static jmp_buf j;
  43 
  44 static void *unity_libhandle = NULL;
  45 
  46 static DbusmenuMenuitem* menu = NULL;
  47 UnityLauncherEntry* entry = NULL;
  48 
  49 static jclass jTaskbarCls = NULL;
  50 static jmethodID jTaskbarCallback = NULL;
  51 static jmethodID jMenuItemGetLabel = NULL;
  52 
  53 GList* globalRefs = NULL;
  54 
  55 static void* dl_symbol(const char* name) {
  56     void* result = dlsym(unity_libhandle, name);
  57     if (!result)
  58         longjmp(j, NO_SYMBOL_EXCEPTION);
  59 
  60     return result;
  61 }
  62 
  63 static gboolean unity_load() {
  64     unity_libhandle = dlopen(UNITY_LIB_VERSIONED, RTLD_LAZY | RTLD_LOCAL);
  65     if (unity_libhandle == NULL) {
  66         unity_libhandle = dlopen(UNITY_LIB, RTLD_LAZY | RTLD_LOCAL);
  67         if (unity_libhandle == NULL) {
  68             return FALSE;
  69         }
  70     }
  71     if (setjmp(j) == 0) {
  72         fp_unity_launcher_entry_get_for_desktop_file = dl_symbol("unity_launcher_entry_get_for_desktop_file");
  73         fp_unity_launcher_entry_set_count = dl_symbol("unity_launcher_entry_set_count");
  74         fp_unity_launcher_entry_set_count_visible = dl_symbol("unity_launcher_entry_set_count_visible");
  75         fp_unity_launcher_entry_set_urgent = dl_symbol("unity_launcher_entry_set_urgent");
  76         fp_unity_launcher_entry_set_progress = dl_symbol("unity_launcher_entry_set_progress");
  77         fp_unity_launcher_entry_set_progress_visible = dl_symbol("unity_launcher_entry_set_progress_visible");
  78 
  79         fp_dbusmenu_menuitem_new = dl_symbol("dbusmenu_menuitem_new");
  80         fp_dbusmenu_menuitem_property_set = dl_symbol("dbusmenu_menuitem_property_set");
  81         fp_dbusmenu_menuitem_property_set_int = dl_symbol("dbusmenu_menuitem_property_set_int");
  82         fp_dbusmenu_menuitem_property_get_int = dl_symbol("dbusmenu_menuitem_property_get_int");
  83         fp_dbusmenu_menuitem_property_set = dl_symbol("dbusmenu_menuitem_property_set");
  84         fp_dbusmenu_menuitem_child_append = dl_symbol("dbusmenu_menuitem_child_append");
  85         fp_dbusmenu_menuitem_child_delete = dl_symbol("dbusmenu_menuitem_child_delete");
  86         fp_dbusmenu_menuitem_take_children = dl_symbol("dbusmenu_menuitem_take_children");
  87         fp_dbusmenu_menuitem_foreach = dl_symbol("dbusmenu_menuitem_foreach");
  88         fp_unity_launcher_entry_set_quicklist = dl_symbol("unity_launcher_entry_set_quicklist");
  89         fp_unity_launcher_entry_get_quicklist = dl_symbol("unity_launcher_entry_get_quicklist");
  90     } else {
  91         dlclose(unity_libhandle);
  92         unity_libhandle = NULL;
  93         return FALSE;
  94     }
  95     return TRUE;
  96 }
  97 
  98 void callback(DbusmenuMenuitem* mi, guint ts, jobject data) {
  99     JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
 100     (*env)->CallStaticVoidMethod(env, jTaskbarCls, jTaskbarCallback, data);
 101 }
 102 
 103 /*
 104  * Class:     sun_awt_X11_XTaskbarPeer
 105  * Method:    init
 106  * Signature: (Ljava/lang/String;)Z
 107  */
 108 JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XTaskbarPeer_init
 109 (JNIEnv *env, jclass cls, jstring jname, jint version, jboolean verbose) {
 110     jclass clazz;
 111 
 112     jTaskbarCls = (*env)->NewGlobalRef(env, cls);
 113 
 114     CHECK_NULL_RETURN(jTaskbarCallback =
 115             (*env)->GetStaticMethodID(env, cls, "menuItemCallback", "(Ljava/awt/MenuItem;)V"), JNI_FALSE);
 116     CHECK_NULL_RETURN(
 117             clazz = (*env)->FindClass(env, "java/awt/MenuItem"), JNI_FALSE);
 118     CHECK_NULL_RETURN(
 119             jMenuItemGetLabel = (*env)->GetMethodID(env, clazz, "getLabel", "()Ljava/lang/String;"), JNI_FALSE);
 120 
 121     if (gtk_load(env, version, verbose) && unity_load()) {
 122         const gchar* name = (*env)->GetStringUTFChars(env, jname, NULL);
 123         if (name) {
 124             entry = fp_unity_launcher_entry_get_for_desktop_file(name);
 125             (*env)->ReleaseStringUTFChars(env, jname, name);
 126             return JNI_TRUE;
 127         }
 128     }
 129     return JNI_FALSE;
 130 }
 131 
 132 /*
 133  * Class:     sun_awt_X11_XTaskbarPeer
 134  * Method:    runloop
 135  * Signature: ()V
 136  */
 137 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_runloop
 138 (JNIEnv *env, jclass cls) {
 139     gtk->gdk_threads_enter();
 140     gtk->gtk_main();
 141     gtk->gdk_threads_leave();
 142 }
 143 
 144 /*
 145  * Class:     sun_awt_X11_XTaskbarPeer
 146  * Method:    setBadge
 147  * Signature: (JZ)V
 148  */
 149 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setBadge
 150 (JNIEnv *env, jobject obj, jlong value, jboolean visible) {
 151     gtk->gdk_threads_enter();
 152     fp_unity_launcher_entry_set_count(entry, value);
 153     fp_unity_launcher_entry_set_count_visible(entry, visible);
 154     DbusmenuMenuitem* m;
 155     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 156         fp_unity_launcher_entry_set_quicklist(entry, m);
 157     }
 158     gtk->gdk_threads_leave();
 159 }
 160 
 161 /*
 162  * Class:     sun_awt_X11_XTaskbarPeer
 163  * Method:    setUrgent
 164  * Signature: (Z)V
 165  */
 166 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setUrgent
 167 (JNIEnv *env, jobject obj, jboolean urgent) {
 168     gtk->gdk_threads_enter();
 169     fp_unity_launcher_entry_set_urgent(entry, urgent);
 170     DbusmenuMenuitem* m;
 171     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 172         fp_unity_launcher_entry_set_quicklist(entry, m);
 173     }
 174     gtk->gdk_threads_leave();
 175 }
 176 
 177 /*
 178  * Class:     sun_awt_X11_XTaskbarPeer
 179  * Method:    updateProgress
 180  * Signature: (DZ)V
 181  */
 182 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_updateProgress
 183 (JNIEnv *env, jobject obj, jdouble value, jboolean visible) {
 184     gtk->gdk_threads_enter();
 185     fp_unity_launcher_entry_set_progress(entry, value);
 186     fp_unity_launcher_entry_set_progress_visible(entry, visible);
 187     DbusmenuMenuitem* m;
 188     if (m = fp_unity_launcher_entry_get_quicklist(entry)) {
 189         fp_unity_launcher_entry_set_quicklist(entry, m);
 190     }
 191     gtk->gdk_threads_leave();
 192 }
 193 
 194 void deleteGlobalRef(gpointer data) {
 195     JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
 196     (*env)->DeleteGlobalRef(env, data);
 197 }
 198 
 199 void fill_menu(JNIEnv *env, jobjectArray items) {
 200     int index;
 201     jsize length = (*env)->GetArrayLength(env, items);
 202     for (index = 0; index < length; index++) {
 203         jobject elem = (*env)->GetObjectArrayElement(env, items, index);
 204         if ((*env)->ExceptionCheck(env)) {
 205             break;
 206         }
 207         elem = (*env)->NewGlobalRef(env, elem);
 208 
 209         globalRefs = gtk->g_list_append(globalRefs, elem);
 210 
 211         jstring jlabel = (jstring) (*env)->CallObjectMethod(env, elem, jMenuItemGetLabel);
 212         if (!(*env)->ExceptionCheck(env) && jlabel) {
 213             const gchar* label = (*env)->GetStringUTFChars(env, jlabel, NULL);
 214             if (label) {
 215                 DbusmenuMenuitem* mi = fp_dbusmenu_menuitem_new();
 216                 if (!strcmp(label, "-")) {
 217                     fp_dbusmenu_menuitem_property_set(mi, "type", "separator");
 218                 } else {
 219                     fp_dbusmenu_menuitem_property_set(mi, "label", label);
 220                 }
 221 
 222                 (*env)->ReleaseStringUTFChars(env, jlabel, label);
 223                 fp_dbusmenu_menuitem_child_append(menu, mi);
 224                 gtk->g_signal_connect_data(mi, "item_activated",
 225                                            G_CALLBACK(callback), elem, NULL, 0);
 226             }
 227         }
 228     }
 229 }
 230 
 231 /*
 232  * Class:     sun_awt_X11_XTaskbarPeer
 233  * Method:    setNativeMenu
 234  * Signature: ([Ljava/awt/MenuItem;)V
 235  */
 236 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setNativeMenu
 237 (JNIEnv *env, jobject obj, jobjectArray items) {
 238 
 239     gtk->gdk_threads_enter();
 240 
 241     if (!menu) {
 242         menu = fp_dbusmenu_menuitem_new();
 243         fp_unity_launcher_entry_set_quicklist(entry, menu);
 244     }
 245 
 246     GList* list = fp_dbusmenu_menuitem_take_children(menu);
 247     gtk->g_list_free_full(list, gtk->g_object_unref);
 248 
 249     gtk->g_list_free_full(globalRefs, deleteGlobalRef);
 250     globalRefs = NULL;
 251 
 252     if (items) {
 253         fill_menu(env, items);
 254     }
 255 
 256     gtk->gdk_threads_leave();
 257 }