* Restore old behavior which depressed taskbar button when the button represent$
[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
20#include <gtk/gtk.h>
21#include <glib/gi18n.h>
22#include <string.h>
18ecfe2a 23#include <unistd.h>
24
389975e0 25#include "misc.h"
28debdd2 26#include <menu-cache.h>
08ea5341 27
1d434622
HJYP
28static GtkWidget* win = NULL; /* the run dialog */
29static GSList* app_list = NULL; /* all known apps in menu cache */
5420dd00 30
1d434622
HJYP
31typedef struct _ThreadData
32{
08067749
HJYP
33 gboolean cancel; /* is the loading cancelled */
34 GSList* files; /* all executable files found */
35 GtkEntry* entry;
1d434622
HJYP
36}ThreadData;
37
38static ThreadData* thread_data = NULL; /* thread data used to load availble programs in PATH */
87cbb654
HJYP
39
40static MenuCacheApp* match_app_by_exec(const char* exec)
41{
42 GSList* l;
43 MenuCacheApp* ret = NULL;
44 char* exec_path = g_find_program_in_path(exec);
45 const char* pexec;
46 int path_len, exec_len, len;
47
48 if( ! exec_path )
49 return NULL;
50
51 path_len = strlen(exec_path);
52 exec_len = strlen(exec);
53
54 for( l = app_list; l; l = l->next )
55 {
56 MenuCacheApp* app = MENU_CACHE_APP(l->data);
57 const char* app_exec = menu_cache_app_get_exec(app);
58#if 0 /* This is useless and incorrect. */
59 /* Dirty hacks to skip sudo programs. This can be a little bit buggy */
60 if( g_str_has_prefix(app_exec, "gksu") )
61 {
62 app_exec += 4;
63 if( app_exec[0] == '\0' ) /* "gksu" itself */
64 app_exec -= 4;
65 else if( app_exec[0] == ' ' ) /* "gksu something..." */
66 ++app_exec;
67 else if( g_str_has_prefix(app_exec, "do ") ) /* "gksudo something" */
68 app_exec += 3;
69 }
70 else if( g_str_has_prefix(app_exec, "kdesu ") ) /* kdesu */
71 app_exec += 6;
72#endif
73
74 if( g_path_is_absolute(app_exec) )
75 {
76 pexec = exec_path;
77 len = path_len;
78 }
79 else
80 {
81 pexec = exec;
82 len = exec_len;
83 }
84
85 if( strncmp(app_exec, pexec, len) == 0 )
86 {
87 /* exact match has the highest priority */
88 if( app_exec[len] == '\0' )
89 {
90 ret = app;
91 break;
92 }
93 /* those matches the pattern: exe_name %F|%f|%U|%u have higher priority */
94 if( app_exec[len] == ' ' )
95 {
96 if( app_exec[len + 1] == '%' )
97 {
98 if( strchr( "FfUu", app_exec[len + 2] ) )
99 {
100 ret = app;
101 break;
102 }
103 }
104 ret = app;
105 }
106 }
107 }
1ce63deb
HJYP
108
109 /* if this is a symlink */
110 if( ! ret && g_file_test(exec_path, G_FILE_TEST_IS_SYMLINK) )
111 {
112 char target[512]; /* FIXME: is this enough? */
113 len = readlink( exec_path, target, sizeof(target) - 1);
114 if( len > 0 )
115 {
116 target[len] = '\0';
117 ret = match_app_by_exec(target);
118 if( ! ret )
119 {
e75e5ce3
HJYP
120 /* FIXME: Actually, target could be relative paths.
121 * So, actually path resolution is needed here. */
1ce63deb
HJYP
122 char* basename = g_path_get_basename(target);
123 char* locate = g_find_program_in_path(basename);
e75e5ce3
HJYP
124 if( locate && strcmp(locate, target) == 0 )
125 {
1ce63deb 126 ret = match_app_by_exec(basename);
e75e5ce3
HJYP
127 g_free(locate);
128 }
1ce63deb
HJYP
129 g_free(basename);
130 }
131 }
132 }
03ec0a93 133
87cbb654
HJYP
134 g_free(exec_path);
135 return ret;
136}
137
1d434622 138static void setup_auto_complete_with_data(ThreadData* data)
08ea5341
HJYP
139{
140 GtkListStore* store;
1d434622 141 GSList *l;
08ea5341
HJYP
142 GtkEntryCompletion* comp = gtk_entry_completion_new();
143 gtk_entry_completion_set_minimum_key_length( comp, 2 );
144 gtk_entry_completion_set_inline_completion( comp, TRUE );
64ec68dc 145#if GTK_CHECK_VERSION( 2, 8, 0 )
08ea5341
HJYP
146 gtk_entry_completion_set_popup_set_width( comp, TRUE );
147 gtk_entry_completion_set_popup_single_match( comp, FALSE );
a68a48a4 148#endif
03ec0a93 149 store = gtk_list_store_new( 1, G_TYPE_STRING );
08ea5341 150
1d434622
HJYP
151 for( l = data->files; l; l = l->next )
152 {
153 const char *name = (const char*)l->data;
154 GtkTreeIter it;
155 gtk_list_store_append( store, &it );
156 gtk_list_store_set( store, &it, 0, name, -1 );
157 }
158
159 gtk_entry_completion_set_model( comp, (GtkTreeModel*)store );
160 g_object_unref( store );
161 gtk_entry_completion_set_text_column( comp, 0 );
162 gtk_entry_set_completion( (GtkEntry*)data->entry, comp );
163 g_object_unref( G_OBJECT(comp) );
164}
165
166void thread_data_free(ThreadData* data)
167{
08067749
HJYP
168 g_slist_foreach(data->files, (GFunc)g_free, NULL);
169 g_slist_free(data->files);
170 g_slice_free(ThreadData, data);
1d434622
HJYP
171}
172
173static gboolean on_thread_finished(ThreadData* data)
174{
08067749
HJYP
175 /* don't setup entry completion if the thread is already cancelled. */
176 if( !data->cancel )
177 setup_auto_complete_with_data(thread_data);
178 thread_data_free(data);
179 thread_data = NULL; /* global thread_data pointer */
180 return FALSE;
1d434622
HJYP
181}
182
183static gpointer thread_func(ThreadData* data)
184{
185 GSList *list = NULL, *l;
186 gchar **dirname;
187 gchar **dirnames = g_strsplit( g_getenv("PATH"), ":", 0 );
188
189 for( dirname = dirnames; !thread_data->cancel && *dirname; ++dirname )
08ea5341
HJYP
190 {
191 GDir *dir = g_dir_open( *dirname, 0, NULL );
192 const char *name;
08ea5341
HJYP
193 if( ! dir )
194 continue;
1d434622 195 while( !thread_data->cancel && (name = g_dir_read_name(dir)) )
08ea5341
HJYP
196 {
197 char* filename = g_build_filename( *dirname, name, NULL );
198 if( g_file_test( filename, G_FILE_TEST_IS_EXECUTABLE ) )
199 {
08067749
HJYP
200 if(thread_data->cancel)
201 break;
1d434622
HJYP
202 if( !g_slist_find_custom( list, name, (GCompareFunc)strcmp ) )
203 list = g_slist_prepend( list, g_strdup( name ) );
08ea5341
HJYP
204 }
205 g_free( filename );
206 }
0dcb6bf5 207 g_dir_close( dir );
08ea5341
HJYP
208 }
209 g_strfreev( dirnames );
210
08067749
HJYP
211 data->files = list;
212 /* install an idle handler to free associated data */
213 g_idle_add((GSourceFunc)on_thread_finished, data);
08ea5341 214
08067749 215 return NULL;
08ea5341
HJYP
216}
217
1d434622 218static void setup_auto_complete( GtkEntry* entry )
08ea5341 219{
08067749
HJYP
220 gboolean cache_is_available = FALSE;
221 /* FIXME: consider saving the list of commands as on-disk cache. */
222 if( cache_is_available )
223 {
224 /* load cached program list */
225 }
226 else
227 {
228 /* load in another working thread */
229 thread_data = g_slice_new0(ThreadData); /* the data will be freed in idle handler later. */
230 thread_data->entry = entry;
231 g_thread_create((GThreadFunc)thread_func, thread_data, FALSE, NULL);
232 }
08ea5341
HJYP
233}
234
235static void on_response( GtkDialog* dlg, gint response, gpointer user_data )
236{
237 GtkEntry* entry = (GtkEntry*)user_data;
238 if( G_LIKELY(response == GTK_RESPONSE_OK) )
239 {
240 GError* err = NULL;
241 if( !g_spawn_command_line_async( gtk_entry_get_text(entry), &err ) )
242 {
243 show_error( (GtkWindow*)dlg, err->message );
244 g_error_free( err );
245 g_signal_stop_emission_by_name( dlg, "response" );
246 return;
247 }
248 }
1d434622 249
08067749
HJYP
250 /* cancel running thread if needed */
251 if( thread_data ) /* the thread is still running */
252 thread_data->cancel = TRUE; /* cancel the thread */
1d434622 253
08ea5341 254 gtk_widget_destroy( (GtkWidget*)dlg );
87cbb654
HJYP
255 win = NULL;
256
257 /* free app list */
258 g_slist_foreach(app_list, (GFunc)menu_cache_item_unref, NULL);
259 g_slist_free(app_list);
260 app_list = NULL;
261}
262
263static void on_entry_changed( GtkEntry* entry, GtkImage* img )
264{
265 const char* str = gtk_entry_get_text(entry);
266 MenuCacheApp* app = NULL;
267 if( str && *str )
268 app = match_app_by_exec(str);
269
270 if( app )
271 {
272 int w, h;
273 GdkPixbuf* pix;
274 gtk_icon_size_lookup(GTK_ICON_SIZE_DIALOG, &w, &h);
2918994e 275 pix = lxpanel_load_icon(menu_cache_item_get_icon(MENU_CACHE_ITEM(app)), w, h, TRUE);
87cbb654
HJYP
276 gtk_image_set_from_pixbuf(img, pix);
277 g_object_unref(pix);
278 }
279 else
280 {
281 gtk_image_set_from_stock(img, GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG);
282 }
08ea5341
HJYP
283}
284
285void gtk_run()
286{
87cbb654
HJYP
287 GtkWidget *entry, *hbox, *img;
288 MenuCache* menu_cache;
289
290 if( win )
291 {
3b6661f3 292 gtk_window_present(GTK_WINDOW(win));
87cbb654
HJYP
293 return;
294 }
295
08ea5341
HJYP
296 win = gtk_dialog_new_with_buttons( _("Run"),
297 NULL,
298 GTK_DIALOG_NO_SEPARATOR,
08ea5341 299 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1d434622 300 GTK_STOCK_OK, GTK_RESPONSE_OK,
08ea5341 301 NULL );
08067749
HJYP
302 gtk_dialog_set_alternative_button_order((GtkDialog*)win,
303 GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1);
08ea5341
HJYP
304 gtk_dialog_set_default_response( (GtkDialog*)win, GTK_RESPONSE_OK );
305 entry = gtk_entry_new();
5420dd00 306
08ea5341
HJYP
307 gtk_entry_set_activates_default( (GtkEntry*)entry, TRUE );
308 gtk_box_pack_start( (GtkBox*)((GtkDialog*)win)->vbox,
309 gtk_label_new(_("Enter the command you want to execute:")),
310 FALSE, FALSE, 8 );
311 hbox = gtk_hbox_new( FALSE, 2 );
87cbb654
HJYP
312 img = gtk_image_new_from_stock( GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG );
313 gtk_box_pack_start( (GtkBox*)hbox, img,
08ea5341
HJYP
314 FALSE, FALSE, 4 );
315 gtk_box_pack_start( (GtkBox*)hbox, entry, TRUE, TRUE, 4 );
316 gtk_box_pack_start( (GtkBox*)((GtkDialog*)win)->vbox,
317 hbox, FALSE, FALSE, 8 );
318 g_signal_connect( win, "response", G_CALLBACK(on_response), entry );
319 gtk_window_set_position( (GtkWindow*)win, GTK_WIN_POS_CENTER );
320 gtk_window_set_default_size( (GtkWindow*)win, 360, -1 );
321 gtk_widget_show_all( win );
1d434622
HJYP
322
323 setup_auto_complete( (GtkEntry*)entry );
c6780e74 324 gtk_widget_show( win );
87cbb654
HJYP
325
326 g_signal_connect(entry ,"changed", G_CALLBACK(on_entry_changed), img);
327
328 /* get all apps */
329 menu_cache = menu_cache_lookup(g_getenv("XDG_MENU_PREFIX") ? "applications.menu" : "lxde-applications.menu" );
330 if( menu_cache )
331 {
332 app_list = (GSList*)menu_cache_list_all_apps(menu_cache);
333 menu_cache_unref(menu_cache);
334 }
08ea5341
HJYP
335}
336