#!/usr/bin/env python # -*- coding: utf-8 -*- """OSD Neo2 ======== On screen display for learning the keyboard layout Neo2 Copyright (c) 2009-2010 Martin Zuther (http://www.mzuther.de/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Thank you for using free software! """ # Here follows a plea in German to keep the comments in English so # that you may understand them, dear visitor ... # # Meine Kommentare in den Quellcodes sind absichtlich auf Englisch # gehalten, damit Leute, die im Internet nach Lösungen suchen, den # Code nachvollziehen können. Daher bitte ich darum, zusätzliche # Kommentare ebenfalls auf Englisch zu schreiben. Vielen Dank! import ctypes import ctypes.util import gettext import os import types # initialise localisation settings module_path = os.path.dirname(os.path.realpath(__file__)) gettext.bindtextdomain('OSDneo2', os.path.join(module_path, 'po/')) gettext.textdomain('OSDneo2') _ = gettext.lgettext class SimpleXkbWrapper: """ Far from complete wrapper for the "X Keyboard Extension" (well, to be honest, it just wraps what I need using Python's "ctypes" library ). """ # set this to true to get lots of debugging information (and # considerably slow things down) DEBUG_XKB = False # "C defines" from file /usr/include/X11/extensions/XKB.h (Ubuntu 9.04): # $XFree86: xc/include/extensions/XKB.h,v 1.5tsi Exp $ # # XkbUseCoreKbd is used to specify the core keyboard without having to # look up its X input extension identifier. XkbUseCoreKbd = 0x0100 # "C defines" from file /usr/include/X11/XKBlib.h (Ubuntu 9.04): # $XFree86: xc/lib/X11/XKBlib.h,v 3.5 2003/04/17 02:06:31 dawes Exp $ # # # XkbOpenDisplay error codes XkbOD_Success = 0 XkbOD_BadLibraryVersion = 1 XkbOD_ConnectionRefused = 2 XkbOD_NonXkbServer = 3 XkbOD_BadServerVersion = 4 # "C typedef" from file /usr/include/X11/extensions/XKBstr.h (Ubuntu 9.04): # $Xorg: XKBstr.h,v 1.3 2000/08/18 04:05:45 coskrey Exp $ # # Common data structures and access macros # # typedef struct _XkbStateRec { # unsigned char group; # unsigned char locked_group; # unsigned short base_group; # unsigned short latched_group; # unsigned char mods; # unsigned char base_mods; # unsigned char latched_mods; # unsigned char locked_mods; # unsigned char compat_state; # unsigned char grab_mods; # unsigned char compat_grab_mods; # unsigned char lookup_mods; # unsigned char compat_lookup_mods; # unsigned short ptr_buttons; # } XkbStateRec,*XkbStatePtr; class XkbStateRec(ctypes.Structure): _fields_ = [ ('group', ctypes.c_ubyte), \ ('locked_group', ctypes.c_ubyte), \ ('base_group', ctypes.c_ushort), \ ('latched_group', ctypes.c_ushort), \ ('mods', ctypes.c_ubyte), \ ('base_mods', ctypes.c_ubyte), \ ('latched_mods', ctypes.c_ubyte), \ ('locked_mods', ctypes.c_ubyte), \ ('compat_state', ctypes.c_ubyte), \ ('grab_mods', ctypes.c_ubyte), \ ('compat_grab_mods', ctypes.c_ubyte), \ ('lookup_mods', ctypes.c_ubyte), \ ('compat_lookup_mods', ctypes.c_ubyte), \ ('ptr_buttons', ctypes.c_ushort) \ ] # "C defines" from file /usr/include/X11/X.h (Ubuntu 9.04): # $XFree86: xc/include/X.h,v 1.6 2003/07/09 15:27:28 tsi Exp $ # # Key masks. Used as modifiers to GrabButton and GrabKey, results of # QueryPointer, state in various key-, mouse-, and button-related # events. ShiftMask = 1 LockMask = 2 ControlMask = 4 Mod1Mask = 8 Mod2Mask = 16 Mod3Mask = 32 Mod4Mask = 64 Mod5Mask = 128 def __init__(self): # dynamically link to "X Keyboard Extension" library while at # the same time checking which library to use xkbd_library_location = ctypes.util.find_library('Xxf86misc') if not xkbd_library_location: xkbd_library_location = ctypes.util.find_library('X11') xkbd_library = ctypes.CDLL(xkbd_library_location) # print debugging information if requested if self.DEBUG_XKB: print print ' %s' % xkbd_library # define "ctypes" prototype for the function # # Display *XkbOpenDisplay(display_name, event_rtrn, error_rtrn, # major_in_out, minor_in_out, reason_rtrn) # # char * display_name; # int * event_rtrn; # int * error_rtrn; # int * major_in_out; # int * minor_in_out; # int * reason_rtrn; paramflags_xkbopendisplay = \ (1, 'display_name'), \ (2, 'event_rtrn'), \ (2, 'error_rtrn'), \ (3, 'major_in_out'), \ (3, 'minor_in_out'), \ (2, 'reason_rtrn') prototype_xkbopendisplay = ctypes.CFUNCTYPE( \ ctypes.c_uint, \ ctypes.c_char_p, \ ctypes.POINTER(ctypes.c_int), \ ctypes.POINTER(ctypes.c_int), \ ctypes.POINTER(ctypes.c_int), \ ctypes.POINTER(ctypes.c_int), \ ctypes.POINTER(ctypes.c_int) \ ) # set-up function (low-level) self.__XkbOpenDisplay__ = prototype_xkbopendisplay( \ ('XkbOpenDisplay', xkbd_library), \ paramflags_xkbopendisplay \ ) # define error handler def errcheck_xkbopendisplay(result, func, args): # print debugging information if requested if self.DEBUG_XKB: print print ' [XkbOpenDisplay]' print ' Display: %#010x' % result print ' display_name: %s' % args[0].value print ' event_rtrn: %d' % args[1].value print ' error_rtrn: %d' % args[2].value print ' major_in_out: %d' % args[3].value print ' minor_in_out: %d' % args[4].value print ' reason_rt: %d' % args[5].value # function didn't return display handle, so let's see why # not if result == 0: # values were taken from file /usr/include/X11/XKBlib.h (Ubuntu 9.04): # $XFree86: xc/lib/X11/XKBlib.h,v 3.5 2003/04/17 02:06:31 dawes Exp $ # error_id = args[5].value if error_id == self.XkbOD_Success: error_name = 'XkbOD_Success' elif error_id == self.XkbOD_BadLibraryVersion: error_name = 'XkbOD_BadLibraryVersion' elif error_id == self.XkbOD_ConnectionRefused: error_name = 'XkbOD_ConnectionRefused' elif error_id == self.XkbOD_NonXkbServer: error_name = 'XkbOD_NonXkbServer' elif error_id == self.XkbOD_BadServerVersion: error_name = 'XkbOD_BadServerVersion' else: error_name = _('undefined') error_message = \ _('"XkbOpenDisplay" reported an error (%(error_name)s).') % \ {'error_name': error_name} raise OSError(error_message) # return display handle and all function arguments return (ctypes.c_uint(result), args) # connect error handler to function self.__XkbOpenDisplay__.errcheck = errcheck_xkbopendisplay # define "ctypes" prototype for the function # # Bool XkbGetState(display, device_spec, state_return) # # Display * display; # unsigned int device_spec; # XkbStatePtr state_return; paramflags_xkbgetstate = \ (1, 'display'), \ (1, 'device_spec'), \ (3, 'state_return') prototype_xkbgetstate = ctypes.CFUNCTYPE( \ ctypes.c_int, # Python 2.5 doesn't yet know c_bool \ ctypes.c_uint, \ ctypes.c_uint, \ ctypes.POINTER(self.XkbStateRec) \ ) # set-up function (low-level) self.__XkbGetState__ = prototype_xkbgetstate( \ ('XkbGetState', xkbd_library), \ paramflags_xkbgetstate \ ) # define error handler def errcheck_xkbgetstate(result, func, args): # print debugging information if requested if self.DEBUG_XKB: print print ' [XkbGetState]' print ' Status: %s' % result print ' display: %#010x' % args[0].value print ' device_spec: %d\n' % args[1].value print ' state_return.group: %d' % \ args[2].group print ' state_return.locked_group: %d' % \ args[2].locked_group print ' state_return.base_group: %d' % \ args[2].base_group print ' state_return.latched_group: %d' % \ args[2].latched_group print ' state_return.mods: %d' % \ args[2].mods print ' state_return.base_mods: %d' % \ args[2].base_mods print ' state_return.latched_mods: %d' % \ args[2].latched_mods print ' state_return.locked_mods: %d' % \ args[2].locked_mods print ' state_return.compat_state: %d' % \ args[2].compat_state print ' state_return.grab_mods: %d' % \ args[2].grab_mods print ' state_return.compat_grab_mods: %d' % \ args[2].compat_grab_mods print ' state_return.lookup_mods: %d' % \ args[2].lookup_mods print ' state_return.compat_lookup_mods: %d' % \ args[2].compat_lookup_mods print ' state_return.ptr_buttons: %d\n' % \ args[2].ptr_buttons print ' Mask mods base_mods latched_mods locked_mods compat_state' print ' --------------------------------------------------------------------------' print ' ShiftMask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.ShiftMask) != 0, \ (args[2].base_mods & self.ShiftMask) != 0, \ (args[2].latched_mods & self.ShiftMask) != 0, \ (args[2].locked_mods & self.ShiftMask) != 0, \ (args[2].compat_state & self.ShiftMask) != 0) print ' LockMask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.LockMask) != 0, \ (args[2].base_mods & self.LockMask) != 0, \ (args[2].latched_mods & self.LockMask) != 0, \ (args[2].locked_mods & self.LockMask) != 0, \ (args[2].compat_state & self.LockMask) != 0) print ' ControlMask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.ControlMask) != 0, \ (args[2].base_mods & self.ControlMask) != 0, \ (args[2].latched_mods & self.ControlMask) != 0, \ (args[2].locked_mods & self.ControlMask) != 0, \ (args[2].compat_state & self.ControlMask) != 0) print ' Mod1Mask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.Mod1Mask) != 0, \ (args[2].base_mods & self.Mod1Mask) != 0, \ (args[2].latched_mods & self.Mod1Mask) != 0, \ (args[2].locked_mods & self.Mod1Mask) != 0, \ (args[2].compat_state & self.Mod1Mask) != 0) print ' Mod2Mask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.Mod2Mask) != 0, \ (args[2].base_mods & self.Mod2Mask) != 0, \ (args[2].latched_mods & self.Mod2Mask) != 0, \ (args[2].locked_mods & self.Mod2Mask) != 0, \ (args[2].compat_state & self.Mod2Mask) != 0) print ' Mod3Mask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.Mod3Mask) != 0, \ (args[2].base_mods & self.Mod3Mask) != 0, \ (args[2].latched_mods & self.Mod3Mask) != 0, \ (args[2].locked_mods & self.Mod3Mask) != 0, \ (args[2].compat_state & self.Mod3Mask) != 0) print ' Mod4Mask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.Mod4Mask) != 0, \ (args[2].base_mods & self.Mod4Mask) != 0, \ (args[2].latched_mods & self.Mod4Mask) != 0, \ (args[2].locked_mods & self.Mod4Mask) != 0, \ (args[2].compat_state & self.Mod4Mask) != 0) print ' Mod5Mask %-5s %-5s %-5s %-5s %-5s' % \ ((args[2].mods & self.Mod5Mask) != 0, \ (args[2].base_mods & self.Mod5Mask) != 0, \ (args[2].latched_mods & self.Mod5Mask) != 0, \ (args[2].locked_mods & self.Mod5Mask) != 0, \ (args[2].compat_state & self.Mod5Mask) != 0) # return function return value and all function arguments return (result, args) # connect error handler to function self.__XkbGetState__.errcheck = errcheck_xkbgetstate # define high-level version of "XkbOpenDisplay" def XkbOpenDisplay(self, display_name, major_in_out, minor_in_out): # if we don't do type checking, nobody ever will assert (type(display_name) == types.NoneType) or \ (type(display_name) == types.StringType) assert type(major_in_out) == types.IntType assert type(minor_in_out) == types.IntType # convert function arguments to "ctypes", ... __display_name__ = ctypes.c_char_p(display_name) __major_in_out__ = ctypes.c_int(major_in_out) __minor_in_out__ = ctypes.c_int(minor_in_out) # ... call low-level function ... ret = self.__XkbOpenDisplay__(__display_name__, __major_in_out__, \ __minor_in_out__) # ... and return converted return value and function arguments return {'display_handle': ret[0].value, \ 'server_major_version': ret[1][3].value, \ 'server_minor_version': ret[1][4].value} # define high-level version of "XkbGetState" def XkbGetState(self, display_handle, device_spec): # if we don't do type checking, nobody ever will assert type(display_handle) == types.LongType assert type(device_spec) == types.IntType # convert function arguments to "ctypes", ... __display_handle__ = ctypes.c_uint(display_handle) __device_spec__ = ctypes.c_uint(device_spec) __xkbstaterec__ = self.XkbStateRec() # ... call low-level function ... ret = self.__XkbGetState__(__display_handle__, __device_spec__, \ __xkbstaterec__) # ... and return converted function argument xkbstaterec = ret[1][2] return xkbstaterec # extract modifier status using bitmasks def ExtractLocks(self, xkbstaterec): return {'group': xkbstaterec.group, \ 'shift': \ (xkbstaterec.base_mods & self.ShiftMask) != 0, \ 'shift_lock': \ (xkbstaterec.locked_mods & self.ShiftMask) != 0, \ 'lock': \ (xkbstaterec.base_mods & self.LockMask) != 0, \ 'lock_lock': \ (xkbstaterec.locked_mods & self.LockMask) != 0, \ 'control': \ (xkbstaterec.base_mods & self.ControlMask) != 0, \ 'control_lock': \ (xkbstaterec.locked_mods & self.ControlMask) != 0, \ 'mod1': \ (xkbstaterec.base_mods & self.Mod1Mask) != 0, \ 'mod1_lock': \ (xkbstaterec.locked_mods & self.Mod1Mask) != 0, \ 'mod2': \ (xkbstaterec.base_mods & self.Mod2Mask) != 0, \ 'mod2_lock': \ (xkbstaterec.locked_mods & self.Mod2Mask) != 0, \ 'mod3': \ (xkbstaterec.base_mods & self.Mod3Mask) != 0, \ 'mod3_lock': \ (xkbstaterec.locked_mods & self.Mod3Mask) != 0, \ 'mod4': \ (xkbstaterec.base_mods & self.Mod4Mask) != 0, \ 'mod4_lock': \ (xkbstaterec.locked_mods & self.Mod4Mask) != 0, \ 'mod5': \ (xkbstaterec.base_mods & self.Mod5Mask) != 0, \ 'mod5_lock': \ (xkbstaterec.locked_mods & self.Mod5Mask) != 0} if __name__ == '__main__': # simple demonstration of this wrapper xkb = SimpleXkbWrapper() # initialise wrapper for the X Keyboard Extension (v1.0) and # open connection to default X display display_name = None major_in_out = 1 minor_in_out = 0 try: ret = xkb.XkbOpenDisplay(display_name, major_in_out, minor_in_out) except OSError, error: print print ' Error: %s' % error print exit(1) # ... get modifier state of core keyboard ... display_handle = ret['display_handle'] device_spec = xkb.XkbUseCoreKbd xkbstaterec = xkb.XkbGetState(display_handle, device_spec) # ... and extract and the information we need mod_states = xkb.ExtractLocks(xkbstaterec) print for mod in mod_states: print ' %-13s %s' % (mod + ':', mod_states[mod]) print