[#3578503]The variable set_wallpaper should be freed after usage.
[lxde/pcmanfm.git] / src / pcmanfm.c
1 /*
2 * pcmanfm.c
3 *
4 * Copyright 2009 - 2010 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 * Copyright 2012 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <gtk/gtk.h>
28 #include <gdk/gdkx.h>
29 #include <stdio.h>
30 #include <glib/gi18n.h>
31
32 #include <stdlib.h>
33 #include <string.h>
34 /* socket is used to keep single instance */
35 #include <sys/types.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 #include "single-inst.h"
47
48 static int signal_pipe[2] = {-1, -1};
49 static gboolean daemon_mode = FALSE;
50 static guint save_config_idle = 0;
51
52 static char** files_to_open = NULL;
53 static int n_files_to_open = 0;
54 static char* profile = 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 one_screen = FALSE;
60 /* static gboolean new_tab = FALSE; */
61 static gint show_pref = -1;
62 static gboolean desktop_pref = FALSE;
63 static char* set_wallpaper = NULL;
64 static char* wallpaper_mode = NULL;
65 static gboolean new_win = FALSE;
66 static gboolean find_files = FALSE;
67 static char* ipc_cwd = NULL;
68 static char* window_role = NULL;
69
70 static int n_pcmanfm_ref = 0;
71
72 static GOptionEntry opt_entries[] =
73 {
74 /* options only acceptable by first pcmanfm instance. These options are not passed through IPC */
75 { "profile", 'p', 0, G_OPTION_ARG_STRING, &profile, N_("Name of configuration profile"), N_("PROFILE") },
76 { "daemon-mode", 'd', 0, G_OPTION_ARG_NONE, &daemon_mode, N_("Run PCManFM as a daemon"), NULL },
77 { "no-desktop", '\0', 0, G_OPTION_ARG_NONE, &no_desktop, N_("No function. Just to be compatible with nautilus"), NULL },
78
79 /* options that are acceptable for every instance of pcmanfm and will be passed through IPC. */
80 { "desktop", '\0', 0, G_OPTION_ARG_NONE, &show_desktop, N_("Launch desktop manager"), NULL },
81 { "desktop-off", '\0', 0, G_OPTION_ARG_NONE, &desktop_off, N_("Turn off desktop manager if it's running"), NULL },
82 { "desktop-pref", '\0', 0, G_OPTION_ARG_NONE, &desktop_pref, N_("Open desktop preference dialog"), NULL },
83 { "one-screen", '\0', 0, G_OPTION_ARG_NONE, &one_screen, N_("Use --desktop option only for one screen"), NULL },
84 { "set-wallpaper", 'w', 0, G_OPTION_ARG_FILENAME, &set_wallpaper, N_("Set desktop wallpaper from image FILE"), N_("FILE") },
85 /* don't translate list of modes in description, please */
86 { "wallpaper-mode", '\0', 0, G_OPTION_ARG_STRING, &wallpaper_mode, N_("Set mode of desktop wallpaper. MODE=(color|stretch|fit|center|tile)"), N_("MODE") },
87 { "show-pref", '\0', 0, G_OPTION_ARG_INT, &show_pref, N_("Open Preferences dialog on the page N"), N_("N") },
88 { "new-win", 'n', 0, G_OPTION_ARG_NONE, &new_win, N_("Open new window"), NULL },
89 /* { "find-files", 'f', 0, G_OPTION_ARG_NONE, &find_files, N_("Open Find Files utility"), NULL }, */
90 { "role", '\0', 0, G_OPTION_ARG_STRING, &window_role, N_("Window role for usage by window manager"), N_("ROLE") },
91 {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_open, NULL, N_("[FILE1, FILE2,...]")},
92 { NULL }
93 };
94
95 static const char* valid_wallpaper_modes[] = {"color", "stretch", "fit", "center", "tile"};
96
97 static gboolean pcmanfm_run(gint screen_num);
98
99 /* it's not safe to call gtk+ functions in unix signal handler
100 * since the process is interrupted here and the state of gtk+ is unpredictable. */
101 static void unix_signal_handler(int sig_num)
102 {
103 /* postpond the signal handling by using a pipe */
104 if (write(signal_pipe[1], &sig_num, sizeof(sig_num)) != sizeof(sig_num)) {
105 g_critical("cannot bounce the signal, stop");
106 exit(2);
107 }
108 }
109
110 static gboolean on_unix_signal(GIOChannel* ch, GIOCondition cond, gpointer user_data)
111 {
112 int sig_num;
113 GIOStatus status;
114 gsize got;
115
116 while(1)
117 {
118 status = g_io_channel_read_chars(ch, (gchar*)&sig_num, sizeof(sig_num),
119 &got, NULL);
120 if(status == G_IO_STATUS_AGAIN) /* we read all the pipe */
121 {
122 g_debug("got G_IO_STATUS_AGAIN");
123 return TRUE;
124 }
125 if(status != G_IO_STATUS_NORMAL || got != sizeof(sig_num)) /* broken pipe */
126 {
127 g_debug("signal pipe is broken");
128 gtk_main_quit();
129 return FALSE;
130 }
131 g_debug("got signal %d from pipe", sig_num);
132 switch(sig_num)
133 {
134 case SIGTERM:
135 default:
136 gtk_main_quit();
137 return FALSE;
138 }
139 }
140 return TRUE;
141 }
142
143 static void single_inst_cb(const char* cwd, int screen_num)
144 {
145 g_free(ipc_cwd);
146 ipc_cwd = g_strdup(cwd);
147
148 if(files_to_open)
149 {
150 int i;
151 n_files_to_open = g_strv_length(files_to_open);
152 /* canonicalize filename if needed. */
153 for(i = 0; i < n_files_to_open; ++i)
154 {
155 char* file = files_to_open[i];
156 char* scheme = g_uri_parse_scheme(file);
157 g_debug("file: %s", file);
158 if(scheme) /* a valid URI */
159 {
160 g_free(scheme);
161 }
162 else /* a file path */
163 {
164 files_to_open[i] = fm_canonicalize_filename(file, cwd);
165 g_free(file);
166 }
167 }
168 }
169 pcmanfm_run(screen_num);
170 window_role = NULL; /* reset it for clients callbacks */
171 }
172
173 int main(int argc, char** argv)
174 {
175 FmConfig* config;
176 GError* err = NULL;
177 SingleInstData inst;
178
179 #ifdef ENABLE_NLS
180 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
181 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
182 textdomain ( GETTEXT_PACKAGE );
183 #endif
184
185 /* initialize GTK+ and parse the command line arguments */
186 if(G_UNLIKELY(!gtk_init_with_args(&argc, &argv, "", opt_entries, GETTEXT_PACKAGE, &err)))
187 {
188 g_printf("%s\n", err->message);
189 g_error_free(err);
190 return 1;
191 }
192
193 /* ensure that there is only one instance of pcmanfm. */
194 inst.prog_name = "pcmanfm";
195 inst.cb = single_inst_cb;
196 inst.opt_entries = opt_entries + 3;
197 inst.screen_num = gdk_x11_get_default_screen();
198 switch(single_inst_init(&inst))
199 {
200 case SINGLE_INST_CLIENT: /* we're not the first instance. */
201 single_inst_finalize(&inst);
202 gdk_notify_startup_complete();
203 return 0;
204 case SINGLE_INST_ERROR: /* error happened. */
205 single_inst_finalize(&inst);
206 return 1;
207 case SINGLE_INST_SERVER: ; /* FIXME */
208 }
209
210 if(pipe(signal_pipe) == 0)
211 {
212 GIOChannel* ch = g_io_channel_unix_new(signal_pipe[0]);
213 g_io_add_watch(ch, G_IO_IN|G_IO_PRI, (GIOFunc)on_unix_signal, NULL);
214 g_io_channel_unref(ch);
215
216 /* intercept signals */
217 // signal( SIGPIPE, SIG_IGN );
218 signal( SIGHUP, unix_signal_handler );
219 signal( SIGTERM, unix_signal_handler );
220 signal( SIGINT, unix_signal_handler );
221 }
222
223 config = fm_app_config_new(); /* this automatically load libfm config file. */
224
225 /* load pcmanfm-specific config file */
226 fm_app_config_load_from_profile(FM_APP_CONFIG(config), profile);
227
228 fm_gtk_init(config);
229 /* the main part */
230 if(pcmanfm_run(gdk_screen_get_number(gdk_screen_get_default())))
231 {
232 window_role = NULL; /* reset it for clients callbacks */
233 fm_volume_manager_init();
234 gtk_main();
235 /* g_debug("main loop ended"); */
236 if(desktop_running)
237 fm_desktop_manager_finalize();
238
239 pcmanfm_save_config(TRUE);
240 if(save_config_idle)
241 {
242 g_source_remove(save_config_idle);
243 save_config_idle = 0;
244 }
245 fm_volume_manager_finalize();
246 }
247
248 single_inst_finalize(&inst);
249 fm_gtk_finalize();
250
251 g_object_unref(config);
252 return 0;
253 }
254
255 gboolean pcmanfm_run(gint screen_num)
256 {
257 FmMainWin *win;
258 gboolean ret = TRUE;
259
260 if(!files_to_open)
261 {
262 /* Launch desktop manager */
263 if(show_desktop)
264 {
265 if(!desktop_running)
266 {
267 fm_desktop_manager_init(one_screen ? screen_num : -1);
268 desktop_running = TRUE;
269 one_screen = FALSE;
270 }
271 show_desktop = FALSE;
272 return TRUE;
273 }
274 else if(desktop_off)
275 {
276 if(desktop_running)
277 {
278 desktop_running = FALSE;
279 fm_desktop_manager_finalize();
280 }
281 desktop_off = FALSE;
282 return FALSE;
283 }
284 else if(show_pref > 0)
285 {
286 /* FIXME: pass screen number from client */
287 fm_edit_preference(GTK_WINDOW(fm_desktop_get(0, 0)), show_pref - 1);
288 show_pref = -1;
289 return TRUE;
290 }
291 else if(desktop_pref)
292 {
293 /* FIXME: pass screen number from client */
294 fm_desktop_preference(NULL, GTK_WINDOW(fm_desktop_get(0, 0)));
295 desktop_pref = FALSE;
296 return TRUE;
297 }
298 else
299 {
300 gboolean need_to_exit = (wallpaper_mode || set_wallpaper);
301 gboolean wallpaper_changed = FALSE;
302 if(set_wallpaper) /* a new wallpaper is assigned */
303 {
304 /* g_debug("\'%s\'", set_wallpaper); */
305 /* Make sure this is a support image file. */
306 if(gdk_pixbuf_get_file_info(set_wallpaper, NULL, NULL))
307 {
308 if(app_config->wallpaper)
309 g_free(app_config->wallpaper);
310 app_config->wallpaper = set_wallpaper;
311 if(! wallpaper_mode) /* if wallpaper mode is not specified */
312 {
313 /* do not use solid color mode; otherwise wallpaper won't be shown. */
314 if(app_config->wallpaper_mode == FM_WP_COLOR)
315 app_config->wallpaper_mode = FM_WP_FIT;
316 }
317 wallpaper_changed = TRUE;
318 }
319 else
320 g_free(set_wallpaper);
321 set_wallpaper = NULL;
322 }
323
324 if(wallpaper_mode)
325 {
326 guint i = 0;
327 for(i = 0; i < G_N_ELEMENTS(valid_wallpaper_modes); ++i)
328 {
329 if(strcmp(valid_wallpaper_modes[i], wallpaper_mode) == 0)
330 {
331 if(i != app_config->wallpaper_mode)
332 {
333 app_config->wallpaper_mode = i;
334 wallpaper_changed = TRUE;
335 }
336 break;
337 }
338 }
339 g_free(wallpaper_mode);
340 wallpaper_mode = NULL;
341 }
342
343 if(wallpaper_changed)
344 {
345 fm_config_emit_changed(FM_CONFIG(app_config), "wallpaper");
346 fm_app_config_save_profile(app_config, profile);
347 }
348
349 if(need_to_exit)
350 return FALSE;
351 }
352 }
353
354 if(G_UNLIKELY(find_files))
355 {
356 /* FIXME: find files */
357 }
358 else
359 {
360 if(files_to_open)
361 {
362 char** filename;
363 FmPath* cwd = NULL;
364 GList* paths = NULL;
365 for(filename=files_to_open; *filename; ++filename)
366 {
367 FmPath* path;
368 if( **filename == '/') /* absolute path */
369 path = fm_path_new_for_path(*filename);
370 else if(strstr(*filename, ":/") ) /* URI */
371 path = fm_path_new_for_uri(*filename);
372 else if( strcmp(*filename, "~") == 0 ) /* special case for home dir */
373 {
374 path = fm_path_get_home();
375 win = fm_main_win_add_win(NULL, path);
376 if(new_win && window_role)
377 gtk_window_set_role(GTK_WINDOW(win), window_role);
378 continue;
379 }
380 else /* basename */
381 {
382 if(G_UNLIKELY(!cwd))
383 {
384 char* cwd_str = g_get_current_dir();
385 cwd = fm_path_new_for_str(cwd_str);
386 g_free(cwd_str);
387 }
388 path = fm_path_new_relative(cwd, *filename);
389 }
390 paths = g_list_append(paths, path);
391 }
392 if(cwd)
393 fm_path_unref(cwd);
394 fm_launch_paths_simple(NULL, NULL, paths, pcmanfm_open_folder, NULL);
395 g_list_foreach(paths, (GFunc)fm_path_unref, NULL);
396 g_list_free(paths);
397 ret = (n_pcmanfm_ref >= 1); /* if there is opened window, return true to run the main loop. */
398
399 g_strfreev(files_to_open);
400 files_to_open = NULL;
401 }
402 else
403 {
404 static gboolean first_run = TRUE;
405 if(first_run && daemon_mode)
406 {
407 /* If the function is called the first time and we're in daemon mode,
408 * don't open any folder.
409 * Checking if pcmanfm_run() is called the first time is needed to fix
410 * #3397444 - pcmanfm dont show window in daemon mode if i call 'pcmanfm' */
411 }
412 else
413 {
414 /* If we're not in daemon mode, or pcmanfm_run() is called because another
415 * instance send signal to us, open cwd by default. */
416 FmPath* path;
417 char* cwd = ipc_cwd ? ipc_cwd : g_get_current_dir();
418 path = fm_path_new_for_path(cwd);
419 win = fm_main_win_add_win(NULL, path);
420 if(new_win && window_role)
421 gtk_window_set_role(GTK_WINDOW(win), window_role);
422 fm_path_unref(path);
423 g_free(cwd);
424 ipc_cwd = NULL;
425 }
426 first_run = FALSE;
427 }
428 }
429 return ret;
430 }
431
432 /* After opening any window/dialog/tool, this should be called. */
433 void pcmanfm_ref()
434 {
435 ++n_pcmanfm_ref;
436 /* g_debug("ref: %d", n_pcmanfm_ref); */
437 }
438
439 /* After closing any window/dialog/tool, this should be called.
440 * If the last window is closed and we are not a deamon, pcmanfm will quit.
441 */
442 void pcmanfm_unref()
443 {
444 --n_pcmanfm_ref;
445 /* g_debug("unref: %d, daemon_mode=%d, desktop_running=%d", n_pcmanfm_ref, daemon_mode, desktop_running); */
446 if( 0 == n_pcmanfm_ref && !daemon_mode && !desktop_running )
447 gtk_main_quit();
448 }
449
450 static void move_window_to_desktop(FmMainWin* win, FmDesktop* desktop)
451 {
452 GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(desktop));
453 Atom atom;
454 char* atom_name = "_NET_WM_DESKTOP";
455 XClientMessageEvent xev;
456
457 gtk_window_set_screen(GTK_WINDOW(win), screen);
458 if(!XInternAtoms(gdk_x11_get_default_xdisplay(), &atom_name, 1, False, &atom))
459 {
460 /* g_debug("cannot get Atom for _NET_WM_DESKTOP"); */
461 return;
462 }
463 xev.type = ClientMessage;
464 xev.window = GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(win)));
465 xev.message_type = atom;
466 xev.format = 32;
467 xev.data.l[0] = desktop->cur_desktop;
468 xev.data.l[1] = 0;
469 xev.data.l[2] = 0;
470 xev.data.l[3] = 0;
471 xev.data.l[4] = 0;
472 /* g_debug("moving window to current desktop"); */
473 XSendEvent(gdk_x11_get_default_xdisplay(), GDK_ROOT_WINDOW(), False,
474 (SubstructureNotifyMask | SubstructureRedirectMask),
475 (XEvent *) &xev);
476 }
477
478 gboolean pcmanfm_open_folder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err)
479 {
480 GList* l = folder_infos;
481 if(new_win)
482 {
483 FmMainWin *win = fm_main_win_add_win(NULL,
484 fm_file_info_get_path((FmFileInfo*)l->data));
485 if(window_role)
486 gtk_window_set_role(GTK_WINDOW(win), window_role);
487 new_win = FALSE;
488 l = l->next;
489 }
490 for(; l; l=l->next)
491 {
492 FmFileInfo* fi = (FmFileInfo*)l->data;
493 fm_main_win_open_in_last_active(fm_file_info_get_path(fi));
494 }
495 if(user_data && FM_IS_DESKTOP(user_data))
496 move_window_to_desktop(fm_main_win_get_last_active(), user_data);
497 return TRUE;
498 }
499
500 static gboolean on_save_config_idle(gpointer user_data)
501 {
502 pcmanfm_save_config(TRUE);
503 save_config_idle = 0;
504 return FALSE;
505 }
506
507 void pcmanfm_save_config(gboolean immediate)
508 {
509 if(immediate)
510 {
511 fm_config_save(fm_config, NULL);
512 fm_app_config_save_profile(app_config, profile);
513 }
514 else
515 {
516 /* install an idle handler to save the config file. */
517 if( 0 == save_config_idle)
518 save_config_idle = g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc)on_save_config_idle, NULL, NULL);
519 }
520 }
521
522 void pcmanfm_open_folder_in_terminal(GtkWindow* parent, FmPath* dir)
523 {
524 GAppInfo* app;
525 char** argv;
526 int argc;
527 if(!fm_config->terminal)
528 {
529 fm_show_error(parent, NULL, _("Terminal emulator is not set."));
530 fm_edit_preference(parent, PREF_ADVANCED);
531 return;
532 }
533 if(!g_shell_parse_argv(fm_config->terminal, &argc, &argv, NULL))
534 return;
535 app = g_app_info_create_from_commandline(argv[0], NULL, 0, NULL);
536 g_strfreev(argv);
537 if(app)
538 {
539 GError* err = NULL;
540 GdkAppLaunchContext* ctx = gdk_app_launch_context_new();
541 char* cwd_str;
542 char* old_cwd = g_get_current_dir();
543
544 if(fm_path_is_native(dir))
545 cwd_str = fm_path_to_str(dir);
546 else
547 {
548 GFile* gf = fm_path_to_gfile(dir);
549 cwd_str = g_file_get_path(gf);
550 g_object_unref(gf);
551 }
552 gdk_app_launch_context_set_screen(ctx, parent ? gtk_widget_get_screen(GTK_WIDGET(parent)) : gdk_screen_get_default());
553 gdk_app_launch_context_set_timestamp(ctx, gtk_get_current_event_time());
554 g_chdir(cwd_str); /* FIXME: currently we don't have better way for this. maybe a wrapper script? */
555 g_free(cwd_str);
556
557 if(!g_app_info_launch(app, NULL, G_APP_LAUNCH_CONTEXT(ctx), &err))
558 {
559 fm_show_error(parent, NULL, err->message);
560 g_error_free(err);
561 }
562 g_object_unref(ctx);
563 g_object_unref(app);
564
565 /* switch back to old cwd and fix #3114626 - PCManFM 0.9.9 Umount partitions problem */
566 g_chdir(old_cwd); /* This is really dirty, but we don't have better solution now. */
567 g_free(old_cwd);
568 }
569 }
570
571 char* pcmanfm_get_profile_dir(gboolean create)
572 {
573 char* dir = g_build_filename(g_get_user_config_dir(), "pcmanfm", profile ? profile : "default", NULL);
574 if(create)
575 g_mkdir_with_parents(dir, 0700);
576 return dir;
577 }