UI improvement for auto-run dialog.
[lxde/pcmanfm.git] / src / pcmanfm.c
CommitLineData
b6e3c554
HJYP
1/*
2 * pcmanfm.c
d3451adb 3 *
f0ce8c37 4 * Copyright 2009 PCMan <pcman.tw@gmail.com>
d3451adb 5 *
b6e3c554
HJYP
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
d3451adb 10 *
b6e3c554
HJYP
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
d3451adb 15 *
b6e3c554
HJYP
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
08e70fea 22#ifdef HAVE_CONFIG_H
b6e3c554 23#include <config.h>
08e70fea
HJYP
24#endif
25
b6e3c554
HJYP
26#include <gtk/gtk.h>
27#include <stdio.h>
f8f2bfad
HJYP
28#include <glib/gi18n.h>
29
30#include <stdlib.h>
31#include <string.h>
32/* socket is used to keep single instance */
33#include <sys/types.h>
34#include <sys/socket.h>
35#include <sys/un.h>
36#include <signal.h>
37#include <unistd.h> /* for getcwd */
b6e3c554
HJYP
38
39#include <fm-gtk.h>
4d55886c 40#include "app-config.h"
b6e3c554 41#include "main-win.h"
8505a8ef 42#include "desktop.h"
71f82759 43#include "volume-manager.h"
c5fccf1d 44#include "pref.h"
df6826e0 45#include "pcmanfm.h"
8505a8ef 46
f8f2bfad
HJYP
47static int sock;
48GIOChannel* io_channel = NULL;
49
50gboolean daemon_mode = FALSE;
51
f8f2bfad 52static char** files_to_open = NULL;
f970e846
HJYP
53static char* profile = NULL;
54static char* config_name = NULL;
f8f2bfad
HJYP
55static gboolean no_desktop = FALSE;
56static gboolean show_desktop = FALSE;
57static gboolean desktop_off = FALSE;
58static gboolean desktop_running = FALSE;
59static gboolean new_tab = FALSE;
60static int show_pref = 0;
61static gboolean desktop_pref = FALSE;
62static char* set_wallpaper = NULL;
63static gboolean find_files = FALSE;
5b890032 64static char* ipc_cwd = NULL;
f8f2bfad
HJYP
65
66static int n_pcmanfm_ref = 0;
67
68static GOptionEntry opt_entries[] =
8505a8ef 69{
f970e846 70 { "new-tab", 't', 0, G_OPTION_ARG_NONE, &new_tab, N_("Open folders in new tabs of the last used window instead of creating new windows"), NULL },
a48f9fc8 71 { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile, N_("Name of configuration profile"), "<profile name>" },
f8f2bfad
HJYP
72 { "desktop", '\0', 0, G_OPTION_ARG_NONE, &show_desktop, N_("Launch desktop manager"), NULL },
73 { "desktop-off", '\0', 0, G_OPTION_ARG_NONE, &desktop_off, N_("Turn off desktop manager if it's running"), NULL },
f8f2bfad 74 { "daemon-mode", 'd', 0, G_OPTION_ARG_NONE, &daemon_mode, N_("Run PCManFM as a daemon"), NULL },
f8f2bfad 75 { "desktop-pref", '\0', 0, G_OPTION_ARG_NONE, &desktop_pref, N_("Open desktop preference dialog"), NULL },
f970e846 76 { "set-wallpaper", 'w', 0, G_OPTION_ARG_FILENAME, &set_wallpaper, N_("Set desktop wallpaper"), N_("<image file>") },
a48f9fc8 77 { "show-pref", '\0', 0, G_OPTION_ARG_INT, &show_pref, N_("Open preference dialog. 'n' is number of the page you want to show (1, 2, 3...)."), "n" },
f8f2bfad 78 { "find-files", 'f', 0, G_OPTION_ARG_NONE, &find_files, N_("Open Find Files utility"), NULL },
f970e846 79 { "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop, N_("No function. Just to be compatible with nautilus"), NULL },
f8f2bfad
HJYP
80 {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_open, NULL, N_("[FILE1, FILE2,...]")},
81 { NULL }
82};
83
84static gboolean single_instance_check();
85static void single_instance_finalize();
86static void get_socket_name(char* buf, int len);
87static gboolean pcmanfm_run();
88static gboolean on_socket_event(GIOChannel* ioc, GIOCondition cond, gpointer data);
b6e3c554
HJYP
89
90int main(int argc, char** argv)
91{
4d55886c 92 FmConfig* config;
f8f2bfad
HJYP
93 GError* err = NULL;
94
95#ifdef ENABLE_NLS
96 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
97 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
98 textdomain ( GETTEXT_PACKAGE );
99#endif
100
101 /* initialize GTK+ and parse the command line arguments */
102 if(G_UNLIKELY(!gtk_init_with_args(&argc, &argv, "", opt_entries, GETTEXT_PACKAGE, &err)))
103 {
104 g_printf("%s\n", err->message);
c5fccf1d 105 g_error_free(err);
f8f2bfad
HJYP
106 return 1;
107 }
108
109 /* ensure that there is only one instance of pcmanfm.
110 if there is an existing instance, command line arguments
111 will be passed to the existing instance, and exit() will be called here. */
112 single_instance_check();
113
114 /* intercept signals */
115 signal( SIGPIPE, SIG_IGN );
116 /* signal( SIGHUP, gtk_main_quit ); */
117 signal( SIGINT, gtk_main_quit );
118 signal( SIGTERM, gtk_main_quit );
b6e3c554 119
f970e846
HJYP
120 config = fm_app_config_new(); /* this automatically load libfm config file. */
121 /* load pcmanfm-specific config file */
122 if(profile)
123 config_name = g_strconcat("pcmanfm/", profile, ".conf", NULL);
4b3e1fe9 124 fm_app_config_load_from_file(FM_APP_CONFIG(config), config_name);
f970e846 125
4d55886c 126 fm_gtk_init(config);
b6e3c554 127
f8f2bfad
HJYP
128 /* the main part */
129 if(pcmanfm_run())
130 {
71f82759 131 fm_volume_manager_init();
f8f2bfad
HJYP
132 gtk_main();
133 if(desktop_running)
134 fm_desktop_manager_finalize();
f8f2bfad 135 fm_config_save(config, NULL); /* save libfm config */
f970e846 136 fm_app_config_save((FmAppConfig*)config, config_name); /* save pcmanfm config */
71f82759 137 fm_volume_manager_finalize();
f8f2bfad
HJYP
138 }
139 single_instance_finalize();
8505a8ef 140
f8f2bfad
HJYP
141 fm_gtk_finalize();
142 g_object_unref(config);
f8f2bfad
HJYP
143 return 0;
144}
145
44409230 146inline static GString* args_to_ipc_buf()
f8f2bfad
HJYP
147{
148 int i;
149 GString* buf = g_string_sized_new(1024);
5b890032
HJYP
150 /* send our current working dir to existing instance via IPC. */
151 ipc_cwd = g_get_current_dir();
152 g_string_append(buf, ipc_cwd);
153 g_string_append_c(buf, '\0');
154 g_free(ipc_cwd);
155 ipc_cwd = NULL;
f8f2bfad
HJYP
156 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
157 {
158 GOptionEntry* ent = &opt_entries[i];
159 if(G_LIKELY(*ent->long_name))
160 g_string_append(buf, ent->long_name);
f8f2bfad
HJYP
161 g_string_append_c(buf, '=');
162 switch(ent->arg)
163 {
164 case G_OPTION_ARG_NONE: /* bool */
165 g_string_append_c(buf, *(gboolean*)ent->arg_data ? '1' : '0');
166 break;
167 case G_OPTION_ARG_INT: /* int */
168 g_string_append_printf(buf, "%d", *(gint*)ent->arg_data);
169 break;
170 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
f970e846 171 case G_OPTION_ARG_STRING_ARRAY:
f8f2bfad
HJYP
172 {
173 char** files = *(char***)ent->arg_data;
174 if(files)
175 {
176 for(;*files;++files)
177 {
1171a9b5
HJYP
178 char* tmp = fm_canonicalize_filename(*files, TRUE);
179 g_string_append(buf, tmp);
180 g_free(tmp);
44409230 181 g_string_append_c(buf, '\0');
f8f2bfad
HJYP
182 }
183 }
0d8dce1a 184 else
0d8dce1a 185 g_string_append_c(buf, '\0');
44409230 186 g_string_append_c(buf, '\0'); /* end of array */
f8f2bfad
HJYP
187 }
188 break;
189 case G_OPTION_ARG_FILENAME:
190 case G_OPTION_ARG_STRING: /* string */
191 if(*(gchar**)ent->arg_data)
cacf261a 192 {
44409230 193 /* FIXME: Handle . and ..*/
cacf261a 194 const char* fn = *(gchar**)ent->arg_data;
f8f2bfad 195 g_string_append(buf, *(gchar**)ent->arg_data);
cacf261a 196 }
f8f2bfad
HJYP
197 break;
198 }
44409230 199 g_string_append_c(buf, '\0');
f8f2bfad 200 }
44409230 201 g_string_append_c(buf, '\0'); /* EOF */
f8f2bfad
HJYP
202 return buf;
203}
204
44409230 205inline static void ipc_buf_to_args(GString* buf)
f8f2bfad 206{
44409230
HJYP
207 char* p;
208 GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
209 int i;
210 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
211 g_hash_table_insert(hash, opt_entries[i].long_name, &opt_entries[i]);
5b890032
HJYP
212 p = buf->str; /* the fist string in buf is cwd */
213 i = strlen(p) + 1;
214 ipc_cwd = g_memdup(p, i);
215 for( p += i; *p; )
b6e3c554 216 {
44409230
HJYP
217 GOptionEntry* ent;
218 char *name = p;
219 char* val = strchr(p, '=');
220 *val = '\0';
221 ++val;
222 p = val + strlen(val) + 1; /* next item */
223 ent = g_hash_table_lookup(hash, name);
224 if(G_LIKELY(ent))
f8f2bfad 225 {
f8f2bfad
HJYP
226 switch(ent->arg)
227 {
228 case G_OPTION_ARG_NONE: /* bool */
44409230 229 *(gboolean*)ent->arg_data = val[0] == '1';
f8f2bfad
HJYP
230 break;
231 case G_OPTION_ARG_INT: /* int */
44409230 232 *(gint*)ent->arg_data = atoi(val);
f8f2bfad
HJYP
233 break;
234 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
f970e846 235 case G_OPTION_ARG_STRING_ARRAY:
19abb8bf 236 {
44409230
HJYP
237 char*** pstrs = (char***)ent->arg_data;
238 if(*pstrs)
239 g_strfreev(*pstrs);
5b890032 240 if(val && *val) /* the array is not empty */
19abb8bf 241 {
5b890032
HJYP
242 GPtrArray* strs = g_ptr_array_new();
243 do
244 {
245 g_ptr_array_add(strs, g_strdup(val));
246 val += (strlen(val) + 1);
247 }while(*val != '\0');
248 g_ptr_array_add(strs, NULL);
249 *pstrs = g_ptr_array_free(strs, FALSE);
250 p = val - 1;
251 }
252 else /* the array is empty */
253 {
254 p = val + 1;
255 *pstrs = NULL;
256 }
44409230 257 continue;
19abb8bf 258 }
f8f2bfad
HJYP
259 break;
260 case G_OPTION_ARG_FILENAME:
261 case G_OPTION_ARG_STRING: /* string */
262 if(*(char**)ent->arg_data)
263 g_free(*(char**)ent->arg_data);
44409230 264 *(char**)ent->arg_data = *val ? g_strdup(val) : NULL;
f8f2bfad
HJYP
265 break;
266 }
267 }
b6e3c554 268 }
44409230 269 g_hash_table_destroy(hash);
f8f2bfad 270}
8505a8ef 271
f8f2bfad
HJYP
272gboolean on_socket_event( GIOChannel* ioc, GIOCondition cond, gpointer data )
273{
274 int client, r;
275 socklen_t addr_len = 0;
276 struct sockaddr_un client_addr ={ 0 };
277 static char buf[ 1024 ];
278 GString* args;
279 char** file;
b6e3c554 280
f8f2bfad
HJYP
281 if ( cond & G_IO_IN )
282 {
283 client = accept( g_io_channel_unix_get_fd( ioc ), (struct sockaddr *)&client_addr, &addr_len );
284 if ( client != -1 )
285 {
286 args = g_string_sized_new(1024);
287 while( (r = read( client, buf, sizeof(buf) )) > 0 )
288 g_string_append_len( args, buf, r);
289 shutdown( client, 2 );
290 close( client );
44409230 291 ipc_buf_to_args(args);
f8f2bfad
HJYP
292 g_string_free( args, TRUE );
293 pcmanfm_run();
294 }
295 }
296 return TRUE;
297}
19fbd668 298
f8f2bfad
HJYP
299void get_socket_name( char* buf, int len )
300{
301 char* dpy = gdk_get_display();
302 g_snprintf( buf, len, "/tmp/.pcmanfm2-socket%s-%s", dpy, g_get_user_name() );
303 g_free( dpy );
304}
8505a8ef 305
f8f2bfad
HJYP
306gboolean single_instance_check()
307{
308 struct sockaddr_un addr;
309 int addr_len;
310 int ret;
311 int reuse;
b6e3c554 312
f8f2bfad
HJYP
313 if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
314 {
315 ret = 1;
316 goto _exit;
317 }
318
319 /* FIXME: use abstract socket */
320 addr.sun_family = AF_UNIX;
321 get_socket_name(addr.sun_path, sizeof( addr.sun_path ));
322#ifdef SUN_LEN
323 addr_len = SUN_LEN(&addr);
324#else
325 addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family );
326#endif
327
328 /* try to connect to existing instance */
329 if(connect(sock, (struct sockaddr*)&addr, addr_len) == 0)
330 {
331 /* connected successfully */
44409230 332 GString* buf = args_to_ipc_buf();
f8f2bfad
HJYP
333 write(sock, buf->str, buf->len);
334 g_string_free(buf, TRUE);
335
336 shutdown( sock, 2 );
337 close( sock );
338 ret = 0;
339 goto _exit;
340 }
341
342 /* There is no existing server, and we are in the first instance. */
343 unlink( addr.sun_path ); /* delete old socket file if it exists. */
344 reuse = 1;
345 ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) );
346 if(bind(sock, (struct sockaddr*)&addr, addr_len) == -1)
347 {
348 ret = 1;
349 goto _exit;
350 }
351
352 io_channel = g_io_channel_unix_new(sock);
353 g_io_channel_set_encoding(io_channel, NULL, NULL);
354 g_io_channel_set_buffered(io_channel, FALSE);
355 g_io_add_watch(io_channel, G_IO_IN,
356 (GIOFunc)on_socket_event, NULL);
357 if(listen(sock, 5) == -1)
358 {
359 ret = 1;
360 goto _exit;
361 }
d3451adb 362 return TRUE;
f8f2bfad
HJYP
363
364_exit:
365
366 gdk_notify_startup_complete();
367 exit( ret );
368}
369
370void single_instance_finalize()
371{
372 char lock_file[256];
373 shutdown(sock, 2);
374 g_io_channel_unref(io_channel);
375 close(sock);
376 get_socket_name(lock_file, sizeof( lock_file ));
377 unlink(lock_file);
378}
379
380
381gboolean pcmanfm_run()
382{
383 gboolean ret = TRUE;
384 char** file;
385 GtkWidget* w;
386
f8f2bfad
HJYP
387 if(!files_to_open)
388 {
389 /* Launch desktop manager */
390 if(show_desktop)
391 {
392 if(!desktop_running)
393 {
394 fm_desktop_manager_init();
395 desktop_running = TRUE;
396 }
19abb8bf 397 show_desktop = FALSE;
f8f2bfad
HJYP
398 return TRUE;
399 }
400 else if(desktop_off)
401 {
402 if(desktop_running)
403 {
404 desktop_running = FALSE;
405 fm_desktop_manager_finalize();
406 }
19abb8bf 407 desktop_off = FALSE;
f8f2bfad
HJYP
408 return FALSE;
409 }
410 else if(show_pref > 0)
411 {
c5fccf1d
HJYP
412 fm_edit_preference(NULL, show_pref - 1);
413 show_pref = 0;
f8f2bfad
HJYP
414 return TRUE;
415 }
416 else if(desktop_pref)
417 {
c5fccf1d 418 fm_desktop_preference();
19abb8bf 419 desktop_pref = FALSE;
f8f2bfad
HJYP
420 return TRUE;
421 }
422 else if(set_wallpaper)
423 {
f970e846 424 /* g_debug("\'%s\'", set_wallpaper); */
f8f2bfad 425 /* Make sure this is a support image file. */
f970e846 426 if(gdk_pixbuf_get_file_info(set_wallpaper, NULL, NULL))
f8f2bfad 427 {
f970e846
HJYP
428 if(app_config->wallpaper)
429 g_free(app_config->wallpaper);
430 app_config->wallpaper = set_wallpaper;
f8f2bfad 431 set_wallpaper = NULL;
f970e846 432 if(app_config->wallpaper_mode == FM_WP_COLOR)
c5fccf1d 433 app_config->wallpaper_mode = FM_WP_FIT;
4b3e1fe9 434 fm_config_emit_changed(FM_CONFIG(app_config), "wallpaper");
dcecce78 435 fm_app_config_save(app_config, config_name);
f8f2bfad
HJYP
436 }
437 return FALSE;
438 }
439 }
440
441 if(G_UNLIKELY(find_files))
442 {
443 /* FIXME: find files */
444 }
445 else
446 {
df6826e0
HJYP
447 if(files_to_open)
448 {
449 char** filename;
450 FmJob* job = fm_file_info_job_new(NULL);
cacf261a 451 FmPath* cwd = NULL;
d951adb8 452 GList* infos;
df6826e0
HJYP
453 for(filename=files_to_open; *filename; ++filename)
454 {
cacf261a
HJYP
455 FmPath* path;
456 if( **filename == '/' || strstr(*filename, ":/") ) /* absolute path or URI */
457 path = fm_path_new(*filename);
0d8dce1a
HJYP
458 else if( strcmp(*filename, "~") == 0 ) /* special case for home dir */
459 {
460 path = fm_path_get_home();
461 fm_main_win_add_win(NULL, path);
462 continue;
463 }
cacf261a
HJYP
464 else /* basename */
465 {
466 if(G_UNLIKELY(!cwd))
467 {
468 /* FIXME: This won't work if those filenames are passed via IPC since the receiving process has different cwd. */
469 char* cwd_str = g_get_current_dir();
470 cwd = fm_path_new(cwd_str);
471 g_free(cwd_str);
472 }
473 path = fm_path_new_relative(cwd, *filename);
474 }
4b3e1fe9 475 fm_file_info_job_add(FM_FILE_INFO_JOB(job), path);
df6826e0
HJYP
476 fm_path_unref(path);
477 }
cacf261a
HJYP
478 if(cwd)
479 fm_path_unref(cwd);
a48f9fc8 480 fm_job_run_sync_with_mainloop(job);
d951adb8
HJYP
481 infos = fm_list_peek_head_link(FM_FILE_INFO_JOB(job)->file_infos);
482 fm_launch_files_simple(NULL, NULL, infos, pcmanfm_open_folder, NULL);
df6826e0 483 g_object_unref(job);
cacf261a 484 ret = (n_pcmanfm_ref >= 1); /* if there is opened window, return true to run the main loop. */
df6826e0
HJYP
485 }
486 else
f8f2bfad
HJYP
487 {
488 FmPath* path;
5b890032 489 char* cwd = ipc_cwd ? ipc_cwd : g_get_current_dir();
0d8dce1a
HJYP
490 path = fm_path_new(cwd);
491 fm_main_win_add_win(NULL, path);
492 fm_path_unref(path);
493 g_free(cwd);
5b890032 494 ipc_cwd = NULL;
f8f2bfad
HJYP
495 }
496 }
497 return ret;
498}
499
500/* After opening any window/dialog/tool, this should be called. */
501void pcmanfm_ref()
502{
503 ++n_pcmanfm_ref;
504 /* g_debug("ref: %d", n_pcmanfm_ref); */
505}
506
507/* After closing any window/dialog/tool, this should be called.
508 * If the last window is closed and we are not a deamon, pcmanfm will quit.
509 */
510void pcmanfm_unref()
511{
512 --n_pcmanfm_ref;
513 /* g_debug("unref: %d, daemon_mode=%d, desktop_running=%d", n_pcmanfm_ref, daemon_mode, desktop_running); */
514 if( 0 == n_pcmanfm_ref && !daemon_mode && !desktop_running )
515 gtk_main_quit();
b6e3c554 516}
df6826e0
HJYP
517
518gboolean pcmanfm_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
519{
520 FmMainWin* win = FM_MAIN_WIN(user_data);
521 GList* l = folder_infos;
522 for(; l; l=l->next)
523 {
524 FmFileInfo* fi = (FmFileInfo*)l->data;
525 fm_main_win_open_in_last_active(fi->path);
526 }
527 return TRUE;
528}
dcecce78
HJYP
529
530void pcmanfm_save_config()
531{
532 fm_config_save(fm_config, NULL);
533 fm_app_config_save(app_config, config_name);
534}
c56a211a
HJYP
535
536void pcmanfm_open_folder_in_terminal(GtkWindow* parent, FmPath* dir)
537{
538 GAppInfo* app;
539 char* cmd;
540 char** argv;
541 int argc;
542 if(!fm_config->terminal)
543 {
544 fm_show_error(parent, _("Terminal emulator is not set."));
545 fm_edit_preference(parent, PREF_ADVANCED);
546 return;
547 }
548 if(!g_shell_parse_argv(fm_config->terminal, &argc, &argv, NULL))
549 return;
550 app = g_app_info_create_from_commandline(argv[0], NULL, 0, NULL);
551 g_strfreev(argv);
552 if(app)
553 {
554 GError* err = NULL;
555 GAppLaunchContext* ctx = gdk_app_launch_context_new();
556 char* cwd_str;
557
558 if(fm_path_is_native(dir))
559 cwd_str = fm_path_to_str(dir);
560 else
561 {
562 GFile* gf = fm_path_to_gfile(dir);
563 cwd_str = g_file_get_path(gf);
564 g_object_unref(gf);
565 }
99476ede 566 gdk_app_launch_context_set_screen(GDK_APP_LAUNCH_CONTEXT(ctx), parent ? gtk_widget_get_screen(GTK_WIDGET(parent)) : gdk_screen_get_default());
c56a211a
HJYP
567 gdk_app_launch_context_set_timestamp(GDK_APP_LAUNCH_CONTEXT(ctx), gtk_get_current_event_time());
568 g_chdir(cwd_str); /* FIXME: currently we don't have better way for this. maybe a wrapper script? */
569 g_free(cwd_str);
570 if(!g_app_info_launch(app, NULL, ctx, &err))
571 {
572 fm_show_error(parent, err->message);
573 g_error_free(err);
574 }
575 g_object_unref(ctx);
576 g_object_unref(app);
577 }
578}