#! perl #:META:X_RESOURCE:%.expr:string:background expression #:META:X_RESOURCE:%.border:boolean:respect the terminal border #:META:X_RESOURCE:%.interval:seconds:minimum time between updates #TODO: once, rootalign =head1 NAME background - manage terminal background =head1 SYNOPSIS urxvt --background-expr 'background expression' --background-border --background-interval seconds =head1 DESCRIPTION This extension manages the terminal background by creating a picture that is behind the text, replacing the normal background colour. It does so by evaluating a Perl expression that I the image on the fly, for example, by grabbing the root background or loading a file. While the full power of Perl is available, the operators have been design to be as simple as possible. 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"' Or specified as a X resource: URxvt.background-expr: scale load "/path/to/mybg.png" =head1 THEORY OF OPERATION At startup, just before the window is mapped for the first time, the expression is evaluated and must yield an image. The image is then extended as necessary to cover the whole terminal window, and is set as a background pixmap. If the image contains an alpha channel, then it will be used as-is in visuals that support alpha channels (for example, for a compositing manager). In other visuals, the terminal background colour will be used to replace any transparency. When the expression relies, directly or indirectly, on the window size, position, the root pixmap, or a timer, then it will be remembered. If not, then it will be removed. If any of the parameters that the expression relies on changes (when the window is moved or resized, its position or size changes; when the root 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 example. That ensures that the picture always fills the terminal, even after it's 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"; } This expression gets 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 little Perl knowledge needed. Basically, you always start with a function that "generates" an image object, such as C, which loads an image from disk, or C, which returns the root window background image: load "$HOME/mypic.png" The path is usually specified as a quoted string (the exact rules can be found in the L manpage). The F<$HOME> at the beginning of the string is expanded to the home directory. Then you prepend one or more modifiers or filtering expressions, such as C: scale load "$HOME/mypic.png" Just like a mathematical expression with functions, you should read these expressions from right to left, as the C is evaluated first, and its result becomes the argument to the C function. 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 scale factor (multiply by 100 to get a percentage): 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 C only has one argument. Arguments are separated from each other by commas. Scale also accepts two arguments, which are then separate factors for both horizontal and vertical dimensions. For example, this halves the image width and doubles the image height: 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: tile load "$HOME/mypic.png" In fact, images returned by C are in C mode by default, so the C operator is kind of superfluous. Another common effect is to mirror the image, so that the same edges touch: mirror load "$HOME/mypic.png" This is also a typical background expression: 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. =head2 CYCLES AND CACHING 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. 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. 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. This allows you to either speed things up by keeping multiple images in memory, or comserve memory by loading images more often. For example, you can keep two images in memory and use a random one like this: my $img1 = load "img1.png"; my $img2 = load "img2.png"; (0.5 > rand) ? $img1 : $img2 Since both images are "loaded" every time the expression is evaluated, they are always kept in memory. Contrast this version: my $path1 = "img1.png"; my $path2 = "img2.png"; load ((0.5 > rand) ? $path1 : $path2) 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. =head1 REFERENCE =head2 COMMAND LINE SWITCHES =over 4 =item --background-expr perl-expression Specifies the Perl expression to evaluate. =item --background-border By default, the expression creates an image that fills the full window, overwriting borders and any other areas, such as the scrollbar. 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 effetively 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 %_IMGCACHE; our $HOME; our ($self, $old, $new); our ($x, $y, $w, $h); # enforce at least this interval between updates 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 from the root screen or by simply generating it. They are used as starting points to get an image you can play with. =over 4 =item load $path Loads the image at the given C<$path>. The image is set to plane tiling mode. Loaded images will be cached for one cycle. =cut sub load($) { my ($path) = @_; $new->{load}{$path} = $old->{load}{$path} || $self->new_img_from_file ($path); } =item root Returns the root window pixmap, that is, hopefully, the background image of your screen. The image is set to extend mode. This function makes your expression root sensitive, that means it will be reevaluated when the bg image changes. =cut sub root() { $new->{rootpmap_sensitive} = 1; die "root op not supported, exg, we need you"; } =item solid $colour =item solid $width, $height, $colour Creates a new image and completely fills it with the given colour. The image is set to tiling mode. If C<$width> and C<$height> are omitted, it creates a 1x1 image, which is useful for solid backgrounds or for use in filtering effects. =cut sub solid($;$$) { my $colour = pop; my $img = $self->new_img (urxvt::PictStandardARGB32, $_[0] || 1, $_[1] || 1); $img->fill ($colour); $img } =item clone $img 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. =cut sub clone($) { $_[0]->clone } =back =head2 TILING MODES The following operators modify the tiling mode of an image, that is, the way that pixels outside the image area are painted when the image is used. =over 4 =item tile $img Tiles the whole plane with the image and returns this new image - or in other words, it returns a copy of the image in plane tiling mode. Example: load an image and tile it over the background, without resizing. The C call is superfluous because C already defaults to tiling mode. tile load "mybg.png" =item mirror $img Similar to tile, but reflects the image each time it uses a new copy, so that top edges always touch top edges, right edges always touch right edges and so on (with normal tiling, left edges always touch right edges and top always touch bottom edges). Example: load an image and mirror it over the background, avoiding sharp edges at the image borders at the expense of mirroring the image itself mirror load "mybg.png" =item pad $img Takes an image and modifies it so that all pixels outside the image area become transparent. This mode is most useful when you want to place an image over another image or the background colour while leaving all 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 in alpha mode, else background colour). pad load "mybg.png" =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 filtering operations and want the pixels outside the image to have the same values as the pixels near the edge. Example: just for curiosity, how does this pixel extension stuff work? extend move 50, 50, load "mybg.png" =cut sub pad($) { my $img = $_[0]->clone; $img->repeat_mode (urxvt::RepeatNone); $img } sub tile($) { my $img = $_[0]->clone; $img->repeat_mode (urxvt::RepeatNormal); $img } sub mirror($) { my $img = $_[0]->clone; $img->repeat_mode (urxvt::RepeatReflect); $img } sub extend($) { my $img = $_[0]->clone; $img->repeat_mode (urxvt::RepeatPad); $img } =back =head2 VARIABLE VALUES 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 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 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, blur 5, root =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 } =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 TW, TH, 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} = $_[0]; } sub counter($) { $new->{again} = $_[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 =item clip $x, $y, $width, $height, $img Clips an image to the given rectangle. If the rectangle is outside the image area (e.g. when C<$x> or C<$y> are negative) or the rectangle is 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<$width> and C<$height> are missing, then the window size will be assumed. Example: load an image, blur it, and clip it to the window size to save memory. clip blur 10, load "mybg.png" =cut sub clip($;$$;$$) { my $img = pop; my $h = pop || TH; my $w = pop || TW; $img->sub_rect ($_[0], $_[1], $w, $h) } =item scale $img =item scale $size_factor, $img =item scale $width_factor, $height_factor, $img Scales the image by the given factors in horizontal (C<$width>) and vertical (C<$height>) direction. If only one factor is give, it is used for both directions. 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. =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. =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, $_[1] * $img->h) : @_ ? $img->scale ($_[0] * $img->w, $_[0] * $img->h) : $img->scale (TW, TH) } sub resize($$$) { my $img = pop; $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 the vertical. Example: move the image right by 20 pixels and down by 30. 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 window. This gives the illusion of a larger area behind the window. It is exactly equivalent to C, that is, it moves the image to the top left of the screen. Example: load a background image, put it in mirror mode and root align it. rootalign 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 =cut sub move($$;$) { my $img = pop->clone; $img->move ($_[0], $_[1]); $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] } =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 =item contrast $r, $g, $b, $a, $img Adjusts the I of an image. 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 $bias, $img =item brightness $r, $g, $b, $img =item brightness $r, $g, $b, $a, $img 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 idiosynchrasies 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 @_ < 3; $a = 1 if @_ < 4; $img = $img->clone; $img->contrast ($r, $g, $b, $a); $img } sub brightness($$;$$;$) { my $img = pop; my ($r, $g, $b, $a) = @_; ($g, $b) = ($r, $r) if @_ < 3; $a = 1 if @_ < 4; $img = $img->clone; $img->brightness ($r, $g, $b, $a); $img } =item blur $radius, $img =item blur $radius_horz, $radius_vert, $img Gaussian-blurs the image with (roughly) C<$radius> pixel radius. The radii can also be specified separately. Blurring is often I slow, at least compared or other operators. Larger blur radii are slower than smaller ones, too, so if you don't want to freeze your screen for long times, start experimenting with low values for radius (<5). =cut sub blur($$;$) { my $img = pop; $img->blur ($_[0], @_ >= 2 ? $_[1] : $_[0]) } =item rotate $new_width, $new_height, $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), generating a new image with width C<$new_width> and height C<$new_height>. #TODO# new width, height, maybe more operators? Example: rotate the image by 90 degrees =cut sub rotate($$$$$$) { my $img = pop; $img->rotate ( $_[0], $_[1], $_[2] * $img->w, $_[3] * $img->h, $_[4] * (3.14159265 / 180), ) } =back =cut } sub parse_expr { my $expr = eval "sub {\npackage urxvt::bgdsl;\n#line 0 'background expression'\n$_[0]\n}"; die if $@; $expr } # compiles a parsed expression sub set_expr { my ($self, $expr) = @_; $self->{expr} = $expr; $self->recalculate; } # evaluate the current bg expression sub recalculate { my ($arg_self) = @_; # rate limit evaluation if ($arg_self->{next_refresh} > urxvt::NOW) { $arg_self->{next_refresh_timer} = urxvt::timer->new->after ($arg_self->{next_refresh} - urxvt::NOW)->cb (sub { $arg_self->recalculate; }); return; } $arg_self->{next_refresh} = urxvt::NOW + $MIN_INTERVAL; # set environment to evaluate user expression local $self = $arg_self; local $HOME = $ENV{HOME}; local $old = $self->{state}; local $new = my $state = $self->{state} = {}; ($x, $y, $w, $h) = $self->background_geometry ($self->{border}); # evaluate user expression my $img = eval { $self->{expr}->() }; warn $@ if $@;#d# die "background-expr did not return an image.\n" if !UNIVERSAL::isa $img, "urxvt::img"; $state->{size_sensitive} = 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"); } # clear stuff we no longer need %$old = (); unless ($repeat) { delete $self->{state}; delete $self->{expr}; } # set background pixmap $self->set_background ($img, $self->{border}); $self->scr_recolour (0); $self->want_refresh; } sub on_start { my ($self) = @_; 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 ("%.border"); $MIN_INTERVAL = $self->x_resource ("%.interval"); () }