ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/CV/bin/cv
Revision: 1.86
Committed: Thu Apr 4 08:52:57 2013 UTC (13 years, 6 months ago) by root
Branch: MAIN
Changes since 1.85: +3 -0 lines
Log Message:
quick ae::fork hack

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