# Copyright (C) 2006-2007, Red Hat, Inc. # # 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 2 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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os import signal import logging import subprocess import errno import tempfile import time from gettext import gettext as _ import gconf import dbus import gtk import wnck from sugar._sugarext import KeyGrabber from sugar.datastore import datastore from jarabe.model import screen from jarabe.model import sound from jarabe.model import shell from jarabe.view.tabbinghandler import TabbingHandler from jarabe.model.shell import ShellModel _BRIGHTNESS_STEP = 2 _VOLUME_STEP = sound.VOLUME_STEP _BRIGHTNESS_MAX = 15 _VOLUME_MAX = 100 _TABBING_MODIFIER = gtk.gdk.MOD1_MASK _actions_table = { 'F1' : 'zoom_mesh', 'F2' : 'zoom_group', 'F3' : 'zoom_home', 'F4' : 'zoom_activity', 'F9' : 'brightness_down', 'F10' : 'brightness_up', 'F9' : 'brightness_min', 'F10' : 'brightness_max', 'F11' : 'volume_down', 'F12' : 'volume_up', 'F11' : 'volume_min', 'F12' : 'volume_max', '1' : 'screenshot', '0x93' : 'frame', '0xEB' : 'rotate', 'Tab' : 'next_window', 'Tab': 'previous_window', 'Escape' : 'close_window', '0xDC' : 'open_search', # the following are intended for emulator users 'f' : 'frame', 'q' : 'quit_emulator', 'o' : 'open_search', 'r' : 'rotate', 's' : 'say_text', } J_DBUS_SERVICE = 'org.laptop.Journal' J_DBUS_PATH = '/org/laptop/Journal' J_DBUS_INTERFACE = 'org.laptop.Journal' SPEECH_DBUS_SERVICE = 'org.laptop.Speech' SPEECH_DBUS_PATH = '/org/laptop/Speech' SPEECH_DBUS_INTERFACE = 'org.laptop.Speech' class KeyHandler(object): def __init__(self, frame): self._frame = frame self._screen_rotation = 0 self._key_pressed = None self._keycode_pressed = 0 self._keystate_pressed = 0 self._speech_proxy = None self._key_grabber = KeyGrabber() self._key_grabber.connect('key-pressed', self._key_pressed_cb) self._key_grabber.connect('key-released', self._key_released_cb) self._tabbing_handler = TabbingHandler(self._frame, _TABBING_MODIFIER) self._key_grabber.grab_keys(_actions_table.keys()) def _change_volume(self, step=None, value=None): if step is not None: volume = sound.get_volume() + step elif value is not None: volume = value volume = min(max(0, volume), _VOLUME_MAX) sound.set_volume(volume) sound.set_muted(volume == 0) def _change_brightness(self, step=None, value=None): if step is not None: level = screen.get_display_brightness() + step elif value is not None: level = value level = min(max(0, level), _BRIGHTNESS_MAX) screen.set_display_brightness(level) if level == 0: screen.set_display_mode(screen.B_AND_W_MODE) else: screen.set_display_mode(screen.COLOR_MODE) def _get_speech_proxy(self): if self._speech_proxy is None: bus = dbus.SessionBus() speech_obj = bus.get_object(SPEECH_DBUS_SERVICE, SPEECH_DBUS_PATH, follow_name_owner_changes=True) self._speech_proxy = dbus.Interface(speech_obj, SPEECH_DBUS_INTERFACE) return self._speech_proxy def _on_speech_err(self, ex): logging.error("An error occurred with the ESpeak service: %r" % (ex, )) def _primary_selection_cb(self, clipboard, text, user_data): logging.debug('KeyHandler._primary_selection_cb: %r' % text) if text: self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \ error_handler=self._on_speech_err) def handle_say_text(self): clipboard = gtk.clipboard_get(selection="PRIMARY") clipboard.request_text(self._primary_selection_cb) def handle_previous_window(self): self._tabbing_handler.previous_activity() def handle_next_window(self): self._tabbing_handler.next_activity() def handle_close_window(self): active_activity = shell.get_model().get_active_activity() if active_activity.is_journal(): return active_activity.get_window().close() def handle_zoom_mesh(self): shell.get_model().zoom_level = ShellModel.ZOOM_MESH def handle_zoom_group(self): shell.get_model().zoom_level = ShellModel.ZOOM_GROUP def handle_zoom_home(self): shell.get_model().zoom_level = ShellModel.ZOOM_HOME def handle_zoom_activity(self): shell.get_model().zoom_level = ShellModel.ZOOM_ACTIVITY def handle_brightness_max(self): self._change_brightness(value=_BRIGHTNESS_MAX) def handle_brightness_min(self): self._change_brightness(value=0) def handle_volume_max(self): self._change_volume(value=_VOLUME_MAX) def handle_volume_min(self): self._change_volume(value=0) def handle_brightness_up(self): self._change_brightness(step=_BRIGHTNESS_STEP) def handle_brightness_down(self): self._change_brightness(step=-_BRIGHTNESS_STEP) def handle_volume_up(self): self._change_volume(step=_VOLUME_STEP) def handle_volume_down(self): self._change_volume(step=-_VOLUME_STEP) def handle_screenshot(self): file_path = os.path.join(tempfile.gettempdir(), '%i' % time.time()) window = gtk.gdk.get_default_root_window() width, height = window.get_size() x_orig, y_orig = window.get_origin() screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False, bits_per_sample=8, width=width, height=height) screenshot.get_from_drawable(window, window.get_colormap(), x_orig, y_orig, 0, 0, width, height) screenshot.save(file_path, "png") client = gconf.client_get_default() color = client.get_string('/desktop/sugar/user/color') jobject = datastore.create() try: jobject.metadata['title'] = _('Screenshot') jobject.metadata['keep'] = '0' jobject.metadata['buddies'] = '' jobject.metadata['preview'] = '' jobject.metadata['icon-color'] = color jobject.metadata['mime_type'] = 'image/png' jobject.file_path = file_path datastore.write(jobject, transfer_ownership=True) finally: jobject.destroy() del jobject def handle_frame(self): self._frame.notify_key_press() def handle_rotate(self): """ Handles rotation of the display (using xrandr) and of the d-pad. Notes: default mappings for keypad on MP KP_Up 80 KP_Right 85 KP_Down 88 KP_Left 83 """ states = [ 'normal', 'left', 'inverted', 'right'] keycodes = (80, 85, 88, 83, 80, 85, 88, 83) keysyms = ("KP_Up", "KP_Right", "KP_Down", "KP_Left") self._screen_rotation -= 1 self._screen_rotation %= 4 actual_keycodes = keycodes[self._screen_rotation:self._screen_rotation + 4] # code_pairs now contains a mapping of keycode -> keysym in the current # orientation code_pairs = zip(actual_keycodes, keysyms) # Using the mappings in code_pairs, we dynamically build up an xmodmap # command to rotate the dpad keys. argv = ['xmodmap'] for arg in [('-e', 'keycode %i = %s' % p) for p in code_pairs]: argv.extend(arg) # If either the xmodmap or xrandr command fails, check_call will fail # with CalledProcessError, which we raise. try: subprocess.check_call(argv) subprocess.check_call(['xrandr', '-o', states[self._screen_rotation]]) except OSError, e: if e.errno != errno.EINTR: raise def handle_quit_emulator(self): if os.environ.has_key('SUGAR_EMULATOR_PID'): pid = int(os.environ['SUGAR_EMULATOR_PID']) os.kill(pid, signal.SIGTERM) def focus_journal_search(self): bus = dbus.SessionBus() obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH) journal = dbus.Interface(obj, J_DBUS_INTERFACE) journal.FocusSearch() def handle_open_search(self): self.focus_journal_search() def _key_pressed_cb(self, grabber, keycode, state): key = grabber.get_key(keycode, state) logging.debug('_key_pressed_cb: %i %i %s' % (keycode, state, key)) if key: self._key_pressed = key self._keycode_pressed = keycode self._keystate_pressed = state action = _actions_table[key] if self._tabbing_handler.is_tabbing(): # Only accept window tabbing events, everything else # cancels the tabbing operation. if not action in ["next_window", "previous_window"]: self._tabbing_handler.stop() return True method = getattr(self, 'handle_' + action) method() return True else: # If this is not a registered key, then cancel tabbing. if self._tabbing_handler.is_tabbing(): if not grabber.is_modifier(keycode): self._tabbing_handler.stop() return True return False def _key_released_cb(self, grabber, keycode, state): if self._tabbing_handler.is_tabbing(): # We stop tabbing and switch to the new window as soon as the # modifier key is raised again. if grabber.is_modifier(keycode, mask=_TABBING_MODIFIER): self._tabbing_handler.stop() return True return False _instance = None def setup(frame): global _instance if _instance: del _instance _instance = KeyHandler(frame)