Skip to content

Instantly share code, notes, and snippets.

@wfpaisa
Last active April 21, 2016 03:35
Show Gist options
  • Save wfpaisa/b6b4398bd147ce6b93d7d04f34379c6d to your computer and use it in GitHub Desktop.
Save wfpaisa/b6b4398bd147ce6b93d7d04f34379c6d to your computer and use it in GitHub Desktop.
screenkey linux personalizando, permitiendo solo comando y agregando el texto espacio en blanco: /lib/python2.7/site-packages/Screenkey
# Copyright (c) 2010 Pablo Seminario <pabluk@gmail.com>
# 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/>.
# for arabic characters
# -*- coding: utf-8 -*-
import threading
import time
import sys
import subprocess
import modmap
import gtk
from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq
MODE_RAW = 0
MODE_NORMAL = 1
MODE_SOLOCOMANDO = 2
REPLACE_KEYS = {
'XK_Escape':_('Esc '),
'XK_Tab':u'\u21B9 ',
'XK_Return':u'\u23CE ',
'XK_Space':u' ',
'XK_Caps_Lock':_('Caps '),
'XK_F1':u'F1 ',
'XK_F2':u'F2 ',
'XK_F3':u'F3 ',
'XK_F4':u'F4 ',
'XK_F5':u'F5 ',
'XK_F6':u'F6 ',
'XK_F7':u'F7 ',
'XK_F8':u'F8 ',
'XK_F9':u'F9 ',
'XK_F10':u'F10 ',
'XK_F11':u'F11 ',
'XK_F12':u'F12 ',
'XK_Home':_('Home '),
'XK_Up':u'\u2191',
'XK_Page_Up':_('PgUp '),
'XK_Left':u'\u2190',
'XK_Right':u'\u2192',
'XK_End':_('End '),
'XK_Down':u'\u2193',
'XK_Next':_('PgDn '),
'XK_Insert':_('Ins '),
'XK_Delete':_('Del '),
'XK_KP_Home':u'(7)',
'XK_KP_Up':u'(8)',
'XK_KP_Prior':u'(9)',
'XK_KP_Left':u'(4)',
'XK_KP_Right':u'(6)',
'XK_KP_End':u'(1)',
'XK_KP_Down':u'(2)',
'XK_KP_Page_Down':u'(3)',
'XK_KP_Begin':u'(5)',
'XK_KP_Insert':u'(0)',
'XK_KP_Delete':u'(.)',
'XK_KP_Add':u'(+)',
'XK_KP_Subtract':u'(-)',
'XK_KP_Multiply':u'(*)',
'XK_KP_Divide':u'(/)',
'XK_Num_Lock':u'NumLock ',
'XK_KP_Enter':u'\u23CE ',
}
class ListenKbd(threading.Thread):
def __init__(self, label, logger, mode):
threading.Thread.__init__(self)
self.mode = mode
self.logger = logger
self.label = label
self.text = ""
self.command = None
self.shift = None
self.cmd_keys = {
'shift': False,
'ctrl': False,
'alt': False,
'capslock': False,
'meta': False,
'super':False
}
self.logger.debug("Thread created")
self.keymap = modmap.get_keymap_table()
self.modifiers = modmap.get_modifier_map()
self.local_dpy = display.Display()
self.record_dpy = display.Display()
if not self.record_dpy.has_extension("RECORD"):
self.logger.error("RECORD extension not found.")
print "RECORD extension not found"
sys.exit(1)
self.ctx = self.record_dpy.record_create_context(
0,
[record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': (X.KeyPress, X.KeyRelease),
'errors': (0, 0),
'client_started': False,
'client_died': False,
}])
def run(self):
self.logger.debug("Thread started.")
self.record_dpy.record_enable_context(self.ctx, self.key_press)
def lookup_keysym(self, keysym):
for name in dir(XK):
if name[:3] == "XK_" and getattr(XK, name) == keysym:
return name[3:]
return ""
def replace_key(self, key, keysym):
for name in dir(XK):
if name[:3] == "XK_" and getattr(XK, name) == keysym:
if name in REPLACE_KEYS:
return REPLACE_KEYS[name]
def update_text(self, string=None):
gtk.gdk.threads_enter()
if not string is None:
self.text = "%s%s" % (self.label.get_text(), string)
self.label.set_text(self.text)
else:
self.label.set_text("")
gtk.gdk.threads_leave()
self.label.emit("text-changed")
def key_press(self, reply):
# FIXME:
# This is not the most efficient way to detect the
# use of sudo/gksudo but it works.
sudo_is_running = subprocess.call(['ps', '-C', 'sudo'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if not sudo_is_running:
return
if reply.category != record.FromServer:
return
if reply.client_swapped:
self.logger.warning(
"* received swapped protocol data, cowardly ignored"
)
return
if not len(reply.data) or ord(reply.data[0]) < 2:
# not an event
return
data = reply.data
key = None
while len(data):
event, data = rq.EventField(None).parse_binary_value(data,
self.record_dpy.display, None, None)
if event.type in [X.KeyPress, X.KeyRelease]:
if self.mode == MODE_NORMAL or self.mode == MODE_SOLOCOMANDO:
key = self.key_normal_mode(event)
if self.mode == MODE_RAW:
key = self.key_raw_mode(event)
if not key:
return
self.update_text(key)
def key_normal_mode(self, event):
key = ''
mod = ''
keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
if event.detail in self.keymap:
key_normal, key_shift, key_dead, key_deadshift = \
self.keymap[event.detail]
self.logger.debug("Key %s(keycode) %s. Symbols %s" %
(event.detail,
event.type == X.KeyPress and "pressed" or "released",
self.keymap[event.detail])
)
else:
self.logger.debug('No mapping for scan_code %d' % event.detail)
return
# Alt key
if event.detail in self.modifiers['mod1']:
if event.type == X.KeyPress:
self.cmd_keys['alt'] = True
else:
self.cmd_keys['alt'] = False
return
# Meta key
# Fixme: it must use self.modifiers['mod5']
# but doesn't work
if event.detail == 108:
if event.type == X.KeyPress:
self.cmd_keys['meta'] = True
else:
self.cmd_keys['meta'] = False
return
# Super key
if event.detail in self.modifiers['mod4']:
if event.type == X.KeyPress:
self.cmd_keys['super'] = True
else:
self.cmd_keys['super'] = False
return
# Ctrl keys
elif event.detail in self.modifiers['control']:
if event.type == X.KeyPress:
self.cmd_keys['ctrl'] = True
else:
self.cmd_keys['ctrl'] = False
return
# Shift keys
elif event.detail in self.modifiers['shift']:
if event.type == X.KeyPress:
self.cmd_keys['shift'] = True
else:
self.cmd_keys['shift'] = False
return
# Capslock key
elif event.detail in self.modifiers['lock']:
if event.type == X.KeyPress:
if self.cmd_keys['capslock']:
self.cmd_keys['capslock'] = False
else:
self.cmd_keys['capslock'] = True
return
# Backspace key
elif event.detail == 22 and event.type == X.KeyPress:
gtk.gdk.threads_enter()
if len(self.label.get_text()) > 0:
self.label.set_text(
unicode(self.label.get_text(), 'utf-8')[:-1]
)
key = ""
gtk.gdk.threads_leave()
else:
gtk.gdk.threads_leave()
return
else:
if event.type == X.KeyPress:
key = key_normal
if self.cmd_keys['ctrl']:
mod = mod + _("Ctrl+")
if self.cmd_keys['alt']:
mod = mod + _("Alt+")
if self.cmd_keys['super']:
mod = mod + _("Super+")
if self.cmd_keys['shift']:
mod = mod + _("Shift+")
key = key_shift
if self.cmd_keys['capslock'] \
and ord(key_normal) in range(97,123):
key = key_shift
if self.cmd_keys['meta']:
key = key_dead
if self.cmd_keys['shift'] and self.cmd_keys['meta']:
key = key_deadshift
# En los espacios blancos muestra el texto: space
if key == ' ':
key = 'Space '
# print 'mod:'+str(event.detail) + ' - key:' + key
# los keys que evita mostrar solos
if self.mode == MODE_SOLOCOMANDO and mod == '':
EVITAMOSTRAR = ['65','111','113','114','116','24','25','26','27','28','29','30','31','32','33','38','39','40','41','42','43','44','45','46','52','53','54','55','56','57','58','10','11','12','13','14','15','16','17','18','19']
for nxn in EVITAMOSTRAR:
if nxn == str(event.detail):
return
string = self.replace_key(key, keysym)
if string:
key = string
if mod != '':
key = "%s%s " % (mod, key)
else:
key = "%s%s" % (mod, key)
else:
return
return key
def key_raw_mode(self, event):
key = ''
if event.type == X.KeyPress:
keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
key = self.lookup_keysym(keysym)
else:
return
return key
def stop(self):
self.local_dpy.record_disable_context(self.ctx)
self.local_dpy.flush()
self.record_dpy.record_free_context(self.ctx)
self.logger.debug("Thread stopped.")
# Copyright (c) 2010 Pablo Seminario <pabluk@gmail.com>
# 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/>.
# for arabic characters
# -*- coding: utf-8 -*-
import os
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import glib
import pango
import pickle
from threading import Timer
from Screenkey import APP_NAME, APP_DESC, APP_URL, VERSION, AUTHOR
from listenkbd import ListenKbd
POS_TOP = 0
POS_CENTER = 1
POS_BOTTOM = 2
SIZE_LARGE = 0
SIZE_MEDIUM = 1
SIZE_SMALL = 2
MODE_RAW = 0
MODE_NORMAL = 1
MODE_SOLOCOMANDO = 2
class Screenkey(gtk.Window):
POSITIONS = {
POS_TOP:_('Top'),
POS_CENTER:_('Center'),
POS_BOTTOM:_('Bottom'),
}
SIZES = {
SIZE_LARGE:_('Large'),
SIZE_MEDIUM:_('Medium'),
SIZE_SMALL:_('Small'),
}
MODES = {
MODE_RAW:_('Raw'),
MODE_NORMAL:_('Normal'),
MODE_SOLOCOMANDO:_('Solo comando'),
}
STATE_FILE = os.path.join(glib.get_user_cache_dir(),
'screenkey.dat')
def __init__(self, logger, nodetach):
gtk.Window.__init__(self)
self.timer = None
self.logger = logger
self.options = self.load_state()
if not self.options:
self.options = {
'timeout': 2.5,
'position': POS_BOTTOM,
'size': SIZE_MEDIUM,
'mode': MODE_NORMAL,
}
if not nodetach:
self.logger.debug("Detach from the parent.")
self.drop_tty()
self.set_skip_taskbar_hint(True)
self.set_skip_pager_hint(True)
self.set_keep_above(True)
self.set_decorated(False)
self.stick()
self.set_property('accept-focus', False)
self.set_property('focus-on-map', False)
self.set_position(gtk.WIN_POS_CENTER)
bgcolor = gtk.gdk.color_parse("black")
self.modify_bg(gtk.STATE_NORMAL, bgcolor)
self.set_opacity(0.7)
gobject.signal_new("text-changed", gtk.Label,
gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
self.label = gtk.Label()
self.label.set_justify(gtk.JUSTIFY_RIGHT)
self.label.set_ellipsize(pango.ELLIPSIZE_START)
self.label.connect("text-changed", self.on_label_change)
self.label.show()
self.add(self.label)
self.screen_width = gtk.gdk.screen_width()
self.screen_height = gtk.gdk.screen_height()
self.set_window_size(self.options['size'])
self.set_gravity(gtk.gdk.GRAVITY_CENTER)
self.set_xy_position(self.options['position'])
self.listenkbd = ListenKbd(self.label, logger=self.logger,
mode=self.options['mode'])
self.listenkbd.start()
menu = gtk.Menu()
show_item = gtk.CheckMenuItem(_("Show keys"))
show_item.set_active(True)
show_item.connect("toggled", self.on_show_keys)
show_item.show()
menu.append(show_item)
preferences_item = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
preferences_item.connect("activate", self.on_preferences_dialog)
preferences_item.show()
menu.append(preferences_item)
about_item = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
about_item.connect("activate", self.on_about_dialog)
about_item.show()
menu.append(about_item)
separator_item = gtk.SeparatorMenuItem()
separator_item.show()
menu.append(separator_item)
image = gtk.ImageMenuItem(gtk.STOCK_QUIT)
image.connect("activate", self.quit)
image.show()
menu.append(image)
menu.show()
try:
import appindicator
self.systray = appindicator.Indicator(APP_NAME,
'indicator-messages',
appindicator.CATEGORY_APPLICATION_STATUS)
self.systray.set_status(appindicator.STATUS_ACTIVE)
self.systray.set_attention_icon("indicator-messages-new")
self.systray.set_icon(
"preferences-desktop-keyboard-shortcuts")
self.systray.set_menu(menu)
self.logger.debug("Using AppIndicator.")
except(ImportError):
self.systray = gtk.StatusIcon()
self.systray.set_from_icon_name(
"preferences-desktop-keyboard-shortcuts")
self.systray.connect("popup-menu",
self.on_statusicon_popup, menu)
self.logger.debug("Using StatusIcon.")
self.connect("delete-event", self.quit)
def quit(self, widget, data=None):
self.listenkbd.stop()
gtk.main_quit()
def load_state(self):
"""Load stored options"""
options = None
try:
f = open(self.STATE_FILE, 'r')
try:
options = pickle.load(f)
self.logger.debug("Options loaded.")
except:
f.close()
except IOError:
self.logger.debug("file %s does not exists." %
self.STATE_FILE)
return options
def store_state(self, options):
"""Store options"""
try:
f = open(self.STATE_FILE, 'w')
try:
pickle.dump(options, f)
self.logger.debug("Options saved.")
except:
f.close()
except IOError:
self.logger.debug("Cannot open %s." % self.STATE_FILE)
def set_window_size(self, setting):
"""Set window and label size."""
window_width = self.screen_width / 4
window_height = -1
if setting == SIZE_LARGE:
window_height = 24 * self.screen_height / 100
if setting == SIZE_MEDIUM:
window_height = 12 * self.screen_height / 100
if setting == SIZE_SMALL:
window_height = 6 * self.screen_height / 100
attr = pango.AttrList()
attr.change(pango.AttrSize((
30 * window_height / 100) * 1000, 0, -1))
attr.change(pango.AttrFamily("Sans", 0, -1))
attr.change(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
attr.change(pango.AttrForeground(65535, 65535, 65535, 0, -1))
self.label.set_attributes(attr)
self.resize(window_width, window_height)
def set_xy_position(self, setting):
"""Set window position."""
window_width, window_height = self.get_size()
if setting == POS_TOP:
self.move(0, window_height * 2)
if setting == POS_CENTER:
self.move(0, self.screen_height / 2)
if setting == POS_BOTTOM:
self.move(0, self.screen_height - window_height * 2)
def on_statusicon_popup(self, widget, button, timestamp, data=None):
if button == 3:
if data:
data.show()
data.popup(None, None, gtk.status_icon_position_menu,
3, timestamp, widget)
def on_label_change(self, widget, data=None):
if not self.get_property('visible'):
gtk.gdk.threads_enter()
self.set_xy_position(self.options['position'])
self.stick()
self.show()
gtk.gdk.threads_leave()
if self.timer:
self.timer.cancel()
self.timer = Timer(self.options['timeout'], self.on_timeout)
self.timer.start()
def on_timeout(self):
gtk.gdk.threads_enter()
self.hide()
self.label.set_text("")
gtk.gdk.threads_leave()
def on_change_mode(self, mode):
self.listenkbd.stop()
self.listenkbd = ListenKbd(self.label, logger=self.logger,
mode=mode)
self.listenkbd.start()
def on_show_keys(self, widget, data=None):
if widget.get_active():
self.logger.debug("Screenkey enabled.")
self.listenkbd = ListenKbd(self.label, logger=self.logger,
mode=self.options['mode'])
self.listenkbd.start()
else:
self.logger.debug("Screenkey disabled.")
self.listenkbd.stop()
def on_preferences_dialog(self, widget, data=None):
prefs = gtk.Dialog(APP_NAME, None,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
def on_sb_time_changed(widget, data=None):
self.options['timeout'] = widget.get_value()
self.logger.debug("Timeout value changed.")
def on_cbox_sizes_changed(widget, data=None):
index = widget.get_active()
if index >= 0:
self.options['size'] = index
self.set_window_size(self.options['size'])
self.logger.debug("Window size changed.")
def on_cbox_modes_changed(widget, data=None):
index = widget.get_active()
if index >= 0:
self.options['mode'] = index
self.on_change_mode(self.options['mode'])
self.logger.debug("Key mode changed.")
def on_cbox_changed(widget, data=None):
index = widget.get_active()
name = widget.get_name()
if index >= 0:
self.options[name] = index
self.logger.debug("Window position changed.")
frm_main = gtk.Frame(_("Preferences"))
frm_main.set_border_width(6)
vbox_main = gtk.VBox()
frm_time = gtk.Frame(_("<b>Time</b>"))
frm_time.set_border_width(4)
frm_time.get_label_widget().set_use_markup(True)
frm_time.set_shadow_type(gtk.SHADOW_NONE)
hbox_time = gtk.HBox()
lbl_time1 = gtk.Label(_("Display for"))
lbl_time2 = gtk.Label(_("seconds"))
sb_time = gtk.SpinButton(digits=1)
sb_time.set_increments(0.5, 1.0)
sb_time.set_range(0.5, 10.0)
sb_time.set_numeric(True)
sb_time.set_update_policy(gtk.UPDATE_IF_VALID)
sb_time.set_value(self.options['timeout'])
sb_time.connect("value-changed", on_sb_time_changed)
hbox_time.pack_start(lbl_time1, expand=False,
fill=False, padding=6)
hbox_time.pack_start(sb_time, expand=False,
fill=False, padding=4)
hbox_time.pack_start(lbl_time2, expand=False,
fill=False, padding=4)
frm_time.add(hbox_time)
frm_time.show_all()
frm_aspect = gtk.Frame(_("<b>Aspect</b>"))
frm_aspect.set_border_width(4)
frm_aspect.get_label_widget().set_use_markup(True)
frm_aspect.set_shadow_type(gtk.SHADOW_NONE)
vbox_aspect = gtk.VBox(spacing=6)
hbox1_aspect = gtk.HBox()
lbl_positions = gtk.Label(_("Position"))
cbox_positions = gtk.combo_box_new_text()
cbox_positions.set_name('position')
for key, value in self.POSITIONS.items():
cbox_positions.insert_text(key, value)
cbox_positions.set_active(self.options['position'])
cbox_positions.connect("changed", on_cbox_changed)
hbox1_aspect.pack_start(lbl_positions, expand=False,
fill=False, padding=6)
hbox1_aspect.pack_start(cbox_positions, expand=False,
fill=False, padding=4)
hbox2_aspect = gtk.HBox()
lbl_sizes = gtk.Label(_("Size"))
cbox_sizes = gtk.combo_box_new_text()
cbox_sizes.set_name('size')
for key, value in self.SIZES.items():
cbox_sizes.insert_text(key, value)
cbox_sizes.set_active(self.options['size'])
cbox_sizes.connect("changed", on_cbox_sizes_changed)
hbox2_aspect.pack_start(lbl_sizes, expand=False,
fill=False, padding=6)
hbox2_aspect.pack_start(cbox_sizes, expand=False,
fill=False, padding=4)
vbox_aspect.pack_start(hbox1_aspect)
vbox_aspect.pack_start(hbox2_aspect)
frm_aspect.add(vbox_aspect)
frm_kbd = gtk.Frame(_("<b>Keys</b>"))
frm_kbd.set_border_width(4)
frm_kbd.get_label_widget().set_use_markup(True)
frm_kbd.set_shadow_type(gtk.SHADOW_NONE)
hbox_kbd = gtk.HBox()
lbl_kbd = gtk.Label(_("Mode"))
cbox_modes = gtk.combo_box_new_text()
cbox_modes.set_name('mode')
for key, value in self.MODES.items():
cbox_modes.insert_text(key, value)
cbox_modes.set_active(self.options['mode'])
cbox_modes.connect("changed", on_cbox_modes_changed)
hbox_kbd.pack_start(lbl_kbd, expand=False,
fill=False, padding=6)
hbox_kbd.pack_start(cbox_modes, expand=False,
fill=False, padding=4)
frm_kbd.add(hbox_kbd)
vbox_main.pack_start(frm_time, False, False, 6)
vbox_main.pack_start(frm_aspect, False, False, 6)
vbox_main.pack_start(frm_kbd, False, False, 6)
frm_main.add(vbox_main)
prefs.vbox.pack_start(frm_main)
prefs.set_destroy_with_parent(True)
prefs.set_resizable(False)
prefs.set_has_separator(False)
prefs.set_default_response(gtk.RESPONSE_CLOSE)
prefs.vbox.show_all()
response = prefs.run()
if response:
self.store_state(self.options)
prefs.destroy()
def on_about_dialog(self, widget, data=None):
about = gtk.AboutDialog()
about.set_program_name(APP_NAME)
about.set_version(VERSION)
about.set_copyright(u"2010 \u00a9 %s" % AUTHOR)
about.set_comments(APP_DESC)
about.set_documenters(
[u"Jos\xe9 Mar\xeda Quiroga <pepelandia@gmail.com>"]
)
about.set_website(APP_URL)
about.set_icon_name('preferences-desktop-keyboard-shortcuts')
about.set_logo_icon_name(
'preferences-desktop-keyboard-shortcuts'
)
about.run()
about.destroy()
def drop_tty(self):
# We fork and setsid so that we drop the controlling
# tty.
if os.fork() != 0:
os._exit(0)
os.setsid()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment