forked from phillipberndt/pqiv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pqiv.c
8325 lines (7548 loc) · 300 KB
/
pqiv.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* pqiv
*
* Copyright (c) 2013-2017, Phillip Berndt
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#include "pqiv.h"
#include "lib/config_parser.h"
#include "lib/thumbnailcache.h"
#include "lib/strnatcmp.h"
#include <cairo/cairo.h>
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>
#include <locale.h>
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x500
#else
#if _WIN32_WINNT < 0x800
#ifdef __MINGW32__
#pragma message "Microsoft Windows supported is limited to Windows 2000 and higher, but your mingw version indicates that it does not support those versions. Building might fail."
#else
#error Microsoft Windows supported is limited to Windows 2000 and higher.
#endif
#endif
#endif
#include <windows.h>
#include <gio/gwin32inputstream.h>
#else
#include <sys/wait.h>
#include <gio/gunixinputstream.h>
#endif
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cairo/cairo-xlib.h>
#if GTK_MAJOR_VERSION < 3
#include <X11/Xatom.h>
#endif
#endif
#ifdef DEBUG
#ifndef _WIN32
#include <sys/resource.h>
#endif
#define PQIV_VERSION_DEBUG "-debug"
#else
#define PQIV_VERSION_DEBUG ""
#endif
#if defined(__clang__) || defined(__GNUC__)
#define UNUSED_FUNCTION __attribute__((unused))
#if defined(__clang__)
#define PQIV_DISABLE_PEDANTIC _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpedantic\"")
#define PQIV_ENABLE_PEDANTIC _Pragma("clang diagnostic pop")
#elif defined(__GNUC__) || defined(__GNUG__)
#define PQIV_DISABLE_PEDANTIC _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wpedantic\"")
#define PQIV_ENABLE_PEDANTIC _Pragma("GCC diagnostic pop")
#endif
#else
#define UNUSED_FUNCTION
#define PQIV_DISABLE_PEDANTIC
#define PQIV_ENABLE_PEDANTIC
#endif
#if !GLIB_CHECK_VERSION(2, 32, 0)
#define g_thread_new(name, func, data) g_thread_create(func, data, FALSE, NULL)
#endif
// GTK 2 does not define keyboard aliases the way we do
#if GTK_MAJOR_VERSION < 3 // {{{
#define GDK_BUTTON_PRIMARY 1
#define GDK_BUTTON_MIDDLE 2
#define GDK_BUTTON_SECONDARY 3
#define GDK_KEY_VoidSymbol 0xffffff
#include <gdk/gdkkeysyms.h>
#endif // }}}
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
#include <pango/pangocairo.h>
#endif
// Global variables and function signatures {{{
// The list of file type handlers and file type initializer function
void initialize_file_type_handlers(const gchar * const * disabled_backends);
// Storage of the file list
// These lists are accessed from multiple threads:
// * The main thread (count, next, prev, ..)
// * The option parser thread, if --lazy-load is used
// * The image loader thread
// Our thread safety strategy is as follows:
// * Wrap all file_tree operations with mutexes
// * Use weak references for any operation during which the image might
// invalidate.
// * If a weak reference is invalid, abort the pending operation
// * If an operation can't be aborted, lock the mutex from the start
// until it completes
// * If an operation takes too long for this to work, redesign the
// operation
G_LOCK_DEFINE_STATIC(file_tree);
// In case of trouble:
#if 0
#define D_LOCK(x) g_print("Waiting for lock " #x " at line %d\n", __LINE__); G_LOCK(x); g_print(" Locked " #x " at line %d\n", __LINE__)
#define D_UNLOCK(x) g_print("Unlocked " #x " at line %d\n", __LINE__); G_UNLOCK(x);
#else
#define D_LOCK(x) G_LOCK(x)
#define D_UNLOCK(x) G_UNLOCK(x)
#endif
BOSTree *file_tree;
BOSNode *current_file_node = NULL;
BOSNode *earlier_file_node = NULL;
BOSNode *image_loader_thread_currently_loading = NULL;
GThread *image_loader_thread_ref;
gboolean file_tree_valid = FALSE;
// We asynchroniously load images in a separate thread
struct image_loader_queue_item {
BOSNode *node_ref;
int purpose;
};
GAsyncQueue *image_loader_queue = NULL;
GCancellable *image_loader_cancellable = NULL;
// Unloading of files is also handled by that thread, in a GC fashion
// For that, we keep a list of loaded files
GList *loaded_files_list = NULL;
// Filter for path traversing upon building the file list
GHashTable *load_images_file_filter_hash_table;
GtkFileFilterInfo *load_images_file_filter_info;
GTimer *load_images_timer;
// Easy access to the file_t within a node
// Remember to always lock file_tree!
#define FILE(x) ((file_t *)(x)->data)
#define CURRENT_FILE FILE(current_file_node)
#define next_file() relative_image_pointer(1)
#define previous_file() relative_image_pointer(-1)
#define is_current_file_loaded() (current_file_node && CURRENT_FILE->is_loaded)
// The node to be displayed first, used in conjunction with --browse
BOSNode *browse_startup_node = NULL;
// When loading additional images via the -r option, we need to know whether the
// image loader initialization succeeded, because we can't just cancel if it does
// not (it checks if any image is loadable and fails if not)
gboolean image_loader_initialization_succeeded = FALSE;
// We sometimes need to decide whether we have to draw the image or if it already
// is. We use this variable for that.
gboolean current_image_drawn = FALSE;
// Variables related to the window, display, etc.
GtkWindow *main_window;
gboolean main_window_visible = FALSE;
GtkWidget *main_window_box;
gint csd_left, csd_right, csd_top, csd_bottom, csd_height, csd_width;
// Detection of tiled WMs: They should ignore our resize events
gint requested_main_window_resize_pos_callback_id = -1;
gint requested_main_window_width = -1;
gint requested_main_window_height = -1;
gboolean wm_ignores_size_requests = FALSE;
gint main_window_width = 10;
gint main_window_height = 10;
gboolean main_window_in_fullscreen = FALSE;
int fullscreen_transition_source_id = -1;
GdkRectangle screen_geometry = { 0, 0, 0, 0 };
gint screen_scale_factor = 1;
gboolean wm_supports_fullscreen = TRUE;
// If a WM indicates no moveresize support that's a hint it's a tiling WM
gboolean wm_supports_moveresize = TRUE;
// If a WM indicates no framedrawn support it's subject to tearing effects
gboolean wm_supports_framedrawn = TRUE;
cairo_pattern_t *background_checkerboard_pattern = NULL;
gboolean gui_initialized = FALSE;
int global_argc;
char **global_argv;
// Those surfaces are here to store scaled image versions (to reduce
// processor load) and to store the last visible image to have something to
// display while fading and while the (new) current image has not been loaded
// yet.
int last_visible_surface_width = -1;
int last_visible_surface_height = -1;
cairo_surface_t *last_visible_surface = NULL;
cairo_surface_t *fading_surface = NULL;
cairo_surface_t *current_scaled_image_surface = NULL;
#if !defined(CONFIGURED_WITHOUT_INFO_TEXT) || !defined(CONFIGURED_WITHOUT_MONTAGE_MODE)
struct {
double fg_red;
double fg_green;
double fg_blue;
double bg_red;
double bg_green;
double bg_blue;
} option_box_colors = { 0., 0., 0., 1., 1., 0. };
#endif
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
gint current_info_text_cached_font_size = -1;
gchar *current_info_text = NULL;
cairo_rectangle_int_t current_info_text_bounding_box = { 0, 0, 0, 0 };
#endif
// Current state of the displayed image and user interaction
// This matrix stores rotations and reflections (makes ui with scaling/transforming easier)
cairo_matrix_t current_transformation;
gdouble current_scale_level = 1.0;
gint current_shift_x = 0;
gint current_shift_y = 0;
guint32 last_button_press_time = 0;
guint32 last_button_release_time = 0;
guint current_image_animation_timeout_id = 0;
gdouble current_image_animation_speed_scale = 1.0;
// -1 means no slideshow, 0 means active slideshow but no current timeout
// source set, anything bigger than that actually is a slideshow id.
gint slideshow_timeout_id = -1;
// A list containing references to the images in shuffled order
typedef struct {
gboolean viewed;
BOSNode *node;
} shuffled_image_ref_t;
guint shuffled_images_visited_count = 0;
guint shuffled_images_list_length = 0;
GList *shuffled_images_list = NULL;
#define LIST_SHUFFLED_IMAGE(x) (((shuffled_image_ref_t *)x->data))
#ifndef CONFIGURED_WITHOUT_EXTERNAL_COMMANDS
// User options
gchar *external_image_filter_commands[] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
#endif
// Scaling mode, only 0-2 are available in the default UI, FIXED_SCALE can be
// set using a command line option, SCALE_TO_FIT_PX and SCALE_TO_FIT_WINDOW only using an action
enum { NO_SCALING=0, AUTO_SCALEDOWN, AUTO_SCALEUP, FIXED_SCALE, SCALE_TO_FIT_WINDOW, SCALE_TO_FIT_PX } option_scale = AUTO_SCALEDOWN;
double option_scale_screen_fraction = .8;
struct {
guint width;
guint height;
} scale_to_fit_size;
gboolean scale_override = FALSE;
const gchar *option_window_title = "pqiv: $FILENAME ($WIDTHx$HEIGHT) $ZOOM% [$IMAGE_NUMBER/$IMAGE_COUNT]";
gdouble option_slideshow_interval = 5.;
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
gboolean option_hide_info_box = FALSE;
const gchar *option_font = "Sans-Serif";
#endif
gboolean option_start_fullscreen = FALSE;
gdouble option_initial_scale = 1.0;
gboolean option_start_with_slideshow_mode = FALSE;
gboolean option_sort = FALSE;
enum { NAME, MTIME } option_sort_key = NAME;
gboolean option_shuffle = FALSE;
gboolean option_transparent_background = FALSE;
gboolean option_watch_directories = FALSE;
gboolean option_wait_for_images_to_appear = FALSE;
gboolean option_fading = FALSE;
gboolean option_lazy_load = FALSE;
gboolean option_allow_empty_window = FALSE;
gboolean option_lowmem = FALSE;
gboolean option_addl_from_stdin = FALSE;
gboolean option_recreate_window = FALSE;
gboolean option_enforce_window_aspect_ratio = FALSE;
gboolean cursor_visible = TRUE;
gboolean cursor_auto_hide_mode_enabled = FALSE;
gboolean option_negate = FALSE;
int cursor_auto_hide_timer_id = 0;
#ifndef CONFIGURED_WITHOUT_ACTIONS
gboolean option_actions_from_stdin = FALSE;
gboolean option_status_output = FALSE;
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
gboolean option_auto_montage_mode = FALSE;
#endif
#else
static const gboolean option_actions_from_stdin = FALSE;
#endif
double option_fading_duration = .5;
double option_keyboard_timeout = .5;
gint option_max_depth = -1;
gboolean option_browse = FALSE;
enum { QUIT, WAIT, WRAP, WRAP_NO_RESHUFFLE } option_end_of_files_action = WRAP;
enum { ON, OFF, CHANGES_ONLY } option_watch_files = ON;
gchar *option_disable_backends;
double fading_current_alpha_stage = 0;
gint64 fading_initial_time;
#ifdef CONFIGURED_WITHOUT_ACTIONS
const
#endif
enum { AUTO, FAST, GOOD, BEST } option_interpolation_quality = AUTO;
enum { CHECKERBOARD, BLACK, WHITE } option_background_pattern = CHECKERBOARD;
gboolean options_background_pattern_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
#ifndef CONFIGURED_WITHOUT_ACTIONS
gboolean options_bind_key_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
char *key_binding_sequence_to_string(guint key_binding_value, gchar *prefix);
gboolean help_show_key_bindings(const gchar *option_name, const gchar *value, gpointer data, GError **error);
#endif
gboolean help_show_version(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_window_position_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_thumbnail_size_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_thumbnail_preload_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_scale_level_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_thumbnail_persistence_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_end_of_files_action_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_watch_files_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_sort_key_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
gboolean option_action_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
#if !defined(CONFIGURED_WITHOUT_INFO_TEXT) || !defined(CONFIGURED_WITHOUT_MONTAGE_MODE)
gboolean option_box_colors_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error);
#endif
void load_images_handle_parameter(char *param, load_images_state_t state, gint depth, GSList *recursion_folder_stack);
struct {
gint x;
gint y;
} option_window_position = { -2, -2 };
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE /* option --without-montage: Do not include support for a thumbnail overview */
struct {
gboolean enabled;
thumbnail_persist_mode_t persist;
gchar *special_thumbnail_directory;
gint width;
gint height;
gint auto_generate_for_adjacents;
} option_thumbnails = { 0, THUMBNAILS_PERSIST_RO, NULL, 128, 128, -1 };
struct {
int scroll_y;
BOSNode *selected_node;
gboolean show_binding_overlays;
} montage_window_control;
enum { MONTAGE_MODE_WRAP_OFF, MONTAGE_MODE_WRAP_ROWS, MONTAGE_MODE_WRAP_FULL, _MONTAGE_MODE_WRAP_SENTINEL } option_montage_mode_wrap_mode = MONTAGE_MODE_WRAP_ROWS;
#endif
struct Point {
int x;
int y;
};
// The standard forbids casting object pointers to function pointers, but
// GLib requires it in its GOptionEntry structure.
PQIV_DISABLE_PEDANTIC
// Hint: Only types G_OPTION_ARG_NONE, G_OPTION_ARG_STRING, G_OPTION_ARG_DOUBLE/INTEGER and G_OPTION_ARG_CALLBACK are
// implemented for option parsing.
GOptionEntry options[] = {
{ "transparent-background", 'c', 0, G_OPTION_ARG_NONE, &option_transparent_background, "Borderless transparent window", NULL },
{ "slideshow-interval", 'd', 0, G_OPTION_ARG_DOUBLE, &option_slideshow_interval, "Set slideshow interval", "n" },
{ "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &option_start_fullscreen, "Start in fullscreen mode", NULL },
{ "fade", 'F', 0, G_OPTION_ARG_NONE, (gpointer)&option_fading, "Fade between images", NULL },
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
{ "hide-info-box", 'i', 0, G_OPTION_ARG_NONE, &option_hide_info_box, "Initially hide the info box", NULL },
#endif
{ "lazy-load", 'l', 0, G_OPTION_ARG_NONE, &option_lazy_load, "Display the main window as soon as one image is loaded", NULL },
{ "sort", 'n', 0, G_OPTION_ARG_NONE, &option_sort, "Sort files in natural order", NULL },
{ "window-position", 'P', 0, G_OPTION_ARG_CALLBACK, &option_window_position_callback, "Set initial window position (`x,y' or `off' to not position the window at all)", "POSITION" },
{ "additional-from-stdin", 'r', 0, G_OPTION_ARG_NONE, &option_addl_from_stdin, "Read additional filenames/folders from stdin", NULL },
{ "slideshow", 's', 0, G_OPTION_ARG_NONE, &option_start_with_slideshow_mode, "Activate slideshow mode", NULL },
{ "scale-images-up", 't', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &option_scale_level_callback, "Scale images up to fill the whole screen", NULL },
{ "window-title", 'T', 0, G_OPTION_ARG_STRING, &option_window_title, "Set the title of the window. See manpage for available variables.", "TITLE" },
{ "zoom-level", 'z', 0, G_OPTION_ARG_DOUBLE, &option_initial_scale, "Set initial zoom level (1.0 is 100%)", "FLOAT" },
#ifndef CONFIGURED_WITHOUT_EXTERNAL_COMMANDS
{ "command-1", '1', 0, G_OPTION_ARG_STRING, &external_image_filter_commands[0], "Bind the external COMMAND to key 1. See manpage for extended usage (commands starting with `>' or `|'). Use 2..9 for further commands.", "COMMAND" },
{ "command-2", '2', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[1], NULL, NULL },
{ "command-3", '3', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[2], NULL, NULL },
{ "command-4", '4', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[3], NULL, NULL },
{ "command-5", '5', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[4], NULL, NULL },
{ "command-6", '6', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[5], NULL, NULL },
{ "command-7", '7', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[6], NULL, NULL },
{ "command-8", '8', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[7], NULL, NULL },
{ "command-9", '9', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &external_image_filter_commands[8], NULL, NULL },
#endif
#ifndef CONFIGURED_WITHOUT_ACTIONS
{ "action", 0, 0, G_OPTION_ARG_CALLBACK, &option_action_callback, "Perform a given action", "ACTION" },
{ "actions-from-stdin", 0, 0, G_OPTION_ARG_NONE, &option_actions_from_stdin, "Read actions from stdin", NULL },
{ "allow-empty-window", 0, 0, G_OPTION_ARG_NONE, &option_allow_empty_window, "Show pqiv/do not quit even though no files are loaded", NULL },
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
{ "auto-montage-mode", 0, 0, G_OPTION_ARG_NONE, &option_auto_montage_mode, "Automatically enter montage mode if multiple images are opened", NULL },
#endif
#endif
{ "background-pattern", 0, 0, G_OPTION_ARG_CALLBACK, &options_background_pattern_callback, "Set the background pattern to use for transparent images", "PATTERN" },
#ifndef CONFIGURED_WITHOUT_ACTIONS
{ "bind-key", 0, 0, G_OPTION_ARG_CALLBACK, &options_bind_key_callback, "Rebind a key to another action, see manpage and --show-bindings output for details.", "KEY BINDING" },
#endif
#if !defined(CONFIGURED_WITHOUT_INFO_TEXT) || !defined(CONFIGURED_WITHOUT_MONTAGE_MODE)
{ "box-colors", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer)&option_box_colors_callback, "Set box colors", "TEXT:BACKGROUND" },
#endif
{ "browse", 0, 0, G_OPTION_ARG_NONE, &option_browse, "For each command line argument, additionally load all images from the image's directory", NULL },
{ "disable-backends", 0, 0, G_OPTION_ARG_STRING, &option_disable_backends, "Disable the given backends", "BACKENDS" },
{ "disable-scaling", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &option_scale_level_callback, "Disable scaling of images", NULL },
{ "end-of-files-action", 0, 0, G_OPTION_ARG_CALLBACK, &option_end_of_files_action_callback, "Action to take after all images have been viewed. (`quit', `wait', `wrap', `wrap-no-reshuffle')", "ACTION" },
{ "enforce-window-aspect-ratio", 0, 0, G_OPTION_ARG_NONE, &option_enforce_window_aspect_ratio, "Fix the aspect ratio of the window to match the current image's", NULL },
{ "fade-duration", 0, 0, G_OPTION_ARG_DOUBLE, &option_fading_duration, "Adjust fades' duration", "SECONDS" },
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
{ "font", 0, 0, G_OPTION_ARG_STRING, &option_font, "Specify the Pango font string for the info box. Note that the font size will be scaled to the window.", "FONT" },
#endif
{ "low-memory", 0, 0, G_OPTION_ARG_NONE, &option_lowmem, "Try to keep memory usage to a minimum", NULL },
{ "max-depth", 0, 0, G_OPTION_ARG_INT, &option_max_depth, "Descend at most LEVELS levels of directories below the command line arguments", "LEVELS" },
{ "negate", 0, 0, G_OPTION_ARG_NONE, &option_negate, "Negate images: show negatives", NULL },
{ "recreate-window", 0, 0, G_OPTION_ARG_NONE, &option_recreate_window, "Create a new window instead of resizing the old one", NULL },
{ "scale-mode-screen-fraction", 0, 0, G_OPTION_ARG_DOUBLE, &option_scale_screen_fraction, "Screen fraction to use for auto-scaling", "FLOAT" },
{ "shuffle", 0, 0, G_OPTION_ARG_NONE, &option_shuffle, "Shuffle files", NULL },
#ifndef CONFIGURED_WITHOUT_ACTIONS
{ "show-bindings", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &help_show_key_bindings, "Display the keyboard and mouse bindings and exit", NULL },
#endif
{ "sort-key", 0, 0, G_OPTION_ARG_CALLBACK, &option_sort_key_callback, "Key to use for sorting", "PROPERTY" },
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
{ "thumbnail-size", 0, 0, G_OPTION_ARG_CALLBACK, &option_thumbnail_size_callback, "Set the dimensions of thumbnails in montage mode", "WIDTHxHEIGHT" },
{ "thumbnail-preload", 0, 0, G_OPTION_ARG_CALLBACK, &option_thumbnail_preload_callback, "Preload the adjacent COUNT thumbnails", "COUNT" },
{ "thumbnail-persistence", 0, 0, G_OPTION_ARG_CALLBACK, &option_thumbnail_persistence_callback, "Persist thumbnails to disk, to DIRECTORY.", "DIRECTORY" },
#endif
{ "wait-for-images-to-appear", 0, 0, G_OPTION_ARG_NONE, &option_wait_for_images_to_appear, "If no images are found, wait until at least one appears", NULL },
{ "watch-directories", 0, 0, G_OPTION_ARG_NONE, &option_watch_directories, "Watch directories for new files", NULL },
{ "watch-files", 0, 0, G_OPTION_ARG_CALLBACK, &option_watch_files_callback, "Watch files for changes on disk (`on`, `off', `changes-only', i.e. do nothing on deletetion)", "VALUE" },
{ "version", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, &help_show_version, "Show version information and quit", NULL },
{ NULL, 0, 0, 0, NULL, NULL, NULL }
};
PQIV_ENABLE_PEDANTIC
/* Key bindings & actions {{{ */
#define KEY_BINDINGS_KEY_TOKEN_BEGIN_SYMBOL '<'
#define KEY_BINDINGS_KEY_TOKEN_END_SYMBOL '>'
#define KEY_BINDINGS_COMMANDS_BEGIN_SYMBOL '{'
#define KEY_BINDINGS_COMMANDS_END_SYMBOL '}'
#define KEY_BINDINGS_COMMAND_SEPARATOR_SYMBOL ';'
#define KEY_BINDINGS_COMMAND_PARAMETER_BEGIN_SYMBOL '('
#define KEY_BINDINGS_COMMAND_PARAMETER_END_SYMBOL ')'
#define KEY_BINDINGS_CONTEXT_SWITCH_SYMBOL '@'
#define KEY_BINDING_STATE_BITS 4
#define KEY_BINDING_VALUE(is_mouse, state, keycode) ((guint)((((unsigned)is_mouse & 1u) << 31) | (((state & ((1u << KEY_BINDING_STATE_BITS) - 1)) << (31 - KEY_BINDING_STATE_BITS)) | (keycode & ((1u << (31 - KEY_BINDING_STATE_BITS)) - 1u)))))
#define KEY_BINDING_CONTEXTS_COUNT 2
#ifndef CONFIGURED_WITHOUT_ACTIONS
const char * const key_binding_context_names[] = {
"DEFAULT",
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
"MONTAGE",
#else
"",
#endif
};
#endif
enum context_t { DEFAULT, MONTAGE };
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
static char montage_mode_default_keys[] = "asdfghjkl";
#endif
static const struct default_key_bindings_struct {
enum context_t context;
guint key_binding_value;
pqiv_action_t action;
pqiv_action_parameter_t parameter;
} default_key_bindings[] = {
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Up ), ACTION_SHIFT_Y , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Up ), ACTION_SHIFT_Y , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_Up ), ACTION_SHIFT_Y , { 50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Up ), ACTION_SHIFT_Y , { 50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Down ), ACTION_SHIFT_Y , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Down ), ACTION_SHIFT_Y , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_Down ), ACTION_SHIFT_Y , { -50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Down ), ACTION_SHIFT_Y , { -50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Left ), ACTION_SHIFT_X , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Left ), ACTION_SHIFT_X , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_Left ), ACTION_SHIFT_X , { 50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Left ), ACTION_SHIFT_X , { 50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Right ), ACTION_SHIFT_X , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Right ), ACTION_SHIFT_X , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_Right ), ACTION_SHIFT_X , { -50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Right ), ACTION_SHIFT_X , { -50 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_plus ), ACTION_SET_SLIDESHOW_INTERVAL_RELATIVE , { .pdouble = 1. }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Add ), ACTION_SET_SLIDESHOW_INTERVAL_RELATIVE , { .pdouble = 1. }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_MOD1_MASK , GDK_KEY_KP_Add ), ACTION_ANIMATION_SET_SPEED_RELATIVE , { .pdouble = 1.1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_MOD1_MASK , GDK_KEY_plus ), ACTION_ANIMATION_SET_SPEED_RELATIVE , { .pdouble = 1.1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_plus ), ACTION_SET_SCALE_LEVEL_RELATIVE , { .pdouble = 1.1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Add ), ACTION_SET_SCALE_LEVEL_RELATIVE , { .pdouble = 1.1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_minus ), ACTION_SET_SLIDESHOW_INTERVAL_RELATIVE , { .pdouble = -1. }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Subtract ), ACTION_SET_SLIDESHOW_INTERVAL_RELATIVE , { .pdouble = -1. }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_MOD1_MASK , GDK_KEY_minus ), ACTION_ANIMATION_SET_SPEED_RELATIVE , { .pdouble = 0.9 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_MOD1_MASK , GDK_KEY_KP_Subtract ), ACTION_ANIMATION_SET_SPEED_RELATIVE , { .pdouble = 0.9 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_minus ), ACTION_SET_SCALE_LEVEL_RELATIVE , { .pdouble = 0.9 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Subtract ), ACTION_SET_SCALE_LEVEL_RELATIVE , { .pdouble = 0.9 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_t ), ACTION_TOGGLE_SCALE_MODE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_t ), ACTION_TOGGLE_SCALE_MODE , { 4 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_MOD1_MASK , GDK_KEY_t ), ACTION_TOGGLE_SCALE_MODE , { 5 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_r ), ACTION_TOGGLE_SHUFFLE_MODE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_r ), ACTION_RELOAD , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_p ), ACTION_GOTO_EARLIER_FILE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_0 ), ACTION_RESET_SCALE_LEVEL , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_f ), ACTION_TOGGLE_FULLSCREEN , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_h ), ACTION_FLIP_HORIZONTALLY , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_v ), ACTION_FLIP_VERTICALLY , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_l ), ACTION_ROTATE_LEFT , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_k ), ACTION_ROTATE_RIGHT , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_i ), ACTION_TOGGLE_INFO_BOX , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_j ), ACTION_JUMP_DIALOG , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_m ), ACTION_MONTAGE_MODE_ENTER , { 0 }},
#ifndef CONFIGURED_WITHOUT_EXTERNAL_COMMANDS
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_o ), ACTION_TOGGLE_MARK , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_o ), ACTION_CLEAR_MARKS , { 0 }},
#endif
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_s ), ACTION_TOGGLE_SLIDESHOW , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_b ), ACTION_TOGGLE_BACKGROUND_PATTERN , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_n ), ACTION_TOGGLE_NEGATE_MODE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_a ), ACTION_HARDLINK_CURRENT_IMAGE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_period ), ACTION_ANIMATION_STEP , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_period ), ACTION_ANIMATION_CONTINUE , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_BackSpace ), ACTION_GOTO_LOGICAL_DIRECTORY_RELATIVE , { -1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_BackSpace ), ACTION_GOTO_FILE_RELATIVE , { -1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_space ), ACTION_GOTO_LOGICAL_DIRECTORY_RELATIVE , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_space ), ACTION_GOTO_FILE_RELATIVE , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_Page_Up ), ACTION_GOTO_FILE_RELATIVE , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_KP_Page_Up ), ACTION_GOTO_FILE_RELATIVE , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Page_Down ), ACTION_GOTO_FILE_RELATIVE , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Page_Up ), ACTION_GOTO_FILE_RELATIVE , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Page_Up ), ACTION_GOTO_FILE_RELATIVE , { 10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Page_Down ), ACTION_GOTO_FILE_RELATIVE , { -10 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_q ), ACTION_QUIT , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Escape ), ACTION_QUIT , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_1 ), ACTION_NUMERIC_COMMAND , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_2 ), ACTION_NUMERIC_COMMAND , { 2 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_3 ), ACTION_NUMERIC_COMMAND , { 3 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_4 ), ACTION_NUMERIC_COMMAND , { 4 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_5 ), ACTION_NUMERIC_COMMAND , { 5 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_6 ), ACTION_NUMERIC_COMMAND , { 6 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_7 ), ACTION_NUMERIC_COMMAND , { 7 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_8 ), ACTION_NUMERIC_COMMAND , { 8 }},
{ DEFAULT, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_9 ), ACTION_NUMERIC_COMMAND , { 9 }},
{ DEFAULT, KEY_BINDING_VALUE(1 , 0 , GDK_BUTTON_PRIMARY ), ACTION_GOTO_FILE_RELATIVE , { -1 }},
{ DEFAULT, KEY_BINDING_VALUE(1 , 0 , GDK_BUTTON_MIDDLE ), ACTION_QUIT , { 0 }},
{ DEFAULT, KEY_BINDING_VALUE(1 , 0 , GDK_BUTTON_SECONDARY ), ACTION_GOTO_FILE_RELATIVE , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(1 , 0 , (GDK_SCROLL_UP+1) << 2 ), ACTION_GOTO_FILE_RELATIVE , { 1 }},
{ DEFAULT, KEY_BINDING_VALUE(1 , 0 , (GDK_SCROLL_DOWN+1) << 2 ), ACTION_GOTO_FILE_RELATIVE , { -1 }},
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Down ), ACTION_MONTAGE_MODE_SHIFT_Y , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Up ), ACTION_MONTAGE_MODE_SHIFT_Y , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Left ), ACTION_MONTAGE_MODE_SHIFT_X , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Right ), ACTION_MONTAGE_MODE_SHIFT_X , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Down ), ACTION_MONTAGE_MODE_SHIFT_Y , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Up ), ACTION_MONTAGE_MODE_SHIFT_Y , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Left ), ACTION_MONTAGE_MODE_SHIFT_X , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Right ), ACTION_MONTAGE_MODE_SHIFT_X , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Page_Down ), ACTION_MONTAGE_MODE_SHIFT_Y_PG , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Page_Up ), ACTION_MONTAGE_MODE_SHIFT_Y_PG , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Page_Up ), ACTION_MONTAGE_MODE_SHIFT_Y_PG , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_KP_Page_Down ), ACTION_MONTAGE_MODE_SHIFT_Y_PG , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Home ), ACTION_GOTO_FILE_BYINDEX , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_End ), ACTION_GOTO_FILE_BYINDEX , { -1 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Return ), ACTION_MONTAGE_MODE_RETURN_PROCEED , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_Escape ), ACTION_MONTAGE_MODE_RETURN_CANCEL , { 0 }},
#ifndef CONFIGURED_WITHOUT_EXTERNAL_COMMANDS
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_o ), ACTION_TOGGLE_MARK , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , GDK_CONTROL_MASK , GDK_KEY_o ), ACTION_CLEAR_MARKS , { 0 }},
#endif
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_m ), ACTION_MONTAGE_MODE_RETURN_CANCEL , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_f ), ACTION_TOGGLE_FULLSCREEN , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_g ), ACTION_MONTAGE_MODE_FOLLOW , { .pcharptr = (char*)montage_mode_default_keys }},
{ MONTAGE, KEY_BINDING_VALUE(0 , 0 , GDK_KEY_q ), ACTION_QUIT , { 0 }},
{ MONTAGE, KEY_BINDING_VALUE(1 , 0 , (GDK_SCROLL_UP+1) << 2 ), ACTION_MONTAGE_MODE_SHIFT_Y_ROWS , { 1 }},
{ MONTAGE, KEY_BINDING_VALUE(1 , 0 , (GDK_SCROLL_DOWN+1) << 2 ), ACTION_MONTAGE_MODE_SHIFT_Y_ROWS , { -1 }},
#endif
{ DEFAULT, 0, 0, { 0 } }
};
enum context_t active_key_binding_context = DEFAULT;
enum context_t application_mode = DEFAULT;
#ifndef CONFIGURED_WITHOUT_ACTIONS
typedef struct key_binding key_binding_t;
struct key_binding {
pqiv_action_t action;
pqiv_action_parameter_t parameter;
struct key_binding *next_action; // For assinging multiple actions to one key
GHashTable *next_key_bindings; // For key sequences
};
GHashTable *key_bindings[KEY_BINDING_CONTEXTS_COUNT];
struct {
key_binding_t *key_binding;
BOSNode *associated_image;
gint timeout_id;
} active_key_binding = { NULL, NULL, -1 };
GQueue action_queue = G_QUEUE_INIT;
gint action_queue_idle_id = -1;
void help_show_single_action(key_binding_t *current_action);
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
key_binding_t follow_mode_key_binding = { ACTION_MONTAGE_MODE_FOLLOW_PROCEED, { .p2short = { -1, -1 } }, NULL, NULL };
#endif
#endif
void UNUSED_FUNCTION action_done();
const struct pqiv_action_descriptor {
const char *name;
enum { PARAMETER_INT, PARAMETER_DOUBLE, PARAMETER_CHARPTR, PARAMETER_2SHORT, PARAMETER_NONE } parameter_type;
} pqiv_action_descriptors[] = {
{ "nop", PARAMETER_NONE },
{ "shift_y", PARAMETER_INT },
{ "shift_x", PARAMETER_INT },
{ "set_slideshow_interval_relative", PARAMETER_DOUBLE },
{ "set_slideshow_interval_absolute", PARAMETER_DOUBLE },
{ "set_scale_level_relative", PARAMETER_DOUBLE },
{ "set_scale_level_absolute", PARAMETER_DOUBLE },
{ "toggle_scale_mode", PARAMETER_INT },
{ "set_scale_mode_screen_fraction", PARAMETER_DOUBLE },
{ "toggle_shuffle_mode", PARAMETER_INT },
{ "reload", PARAMETER_NONE },
{ "reset_scale_level", PARAMETER_NONE },
{ "toggle_fullscreen", PARAMETER_INT },
{ "flip_horizontally", PARAMETER_NONE },
{ "flip_vertically", PARAMETER_NONE },
{ "rotate_left", PARAMETER_NONE },
{ "rotate_right", PARAMETER_NONE },
{ "toggle_info_box", PARAMETER_NONE },
{ "jump_dialog", PARAMETER_NONE },
{ "toggle_slideshow", PARAMETER_NONE },
{ "hardlink_current_image", PARAMETER_NONE },
{ "goto_directory_relative", PARAMETER_INT },
{ "goto_logical_directory_relative", PARAMETER_INT },
{ "goto_file_relative", PARAMETER_INT },
{ "quit", PARAMETER_NONE },
{ "numeric_command", PARAMETER_INT },
{ "command", PARAMETER_CHARPTR },
{ "add_file", PARAMETER_CHARPTR },
{ "goto_file_byindex", PARAMETER_INT },
{ "goto_file_byname", PARAMETER_CHARPTR },
{ "remove_file_byindex", PARAMETER_INT },
{ "remove_file_byname", PARAMETER_CHARPTR },
{ "output_file_list", PARAMETER_NONE },
{ "set_cursor_visibility", PARAMETER_INT },
{ "set_status_output", PARAMETER_INT },
{ "set_scale_mode_fit_px", PARAMETER_2SHORT },
{ "set_shift_x", PARAMETER_INT },
{ "set_shift_y", PARAMETER_INT },
{ "bind_key", PARAMETER_CHARPTR },
{ "send_keys", PARAMETER_CHARPTR },
{ "set_shift_align_corner", PARAMETER_CHARPTR },
{ "set_interpolation_quality", PARAMETER_INT },
{ "animation_step", PARAMETER_INT },
{ "animation_continue", PARAMETER_NONE },
{ "animation_set_speed_absolute", PARAMETER_DOUBLE },
{ "animation_set_speed_relative", PARAMETER_DOUBLE },
{ "goto_earlier_file", PARAMETER_NONE },
{ "set_cursor_auto_hide", PARAMETER_INT },
{ "set_fade_duration", PARAMETER_DOUBLE },
{ "set_keyboard_timeout", PARAMETER_DOUBLE },
{ "set_thumbnail_size", PARAMETER_2SHORT },
{ "set_thumbnail_preload", PARAMETER_INT },
{ "montage_mode_enter", PARAMETER_NONE },
{ "montage_mode_shift_x", PARAMETER_INT },
{ "montage_mode_shift_y", PARAMETER_INT },
{ "montage_mode_set_shift_x", PARAMETER_INT },
{ "montage_mode_set_shift_y", PARAMETER_INT },
{ "montage_mode_set_wrap_mode", PARAMETER_INT },
{ "montage_mode_shift_y_pg", PARAMETER_INT },
{ "montage_mode_shift_y_rows", PARAMETER_INT },
{ "montage_mode_show_binding_overlays", PARAMETER_INT },
{ "montage_mode_follow", PARAMETER_CHARPTR },
{ "montage_mode_follow_proceed", PARAMETER_2SHORT },
{ "montage_mode_return_proceed", PARAMETER_NONE },
{ "montage_mode_return_cancel", PARAMETER_NONE },
{ "move_window", PARAMETER_2SHORT },
{ "toggle_background_pattern", PARAMETER_INT },
{ "toggle_negate_mode", PARAMETER_INT },
{ "toggle_mark", PARAMETER_NONE },
{ "clear_marks", PARAMETER_NONE },
{ NULL, 0 }
};
/* }}} */
typedef struct {
gint depth;
GTree *outstanding_files;
GSList *recursion_folder_stack;
gchar *base_param;
} directory_watch_options_t;
GHashTable *active_directory_watches;
void set_scale_level_to_fit();
void set_scale_level_for_screen();
#ifndef CONFIGURED_WITHOUT_INFO_TEXT
void info_text_queue_redraw();
void update_info_text(const char *);
#define UPDATE_INFO_TEXT(fmt, ...) { \
gchar *_info_text = g_strdup_printf(fmt, __VA_ARGS__);\
update_info_text(_info_text); \
g_free(_info_text); \
}
#else
#define info_text_queue_redraw(...)
#define update_info_text(...)
#define UPDATE_INFO_TEXT(fmt, ...)
#endif
void queue_draw();
gboolean main_window_center();
void window_screen_changed_callback(GtkWidget *widget, GdkScreen *previous_screen, gpointer user_data);
typedef int image_loader_purpose_t;
gboolean test_and_invalidate_thumbnail(file_t *file);
gboolean image_loader_load_single(BOSNode *node, gboolean called_from_main);
gboolean fading_timeout_callback(gpointer user_data);
void queue_image_load(BOSNode *);
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
void queue_thumbnail_load(BOSNode *);
#endif
void unload_image(BOSNode *);
void remove_image(BOSNode *);
gboolean initialize_gui_callback(gpointer);
gboolean initialize_image_loader();
void window_hide_cursor();
void window_show_cursor();
void preload_adjacent_images();
void window_center_mouse();
double calculate_scale_level_to_fit(int image_width, int image_height, int window_width, int window_height);
gboolean main_window_calculate_ideal_size(int *new_window_width, int *new_window_height);
void calculate_current_image_transformed_size(int *image_width, int *image_height);
double calculate_auto_scale_level_for_screen(int image_width, int image_height);
cairo_surface_t *get_scaled_image_surface_for_current_image();
gboolean window_state_into_fullscreen_actions(gpointer user_data);
gboolean window_state_out_of_fullscreen_actions(gpointer user_data);
gboolean window_draw_callback(GtkWidget *widget, cairo_t *cr_arg, gpointer user_data);
void window_prerender_background_pixmap(int window_width, int window_height, double scale_level, gboolean fullscreen);
void window_clear_background_pixmap();
gboolean window_show_background_pixmap_cb(gpointer user_data);
BOSNode *image_pointer_by_name(gchar *display_name);
BOSNode *relative_image_pointer(ptrdiff_t movement);
void file_tree_free_helper(BOSNode *node);
gint relative_image_pointer_shuffle_list_cmp(shuffled_image_ref_t *ref, BOSNode *node);
void relative_image_pointer_shuffle_list_unref_fn(shuffled_image_ref_t *ref);
gboolean slideshow_timeout_callback(gpointer user_data);
gboolean absolute_image_movement(BOSNode *ref);
#ifndef CONFIGURED_WITHOUT_ACTIONS
void parse_key_bindings(const gchar *bindings);
gboolean read_commands_thread_helper(gpointer command);
#endif
void recreate_window();
static void status_output();
void handle_input_event(guint key_binding_value);
void draw_current_image_to_context(cairo_t *cr);
gboolean window_auto_hide_cursor_callback(gpointer user_data);
#ifndef CONFIGURED_WITHOUT_ACTIONS
gboolean handle_input_event_timeout_callback(gpointer user_data);
void queue_action(pqiv_action_t action_id, pqiv_action_parameter_t parameter);
#endif
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
gboolean montage_window_get_move_cursor_target(int, int, int, int*, int*, int*, BOSNode **);
void montage_window_move_cursor(int, int, int);
#endif
#ifndef CONFIGURED_WITHOUT_EXTERNAL_COMMANDS
void toggle_mark();
void clear_marks();
GString *get_all_marked();
#endif
// }}}
/* Helper functions {{{ */
gboolean strv_contains(const gchar * const *strv, const gchar *str) {
#if GLIB_CHECK_VERSION(2, 44, 0)
return g_strv_contains(strv, str);
#else
while(*strv) {
if(g_strcmp0(*strv, str) == 0) {
return TRUE;
}
strv++;
}
return FALSE;
#endif
}
/* }}} */
/* Command line handling, creation of the image list {{{ */
gboolean options_background_pattern_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(strcasecmp(value, "checkerboard") == 0) {
option_background_pattern = CHECKERBOARD;
}
else if(strcasecmp(value, "black") == 0) {
option_background_pattern = BLACK;
}
else if(strcasecmp(value, "white") == 0) {
option_background_pattern = WHITE;
}
else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --background-pattern option. Allowed values are BLACK, WHITE and CHECKERBOARD.");
return FALSE;
}
return TRUE;
}/*}}}*/
#ifndef CONFIGURED_WITHOUT_ACTIONS /* option --without-actions: Do not include support for configurable key/mouse bindings and actions */
gboolean options_bind_key_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
// Format for value:
// {key sequence description, special keys as <name>} { {action}({parameter});[...] } [...]
//
// Special names are: <Shift>, <Control>, <Alt> (GDK_MOD1_MASK), <Mouse-%d> and any other must be fed to gdk_keyval_from_name
// String parameters must be given in quotes
// To set an error:
// g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "The argument to the alias option must have a multiple of two characters: Every odd one is mapped to the even one following it.");
//
parse_key_bindings(value);
return TRUE;
}/*}}}*/
#endif
#ifndef CONFIGURED_WITHOUT_MONTAGE_MODE
gboolean option_thumbnail_size_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
gchar *second;
option_thumbnails.width = g_ascii_strtoll(value, &second, 10);
if(second != value && (*second == 'x' || *second == ',')) {
option_thumbnails.height = g_ascii_strtoll(second + 1, &second, 10);
if(*second == 0) {
return TRUE;
}
}
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --thumbnail-size option. Format must be e.g. `320x240'.");
return FALSE;
}/*}}}*/
gboolean option_thumbnail_preload_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
option_thumbnails.enabled = 1;
option_thumbnails.auto_generate_for_adjacents = g_ascii_strtoll(value, NULL, 10);
if(errno == EINVAL || errno == ERANGE) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --thumbnail-preload option.");
return FALSE;
}
return TRUE;
}/*}}}*/
gboolean option_thumbnail_persistence_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(option_thumbnails.special_thumbnail_directory != NULL) {
g_free(option_thumbnails.special_thumbnail_directory);
option_thumbnails.special_thumbnail_directory = NULL;
}
if(value == NULL || !*value || strcasecmp(value, "yes") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "on") == 0) {
option_thumbnails.persist = THUMBNAILS_PERSIST_ON;
}
else if(strcasecmp(value, "read-only") == 0) {
option_thumbnails.persist = THUMBNAILS_PERSIST_RO;
}
else if(strcasecmp(value, "standard") == 0) {
option_thumbnails.persist = THUMBNAILS_PERSIST_STANDARD;
}
else if(strcasecmp(value, "no") == 0 || strcasecmp(value, "false") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "off") == 0) {
option_thumbnails.persist = THUMBNAILS_PERSIST_OFF;
}
else if(strcasecmp(value, "local") == 0) {
option_thumbnails.persist = THUMBNAILS_PERSIST_LOCAL;
}
else if(value[0] == '/') {
option_thumbnails.persist = THUMBNAILS_PERSIST_ON;
option_thumbnails.special_thumbnail_directory = g_strdup(value);
}
else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --thumbnail-persistence option.");
return FALSE;
}
return TRUE;
}/*}}}*/
#endif
gboolean option_window_position_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(strcmp(value, "off") == 0) {
option_window_position.x = option_window_position.y = -1;
return TRUE;
}
gchar *second;
option_window_position.x = g_ascii_strtoll(value, &second, 10);
if(second != value && *second == ',') {
option_window_position.y = g_ascii_strtoll(second + 1, &second, 10);
if(*second == 0) {
return TRUE;
}
}
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the -P option. Allowed formats are: `x,y' and `off'.");
return FALSE;
}/*}}}*/
gboolean option_scale_level_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(g_strcmp0(option_name, "-t") == 0 || g_strcmp0(option_name, "--scale-images-up") == 0) {
option_scale = AUTO_SCALEUP;
}
else {
option_scale = NO_SCALING;
}
return TRUE;
}/*}}}*/
gboolean option_watch_files_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(strcmp(value, "off") == 0) {
option_watch_files = OFF;
}
else if(strcmp(value, "on") == 0) {
option_watch_files = ON;
}
else if(strcmp(value, "changes-only") == 0) {
option_watch_files = CHANGES_ONLY;
}
else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --watch-files option. Allowed values are: on, off and changes-only.");
return FALSE;
}
return TRUE;
}/*}}}*/
gboolean option_end_of_files_action_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
if(strcmp(value, "quit") == 0) {
option_end_of_files_action = QUIT;
}
else if(strcmp(value, "wait") == 0) {
option_end_of_files_action = WAIT;
}
else if(strcmp(value, "wrap") == 0) {
option_end_of_files_action = WRAP;
}
else if(strcmp(value, "wrap-no-reshuffle") == 0) {
option_end_of_files_action = WRAP_NO_RESHUFFLE;
}
else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Unexpected argument value for the --end-of-files-action option. Allowed values are: quit, wait, wrap (default) and wrap-no-reshuffle.");
return FALSE;
}
return TRUE;
}/*}}}*/
#if !defined(CONFIGURED_WITHOUT_INFO_TEXT) || !defined(CONFIGURED_WITHOUT_MONTAGE_MODE)
gboolean option_box_colors_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) {/*{{{*/
// Parse manually rather than with sscanf to have the flexibility to use
// hex notation without doing black magic
unsigned char pos;
for(pos=0; pos < 6 && value && *value; pos++) {
while(*value == ' ') {
value++;
}
if(pos % 3 == 0 && *value == '#') {
value++;