Add support for monitors "All" to span panel over all monitors.
[lxde/lxpanel.git] / src / gtk-run.c
CommitLineData
08ea5341
HJYP
1/*
2 * gtk-run.c: Little application launcher
1d434622 3 * Copyright (C) 2006 Hong Jen Yee (PCMan) pcman.tw(AT)gmail.com
08ea5341
HJYP
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
08ea5341
HJYP
18 */
19
2e836237
AG
20#ifdef HAVE_CONFIG_H
21#include "config.h"
22#endif
23
08ea5341 24#include <gtk/gtk.h>
37f5734a 25#include <gdk/gdkx.h>
08ea5341
HJYP
26#include <glib/gi18n.h>
27#include <string.h>
18ecfe2a 28#include <unistd.h>
29
389975e0 30#include "misc.h"
4a43a4ad 31#ifndef DISABLE_MENU
28debdd2 32#include <menu-cache.h>
4a43a4ad 33#endif
fde71d42 34#include <libfm/fm-gtk.h>
08ea5341 35
1d434622 36static GtkWidget* win = NULL; /* the run dialog */
4a43a4ad 37#ifndef DISABLE_MENU
e275fb1e 38static MenuCache* menu_cache = NULL;
1d434622 39static GSList* app_list = NULL; /* all known apps in menu cache */
e275fb1e 40static gpointer reload_notify_id = NULL;
4a43a4ad 41#endif
5420dd00 42
1d434622
HJYP
43typedef struct _ThreadData
44{
08067749
HJYP
45 gboolean cancel; /* is the loading cancelled */
46 GSList* files; /* all executable files found */
47 GtkEntry* entry;
1d434622
HJYP
48}ThreadData;
49
50static ThreadData* thread_data = NULL; /* thread data used to load availble programs in PATH */
87cbb654 51
4a43a4ad 52#ifndef DISABLE_MENU
87cbb654
HJYP
53static MenuCacheApp* match_app_by_exec(const char* exec)
54{
55 GSList* l;
56 MenuCacheApp* ret = NULL;
57 char* exec_path = g_find_program_in_path(exec);
58 const char* pexec;
59 int path_len, exec_len, len;
60
61 if( ! exec_path )
62 return NULL;
63
64 path_len = strlen(exec_path);
65 exec_len = strlen(exec);
66
67 for( l = app_list; l; l = l->next )
68 {
69 MenuCacheApp* app = MENU_CACHE_APP(l->data);
70 const char* app_exec = menu_cache_app_get_exec(app);
f4b48897
MJ
71 if ( ! app_exec)
72 continue;
87cbb654
HJYP
73#if 0 /* This is useless and incorrect. */
74 /* Dirty hacks to skip sudo programs. This can be a little bit buggy */
75 if( g_str_has_prefix(app_exec, "gksu") )
76 {
77 app_exec += 4;
78 if( app_exec[0] == '\0' ) /* "gksu" itself */
79 app_exec -= 4;
80 else if( app_exec[0] == ' ' ) /* "gksu something..." */
81 ++app_exec;
82 else if( g_str_has_prefix(app_exec, "do ") ) /* "gksudo something" */
83 app_exec += 3;
84 }
85 else if( g_str_has_prefix(app_exec, "kdesu ") ) /* kdesu */
86 app_exec += 6;
87#endif
88
89 if( g_path_is_absolute(app_exec) )
90 {
91 pexec = exec_path;
92 len = path_len;
93 }
94 else
95 {
96 pexec = exec;
97 len = exec_len;
98 }
99
100 if( strncmp(app_exec, pexec, len) == 0 )
101 {
102 /* exact match has the highest priority */
103 if( app_exec[len] == '\0' )
104 {
105 ret = app;
106 break;
107 }
108 /* those matches the pattern: exe_name %F|%f|%U|%u have higher priority */
109 if( app_exec[len] == ' ' )
110 {
111 if( app_exec[len + 1] == '%' )
112 {
113 if( strchr( "FfUu", app_exec[len + 2] ) )
114 {
115 ret = app;
116 break;
117 }
118 }
119 ret = app;
120 }
121 }
122 }
1ce63deb
HJYP
123
124 /* if this is a symlink */
125 if( ! ret && g_file_test(exec_path, G_FILE_TEST_IS_SYMLINK) )
126 {
127 char target[512]; /* FIXME: is this enough? */
128 len = readlink( exec_path, target, sizeof(target) - 1);
129 if( len > 0 )
130 {
131 target[len] = '\0';
132 ret = match_app_by_exec(target);
133 if( ! ret )
134 {
e75e5ce3
HJYP
135 /* FIXME: Actually, target could be relative paths.
136 * So, actually path resolution is needed here. */
1ce63deb
HJYP
137 char* basename = g_path_get_basename(target);
138 char* locate = g_find_program_in_path(basename);
e75e5ce3
HJYP
139 if( locate && strcmp(locate, target) == 0 )
140 {
1ce63deb 141 ret = match_app_by_exec(basename);
e75e5ce3
HJYP
142 g_free(locate);
143 }
1ce63deb
HJYP
144 g_free(basename);
145 }
146 }
147 }
03ec0a93 148
87cbb654
HJYP
149 g_free(exec_path);
150 return ret;
151}
4a43a4ad 152#endif
87cbb654 153
1d434622 154static void setup_auto_complete_with_data(ThreadData* data)
08ea5341
HJYP
155{
156 GtkListStore* store;
1d434622 157 GSList *l;
08ea5341
HJYP
158 GtkEntryCompletion* comp = gtk_entry_completion_new();
159 gtk_entry_completion_set_minimum_key_length( comp, 2 );
160 gtk_entry_completion_set_inline_completion( comp, TRUE );
161 gtk_entry_completion_set_popup_set_width( comp, TRUE );
162 gtk_entry_completion_set_popup_single_match( comp, FALSE );
03ec0a93 163 store = gtk_list_store_new( 1, G_TYPE_STRING );
08ea5341 164
1d434622
HJYP
165 for( l = data->files; l; l = l->next )
166 {
167 const char *name = (const char*)l->data;
168 GtkTreeIter it;
169 gtk_list_store_append( store, &it );
170 gtk_list_store_set( store, &it, 0, name, -1 );
171 }
172
173 gtk_entry_completion_set_model( comp, (GtkTreeModel*)store );
174 g_object_unref( store );
175 gtk_entry_completion_set_text_column( comp, 0 );
176 gtk_entry_set_completion( (GtkEntry*)data->entry, comp );
ff558cc5
HJYP
177
178 /* trigger entry completion */
179 gtk_entry_completion_complete(comp);
180 g_object_unref( comp );
1d434622
HJYP
181}
182
adf42c84 183static void thread_data_free(ThreadData* data)
1d434622 184{
08067749
HJYP
185 g_slist_foreach(data->files, (GFunc)g_free, NULL);
186 g_slist_free(data->files);
187 g_slice_free(ThreadData, data);
1d434622
HJYP
188}
189
190static gboolean on_thread_finished(ThreadData* data)
191{
08067749
HJYP
192 /* don't setup entry completion if the thread is already cancelled. */
193 if( !data->cancel )
194 setup_auto_complete_with_data(thread_data);
195 thread_data_free(data);
196 thread_data = NULL; /* global thread_data pointer */
197 return FALSE;
1d434622
HJYP
198}
199
200static gpointer thread_func(ThreadData* data)
201{
d40b6601 202 GSList *list = NULL;
1d434622
HJYP
203 gchar **dirname;
204 gchar **dirnames = g_strsplit( g_getenv("PATH"), ":", 0 );
205
206 for( dirname = dirnames; !thread_data->cancel && *dirname; ++dirname )
08ea5341
HJYP
207 {
208 GDir *dir = g_dir_open( *dirname, 0, NULL );
209 const char *name;
08ea5341
HJYP
210 if( ! dir )
211 continue;
1d434622 212 while( !thread_data->cancel && (name = g_dir_read_name(dir)) )
08ea5341
HJYP
213 {
214 char* filename = g_build_filename( *dirname, name, NULL );
215 if( g_file_test( filename, G_FILE_TEST_IS_EXECUTABLE ) )
216 {
08067749
HJYP
217 if(thread_data->cancel)
218 break;
1d434622
HJYP
219 if( !g_slist_find_custom( list, name, (GCompareFunc)strcmp ) )
220 list = g_slist_prepend( list, g_strdup( name ) );
08ea5341
HJYP
221 }
222 g_free( filename );
223 }
0dcb6bf5 224 g_dir_close( dir );
08ea5341
HJYP
225 }
226 g_strfreev( dirnames );
227
08067749
HJYP
228 data->files = list;
229 /* install an idle handler to free associated data */
230 g_idle_add((GSourceFunc)on_thread_finished, data);
08ea5341 231
08067749 232 return NULL;
08ea5341
HJYP
233}
234
1d434622 235static void setup_auto_complete( GtkEntry* entry )
08ea5341 236{
08067749
HJYP
237 gboolean cache_is_available = FALSE;
238 /* FIXME: consider saving the list of commands as on-disk cache. */
239 if( cache_is_available )
240 {
241 /* load cached program list */
242 }
243 else
244 {
245 /* load in another working thread */
246 thread_data = g_slice_new0(ThreadData); /* the data will be freed in idle handler later. */
247 thread_data->entry = entry;
248 g_thread_create((GThreadFunc)thread_func, thread_data, FALSE, NULL);
249 }
08ea5341
HJYP
250}
251
4a43a4ad 252#ifndef DISABLE_MENU
e275fb1e
HJYP
253static void reload_apps(MenuCache* cache, gpointer user_data)
254{
5431fc11 255 g_debug("reload apps!");
e275fb1e
HJYP
256 if(app_list)
257 {
258 g_slist_foreach(app_list, (GFunc)menu_cache_item_unref, NULL);
259 g_slist_free(app_list);
260 }
880d25d2 261 app_list = menu_cache_list_all_apps(cache);
e275fb1e 262}
4a43a4ad 263#endif
e275fb1e 264
08ea5341
HJYP
265static void on_response( GtkDialog* dlg, gint response, gpointer user_data )
266{
267 GtkEntry* entry = (GtkEntry*)user_data;
268 if( G_LIKELY(response == GTK_RESPONSE_OK) )
269 {
908d9fdc 270 if (!fm_launch_command_simple(GTK_WINDOW(dlg), NULL, 0, gtk_entry_get_text(entry), NULL))
08ea5341 271 {
08ea5341
HJYP
272 g_signal_stop_emission_by_name( dlg, "response" );
273 return;
274 }
275 }
1d434622 276
08067749
HJYP
277 /* cancel running thread if needed */
278 if( thread_data ) /* the thread is still running */
279 thread_data->cancel = TRUE; /* cancel the thread */
1d434622 280
08ea5341 281 gtk_widget_destroy( (GtkWidget*)dlg );
87cbb654
HJYP
282 win = NULL;
283
4a43a4ad 284#ifndef DISABLE_MENU
87cbb654
HJYP
285 /* free app list */
286 g_slist_foreach(app_list, (GFunc)menu_cache_item_unref, NULL);
287 g_slist_free(app_list);
288 app_list = NULL;
e275fb1e
HJYP
289
290 /* free menu cache */
291 menu_cache_remove_reload_notify(menu_cache, reload_notify_id);
292 reload_notify_id = NULL;
293 menu_cache_unref(menu_cache);
294 menu_cache = NULL;
4a43a4ad 295#endif
87cbb654
HJYP
296}
297
4a43a4ad 298#ifndef DISABLE_MENU
87cbb654
HJYP
299static void on_entry_changed( GtkEntry* entry, GtkImage* img )
300{
301 const char* str = gtk_entry_get_text(entry);
302 MenuCacheApp* app = NULL;
303 if( str && *str )
304 app = match_app_by_exec(str);
305
306 if( app )
307 {
308 int w, h;
fde71d42
AG
309 const char *name = menu_cache_item_get_icon(MENU_CACHE_ITEM(app));
310 FmIcon * fm_icon;
87cbb654 311 GdkPixbuf* pix;
fde71d42 312
f9e8104e 313 gtk_icon_size_lookup(GTK_ICON_SIZE_DIALOG, &w, &h);
fde71d42
AG
314 fm_icon = fm_icon_from_name(name ? name : "application-x-executable");
315 pix = fm_pixbuf_from_icon_with_fallback(fm_icon, h, "application-x-executable");
316 g_object_unref(fm_icon);
87cbb654
HJYP
317 gtk_image_set_from_pixbuf(img, pix);
318 g_object_unref(pix);
319 }
320 else
321 {
322 gtk_image_set_from_stock(img, GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG);
323 }
08ea5341 324}
4a43a4ad 325#endif
08ea5341 326
37f5734a
HJYP
327static void activate_window(GtkWindow* toplevel_window)
328{
0bcf9045
AG
329 /* Calling gtk_window_present() cannot support
330 * source indication. Use our own implementation.
331 * Without this, the window activated might be
332 * put in background by WM to avoid stealing
333 * of focus.
334 * See EWMH spec, source indication part:
335 * http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#sourceindication
336 */
337 GtkWidget* widget = GTK_WIDGET(toplevel_window);
338 GdkScreen* screen = gtk_widget_get_screen(widget);
339 if (gdk_x11_screen_supports_net_wm_hint (screen,
37f5734a
HJYP
340 gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW")))
341 {
0bcf9045
AG
342 GdkWindow* window = gtk_widget_get_window(widget);
343 GdkDisplay* display = gtk_widget_get_display(widget);
344 GdkWindow* root = gdk_screen_get_root_window(screen);
345
346 /* show the window first */
347 gtk_widget_show(widget);
348
349 /* then, activate it */
350 XClientMessageEvent xclient;
351 memset(&xclient, 0, sizeof (xclient));
352 xclient.type = ClientMessage;
353 xclient.window = GDK_WINDOW_XID(window);
354 xclient.message_type = gdk_x11_get_xatom_by_name_for_display(display,
355 "_NET_ACTIVE_WINDOW");
356 xclient.format = 32;
357 xclient.data.l[0] = 2; /* source indication: 2 represents direct user actions. */
358 xclient.data.l[1] = gtk_get_current_event_time();
359 xclient.data.l[2] = None; /* currently active window */
360 xclient.data.l[3] = 0;
361 xclient.data.l[4] = 0;
362
363 XSendEvent(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(root), False,
364 SubstructureRedirectMask|SubstructureNotifyMask,
365 (XEvent *)&xclient);
37f5734a 366 } /* if _NET_ACTIVE_WINDOW command is not supported */
0bcf9045
AG
367 else
368 gtk_window_present(toplevel_window);
37f5734a
HJYP
369}
370
08ea5341
HJYP
371void gtk_run()
372{
175f73d1 373 GtkWidget *entry, *hbox, *img, *dlg_vbox;
87cbb654 374
37f5734a 375 if(!win)
87cbb654 376 {
0bcf9045
AG
377 win = gtk_dialog_new_with_buttons( _("Run"),
378 NULL,
379 GTK_DIALOG_NO_SEPARATOR,
380 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
381 GTK_STOCK_OK, GTK_RESPONSE_OK,
382 NULL );
383 gtk_dialog_set_alternative_button_order((GtkDialog*)win,
384 GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1);
385 gtk_dialog_set_default_response( (GtkDialog*)win, GTK_RESPONSE_OK );
386 entry = gtk_entry_new();
387
388 gtk_entry_set_activates_default( (GtkEntry*)entry, TRUE );
389 dlg_vbox = gtk_dialog_get_content_area((GtkDialog*)win);
390 gtk_box_pack_start( (GtkBox*)dlg_vbox,
391 gtk_label_new(_("Enter the command you want to execute:")),
392 FALSE, FALSE, 8 );
393 hbox = gtk_hbox_new( FALSE, 2 );
394 img = gtk_image_new_from_stock( GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG );
395 gtk_box_pack_start( (GtkBox*)hbox, img,
396 FALSE, FALSE, 4 );
397 gtk_box_pack_start( (GtkBox*)hbox, entry, TRUE, TRUE, 4 );
398 gtk_box_pack_start( (GtkBox*)dlg_vbox,
399 hbox, FALSE, FALSE, 8 );
400 g_signal_connect( win, "response", G_CALLBACK(on_response), entry );
401 gtk_window_set_position( (GtkWindow*)win, GTK_WIN_POS_CENTER );
402 gtk_window_set_default_size( (GtkWindow*)win, 360, -1 );
403 gtk_widget_show_all( win );
404
405 setup_auto_complete( (GtkEntry*)entry );
406 gtk_widget_show(win);
407
4a43a4ad 408#ifndef DISABLE_MENU
0bcf9045
AG
409 g_signal_connect(entry ,"changed", G_CALLBACK(on_entry_changed), img);
410
411 /* get all apps */
53eed543 412#if defined(MENU_CACHE_CHECK_VERSION) && MENU_CACHE_CHECK_VERSION(0, 6, 1)
880d25d2 413 menu_cache = menu_cache_lookup_sync(g_getenv("XDG_MENU_PREFIX") ? "applications.menu" : "lxde-applications.menu" );
0bcf9045
AG
414 if( menu_cache )
415 {
53eed543
AG
416#else
417 /* SF bug #689: menu_cache_lookup_sync() was fail-prone before 0.6.1 */
418 menu_cache = menu_cache_lookup(g_getenv("XDG_MENU_PREFIX") ? "applications.menu" : "lxde-applications.menu" );
419 if( menu_cache )
420 {
421 menu_cache_reload(menu_cache);
422#endif
880d25d2 423 app_list = menu_cache_list_all_apps(menu_cache);
0bcf9045
AG
424 reload_notify_id = menu_cache_add_reload_notify(menu_cache, reload_apps, NULL);
425 }
4a43a4ad 426#endif
0bcf9045
AG
427 }
428
429 activate_window(GTK_WINDOW(win));
08ea5341
HJYP
430}
431
b8d0aacd
HG
432
433/* vim: set sw=4 et sts=4 ts=4 : */