5382693e3df38b56c39a62157d491e81f7a3900d
[geeqie.git] / src / remote.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
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 along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "remote.h"
23
24 #include <config.h>
25
26 #include "cache-maint.h"
27 #include "collect.h"
28 #include "collect-io.h"
29 #include "compat.h"
30 #include "debug.h"
31 #include "exif.h"
32 #include "filedata.h"
33 #include "filefilter.h"
34 #include "image.h"
35 #include "img-view.h"
36 #include "intl.h"
37 #include "layout-image.h"
38 #include "layout-util.h"
39 #include "main.h"
40 #include "main-defines.h"
41 #include "misc.h"
42 #include "pixbuf-renderer.h"
43 #include "slideshow.h"
44 #include "ui-fileops.h"
45 #include "ui-misc.h"
46 #include "utilops.h"
47 #include "rcfile.h"
48 #include "view-file.h"
49
50 #include <csignal>
51 #include <sys/socket.h>
52 #include <sys/un.h>
53
54 #include "glua.h"
55
56 enum {
57         SERVER_MAX_CLIENTS = 8
58 };
59
60 enum {
61         REMOTE_SERVER_BACKLOG = 4
62 };
63
64
65 #ifndef UNIX_PATH_MAX
66 #define UNIX_PATH_MAX 108
67 #endif
68
69
70 static RemoteConnection *remote_client_open(const gchar *path);
71 static gint remote_client_send(RemoteConnection *rc, const gchar *text);
72 static void gr_raise(const gchar *text, GIOChannel *channel, gpointer data);
73
74 static LayoutWindow *lw_id = nullptr; /* points to the window set by the --id option */
75
76 struct RemoteClient {
77         gint fd;
78         guint channel_id; /* event source id */
79         RemoteConnection *rc;
80 };
81
82 struct RemoteData {
83         CollectionData *command_collection;
84         GList *file_list;
85         gboolean single_dir;
86 };
87
88 /* To enable file names containing newlines to be processed correctly,
89  * the --print0 remote option sets returned data to be terminated with a null
90  * character rather a newline
91  */
92 static gboolean print0 = FALSE;
93
94 /* Remote commands from main.cc are prepended with the current dir the remote
95  * command was made from. Some remote commands require this. The
96  * value is stored here
97  */
98 static gchar *pwd = nullptr;
99
100 /**
101  * @brief Ensures file path is absolute.
102  * @param[in] filename Filepath, absolute or relative to calling directory
103  * @returns absolute path
104  *
105  * If first character of input filepath is not the directory
106  * separator, assume it as a relative path and prepend
107  * the directory the remote command was initiated from
108  *
109  * Return value must be freed with g_free()
110  */
111 static gchar *set_pwd(gchar *filename)
112 {
113         gchar *temp;
114
115         if (strncmp(filename, G_DIR_SEPARATOR_S, 1) != 0)
116                 {
117                 temp = g_build_filename(pwd, filename, NULL);
118                 }
119         else
120                 {
121                 temp = g_strdup(filename);
122                 }
123
124         return temp;
125 }
126
127 static gboolean remote_server_client_cb(GIOChannel *source, GIOCondition condition, gpointer data)
128 {
129         auto client = static_cast<RemoteClient *>(data);
130         RemoteConnection *rc;
131         GIOStatus status = G_IO_STATUS_NORMAL;
132
133         lw_id = nullptr;
134         rc = client->rc;
135
136         if (condition & G_IO_IN)
137                 {
138                 gchar *buffer = nullptr;
139                 GError *error = nullptr;
140                 gsize termpos;
141                 /** @FIXME it should be possible to terminate the command with a null character */
142                 g_io_channel_set_line_term(source, "<gq_end_of_command>", -1);
143                 while ((status = g_io_channel_read_line(source, &buffer, nullptr, &termpos, &error)) == G_IO_STATUS_NORMAL)
144                         {
145                         if (buffer)
146                                 {
147                                 buffer[termpos] = '\0';
148
149                                 if (strlen(buffer) > 0)
150                                         {
151                                         if (rc->read_func) rc->read_func(rc, buffer, source, rc->read_data);
152                                         g_io_channel_write_chars(source, "<gq_end_of_command>", -1, nullptr, nullptr); /* empty line finishes the command */
153                                         g_io_channel_flush(source, nullptr);
154                                         }
155                                 g_free(buffer);
156
157                                 buffer = nullptr;
158                                 }
159                         }
160
161                 if (error)
162                         {
163                         log_printf("error reading socket: %s\n", error->message);
164                         g_error_free(error);
165                         }
166                 }
167
168         if (condition & G_IO_HUP || status == G_IO_STATUS_EOF || status == G_IO_STATUS_ERROR)
169                 {
170                 rc->clients = g_list_remove(rc->clients, client);
171
172                 DEBUG_1("HUP detected, closing client.");
173                 DEBUG_1("client count %d", g_list_length(rc->clients));
174
175                 g_source_remove(client->channel_id);
176                 close(client->fd);
177                 g_free(client);
178                 }
179
180         return TRUE;
181 }
182
183 static void remote_server_client_add(RemoteConnection *rc, gint fd)
184 {
185         RemoteClient *client;
186         GIOChannel *channel;
187
188         if (g_list_length(rc->clients) > SERVER_MAX_CLIENTS)
189                 {
190                 log_printf("maximum remote clients of %d exceeded, closing connection\n", SERVER_MAX_CLIENTS);
191                 close(fd);
192                 return;
193                 }
194
195         client = g_new0(RemoteClient, 1);
196         client->rc = rc;
197         client->fd = fd;
198
199         channel = g_io_channel_unix_new(fd);
200         client->channel_id = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, static_cast<GIOCondition>(G_IO_IN | G_IO_HUP),
201                                                  remote_server_client_cb, client, nullptr);
202         g_io_channel_unref(channel);
203
204         rc->clients = g_list_append(rc->clients, client);
205         DEBUG_1("client count %d", g_list_length(rc->clients));
206 }
207
208 static void remote_server_clients_close(RemoteConnection *rc)
209 {
210         while (rc->clients)
211                 {
212                 auto client = static_cast<RemoteClient *>(rc->clients->data);
213
214                 rc->clients = g_list_remove(rc->clients, client);
215
216                 g_source_remove(client->channel_id);
217                 close(client->fd);
218                 g_free(client);
219                 }
220 }
221
222 static gboolean remote_server_read_cb(GIOChannel *, GIOCondition, gpointer data)
223 {
224         auto rc = static_cast<RemoteConnection *>(data);
225         gint fd;
226         guint alen;
227
228         fd = accept(rc->fd, nullptr, &alen);
229         if (fd == -1)
230                 {
231                 log_printf("error accepting socket: %s\n", strerror(errno));
232                 return TRUE;
233                 }
234
235         remote_server_client_add(rc, fd);
236
237         return TRUE;
238 }
239
240 gboolean remote_server_exists(const gchar *path)
241 {
242         RemoteConnection *rc;
243
244         /* verify server up */
245         rc = remote_client_open(path);
246         remote_close(rc);
247
248         if (rc) return TRUE;
249
250         /* unable to connect, remove socket file to free up address */
251         unlink(path);
252         return FALSE;
253 }
254
255 static RemoteConnection *remote_server_open(const gchar *path)
256 {
257         RemoteConnection *rc;
258         struct sockaddr_un addr;
259         gint fd;
260         GIOChannel *channel;
261
262         if (strlen(path) >= sizeof(addr.sun_path))
263                 {
264                 log_printf("Address is too long: %s\n", path);
265                 return nullptr;
266                 }
267
268         if (remote_server_exists(path))
269                 {
270                 log_printf("Address already in use: %s\n", path);
271                 return nullptr;
272                 }
273
274         fd = socket(PF_UNIX, SOCK_STREAM, 0);
275         if (fd == -1) return nullptr;
276
277         addr.sun_family = AF_UNIX;
278 #pragma GCC diagnostic push
279 #pragma GCC diagnostic ignored "-Wstringop-truncation"
280         strncpy(addr.sun_path, path, sizeof(addr.sun_path));
281 #pragma GCC diagnostic pop
282         if (bind(fd, reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)) == -1 ||
283             listen(fd, REMOTE_SERVER_BACKLOG) == -1)
284                 {
285                 log_printf("error subscribing to socket: %s\n", strerror(errno));
286                 close(fd);
287                 return nullptr;
288                 }
289
290         rc = g_new0(RemoteConnection, 1);
291
292         rc->server = TRUE;
293         rc->fd = fd;
294         rc->path = g_strdup(path);
295
296         channel = g_io_channel_unix_new(rc->fd);
297         g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, nullptr);
298
299         rc->channel_id = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN,
300                                              remote_server_read_cb, rc, nullptr);
301         g_io_channel_unref(channel);
302
303         return rc;
304 }
305
306 static void remote_server_subscribe(RemoteConnection *rc, RemoteConnection::ReadFunc *func, gpointer data)
307 {
308         if (!rc || !rc->server) return;
309
310         rc->read_func = func;
311         rc->read_data = data;
312 }
313
314
315 static RemoteConnection *remote_client_open(const gchar *path)
316 {
317         RemoteConnection *rc;
318         struct stat st;
319         struct sockaddr_un addr;
320         gint fd;
321
322         if (strlen(path) >= sizeof(addr.sun_path)) return nullptr;
323
324         if (stat(path, &st) != 0 || !S_ISSOCK(st.st_mode)) return nullptr;
325
326         fd = socket(PF_UNIX, SOCK_STREAM, 0);
327         if (fd == -1) return nullptr;
328
329         addr.sun_family = AF_UNIX;
330 #pragma GCC diagnostic push
331 #pragma GCC diagnostic ignored "-Wstringop-truncation"
332         strncpy(addr.sun_path, path, sizeof(addr.sun_path));
333 #pragma GCC diagnostic pop
334         if (connect(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
335                 {
336                 DEBUG_1("error connecting to socket: %s", strerror(errno));
337                 close(fd);
338                 return nullptr;
339                 }
340
341         rc = g_new0(RemoteConnection, 1);
342         rc->server = FALSE;
343         rc->fd = fd;
344         rc->path = g_strdup(path);
345
346         return rc;
347 }
348
349 static sig_atomic_t sigpipe_occurred = FALSE;
350
351 static void sighandler_sigpipe(gint)
352 {
353         sigpipe_occurred = TRUE;
354 }
355
356 static gboolean remote_client_send(RemoteConnection *rc, const gchar *text)
357 {
358         struct sigaction new_action;
359         struct sigaction old_action;
360         gboolean ret = FALSE;
361         GError *error = nullptr;
362         GIOChannel *channel;
363
364         if (!rc || rc->server) return FALSE;
365         if (!text) return TRUE;
366
367         sigpipe_occurred = FALSE;
368
369         new_action.sa_handler = sighandler_sigpipe;
370         sigemptyset(&new_action.sa_mask);
371         new_action.sa_flags = 0;
372
373         /* setup our signal handler */
374         sigaction(SIGPIPE, &new_action, &old_action);
375
376         channel = g_io_channel_unix_new(rc->fd);
377
378         g_io_channel_write_chars(channel, text, -1, nullptr, &error);
379         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, &error);
380         g_io_channel_flush(channel, &error);
381
382         if (error)
383                 {
384                 log_printf("error reading socket: %s\n", error->message);
385                 g_error_free(error);
386                 ret = FALSE;;
387                 }
388         else
389                 {
390                 ret = TRUE;
391                 }
392
393         if (ret)
394                 {
395                 gchar *buffer = nullptr;
396                 gsize termpos;
397                 g_io_channel_set_line_term(channel, "<gq_end_of_command>", -1);
398                 while (g_io_channel_read_line(channel, &buffer, nullptr, &termpos, &error) == G_IO_STATUS_NORMAL)
399                         {
400                         if (buffer)
401                                 {
402                                 if (g_strstr_len(buffer, -1, "<gq_end_of_command>") == buffer) /* empty line finishes the command */
403                                         {
404                                         g_free(buffer);
405                                         fflush(stdout);
406                                         break;
407                                         }
408                                 buffer[termpos] = '\0';
409                                 if (g_strstr_len(buffer, -1, "print0") != nullptr)
410                                         {
411                                         print0 = TRUE;
412                                         }
413                                 else
414                                         {
415                                         if (print0)
416                                                 {
417                                                 printf("%s%c", buffer, 0);
418                                                 }
419                                         else
420                                                 {
421                                                 printf("%s\n", buffer);
422                                                 }
423                                         }
424                                 g_free(buffer);
425                                 buffer = nullptr;
426                                 }
427                         }
428
429                 if (error)
430                         {
431                         log_printf("error reading socket: %s\n", error->message);
432                         g_error_free(error);
433                         ret = FALSE;
434                         }
435                 }
436
437
438         /* restore the original signal handler */
439         sigaction(SIGPIPE, &old_action, nullptr);
440         g_io_channel_unref(channel);
441         return ret;
442 }
443
444 void remote_close(RemoteConnection *rc)
445 {
446         if (!rc) return;
447
448         if (rc->server)
449                 {
450                 remote_server_clients_close(rc);
451
452                 g_source_remove(rc->channel_id);
453                 unlink(rc->path);
454                 }
455
456         if (rc->read_data)
457                 g_free(rc->read_data);
458
459         close(rc->fd);
460
461         g_free(rc->path);
462         g_free(rc);
463 }
464
465 /*
466  *-----------------------------------------------------------------------------
467  * remote functions
468  *-----------------------------------------------------------------------------
469  */
470
471 static void gr_image_next(const gchar *, GIOChannel *, gpointer)
472 {
473         layout_image_next(lw_id);
474 }
475
476 static void gr_new_window(const gchar *, GIOChannel *, gpointer)
477 {
478         LayoutWindow *lw = nullptr;
479
480         if (!layout_valid(&lw)) return;
481
482         lw_id = layout_new_from_default();
483
484         layout_set_path(lw_id, pwd);
485 }
486
487 static gboolean gr_close_window_cb(gpointer)
488 {
489         if (!layout_valid(&lw_id)) return FALSE;
490
491         layout_menu_close_cb(nullptr, lw_id);
492
493         return G_SOURCE_REMOVE;
494 }
495
496 static void gr_close_window(const gchar *, GIOChannel *, gpointer)
497 {
498         g_idle_add((gr_close_window_cb), nullptr);
499 }
500
501 static void gr_image_prev(const gchar *, GIOChannel *, gpointer)
502 {
503         layout_image_prev(lw_id);
504 }
505
506 static void gr_image_first(const gchar *, GIOChannel *, gpointer)
507 {
508         layout_image_first(lw_id);
509 }
510
511 static void gr_image_last(const gchar *, GIOChannel *, gpointer)
512 {
513         layout_image_last(lw_id);
514 }
515
516 static void gr_fullscreen_toggle(const gchar *, GIOChannel *, gpointer)
517 {
518         layout_image_full_screen_toggle(lw_id);
519 }
520
521 static void gr_fullscreen_start(const gchar *, GIOChannel *, gpointer)
522 {
523         layout_image_full_screen_start(lw_id);
524 }
525
526 static void gr_fullscreen_stop(const gchar *, GIOChannel *, gpointer)
527 {
528         layout_image_full_screen_stop(lw_id);
529 }
530
531 static void gr_lw_id(const gchar *text, GIOChannel *, gpointer)
532 {
533         lw_id = layout_find_by_layout_id(text);
534         if (!lw_id)
535                 {
536                 log_printf("remote sent window ID that does not exist:\"%s\"\n",text);
537                 }
538         layout_valid(&lw_id);
539 }
540
541 static void gr_slideshow_start_rec(const gchar *text, GIOChannel *, gpointer)
542 {
543         GList *list;
544         gchar *tilde_filename;
545
546         tilde_filename = expand_tilde(text);
547
548         FileData *dir_fd = file_data_new_dir(tilde_filename);
549         g_free(tilde_filename);
550
551         layout_valid(&lw_id);
552         list = filelist_recursive_full(dir_fd, lw_id->options.file_view_list_sort.method, lw_id->options.file_view_list_sort.ascend, lw_id->options.file_view_list_sort.case_sensitive);
553         file_data_unref(dir_fd);
554         if (!list) return;
555
556         layout_image_slideshow_stop(lw_id);
557         layout_image_slideshow_start_from_list(lw_id, list);
558 }
559
560 static void gr_cache_thumb(const gchar *text, GIOChannel *, gpointer)
561 {
562         if (!g_strcmp0(text, "clear"))
563                 {
564                 cache_maintain_home_remote(FALSE, TRUE, nullptr);
565                 }
566         else if (!g_strcmp0(text, "clean"))
567                 {
568                 cache_maintain_home_remote(FALSE, FALSE, nullptr);
569                 }
570 }
571
572 static void gr_cache_shared(const gchar *text, GIOChannel *, gpointer)
573 {
574         if (!g_strcmp0(text, "clear"))
575                 cache_manager_standard_process_remote(TRUE);
576         else if (!g_strcmp0(text, "clean"))
577                 cache_manager_standard_process_remote(FALSE);
578 }
579
580 static void gr_cache_metadata(const gchar *, GIOChannel *, gpointer)
581 {
582         cache_maintain_home_remote(TRUE, FALSE, nullptr);
583 }
584
585 static void gr_cache_render(const gchar *text, GIOChannel *, gpointer)
586 {
587         cache_manager_render_remote(text, FALSE, FALSE, nullptr);
588 }
589
590 static void gr_cache_render_recurse(const gchar *text, GIOChannel *, gpointer)
591 {
592         cache_manager_render_remote(text, TRUE, FALSE, nullptr);
593 }
594
595 static void gr_cache_render_standard(const gchar *text, GIOChannel *, gpointer)
596 {
597         if(options->thumbnails.spec_standard)
598                 {
599                 cache_manager_render_remote(text, FALSE, TRUE, nullptr);
600                 }
601 }
602
603 static void gr_cache_render_standard_recurse(const gchar *text, GIOChannel *, gpointer)
604 {
605         if(options->thumbnails.spec_standard)
606                 {
607                 cache_manager_render_remote(text, TRUE, TRUE, nullptr);
608                 }
609 }
610
611 static void gr_slideshow_toggle(const gchar *, GIOChannel *, gpointer)
612 {
613         layout_image_slideshow_toggle(lw_id);
614 }
615
616 static void gr_slideshow_start(const gchar *, GIOChannel *, gpointer)
617 {
618         layout_image_slideshow_start(lw_id);
619 }
620
621 static void gr_slideshow_stop(const gchar *, GIOChannel *, gpointer)
622 {
623         layout_image_slideshow_stop(lw_id);
624 }
625
626 static void gr_slideshow_delay(const gchar *text, GIOChannel *, gpointer)
627 {
628         gdouble t1;
629         gdouble t2;
630         gdouble t3;
631         gdouble n;
632         gint res;
633
634         res = sscanf(text, "%lf:%lf:%lf", &t1, &t2, &t3);
635         if (res == 3)
636                 {
637                 n = (t1 * 3600) + (t2 * 60) + t3;
638                 if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS ||
639                                 t1 >= 24 || t2 >= 60 || t3 >= 60)
640                         {
641                         printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n",
642                                                                 SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS);
643                         return;
644                         }
645                 }
646         else if (res == 2)
647                 {
648                 n = t1 * 60 + t2;
649                 if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS ||
650                                 t1 >= 60 || t2 >= 60)
651                         {
652                         printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n",
653                                                                 SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS);
654                         return;
655                         }
656                 }
657         else if (res == 1)
658                 {
659                 n = t1;
660                 if (n < SLIDESHOW_MIN_SECONDS || n > SLIDESHOW_MAX_SECONDS)
661                         {
662                         printf_term(TRUE, "Remote slideshow delay out of range (%.1f to %.1f)\n",
663                                                                 SLIDESHOW_MIN_SECONDS, SLIDESHOW_MAX_SECONDS);
664                         return;
665                         }
666                 }
667         else
668                 {
669                 n = 0;
670                 }
671
672         options->slideshow.delay = static_cast<gint>(n * 10.0 + 0.01);
673 }
674
675 static void gr_tools_show(const gchar *, GIOChannel *, gpointer)
676 {
677         gboolean popped;
678         gboolean hidden;
679
680         if (layout_tools_float_get(lw_id, &popped, &hidden) && hidden)
681                 {
682                 layout_tools_float_set(lw_id, popped, FALSE);
683                 }
684 }
685
686 static void gr_tools_hide(const gchar *, GIOChannel *, gpointer)
687 {
688         gboolean popped;
689         gboolean hidden;
690
691         if (layout_tools_float_get(lw_id, &popped, &hidden) && !hidden)
692                 {
693                 layout_tools_float_set(lw_id, popped, TRUE);
694                 }
695 }
696
697 static gboolean gr_quit_idle_cb(gpointer)
698 {
699         exit_program();
700
701         return G_SOURCE_REMOVE;
702 }
703
704 static void gr_quit(const gchar *, GIOChannel *, gpointer)
705 {
706         /* schedule exit when idle, if done from within a
707          * remote handler remote_close will crash
708          */
709         g_idle_add(gr_quit_idle_cb, nullptr);
710 }
711
712 static void gr_file_load_no_raise(const gchar *text, GIOChannel *, gpointer)
713 {
714         gchar *filename;
715         gchar *tilde_filename;
716
717         if (!download_web_file(text, TRUE, nullptr))
718                 {
719                 tilde_filename = expand_tilde(text);
720                 filename = set_pwd(tilde_filename);
721
722                 if (isfile(filename))
723                         {
724                         if (file_extension_match(filename, GQ_COLLECTION_EXT))
725                                 {
726                                 collection_window_new(filename);
727                                 }
728                         else
729                                 {
730                                 layout_set_path(lw_id, filename);
731                                 }
732                         }
733                 else if (isdir(filename))
734                         {
735                         layout_set_path(lw_id, filename);
736                         }
737                 else
738                         {
739                         log_printf("remote sent filename that does not exist:\"%s\"\n", filename);
740                         layout_set_path(lw_id, homedir());
741                         }
742
743                 g_free(filename);
744                 g_free(tilde_filename);
745                 }
746 }
747
748 static void gr_file_load(const gchar *text, GIOChannel *channel, gpointer data)
749 {
750         gr_file_load_no_raise(text, channel, data);
751
752         gr_raise(text, channel, data);
753 }
754
755 static void gr_pixel_info(const gchar *, GIOChannel *channel, gpointer)
756 {
757         gchar *pixel_info;
758         gint x_pixel;
759         gint y_pixel;
760         gint width;
761         gint height;
762         gint r_mouse;
763         gint g_mouse;
764         gint b_mouse;
765         PixbufRenderer *pr;
766
767         if (!layout_valid(&lw_id)) return;
768
769         pr = reinterpret_cast<PixbufRenderer*>(lw_id->image->pr);
770
771         if (pr)
772                 {
773                 pixbuf_renderer_get_image_size(pr, &width, &height);
774                 if (width < 1 || height < 1) return;
775
776                 pixbuf_renderer_get_mouse_position(pr, &x_pixel, &y_pixel);
777
778                 if (x_pixel >= 0 && y_pixel >= 0)
779                         {
780                         pixbuf_renderer_get_pixel_colors(pr, x_pixel, y_pixel,
781                                                          &r_mouse, &g_mouse, &b_mouse);
782
783                         pixel_info = g_strdup_printf(_("[%d,%d]: RGB(%3d,%3d,%3d)"),
784                                                  x_pixel, y_pixel,
785                                                  r_mouse, g_mouse, b_mouse);
786
787                         g_io_channel_write_chars(channel, pixel_info, -1, nullptr, nullptr);
788                         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
789
790                         g_free(pixel_info);
791                         }
792                 else
793                         {
794                         return;
795                         }
796                 }
797         else
798                 {
799                 return;
800                 }
801 }
802
803 static void gr_rectangle(const gchar *, GIOChannel *channel, gpointer)
804 {
805         gchar *rectangle_info;
806         PixbufRenderer *pr;
807         gint x1;
808         gint y1;
809         gint x2;
810         gint y2;
811
812         if (!options->draw_rectangle) return;
813         if (!layout_valid(&lw_id)) return;
814
815         pr = reinterpret_cast<PixbufRenderer*>(lw_id->image->pr);
816
817         if (pr)
818                 {
819                 image_get_rectangle(&x1, &y1, &x2, &y2);
820                 rectangle_info = g_strdup_printf(_("%dx%d+%d+%d"),
821                                         (x2 > x1) ? x2 - x1 : x1 - x2,
822                                         (y2 > y1) ? y2 - y1 : y1 - y2,
823                                         (x2 > x1) ? x1 : x2,
824                                         (y2 > y1) ? y1 : y2);
825
826                 g_io_channel_write_chars(channel, rectangle_info, -1, nullptr, nullptr);
827                 g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
828
829                 g_free(rectangle_info);
830                 }
831 }
832
833 static void gr_render_intent(const gchar *, GIOChannel *channel, gpointer)
834 {
835         gchar *render_intent;
836
837         switch (options->color_profile.render_intent)
838                 {
839                 case 0:
840                         render_intent = g_strdup("Perceptual");
841                         break;
842                 case 1:
843                         render_intent = g_strdup("Relative Colorimetric");
844                         break;
845                 case 2:
846                         render_intent = g_strdup("Saturation");
847                         break;
848                 case 3:
849                         render_intent = g_strdup("Absolute Colorimetric");
850                         break;
851                 default:
852                         render_intent = g_strdup("none");
853                         break;
854                 }
855
856         g_io_channel_write_chars(channel, render_intent, -1, nullptr, nullptr);
857         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
858
859         g_free(render_intent);
860 }
861
862 static void get_filelist(const gchar *text, GIOChannel *channel, gboolean recurse)
863 {
864         GList *list = nullptr;
865         FileFormatClass format_class;
866         FileData *dir_fd;
867         FileData *fd;
868         GList *work;
869         gchar *tilde_filename;
870
871         if (strcmp(text, "") == 0)
872                 {
873                 if (layout_valid(&lw_id))
874                         {
875                         dir_fd = file_data_new_dir(lw_id->dir_fd->path);
876                         }
877                 else
878                         {
879                         return;
880                         }
881                 }
882         else
883                 {
884                 tilde_filename = expand_tilde(text);
885                 if (isdir(tilde_filename))
886                         {
887                         dir_fd = file_data_new_dir(tilde_filename);
888                         }
889                 else
890                         {
891                         g_free(tilde_filename);
892                         return;
893                         }
894                 g_free(tilde_filename);
895                 }
896
897         if (recurse)
898                 {
899                 list = filelist_recursive(dir_fd);
900                 }
901         else
902                 {
903                 filelist_read(dir_fd, &list, nullptr);
904                 }
905
906         GString *out_string = g_string_new(nullptr);
907         work = list;
908         while (work)
909                 {
910                 fd = static_cast<FileData *>(work->data);
911                 g_string_append(out_string, fd->path);
912                 format_class = filter_file_get_class(fd->path);
913
914                 switch (format_class)
915                         {
916                         case FORMAT_CLASS_IMAGE:
917                                 out_string = g_string_append(out_string, "    Class: Image");
918                                 break;
919                         case FORMAT_CLASS_RAWIMAGE:
920                                 out_string = g_string_append(out_string, "    Class: RAW image");
921                                 break;
922                         case FORMAT_CLASS_META:
923                                 out_string = g_string_append(out_string, "    Class: Metadata");
924                                 break;
925                         case FORMAT_CLASS_VIDEO:
926                                 out_string = g_string_append(out_string, "    Class: Video");
927                                 break;
928                         case FORMAT_CLASS_COLLECTION:
929                                 out_string = g_string_append(out_string, "    Class: Collection");
930                                 break;
931                         case FORMAT_CLASS_DOCUMENT:
932                                 out_string = g_string_append(out_string, "    Class: Document");
933                                 break;
934                         case FORMAT_CLASS_ARCHIVE:
935                                 out_string = g_string_append(out_string, "    Class: Archive");
936                                 break;
937                         case FORMAT_CLASS_UNKNOWN:
938                                 out_string = g_string_append(out_string, "    Class: Unknown");
939                                 break;
940                         default:
941                                 out_string = g_string_append(out_string, "    Class: Unknown");
942                                 break;
943                         }
944                 out_string = g_string_append(out_string, "\n");
945                 work = work->next;
946                 }
947
948         g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr);
949         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
950
951         g_string_free(out_string, TRUE);
952         filelist_free(list);
953         file_data_unref(dir_fd);
954 }
955
956 static void gr_get_selection(const gchar *, GIOChannel *channel, gpointer)
957 {
958         if (!layout_valid(&lw_id)) return;
959
960         GList *selected = layout_selection_list(lw_id);  // Keep copy to free.
961         GString *out_string = g_string_new(nullptr);
962
963         GList *work = selected;
964         while (work)
965                 {
966                 auto fd = static_cast<FileData *>(work->data);
967                 g_assert(fd->magick == FD_MAGICK);
968
969                 g_string_append_printf(out_string, "%s    %s\n",
970                                        fd->path,
971                                        format_class_list[filter_file_get_class(fd->path)]);
972
973                 work = work->next;
974                 }
975
976         g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr);
977         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
978
979         filelist_free(selected);
980         g_string_free(out_string, TRUE);
981 }
982
983 static void gr_selection_add(const gchar *text, GIOChannel *, gpointer)
984 {
985         if (!layout_valid(&lw_id)) return;
986
987         FileData *fd_to_select = nullptr;
988         if (strcmp(text, "") == 0)
989                 {
990                 // No file specified, use current fd.
991                 fd_to_select = layout_image_get_fd(lw_id);
992                 }
993         else
994                 {
995                 // Search through the current file list for a file matching the specified path.
996                 // "Match" is either a basename match or a file path match.
997                 gchar *path = expand_tilde(text);
998                 gchar *filename = g_path_get_basename(path);
999                 gchar *slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename);
1000
1001                 GList *file_list = layout_list(lw_id);
1002                 for (GList *work = file_list; work && !fd_to_select; work = work->next)
1003                         {
1004                         auto fd = static_cast<FileData *>(work->data);
1005                         if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename))
1006                                 {
1007                                 fd_to_select = file_data_ref(fd);
1008                                 continue;  // will exit loop.
1009                                 }
1010
1011                         for (GList *sidecar = fd->sidecar_files; sidecar && !fd_to_select; sidecar = sidecar->next)
1012                                 {
1013                                 auto side_fd = static_cast<FileData *>(sidecar->data);
1014                                 if (!strcmp(path, side_fd->path)
1015                                     || g_str_has_suffix(side_fd->path, slash_plus_filename))
1016                                         {
1017                                         fd_to_select = file_data_ref(side_fd);
1018                                         continue;  // will exit both nested loops.
1019                                         }
1020                                 }
1021                         }
1022
1023                 if (!fd_to_select)
1024                         {
1025                         log_printf("remote sent --selection-add filename that could not be found: \"%s\"\n",
1026                                    filename);
1027                         }
1028
1029                 filelist_free(file_list);
1030                 g_free(slash_plus_filename);
1031                 g_free(filename);
1032                 g_free(path);
1033                 }
1034
1035         if (fd_to_select)
1036                 {
1037                 GList *to_select = g_list_append(nullptr, fd_to_select);
1038                 // Using the "_list" variant doesn't clear the existing selection.
1039                 layout_select_list(lw_id, to_select);
1040                 filelist_free(to_select);
1041                 }
1042 }
1043
1044 static void gr_selection_clear(const gchar *, GIOChannel *, gpointer)
1045 {
1046         layout_select_none(lw_id);  // Checks lw_id validity internally.
1047 }
1048
1049 static void gr_selection_remove(const gchar *text, GIOChannel *, gpointer)
1050 {
1051         if (!layout_valid(&lw_id)) return;
1052
1053         GList *selected = layout_selection_list(lw_id);  // Keep copy to free.
1054         if (!selected)
1055                 {
1056                 log_printf("remote sent --selection-remove with empty selection.");
1057                 return;
1058                 }
1059
1060         FileData *fd_to_deselect = nullptr;
1061         gchar *path = nullptr;
1062         gchar *filename = nullptr;
1063         gchar *slash_plus_filename = nullptr;
1064         if (strcmp(text, "") == 0)
1065                 {
1066                 // No file specified, use current fd.
1067                 fd_to_deselect = layout_image_get_fd(lw_id);
1068                 if (!fd_to_deselect)
1069                         {
1070                         log_printf("remote sent \"--selection-remove:\" with no current image");
1071                         filelist_free(selected);
1072                         return;
1073                         }
1074                 }
1075         else
1076                 {
1077                 // Search through the selection list for a file matching the specified path.
1078                 // "Match" is either a basename match or a file path match.
1079                 path = expand_tilde(text);
1080                 filename = g_path_get_basename(path);
1081                 slash_plus_filename = g_strdup_printf("%s%s", G_DIR_SEPARATOR_S, filename);
1082                 }
1083
1084         GList *prior_link = nullptr;  // Stash base for link removal to avoid a second traversal.
1085         GList *link_to_remove = nullptr;
1086         for (GList *work = selected; work; prior_link = work, work = work->next)
1087                 {
1088                 auto fd = static_cast<FileData *>(work->data);
1089                 if (fd_to_deselect)
1090                         {
1091                         if (fd == fd_to_deselect)
1092                                 {
1093                                 link_to_remove = work;
1094                                 break;
1095                                 }
1096                         }
1097                 else
1098                         {
1099                         // path, filename, and slash_plus_filename should be defined.
1100
1101                         if (!strcmp(path, fd->path) || g_str_has_suffix(fd->path, slash_plus_filename))
1102                                 {
1103                                 link_to_remove = work;
1104                                 break;
1105                                 }
1106                         }
1107                 }
1108
1109         if (!link_to_remove)
1110                 {
1111                 if (fd_to_deselect)
1112                         {
1113                         log_printf("remote sent \"--selection-remove:\" but current image is not selected");
1114                         }
1115                 else
1116                         {
1117                         log_printf("remote sent \"--selection-remove:%s\" but that filename is not selected",
1118                                    filename);
1119                         }
1120                 }
1121         else
1122                 {
1123                 if (link_to_remove == selected)
1124                         {
1125                         // Remove first link.
1126                         selected = g_list_remove_link(selected, link_to_remove);
1127                         filelist_free(link_to_remove);
1128                         link_to_remove = nullptr;
1129                         }
1130                 else
1131                         {
1132                         // Remove a subsequent link.
1133                         prior_link = g_list_remove_link(prior_link, link_to_remove);
1134                         filelist_free(link_to_remove);
1135                         link_to_remove = nullptr;
1136                         }
1137
1138                 // Re-select all but the deselected item.
1139                 layout_select_none(lw_id);
1140                 layout_select_list(lw_id, selected);
1141                 }
1142
1143         filelist_free(selected);
1144         file_data_unref(fd_to_deselect);
1145         g_free(slash_plus_filename);
1146         g_free(filename);
1147         g_free(path);
1148 }
1149
1150 static void gr_collection(const gchar *text, GIOChannel *channel, gpointer)
1151 {
1152         GString *contents = g_string_new(nullptr);
1153
1154         if (is_collection(text))
1155                 {
1156                 collection_contents(text, &contents);
1157                 }
1158         else
1159                 {
1160                 return;
1161                 }
1162
1163         g_io_channel_write_chars(channel, contents->str, -1, nullptr, nullptr);
1164         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1165
1166         g_string_free(contents, TRUE);
1167 }
1168
1169 static void gr_collection_list(const gchar *, GIOChannel *channel, gpointer)
1170 {
1171
1172         GList *collection_list = nullptr;
1173         GList *work;
1174         GString *out_string = g_string_new(nullptr);
1175
1176         collect_manager_list(&collection_list, nullptr, nullptr);
1177
1178         work = collection_list;
1179         while (work)
1180                 {
1181                 auto collection_name = static_cast<const gchar *>(work->data);
1182                 out_string = g_string_append(out_string, collection_name);
1183                 out_string = g_string_append(out_string, "\n");
1184
1185                 work = work->next;
1186                 }
1187
1188         g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr);
1189         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1190
1191         g_list_free_full(collection_list, g_free);
1192         g_string_free(out_string, TRUE);
1193 }
1194
1195 static gboolean wait_cb(gpointer data)
1196 {
1197         gint position = GPOINTER_TO_INT(data);
1198         gint x = position >> 16;
1199         gint y = position - (x << 16);
1200
1201         gq_gtk_window_move(GTK_WINDOW(lw_id->window), x, y);
1202
1203         return G_SOURCE_REMOVE;
1204 }
1205
1206 static void gr_geometry(const gchar *text, GIOChannel *, gpointer)
1207 {
1208         gchar **geometry;
1209
1210         if (!layout_valid(&lw_id) || !text)
1211                 {
1212                 return;
1213                 }
1214
1215         if (text[0] == '+')
1216                 {
1217                 geometry = g_strsplit_set(text, "+", 3);
1218                 if (geometry[1] != nullptr && geometry[2] != nullptr )
1219                         {
1220                         gq_gtk_window_move(GTK_WINDOW(lw_id->window), atoi(geometry[1]), atoi(geometry[2]));
1221                         }
1222                 }
1223         else
1224                 {
1225                 geometry = g_strsplit_set(text, "+x", 4);
1226                 if (geometry[0] != nullptr && geometry[1] != nullptr)
1227                         {
1228                         gtk_window_resize(GTK_WINDOW(lw_id->window), atoi(geometry[0]), atoi(geometry[1]));
1229                         }
1230                 if (geometry[2] != nullptr && geometry[3] != nullptr)
1231                         {
1232                         /* There is an occasional problem with a window_move immediately after a window_resize */
1233                         g_idle_add(wait_cb, GINT_TO_POINTER((atoi(geometry[2]) << 16) + atoi(geometry[3])));
1234                         }
1235                 }
1236         g_strfreev(geometry);
1237 }
1238
1239 static void gr_filelist(const gchar *text, GIOChannel *channel, gpointer)
1240 {
1241         get_filelist(text, channel, FALSE);
1242 }
1243
1244 static void gr_filelist_recurse(const gchar *text, GIOChannel *channel, gpointer)
1245 {
1246         get_filelist(text, channel, TRUE);
1247 }
1248
1249 static void gr_file_tell(const gchar *, GIOChannel *channel, gpointer)
1250 {
1251         gchar *out_string;
1252         gchar *collection_name = nullptr;
1253
1254         if (!layout_valid(&lw_id)) return;
1255
1256         if (image_get_path(lw_id->image))
1257                 {
1258                 if (lw_id->image->collection && lw_id->image->collection->name)
1259                         {
1260                         collection_name = remove_extension_from_path(lw_id->image->collection->name);
1261                         out_string = g_strconcat(image_get_path(lw_id->image), "    Collection: ", collection_name, NULL);
1262                         }
1263                 else
1264                         {
1265                         out_string = g_strconcat(image_get_path(lw_id->image), NULL);
1266                         }
1267                 }
1268         else
1269                 {
1270                 out_string = g_strconcat(lw_id->dir_fd->path, G_DIR_SEPARATOR_S, NULL);
1271                 }
1272
1273         g_io_channel_write_chars(channel, out_string, -1, nullptr, nullptr);
1274         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1275
1276         g_free(collection_name);
1277         g_free(out_string);
1278 }
1279
1280 static void gr_file_info(const gchar *, GIOChannel *channel, gpointer)
1281 {
1282         gchar *filename;
1283         FileData *fd;
1284         gchar *country_name;
1285         gchar *country_code;
1286         gchar *timezone;
1287         gchar *local_time;
1288         GString *out_string;
1289         FileFormatClass format_class;
1290
1291         if (!layout_valid(&lw_id)) return;
1292
1293         if (image_get_path(lw_id->image))
1294                 {
1295                 filename = g_strdup(image_get_path(lw_id->image));
1296                 fd = file_data_new_group(filename);
1297                 out_string = g_string_new(nullptr);
1298
1299                 if (fd->pixbuf)
1300                         {
1301                         format_class = filter_file_get_class(image_get_path(lw_id->image));
1302                         }
1303                 else
1304                         {
1305                         format_class = FORMAT_CLASS_UNKNOWN;
1306                         }
1307
1308                 g_string_append_printf(out_string, _("Class: %s\n"), format_class_list[format_class]);
1309
1310                 if (fd->page_total > 1)
1311                         {
1312                         g_string_append_printf(out_string, _("Page no: %d/%d\n"), fd->page_num + 1, fd->page_total);
1313                         }
1314
1315                 if (fd->exif)
1316                         {
1317                         country_name = exif_get_data_as_text(fd->exif, "formatted.countryname");
1318                         if (country_name)
1319                                 {
1320                                 g_string_append_printf(out_string, _("Country name: %s\n"), country_name);
1321                                 g_free(country_name);
1322                                 }
1323
1324                         country_code = exif_get_data_as_text(fd->exif, "formatted.countrycode");
1325                         if (country_name)
1326                                 {
1327                                 g_string_append_printf(out_string, _("Country code: %s\n"), country_code);
1328                                 g_free(country_code);
1329                                 }
1330
1331                         timezone = exif_get_data_as_text(fd->exif, "formatted.timezone");
1332                         if (timezone)
1333                                 {
1334                                 g_string_append_printf(out_string, _("Timezone: %s\n"), timezone);
1335                                 g_free(timezone);
1336                                 }
1337
1338                         local_time = exif_get_data_as_text(fd->exif, "formatted.localtime");
1339                         if (local_time)
1340                                 {
1341                                 g_string_append_printf(out_string, ("Local time: %s\n"), local_time);
1342                                 g_free(local_time);
1343                                 }
1344                         }
1345
1346                 g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr);
1347                 g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1348
1349                 g_string_free(out_string, TRUE);
1350                 file_data_unref(fd);
1351                 g_free(filename);
1352                 }
1353 }
1354
1355 static gchar *config_file_path(const gchar *param)
1356 {
1357         gchar *path = nullptr;
1358         gchar *full_name = nullptr;
1359
1360         if (file_extension_match(param, ".xml"))
1361                 {
1362                 path = g_build_filename(get_window_layouts_dir(), param, NULL);
1363                 }
1364         else if (file_extension_match(param, nullptr))
1365                 {
1366                 full_name = g_strconcat(param, ".xml", NULL);
1367                 path = g_build_filename(get_window_layouts_dir(), full_name, NULL);
1368                 }
1369
1370         if (!isfile(path))
1371                 {
1372                 g_free(path);
1373                 path = nullptr;
1374                 }
1375
1376         g_free(full_name);
1377         return path;
1378 }
1379
1380 static gboolean is_config_file(const gchar *param)
1381 {
1382         gchar *name = nullptr;
1383
1384         name = config_file_path(param);
1385         if (name)
1386                 {
1387                 g_free(name);
1388                 return TRUE;
1389                 }
1390         return FALSE;
1391 }
1392
1393 static void gr_config_load(const gchar *text, GIOChannel *, gpointer)
1394 {
1395         gchar *filename = expand_tilde(text);
1396
1397         if (!g_strstr_len(filename, -1, G_DIR_SEPARATOR_S))
1398                 {
1399                 if (is_config_file(filename))
1400                         {
1401                         gchar *tmp = config_file_path(filename);
1402                         g_free(filename);
1403                         filename = tmp;
1404                         }
1405                 }
1406
1407         if (isfile(filename))
1408                 {
1409                 load_config_from_file(filename, FALSE);
1410                 }
1411         else
1412                 {
1413                 log_printf("remote sent filename that does not exist:\"%s\"\n", filename);
1414                 layout_set_path(nullptr, homedir());
1415                 }
1416
1417         g_free(filename);
1418 }
1419
1420 static void gr_get_sidecars(const gchar *text, GIOChannel *channel, gpointer)
1421 {
1422         gchar *filename = expand_tilde(text);
1423         FileData *fd = file_data_new_group(filename);
1424
1425         GList *work;
1426         if (fd->parent) fd = fd->parent;
1427
1428         g_io_channel_write_chars(channel, fd->path, -1, nullptr, nullptr);
1429         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1430
1431         work = fd->sidecar_files;
1432
1433         while (work)
1434                 {
1435                 fd = static_cast<FileData *>(work->data);
1436                 work = work->next;
1437                 g_io_channel_write_chars(channel, fd->path, -1, nullptr, nullptr);
1438                 g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1439                 }
1440         g_free(filename);
1441 }
1442
1443 static void gr_get_destination(const gchar *text, GIOChannel *channel, gpointer)
1444 {
1445         gchar *filename = expand_tilde(text);
1446         FileData *fd = file_data_new_group(filename);
1447
1448         if (fd->change && fd->change->dest)
1449                 {
1450                 g_io_channel_write_chars(channel, fd->change->dest, -1, nullptr, nullptr);
1451                 g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1452                 }
1453         g_free(filename);
1454 }
1455
1456 static void gr_file_view(const gchar *text, GIOChannel *, gpointer)
1457 {
1458         gchar *filename;
1459         gchar *tilde_filename = expand_tilde(text);
1460
1461         filename = set_pwd(tilde_filename);
1462
1463         view_window_new(file_data_new_group(filename));
1464         g_free(filename);
1465         g_free(tilde_filename);
1466 }
1467
1468 static void gr_list_clear(const gchar *, GIOChannel *, gpointer data)
1469 {
1470         auto remote_data = static_cast<RemoteData *>(data);
1471
1472         remote_data->command_collection = nullptr;
1473         remote_data->file_list = nullptr;
1474         remote_data->single_dir = TRUE;
1475 }
1476
1477 static void gr_list_add(const gchar *text, GIOChannel *, gpointer data)
1478 {
1479         auto remote_data = static_cast<RemoteData *>(data);
1480         gboolean is_new = TRUE;
1481         gchar *path = nullptr;
1482         FileData *fd;
1483         FileData *first;
1484
1485         /** @FIXME Should check if file is in current dir, has tilde or is relative */
1486         if (!isfile(text))
1487                 {
1488                 log_printf("Warning: File does not exist --remote --list-add:%s", text);
1489
1490                 return;
1491                 }
1492
1493         /* If there is a files list on the command line
1494          * check if they are all in the same folder
1495          */
1496         if (remote_data->single_dir)
1497                 {
1498                 GList *work;
1499                 work = remote_data->file_list;
1500                 while (work && remote_data->single_dir)
1501                         {
1502                         gchar *dirname;
1503                         dirname = g_path_get_dirname((static_cast<FileData *>(work->data))->path);
1504                         if (!path)
1505                                 {
1506                                 path = g_strdup(dirname);
1507                                 }
1508                         else
1509                                 {
1510                                 if (g_strcmp0(path, dirname) != 0)
1511                                         {
1512                                         remote_data->single_dir = FALSE;
1513                                         }
1514                                 }
1515                         g_free(dirname);
1516                         work = work->next;
1517                         }
1518                 g_free(path);
1519                 }
1520
1521         gchar *pathname = g_path_get_dirname(text);
1522         layout_set_path(lw_id, pathname);
1523         g_free(pathname);
1524
1525         fd = file_data_new_simple(text);
1526         remote_data->file_list = g_list_append(remote_data->file_list, fd);
1527         file_data_unref(fd);
1528
1529         vf_select_none(lw_id->vf);
1530         remote_data->file_list = g_list_reverse(remote_data->file_list);
1531
1532         layout_select_list(lw_id, remote_data->file_list);
1533         layout_refresh(lw_id);
1534         first = static_cast<FileData *>(g_list_first(vf_selection_get_list(lw_id->vf))->data);
1535         layout_set_fd(lw_id, first);
1536
1537                 CollectionData *cd;
1538                 CollectWindow *cw;
1539
1540         if (!remote_data->command_collection && !remote_data->single_dir)
1541                 {
1542                 cw = collection_window_new(nullptr);
1543                 cd = cw->cd;
1544
1545                 collection_path_changed(cd);
1546
1547                 remote_data->command_collection = cd;
1548                 }
1549         else if (!remote_data->single_dir)
1550                 {
1551                 is_new = (!collection_get_first(remote_data->command_collection));
1552                 }
1553
1554         if (!remote_data->single_dir)
1555                 {
1556                 layout_image_set_collection(lw_id, remote_data->command_collection, collection_get_first(remote_data->command_collection));
1557                 if (collection_add(remote_data->command_collection, file_data_new_group(text), FALSE) && is_new)
1558                         {
1559                         layout_image_set_collection(lw_id, remote_data->command_collection, collection_get_first(remote_data->command_collection));
1560                         }
1561                 }
1562 }
1563
1564 static void gr_action(const gchar *text, GIOChannel *, gpointer)
1565 {
1566         GtkAction *action;
1567
1568         if (!layout_valid(&lw_id))
1569                 {
1570                 return;
1571                 }
1572
1573         if (g_strstr_len(text, -1, ".desktop") != nullptr)
1574                 {
1575                 file_util_start_editor_from_filelist(text, layout_selection_list(lw_id), layout_get_path(lw_id), lw_id->window);
1576                 }
1577         else
1578                 {
1579                 action = gtk_action_group_get_action(lw_id->action_group, text);
1580                 if (action)
1581                         {
1582                         gtk_action_activate(action);
1583                         }
1584                 else
1585                         {
1586                         log_printf("Action %s unknown", text);
1587                         }
1588                 }
1589 }
1590
1591 static void gr_action_list(const gchar *, GIOChannel *channel, gpointer)
1592 {
1593         ActionItem *action_item;
1594         gint max_length = 0;
1595         GList *list = nullptr;
1596         GString *out_string = g_string_new(nullptr);
1597
1598         if (!layout_valid(&lw_id))
1599                 {
1600                 return;
1601                 }
1602
1603         list = get_action_items();
1604
1605         /* Get the length required for padding */
1606         for (GList *work = list; work; work = work->next)
1607                 {
1608                 action_item = static_cast<ActionItem *>(work->data);
1609                 const auto length = g_utf8_strlen(action_item->name, -1);
1610                 max_length = MAX(length, max_length);
1611                 }
1612
1613         /* Pad the action names to the same column for readable output */
1614         for (GList *work = list; work; work = work->next)
1615                 {
1616                 action_item = static_cast<ActionItem *>(work->data);
1617
1618                 g_string_append_printf(out_string, "%-*s", max_length + 4, action_item->name);
1619                 out_string = g_string_append(out_string, action_item->label);
1620                 out_string = g_string_append(out_string, "\n");
1621                 }
1622
1623         action_items_free(list);
1624
1625         g_io_channel_write_chars(channel, out_string->str, -1, nullptr, nullptr);
1626         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1627
1628         g_string_free(out_string, TRUE);
1629 }
1630
1631 static void gr_raise(const gchar *, GIOChannel *, gpointer)
1632 {
1633         if (layout_valid(&lw_id))
1634                 {
1635                 gtk_window_present(GTK_WINDOW(lw_id->window));
1636                 }
1637 }
1638
1639 static void gr_pwd(const gchar *text, GIOChannel *, gpointer)
1640 {
1641         LayoutWindow *lw = nullptr;
1642
1643         layout_valid(&lw);
1644
1645         g_free(pwd);
1646         pwd = g_strdup(text);
1647         lw_id = lw;
1648 }
1649
1650 static void gr_print0(const gchar *, GIOChannel *channel, gpointer)
1651 {
1652         g_io_channel_write_chars(channel, "print0", -1, nullptr, nullptr);
1653         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1654 }
1655
1656 #ifdef HAVE_LUA
1657 static void gr_lua(const gchar *text, GIOChannel *channel, gpointer)
1658 {
1659         gchar *result = nullptr;
1660         gchar **lua_command;
1661
1662         lua_command = g_strsplit(text, ",", 2);
1663
1664         if (lua_command[0] && lua_command[1])
1665                 {
1666                 FileData *fd = file_data_new_group(lua_command[0]);
1667                 result = g_strdup(lua_callvalue(fd, lua_command[1], nullptr));
1668                 if (result)
1669                         {
1670                         g_io_channel_write_chars(channel, result, -1, nullptr, nullptr);
1671                         }
1672                 else
1673                         {
1674                         g_io_channel_write_chars(channel, N_("lua error: no data"), -1, nullptr, nullptr);
1675                         }
1676                 }
1677         else
1678                 {
1679                 g_io_channel_write_chars(channel, N_("lua error: no data"), -1, nullptr, nullptr);
1680                 }
1681
1682         g_io_channel_write_chars(channel, "<gq_end_of_command>", -1, nullptr, nullptr);
1683
1684         g_strfreev(lua_command);
1685         g_free(result);
1686 }
1687 #endif
1688
1689 struct RemoteCommandEntry {
1690         const gchar *opt_s;
1691         const gchar *opt_l;
1692         void (*func)(const gchar *text, GIOChannel *channel, gpointer data);
1693         gboolean needs_extra;
1694         gboolean prefer_command_line;
1695         const gchar *parameter;
1696         const gchar *description;
1697 };
1698
1699 static RemoteCommandEntry remote_commands[] = {
1700         /* short, long                  callback,               extra, prefer, parameter, description */
1701         { nullptr, "--action:",          gr_action,            TRUE,  FALSE, N_("<ACTION>"), N_("execute keyboard action (See Help/Reference/Remote Keyboard Actions)") },
1702         { nullptr, "--action-list",          gr_action_list,    FALSE,  FALSE, nullptr, N_("list available keyboard actions (some are redundant)") },
1703         { "-b", "--back",               gr_image_prev,          FALSE, FALSE, nullptr, N_("previous image") },
1704         { nullptr, "--close-window",       gr_close_window,        FALSE, FALSE, nullptr, N_("close window") },
1705         { nullptr, "--config-load:",       gr_config_load,         TRUE,  FALSE, N_("<FILE>|layout ID"), N_("load configuration from FILE") },
1706         { "-cm","--cache-metadata",      gr_cache_metadata,               FALSE, FALSE, nullptr, N_("clean the metadata cache") },
1707         { "-cr:", "--cache-render:",    gr_cache_render,        TRUE, FALSE, N_("<folder>  "), N_(" render thumbnails") },
1708         { "-crr:", "--cache-render-recurse:", gr_cache_render_recurse, TRUE, FALSE, N_("<folder> "), N_("render thumbnails recursively") },
1709         { "-crs:", "--cache-render-shared:", gr_cache_render_standard, TRUE, FALSE, N_("<folder> "), N_(" render thumbnails (see Help)") },
1710         { "-crsr:", "--cache-render-shared-recurse:", gr_cache_render_standard_recurse, TRUE, FALSE, N_("<folder>"), N_(" render thumbnails recursively (see Help)") },
1711         { "-cs:", "--cache-shared:",    gr_cache_shared,        TRUE, FALSE, N_("clear|clean"), N_("clear or clean shared thumbnail cache") },
1712         { "-ct:", "--cache-thumbs:",    gr_cache_thumb,         TRUE, FALSE, N_("clear|clean"), N_("clear or clean thumbnail cache") },
1713         { "-d", "--delay=",             gr_slideshow_delay,     TRUE,  FALSE, N_("<[H:][M:][N][.M]>"), N_("set slide show delay to Hrs Mins N.M seconds") },
1714         { nullptr, "--first",              gr_image_first,         FALSE, FALSE, nullptr, N_("first image") },
1715         { "-f", "--fullscreen",         gr_fullscreen_toggle,   FALSE, TRUE,  nullptr, N_("toggle full screen") },
1716         { nullptr, "--file:",              gr_file_load,           TRUE,  FALSE, N_("<FILE>|<URL>"), N_("open FILE or URL, bring Geeqie window to the top") },
1717         { nullptr, "file:",                gr_file_load,           TRUE,  FALSE, N_("<FILE>|<URL>"), N_("open FILE or URL, bring Geeqie window to the top") },
1718         { nullptr, "--File:",              gr_file_load_no_raise,  TRUE,  FALSE, N_("<FILE>|<URL>"), N_("open FILE or URL, do not bring Geeqie window to the top") },
1719         { nullptr, "File:",                gr_file_load_no_raise,  TRUE,  FALSE, N_("<FILE>|<URL>"), N_("open FILE or URL, do not bring Geeqie window to the top") },
1720         { "-fs","--fullscreen-start",   gr_fullscreen_start,    FALSE, FALSE, nullptr, N_("start full screen") },
1721         { "-fS","--fullscreen-stop",    gr_fullscreen_stop,     FALSE, FALSE, nullptr, N_("stop full screen") },
1722         { nullptr, "--geometry=",          gr_geometry,            TRUE, FALSE, N_("<GEOMETRY>"), N_("set window geometry") },
1723         { nullptr, "--get-collection:",    gr_collection,          TRUE,  FALSE, N_("<COLLECTION>"), N_("get collection content") },
1724         { nullptr, "--get-collection-list", gr_collection_list,    FALSE, FALSE, nullptr, N_("get collection list") },
1725         { nullptr, "--get-destination:",        gr_get_destination,     TRUE,  FALSE, N_("<FILE>"), N_("get destination path of FILE (See Plugins Configuration)") },
1726         { nullptr, "--get-file-info",      gr_file_info,           FALSE, FALSE, nullptr, N_("get file info") },
1727         { nullptr, "--get-filelist:",      gr_filelist,            TRUE,  FALSE, N_("[<FOLDER>]"), N_("get list of files and class") },
1728         { nullptr, "--get-filelist-recurse:", gr_filelist_recurse, TRUE,  FALSE, N_("[<FOLDER>]"), N_("get list of files and class recursive") },
1729         { nullptr, "--get-rectangle",      gr_rectangle,           FALSE, FALSE, nullptr, N_("get rectangle co-ordinates") },
1730         { nullptr, "--get-render-intent",  gr_render_intent,       FALSE, FALSE, nullptr, N_("get render intent") },
1731         { nullptr, "--get-selection",      gr_get_selection,       FALSE, FALSE, nullptr, N_("get list of selected files") },
1732         { nullptr, "--get-sidecars:",      gr_get_sidecars,        TRUE,  FALSE, N_("<FILE>"), N_("get list of sidecars of FILE") },
1733         { nullptr, "--id:",                gr_lw_id,               TRUE, FALSE, N_("<ID>"), N_("window id for following commands") },
1734         { nullptr, "--last",               gr_image_last,          FALSE, FALSE, nullptr, N_("last image") },
1735         { nullptr, "--list-add:",          gr_list_add,            TRUE,  FALSE, N_("<FILE>"), N_("add FILE to command line collection list") },
1736         { nullptr, "--list-clear",         gr_list_clear,          FALSE, FALSE, nullptr, N_("clear command line collection list") },
1737 #ifdef HAVE_LUA
1738         { nullptr, "--lua:",               gr_lua,                 TRUE, FALSE, N_("<FILE>,<lua script>"), N_("run lua script on FILE") },
1739 #endif
1740         { nullptr, "--new-window",         gr_new_window,          FALSE, FALSE, nullptr, N_("new window") },
1741         { "-n", "--next",               gr_image_next,          FALSE, FALSE, nullptr, N_("next image") },
1742         { nullptr, "--pixel-info",         gr_pixel_info,          FALSE, FALSE, nullptr, N_("print pixel info of mouse pointer on current image") },
1743         { nullptr, "--print0",             gr_print0,              TRUE, FALSE, nullptr, N_("terminate returned data with null character instead of newline") },
1744         { nullptr, "--PWD:",               gr_pwd,                 TRUE, FALSE, N_("<PWD>"), N_("use PWD as working directory for following commands") },
1745         { "-q", "--quit",               gr_quit,                FALSE, FALSE, nullptr, N_("quit") },
1746         { nullptr, "--raise",              gr_raise,               FALSE, FALSE, nullptr, N_("bring the Geeqie window to the top") },
1747         { nullptr, "raise",                gr_raise,               FALSE, FALSE, nullptr, N_("bring the Geeqie window to the top") },
1748         { nullptr, "--selection-add:",     gr_selection_add,       TRUE,  FALSE, N_("[<FILE>]"), N_("adds the current file (or the specified file) to the current selection") },
1749         { nullptr, "--selection-clear",    gr_selection_clear,     FALSE, FALSE, nullptr, N_("clears the current selection") },
1750         { nullptr, "--selection-remove:",  gr_selection_remove,    TRUE,  FALSE, N_("[<FILE>]"), N_("removes the current file (or the specified file) from the current selection") },
1751         { "-s", "--slideshow",          gr_slideshow_toggle,    FALSE, TRUE,  nullptr, N_("toggle slide show") },
1752         { nullptr, "--slideshow-recurse:", gr_slideshow_start_rec, TRUE,  FALSE, N_("<FOLDER>"), N_("start recursive slide show in FOLDER") },
1753         { "-ss","--slideshow-start",    gr_slideshow_start,     FALSE, FALSE, nullptr, N_("start slide show") },
1754         { "-sS","--slideshow-stop",     gr_slideshow_stop,      FALSE, FALSE, nullptr, N_("stop slide show") },
1755         { nullptr, "--tell",               gr_file_tell,           FALSE, FALSE, nullptr, N_("print filename [and Collection] of current image") },
1756         { "+t", "--tools-show",         gr_tools_show,          FALSE, TRUE,  nullptr, N_("show tools") },
1757         { "-t", "--tools-hide",         gr_tools_hide,          FALSE, TRUE,  nullptr, N_("hide tools") },
1758         { nullptr, "--view:",              gr_file_view,           TRUE,  FALSE, N_("<FILE>"), N_("open FILE in new window") },
1759         { nullptr, "view:",                gr_file_view,           TRUE,  FALSE, N_("<FILE>"), N_("open FILE in new window") },
1760         { nullptr, nullptr, nullptr, FALSE, FALSE, nullptr, nullptr }
1761 };
1762
1763 static RemoteCommandEntry *remote_command_find(const gchar *text, const gchar **offset)
1764 {
1765         gboolean match = FALSE;
1766         gint i;
1767
1768         i = 0;
1769         while (!match && remote_commands[i].func != nullptr)
1770                 {
1771                 if (remote_commands[i].needs_extra)
1772                         {
1773                         if (remote_commands[i].opt_s &&
1774                             strncmp(remote_commands[i].opt_s, text, strlen(remote_commands[i].opt_s)) == 0)
1775                                 {
1776                                 if (offset) *offset = text + strlen(remote_commands[i].opt_s);
1777                                 return &remote_commands[i];
1778                                 }
1779
1780                         if (remote_commands[i].opt_l &&
1781                                  strncmp(remote_commands[i].opt_l, text, strlen(remote_commands[i].opt_l)) == 0)
1782                                 {
1783                                 if (offset) *offset = text + strlen(remote_commands[i].opt_l);
1784                                 return &remote_commands[i];
1785                                 }
1786                         }
1787                 else
1788                         {
1789                         if ((remote_commands[i].opt_s && strcmp(remote_commands[i].opt_s, text) == 0) ||
1790                             (remote_commands[i].opt_l && strcmp(remote_commands[i].opt_l, text) == 0))
1791                                 {
1792                                 if (offset) *offset = text;
1793                                 return &remote_commands[i];
1794                                 }
1795                         }
1796
1797                 i++;
1798                 }
1799
1800         return nullptr;
1801 }
1802
1803 static void remote_cb(RemoteConnection *, const gchar *text, GIOChannel *channel, gpointer data)
1804 {
1805         RemoteCommandEntry *entry;
1806         const gchar *offset;
1807
1808         entry = remote_command_find(text, &offset);
1809         if (entry && entry->func)
1810                 {
1811                 entry->func(offset, channel, data);
1812                 }
1813         else
1814                 {
1815                 log_printf("unknown remote command:%s\n", text);
1816                 }
1817 }
1818
1819 void remote_help()
1820 {
1821         gint i;
1822         gchar *s_opt_param;
1823         gchar *l_opt_param;
1824
1825         print_term(FALSE, _("Remote command list:\n"));
1826
1827         i = 0;
1828         while (remote_commands[i].func != nullptr)
1829                 {
1830                 if (remote_commands[i].description)
1831                         {
1832                         s_opt_param = g_strdup(remote_commands[i].opt_s  ? remote_commands[i].opt_s : "" );
1833                         l_opt_param = g_strconcat(remote_commands[i].opt_l, remote_commands[i].parameter, NULL);
1834
1835                         if (g_str_has_prefix(l_opt_param, "--"))
1836                                 {
1837                                 printf_term(FALSE, "  %-4s %-40s%-s\n", s_opt_param, l_opt_param, _(remote_commands[i].description));
1838                                 }
1839                         g_free(s_opt_param);
1840                         g_free(l_opt_param);
1841                         }
1842                 i++;
1843                 }
1844         printf_term(FALSE, _("\n\n  All other command line parameters are used as plain files if they exist.\n\n  The name of a collection, with or without either path or extension (.gqv) may be used.\n"));
1845 }
1846
1847 GList *remote_build_list(GList *list, gint argc, gchar *argv[], GList **errors)
1848 {
1849         gint i;
1850
1851         i = 1;
1852         while (i < argc)
1853                 {
1854                 RemoteCommandEntry *entry;
1855
1856                 entry = remote_command_find(argv[i], nullptr);
1857                 if (entry)
1858                         {
1859                         list = g_list_append(list, argv[i]);
1860                         }
1861                 else if (errors && !isname(argv[i]))
1862                         {
1863                         *errors = g_list_append(*errors, argv[i]);
1864                         }
1865                 i++;
1866                 }
1867
1868         return list;
1869 }
1870
1871 /**
1872  * @param arg_exec Binary (argv0)
1873  * @param remote_list Evaluated and recognized remote commands
1874  * @param path The current path
1875  * @param cmd_list List of all non collections in Path (gchar *path)
1876  * @param collection_list List of all collections in argv
1877  */
1878 void remote_control(const gchar *arg_exec, GList *remote_list, const gchar *path,
1879                     GList *cmd_list, GList *collection_list)
1880 {
1881         RemoteConnection *rc;
1882         gboolean started = FALSE;
1883         gchar *buf;
1884
1885         buf = g_build_filename(get_rc_dir(), ".command", NULL);
1886         rc = remote_client_open(buf);
1887         if (!rc)
1888                 {
1889                 GString *command;
1890                 GList *work;
1891                 gint retry_count = 12;
1892                 gboolean blank = FALSE;
1893
1894                 printf_term(FALSE, _("Remote %s not running, starting..."), GQ_APPNAME);
1895
1896                 command = g_string_new(arg_exec);
1897
1898                 work = remote_list;
1899                 while (work)
1900                         {
1901                         gchar *text;
1902                         RemoteCommandEntry *entry;
1903
1904                         text = static_cast<gchar *>(work->data);
1905                         work = work->next;
1906
1907                         entry = remote_command_find(text, nullptr);
1908                         if (entry)
1909                                 {
1910                                 /* If Geeqie is not running, stop the --new-window command opening a second window */
1911                                 if (g_strcmp0(text, "--new-window") == 0)
1912                                         {
1913                                         remote_list = g_list_remove(remote_list, text);
1914                                         }
1915                                 if (entry->prefer_command_line)
1916                                         {
1917                                         remote_list = g_list_remove(remote_list, text);
1918                                         g_string_append(command, " ");
1919                                         g_string_append(command, text);
1920                                         }
1921                                 if (entry->opt_l && strcmp(entry->opt_l, "file:") == 0)
1922                                         {
1923                                         blank = TRUE;
1924                                         }
1925                                 }
1926                         }
1927
1928                 if (blank || cmd_list || path) g_string_append(command, " --blank");
1929                 if (get_debug_level()) g_string_append(command, " --debug");
1930
1931                 g_string_append(command, " &");
1932                 runcmd(command->str);
1933                 g_string_free(command, TRUE);
1934
1935                 while (!rc && retry_count > 0)
1936                         {
1937                         usleep((retry_count > 10) ? 500000 : 1000000);
1938                         rc = remote_client_open(buf);
1939                         if (!rc) print_term(FALSE, ".");
1940                         retry_count--;
1941                         }
1942
1943                 print_term(FALSE, "\n");
1944
1945                 started = TRUE;
1946                 }
1947         g_free(buf);
1948
1949         if (rc)
1950                 {
1951                 GList *work;
1952                 const gchar *prefix;
1953                 gboolean use_path = TRUE;
1954                 gboolean sent = FALSE;
1955
1956                 work = remote_list;
1957                 while (work)
1958                         {
1959                         gchar *text;
1960                         RemoteCommandEntry *entry;
1961
1962                         text = static_cast<gchar *>(work->data);
1963                         work = work->next;
1964
1965                         entry = remote_command_find(text, nullptr);
1966                         if (entry &&
1967                             entry->opt_l &&
1968                             strcmp(entry->opt_l, "file:") == 0) use_path = FALSE;
1969
1970                         remote_client_send(rc, text);
1971
1972                         sent = TRUE;
1973                         }
1974
1975                 if (cmd_list && cmd_list->next)
1976                         {
1977                         prefix = "--list-add:";
1978                         remote_client_send(rc, "--list-clear");
1979                         }
1980                 else
1981                         {
1982                         prefix = "file:";
1983                         }
1984
1985                 work = cmd_list;
1986                 while (work)
1987                         {
1988                         gchar *text;
1989
1990                         text = g_strconcat(prefix, work->data, NULL);
1991                         remote_client_send(rc, text);
1992                         g_free(text);
1993                         work = work->next;
1994
1995                         sent = TRUE;
1996                         }
1997
1998                 if (path && !cmd_list && use_path)
1999                         {
2000                         gchar *text;
2001
2002                         text = g_strdup_printf("file:%s", path);
2003                         remote_client_send(rc, text);
2004                         g_free(text);
2005
2006                         sent = TRUE;
2007                         }
2008
2009                 work = collection_list;
2010                 while (work)
2011                         {
2012                         const gchar *name;
2013                         gchar *text;
2014
2015                         name = static_cast<const gchar *>(work->data);
2016                         work = work->next;
2017
2018                         text = g_strdup_printf("file:%s", name);
2019                         remote_client_send(rc, text);
2020                         g_free(text);
2021
2022                         sent = TRUE;
2023                         }
2024
2025                 if (!started && !sent)
2026                         {
2027                         remote_client_send(rc, "raise");
2028                         }
2029                 }
2030         else
2031                 {
2032                 print_term(TRUE, _("Remote not available\n"));
2033                 }
2034
2035         _exit(0);
2036 }
2037
2038 RemoteConnection *remote_server_init(gchar *path, CollectionData *command_collection)
2039 {
2040         RemoteConnection *remote_connection = remote_server_open(path);
2041         auto remote_data = g_new(RemoteData, 1);
2042
2043         remote_data->command_collection = command_collection;
2044
2045         remote_server_subscribe(remote_connection, remote_cb, remote_data);
2046         return remote_connection;
2047 }
2048 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */