Requires libmenu-cache 0.3.2.
[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;
64
65static int n_pcmanfm_ref = 0;
66
67static GOptionEntry opt_entries[] =
8505a8ef 68{
f970e846 69 { "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 70 { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile, N_("Name of configuration profile"), "<profile name>" },
f8f2bfad
HJYP
71 { "desktop", '\0', 0, G_OPTION_ARG_NONE, &show_desktop, N_("Launch desktop manager"), NULL },
72 { "desktop-off", '\0', 0, G_OPTION_ARG_NONE, &desktop_off, N_("Turn off desktop manager if it's running"), NULL },
f8f2bfad 73 { "daemon-mode", 'd', 0, G_OPTION_ARG_NONE, &daemon_mode, N_("Run PCManFM as a daemon"), NULL },
f8f2bfad 74 { "desktop-pref", '\0', 0, G_OPTION_ARG_NONE, &desktop_pref, N_("Open desktop preference dialog"), NULL },
f970e846 75 { "set-wallpaper", 'w', 0, G_OPTION_ARG_FILENAME, &set_wallpaper, N_("Set desktop wallpaper"), N_("<image file>") },
a48f9fc8 76 { "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 77 { "find-files", 'f', 0, G_OPTION_ARG_NONE, &find_files, N_("Open Find Files utility"), NULL },
f970e846 78 { "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop, N_("No function. Just to be compatible with nautilus"), NULL },
f8f2bfad
HJYP
79 {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_open, NULL, N_("[FILE1, FILE2,...]")},
80 { NULL }
81};
82
83static gboolean single_instance_check();
84static void single_instance_finalize();
85static void get_socket_name(char* buf, int len);
86static gboolean pcmanfm_run();
87static gboolean on_socket_event(GIOChannel* ioc, GIOCondition cond, gpointer data);
b6e3c554
HJYP
88
89int main(int argc, char** argv)
90{
4d55886c 91 FmConfig* config;
f8f2bfad
HJYP
92 GError* err = NULL;
93
94#ifdef ENABLE_NLS
95 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
96 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
97 textdomain ( GETTEXT_PACKAGE );
98#endif
99
100 /* initialize GTK+ and parse the command line arguments */
101 if(G_UNLIKELY(!gtk_init_with_args(&argc, &argv, "", opt_entries, GETTEXT_PACKAGE, &err)))
102 {
103 g_printf("%s\n", err->message);
c5fccf1d 104 g_error_free(err);
f8f2bfad
HJYP
105 return 1;
106 }
107
108 /* ensure that there is only one instance of pcmanfm.
109 if there is an existing instance, command line arguments
110 will be passed to the existing instance, and exit() will be called here. */
111 single_instance_check();
112
113 /* intercept signals */
114 signal( SIGPIPE, SIG_IGN );
115 /* signal( SIGHUP, gtk_main_quit ); */
116 signal( SIGINT, gtk_main_quit );
117 signal( SIGTERM, gtk_main_quit );
b6e3c554 118
f970e846
HJYP
119 config = fm_app_config_new(); /* this automatically load libfm config file. */
120 /* load pcmanfm-specific config file */
121 if(profile)
122 config_name = g_strconcat("pcmanfm/", profile, ".conf", NULL);
4b3e1fe9 123 fm_app_config_load_from_file(FM_APP_CONFIG(config), config_name);
f970e846 124
4d55886c 125 fm_gtk_init(config);
b6e3c554 126
f8f2bfad
HJYP
127 /* the main part */
128 if(pcmanfm_run())
129 {
71f82759 130 fm_volume_manager_init();
f8f2bfad
HJYP
131 gtk_main();
132 if(desktop_running)
133 fm_desktop_manager_finalize();
f8f2bfad 134 fm_config_save(config, NULL); /* save libfm config */
f970e846 135 fm_app_config_save((FmAppConfig*)config, config_name); /* save pcmanfm config */
71f82759 136 fm_volume_manager_finalize();
f8f2bfad
HJYP
137 }
138 single_instance_finalize();
8505a8ef 139
f8f2bfad
HJYP
140 fm_gtk_finalize();
141 g_object_unref(config);
f8f2bfad
HJYP
142 return 0;
143}
144
44409230 145inline static GString* args_to_ipc_buf()
f8f2bfad
HJYP
146{
147 int i;
148 GString* buf = g_string_sized_new(1024);
f8f2bfad
HJYP
149 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
150 {
151 GOptionEntry* ent = &opt_entries[i];
152 if(G_LIKELY(*ent->long_name))
153 g_string_append(buf, ent->long_name);
f8f2bfad
HJYP
154 g_string_append_c(buf, '=');
155 switch(ent->arg)
156 {
157 case G_OPTION_ARG_NONE: /* bool */
158 g_string_append_c(buf, *(gboolean*)ent->arg_data ? '1' : '0');
159 break;
160 case G_OPTION_ARG_INT: /* int */
161 g_string_append_printf(buf, "%d", *(gint*)ent->arg_data);
162 break;
163 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
f970e846 164 case G_OPTION_ARG_STRING_ARRAY:
f8f2bfad
HJYP
165 {
166 char** files = *(char***)ent->arg_data;
167 if(files)
168 {
169 for(;*files;++files)
170 {
1171a9b5
HJYP
171 char* tmp = fm_canonicalize_filename(*files, TRUE);
172 g_string_append(buf, tmp);
173 g_free(tmp);
44409230 174 g_string_append_c(buf, '\0');
f8f2bfad
HJYP
175 }
176 }
0d8dce1a
HJYP
177 else
178 {
179 char* cwd = g_get_current_dir();
180 g_string_append(buf, cwd);
181 g_free(cwd);
182 g_string_append_c(buf, '\0');
183 }
44409230 184 g_string_append_c(buf, '\0'); /* end of array */
f8f2bfad
HJYP
185 }
186 break;
187 case G_OPTION_ARG_FILENAME:
188 case G_OPTION_ARG_STRING: /* string */
189 if(*(gchar**)ent->arg_data)
cacf261a 190 {
44409230 191 /* FIXME: Handle . and ..*/
cacf261a 192 const char* fn = *(gchar**)ent->arg_data;
f8f2bfad 193 g_string_append(buf, *(gchar**)ent->arg_data);
cacf261a 194 }
f8f2bfad
HJYP
195 break;
196 }
44409230 197 g_string_append_c(buf, '\0');
f8f2bfad 198 }
44409230 199 g_string_append_c(buf, '\0'); /* EOF */
f8f2bfad
HJYP
200 return buf;
201}
202
44409230 203inline static void ipc_buf_to_args(GString* buf)
f8f2bfad 204{
44409230
HJYP
205 char* p;
206 GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
207 int i;
208 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
209 g_hash_table_insert(hash, opt_entries[i].long_name, &opt_entries[i]);
210
211 for( p = buf->str; *p; )
b6e3c554 212 {
44409230
HJYP
213 GOptionEntry* ent;
214 char *name = p;
215 char* val = strchr(p, '=');
216 *val = '\0';
217 ++val;
218 p = val + strlen(val) + 1; /* next item */
219 ent = g_hash_table_lookup(hash, name);
220 if(G_LIKELY(ent))
f8f2bfad 221 {
f8f2bfad
HJYP
222 switch(ent->arg)
223 {
224 case G_OPTION_ARG_NONE: /* bool */
44409230 225 *(gboolean*)ent->arg_data = val[0] == '1';
f8f2bfad
HJYP
226 break;
227 case G_OPTION_ARG_INT: /* int */
44409230 228 *(gint*)ent->arg_data = atoi(val);
f8f2bfad
HJYP
229 break;
230 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
f970e846 231 case G_OPTION_ARG_STRING_ARRAY:
19abb8bf 232 {
44409230
HJYP
233 GPtrArray* strs = g_ptr_array_new();
234 char*** pstrs = (char***)ent->arg_data;
235 if(*pstrs)
236 g_strfreev(*pstrs);
237 do
19abb8bf 238 {
44409230
HJYP
239 g_ptr_array_add(strs, g_strdup(val));
240 val += (strlen(val) + 1);
241 }while(*val != '\0');
242 g_ptr_array_add(strs, NULL);
243 *pstrs = g_ptr_array_free(strs, FALSE);
244 p = val - 1;
245 continue;
19abb8bf 246 }
f8f2bfad
HJYP
247 break;
248 case G_OPTION_ARG_FILENAME:
249 case G_OPTION_ARG_STRING: /* string */
250 if(*(char**)ent->arg_data)
251 g_free(*(char**)ent->arg_data);
44409230 252 *(char**)ent->arg_data = *val ? g_strdup(val) : NULL;
f8f2bfad
HJYP
253 break;
254 }
255 }
b6e3c554 256 }
44409230 257 g_hash_table_destroy(hash);
f8f2bfad 258}
8505a8ef 259
f8f2bfad
HJYP
260gboolean on_socket_event( GIOChannel* ioc, GIOCondition cond, gpointer data )
261{
262 int client, r;
263 socklen_t addr_len = 0;
264 struct sockaddr_un client_addr ={ 0 };
265 static char buf[ 1024 ];
266 GString* args;
267 char** file;
b6e3c554 268
f8f2bfad
HJYP
269 if ( cond & G_IO_IN )
270 {
271 client = accept( g_io_channel_unix_get_fd( ioc ), (struct sockaddr *)&client_addr, &addr_len );
272 if ( client != -1 )
273 {
274 args = g_string_sized_new(1024);
275 while( (r = read( client, buf, sizeof(buf) )) > 0 )
276 g_string_append_len( args, buf, r);
277 shutdown( client, 2 );
278 close( client );
44409230 279 ipc_buf_to_args(args);
f8f2bfad
HJYP
280 g_string_free( args, TRUE );
281 pcmanfm_run();
282 }
283 }
284 return TRUE;
285}
19fbd668 286
f8f2bfad
HJYP
287void get_socket_name( char* buf, int len )
288{
289 char* dpy = gdk_get_display();
290 g_snprintf( buf, len, "/tmp/.pcmanfm2-socket%s-%s", dpy, g_get_user_name() );
291 g_free( dpy );
292}
8505a8ef 293
f8f2bfad
HJYP
294gboolean single_instance_check()
295{
296 struct sockaddr_un addr;
297 int addr_len;
298 int ret;
299 int reuse;
b6e3c554 300
f8f2bfad
HJYP
301 if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
302 {
303 ret = 1;
304 goto _exit;
305 }
306
307 /* FIXME: use abstract socket */
308 addr.sun_family = AF_UNIX;
309 get_socket_name(addr.sun_path, sizeof( addr.sun_path ));
310#ifdef SUN_LEN
311 addr_len = SUN_LEN(&addr);
312#else
313 addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family );
314#endif
315
316 /* try to connect to existing instance */
317 if(connect(sock, (struct sockaddr*)&addr, addr_len) == 0)
318 {
319 /* connected successfully */
44409230 320 GString* buf = args_to_ipc_buf();
f8f2bfad
HJYP
321 write(sock, buf->str, buf->len);
322 g_string_free(buf, TRUE);
323
324 shutdown( sock, 2 );
325 close( sock );
326 ret = 0;
327 goto _exit;
328 }
329
330 /* There is no existing server, and we are in the first instance. */
331 unlink( addr.sun_path ); /* delete old socket file if it exists. */
332 reuse = 1;
333 ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) );
334 if(bind(sock, (struct sockaddr*)&addr, addr_len) == -1)
335 {
336 ret = 1;
337 goto _exit;
338 }
339
340 io_channel = g_io_channel_unix_new(sock);
341 g_io_channel_set_encoding(io_channel, NULL, NULL);
342 g_io_channel_set_buffered(io_channel, FALSE);
343 g_io_add_watch(io_channel, G_IO_IN,
344 (GIOFunc)on_socket_event, NULL);
345 if(listen(sock, 5) == -1)
346 {
347 ret = 1;
348 goto _exit;
349 }
d3451adb 350 return TRUE;
f8f2bfad
HJYP
351
352_exit:
353
354 gdk_notify_startup_complete();
355 exit( ret );
356}
357
358void single_instance_finalize()
359{
360 char lock_file[256];
361 shutdown(sock, 2);
362 g_io_channel_unref(io_channel);
363 close(sock);
364 get_socket_name(lock_file, sizeof( lock_file ));
365 unlink(lock_file);
366}
367
368
369gboolean pcmanfm_run()
370{
371 gboolean ret = TRUE;
372 char** file;
373 GtkWidget* w;
374
f8f2bfad
HJYP
375 if(!files_to_open)
376 {
377 /* Launch desktop manager */
378 if(show_desktop)
379 {
380 if(!desktop_running)
381 {
382 fm_desktop_manager_init();
383 desktop_running = TRUE;
384 }
19abb8bf 385 show_desktop = FALSE;
f8f2bfad
HJYP
386 return TRUE;
387 }
388 else if(desktop_off)
389 {
390 if(desktop_running)
391 {
392 desktop_running = FALSE;
393 fm_desktop_manager_finalize();
394 }
19abb8bf 395 desktop_off = FALSE;
f8f2bfad
HJYP
396 return FALSE;
397 }
398 else if(show_pref > 0)
399 {
c5fccf1d
HJYP
400 fm_edit_preference(NULL, show_pref - 1);
401 show_pref = 0;
f8f2bfad
HJYP
402 return TRUE;
403 }
404 else if(desktop_pref)
405 {
c5fccf1d 406 fm_desktop_preference();
19abb8bf 407 desktop_pref = FALSE;
f8f2bfad
HJYP
408 return TRUE;
409 }
410 else if(set_wallpaper)
411 {
f970e846 412 /* g_debug("\'%s\'", set_wallpaper); */
f8f2bfad 413 /* Make sure this is a support image file. */
f970e846 414 if(gdk_pixbuf_get_file_info(set_wallpaper, NULL, NULL))
f8f2bfad 415 {
f970e846
HJYP
416 if(app_config->wallpaper)
417 g_free(app_config->wallpaper);
418 app_config->wallpaper = set_wallpaper;
f8f2bfad 419 set_wallpaper = NULL;
f970e846 420 if(app_config->wallpaper_mode == FM_WP_COLOR)
c5fccf1d 421 app_config->wallpaper_mode = FM_WP_FIT;
4b3e1fe9 422 fm_config_emit_changed(FM_CONFIG(app_config), "wallpaper");
dcecce78 423 fm_app_config_save(app_config, config_name);
f8f2bfad
HJYP
424 }
425 return FALSE;
426 }
427 }
428
429 if(G_UNLIKELY(find_files))
430 {
431 /* FIXME: find files */
432 }
433 else
434 {
df6826e0
HJYP
435 if(files_to_open)
436 {
437 char** filename;
438 FmJob* job = fm_file_info_job_new(NULL);
cacf261a 439 FmPath* cwd = NULL;
d951adb8 440 GList* infos;
df6826e0
HJYP
441 for(filename=files_to_open; *filename; ++filename)
442 {
cacf261a
HJYP
443 FmPath* path;
444 if( **filename == '/' || strstr(*filename, ":/") ) /* absolute path or URI */
445 path = fm_path_new(*filename);
0d8dce1a
HJYP
446 else if( strcmp(*filename, "~") == 0 ) /* special case for home dir */
447 {
448 path = fm_path_get_home();
449 fm_main_win_add_win(NULL, path);
450 continue;
451 }
cacf261a
HJYP
452 else /* basename */
453 {
454 if(G_UNLIKELY(!cwd))
455 {
456 /* FIXME: This won't work if those filenames are passed via IPC since the receiving process has different cwd. */
457 char* cwd_str = g_get_current_dir();
458 cwd = fm_path_new(cwd_str);
459 g_free(cwd_str);
460 }
461 path = fm_path_new_relative(cwd, *filename);
462 }
4b3e1fe9 463 fm_file_info_job_add(FM_FILE_INFO_JOB(job), path);
df6826e0
HJYP
464 fm_path_unref(path);
465 }
cacf261a
HJYP
466 if(cwd)
467 fm_path_unref(cwd);
a48f9fc8 468 fm_job_run_sync_with_mainloop(job);
d951adb8
HJYP
469 infos = fm_list_peek_head_link(FM_FILE_INFO_JOB(job)->file_infos);
470 fm_launch_files_simple(NULL, NULL, infos, pcmanfm_open_folder, NULL);
df6826e0 471 g_object_unref(job);
cacf261a 472 ret = (n_pcmanfm_ref >= 1); /* if there is opened window, return true to run the main loop. */
df6826e0
HJYP
473 }
474 else
f8f2bfad
HJYP
475 {
476 FmPath* path;
0d8dce1a
HJYP
477 char* cwd = g_get_current_dir();
478 path = fm_path_new(cwd);
479 fm_main_win_add_win(NULL, path);
480 fm_path_unref(path);
481 g_free(cwd);
f8f2bfad
HJYP
482 }
483 }
484 return ret;
485}
486
487/* After opening any window/dialog/tool, this should be called. */
488void pcmanfm_ref()
489{
490 ++n_pcmanfm_ref;
491 /* g_debug("ref: %d", n_pcmanfm_ref); */
492}
493
494/* After closing any window/dialog/tool, this should be called.
495 * If the last window is closed and we are not a deamon, pcmanfm will quit.
496 */
497void pcmanfm_unref()
498{
499 --n_pcmanfm_ref;
500 /* g_debug("unref: %d, daemon_mode=%d, desktop_running=%d", n_pcmanfm_ref, daemon_mode, desktop_running); */
501 if( 0 == n_pcmanfm_ref && !daemon_mode && !desktop_running )
502 gtk_main_quit();
b6e3c554 503}
df6826e0
HJYP
504
505gboolean pcmanfm_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
506{
507 FmMainWin* win = FM_MAIN_WIN(user_data);
508 GList* l = folder_infos;
509 for(; l; l=l->next)
510 {
511 FmFileInfo* fi = (FmFileInfo*)l->data;
512 fm_main_win_open_in_last_active(fi->path);
513 }
514 return TRUE;
515}
dcecce78
HJYP
516
517void pcmanfm_save_config()
518{
519 fm_config_save(fm_config, NULL);
520 fm_app_config_save(app_config, config_name);
521}