~pieq/checkbox/fix-1576570-stress-test-progress-log

« back to all changes in this revision

Viewing changes to checkbox-support/checkbox_support/contrib/xrandr.py

  • Committer: Sylvain Pineau
  • Date: 2014-01-07 13:39:38 UTC
  • mto: This revision was merged to the branch mainline in revision 2588.
  • Revision ID: sylvain.pineau@canonical.com-20140107133938-46v5ehofwa9whl1e
checkbox-support: Copy required modules from checkbox-old/checkbox

and their corresponding tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python3
 
2
# -*- coding: utf-8 -*-
 
3
#
 
4
# Python-XRandR provides a high level API for the XRandR extension of the
 
5
# X.org server. XRandR allows to configure resolution, refresh rate, rotation
 
6
# of the screen and multiple outputs of graphics cards.
 
7
#
 
8
# Copyright 2007 © Sebastian Heinlein <sebastian.heinlein@web.de>
 
9
# Copyright 2007 © Michael Vogt <mvo@ubuntu.com>
 
10
# Copyright 2007 © Canonical Ltd.
 
11
#
 
12
# In many aspects it follows the design of the xrand tool of the X.org, which
 
13
# comes with the following copyright:
 
14
#
 
15
# Copyright © 2001 Keith Packard, member of The XFree86 Project, Inc.
 
16
# Copyright © 2002 Hewlett Packard Company, Inc.
 
17
# Copyright © 2006 Intel Corporation
 
18
#
 
19
# And can be downloaded here:
 
20
#
 
21
# git://anongit.freedesktop.org/git/xorg/app/xrandr
 
22
#
 
23
# This library is free software; you can redistribute it and/or
 
24
# modify it under the terms of the GNU Lesser General Public
 
25
# License as published by the Free Software Foundation; either
 
26
# version 2.1 of the License, or any later version.
 
27
#
 
28
# This library is distributed in the hope that it will be useful,
 
29
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
30
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
31
# Lesser General Public License for more details.
 
32
#
 
33
# You should have received a copy of the GNU Lesser General Public
 
34
# License along with this library; if not, write to the Free Software
 
35
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
36
# MA  02110-1301  USA
 
37
 
 
38
from ctypes import (
 
39
    POINTER,
 
40
    Structure,
 
41
    byref,
 
42
    c_char_p,
 
43
    c_void_p,
 
44
    c_int,
 
45
    c_long,
 
46
    c_ulong,
 
47
    c_ushort,
 
48
    cdll,
 
49
)
 
50
import os
 
51
 
 
52
RR_ROTATE_0 = 1
 
53
RR_ROTATE_90 = 2
 
54
RR_ROTATE_180 = 4
 
55
RR_ROTATE_270 = 8
 
56
RR_REFLECT_X = 16
 
57
RR_REFLECT_Y = 32
 
58
 
 
59
RR_CONNECTED = 0
 
60
RR_DISCONNECTED = 1
 
61
RR_UNKOWN_CONNECTION = 2
 
62
 
 
63
RR_BAD_OUTPUT = 0
 
64
RR_BAD_CRTC = 1
 
65
RR_BAD_MODE = 2
 
66
 
 
67
RR_SET_CONFIG_SUCCESS = 0
 
68
RR_SET_CONFIG_INVALID_CONFIG_TIME = 1
 
69
RR_SET_CONFIG_INVALID_TIME = 2
 
70
RR_SET_CONFIG_FAILED = 3
 
71
 
 
72
# Flags to keep track of changes
 
73
CHANGES_NONE = 0
 
74
CHANGES_CRTC = 1
 
75
CHANGES_MODE = 2
 
76
CHANGES_RELATION = 4
 
77
CHANGES_POSITION = 8
 
78
CHANGES_ROTATION = 16
 
79
CHANGES_REFLECTION = 32
 
80
CHANGES_AUTOMATIC = 64
 
81
CHANGES_REFRESH = 128
 
82
CHANGES_PROPERTY = 256
 
83
 
 
84
# Relation information
 
85
RELATION_ABOVE = 0
 
86
RELATION_BELOW = 1
 
87
RELATION_RIGHT_OF = 2
 
88
RELATION_LEFT_OF = 3
 
89
RELATION_SAME_AS = 4
 
90
 
 
91
# some fundamental datatypes
 
92
RRCrtc = c_long
 
93
RROutput = c_long
 
94
RRMode = c_long
 
95
Connection = c_ushort
 
96
SubpixelOrder = c_ushort
 
97
Time = c_ulong
 
98
Rotation = c_ushort
 
99
Status = c_int
 
100
 
 
101
# load the libs
 
102
xlib = cdll.LoadLibrary('libX11.so.6')
 
103
rr = cdll.LoadLibrary('libXrandr.so.2')
 
104
 
 
105
 
 
106
# query resources
 
107
class _XRRModeInfo(Structure):
 
108
    _fields_ = [
 
109
        ("id", RRMode),  # XID is c_long
 
110
        ("width", c_int),
 
111
        ("height", c_int),
 
112
        ("dotClock", c_long),
 
113
        ("hSyncStart", c_int),
 
114
        ("hSyncEnd", c_int),
 
115
        ("hTotal", c_int),
 
116
        ("hSkew", c_int),
 
117
        ("vSyncStart", c_int),
 
118
        ("vSyncEnd", c_int),
 
119
        ("vTotal", c_int),
 
120
        ("name", c_char_p),
 
121
        ("nameLength", c_int),
 
122
        ("modeFlags", c_long),
 
123
        ]
 
124
 
 
125
 
 
126
class _XRRScreenSize(Structure):
 
127
    _fields_ = [
 
128
        ("width", c_int),
 
129
        ("height", c_int),
 
130
        ("mwidth", c_int),
 
131
        ("mheight", c_int)
 
132
        ]
 
133
 
 
134
 
 
135
class _XRRCrtcInfo(Structure):
 
136
    _fields_ = [
 
137
        ("timestamp", Time),
 
138
        ("x", c_int),
 
139
        ("y", c_int),
 
140
        ("width", c_int),
 
141
        ("height", c_int),
 
142
        ("mode", RRMode),
 
143
        ("rotation", c_int),
 
144
        ("noutput", c_int),
 
145
        ("outputs", POINTER(RROutput)),
 
146
        ("rotations", Rotation),
 
147
        ("npossible", c_int),
 
148
        ("possible", POINTER(RROutput)),
 
149
        ]
 
150
 
 
151
 
 
152
class _XRRScreenResources(Structure):
 
153
    _fields_ = [
 
154
        ("timestamp", Time),
 
155
        ("configTimestamp", Time),
 
156
        ("ncrtc", c_int),
 
157
        ("crtcs", POINTER(RRCrtc)),
 
158
        ("noutput", c_int),
 
159
        ("outputs", POINTER(RROutput)),
 
160
        ("nmode", c_int),
 
161
        ("modes", POINTER(_XRRModeInfo)),
 
162
        ]
 
163
 
 
164
 
 
165
class RRError(Exception):
 
166
    """Base exception class of the module"""
 
167
    pass
 
168
 
 
169
 
 
170
class UnsupportedRRError(RRError):
 
171
    """Raised if the required XRandR extension version is not available"""
 
172
    def __init__(self, required, current):
 
173
        self.required = required
 
174
        self.current = current
 
175
 
 
176
 
 
177
# XRRGetOutputInfo
 
178
class _XRROutputInfo(Structure):
 
179
    _fields_ = [
 
180
        ("timestamp", Time),
 
181
        ("crtc", c_int),
 
182
        ("name", c_char_p),
 
183
        ("nameLen", c_int),
 
184
        ("mm_width", c_ulong),
 
185
        ("mm_height", c_ulong),
 
186
        ("connection", Connection),
 
187
        ("subpixel_order", SubpixelOrder),
 
188
        ("ncrtc", c_int),
 
189
        ("crtcs", POINTER(RRCrtc)),
 
190
        ("nclone", c_int),
 
191
        ("clones", POINTER(RROutput)),
 
192
        ("nmode", c_int),
 
193
        ("npreferred", c_int),
 
194
        ("modes", POINTER(RRMode))
 
195
        ]
 
196
 
 
197
 
 
198
class _XRRCrtcGamma(Structure):
 
199
    _fields_ = [
 
200
        ('size', c_int),
 
201
        ('red', POINTER(c_ushort)),
 
202
        ('green', POINTER(c_ushort)),
 
203
        ('blue', POINTER(c_ushort)),
 
204
        ]
 
205
 
 
206
 
 
207
def _array_conv(array, type, conv=lambda x: x):
 
208
    length = len(array)
 
209
    res = (type * length)()
 
210
    for i in range(length):
 
211
        res[i] = conv(array[i])
 
212
    return res
 
213
 
 
214
 
 
215
class Output:
 
216
    """The output is a reference to a supported output jacket of the graphics
 
217
       card. Outputs are attached to a hardware pipe to be used. Furthermore
 
218
       they can be a clone of another output or show a subset of the screen"""
 
219
    def __init__(self, info, id, screen):
 
220
        """Initializes an output instance"""
 
221
        self._info = info
 
222
        self.id = id
 
223
        self._screen = screen
 
224
        # Store changes later here
 
225
        self._mode = None
 
226
        self._crtc = None
 
227
        self._rotation = RR_ROTATE_0
 
228
        self._relation = None
 
229
        self._relation_offset = 0
 
230
        self._relative_to = None
 
231
        self._position = None
 
232
        self._reflection = None
 
233
        self._automatic = None
 
234
        self._rate = None
 
235
        self._changes = CHANGES_NONE
 
236
        self._x = 0
 
237
        self._y = 0
 
238
 
 
239
        self.name = self._info.contents.name
 
240
 
 
241
    def __del__(self):
 
242
        """Frees the internal reference to the output info if the output gets
 
243
           removed"""
 
244
        rr.XRRFreeOutputInfo(self._info)
 
245
 
 
246
    def get_physical_width(self):
 
247
        """Returns the display width reported by the connected output device"""
 
248
        return self._info.contents.mm_width
 
249
 
 
250
    def get_physical_height(self):
 
251
        """
 
252
        Returns the display height reported by the connected output device
 
253
        """
 
254
        return self._info.contents.mm_height
 
255
 
 
256
    def get_crtc(self):
 
257
        """Returns the xid of the hardware pipe to which the output is
 
258
           attached. If the output is disabled it will return 0"""
 
259
        return self._info.contents.crtc
 
260
 
 
261
    def get_crtcs(self):
 
262
        """Returns the xids of the hardware pipes to which the output could
 
263
           be attached"""
 
264
        crtcs = []
 
265
        for i in range(self._info.contents.ncrtc):
 
266
            for crtc in self._screen.crtcs:
 
267
                if crtc.xid == self._info.contents.crtcs[i]:
 
268
                    crtcs.append(crtc)
 
269
        return crtcs
 
270
 
 
271
    def get_available_rotations(self):
 
272
        """Returns a binary flag of the supported rotations of the output or
 
273
           0 if the output is disabled"""
 
274
        rotations = RR_ROTATE_0
 
275
        found = False
 
276
        if self.is_active():
 
277
            # Get the rotations supported by all crtcs to make assigning
 
278
            # crtcs easier. Furthermore there don't seem to be so many
 
279
            # cards which show another behavior
 
280
            for crtc in self.get_crtcs():
 
281
                # Set rotations to the value of the first found crtc and
 
282
                # then create a subset only for all other crtcs
 
283
                if not found:
 
284
                    rotations = crtc.get_available_rotations()
 
285
                    found = True
 
286
                else:
 
287
                    rotations = rotations & crtc.get_available_rotations()
 
288
        return rotations
 
289
 
 
290
    def get_available_modes(self):
 
291
        """Returns the list of supported mode lines (resolution, refresh rate)
 
292
           that are supported by the connected device"""
 
293
        modes = []
 
294
        for m in range(self._info.contents.nmode):
 
295
            output_modes = self._info.contents.modes
 
296
            for s in range(self._screen._resources.contents.nmode):
 
297
                screen_modes = self._screen._resources.contents.modes
 
298
                if screen_modes[s].id == output_modes[m]:
 
299
                    modes.append(screen_modes[s])
 
300
        return modes
 
301
 
 
302
    def get_preferred_mode(self):
 
303
        """Returns an index that refers to the list of available modes and
 
304
           points to the preferred mode of the connected device"""
 
305
        return self._info.contents.npreferred
 
306
 
 
307
    def is_active(self):
 
308
        """Returns True if the output is attached to a hardware pipe, is
 
309
           enabled"""
 
310
        return self._info.contents.crtc != 0
 
311
 
 
312
    def is_connected(self):
 
313
        """Return True if a device is detected at the output"""
 
314
        if self._info.contents.connection in (RR_CONNECTED,
 
315
                                              RR_UNKOWN_CONNECTION):
 
316
            return True
 
317
        return False
 
318
 
 
319
    def disable(self):
 
320
        """Disables the output"""
 
321
        if not self.is_active():
 
322
            return
 
323
        self._mode = None
 
324
        self._crtc._outputs.remove(self)
 
325
        self._crtc = None
 
326
        self._changes = self._changes | CHANGES_CRTC | CHANGES_MODE
 
327
 
 
328
    def set_to_mode(self, mode):
 
329
        modes = self.get_available_modes()
 
330
        if mode in range(len(modes)):
 
331
            self._mode = modes[mode].id
 
332
            return
 
333
        raise RRError("Mode is not available")
 
334
 
 
335
    def set_to_preferred_mode(self):
 
336
        modes = self.get_available_modes()
 
337
        mode = modes[self.get_preferred_mode()]
 
338
        if mode != None:
 
339
            self._mode = mode.id
 
340
            return
 
341
        raise RRError("Preferred mode is not available")
 
342
 
 
343
    def get_clones(self):
 
344
        """Returns the xids of the outputs which can be clones of the output"""
 
345
        clones = []
 
346
        for i in range(self._info.contents.nclone):
 
347
            id = self._info.contents.clones[i]
 
348
            o = self._screen.get_output_by_id(id)
 
349
            clones.append(o)
 
350
        return clones
 
351
 
 
352
    def set_relation(self, relative, relation, offset=0):
 
353
        """Sets the position of the output in relation to the given one"""
 
354
        rel = self._screen.get_output_by_name(relative)
 
355
        if rel and relation in (RELATION_LEFT_OF, RELATION_RIGHT_OF,
 
356
                                RELATION_ABOVE, RELATION_BELOW,
 
357
                                RELATION_SAME_AS):
 
358
            self._relation = relation
 
359
            self._relative_to = rel
 
360
            self._relation_offset = offset
 
361
            self._changes = self._changes | CHANGES_RELATION
 
362
        else:
 
363
            raise RRError("The given relative output or relation is not "
 
364
                          "available")
 
365
 
 
366
    def has_changed(self, changes=None):
 
367
        """Checks if the output has changed: Either for a specific change or
 
368
           generally"""
 
369
        if changes:
 
370
            return self._changes & changes
 
371
        else:
 
372
            return self._changes != CHANGES_NONE
 
373
 
 
374
 
 
375
class Crtc:
 
376
    """The crtc is a reference to a hardware pipe that is provided by the
 
377
       graphics device. Outputs can be attached to crtcs"""
 
378
 
 
379
    def __init__(self, info, xid, screen):
 
380
        """Initializes the hardware pipe object"""
 
381
        self._info = info
 
382
        self.xid = xid
 
383
        self._screen = screen
 
384
        self._outputs = []
 
385
 
 
386
    def __del__(self):
 
387
        """Frees the reference to the rendering pipe if the instance gets
 
388
           removed"""
 
389
        rr.XRRFreeCrtcConfigInfo(self._info)
 
390
 
 
391
    def get_xid(self):
 
392
        """Returns the internal id of the crtc from the X server"""
 
393
        return self.xid
 
394
 
 
395
    def get_available_rotations(self):
 
396
        """Returns a binary flag that contains the supported rotations of the
 
397
           hardware pipe"""
 
398
        return self._info.contents.rotations
 
399
 
 
400
    def set_config(self, x, y, mode, outputs, rotation=RR_ROTATE_0):
 
401
        """Configures the render pipe with the given mode and outputs. X and y
 
402
           set the position of the crtc output in the screen"""
 
403
        rr.XRRSetCrtcConfig(self._screen._display,
 
404
                            self._screen._resources,
 
405
                            self.xid,
 
406
                            self._screen.get_timestamp(),
 
407
                            c_int(x), c_int(y),
 
408
                            mode,
 
409
                            rotation,
 
410
                            _array_conv(outputs, RROutput, lambda x: x.id),
 
411
                            len(outputs))
 
412
 
 
413
    def apply_changes(self):
 
414
        """Applies the stored changes"""
 
415
        if len(self._outputs) > 0:
 
416
            output = self._outputs[0]
 
417
            self.set_config(output._x, output._y, output._mode,
 
418
                            self._outputs, output._rotation)
 
419
        else:
 
420
            self.disable()
 
421
 
 
422
    def disable(self):
 
423
        """Turns off all outputs on the crtc"""
 
424
        rr.XRRSetCrtcConfig(self._screen._display,
 
425
                            self._screen._resources,
 
426
                            self.xid,
 
427
                            self._screen.get_timestamp(),
 
428
                            0, 0,
 
429
                            None,
 
430
                            RR_ROTATE_0,
 
431
                            0, 0)
 
432
 
 
433
    #FIXME: support gamma settings
 
434
    """
 
435
    def get_gamma_size(self):
 
436
        return rr.XRRGetCrtcGammaSize(self._screen._display, self.id)
 
437
    def get_gamma(self):
 
438
        result = rr.XRRGetCrtcGamma(self._screen._display, self.id)
 
439
        return _from_gamma(result)
 
440
    def set_gamma(self, gamma):
 
441
        g = _to_gamma(gamma)
 
442
        rr.XRRSetCrtcGamma(self._screen._display, self.id, g)
 
443
        rr.XRRFreeGamma(g)
 
444
        gamma = property(get_gamma, set_gamma)"""
 
445
 
 
446
    def load_outputs(self):
 
447
        """Get the currently assigned outputs"""
 
448
        outputs = []
 
449
        for i in range(self._info.contents.noutput):
 
450
            id = self._info.contents.outputs[i]
 
451
            o = self._screen.get_output_by_id(id)
 
452
            outputs.append(o)
 
453
        self._outputs = outputs
 
454
 
 
455
    def get_outputs(self):
 
456
        """Returns the list of attached outputs"""
 
457
        return self._outputs
 
458
 
 
459
    def add_output(self, output):
 
460
        """Adds the specified output to the crtc"""
 
461
        output._crtc = self
 
462
        self._outputs.append(output)
 
463
 
 
464
    def supports_output(self, output):
 
465
        """Check if the output can be used by the crtc.
 
466
           See check_crtc_for_output in xrandr.c"""
 
467
        if not self.xid in [c.xid for c in output.get_crtcs()]:
 
468
            return False
 
469
        if len(self._outputs):
 
470
            for other in self._outputs:
 
471
                if other == output:
 
472
                    continue
 
473
                if other._x != output._x:
 
474
                    return False
 
475
                if other._y != output._y:
 
476
                    return False
 
477
                if other._mode != output._mode:
 
478
                    return False
 
479
                if other._rotation != output._rotation:
 
480
                    return False
 
481
        #FIXME: pick_crtc is still missing
 
482
        elif self._info.contents.noutput > 0:
 
483
            if self._info.contents.x != output._x:
 
484
                return False
 
485
            if self._info.contents.y != output._y:
 
486
                return False
 
487
            if self._info.contents.mode_info != output._mode:
 
488
                return False
 
489
            if self._info.rotation != output._rotation:
 
490
                return False
 
491
        return True
 
492
 
 
493
    def supports_rotation(self, rotation):
 
494
        """Check if the given rotation is supported by the crtc"""
 
495
        rotations = self._info.contents.rotations
 
496
        dir = rotation & (RR_ROTATE_0 | RR_ROTATE_90 | RR_ROTATE_180 |
 
497
                          RR_ROTATE_270)
 
498
        reflect = rotation & (RR_REFLECT_X | RR_REFLECT_Y)
 
499
        if (((rotations & dir) != 0) and ((rotations & reflect) == reflect)):
 
500
            return True
 
501
        return False
 
502
 
 
503
    def has_changed(self):
 
504
        """Check if there are any new outputs assigned to the crtc or any
 
505
           outputs with a changed mode or position"""
 
506
        if len(self._outputs) != self._info.contents.noutput:
 
507
            return True
 
508
        for i in range(self._info.contents.noutput):
 
509
            id = self._info.contents.outputs[i]
 
510
            output = self._screen.get_output_by_id(id)
 
511
            if not output in self._outputs:
 
512
                return True
 
513
            if output.has_changed():
 
514
                return True
 
515
        return False
 
516
 
 
517
 
 
518
class Screen:
 
519
    def __init__(self, dpy, screen=-1):
 
520
        """Initializes the screen"""
 
521
        # Some sane default values
 
522
        self.outputs = {}
 
523
        self.crtcs = []
 
524
        self._width = 0
 
525
        self._height = 0
 
526
        self._width_max = 0
 
527
        self._height_max = 0
 
528
        self._width_min = 0
 
529
        self._height_min = 0
 
530
        self._width_mm = 0
 
531
        self._height_mm = 0
 
532
 
 
533
        self._display = dpy
 
534
        if not -1 <= screen < xlib.XScreenCount(dpy):
 
535
            raise RRError("The chosen screen is not available", screen)
 
536
        elif screen == -1:
 
537
            self._screen = xlib.XDefaultScreen(dpy)
 
538
        else:
 
539
            self._screen = screen
 
540
        self._root = xlib.XDefaultRootWindow(self._display, self._screen)
 
541
        self._id = rr.XRRRootToScreen(self._display, self._root)
 
542
 
 
543
        self._load_resources()
 
544
        self._load_config()
 
545
        (self._width, self._height,
 
546
         self._width_mm, self._height_mm) = self.get_size()
 
547
        if XRANDR_VERSION >= (1, 2):
 
548
            self._load_screen_size_range()
 
549
            self._load_crtcs()
 
550
            self._load_outputs()
 
551
 
 
552
        # Store XRandR 1.0 changes here
 
553
        self._rate = self.get_current_rate()
 
554
        self._rotation = self.get_current_rotation()
 
555
        self._size_index = self.get_current_size_index()
 
556
 
 
557
    def __del__(self):
 
558
        """Free the reference to the interal screen config if the screen
 
559
           gets removed"""
 
560
        rr.XRRFreeScreenConfigInfo(self._config)
 
561
 
 
562
    def _load_config(self):
 
563
        """Loads the screen configuration. Only needed privately by the
 
564
           the bindings"""
 
565
        class XRRScreenConfiguration(Structure):
 
566
            " private to Xrandr "
 
567
            pass
 
568
        gsi = rr.XRRGetScreenInfo
 
569
        gsi.restype = POINTER(XRRScreenConfiguration)
 
570
        self._config = gsi(self._display, self._root)
 
571
 
 
572
    def _load_screen_size_range(self):
 
573
        """Detects the dimensionios of the screen"""
 
574
        minWidth = c_int()
 
575
        minHeight = c_int()
 
576
        maxWidth = c_int()
 
577
        maxHeight = c_int()
 
578
        res = rr.XRRGetScreenSizeRange(self._display, self._root,
 
579
                                       byref(minWidth), byref(minHeight),
 
580
                                       byref(maxWidth), byref(maxHeight))
 
581
        if res:
 
582
            self._width_max = maxWidth.value
 
583
            self._width_min = minWidth.value
 
584
            self._height_max = maxHeight.value
 
585
            self._height_min = minHeight.value
 
586
 
 
587
    def _load_resources(self):
 
588
        """Loads the screen resources. Only needed privately for the
 
589
           bindings"""
 
590
        gsr = rr.XRRGetScreenResources
 
591
        gsr.restype = POINTER(_XRRScreenResources)
 
592
        self._resources = gsr(self._display, self._root)
 
593
 
 
594
    def _load_crtcs(self):
 
595
        """Loads the available XRandR 1.2 crtcs (hardware pipes) of
 
596
           the screen"""
 
597
        gci = rr.XRRGetCrtcInfo
 
598
        gci.restype = POINTER(_XRRCrtcInfo)
 
599
        c = self._resources.contents.crtcs
 
600
        for i in range(self._resources.contents.ncrtc):
 
601
            xrrcrtcinfo = gci(self._display, self._resources, c[i])
 
602
            self.crtcs.append(Crtc(xrrcrtcinfo, c[i], self))
 
603
 
 
604
    def _load_outputs(self):
 
605
        """Loads the available XRandR 1.2 outputs of the screen"""
 
606
        goi = rr.XRRGetOutputInfo
 
607
        goi.restype = POINTER(_XRROutputInfo)
 
608
        o = self._resources.contents.outputs
 
609
        for i in range(self._resources.contents.noutput):
 
610
            xrroutputinfo = goi(self._display, self._resources, o[i])
 
611
            output = Output(xrroutputinfo, o[i], self)
 
612
            self.outputs[xrroutputinfo.contents.name] = output
 
613
            # Store the mode of the crtc in the output instance
 
614
            crtc = self.get_crtc_by_xid(output.get_crtc())
 
615
            if crtc:
 
616
                output._mode = crtc._info.contents.mode
 
617
                crtc.add_output(output)
 
618
 
 
619
    def get_size(self):
 
620
        """Returns the current pixel and physical size of the screen"""
 
621
        width = xlib.XDisplayWidth(self._display, self._screen)
 
622
        width_mm = xlib.XDisplayWidthMM(self._display, self._screen)
 
623
        height = xlib.XDisplayHeight(self._display, self._screen)
 
624
        height_mm = xlib.XDisplayHeightMM(self._display, self._screen)
 
625
        return width, height, width_mm, height_mm
 
626
 
 
627
    def get_timestamp(self):
 
628
        """Creates a X timestamp that must be used when applying changes, since
 
629
           they can be delayed"""
 
630
        config_timestamp = Time()
 
631
        rr.XRRTimes.restpye = c_ulong
 
632
        return rr.XRRTimes(self._display, self._id, byref(config_timestamp))
 
633
 
 
634
    def get_crtc_by_xid(self, xid):
 
635
        """Returns the crtc with the given xid or None"""
 
636
        for crtc in self.crtcs:
 
637
            if crtc.xid == xid:
 
638
                return crtc
 
639
        return None
 
640
 
 
641
    def get_current_rate(self):
 
642
        """Returns the currently used refresh rate"""
 
643
        _check_required_version((1, 0))
 
644
        xccr = rr.XRRConfigCurrentRate
 
645
        xccr.restype = c_int
 
646
        return xccr(self._config)
 
647
 
 
648
    def get_available_rates_for_size_index(self, size_index):
 
649
        """Returns the refresh rates that are supported by the screen for
 
650
           the given resolution. See get_available_sizes for the resolution to
 
651
           which size_index points"""
 
652
        _check_required_version((1, 0))
 
653
        rates = []
 
654
        nrates = c_int()
 
655
        rr.XRRConfigRates.restype = POINTER(c_ushort)
 
656
        _rates = rr.XRRConfigRates(self._config, size_index, byref(nrates))
 
657
        for r in range(nrates.value):
 
658
            rates.append(_rates[r])
 
659
        return rates
 
660
 
 
661
    def get_current_rotation(self):
 
662
        """Returns the currently used rotation. Can be RR_ROTATE_0,
 
663
        RR_ROTATE_90, RR_ROTATE_180 or RR_ROTATE_270"""
 
664
        _check_required_version((1, 0))
 
665
        current = c_ushort()
 
666
        rr.XRRConfigRotations(self._config, byref(current))
 
667
        return current.value
 
668
 
 
669
    def get_available_rotations(self):
 
670
        """Returns a binary flag that holds the available resolutions"""
 
671
        _check_required_version((1, 0))
 
672
        current = c_ushort()
 
673
        rotations = rr.XRRConfigRotations(self._config, byref(current))
 
674
        return rotations
 
675
 
 
676
    def get_current_size_index(self):
 
677
        """Returns the position of the currently used resolution size in the
 
678
           list of available resolutions. See get_available_sizes"""
 
679
        _check_required_version((1, 0))
 
680
        rotation = c_ushort()
 
681
        size = rr.XRRConfigCurrentConfiguration(self._config,
 
682
                                                byref(rotation))
 
683
        return size
 
684
 
 
685
    def get_available_sizes(self):
 
686
        """Returns the available resolution sizes of the screen. The size
 
687
           index points to the corresponding resolution of this list"""
 
688
        _check_required_version((1, 0))
 
689
        sizes = []
 
690
        nsizes = c_int()
 
691
        xcs = rr.XRRConfigSizes
 
692
        xcs.restype = POINTER(_XRRScreenSize)
 
693
        _sizes = xcs(self._config, byref(nsizes))
 
694
        for r in range(nsizes.value):
 
695
            sizes.append(_sizes[r])
 
696
        return sizes
 
697
 
 
698
    def set_config(self, size_index, rate, rotation):
 
699
        """Configures the screen with the given resolution at the given size
 
700
           index, rotation and refresh rate. To get in effect call
 
701
           Screen.apply_config()"""
 
702
        _check_required_version((1, 0))
 
703
        self.set_size_index(size_index)
 
704
        self.set_refresh_rate(rate)
 
705
        self.set_rotation(rotation)
 
706
 
 
707
    def set_size_index(self, index):
 
708
        """Sets the reoslution of the screen. To get in effect call
 
709
           Screen.apply_config()"""
 
710
        if index in range(len(self.get_available_sizes())):
 
711
            self._size_index = index
 
712
        else:
 
713
            raise RRError("There isn't any size associated "
 
714
                          "to the index %s" % index)
 
715
 
 
716
    def set_rotation(self, rotation):
 
717
        """Sets the rotation of the screen. To get in effect call
 
718
           Screen.apply_config()"""
 
719
        if self.get_available_rotations() & rotation:
 
720
            self._rotation = rotation
 
721
        else:
 
722
            raise RRError("The chosen rotation is not supported")
 
723
 
 
724
    def set_refresh_rate(self, rate):
 
725
        """Sets the refresh rate of the screen. To get in effect call
 
726
           Screen.apply_config()"""
 
727
        if rate in self.get_available_rates_for_size_index(self._size_index):
 
728
            self._rate = rate
 
729
        else:
 
730
            raise RRError("The chosen refresh rate %s is not "
 
731
                          "supported" % rate)
 
732
 
 
733
    def get_mode_by_xid(self, xid):
 
734
        """Returns the mode of the given xid"""
 
735
        screen_modes = self._resources.contents.modes
 
736
        for s in range(self._resources.contents.nmode):
 
737
            if screen_modes[s].id == xid:
 
738
                return screen_modes[s]
 
739
        return None
 
740
 
 
741
    def get_output_by_name(self, name):
 
742
        """Returns the output of the screen with the given name or None"""
 
743
        if name in self.outputs:
 
744
            return self.outputs[name]
 
745
        else:
 
746
            return None
 
747
 
 
748
    def get_output_by_id(self, id):
 
749
        """Returns the output of the screen with the given xid or None"""
 
750
        for o in list(self.outputs.values()):
 
751
            if o.id == id:
 
752
                return o
 
753
        return None
 
754
 
 
755
    def print_info(self, verbose=False):
 
756
        """Prints some information about the detected screen and its outputs"""
 
757
        _check_required_version((1, 0))
 
758
        print("Screen %s: minimum %s x %s, current %s x %s, maximum %s x %s" %\
 
759
              (self._screen,
 
760
               self._width_min, self._height_min,
 
761
               self._width, self._height,
 
762
               self._width_max, self._height_max))
 
763
        print("          %smm x %smm" % (self._width_mm, self._height_mm))
 
764
        print("Crtcs: %s" % len(self.crtcs))
 
765
        if verbose:
 
766
            print("Modes (%s):" % self._resources.contents.nmode)
 
767
            modes = self._resources.contents.modes
 
768
            for i in range(self._resources.contents.nmode):
 
769
                print("  %s - %sx%s" % (modes[i].name,
 
770
                                       modes[i].width,
 
771
                                       modes[i].height))
 
772
        i = 0
 
773
        print("Sizes @ Refresh Rates:")
 
774
        for s in self.get_available_sizes():
 
775
            print("  [%s] %s x %s @ %s" % (
 
776
                i, s.width, s.height,
 
777
                self.get_available_rates_for_size_index(i)))
 
778
            i += 1
 
779
        print("Rotations:")
 
780
        rots = self.get_available_rotations()
 
781
        if rots & RR_ROTATE_0:
 
782
            print("normal")
 
783
        if rots & RR_ROTATE_90:
 
784
            print("right")
 
785
        if rots & RR_ROTATE_180:
 
786
            print("inverted")
 
787
        if rots & RR_ROTATE_270:
 
788
            print("left")
 
789
        print("")
 
790
        print("Outputs:")
 
791
        for o in list(self.outputs.keys()):
 
792
            output = self.outputs[o]
 
793
            print("  %s" % o)
 
794
            if output.is_connected():
 
795
                print("(%smm x %smm)" % (output.get_physical_width(),
 
796
                                         output.get_physical_height()))
 
797
                modes = output.get_available_modes()
 
798
                print("    Modes:")
 
799
                for m in range(len(modes)):
 
800
                    mode = modes[m]
 
801
                    refresh = mode.dotClock / (mode.hTotal * mode.vTotal)
 
802
                    print("      [%s] %s x %s @ %s" % (m,
 
803
                                                       mode.width,
 
804
                                                       mode.height,
 
805
                                                       refresh))
 
806
                    if mode.id == output._mode:
 
807
                        print("*")
 
808
                    if m == output.get_preferred_mode():
 
809
                        print("(preferred)")
 
810
                    print("")
 
811
                print("    Rotations:")
 
812
                rots = output.get_available_rotations()
 
813
                if rots & RR_ROTATE_0:
 
814
                    print("normal")
 
815
                if rots & RR_ROTATE_90:
 
816
                    print("right")
 
817
                if rots & RR_ROTATE_180:
 
818
                    print("inverted")
 
819
                if rots & RR_ROTATE_270:
 
820
                    print("left")
 
821
                print("")
 
822
            else:
 
823
                print("(not connected)")
 
824
            if verbose:
 
825
                print("    Core properties:")
 
826
                for (f, t) in output._info.contents._fields_:
 
827
                    print("      %s: %s" % (
 
828
                        f, getattr(output._info.contents, f)))
 
829
 
 
830
    def get_outputs(self):
 
831
        """Returns the outputs of the screen"""
 
832
        _check_required_version((1, 2))
 
833
        return list(self.outputs.values())
 
834
 
 
835
    def get_output_names(self):
 
836
        _check_required_version((1, 2))
 
837
        return list(self.outputs.keys())
 
838
 
 
839
    def set_size(self, width, height, width_mm, height_mm):
 
840
        """Apply the given pixel and physical size to the screen"""
 
841
        _check_required_version((1, 2))
 
842
        # Check if we really need to apply the changes
 
843
        if (width, height, width_mm, height_mm) == self.get_size():
 
844
            return
 
845
        rr.XRRSetScreenSize(self._display, self._root,
 
846
                            c_int(width), c_int(height),
 
847
                            c_int(width_mm), c_int(height_mm))
 
848
 
 
849
    def apply_output_config(self):
 
850
        """Used for instantly applying RandR 1.2 changes"""
 
851
        _check_required_version((1, 2))
 
852
        self._arrange_outputs()
 
853
        self._calculate_size()
 
854
        self.set_size(self._width, self._height,
 
855
                      self._width_mm, self._height_mm)
 
856
 
 
857
        # Assign all active outputs to crtcs
 
858
        for output in list(self.outputs.values()):
 
859
            if not output._mode or output._crtc:
 
860
                continue
 
861
            for crtc in output.get_crtcs():
 
862
                if crtc and crtc.supports_output(output):
 
863
                    crtc.add_output(output)
 
864
                    output._changes = output._changes | CHANGES_CRTC
 
865
                    break
 
866
            if not output._crtc:
 
867
                #FIXME: Take a look at the pick_crtc code in xrandr.c
 
868
                raise RRError("There is no matching crtc for the output")
 
869
 
 
870
        # Apply stored changes of crtcs
 
871
        for crtc in self.crtcs:
 
872
            if crtc.has_changed():
 
873
                crtc.apply_changes()
 
874
 
 
875
    def apply_config(self):
 
876
        """Used for instantly applying RandR 1.0 changes"""
 
877
        _check_required_version((1, 0))
 
878
        status = rr.XRRSetScreenConfigAndRate(self._display,
 
879
                                              self._config,
 
880
                                              self._root,
 
881
                                              self._size_index,
 
882
                                              self._rotation,
 
883
                                              self._rate,
 
884
                                              self.get_timestamp())
 
885
        return status
 
886
 
 
887
    def _arrange_outputs(self):
 
888
        """Arrange all output positions according to their relative position"""
 
889
        for output in self.get_outputs():
 
890
            # Skip not changed and not used outputs
 
891
            if not output.has_changed(CHANGES_RELATION) or \
 
892
               output._mode == None:
 
893
                continue
 
894
            relative = output._relative_to
 
895
            mode = self.get_mode_by_xid(output._mode)
 
896
            mode_relative = self.get_mode_by_xid(relative._mode)
 
897
            if not relative or not relative._mode:
 
898
                output._x = 0
 
899
                output._y = 0
 
900
                output._changes = output._changes | CHANGES_POSITION
 
901
            if output._relation == RELATION_LEFT_OF:
 
902
                output._y = relative._y + output._relation_offset
 
903
                output._x = relative._x - \
 
904
                            get_mode_width(mode, output._rotation)
 
905
            elif output._relation == RELATION_RIGHT_OF:
 
906
                output._y = relative._y + output._relation_offset
 
907
                output._x = relative._x + get_mode_width(mode_relative,
 
908
                                                         output._rotation)
 
909
            elif output._relation == RELATION_ABOVE:
 
910
                output._y = relative._y - get_mode_height(mode,
 
911
                                                          output._rotation)
 
912
                output._x = relative._x + output._relation_offset
 
913
            elif output._relation == RELATION_BELOW:
 
914
                output._y = relative._y + get_mode_height(mode_relative,
 
915
                                                          output._rotation)
 
916
                output._x = relative._x + output._relation_offset
 
917
            elif output._relation == RELATION_SAME_AS:
 
918
                output._y = relative._y + output._relation_offset
 
919
                output._x = relative._x + output._relation_offset
 
920
            output._changes = output._changes | CHANGES_POSITION
 
921
        # Normalize the postions so to the upper left cornor of all outputs
 
922
        # is at 0,0
 
923
        min_x = 32768
 
924
        min_y = 32768
 
925
        for output in self.get_outputs():
 
926
            if output._mode == None:
 
927
                continue
 
928
            if output._x < min_x:
 
929
                min_x = output._x
 
930
            if output._y < min_y:
 
931
                min_y = output._y
 
932
        for output in self.get_outputs():
 
933
            if output._mode == None:
 
934
                continue
 
935
            output._x -= min_x
 
936
            output._y -= min_y
 
937
            output._changes = output._changes | CHANGES_POSITION
 
938
 
 
939
    def _calculate_size(self):
 
940
        """Recalculate the pixel and physical size of the screen so that
 
941
           it covers all outputs"""
 
942
        width = self._width
 
943
        height = self._height
 
944
        for output in self.get_outputs():
 
945
            if not output._mode:
 
946
                continue
 
947
            mode = self.get_mode_by_xid(output._mode)
 
948
            x = output._x
 
949
            y = output._y
 
950
            w = get_mode_width(mode, output._rotation)
 
951
            h = get_mode_height(mode, output._rotation)
 
952
            if x + w > width:
 
953
                width = x + w
 
954
            if y + h > height:
 
955
                height = y + h
 
956
        if width > self._width_max or height > self._height_max:
 
957
            raise RRError("The required size is not supported",
 
958
                          (width, height), (self._width_max, self._width_min))
 
959
        else:
 
960
            if height < self._height_min:
 
961
                self._fb_height = self._height_min
 
962
            else:
 
963
                self._height = height
 
964
            if width < self._width_min:
 
965
                self._width = self._width_min
 
966
            else:
 
967
                self._width = width
 
968
        #FIXME: Physical size is missing
 
969
 
 
970
 
 
971
def get_current_display():
 
972
    """Returns the currently used display"""
 
973
    display_url = os.getenv("DISPLAY")
 
974
    open_display = xlib.XOpenDisplay
 
975
    # Set .argtypes and .restype, to ensure proper
 
976
    # type check and conversion
 
977
    open_display.restype = c_void_p
 
978
    open_display.argtypes = [c_char_p]
 
979
    # XOpenDisplay accepts a char*, but
 
980
    # display_url is a unicode string therefore
 
981
    # we convert it to a bytes string
 
982
    dpy = open_display(display_url.encode('utf-8'))
 
983
    return dpy
 
984
 
 
985
 
 
986
def get_current_screen():
 
987
    """Returns the currently used screen"""
 
988
    screen = Screen(get_current_display())
 
989
    return screen
 
990
 
 
991
 
 
992
def get_screen_of_display(display, count):
 
993
    """Returns the screen of the given display"""
 
994
    dpy = xlib.XOpenDisplay(display)
 
995
    return Screen(dpy, count)
 
996
 
 
997
 
 
998
def get_version():
 
999
    """Returns a tuple containing the major and minor version of the xrandr
 
1000
       extension or None if the extension is not available"""
 
1001
    major = c_int()
 
1002
    minor = c_int()
 
1003
    res = rr.XRRQueryVersion(get_current_display(),
 
1004
                             byref(major), byref(minor))
 
1005
    if res:
 
1006
        return (major.value, minor.value)
 
1007
    return None
 
1008
 
 
1009
 
 
1010
def has_extension():
 
1011
    """Returns True if the xrandr extension is available"""
 
1012
    if XRANDR_VERSION:
 
1013
        return True
 
1014
    return False
 
1015
 
 
1016
 
 
1017
def _to_gamma(gamma):
 
1018
    g = rr.XRRAllocGamma(len(gamma[0]))
 
1019
    for i in range(gamma[0]):
 
1020
        g.red[i] = gamma[0][i]
 
1021
        g.green[i] = gamma[1][i]
 
1022
        g.blue[i] = gamma[2][i]
 
1023
    return g
 
1024
 
 
1025
 
 
1026
def _from_gamma(g):
 
1027
    gamma = ([], [], [])
 
1028
    for i in range(g.size):
 
1029
        gamma[0].append(g.red[i])
 
1030
        gamma[1].append(g.green[i])
 
1031
        gamma[2].append(g.blue[i])
 
1032
    rr.XRRFreeGamma(g)
 
1033
 
 
1034
 
 
1035
def _check_required_version(version):
 
1036
    """Raises an exception if the given or a later version of xrandr is not
 
1037
       available"""
 
1038
    if XRANDR_VERSION == None or XRANDR_VERSION < version:
 
1039
        raise UnsupportedRRError(version, XRANDR_VERSION)
 
1040
 
 
1041
 
 
1042
def get_mode_height(mode, rotation):
 
1043
    """Return the height of the given mode taking the rotation into account"""
 
1044
    if rotation & (RR_ROTATE_0 | RR_ROTATE_180):
 
1045
        return mode.height
 
1046
    elif rotation & (RR_ROTATE_90 | RR_ROTATE_270):
 
1047
        return mode.width
 
1048
    else:
 
1049
        return 0
 
1050
 
 
1051
 
 
1052
def get_mode_width(mode, rotation):
 
1053
    """Return the width of the given mode taking the rotation into account"""
 
1054
    if rotation & (RR_ROTATE_0 | RR_ROTATE_180):
 
1055
        return mode.width
 
1056
    elif rotation & (RR_ROTATE_90 | RR_ROTATE_270):
 
1057
        return mode.height
 
1058
    else:
 
1059
        return 0
 
1060
 
 
1061
 
 
1062
XRANDR_VERSION = get_version()
 
1063
 
 
1064
# vim:ts=4:sw=4:et