ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.102
Committed: Thu Jul 15 00:44:44 2021 UTC (2 years, 10 months ago) by root
Branch: MAIN
CVS Tags: rel-2_0
Changes since 1.101: +6 -2 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #!/opt/bin/perl
2
3 BEGIN {
4 # work around libraries (and perl) outputting numbers in weird formats
5 # when they shouldn't. this is somewhat brutal - better suggestions
6 # appreciated.
7 $ENV{LC_NUMERIC} = "C";
8 $ENV{LANG} = delete $ENV{LC_ALL} if exists $ENV{LC_ALL};
9 }
10
11 BEGIN {
12 require Gtk2::CV::Plugin;
13 require "$ENV{HOME}/.cvrc" if -r "$ENV{HOME}/.cvrc";
14 }
15
16 use common::sense;
17
18 use Cwd ();
19 use Encode ();
20 use File::Glob ();
21 use Scalar::Util ();
22
23 use Gtk2; qw(-init -threads-init); BEGIN { Gtk2::Gdk::Threads->enter };
24 use Gtk2::Gdk::Keysyms;
25
26 use Gtk2::CV;
27
28 use Gtk2::CV::ImageWindow;
29 use Gtk2::CV::Schnauzer;
30
31 use Carp (); $Carp::MaxArgLen = 256;
32
33 use Gtk2::CV::Plugin::NameCluster;
34 use Gtk2::CV::Plugin::RCluster;
35 use Gtk2::CV::Plugin::PatRenamer;
36 use Gtk2::CV::Plugin::MetaCluster;
37
38 use AnyEvent::Fork::Template;
39
40 Gtk2::CV::Jobber::set_template $AnyEvent::Fork::Template;
41
42 # now we can initialize Gtk2 etc.
43 init Gtk2;
44
45 Gtk2::Rc->parse (Gtk2::CV::find_rcfile "gtkrc");
46
47 use File::Spec;
48
49 my $mainwin;
50 my $viewer;
51 my $viewer_count;
52 my $schnauzer;
53 my $info;
54 my $help;
55
56 my $schnauzer_idx = 0;
57
58 sub new_schnauzer {
59 my $s = new Gtk2::CV::Schnauzer;
60
61 $s->signal_connect_after (key_press_event => \&std_keys);
62 $s->signal_connect (activate => sub {
63 my $label = sprintf "%s (%d)",
64 (Glib::filename_display_name +(File::Spec->splitpath ($_[1]))[2]),
65 -s $_[1];
66 $info->set_label ($label);
67 $viewer->load_image ($_[1]) if $viewer; # TODO: error, or chose ANY viewer
68 });
69
70 Gtk2::CV::Plugin->call (new_schnauzer => $s);
71
72 $s
73 }
74
75 our %VIEWER; # global viewer container so we can propagate signals
76
77 $SIG{USR1} = sub {
78 # I assume glib calls us in a safe enough context to create an idle watcher
79 add Glib::Idle sub {
80 $_->reload for values %VIEWER;
81 0
82 };
83 };
84
85 sub new_viewer {
86 my $self = new Gtk2::CV::ImageWindow;
87
88 Scalar::Util::weaken ($VIEWER{$self+0} = $self);
89
90 $viewer_count++;
91
92 $self->set_title ("CV: Image");
93
94 $self->signal_connect (key_press_event => sub {
95 $viewer = $_[0];
96
97 my $key = $_[1]->keyval;
98 my $state = $_[1]->state;
99
100 if ($state * "control-mask" && $key == $Gtk2::Gdk::Keysyms{c}) {
101 my $viewer = new_viewer ();
102 $viewer->set_image ($_[0]->{image});
103 $viewer->show_all;
104 1
105 } else {
106 &std_keys
107 or $schnauzer->signal_emit (key_press_event => $_[1])
108 }
109 });
110 $self->signal_connect (delete_event => sub { $_[0]->destroy; 0 });
111 $self->signal_connect (destroy => sub {
112 delete $VIEWER{$_[0]+0};
113 $viewer = undef if $viewer == $_[0];
114
115 main_quit Gtk2 unless --$viewer_count;
116
117 0
118 });
119
120 $self->signal_connect (button3_press_event => sub {
121 $mainwin->visible
122 ? $mainwin->hide
123 : $mainwin->show_all;
124
125 1
126 });
127
128 Gtk2::CV::Plugin->call (new_imagewindow => $self);
129
130 $self
131 }
132
133 sub std_keys {
134 my $key = $_[1]->keyval;
135 my $state = $_[1]->state;
136
137 my $ctrl = $state * "control-mask";
138
139 if ($key == $Gtk2::Gdk::Keysyms{q}) {
140 $viewer->destroy;
141 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{v}) {
142 my $w = new Gtk2::Window;
143
144 $w->set_role ("schnauzer");
145 $w->set_title ("CV: Schnauzer");
146 $w->add (my $s = new_schnauzer);
147 $s->set_dir (File::Spec->curdir);
148 $s->set_geometry_hints;
149 $w->show_all;
150
151 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{h}) {
152 unless ($help) {
153 require Gtk2::Ex::PodViewer;
154
155 $help = new Gtk2::Window;
156 $help->set_role ("help");
157 $help->set_title ("CV: Help");
158 $help->set_default_size (500, 300);
159 $help->signal_connect (delete_event => sub { $help->hide; 1 });
160
161 $help->add (my $sw = new Gtk2::ScrolledWindow);
162 $sw->add (my $h = new Gtk2::Ex::PodViewer);
163
164 #binmode DATA, ":utf8";
165 $h->load_string (do { local $/; <DATA> });
166 }
167
168 $help->show_all;
169 } elsif (!$state && $Gtk2::Gdk::Keysyms{a} <= $key && $key <= $Gtk2::Gdk::Keysyms{z}) {
170 #
171 } else {
172 return 0;
173 }
174
175 1
176 }
177
178 &cvrc_boot if defined &cvrc_boot;
179
180 {
181 $viewer = new_viewer;
182 $::cur_viewer = $viewer;
183
184 $schnauzer = new_schnauzer;
185
186 $mainwin = new Gtk2::Window;
187 $mainwin->set_role ("main");
188 $mainwin->set_title ("CV");
189 $mainwin->add (my $vbox = new Gtk2::VBox);
190 $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1 });
191
192 $vbox->add ($schnauzer);
193 $vbox->pack_end (my $frame = new Gtk2::Frame, 0, 0, 0);
194 $frame->add (my $hbox = new Gtk2::HBox 0, 0);
195 $hbox->pack_start ((new Gtk2::Label "Info: "), 0, 0, 0);
196 $hbox->pack_end (my $labelwindow = new Gtk2::EventBox, 1, 1, 0);
197 $labelwindow->add ($info = new Gtk2::Label);
198 $labelwindow->signal_connect_after (size_request => sub { $_[1]->width (0); 0 });
199 $info->set (selectable => 1, xalign => 0, justify => "left");
200
201 $schnauzer->set_geometry_hints;
202 }
203
204 if (@ARGV) {
205 my $show_first = sub {
206 $schnauzer->show_all;
207
208 # activate first file, but avoid dirs
209 my $entry = $schnauzer->{entry}[0];
210 my $path = "$entry->[0]/$entry->[1]";
211
212 $schnauzer->handle_key ($Gtk2::Gdk::Keysyms{space}, new Gtk2::Gdk::ModifierType [])
213 unless -d $path;
214
215 $viewer->show_all;
216 };
217
218 my $force_sort = $ARGV[0] eq "--sort" ? shift @ARGV : 0;
219
220 if (@ARGV == 1 && $ARGV[0] eq "-0r") {
221 local $/;
222 $schnauzer->set_paths ([split /\x00/, <STDIN>], !$force_sort, $show_first);
223 } elsif (@ARGV == 1 && -d $ARGV[0]) {
224 $schnauzer->set_dir (shift, $show_first);
225 } else {
226 if ($ARGV[0] eq "-g") {
227 shift @ARGV;
228 @ARGV = map +(File::Glob::bsd_glob $_, File::Glob::GLOB_BRACE | File::Glob::GLOB_QUOTE), @ARGV;
229 }
230 $schnauzer->set_paths ([@ARGV], !$force_sort, $show_first);
231 }
232 } else {
233 $schnauzer->set_dir (File::Spec->curdir, sub {
234 $mainwin->show_all;
235 $viewer->show_all;
236 });
237 }
238
239 &cvrc_start if defined &cvrc_start;
240
241 main Gtk2;
242
243 Gtk2::CV::flush_aio;
244
245 __DATA__
246
247 =encoding utf-8
248
249 =head1 NAME
250
251 cv - a fast gtk+ image viewer loosely modeled after XV
252
253 =head1 SYNOPSIS
254
255 cv
256
257 cv directory
258
259 cv path...
260
261 cv -g <glob expression...>
262
263 find .. -print0 | cv -0r
264
265 cv --sort ...
266
267 =head1 FEATURES
268
269 CV is supposed to work similar to the venerable XV image viewer, just
270 faster. Why faster?
271
272 =over 4
273
274 =item * optimized directory scanning algorithm
275
276 The directory scanning in CV uses some tricks that - on most modern
277 filesystems - makes it possible to detect filetypes faster than stat()'ing
278 every file. This makes CV suitable for directories with lots of files
279 (10000+).
280
281 This algorithm is quite unprecise - it doesn't make a difference between
282 files, device nodes, symlinks and the like, and filetype detection is done
283 using the file extension only.
284
285 On the positive side, it is usually many orders of magnitude faster than
286 traditional scanning techniques (good for directories with 10000 or
287 100000+ files).
288
289 =item * queuing for all time-consuming background tasks
290
291 All tasks, such as unlinking files or generating thumbnails, that can be
292 done in the background will be done so - no waiting required, even when
293 changing directories.
294
295 =item * use of asynchronous I/O
296
297 CV tries to use asynchronous I/O whereever it makes sense, for example
298 while scanning directories, waiting for stat data, unlinking files or
299 generating thumbnails. This usually decreases scanning times for large
300 directories a bit (especially on RAID devices and over NFS) and makes CV
301 much more interactive.
302
303 =item * fast image loading
304
305 The time span between the user issuing a command and displaying the new
306 image should be as small as possible. CV uses optimized (especially
307 for JPEG) loading functions and sacrifices some quality (e.g no gamma
308 correction, although this might change) to achieve this speed.
309
310 =item * fast thumbnail creation
311
312 Thumbnail creation uses both CPU and Disk-I/O. CV interleaves both, so
313 on modern CPUs, thumbnailing is usually limited by I/O speed. Thumbnail
314 creation for JPEGs has been specially optimized and can even take
315 advantage of multiple CPUs.
316
317 =item * minimum optical clutter
318
319 CV has no menus or other user interface elements that take up a lot of
320 screen space (or are useful for beginning users). The schnauzer windows
321 can also be somewhat crowded.
322
323 The point of an image viewer is viewing images, not a nice GUI. This is
324 similar to XV's behaviour.
325
326 =item * efficient (and hard to learn) user interface
327
328 CV uses key combinations. A lot. If you are an experienced XV user, you
329 will find most of these keys familiar. If not, CV might be hard to use at
330 first, but will be an efficient tool later.
331
332 =item * multi-window GUI
333
334 CV doesn't force you to use a specific layout, instead it relies on your
335 window manager, thus enabling you to chose whatever layout that suits you
336 most.
337
338 =item * i18n'ed filename handling throughout
339
340 As long as glib can recognize your filename encoding (either UTF-8 or
341 locale-specific, depending on the setting of G_BROKEN_FILENAMES) and you
342 have the relevant fonts, CV will display your filenames correctly.
343
344 =item * extensible through plug-ins
345
346 I have weird plug-ins that access remote databases to find a
347 directory. This is not likely to be of any use to other people. Likewise,
348 others might have weird requirements I cannot dream of.
349
350 =item * filename clustering
351
352 Among the standard plug-ins is a filename clustering plug-in, that (in
353 case of tens of thousands images in one directory) might be able to
354 cluster similar names together.
355
356 =back
357
358 =head1 DESCRIPTION
359
360 =head2 THE IMAGE WINDOW
361
362 You can use the following keys in the image window:
363
364 q quit the program
365 < half the image size
366 > double the image size
367 , shrink the image by ~9% (opposite of .)
368 . enlarge the image by 10%
369 n reset to normal size
370 m maximize to screensize
371 M maximize to screensize, respecting image aspect
372 ctrl-m toggle maxpect-always mode
373 ctrl-sift-m toggle using current image size as max image size
374 u uncrop
375 r set scaling mode to 'nearest' (fastest)
376 s set scaling mode to 'bilinear' (default)
377 t rotate clockwise 90°
378 T rotate counterclockwise°
379 a apply all rotations loslessly to a jpeg file (using exiftran)
380 ctrl-shift-t apply current rotation for future image loads
381 ctrl-v open a new visual schnauzer window for the current dir
382 ctrl-c clone the current image window
383 ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image
384 ctrl-p fire up the print dialog
385 ctrl-shift-p same as ctrl-p, but automatically selects "ok"
386 escape cancel a crop action
387
388 And when playing movies, these additional keys are active:
389
390 left rewind by 10 seconds
391 right forward by 10 seconds
392 down rewind by 60 seconds
393 up forward by 60 seconds
394 pg_up rewind by 600 seconds
395 pg_down forward by 600 seconds
396 o toggle on-screen display
397 p pause/unpause
398 escape stop playing
399 9 turn volume down
400 0 turn volume up
401
402 Any other keys will be sent to the default schnauzer window, which can be
403 toggled on and off by right-clicking into the image window.
404
405 Left-clicking into the image window will let you crop the image (usually
406 to zoom into large images that CV scales down).
407
408 =head2 THE VISUAL SCHNAUZER
409
410 Any image-loading action in a schnauzer window acts on the
411 "last-recently-activated" imagewindow, which currently is simply the last
412 image window that received a keypress.
413
414 You can use the following keys in the schnauzer window:
415
416 ctrl-space,
417 space move to and display next image
418 ctrl-backspace,
419 backspace move to and display previous image
420 ctrl-return,
421 return display selected picture, or enter directory
422
423 cursor keys move selection
424 page-up move one page up
425 page-down move one page down
426 home move to first file
427 end move to last file
428
429 ctrl-a select all files
430 ctrl-shift-a select all files currently displayed in the schnauzer window
431 ctrl-d delete selected files WITHOUT ASKING AGAIN
432 ctrl-g force generation of thumbnails for the selected files
433 ctrl-shift-g remove thumbnails for the selected files
434 ctrl-s rescan current direcory or files updates/deletes etc.
435 ctrl-u update selected (or all) icons if neccessary
436 ctrl-- unselected thumbnailed images
437 ctrl-+ keep only thumbnailed images, deselect others
438
439 ^ go to parent directory (caret).
440
441 0-9,
442 a-z find the first filename beginning with this letter
443
444 Right-clicking into the schnauzer window displays a pop-up menu with
445 additional actions.
446
447 =head3 SELECTION
448
449 You can select entries in the Schnauzer in a variety of ways:
450
451 =over 4
452
453 =item Keyboard
454
455 Moving the cursor with the keyboard will first deselect all files and then
456 select the file you moved to.
457
458 =item Clicking
459
460 Clicking on an entry will select the one you clicked and deselect all
461 others.
462
463 =item Shift-Clicking
464
465 Shift-clicking will toggle the selection on the entry under the mouse.
466
467 =item Dragging
468
469 Dragging will select all entries between the one selected when pushing the
470 button and the one selected when releasing the button. If you move above
471 or below the schnauzer area while drag-selecting, the schnauzer will move
472 up/down one row twice per second. In addition, horizontal mouse movement
473 acts as a kind of invisible horizontal scrollbar.
474
475 =item Hint: double-click works while click-selecting
476
477 You can double-click any image while click-selecting to display it
478 without stopping the selection process. This will act as if you normally
479 double-clicked the image to display it, and will toggle the selection
480 twice, resulting in no change.
481
482 =back
483
484 =head1 FILES
485
486 When starting, CV runs the F<.cvrc> file in your F<$HOME> directory as if
487 it were a perl script. in that, you will mostly load plug-ins.
488
489 Example:
490
491 system "fping -q -t 10 ether"
492 or require "/fs/cv/cvplugin.pl";
493
494 This will load a plug-in, but only if the machine I<ether> is reachable
495 (supposedly the plug-in is networked in some way :).
496
497 =head1 ENVIRONMENT VARIABLES
498
499 =over 4
500
501 =item CV_EDITOR
502
503 The program that gets executed when the user presses C<CTRL-e> in the
504 Schnauzer or image window. The default is C<gimp>.
505
506 =item CV_AUDIO_PLAYER
507
508 EXPERIMENTAL: audio playback is now via mpv, this variable is currently
509 ignored.
510
511 Program used to play all sorts of audio (wav, aif, mp3, ogg...), default "play".
512 Will be called like C<< $CV_AUDIO_PLAYER -- <path> >>.
513
514 =item CV_MPLAYER
515
516 Program used to play all sorts of video files. Unlike C<CV_AUDIO_PLAYER>,
517 this really must be some version of the C<mpv> programs, or something that
518 is very command-line compatible to them.
519
520 Note: for video-thumbnailing, mplayer is still used (and hardcoded).
521
522 =item CV_PRINT_DESTINATION
523
524 The default (perl-style) destination to use in the print dialog.
525
526 =item CV_TRASHCAN
527
528 When set, must point to a directory where all files that are deleted by
529 the "Delete Physically" (ctrl-d) action are moved to (other deletion
530 actions still delete!). If unset, files that are deleted are really being
531 deleted.
532
533 =back
534
535 =head1 SIGNALS
536
537 Sending CV a SIGUSR1 signal will cause all image viewers to reload the
538 currently loaded image. This is useful if you use CV as a viewer for
539 changing data - just run it in the background with some path and each time
540 the image changes, send it a SIGUSR1.
541
542 =head1 SECURITY CONSIDERATIONS
543
544 CV uses Pixbuf to load non-JPEG images. Pixbuf is not considered safe for
545 this purpose, though (from the gtk-2.2 release notes):
546
547 "While efforts have been made to make gdk-pixbuf robust against invalid
548 images, using gdk-pixbuf to load untrusted data is not recommended, due to
549 the likelyhood that there are additional problems where an invalid image
550 could cause gdk-pixbuf to crash or worse."
551
552 =head1 BUGS/TODO
553
554 Lots of functionality is missing.
555
556 Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with
557 names incompatible with utf-8 might fail.
558
559 rotate on disk
560 lots of ui issues
561 save(?)
562 preferences
563
564 =head1 AUTHOR
565
566 Marc Lehmann <cv@plan9.de>.
567
568 =cut
569