ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/CV/bin/cv
Revision: 1.101
Committed: Sat Sep 26 22:39:08 2020 UTC (6 years ago) by root
Branch: MAIN
CVS Tags: rel-1_9
Changes since 1.100: +1 -2 lines
Log Message:
*** empty log message ***

File Contents

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