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