* Use new fm_launch_files_simple API.
[lxde/pcmanfm.git] / src / pcmanfm.c
1 /*
2 * pcmanfm.c
3 *
4 * Copyright 2009 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 <fm-gtk.h>
40 #include "app-config.h"
41 #include "main-win.h"
42 #include "desktop.h"
43 #include "pref.h"
44 #include "pcmanfm.h"
45
46 static int sock;
47 GIOChannel* io_channel = NULL;
48
49 gboolean daemon_mode = FALSE;
50
51 static char** files_to_open = NULL;
52 static char* profile = NULL;
53 static char* config_name = NULL;
54 static gboolean no_desktop = FALSE;
55 static gboolean show_desktop = FALSE;
56 static gboolean desktop_off = FALSE;
57 static gboolean desktop_running = FALSE;
58 static gboolean new_tab = FALSE;
59 static int show_pref = 0;
60 static gboolean desktop_pref = FALSE;
61 static char* set_wallpaper = NULL;
62 static gboolean find_files = FALSE;
63
64 static int n_pcmanfm_ref = 0;
65
66 static GOptionEntry opt_entries[] =
67 {
68 { "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 },
69 { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile, N_("Name of config profile"), "<profile name>" },
70 { "desktop", '\0', 0, G_OPTION_ARG_NONE, &show_desktop, N_("Launch desktop manager"), NULL },
71 { "desktop-off", '\0', 0, G_OPTION_ARG_NONE, &desktop_off, N_("Turn off desktop manager if it's running"), NULL },
72 { "daemon-mode", 'd', 0, G_OPTION_ARG_NONE, &daemon_mode, N_("Run PCManFM as a daemon"), NULL },
73 { "desktop-pref", '\0', 0, G_OPTION_ARG_NONE, &desktop_pref, N_("Open desktop preference dialog"), NULL },
74 { "set-wallpaper", 'w', 0, G_OPTION_ARG_FILENAME, &set_wallpaper, N_("Set desktop wallpaper"), N_("<image file>") },
75 { "show-pref", '\0', 0, G_OPTION_ARG_INT, &show_pref, N_("Open preference dialog. 'n' is the number of page you want to show (1, 2, 3...)."), "n" },
76 { "find-files", 'f', 0, G_OPTION_ARG_NONE, &find_files, N_("Open Find Files utility"), NULL },
77 { "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop, N_("No function. Just to be compatible with nautilus"), NULL },
78 {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_open, NULL, N_("[FILE1, FILE2,...]")},
79 { NULL }
80 };
81
82 static gboolean single_instance_check();
83 static void single_instance_finalize();
84 static void get_socket_name(char* buf, int len);
85 static gboolean pcmanfm_run();
86 static gboolean on_socket_event(GIOChannel* ioc, GIOCondition cond, gpointer data);
87
88 int main(int argc, char** argv)
89 {
90 FmConfig* config;
91 GError* err = NULL;
92
93 #ifdef ENABLE_NLS
94 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
95 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
96 textdomain ( GETTEXT_PACKAGE );
97 #endif
98
99 /* initialize GTK+ and parse the command line arguments */
100 if(G_UNLIKELY(!gtk_init_with_args(&argc, &argv, "", opt_entries, GETTEXT_PACKAGE, &err)))
101 {
102 g_printf("%s\n", err->message);
103 g_error_free(err);
104 return 1;
105 }
106
107 /* ensure that there is only one instance of pcmanfm.
108 if there is an existing instance, command line arguments
109 will be passed to the existing instance, and exit() will be called here. */
110 single_instance_check();
111
112 /* intercept signals */
113 signal( SIGPIPE, SIG_IGN );
114 /* signal( SIGHUP, gtk_main_quit ); */
115 signal( SIGINT, gtk_main_quit );
116 signal( SIGTERM, gtk_main_quit );
117
118 config = fm_app_config_new(); /* this automatically load libfm config file. */
119 /* load pcmanfm-specific config file */
120 if(profile)
121 config_name = g_strconcat("pcmanfm/", profile, ".conf", NULL);
122 fm_app_config_load_from_file(config, config_name);
123
124 fm_gtk_init(config);
125
126 /* the main part */
127 if(pcmanfm_run())
128 {
129 gtk_main();
130 if(desktop_running)
131 fm_desktop_manager_finalize();
132 fm_config_save(config, NULL); /* save libfm config */
133 fm_app_config_save((FmAppConfig*)config, config_name); /* save pcmanfm config */
134 }
135 single_instance_finalize();
136
137 fm_gtk_finalize();
138 g_object_unref(config);
139 return 0;
140 }
141
142
143 inline static GString* args_to_keyfile()
144 {
145 int i;
146 GString* buf = g_string_sized_new(1024);
147 g_string_assign(buf, "[a]\n");
148 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
149 {
150 GOptionEntry* ent = &opt_entries[i];
151 if(G_LIKELY(*ent->long_name))
152 g_string_append(buf, ent->long_name);
153 else /* G_OPTION_REMAINING */
154 g_string_append(buf, "@");
155 g_string_append_c(buf, '=');
156 switch(ent->arg)
157 {
158 case G_OPTION_ARG_NONE: /* bool */
159 g_string_append_c(buf, *(gboolean*)ent->arg_data ? '1' : '0');
160 break;
161 case G_OPTION_ARG_INT: /* int */
162 g_string_append_printf(buf, "%d", *(gint*)ent->arg_data);
163 break;
164 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
165 case G_OPTION_ARG_STRING_ARRAY:
166 {
167 char** files = *(char***)ent->arg_data;
168 if(files)
169 {
170 for(;*files;++files)
171 {
172 g_string_append(buf, *files);
173 g_string_append_c(buf, ';');
174 }
175 }
176 }
177 break;
178 case G_OPTION_ARG_FILENAME:
179 case G_OPTION_ARG_STRING: /* string */
180 if(*(gchar**)ent->arg_data)
181 g_string_append(buf, *(gchar**)ent->arg_data);
182 break;
183 }
184 g_string_append_c(buf, '\n');
185 }
186 return buf;
187 }
188
189 inline static void keyfile_to_args(GString* buf)
190 {
191 GKeyFile* kf = g_key_file_new();
192 /* g_debug("\n%s", buf->str); */
193 if(g_key_file_load_from_data(kf, buf->str, buf->len, 0, NULL))
194 {
195 int i;
196 char*** pstrs;
197 for(i = 0; i < G_N_ELEMENTS(opt_entries)-1;++i)
198 {
199 GOptionEntry* ent = &opt_entries[i];
200 switch(ent->arg)
201 {
202 case G_OPTION_ARG_NONE: /* bool */
203 *(gboolean*)ent->arg_data = g_key_file_get_boolean(kf, "a", ent->long_name, NULL);
204 break;
205 case G_OPTION_ARG_INT: /* int */
206 *(gint*)ent->arg_data = g_key_file_get_integer(kf, "a", ent->long_name, NULL);
207 break;
208 case G_OPTION_ARG_FILENAME_ARRAY: /* string array */
209 case G_OPTION_ARG_STRING_ARRAY:
210 pstrs = (char**)ent->arg_data;
211 if(*pstrs)
212 g_strfreev(*pstrs);
213 *pstrs = g_key_file_get_string_list(kf, "a", *ent->long_name ? *ent->long_name : "@", NULL, NULL);
214 if(*pstrs)
215 {
216 char** strs = *pstrs;
217 char* str = *strs;
218 if(!str || str[0] == '\0')
219 {
220 g_strfreev(strs);
221 *pstrs = NULL;
222 }
223 }
224 break;
225 case G_OPTION_ARG_FILENAME:
226 case G_OPTION_ARG_STRING: /* string */
227 if(*(char**)ent->arg_data)
228 g_free(*(char**)ent->arg_data);
229 *(char**)ent->arg_data = g_key_file_get_string(kf, "a", ent->long_name, NULL);
230 if(*(char**)ent->arg_data && **(char**)ent->arg_data == '\0')
231 {
232 g_free(*(char**)ent->arg_data);
233 *(char**)ent->arg_data = NULL;
234 }
235 break;
236 }
237 }
238 }
239 g_key_file_free(kf);
240 }
241
242 gboolean on_socket_event( GIOChannel* ioc, GIOCondition cond, gpointer data )
243 {
244 int client, r;
245 socklen_t addr_len = 0;
246 struct sockaddr_un client_addr ={ 0 };
247 static char buf[ 1024 ];
248 GString* args;
249 char** file;
250
251 if ( cond & G_IO_IN )
252 {
253 client = accept( g_io_channel_unix_get_fd( ioc ), (struct sockaddr *)&client_addr, &addr_len );
254 if ( client != -1 )
255 {
256 args = g_string_sized_new(1024);
257 while( (r = read( client, buf, sizeof(buf) )) > 0 )
258 g_string_append_len( args, buf, r);
259 shutdown( client, 2 );
260 close( client );
261 keyfile_to_args(args);
262 g_string_free( args, TRUE );
263 pcmanfm_run();
264 }
265 }
266 return TRUE;
267 }
268
269 void get_socket_name( char* buf, int len )
270 {
271 char* dpy = gdk_get_display();
272 g_snprintf( buf, len, "/tmp/.pcmanfm2-socket%s-%s", dpy, g_get_user_name() );
273 g_free( dpy );
274 }
275
276 gboolean single_instance_check()
277 {
278 struct sockaddr_un addr;
279 int addr_len;
280 int ret;
281 int reuse;
282
283 if((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
284 {
285 ret = 1;
286 goto _exit;
287 }
288
289 /* FIXME: use abstract socket */
290 addr.sun_family = AF_UNIX;
291 get_socket_name(addr.sun_path, sizeof( addr.sun_path ));
292 #ifdef SUN_LEN
293 addr_len = SUN_LEN(&addr);
294 #else
295 addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family );
296 #endif
297
298 /* try to connect to existing instance */
299 if(connect(sock, (struct sockaddr*)&addr, addr_len) == 0)
300 {
301 /* connected successfully */
302 GString* buf = args_to_keyfile();
303 write(sock, buf->str, buf->len);
304 g_string_free(buf, TRUE);
305
306 shutdown( sock, 2 );
307 close( sock );
308 ret = 0;
309 goto _exit;
310 }
311
312 /* There is no existing server, and we are in the first instance. */
313 unlink( addr.sun_path ); /* delete old socket file if it exists. */
314 reuse = 1;
315 ret = setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) );
316 if(bind(sock, (struct sockaddr*)&addr, addr_len) == -1)
317 {
318 ret = 1;
319 goto _exit;
320 }
321
322 io_channel = g_io_channel_unix_new(sock);
323 g_io_channel_set_encoding(io_channel, NULL, NULL);
324 g_io_channel_set_buffered(io_channel, FALSE);
325 g_io_add_watch(io_channel, G_IO_IN,
326 (GIOFunc)on_socket_event, NULL);
327 if(listen(sock, 5) == -1)
328 {
329 ret = 1;
330 goto _exit;
331 }
332 return TRUE;
333
334 _exit:
335
336 gdk_notify_startup_complete();
337 exit( ret );
338 }
339
340 void single_instance_finalize()
341 {
342 char lock_file[256];
343 shutdown(sock, 2);
344 g_io_channel_unref(io_channel);
345 close(sock);
346 get_socket_name(lock_file, sizeof( lock_file ));
347 unlink(lock_file);
348 }
349
350
351 gboolean pcmanfm_run()
352 {
353 gboolean ret = TRUE;
354 char** file;
355 GtkWidget* w;
356
357 if(!files_to_open)
358 {
359 /* Launch desktop manager */
360 if(show_desktop)
361 {
362 if(!desktop_running)
363 {
364 fm_desktop_manager_init();
365 desktop_running = TRUE;
366 }
367 show_desktop = FALSE;
368 return TRUE;
369 }
370 else if(desktop_off)
371 {
372 if(desktop_running)
373 {
374 desktop_running = FALSE;
375 fm_desktop_manager_finalize();
376 }
377 desktop_off = FALSE;
378 return FALSE;
379 }
380 else if(show_pref > 0)
381 {
382 fm_edit_preference(NULL, show_pref - 1);
383 show_pref = 0;
384 return TRUE;
385 }
386 else if(desktop_pref)
387 {
388 fm_desktop_preference();
389 desktop_pref = FALSE;
390 return TRUE;
391 }
392 else if(set_wallpaper)
393 {
394 /* g_debug("\'%s\'", set_wallpaper); */
395 /* Make sure this is a support image file. */
396 if(gdk_pixbuf_get_file_info(set_wallpaper, NULL, NULL))
397 {
398 if(app_config->wallpaper)
399 g_free(app_config->wallpaper);
400 app_config->wallpaper = set_wallpaper;
401 set_wallpaper = NULL;
402 if(app_config->wallpaper_mode == FM_WP_COLOR)
403 app_config->wallpaper_mode = FM_WP_FIT;
404 fm_config_emit_changed(app_config, "wallpaper");
405 fm_app_config_save(app_config, NULL);
406 }
407 return FALSE;
408 }
409 }
410
411 if(G_UNLIKELY(find_files))
412 {
413 /* FIXME: find files */
414 }
415 else
416 {
417 if(files_to_open)
418 {
419 char** filename;
420 FmJob* job = fm_file_info_job_new(NULL);
421 for(filename=files_to_open; *filename; ++filename)
422 {
423 FmPath* path = fm_path_new(*filename);
424 fm_file_info_job_add(job, path);
425 fm_path_unref(path);
426 }
427 fm_job_run_sync(job);
428 fm_launch_files_simple(NULL, NULL, FM_FILE_INFO_JOB(job)->file_infos, pcmanfm_open_folder, NULL);
429 g_object_unref(job);
430 }
431 else
432 {
433 FmPath* path;
434 w = fm_main_win_new();
435 gtk_window_set_default_size(w, app_config->win_width, app_config->win_height);
436 gtk_widget_show(w);
437 path = fm_path_get_home();
438 fm_main_win_chdir(w, path);
439 }
440 }
441 return ret;
442 }
443
444 /* After opening any window/dialog/tool, this should be called. */
445 void pcmanfm_ref()
446 {
447 ++n_pcmanfm_ref;
448 /* g_debug("ref: %d", n_pcmanfm_ref); */
449 }
450
451 /* After closing any window/dialog/tool, this should be called.
452 * If the last window is closed and we are not a deamon, pcmanfm will quit.
453 */
454 void pcmanfm_unref()
455 {
456 --n_pcmanfm_ref;
457 /* g_debug("unref: %d, daemon_mode=%d, desktop_running=%d", n_pcmanfm_ref, daemon_mode, desktop_running); */
458 if( 0 == n_pcmanfm_ref && !daemon_mode && !desktop_running )
459 gtk_main_quit();
460 }
461
462 gboolean pcmanfm_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
463 {
464 FmMainWin* win = FM_MAIN_WIN(user_data);
465 GList* l = folder_infos;
466 for(; l; l=l->next)
467 {
468 FmFileInfo* fi = (FmFileInfo*)l->data;
469 fm_main_win_open_in_last_active(fi->path);
470 }
471 return TRUE;
472 }