1# Xlib.xobject.drawable -- drawable objects (window and pixmap)
2#
3#    Copyright (C) 2000 Peter Liljenberg <petli@ctrl-c.liu.se>
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public License
7# as published by the Free Software Foundation; either version 2.1
8# of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13# See the GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the
17#    Free Software Foundation, Inc.,
18#    59 Temple Place,
19#    Suite 330,
20#    Boston, MA 02111-1307 USA
21
22from Xlib import X, Xatom, Xutil
23from Xlib.protocol import request, rq
24
25# Other X resource objects
26from . import resource
27from . import colormap
28from . import cursor
29from . import fontable
30
31# Inter-client communication conventions
32from . import icccm
33
34class Drawable(resource.Resource):
35    __drawable__ = resource.Resource.__resource__
36
37    def get_geometry(self):
38        return request.GetGeometry(display = self.display,
39                                   drawable = self)
40
41    def create_pixmap(self, width, height, depth):
42        pid = self.display.allocate_resource_id()
43        request.CreatePixmap(display = self.display,
44                             depth = depth,
45                             pid = pid,
46                             drawable = self.id,
47                             width = width,
48                             height = height)
49
50        cls = self.display.get_resource_class('pixmap', Pixmap)
51        return cls(self.display, pid, owner = 1)
52
53    def create_gc(self, **keys):
54        cid = self.display.allocate_resource_id()
55        request.CreateGC(display = self.display,
56                         cid = cid,
57                         drawable = self.id,
58                         attrs = keys)
59
60        cls = self.display.get_resource_class('gc', fontable.GC)
61        return cls(self.display, cid, owner = 1)
62
63    def copy_area(self, gc, src_drawable, src_x, src_y, width, height, dst_x, dst_y, onerror = None):
64        request.CopyArea(display = self.display,
65                         onerror = onerror,
66                         src_drawable = src_drawable,
67                         dst_drawable = self.id,
68                         gc = gc,
69                         src_x = src_x,
70                         src_y = src_y,
71                         dst_x = dst_x,
72                         dst_y = dst_y,
73                         width = width,
74                         height = height)
75
76    def copy_plane(self, gc, src_drawable, src_x, src_y, width, height,
77                   dst_x, dst_y, bit_plane, onerror = None):
78        request.CopyPlane(display = self.display,
79                          onerror = onerror,
80                          src_drawable = src_drawable,
81                          dst_drawable = self.id,
82                          gc = gc,
83                          src_x = src_x,
84                          src_y = src_y,
85                          dst_x = dst_x,
86                          dst_y = dst_y,
87                          width = width,
88                          height = height,
89                          bit_plane = bit_plane)
90
91    def poly_point(self, gc, coord_mode, points, onerror = None):
92        request.PolyPoint(display = self.display,
93                          onerror = onerror,
94                          coord_mode = coord_mode,
95                          drawable = self.id,
96                          gc = gc,
97                          points = points)
98
99    def point(self, gc, x, y, onerror = None):
100        request.PolyPoint(display = self.display,
101                          onerror = onerror,
102                          coord_mode = X.CoordModeOrigin,
103                          drawable = self.id,
104                          gc = gc,
105                          points = [(x, y)])
106
107    def poly_line(self, gc, coord_mode, points, onerror = None):
108        request.PolyLine(display = self.display,
109                         onerror = onerror,
110                         coord_mode = coord_mode,
111                         drawable = self.id,
112                         gc = gc,
113                         points = points)
114
115    def line(self, gc, x1, y1, x2, y2, onerror = None):
116        request.PolySegment(display = self.display,
117                            onerror = onerror,
118                            drawable = self.id,
119                            gc = gc,
120                            segments = [(x1, y1, x2, y2)])
121
122    def poly_segment(self, gc, segments, onerror = None):
123        request.PolySegment(display = self.display,
124                            onerror = onerror,
125                            drawable = self.id,
126                            gc = gc,
127                            segments = segments)
128
129    def poly_rectangle(self, gc, rectangles, onerror = None):
130        request.PolyRectangle(display = self.display,
131                              onerror = onerror,
132                              drawable = self.id,
133                              gc = gc,
134                              rectangles = rectangles)
135
136    def rectangle(self, gc, x, y, width, height, onerror = None):
137        request.PolyRectangle(display = self.display,
138                              onerror = onerror,
139                              drawable = self.id,
140                              gc = gc,
141                              rectangles = [(x, y, width, height)])
142
143
144    def poly_arc(self, gc, arcs, onerror = None):
145        request.PolyArc(display = self.display,
146                        onerror = onerror,
147                        drawable = self.id,
148                        gc = gc,
149                        arcs = arcs)
150
151    def arc(self, gc,  x, y, width, height, angle1, angle2, onerror = None):
152        request.PolyArc(display = self.display,
153                        onerror = onerror,
154                        drawable = self.id,
155                        gc = gc,
156                        arcs = [(x, y, width, height, angle1, angle2)])
157
158    def fill_poly(self, gc, shape, coord_mode, points, onerror = None):
159        request.FillPoly(display = self.display,
160                         onerror = onerror,
161                         shape = shape,
162                         coord_mode = coord_mode,
163                         drawable = self.id,
164                         gc = gc,
165                         points = points)
166
167    def poly_fill_rectangle(self, gc, rectangles, onerror = None):
168        request.PolyFillRectangle(display = self.display,
169                                  onerror = onerror,
170                                  drawable = self.id,
171                                  gc = gc,
172                                  rectangles = rectangles)
173
174    def fill_rectangle(self, gc, x, y, width, height, onerror = None):
175        request.PolyFillRectangle(display = self.display,
176                                  onerror = onerror,
177                                  drawable = self.id,
178                                  gc = gc,
179                                  rectangles = [(x, y, width, height)])
180
181    def poly_fill_arc(self, gc, arcs, onerror = None):
182        request.PolyFillArc(display = self.display,
183                            onerror = onerror,
184                            drawable = self.id,
185                            gc = gc,
186                            arcs = arcs)
187
188    def fill_arc(self, gc,  x, y, width, height, angle1, angle2, onerror = None):
189        request.PolyFillArc(display = self.display,
190                            onerror = onerror,
191                            drawable = self.id,
192                            gc = gc,
193                            arcs = [(x, y, width, height, angle1, angle2)])
194
195
196    def put_image(self, gc, x, y, width, height, format,
197                  depth, left_pad, data, onerror = None):
198        request.PutImage(display = self.display,
199                         onerror = onerror,
200                         format = format,
201                         drawable = self.id,
202                         gc = gc,
203                         width = width,
204                         height = height,
205                         dst_x = x,
206                         dst_y = y,
207                         left_pad = left_pad,
208                         depth = depth,
209                         data = data)
210
211    # Trivial little method for putting PIL images.  Will break on anything
212    # but depth 1 or 24...
213    def put_pil_image(self, gc, x, y, image, onerror = None):
214        width, height = image.size
215        if image.mode == '1':
216            format = X.XYBitmap
217            depth = 1
218            if self.display.info.bitmap_format_bit_order == 0:
219                rawmode = '1;R'
220            else:
221                rawmode = '1'
222            pad = self.display.info.bitmap_format_scanline_pad
223            stride = roundup(width, pad) >> 3
224        elif image.mode == 'RGB':
225            format = X.ZPixmap
226            depth = 24
227            if self.display.info.image_byte_order == 0:
228                rawmode = 'BGRX'
229            else:
230                rawmode = 'RGBX'
231            pad = self.display.info.bitmap_format_scanline_pad
232            unit = self.display.info.bitmap_format_scanline_unit
233            stride = roundup(width * unit, pad) >> 3
234        else:
235            raise ValueError('Unknown data format')
236
237        maxlen = (self.display.info.max_request_length << 2) \
238                 - request.PutImage._request.static_size
239        split = maxlen // stride
240
241        x1 = 0
242        x2 = width
243        y1 = 0
244
245        while y1 < height:
246            h = min(height, split)
247            if h < height:
248                subimage = image.crop((x1, y1, x2, y1 + h))
249            else:
250                subimage = image
251            w, h = subimage.size
252            data = subimage.tobytes("raw", rawmode, stride, 0)
253            self.put_image(gc, x, y, w, h, format, depth, 0, data)
254            y1 = y1 + h
255            y = y + h
256
257
258    def get_image(self, x, y, width, height, format, plane_mask):
259        return request.GetImage(display = self.display,
260                                format = format,
261                                drawable = self.id,
262                                x = x,
263                                y = y,
264                                width = width,
265                                height = height,
266                                plane_mask = plane_mask)
267
268    def draw_text(self, gc, x, y, text, onerror = None):
269        request.PolyText8(display = self.display,
270                          onerror = onerror,
271                          drawable = self.id,
272                          gc = gc,
273                          x = x,
274                          y = y,
275                          items = [text])
276
277    def poly_text(self, gc, x, y, items, onerror = None):
278        request.PolyText8(display = self.display,
279                          onerror = onerror,
280                          drawable = self.id,
281                          gc = gc,
282                          x = x,
283                          y = y,
284                          items = items)
285
286    def poly_text_16(self, gc, x, y, items, onerror = None):
287        request.PolyText16(display = self.display,
288                           onerror = onerror,
289                           drawable = self.id,
290                           gc = gc,
291                           x = x,
292                           y = y,
293                           items = items)
294
295    def image_text(self, gc, x, y, string, onerror = None):
296        request.ImageText8(display = self.display,
297                           onerror = onerror,
298                           drawable = self.id,
299                           gc = gc,
300                           x = x,
301                           y = y,
302                           string = string)
303
304    def image_text_16(self, gc, x, y, string, onerror = None):
305        request.ImageText16(display = self.display,
306                            onerror = onerror,
307                            drawable = self.id,
308                            gc = gc,
309                            x = x,
310                            y = y,
311                            string = string)
312
313    def query_best_size(self, item_class, width, height):
314        return request.QueryBestSize(display = self.display,
315                                     item_class = item_class,
316                                     drawable = self.id,
317                                     width = width,
318                                     height = height)
319
320class Window(Drawable):
321    __window__ = resource.Resource.__resource__
322
323    _STRING_ENCODING = 'ISO-8859-1'
324    _UTF8_STRING_ENCODING = 'UTF-8'
325
326    def create_window(self, x, y, width, height, border_width, depth,
327                      window_class =  X.CopyFromParent,
328                      visual = X.CopyFromParent,
329                      onerror = None,
330                      **keys):
331
332        wid = self.display.allocate_resource_id()
333        request.CreateWindow(display = self.display,
334                             onerror = onerror,
335                             depth = depth,
336                             wid = wid,
337                             parent = self.id,
338                             x = x,
339                             y = y,
340                             width = width,
341                             height = height,
342                             border_width = border_width,
343                             window_class = window_class,
344                             visual = visual,
345                             attrs = keys)
346
347        cls = self.display.get_resource_class('window', Window)
348        return cls(self.display, wid, owner = 1)
349
350    def change_attributes(self, onerror = None, **keys):
351        request.ChangeWindowAttributes(display = self.display,
352                                       onerror = onerror,
353                                       window = self.id,
354                                       attrs = keys)
355
356    def get_attributes(self):
357        return request.GetWindowAttributes(display = self.display,
358                                           window = self.id)
359
360    def destroy(self, onerror = None):
361        request.DestroyWindow(display = self.display,
362                              onerror = onerror,
363                              window = self.id)
364
365        self.display.free_resource_id(self.id)
366
367    def destroy_sub_windows(self, onerror = None):
368        request.DestroySubWindows(display = self.display,
369                                  onerror = onerror,
370                                  window = self.id)
371
372
373    def change_save_set(self, mode, onerror = None):
374        request.ChangeSaveSet(display = self.display,
375                              onerror = onerror,
376                              mode = mode,
377                              window = self.id)
378
379    def reparent(self, parent, x, y, onerror = None):
380        request.ReparentWindow(display = self.display,
381                               onerror = onerror,
382                               window = self.id,
383                               parent = parent,
384                               x = x,
385                               y = y)
386
387    def map(self, onerror = None):
388        request.MapWindow(display = self.display,
389                          onerror = onerror,
390                          window = self.id)
391
392    def map_sub_windows(self, onerror = None):
393        request.MapSubwindows(display = self.display,
394                              onerror = onerror,
395                              window = self.id)
396
397    def unmap(self, onerror = None):
398        request.UnmapWindow(display = self.display,
399                            onerror = onerror,
400                            window = self.id)
401
402    def unmap_sub_windows(self, onerror = None):
403        request.UnmapSubwindows(display = self.display,
404                                onerror = onerror,
405                                window = self.id)
406
407    def configure(self, onerror = None, **keys):
408        request.ConfigureWindow(display = self.display,
409                                onerror = onerror,
410                                window = self.id,
411                                attrs = keys)
412
413    def circulate(self, direction, onerror = None):
414        request.CirculateWindow(display = self.display,
415                                onerror = onerror,
416                                direction = direction,
417                                window = self.id)
418
419    def raise_window(self, onerror = None):
420        """alias for raising the window to the top - as in XRaiseWindow"""
421        self.configure(onerror, stack_mode = X.Above)
422
423    def query_tree(self):
424        return request.QueryTree(display = self.display,
425                                 window = self.id)
426
427    def change_property(self, property, property_type, format, data,
428                        mode = X.PropModeReplace, onerror = None):
429
430        request.ChangeProperty(display = self.display,
431                               onerror = onerror,
432                               mode = mode,
433                               window = self.id,
434                               property = property,
435                               type = property_type,
436                               data = (format, data))
437
438    def change_text_property(self, property, property_type, data,
439                        mode = X.PropModeReplace, onerror = None):
440        if not isinstance(data, bytes):
441            if property_type == Xatom.STRING:
442                data = data.encode(self._STRING_ENCODING)
443            elif property_type == self.display.get_atom('UTF8_STRING'):
444                data = data.encode(self._UTF8_STRING_ENCODING)
445        self.change_property(property, property_type, 8, data,
446                             mode=mode, onerror=onerror)
447
448    def delete_property(self, property, onerror = None):
449        request.DeleteProperty(display = self.display,
450                               onerror = onerror,
451                               window = self.id,
452                               property = property)
453
454    def get_property(self, property, property_type, offset, length, delete = 0):
455        r = request.GetProperty(display = self.display,
456                                delete = delete,
457                                window = self.id,
458                                property = property,
459                                type = property_type,
460                                long_offset = offset,
461                                long_length = length)
462
463        if r.property_type:
464            fmt, value = r.value
465            r.format = fmt
466            r.value = value
467            return r
468        else:
469            return None
470
471    def get_full_property(self, property, property_type, sizehint = 10):
472        prop = self.get_property(property, property_type, 0, sizehint)
473        if prop:
474            val = prop.value
475            if prop.bytes_after:
476                prop = self.get_property(property, property_type, sizehint,
477                                         prop.bytes_after // 4 + 1)
478                val = val + prop.value
479
480            prop.value = val
481            return prop
482        else:
483            return None
484
485    def get_full_text_property(self, property, property_type=X.AnyPropertyType, sizehint = 10):
486        prop = self.get_full_property(property, property_type,
487                                      sizehint=sizehint)
488        if prop is None or prop.format != 8:
489            return None
490        if prop.property_type == Xatom.STRING:
491            prop.value = prop.value.decode(self._STRING_ENCODING)
492        elif prop.property_type == self.display.get_atom('UTF8_STRING'):
493            prop.value = prop.value.decode(self._UTF8_STRING_ENCODING)
494        # FIXME: at least basic support for compound text would be nice.
495        # elif prop.property_type == self.display.get_atom('COMPOUND_TEXT'):
496        return prop.value
497
498    def list_properties(self):
499        r = request.ListProperties(display = self.display,
500                                   window = self.id)
501        return r.atoms
502
503    def set_selection_owner(self, selection, time, onerror = None):
504        request.SetSelectionOwner(display = self.display,
505                                  onerror = onerror,
506                                  window = self.id,
507                                  selection = selection,
508                                  time = time)
509
510    def convert_selection(self, selection, target, property, time, onerror = None):
511        request.ConvertSelection(display = self.display,
512                                 onerror = onerror,
513                                 requestor = self.id,
514                                 selection = selection,
515                                 target = target,
516                                 property = property,
517                                 time = time)
518
519    def send_event(self, event, event_mask = 0, propagate = 0, onerror = None):
520        request.SendEvent(display = self.display,
521                          onerror = onerror,
522                          propagate = propagate,
523                          destination = self.id,
524                          event_mask = event_mask,
525                          event = event)
526
527    def grab_pointer(self, owner_events, event_mask,
528                     pointer_mode, keyboard_mode,
529                     confine_to, cursor, time):
530
531        r = request.GrabPointer(display = self.display,
532                                owner_events = owner_events,
533                                grab_window = self.id,
534                                event_mask = event_mask,
535                                pointer_mode = pointer_mode,
536                                keyboard_mode = keyboard_mode,
537                                confine_to = confine_to,
538                                cursor = cursor,
539                                time = time)
540        return r.status
541
542    def grab_button(self, button, modifiers, owner_events, event_mask,
543                    pointer_mode, keyboard_mode,
544                    confine_to, cursor, onerror = None):
545
546        request.GrabButton(display = self.display,
547                           onerror = onerror,
548                           owner_events = owner_events,
549                           grab_window = self.id,
550                           event_mask = event_mask,
551                           pointer_mode = pointer_mode,
552                           keyboard_mode = keyboard_mode,
553                           confine_to = confine_to,
554                           cursor = cursor,
555                           button = button,
556                           modifiers = modifiers)
557
558    def ungrab_button(self, button, modifiers, onerror = None):
559        request.UngrabButton(display = self.display,
560                             onerror = onerror,
561                             button = button,
562                             grab_window = self.id,
563                             modifiers = modifiers)
564
565
566    def grab_keyboard(self, owner_events, pointer_mode, keyboard_mode, time):
567        r = request.GrabKeyboard(display = self.display,
568                                 owner_events = owner_events,
569                                 grab_window = self.id,
570                                 time = time,
571                                 pointer_mode = pointer_mode,
572                                 keyboard_mode = keyboard_mode)
573
574        return r.status
575
576    def grab_key(self, key, modifiers, owner_events, pointer_mode, keyboard_mode, onerror = None):
577        request.GrabKey(display = self.display,
578                        onerror = onerror,
579                        owner_events = owner_events,
580                        grab_window = self.id,
581                        modifiers = modifiers,
582                        key = key,
583                        pointer_mode = pointer_mode,
584                        keyboard_mode = keyboard_mode)
585
586    def ungrab_key(self, key, modifiers, onerror = None):
587        request.UngrabKey(display = self.display,
588                          onerror = onerror,
589                          key = key,
590                          grab_window = self.id,
591                          modifiers = modifiers)
592
593    def query_pointer(self):
594        return request.QueryPointer(display = self.display,
595                                    window = self.id)
596
597    def get_motion_events(self, start, stop):
598        r = request.GetMotionEvents(display = self.display,
599                                    window = self.id,
600                                    start = start,
601                                    stop = stop)
602        return r.events
603
604    def translate_coords(self, src_window, src_x, src_y):
605        return request.TranslateCoords(display = self.display,
606                                       src_wid = src_window,
607                                       dst_wid = self.id,
608                                       src_x = src_x,
609                                       src_y = src_y)
610
611    def warp_pointer(self, x, y, src_window = 0, src_x = 0, src_y = 0,
612                     src_width = 0, src_height = 0, onerror = None):
613
614        request.WarpPointer(display = self.display,
615                            onerror = onerror,
616                            src_window = src_window,
617                            dst_window = self.id,
618                            src_x = src_x,
619                            src_y = src_y,
620                            src_width = src_width,
621                            src_height = src_height,
622                            dst_x = x,
623                            dst_y = y)
624
625    def set_input_focus(self, revert_to, time, onerror = None):
626        request.SetInputFocus(display = self.display,
627                              onerror = onerror,
628                              revert_to = revert_to,
629                              focus = self.id,
630                              time = time)
631
632    def clear_area(self, x = 0, y = 0, width = 0, height = 0, exposures = 0, onerror = None):
633        request.ClearArea(display = self.display,
634                          onerror = onerror,
635                          exposures = exposures,
636                          window = self.id,
637                          x = x,
638                          y = y,
639                          width = width,
640                          height = height)
641
642    def create_colormap(self, visual, alloc):
643        mid = self.display.allocate_resource_id()
644        request.CreateColormap(display = self.display,
645                               alloc = alloc,
646                               mid = mid,
647                               window = self.id,
648                               visual = visual)
649        cls = self.display.get_resource_class('colormap', colormap.Colormap)
650        return cls(self.display, mid, owner = 1)
651
652    def list_installed_colormaps(self):
653        r = request.ListInstalledColormaps(display = self.display,
654                                           window = self.id)
655        return r.cmaps
656
657    def rotate_properties(self, properties, delta, onerror = None):
658        request.RotateProperties(display = self.display,
659                                 onerror = onerror,
660                                 window = self.id,
661                                 delta = delta,
662                                 properties = properties)
663
664    def set_wm_name(self, name, onerror = None):
665        self.change_text_property(Xatom.WM_NAME, Xatom.STRING, name,
666                                  onerror = onerror)
667
668    def get_wm_name(self):
669        return self.get_full_text_property(Xatom.WM_NAME, Xatom.STRING)
670
671    def set_wm_icon_name(self, name, onerror = None):
672        self.change_text_property(Xatom.WM_ICON_NAME, Xatom.STRING, name,
673                                  onerror = onerror)
674
675    def get_wm_icon_name(self):
676        return self.get_full_text_property(Xatom.WM_ICON_NAME, Xatom.STRING)
677
678    def set_wm_class(self, inst, cls, onerror = None):
679        self.change_text_property(Xatom.WM_CLASS, Xatom.STRING,
680                                  '%s\0%s\0' % (inst, cls),
681                                  onerror = onerror)
682
683    def get_wm_class(self):
684        value = self.get_full_text_property(Xatom.WM_CLASS, Xatom.STRING)
685        if value is None:
686            return None
687        parts = value.split('\0')
688        if len(parts) < 2:
689            return None
690        else:
691            return parts[0], parts[1]
692
693    def set_wm_transient_for(self, window, onerror = None):
694        self.change_property(Xatom.WM_TRANSIENT_FOR, Xatom.WINDOW,
695                             32, [window.id],
696                             onerror = onerror)
697
698    def get_wm_transient_for(self):
699        d = self.get_property(Xatom.WM_TRANSIENT_FOR, Xatom.WINDOW, 0, 1)
700        if d is None or d.format != 32 or len(d.value) < 1:
701            return None
702        else:
703            cls = self.display.get_resource_class('window', Window)
704            return cls(self.display, d.value[0])
705
706
707    def set_wm_protocols(self, protocols, onerror = None):
708        self.change_property(self.display.get_atom('WM_PROTOCOLS'),
709                             Xatom.ATOM, 32, protocols,
710                             onerror = onerror)
711
712    def get_wm_protocols(self):
713        d = self.get_full_property(self.display.get_atom('WM_PROTOCOLS'), Xatom.ATOM)
714        if d is None or d.format != 32:
715            return []
716        else:
717            return d.value
718
719    def set_wm_colormap_windows(self, windows, onerror = None):
720        self.change_property(self.display.get_atom('WM_COLORMAP_WINDOWS'),
721                             Xatom.WINDOW, 32,
722                             map(lambda w: w.id, windows),
723                             onerror = onerror)
724
725    def get_wm_colormap_windows(self):
726        d = self.get_full_property(self.display.get_atom('WM_COLORMAP_WINDOWS'),
727                                   Xatom.WINDOW)
728        if d is None or d.format != 32:
729            return []
730        else:
731            cls = self.display.get_resource_class('window', Window)
732            return map(lambda i, d = self.display, c = cls: c(d, i),
733                       d.value)
734
735
736    def set_wm_client_machine(self, name, onerror = None):
737        self.change_text_property(Xatom.WM_CLIENT_MACHINE, Xatom.STRING, name,
738                                  onerror = onerror)
739
740    def get_wm_client_machine(self):
741        return self.get_full_text_property(Xatom.WM_CLIENT_MACHINE, Xatom.STRING)
742
743    def set_wm_normal_hints(self, hints = {}, onerror = None, **keys):
744        self._set_struct_prop(Xatom.WM_NORMAL_HINTS, Xatom.WM_SIZE_HINTS,
745                              icccm.WMNormalHints, hints, keys, onerror)
746
747    def get_wm_normal_hints(self):
748        return self._get_struct_prop(Xatom.WM_NORMAL_HINTS, Xatom.WM_SIZE_HINTS,
749                                     icccm.WMNormalHints)
750
751    def set_wm_hints(self, hints = {}, onerror = None, **keys):
752        self._set_struct_prop(Xatom.WM_HINTS, Xatom.WM_HINTS,
753                              icccm.WMHints, hints, keys, onerror)
754
755    def get_wm_hints(self):
756        return self._get_struct_prop(Xatom.WM_HINTS, Xatom.WM_HINTS,
757                                     icccm.WMHints)
758
759    def set_wm_state(self, hints = {}, onerror = None, **keys):
760        atom = self.display.get_atom('WM_STATE')
761        self._set_struct_prop(atom, atom, icccm.WMState, hints, keys, onerror)
762
763    def get_wm_state(self):
764        atom = self.display.get_atom('WM_STATE')
765        return self._get_struct_prop(atom, atom, icccm.WMState)
766
767    def set_wm_icon_size(self, hints = {}, onerror = None, **keys):
768        self._set_struct_prop(Xatom.WM_ICON_SIZE, Xatom.WM_ICON_SIZE,
769                              icccm.WMIconSize, hints, keys, onerror)
770
771    def get_wm_icon_size(self):
772        return self._get_struct_prop(Xatom.WM_ICON_SIZE, Xatom.WM_ICON_SIZE,
773                                     icccm.WMIconSize)
774
775    # Helper function for getting structured properties.
776    # pname and ptype are atoms, and pstruct is a Struct object.
777    # Returns a DictWrapper, or None
778
779    def _get_struct_prop(self, pname, ptype, pstruct):
780        r = self.get_property(pname, ptype, 0, pstruct.static_size // 4)
781        if r and r.format == 32:
782            value = r.value.tostring()
783            if len(value) == pstruct.static_size:
784                return pstruct.parse_binary(value, self.display)[0]
785
786        return None
787
788    # Helper function for setting structured properties.
789    # pname and ptype are atoms, and pstruct is a Struct object.
790    # hints is a mapping or a DictWrapper, keys is a mapping.  keys
791    # will be modified.  onerror is the error handler.
792
793    def _set_struct_prop(self, pname, ptype, pstruct, hints, keys, onerror):
794        if isinstance(hints, rq.DictWrapper):
795            keys.update(hints._data)
796        else:
797            keys.update(hints)
798
799        value = pstruct.to_binary(*(), **keys)
800
801        self.change_property(pname, ptype, 32, value, onerror = onerror)
802
803
804class Pixmap(Drawable):
805    __pixmap__ = resource.Resource.__resource__
806
807    def free(self, onerror = None):
808        request.FreePixmap(display = self.display,
809                           onerror = onerror,
810                           pixmap = self.id)
811
812        self.display.free_resource_id(self.id)
813
814    def create_cursor(self, mask, foreground, background, x, y):
815        fore_red, fore_green, fore_blue = foreground
816        back_red, back_green, back_blue = background
817        cid = self.display.allocate_resource_id()
818        request.CreateCursor(display = self.display,
819                             cid = cid,
820                             source = self.id,
821                             mask = mask,
822                             fore_red = fore_red,
823                             fore_green = fore_green,
824                             fore_blue = fore_blue,
825                             back_red = back_red,
826                             back_green = back_green,
827                             back_blue = back_blue,
828                             x = x,
829                             y = y)
830        cls = self.display.get_resource_class('cursor', cursor.Cursor)
831        return cls(self.display, cid, owner = 1)
832
833
834def roundup(value, unit):
835    return (value + (unit - 1)) & ~(unit - 1)
836