Andrew Stacey


About
Andrew Stacey
Information about my research, teaching, and other interests.

By: Andrew Stacey
Contact details


Andrew Stacey


blosxom icon


Tue, 8th Jul 2008 (HowDidIDoThat :: Unix)

Transparency Revisited

My latest Linux installation came with Compiz Fusion. It's too much fun to not use. However, using Compiz to set the background image seems to mess up the pseudo-transparency that I have with emacs and urxvt. The problem is twofold. Emacs uses its own picture for the backdrop which just happens to be the same as the picture on the background; no difficulty there except that using Compiz I can set this to something different on each face of the cube so Emacs needs to know which face it is on. On the other hand, urxvt figures out its backdrop from what it considers to be the program in charge of the root desktop. This is Nautilus which conflicts with reality.

Fortunately, I figured out a solution.


The solution for Emacs is for it to figure out what viewport it is on and then load the correct image accordingly. The solution for Urxvt is essentially the same.

However, the version of Urxvt that comes with Ubuntu (8.04) does not have the correct capability compiled in. So I had to download the source and recompile. I had most of the required libraries already, the important one that I hadn't yet installed was libafterimage-dev. The important option is:

urxvt -pixmap 'image;1x1+0+0:root'

Quotes of some kind are essential, otherwise the shell with think that the command ends at the semi-colon. All that remains is to figure out the viewport and the background image.

To figure out the viewport, I use wmctrl following a hint on someone else's blog. The output of wmctrl -d can be used to figure out the viewport, and the number of faces of the cube. A typical output is:

~% wmctrl -d
0  * DG: 5760x900  VP: 1440,0  WA: 0,24 1440x876  N/A

The three numbers we want from this are the 5760, and the two 1440s. The numbers we want are the first two divided by the last. The first ratio is the number of faces, the second the current face. Here's the shell script to get that:

#!/usr/bin/zsh -f
wminfo=$(wmctrl -d)
dwidth=${${wmctrl#*DG: }%%x*}
vpos=${${wminfo#*VP: }%%,*}
swidth=${${${wminfo#*WA: }#* }%%x*}

viewport=$(( $vpos / $swidth ))
faces=$(( $dwidth / $swidth ))

Here's the perl script:

#!/usr/bin/perl -w
`wmctrl -d` =~ /DG: (\d+).*?VP: (\d+).*?WA:.*?(\d+)x/;
$viewport=$2/$3;
$faces=$1/$3;

And here's the lisp code:

(setq wminfo (shell-command-to-string "wmctrl -d"))
(string-match "DG: \\(:digit:*\\).*?VP: \\(:digit:*\\).*?\\(:digit:*\\)x" wminfo)
(setq dwidth (string-to-number (match-string 1 wminfo)))
(setq vpos (string-to-number (match-string 2 wminfo)))
(setq swidth (string-to-number (match-string 3 wminfo)))
(setq viewport (/ vpos swidth))
(setq cube-faces (/ dwidth swidth))

I'm sure that those could be improved upon considerably.

Next is to get a list of the face background images. The best method of doing this will depend on the exact set up of Compiz. I use gconf and also have the dbus plugin enabled and both of those give access to the required list via the following commands:

gconftool-2 --get /apps/compiz/plugins/cube/screen0/options/backgrounds

dbus-send --print-reply --type=method_call --dest=org.freedesktop.compiz \
 /org/freedesktop/compiz/cube/screen0/backgrounds \
 org.freedesktop.compiz.get

So all that is needed is to capture the output of these two, turn it into an array, and choose the correct entry. Simple. One slight technicality. If the number of images is different to the number of faces then Compiz works modulo the number of faces. Thus extra images are ignored whilst if there are too few images they are repeated. Emacs Lisp has a wonderful data structure called a ring (I know, I know; extremely confusing name) which is exactly what is needed here. So in Emacs, we capture the command output and strip off the filenames one by one adding them in to the ring.

(setq cube-images (make-ring cube-faces))
;; DBus method
; (setq images (shell-command-to-string
;  "dbus-send --print-reply --type=method_call \
;   --dest=org.freedesktop.compiz \
;   /org/freedesktop/compiz/cube/screen0/backgrounds \
;   org.freedesktop.compiz.get"))
;(while (string-match "\\([\n[:print:]]*\\)string \"\\([^\"]+\\)" images)
;; GConf method
(setq images (shell-command-to-string "gconftool-2 --get \
 /apps/compiz/plugins/cube/screen0/options/backgrounds"))
;(while (string-match "\\(.*\\)[[,]\\(.*\\)]?" images)

 (ring-insert cube-images (match-string 2 images))
 (setq images (match-string 1 images))
)
(setq viewport-background (ring-ref cube-images viewport))

The variable viewport-background should now hold the image filename of the viewport background. As I use a faded version of this, I need to choose the appropriate faded version of the background image. Exactly how this is done depends on how I've chosen to name the various versions, which I'm not settled on yet.

Here's the shell version of the above.

if [[ $1 = "dbus" ]]
then

    images=${(z)${${${${(j: :)${${$(dbus-send \
    --print-reply --type=method_call --dest=org.freedesktop.compiz \
    /org/freedesktop/compiz/cube/screen0/backgrounds \
    org.freedesktop.compiz.get)#*\"}%\"}}#*\[ string }% \]*}}:gs/ string / /}

else

    images=(${(s.,.)${${$(gconftool-2 \
    --get /apps/compiz/plugins/cube/screen0/options/backgrounds)#\[}%\]}})

fi

viewportbg=$images[$(( ($viewport % $#images) + 1 ))]

And the perl version.

my @images;

if (@ARGV && $ARGV[0] eq 'dbus') {

    use Net::DBus;

    my $bus = Net::DBus->session;
    my $compiz = $bus->get_service("org.freedesktop.compiz");
    my $backgrounds = $compiz->get_object("/org/freedesktop/compiz/cube/screen0/backgrounds");

    @images=@{$backgrounds->get};

} else {

    use Gnome2::GConf;

    my $client = Gnome2::GConf::Client->get_default;
    @images = @{$client->get_list("/apps/compiz/plugins/cube/screen0/options/backgrounds")};

}

$viewportbg=$images[$viewport % ($#images + 1)];

On my system, GConf seems about twice as fast as DBus.

The last piece of the puzzle is to assemble the appropriate command. Obviously here it depends on what which program the command is used for and how it is being implemented. The lisp version is clearly for Emacs. The shell version seems best for invoking Urxvt. As there is a way to alter a running Urxvt using perl it seems appropriate to use the perl script above to do this; perhaps if I move a Urxvt from one viewport to another I may need to reload the background image.

As I said above, the actual image is not the one on the background but rather a faded version of that. Therefore we need to modify the image location first.

(setq emacs-background '(image :type jpeg :file))
(string-match "wrap.jpg" viewport-background)
(add-to-list 'emacs-background (replace-match "fade.jpg" t t viewport-background nil) t)
(setq emacs-bg (list emacs-background ':origin 'display))

(setcdr (assq 'background-tile special-display-frame-alist) emacs-bg)
(setcdr (assq 'background-tile default-frame-alist) emacs-bg)

For invoking Urxvt we use the -pixmap option.

urxvt -pixmap "${viewportbg:s/_wrap.jpg/_fade.jpg};1x1+0+0:root"

The perl code is used to reset the background image in Urxvt whenever it is needed. This uses the fact that Urxvt allows loading of perl modules that can be assigned to various hooks. A little experimenting showed that on_configure_notify was the best hook to use (oddly, on_motion_notify never seemed to be triggered). The following code reloads the background image whenever it thinks it necessary (it tries to do this as little as possible).

#! perl

use Gnome2::GConf;

my $client = Gnome2::GConf::Client->get_default;
my @images = @{$client->get_list("/apps/compiz/plugins/cube/screen0/options/backgrounds")};

my $x;
my $v;
my $i;

sub on_start {
  my ($state) = @_;

  `wmctrl -d` =~ /DG: (\d+).*?VP: (\d+).*?WA:.*?(\d+)x/;
  my $viewport=$2/$3;

  my $viewportbg=$images[$viewport % ($#images + 1)];
  $viewportbg =~ s/_wrap\.jpg$/_fade\.jpg/;
  $v=$viewport;

  $state->cmd_parse ("\033]20;$viewportbg;1x1+0+0:root\007");
  $i=$viewportbg;
  ();
}

sub on_configure_notify {
  my ($state,$event) = @_;
  if ($$event{"x"} != $x) {
    $x = $$event{"x"};

    `wmctrl -d` =~ /DG: (\d+).*?VP: (\d+).*?WA:.*?(\d+)x/;
    my $viewport=$2/$3;

    if ($viewport != $v) {
      my $viewportbg=$images[$viewport % ($#images + 1)];
      $viewportbg =~ s/_wrap\.jpg$/_fade\.jpg/;
      $v=$viewport;

      if ($viewportbg ne $i) {
        $state->cmd_parse ("\033]20;$viewportbg;1x1+0+0:root\007");

        $i=$viewportbg;
      }
    }
  }
  ();
}

I suppose there ought to be a reset key binding to reload the background images. Occaisionally it gets it wrong but corrects itself almost immediately afterwards. I've yet to notice any performance degredation.

You can download all of these scripts.

[Full link]
Last modified on:
Tue, 8th Jul 2008