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
35extern 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
42static jmp_buf j;
43
44static void *unity_libhandle = NULL;
45
46static DbusmenuMenuitem* menu = NULL;
47UnityLauncherEntry* entry = NULL;
48
49static jclass jTaskbarCls = NULL;
50static jmethodID jTaskbarCallback = NULL;
51static jmethodID jMenuItemGetLabel = NULL;
52
53GList* globalRefs = NULL;
54
55static 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
63static 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
98void 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 */
108JNIEXPORT 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 */
137JNIEXPORT 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 */
149JNIEXPORT 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 */
166JNIEXPORT 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 */
182JNIEXPORT 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
194void deleteGlobalRef(gpointer data) {
195    JNIEnv* env = (JNIEnv*) JNU_GetEnv(jvm, JNI_VERSION_1_2);
196    (*env)->DeleteGlobalRef(env, data);
197}
198
199void 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 */
236JNIEXPORT 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}
258