File Coverage

blib/lib/X11/Muralis.pm
Criterion Covered Total %
statement 23 266 8.6
branch 0 126 0.0
condition 0 62 0.0
subroutine 8 23 34.7
pod 15 15 100.0
total 46 492 9.3


line stmt bran cond sub pod time code
1             package X11::Muralis;
2 1     1   21021 use strict;
  1         3  
  1         27  
3 1     1   5 use warnings;
  1         1  
  1         24  
4 1     1   14 use 5.8.3;
  1         3  
5              
6             =head1 NAME
7              
8             X11::Muralis - Perl module to display wallpaper on your desktop.
9              
10             =head1 VERSION
11              
12             This describes version B<0.1001> of X11::Muralis.
13              
14             =cut
15              
16             our $VERSION = '0.1001';
17              
18             =head1 SYNOPSIS
19              
20             use X11::Muralis;
21              
22             my $obj = X11::Muralis->new(%args);
23              
24             =head1 DESCRIPTION
25              
26             The X11::Muralis module (and accompanying script, 'muralis') displays a
27             given image file on the desktop background (that is, the root window) of
28             an X-windows display.
29              
30             This tries to determine what size would best suit the image; whether to
31             show it fullscreen or normal size, whether to show it tiled or centred
32             on the screen. Setting the options overrides this behaviour.
33              
34             One can also repeat the display of the last-displayed image, changing the
35             display options as one desires.
36              
37             This uses an external program (xloadimage, xsri, or feh) to display
38             the image file.
39              
40             This also depends on xwininfo to get information about the root window.
41              
42             =head2 The Name
43              
44             The name "muralis" comes from the Latin "muralis" which is the word from
45             which "mural" was derived. I just thought it was a cool name for a
46             wallpaper script.
47              
48             =cut
49              
50 1     1   1241 use Image::Info;
  1         1586  
  1         50  
51 1     1   6 use File::Basename;
  1         2  
  1         95  
52 1     1   838 use File::Find::Rule;
  1         8355  
  1         7  
53 1     1   674 use X11::Muralis::Backend;
  1         3  
  1         37  
54 1     1   759 use Module::Pluggable instantiate => 'new', search_path => 'X11::Muralis::Backend', sub_name => 'backends';
  1         17042  
  1         8  
55              
56             =head1 METHODS
57              
58             =head2 new
59              
60             Create a new object, setting global values for the object.
61              
62             my $obj = X11::Muralis->new(
63             config_dir=>"$ENV{HOME}/.muralis",
64             is_image => qr/.(gif|jpeg|jpg|tiff|tif|png|pbm|xwd|pcx|gem|xpm|xbm)/i,
65             );
66              
67             =cut
68              
69             sub new {
70 0     0 1   my $class = shift;
71 0           my %parameters = (
72             config_dir => "$ENV{HOME}/.muralis",
73             is_image => qr/.(gif|jpeg|jpg|tiff|tif|png|pbm|xwd|pcx|gem|xpm|xbm)/i,
74             imgcmd => 'xloadimage',
75             @_
76             );
77 0   0       my $self = bless ({%parameters}, ref ($class) || $class);
78 0           return ($self);
79             } # new
80              
81             =head2 get_backends
82              
83             my @backends = $obj->list_backends();
84              
85             Return which backends are available.
86              
87             =cut
88             sub get_backends($) {
89 0     0 1   my $self = shift;
90              
91 0           my @avail_backends = ();
92 0           my @backends = $self->backends();
93 0           foreach my $be (@backends)
94             {
95 0 0         if ($be->active())
96             {
97 0           push @avail_backends, X11::Muralis::Backend::name($be);
98             }
99             }
100 0           return @avail_backends;
101             } # get_backends
102              
103             =head2 list_backends
104              
105             $obj->list_backends();
106              
107             List which backends are available.
108              
109             =cut
110             sub list_backends($) {
111 0     0 1   my $self = shift;
112              
113 0           my @backends = $self->get_backends();
114 0           print join("\n", @backends);
115 0           print "\n";
116             } # list_backends
117              
118             =head2 list_images
119              
120             $dr->list_images();
121              
122             $dr->list_images(match=>'animals',
123             list=>'fullname');
124              
125             List all the images which match the match-string.
126             (prints to STDOUT)
127              
128             Arguments:
129              
130             =over
131              
132             =item match => I
133              
134             Limit the images which match the given string.
135              
136             =item listformat => I
137              
138             Give the list format. If not defined or empty or "normal", will do a "normal"
139             listing, which gives the directory names followed by the files.
140             If 'fullname' then it will list all the files with their full names
141             (and doesn't list the directory names).
142              
143             =item outfile => I
144              
145             Print the list to the given file rather than to STDOUT.
146              
147             =back
148              
149             =cut
150             sub list_images {
151 0     0 1   my $self = shift;
152 0           my %args = (@_);
153              
154 0           my @files = $self->get_image_files(%args);
155              
156 0           my $count = 0;
157 0           my $fh = \*STDOUT;
158 0 0 0       if ($args{outfile} and $args{outfile} ne '-')
159             {
160             open $fh, ">", $args{outfile}
161 0   0       || die "Cannot open '$args{outfile}' for writing";
162             }
163 0 0 0       if ($args{listformat} and $args{listformat} =~ /full/i)
164             {
165 0           print $fh join("\n", @files);
166 0           print $fh "\n";
167             }
168             else
169             {
170 0           my $this_dir = '';
171 0           foreach my $file (@files)
172             {
173 0           my ($shortfile,$dir,$suffix) = fileparse($file,'');
174 0           $dir =~ s#/$##;
175 0 0         if ($dir ne $this_dir)
176             {
177 0           print $fh "${dir}:\n";
178 0           $this_dir = $dir;
179             }
180 0           print $fh $shortfile;
181 0           print $fh "\n";
182             }
183             }
184 0 0 0       if ($args{outfile} and $args{outfile} ne '-')
185             {
186 0           close $fh;
187             }
188 0           $count;
189             }
190              
191             =head2 provides
192              
193             my %prov = $obj->provides($backend_name);
194              
195             What does this backend provide?
196              
197             =cut
198             sub provides($$) {
199 0     0 1   my $self = shift;
200 0           my $backend_name = shift;
201              
202 0           my @backends = $self->backends();
203 0           foreach my $be (@backends)
204             {
205 0 0         if (X11::Muralis::Backend::name($be) eq $backend_name)
206             {
207 0           return $be->provides();
208             }
209             }
210 0           return ();
211             } # provides
212              
213             =head2 display_image
214              
215             $obj->display_image(%args);
216              
217             Arguments:
218              
219             =over
220              
221             =item center=>1
222              
223             Centre the image on the root window.
224              
225             =item colors=>I
226              
227             Limit the number of colours used to display the image. This is useful
228             for a 256-colour display.
229              
230             =item fullscreen=>1
231              
232             The image will be zoomed to fit the size of the screen.
233              
234             =item match=>I
235              
236             If using the --list or --random options, limit the image(s) to those
237             which match the string.
238              
239             =item random=>1
240              
241             Pick a random image to display. If --match is given, limit
242             the selection to images in directories which match the match-string.
243              
244             =item repeat_last=>1
245              
246             Display the last image which was displayed. This is useful to
247             re-display an image while overriding the default display options.
248              
249             =item option=>I
250              
251             Additional option or options to pass on to the backend.
252              
253             =item tile=>1
254              
255             Tile the image to fill the root window.
256              
257             =item use=>I
258              
259             Use the given backend.
260              
261             =item verbose=>1
262              
263             Print informational messages.
264              
265             =item zoom=>I
266              
267             Enlarge or reduce the size of the image by the given percent.
268              
269             =back
270              
271             =cut
272             sub display_image {
273 0     0 1   my $self = shift;
274 0           my %args = (
275             @_
276             );
277              
278 0           my $filename = '';
279 0           undef $self->{_files};
280 0 0         if ($args{random}) # get a random file
    0          
    0          
281             {
282 0           $filename = $self->get_random_file(%args);
283             }
284             elsif ($args{nth}) # get nth file (counting from 1)
285             {
286 0           $filename = $self->find_nth_file($args{nth}, %args);
287             }
288             elsif ($args{repeat_last}) # repeat the last image
289             {
290 0           my $cdir = $self->{config_dir};
291 0 0         if (-f "$cdir/last")
292             {
293 0 0         open(LIN, "$cdir/last") || die "Cannot open $cdir/last";
294 0           $filename = ;
295 0           close(LIN);
296 0           $filename =~ s/\n//;
297 0           $filename =~ s/\r//;
298             }
299             }
300 0 0         if (!$filename)
301             {
302 0           $filename = $args{filename};
303             }
304              
305 0           my ($fullname, $opt_ref) = $self->get_display_options($filename, %args);
306 0           my $backend_name = $args{use};
307 0           my @backends = $self->backends();
308 0           foreach my $be (@backends)
309             {
310 0 0         if (X11::Muralis::Backend::name($be) eq $backend_name)
311             {
312 0           $be->display($fullname, %{$opt_ref});
  0            
313 0           last;
314             }
315             }
316 0           $self->save_last_displayed($fullname, %args);
317             } # display_image
318              
319             =head1 Private Methods
320              
321             =head2 count_images
322              
323             my $count = $dr->count_images();
324              
325             my $count = $dr->count_images(match=>'animals');
326              
327             Counts all the images.
328              
329             Optional argument: match => I
330              
331             Counts the images which match the string.
332              
333             =cut
334             sub count_images ($;%) {
335 0     0 1   my $self = shift;
336 0           my %args = (@_);
337              
338 0 0 0       if (!defined $self->{_files}
339             || !$self->{_files})
340             {
341 0           my @files = $self->get_image_files(%args);
342 0           $self->{_files} = \@files;
343             }
344 0           my $files_ref = $self->{_files};
345              
346 0           my $count = @{$files_ref};
  0            
347 0           return $count;
348             } #count_images
349              
350             =head2 get_image_files
351              
352             my @files = $self->get_image_files();
353              
354             my @files = $self->get_image_files(
355             match=>$match,
356             exclude=>$exclude
357             unseen=>1);
358              
359             Get a list of matching image files.
360              
361             If 'unseen' is true, then get the file names from the ~/.muralis/unseen
362             file, if it exists.
363              
364             =cut
365             sub get_image_files {
366 0     0 1   my $self = shift;
367 0           my %args = (@_);
368              
369 0           my @files = ();
370 0           my $get_all_files = 1;
371 0           my $update_unseen = 0;
372 0           my $unseen_file = $self->{config_dir} . "/unseen";
373 0 0 0       if ($args{unseen} and -f $unseen_file)
374             {
375 0           $get_all_files = 0;
376 0 0         open(UNSEEN, "<", $unseen_file)
377             || die "Cannot read $unseen_file";
378 0           while()
379             {
380 0           chomp;
381 0           push @files, $_;
382             }
383 0           close(UNSEEN);
384             # if there are no files there
385             # then delete the file and start afresh
386 0 0         if (!@files)
387             {
388 0           unlink $unseen_file;
389 0           $get_all_files = 1;
390 0           $update_unseen = 1;
391             }
392             }
393 0 0         if ($get_all_files)
394             {
395 0 0 0       if (!defined $self->{_dirs}
396             || !$self->{_dirs})
397             {
398 0           my @dirs = $self->get_dirs(%args);
399 0           $self->{_dirs} = \@dirs;
400             }
401             @files = File::Find::Rule->file()
402             ->name($self->{is_image})
403 0           ->in(@{$self->{_dirs}});
  0            
404             }
405             # if we need to update the unseen-images file, do so
406 0 0         if ($update_unseen)
407             {
408 0 0         if (!-d $self->{config_dir})
409             {
410 0           mkdir $self->{config_dir};
411             }
412 0 0         open(LOUT, ">$unseen_file") || die "Cannot write to $unseen_file";
413 0           print LOUT join("\n", @files);
414 0           print LOUT "\n";
415 0           close LOUT;
416 0 0         if ($args{verbose})
417             {
418 0           print STDERR "updated $unseen_file\n";
419             }
420             }
421              
422 0 0 0       if ($self->{verbose} and !@files)
423             {
424 0           print STDERR "No files at all!\n";
425             }
426 0           my @ret_files = ();
427 0 0 0       if ($args{match} and $args{exclude})
    0          
    0          
428             {
429 0 0         @ret_files = grep {/$args{match}/ && !/$args{exclude}/} @files;
  0            
430             }
431             elsif ($args{match})
432             {
433 0           @ret_files = grep {/$args{match}/} @files;
  0            
434             }
435             elsif ($args{exclude})
436             {
437 0           @ret_files = grep {!/$args{exclude}/} @files;
  0            
438             }
439             else
440             {
441 0           @ret_files = @files;
442             }
443 0 0 0       if ($self->{verbose} and !@ret_files)
444             {
445 0           print STDERR "No files found.\n";
446             }
447 0           return @ret_files;
448             } #get_image_files
449              
450             =head2 get_dirs
451              
452             my @dirs = $self->get_dirs();
453              
454             Get the list of directories.
455              
456             =cut
457             sub get_dirs {
458 0     0 1   my $self = shift;
459 0           my %args = (@_);
460              
461 0           my @dirs = @{$args{dir}};
  0            
462 0 0         if ($args{recursive})
463             {
464 0           push @dirs, File::Find::Rule->directory->in(@{$args{dir}});
  0            
465             }
466 0           return @dirs;
467             } #get_dirs
468              
469             =head2 get_root_info
470              
471             Get info about the root window. This uses xwininfo.
472              
473             =cut
474              
475             sub get_root_info ($) {
476 0     0 1   my $self = shift;
477              
478 0           my $verbose = $self->{verbose};
479              
480 0           my $width = 0;
481 0           my $height = 0;
482 0           my $depth = 0;
483              
484 0           my $fh;
485 0 0         open($fh, "xwininfo -root |") || die "Cannot pipe from xwininfo -root";
486 0           while (<$fh>)
487             {
488 0 0         if (/Width/)
489             {
490 0           /Width:?\s([0-9]*)/;
491 0           $width = $1;
492             }
493 0 0         if (/Height/)
494             {
495 0           /Height:?\s([0-9]*)/;
496 0           $height = $1;
497             }
498 0 0         if (/Depth/)
499             {
500 0           /Depth:?\s([0-9]*)/;
501 0           $depth = $1;
502             }
503             }
504 0           close($fh);
505 0 0         if ($verbose)
506             {
507 0           print STDERR "SCREEN: width = $width, height = $height, depth = $depth\n";
508             }
509 0           $self->{_root_width} = $width;
510 0           $self->{_root_height} = $height;
511 0           $self->{_root_depth} = $depth;
512             }
513              
514             =head2 find_nth_file
515              
516             Find the full name of the nth (matching) file
517             starting the count from 1.
518              
519             =cut
520              
521             sub find_nth_file ($$) {
522 0     0 1   my $self = shift;
523 0           my $nth = shift;
524 0           my %args = @_;
525              
526 0 0         if ($nth <= 0)
527             {
528 0           $nth = 1;
529             }
530 0 0 0       if (!defined $self->{_files}
531             || !$self->{_files})
532             {
533 0           my @files = $self->get_image_files(%args);
534 0           $self->{_files} = \@files;
535             }
536 0           my $files_ref = $self->{_files};
537              
538 0           my $full_name = $files_ref->[$nth - 1];
539 0           return $full_name;
540             }
541              
542             =head2 get_random_file
543              
544             Get the name of a random file.
545              
546             =cut
547             sub get_random_file ($) {
548 0     0 1   my $self = shift;
549 0           my %args = @_;
550              
551 0           my $total_files = $self->count_images(%args);
552             # get a random number between 1 and the number of files
553 0           my $rnum = int(rand $total_files) + 1;
554              
555 0           my $file_name = $self->find_nth_file($rnum, %args);
556 0 0 0       if (!$file_name or ! -f $file_name)
557             {
558 0           print STDERR "NOT FOUND #$rnum (of $total_files) $file_name\n";
559             }
560              
561 0 0         if ($args{verbose})
562             {
563 0 0 0       if ($args{match} || $args{exclude})
564             {
565 0           print STDERR "picked image #${rnum} out of $total_files";
566 0 0         print STDERR " matching '$args{match}'" if $args{match};
567 0 0         print STDERR " excluding '$args{exclude}'" if $args{exclude};
568 0           print "\n";
569             }
570             else
571             {
572 0           print STDERR "picked image #${rnum} out of $total_files\n";
573             }
574             }
575              
576 0           return $file_name;
577             } # get_random_file
578              
579             =head2 find_fullname
580              
581             Find the full filename of an image file.
582              
583             =cut
584             sub find_fullname ($$;%) {
585 0     0 1   my $self = shift;
586 0           my $image_name = shift;
587 0           my %args = @_;
588              
589 0 0         if (!defined $image_name)
590             {
591 0           die "image name not defined!";
592             }
593 0           my $full_name = '';
594              
595             # first check if it's local
596 0 0         if (-f $image_name)
597             {
598 0           $full_name = $image_name;
599             }
600             else # go looking
601             {
602 0           my @files = $self->get_image_files(%args);
603            
604 0           my @match_files = grep {/$image_name/ } @files;
  0            
605 0           foreach my $ff (@match_files)
606             {
607 0 0         if (-f $ff)
608             {
609 0           $full_name = $ff;
610 0           last;
611             }
612             }
613             }
614 0           return $full_name;
615             } # find_fullname
616              
617             =head2 get_display_options
618              
619             Use the options passed in or figure out the best default options.
620             Return a string containing the options.
621              
622             $options = $obj->get_display_options($filename, %args);
623              
624             =cut
625             sub get_display_options ($$;%) {
626 0     0 1   my $self = shift;
627 0           my $filename = shift;
628 0           my %args = (
629             verbose=>0,
630             fullscreen=>undef,
631             option=>undef,
632             center=>undef,
633             tile=>0,
634             colors=>undef,
635             window=>undef,
636             zoom=>undef,
637             @_
638             );
639              
640 0 0 0       if (!defined $self->{_root_width}
641             || !$self->{_root_width})
642             {
643 0           $self->get_root_info();
644             }
645 0           my $options = '';
646              
647 0           my $fullname = $self->find_fullname($filename, %args);
648 0           my $info = Image::Info::image_info($fullname);
649 0 0         if (my $error = $info->{error})
650             {
651 0           warn "Can't parse info for $fullname: $error\n";
652 0 0         $args{fullscreen} = 0 if !defined $args{fullscreen};
653 0 0         $args{center} = 0 if !defined $args{center};
654             }
655             else
656             {
657 0 0         if ($args{verbose})
658             {
659             print STDERR "IMAGE: $filename",
660             " ", $info->{file_media_type}, " ",
661             $info->{width}, "x", $info->{height},
662             " ", $info->{color_type},
663 0           "\n";
664             }
665 0 0 0       if (defined $args{tile} and $args{tile})
666             {
667             # if we want it tiled, we don't want it fullscreen
668 0           $args{fullscreen} = 0;
669             }
670 0 0         if (!defined $args{fullscreen}) # not set
671             {
672             # default is off
673 0           $args{fullscreen} = 0;
674             # If the width and height are more than half the width
675             # and height of the screen, make it fullscreen
676             # However, if the the image is a square, it's likely to be a tile,
677             # in which case we don't want to expand it unless it's quite big
678             # Also, if one of the sides is the exact size of the screen,
679             # and the other dimension is smaller or equal to the size of the screen,
680             # we don't need to make the image fullscreen, because it already is.
681 0 0 0       if ($info->{width} == $info->{height})
    0 0        
      0        
682             {
683 0 0         if ($info->{width} > ($self->{_root_width} * 0.7))
684             {
685 0           $args{fullscreen} = 1;
686             }
687             }
688             elsif (($info->{width} > ($self->{_root_width} * 0.5))
689             && ($info->{height} > ($self->{_root_height} * 0.5))
690             && !(($info->{width} == $self->{_root_width}
691             && $info->{height} <= $self->{_root_height})
692             || ($info->{height} == $self->{_root_height}
693             && $info->{width} <= $self->{_root_width})
694             )
695             )
696             {
697 0           $args{fullscreen} = 1;
698             }
699             }
700             my $overlarge = ($info->{width} > $self->{_root_width}
701 0   0       || $info->{height} > $self->{_root_height});
702              
703             # do we want it tiled or centred?
704 0 0         if (!defined $args{center}) # not set
705             {
706             # default is off
707 0           $args{center} = 0;
708 0 0         if (!$args{fullscreen})
709             {
710             # if the width and height of the image are both
711             # close to the full screen size, don't tile the image
712 0 0 0       if (($info->{width} > ($self->{_root_width} * 0.9))
713             && ($info->{height} > ($self->{_root_height} * 0.9))
714             )
715             {
716 0           $args{center} = 1;
717             }
718             }
719             }
720             }
721              
722 0           return ($fullname, \%args);
723             } # get_display_options
724              
725             =head2 save_last_displayed
726              
727             Save the name of the image most recently displayed.
728             Also update the "unseen" file if 'unseen' is true.
729              
730             =cut
731             sub save_last_displayed ($;%) {
732 0     0 1   my $self = shift;
733 0           my $filename = shift;
734 0           my %args = (@_);
735              
736 0 0         if (!-d $self->{config_dir})
737             {
738 0           mkdir $self->{config_dir};
739             }
740 0           my $cdir = $self->{config_dir};
741 0 0         open(LOUT, ">$cdir/last") || die "Cannot write to $cdir/last";
742 0           print LOUT $filename, "\n";
743 0           close LOUT;
744 0 0         if ($args{unseen})
745             {
746             # get the current files without the match/exclude stuff
747 0           my @files = $self->get_image_files(unseen=>1);
748              
749 0           my $unseen_file = $self->{config_dir} . "/unseen";
750 0 0         open(UNSEEN, ">", $unseen_file)
751             || die "Cannot write to $unseen_file";
752 0           foreach my $file (@files)
753             {
754 0 0         if ($file ne $filename)
755             {
756 0           print UNSEEN $file, "\n";
757             }
758             }
759 0           close(UNSEEN);
760             }
761             } # save_last_displayed
762              
763             =head1 REQUIRES
764              
765             Image::Info
766             File::Basename
767             File::Find::Rule
768             Test::More
769              
770             =head1 INSTALLATION
771              
772             To install this module, run the following commands:
773              
774             perl Build.PL
775             ./Build
776             ./Build test
777             ./Build install
778              
779             Or, if you're on a platform (like DOS or Windows) that doesn't like the
780             "./" notation, you can do this:
781              
782             perl Build.PL
783             perl Build
784             perl Build test
785             perl Build install
786              
787             In order to install somewhere other than the default, such as
788             in a directory under your home directory, like "/home/fred/perl"
789             go
790              
791             perl Build.PL --install_base /home/fred/perl
792              
793             as the first step instead.
794              
795             This will install the files underneath /home/fred/perl.
796              
797             You will then need to make sure that you alter the PERL5LIB variable to
798             find the modules, and the PATH variable to find the script.
799              
800             Therefore you will need to change:
801             your path, to include /home/fred/perl/script (where the script will be)
802              
803             PATH=/home/fred/perl/script:${PATH}
804              
805             the PERL5LIB variable to add /home/fred/perl/lib
806              
807             PERL5LIB=/home/fred/perl/lib:${PERL5LIB}
808              
809              
810             =head1 SEE ALSO
811              
812             perl(1).
813              
814             =head1 BUGS
815              
816             Please report any bugs or feature requests to the author.
817              
818             =head1 AUTHOR
819              
820             Kathryn Andersen (RUBYKAT)
821             perlkat AT katspace dot com
822             http://www.katspace.org/tools/muralis
823              
824             =head1 COPYRIGHT AND LICENCE
825              
826             Copyright (c) 2005-2006 by Kathryn Andersen
827              
828             This program is free software; you can redistribute it and/or modify it
829             under the same terms as Perl itself.
830              
831             =cut
832              
833             1; # End of X11::Muralis
834             __END__