1# This file is part of ReText 2# Copyright: 2015-2017 Dmitry Shachnev 3# 4# This program is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 2 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17# This is implementation of XSettings specification, described at 18# <http://standards.freedesktop.org/xsettings-spec/xsettings-spec-0.5.html> 19 20import ctypes 21import ctypes.util 22import struct 23 24class _xcb_reply_t(ctypes.Structure): 25 # this can be used instead of xcb_intern_atom_reply_t, 26 # xcb_get_selection_owner_reply_t, etc 27 _fields_ = [('response_type', ctypes.c_uint8), 28 ('pad0', ctypes.c_uint8), 29 ('sequence', ctypes.c_uint16), 30 ('length', ctypes.c_uint32), 31 ('payload', ctypes.c_uint32)] 32 33class _xcb_cookie_t(ctypes.Structure): 34 # this can be used instead of xcb_intern_atom_cookie_t, 35 # xcb_get_selection_owner_cookie_t, etc 36 _fields_ = [('sequence', ctypes.c_uint)] 37 38_xcb_error_messages = [ 39 None, 40 'XCB error: socket, pipe and other stream error', 41 'XCB connection closed: extension unsupported', 42 'XCB connection closed: insufficient memory', 43 'XCB connection closed: request length exceeded', 44 'XCB connection closed: DISPLAY parse error', 45 'XCB connection closed: invalid screen' 46] 47 48class XSettingsError(RuntimeError): 49 pass 50 51class XSettingsParseError(XSettingsError): 52 pass 53 54def get_raw_xsettings(display=0): 55 # initialize the libraries 56 xcb_library_name = ctypes.util.find_library('xcb') 57 if xcb_library_name is None: 58 raise XSettingsError('Xcb library not found') 59 xcb = ctypes.CDLL(xcb_library_name) 60 61 c_library_name = ctypes.util.find_library('c') 62 if c_library_name is None: 63 raise XSettingsError('C library not found') 64 c = ctypes.CDLL(c_library_name) 65 66 # set some args and return types 67 c.free.argtypes = [ctypes.c_void_p] 68 c.free.restype = None 69 xcb.xcb_connect.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_int)] 70 xcb.xcb_connect.restype = ctypes.c_void_p 71 xcb.xcb_connection_has_error.argtypes = [ctypes.c_void_p] 72 xcb.xcb_connection_has_error.restype = ctypes.c_int 73 xcb.xcb_disconnect.argtypes = [ctypes.c_void_p] 74 xcb.xcb_disconnect.restype = None 75 xcb.xcb_intern_atom.argtypes = [ctypes.c_void_p, ctypes.c_uint8, ctypes.c_uint16, ctypes.c_char_p] 76 xcb.xcb_intern_atom.restype = _xcb_cookie_t 77 xcb.xcb_intern_atom_reply.argtypes = [ctypes.c_void_p, _xcb_cookie_t, ctypes.c_void_p] 78 xcb.xcb_intern_atom_reply.restype = ctypes.POINTER(_xcb_reply_t) 79 xcb.xcb_get_selection_owner.argtypes = [ctypes.c_void_p, ctypes.c_uint32] 80 xcb.xcb_get_selection_owner.restype = _xcb_cookie_t 81 xcb.xcb_get_selection_owner_reply.argtypes = [ctypes.c_void_p, _xcb_cookie_t, ctypes.c_void_p] 82 xcb.xcb_get_selection_owner_reply.restype = ctypes.POINTER(_xcb_reply_t) 83 xcb.xcb_get_property.argtypes = [ctypes.c_void_p, ctypes.c_uint8, ctypes.c_uint32, ctypes.c_uint32, 84 ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32] 85 xcb.xcb_get_property.restype = _xcb_cookie_t 86 xcb.xcb_get_property_reply.argtypes = [ctypes.c_void_p, _xcb_cookie_t, ctypes.c_void_p] 87 xcb.xcb_get_property_reply.restype = ctypes.c_void_p 88 xcb.xcb_get_property_value_length.argtypes = [ctypes.c_void_p] 89 xcb.xcb_get_property_value_length.restype = ctypes.c_int 90 xcb.xcb_get_property_value.argtypes = [ctypes.c_void_p] 91 xcb.xcb_get_property_value.restype = ctypes.c_void_p 92 93 # open the connection 94 connection = xcb.xcb_connect(None, None) 95 error = xcb.xcb_connection_has_error(connection) 96 if error: 97 raise XSettingsError(_xcb_error_messages[error]) 98 99 # get selection atom cookie 100 buffer = ('_XSETTINGS_S%d' % display).encode() 101 cookie = xcb.xcb_intern_atom(connection, 0, len(buffer), buffer) 102 103 # get selection atom reply 104 reply = xcb.xcb_intern_atom_reply(connection, cookie, None) 105 selection_atom = reply.contents.payload 106 c.free(reply) 107 108 # get selection owner cookie 109 cookie = xcb.xcb_get_selection_owner(connection, selection_atom) 110 111 # get selection owner reply 112 reply = xcb.xcb_get_selection_owner_reply(connection, cookie, None) 113 window = reply.contents.payload 114 c.free(reply) 115 116 # get settings atom cookie 117 buffer = b'_XSETTINGS_SETTINGS' 118 cookie = xcb.xcb_intern_atom(connection, 0, len(buffer), buffer) 119 120 # get settings atom reply 121 reply = xcb.xcb_intern_atom_reply(connection, cookie, None) 122 settings_atom = reply.contents.payload 123 c.free(reply) 124 125 # get property cookie 126 cookie = xcb.xcb_get_property(connection, 0, window, settings_atom, 0, 0, 0x2000) 127 128 # get property reply 129 reply = xcb.xcb_get_property_reply(connection, cookie, None) 130 if reply is not None: 131 length = xcb.xcb_get_property_value_length(reply) 132 pointer = xcb.xcb_get_property_value(reply) if length else None 133 result = ctypes.string_at(pointer, length) 134 c.free(reply) 135 136 # close the connection 137 xcb.xcb_disconnect(connection) 138 139 # handle possible errors 140 if reply is None or not length: 141 raise XSettingsError('XSettings not available') 142 143 return result 144 145def parse_xsettings(raw_xsettings): 146 if len(raw_xsettings) < 12: 147 raise XSettingsParseError('length < 12') 148 149 if raw_xsettings[0] not in (0, 1): 150 raise XSettingsParseError('wrong order byte: %d' % raw_xsettings[0]) 151 byte_order = '<>'[raw_xsettings[0]] 152 settings_count = struct.unpack(byte_order + 'I', raw_xsettings[8:12])[0] 153 154 TypeInteger, TypeString, TypeColor = range(3) 155 result = {} 156 157 raw_xsettings = raw_xsettings[12:] 158 offset = 0 159 for i in range(settings_count): 160 setting_type = raw_xsettings[offset] 161 offset += 2 162 name_length = struct.unpack(byte_order + 'H', raw_xsettings[offset:offset + 2])[0] 163 offset += 2 164 name = raw_xsettings[offset:offset + name_length] 165 offset += name_length 166 if offset & 3: 167 offset += 4 - (offset & 3) 168 offset += 4 # skip last-change-serial 169 170 if setting_type == TypeInteger: 171 value = struct.unpack(byte_order + 'I', raw_xsettings[offset:offset + 4])[0] 172 offset += 4 173 elif setting_type == TypeString: 174 value_length = struct.unpack(byte_order + 'I', raw_xsettings[offset:offset + 4])[0] 175 offset += 4 176 value = raw_xsettings[offset:offset + value_length] 177 offset += value_length 178 if offset & 3: 179 offset += 4 - (offset & 3) 180 elif setting_type == TypeColor: 181 value = struct.unpack(byte_order + 'HHHH', raw_xsettings[offset:offset + 8]) 182 offset += 8 183 else: 184 raise XSettingsParseError('Wrong setting type: %d' % setting_type) 185 result[name] = value 186 return result 187 188def get_xsettings(display=0): 189 raw_xsettings = get_raw_xsettings(display) 190 return parse_xsettings(raw_xsettings) 191