summaryrefslogtreecommitdiffstats
path: root/linux/osd/OSDneo2.py
diff options
context:
space:
mode:
Diffstat (limited to 'linux/osd/OSDneo2.py')
-rwxr-xr-xlinux/osd/OSDneo2.py603
1 files changed, 603 insertions, 0 deletions
diff --git a/linux/osd/OSDneo2.py b/linux/osd/OSDneo2.py
new file mode 100755
index 0000000..e2b69ff
--- /dev/null
+++ b/linux/osd/OSDneo2.py
@@ -0,0 +1,603 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""OSD Neo2
+ ========
+ On screen display for learning the keyboard layout Neo2
+
+ Copyright (c) 2009 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 <http://www.gnu.org/licenses/>.
+
+ 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 pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+import gettext
+import locale
+import os
+import time
+
+import SimpleXkbWrapper
+from optparse import OptionParser
+from Settings import *
+
+# set standard localisation for application
+locale.setlocale(locale.LC_ALL, '')
+
+# 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
+
+# specifies distance between main keyboard and numeric keyboard (in pixels)
+DISTANCE_LAYOUT_BLOCKS = 10
+
+class OSDneo2:
+ # layer matrix for "xkbdmap" with disabled Locks (plain)
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 1 | 4 |
+ # | Mod3 on | 3 | 6 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 2 | 0 |
+ # | Mod3 on | 5 | 0 |
+ # |-----------+----------+---------|
+ xkbdmap_layers_plain = {' ': 1, \
+ ' 3 ': 3, \
+ ' 4': 4, \
+ ' 34': 6, \
+ 'S ': 2, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 0}
+
+ # layer matrix for "xkbdmap" with enabled Caps Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 7 | 4 |
+ # | Mod3 on | 3 | 6 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 8 | 0 |
+ # | Mod3 on | 5 | 0 |
+ # |-----------+----------+---------|
+ xkbdmap_layers_caps_lock = {' ': 7, \
+ ' 3 ': 3, \
+ ' 4': 4, \
+ ' 34': 6, \
+ 'S ': 8, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 0}
+
+ # layer matrix for "xkbdmap" with enabled Mod4 Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 4 | 1 |
+ # | Mod3 on | 6 | 3 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 0 | 2 |
+ # | Mod3 on | 0 | 5 |
+ # |-----------+----------+---------|
+ xkbdmap_layers_mod4_lock = {' ': 4, \
+ ' 3 ': 6, \
+ ' 4': 1, \
+ ' 34': 3, \
+ 'S ': 0, \
+ 'S3 ': 0, \
+ 'S 4': 2, \
+ 'S34': 5}
+
+ # layer matrix for "xkbdmap" with enabled Caps+Mod4 Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 4 | 7 |
+ # | Mod3 on | 6 | 3 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 0 | 8 |
+ # | Mod3 on | 0 | 5 |
+ # |-----------+----------+---------|
+ xkbdmap_layers_caps_mod4_lock = {' ': 4, \
+ ' 3 ': 6, \
+ ' 4': 7, \
+ ' 34': 3, \
+ 'S ': 0, \
+ 'S3 ': 0, \
+ 'S 4': 8, \
+ 'S34': 5}
+
+ # layer matrix for "xmodmap" with disabled Locks (plain)
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 1 | 4 |
+ # | Mod3 on | 3 | 6 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 2 | 0 |
+ # | Mod3 on | 5 | 6 |
+ # |-----------+----------+---------|
+ xmodmap_layers_plain = {' ': 1, \
+ ' 3 ': 3, \
+ ' 4': 4, \
+ ' 34': 6, \
+ 'S ': 2, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 6}
+
+ # layer matrix for "xmodmap" with enabled Caps Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 2 | 0 |
+ # | Mod3 on | 5 | 6 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 2 | 0 |
+ # | Mod3 on | 5 | 6 |
+ # |-----------+----------+---------|
+ xmodmap_layers_caps_lock = {' ': 2, \
+ ' 3 ': 5, \
+ ' 4': 0, \
+ ' 34': 6, \
+ 'S ': 2, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 6}
+
+ # layer matrix for "xmodmap" with enabled Mod4 Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 4 | 4 |
+ # | Mod3 on | 3 | 6 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 0 | 0 |
+ # | Mod3 on | 5 | 6 |
+ # |-----------+----------+---------|
+ xmodmap_layers_mod4_lock = {' ': 4, \
+ ' 3 ': 3, \
+ ' 4': 4, \
+ ' 34': 6, \
+ 'S ': 0, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 6}
+
+ # layer matrix for "xmodmap" with enabled Caps+Mod4 Lock
+ #
+ # |-----------+----------+---------|
+ # | Shift off | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 0 | 0 |
+ # | Mod3 on | 5 | 5 |
+ # |-----------+----------+---------|
+ # | Shift on | Mod4 off | Mod4 on |
+ # |-----------+----------+---------|
+ # | Mod3 off | 0 | 0 |
+ # | Mod3 on | 5 | 5 |
+ # |-----------+----------+---------|
+ xmodmap_layers_caps_mod4_lock = {' ': 0, \
+ ' 3 ': 5, \
+ ' 4': 0, \
+ ' 34': 5, \
+ 'S ': 0, \
+ 'S3 ': 5, \
+ 'S 4': 0, \
+ 'S34': 5}
+
+
+ def __init__(self):
+ # initialise version information, ...
+ version_long = _('%(description)s\n%(copyrights)s\n\n%(license)s') % \
+ {'description':settings.get_description(True), \
+ 'copyrights':settings.get_copyrights(), \
+ 'license':settings.get_license(True)}
+ # ... ,usage information and...
+ usage = 'Usage: %(cmd_line)s [options]' % \
+ {'cmd_line':settings.get_variable('cmd_line')}
+ # ... the command line parser itself
+ parser = OptionParser(usage=usage, version=version_long)
+
+ # parse command line
+ (options, args) = parser.parse_args()
+
+ # setting: display main keyboard (Boolean)
+ self.display_main_keyboard = (settings.get( \
+ 'settings', 'display_main_keyboard', str(True)) == "True")
+
+ # setting: display numeric keyboard (Boolean)
+ self.display_numeric_keyboard = (settings.get( \
+ 'settings', 'display_numeric_keyboard', str(True)) == "True")
+
+ # setting: magnification of keyboard (in percent)
+ self.magnification = int(settings.get( \
+ 'settings', 'magnification_in_percent', str(100)))
+
+ # setting: interval of update timer (in milliseconds)
+ self.polling = int(settings.get( \
+ 'settings', 'polling_in_milliseconds', str(100)))
+
+ # setting: selected driver ("xkbdmap" or "xmodmap")
+ self.keyboard_driver = settings.get( \
+ 'settings', 'selected_keyboard_driver', 'xkbdmap')
+
+ # initialise core keyboard
+ self.initialise_keyboard()
+
+ # set currently selected keyboard layer to defaults (for your
+ # information, "leer" is German for "empty")
+ self.current_modifier = 'leer'
+ self.mod_states = None
+
+ # create main window and set its title
+ self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ self.window.set_title(settings.get_description(False))
+
+ # allow window to get killed and keep it on top
+ self.window.connect('delete-event', self.on_delete_event)
+ self.window.set_keep_above(True)
+
+ # restore old window position
+ x = int(settings.get('settings', 'window_position_x', str(0)))
+ y = int(settings.get('settings', 'window_position_y', str(0)))
+ if (x > 0) and (y > 0):
+ self.window.move(x, y)
+
+ # create an HBox, ...
+ self.hbox = gtk.HBox(False, DISTANCE_LAYOUT_BLOCKS)
+ self.window.add(self.hbox)
+
+ # ..., attach image for main keyboard (if requested) ...
+ if self.display_main_keyboard:
+ self.image_main = gtk.Image()
+ self.hbox.pack_start(self.image_main)
+
+ # ... and attach image for numeric keyboard (if requested)
+ if self.display_numeric_keyboard:
+ self.image_numeric = gtk.Image()
+ self.hbox.pack_start(self.image_numeric)
+
+ # the window size depends on the loaded images and
+ # "self.magnification", so we'll set it later
+ self.window_width = -1
+ self.window_height = -1
+
+ # later on, the keyboard layout will only be drawn when the
+ # selected keyboard layer changes, so we'll force the initial
+ # drawing
+ self.update_display()
+
+ # show everything in window
+ self.window.show_all()
+
+ # update status of modifier leys once ...
+ self.update_status()
+
+ # ... before starting the timer for polling modifier keys
+ gobject.timeout_add(self.polling, self.update_status)
+
+
+ def main(self):
+ # main event loop
+ gtk.main()
+
+
+ def on_delete_event(self, widget, event, data=None):
+ # store current window position, ...
+ (x,y) = self.window.get_position()
+ settings.set('settings', 'window_position_x', str(x))
+ settings.set('settings', 'window_position_y', str(y))
+
+ # ... and quit the application
+ gtk.main_quit()
+ return False
+
+
+ def initialise_keyboard(self):
+ # initialise wrapper for the X Keyboard Extension (v1.0) and
+ # open connection to X display
+ self.xkb = SimpleXkbWrapper.SimpleXkbWrapper()
+
+ # we'll use the default X display
+ display_name = None
+
+ # we need version 1.0 of the X Keyboard Extension
+ major_in_out = 1
+ minor_in_out = 0
+
+ # open X display and check for compatible X Keyboard Extension
+ try:
+ ret = self.xkb.XkbOpenDisplay(display_name, major_in_out, \
+ minor_in_out)
+ except OSError, error:
+ self.error_dialog(_('Error'), error)
+
+ # store handle to X display for later use
+ self.display_handle = ret['display_handle']
+
+
+ def update_status(self):
+ """
+ This function is called by the timer in order to check the
+ status of modifier keys.
+ """
+
+ # we only have to update the main window if the modifier
+ # states have changed, so store the current modifier states
+ old_mod_states = self.mod_states
+
+ # select the core keyboard ...
+ device_spec = self.xkb.XkbUseCoreKbd
+
+ # ... and poll modifier state
+ xkbstaterec = self.xkb.XkbGetState(self.display_handle, device_spec)
+ self.mod_states = self.xkb.ExtractLocks(xkbstaterec)
+
+ # as promised above, we'll only update the main window if the
+ # modifier states have changed
+ if self.mod_states != old_mod_states:
+ self.set_current_modifier()
+
+ # keep the timer running
+ return True
+
+
+ def set_current_modifier(self):
+ # we'll keep CPU usage low by updating the main window only
+ # when the selected keyboard layer has changed, so let's store
+ # the currently selected keyboard layer
+ old_modifier = self.current_modifier
+
+ # please don't confuse the modifiers defined by Neo2 ("MOD3"
+ # in the following section) with modifiers defined by X11
+ # ("mod3") -- let's set the modifiers for accessing the layer
+ # matrices
+
+ # user selected Neo2 keyboard driver "xkbdmap"
+ if self.keyboard_driver == 'xkbdmap':
+ if self.mod_states['shift']:
+ SHIFT = 'S'
+ else:
+ SHIFT = ' '
+
+ if self.mod_states['mod5']:
+ MOD3 = '3'
+ else:
+ MOD3 = ' '
+
+ if self.mod_states['mod3']:
+ MOD4 = '4'
+ else:
+ MOD4 = ' '
+
+ # get status of locks
+ CAPS_LOCK = self.mod_states['lock_lock']
+ MOD4_LOCK = self.mod_states['mod2_lock']
+ # user selected Neo2 keyboard driver "xmodmap"
+ elif self.keyboard_driver == 'xmodmap':
+ if self.mod_states['shift']:
+ SHIFT = 'S'
+ else:
+ SHIFT = ' '
+
+ if self.mod_states['mod3']:
+ MOD4 = '4'
+ else:
+ MOD4 = ' '
+
+ if self.mod_states['group'] == 0:
+ MOD3 = ' '
+ elif self.mod_states['group'] == 1:
+ MOD3 = '3'
+ elif self.mod_states['group'] == 2:
+ MOD3 = '3'
+ MOD4 = '4'
+
+ # get status of locks
+ CAPS_LOCK = self.mod_states['shift_lock']
+ MOD4_LOCK = self.mod_states['mod3_lock']
+ # user selected invalid Neo2 keyboard driver
+ else:
+ error = _('Invalid keyboard driver "%s" selected.') % \
+ self.keyboard_driver
+ self.error_dialog(_('Error'), error)
+
+ # assemble matrix key
+ MODIFIERS = '%s%s%s' % (SHIFT, MOD3, MOD4)
+
+ # select correct matrix and get current layer for Neo2
+ # keyboard driver "xkbdmap" ...
+ if self.keyboard_driver == 'xkbdmap':
+ if CAPS_LOCK:
+ if MOD4_LOCK:
+ current_modifier_temp = \
+ self.xkbdmap_layers_caps_mod4_lock[MODIFIERS]
+ else:
+ current_modifier_temp = \
+ self.xkbdmap_layers_caps_lock[MODIFIERS]
+ elif MOD4_LOCK:
+ current_modifier_temp = \
+ self.xkbdmap_layers_mod4_lock[MODIFIERS]
+ else:
+ current_modifier_temp = \
+ self.xkbdmap_layers_plain[MODIFIERS]
+ # ... or keyboard driver "xmodmap"
+ elif self.keyboard_driver == 'xmodmap':
+ if CAPS_LOCK:
+ if MOD4_LOCK:
+ current_modifier_temp = \
+ self.xmodmap_layers_caps_mod4_lock[MODIFIERS]
+ else:
+ current_modifier_temp = \
+ self.xmodmap_layers_caps_lock[MODIFIERS]
+ elif MOD4_LOCK:
+ current_modifier_temp = \
+ self.xmodmap_layers_mod4_lock[MODIFIERS]
+ else:
+ current_modifier_temp = \
+ self.xmodmap_layers_plain[MODIFIERS]
+ else:
+ error = _('Invalid keyboard driver "%s" selected.') % \
+ self.keyboard_driver
+ self.error_dialog(_('Error'), error)
+
+ # for your information, "Ebene" is German for "layer", while
+ # "leer" is German for "empty"
+ if current_modifier_temp < 1:
+ self.current_modifier = 'leer'
+ # add Caps Lock to layers 1 and 2
+ elif current_modifier_temp > 6:
+ self.current_modifier = 'ebene%d-caps' % \
+ (current_modifier_temp - 6)
+ # plain (i.e. no locks)
+ else:
+ self.current_modifier = 'ebene%d' % current_modifier_temp
+
+ # as promised above, we'll only update the main window if the
+ # selected keyboard layer has changed
+ if self.current_modifier != old_modifier:
+ self.update_display()
+
+
+ def update_display(self):
+ if self.display_main_keyboard:
+ # check whether image for main keyboard exists
+ path_main = os.path.join(module_path, 'images', \
+ 'neo2-hauptfeld_' + \
+ self.current_modifier + '.png')
+ if not os.path.exists(path_main):
+ error = _('The following image file was not found:\n"%s"') % \
+ path_main
+ self.error_dialog(_('Error'), error)
+
+ # load image for main keyboard in PixBuf, ...
+ pixbuf_main = gtk.gdk.pixbuf_new_from_file(path_main)
+
+ # ... re-size it according to "self.magnification" ...
+ if self.magnification != 100:
+ pixbuf_main = pixbuf_main.scale_simple( \
+ int(pixbuf_main.get_width() * \
+ self.magnification / 100), \
+ int(pixbuf_main.get_height() * \
+ self.magnification / 100), \
+ gtk.gdk.INTERP_BILINEAR)
+ # ... and copy it to the main window
+ self.image_main.set_from_pixbuf(pixbuf_main)
+
+ if self.display_numeric_keyboard:
+ # check whether image for numeric keyboard exists
+ path_numeric = os.path.join(module_path, 'images', \
+ 'neo2-ziffernfeld_' + \
+ self.current_modifier + '.png')
+ if not os.path.exists(path_numeric):
+ error = _('The following image file was not found:\n"%s"') % \
+ path_numeric
+ self.error_dialog(_('Error'), error)
+
+ # load image for numeric keyboard in PixBuf, ...
+ pixbuf_numeric = gtk.gdk.pixbuf_new_from_file(path_numeric)
+ # ... re-size it according to "self.magnification" ...
+ if self.magnification != 100:
+ pixbuf_numeric = pixbuf_numeric.scale_simple( \
+ int(pixbuf_numeric.get_width() * \
+ self.magnification / 100), \
+ int(pixbuf_numeric.get_height() * \
+ self.magnification / 100), \
+ gtk.gdk.INTERP_BILINEAR)
+ # ... and copy it to the main window
+ self.image_numeric.set_from_pixbuf(pixbuf_numeric)
+
+ # the window size depends on the loaded images and
+ # "self.magnification", so we'll set it here if not yet done
+ if (self.window_width == -1) or (self.window_height == -1):
+ # only main keyboard has been requested
+ if self.display_main_keyboard and not self.display_numeric_keyboard:
+ self.window_width = pixbuf_main.get_width()
+ self.window_height = pixbuf_main.get_height()
+ # only numeric keyboard has been requested
+ elif self.display_numeric_keyboard and not \
+ self.display_main_keyboard:
+ self.window_width = pixbuf_numeric.get_width()
+ self.window_height = pixbuf_numeric.get_height()
+ # only main and numeric keyboard have been requested
+ else:
+ self.window_width = pixbuf_main.get_width() + \
+ DISTANCE_LAYOUT_BLOCKS + pixbuf_numeric.get_width()
+ # set window height to highest image (in case they differ)
+ if pixbuf_main.get_height() >= pixbuf_numeric.get_height():
+ self.window_height = pixbuf_main.get_height()
+ else:
+ self.window_height = pixbuf_numeric.get_height()
+
+ # re-size main window accordingly
+ self.window.resize(self.window_width, self.window_height)
+
+
+ def error_dialog(self, title, error):
+ # display a dialog with the given error ...
+ dialog = gtk.Dialog(title, None, gtk.DIALOG_NO_SEPARATOR, \
+ (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
+ dialog.vbox.pack_start(gtk.Label(str(error)))
+ dialog.show_all()
+ dialog.run()
+ # ... and exit after user has pressed "Ok"
+ exit(1)
+
+
+if __name__ == '__main__':
+ # check whether the script runs with superuser rights
+ if (os.getuid() == 0) or (os.getgid() == 0):
+ print _('For security reasons you may not run this application with superuser rights.')
+
+ base = OSDneo2()
+ base.main()