implement keyboard selection (#276)

A new screen immediately after language.
This commit is contained in:
Michael Hudson-Doyle 2018-02-08 10:37:22 +13:00 committed by GitHub
parent 3d5bb1a280
commit 1794ed53dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 34391 additions and 7 deletions

View File

@ -1,5 +1,7 @@
Welcome:
lang: en_US
Keyboard:
layout: en
Network:
accept-default: yes
Filesystem:

33600
kbdnames.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,15 @@ parts:
cut -d ' ' -f 2- > users-and-groups
stage:
- users-and-groups
kbdnames:
plugin: dump
build-packages:
- console-setup
- xkb-data-i18n
prepare: |
/usr/share/console-setup/kbdnames-maker /usr/share/console-setup/KeyboardNames.pl > kbdnames.txt
stage:
- kbdnames.gz
probert:
plugin: python
build-packages: [python-setuptools, libnl-3-dev, libnl-genl-3-dev, libnl-route-3-dev]

View File

@ -20,4 +20,5 @@ from .identity import IdentityController # NOQA
from .installpath import InstallpathController # NOQA
from .installprogress import InstallProgressController # NOQA
from .filesystem import FilesystemController # NOQA
from .keyboard import KeyboardController # NOQA
from .welcome import WelcomeController # NOQA

View File

@ -0,0 +1,67 @@
# Copyright 2015 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from subiquitycore.controller import BaseController
from subiquity.ui.views import KeyboardView
log = logging.getLogger('subiquity.controllers.keyboard')
class KeyboardController(BaseController):
signals = [
('l10n:language-selected', 'language_selected'),
]
def __init__(self, common):
super().__init__(common)
self.model = self.base_model.keyboard
self.answers = self.all_answers.get("Keyboard", {})
def language_selected(self, code):
log.debug("language_selected %s", code)
if not self.model.has_language(code):
code = code.split('_')[0]
if not self.model.has_language(code):
code = 'C'
log.debug("loading launguage %s", code)
self.model.load_language(code)
def default(self):
if self.model.current_lang is None:
self.model.load_language('C')
title = "Keyboard configuration"
if self.opts.run_on_serial:
excerpt = 'Please select the layout of the keyboard directly attached to the system, if any.'
else:
excerpt = 'Please select your keyboard layout below, or select "Identify keyboard" to detect your layout automatically.'
footer = _("Use UP, DOWN and ENTER keys to select your keyboard.")
self.ui.set_header(title, excerpt)
self.ui.set_footer(footer)
view = KeyboardView(self.model, self, self.opts)
self.ui.set_body(view)
if 'layout' in self.answers:
layout = self.answers['layout']
variant = self.answers.get('variant')
self.done(layout, variant)
def done(self, layout, variant):
self.model.set_keyboard(layout, variant)
self.signal.emit_signal('next-screen')
def cancel(self):
self.signal.emit_signal('prev-screen')

View File

@ -32,6 +32,7 @@ class Subiquity(Application):
controllers = [
"Welcome",
"Keyboard",
"Network",
"Filesystem",
"Identity",

View File

@ -0,0 +1,103 @@
from collections import defaultdict
import gzip
import io
import logging
import os
import re
from subiquitycore.utils import run_command
log = logging.getLogger("subiquity.models.keyboard")
etc_default_keyboard_template = """\
# KEYBOARD CONFIGURATION FILE
# Consult the keyboard(5) manual page.
XKBMODEL="pc105"
XKBLAYOUT="{layout}"
XKBVARIANT="{variant}"
XKBOPTIONS=""
BACKSPACE="guess"
"""
class KeyboardModel:
def __init__(self, root):
self.root = root
self.layout = 'us'
self.variant = ''
self._kbnames_file = os.path.join(os.environ.get("SNAP", '.'), 'kbdnames.txt')
self._clear()
if os.path.exists(self.config_path):
content = open(self.config_path).read()
pat_tmpl = '(?m)^\s*%s=(.*)$'
log.debug("%r", content)
layout_match = re.search(pat_tmpl%("XKBLAYOUT",), content)
if layout_match:
log.debug("%s", layout_match)
self.layout = layout_match.group(1).strip('"')
variant_match = re.search(pat_tmpl%("XKBVARIANT",), content)
if variant_match:
log.debug("%s", variant_match)
self.variant = variant_match.group(1).strip('"')
if self.variant == '':
self.variant = None
@property
def config_path(self):
return os.path.join(self.root, 'etc', 'default', 'keyboard')
@property
def config_content(self):
return etc_default_keyboard_template.format(layout=self.layout, variant=self.variant)
def has_language(self, code):
self.load_language(code)
return bool(self.layouts)
def load_language(self, code):
if code == self.current_lang:
return
self._clear()
with open(self._kbnames_file) as kbdnames:
self._load_file(code, kbdnames)
self.current_lang = code
def _clear(self):
self.current_lang = None
self.layouts = {}
self.variants = defaultdict(dict)
def _load_file(self, code, kbdnames):
for line in kbdnames:
line = line.rstrip('\n')
got_lang, element, name, value = line.split("*", 3)
if got_lang != code:
continue
if element == "layout":
self.layouts[name] = value
elif element == "variant":
variantname, variantdesc = value.split("*", 1)
self.variants[name][variantname] = variantdesc
def lookup(self, code):
if ':' in code:
layout_code, variant_code = code.split(":", 1)
return self.layouts.get(layout_code, '?'), self._variants.get(variant_code, '?')
else:
return self.layouts.get(code, '?'), None
def set_keyboard(self, layout, variant):
path = self.config_path
os.makedirs(os.path.dirname(path), exist_ok=True)
self.layout = layout
self.variant = variant
with open(path, 'w') as fp:
fp.write(self.config_content)
if self.root == '/':
run_command(['setupcon', '--save', '--force'])

View File

@ -25,6 +25,9 @@ class LocaleModel(object):
XXX Only represents *language* selection for now.
"""
def __init__(self, signal):
self.signal = signal
supported_languages = [
('en_US', 'English'),
('ca_EN', 'Catalan'),
@ -47,6 +50,7 @@ class LocaleModel(object):
def switch_language(self, code):
self.selected_language = code
self.signal.emit_signal('l10n:language-selected', code)
i18n.switch_language(code)
def __repr__(self):

View File

@ -21,6 +21,7 @@ from subiquitycore.models.identity import IdentityModel
from subiquitycore.models.network import NetworkModel
from .filesystem import FilesystemModel
from .keyboard import KeyboardModel
from .locale import LocaleModel
@ -28,7 +29,11 @@ class SubiquityModel:
"""The overall model for subiquity."""
def __init__(self, common):
self.locale = LocaleModel()
root = '/'
if common['opts'].dry_run:
root = os.path.abspath(".subiquity")
self.locale = LocaleModel(common['signal'])
self.keyboard = KeyboardModel(root)
self.network = NetworkModel()
self.filesystem = FilesystemModel(common['prober'])
self.identity = IdentityModel()
@ -102,6 +107,12 @@ class SubiquityModel:
}
if install_step == "install":
config.update(self.network.render())
config['write_files'] = {
'etc_default_keyboard': {
'path': 'etc/default/keyboard',
'content': self.keyboard.config_content,
},
}
else:
config['write_files'] = self._write_files_config()

View File

@ -28,4 +28,5 @@ from .lvm import LVMVolumeGroupView # NOQA
from .identity import IdentityView # NOQA
from .installpath import InstallpathView # NOQA
from .installprogress import ProgressView # NOQA
from .keyboard import KeyboardView
from .welcome import WelcomeView

View File

@ -0,0 +1,347 @@
# Copyright 2018 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from urwid import (
connect_signal,
LineBox,
Text,
WidgetWrap,
)
from subiquitycore.ui.buttons import ok_btn, other_btn
from subiquitycore.ui.container import (
Columns,
ListBox,
Pile,
)
from subiquitycore.ui.form import (
Form,
FormField,
)
from subiquitycore.ui.selector import Option, Selector
from subiquitycore.ui.utils import button_pile, Color, Padding
from subiquitycore.view import BaseView
from subiquity.ui.views import pc105
log = logging.getLogger("subiquity.ui.views.keyboard")
class AutoDetectBase(WidgetWrap):
def __init__(self, keyboard_detector, step):
# step is an instance of pc105.Step
self.keyboard_detector = keyboard_detector
self.step = step
lb = LineBox(self.make_body(), _("Keyboard auto-detection"))
super().__init__(lb)
def start(self):
pass
def stop(self):
pass
def keypress(self, size, key):
if key == 'esc':
self.keyboard_detector.backup()
else:
return super().keypress(size, key)
class AutoDetectIntro(AutoDetectBase):
def ok(self, sender):
self.keyboard_detector.do_step(0)
def cancel(self, sender):
self.keyboard_detector.abort()
def make_body(self):
return Pile([
Text(_("Keyboard detection starting. You will be asked a series of questions about your keyboard. Press escape at any time to go back to the previous screen.")),
Text(""),
button_pile([
ok_btn(label=_("OK"), on_press=self.ok),
ok_btn(label=_("Cancel"), on_press=self.cancel),
]),
])
class AutoDetectFailed(AutoDetectBase):
def ok(self, sender):
self.keyboard_detector.abort()
def make_body(self):
return Pile([
Text(_("Keyboard auto detection failed, sorry")),
Text(""),
button_pile([ok_btn(label="OK", on_press=self.ok)]),
])
class AutoDetectResult(AutoDetectBase):
preamble = _("""\
Keyboard auto detection completed.
Your keyboard was detected as:
""")
postamble = _("""\
If this is correct, select Done on the next screen. If not you can select \
another layout or run the automated detection again.
""")
def ok(self, sender):
self.keyboard_detector.keyboard_view.found_layout(self.step.result)
def make_body(self):
model = self.keyboard_detector.keyboard_view.model
layout, variant = model.lookup(self.step.result)
var_desc = []
if variant is not None:
var_desc = [Text(_(" Variant: ") + variant)]
return Pile([
Text(self.preamble),
Text(_(" Layout: ") + layout),
] + var_desc + [
Text(self.postamble),
button_pile([ok_btn(label=_("OK"), on_press=self.ok)]),
])
class AutoDetectPressKey(AutoDetectBase):
# This is the tricky case. We need access to the "keycodes" not
# the characters that the current keyboard set up maps the
# keycodes to. The heavy lifting is done by the InputFilter class
# in subiquitycore.core.
def selectable(self):
return True
def make_body(self):
self.error_text = Text("", align="center")
return Pile([
Text(_("Please press one of the following keys:")),
Text(""),
Columns([Text(s, align="center") for s in self.step.symbols], dividechars=1),
Text(""),
Color.info_error(self.error_text),
])
@property
def input_filter(self):
return self.keyboard_detector.keyboard_view.controller.input_filter
def start(self):
self.input_filter.enter_keycodes_mode()
def stop(self):
self.input_filter.exit_keycodes_mode()
def keypress(self, size, key):
log.debug('keypress %r %r', size, key)
if key.startswith('release '):
# Escape is key 1 on all keyboards and all layouts except
# amigas and very old Macs so this seems safe enough.
if key == 'release 1':
return super().keypress(size, 'esc')
else:
return
elif key.startswith('press '):
code = int(key[len('press '):])
if code not in self.step.keycodes:
self.error_text.set_text(_("Input was not recognized, try again"))
return
v = self.step.keycodes[code]
else:
# If we're not on a linux tty, the filtering won't have
# happened and so there's no way to get the keycodes. Do
# something literally random instead.
import random
v = random.choice(list(self.step.keycodes.values()))
self.keyboard_detector.do_step(v)
class AutoDetectKeyPresent(AutoDetectBase):
def yes(self, sender):
self.keyboard_detector.do_step(self.step.yes)
def no(self, sender):
self.keyboard_detector.do_step(self.step.no)
def make_body(self):
return Pile([
Text(_("Is the following key present on your keyboard?")),
Text(""),
Text(self.step.symbol, align="center"),
Text(""),
button_pile([
ok_btn(label=_("Yes"), on_press=self.yes),
other_btn(label=_("No"), on_press=self.no),
]),
])
class Detector:
# Encapsulates the state of the autodetection process.
def __init__(self, kview):
self.keyboard_view = kview
self.pc105tree = pc105.PC105Tree()
self.pc105tree.read_steps()
self.seen_steps = []
def start(self):
o = AutoDetectIntro(self, None)
self.keyboard_view.show_overlay(o)
def abort(self):
overlay = self.keyboard_view._w.top_w
overlay.stop()
self.keyboard_view.remove_overlay()
step_cls_to_view_cls = {
pc105.StepResult: AutoDetectResult,
pc105.StepPressKey: AutoDetectPressKey,
pc105.StepKeyPresent: AutoDetectKeyPresent,
}
def backup(self):
if len(self.seen_steps) == 0:
self.seen_steps = []
self.abort()
return
if len(self.seen_steps) == 1:
self.seen_steps = []
self.abort()
self.start()
return
self.seen_steps.pop()
step_index = self.seen_steps.pop()
self.do_step(step_index)
def do_step(self, step_index):
self.abort()
log.debug("moving to step %s", step_index)
try:
step = self.pc105tree.steps[step_index]
except KeyError:
view = AutoDetectFailed(self, None)
else:
self.seen_steps.append(step_index)
log.debug("step: %s", repr(step))
view = self.step_cls_to_view_cls[type(step)](self, step)
view.start()
self.keyboard_view.show_overlay(view)
class ChoiceField(FormField):
def __init__(self, caption=None, help=None, choices=[]):
super().__init__(caption, help)
self.choices = choices
def _make_widget(self, form):
return Selector(self.choices)
class KeyboardForm(Form):
cancel_label = _("Back")
layout = ChoiceField(_("Layout:"), choices=["dummy"])
variant = ChoiceField(_("Variant:"), choices=["dummy"])
class KeyboardView(BaseView):
def __init__(self, model, controller, opts):
self.model = model
self.controller = controller
self.opts = opts
self.form = KeyboardForm()
opts = []
for layout, desc in model.layouts.items():
opts.append(Option((desc, True, layout)))
opts.sort(key=lambda o:o.label)
connect_signal(self.form, 'submit', self.done)
connect_signal(self.form, 'cancel', self.cancel)
connect_signal(self.form.layout.widget, "select", self.select_layout)
self.form.layout.widget._options = opts
self.form.layout.widget.value = model.layout
self.form.variant.widget.value = model.variant
self._rows = self.form.as_rows(self)
lb_contents = [self._rows]
if not self.opts.run_on_serial:
lb_contents.extend([
Text(""),
button_pile([
other_btn(label=_("Identify keyboard"), on_press=self.detect)]),
])
lb = ListBox(lb_contents)
pile = Pile([
('pack', Text("")),
Padding.center_90(lb),
('pack', Pile([
Text(""),
self.form.buttons,
Text(""),
])),
])
lb._select_last_selectable()
pile.focus_position = 2
super().__init__(pile)
def detect(self, sender):
detector = Detector(self)
detector.start()
def found_layout(self, result):
self.remove_overlay()
log.debug("found_layout %s", result)
if ':' in result:
layout, variant = result.split(':')
else:
layout, variant = result, None
self.form.layout.widget.value = layout
self.form.variant.widget.value = variant
self._w.focus_position = 2
def done(self, result):
layout = self.form.layout.widget.value
variant = ''
if self.form.variant.widget.value is not None:
variant = self.form.variant.widget.value
self.controller.done(layout, variant)
def cancel(self, result=None):
self.controller.cancel()
def select_layout(self, sender, layout):
log.debug("%s", layout)
opts = []
for variant, variant_desc in self.model.variants[layout].items():
opts.append(Option((variant_desc, True, variant)))
opts.sort(key=lambda o:o.label)
opts.insert(0, Option(("default", True, None)))
self.form.variant.widget._options = opts
self.form.variant.widget.index = 0
self.form.variant.enabled = len(opts) > 1

133
subiquity/ui/views/pc105.py Normal file
View File

@ -0,0 +1,133 @@
# Copyright 2018 Canonical, Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# The keyboard autodetection process is driven by the data in
# /usr/share/console-setup/pc105.tree. This code parses that data into
# subclasses of Step.
class Step:
def __repr__(self):
kvs = []
for k, v in self.__dict__.items():
kvs.append("%s=%r" % (k, v))
return "%s(%s)" % (self.__class__.__name__, ", ".join(sorted(kvs)))
def check(self):
pass
class StepPressKey(Step):
# "Press one of the following keys"
def __init__(self):
self.symbols = []
self.keycodes = {}
def check(self):
if len(self.symbols) == 0 or len(self.keycodes) == 0:
raise Exception
class StepKeyPresent(Step):
# "Is this symbol present on your keyboard"
def __init__(self, symbol):
self.symbol = symbol
self.yes = None
self.no = None
def check(self):
if self.yes is None or self.no is None:
raise Exception
class StepResult(Step):
# "This is the autodetected layout"
def __init__(self, result):
self.result = result
class PC105Tree:
"""Parses the pc105.tree file into subclasses of Step"""
# This is adapted (quite heavily) from the code in ubiquity.
def __init__(self):
self.steps = {}
def _add_step_from_lines(self, lines):
step = None
step_index = -1
for line in lines:
if line.startswith('STEP '):
step_index = int(line[5:])
elif line.startswith('PRESS '):
# Ask the user to press a character on the keyboard.
if step is None:
step = StepPressKey()
elif not isinstance(step, StepPressKey):
raise Exception
step.symbols.append(line[6:].strip())
elif line.startswith('CODE '):
# Direct the evaluating code to process step ## next if the
# user has pressed a key which returned that keycode.
if not isinstance(step, StepPressKey):
raise Exception
keycode = int(line[5:line.find(' ', 5)])
s = int(line[line.find(' ', 5) + 1:])
step.keycodes[keycode] = s
elif line.startswith('FIND '):
# Ask the user whether that character is present on their
# keyboard.
if step is None:
step = StepKeyPresent(line[5:].strip())
else:
raise Exception
elif line.startswith('FINDP '):
# Equivalent to FIND, except that the user is asked to consider
# only the primary symbols (i.e. Plain and Shift).
if step is None:
step = StepKeyPresent(line[6:].strip())
else:
raise Exception
elif line.startswith('YES '):
# Direct the evaluating code to process step ## next if the
# user does have this key.
if not isinstance(step, StepKeyPresent):
raise Exception
step.yes = int(line[4:].strip())
elif line.startswith('NO '):
# Direct the evaluating code to process step ## next if the
# user does not have this key.
if not isinstance(step, StepKeyPresent):
raise Exception
step.no = int(line[3:].strip())
elif line.startswith('MAP '):
# This step uniquely identifies a keymap.
if step is None:
step = StepResult(line[4:].strip())
else:
raise Exception
else:
raise Exception
if step is None or step_index == -1:
raise Exception
step.check()
self.steps[step_index] = step
def read_steps(self):
cur_step_lines = []
with open('/usr/share/console-setup/pc105.tree') as fp:
for line in fp:
if line.startswith('STEP '):
if cur_step_lines:
self._add_step_from_lines(cur_step_lines)
cur_step_lines = [line]
else:
cur_step_lines.append(line)
if cur_step_lines:
self._add_step_from_lines(cur_step_lines)

View File

@ -36,6 +36,7 @@ class BaseController(ABC):
self.pool = common['pool']
self.base_model = common['base_model']
self.all_answers = common['answers']
self.input_filter = common['input_filter']
def register_signals(self):
"""Defines signals associated with controller from model."""

View File

@ -16,7 +16,11 @@
from concurrent import futures
import fcntl
import logging
import os
import struct
import sys
import termios
import tty
import urwid
import yaml
@ -31,15 +35,23 @@ class ApplicationError(Exception):
""" Basecontroller exception """
pass
# The next little bit is cribbed from
# https://github.com/EvanPurkhiser/linux-vt-setcolors/blob/master/setcolors.c:
# From uapi/linux/kd.h:
KDGKBTYPE = 0x4B33 # get keyboard type
GIO_CMAP = 0x4B70 # gets colour palette on VGA+
PIO_CMAP = 0x4B71 # sets colour palette on VGA+
UO_R, UO_G, UO_B = 0xe9, 0x54, 0x20
# /usr/include/linux/kd.h
K_RAW = 0x00
K_XLATE = 0x01
K_MEDIUMRAW = 0x02
K_UNICODE = 0x03
K_OFF = 0x04
KDGKBMODE = 0x4B44 # gets current keyboard mode
KDSKBMODE = 0x4B45 # sets current keyboard mode
class ISO_8613_3_Screen(urwid.raw_display.Screen):
@ -116,6 +128,92 @@ def setup_screen(colors, styles):
return ISO_8613_3_Screen(_urwid_name_to_rgb), urwid_palette
class KeyCodesFilter:
"""input_filter that can pass (medium) raw keycodes to the application
See http://lct.sourceforge.net/lct/x60.html for terminology.
Call enter_keycodes_mode()/exit_keycodes_mode() to switch into and
out of keycodes mode. In keycodes mode, the only events passed to
the application are "press $N" / "release $N" where $N is the
keycode the user pressed or released.
Much of this is cribbed from the source of the "showkeys" utility.
"""
def __init__(self):
self._fd = os.open("/proc/self/fd/0", os.O_RDWR)
self.filtering = False
def enter_keycodes_mode(self):
log.debug("enter_keycodes_mode")
self.filtering = True
# Read the old keyboard mode (it will proably always be K_UNICODE but well).
o = bytearray(4)
fcntl.ioctl(self._fd, KDGKBMODE, o)
self._old_mode = struct.unpack('i', o)[0]
# Make some changes to the terminal settings.
# If you don't do this, sometimes writes to the terminal hang (and no,
# I don't know exactly why).
self._old_settings = termios.tcgetattr(self._fd)
new_settings = termios.tcgetattr(self._fd)
new_settings[tty.IFLAG] = 0
new_settings[tty.LFLAG] = new_settings[tty.LFLAG] & ~(termios.ECHO | termios.ICANON | termios.ISIG)
new_settings[tty.CC][termios.VMIN] = 0
new_settings[tty.CC][termios.VTIME] = 0
termios.tcsetattr(self._fd, termios.TCSAFLUSH, new_settings)
# Finally, set the keyboard mode to K_MEDIUMRAW, which causes
# the keyboard driver in the kernel to pass us keycodes.
log.debug("old mode was %s, setting mode to %s", self._old_mode, K_MEDIUMRAW)
fcntl.ioctl(self._fd, KDSKBMODE, K_MEDIUMRAW)
def exit_keycodes_mode(self):
log.debug("exit_keycodes_mode")
self.filtering = False
log.debug("setting mode back to %s", self._old_mode)
fcntl.ioctl(self._fd, KDSKBMODE, self._old_mode)
termios.tcsetattr(self._fd, termios.TCSANOW, self._old_settings)
def filter(self, keys, codes):
# Luckily urwid passes us the raw results from read() we can
# turn into keycodes.
if self.filtering:
i = 0
r = []
n = len(codes)
while i < len(codes):
# This is straight from showkeys.c.
if codes[i] & 0x80:
p = 'release '
else:
p = 'press '
if i + 2 < n and (codes[i] & 0x7f) == 0 and (codes[i + 1] & 0x80) != 0 and (codes[i + 2] & 0x80) != 0:
kc = ((codes[i + 1] & 0x7f) << 7) | (codes[i + 2] & 0x7f)
i += 3
else:
kc = codes[i] & 0x7f
i += 1
r.append(p + str(kc))
return r
else:
return keys
class DummyKeycodesFilter:
# A dummy implementation of the same interface as KeyCodesFilter
# we can use when not running in a linux tty.
def enter_keycodes_mode(self):
pass
def exit_keycodes_mode(self):
pass
def filter(self, keys, codes):
return keys
class Application:
# A concrete subclass must set project and controllers attributes, e.g.:
@ -151,6 +249,12 @@ class Application:
if not opts.dry_run:
open('/run/casper-no-prompt', 'w').close()
if is_linux_tty():
log.debug("is_linux_tty")
input_filter = KeyCodesFilter()
else:
input_filter = DummyKeycodesFilter()
self.common = {
"ui": ui,
"opts": opts,
@ -159,6 +263,7 @@ class Application:
"loop": None,
"pool": futures.ThreadPoolExecutor(1),
"answers": answers,
"input_filter": input_filter,
}
if opts.screens:
self.controllers = [c for c in self.controllers if c in opts.screens]
@ -226,12 +331,11 @@ class Application:
self.common['loop'] = urwid.MainLoop(
self.common['ui'], palette=palette, screen=screen,
handle_mouse=False, pop_ups=True)
handle_mouse=False, pop_ups=True, input_filter=self.common['input_filter'].filter)
log.debug("Running event loop: {}".format(
self.common['loop'].event_loop))
self.common['base_model'] = self.model_class(self.common)
try:
self.common['loop'].set_alarm_in(0.05, self.next_screen)
controllers_mod = __import__('%s.controllers' % self.project, None, None, [''])