From fd5dafe545827747e9d3a58c6e999232ae8dc72d Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Thu, 17 Oct 2013 10:04:20 +0200 Subject: [PATCH 01/26] Add CVE-IDs to changelog --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 838cb2d..c00bd57 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ Release 0.2 : • Security: Check correctly for the result of the - xcb_grab_{pointer,keyboard} commands + xcb_grab_{pointer,keyboard} commands (CVE-2013-4427). • Security: Limit length of buffered password to prevent memory exhaustion (this is a real concern when attacked with custom hardware which simulates most rapid keystrokes) @@ -11,4 +11,4 @@ Release 0.2 : Release 0.1: • [#8] Security: Fixed a typo that could in some circumstances lead to a crash after multiple failed authentication attempts. Thanks, - Paul Lhussiez. + Paul Lhussiez (CVE-2013-4426). From 5f2cbe5ded384862c4882777fe10dadb07fba7a2 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Fri, 14 Feb 2014 20:34:39 +0100 Subject: [PATCH 02/26] Add pyxtrlock mailing list --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 10128b5..6d4f606 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,20 @@ with a man page. An alternative to ``xautolock`` is the use of [autolockd](https://github.com/zombofant/autolockd) which also monitors for lid close and suspend events. +Staying up-to-date +------------------ +As pyxtrlock is a security tool, it is important to stay up-to-date with +security updates. We take security seriously and try to handle any +vulnerabilities quickly. However, our efforts are useless if the users +aren’t notified that updates are available, so if you use pyxtrlock, we +urge you to subscribe to +[the pyxtrlock mailing list](http://lists.zombofant.net/mailman/listinfo/pyxtrlock). +This list is likely very low traffic and will ensure you get +notifications of security updates in time. + +We also appreciate any feedback you have regarding pyxtrlock on this +mailing list. + Bugs & Limitations ------------------ Additional input devices other than the keyboard and mouse are not disabled. From 8921069f54b52b8994d4e8704e718a43d99d7975 Mon Sep 17 00:00:00 2001 From: Cristian Ciupitu Date: Wed, 25 Jun 2014 23:44:17 +0300 Subject: [PATCH 03/26] Make pyxtrlock conform with the XDG specification According to the [XDG Base Directory Specification][1], XDG_CONFIG_HOME is not always ~/.config. It can depend on the environment variable $XDG_CONFIG_HOME. Therefor use the right directory with the help of pyxdg. [1]: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html --- README.md | 8 ++++++++ pyxtrlock | 3 ++- setup.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d4f606..6e2738b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,13 @@ dependencies: $ cd python3-simplepam $ sudo python3 setup.py install +Install [pyxdg](http://freedesktop.org/Software/pyxdg) for +dependencies: + + $ git clone git://anongit.freedesktop.org/xdg/pyxdg + $ cd pyxdg + $ sudo python3 setup.py install + Clone and install pyxtrlock: $ git clone git://github.com/leonnnn/pyxtrlock.git @@ -108,6 +115,7 @@ system. The default padlock file is created on install (by Requirements ------------ * [python3-simplepam](https://github.com/leonnnn/python3-simplepam) +* [pyxdg](http://freedesktop.org/Software/pyxdg) * Python ≥ 3.0 * libxcb * libxcb-image diff --git a/pyxtrlock b/pyxtrlock index 913b133..788858b 100755 --- a/pyxtrlock +++ b/pyxtrlock @@ -9,6 +9,7 @@ import getpass from ctypes import byref, cast, sizeof from ctypes import POINTER, c_int, c_uint32, c_char +from xdg.BaseDirectory import xdg_config_home import simplepam as pam import pyxtrlock @@ -32,7 +33,7 @@ if getpass.getuser() == 'root' and sys.argv[1:] != ['-f']: # load cursor data file try: - f_name = os.path.expanduser("~/.config/pyxtrlock/lock.pickle") + f_name = os.path.join(xdg_config_home, "pyxtrlock", "lock.pickle") if os.path.exists(f_name): f = open(f_name, "rb") else: diff --git a/setup.py b/setup.py index 0a50e0f..0905f53 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def run(self): version='0.2', author=authors, author_email='leon@leonweber.de', - requires=['simplepam'], + requires=['simplepam', 'pyxdg'], package_dir={'pyxtrlock': 'lib'}, data_files=[('share/pyxtrlock/', ['lock.pickle'])], packages=['pyxtrlock'], From 46a16c1dad1295ce66be423682066bae5384ef47 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Mon, 18 Aug 2014 11:05:15 +0200 Subject: [PATCH 04/26] Recommend to install pyxdg via package manager --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6e2738b..6d4309c 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,8 @@ dependencies: $ cd python3-simplepam $ sudo python3 setup.py install -Install [pyxdg](http://freedesktop.org/Software/pyxdg) for -dependencies: - - $ git clone git://anongit.freedesktop.org/xdg/pyxdg - $ cd pyxdg - $ sudo python3 setup.py install +Install pyxdg, which is available as python3-pyxdg or similar in most Linux +distributions. Clone and install pyxtrlock: From cf5860d2fb31abf3f3155699ab3dbd9c0e15c2c4 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Sun, 24 Aug 2014 17:16:05 +0200 Subject: [PATCH 05/26] Add Changelog entry --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c00bd57..547e0b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +Release xxx: +• Enhancement: Make pyxtrlock conform to the XDG specification. Patch by + Cristian Ciupitu. + Release 0.2 : • Security: Check correctly for the result of the xcb_grab_{pointer,keyboard} commands (CVE-2013-4427). From eb90b1382021c83bfc297bb5b88418606bb1aa50 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Sat, 17 Jan 2015 22:41:00 +0100 Subject: [PATCH 06/26] Get rid of useless import and fix indentation --- make_default_lock.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/make_default_lock.py b/make_default_lock.py index 549fdb6..0c70ad8 100644 --- a/make_default_lock.py +++ b/make_default_lock.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 import pickle -import sys fg_bitmap = bytes([ 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0x7f, 0x00, 0xe0, 0xff, @@ -39,13 +38,13 @@ with open("lock.pickle", "wb") as f: pickle.dump({ - "width": 28, - "height": 40, - "x_hot": 14, - "y_hot": 21, - "fg_bitmap": fg_bitmap, - "bg_bitmap": bg_bitmap, - "color_mode": "named", - "bg_color": "steelblue3", - "fg_color": "grey25" + "width": 28, + "height": 40, + "x_hot": 14, + "y_hot": 21, + "fg_bitmap": fg_bitmap, + "bg_bitmap": bg_bitmap, + "color_mode": "named", + "bg_color": "steelblue3", + "fg_color": "grey25" }, f) From 6f1c0ddc001c10d9e200aa2eeb80283bf2b1b591 Mon Sep 17 00:00:00 2001 From: Alexander Klink Date: Thu, 15 Jan 2015 12:23:49 +0100 Subject: [PATCH 07/26] script to make empty lock cursor --- README.md | 3 ++- make_empty_lock.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 make_empty_lock.py diff --git a/README.md b/README.md index 6d4309c..fe181df 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,8 @@ The default cursor file is placed at resistant against maliciously crafted files. Therfore do not open ``pickle`` files from untrusted sources as they may compromise your system. The default padlock file is created on install (by -``make_default_lock.py``). +``make_default_lock.py``). In case you do not want to see any kind +of cursor, an empty one can be created using make_empty_lock.py. Requirements ------------ diff --git a/make_empty_lock.py b/make_empty_lock.py new file mode 100644 index 0000000..e1d5a8c --- /dev/null +++ b/make_empty_lock.py @@ -0,0 +1,20 @@ +#!/usr/bin/python3 + +import pickle +import sys + +fg_bitmap = bytes([0x00]) +bg_bitmap = bytes([0x00]) + +with open("lock.pickle", "wb") as f: + pickle.dump({ + "width": 1, + "height": 1, + "x_hot": 1, + "y_hot": 1, + "fg_bitmap": fg_bitmap, + "bg_bitmap": bg_bitmap, + "color_mode": "named", + "bg_color": "steelblue3", + "fg_color": "grey25" + }, f) From 98c56f7523206f62d72e4577a91357d6fd6dc53a Mon Sep 17 00:00:00 2001 From: Alexander Klink Date: Thu, 5 Feb 2015 11:38:04 +0100 Subject: [PATCH 08/26] Get rid of useless import and fix indentation --- make_empty_lock.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/make_empty_lock.py b/make_empty_lock.py index e1d5a8c..f92fffd 100644 --- a/make_empty_lock.py +++ b/make_empty_lock.py @@ -1,20 +1,19 @@ #!/usr/bin/python3 import pickle -import sys fg_bitmap = bytes([0x00]) bg_bitmap = bytes([0x00]) with open("lock.pickle", "wb") as f: pickle.dump({ - "width": 1, - "height": 1, - "x_hot": 1, - "y_hot": 1, - "fg_bitmap": fg_bitmap, - "bg_bitmap": bg_bitmap, - "color_mode": "named", - "bg_color": "steelblue3", - "fg_color": "grey25" + "width": 1, + "height": 1, + "x_hot": 1, + "y_hot": 1, + "fg_bitmap": fg_bitmap, + "bg_bitmap": bg_bitmap, + "color_mode": "named", + "bg_color": "steelblue3", + "fg_color": "grey25" }, f) From 49f0e7bd55f99f73d401b1132bc9c29643956ada Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Thu, 5 Feb 2015 13:56:45 +0100 Subject: [PATCH 09/26] Markup README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe181df..6d1a66b 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ resistant against maliciously crafted files. Therfore do not open ``pickle`` files from untrusted sources as they may compromise your system. The default padlock file is created on install (by ``make_default_lock.py``). In case you do not want to see any kind -of cursor, an empty one can be created using make_empty_lock.py. +of cursor, an empty one can be created using ̀ ̀̀̀̀̀̀̀̀make_empty_lock.py``. Requirements ------------ From a6ebd420f0de864f464f3f5d6747348592874295 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Thu, 5 Feb 2015 13:58:44 +0100 Subject: [PATCH 10/26] Update CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 547e0b9..c26dd37 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Release xxx: • Enhancement: Make pyxtrlock conform to the XDG specification. Patch by Cristian Ciupitu. +• Enhancement: Ship script to create an empty lock symbol. Patch by Alexander + Klink. Release 0.2 : • Security: Check correctly for the result of the From 9e7008baf4d9aca3d9147a46bbc9998258058741 Mon Sep 17 00:00:00 2001 From: Leon Weber Date: Thu, 5 Feb 2015 14:04:41 +0100 Subject: [PATCH 11/26] Fix markup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d1a66b..b3a7fa9 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ resistant against maliciously crafted files. Therfore do not open ``pickle`` files from untrusted sources as they may compromise your system. The default padlock file is created on install (by ``make_default_lock.py``). In case you do not want to see any kind -of cursor, an empty one can be created using ̀ ̀̀̀̀̀̀̀̀make_empty_lock.py``. +of cursor, an empty one can be created using ``make_empty_lock.py``. Requirements ------------ From 2b96a94c0c1330936e6a1a7c9846a22f5baee47f Mon Sep 17 00:00:00 2001 From: Denys Vitali Date: Mon, 1 Feb 2016 09:23:17 +0100 Subject: [PATCH 12/26] Fixed PAM link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3a7fa9..73b8e60 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ pyxtrlock ``pyxtrlock`` is a rewrite of Ian Jackson's great ``xtrlock`` program using modern libraries, most importantly the obsolete direct passwd/shadow authentication has been replaced by today's -[PAM](http://en.wikipedia.org/wiki/Pluggabe_authentication_module) authentication +[PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) authentication mechanism, hence it also works on Fedora. Also, it's mostly written using [XCB](http://xcb.freedesktop.org/) instead of Xlib, although some Xlib/XCB interaction is still necessary. As soon as XCB can provide the required From 0d807eb0b2c1d1523642f3772e0705137f1e8d0b Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Sat, 2 Dec 2017 22:11:55 +0100 Subject: [PATCH 13/26] retry grabbing the keyboard * Thanks to Nils Ballman for reporting the issue and supplying the patch. --- CHANGELOG | 3 +++ pyxtrlock | 26 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c26dd37..7195694 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,9 @@ Release xxx: Cristian Ciupitu. • Enhancement: Ship script to create an empty lock symbol. Patch by Alexander Klink. +• Enhancement: Retry grabbing the keyboard as well as the mouse to + prevent issues when starting pyxtrlock from a keyboard shortcut. + Patch by Nils Ballmann. Release 0.2 : • Security: Check correctly for the result of the diff --git a/pyxtrlock b/pyxtrlock index 788858b..419798b 100755 --- a/pyxtrlock +++ b/pyxtrlock @@ -111,22 +111,28 @@ except xcb.XCBError as e: xcb.map_window(conn, window) # Grab keyboard -try: - status = xcb.grab_keyboard_sync(conn, 0, window, xcb.CURRENT_TIME, - xcb.GRAB_MODE_ASYNC, xcb.GRAB_MODE_ASYNC) - - if status != xcb.GrabSuccess: - panic("pyxtrlock: Could not grab keyboard") -except xcb.XCBError as e: - panic("pyxtrlock: Could not grab keyboard") - -# Grab pointer # Use the method from the original xtrlock code: # "Sometimes the WM doesn't ungrab the keyboard quickly enough if # launching xtrlock from a keystroke shortcut, meaning xtrlock fails # to start We deal with this by waiting (up to 100 times) for 10,000 # microsecs and trying to grab each time. If we still fail # (i.e. after 1s in total), then give up, and emit an error" + +for i in range(100): + try: + status = xcb.grab_keyboard_sync(conn, 0, window, xcb.CURRENT_TIME, + xcb.GRAB_MODE_ASYNC, xcb.GRAB_MODE_ASYNC) + + if status == xcb.GrabSuccess: + break + else: + time.sleep(0.01) + except xcb.XCBError as e: + time.sleep(0.01) +else: + panic("pyxtrlock: Could not grab keyboard") + +# Grab pointer for i in range(100): try: status = xcb.grab_pointer_sync(conn, False, window, 0, From 87ac9aa5cb4fc660ce6fba24a42cdf12932428e2 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 19:30:08 +0100 Subject: [PATCH 14/26] fix memleak when XCBError is raised --- lib/xcb.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/xcb.py b/lib/xcb.py index 01ee8b0..d8ff715 100644 --- a/lib/xcb.py +++ b/lib/xcb.py @@ -2,9 +2,17 @@ from pyxtrlock.utils import check_and_load_library class XCBError(Exception): + """Raised on XCBErrors. + + This encapsulates the underlying xcb error object. Note that the + xcb error is freed when this object is garbage collected. So you + should never copy the xcb_error property. """ - Raised on XCBErrors - """ + def __init__(self, xcb_error): + self.xcb_error = xcb_error + + def __del__(self): + free(self.xcb_error) class Connection(Structure): From d637dfcc1634bf053e9963385e0750c0701c8518 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 19:30:51 +0100 Subject: [PATCH 15/26] fix colour component order in the xcb_alloc_color wrapper --- lib/xcb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/xcb.py b/lib/xcb.py index d8ff715..67c7a28 100644 --- a/lib/xcb.py +++ b/lib/xcb.py @@ -345,7 +345,7 @@ def alloc_color_sync(conn, colormap, r, g, b): if error_p: raise XCBERror(error_p.contents) - ret = (res.contents.red, res.contents.blue, res.contents.green) + ret = (res.contents.red, res.contents.green, res.contents.blue) free(res) return ret From c151a833af4510c5db4a50886a9a210afbbca940 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 19:31:34 +0100 Subject: [PATCH 16/26] avoid endless loop if the X server connection is lost --- lib/xcb.py | 6 ++++++ pyxtrlock | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/xcb.py b/lib/xcb.py index 67c7a28..f1d7ebc 100644 --- a/lib/xcb.py +++ b/lib/xcb.py @@ -509,6 +509,12 @@ def __exit__(self, etype, evalue, traceback): def wait_for_event(conn): return FreeWrapper(wait_for_event_(conn)) +connection_has_error = libxcb.xcb_connection_has_error +connection_has_error.restype = c_int +connection_has_error.argtypes = [ + POINTER(Connection), +] + # xcb_image image_create_pixmap_from_bitmap_data = \ libxcb_image.xcb_create_pixmap_from_bitmap_data diff --git a/pyxtrlock b/pyxtrlock index 419798b..0ef0986 100755 --- a/pyxtrlock +++ b/pyxtrlock @@ -180,6 +180,17 @@ timeout = 0 goodwill = INITIALGOODWILL while True: with xcb.wait_for_event(conn) as event: + if event is None: + # this test should always be true, we have it here as an + # extra precaution, so we do not kill pyxtrlock if there + # still is a connection to the X server (although + # wait_for_event should never return None in that case) + if xcb.connection_has_error(conn): + # prevent looping if the server connection breaks + break + else: + continue + if event.contents.response_type == xcb.KEY_PRESS: xcb_key_press_event = cast(event, POINTER(xcb.KeyPressEvent)).contents From d302c217cecba408ee350de55f9a1c472fbda2ba Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 19:53:37 +0100 Subject: [PATCH 17/26] fix the handling of cursor files * the default cursor is a python module (solving all install issues) * the cursor format is no longer pickle (but json+base64) * the named color mode has been removed --- lib/__init__.py | 1 - lib/cursor_file.py | 49 +++++++++++++++ lib/default_cursor.py | 44 ++++++++++++++ lib/xcb.py | 8 +-- make_default_lock.py | 50 --------------- make_empty_lock.py | 19 ------ pyxtrlock | 36 +++++------ tools/README | 31 ++++------ tools/make_lock.py | 137 ++++++++++++++++-------------------------- 9 files changed, 177 insertions(+), 198 deletions(-) create mode 100644 lib/cursor_file.py create mode 100644 lib/default_cursor.py delete mode 100644 make_default_lock.py delete mode 100644 make_empty_lock.py diff --git a/lib/__init__.py b/lib/__init__.py index 9af987c..cff5ccf 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -2,7 +2,6 @@ import sys import os -data_dir = os.path.join(sys.prefix, "share/pyxtrlock") def panic(message, exit_code=1): """Print an error message to stderr and exit""" diff --git a/lib/cursor_file.py b/lib/cursor_file.py new file mode 100644 index 0000000..0eb9a12 --- /dev/null +++ b/lib/cursor_file.py @@ -0,0 +1,49 @@ + +import json +import base64 + + +INTEGER_ATTRIBUTES = ["width", "height", "x_hot", "y_hot"] +COLOR_ATTRIBUTES = ["bg_color", "fg_color"] +BINARY_ATTRIBUTES = ["fg_bitmap", "bg_bitmap"] + + +def save_cursor(cursor, f): + res = {} + for attr in INTEGER_ATTRIBUTES: + if not isinstance(cursor[attr], int): + raise ValueError("{} must be integer".format(attr)) + res[attr] = cursor[attr] + + for attr in COLOR_ATTRIBUTES: + if not (len(cursor[attr]) == 3 and + all(isinstance(comp, int) and 0 <= comp <= 255 + for comp in cursor[attr])): + raise ValueError("invalid color specification") + res[attr] = cursor[attr] + + for attr in BINARY_ATTRIBUTES: + res[attr] = base64.b64encode(cursor[attr]).decode("ascii") + json.dump(res, f, sort_keys=True) + + +def load_cursor(f): + cursor = json.load(f) + res = {} + + for attr in INTEGER_ATTRIBUTES: + if not isinstance(cursor[attr], int): + raise ValueError("{} must be integer".format(attr)) + res[attr] = cursor[attr] + + for attr in COLOR_ATTRIBUTES: + if not (len(cursor[attr]) == 3 and + all(isinstance(comp, int) and 0 <= comp <= 255 + for comp in cursor[attr])): + raise ValueError("invalid color specification") + res[attr] = tuple(cursor[attr]) + + for attr in BINARY_ATTRIBUTES: + res[attr] = base64.b64decode(cursor[attr]) + + return res diff --git a/lib/default_cursor.py b/lib/default_cursor.py new file mode 100644 index 0000000..49f80aa --- /dev/null +++ b/lib/default_cursor.py @@ -0,0 +1,44 @@ + +DEFAULT_CURSOR = { + "width": 28, + "height": 40, + "x_hot": 14, + "y_hot": 21, + + "bg_color": (79, 148, 205), + "fg_color": (64, 64, 64), + + "fg_bitmap": bytes([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0x7f, 0x00, 0xe0, 0xff, + 0x3f, 0x00, 0xc0, 0xff, 0x1f, 0x00, 0x80, 0xff, 0x0f, 0xfc, 0x03, 0xff, + 0x0f, 0xfe, 0x07, 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x07, 0xff, 0x0f, 0xfe, + 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, + 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, + 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, + 0x87, 0xff, 0x1f, 0xfe, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, + 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, 0x01, 0xf0, 0x00, 0xf8, + 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, + 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf0, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, + 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, + 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, + 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, + 0xff, 0xff, 0xff, 0xff + ]), + + "bg_bitmap": bytes([ + 0x00, 0xfe, 0x07, 0x00, 0x80, 0xff, 0x1f, 0x00, 0xc0, 0xff, 0x3f, 0x00, + 0xe0, 0xff, 0x7f, 0x00, 0xf0, 0xff, 0xff, 0x00, 0xf8, 0xff, 0xff, 0x01, + 0xf8, 0x03, 0xfc, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xfc, 0x01, 0xf8, 0x03, + 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, + 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, + 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, + 0xff, 0xff, 0xff, 0x0f + ]), +} diff --git a/lib/xcb.py b/lib/xcb.py index f1d7ebc..63b8619 100644 --- a/lib/xcb.py +++ b/lib/xcb.py @@ -332,10 +332,10 @@ def alloc_color_sync(conn, colormap, r, g, b): Raises ``XCBError`` on xcb errors and value errors for invalid values of r, g, b. """ - if r < 0 or b < 0 or g < 0: - raise ValueError - if r > 255 or b > 255 or g > 255: - raise ValueError + if r < 0 or g < 0 or b < 0: + raise ValueError("color component out of range") + if r > 255 or g > 255 or b > 255: + raise ValueError("color component our of range") r <<= 8; g <<= 8; b <<= 8 diff --git a/make_default_lock.py b/make_default_lock.py deleted file mode 100644 index 0c70ad8..0000000 --- a/make_default_lock.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/python3 - -import pickle - -fg_bitmap = bytes([ - 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xf8, 0xff, 0x7f, 0x00, 0xe0, 0xff, - 0x3f, 0x00, 0xc0, 0xff, 0x1f, 0x00, 0x80, 0xff, 0x0f, 0xfc, 0x03, 0xff, - 0x0f, 0xfe, 0x07, 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x07, 0xff, 0x0f, 0xfe, - 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, - 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, - 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, 0x87, 0xff, 0x1f, 0xfe, - 0x87, 0xff, 0x1f, 0xfe, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, - 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, 0x01, 0xf0, 0x00, 0xf8, - 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, - 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf0, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, - 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, - 0x01, 0x60, 0x00, 0xf8, 0x01, 0x60, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, - 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0xf8, - 0xff, 0xff, 0xff, 0xff -]) - -bg_bitmap = bytes([ - 0x00, 0xfe, 0x07, 0x00, 0x80, 0xff, 0x1f, 0x00, 0xc0, 0xff, 0x3f, 0x00, - 0xe0, 0xff, 0x7f, 0x00, 0xf0, 0xff, 0xff, 0x00, 0xf8, 0xff, 0xff, 0x01, - 0xf8, 0x03, 0xfc, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xfc, 0x01, 0xf8, 0x03, - 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, - 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, - 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, 0xfc, 0x00, 0xf0, 0x03, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0x0f, - 0xff, 0xff, 0xff, 0x0f -]) - -with open("lock.pickle", "wb") as f: - pickle.dump({ - "width": 28, - "height": 40, - "x_hot": 14, - "y_hot": 21, - "fg_bitmap": fg_bitmap, - "bg_bitmap": bg_bitmap, - "color_mode": "named", - "bg_color": "steelblue3", - "fg_color": "grey25" - }, f) diff --git a/make_empty_lock.py b/make_empty_lock.py deleted file mode 100644 index f92fffd..0000000 --- a/make_empty_lock.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/python3 - -import pickle - -fg_bitmap = bytes([0x00]) -bg_bitmap = bytes([0x00]) - -with open("lock.pickle", "wb") as f: - pickle.dump({ - "width": 1, - "height": 1, - "x_hot": 1, - "y_hot": 1, - "fg_bitmap": fg_bitmap, - "bg_bitmap": bg_bitmap, - "color_mode": "named", - "bg_color": "steelblue3", - "fg_color": "grey25" - }, f) diff --git a/pyxtrlock b/pyxtrlock index 0ef0986..2c9ff5f 100755 --- a/pyxtrlock +++ b/pyxtrlock @@ -4,15 +4,14 @@ import os import sys import time -import pickle import getpass from ctypes import byref, cast, sizeof from ctypes import POINTER, c_int, c_uint32, c_char -from xdg.BaseDirectory import xdg_config_home +from xdg.BaseDirectory import load_data_paths import simplepam as pam -import pyxtrlock +from pyxtrlock.cursor_file import load_cursor from pyxtrlock import panic try: import pyxtrlock.xcb as xcb @@ -33,18 +32,18 @@ if getpass.getuser() == 'root' and sys.argv[1:] != ['-f']: # load cursor data file try: - f_name = os.path.join(xdg_config_home, "pyxtrlock", "lock.pickle") - if os.path.exists(f_name): - f = open(f_name, "rb") + for directory in load_data_paths("pyxtrlock"): + f_name = os.path.join(directory, "cursor.json") + if os.path.exists(f_name): + with open(f_name, "r") as f: + cursor = load_cursor(f) + break else: - f = open(os.path.join(pyxtrlock.data_dir, "lock.pickle"), "rb") - cursor = pickle.load(f) + from pyxtrlock.default_cursor import DEFAULT_CURSOR as cursor except OSError as e: panic(e.strerror) -except pickle.UnpicklingError as e: - panic(e.args) -finally: - f.close() +except Exception as e: + panic(str(e)) display = X.create_window(None) conn = X.get_xcb_connection(display) @@ -86,20 +85,17 @@ csr_mask = xcb.image_create_pixmap_from_bitmap_data(conn, window, cursor["height"], 1, 0, 0, None) -if cursor["color_mode"] == "named": - csr_bg = xcb.alloc_named_color_sync(conn, screen.default_colormap, - cursor["bg_color"]) - csr_fg = xcb.alloc_named_color_sync(conn, screen.default_colormap, - cursor["fg_color"]) -elif cursor["color_mode"] == "rgb": +try: r, g, b = cursor["bg_color"] csr_bg = xcb.alloc_color_sync(conn, screen.default_colormap, r, g, b) r, g, b = cursor["fg_color"] csr_fg = xcb.alloc_color_sync(conn, screen.default_colormap, r, g, b) -else: - panic("Invalid color mode") +except ValueError as e: + panic("pyxtrlock: " + str(e)) +except xcb.XCBError as e: + panic("pyxtrlock: Could not allocate colors") try: cursor = xcb.create_cursor_sync(conn, csr_map, csr_mask, csr_fg, csr_bg, diff --git a/tools/README b/tools/README index d39cf21..ea293b2 100644 --- a/tools/README +++ b/tools/README @@ -1,19 +1,12 @@ make_lock.py ============ -PLEASE NOTE: make_lock.py requires python2 as the PIL is not packaged -for python3 on most distris. - -Therefore another tool – repickle.py – must be used to postprocess the -generated files. - usage: make_lock.py [-h] [--x-hit X_HIT] [--y-hit Y_HIT] [--fg-color FG_COLOR] [--bg-color BG_COLOR] [--output OUTPUT] [--debug] - bg_bitmap [fg_bitmap] + [bitmaps [bitmaps ...]] positional arguments: - bg_bitmap The single image or the 1-bit mask - fg_bitmap If given, the 1-bit foreground pixels + bitmaps the bitmaps to create the cursor from optional arguments: -h, --help show this help message and exit @@ -29,7 +22,7 @@ optional arguments: --bg-color BG_COLOR, -b BG_COLOR The background colour. --output OUTPUT, -o OUTPUT - The output file, by default stdout + The output file, by default install --debug Check for consistency and printthe bitmaps to the stdout @@ -41,8 +34,9 @@ various forms of transparency, that can be opened by python imaging). The recommended file type is PNG. There are several modes of operation which are guessed from the -supplied file: +supplied files: +*No image is given: create an empty locking cursor. *A single colour image with 2 colours and transparency is compiled to the appropriate cursor (transparency may either be an alpha threshold or single colour transparency). @@ -58,6 +52,11 @@ Additionally, the cursor hotspot can be given. If not specified, it is the center of the image (this is more or less irrelevant, as the all cursor events are blocked by pyxtrlock). +If no output file is given the resulting cursor is installed in the +appropriate location to be found by pyxtrlock (which is +$XDG_DATA_HOME/pyxtrlock/cursor.json or if XDG_DATA_HOME is not set +$HOME/.local/share/pyxtrlock/cursor.json). + Typical usage ------------- @@ -65,10 +64,7 @@ To create a cursor from a PNG with two colors (foreground and background) and transparent pixels and then install it for your user do the following: - $ ./make_lock.py lock.png -o lock.pickle - $ ./repickle lock.pickle - $ mkdir ~/.config/pyxtrlock/ - $ cp lock.pickle ~/.config/pyxtrlock + $ ./make_lock.py lock.png If the tool fails to grok an image file it may help to use ImageMagick to convert it to PNG: @@ -77,9 +73,8 @@ to convert it to PNG: Requirements ------------ -*Python 2.7 -*Python 3 for repickle.py -*python-imaging (PIL) +* Python 3 +* pillow (or PIL) Bugs ---- diff --git a/tools/make_lock.py b/tools/make_lock.py index 8bc73c5..77116b9 100755 --- a/tools/make_lock.py +++ b/tools/make_lock.py @@ -1,19 +1,20 @@ -#!/usr/bin/python2 - -from __future__ import division, print_function +#!/usr/bin/python3 import sys import argparse -import pickle import re from abc import ABCMeta, abstractmethod +import os + +from xdg import BaseDirectory + +from PIL import Image, ImageColor -import Image +from pyxtrlock.cursor_file import save_cursor ap = argparse.ArgumentParser() -ap.add_argument('bg_bitmap', help="The single image or the 1-bit mask") -ap.add_argument('fg_bitmap', nargs='?', - help="If given, the 1-bit foreground pixels") +ap.add_argument('bitmaps', nargs='*', + help="the bitmaps to create the cursor from") ap.add_argument('--x-hit', '-x', type=int, default=None, help="x-coordinate of the cursor hotspot") @@ -28,9 +29,9 @@ ap.add_argument('--bg-color', '-b', default=None, help="The background colour.") -ap.add_argument('--output', '-o', type=argparse.FileType('wb'), - default=sys.stdout, - help="The output file, by default stdout") +ap.add_argument('--output', '-o', type=argparse.FileType('w'), + default=None, + help="The output file [default: the data file location]") ap.add_argument('--debug', action='store_true', default=False, help="Check for consistency and print" "the bitmaps to stdout") @@ -155,12 +156,12 @@ def _register_subclass(cls, sub_class, marked): else: cls._register_recurse(sub_class, marked) -class ColorHandler(object): - __metaclass__ = ColorHandlerMeta +class ColorHandler(metaclass=ColorHandlerMeta): MODES = {} - def __new__(cls, PIL_image, **kwargs): - return super(ColorHandler, cls).__new__(cls.MODES[PIL_image.mode], PIL_image) + @classmethod + def make(cls, PIL_image, **kwargs): + return cls.MODES[PIL_image.mode](PIL_image, **kwargs) def __init__(self, PIL_image, thresh=127): self._image = PIL_image @@ -233,12 +234,9 @@ def __getitem__(self, item): class LockMaker(object): - RGB_TRIPLE_RE = \ - r'\s*rgb\s*\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)\s*' def __init__(self, args): self.args = args - self.color_mode = None self.width = None self.height = None @@ -246,11 +244,11 @@ def __init__(self, args): self._fg_filter = None self._bg_filter = None - self._bg_bitmap_raw = Image.open(args.bg_bitmap, "r") + self._bg_bitmap_raw = Image.open(args.bitmaps[0], "r") self._fg_bitmap_raw = None self.uni_image = False - if args.fg_bitmap is not None: - self._fg_bitmap_raw = Image.open(args.fg_bitmap, "r") + if len(args.bitmaps) > 1: + self._fg_bitmap_raw = Image.open(args.bitmaps[1], "r") else: self.uni_image = True @@ -322,7 +320,7 @@ def _guess_colors(self): mode = self._bg_bitmap_raw.mode info = self._bg_bitmap_raw.info - bg_color_handler = ColorHandler(self._bg_bitmap_raw) + bg_color_handler = ColorHandler.make(self._bg_bitmap_raw) tr_filter = bg_color_handler.make_transparency_filter() effective_colors = {} for color, num in bg_hist.items(): @@ -362,7 +360,7 @@ def _guess_colors(self): f, b = effective_colors image_fg = (f, f, f) image_bg = (b, b, b) - self.fg_filter = lambda x: f + self._fg_filter = lambda x: f self._bg_filter = lambda x: not tr_filter(x) elif mode == '1': @@ -396,25 +394,16 @@ def _guess_colors(self): if self.args.bg_color is None: print("Inconsistent color specification", file=sys.stderr) sys.exit(1) - self.fg_color = self._parse_color(self.args.fg_color) - self.bg_color = self._parse_color(self.args.bg_color) + self.fg_color = ImageColor.getrgb(self.args.fg_color) + self.bg_color = ImageColor.getrgb(self.args.bg_color) elif image_has_colors: - self._check_color_mode('rgb') self.fg_color = image_fg self.bg_color = image_bg else: if self.uni_image: self.stroke_border = True - self.fg_color = 'white' - self.bg_color = 'black' - self.color_mode = 'named' - - def _check_color_mode(self, color_mode): - if self.color_mode is None: - self.color_mode = color_mode - elif self.color_mode != color_mode: - print("Color mode mismatch", file=sys.stderr) - sys.exit(1) + self.fg_color = (255,255,255) + self.bg_color = (0,0,0) def _histogram(self, PIL_img): hist = {} @@ -467,51 +456,6 @@ def finish(i, j, in_img): in_img = action(i, j, 1, 0, in_img) finish(i, j, in_img) - def _parse_color(self, color_string): - """Parse a string representing a color the formats - * rgb(255, 127, 0) - * rgb(1.0, 0.5, 0.0) - * #f70 - * #ff7f00 - * named color - """ - if color_string.startswith('#'): - self._check_color_mode('rgb') - - if len(color_string) == 4: - return tuple(17*int(color_string[i], base=16) - for i in range(1,4)) - elif len(color_string) == 7: - return tuple(int(color_string[i:i+1], base=16) - for i in range(1,6,2)) - else: - print("Invalid color format", file=sys.stderr) - sys.exit(1) - else: - match = re.match(self.RGB_TRIPLE_RE, color_string) - if match is not None: - self._check_color_mode('rgb') - - try: - r, g, b = map(int, (match.group(i) for i in range(1, 4))) - except ValueError: - try: - r, g, b = map(lambda x: int(float(x)*255), - (match.group(i) for i in range(1, 4))) - except ValueError: - print("Invalid color format", file=sys.stderr) - sys.exit(1) - - # note: no check for negative values is required as - # the regex does not allow - - if r > 255 or g > 255 or b > 255: - print("Invalid color format", file=sys.stderr) - sys.exit(1) - return (r, g, b) - - self._check_color_mode('named') - return color_string - @property def fg_bitmap(self): return bytes(self._fg_bitmap.buffer) @@ -523,18 +467,39 @@ def bg_bitmap(self): args = ap.parse_args() -lock_maker = LockMaker(args) +if args.output is None: + args.output = open( + os.path.join(BaseDirectory.save_data_path("pyxtrlock"), + "cursor.json"), + "w" + ) +if len(args.bitmaps): + lock_maker = LockMaker(args) -with args.output as f: - pickle.dump({ + with args.output as f: + save_cursor({ "width": lock_maker.width, "height": lock_maker.height, "x_hot": lock_maker.x_hot, "y_hot": lock_maker.y_hot, "fg_bitmap": lock_maker.fg_bitmap, "bg_bitmap": lock_maker.bg_bitmap, - "color_mode": lock_maker.color_mode, "bg_color": lock_maker.bg_color, "fg_color": lock_maker.fg_color - }, f, protocol=2) + }, f) +else: + with args.output as f: + fg_bitmap = bytes([0x00]) + bg_bitmap = bytes([0x00]) + + save_cursor({ + "width": 1, + "height": 1, + "x_hot": 1, + "y_hot": 1, + "fg_bitmap": fg_bitmap, + "bg_bitmap": bg_bitmap, + "bg_color": (0,0,0), + "fg_color": (0,0,0) + }, f) From 9d0544b868af92a2294af2dd01936ab5b99bad88 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 20:07:45 +0100 Subject: [PATCH 18/26] fix the check for a returned NULL pointer --- pyxtrlock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyxtrlock b/pyxtrlock index 2c9ff5f..2e47a92 100755 --- a/pyxtrlock +++ b/pyxtrlock @@ -176,7 +176,7 @@ timeout = 0 goodwill = INITIALGOODWILL while True: with xcb.wait_for_event(conn) as event: - if event is None: + if not event: # this test should always be true, we have it here as an # extra precaution, so we do not kill pyxtrlock if there # still is a connection to the X server (although From 4000d34cdc4a75f1d983555dfcd3cc53db8d133f Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Mon, 4 Dec 2017 20:23:07 +0100 Subject: [PATCH 19/26] reorganize the package structure and switch to setuptools --- README.md | 7 ++++--- {tools => bin}/make_lock.py | 2 +- pyxtrlock => bin/pyxtrlock | 0 tools/README => doc/make_lock.txt | 0 {lib => pyxtrlock}/X.py | 0 {lib => pyxtrlock}/__init__.py | 0 {lib => pyxtrlock}/cursor_file.py | 0 {lib => pyxtrlock}/default_cursor.py | 0 {lib => pyxtrlock}/utils.py | 0 {lib => pyxtrlock}/xcb.py | 0 setup.py | 26 +++----------------------- tools/repickle.py | 16 ---------------- 12 files changed, 8 insertions(+), 43 deletions(-) rename {tools => bin}/make_lock.py (99%) rename pyxtrlock => bin/pyxtrlock (100%) rename tools/README => doc/make_lock.txt (100%) rename {lib => pyxtrlock}/X.py (100%) rename {lib => pyxtrlock}/__init__.py (100%) rename {lib => pyxtrlock}/cursor_file.py (100%) rename {lib => pyxtrlock}/default_cursor.py (100%) rename {lib => pyxtrlock}/utils.py (100%) rename {lib => pyxtrlock}/xcb.py (100%) delete mode 100755 tools/repickle.py diff --git a/README.md b/README.md index 73b8e60..16b01a2 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,10 @@ we recommend the ``xautolock`` tool. Just add something like to your X autostart file to lock the screen with ``pyxtrlock`` after 5 minutes idle time. ``xautolock`` has many other useful features, see its documentation. Most distributions provide an ``xautolock`` package -with a man page. An alternative to ``xautolock`` is the use of -[autolockd](https://github.com/zombofant/autolockd) which also -monitors for lid close and suspend events. +with a man page. + +An modern alternative to ``xautolock`` is ``xss-lock`` which +integrates with ``systemd-logind``. Staying up-to-date ------------------ diff --git a/tools/make_lock.py b/bin/make_lock.py similarity index 99% rename from tools/make_lock.py rename to bin/make_lock.py index 77116b9..e48853b 100755 --- a/tools/make_lock.py +++ b/bin/make_lock.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 import sys import argparse diff --git a/pyxtrlock b/bin/pyxtrlock similarity index 100% rename from pyxtrlock rename to bin/pyxtrlock diff --git a/tools/README b/doc/make_lock.txt similarity index 100% rename from tools/README rename to doc/make_lock.txt diff --git a/lib/X.py b/pyxtrlock/X.py similarity index 100% rename from lib/X.py rename to pyxtrlock/X.py diff --git a/lib/__init__.py b/pyxtrlock/__init__.py similarity index 100% rename from lib/__init__.py rename to pyxtrlock/__init__.py diff --git a/lib/cursor_file.py b/pyxtrlock/cursor_file.py similarity index 100% rename from lib/cursor_file.py rename to pyxtrlock/cursor_file.py diff --git a/lib/default_cursor.py b/pyxtrlock/default_cursor.py similarity index 100% rename from lib/default_cursor.py rename to pyxtrlock/default_cursor.py diff --git a/lib/utils.py b/pyxtrlock/utils.py similarity index 100% rename from lib/utils.py rename to pyxtrlock/utils.py diff --git a/lib/xcb.py b/pyxtrlock/xcb.py similarity index 100% rename from lib/xcb.py rename to pyxtrlock/xcb.py diff --git a/setup.py b/setup.py index 0905f53..d1b1eda 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,4 @@ -from distutils.core import setup -from distutils.command.install import install - -import os -import stat -import subprocess - -class my_install(install): - def run(self): - stat_make_lock = os.stat("make_default_lock.py") - try: - stat_lock = os.stat("lock.pickle") - except OSError: - stat_lock = None - if stat_lock is None \ - or stat_lock[stat.ST_MTIME] < stat_make_lock[stat.ST_MTIME]: - subprocess.call(["python3", "./make_default_lock.py"]) - super().run() +from setuptools import setup authors = ( 'Leon Weber , ' @@ -50,15 +33,12 @@ def run(self): ] setup(name='pyxtrlock', - version='0.2', + version='0.3alpha', author=authors, author_email='leon@leonweber.de', requires=['simplepam', 'pyxdg'], - package_dir={'pyxtrlock': 'lib'}, - data_files=[('share/pyxtrlock/', ['lock.pickle'])], packages=['pyxtrlock'], - scripts=['pyxtrlock'], - cmdclass={'install': my_install}, + scripts=['bin/pyxtrlock'], license='GPLv3+', url='https://zombofant.net/hacking/pyxtrlock', description=desc, diff --git a/tools/repickle.py b/tools/repickle.py deleted file mode 100755 index f154a28..0000000 --- a/tools/repickle.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python3 - -import pickle -import sys - -data = None -for arg in sys.argv[1:]: - with open(arg, "rb") as f: - # any single byte, 8-bit encoding will work here - # as long as it is consistent - data = pickle.load(f, encoding='latin1') - if data is not None: - data["fg_bitmap"] = bytes(data["fg_bitmap"], encoding='latin1') - data["bg_bitmap"] = bytes(data["bg_bitmap"], encoding='latin1') - with open(arg, "wb") as f: - pickle.dump(data, f) From 3b8c3b6f64a1fc3f3b448ea357c5b76915c9b6f3 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Tue, 5 Dec 2017 17:02:30 +0100 Subject: [PATCH 20/26] implement the xss-lock -l sleep inhibition protocol --- CHANGELOG | 3 +++ README.md | 12 +++++++++++- bin/pyxtrlock | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7195694..9003c36 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,9 @@ Release xxx: • Enhancement: Retry grabbing the keyboard as well as the mouse to prevent issues when starting pyxtrlock from a keyboard shortcut. Patch by Nils Ballmann. +• Enhancement: Support the XSS_SLEEP_LOCK_FD protocol implemented by + xss-lock with the option -l to inhibit standby/hibernate/... until + the the screen is locked. Release 0.2 : • Security: Check correctly for the result of the diff --git a/README.md b/README.md index 16b01a2..0c03433 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,17 @@ its documentation. Most distributions provide an ``xautolock`` package with a man page. An modern alternative to ``xautolock`` is ``xss-lock`` which -integrates with ``systemd-logind``. +integrates with ``systemd-logind`` and manages locking on idleness and +lid close events. ``pyxtrlock`` supports the ``XSS_SLEEP_LOCK_FD`` +protocol used by ``xss-lock -l`` to delay system standby until the +screen is locked, the recommended way to start ``pyxtrlock`` with +``xss-lock`` is: + + xss-lock -l -- pyxtrlock + +to explicitly lock the screen use: + + loginctl lock-session Staying up-to-date ------------------ diff --git a/bin/pyxtrlock b/bin/pyxtrlock index 2e47a92..e05fbd4 100755 --- a/bin/pyxtrlock +++ b/bin/pyxtrlock @@ -148,6 +148,18 @@ else: xcb.flush(conn) +# implement the XSS_SLEEP_LOCK_FD sleep delay protocol +xss_fd = os.getenv("XSS_SLEEP_LOCK_FD") +if xss_fd is not None: + try: + os.close(int(xss_fd)) + except OSError: + # ignore if the fd was invalid + pass + except ValueError: + # ignore if the variable did not contain an fd + pass + # Prepare X Input im = X.open_IM(display, None, None, None) if not im: From 14ea4305491258c1ab03987ecb16d63f6972212d Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Wed, 6 Dec 2017 13:12:32 +0100 Subject: [PATCH 21/26] fix MANIFEST.in --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 9612e95..92c56d9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,2 @@ include COPYING CHANGELOG README.md -include tools/*.py tools/README -include make_default_lock.py +include bin/make_lock.py doc/make_lock.txt From 01a747bd2c4a86ffe5e04dcca690a5413549b2e3 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Sun, 10 Dec 2017 14:29:42 +0100 Subject: [PATCH 22/26] make the cursor file checking more strict --- pyxtrlock/cursor_file.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pyxtrlock/cursor_file.py b/pyxtrlock/cursor_file.py index 0eb9a12..42fbcc4 100644 --- a/pyxtrlock/cursor_file.py +++ b/pyxtrlock/cursor_file.py @@ -7,11 +7,20 @@ COLOR_ATTRIBUTES = ["bg_color", "fg_color"] BINARY_ATTRIBUTES = ["fg_bitmap", "bg_bitmap"] +MAX_CURSOR_SIZE = 512 + +def _check_size(w, h, data): + pitch = (w + 7) // 8 + size = pitch * h + if len(data) < size: + raise ValueError("invalid cursor file: bitmap data is too small") + def save_cursor(cursor, f): res = {} for attr in INTEGER_ATTRIBUTES: - if not isinstance(cursor[attr], int): + if not (isinstance(cursor[attr], int) and + 0 <= cursor[attr] <= MAX_CURSOR_SIZE): raise ValueError("{} must be integer".format(attr)) res[attr] = cursor[attr] @@ -22,8 +31,12 @@ def save_cursor(cursor, f): raise ValueError("invalid color specification") res[attr] = cursor[attr] + _check_size(cursor["width"], cursor["height"], cursor["fg_bitmap"]) + _check_size(cursor["width"], cursor["height"], cursor["bg_bitmap"]) + for attr in BINARY_ATTRIBUTES: res[attr] = base64.b64encode(cursor[attr]).decode("ascii") + json.dump(res, f, sort_keys=True) @@ -32,7 +45,8 @@ def load_cursor(f): res = {} for attr in INTEGER_ATTRIBUTES: - if not isinstance(cursor[attr], int): + if not (isinstance(cursor[attr], int) and + 0 <= cursor[attr] <= MAX_CURSOR_SIZE): raise ValueError("{} must be integer".format(attr)) res[attr] = cursor[attr] @@ -46,4 +60,7 @@ def load_cursor(f): for attr in BINARY_ATTRIBUTES: res[attr] = base64.b64decode(cursor[attr]) + _check_size(res["width"], res["height"], res["fg_bitmap"]) + _check_size(res["width"], res["height"], res["bg_bitmap"]) + return res From 020eacb81346ac0cb88e018757c55b73d060092a Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Sun, 10 Dec 2017 14:38:13 +0100 Subject: [PATCH 23/26] update the readme with respect to the padlock configuration --- README.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0c03433..c5e67a3 100644 --- a/README.md +++ b/README.md @@ -96,29 +96,31 @@ Although this is not a bug, please note that pyxtrlock does not prevent a user from switching to a virtual terminal, so be advised to always log out from your terminals. -The lenght of the password is limited to 100 KiB to prevent memory +The length of the password is limited to 100 KiB to prevent memory exhaustion attacks. This limit can only be adapted in the source code. +The width and height of the cursor bitmaps is limited to 512 pixels +(primarily to protect the user from faulty cursor files). This limit +can be only adapted in the source code. + Please report any new bugs you may find to our [Github issue tracker](https://github.com/leonnnn/pyxtrlock/issues). Configuration ------------- -The padlock icon can be changed. It is stored as a -[pickle](http://docs.python.org/3/library/pickle.html) of a -dictionary, and the ``tools`` directory contains a tool for generating -cursors from image files. - -The default cursor file is placed at -``PREFIX/share/pyxtrlock/lock.pickle`` while the cursor file at -``~/.config/pyxtrlock/lock.pickle`` takes precedence if present. - -*PLEASE NOTE:* The ``pickle`` file format is not designed to be -resistant against maliciously crafted files. Therfore do not open -``pickle`` files from untrusted sources as they may compromise your -system. The default padlock file is created on install (by -``make_default_lock.py``). In case you do not want to see any kind -of cursor, an empty one can be created using ``make_empty_lock.py``. +The padlock icon can be changed. While the default lock is stored in +the source code, an alternative lock can be stored in one of the the +xdg data paths for pyxtrlock. + +The user configured lock is stored as a json file containing the +necessary information. ``bin/make_lock.py`` is a tool for generating +cursors from image files. See ``doc/make_lock.txt`` for the full +documentation of the tool. + +Note, that even though loading json does not allow arbitrary code +execution and the cursor data is checked for consistency, cursor files +should be created on your machine and should not be installed from +untrusted sources. Requirements ------------ From b59ac2cc4301266b482d67e927e71780e8e777da Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Sun, 10 Dec 2017 14:52:41 +0100 Subject: [PATCH 24/26] simplify panic reporting * panic now takes multiple string arguments that are concatenated * the common "pyxtrlock:" component is provided by panic and must no longer be given manually for every panic --- bin/pyxtrlock | 22 +++++++++++----------- pyxtrlock/__init__.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bin/pyxtrlock b/bin/pyxtrlock index e05fbd4..53ee44a 100755 --- a/bin/pyxtrlock +++ b/bin/pyxtrlock @@ -25,7 +25,7 @@ except ImportError as err: if getpass.getuser() == 'root' and sys.argv[1:] != ['-f']: msg = ( - "pyxtrlock: refusing to run as root. Use -f to force. Warning: " + "refusing to run as root. Use -f to force. Warning: " "Your PAM configuration may deny unlocking as root." ) panic(msg) @@ -41,15 +41,15 @@ try: else: from pyxtrlock.default_cursor import DEFAULT_CURSOR as cursor except OSError as e: - panic(e.strerror) + panic("error reading cursor:", e.strerror) except Exception as e: - panic(str(e)) + panic("error reading cursor:", str(e)) display = X.create_window(None) conn = X.get_xcb_connection(display) if not display: - panic("pyxtrlock: Could not connect to X server") + panic("Could not connect to X server") screen_num = c_int() @@ -93,15 +93,15 @@ try: csr_fg = xcb.alloc_color_sync(conn, screen.default_colormap, r, g, b) except ValueError as e: - panic("pyxtrlock: " + str(e)) + panic(str(e)) except xcb.XCBError as e: - panic("pyxtrlock: Could not allocate colors") + panic("Could not allocate colors") try: cursor = xcb.create_cursor_sync(conn, csr_map, csr_mask, csr_fg, csr_bg, cursor["x_hot"], cursor["y_hot"]) except xcb.XCBError as e: - panic("pyxtrlock: Could not create cursor") + panic("Could not create cursor") # map window xcb.map_window(conn, window) @@ -126,7 +126,7 @@ for i in range(100): except xcb.XCBError as e: time.sleep(0.01) else: - panic("pyxtrlock: Could not grab keyboard") + panic("Could not grab keyboard") # Grab pointer for i in range(100): @@ -144,7 +144,7 @@ for i in range(100): except xcb.XCBError as e: time.sleep(0.01) else: - panic("pyxtrlock: Could not grab pointing device") + panic("Could not grab pointing device") xcb.flush(conn) @@ -163,12 +163,12 @@ if xss_fd is not None: # Prepare X Input im = X.open_IM(display, None, None, None) if not im: - panic("pyxtrlock: Could not open Input Method") + panic("Could not open Input Method") ic = X.create_IC(im, X.N_INPUT_STYLE, X.IM_PRE_EDIT_NOTHING | X.IM_STATUS_NOTHING, None) if not ic: - panic("pyxtrlock: Could not open Input Context") + panic("Could not open Input Context") X.set_ic_focus(ic) diff --git a/pyxtrlock/__init__.py b/pyxtrlock/__init__.py index cff5ccf..7dd8e14 100644 --- a/pyxtrlock/__init__.py +++ b/pyxtrlock/__init__.py @@ -3,7 +3,7 @@ import os -def panic(message, exit_code=1): +def panic(*message_parts, exit_code=1): """Print an error message to stderr and exit""" - print(message, file=sys.stderr) + print("pyxtrlock:", *message_parts, file=sys.stderr) sys.exit(exit_code) From f50ea67b0ed04145ff38d83a96c4057c2d1cb4be Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Sun, 10 Dec 2017 16:00:46 +0100 Subject: [PATCH 25/26] fix some style issues --- bin/make_lock.py | 40 +++++++++++++++++++--------------------- bin/pyxtrlock | 8 +++++--- pyxtrlock/__init__.py | 1 - pyxtrlock/cursor_file.py | 3 ++- pyxtrlock/utils.py | 1 + pyxtrlock/xcb.py | 20 +++++++++++++++++++- 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/bin/make_lock.py b/bin/make_lock.py index e48853b..2907551 100755 --- a/bin/make_lock.py +++ b/bin/make_lock.py @@ -2,7 +2,6 @@ import sys import argparse -import re from abc import ABCMeta, abstractmethod import os @@ -36,6 +35,7 @@ help="Check for consistency and print" "the bitmaps to stdout") + class Bitmap(object): def __init__(self, width, height, buf=None): self.width = width @@ -53,10 +53,10 @@ def __str__(self): lines = [] for i in range(self.height): lines.append(''.join( - 'o' if bit else '.' - for byte in self.buffer[i*self.pitch:(i+1)*self.pitch] - for bit in ((byte >> j) & 0x1 for j in range(8)) - )[:self.width]) + 'o' if bit else '.' + for byte in self.buffer[i*self.pitch:(i+1)*self.pitch] + for bit in ((byte >> j) & 0x1 for j in range(8)) + )[:self.width]) return '\n'.join(lines) def wipe(self): @@ -136,6 +136,7 @@ def __xor__(self, other): cpy |= other return cpy + class ColorHandlerMeta(ABCMeta): def __new__(cls, name, bases, dict): @@ -156,6 +157,7 @@ def _register_subclass(cls, sub_class, marked): else: cls._register_recurse(sub_class, marked) + class ColorHandler(metaclass=ColorHandlerMeta): MODES = {} @@ -205,6 +207,7 @@ def make_transparency_filter(self): else: return lambda x: False + class PColorHander(ColorHandler): MODE = ['P'] @@ -215,6 +218,7 @@ def make_transparency_filter(self): else: return lambda x: False + class OneColorHandler(ColorHandler): MODE = ['1'] @@ -230,7 +234,7 @@ def __init__(self, palette): self._palette = bytearray(palette.palette) def __getitem__(self, item): - return tuple(self._palette[i] for i in range(3*item, 3*item+3)) + return tuple(self._palette[i] for i in range(3*item, 3*item + 3)) class LockMaker(object): @@ -252,7 +256,6 @@ def __init__(self, args): else: self.uni_image = True - self._guess_size() self._guess_hotspot() self._guess_colors() @@ -312,13 +315,10 @@ def _guess_hotspot(self): def _guess_colors(self): image_has_colors = False - bg_hist = self._histogram(self._bg_bitmap_raw) - if not self.uni_image: - fg_hist = self._histogram(self._fg_bitmap_raw) if self.uni_image: + bg_hist = self._histogram(self._bg_bitmap_raw) mode = self._bg_bitmap_raw.mode - info = self._bg_bitmap_raw.info bg_color_handler = ColorHandler.make(self._bg_bitmap_raw) tr_filter = bg_color_handler.make_transparency_filter() @@ -371,7 +371,6 @@ def _guess_colors(self): sys.exit(1) else: mode = self._bg_bitmap_raw.mode - info = self._bg_bitmap_raw.info mode_fg = self._fg_bitmap_raw.mode if mode_fg != mode: @@ -402,8 +401,8 @@ def _guess_colors(self): else: if self.uni_image: self.stroke_border = True - self.fg_color = (255,255,255) - self.bg_color = (0,0,0) + self.fg_color = (255, 255, 255) + self.bg_color = (0, 0, 0) def _histogram(self, PIL_img): hist = {} @@ -424,13 +423,13 @@ def _stroke(self, PIL_img, bitmap, filter, wipe=True): for i in range(self.width): for j in range(self.height): if filter(data[i, j]): - bitmap[i,j] = 1 + bitmap[i, j] = 1 def _stroke_border(self): def action(i, j, di, dj, in_img): - if self._bg_bitmap[i,j]: + if self._bg_bitmap[i, j]: if not in_img: - self._fg_bitmap[i,j] = 1 + self._fg_bitmap[i, j] = 1 return True else: if in_img: @@ -441,13 +440,12 @@ def finish(i, j, in_img): if in_img: self._fg_bitmap[i, j] = 1 - # stroke vertically for i in range(self.width): in_img = False for j in range(self.height): in_img = action(i, j, 0, 1, in_img) - finish(i, j, in_img) + finish(i, j, in_img) # stroke horizontally for j in range(self.height): @@ -500,6 +498,6 @@ def bg_bitmap(self): "y_hot": 1, "fg_bitmap": fg_bitmap, "bg_bitmap": bg_bitmap, - "bg_color": (0,0,0), - "fg_color": (0,0,0) + "bg_color": (0, 0, 0), + "fg_color": (0, 0, 0) }, f) diff --git a/bin/pyxtrlock b/bin/pyxtrlock index 53ee44a..d2c50bd 100755 --- a/bin/pyxtrlock +++ b/bin/pyxtrlock @@ -5,7 +5,7 @@ import os import sys import time import getpass -from ctypes import byref, cast, sizeof +from ctypes import byref, cast from ctypes import POINTER, c_int, c_uint32, c_char from xdg.BaseDirectory import load_data_paths @@ -116,8 +116,10 @@ xcb.map_window(conn, window) for i in range(100): try: - status = xcb.grab_keyboard_sync(conn, 0, window, xcb.CURRENT_TIME, - xcb.GRAB_MODE_ASYNC, xcb.GRAB_MODE_ASYNC) + status = xcb.grab_keyboard_sync(conn, 0, window, + xcb.CURRENT_TIME, + xcb.GRAB_MODE_ASYNC, + xcb.GRAB_MODE_ASYNC) if status == xcb.GrabSuccess: break diff --git a/pyxtrlock/__init__.py b/pyxtrlock/__init__.py index 7dd8e14..eb42235 100644 --- a/pyxtrlock/__init__.py +++ b/pyxtrlock/__init__.py @@ -1,6 +1,5 @@ import sys -import os def panic(*message_parts, exit_code=1): diff --git a/pyxtrlock/cursor_file.py b/pyxtrlock/cursor_file.py index 42fbcc4..afe5aac 100644 --- a/pyxtrlock/cursor_file.py +++ b/pyxtrlock/cursor_file.py @@ -9,6 +9,7 @@ MAX_CURSOR_SIZE = 512 + def _check_size(w, h, data): pitch = (w + 7) // 8 size = pitch * h @@ -45,7 +46,7 @@ def load_cursor(f): res = {} for attr in INTEGER_ATTRIBUTES: - if not (isinstance(cursor[attr], int) and + if not (isinstance(cursor[attr], int) and 0 <= cursor[attr] <= MAX_CURSOR_SIZE): raise ValueError("{} must be integer".format(attr)) res[attr] = cursor[attr] diff --git a/pyxtrlock/utils.py b/pyxtrlock/utils.py index a352a09..598d98c 100644 --- a/pyxtrlock/utils.py +++ b/pyxtrlock/utils.py @@ -1,6 +1,7 @@ from ctypes import cdll from ctypes.util import find_library + def check_and_load_library(libname): handle = find_library(libname) if handle is None: diff --git a/pyxtrlock/xcb.py b/pyxtrlock/xcb.py index 63b8619..770a402 100644 --- a/pyxtrlock/xcb.py +++ b/pyxtrlock/xcb.py @@ -1,6 +1,7 @@ from ctypes import * from pyxtrlock.utils import check_and_load_library + class XCBError(Exception): """Raised on XCBErrors. @@ -86,6 +87,7 @@ class Cookie(Structure): ("sequence", c_uint) ] + VoidCookie = Cookie AllocNamedColorCookie = Cookie AllocColorCookie = Cookie @@ -117,6 +119,7 @@ class AllocNamedColorReply(Structure): ("visual_blue", c_uint16) ] + class AllocColorReply(Structure): _fields_ = [ ("response_type", c_uint8), @@ -167,6 +170,7 @@ def __str__(self): ','.join(str(getattr(self, field)) for field, _ in self._fields_)) + class GenericEvent(Structure): _fields_ = [ ("response_type", c_uint8), @@ -176,9 +180,11 @@ class GenericEvent(Structure): ("full_sequence", c_uint32) ] + Keycode = c_uint8 Timestamp = c_uint32 + class KeyPressEvent(Structure): _fields_ = [ ("response_type", c_uint8), @@ -198,7 +204,6 @@ class KeyPressEvent(Structure): ] - GrabKeyboardReply = GrabReply GrabPointerReply = GrabReply @@ -321,6 +326,7 @@ def alloc_named_color_sync(conn, colormap, color_string): free(res) return ret + def alloc_color_sync(conn, colormap, r, g, b): """Synchronously allocate a color @@ -349,6 +355,7 @@ def alloc_color_sync(conn, colormap, r, g, b): free(res) return ret + request_check = libxcb.xcb_request_check request_check.argtypes = [POINTER(Connection), VoidCookie] request_check.restype = POINTER(GenericError) @@ -387,14 +394,17 @@ def create_cursor_sync(conn, source, mask, fg, bg, x, y): return cursor + map_window = libxcb.xcb_map_window map_window.argtypes = [POINTER(Connection), Window] map_window.restype = VoidCookie + flush = libxcb.xcb_flush flush.argtypes = [POINTER(Connection)] flush.restype = c_int + grab_keyboard = libxcb.xcb_grab_keyboard grab_keyboard.argtypes = [ POINTER(Connection), # connection @@ -406,6 +416,7 @@ def create_cursor_sync(conn, source, mask, fg, bg, x, y): ] grab_keyboard.restype = GrabKeyboardCookie + grab_keyboard_reply = libxcb.xcb_grab_keyboard_reply grab_keyboard_reply.argtypes = [ POINTER(Connection), # connection @@ -459,6 +470,7 @@ def grab_keyboard_sync(conn, owner_events, grab_window, time, ptr_mode, ] grab_pointer_reply.restype = POINTER(GrabPointerReply) + # constants to interpret grab results GrabSuccess = 0 AlreadyGrabbed = 1 @@ -466,6 +478,7 @@ def grab_keyboard_sync(conn, owner_events, grab_window, time, ptr_mode, GrabNotViewable = 3 GrabFrozen = 4 + def grab_pointer_sync(conn, owner_events, window, event_mask, ptr_mode, kbd_mode, confine_to, cursor, timestamp): """ @@ -486,14 +499,17 @@ def grab_pointer_sync(conn, owner_events, window, event_mask, ptr_mode, free(ptr_grab) return status + wait_for_event_ = libxcb.xcb_wait_for_event wait_for_event_.argtypes = [POINTER(Connection)] wait_for_event_.restype = POINTER(GenericEvent) + free = libc.free free.argtypes = [c_void_p] free.restype = None + class FreeWrapper(object): def __init__(self, pointer): @@ -509,12 +525,14 @@ def __exit__(self, etype, evalue, traceback): def wait_for_event(conn): return FreeWrapper(wait_for_event_(conn)) + connection_has_error = libxcb.xcb_connection_has_error connection_has_error.restype = c_int connection_has_error.argtypes = [ POINTER(Connection), ] + # xcb_image image_create_pixmap_from_bitmap_data = \ libxcb_image.xcb_create_pixmap_from_bitmap_data From f8c11a93770225eb6079c83a60b6a00563e383a6 Mon Sep 17 00:00:00 2001 From: Sebastian Riese Date: Tue, 24 Apr 2018 20:08:42 +0200 Subject: [PATCH 26/26] bump version number to 0.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d1b1eda..5663684 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ ] setup(name='pyxtrlock', - version='0.3alpha', + version='0.3', author=authors, author_email='leon@leonweber.de', requires=['simplepam', 'pyxdg'],