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