ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.95
Committed: Sun Sep 23 07:03:16 2018 UTC (5 years, 8 months ago) by root
Branch: MAIN
Changes since 1.94: +1 -0 lines
Log Message:
*** empty log message ***

File Contents

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