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