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