Fix #2979578 unable to open second pcmanfm instance in specific case.
[lxde/pcmanfm.git] / src / pcmanfm.c
1 /*
2 * pcmanfm.c
3 *
4 * Copyright 2009 - 2010 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 *
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.
10 *
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.
15 *
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
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <gtk/gtk.h>
27 #include <stdio.h>
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 */
38
39 #include <libfm/fm-gtk.h>
40 #include "app-config.h"
41 #include "main-win.h"
42 #include "desktop.h"
43 #include "volume-manager.h"
44 #include "pref.h"
45 #include "pcmanfm.h"
46
47 static int sock;
48 GIOChannel* io_channel = NULL;
49
50 gboolean daemon_mode = FALSE;
51
52 static char** files_to_open = NULL;
53 static char* profile = NULL;
54 static char* config_name = NULL;
55 static gboolean no_desktop = FALSE;
56 static gboolean show_desktop = FALSE;
57 static gboolean desktop_off = FALSE;
58 static gboolean desktop_running = FALSE;
59 static gboolean new_tab = FALSE;
60 static int show_pref = 0;
61 static gboolean desktop_pref = FALSE;
62 static char* set_wallpaper = NULL;
63 static gboolean find_files = FALSE;
64 static char* ipc_cwd = NULL;
65
66 static int n_pcmanfm_ref = 0;
67
68 static GOptionEntry opt_entries[] =
69 {
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 },
71 { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile, N_("Name of configuration profile"), "<profile name>" },
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 },
74 { "daemon-mode", 'd', 0, G_OPTION_ARG_NONE, &daemon_mode, N_("Run PCManFM as a daemon"), NULL },
75 { "desktop-pref", '\0', 0, G_OPTION_ARG_NONE, &desktop_pref, N_("Open desktop preference dialog"), NULL },
76 { "set-wallpaper", 'w', 0, G_OPTION_ARG_FILENAME, &set_wallpaper, N_("Set desktop wallpaper"), N_("<image file>") },
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" },
78 { "find-files", 'f', 0, G_OPTION_ARG_NONE, &find_files, N_("Open Find Files utility"), NULL },
79 { "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop, N_("No function. Just to be compatible with nautilus"), NULL },
80 {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_open, NULL, N_("[FILE1, FILE2,...]")},
81 { NULL }
82 };
83
84 static gboolean single_instance_check();
85 static void single_instance_finalize();
86 static void get_socket_name(char* buf, int len);
87 static gboolean pcmanfm_run();
88 static gboolean on_socket_event(GIOChannel* ioc, GIOCondition cond, gpointer data);
89
90 int main(int argc, char** argv)
91 {
92 FmConfig* config;
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);
105 g_error_free(err);
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 );
119
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);
124 fm_app_config_load_from_file(FM_APP_CONFIG(config), config_name);
125
126 fm_gtk_init(config);
127
128 /* the main part */
129 if(pcmanfm_run())
130 {
131 fm_volume_manager_init();
132 gtk_main();
133 if(desktop_running)
134 fm_desktop_manager_finalize();
135 fm_config_save(config, NULL); /* save libfm config */
136 fm_app_config_save((FmAppConfig*)config, config_name); /* save pcmanfm config */
137 fm_volume_manager_finalize();
138 }
139 single_instance_finalize();
140
141 fm_gtk_finalize();
142 g_object_unref(config);
143 return 0;
144 }
145
146 inline static void buf_append_str(GByteArray* buf, const char* str)
147 {
148 int len;
149 if(G_LIKELY(str))
150 {
151 len = strlen(str) + 1;
152 g_byte_array_append(buf, (guint8*)&len, sizeof(len));
153 g_byte_array_append(buf, (guint8*)str, len);
154 }
155 else
156 {
157 len = 0;
158 g_byte_array_append(buf, (guint8*)&len, sizeof(len));
159 }
160 }
161
162 inline static GByteArray* args_to_ipc_buf()
163 {
164 int i, len;
165 GByteArray* buf = g_byte_array_sized_new(4096);
166 /* send our current working dir to existing instance via IPC. */
167 ipc_cwd = g_get_current_dir();
168 buf_append_str(buf, ipc_cwd);
169 g_free(ipc_cwd);
170
171 g_byte_array_append(buf, (guint8*)&new_tab, sizeof(new_tab));
172 g_byte_array_append(buf, (guint8*)&show_desktop, sizeof(show_desktop));
173 g_byte_array_append(buf, (guint8*)&desktop_off, sizeof(desktop_off));
174 g_byte_array_append(buf, (guint8*)&desktop_pref, sizeof(desktop_pref));
175 buf_append_str(buf, set_wallpaper);
176 g_byte_array_append(buf, (guint8*)&show_pref, sizeof(show_pref));
177 g_byte_array_append(buf, (guint8*)&find_files, sizeof(find_files));
178 g_byte_array_append(buf, (guint8*)&no_desktop, sizeof(no_desktop));
179
180 len = files_to_open ? g_strv_length(files_to_open) : 0;
181 g_byte_array_append(buf, (guint8*)&len, sizeof(len));
182 for(i = 0; i < len; ++i)
183 buf_append_str(buf, files_to_open[i]);
184
185 return buf;
186 }
187
188 inline static gboolean buf_read_bool(const char**p)
189 {
190 gboolean ret;
191 memcpy(&ret, *p, sizeof(ret));
192 *p += sizeof(ret);
193 return ret;
194 }
195
196 inline static int buf_read_int(const char**p)
197 {
198 int ret;
199 memcpy(&ret, *p, sizeof(ret));
200 *p += sizeof(ret);
201 return ret;
202 }
203
204 inline static char* buf_read_str(const char**p)
205 {
206 char* ret;
207 int len = buf_read_int(p);
208 if(len > 0)
209 {
210 ret = g_malloc(len);
211 memcpy(ret, *p, len);
212 *p += len;
213 }
214 else
215 ret = NULL;
216 return ret;
217 }
218
219 inline static void ipc_buf_to_args(GByteArray* buf)
220 {
221 int i, len;
222 char* p = buf->data;
223 char* cwd = buf_read_str(&p);
224 new_tab = buf_read_bool(&p);
225 show_desktop = buf_read_bool(&p);
226 desktop_off = buf_read_bool(&p);
227 desktop_pref = buf_read_bool(&p);
228 g_free(set_wallpaper);
229 set_wallpaper = buf_read_str(&p);
230 show_pref = buf_read_int(&p);
231 find_files = buf_read_bool(&p);
232 no_desktop = buf_read_bool(&p);
233
234 len = buf_read_int(&p);
235 g_debug("len = %d", len);
236 if(len > 0)
237 {
238 files_to_open = g_new(char*, len + 1);
239 for(i = 0; i < len; ++i)
240 {
241 char* file = buf_read_str(&p);
242 char* scheme = g_uri_parse_scheme(file);
243 if(scheme) /* a valid URI */
244 {
245 /* FIXME: should we canonicalize URIs? and how about file:///? */
246 files_to_open[i] = file;
247 g_free(scheme);
248 }
249 else /* a file path */
250 {
251 files_to_open[i] = fm_canonicalize_filename(file, cwd);
252 g_free(file);
253 }
254 }
255 files_to_open[i] = NULL;
256 }
257 else
258 files_to_open = NULL;
259 g_free(cwd);
260 }
261
262 gboolean on_socket_event( GIOChannel* ioc, GIOCondition cond, gpointer data )
263 {
264 int client, r;
265 socklen_t addr_len = 0;
266 struct sockaddr_un client_addr ={ 0 };
267 static char buf[ 1024 ];
268 GByteArray* args;
269
270 if ( cond & G_IO_IN )
271 {
272 client = accept( g_io_channel_unix_get_fd( ioc ), (struct sockaddr *)&client_addr, &addr_len );
273 if ( client != -1 )
274 {
275 args = g_byte_array_sized_new(4096);
276 while( (r = read( client, buf, sizeof(buf) )) > 0 )
277 g_byte_array_append( args, (guint8*)buf, r);
278 shutdown( client, 2 );
279 close( client );
280 ipc_buf_to_args(args);
281 g_byte_array_free( args, TRUE );
282 pcmanfm_run();
283 }
284 }
285 return TRUE;
286 }
287
288 void get_socket_name( char* buf, int len )
289 {
290 char* dpy = gdk_get_display();
291 g_snprintf( buf, len, "/tmp/.pcmanfm2-socket%s-%s", dpy, g_get_user_name() );
292 g_free( dpy );
293 }
294
295 gboolean single_instance_check()
296 {
297 struct sockaddr_un addr;
298 int addr_len;
299 int ret;
300 int reuse;
301
302 if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
303 {
304 ret = 1;
305 goto _exit;
306 }
307
308 /* FIXME: use abstract socket */
309 addr.sun_family = AF_UNIX;
310 get_socket_name(addr.sun_path, sizeof( addr.sun_path ));
311 #ifdef SUN_LEN
312 addr_len = SUN_LEN(&addr);
313 #else
314 addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family );
315 #endif
316
317 /* try to connect to existing instance */
318 if(connect(sock, (struct sockaddr*)&addr, addr_len) == 0)
319 {
320 /* connected successfully */
321 GByteArray* buf = args_to_ipc_buf();
322 write(sock, buf->data, buf->len);
323 g_byte_array_free(buf, TRUE);
324
325 shutdown( sock, 2 );
326 close( sock );
327 ret = 0;
328 goto _exit;
329 }
330
331 /* There is no existing server, and we are in the first instance. */
332 unlink( addr.sun_path ); /* delete old socket file if it exists. */
333 reuse = 1;
334 ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) );
335 if(bind(sock, (struct sockaddr*)&addr, addr_len) == -1)
336 {
337 ret = 1;
338 goto _exit;
339 }
340
341 io_channel = g_io_channel_unix_new(sock);
342 g_io_channel_set_encoding(io_channel, NULL, NULL);
343 g_io_channel_set_buffered(io_channel, FALSE);
344 g_io_add_watch(io_channel, G_IO_IN,
345 (GIOFunc)on_socket_event, NULL);
346 if(listen(sock, 5) == -1)
347 {
348 ret = 1;
349 goto _exit;
350 }
351 return TRUE;
352
353 _exit:
354
355 gdk_notify_startup_complete();
356 exit( ret );
357 }
358
359 void single_instance_finalize()
360 {
361 char lock_file[256];
362 shutdown(sock, 2);
363 g_io_channel_unref(io_channel);
364 close(sock);
365 get_socket_name(lock_file, sizeof( lock_file ));
366 unlink(lock_file);
367 }
368
369 static FmJobErrorAction on_file_info_job_error(FmFileInfoJob* job, GError* err, FmJobErrorSeverity severity, gpointer user_data)
370 {
371 if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_NOT_MOUNTED)
372 {
373 FmPath* current = fm_file_info_job_get_current(job);
374 if( fm_mount_path(NULL, current, TRUE) )
375 return FM_JOB_RETRY;
376 }
377 return FM_JOB_CONTINUE;
378 }
379
380 gboolean pcmanfm_run()
381 {
382 gboolean ret = TRUE;
383
384 if(!files_to_open)
385 {
386 /* Launch desktop manager */
387 if(show_desktop)
388 {
389 if(!desktop_running)
390 {
391 fm_desktop_manager_init();
392 desktop_running = TRUE;
393 }
394 show_desktop = FALSE;
395 return TRUE;
396 }
397 else if(desktop_off)
398 {
399 if(desktop_running)
400 {
401 desktop_running = FALSE;
402 fm_desktop_manager_finalize();
403 }
404 desktop_off = FALSE;
405 return FALSE;
406 }
407 else if(show_pref > 0)
408 {
409 fm_edit_preference(NULL, show_pref - 1);
410 show_pref = 0;
411 return TRUE;
412 }
413 else if(desktop_pref)
414 {
415 fm_desktop_preference();
416 desktop_pref = FALSE;
417 return TRUE;
418 }
419 else if(set_wallpaper)
420 {
421 /* g_debug("\'%s\'", set_wallpaper); */
422 /* Make sure this is a support image file. */
423 if(gdk_pixbuf_get_file_info(set_wallpaper, NULL, NULL))
424 {
425 if(app_config->wallpaper)
426 g_free(app_config->wallpaper);
427 app_config->wallpaper = set_wallpaper;
428 set_wallpaper = NULL;
429 if(app_config->wallpaper_mode == FM_WP_COLOR)
430 app_config->wallpaper_mode = FM_WP_FIT;
431 fm_config_emit_changed(FM_CONFIG(app_config), "wallpaper");
432 fm_app_config_save(app_config, config_name);
433 }
434 return FALSE;
435 }
436 }
437
438 if(G_UNLIKELY(find_files))
439 {
440 /* FIXME: find files */
441 }
442 else
443 {
444 if(files_to_open)
445 {
446 char** filename;
447 FmJob* job = fm_file_info_job_new(NULL, 0);
448 FmPath* cwd = NULL;
449 GList* infos;
450 for(filename=files_to_open; *filename; ++filename)
451 {
452 FmPath* path;
453 if( **filename == '/' || strstr(*filename, ":/") ) /* absolute path or URI */
454 path = fm_path_new(*filename);
455 else if( strcmp(*filename, "~") == 0 ) /* special case for home dir */
456 {
457 path = fm_path_get_home();
458 fm_main_win_add_win(NULL, path);
459 continue;
460 }
461 else /* basename */
462 {
463 if(G_UNLIKELY(!cwd))
464 {
465 /* FIXME: This won't work if those filenames are passed via IPC since the receiving process has different cwd. */
466 char* cwd_str = g_get_current_dir();
467 cwd = fm_path_new(cwd_str);
468 g_free(cwd_str);
469 }
470 path = fm_path_new_relative(cwd, *filename);
471 }
472 fm_file_info_job_add(FM_FILE_INFO_JOB(job), path);
473 fm_path_unref(path);
474 }
475 if(cwd)
476 fm_path_unref(cwd);
477 g_signal_connect(job, "error", G_CALLBACK(on_file_info_job_error), NULL);
478 fm_job_run_sync_with_mainloop(job);
479 infos = fm_list_peek_head_link(FM_FILE_INFO_JOB(job)->file_infos);
480 fm_launch_files_simple(NULL, NULL, infos, pcmanfm_open_folder, NULL);
481 g_object_unref(job);
482 ret = (n_pcmanfm_ref >= 1); /* if there is opened window, return true to run the main loop. */
483
484 g_strfreev(files_to_open);
485 files_to_open = NULL;
486 }
487 else
488 {
489 FmPath* path;
490 char* cwd = ipc_cwd ? ipc_cwd : g_get_current_dir();
491 path = fm_path_new(cwd);
492 fm_main_win_add_win(NULL, path);
493 fm_path_unref(path);
494 g_free(cwd);
495 ipc_cwd = NULL;
496 }
497 }
498 return ret;
499 }
500
501 /* After opening any window/dialog/tool, this should be called. */
502 void pcmanfm_ref()
503 {
504 ++n_pcmanfm_ref;
505 /* g_debug("ref: %d", n_pcmanfm_ref); */
506 }
507
508 /* After closing any window/dialog/tool, this should be called.
509 * If the last window is closed and we are not a deamon, pcmanfm will quit.
510 */
511 void pcmanfm_unref()
512 {
513 --n_pcmanfm_ref;
514 /* g_debug("unref: %d, daemon_mode=%d, desktop_running=%d", n_pcmanfm_ref, daemon_mode, desktop_running); */
515 if( 0 == n_pcmanfm_ref && !daemon_mode && !desktop_running )
516 gtk_main_quit();
517 }
518
519 gboolean pcmanfm_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
520 {
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 }
529
530 void pcmanfm_save_config()
531 {
532 fm_config_save(fm_config, NULL);
533 fm_app_config_save(app_config, config_name);
534 }
535
536 void pcmanfm_open_folder_in_terminal(GtkWindow* parent, FmPath* dir)
537 {
538 GAppInfo* app;
539 char** argv;
540 int argc;
541 if(!fm_config->terminal)
542 {
543 fm_show_error(parent, _("Terminal emulator is not set."));
544 fm_edit_preference(parent, PREF_ADVANCED);
545 return;
546 }
547 if(!g_shell_parse_argv(fm_config->terminal, &argc, &argv, NULL))
548 return;
549 app = g_app_info_create_from_commandline(argv[0], NULL, 0, NULL);
550 g_strfreev(argv);
551 if(app)
552 {
553 GError* err = NULL;
554 GAppLaunchContext* ctx = gdk_app_launch_context_new();
555 char* cwd_str;
556
557 if(fm_path_is_native(dir))
558 cwd_str = fm_path_to_str(dir);
559 else
560 {
561 GFile* gf = fm_path_to_gfile(dir);
562 cwd_str = g_file_get_path(gf);
563 g_object_unref(gf);
564 }
565 gdk_app_launch_context_set_screen(GDK_APP_LAUNCH_CONTEXT(ctx), parent ? gtk_widget_get_screen(GTK_WIDGET(parent)) : gdk_screen_get_default());
566 gdk_app_launch_context_set_timestamp(GDK_APP_LAUNCH_CONTEXT(ctx), gtk_get_current_event_time());
567 g_chdir(cwd_str); /* FIXME: currently we don't have better way for this. maybe a wrapper script? */
568 g_free(cwd_str);
569 if(!g_app_info_launch(app, NULL, ctx, &err))
570 {
571 fm_show_error(parent, err->message);
572 g_error_free(err);
573 }
574 g_object_unref(ctx);
575 g_object_unref(app);
576 }
577 }
578
579 /* FIXME: Need to load content of ~/Templates and list available templates in popup menus. */
580 void pcmanfm_create_new(GtkWindow* parent, FmPath* cwd, const char* templ)
581 {
582 GError* err = NULL;
583 FmPath* dest;
584 char* basename;
585 _retry:
586 basename = fm_get_user_input(parent, _("Create New..."), _("Enter a name for the newly created file:"), _("New"));
587 if(!basename)
588 return;
589
590 dest = fm_path_new_child(cwd, basename);
591 g_free(basename);
592
593 if( templ == TEMPL_NAME_FOLDER )
594 {
595 GFile* gf = fm_path_to_gfile(dest);
596 if(!g_file_make_directory(gf, NULL, &err))
597 {
598 if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
599 {
600 fm_path_unref(dest);
601 g_error_free(err);
602 g_object_unref(gf);
603 err = NULL;
604 goto _retry;
605 }
606 fm_show_error(parent, err->message);
607 g_error_free(err);
608 }
609
610 if(!err) /* select the newly created file */
611 {
612 /*FIXME: this doesn't work since the newly created file will
613 * only be shown after file-created event was fired on its
614 * folder's monitor and after FmFolder handles it in idle
615 * handler. So, we cannot select it since it's not yet in
616 * the folder model now. */
617 /* fm_folder_view_select_file_path(fv, dest); */
618 }
619 g_object_unref(gf);
620 }
621 else if( templ == TEMPL_NAME_BLANK )
622 {
623 GFile* gf = fm_path_to_gfile(dest);
624 GFileOutputStream* f = g_file_create(gf, G_FILE_CREATE_NONE, NULL, &err);
625 if(f)
626 {
627 g_output_stream_close(G_OUTPUT_STREAM(f), NULL, NULL);
628 g_object_unref(f);
629 }
630 else
631 {
632 if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS)
633 {
634 fm_path_unref(dest);
635 g_error_free(err);
636 g_object_unref(gf);
637 err = NULL;
638 goto _retry;
639 }
640 fm_show_error(parent, err->message);
641 g_error_free(err);
642 }
643
644 if(!err) /* select the newly created file */
645 {
646 /*FIXME: this doesn't work since the newly created file will
647 * only be shown after file-created event was fired on its
648 * folder's monitor and after FmFolder handles it in idle
649 * handler. So, we cannot select it since it's not yet in
650 * the folder model now. */
651 /* fm_folder_view_select_file_path(fv, dest); */
652 }
653 g_object_unref(gf);
654 }
655 else /* templates in ~/Templates */
656 {
657 FmPath* dir = fm_path_new(g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES));
658 FmPath* template = fm_path_new_child(dir, templ);
659 fm_copy_file(template, cwd);
660 fm_path_unref(template);
661 }
662 fm_path_unref(dest);
663 }