#! perl #:META:X_RESOURCE:%.expr:string:background expression #:META:X_RESOURCE:%.enable:boolean:some boolean #:META:X_RESOURCE:%.extra.:value:extra config our $EXPR; #$EXPR = 'move W * 0.1, -H * 0.1, resize W * 0.5, H * 0.5, repeat_none load "opensource.png"'; $EXPR = 'move -X, -Y, 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 use Safe; our ($bgdsl_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 =over 4 =item load $path Loads the image at the given C<$path>. The image is set to plane tiling mode. =cut sub load($) { my ($path) = @_; $new->{load}{$path} = $old->{load}{$path} || $bgdsl_self->new_img_from_file ($path); } sub root() { $new->{rootpmap_sensitive} = 1; die "root op not supported, exg, we need you"; } sub solid($;$$) { my $img = $bgdsl_self->new_img (urxvt::PictStandardARGB32, $_[1] || 1, $_[2] || 1); $img->fill ($_[0]); $img } =back =head2 VARIABLES =over 4 =cut sub X() { $new->{position_sensitive} = 1; $x } sub Y() { $new->{position_sensitive} = 1; $y } sub W() { $new->{size_sensitive} = 1; $w } sub H() { $new->{size_sensitive} = 1; $h } sub now() { urxvt::NOW } sub again($) { $new->{again} = $_[0]; } sub counter($) { $new->{again} = $_[0]; $bgdsl_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 || H; my $w = pop || W; $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 (W, H) } sub resize($$$) { my $img = pop; $img->scale ($_[0], $_[1]) } # TODO: ugly sub move($$;$) { my $img = pop->clone; $img->move ($_[0], $_[1]); $img # my $img = pop; # $img->sub_rect ( # $_[0], $_[1], # $img->w, $img->h, # $_[2], # ) } 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 ($self) = @_; # rate limit evaluation if ($self->{next_refresh} > urxvt::NOW) { $self->{next_refresh_timer} = urxvt::timer->new->after ($self->{next_refresh} - urxvt::NOW)->cb (sub { $self->recalculate; }); return; } $self->{next_refresh} = urxvt::NOW + $MIN_INTERVAL; # set environment to evaluate user expression local $bgdsl_self = $self; local $old = $self->{state}; local $new = my $state = $self->{state} = {}; my $border = 0; #d# ($x, $y, $w, $h) = $self->background_geometry ($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, $border); $self->scr_recolour (0); $self->want_refresh; } sub on_start { my ($self) = @_; $self->set_expr (parse_expr $EXPR); () }