--- rxvt-unicode/src/perl/background 2012/06/08 22:21:48 1.41 +++ rxvt-unicode/src/perl/background 2012/06/15 19:50:56 1.61 @@ -1,9 +1,8 @@ #! perl #:META:X_RESOURCE:%.expr:string:background expression -#:META:X_RESOURCE:%.border.:boolean:respect the terminal border - -#TODO: once, rootalign +#:META:X_RESOURCE:%.border:boolean:respect the terminal border +#:META:X_RESOURCE:%.interval:seconds:minimum time between updates =head1 NAME @@ -13,6 +12,7 @@ urxvt --background-expr 'background expression' --background-border + --background-interval seconds =head1 DESCRIPTION @@ -59,7 +59,7 @@ image to the window size, so it relies on the window size and will be reevaluated each time it is changed, but not when it moves for example. That ensures that the picture always fills the terminal, even -after it's size changes. +after its size changes. =head2 EXPRESSIONS @@ -73,7 +73,7 @@ return scale load "$HOME/sunday.png"; } -This expression gets evaluated once per hour. It will set F as +This expression is evaluated once per hour. It will set F as background on Sundays, and F on all other days. Fortunately, we expect that most expressions will be much simpler, with @@ -101,9 +101,10 @@ Many operators also allow some parameters preceding the input image that modify its behaviour. For example, C without any additional arguments scales the image to size of the terminal window. If you specify -an additional argument, it uses it as a percentage: +an additional argument, it uses it as a scale factor (multiply by 100 to +get a percentage): - scale 200, load "$HOME/mypic.png" + scale 2, load "$HOME/mypic.png" This enlarges the image by a factor of 2 (200%). As you can see, C has now two arguments, the C<200> and the C expression, while @@ -114,9 +115,9 @@ horizontal and vertical dimensions. For example, this halves the image width and doubles the image height: - scale 50, 200, load "$HOME/mypic.png" + scale 0.5, 2, load "$HOME/mypic.png" -Other effects than scalign are also readily available, for exmaple, you can +Other effects than scaling are also readily available, for example, you can tile the image to fill the whole window, instead of resizing it: tile load "$HOME/mypic.png" @@ -152,7 +153,7 @@ image, it will forget about the first one. This allows you to either speed things up by keeping multiple images in -memory, or comserve memory by loading images more often. +memory, or conserve memory by loading images more often. For example, you can keep two images in memory and use a random one like this: @@ -190,20 +191,32 @@ Specifying this flag changes the behaviour, so that the image only replaces the background of the character area. +=item --background-interval seconds + +Since some operations in the underlying XRender extension can effectively +freeze your X-server for prolonged time, this extension enforces a minimum +time between updates, which is normally about 0.1 seconds. + +If you want to do updates more often, you can decrease this safety +interval with this switch. + =back =cut +our %_IMG_CACHE; our $HOME; our ($self, $old, $new); our ($x, $y, $w, $h); # enforce at least this interval between updates -our $MIN_INTERVAL = 1/100; +our $MIN_INTERVAL = 6/59.951; { package urxvt::bgdsl; # background language + use List::Util qw(min max sum shuffle); + =head2 PROVIDERS/GENERATORS These functions provide an image, by loading it from disk, grabbing it @@ -217,14 +230,31 @@ Loads the image at the given C<$path>. The image is set to plane tiling mode. -Loaded images will be cached for one cycle. +Loaded images will be cached for one cycle, and shared between temrinals +running in the same process (e.g. in C). + +=item load_uc $path + +Load uncached - same as load, but does not cache the image. This function +is most useufl if you want to optimise a background expression in some +way. =cut + sub load_uc($) { + my ($path) = @_; + + $_IMG_CACHE{$path} || do { + my $img = $self->new_img_from_file ($path); + Scalar::Util::weaken ($_IMG_CACHE{$path} = $img); + $img + } + } + sub load($) { my ($path) = @_; - $new->{load}{$path} = $old->{load}{$path} || $self->new_img_from_file ($path); + $new->{load}{$path} = $old->{load}{$path} || load_uc $path; } =item root @@ -238,8 +268,8 @@ =cut sub root() { - $new->{rootpmap_sensitive} = 1; - die "root op not supported, exg, we need you"; + $new->{again}{rootpmap} = 1; + $self->new_img_from_root } =item solid $colour @@ -254,103 +284,67 @@ =cut - sub solid($$;$) { + sub solid($;$$) { my $colour = pop; - my $img = $self->new_img (urxvt::PictStandardARGB32, $_[0] || 1, $_[1] || 1); + my $img = $self->new_img (urxvt::PictStandardARGB32, 0, 0, $_[0] || 1, $_[1] || 1); $img->fill ($colour); $img } -=back - -=head2 VARIABLES - -The following functions provide variable data such as the terminal window -dimensions. They are not (Perl-) variables, they jsut return stuff that -varies. Most of them make your expression sensitive to some events, for -example using C (terminal width) means your expression is evaluated -again when the terminal is resized. - -=over 4 - -=item TX - -=item TY - -Return the X and Y coordinates of the terminal window (the terminal -window is the full window by default, and the character area only when in -border-respect mode). - -Using these functions make your expression sensitive to window moves. - -These functions are mainly useful to align images to the root window. - -Example: load an image and align it so it looks as if anchored to the -background. - - move -TX, -TY, load "mybg.png" +=item clone $img -=item TW +Returns an exact copy of the image. This is useful if you want to have +multiple copies of the same image to apply different effects to. -Return the width (C) and height (C) of the terminal window (the -terminal window is the full window by default, and the character area only -when in border-respect mode). +=cut -Using these functions make your expression sensitive to window resizes. + sub clone($) { + $_[0]->clone + } -These functions are mainly useful to scale images, or to clip images to -the window size to conserve memory. +=item merge $img ... -Example: take the screen background, clip it to the window size, blur it a -bit, align it to the window position and use it as background. +Takes any number of images and merges them together, creating a single +image containing them all. - clip move -TX, -TY, blur 5, root +This function is called automatically when an expression returns multiple +images. =cut - sub TX() { $new->{position_sensitive} = 1; $x } - sub TY() { $new->{position_sensitive} = 1; $y } - sub TW() { $new->{size_sensitive} = 1; $w } - sub TH() { $new->{size_sensitive} = 1; $h } + sub merge(@) { + return $_[0] unless $#_; -=item now + # rather annoyingly clumsy, but optimisation is for another time -Returns the current time as (fractional) seconds since the epoch. + my $x0 = +1e9; + my $y0 = +1e9; + my $x1 = -1e9; + my $y1 = -1e9; -Using this expression does I make your expression sensitive to time, -but the next two functions do. + for (@_) { + my ($x, $y, $w, $h) = $_->geometry; -=item again $seconds + $x0 = $x if $x0 > $x; + $y0 = $y if $y0 > $y; -When this function is used the expression will be reevaluated again in -C<$seconds> seconds. + $x += $w; + $y += $h; -Example: load some image and rotate it according to the time of day (as if it were -the hour pointer of a clock). Update this image every minute. - - again 60; rotate TW, TH, 50, 50, (now % 86400) * -720 / 86400, scale load "myclock.png" - -=item counter $seconds + $x1 = $x if $x1 < $x; + $y1 = $y if $y1 < $y; + } -Like C, but also returns an increasing counter value, starting at -0, which might be useful for some simple animation effects. + my $base = $self->new_img (urxvt::PictStandardARGB32, $x0, $y0, $x1 - $x0, $y1 - $y0); + $base->fill ([0, 0, 0, 0]); -=cut + $base->draw ($_) + for @_; - sub now() { urxvt::NOW } - - sub again($) { - $new->{again} = $_[0]; - } - - sub counter($) { - $new->{again} = $_[0]; - $self->{counter} + 0 + $base } -=back - =head2 TILING MODES The following operators modify the tiling mode of an image, that is, the @@ -389,7 +383,7 @@ background pixels outside the image unchanged. Example: load an image and display it in the upper left corner. The rest -of the space is left "empty" (transparent or wahtever your compisotr does +of the space is left "empty" (transparent or whatever your compositor does in alpha mode, else background colour). pad load "mybg.png" @@ -397,7 +391,7 @@ =item extend $img Extends the image over the whole plane, using the closest pixel in the -area outside the image. This mode is mostly useful when you more complex +area outside the image. This mode is mostly useful when you use more complex filtering operations and want the pixels outside the image to have the same values as the pixels near the edge. @@ -433,22 +427,99 @@ =back -=head2 PIXEL OPERATORS +=head2 VARIABLE VALUES -The following operators modify the image pixels in various ways. +The following functions provide variable data such as the terminal window +dimensions. They are not (Perl-) variables, they just return stuff that +varies. Most of them make your expression sensitive to some events, for +example using C (terminal width) means your expression is evaluated +again when the terminal is resized. =over 4 -=item clone $img +=item TX -Returns an exact copy of the image. +=item TY + +Return the X and Y coordinates of the terminal window (the terminal +window is the full window by default, and the character area only when in +border-respect mode). + +Using these functions make your expression sensitive to window moves. + +These functions are mainly useful to align images to the root window. + +Example: load an image and align it so it looks as if anchored to the +background. + + move -TX, -TY, load "mybg.png" + +=item TW + +Return the width (C) and height (C) of the terminal window (the +terminal window is the full window by default, and the character area only +when in border-respect mode). + +Using these functions make your expression sensitive to window resizes. + +These functions are mainly useful to scale images, or to clip images to +the window size to conserve memory. + +Example: take the screen background, clip it to the window size, blur it a +bit, align it to the window position and use it as background. + + clip move -TX, -TY, once { blur 5, root } =cut - sub clone($) { - $_[0]->clone + sub TX() { $new->{again}{position} = 1; $x } + sub TY() { $new->{again}{position} = 1; $y } + sub TW() { $new->{again}{size} = 1; $w } + sub TH() { $new->{again}{size} = 1; $h } + +=item now + +Returns the current time as (fractional) seconds since the epoch. + +Using this expression does I make your expression sensitive to time, +but the next two functions do. + +=item again $seconds + +When this function is used the expression will be reevaluated again in +C<$seconds> seconds. + +Example: load some image and rotate it according to the time of day (as if it were +the hour pointer of a clock). Update this image every minute. + + again 60; rotate 50, 50, (now % 86400) * -720 / 86400, scale load "myclock.png" + +=item counter $seconds + +Like C, but also returns an increasing counter value, starting at +0, which might be useful for some simple animation effects. + +=cut + + sub now() { urxvt::NOW } + + sub again($) { + $new->{again}{time} = $_[0]; } + sub counter($) { + $new->{again}{time} = $_[0]; + $self->{counter} + 0 + } + +=back + +=head2 SHAPE CHANGING OPERATORS + +The following operators modify the shape, size or position of the image. + +=over 4 + =item clip $img =item clip $width, $height, $img @@ -481,31 +552,45 @@ =item scale $img -=item scale $size_percent, $img +=item scale $size_factor, $img -=item scale $width_percent, $height_percent, $img +=item scale $width_factor, $height_factor, $img -Scales the image by the given percentages in horizontal -(C<$width_percent>) and vertical (C<$height_percent>) direction. +Scales the image by the given factors in horizontal +(C<$width>) and vertical (C<$height>) direction. -If only one percentage is give, it is used for both directions. +If only one factor is give, it is used for both directions. -If no percentages are given, scales the image to the window size without +If no factors are given, scales the image to the window size without keeping aspect. =item resize $width, $height, $img Resizes the image to exactly C<$width> times C<$height> pixels. -=cut +=item fit $img + +=item fit $width, $height, $img + +Fits the image into the given C<$width> and C<$height> without changing +aspect, or the terminal size. That means it will be shrunk or grown until +the whole image fits into the given area, possibly leaving borders. -#TODO: maximise, maximise_fill? +=item cover $img + +=item cover $width, $height, $img + +Similar to C, but shrinks or grows until all of the area is covered +by the image, so instead of potentially leaving borders, it will cut off +image data that doesn't fit. + +=cut sub scale($;$;$) { my $img = pop; - @_ == 2 ? $img->scale ($_[0] * $img->w * 0.01, $_[1] * $img->h * 0.01) - : @_ ? $img->scale ($_[0] * $img->w * 0.01, $_[0] * $img->h * 0.01) + @_ == 2 ? $img->scale ($_[0] * $img->w, $_[1] * $img->h) + : @_ ? $img->scale ($_[0] * $img->w, $_[0] * $img->h) : $img->scale (TW, TH) } @@ -514,6 +599,20 @@ $img->scale ($_[0], $_[1]) } + sub fit($;$$) { + my $img = pop; + my $w = ($_[0] || TW) / $img->w; + my $h = ($_[1] || TH) / $img->h; + scale +(min $w, $h), $img + } + + sub cover($;$$) { + my $img = pop; + my $w = ($_[0] || TW) / $img->w; + my $h = ($_[1] || TH) / $img->h; + scale +(max $w, $h), $img + } + =item move $dx, $dy, $img Moves the image by C<$dx> pixels in the horizontal, and C<$dy> pixels in @@ -523,6 +622,29 @@ move 20, 30, ... +=item align $xalign, $yalign, $img + +Aligns the image according to a factor - C<0> means the image is moved to +the left or top edge (for C<$xalign> or C<$yalign>), C<0.5> means it is +exactly centered and C<1> means it touches the right or bottom edge. + +Example: remove any visible border around an image, center it vertically but move +it to the right hand side. + + align 1, 0.5, pad $img + +=item center $img + +=item center $width, $height, $img + +Centers the image, i.e. the center of the image is moved to the center of +the terminal window (or the box specified by C<$width> and C<$height> if +given). + +Example: load an image and center it. + + center pad load "mybg.png" + =item rootalign $img Moves the image so that it appears glued to the screen as opposed to the @@ -537,7 +659,7 @@ Example: take the screen background and align it, giving the illusion of transparency as long as the window isn't in front of other windows. - rootalign root + rootalign root =cut @@ -547,10 +669,55 @@ $img } + sub align($;$$) { + my $img = pop; + + move $_[0] * (TW - $img->w), + $_[1] * (TH - $img->h), + $img + } + + sub center($;$$) { + my $img = pop; + my $w = $_[0] || TW; + my $h = $_[1] || TH; + + move 0.5 * ($w - $img->w), 0.5 * ($h - $img->h), $img + } + sub rootalign($) { move -TX, -TY, $_[0] } +=item rotate $center_x, $center_y, $degrees + +Rotates the image by C<$degrees> degrees, counter-clockwise, around the +pointer at C<$center_x> and C<$center_y> (specified as factor of image +width/height). + +#TODO# new width, height, maybe more operators? + +Example: rotate the image by 90 degrees + +=cut + + sub rotate($$$$) { + my $img = pop; + $img->rotate ( + $_[0] * ($img->w + $img->x), + $_[1] * ($img->h + $img->y), + $_[2] * (3.14159265 / 180), + ) + } + +=back + +=head2 COLOUR MODIFICATIONS + +The following operators change the pixels of the image. + +=over 4 + =item contrast $factor, $img =item contrast $r, $g, $b, $img @@ -559,9 +726,18 @@ Adjusts the I of an image. -#TODO# +The first form applies a single C<$factor> to red, green and blue, the +second form applies separate factors to each colour channel, and the last +form includes the alpha channel. + +Values from 0 to 1 lower the contrast, values higher than 1 increase the +contrast. + +Due to limitations in the underlying XRender extension, lowering contrast +also reduces brightness, while increasing contrast currently also +increases brightness. -=item brightness $factor, $img +=item brightness $bias, $img =item brightness $r, $g, $b, $img @@ -569,14 +745,25 @@ Adjusts the brightness of an image. +The first form applies a single C<$bias> to red, green and blue, the +second form applies separate biases to each colour channel, and the last +form includes the alpha channel. + +Values less than 0 reduce brightness, while values larger than 0 increase +it. Useful range is from -1 to 1 - the former results in a black, the +latter in a white picture. + +Due to idiosyncrasies in the underlying XRender extension, biases less +than zero can be I slow. + =cut sub contrast($$;$$;$) { my $img = pop; my ($r, $g, $b, $a) = @_; - ($g, $b) = ($r, $r) if @_ < 4; - $a = 1 if @_ < 5; + ($g, $b) = ($r, $r) if @_ < 3; + $a = 1 if @_ < 4; $img = $img->clone; $img->contrast ($r, $g, $b, $a); @@ -587,8 +774,8 @@ my $img = pop; my ($r, $g, $b, $a) = @_; - ($g, $b) = ($r, $r) if @_ < 4; - $a = 1 if @_ < 5; + ($g, $b) = ($r, $r) if @_ < 3; + $a = 1 if @_ < 4; $img = $img->clone; $img->brightness ($r, $g, $b, $a); @@ -614,28 +801,67 @@ $img->blur ($_[0], @_ >= 2 ? $_[1] : $_[0]) } -=item rotate $new_width, $new_height, $center_x, $center_y, $degrees +=back -Rotates the image by C<$degrees> degrees, counter-clockwise, around the -pointer at C<$center_x> and C<$center_y> (specified as percentage of image -width/height), generating a new image with width C<$new_width> and height -C<$new_height>. +=head2 OTHER STUFF -#TODO# new width, height, maybe more operators? +Anything that didn't fit any of the other categories, even after applying +force and closing our eyes. -Example: rotate the image by 90 degrees +=over 4 + +=item once { ... } + +This function takes a code block as argument, that is, one or more +statements enclosed by braces. + +The trick is that this code block is only evaluated once - future calls +will simply return the original image (yes, it should only be used with +images). + +This can be extremely useful to avoid redoign the same slow operations +again and again- for example, if your background expression takes the root +background, blurs it and then root-aligns it it would have to blur the +root background on every window move or resize. + +Putting the blur into a C block will make sure the blur is only done +once: + + rootlign once { blur 10, root } + +This leaves the question of how to force reevaluation of the block, in +case the root background changes: Right now, all once blocks forget that +they ahve been executed before each time the root background changes (if +the expression is sensitive to that) or when C is called. + +=item once_again + +Resets all C block as if they had never been called, i.e. on the +next call they will be reevaluated again. =cut - sub rotate($$$$$$) { - my $img = pop; - $img->rotate ( - $_[0], - $_[1], - $_[2] * $img->w * .01, - $_[3] * $img->h * .01, - $_[4] * (3.14159265 / 180), - ) + sub once(&) { + my $once = $self->{once_cache}{$_[0]+0} ||= do { + local $new->{again}; + my @res = $_[0](); + [$new->{again}, \@res] + }; + + $new->{again} = { + %{ $new->{again} }, + %{ $once->[0] } + }; + + # in scalar context we always return the first original result, which + # is not quite how perl works. + wantarray + ? @{ $once->[1] } + : $once->[1][0] + } + + sub once_again() { + delete $self->{once_cache}; } =back @@ -686,19 +912,18 @@ # evaluate user expression - my $img = eval { $self->{expr}->() }; - warn $@ if $@;#d# - die if !UNIVERSAL::isa $img, "urxvt::img"; - - $state->{size_sensitive} = 1 - if $img->repeat_mode != urxvt::RepeatNormal; + my $img = eval { urxvt::bgdsl::merge $self->{expr}->() }; + die $@ if $@; + die "background-expr did not return an image.\n" if !UNIVERSAL::isa $img, "urxvt::img"; # if the expression is sensitive to external events, prepare reevaluation then - my $repeat; + my $again = delete $state->{again}; + + $again->{size} = 1 + if $img->repeat_mode != urxvt::RepeatNormal; - if (my $again = $state->{again}) { - $repeat = 1; + if (my $again = $again->{time}) { my $self = $self; $state->{timer} = $again == $old->{again} ? $old->{timer} @@ -708,23 +933,23 @@ }); } - if (delete $state->{position_sensitive}) { - $repeat = 1; + if ($again->{position}) { $self->enable (position_change => sub { $_[0]->recalculate }); } else { $self->disable ("position_change"); } - if (delete $state->{size_sensitive}) { - $repeat = 1; + if ($again->{size}) { $self->enable (size_change => sub { $_[0]->recalculate }); } else { $self->disable ("size_change"); } - if (delete $state->{rootpmap_sensitive}) { - $repeat = 1; - $self->enable (rootpmap_change => sub { $_[0]->recalculate }); + if ($again->{rootpmap}) { + $self->enable (rootpmap_change => sub { + delete $_[0]{once_cache}; # this will override once-block values from + $_[0]->recalculate; + }); } else { $self->disable ("rootpmap_change"); } @@ -733,7 +958,7 @@ %$old = (); - unless ($repeat) { + unless (%$again) { delete $self->{state}; delete $self->{expr}; } @@ -748,11 +973,16 @@ sub on_start { my ($self) = @_; - my $expr = $self->x_resource ("background.expr") + my $expr = $self->x_resource ("%.expr") or return; + $self->has_render + or die "background extension needs RENDER extension 0.10 or higher, ignoring background-expr.\n"; + $self->set_expr (parse_expr $expr); - $self->{border} = $self->x_resource_boolean ("background.border"); + $self->{border} = $self->x_resource_boolean ("%.border"); + + $MIN_INTERVAL = $self->x_resource ("%.interval"); () }