--- rxvt-unicode/src/perl/background 2012/07/02 01:35:37 1.70 +++ rxvt-unicode/src/perl/background 2019/09/17 17:15:29 1.94 @@ -1,12 +1,22 @@ #! 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 +#:META:RESOURCE:%.expr:string:background expression +#:META:RESOURCE:%.border:boolean:respect the terminal border +#:META:RESOURCE:%.interval:seconds:minimum time between updates +#:META:RESOURCE:pixmap:file[;geom]:set image as background +#:META:RESOURCE:backgroundPixmap:file[;geom]:set image as background +#:META:RESOURCE:tr:boolean:set root pixmap as background +#:META:RESOURCE:transparent:boolean:set root pixmap as background +#:META:RESOURCE:tint:color:tint background with color +#:META:RESOURCE:tintColor:color:tint background with color +#:META:RESOURCE:sh:number:shade background by number % +#:META:RESOURCE:shading:number:shade background by number % +#:META:RESOURCE:blr:HxV:gaussian-blur background with radii +#:META:RESOURCE:blurRadius:HxV:gaussian-blur background with radii =head1 NAME - background - manage terminal background +background - manage terminal background =head1 SYNOPSIS @@ -14,6 +24,30 @@ --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 This extension manages the terminal background by creating a picture that @@ -32,7 +66,7 @@ Or specified as a X resource: - URxvt.background-expr: scale keep { load "/path/to/mybg.png" } + URxvt.background.expr: scale keep { load "/path/to/mybg.png" } =head1 THEORY OF OPERATION @@ -76,7 +110,7 @@ } This inner expression is evaluated once per hour (and whenever the -temrinal window is resized). It sets F as background on +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 @@ -110,7 +144,7 @@ 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 +has now two arguments, the C<2> and the C expression, while C only has one argument. Arguments are separated from each other by commas. @@ -121,7 +155,7 @@ scale 0.5, 2, load "$HOME/mypic.png" IF you try out these expressions, you might suffer from some sluggishness, -because each time the terminal is resized, it loads the PNG image agin +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: @@ -159,6 +193,22 @@ 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, @@ -241,7 +291,7 @@ our %_IMG_CACHE; our $HOME; our ($self, $frame); -our ($x, $y, $w, $h); +our ($x, $y, $w, $h, $focus); # enforce at least this interval between updates our $MIN_INTERVAL = 6/59.951; @@ -270,20 +320,25 @@ mode. If the image is already in memory (e.g. because another terminal instance -uses it), then the in-memory copy us returned instead. +uses it), then the in-memory copy is 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. +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) = @_; $_IMG_CACHE{$path} || do { - my $img = $self->new_img_from_file ($path); + my $img = load_uc $path; Scalar::Util::weaken ($_IMG_CACHE{$path} = $img); $img } @@ -379,6 +434,8 @@ $base } +=back + =head2 TILING MODES The following operators modify the tiling mode of an image, that is, the @@ -479,7 +536,7 @@ 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. +Using these functions makes your expression sensitive to window moves. These functions are mainly useful to align images to the root window. @@ -490,11 +547,13 @@ =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. +Using these functions makes 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. @@ -504,12 +563,29 @@ clip move -TX, -TY, keep { blur 5, root } +=item FOCUS + +Returns a boolean indicating whether the terminal window has keyboard +focus, in which case it returns true. + +Using this function makes your expression sensitive to focus changes. + +A common use case is to fade the background image when the terminal loses +focus, often together with the C<-fade> command line option. In fact, +there is a special function for just that use case: C. + +Example: use two entirely different background images, depending on +whether the window has focus. + + FOCUS ? keep { load "has_focus.jpg" } : keep { load "no_focus.jpg" } + =cut - 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 } + 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 } + sub FOCUS() { $frame->[FR_AGAIN]{focus} = 1; $focus } =item now @@ -566,7 +642,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. @@ -594,7 +670,7 @@ 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 only one factor is given, it is used for both directions. If no factors are given, scales the image to the window size without keeping aspect. @@ -729,7 +805,7 @@ 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. +Example: rotate the image by 90 degrees around its center. rotate 0.5, 0.5, 90, keep { load "$HOME/mybg.png" } @@ -770,6 +846,16 @@ $_[1]->tint ($_[0]) } +=item shade $factor, $img + +Shade the image by the given factor. + +=cut + + sub shade($$) { + $_[1]->shade ($_[0]) + } + =item contrast $factor, $img =item contrast $r, $g, $b, $img @@ -808,6 +894,8 @@ 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($$;$$;$) { @@ -834,6 +922,26 @@ $img } +=item muladd $mul, $add, $img # EXPERIMENTAL + +First multiplies the pixels by C<$mul>, then adds C<$add>. This can 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 @@ -853,6 +961,39 @@ $img->blur ($_[0], @_ >= 2 ? $_[1] : $_[0]) } +=item focus_fade $img + +=item focus_fade $factor, $img + +=item focus_fade $factor, $color, $img + +Fades the image by the given factor (and colour) when focus is lost (the +same as the C<-fade>/C<-fadecolor> command line options, which also supply +the default values for C and C<$color>. Unlike with C<-fade>, the +C<$factor> is a real value, not a percentage value (that is, 0..1, not +0..100). + +Example: do the right thing when focus fading is requested. + + focus_fade load "mybg.jpg"; + +=cut + + sub focus_fade($;$$) { + my $img = pop; + + return $img + if FOCUS; + + my $fade = @_ >= 1 ? $_[0] : defined $self->resource ("fade") ? $self->resource ("fade") * 0.01 : 0; + my $color = @_ >= 2 ? $_[1] : $self->resource ("color+" . urxvt::Color_fade); + + $img = $img->tint ($color) if $color ne "rgb:00/00/00"; + $img = $img->muladd (1 - $fade, 0) if $fade; + + $img + } + =back =head2 OTHER STUFF @@ -886,13 +1027,154 @@ 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. - rootlign keep { blur 10, root } + 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. +=back + +=head1 OLD BACKGROUND IMAGE SETTINGS + +This extension also provides support for the old options/resources and +OSC sequences for setting a background image. These settings are +B and will be removed in future versions. + +=head2 OPTIONS AND RESOURCES + +=over 4 + +=item B<-pixmap> I + +=item B I + +Use the specified image file as the window's background and also +optionally specify a colon separated list of operations to modify it. +Note that you may need to quote the C<;> character when using the +command line option, as C<;> is usually a metacharacter in shells. +Supported operations are: + +=over 4 + +=item B + +sets scale and position. B<"W" / "H"> specify the horizontal/vertical +scale (percent), and B<"X" / "Y"> locate the image centre (percent). A +scale of 0 disables scaling. + +=item B + +enables tiling + +=item B + +maintain the image aspect ratio when scaling + +=item B + +use the position of the terminal window relative to the root window as +the image offset, simulating a root window background + +=back + +The default scale and position setting is C<100x100+50+50>. +Alternatively, a predefined set of templates can be used to achieve +the most common setups: + +=over 4 + +=item B + +the image is tiled with no scaling. Equivalent to 0x0+0+0:op=tile + +=item B + +the image is scaled to fill the whole window maintaining the aspect +ratio and centered. Equivalent to 100x100+50+50:op=keep-aspect + +=item B + +the image is scaled to fill the whole window. Equivalent to 100x100 + +=item B + +the image is centered with no scaling. Equivalent to 0x0+50+50 + +=item B + +the image is tiled with no scaling and using 'root' positioning. +Equivalent to 0x0:op=tile:op=root-align + +=back + +If multiple templates are specified the last one wins. Note that a +template overrides all the scale, position and operations settings. + +If used in conjunction with pseudo-transparency, the specified image +will be blended over the transparent background using alpha-blending. + +=item B<-tr>|B<+tr> + +=item B I + +Turn on/off pseudo-transparency by using the root pixmap as background. + +=item B<-tint> I + +=item B I + +Tint the transparent background with the given colour. Note that a +black tint yields a completely black image while a white tint yields +the image unchanged. + +=item B<-sh> I + +=item B I + +Darken (0 .. 99) or lighten (101 .. 200) the transparent background. +A value of 100 means no shading. + +=item B<-blr> I + +=item B I + +Apply gaussian blur with the specified radius to the transparent +background. If a single number is specified, the vertical and +horizontal radii are considered to be the same. Setting one of the +radii to 1 and the other to a large number creates interesting effects +on some backgrounds. The maximum radius value is 128. An horizontal or +vertical radius of 0 disables blurring. + +=back + +=head2 OSC sequences + +=over 4 + +=item B<< C >> Change transparent background tint colour to B<< C >>. + +=item B<< C >> Change/Query background image +parameters: the value of B<< C >> can be one of the following +commands: + +=over 4 + +=item B<< C >> + +display scale and position in the title + +=item B<< C<;WxH+X+Y> >> + +change scale and/or position + +=item B<< C >> + +change background image + +=back + =cut sub keep(&) { @@ -948,7 +1230,7 @@ sub set_expr { my ($self, $expr) = @_; - $self->{root} = []; + $self->{root} = []; # the outermost frame $self->{expr} = $expr; $self->recalculate; } @@ -999,6 +1281,12 @@ } else { delete $state->{rootpmap}; } + + if ($again->{focus}) { + $state->{focus} = $self->on (focus_in => $cb, focus_out => $cb); + } else { + delete $state->{focus}; + } } # evaluate the current bg expression @@ -1020,9 +1308,10 @@ local $self = $arg_self; local $HOME = $ENV{HOME}; - local $frame = []; + local $frame = $self->{root}; ($x, $y, $w, $h) = $self->background_geometry ($self->{border}); + $focus = $self->focus; # evaluate user expression @@ -1050,15 +1339,219 @@ # set background pixmap $self->set_background ($img, $self->{border}); - $self->scr_recolour (0); + $self->scr_recolor (0); $self->want_refresh; } +sub old_bg_opts { + my ($self, $arg) = @_; + + $arg or return; + + my @str = split /;/, $arg; + + return unless $str[0] or $self->{bg_opts}->{path}; + + my $bg_opts = $self->{bg_opts}; + + if ($str[0]) { + $bg_opts->{tile} = 0; + $bg_opts->{keep_aspect} = 0; + $bg_opts->{root_align} = 0; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 100; + $bg_opts->{h_align} = $bg_opts->{v_align} = 50; + $bg_opts->{path} = unpack "H*", $str[0]; + } + + my @oplist = split /:/, $str[1]; + + for (@oplist) { + if (/style=tiled/i) { + $bg_opts->{tile} = 1; + $bg_opts->{keep_aspect} = 0; + $bg_opts->{root_align} = 0; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 0; + $bg_opts->{h_align} = $bg_opts->{v_align} = 0; + } elsif (/style=aspect-stretched/i) { + $bg_opts->{tile} = 0; + $bg_opts->{keep_aspect} = 1; + $bg_opts->{root_align} = 0; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 100; + $bg_opts->{h_align} = $bg_opts->{v_align} = 50; + } elsif (/style=stretched/i) { + $bg_opts->{tile} = 0; + $bg_opts->{keep_aspect} = 0; + $bg_opts->{root_align} = 0; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 100; + $bg_opts->{h_align} = $bg_opts->{v_align} = 50; + } elsif (/style=centered/i) { + $bg_opts->{tile} = 0; + $bg_opts->{keep_aspect} = 0; + $bg_opts->{root_align} = 0; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 0; + $bg_opts->{h_align} = $bg_opts->{v_align} = 50; + } elsif (/style=root-tiled/i) { + $bg_opts->{tile} = 1; + $bg_opts->{keep_aspect} = 0; + $bg_opts->{root_align} = 1; + $bg_opts->{h_scale} = $bg_opts->{v_scale} = 0; + $bg_opts->{h_align} = $bg_opts->{v_align} = 0; + } elsif (/op=tile/i) { + $bg_opts->{tile} = 1; + } elsif (/op=keep-aspect/i) { + $bg_opts->{keep_aspect} = 1; + } elsif (/op=root-align/i) { + $bg_opts->{root_align} = 1; + } elsif (/^ =? ([0-9]+)? (?:[xX] ([0-9]+))? ([+-][0-9]+)? ([+-][0-9]+)? $/x) { + my ($w, $h, $x, $y) = ($1, $2, $3, $4); + + if ($str[0]) { + $w = $h unless defined $w; + $h = $w unless defined $h; + $y = $x unless defined $y; + } + + $bg_opts->{h_scale} = $w if defined $w; + $bg_opts->{v_scale} = $h if defined $h; + $bg_opts->{h_align} = $x if defined $x; + $bg_opts->{v_align} = $y if defined $y; + } + } +} + +sub old_bg_expr { + my ($self) = @_; + + my $expr; + + my $bg_opts = $self->{bg_opts}; + + if ($bg_opts->{root} =~ /^\s*(?:true|yes|on|1)\s*$/i) { + $expr .= "tile ("; + + my $shade = $bg_opts->{shade}; + + if ($shade) { + $shade = List::Util::min $shade, 200; + $shade = List::Util::max $shade, -100; + $shade = 200 - (100 + $shade) if $shade < 0; + + $shade = $shade * 0.01 - 1; + $expr .= "shade $shade, "; + } + + my $tint = $bg_opts->{tint}; + + if ($tint) { + $expr .= "tint $tint, "; + } + + my $blur = $bg_opts->{blur}; + + if ($blur and $blur =~ /^ =? ([0-9]+)? (?:[xX] ([0-9]+))? $/x) { + my $hr = defined $1 ? $1 : 1; + my $vr = defined $2 ? $2 : $hr; + + if ($hr != 0 and $vr != 0) { + $expr .= "blur $hr, $vr, "; + } + } + + $expr .= "rootalign root)"; + } + + if ($bg_opts->{path}) { + my $file_expr; + my $h_scale = $bg_opts->{h_scale} * 0.01; + my $v_scale = $bg_opts->{v_scale} * 0.01; + my $h_align = $bg_opts->{h_align} * 0.01; + my $v_align = $bg_opts->{v_align} * 0.01; + + if (!$bg_opts->{tile}) { + $file_expr .= "pad ("; + } else { + $file_expr .= "tile ("; + } + + if ($bg_opts->{root_align}) { + $file_expr .= "rootalign "; + } else { + $file_expr .= "align $h_align, $v_align, "; + } + + if ($h_scale != 0 and $v_scale != 0) { + my $op = $bg_opts->{keep_aspect} ? "fit" : "resize"; + $file_expr .= "$op TW * $h_scale, TH * $v_scale, "; + } + + $file_expr .= "keep { load pack \"H*\", \"$bg_opts->{path}\" })"; + + if ($expr) { + $expr .= ", tint (\"[50]white\", $file_expr)"; + } else { + $expr = $file_expr; + } + } + + $expr +} + +sub on_osc_seq { + my ($self, $op, $arg) = @_; + + $self->{bg_opts} or return; + + $op =~ /^(20|705)$/ or return; + + if ($op eq "20") { + if ($arg eq "?") { + my $h_scale = $self->{bg_opts}->{h_scale}; + my $v_scale = $self->{bg_opts}->{v_scale}; + my $h_align = $self->{bg_opts}->{h_align}; + my $v_align = $self->{bg_opts}->{v_align}; + $self->cmd_parse ("\033]2;[${h_scale}x${v_scale}+${h_align}+${v_align}]\007"); + } else { + $self->old_bg_opts ($arg); + my $expr = $self->old_bg_expr; + $self->set_expr (parse_expr $expr) if $expr; + } + } elsif ($op eq "705") { + $self->{bg_opts}->{tint} = $arg; + my $expr = $self->old_bg_expr; + $self->set_expr (parse_expr $expr) if $expr; + } + + 1 +} + +sub find_resource { + my ($self, $res, $opt) = @_; + + my $v = $self->x_resource ($opt); + $v = $self->x_resource ($res) unless defined $v; + + $v +} + sub on_start { my ($self) = @_; - my $expr = $self->x_resource ("%.expr") - or return; + my $expr = $self->x_resource ("%.expr"); + + if (!$expr) { + $self->{bg_opts} = { h_scale => 100, v_scale => 100, + h_align => 50, v_align => 50 }; + + $self->{bg_opts}->{shade} = $self->find_resource ("shading", "sh"); + $self->{bg_opts}->{tint} = $self->find_resource ("tintColor", "tint"); + $self->{bg_opts}->{blur} = $self->find_resource ("blurRadius", "blr"); + $self->{bg_opts}->{root} = $self->find_resource ("transparent", "tr"); + + $self->old_bg_opts ($self->find_resource ("backgroundPixmap", "pixmap")); + $expr = $self->old_bg_expr; + } + + $expr or return; $self->has_render or die "background extension needs RENDER extension 0.10 or higher, ignoring background-expr.\n";