#! perl #:META:X_RESOURCE:%.expr:string:background expression #:META:X_RESOURCE:%.border.:boolean:respect the terminal border #TODO: once, rootalign =head1 background - manage terminal background =head2 SYNOPSIS urxvt --background-expr 'background expression' --background-border =head2 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" =head2 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. =head3 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 percentage: scale 200, 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 50, 200, load "$HOME/mypic.png" TODO =head3 CYCLES AND CACHING TODO 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 =head2 REFERENCE =head3 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. =back =cut our $EXPR;#d# #$EXPR = 'move W * 0.1, -H * 0.1, resize W * 0.5, H * 0.5, repeat_none load "opensource.png"'; $EXPR = 'move -TX, -TY, load "argb.png"'; #$EXPR = ' # rotate W, H, 50, 50, counter 1/59.95, repeat_mirror, # clip X, Y, W, H, repeat_mirror, # load "/root/pix/das_fette_schwein.jpg" #'; #$EXPR = 'solid "red"'; #$EXPR = 'blur root, 10, 10' #$EXPR = 'blur move (root, -x, -y), 5, 5' #resize load "/root/pix/das_fette_schwein.jpg", w, h our $HOME; our ($self, $old, $new); our ($x, $y, $w, $h); # enforce at least this interval between updates our $MIN_INTERVAL = 1/100; { package urxvt::bgdsl; # background language =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 <$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 } =back =head2 VARIABLES The following functions provide variable data such as the terminal window dimensions. 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 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 PIXEL OPERATORS The following operators modify the image pixels in various ways. =over 4 =item clone $img Returns an exact copy of the image. =cut sub clone($) { $_[0]->clone } =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_percent, $img =item scale $width_percent, $height_percent, $img Scales the image by the given percentages in horizontal (C<$width_percent>) and vertical (C<$height_percent>) direction. If only one percentage is give, it is used for both directions. If no percentages 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 #TODO: maximise, maximise_fill? 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) : $img->scale (TW, TH) } sub resize($$$) { my $img = pop; $img->scale ($_[0], $_[1]) } =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 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 rootalign($) { move -TX, -TY, $_[0] } =item contrast $factor, $img =item contrast $r, $g, $b, $img =item contrast $r, $g, $b, $a, $img Adjusts the I of an image. #TODO# =item brightness $factor, $img =item brightness $r, $g, $b, $img =item brightness $r, $g, $b, $a, $img Adjusts the brightness of an image. =cut sub contrast($$;$$;$) { my $img = pop; my ($r, $g, $b, $a) = @_; ($g, $b) = ($r, $r) if @_ < 4; $a = 1 if @_ < 5; $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 @_ < 4; $a = 1 if @_ < 5; $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. =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 percentage 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 * .01, $_[3] * $img->h * .01, $_[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 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 ("background.expr") or return; $self->set_expr (parse_expr $expr); $self->{border} = $self->x_resource_boolean ("background.border"); () }