c68e8c8f869fb32a2595c73ae9029e38805fc282
[lxde/lxpanel.git] / src / gtk-run.c
1 /*
2 * gtk-run.c: Little application launcher
3 * Copyright (C) 2006 Hong Jen Tee (PCMan) pcman.tw(AT)gmail.com
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.
18 */
19
20 #include <gtk/gtk.h>
21 #include <glib/gi18n.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "misc.h"
26 #include <menu-cache.h>
27
28 extern Panel *p; /* FIXME: this should be removed */
29
30 static GtkWidget* win = NULL;
31 static GSList* app_list = NULL;
32
33 static MenuCacheApp* match_app_by_exec(const char* exec)
34 {
35 GSList* l;
36 MenuCacheApp* ret = NULL;
37 char* exec_path = g_find_program_in_path(exec);
38 const char* pexec;
39 int path_len, exec_len, len;
40
41 if( ! exec_path )
42 return NULL;
43
44 path_len = strlen(exec_path);
45 exec_len = strlen(exec);
46
47 for( l = app_list; l; l = l->next )
48 {
49 MenuCacheApp* app = MENU_CACHE_APP(l->data);
50 const char* app_exec = menu_cache_app_get_exec(app);
51 #if 0 /* This is useless and incorrect. */
52 /* Dirty hacks to skip sudo programs. This can be a little bit buggy */
53 if( g_str_has_prefix(app_exec, "gksu") )
54 {
55 app_exec += 4;
56 if( app_exec[0] == '\0' ) /* "gksu" itself */
57 app_exec -= 4;
58 else if( app_exec[0] == ' ' ) /* "gksu something..." */
59 ++app_exec;
60 else if( g_str_has_prefix(app_exec, "do ") ) /* "gksudo something" */
61 app_exec += 3;
62 }
63 else if( g_str_has_prefix(app_exec, "kdesu ") ) /* kdesu */
64 app_exec += 6;
65 #endif
66
67 if( g_path_is_absolute(app_exec) )
68 {
69 pexec = exec_path;
70 len = path_len;
71 }
72 else
73 {
74 pexec = exec;
75 len = exec_len;
76 }
77
78 if( strncmp(app_exec, pexec, len) == 0 )
79 {
80 /* exact match has the highest priority */
81 if( app_exec[len] == '\0' )
82 {
83 ret = app;
84 break;
85 }
86 /* those matches the pattern: exe_name %F|%f|%U|%u have higher priority */
87 if( app_exec[len] == ' ' )
88 {
89 if( app_exec[len + 1] == '%' )
90 {
91 if( strchr( "FfUu", app_exec[len + 2] ) )
92 {
93 ret = app;
94 break;
95 }
96 }
97 ret = app;
98 }
99 }
100 }
101
102 /* if this is a symlink */
103 if( ! ret && g_file_test(exec_path, G_FILE_TEST_IS_SYMLINK) )
104 {
105 char target[512]; /* FIXME: is this enough? */
106 len = readlink( exec_path, target, sizeof(target) - 1);
107 if( len > 0 )
108 {
109 target[len] = '\0';
110 ret = match_app_by_exec(target);
111 if( ! ret )
112 {
113 /* FIXME: Actually, target could be relative paths.
114 * So, actually path resolution is needed here. */
115 char* basename = g_path_get_basename(target);
116 char* locate = g_find_program_in_path(basename);
117 if( locate && strcmp(locate, target) == 0 )
118 {
119 ret = match_app_by_exec(basename);
120 g_free(locate);
121 }
122 g_free(basename);
123 }
124 }
125 }
126
127 g_free(exec_path);
128 return ret;
129 }
130
131 static gboolean setup_auto_complete( gpointer entry )
132 {
133 GtkListStore* store;
134 GList *list = NULL, *l;
135 gchar **dirname;
136 gchar **dirnames = g_strsplit( g_getenv("PATH"), ":", 0 );
137 GtkEntryCompletion* comp = gtk_entry_completion_new();
138 gtk_entry_completion_set_minimum_key_length( comp, 2 );
139 gtk_entry_completion_set_inline_completion( comp, TRUE );
140 #if GTK_CHECK_VERSION( 2, 8, 0 )
141 gtk_entry_completion_set_popup_set_width( comp, TRUE );
142 gtk_entry_completion_set_popup_single_match( comp, FALSE );
143 #endif
144 store = gtk_list_store_new( 1, G_TYPE_STRING );
145
146 for( dirname = dirnames; *dirname; ++dirname )
147 {
148 GDir *dir = g_dir_open( *dirname, 0, NULL );
149 const char *name;
150 if( ! dir )
151 continue;
152 while( ( name = g_dir_read_name( dir ) ) )
153 {
154 char* filename = g_build_filename( *dirname, name, NULL );
155 if( g_file_test( filename, G_FILE_TEST_IS_EXECUTABLE ) )
156 {
157 if( !g_list_find_custom( list, name, (GCompareFunc)strcmp ) )
158 list = g_list_prepend( list, g_strdup( name ) );
159 }
160 g_free( filename );
161 }
162 g_dir_close( dir );
163 }
164 g_strfreev( dirnames );
165
166 for( l = list; l; l = l->next )
167 {
168 GtkTreeIter it;
169 gtk_list_store_append( store, &it );
170 gtk_list_store_set( store, &it, 0, l->data, -1 );
171 g_free( l->data );
172 }
173 g_list_free( list );
174
175 gtk_entry_completion_set_model( comp, (GtkTreeModel*)store );
176 g_object_unref( store );
177 gtk_entry_completion_set_text_column( comp, 0 );
178 gtk_entry_set_completion( (GtkEntry*)entry, comp );
179 g_object_unref( G_OBJECT(comp) );
180 return FALSE;
181 }
182
183 /*
184 static void show_error( GtkWindow* parent_win, const char* msg )
185 {
186 GtkWidget* dlg = gtk_message_dialog_new( parent_win,
187 GTK_DIALOG_MODAL,
188 GTK_MESSAGE_ERROR,
189 GTK_BUTTONS_OK, msg );
190 gtk_dialog_run( (GtkDialog*)dlg );
191 gtk_widget_destroy( dlg );
192 }
193 */
194
195 static void on_response( GtkDialog* dlg, gint response, gpointer user_data )
196 {
197 GtkEntry* entry = (GtkEntry*)user_data;
198 if( G_LIKELY(response == GTK_RESPONSE_OK) )
199 {
200 GError* err = NULL;
201 if( !g_spawn_command_line_async( gtk_entry_get_text(entry), &err ) )
202 {
203 show_error( (GtkWindow*)dlg, err->message );
204 g_error_free( err );
205 g_signal_stop_emission_by_name( dlg, "response" );
206 return;
207 }
208 }
209 g_source_remove_by_user_data( entry ); /* remove timeout */
210 gtk_widget_destroy( (GtkWidget*)dlg );
211 win = NULL;
212
213 /* free app list */
214 g_slist_foreach(app_list, (GFunc)menu_cache_item_unref, NULL);
215 g_slist_free(app_list);
216 app_list = NULL;
217 }
218
219 static void on_entry_changed( GtkEntry* entry, GtkImage* img )
220 {
221 const char* str = gtk_entry_get_text(entry);
222 MenuCacheApp* app = NULL;
223 if( str && *str )
224 app = match_app_by_exec(str);
225
226 if( app )
227 {
228 int w, h;
229 GdkPixbuf* pix;
230 gtk_icon_size_lookup(GTK_ICON_SIZE_DIALOG, &w, &h);
231 pix = lxpanel_load_icon(menu_cache_item_get_icon(MENU_CACHE_ITEM(app)), w, h, TRUE);
232 gtk_image_set_from_pixbuf(img, pix);
233 g_object_unref(pix);
234 }
235 else
236 {
237 gtk_image_set_from_stock(img, GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG);
238 }
239 }
240
241 void gtk_run()
242 {
243 GtkWidget *entry, *hbox, *img;
244 MenuCache* menu_cache;
245
246 if( win )
247 {
248 gtk_window_present(GTK_WINDOW(win));
249 return;
250 }
251
252 win = gtk_dialog_new_with_buttons( _("Run"),
253 NULL,
254 GTK_DIALOG_NO_SEPARATOR,
255 GTK_STOCK_OK, GTK_RESPONSE_OK,
256 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
257 NULL );
258 gtk_dialog_set_default_response( (GtkDialog*)win, GTK_RESPONSE_OK );
259 entry = gtk_entry_new();
260
261 /* fix background */
262 //gtk_widget_set_style(win, p->defstyle);
263
264 gtk_entry_set_activates_default( (GtkEntry*)entry, TRUE );
265 gtk_box_pack_start( (GtkBox*)((GtkDialog*)win)->vbox,
266 gtk_label_new(_("Enter the command you want to execute:")),
267 FALSE, FALSE, 8 );
268 hbox = gtk_hbox_new( FALSE, 2 );
269 img = gtk_image_new_from_stock( GTK_STOCK_EXECUTE, GTK_ICON_SIZE_DIALOG );
270 gtk_box_pack_start( (GtkBox*)hbox, img,
271 FALSE, FALSE, 4 );
272 gtk_box_pack_start( (GtkBox*)hbox, entry, TRUE, TRUE, 4 );
273 gtk_box_pack_start( (GtkBox*)((GtkDialog*)win)->vbox,
274 hbox, FALSE, FALSE, 8 );
275 g_signal_connect( win, "response", G_CALLBACK(on_response), entry );
276 gtk_window_set_position( (GtkWindow*)win, GTK_WIN_POS_CENTER );
277 gtk_window_set_default_size( (GtkWindow*)win, 360, -1 );
278 gtk_widget_show_all( win );
279 /* g_timeout_add( 500, setup_auto_complete, entry ); */
280 setup_auto_complete( entry );
281 gtk_widget_show( win );
282
283 g_signal_connect(entry ,"changed", G_CALLBACK(on_entry_changed), img);
284
285 /* get all apps */
286 menu_cache = menu_cache_lookup(g_getenv("XDG_MENU_PREFIX") ? "applications.menu" : "lxde-applications.menu" );
287 if( menu_cache )
288 {
289 app_list = (GSList*)menu_cache_list_all_apps(menu_cache);
290 menu_cache_unref(menu_cache);
291 }
292 }
293