~schnouki/smartbg

ref: 6a6a961a05d1963a6ed46821b7a5eab4a29149a2 smartbg/smartbg -rwxr-xr-x 5.8 KiB
6a6a961a — Thomas Jost Use Xlib rather than GTK/GDK for setting the wallpaper 9 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env python2
# -*- mode: python -*-

# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.

import glob
import os.path
import optparse
import random
import time

import pygtk
pygtk.require('2.0')
import gtk

import Xlib.Xatom, Xlib.display
import Xlib.X as X

IMG_EXTENSIONS = ("bmp", "jpg", "jpeg", "png")
TRANSITION_FPS = 25

def get_image_path(args):
    paths = []
    for path in args:
        if os.path.isdir(path):
            # Add all matching images from this directory
            for ext in IMG_EXTENSIONS:
                paths.extend(glob.iglob(os.path.join(path, "*.%s" % ext)))
        elif os.path.isfile(path):
            paths.append(path)        
        else:
            raise Exception("%s is not a file nor a directory" % path)

    if len(paths) > 0:
        return random.choice(paths)
    else:
        raise Exception("Missing path (directory or file) on the command line")

def add_image_to_pixbuf(pb, geom, img):
    gw, gh = geom.width, geom.height
    gx, gy = geom.x, geom.y

    pix = gtk.gdk.pixbuf_new_from_file(img)
    iw, ih = pix.get_width(), pix.get_height()

    print "Setting image for display %d to %s..." % (mon, img)
    print " * image size: %dx%d" % (iw, ih)
    print " * display geometry: %dx%d+%d+%d" % (gw, gh, gx, gy)

    # Scaling needed?
    if iw > gw or ih > gh or (iw < gw and ih < gh):
        ratio = float(iw)/float(ih)
        
        # Try width first
        if gw / ratio <= gh:
            print " * scaling (based on height)"
            iw = gw
            ih = int(gw / ratio)
        else:
            print " * scaling (based on width)"
            ih = gh
            iw = int(gh * ratio)

        print " * new size: %dx%d" % (iw, ih)

        pix = pix.scale_simple(iw, ih, gtk.gdk.INTERP_BILINEAR)

    # Draw (centered) on the relevant part of the window
    off_x = (gw - iw) / 2
    off_y = (gh - ih) / 2
    print " * offsets: (%d, %d)" % (off_x, off_y)

    pix.copy_area(0, 0, iw, ih, pb, gx + off_x, gy + off_y)

def set_wallpaper(pb):
    # Get a pixmap from the pixbuf
    (pm, mask) = pb.render_pixmap_and_mask()

    # Let the Xlib fun begin!
    # This is largely inspired by feh and esetroot
    # (http://www.eterm.org/docs/view.php?doc=ref#trans)
    # Create a new display,
    dpy = Xlib.display.Display()
    xscr = dpy.screen()
    xwin = xscr.root

    # Copy pixmap to this display
    pm2 = xwin.create_pixmap(xscr.width_in_pixels, xscr.height_in_pixels, xscr.root_depth)
    gc = pm2.create_gc(fill_style=X.FillTiled, tile=pm.xid)
    pm2.fill_rectangle(gc, 0, 0, xscr.width_in_pixels, xscr.height_in_pixels)
    gc.free()
    dpy.sync()

    # Check if the properties exist and are equal
    prop_root = dpy.intern_atom("_XROOTPMAP_ID", True)
    prop_esetroot = dpy.intern_atom("ESETROOT_PMAP_ID", True)
    if prop_root != X.NONE and prop_esetroot != X.NONE:
        p1 = xwin.get_property(prop_root, X.AnyPropertyType, 0, 1, False)
        if p1 is not None and p1.property_type == Xlib.Xatom.PIXMAP:
            p2 = xwin.get_property(prop_root, X.AnyPropertyType, 0, 1, False)
            if p2 is not None and p2.property_type == Xlib.Xatom.PIXMAP and p1.value == p2.value:
                # It's safe: kill the pixmap
                xpm = dpy.create_resource_object("pixmap", p1.value[0])
                xpm.kill_client()

    # Locate the properties, create them if they don't exist
    prop_root = dpy.intern_atom("_XROOTPMAP_ID", False)
    prop_esetroot = dpy.intern_atom("ESETROOT_PMAP_ID", False)
    if prop_root == X.NONE or prop_esetroot == X.NONE:
        raise ValueError("prop is X.NONE")

    # Set them
    xwin.change_property(prop_root, Xlib.Xatom.PIXMAP, 32, [pm2.__resource__()], X.PropModeReplace)
    xwin.change_property(prop_esetroot, Xlib.Xatom.PIXMAP, 32, [pm2.__resource__()], X.PropModeReplace)

    # Now set the root window background pixmap
    xwin.change_attributes(background_pixmap=pm2.__resource__())
    xwin.clear_area()

    # And we're done!
    dpy.sync()
    dpy.set_close_down_mode(X.RetainPermanent)
    dpy.close()

# Parse command-line
parser = optparse.OptionParser()
parser.add_option("-d", "--duration", dest="duration", type=int, default=0,
                  help="Set the duration of the transition (in milliseconds)")

(options, args) = parser.parse_args()

# Root window and screen geometry
scr = gtk.gdk.screen_get_default()
win = gtk.gdk.get_default_root_window()

sw, sh = scr.get_width(), scr.get_height()

# Pixbuf for rendering the pictures
wp = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, sw, sh)
wp.fill(0x000000ff)

# Prepare an image for each monitor
for mon in range(scr.get_n_monitors()):
    geom = scr.get_monitor_geometry(mon)
    img = get_image_path(args)
    add_image_to_pixbuf(wp, geom, img)

if options.duration > 0:
    # Prepare the transition
    step_duration = 1000. / TRANSITION_FPS
    steps = int((options.duration / step_duration) + .5)

    cm = win.get_colormap()
    src = gtk.gdk.pixbuf_get_from_drawable(None, win, cm, 0, 0, 0, 0, sw, sh)
    pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, sw, sh)

    alpha = 0.
    d = 0.
    for step in xrange(steps):
        if d > 0.:
            time.sleep(d / 1000.)
        t1 = time.time()
        obj = (step + 1.) / steps
        beta = (obj - alpha) / (1. - alpha)
        ctx = win.cairo_create()
        ctx.set_source_pixbuf(wp, 0, 0)
        ctx.paint_with_alpha(beta)
        alpha = beta
        t2 = time.time()
        d = step_duration - ((t2 - t1) * 1000.)

# Now render the wallpaper for good (even if there was a transition)
set_wallpaper(wp)