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             fp_dbusmenu_menuitem_property_get_int(mi, "toggle-state")
 102             ? JNI_FALSE
 103             : JNI_TRUE);
 104 }
 105 
 106 /*
 107  * Class:     sun_awt_X11_XTaskbarPeer
 108  * Method:    init
 109  * Signature: (Ljava/lang/String;)Z
 110  */
 111 JNIEXPORT jboolean JNICALL Java_sun_awt_X11_XTaskbarPeer_init
 112 (JNIEnv *env, jclass cls, jstring jname, jint version, jboolean verbose) {
 113     jclass clazz;
 114 
 115     jTaskbarCls = (*env)->NewGlobalRef(env, cls);
 116 
 117     CHECK_NULL_RETURN(jTaskbarCallback =
 118             (*env)->GetStaticMethodID(env, cls, "menuItemCallback", "(Ljava/awt/MenuItem;)V"), JNI_FALSE);
 119     CHECK_NULL_RETURN(
 120             clazz = (*env)->FindClass(env, "java/awt/MenuItem"), JNI_FALSE);
 121     CHECK_NULL_RETURN(
 122             jMenuItemGetLabel = (*env)->GetMethodID(env, clazz, "getLabel", "()Ljava/lang/String;"), JNI_FALSE);
 123 
 124     if (gtk_load(env, version, verbose) && unity_load()) {
 125         const gchar* name = (*env)->GetStringUTFChars(env, jname, NULL);
 126         if (name) {
 127             entry = fp_unity_launcher_entry_get_for_desktop_file(name);
 128             (*env)->ReleaseStringUTFChars(env, jname, name);
 129             return JNI_TRUE;
 130         }
 131     }
 132     return JNI_FALSE;
 133 }
 134 
 135 /*
 136  * Class:     sun_awt_X11_XTaskbarPeer
 137  * Method:    runloop
 138  * Signature: ()V
 139  */
 140 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_runloop
 141 (JNIEnv *env, jclass cls) {
 142     gtk->gdk_threads_enter();
 143     gtk->gtk_main();
 144     gtk->gdk_threads_leave();
 145 }
 146 
 147 /*
 148  * Class:     sun_awt_X11_XTaskbarPeer
 149  * Method:    setBadge
 150  * Signature: (JZ)V
 151  */
 152 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setBadge
 153 (JNIEnv *env, jobject obj, jlong value, jboolean visible) {
 154     gtk->gdk_threads_enter();
 155     fp_unity_launcher_entry_set_count(entry, value);
 156     fp_unity_launcher_entry_set_count_visible(entry, visible);
 157     DbusmenuMenuitem* m = fp_unity_launcher_entry_get_quicklist(entry);
 158     if (m) {
 159         fp_unity_launcher_entry_set_quicklist(entry, m);
 160     }
 161     gtk->gdk_threads_leave();
 162 }
 163 
 164 /*
 165  * Class:     sun_awt_X11_XTaskbarPeer
 166  * Method:    setUrgent
 167  * Signature: (Z)V
 168  */
 169 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setUrgent
 170 (JNIEnv *env, jobject obj, jboolean urgent) {
 171     gtk->gdk_threads_enter();
 172     fp_unity_launcher_entry_set_urgent(entry, urgent);
 173     DbusmenuMenuitem* m = fp_unity_launcher_entry_get_quicklist(entry);
 174     if (m) {
 175         fp_unity_launcher_entry_set_quicklist(entry, m);
 176     }
 177     gtk->gdk_threads_leave();
 178 }
 179 
 180 /*
 181  * Class:     sun_awt_X11_XTaskbarPeer
 182  * Method:    updateProgress
 183  * Signature: (DZ)V
 184  */
 185 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_updateProgress
 186 (JNIEnv *env, jobject obj, jdouble value, jboolean visible) {
 187     gtk->gdk_threads_enter();
 188     fp_unity_launcher_entry_set_progress(entry, value);
 189     fp_unity_launcher_entry_set_progress_visible(entry, visible);
 190     DbusmenuMenuitem* m = fp_unity_launcher_entry_get_quicklist(entry);
 191     if (m) {
 192         fp_unity_launcher_entry_set_quicklist(entry, m);
 193     }
 194     gtk->gdk_threads_leave();
 195 }
 196 
 197 void deleteGlobalRef(gpointer data) {
 198     JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
 199     (*env)->DeleteGlobalRef(env, data);
 200 }
 201 
 202 void fill_menu(JNIEnv *env, jobjectArray items) {
 203     int index;
 204     jsize length = (*env)->GetArrayLength(env, items);
 205     for (index = 0; index < length; index++) {
 206         jobject elem = (*env)->GetObjectArrayElement(env, items, index);
 207         if ((*env)->ExceptionCheck(env)) {
 208             break;
 209         }
 210         elem = (*env)->NewGlobalRef(env, elem);
 211 
 212         globalRefs = gtk->g_list_append(globalRefs, elem);
 213 
 214         jstring jlabel = (jstring) (*env)->CallObjectMethod(env, elem, jMenuItemGetLabel);
 215         if (!(*env)->ExceptionCheck(env) && jlabel) {
 216             const gchar* label = (*env)->GetStringUTFChars(env, jlabel, NULL);
 217             if (label) {
 218                 DbusmenuMenuitem* mi = fp_dbusmenu_menuitem_new();
 219                 if (!strcmp(label, "-")) {
 220                     fp_dbusmenu_menuitem_property_set(mi, "type", "separator");
 221                 } else {
 222                     fp_dbusmenu_menuitem_property_set(mi, "label", label);
 223                 }
 224 
 225                 (*env)->ReleaseStringUTFChars(env, jlabel, label);
 226                 fp_dbusmenu_menuitem_child_append(menu, mi);
 227                 gtk->g_signal_connect_data(mi, "item_activated",
 228                                            G_CALLBACK(callback), elem, NULL, 0);
 229             }
 230         }
 231     }
 232 }
 233 
 234 /*
 235  * Class:     sun_awt_X11_XTaskbarPeer
 236  * Method:    setNativeMenu
 237  * Signature: ([Ljava/awt/MenuItem;)V
 238  */
 239 JNIEXPORT void JNICALL Java_sun_awt_X11_XTaskbarPeer_setNativeMenu
 240 (JNIEnv *env, jobject obj, jobjectArray items) {
 241 
 242     gtk->gdk_threads_enter();
 243 
 244     if (!menu) {
 245         menu = fp_dbusmenu_menuitem_new();
 246     }
 247 
 248     fp_unity_launcher_entry_set_quicklist(entry, menu);
 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 }