--- rxvt-unicode/src/perl/background 2012/06/10 11:31:22 1.44 +++ rxvt-unicode/src/perl/background 2012/09/04 22:41:11 1.79 @@ -1,18 +1,42 @@ #! 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 - background - manage terminal background +background - manage terminal background =head1 SYNOPSIS urxvt --background-expr 'background expression' --background-border + --background-interval seconds + +=head1 QUICK AND DIRTY CHEAT SHEET + +Just load a random jpeg image and tile the background with it without +scaling or anything else: + + load "/path/to/img.jpg" + +The same, but use mirroring/reflection instead of tiling: + + mirror load "/path/to/img.jpg" + +Load an image and scale it to exactly fill the terminal window: + + scale keep { load "/path/to/img.jpg" } + +Implement pseudo-transparency by using a suitably-aligned root pixmap +as window background: + + rootalign root + +Likewise, but keep a blurred copy: + + rootalign keep { blur 10, root } =head1 DESCRIPTION @@ -28,11 +52,11 @@ For example, to load an image and scale it to the window size, you would use: - urxvt --background-expr 'scale load "/path/to/mybg.png"' + urxvt --background-expr 'scale keep { load "/path/to/mybg.png" }' Or specified as a X resource: - URxvt.background-expr: scale load "/path/to/mybg.png" + URxvt.background-expr: scale keep { load "/path/to/mybg.png" } =head1 THEORY OF OPERATION @@ -55,26 +79,29 @@ pixmap is replaced by another one the root background changes; or when the timer elapses), then the expression will be evaluated again. -For example, an expression such as C scales the -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 +For example, an expression such as C scales the 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 Expressions are normal Perl expressions, in fact, they are Perl blocks - which means you could use multiple lines and statements: - again 3600; - if (localtime now)[6]) { - return scale load "$HOME/weekday.png"; - } else { - return scale load "$HOME/sunday.png"; + scale keep { + again 3600; + if (localtime now)[6]) { + return load "$HOME/weekday.png"; + } else { + return load "$HOME/sunday.png"; + } } -This expression gets evaluated once per hour. It will set F as -background on Sundays, and F on all other days. +This inner expression is evaluated once per hour (and whenever the +terminal window is resized). It sets F as background on +Sundays, and F on all other days. Fortunately, we expect that most expressions will be much simpler, with little Perl knowledge needed. @@ -117,61 +144,108 @@ scale 0.5, 2, load "$HOME/mypic.png" -Other effects than scalign are also readily available, for exmaple, you can -tile the image to fill the whole window, instead of resizing it: +IF you try out these expressions, you might suffer from some sluggishness, +because each time the terminal is resized, it loads the PNG image again +and scales it. Scaling is usually fast (and unavoidable), but loading the +image can be quite time consuming. This is where C comes in handy: + + scale 0.5, 2, keep { load "$HOME/mypic.png" } + +The C operator executes all the statements inside the braces only +once, or when it thinks the outcome might change. In other cases it +returns the last value computed by the brace block. + +This means that the C is only executed once, which makes it much +faster, but also means that more memory is being used, because the loaded +image must be kept in memory at all times. In this expression, the +trade-off is likely worth it. - tile load "$HOME/mypic.png" +But back to effects: Other effects than scaling are also readily +available, for example, you can tile the image to fill the whole window, +instead of resizing it: -In fact, images returned by C are in C mode by default, so the C operator -is kind of superfluous. + tile keep { load "$HOME/mypic.png" } -Another common effect is to mirror the image, so that the same edges touch: +In fact, images returned by C are in C mode by default, so the +C operator is kind of superfluous. - mirror load "$HOME/mypic.png" +Another common effect is to mirror the image, so that the same edges +touch: -This is also a typical background expression: + mirror keep { load "$HOME/mypic.png" } + +Another common background expression is: rootalign root -It first takes a snapshot of the screen background image, and then -moves it to the upper left corner of the screen - the result is -pseudo-transparency, as the image seems to be static while the window is -moved around. +This one first takes a snapshot of the screen background image, and then +moves it to the upper left corner of the screen (as opposed to the upper +left corner of the terminal window)- the result is pseudo-transparency: +the image seems to be static while the window is moved around. + +=head2 COLOUR SPECIFICATIONS + +Whenever an operator expects a "colour", then this can be specified in one +of two ways: Either as string with an X11 colour specification, such as: + + "red" # named colour + "#f00" # simple rgb + "[50]red" # red with 50% alpha + "TekHVC:300/50/50" # anything goes + +OR as an array reference with one, three or four components: + + [0.5] # 50% gray, 100% alpha + [0.5, 0, 0] # dark red, no green or blur, 100% alpha + [0.5, 0, 0, 0.7] # same with explicit 70% alpha + +=head2 CACHING AND SENSITIVITY + +Since some operations (such as C and C) can take a long time, +caching results can be very important for a smooth operation. Caching can +also be useful to reduce memory usage, though, for example, when an image +is cached by C, it could be shared by multiple terminal windows +running inside urxvtd. + +=head3 C caching -=head2 CYCLES AND CACHING +The most important way to cache expensive operations is to use C. The C operator takes a block of multiple statements enclosed +by C<{}> and keeps the return value in memory. -As has been mentioned before, the expression might be evaluated multiple -times. Each time the expression is reevaluated, a new cycle is said to -have begun. Many operators cache their results till the next cycle. +An expression can be "sensitive" to various external events, such as +scaling or moving the window, root background changes and timers. Simply +using an expression (such as C without parameters) that depends on +certain changing values (called "variables"), or using those variables +directly, will make an expression sensitive to these events - for example, +using C or C will make the expression sensitive to the terminal +size, and thus to resizing events. -For example, the C operator keeps a copy of the image. If it is -asked to load the same image on the next cycle it will not load it again, -but return the cached copy. +When such an event happens, C will automatically trigger a +reevaluation of the whole expression with the new value of the expression. -This only works for one cycle though, so as long as you load the same -image every time, it will always be cached, but when you load a different -image, it will forget about the first one. +C is most useful for expensive operations, such as C: -This allows you to either speed things up by keeping multiple images in -memory, or comserve memory by loading images more often. + rootalign keep { blur 20, root } -For example, you can keep two images in memory and use a random one like -this: +This makes a blurred copy of the root background once, and on subsequent +calls, just root-aligns it. Since C is usually quite slow and +C is quite fast, this trades extra memory (for the cached +blurred pixmap) with speed (blur only needs to be redone when root +changes). - my $img1 = load "img1.png"; - my $img2 = load "img2.png"; - (0.5 > rand) ? $img1 : $img2 +=head3 C caching -Since both images are "loaded" every time the expression is evaluated, -they are always kept in memory. Contrast this version: +The C operator itself does not keep images in memory, but as long as +the image is still in memory, C will use the in-memory image instead +of loading it freshly from disk. - my $path1 = "img1.png"; - my $path2 = "img2.png"; - load ((0.5 > rand) ? $path1 : $path2) +That means that this expression: -Here, a path is selected randomly, and load is only called for one image, -so keeps only one image in memory. If, on the next evaluation, luck -decides to use the other path, then it will have to load that image again. + keep { load "$HOME/path..." } + +Not only caches the image in memory, other terminal instances that try to +C it can reuse that in-memory copy. =head1 REFERENCE @@ -191,20 +265,35 @@ 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 ($self, $frame); 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 + sub FR_PARENT() { 0 } # parent frame, if any - must be #0 + sub FR_CACHE () { 1 } # cached values + sub FR_AGAIN () { 2 } # what this expr is sensitive to + sub FR_STATE () { 3 } # watchers etc. + use List::Util qw(min max sum shuffle); =head2 PROVIDERS/GENERATORS @@ -220,20 +309,35 @@ Loads the image at the given C<$path>. The image is set to plane tiling mode. -Loaded images will be cached for one cycle. +If the image is already in memory (e.g. because another terminal instance +uses it), then the in-memory copy us returned instead. + +=item load_uc $path + +Load uncached - same as load, but does not cache the image, which means it +is I loaded from the filesystem again, even if another copy of it +is in memory at the time. =cut + sub load_uc($) { + $self->new_img_from_file ($_[0]) + } + sub load($) { my ($path) = @_; - $new->{load}{$path} = $old->{load}{$path} || $self->new_img_from_file ($path); + $_IMG_CACHE{$path} || do { + my $img = load_uc $path; + Scalar::Util::weaken ($_IMG_CACHE{$path} = $img); + $img + } } =item root Returns the root window pixmap, that is, hopefully, the background image -of your screen. The image is set to extend mode. +of your screen. This function makes your expression root sensitive, that means it will be reevaluated when the bg image changes. @@ -241,8 +345,8 @@ =cut sub root() { - $new->{rootpmap_sensitive} = 1; - die "root op not supported, exg, we need you"; + $frame->[FR_AGAIN]{rootpmap} = 1; + $self->new_img_from_root } =item solid $colour @@ -260,96 +364,64 @@ 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. +=item clone $img - move -TX, -TY, load "mybg.png" +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. -=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). +=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. The tiling mode of the first image is used as +the tiling mode of the resulting image. - 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 - -Returns the current time as (fractional) seconds since the epoch. + # rather annoyingly clumsy, but optimisation is for another time -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. + my $x0 = +1e9; + my $y0 = +1e9; + my $x1 = -1e9; + my $y1 = -1e9; - again 60; rotate TW, TH, 50, 50, (now % 86400) * -720 / 86400, scale load "myclock.png" + for (@_) { + my ($x, $y, $w, $h) = $_->geometry; -=item counter $seconds + $x0 = $x if $x0 > $x; + $y0 = $y if $y0 > $y; -Like C, but also returns an increasing counter value, starting at -0, which might be useful for some simple animation effects. + $x += $w; + $y += $h; -=cut + $x1 = $x if $x1 < $x; + $y1 = $y if $y1 < $y; + } - sub now() { urxvt::NOW } + my $base = $self->new_img (urxvt::PictStandardARGB32, $x0, $y0, $x1 - $x0, $y1 - $y0); + $base->repeat_mode ($_[0]->repeat_mode); + $base->fill ([0, 0, 0, 0]); - sub again($) { - $new->{again} = $_[0]; - } + $base->draw ($_) + for @_; - sub counter($) { - $new->{again} = $_[0]; - $self->{counter} + 0 + $base } =back @@ -392,7 +464,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" @@ -400,7 +472,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. @@ -436,22 +508,102 @@ =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 + +=item TY -Returns an exact copy of the image. +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 (that's exactly what C does btw.): + + move -TX, -TY, keep { load "mybg.png" } + +=item TW + +=item TH + +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, keep { blur 5, root } =cut - sub clone($) { - $_[0]->clone + sub TX() { $frame->[FR_AGAIN]{position} = 1; $x } + sub TY() { $frame->[FR_AGAIN]{position} = 1; $y } + sub TW() { $frame->[FR_AGAIN]{size} = 1; $w } + sub TH() { $frame->[FR_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) * -72 / 8640, scale keep { 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($) { + $frame->[FR_AGAIN]{time} = $_[0]; } + sub counter($) { + $frame->[FR_AGAIN]{time} = $_[0]; + $frame->[FR_STATE]{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 @@ -463,7 +615,7 @@ larger than the image, then the tiling mode defines how the extra pixels will be filled. -If C<$x> an C<$y> are missing, then C<0> is assumed for both. +If C<$x> and C<$y> are missing, then C<0> is assumed for both. If C<$width> and C<$height> are missing, then the window size will be assumed. @@ -471,7 +623,7 @@ Example: load an image, blur it, and clip it to the window size to save memory. - clip blur 10, load "mybg.png" + clip keep { blur 10, load "mybg.png" } =cut @@ -554,6 +706,17 @@ 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 @@ -562,6 +725,10 @@ the terminal window (or the box specified by C<$width> and C<$height> if given). +Example: load an image and center it. + + center keep { pad load "mybg.png" } + =item rootalign $img Moves the image so that it appears glued to the screen as opposed to the @@ -571,12 +738,12 @@ Example: load a background image, put it in mirror mode and root align it. - rootalign mirror load "mybg.png" + rootalign keep { mirror load "mybg.png" } 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 @@ -586,10 +753,18 @@ $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 = $_[0] || TH; + my $h = $_[1] || TH; move 0.5 * ($w - $img->w), 0.5 * ($h - $img->h), $img } @@ -598,6 +773,52 @@ move -TX, -TY, $_[0] } +=item rotate $center_x, $center_y, $degrees, $img + +Rotates the image clockwise by C<$degrees> degrees, around the point at +C<$center_x> and C<$center_y> (specified as factor of image width/height). + +Example: rotate the image by 90 degrees around it's center. + + rotate 0.5, 0.5, 90, keep { load "$HOME/mybg.png" } + +=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 tint $color, $img + +Tints the image in the given colour. + +Example: tint the image red. + + tint "red", load "rgb.png" + +Example: the same, but specify the colour by component. + + tint [1, 0, 0], load "rgb.png" + +=cut + + sub tint($$) { + $_[1]->tint ($_[0]) + } + =item contrast $factor, $img =item contrast $r, $g, $b, $img @@ -606,9 +827,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 @@ -616,14 +846,27 @@ 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. + +You can also try the experimental(!) C operator. + =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); @@ -634,14 +877,34 @@ 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); $img } +=item muladd $mul, $add, $img # EXPERIMENTAL + +First multipliesthe pixels by C<$mul>, then adds C<$add>. This cna be used +to implement brightness and contrast at the same time, with a wider value +range than contrast and brightness operators. + +Due to numerous bugs in XRender implementations, it can also introduce a +number of visual artifacts. + +Example: increase contrast by a factor of C<$c> without changing image +brightness too much. + + muladd $c, (1 - $c) * 0.5, $img + +=cut + + sub muladd($$$) { + $_[2]->muladd ($_[0], $_[1]) + } + =item blur $radius, $img =item blur $radius_horz, $radius_vert, $img @@ -661,29 +924,79 @@ $img->blur ($_[0], @_ >= 2 ? $_[1] : $_[0]) } -=item rotate $new_width, $new_height, $center_x, $center_y, $degrees +=back + +=head2 OTHER STUFF + +Anything that didn't fit any of the other categories, even after applying +force and closing our eyes. + +=over 4 + +=item keep { ... } + +This operator 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 when the outcome +changes - on other calls the C simply returns the image it computed +previously (yes, it should only be used with images). Or in other words, +C I the result of the code block so it doesn't need to be +computed again. + +This can be extremely useful to avoid redoing slow operations - 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. + +Another example is C, which can be quite slow. -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), generating a new image with width C<$new_width> and height -C<$new_height>. +In fact, urxvt itself encloses the whole expression in some kind of +C block so it only is reevaluated as required. -#TODO# new width, height, maybe more operators? +Putting the blur into a C block will make sure the blur is only done +once, while the C is still done each time the window moves. -Example: rotate the image by 90 degrees + rootalign keep { blur 10, root } + +This leaves the question of how to force reevaluation of the block, +in case the root background changes: If expression inside the block +is sensitive to some event (root background changes, window geometry +changes), then it will be reevaluated automatically as needed. =cut - sub rotate($$$$$$) { - my $img = pop; - $img->rotate ( - $_[0], - $_[1], - $_[2] * $img->w, - $_[3] * $img->h, - $_[4] * (3.14159265 / 180), - ) - } + sub keep(&) { + my $id = $_[0]+0; + + local $frame = $self->{frame_cache}{$id} ||= [$frame]; + + unless ($frame->[FR_CACHE]) { + $frame->[FR_CACHE] = [ $_[0]() ]; + + my $self = $self; + my $frame = $frame; + Scalar::Util::weaken $frame; + $self->compile_frame ($frame, sub { + # clear this frame cache, also for all parents + for (my $frame = $frame; $frame; $frame = $frame->[0]) { + undef $frame->[FR_CACHE]; + } + + $self->recalculate; + }); + }; + + # in scalar context we always return the first original result, which + # is not quite how perl works. + wantarray + ? @{ $frame->[FR_CACHE] } + : $frame->[FR_CACHE][0] + } + +# sub keep_clear() { +# delete $self->{frame_cache}; +# } =back @@ -692,7 +1005,12 @@ } sub parse_expr { - my $expr = eval "sub {\npackage urxvt::bgdsl;\n#line 0 'background expression'\n$_[0]\n}"; + my $expr = eval + "sub {\n" + . "package urxvt::bgdsl;\n" + . "#line 0 'background expression'\n" + . "$_[0]\n" + . "}"; die if $@; $expr } @@ -701,10 +1019,59 @@ sub set_expr { my ($self, $expr) = @_; + $self->{root} = []; # the outermost frame $self->{expr} = $expr; $self->recalculate; } +# takes a hash of sensitivity indicators and installs watchers +sub compile_frame { + my ($self, $frame, $cb) = @_; + + my $state = $frame->[urxvt::bgdsl::FR_STATE] ||= {}; + my $again = $frame->[urxvt::bgdsl::FR_AGAIN]; + + # don't keep stuff alive + Scalar::Util::weaken $state; + + if ($again->{nested}) { + $state->{nested} = 1; + } else { + delete $state->{nested}; + } + + if (my $interval = $again->{time}) { + $state->{time} = [$interval, urxvt::timer->new->after ($interval)->interval ($interval)] + if $state->{time}[0] != $interval; + + # callback *might* have changed, although we could just rule that out + $state->{time}[1]->cb (sub { + ++$state->{counter}; + $cb->(); + }); + } else { + delete $state->{time}; + } + + if ($again->{position}) { + $state->{position} = $self->on (position_change => $cb); + } else { + delete $state->{position}; + } + + if ($again->{size}) { + $state->{size} = $self->on (size_change => $cb); + } else { + delete $state->{size}; + } + + if ($again->{rootpmap}) { + $state->{rootpmap} = $self->on (rootpmap_change => $cb); + } else { + delete $state->{rootpmap}; + } +} + # evaluate the current bg expression sub recalculate { my ($arg_self) = @_; @@ -722,68 +1089,34 @@ # set environment to evaluate user expression - local $self = $arg_self; + local $self = $arg_self; + local $HOME = $ENV{HOME}; + local $frame = $self->{root}; - local $HOME = $ENV{HOME}; - local $old = $self->{state}; - local $new = my $state = $self->{state} = {}; - - ($x, $y, $w, $h) = - $self->background_geometry ($self->{border}); + ($x, $y, $w, $h) = $self->background_geometry ($self->{border}); # evaluate user expression - my $img = eval { $self->{expr}->() }; - warn $@ if $@;#d# - die if !UNIVERSAL::isa $img, "urxvt::img"; + my @img = eval { $self->{expr}->() }; + die $@ if $@; + die "background-expr did not return anything.\n" unless @img; + die "background-expr: expected image(s), got something else.\n" + if grep { !UNIVERSAL::isa $_, "urxvt::img" } @img; + + my $img = urxvt::bgdsl::merge @img; - $state->{size_sensitive} = 1 + $frame->[FR_AGAIN]{size} = 1 if $img->repeat_mode != urxvt::RepeatNormal; # if the expression is sensitive to external events, prepare reevaluation then - - my $repeat; - - if (my $again = $state->{again}) { - $repeat = 1; - my $self = $self; - $state->{timer} = $again == $old->{again} - ? $old->{timer} - : urxvt::timer->new->after ($again)->interval ($again)->cb (sub { - ++$self->{counter}; - $self->recalculate - }); - } - - if (delete $state->{position_sensitive}) { - $repeat = 1; - $self->enable (position_change => sub { $_[0]->recalculate }); - } else { - $self->disable ("position_change"); - } - - if (delete $state->{size_sensitive}) { - $repeat = 1; - $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 }); - } else { - $self->disable ("rootpmap_change"); - } + $self->compile_frame ($frame, sub { $arg_self->recalculate }); # clear stuff we no longer need - %$old = (); - - unless ($repeat) { - delete $self->{state}; - delete $self->{expr}; - } +# unless (%{ $frame->[FR_STATE] }) { +# delete $self->{state}; +# delete $self->{expr}; +# } # set background pixmap @@ -795,11 +1128,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"); () }