#! perl #:META:X_RESOURCE:%.expr:string:background expression #:META:X_RESOURCE:%.border.:boolean:respect the terminal border =head1 background - manage terminal background =head2 SYNOPSIS rxvt -background-expr 'background expression' -background-border =head2 DESCRIPTION =head2 REFERENCE =cut our $EXPR; #$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 ($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. =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). =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. =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. =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]) } sub move($$;$) { my $img = pop->clone; $img->move ($_[0], $_[1]); $img } sub rotate($$$$$$) { my $img = pop; $img->rotate ( $_[0], $_[1], $_[2] * $img->w * .01, $_[3] * $img->h * .01, $_[4] * (3.14159265 / 180), ) } sub blur($$;$) { my $img = pop; $img->blur ($_[0], @_ >= 2 ? $_[1] : $_[0]) } 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 } =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 $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"; # if the expression is sensitive to external events, prepare reevaluation then my $repeat; if (my $again = $state->{again}) { $repeat = 1; $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}; } # prepare and set background pixmap $img = $img->sub_rect (0, 0, $w, $h) if $img->w != $w || $img->h != $h; $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"); () }