summaryrefslogtreecommitdiff
path: root/XcbPythonBinding.mdwn
blob: 6b24bfa90b30b756467d8518b534d359608de654 (plain)
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# Introduction

The Python binding for XCB allows the X protocol to be accessed directly from Python.  There are two components:

1. A Python extension written in C.  This exposes XCB-specific objects and library functions, as well as providing various base classes used by the generated code.
2. Python modules which are generated directly from the XCB-XML protocol descriptions.

The Python binding requires libxcb.so to work.  The additional extension libraries are not required.


# Installation from Source

You need a version of xcb/proto that includes the Python generator libraries (xcbgen).  Version 1.6 or up is recommended.  libxcb.so must be present as well.

Python 2.5 or 2.6 must be installed on the system.  Lower versions may work, but have not been tested.  Python 3 has not been tested.

The X Python binding can be obtained from `git://git.freedesktop.org/git/xcb/xpyb`.  After cloning the repo, the standard `./autogen.sh; make; make install` should suffice to build and install it.

Note that Python has a path that it uses when searching for modules to import.  This path must include the place where you install the software.  For example if you install with a prefix of `/usr/local` then your Python path must include `/usr/local/lib/python2.5/site-packages`.  There are at least four ways to accomplish this:

1. Install to /usr/lib which is already in the Python path.
2. Set a `PYTHONPATH` environment variable containing the path.
3. Create a file with a `.pth` extension, containing the path, in a place that is on the existing path, such as `/usr/lib/python2.5/site-packages`.
4. In your Python code, do a `sys.path.append(foo)` before you import the XCB modules.

# Usage

The usage model is essentially an object-oriented version of libxcb.  Refer to the [[PublicApi]] and [[ProtocolExtensionApi]] documentation for libxcb.

A note on naming: the Python binding does not attempt to do any significant name normalization.  This means that XCB library functions retain the lower-case/underscore convention, while X protocol names retain the CamelCase from the XML.  Names that are reserved words in Python, such as the words "class", "def", and "None", are prefixed with underscores as necessary.  Enum member names consisting of only numbers are treated likewise, e.g. `ButtonMask._1`.

You always need to import `xcb` and `xcb.xproto` at the start of the program.  Also import any other extensions you plan on using.

To connect, use:

    conn = xcb.connect(display=':0.0', fd=3, auth='NAME:binary-data')

The arguments are all optional, with display and fd being mutually exclusive.  The returned object has attributes and methods corresponding to the various XCB calls:

    conn.pref_screen
    conn.flush()
    conn.wait_for_event()
    conn.generate_id()
    conn.get_setup()

Et cetera.  To make a core protocol request, use the `conn.core` attribute:

    cookie = conn.core.GetInputFocus()
    reply = cookie.reply()

    cookie = conn.core.CreateWindowChecked(...)
    cookie.check()

To make an extension protocol request, call the connection object, passing the extension key you want, and use the returned object.  For example:

    render = conn(xcb.render.key)
    cookie = render.FillRectangles(...)

Protocol errors are always thrown as exceptions, with the actual error object available as the first exception argument:

    try:
        cookie.check()
        conn.wait_for_event()
    except xcb.xproto.BadMatch, e:
        error = e.args[0]
        print "Bad match on value %d" % error.bad_value

When making a request, any lists or internal sub-structures must be passed as sequences of cardinal values.  These lists can contain arbitrary internal nesting.  For example:

    color = [ 65535, 0, 0, 65535 ]
    rectangles = [ (0, 0, 25, 25), (100, 100, 10, 10), (250, 0, 64, 64) ]
    render.FillRectangles(op, pid, color, 3, rectangles)

Note that the `rects_len` parameter in this case should always be 3, even if the rectangle list is flattened.  The length parameter refers to the number of logical elements.

Reply, event, and error objects have attributes corresponding to each structure field.  These objects also implement the buffer interface, allowing them to be addressed as raw binary or written to a file as they appear on the wire.

Reply fields that are themselves lists can accessed using the usual array notation, and can be turned into a buffer object using the .buf() method.  The following example shows how to turn a reply array of bytes into a Python string.

    # get string "BITMAP"
    atom = 5
    cookie = conn.core.GetAtomName(5)
    reply = cookie.reply()
    print str(reply.name.buf())

Reply arrays of bytes also may need to be unpacked or processed.  For example, the GetProperty reply needs to be unpacked if the property consists of packed integers or cardinals:

    import struct

    conn = xcb.connect()
    setup = conn.get_setup()
    root = setup.roots[0].root

    cookie1 = conn.core.InternAtom(True, 17, "_NET_WM_USER_TIME");
    cookie2 = conn.core.InternAtom(True, 8, "CARDINAL");
    name = cookie1.reply().atom
    type = cookie2.reply().atom

    cookie = conn.core.GetProperty(False, root, name, type, 0, 1)
    reply = cookie.reply()

    print len(reply.value)
    print struct.unpack_from('I', reply.value.buf())[0]

# Full Example

The following complete program creates a window, sets a property, and does some drawing with Render.  There is also a basic event loop.

    import xcb
    from xcb.xproto import *
    import xcb.render
    
    def find_format(screen):
        for d in screen.depths:
            if d.depth == depth:
                for v in d.visuals:
                    if v.visual == visual:
                        return v.format
    
        raise Exception("Failed to find an appropriate Render pictformat!")
    
    
    def startup():
        white = setup.roots[0].white_pixel
    
        conn.core.CreateWindow(depth, window, root,
                               0, 0, 640, 480, 0,
                               WindowClass.InputOutput,
                               visual,
                               CW.BackPixel | CW.EventMask,
                               [ white, EventMask.ButtonPress | EventMask.EnterWindow | EventMask.LeaveWindow | EventMask.Exposure ])
    
        cookie = conn.render.QueryPictFormats()
        reply = cookie.reply()
        format = find_format(reply.screens[0])
    
        name = 'X Python Binding Demo'
        conn.core.ChangeProperty(PropMode.Replace, window, xcb.XA_WM_NAME, xcb.XA_STRING, 8, len(name), name)
        conn.render.CreatePicture(pid, window, format, 0, [])
        conn.core.MapWindow(window)
        conn.flush()
    
    
    def paint():
        conn.core.ClearArea(False, window, 0, 0, 0, 0)
    
        for x in xrange(0, 7):
            for y in xrange(0, 5):
                rectangle = ((x + 1) * 24 + x * 64, (y + 1) * 24 + y * 64, 64, 64)
                color = (x * 65535 / 7, y * 65535 / 5, (x * y) * 65535 / 35, 65535)
                conn.render.FillRectangles(xcb.render.PictOp.Src, pid, color, 1, rectangle)
    
        conn.flush()
    
    
    def run():
        startup()
        print 'Click in window to exit.'
    
        while True:
            try:
                event = conn.wait_for_event()
            except xcb.ProtocolException, error:
                print "Protocol error %s received!" % error.__class__.__name__
                break
            except:
                print "Unexpected error received: %s" % error.message
                break
    
            if isinstance(event, ExposeEvent):
                paint()
            elif isinstance(event, EnterNotifyEvent):
                print 'Enter (%d, %d)' % (event.event_x, event.event_y)
            elif isinstance(event, LeaveNotifyEvent):
                print 'Leave (%d, %d)' % (event.event_x, event.event_y)
            elif isinstance(event, ButtonPressEvent):
                print 'Button %d down' % event.detail
                break
    
        conn.disconnect()
    
    

    conn = xcb.connect()
    conn.render = conn(xcb.render.key)
    
    setup = conn.get_setup()
    root = setup.roots[0].root
    depth = setup.roots[0].root_depth
    visual = setup.roots[0].root_visual
    
    window = conn.generate_id()
    pid = conn.generate_id()
    
    run()

# Display Authentication

Early versions of xpyb had a bug that prevented the XAUTHORITY variable from being used.  This has been fixed.  Calling xcb.connect() without arguments will automatically use the DISPLAY and XAUTHORITY variables as appropriate.

The example below shows how to manually specify a standard authentication cookie using the optional auth= parameter to xcb.connect().

    import xcb
    import xcb.xproto

    authname = "MIT-MAGIC-COOKIE-1"
    authdata = "\xa5\xcf\x95\xfa\x19\x49\x03\x60\xaf\xe4\x1e\xcd\xa3\xe2\xad\x47"

    authstr = authname + ':' + authdata

    conn = xcb.connect(display=":0",auth=authstr)

    print conn.get_setup().roots[0].root