Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions oec/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,27 @@
IS_VT100_AVAILABLE = True

from .keymap_3278_typewriter import KEYMAP as KEYMAP_3278_TYPEWRITER
from .keymap_3278_typewriter_de import KEYMAP as KEYMAP_3278_TYPEWRITER_DE
from .keymap_ibm_typewriter import KEYMAP as KEYMAP_IBM_TYPEWRITER
from .keymap_ibm_enhanced import KEYMAP as KEYMAP_IBM_ENHANCED

logging.basicConfig(level=logging.INFO)
_LOG_LEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
}

logger = logging.getLogger('oec.main')

def _get_keymap(_args, keyboard_description):
KEYMAP_3278_LANGUAGE = {
'us': KEYMAP_3278_TYPEWRITER,
'de': KEYMAP_3278_TYPEWRITER_DE
}

def _get_keymap(args, keyboard_description):
if keyboard_description.startswith('3278'):
return KEYMAP_3278_TYPEWRITER
return KEYMAP_3278_LANGUAGE.get(args.keyboard_language, KEYMAP_3278_TYPEWRITER)

if keyboard_description.startswith('IBM-TYPEWRITER'):
return KEYMAP_IBM_TYPEWRITER
Expand Down Expand Up @@ -71,11 +82,14 @@ def _create_device(args, interface, device_address, _poll_response):

terminal = Terminal(interface, device_address, terminal_id, extended_id, features, keymap)

if args.clicker:
terminal.keyboard.clicker = True

return terminal

def _create_session(args, device):
if args.emulator == 'tn3270':
return TN3270Session(device, args.host, args.port, args.device_names, args.character_encoding, args.tn3270e_profile)
return TN3270Session(device, args.host, args.port, args.device_names, args.character_encoding, args.tn3270e_profile, args.ssl, args.no_starttls, args.ssl_no_verify, args.no_hostname_status)

if args.emulator == 'vt100' and IS_VT100_AVAILABLE:
host_command = [args.command, *args.command_args]
Expand All @@ -88,6 +102,8 @@ def _create_session(args, device):
def main():
args = parse_args(sys.argv[1:], IS_VT100_AVAILABLE)

logging.basicConfig(level=_LOG_LEVELS[args.log_level])

def create_device(interface, device_address, poll_response):
return _create_device(args, interface, device_address, poll_response)

Expand Down
26 changes: 26 additions & 0 deletions oec/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ def parse_args(args, is_vt100_available):

parser.add_argument('serial_port', help='serial port')

parser.add_argument('--log-level', choices=['debug', 'info', 'warning', 'error'],
default='info', dest='log_level',
help='logging level (default: info)')

parser.add_argument('--keyboard-language', choices=['us', 'de'], default='us',
dest='keyboard_language',
help='keyboard national layout (default: us)')

parser.add_argument('--clicker', action='store_true', default=False,
help='enable keyboard clicker')

subparsers = parser.add_subparsers(dest='emulator', required=True,
description='emulator')

Expand All @@ -28,6 +39,21 @@ def parse_args(args, is_vt100_available):
dest='character_encoding', type=get_character_encoding,
help='host EBCDIC code page')

tn3270_parser.add_argument('--ssl', action='store_true', default=False,
help='enable implicit SSL/TLS')

tn3270_parser.add_argument('--no-starttls', action='store_true', default=False,
dest='no_starttls',
help='disable STARTTLS negotiation')

tn3270_parser.add_argument('--ssl-no-verify', action='store_true', default=False,
dest='ssl_no_verify',
help='disable SSL/TLS certificate verification')

tn3270_parser.add_argument('--no-hostname-status', action='store_true', default=False,
dest='no_hostname_status',
help='do not display host name on status line')

tn3270_parser.add_argument('--tn3270e', choices=['off', 'basic', 'default'],
metavar='profile', default='default',
dest='tn3270e_profile',
Expand Down
7 changes: 7 additions & 0 deletions oec/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ def _select_sessions(self, duration):
if not self.session_selector.get_map():
return []

# Check for sessions with SSL pending data first - select() won't
# report these as readable since the data is buffered in the SSL layer.
pending_sessions = [key.fileobj for key in self.session_selector.get_map().values() if key.fileobj.has_pending_data()]

if pending_sessions:
return pending_sessions

selected = self.session_selector.select(duration)

return [key.fileobj for (key, _) in selected]
Expand Down
8 changes: 8 additions & 0 deletions oec/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ def _get_dirty_ranges(self):
return [(self.dirty[0], self.dirty[-1])]

CHAR_MAP = {
# GE/APL plane (0x40-0x7F) - multinational accented characters
'ä': 0x50,
'ö': 0x53,
'ü': 0x54,
'Ä': 0x70,
'Ö': 0x73,
'Ü': 0x74,

'>': 0x08,
'<': 0x09,
'[': 0x0a,
Expand Down
16 changes: 15 additions & 1 deletion oec/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,17 @@ class Key(Enum):
SLASH = ord('/')
QUESTION = ord('?')

# German
ESZETT = ord('ß')
SECTION = ord('§')
CARET = ord('^')
LOWER_A_UMLAUT = ord('ä')
UPPER_A_UMLAUT = ord('Ä')
LOWER_O_UMLAUT = ord('ö')
UPPER_O_UMLAUT = ord('Ö')
LOWER_U_UMLAUT = ord('ü')
UPPER_U_UMLAUT = ord('Ü')

KEY_UPPER_MAP = {
Key.LOWER_A: Key.UPPER_A,
Key.LOWER_B: Key.UPPER_B,
Expand All @@ -254,7 +265,10 @@ class Key(Enum):
Key.LOWER_W: Key.UPPER_W,
Key.LOWER_X: Key.UPPER_X,
Key.LOWER_Y: Key.UPPER_Y,
Key.LOWER_Z: Key.UPPER_Z
Key.LOWER_Z: Key.UPPER_Z,
Key.LOWER_A_UMLAUT: Key.UPPER_A_UMLAUT,
Key.LOWER_O_UMLAUT: Key.UPPER_O_UMLAUT,
Key.LOWER_U_UMLAUT: Key.UPPER_U_UMLAUT
}

KEY_LOWER_MAP = {upper_key: lower_key for lower_key, upper_key in KEY_UPPER_MAP.items()}
Expand Down
32 changes: 30 additions & 2 deletions oec/keymap_3278_typewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,21 @@
14: Key.UP,
19: Key.DOWN,
22: Key.LEFT,
26: Key.RIGHT
26: Key.RIGHT,

# PF keys on APL keyboard
64: Key.PF1,
65: Key.PF2,
66: Key.PF3,
67: Key.PF4,
68: Key.PF5,
69: Key.PF6,
70: Key.PF7,
71: Key.PF8,
72: Key.PF9,
73: Key.PF10,
74: Key.PF11,
75: Key.PF12
}

KEYMAP_SHIFT = {
Expand Down Expand Up @@ -153,7 +167,21 @@
108: Key.UPPER_M,
51: Key.COMMA, # TODO: Confirm this mapping
50: Key.CENTER_PERIOD,
20: Key.QUESTION
20: Key.QUESTION,

# PF keys on APL keyboard
64: Key.PF13,
65: Key.PF14,
66: Key.PF15,
67: Key.PF16,
68: Key.PF17,
69: Key.PF18,
70: Key.PF19,
71: Key.PF20,
72: Key.PF21,
73: Key.PF22,
74: Key.PF23,
75: Key.PF24
}

KEYMAP_ALT = {
Expand Down
66 changes: 66 additions & 0 deletions oec/keymap_3278_typewriter_de.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
oec.keymap_3278_typewriter_de
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""

from .keyboard import Key, Keymap
from .keymap_3278_typewriter import KEYMAP_DEFAULT as _US_DEFAULT, KEYMAP_SHIFT as _US_SHIFT, \
KEYMAP_ALT, MODIFIER_RELEASE_MAP

KEYMAP_DEFAULT = {
**_US_DEFAULT,

# First Row
48: Key.ESZETT, # was MINUS
17: Key.SINGLE_QUOTE, # was EQUAL

# Second Row
120: Key.LOWER_Z, # was LOWER_Y
27: Key.LOWER_U_UMLAUT, # was CENT
21: Key.PLUS, # was BACKSLASH

# Third Row
126: Key.LOWER_O_UMLAUT, # was SEMICOLON
18: Key.LOWER_A_UMLAUT, # was SINGLE_QUOTE
15: Key.HASH, # was LEFT_BRACE

# Fourth Row
121: Key.LOWER_Y, # was LOWER_Z
51: Key.COMMA, # unchanged
50: Key.PERIOD, # unchanged
20: Key.MINUS, # was SLASH
}

KEYMAP_SHIFT = {
**_US_SHIFT,

# First Row - number row
33: Key.EXCLAMATION, # was BAR
34: Key.DOUBLE_QUOTE, # was AT
35: Key.SECTION, # was HASH
38: Key.AMPERSAND, # was NOT
39: Key.SLASH, # was AMPERSAND
40: Key.LEFT_PAREN, # was ASTERISK
41: Key.RIGHT_PAREN, # was LEFT_PAREN
32: Key.EQUAL, # was RIGHT_PAREN
48: Key.QUESTION, # was UNDERSCORE
17: Key.BACKTICK, # was PLUS

# Second Row
120: Key.UPPER_Z, # was UPPER_Y
27: Key.UPPER_U_UMLAUT, # was EXCLAMATION
21: Key.ASTERISK, # was BROKEN_BAR

# Third Row
126: Key.UPPER_O_UMLAUT, # was COLON
18: Key.UPPER_A_UMLAUT, # was DOUBLE_QUOTE
15: Key.CARET, # was RIGHT_BRACE

# Fourth Row
121: Key.UPPER_Y, # was UPPER_Z
51: Key.SEMICOLON, # was COMMA
50: Key.COLON, # was CENTER_PERIOD
20: Key.UNDERSCORE, # was QUESTION
}

KEYMAP = Keymap('3278 Typewriter (DE)', KEYMAP_DEFAULT, KEYMAP_SHIFT, KEYMAP_ALT, MODIFIER_RELEASE_MAP)
3 changes: 3 additions & 0 deletions oec/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def terminate(self):
def fileno(self):
raise NotImplementedError

def has_pending_data(self):
return False

def handle_host(self):
raise NotImplementedError

Expand Down
11 changes: 9 additions & 2 deletions oec/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
~~~~~~~~~~~~
"""

import logging

from coax import LoadControlRegister, Feature, PollAction, Control

from .device import Device, UnsupportedDeviceError
from .display import Dimensions, BufferedDisplay

logger = logging.getLogger(__name__)
from .keyboard import Keyboard

MODEL_DIMENSIONS = {
Expand Down Expand Up @@ -50,8 +54,9 @@ def setup(self):

self.display.clear(clear_status_line=True)

# Show the attached indicator on the status line.
self.display.status_line.write_string(0, 'OEC')
# Show the attached indicator on the status line, padded to full width.
columns = self.display.status_line.columns
self.display.status_line.write_string(0, 'OEC'.ljust(columns))

self.display.move_cursor(row=0, column=0)

Expand All @@ -62,6 +67,7 @@ def get_poll_action(self):
# Convert a queued alarm or keyboard clicker change to POLL action.
if self.alarm:
poll_action = PollAction.ALARM
logger.debug('Alarm delivered via POLL')

self.alarm = False
elif self.keyboard.clicker != self.last_poll_keyboard_clicker:
Expand All @@ -76,6 +82,7 @@ def get_poll_action(self):

def sound_alarm(self):
"""Queue an alarm on next POLL command."""
logger.debug('Alarm queued')
self.alarm = True

def load_control_register(self):
Expand Down
Loading