diff --git a/src/fenrirscreenreader/core/settingsManager.py b/src/fenrirscreenreader/core/settingsManager.py index 897e2d12..94801155 100644 --- a/src/fenrirscreenreader/core/settingsManager.py +++ b/src/fenrirscreenreader/core/settingsManager.py @@ -49,33 +49,35 @@ class settingsManager(): def getBindingBackup(self): return self.bindingsBackup.copy() def loadSoundIcons(self, soundIconPath): - siConfig = open(soundIconPath + '/soundicons.conf',"r") - while(True): - line = siConfig.readline() - if not line: - break - line = line.replace('\n','') - if line.replace(" ","") == '': - continue - if line.replace(" ","").startswith("#"): - continue - if line.count("=") != 1: - continue - Values = line.split('=') - soundIcon = Values[0].upper() - Values[1] = Values[1].replace("'","") - Values[1] = Values[1].replace('"',"") - soundIconFile = '' - if os.path.exists(Values[1]): - soundIconFile = Values[1] - else: - if not soundIconPath.endswith("/"): - soundIconPath += '/' - if os.path.exists(soundIconPath + Values[1]): - soundIconFile = soundIconPath + Values[1] - self.env['soundIcons'][soundIcon] = soundIconFile - self.env['runtime']['debug'].writeDebugOut("SoundIcon: " + soundIcon + '.' + soundIconFile, debug.debugLevel.INFO, onAnyLevel=True) - siConfig.close() + try: + with open(soundIconPath + '/soundicons.conf', "r") as siConfig: + while(True): + line = siConfig.readline() + if not line: + break + line = line.replace('\n','') + if line.replace(" ","") == '': + continue + if line.replace(" ","").startswith("#"): + continue + if line.count("=") != 1: + continue + Values = line.split('=') + soundIcon = Values[0].upper() + Values[1] = Values[1].replace("'","") + Values[1] = Values[1].replace('"',"") + soundIconFile = '' + if os.path.exists(Values[1]): + soundIconFile = Values[1] + else: + if not soundIconPath.endswith("/"): + soundIconPath += '/' + if os.path.exists(soundIconPath + Values[1]): + soundIconFile = soundIconPath + Values[1] + self.env['soundIcons'][soundIcon] = soundIconFile + self.env['runtime']['debug'].writeDebugOut("SoundIcon: " + soundIcon + '.' + soundIconFile, debug.debugLevel.INFO, onAnyLevel=True) + except (IOError, OSError) as e: + self.env['runtime']['debug'].writeDebugOut('loadSoundIcons: failed to load sound icons from ' + soundIconPath + '. Error: ' + str(e), debug.debugLevel.ERROR) def getSettingsFile(self): return self.settingsFile def setSettingsFile(self, settingsFile): diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 104d931a..c59ee889 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributers. -version = "2025.06.16" +version = "2025.06.18" codeName = "testing" diff --git a/src/fenrirscreenreader/inputDriver/atspiDriver.py b/src/fenrirscreenreader/inputDriver/atspiDriver.py deleted file mode 100644 index 4f2650af..00000000 --- a/src/fenrirscreenreader/inputDriver/atspiDriver.py +++ /dev/null @@ -1,351 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Fenrir TTY screen reader -# By Chrys, Storm Dragon, and contributers. - -_evdevAvailable = False -_udevAvailable = False -_evdevAvailableError = '' -_udevAvailableError = '' - -try: - import gi - from gi.repository import GLib - gi.require_version('Gst', '1.0') - from gi.repository import Gst - gi.require_version('Atspi', '2.0') - import pyatspi - available = True -except Exception as e: - _availableError = str(e) - -import time - -from fenrirscreenreader.core.eventData import fenrirEventType -from fenrirscreenreader.core import inputData -from fenrirscreenreader.core import debug -from fenrirscreenreader.core.inputDriver import inputDriver - -''' -def on_key_input(event): - print(event) -mainloop = GLib.MainLoop() -thread = threading.Thread(target=mainloop.run) -thread.start() -#pyatspi.Registry.registerKeystrokeListener(on_key_input, kind=(pyatspi.KEY_PRESSED_EVENT, pyatspi.KEY_RELEASED_EVENT)) -pyatspi.Registry.registerKeystrokeListener(on_key_input,mask=pyatspi.allModifiers(), kind=(pyatspi.KEY_PRESS,pyatspi.KEY_RELEASE,pyatspi.KEY_PRESSRELEASE), synchronous=True, preemptive=True) -pyatspi.Registry.start() -''' - - -class driver(inputDriver): - def __init__(self): - inputDriver.__init__(self) - def initialize(self, environment): - self.env = environment - self.env['runtime']['inputManager'].setShortcutType('KEY') - global _available - self._initialized = _available - if not self._initialized: - global _availableError - currError = ' ' - if not _evdevAvailable: - currError += _availableError - self.env['runtime']['debug'].writeDebugOut('InputDriver:' + currError, debug.debugLevel.ERROR) - return - - self.env['runtime']['processManager'].addCustomEventThread(self.inputWatchdog) - - - def inputWatchdog(self,active , eventQueue): - try: - while active.value: - r, w, x = select(self.iDevices, [], [], 0.7) - for fd in r: - event = None - foreward = False - eventFired = False - try: - event = self.iDevices[fd].read_one() - except: - self.removeDevice(fd) - while(event): - self.env['input']['eventBuffer'].append( [self.iDevices[fd], self.uDevices[fd], event]) - if event.type == evdev.events.EV_KEY: - if event.code != 0: - currMapEvent = self.mapEvent(event) - if not currMapEvent: - foreward = True - if not isinstance(currMapEvent['EventName'], str): - foreward = True - if not foreward or eventFired: - if currMapEvent['EventState'] in [0,1,2]: - eventQueue.put({"Type":fenrirEventType.KeyboardInput,"Data":currMapEvent.copy()}) - eventFired = True - else: - if not event.type in [0,4]: - foreward = True - - event = self.iDevices[fd].read_one() - if foreward and not eventFired: - self.writeEventBuffer() - self.clearEventBuffer() - except Exception as e: - self.env['runtime']['debug'].writeDebugOut("INPUT WATCHDOG CRASH: "+str(e),debug.debugLevel.ERROR) - - def writeEventBuffer(self): - if not self._initialized: - return - for iDevice, uDevice, event in self.env['input']['eventBuffer']: - try: - if uDevice: - if self.gDevices[iDevice.fd]: - self.writeUInput(uDevice, event) - except Exception as e: - pass - - def writeUInput(self, uDevice, event): - if not self._initialized: - return - uDevice.write_event(event) - uDevice.syn() - - def updateInputDevices(self, newDevices = None, init = False): - if init: - self.removeAllDevices() - - deviceFileList = None - - if newDevices and not init: - deviceFileList = newDevices - else: - deviceFileList = evdev.list_devices() - if len(deviceFileList) == self.iDeviceNo: - return - if not deviceFileList: - return - - mode = self.env['runtime']['settingsManager'].getSetting('keyboard', 'device').upper() - - iDevicesFiles = [] - for device in self.iDevices: - iDevicesFiles.append(self.iDevices[device].fn) - - eventType = evdev.events - for deviceFile in deviceFileList: - try: - if not deviceFile: - continue - if deviceFile == '': - continue - if deviceFile in iDevicesFiles: - continue - try: - with open(deviceFile) as f: - pass - except Exception as e: - continue - # 3 pos absolute - # 2 pos relative - # 1 Keys - try: - currDevice = evdev.InputDevice(deviceFile) - except: - continue - try: - if currDevice.name.upper() in ['','SPEAKUP','PY-EVDEV-UINPUT']: - continue - if currDevice.phys.upper() in ['','SPEAKUP','PY-EVDEV-UINPUT']: - continue - if 'BRLTTY' in currDevice.name.upper(): - continue - except: - pass - cap = currDevice.capabilities() - if mode in ['ALL','NOMICE']: - if eventType.EV_KEY in cap: - if 116 in cap[eventType.EV_KEY] and len(cap[eventType.EV_KEY]) < 10: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (has 116):' + currDevice.name,debug.debugLevel.INFO) - continue - if len(cap[eventType.EV_KEY]) < 60: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (< 60 keys):' + currDevice.name,debug.debugLevel.INFO) - continue - if mode == 'ALL': - self.addDevice(currDevice) - self.env['runtime']['debug'].writeDebugOut('Device added (ALL):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO) - elif mode == 'NOMICE': - if not ((eventType.EV_REL in cap) or (eventType.EV_ABS in cap)): - self.addDevice(currDevice) - self.env['runtime']['debug'].writeDebugOut('Device added (NOMICE):' + self.iDevices[currDevice.fd].name,debug.debugLevel.INFO) - else: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (NOMICE):' + currDevice.name,debug.debugLevel.INFO) - else: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (no EV_KEY):' + currDevice.name,debug.debugLevel.INFO) - elif currDevice.name.upper() in mode.split(','): - self.addDevice(currDevice) - self.env['runtime']['debug'].writeDebugOut('Device added (Name):' + self.iDevices[currDevice.fd].name,debug.debugLevel.INFO) - except Exception as e: - self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile +' ' + currDevice.name +' '+ str(e),debug.debugLevel.INFO) - self.iDeviceNo = len(evdev.list_devices()) - self.updateMPiDevicesFD() - - def updateMPiDevicesFD(self): - try: - for fd in self.iDevices: - if not fd in self.iDevicesFD: - self.iDevicesFD.append(fd) - for fd in self.iDevicesFD: - if not fd in self.iDevices: - self.iDevicesFD.remove(fd) - except: - pass - def mapEvent(self, event): - if not self._initialized: - return None - if not event: - return None - mEvent = inputData.inputEvent - try: - mEvent['EventName'] = evdev.ecodes.keys[event.code] - mEvent['EventValue'] = event.code - mEvent['EventSec'] = event.sec - mEvent['EventUsec'] = event.usec - mEvent['EventState'] = event.value - mEvent['EventType'] = event.type - return mEvent - except Exception as e: - return None - - def getLedState(self, led = 0): - if not self.hasIDevices(): - return False - # 0 = Numlock - # 1 = Capslock - # 2 = Rollen - for fd, dev in self.iDevices.items(): - if led in dev.leds(): - return True - return False - def toggleLedState(self, led = 0): - if not self.hasIDevices(): - return False - ledState = self.getLedState(led) - for i in self.iDevices: - # 17 LEDs - if 17 in self.iDevices[i].capabilities(): - if ledState == 1: - self.iDevices[i].set_led(led , 0) - else: - self.iDevices[i].set_led(led , 1) - def grabAllDevices(self): - if not self._initialized: - return - for fd in self.iDevices: - self.grabDevice(fd) - - def ungrabAllDevices(self): - if not self._initialized: - return - for fd in self.iDevices: - self.ungrabDevice(fd) - - def createUInputDev(self, fd): - if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): - self.uDevices[fd] = None - return - try: - test = self.uDevices[fd] - return - except KeyError: - self.uDevices[fd] = None - if self.uDevices[fd] != None: - return - try: - self.uDevices[fd] = UInput.from_device(self.iDevices[fd]) - except Exception as e: - try: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: compat fallback: ' + str(e),debug.debugLevel.WARNING) - dev = self.iDevices[fd] - cap = dev.capabilities() - del cap[0] - self.uDevices[fd] = UInput( - cap, - dev.name, - ) - except Exception as e: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: init Uinput not possible: ' + str(e),debug.debugLevel.ERROR) - return - def addDevice(self, newDevice): - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' +str(newDevice),debug.debugLevel.INFO) - self.iDevices[newDevice.fd] = newDevice - self.gDevices[newDevice.fd] = False - self.createUInputDev(newDevice.fd) - def grabDevice(self, fd): - if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): - return - try: - self.iDevices[fd].grab() - self.gDevices[fd] = True - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO) - except IOError: - self.gDevices[fd] = True - except Exception as e: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(e),debug.debugLevel.ERROR) - def ungrabDevice(self,fd): - if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): - return - try: - self.gDevices[fd] = False - self.iDevices[fd].ungrab() - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO) - except: - pass - def removeDevice(self,fd): - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' +str(self.iDevices[fd]),debug.debugLevel.INFO) - self.clearEventBuffer() - try: - self.ungrabDevice(fd) - except: - pass - try: - self.iDevices[fd].close() - except: - pass - try: - self.uDevices[fd].close() - except: - pass - try: - del(self.iDevices[fd]) - except: - pass - try: - del(self.uDevices[fd]) - except: - pass - try: - del(self.gDevices[fd]) - except: - pass - self.updateMPiDevicesFD() - - def hasIDevices(self): - if not self._initialized: - return False - if not self.iDevices: - return False - if len(self.iDevices) == 0: - return False - return True - - def removeAllDevices(self): - if not self.hasIDevices(): - return - devices = self.iDevices.copy() - for fd in devices: - self.removeDevice(fd) - self.iDevices.clear() - self.uDevices.clear() - self.gDevices.clear() - self.iDeviceNo = 0 diff --git a/src/fenrirscreenreader/inputDriver/evdevDriver.py b/src/fenrirscreenreader/inputDriver/evdevDriver.py index 701a5d9f..3c880cf6 100644 --- a/src/fenrirscreenreader/inputDriver/evdevDriver.py +++ b/src/fenrirscreenreader/inputDriver/evdevDriver.py @@ -24,6 +24,7 @@ from select import select import multiprocessing from multiprocessing.sharedctypes import Value from ctypes import c_bool +import threading from fenrirscreenreader.core.eventData import fenrirEventType from fenrirscreenreader.core import inputData @@ -41,6 +42,7 @@ class driver(inputDriver): self.iDeviceNo = 0 self.watchDog = Value(c_bool, True) self.UInputinject = UInput() + self._deviceLock = threading.Lock() def initialize(self, environment): self.env = environment self.env['runtime']['inputManager'].setShortcutType('KEY') @@ -101,29 +103,45 @@ class driver(inputDriver): def inputWatchdog(self, active, eventQueue): try: while active.value: - r, w, x = select(self.iDevices, [], [], 0.8) + # Get a snapshot of devices for select() to avoid lock contention + with self._deviceLock: + devices_snapshot = self.iDevices.copy() + + if not devices_snapshot: + time.sleep(0.1) + continue + + r, w, x = select(devices_snapshot, [], [], 0.8) event = None foundKeyInSequence = False foreward = False eventFired = False for fd in r: + # Check if device still exists before accessing + with self._deviceLock: + if fd not in self.iDevices: + continue + device = self.iDevices[fd] + udevice = self.uDevices.get(fd) + try: - event = self.iDevices[fd].read_one() + event = device.read_one() except: self.removeDevice(fd) + continue while(event): self.env['runtime']['debug'].writeDebugOut('inputWatchdog: EVENT:' + str(event), debug.debugLevel.INFO) - self.env['input']['eventBuffer'].append([self.iDevices[fd], self.uDevices[fd], event]) + self.env['input']['eventBuffer'].append([device, udevice, event]) if event.type == evdev.events.EV_KEY: if not foundKeyInSequence: foundKeyInSequence = True if event.code != 0: currMapEvent = self.mapEvent(event) if not currMapEvent: - event = self.iDevices[fd].read_one() + event = device.read_one() continue if not isinstance(currMapEvent['EventName'], str): - event = self.iDevices[fd].read_one() + event = device.read_one() continue if currMapEvent['EventState'] in [0, 1, 2]: eventQueue.put({"Type": fenrirEventType.KeyboardInput, "Data": currMapEvent.copy()}) @@ -132,7 +150,7 @@ class driver(inputDriver): if event.type in [2, 3]: foreward = True - event = self.iDevices[fd].read_one() + event = device.read_one() if not foundKeyInSequence: if foreward and not eventFired: self.writeEventBuffer() @@ -351,24 +369,26 @@ class driver(inputDriver): def addDevice(self, newDevice): self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' + str(newDevice), debug.debugLevel.INFO) - try: - self.iDevices[newDevice.fd] = newDevice - self.createUInputDev(newDevice.fd) - self.gDevices[newDevice.fd] = False - except: - # if it doesnt work clean up + with self._deviceLock: try: - del(self.iDevices[newDevice.fd]) - except: - pass - try: - del(self.uDevices[newDevice.fd]) - except: - pass - try: - del(self.gDevices[newDevice.fd]) - except: - pass + self.iDevices[newDevice.fd] = newDevice + self.createUInputDev(newDevice.fd) + self.gDevices[newDevice.fd] = False + except Exception as e: + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: error adding device: ' + str(e), debug.debugLevel.ERROR) + # if it doesnt work clean up + try: + del(self.iDevices[newDevice.fd]) + except: + pass + try: + del(self.uDevices[newDevice.fd]) + except: + pass + try: + del(self.gDevices[newDevice.fd]) + except: + pass def grabDevice(self, fd): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): @@ -424,33 +444,37 @@ class driver(inputDriver): return True def removeDevice(self, fd): - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO) - self.clearEventBuffer() - try: - self.ungrabDevice(fd) - except: - pass - try: - self.iDevices[fd].close() - except: - pass - try: - self.uDevices[fd].close() - except: - pass - try: - del(self.iDevices[fd]) - except: - pass - try: - del(self.uDevices[fd]) - except: - pass - try: - del(self.gDevices[fd]) - except: - pass - self.updateMPiDevicesFD() + with self._deviceLock: + try: + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO) + except: + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd), debug.debugLevel.INFO) + self.clearEventBuffer() + try: + self.ungrabDevice(fd) + except: + pass + try: + self.iDevices[fd].close() + except: + pass + try: + self.uDevices[fd].close() + except: + pass + try: + del(self.iDevices[fd]) + except: + pass + try: + del(self.uDevices[fd]) + except: + pass + try: + del(self.gDevices[fd]) + except: + pass + self.updateMPiDevicesFD() def hasIDevices(self): if not self._initialized: diff --git a/src/fenrirscreenreader/screenDriver/vcsaDriver.py b/src/fenrirscreenreader/screenDriver/vcsaDriver.py index dcf6245e..a0add6ed 100644 --- a/src/fenrirscreenreader/screenDriver/vcsaDriver.py +++ b/src/fenrirscreenreader/screenDriver/vcsaDriver.py @@ -58,9 +58,8 @@ class driver(screenDriver): def getCurrScreen(self): self.env['screen']['oldTTY'] = self.env['screen']['newTTY'] try: - currScreenFile = open('/sys/devices/virtual/tty/tty0/active','r') - self.env['screen']['newTTY'] = str(currScreenFile.read()[3:-1]) - currScreenFile.close() + with open('/sys/devices/virtual/tty/tty0/active','r') as currScreenFile: + self.env['screen']['newTTY'] = str(currScreenFile.read()[3:-1]) except Exception as e: self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) def injectTextToScreen(self, text, screen = None): @@ -118,26 +117,35 @@ class driver(screenDriver): break return d def updateWatchdog(self, active , eventQueue): + vcsa = {} + vcsu = {} + tty = None + watchdog = None try: useVCSU = os.access('/dev/vcsu', os.R_OK) - vcsa = {} vcsaDevices = glob.glob('/dev/vcsa*') - vcsu = {} vcsuDevices = None lastScreenContent = b'' + + # Open TTY file with proper cleanup tty = open('/sys/devices/virtual/tty/tty0/active','r') currScreen = str(tty.read()[3:-1]) oldScreen = currScreen + + # Open VCSA devices with proper cleanup tracking for vcsaDev in vcsaDevices: index = str(vcsaDev[9:]) vcsa[index] = open(vcsaDev,'rb') if index == currScreen: lastScreenContent = self.readFile(vcsa[index]) + + # Open VCSU devices if available if useVCSU: vcsuDevices = glob.glob('/dev/vcsu*') for vcsuDev in vcsuDevices: index = str(vcsuDev[9:]) vcsu[index] = open(vcsuDev,'rb') + self.updateCharMap(currScreen) watchdog = select.epoll() watchdog.register(vcsa[currScreen], select.POLLPRI | select.POLLERR) @@ -220,6 +228,28 @@ class driver(screenDriver): except Exception as e: self.env['runtime']['debug'].writeDebugOut('VCSA:updateWatchdog:' + str(e),debug.debugLevel.ERROR) time.sleep(0.2) + finally: + # Clean up all file handles + try: + if watchdog: + watchdog.close() + except: + pass + try: + if tty: + tty.close() + except: + pass + for handle in vcsa.values(): + try: + handle.close() + except: + pass + for handle in vcsu.values(): + try: + handle.close() + except: + pass def createScreenEventData(self, screen, vcsaContent, vcsuContent = None): eventData = { @@ -252,27 +282,26 @@ class driver(screenDriver): def updateCharMap(self, screen): self.charmap = {} try: - tty = open('/dev/tty' + screen, 'rb') + with open('/dev/tty' + screen, 'rb') as tty: + GIO_UNIMAP = 0x4B66 + VT_GETHIFONTMASK = 0x560D + himask = array("H", (0,)) + ioctl(tty, VT_GETHIFONTMASK, himask) + self.hichar, = unpack_from("@H", himask) + sz = 512 + line = '' + while True: + try: + unipairs = array("H", [0]*(2*sz)) + unimapdesc = array("B", pack("@HP", sz, unipairs.buffer_info()[0])) + ioctl(tty.fileno(), GIO_UNIMAP, unimapdesc) + break + except Exception as e: + self.env['runtime']['debug'].writeDebugOut('VCSA:updateCharMap:scaling up sz=' + str(sz) + ' ' + str(e),debug.debugLevel.WARNING) + sz *= 2 except Exception as e: self.env['runtime']['debug'].writeDebugOut('VCSA:updateCharMap:' + str(e),debug.debugLevel.ERROR) return - GIO_UNIMAP = 0x4B66 - VT_GETHIFONTMASK = 0x560D - himask = array("H", (0,)) - ioctl(tty, VT_GETHIFONTMASK, himask) - self.hichar, = unpack_from("@H", himask) - sz = 512 - line = '' - while True: - try: - unipairs = array("H", [0]*(2*sz)) - unimapdesc = array("B", pack("@HP", sz, unipairs.buffer_info()[0])) - ioctl(tty.fileno(), GIO_UNIMAP, unimapdesc) - break - except Exception as e: - self.env['runtime']['debug'].writeDebugOut('VCSA:updateCharMap:scaling up sz=' + str(sz) + ' ' + str(e),debug.debugLevel.WARNING) - sz *= 2 - tty.close() ncodes, = unpack_from("@H", unimapdesc) utable = unpack_from("@%dH" % (2*ncodes), unipairs) for u, b in zip(utable[::2], utable[1::2]):