diff --git a/TermTk/TTkCore/drivers/__init__.py b/TermTk/TTkCore/drivers/__init__.py index c2a77f6f..df54fb18 100644 --- a/TermTk/TTkCore/drivers/__init__.py +++ b/TermTk/TTkCore/drivers/__init__.py @@ -6,7 +6,11 @@ if importlib.util.find_spec('pyodideProxy'): from .term_pyodide import * elif platform.system() == 'Linux': from .unix import * - from .term_unix import * + import os + if os.environ.get("TERMTK_FORCESERIAL",False): + from .term_unix_serial import * + else: + from .term_unix import * elif platform.system() == 'Darwin': from .unix import * from .term_unix import * diff --git a/TermTk/TTkCore/drivers/term_unix_serial.py b/TermTk/TTkCore/drivers/term_unix_serial.py new file mode 100644 index 00000000..b89b4467 --- /dev/null +++ b/TermTk/TTkCore/drivers/term_unix_serial.py @@ -0,0 +1,82 @@ +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +__all__ = ['TTkTerm'] + +from ..TTkTerm.term_base import TTkTermBase +from .term_unix import * + +class _TTkTermSerial(): + @staticmethod + def _getTerminalSizeSerial(): + import sys, os, tty, select, re + try: import termios, fcntl + except Exception as e: + print(f'ERROR: {e}') + exit(1) + _attr = termios.tcgetattr(sys.stdin) + tty.setcbreak(sys.stdin) + def _read_new(): + stdinRead = '' + while rlist := select.select( [sys.stdin], [], [] )[0]: + _fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL) + fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl | os.O_NONBLOCK) # Set the input as NONBLOCK to read the full sequence + stdinRead = sys.stdin.buffer.read() + fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl) + try: + stdinRead = stdinRead.decode() + except Exception as e: + yield f"bin: {stdinRead}" + continue + print(f"{len(stdinRead)=}") + if '\033' in stdinRead: + stdinSplit = stdinRead.split('\033') + for ansi in stdinSplit[1:]: + print(f"{ansi=}") + yield ansi + else: + for ch in stdinRead: + yield ch + w,h = 80,24 + try: + sys.stdout.write('\033[s\033[999;999H\033[6n\033[u') + sys.stdout.flush() + for stdinRead in _read_new(): + if m := re.match(r"\[(\d+);(\d+)R",stdinRead): + h = int(m.group(1)) + w = int(m.group(2)) + break + finally: + termios.tcsetattr(sys.stdin, termios.TCSANOW, _attr) + return w,h + _w, _h = None,None + + @staticmethod + def _getTerminalSize(): + if _TTkTermSerial._w is None: + _TTkTermSerial._w, _TTkTermSerial._h = _TTkTermSerial._getTerminalSizeSerial() + return _TTkTermSerial._w, _TTkTermSerial._h + + TTkTermBase.getTerminalSize = _getTerminalSize + + + diff --git a/docs/source/info/debug.rst b/docs/source/info/debug.rst index ee3b114b..2cf8e48c 100644 --- a/docs/source/info/debug.rst +++ b/docs/source/info/debug.rst @@ -27,6 +27,14 @@ Use this env variable to force a stacktrace generation to the file defined (i.e. TERMTK_STACKTRACE=stacktrace.txt python3 demo/demo.py +**TERMTK_FORCESERIAL** - Force some workaround to pickup the terminal size from the serial console +-------------------------------------------------------------- + +Use this env variable if pyTermTk is running on a serial console + +.. code:: bash + + TERMTK_FORCESERIAL=1 python3 demo/demo.py Gui === diff --git a/tests/t.pty/test.serial.001.py b/tests/t.pty/test.serial.001.py new file mode 100644 index 00000000..f94e093b --- /dev/null +++ b/tests/t.pty/test.serial.001.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Example from: +# https://stackoverflow.com/questions/40931467/how-can-i-manually-get-my-terminal-to-return-its-character-size + +import sys, os, select, signal, re +import logging + +try: import fcntl, termios, tty +except Exception as e: + print(f'ERROR: {e}') + exit(1) + +# Init +_attr = termios.tcgetattr(sys.stdin) +tty.setcbreak(sys.stdin) + +def read_new(): + stdinRead = '' + while rlist := select.select( [sys.stdin], [], [] )[0]: + _fl = fcntl.fcntl(sys.stdin, fcntl.F_GETFL) + fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl | os.O_NONBLOCK) # Set the input as NONBLOCK to read the full sequence + stdinRead = sys.stdin.buffer.read() + fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl) + try: + stdinRead = stdinRead.decode() + except Exception as e: + yield f"bin: {stdinRead}" + continue + print(f"{len(stdinRead)=}") + if '\033' in stdinRead: + stdinSplit = stdinRead.split('\033') + for ansi in stdinSplit[1:]: + print(f"{ansi=}") + yield ''+ansi + else: + for ch in stdinRead: + yield ch + +try: + sys.stdout.write('\033[s\033[999;999H\033[6n\033[u') + sys.stdout.flush() + for stdinRead in read_new(): + print(f"{stdinRead=}") + if m := re.match(r"\[(\d+);(\d+)R",stdinRead): + h = int(m.group(1)) + w = int(m.group(2)) + print(f"{w=} {h=}") + break +finally: + # Reset + termios.tcsetattr(sys.stdin, termios.TCSANOW, _attr) diff --git a/tools/check.import.sh b/tools/check.import.sh index 517ce320..68483bcf 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -63,6 +63,8 @@ __check(){ -e "drivers/term_windows.py:from .windows import *" \ -e "drivers/term_unix.py:from ..TTkTerm.term_base import TTkTermBase" \ -e "drivers/term_unix.py:from threading import Thread, Lock" \ + -e "drivers/term_unix_serial.py:from ..TTkTerm.term_base import TTkTermBase" \ + -e "drivers/term_unix_serial.py:from .term_unix import *" \ -e "drivers/term_pyodide.py:import pyodideProxy" \ -e "drivers/term_pyodide.py:from ..TTkTerm.term_base import TTkTermBase" \ -e "drivers/__init__.py:import importlib.util" \