diff --git a/.gitignore b/.gitignore index ac3327fb..d8efd36c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,9 @@ docs/_build/ target/ # IntelliJ - .idea/ +# Visual Studio Code +.vscode/ + /.mypy_cache/ diff --git a/.travis.yml b/.travis.yml index 524f0998..e7b949f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: - "3.6" - "3.7" - "3.8" + - "3.9" # command to install dependencies install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd9e2a6..74eb66b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,88 @@ NEWS for Python X Library +Version 0.33 +============ + +Bug Fixes +--------- + +- Removed unused imports (thanks @Avasam). +- Avoid to use fcntl module on some environments (thanks @i2y). +- Change a test behavior for `unix_connect.get_socket` (thanks @i2y). +- Fix accidental data change (thanks @Avasam). +- Prefer `bool` over `Literal[0, 1, None]` (thanks @Avasam). +- Change parentheses to brackets in LICENSE (thanks @mtelka). + +--- +Version 0.32 +============ + +Bug Fixes +--------- + +- Use archived link for X documentation resource (thanks @yaxollum). +- Fix for auth entry having no display number (thanks @Majiir). +- Fix return type inconsistency with the `pack_value` for class `Object` (thanks @allfro). +- Rename `add_extension_error` method to `extension_add_error` (thanks @mattalexx). + +Extensions +-------------------- + +- screensaver: fix screensaver protocol mismatch (thanks @yut23). +- XRandr: add version 1.5 support for RRSetMonitor RRGetMonitors and RRDeleteMonitors (thanks @allfro and @jklong). + +--- +Version 0.31 +============ + +Extensions +-------------------- + +- XInput: add event methods (thanks @dd4e). + +--- +Version 0.30 +============ + +Extensions +-------------------- + +- XResource: first implementation (thanks @alebastr). +- XRandr: add missing parameters to delete_output_mode function (thanks @jimmy-loyola). + +--- +Version 0.29 +============ + +Extensions +-------------------- + +- Drawable & XInput: Avoid using array.array.tostring() which wiil be removed in Python 3.9 (thanks @t-wissmann). + +--- +Version 0.28 +============ + +Extensions +-------------------- + +- DPMS: Display Power Management Signaling (by @thiagokokada) + +--- +Version 0.27 +============ + +Bug Fixes +--------- + +- fix TypeError in socket.error exception handling for Python 3.x (by @t-wissmann) + +Extensions +-------------------- + +- NV-CONTROL: set offset for all perf levels (by @Sporif) + +--- Version 0.26 ============ diff --git a/LICENSE b/LICENSE index 19e30718..d762d815 100644 --- a/LICENSE +++ b/LICENSE @@ -6,9 +6,9 @@ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. -(This is the first released version of the Lesser GPL. It also counts +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.) + the version number 2.1.] Preamble diff --git a/README.rst b/README.rst index e28f80cf..ad040fe4 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,9 @@ Requirements The Python X Library requires Python 2.7 or newer. It has been tested to various extents with Python 2.7 and 3.3 through 3.6. +The Python X Library will only work on systems that have an X server installed, +such as most Linux distros, but will not work on Windows or MacOS. + Installation ~~~~~~~~~~~~ @@ -70,15 +73,15 @@ There are three advantages of implementing a pure Python library: - Maintainability: It is much easier to develop and debug native Python modules than modules written in C. - + Documentation ~~~~~~~~~~~~~ The reference manual is not finished by far, but is probably still useful. It -can be `browsed online `__. +can be `browsed online `__. There are also some `example programs `_ and, of course, -`the standard X11 documentation `__ applies. +`the standard X11 documentation `__ applies. Project status @@ -92,7 +95,8 @@ starting with version 2.0. There is a resource database implementation, ICCCM support and a framework for adding X extension code. Several extensions have been -implemented; (RECORD, SHAPE, Xinerama, Composite, RANDR, and XTEST) +implemented (RECORD, SHAPE, Xinerama, Composite, RANDR, DAMAGE, +Generic Event, SECURITY, XFIXES, XInput, XTEST, NV-CONTROL, DPMS and XRes); patches for additions are very welcome. There are most likely still bugs, but the library is at least stable diff --git a/Xlib/X.py b/Xlib/X.py index 3105e798..1a09e392 100644 --- a/Xlib/X.py +++ b/Xlib/X.py @@ -197,6 +197,8 @@ FamilyInternet = 0 FamilyDECnet = 1 FamilyChaos = 2 +FamilyServerInterpreted = 5 +FamilyInternetV6 = 6 PropertyNewValue = 0 PropertyDelete = 1 ColormapUninstalled = 0 diff --git a/Xlib/__init__.py b/Xlib/__init__.py index 6164e53a..28a0e017 100644 --- a/Xlib/__init__.py +++ b/Xlib/__init__.py @@ -19,7 +19,7 @@ # Suite 330, # Boston, MA 02111-1307 USA -__version__ = (0, 26) +__version__ = (0, 33) __version_extra__ = '' diff --git a/Xlib/display.py b/Xlib/display.py index beaeaad6..87b9aa61 100644 --- a/Xlib/display.py +++ b/Xlib/display.py @@ -71,7 +71,7 @@ def __init__(self, *args, **keys): protocol_display.Display.__init__(self, *args, **keys) self._atom_cache = {} - def get_atom(self, atomname, only_if_exists=0): + def get_atom(self, atomname, only_if_exists=False): if atomname in self._atom_cache: return self._atom_cache[atomname] @@ -340,8 +340,8 @@ def extension_add_subevent(self, code, subcode, evt, name = None): # extension dict maintained in the display object setattr(self.extension_event, name, (code,subcode)) - def add_extension_error(self, code, err): - """add_extension_error(code, err) + def extension_add_error(self, code, err): + """extension_add_error(code, err) Add an extension error. CODE is the numeric code, and ERR is the error class. @@ -473,7 +473,7 @@ def rebind_string(self, keysym, newstring): ### X requests ### - def intern_atom(self, name, only_if_exists = 0): + def intern_atom(self, name, only_if_exists = False): """Intern the string name, returning its atom number. If only_if_exists is true and the atom does not already exist, it will not be created and X.NONE is returned.""" @@ -482,7 +482,7 @@ def intern_atom(self, name, only_if_exists = 0): only_if_exists = only_if_exists) return r.atom - def get_atom(self, atom, only_if_exists = 0): + def get_atom(self, atom, only_if_exists = False): """Alias for intern_atom, using internal cache""" return self.display.get_atom(atom, only_if_exists) @@ -501,7 +501,7 @@ def get_selection_owner(self, selection): selection = selection) return r.owner - def send_event(self, destination, event, event_mask = 0, propagate = 0, + def send_event(self, destination, event, event_mask = 0, propagate = False, onerror = None): """Send a synthetic event to the window destination which can be a window object, or X.PointerWindow or X.InputFocus. event is the @@ -849,7 +849,8 @@ def get_screen_saver(self): def change_hosts(self, mode, host_family, host, onerror = None): """mode is either X.HostInsert or X.HostDelete. host_family is - one of X.FamilyInternet, X.FamilyDECnet or X.FamilyChaos. + one of X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos, + X.FamilyServerInterpreted or X.FamilyInternetV6. host is a list of bytes. For the Internet family, it should be the four bytes of an IPv4 address.""" @@ -868,7 +869,7 @@ def list_hosts(self): The hosts on the access list. Each entry has the following attributes: family - X.FamilyInternet, X.FamilyDECnet, or X.FamilyChaos. + X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos, X.FamilyServerInterpreted or X.FamilyInternetV6. name A list of byte values, the coding depends on family. For the Internet family, it is the 4 bytes of an IPv4 address. diff --git a/Xlib/error.py b/Xlib/error.py index cb6d0d07..3b6e13f8 100644 --- a/Xlib/error.py +++ b/Xlib/error.py @@ -70,7 +70,7 @@ class XError(rq.GetAttrData, Exception): ) def __init__(self, display, data): - self._data, data = self._fields.parse_binary(data, display, rawdict = 1) + self._data, _ = self._fields.parse_binary(data, display, rawdict = True) def __str__(self): s = [] diff --git a/Xlib/ext/__init__.py b/Xlib/ext/__init__.py index fbcc8e64..37229bac 100644 --- a/Xlib/ext/__init__.py +++ b/Xlib/ext/__init__.py @@ -38,6 +38,9 @@ ('XInputExtension', 'xinput'), ('NV-CONTROL', 'nvcontrol'), ('DAMAGE', 'damage'), + ('DPMS', 'dpms'), + ('X-Resource', 'res'), + ('MIT-SCREEN-SAVER', 'screensaver'), ] __all__ = map(lambda x: x[1], __extensions__) diff --git a/Xlib/ext/composite.py b/Xlib/ext/composite.py index 0e10b635..5909b31e 100644 --- a/Xlib/ext/composite.py +++ b/Xlib/ext/composite.py @@ -33,7 +33,6 @@ graphics. """ -from Xlib import X from Xlib.protocol import rq from Xlib.xobject import drawable diff --git a/Xlib/ext/damage.py b/Xlib/ext/damage.py index 0cde5ed0..126d8507 100644 --- a/Xlib/ext/damage.py +++ b/Xlib/ext/damage.py @@ -22,7 +22,6 @@ from Xlib import X from Xlib.protocol import rq, structs -from Xlib.xobject import resource from Xlib.error import XError extname = 'DAMAGE' @@ -179,4 +178,4 @@ def init(disp, info): disp.extension_add_event(info.first_event + DamageNotifyCode, DamageNotify) - disp.add_extension_error(code=BadDamageCode, err=BadDamageError) + disp.extension_add_error(code=BadDamageCode, err=BadDamageError) diff --git a/Xlib/ext/dpms.py b/Xlib/ext/dpms.py new file mode 100644 index 00000000..20b570bc --- /dev/null +++ b/Xlib/ext/dpms.py @@ -0,0 +1,232 @@ +# Xlib.ext.dpms -- X Display Power Management Signaling +# +# Copyright (C) 2020 Thiago Kenji Okada +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, +# Suite 330, +# Boston, MA 02111-1307 USA + +''' +This extension provides X Protocol control over the VESA Display +Power Management Signaling (DPMS) characteristics of video boards +under control of the X Window System. + +Documentation: https://www.x.org/releases/X11R7.7/doc/xextproto/dpms.html +''' + +from Xlib.protocol import rq + +extname = 'DPMS' + + +# DPMS Extension Power Levels +# 0 DPMSModeOn In use +# 1 DPMSModeStandby Blanked, low power +# 2 DPMSModeSuspend Blanked, lower power +# 3 DPMSModeOff Shut off, awaiting activity +DPMSModeOn = 0 +DPMSModeStandby = 1 +DPMSModeSuspend = 2 +DPMSModeOff = 3 + +DPMSPowerLevel = ( + DPMSModeOn, + DPMSModeStandby, + DPMSModeSuspend, + DPMSModeOff, +) + + +class DPMSGetVersion(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(0), + rq.RequestLength(), + rq.Card16('major_version'), + rq.Card16('minor_version'), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card16('major_version'), + rq.Card16('minor_version'), + rq.Pad(20), + ) + + +def get_version(self): + return DPMSGetVersion(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +class DPMSCapable(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(1), + rq.RequestLength(), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Bool('capable'), + rq.Pad(23), + ) + + +def capable(self): + return DPMSCapable(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +class DPMSGetTimeouts(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(2), + rq.RequestLength(), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card16('standby_timeout'), + rq.Card16('suspend_timeout'), + rq.Card16('off_timeout'), + rq.Pad(18), + ) + + +def get_timeouts(self): + return DPMSGetTimeouts(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +class DPMSSetTimeouts(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(3), + rq.RequestLength(), + rq.Card16('standby_timeout'), + rq.Card16('suspend_timeout'), + rq.Card16('off_timeout'), + rq.Pad(2) + ) + + +def set_timeouts(self, standby_timeout, suspend_timeout, off_timeout): + return DPMSSetTimeouts(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1, + standby_timeout=standby_timeout, + suspend_timeout=suspend_timeout, + off_timeout=off_timeout) + + +class DPMSEnable(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(4), + rq.RequestLength(), + ) + + +def enable(self): + return DPMSEnable(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +class DPMSDisable(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(5), + rq.RequestLength(), + ) + + +def disable(self): + return DPMSDisable(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +class DPMSForceLevel(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(6), + rq.RequestLength(), + rq.Resource('power_level', DPMSPowerLevel), + ) + + +def force_level(self, power_level): + return DPMSForceLevel(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1, + power_level=power_level) + + +class DPMSInfo(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(7), + rq.RequestLength(), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card16('power_level'), + rq.Bool('state'), + rq.Pad(21), + ) + + +def info(self): + return DPMSInfo(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=1) + + +def init(disp, _info): + disp.extension_add_method('display', 'dpms_get_version', get_version) + disp.extension_add_method('display', 'dpms_capable', capable) + disp.extension_add_method('display', 'dpms_get_timeouts', get_timeouts) + disp.extension_add_method('display', 'dpms_set_timeouts', set_timeouts) + disp.extension_add_method('display', 'dpms_enable', enable) + disp.extension_add_method('display', 'dpms_disable', disable) + disp.extension_add_method('display', 'dpms_force_level', force_level) + disp.extension_add_method('display', 'dpms_info', info) diff --git a/Xlib/ext/randr.py b/Xlib/ext/randr.py index 0a1dfb9c..9cbfe2da 100644 --- a/Xlib/ext/randr.py +++ b/Xlib/ext/randr.py @@ -22,10 +22,10 @@ """RandR - provide access to the RandR extension information. -This implementation is based off version 1.3 of the XRandR protocol, and may +This implementation is based off version 1.5 of the XRandR protocol, and may not be compatible with other versions. -Version 1.2 of the protocol is documented at: +Version 1.5 of the protocol is documented at: http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt Version 1.3.1 here: @@ -35,7 +35,7 @@ from Xlib import X -from Xlib.protocol import rq, structs +from Xlib.protocol import rq extname = 'RANDR' @@ -122,6 +122,12 @@ BadRRCrtc = 1 BadRRMode = 2 +# Error classes # +class BadRROutputError(Exception): pass + +class BadRRCrtcError(Exception): pass + +class BadRRModeError(Exception): pass # Data Structures # @@ -168,6 +174,19 @@ rq.Card32('matrix33'), ) +MonitorInfo = rq.Struct( + rq.Card32('name'), + rq.Bool('primary'), + rq.Bool('automatic'), + rq.LengthOf('crtcs', 2), + rq.Int16('x'), + rq.Int16('y'), + rq.Card16('width_in_pixels'), + rq.Card16('height_in_pixels'), + rq.Card32('width_in_millimeters'), + rq.Card32('height_in_millimeters'), + rq.List('crtcs', rq.Card32Obj) +) # Requests # @@ -197,7 +216,7 @@ def query_version(self): display=self.display, opcode=self.display.get_extension_major(extname), major_version=1, - minor_version=3, + minor_version=5, ) @@ -699,7 +718,7 @@ class DeleteOutputMode(rq.Request): rq.Card32('mode'), ) -def delete_output_mode(self): +def delete_output_mode(self, output, mode): return DeleteOutputMode( display=self.display, opcode=self.display.get_extension_major(extname), @@ -1078,6 +1097,76 @@ def get_output_primary(self): ) +# Version 1.5 methods + +class GetMonitors(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(42), + rq.RequestLength(), + rq.Window('window'), + rq.Bool('is_active'), + rq.Pad(3) + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card32('timestamp'), + rq.LengthOf('monitors', 4), + rq.Card32('outputs'), + rq.Pad(12), + rq.List('monitors', MonitorInfo) + ) + + +def get_monitors(self, is_active=True): + return GetMonitors( + display=self.display, + opcode=self.display.get_extension_major(extname), + window=self, + is_active=is_active + ) + +class SetMonitor(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(43), + rq.RequestLength(), + rq.Window('window'), + rq.Object('monitor_info', MonitorInfo) + ) + + +def set_monitor(self, monitor_info): + return SetMonitor( + display=self.display, + opcode=self.display.get_extension_major(extname), + window=self, + monitor_info=monitor_info + ) + + +class DeleteMonitor(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(44), + rq.RequestLength(), + rq.Window('window'), + rq.Card32('name') + ) + + +def delete_monitor(self, name): + return DeleteMonitor( + display=self.display, + opcode=self.display.get_extension_major(extname), + window=self, + name=name + ) + # Events # class ScreenChangeNotify(rq.Event): @@ -1149,8 +1238,6 @@ class OutputPropertyNotify(rq.Event): rq.Card8('state'), rq.Pad(11), ) - - # Initialization # def init(disp, info): @@ -1186,12 +1273,20 @@ def init(disp, info): disp.extension_add_method('display', 'xrandr_get_panning', get_panning) disp.extension_add_method('display', 'xrandr_set_panning', set_panning) - disp.extension_add_event(info.first_event + RRScreenChangeNotify, ScreenChangeNotify) - # add RRNotify events (1 event code with 3 subcodes) - disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_CrtcChange, CrtcChangeNotify) - disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_OutputChange, OutputChangeNotify) - disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_OutputProperty, OutputPropertyNotify) - - #disp.extension_add_error(BadRROutput, BadRROutputError) - #disp.extension_add_error(BadRRCrtc, BadRRCrtcError) - #disp.extension_add_error(BadRRMode, BadRRModeError) + # If the server is running RANDR 1.5+, enable 1.5 compatible methods and events + version = query_version(disp) + if version.major_version == 1 and version.minor_version >= 5: + # version 1.5 compatible + disp.extension_add_method('window', 'xrandr_get_monitors', get_monitors) + disp.extension_add_method('window', 'xrandr_set_monitor', set_monitor) + disp.extension_add_method('window', 'xrandr_delete_monitor', delete_monitor) + + disp.extension_add_event(info.first_event + RRScreenChangeNotify, ScreenChangeNotify) + # add RRNotify events (1 event code with 3 subcodes) + disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_CrtcChange, CrtcChangeNotify) + disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_OutputChange, OutputChangeNotify) + disp.extension_add_subevent(info.first_event + RRNotify, RRNotify_OutputProperty, OutputPropertyNotify) + + disp.extension_add_error(BadRROutput, BadRROutputError) + disp.extension_add_error(BadRRCrtc, BadRRCrtcError) + disp.extension_add_error(BadRRMode, BadRRModeError) diff --git a/Xlib/ext/record.py b/Xlib/ext/record.py index bb53ec19..638a5b38 100644 --- a/Xlib/ext/record.py +++ b/Xlib/ext/record.py @@ -19,7 +19,6 @@ # Suite 330, # Boston, MA 02111-1307 USA -from Xlib import X from Xlib.protocol import rq extname = 'RECORD' diff --git a/Xlib/ext/res.py b/Xlib/ext/res.py new file mode 100644 index 00000000..f2c4e9fe --- /dev/null +++ b/Xlib/ext/res.py @@ -0,0 +1,288 @@ +# Xlib.ext.res -- X-Resource extension module +# +# Copyright (C) 2021 Aleksei Bavshin +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, +# Fifth Floor, +# Boston, MA 02110-1301 USA + +"""X-Resource extension allows a client to query the X server about its usage +of various resources. + +For detailed description see any of the following documents. +Protocol specification: + https://www.x.org/releases/current/doc/resourceproto/resproto.txt +XCB Protocol specification: + https://cgit.freedesktop.org/xcb/proto/tree/src/res.xml +""" +from Xlib.protocol import rq + +RES_MAJOR_VERSION = 1 +RES_MINOR_VERSION = 2 + +extname = "X-Resource" + +# v1.0 +ResQueryVersion = 0 +ResQueryClients = 1 +ResQueryClientResources = 2 +ResQueryClientPixmapBytes = 3 +# v1.2 +ResQueryClientIds = 4 +ResQueryResourceBytes = 5 + + +class QueryVersion(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryVersion), + rq.RequestLength(), + rq.Card8("client_major"), + rq.Card8("client_minor"), + rq.Pad(2)) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.Card16("server_major"), + rq.Card16("server_minor"), + rq.Pad(20)) + + +def query_version(self, client_major=RES_MAJOR_VERSION, + client_minor=RES_MINOR_VERSION): + """ Query the protocol version supported by the X server. + + The client sends the highest supported version to the server and the + server sends the highest version it supports, but no higher than the + requested version.""" + return QueryVersion( + display=self.display, + opcode=self.display.get_extension_major(extname), + client_major=client_major, + client_minor=client_minor) + + +Client = rq.Struct( + rq.Card32("resource_base"), + rq.Card32("resource_mask")) + + +class QueryClients(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryClients), + rq.RequestLength()) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.LengthOf("clients", 4), + rq.Pad(20), + rq.List("clients", Client)) + + +def query_clients(self): + """Request the list of all currently connected clients.""" + return QueryClients( + display=self.display, + opcode=self.display.get_extension_major(extname)) + + +Type = rq.Struct( + rq.Card32("resource_type"), + rq.Card32("count")) + + +class QueryClientResources(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryClientResources), + rq.RequestLength(), + rq.Card32("client")) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.LengthOf("types", 4), + rq.Pad(20), + rq.List("types", Type)) + + +def query_client_resources(self, client): + """Request the number of resources owned by a client. + + The server will return the counts of each type of resource. + """ + return QueryClientResources( + display=self.display, + opcode=self.display.get_extension_major(extname), + client=client) + + +class QueryClientPixmapBytes(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryClientPixmapBytes), + rq.RequestLength(), + rq.Card32("client")) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.Card32("bytes"), + rq.Card32("bytes_overflow"), + rq.Pad(16)) + + +def query_client_pixmap_bytes(self, client): + """Query the pixmap usage of some client. + + The returned number is a sum of memory usage of each pixmap that can be + attributed to the given client. + """ + return QueryClientPixmapBytes( + display=self.display, + opcode=self.display.get_extension_major(extname), + client=client) + + +class SizeOf(rq.LengthOf): + """A SizeOf stores the size in bytes of some other Field whose size + may vary, e.g. List + """ + def __init__(self, name, size, item_size): + rq.LengthOf.__init__(self, name, size) + self.item_size = item_size + + def parse_value(self, length, display): + return length // self.item_size + + +ClientXIDMask = 1 << 0 +LocalClientPIDMask = 1 << 1 + + +ClientIdSpec = rq.Struct( + rq.Card32("client"), + rq.Card32("mask")) + + +ClientIdValue = rq.Struct( + rq.Object("spec", ClientIdSpec), + SizeOf("value", 4, 4), + rq.List("value", rq.Card32Obj)) + + +class QueryClientIds(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryClientIds), + rq.RequestLength(), + rq.LengthOf("specs", 4), + rq.List("specs", ClientIdSpec)) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.LengthOf("ids", 4), + rq.Pad(20), + rq.List("ids", ClientIdValue)) + + +def query_client_ids(self, specs): + """Request to identify a given set of clients with some identification method. + + The request sends a list of specifiers that select clients and + identification methods to server. The server then tries to identify the + chosen clients using the identification methods specified for each client. + The server returns IDs for those clients that were successfully identified. + """ + return QueryClientIds( + display=self.display, + opcode=self.display.get_extension_major(extname), + specs=specs) + + +ResourceIdSpec = rq.Struct( + rq.Card32("resource"), + rq.Card32("type")) + + +ResourceSizeSpec = rq.Struct( + # inline struct ResourceIdSpec to work around + # a parser bug with nested objects + rq.Card32("resource"), + rq.Card32("type"), + rq.Card32("bytes"), + rq.Card32("ref_count"), + rq.Card32("use_count")) + + +ResourceSizeValue = rq.Struct( + rq.Object("size", ResourceSizeSpec), + rq.LengthOf("cross_references", 4), + rq.List("cross_references", ResourceSizeSpec)) + + +class QueryResourceBytes(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8("opcode"), + rq.Opcode(ResQueryResourceBytes), + rq.RequestLength(), + rq.Card32("client"), + rq.LengthOf("specs", 4), + rq.List("specs", ResourceIdSpec)) + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16("sequence_number"), + rq.ReplyLength(), + rq.LengthOf("sizes", 4), + rq.Pad(20), + rq.List("sizes", ResourceSizeValue)) + + +def query_resource_bytes(self, client, specs): + """Query the sizes of resources from X server. + + The request sends a list of specifiers that selects resources for size + calculation. The server tries to calculate the sizes of chosen resources + and returns an estimate for a resource only if the size could be determined + """ + return QueryResourceBytes( + display=self.display, + opcode=self.display.get_extension_major(extname), + client=client, + specs=specs) + + +def init(disp, info): + disp.extension_add_method("display", "res_query_version", query_version) + disp.extension_add_method("display", "res_query_clients", query_clients) + disp.extension_add_method("display", "res_query_client_resources", + query_client_resources) + disp.extension_add_method("display", "res_query_client_pixmap_bytes", + query_client_pixmap_bytes) + disp.extension_add_method("display", "res_query_client_ids", + query_client_ids) + disp.extension_add_method("display", "res_query_resource_bytes", + query_resource_bytes) diff --git a/Xlib/ext/screensaver.py b/Xlib/ext/screensaver.py new file mode 100644 index 00000000..12f4325c --- /dev/null +++ b/Xlib/ext/screensaver.py @@ -0,0 +1,198 @@ +# Xlib.ext.screensaver -- X ScreenSaver extension module +# +# Copyright (C) 2022 Vladimir Panteleev +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, +# Fifth Floor, +# Boston, MA 02110-1301 USA + +"""This extension allows registering the client as an X screensaver, +or query information about the current screensaver. + +For detailed description see any of the following documents. +Protocol specification: + https://www.x.org/releases/X11R7.7/doc/scrnsaverproto/saver.html +XCB Protocol specification: + https://cgit.freedesktop.org/xcb/proto/tree/src/screensaver.xml + +""" + +from Xlib import X +from Xlib.protocol import rq, structs + +extname = 'MIT-SCREEN-SAVER' + +# Event members +NotifyMask = 1 +CycleMask = 2 + +# Notify state +StateOff = 0 +StateOn = 1 +StateCycle = 2 + +# Notify kind +KindBlanked = 0 +KindInternal = 1 +KindExternal = 2 + +class QueryVersion(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(0), + rq.RequestLength(), + rq.Card8('major_version'), + rq.Card8('minor_version'), + rq.Pad(2), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card16('major_version'), + rq.Card16('minor_version'), + rq.Pad(20), + ) + +def query_version(self): + return QueryVersion(display=self.display, + opcode=self.display.get_extension_major(extname), + major_version=1, + minor_version=0) + + +class QueryInfo(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(1), + rq.RequestLength(), + rq.Drawable('drawable'), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Card8('state'), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Window('saver_window'), + rq.Card32('til_or_since'), + rq.Card32('idle'), + rq.Card32('event_mask'), # rq.Set('event_mask', 4, (NotifyMask, CycleMask)), + rq.Card8('kind'), + rq.Pad(7), + ) + +def query_info(self): + return QueryInfo(display=self.display, + opcode=self.display.get_extension_major(extname), + drawable=self, + ) + + +class SelectInput(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(2), + rq.RequestLength(), + rq.Drawable('drawable'), + rq.Card32('event_mask'), # rq.Set('event_mask', 4, (NotifyMask, CycleMask)), + ) + +def select_input(self, mask): + return SelectInput(display=self.display, + opcode=self.display.get_extension_major(extname), + drawable=self, + event_mask=mask, + ) + + +class SetAttributes(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(3), + rq.RequestLength(), + rq.Drawable('drawable'), + rq.Int16('x'), + rq.Int16('y'), + rq.Card16('width'), + rq.Card16('height'), + rq.Card16('border_width'), + rq.Set('window_class', 1, (X.CopyFromParent, X.InputOutput, X.InputOnly)), + rq.Card8('depth'), + rq.Card32('visual'), + structs.WindowValues('attrs'), + ) + +def set_attributes(self, x, y, width, height, border_width, + window_class = X.CopyFromParent, + depth = X.CopyFromParent, + visual = X.CopyFromParent, + onerror = None, + **keys): + return SetAttributes(display=self.display, + onerror = onerror, + opcode=self.display.get_extension_major(extname), + drawable=self, + x = x, + y = y, + width = width, + height = height, + border_width = border_width, + window_class = window_class, + depth = depth, + visual = visual, + attrs = keys) + + +class UnsetAttributes(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(4), + rq.RequestLength(), + rq.Drawable('drawable'), + ) + +def unset_attributes(self, onerror = None): + return UnsetAttributes(display=self.display, + onerror = onerror, + opcode=self.display.get_extension_major(extname), + drawable=self) + + +class Notify(rq.Event): + _code = None + _fields = rq.Struct( + rq.Card8('type'), + rq.Set('state', 1, (StateOff, StateOn, StateCycle)), + rq.Card16('sequence_number'), + rq.Card32('timestamp'), + rq.Window('root'), + rq.Window('window'), + rq.Set('kind', 1, (KindBlanked, KindInternal, KindExternal)), + rq.Bool('forced'), + rq.Pad(14), + ) + +def init(disp, info): + disp.extension_add_method('display', 'screensaver_query_version', query_version) + disp.extension_add_method('drawable', 'screensaver_query_info', query_info) + disp.extension_add_method('drawable', 'screensaver_select_input', select_input) + disp.extension_add_method('drawable', 'screensaver_set_attributes', set_attributes) + disp.extension_add_method('drawable', 'screensaver_unset_attributes', unset_attributes) + + disp.extension_add_event(info.first_event + 0, Notify) diff --git a/Xlib/ext/xfixes.py b/Xlib/ext/xfixes.py index 5f2f6438..8b3c35fe 100644 --- a/Xlib/ext/xfixes.py +++ b/Xlib/ext/xfixes.py @@ -30,14 +30,17 @@ extname = 'XFIXES' XFixesSelectionNotify = 0 +XFixesCursorNotify = 1 XFixesSetSelectionOwnerNotifyMask = (1 << 0) XFixesSelectionWindowDestroyNotifyMask = (1 << 1) XFixesSelectionClientCloseNotifyMask = (1 << 2) +XFixesDisplayCursorNotifyMask = (1 << 0) XFixesSetSelectionOwnerNotify = 0 XFixesSelectionWindowDestroyNotify = 1 XFixesSelectionClientCloseNotify = 2 +XFixesDisplayCursorNotify = 0 class QueryVersion(rq.ReplyRequest): _request = rq.Struct(rq.Card8('opcode'), @@ -131,12 +134,67 @@ class SelectionClientCloseNotify(SelectionNotify): pass +class SelectCursorInput(rq.Request): + _request = rq.Struct(rq.Card8('opcode'), + rq.Opcode(3), + rq.RequestLength(), + rq.Window('window'), + rq.Card32('mask') + ) + +def select_cursor_input(self, window, mask): + return SelectCursorInput(opcode=self.display.get_extension_major(extname), + display=self.display, + window=window, + cursor_serial=0, + mask=mask) + + +class GetCursorImage(rq.ReplyRequest): + _request = rq.Struct(rq.Card8('opcode'), + rq.Opcode(4), + rq.RequestLength() + ) + _reply = rq.Struct(rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Int16('x'), + rq.Int16('y'), + rq.Card16('width'), + rq.Card16('height'), + rq.Card16('xhot'), + rq.Card16('yhot'), + rq.Card32('cursor_serial'), + rq.Pad(8), + rq.List('cursor_image', rq.Card32) + ) + +def get_cursor_image(self, window): + return GetCursorImage(opcode=self.display.get_extension_major(extname), + display=self.display, + ) + + +class DisplayCursorNotify(rq.Event): + _code = None + _fields = rq.Struct(rq.Card8('type'), + rq.Card8('sub_code'), + rq.Card16('sequence_number'), + rq.Window('window'), + rq.Card32('cursor_serial'), + rq.Card32('timestamp')) + + def init(disp, info): disp.extension_add_method('display', 'xfixes_select_selection_input', select_selection_input) disp.extension_add_method('display', 'xfixes_query_version', query_version) disp.extension_add_method('window', 'xfixes_hide_cursor', hide_cursor) disp.extension_add_method('window', 'xfixes_show_cursor', show_cursor) + disp.extension_add_method('display', 'xfixes_select_cursor_input', select_cursor_input) + disp.extension_add_method('display', 'xfixes_get_cursor_image', get_cursor_image) disp.extension_add_subevent(info.first_event + XFixesSelectionNotify, XFixesSetSelectionOwnerNotify, SetSelectionOwnerNotify) disp.extension_add_subevent(info.first_event + XFixesSelectionNotify, XFixesSelectionWindowDestroyNotify, SelectionWindowDestroyNotify) disp.extension_add_subevent(info.first_event + XFixesSelectionNotify, XFixesSelectionClientCloseNotify, SelectionClientCloseNotify) + disp.extension_add_subevent(info.first_event + XFixesCursorNotify, XFixesDisplayCursorNotify, DisplayCursorNotify) diff --git a/Xlib/ext/xinerama.py b/Xlib/ext/xinerama.py index f0546707..dcb78b89 100644 --- a/Xlib/ext/xinerama.py +++ b/Xlib/ext/xinerama.py @@ -35,7 +35,6 @@ returns the state information - because that's what libXinerama does.""" -from Xlib import X from Xlib.protocol import rq, structs extname = 'XINERAMA' diff --git a/Xlib/ext/xinput.py b/Xlib/ext/xinput.py index fee0d93c..f9218064 100644 --- a/Xlib/ext/xinput.py +++ b/Xlib/ext/xinput.py @@ -158,6 +158,8 @@ DEVICE = rq.Card16 DEVICEUSE = rq.Card8 +PROPERTY_TYPE_FLOAT = 'FLOAT' + class FP1616(rq.Int32): def check_value(self, value): @@ -236,7 +238,7 @@ def fun(val): else: mask_seq.extend(val) - return mask_seq.tostring(), len(mask_seq), None + return rq.encode_array(mask_seq), len(mask_seq), None EventMask = rq.Struct( DEVICE('deviceid'), @@ -426,6 +428,114 @@ def query_device(self, deviceid): deviceid=deviceid, ) +class XIListProperties(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(56), + rq.RequestLength(), + DEVICEID('deviceid'), + rq.Pad(2), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.LengthOf('atoms', 2), + rq.Pad(22), + rq.List('atoms', rq.Card32Obj), + ) + +def list_device_properties(self, deviceid): + return XIListProperties( + display=self.display, + opcode=self.display.get_extension_major(extname), + deviceid=deviceid, + ) + +class XIGetProperty(rq.ReplyRequest): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(59), + rq.RequestLength(), + DEVICEID('deviceid'), + rq.Card8('delete'), + rq.Pad(1), + rq.Card32('property'), + rq.Card32('type'), + rq.Card32('offset'), + rq.Card32('length'), + ) + + _reply = rq.Struct( + rq.ReplyCode(), + rq.Pad(1), + rq.Card16('sequence_number'), + rq.ReplyLength(), + rq.Card32('type'), + rq.Card32('bytes_after'), + rq.LengthOf('value', 4), + rq.Format('value', 1), + rq.Pad(11), + rq.PropertyData('value') + ) + +def get_device_property(self, deviceid, property, type, offset, length, delete=False): + return XIGetProperty( + display=self.display, + opcode=self.display.get_extension_major(extname), + deviceid=deviceid, + property=property, + type=type, + offset=offset, + length=length, + delete=delete, + ) + +class XIChangeProperty(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(57), + rq.RequestLength(), + DEVICEID('deviceid'), + rq.Card8('mode'), + rq.Format('value', 1), + rq.Card32('property'), + rq.Card32('type'), + rq.LengthOf('value', 4), + rq.PropertyData('value'), + ) + +def change_device_property(self, deviceid, property, type, mode, value): + return XIChangeProperty( + display=self.display, + opcode=self.display.get_extension_major(extname), + deviceid=deviceid, + property=property, + type=type, + mode=mode, + value=value, + ) + +class XIDeleteProperty(rq.Request): + _request = rq.Struct( + rq.Card8('opcode'), + rq.Opcode(58), + rq.RequestLength(), + DEVICEID('deviceid'), + rq.Pad(2), + rq.Card32('property'), + ) + +def delete_device_property(self, deviceid, property): + return XIDeleteProperty( + display=self.display, + opcode=self.display.get_extension_major(extname), + deviceid=deviceid, + property=property, + ) + class XIGrabDevice(rq.ReplyRequest): _request = rq.Struct( rq.Card8('opcode'), @@ -639,6 +749,14 @@ def ungrab_keycode(self, deviceid, keycode, modifiers): rq.List('classes', ClassInfo), ) +PropertyEventData = rq.Struct( + DEVICEID('deviceid'), + rq.Card32('time'), + rq.Card32('property'), + rq.Card8('what'), + rq.Pad(11), +) + def init(disp, info): disp.extension_add_method('display', 'xinput_query_version', query_version) disp.extension_add_method('window', 'xinput_select_events', select_events) @@ -647,8 +765,13 @@ def init(disp, info): disp.extension_add_method('display', 'xinput_ungrab_device', ungrab_device) disp.extension_add_method('window', 'xinput_grab_keycode', grab_keycode) disp.extension_add_method('window', 'xinput_ungrab_keycode', ungrab_keycode) + disp.extension_add_method('display', 'xinput_get_device_property', get_device_property) + disp.extension_add_method('display', 'xinput_list_device_properties', list_device_properties) + disp.extension_add_method('display', 'xinput_change_device_property', change_device_property) + disp.extension_add_method('display', 'xinput_delete_device_property', delete_device_property) if hasattr(disp,"ge_add_event_data"): for device_event in (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion): disp.ge_add_event_data(info.major_opcode, device_event, DeviceEventData) disp.ge_add_event_data(info.major_opcode, DeviceChanged, DeviceEventData) disp.ge_add_event_data(info.major_opcode, HierarchyChanged, HierarchyEventData) + disp.ge_add_event_data(info.major_opcode, PropertyEvent, PropertyEventData) diff --git a/Xlib/keysymdef/xf86.py b/Xlib/keysymdef/xf86.py index 2640535c..4ccf12a7 100644 --- a/Xlib/keysymdef/xf86.py +++ b/Xlib/keysymdef/xf86.py @@ -1,8 +1,11 @@ -XK_XF86_MonBrightnessUp = 0x1008FF02 -XK_XF86_MonBrightnessDown = 0x1008FF03 -XK_XF86_KbdLightOnOff = 0x1008FF04 -XK_XF86_KbdBrightnessUp = 0x1008FF05 -XK_XF86_KbdBrightnessDown = 0x1008FF06 +XK_XF86_ModeLock = 0x1008FF01 + +XK_XF86_MonBrightnessUp = 0x1008FF02 +XK_XF86_MonBrightnessDown = 0x1008FF03 +XK_XF86_KbdLightOnOff = 0x1008FF04 +XK_XF86_KbdBrightnessUp = 0x1008FF05 +XK_XF86_KbdBrightnessDown = 0x1008FF06 +XK_XF86_MonBrightnessCycle = 0x1008FF07 XK_XF86_Standby = 0x1008FF10 XK_XF86_AudioLowerVolume = 0x1008FF11 @@ -159,6 +162,25 @@ XK_XF86_Yellow = 0x1008FFA5 XK_XF86_Blue = 0x1008FFA6 +XK_XF86_Suspend = 0x1008FFA7 +XK_XF86_Hibernate = 0x1008FFA8 +XK_XF86_TouchpadToggle = 0x1008FFA9 +XK_XF86_TouchpadOn = 0x1008FFB0 +XK_XF86_TouchpadOff = 0x1008FFB1 + +XK_XF86_AudioMicMute = 0x1008FFB2 + +XK_XF86_Keyboard = 0x1008FFB3 + +XK_XF86_WWAN = 0x1008FFB4 +XK_XF86_RFKill = 0x1008FFB5 + +XK_XF86_AudioPreset = 0x1008FFB6 + +XK_XF86_RotationLockToggle = 0x1008FFB7 + +XK_XF86_FullScreen = 0x1008FFB8 + XK_XF86_Switch_VT_1 = 0x1008FE01 XK_XF86_Switch_VT_2 = 0x1008FE02 XK_XF86_Switch_VT_3 = 0x1008FE03 @@ -176,3 +198,5 @@ XK_XF86_ClearGrab = 0x1008FE21 XK_XF86_Next_VMode = 0x1008FE22 XK_XF86_Prev_VMode = 0x1008FE23 +XK_XF86_LogWindowTree = 0x1008FE24 +XK_XF86_LogGrabInfo = 0x1008FE25 diff --git a/Xlib/protocol/display.py b/Xlib/protocol/display.py index 2f8a9e24..0d910dab 100644 --- a/Xlib/protocol/display.py +++ b/Xlib/protocol/display.py @@ -214,7 +214,7 @@ def next_event(self): # Call send_and_recv, which will return when # something has occured - self.send_and_recv(event = 1) + self.send_and_recv(event = True) # Before looping around, lock the event queue against # modifications. @@ -240,7 +240,7 @@ def pending_events(self): # Make a send_and_recv pass, receiving any events self.send_recv_lock.acquire() - self.send_and_recv(recv = 1) + self.send_and_recv(recv = True) # Lock the queue, get the event count, and unlock again. self.event_queue_write_lock.acquire() @@ -252,7 +252,7 @@ def pending_events(self): def flush(self): self.check_for_error() self.send_recv_lock.acquire() - self.send_and_recv(flush = 1) + self.send_and_recv(flush = True) def close(self): self.flush() @@ -384,7 +384,7 @@ def close_internal(self, whom): self.socket_error_lock.release() - def send_and_recv(self, flush = None, event = None, request = None, recv = None): + def send_and_recv(self, flush = False, event = False, request = None, recv = False): """send_and_recv(flush = None, event = None, request = None, recv = None) Perform I/O, or wait for some other thread to do it for us. @@ -402,7 +402,7 @@ def send_and_recv(self, flush = None, event = None, request = None, recv = None) To wait for an event to be received, event should be true. To wait for a response to a certain request (either an error - or a response), request should be set the that request's + or a response), request should be set to that request's serial number. To just read any pending data from the server, recv should be true. @@ -582,7 +582,7 @@ def send_and_recv(self, flush = None, event = None, request = None, recv = None) try: i = self.socket.send(self.data_send) except socket.error as err: - self.close_internal('server: %s' % err[1]) + self.close_internal('server: %s' % err) raise self.socket_error self.data_send = self.data_send[i:] @@ -600,7 +600,7 @@ def send_and_recv(self, flush = None, event = None, request = None, recv = None) count = max(self.recv_buffer_size, count) bytes_recv = self.socket.recv(count) except socket.error as err: - self.close_internal('server: %s' % err[1]) + self.close_internal('server: %s' % err) raise self.socket_error if not bytes_recv: @@ -689,8 +689,8 @@ def parse_response(self, request): return self.parse_connection_setup() # Parse ordinary server response - gotreq = 0 - while 1: + gotreq = False + while True: if self.data_recv: # Check the first byte to find out what kind of response it is rtype = byte2int(self.data_recv) @@ -772,7 +772,7 @@ def parse_error_response(self, request): else: self.default_error_handler(e) - return 0 + return False def default_error_handler(self, err): @@ -937,7 +937,7 @@ def parse_connection_setup(self): # Only the ConnectionSetupRequest has been sent so far r = self.sent_requests[0] - while 1: + while True: # print 'data_send:', repr(self.data_send) # print 'data_recv:', repr(self.data_recv) @@ -946,7 +946,7 @@ def parse_connection_setup(self): # The full response haven't arrived yet if len(self.data_recv) < alen: - return 0 + return False # Connection failed or further authentication is needed. # Set reason to the reason string @@ -956,22 +956,22 @@ def parse_connection_setup(self): # Else connection succeeded, parse the reply else: x, d = r._success_reply.parse_binary(self.data_recv[:alen], - self, rawdict = 1) + self, rawdict = True) r._data.update(x) del self.sent_requests[0] self.data_recv = self.data_recv[alen:] - return 1 + return True else: # The base reply is 8 bytes long if len(self.data_recv) < 8: - return 0 + return False r._data, d = r._reply.parse_binary(self.data_recv[:8], - self, rawdict = 1) + self, rawdict = True) self.data_recv = self.data_recv[8:] # Loop around to see if we have got the additional data @@ -1066,7 +1066,7 @@ def __init__(self, display, *args, **keys): # Don't bother about locking, since no other threads have # access to the display yet - display.request_queue.append((self, 1)) + display.request_queue.append((self, True)) # However, we must lock send_and_recv, but we don't have # to loop. diff --git a/Xlib/protocol/request.py b/Xlib/protocol/request.py index efe62ddb..b431e137 100644 --- a/Xlib/protocol/request.py +++ b/Xlib/protocol/request.py @@ -1640,7 +1640,8 @@ class ChangeHosts(rq.Request): rq.Opcode(109), rq.Set('mode', 1, (X.HostInsert, X.HostDelete)), rq.RequestLength(), - rq.Set('host_family', 1, (X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos)), + rq.Set('host_family', 1, (X.FamilyInternet, X.FamilyDECnet, X.FamilyChaos, + X.FamilyServerInterpreted, X.FamilyInternetV6)), rq.Pad(1), rq.LengthOf('host', 2), rq.List('host', rq.Card8Obj) diff --git a/Xlib/protocol/rq.py b/Xlib/protocol/rq.py index 24042176..8bc82059 100644 --- a/Xlib/protocol/rq.py +++ b/Xlib/protocol/rq.py @@ -24,7 +24,6 @@ import traceback import struct from array import array -import types # Python 2/3 compatibility. from six import PY3, binary_type, byte2int, indexbytes, iterbytes @@ -119,7 +118,7 @@ class Field(object): check_value = None parse_value = None - keyword_args = 0 + keyword_args = False def __init__(self): pass @@ -724,7 +723,7 @@ def pack_value(self, value): class ValueList(Field): structcode = None - keyword_args = 1 + keyword_args = True default = 'usekeywords' def __init__(self, name, mask, pad, *fields): @@ -1089,7 +1088,7 @@ def pack_value(self, value): raise BadDataError('%s is not a tuple or a list' % (value)) - def parse_value(self, val, display, rawdict = 0): + def parse_value(self, val, display, rawdict = False): """This function is used by List and Object fields to convert Struct objects with no var_fields into Python values. @@ -1132,9 +1131,9 @@ def parse_value(self, val, display, rawdict = 0): return DictWrapper(ret) return ret - def parse_binary(self, data, display, rawdict = 0): + def parse_binary(self, data, display, rawdict = False): - """values, remdata = s.parse_binary(data, display, rawdict = 0) + """values, remdata = s.parse_binary(data, display, rawdict = False) Convert a binary representation of the structure into Python values. @@ -1320,7 +1319,7 @@ def __str__(self): return str(self._data) def __repr__(self): - return '%s(%s)' % (self.__class__, repr(self._data)) + return '%s(%s)' % (self.__class__.__name__, repr(self._data)) def __lt__(self, other): if isinstance(other, DictWrapper): @@ -1355,7 +1354,7 @@ def _set_error(self, error): return 0 class ReplyRequest(GetAttrData): - def __init__(self, display, defer = 0, *args, **keys): + def __init__(self, display, defer = False, *args, **keys): self._display = display self._binary = self._request.to_binary(*args, **keys) self._serial = None @@ -1364,7 +1363,7 @@ def __init__(self, display, defer = 0, *args, **keys): self._response_lock = lock.allocate_lock() - self._display.send_request(self, 1) + self._display.send_request(self, True) if not defer: self.reply() @@ -1390,7 +1389,7 @@ def reply(self): def _parse_response(self, data): self._response_lock.acquire() - self._data, d = self._reply.parse_binary(data, self._display, rawdict = 1) + self._data, d = self._reply.parse_binary(data, self._display, rawdict = True) self._response_lock.release() def _set_error(self, error): @@ -1400,7 +1399,7 @@ def _set_error(self, error): return 1 def __repr__(self): - return '<%s serial = %s, data = %s, error = %s>' % (self.__class__, self._serial, self._data, self._error) + return '<%s serial = %s, data = %s, error = %s>' % (self.__class__.__name__, self._serial, self._data, self._error) class Event(GetAttrData): @@ -1409,7 +1408,7 @@ def __init__(self, binarydata = None, display = None, if binarydata: self._binary = binarydata self._data, data = self._fields.parse_binary(binarydata, display, - rawdict = 1) + rawdict = True) # split event type into type and send_event bit self._data['send_event'] = not not self._data['type'] & 0x80 self._data['type'] = self._data['type'] & 0x7f @@ -1434,7 +1433,7 @@ def __repr__(self): kwlist.append('%s = %s' % (kw, repr(val))) kws = ', '.join(kwlist) - return '%s(%s)' % (self.__class__, kws) + return '%s(%s)' % (self.__class__.__name__, kws) def __lt__(self, other): if isinstance(other, Event): diff --git a/Xlib/support/unix_connect.py b/Xlib/support/unix_connect.py index c2261dae..bd690c18 100644 --- a/Xlib/support/unix_connect.py +++ b/Xlib/support/unix_connect.py @@ -23,23 +23,6 @@ import os import platform import socket - -# FCNTL is deprecated from Python 2.2, so only import it if we doesn't -# get the names we need. Furthermore, FD_CLOEXEC seems to be missing -# in Python 2.2. - -import fcntl - -if hasattr(fcntl, 'F_SETFD'): - F_SETFD = fcntl.F_SETFD - if hasattr(fcntl, 'FD_CLOEXEC'): - FD_CLOEXEC = fcntl.FD_CLOEXEC - else: - FD_CLOEXEC = 1 -else: - from FCNTL import F_SETFD, FD_CLOEXEC - - from Xlib import error, xauth @@ -93,11 +76,13 @@ def _get_tcp_socket(host, dno): s.connect((host, 6000 + dno)) return s + def _get_unix_socket(address): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(address) return s + def get_socket(dname, protocol, host, dno): assert protocol in SUPPORTED_PROTOCOLS try: @@ -127,11 +112,37 @@ def get_socket(dname, protocol, host, dno): raise error.DisplayConnectionError(dname, str(val)) # Make sure that the connection isn't inherited in child processes. - fcntl.fcntl(s.fileno(), F_SETFD, FD_CLOEXEC) + _ensure_not_inheritable(s) return s +def _ensure_not_inheritable(sock): + # According to PEP446, in Python 3.4 and above, + # it is not inherited in child processes by default. + # However, just in case, we explicitly make it non-inheritable. + # Also, we don't use the code like the following, + # because there would be no possibility of backporting to past versions. + # if sys.version_info.major == 3 and sys.version_info.minor >= 4: + # sock.set_inheritable(False) + # return + # We just check if the socket has `set_inheritable`. + if hasattr(sock, 'set_inheritable'): + sock.set_inheritable(False) + return + + # On Windows, + # Python doesn't support fcntl module because Windows doesn't have fcntl API. + # At least by not importing fcntl, we will be able to import python-xlib on Windows. + if platform.system() == 'Windows': + # so.. unfortunately, for Python 3.3 and below, on Windows, + # we can't make sure that the connection isn't inherited in child processes for now. + return + + import fcntl + fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) + + def new_get_auth(sock, dname, protocol, host, dno): assert protocol in SUPPORTED_PROTOCOLS # Translate socket address into the xauth domain diff --git a/Xlib/xauth.py b/Xlib/xauth.py index 63eb7ef7..303bd491 100644 --- a/Xlib/xauth.py +++ b/Xlib/xauth.py @@ -27,6 +27,8 @@ FamilyInternet = X.FamilyInternet FamilyDECnet = X.FamilyDECnet FamilyChaos = X.FamilyChaos +FamilyServerInterpreted = X.FamilyServerInterpreted +FamilyInternetV6 = X.FamilyInternetV6 FamilyLocal = 256 class Xauthority(object): @@ -118,6 +120,8 @@ def get_best_auth(self, family, address, dispno, matches = {} for efam, eaddr, enum, ename, edata in self.entries: + if enum == b'' and ename not in matches: + enum = num if efam == family and eaddr == address and num == enum: matches[ename] = edata diff --git a/Xlib/xobject/drawable.py b/Xlib/xobject/drawable.py index 2f688c88..f8e625f6 100644 --- a/Xlib/xobject/drawable.py +++ b/Xlib/xobject/drawable.py @@ -19,7 +19,7 @@ # Suite 330, # Boston, MA 02111-1307 USA -from Xlib import X, Xatom, Xutil +from Xlib import X, Xatom from Xlib.protocol import request, rq # Other X resource objects @@ -451,7 +451,7 @@ def delete_property(self, property, onerror = None): window = self.id, property = property) - def get_property(self, property, property_type, offset, length, delete = 0): + def get_property(self, property, property_type, offset, length, delete = False): r = request.GetProperty(display = self.display, delete = delete, window = self.id, @@ -516,7 +516,7 @@ def convert_selection(self, selection, target, property, time, onerror = None): property = property, time = time) - def send_event(self, event, event_mask = 0, propagate = 0, onerror = None): + def send_event(self, event, event_mask = 0, propagate = False, onerror = None): request.SendEvent(display = self.display, onerror = onerror, propagate = propagate, @@ -629,7 +629,7 @@ def set_input_focus(self, revert_to, time, onerror = None): focus = self.id, time = time) - def clear_area(self, x = 0, y = 0, width = 0, height = 0, exposures = 0, onerror = None): + def clear_area(self, x = 0, y = 0, width = 0, height = 0, exposures = False, onerror = None): request.ClearArea(display = self.display, onerror = onerror, exposures = exposures, @@ -779,7 +779,7 @@ def get_wm_icon_size(self): def _get_struct_prop(self, pname, ptype, pstruct): r = self.get_property(pname, ptype, 0, pstruct.static_size // 4) if r and r.format == 32: - value = r.value.tostring() + value = rq.encode_array(r.value) if len(value) == pstruct.static_size: return pstruct.parse_binary(value, self.display)[0] diff --git a/Xlib/xobject/resource.py b/Xlib/xobject/resource.py index 0492ffec..ea256ca1 100644 --- a/Xlib/xobject/resource.py +++ b/Xlib/xobject/resource.py @@ -45,11 +45,8 @@ def __ne__(self, obj): def __hash__(self): return int(self.id) - def __str__(self): - return '%s(0x%08x)' % (self.__class__, self.id) - def __repr__(self): - return '<%s 0x%08x>' % (self.__class__, self.id) + return '<%s 0x%08x>' % (self.__class__.__name__, self.id) def kill_client(self, onerror = None): request.KillClient(display = self.display, diff --git a/doc/src/concepts.texi b/doc/src/concepts.texi index 8c9f08b0..d71f62fe 100644 --- a/doc/src/concepts.texi +++ b/doc/src/concepts.texi @@ -9,4 +9,4 @@ Here you might find an introduction to X concepts sometime in the future. For now, I just refer to the introduction parts of the standard X documentation. A vast collection of X documentation links can be -found at @uref{http://www.rahul.net/kenton/xsites.html}. +found at @uref{https://web.archive.org/web/20201228053920/http://www.rahul.net/kenton/xsites.html}. diff --git a/doc/src/objects.texi b/doc/src/objects.texi index 6d47137a..094bf95c 100644 --- a/doc/src/objects.texi +++ b/doc/src/objects.texi @@ -678,7 +678,8 @@ XGetScreenSaver(3X11) for details. @var{mode} is either @code{X.HostInsert} or @code{X.HostDelete}. @var{host_family} is one of @code{X.FamilyInternet}, -@code{X.FamilyDECnet} or @code{X.FamilyChaos}. +@code{X.FamilyDECnet}, @code{X.FamilyChaos}, @code{X.FamilyServerInterpreted} +or @code{X.FamilyInternetV6}. @var{host} is a list of bytes. For the Internet family, it should be the four bytes of an IPv4 address. @@ -699,7 +700,8 @@ The hosts on the access list. Each entry has the following attributes: @table @code @item family -@code{X.FamilyInternet}, @code{X.FamilyDECnet}, or @code{X.FamilyChaos}. +@code{X.FamilyInternet}, @code{X.FamilyDECnet}, @code{X.FamilyChaos}, +@code{X.FamilyServerInterpreted} or @code{X.FamilyInternetV6}. @item name A list of byte values, the coding depends on @code{family}. For the @@ -1175,7 +1177,7 @@ Returns None or string. @end defmethod @defmethod Window get_wm_class ( ) -Returns None or (isntance, class) +Returns None or (instance, class) @end defmethod @defmethod Window set_wm_transient_for ( window, onerror = None ) diff --git a/examples/dpms.py b/examples/dpms.py new file mode 100755 index 00000000..0a04be57 --- /dev/null +++ b/examples/dpms.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# +# examples/dpms.py -- DPMS usage examples. +# +# Copyright (C) 2020 Thiago Kenji Okada +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, +# Suite 330, +# Boston, MA 02111-1307 USA + +import random +import time + +from Xlib import display +from Xlib.ext import dpms + + +class DPMSExamples(object): + def __init__(self): + self.d = display.Display() + + capable = self.d.dpms_capable() + assert capable, 'DPMS is not supported in your system' + + self.initial_info = self.d.dpms_info() + self.initial_timeouts = self.d.dpms_get_timeouts() + + # Making sure that DPMS is enable for this examples + self.d.dpms_enable() + self.d.sync() + + def print_dpms(self): + current_info = self.d.dpms_info() + print('\nDPMS state: {}\nPower level: {}'.format(current_info.state, + current_info.power_level)) + current_timeouts = self.d.dpms_get_timeouts() + print('Standby: {}, Suspend: {}, Off: {}\n'.format(current_timeouts.standby_timeout, + current_timeouts.suspend_timeout, + current_timeouts.off_timeout)) + + def toggle_dpms(self): + current_info = self.d.dpms_info() + if current_info.state: + self.d.dpms_disable() + else: + self.d.dpms_enable() + + self.d.sync() + + def restore(self): + print('Restoring DPMS configuration') + self.d.dpms_set_timeouts(self.initial_timeouts.standby_timeout, + self.initial_timeouts.suspend_timeout, + self.initial_timeouts.off_timeout) + if self.initial_info.state: + self.d.dpms_enable() + else: + self.d.dpms_disable() + + self.d.sync() + + self.print_dpms() + + def set_random_timeouts(self): + # Can be any number greater than 0 + # Using 10 just to not turnoff the screen suddenly + standby_timeout = random.randint(10, 600) + # Shouldn't be smaller than standby_timeout + suspend_timeout = random.randint(standby_timeout, 600) + # Shouldn't be smaller than standby_timeout or suspend_timeout + off_timeout = random.randint(suspend_timeout, 600) + self.d.dpms_set_timeouts(standby_timeout, suspend_timeout, off_timeout) + self.d.sync() + + def turn_off_display(self): + self.d.dpms_force_level(dpms.DPMSModeOff) + self.d.sync() + + def turn_on_display(self): + self.d.dpms_force_level(dpms.DPMSModeOn) + self.d.sync() + + +def main(): + try: + examples = DPMSExamples() + + print('Initial state') + examples.print_dpms() + + print('Setting random timeouts') + examples.set_random_timeouts() + examples.print_dpms() + + print('The next example will turn-off your screen, press Ctrl-C to cancel.') + time.sleep(2) + examples.turn_off_display() + + print('Turning it on again...') + time.sleep(2) + examples.turn_on_display() + + print() + + print('Toggle DPMS') + examples.toggle_dpms() + examples.print_dpms() + + print('Toggle it again') + examples.toggle_dpms() + examples.print_dpms() + finally: + examples.restore() + + +if __name__ == '__main__': + main() diff --git a/examples/run_examples.py b/examples/run_examples.py index f97a4e0b..9c834dcd 100644 --- a/examples/run_examples.py +++ b/examples/run_examples.py @@ -77,6 +77,10 @@ def test_xlsatoms(self): """ Run xlsatoms.py -- show list atoms on X server """ self.assertEqual(run_example(examples_folder + "xlsatoms.py"), 0) + def test_xres(self): + """ Run xres.py -- demonstrate the X-Resource extension """ + self.assertEqual(run_example(examples_folder + "xres.py"), 0) + if __name__ == '__main__': unittest.main() diff --git a/examples/xfixes-cursor-notify.py b/examples/xfixes-cursor-notify.py new file mode 100755 index 00000000..cc443fe9 --- /dev/null +++ b/examples/xfixes-cursor-notify.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +# +# examples/xfixes-cursor-notify.py -- demonstrate the XFIXES extension +# CursorNotify event. +# +# Copyright (C) 2022 +# Dan Isla +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, +# Suite 330, +# Boston, MA 02111-1307 USA + +# Python 2/3 compatibility. +from __future__ import print_function + +import sys +from Xlib.display import Display +from Xlib.ext import xfixes + +def main(): + display = Display() + + if not display.has_extension('XFIXES'): + if display.query_extension('XFIXES') is None: + print('XFIXES extension not supported') + return 1 + + xfixes_version = display.xfixes_query_version() + print('Found XFIXES version {}.{}'.format( + xfixes_version.major_version, + xfixes_version.minor_version + )) + + screen = display.screen() + + display.xfixes_select_cursor_input(screen.root, xfixes.XFixesDisplayCursorNotifyMask) + + cursor_cache = {} + + while True: + e = display.next_event() + print(e) + + if (e.type, e.sub_code) == display.extension_event.DisplayCursorNotify: + print("DisplayCursorNotify: cursor_serial={}".format(e.cursor_serial)) + image = display.xfixes_get_cursor_image(screen.root) + cached = False + if cursor_cache.get(image.cursor_serial): + cached = True + else: + cursor_cache[image.cursor_serial] = image.cursor_image + + print("Cursor position={},{}, size={}x{}, xyhot={},{}, cursor_serial={}, cached={}".format( + image.x, image.y, image.width,image.height, image.xhot, image.yhot, image.cursor_serial, cached + )) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/xres.py b/examples/xres.py new file mode 100755 index 00000000..b28d7c38 --- /dev/null +++ b/examples/xres.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# +# examples/xres.py -- demonstrate the X-Resource extension +# +# Copyright (C) 2021 Aleksei Bavshin +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 +# of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, +# Fifth Floor, +# Boston, MA 02110-1301 USA + +import os +import sys + +# Change path so we find Xlib +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +from Xlib.display import Display +from Xlib.ext import res as XRes + + +def check_ext(disp, extname, version): + if disp.query_extension(extname) is None: + raise AssertionError("Server has {} extension".format(extname)) + + r = disp.res_query_version() + if (r.server_major, r.server_minor) < version: + raise AssertionError( + "Server has requested version {} of {} extension".format(version, extname) + ) + + +def query_client_id(display, wid): + specs = [{"client": wid, "mask": XRes.LocalClientPIDMask}] + r = display.res_query_client_ids(specs) + for id in r.ids: + if id.spec.client > 0 and id.spec.mask == XRes.LocalClientPIDMask: + for value in id.value: + return value + return None + + +def print_client_info(disp, client): + print("client: {}".format(client)) + + resources = disp.res_query_client_resources(client) + rc = [r.count for r in resources.types] + print("\tresouces: {} resources of {} types".format(sum(rc), len(rc))) + + pb = disp.res_query_client_pixmap_bytes(client) + print("\tpixmaps: {} bytes {} overflow".format(pb.bytes, pb.bytes_overflow)) + + pid = query_client_id(disp, client) + print("\tpid: {}".format(pid)) + + rb = disp.res_query_resource_bytes(client, [{"resource": 0, "type": 0}]) + sizes = [s.size.bytes for s in rb.sizes] + print("\t{} resources consume {} bytes".format(len(sizes), sum(sizes))) + + +def main(): + display = Display() + check_ext(display, XRes.extname, (1, 2)) + + clients = display.res_query_clients().clients + print("{} clients connected to the server".format(len(clients))) + + for client in clients: + print_client_info(display, client.resource_base) + + +if __name__ == "__main__": + main() diff --git a/test/test_unix_connect.py b/test/test_unix_connect.py index 7680ba5b..3199fa5f 100644 --- a/test/test_unix_connect.py +++ b/test/test_unix_connect.py @@ -72,8 +72,8 @@ def _get_socket(socket_type, raises, *params): def path_exists(returns, path): calls.append(('os.path.exists', path)) return returns - def fcntl(*args): - calls.append(('fcntl',) + args) + def ensure_not_inheritable(*args): + calls.append(('ensure_not_inheritable',) + args) for params, allow_unix, unix_addr_exists, allow_tcp, expect_connection_error, expected_calls in ( # Successful explicit TCP socket connection. (('tcp/host:6', None, 'host', 6), False, False, True, False, [ @@ -141,7 +141,7 @@ def fcntl(*args): partial(_get_socket, 'tcp', not allow_tcp)), \ patch('os.path.exists', partial(path_exists, unix_addr_exists)), \ - patch('fcntl.fcntl', fcntl): + patch('Xlib.support.unix_connect._ensure_not_inheritable', ensure_not_inheritable): del calls[:] if expect_connection_error: with self.assertRaises(DisplayConnectionError): @@ -149,9 +149,7 @@ def fcntl(*args): else: s = unix_connect.get_socket(*params) self.assertIsInstance(s, FakeSocket) - expected_calls.append(('fcntl', 42, - unix_connect.F_SETFD, - unix_connect.FD_CLOEXEC)) + expected_calls.append(('ensure_not_inheritable', s)) self.assertEqual(calls, expected_calls) diff --git a/test/test_xlib_display.py b/test/test_xlib_display.py index fb617a3a..61994617 100644 --- a/test/test_xlib_display.py +++ b/test/test_xlib_display.py @@ -87,7 +87,7 @@ def test_cannot_add_existing_font_method(self): self.assertRaises(AssertionError, self.display.extension_add_method, "font", "__init__", lambda x: x) def test_can_add_extension_error(self): - self.display.add_extension_error(1, Xlib.error.XError) + self.display.extension_add_error(1, Xlib.error.XError) self.assertEqual(self.display.display.error_classes[1], Xlib.error.XError) def test_keycode_to_keysym_for_invalid_index(self):