Browse Source

Starting the Pyodide porting

pull/54/head
Eugenio Parodi 4 years ago
parent
commit
abfb1795dc
  1. 178
      TermTk/TTkCore/TTkTerm/input.py
  2. 199
      TermTk/TTkCore/TTkTerm/term.py
  3. 141
      TermTk/TTkCore/TTkTerm/term_base.py
  4. 43
      TermTk/TTkCore/TTkTerm/term_pyodide.py
  5. 133
      TermTk/TTkCore/TTkTerm/term_unix.py
  6. 91
      TermTk/TTkCore/ttk.py
  7. 8
      TermTk/TTkTestWidgets/keypressview.py

178
TermTk/TTkCore/TTkTerm/input.py

@ -35,18 +35,26 @@ elif platform.system() == 'Darwin':
elif platform.system() == 'Windows':
raise NotImplementedError('Windows OS not yet supported')
elif platform.system() == 'Emscripten':
raise NotImplementedError('Pyodide not yet supported')
pass
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.signal import pyTTkSignal
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
class TTkInput:
__slots__ = ('_readInput', '_leftLastTime', '_midLastTime', '_rightLastTime', '_leftTap', '_midTap', '_rightTap')
__slots__ = (
'_readInput',
'_leftLastTime', '_midLastTime', '_rightLastTime',
'_leftTap', '_midTap', '_rightTap',
# Signals
'inputEvent'
)
def __init__(self):
self._readInput = ReadInput()
self.inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent)
self._readInput = None
self._leftLastTime = 0
self._midLastTime = 0
self._rightLastTime = 0
@ -55,94 +63,100 @@ class TTkInput:
self._rightTap = 0
def close(self):
self._readInput.close()
if self._readInput:
self._readInput.close()
def stop(self):
pass
def cont(self):
self._readInput.cont()
if self._readInput:
self._readInput.cont()
def get_key(self, callback=None):
mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
def start(self):
self._readInput = ReadInput()
while stdinRead := self._readInput.read():
mevt,kevt = None, None
if not stdinRead.startswith("\033[<"):
# Key Event
kevt = TTkKeyEvent.parse(stdinRead)
else:
# Mouse Event
m = mouse_re.match(stdinRead)
if not m:
# TODO: Return Error
hex = [f"0x{ord(x):02x}" for x in stdinRead]
TTkLog.error("UNHANDLED (mouse): "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
continue
code = int(m.group(1))
x = int(m.group(2))-1
y = int(m.group(3))-1
state = m.group(4)
key = TTkMouseEvent.NoButton
evt = TTkMouseEvent.NoEvent
tap = 0
def _checkTap(lastTime, tap):
if state=="M":
t = time()
if (t-lastTime) < 0.4:
return t, tap+1
else:
return t, 1
return lastTime, tap
mod = TTkK.NoModifier
if code & 0x10:
code &= ~0x10
mod |= TTkK.ControlModifier
if code & 0x08:
code &= ~0x08
mod |= TTkK.AltModifier
if code == 0x00:
self._leftLastTime, self._leftTap = _checkTap(self._leftLastTime, self._leftTap)
tap = self._leftTap
key = TTkMouseEvent.LeftButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x01:
self._midLastTime, self._midTap = _checkTap(self._midLastTime, self._midTap)
tap = self._midTap
key = TTkMouseEvent.MidButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x02:
self._rightLastTime, self._rightTap = _checkTap(self._rightLastTime, self._rightTap)
tap = self._rightTap
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x20:
key = TTkMouseEvent.LeftButton
evt = TTkMouseEvent.Drag
elif code == 0x21:
key = TTkMouseEvent.MidButton
evt = TTkMouseEvent.Drag
elif code == 0x22:
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Drag
elif code == 0x40:
key = TTkMouseEvent.Wheel
evt = TTkMouseEvent.Up
elif code == 0x41:
key = TTkMouseEvent.Wheel
evt = TTkMouseEvent.Down
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>"))
if kevt is None and mevt is None:
kevt, mevt = self.key_process(stdinRead)
self.inputEvent.emit(kevt, mevt)
TTkLog.debug("Close TTkInput")
mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
def key_process(self, stdinRead):
mevt,kevt = None, None
if not stdinRead.startswith("\033[<"):
# Key Event
kevt = TTkKeyEvent.parse(stdinRead)
else:
# Mouse Event
m = self.mouse_re.match(stdinRead)
if not m:
# TODO: Return Error
hex = [f"0x{ord(x):02x}" for x in stdinRead]
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
TTkLog.error("UNHANDLED (mouse): "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
return None, None
code = int(m.group(1))
x = int(m.group(2))-1
y = int(m.group(3))-1
state = m.group(4)
key = TTkMouseEvent.NoButton
evt = TTkMouseEvent.NoEvent
tap = 0
def _checkTap(lastTime, tap):
if state=="M":
t = time()
if (t-lastTime) < 0.4:
return t, tap+1
else:
return t, 1
return lastTime, tap
mod = TTkK.NoModifier
if code & 0x10:
code &= ~0x10
mod |= TTkK.ControlModifier
if code & 0x08:
code &= ~0x08
mod |= TTkK.AltModifier
if code == 0x00:
self._leftLastTime, self._leftTap = _checkTap(self._leftLastTime, self._leftTap)
tap = self._leftTap
key = TTkMouseEvent.LeftButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x01:
self._midLastTime, self._midTap = _checkTap(self._midLastTime, self._midTap)
tap = self._midTap
key = TTkMouseEvent.MidButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x02:
self._rightLastTime, self._rightTap = _checkTap(self._rightLastTime, self._rightTap)
tap = self._rightTap
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x20:
key = TTkMouseEvent.LeftButton
evt = TTkMouseEvent.Drag
elif code == 0x21:
key = TTkMouseEvent.MidButton
evt = TTkMouseEvent.Drag
elif code == 0x22:
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Drag
elif code == 0x40:
key = TTkMouseEvent.Wheel
evt = TTkMouseEvent.Up
elif code == 0x41:
key = TTkMouseEvent.Wheel
evt = TTkMouseEvent.Down
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>"))
if kevt is None and mevt is None:
hex = [f"0x{ord(x):02x}" for x in stdinRead]
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
return kevt, mevt
if callback is not None:
if not callback(kevt, mevt):
break
TTkLog.debug("Close TTkInput")
def main():
print("Retrieve Keyboard, Mouse press/drag/wheel Events")

199
TermTk/TTkCore/TTkTerm/term.py

@ -22,198 +22,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sys, os, signal
import importlib.util
try: import termios
except Exception as e:
print(f'ERROR: {e}')
exit(1)
class TTkTerm():
CLEAR = "\033[2J\033[0;0f" # Clear screen and set cursor to position 0,0
ALT_SCREEN = "\033[?1049h" #* Switch to alternate screen
NORMAL_SCREEN = "\033[?1049l" #* Switch to normal screen
class Mouse():
ON = "\033[?1002h\033[?1015h\033[?1006h" # Enable reporting of mouse position on click and release
OFF = "\033[?1002l" # Disable mouse reporting
DIRECT_ON = "\033[?1003h" # Enable reporting of mouse position at any movement
DIRECT_OFF = "\033[?1003l" # Disable direct mouse reporting
class Cursor():
# from:
# https://superuser.com/questions/607478/how-do-you-change-the-xterm-cursor-to-an-i-beam-or-vertical-bar
# echo -e -n "\x1b[\x30 q" # changes to blinking block
# echo -e -n "\x1b[\x31 q" # changes to blinking block also
# echo -e -n "\x1b[\x32 q" # changes to steady block
# echo -e -n "\x1b[\x33 q" # changes to blinking underline
# echo -e -n "\x1b[\x34 q" # changes to steady underline
# echo -e -n "\x1b[\x35 q" # changes to blinking bar
# echo -e -n "\x1b[\x36 q" # changes to steady bar
BLINKING_BLOCK = "\033[\x30 q"
BLINKING_BLOCK_ALSO = "\033[\x31 q"
STEADY_BLOCK = "\033[\x32 q"
BLINKING_UNDERLINE = "\033[\x33 q"
STEADY_UNDERLINE = "\033[\x34 q"
BLINKING_BAR = "\033[\x35 q"
STEADY_BAR = "\033[\x36 q"
HIDE = "\033[?25l"
SHOW = "\033[?25h"
@staticmethod
def moveTo(y:int,x:int)->str: return f'\033[{y};{x}f'
@staticmethod
def moveRight(n:int)->str: return f'\033[{n}C'
@staticmethod
def moveLeft(n:int)->str: return f'\033[{n}D'
@staticmethod
def modeUp(n:int)->str: return f'\033[{n}A'
@staticmethod
def moveDown(n:int)->str: return f'\033[{n}B'
@staticmethod
def show(cursorType):
TTkTerm.push(cursorType)
TTkTerm.push(TTkTerm.Cursor.SHOW)
@staticmethod
def hide():
TTkTerm.push(TTkTerm.Cursor.HIDE)
class Sigmask():
CTRL_C = 0x0001
CTRL_S = 0x0002
CTRL_Z = 0x0004
CTRL_Q = 0x0008
title: str = "TermTk"
mouse: bool = True
width: int = 0
height: int = 0
_sigWinChCb = None
# Save treminal attributes during the initialization in order to
# restore later the original states
_termAttr = termios.tcgetattr(sys.stdin)
_termAttrBk = []
@staticmethod
def saveTermAttr():
TTkTerm._termAttrBk.append(termios.tcgetattr(sys.stdin))
@staticmethod
def restoreTermAttr():
if TTkTerm._termAttrBk:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttrBk.pop())
else:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttr)
@staticmethod
def setSigmask(mask, value=True):
attr = termios.tcgetattr(sys.stdin)
if mask & TTkTerm.Sigmask.CTRL_C:
attr[6][termios.VINTR]= b'\x03' if value else 0
if mask & TTkTerm.Sigmask.CTRL_S:
attr[6][termios.VSTOP]= b'\x13' if value else 0
if mask & TTkTerm.Sigmask.CTRL_Z:
attr[6][termios.VSUSP]= b'\x1a' if value else 0
if mask & TTkTerm.Sigmask.CTRL_Q:
attr[6][termios.VSTART]= b'\x11' if value else 0
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr)
@staticmethod
def getSigmask():
mask = 0x00
attr = termios.tcgetattr(sys.stdin)
mask |= TTkTerm.Sigmask.CTRL_C if attr[6][termios.VINTR] else 0
mask |= TTkTerm.Sigmask.CTRL_S if attr[6][termios.VSTOP] else 0
mask |= TTkTerm.Sigmask.CTRL_Z if attr[6][termios.VSUSP] else 0
mask |= TTkTerm.Sigmask.CTRL_Q if attr[6][termios.VSTART] else 0
return mask
@staticmethod
def init(mouse: bool = True, title: str = "TermTk", sigmask=0):
TTkTerm.title = title
TTkTerm.mouse = mouse
TTkTerm.push(TTkTerm.ALT_SCREEN + TTkTerm.CLEAR + TTkTerm.Cursor.HIDE + TTkTerm.escTitle(TTkTerm.title))
if TTkTerm.mouse:
TTkTerm.push(TTkTerm.Mouse.ON)
TTkTerm.setEcho(False)
TTkTerm.CRNL(False)
TTkTerm.setSigmask(sigmask, False)
@staticmethod
def exit():
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkTerm.push(TTkTerm.CLEAR + TTkTerm.NORMAL_SCREEN + TTkTerm.Cursor.SHOW + TTkTerm.escTitle())
TTkTerm.setEcho(True)
TTkTerm.CRNL(True)
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttr)
@staticmethod
def stop():
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkTerm.push(TTkTerm.CLEAR + TTkTerm.NORMAL_SCREEN + TTkTerm.Cursor.SHOW + TTkTerm.escTitle())
TTkTerm.setEcho(True)
TTkTerm.CRNL(True)
@staticmethod
def cont():
TTkTerm.push(TTkTerm.ALT_SCREEN + TTkTerm.CLEAR + TTkTerm.Cursor.HIDE + TTkTerm.escTitle(TTkTerm.title))
if TTkTerm.mouse:
TTkTerm.push(TTkTerm.Mouse.ON)
TTkTerm.setEcho(False)
TTkTerm.CRNL(False)
@staticmethod
def escTitle(txt = "") -> str:
tt = os.environ.get("TERMINAL_TITLE", "")
if tt and txt:
return f'\033]0;{tt} {txt}\a'
else:
return f'\033]0;{tt}{txt}\a'
@staticmethod
def push(*args):
sys.stdout.write(str(*args))
sys.stdout.flush()
@staticmethod
def flush():
sys.stdout.flush()
@staticmethod
def setEcho(val: bool):
# Set/Unset Terminal Input Echo
(i,o,c,l,isp,osp,cc) = termios.tcgetattr(sys.stdin.fileno())
if val: l |= termios.ECHO
else: l &= ~termios.ECHO
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, [i,o,c,l,isp,osp,cc])
@staticmethod
def CRNL(val: bool):
#Translate carriage return to newline on input (unless IGNCR is set).
# '\n' CTRL-J
# '\r' CTRL-M (Enter)
(i,o,c,l,isp,osp,cc) = termios.tcgetattr(sys.stdin.fileno())
if val: i |= termios.ICRNL
else: i &= ~termios.ICRNL
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, [i,o,c,l,isp,osp,cc])
@staticmethod
def getTerminalSize():
return os.get_terminal_size()
@staticmethod
def _sigWinCh(signum, frame):
TTkTerm.width, TTkTerm.height = TTkTerm.getTerminalSize()
if TTkTerm._sigWinChCb is not None:
TTkTerm._sigWinChCb(TTkTerm.width, TTkTerm.height)
@staticmethod
def registerResizeCb(callback):
TTkTerm._sigWinChCb = callback
# Dummy call to retrieve the terminal size
TTkTerm._sigWinCh(signal.SIGWINCH, None)
signal.signal(signal.SIGWINCH, TTkTerm._sigWinCh)
if importlib.util.find_spec('pyodideProxy'):
from .term_pyodide import TTkTerm
else:
from .term_unix import TTkTerm

141
TermTk/TTkCore/TTkTerm/term_base.py

@ -0,0 +1,141 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
import os
class TTkTermBase():
CLEAR = "\033[2J\033[0;0f" # Clear screen and set cursor to position 0,0
ALT_SCREEN = "\033[?1049h" #* Switch to alternate screen
NORMAL_SCREEN = "\033[?1049l" #* Switch to normal screen
class Mouse():
ON = "\033[?1002h\033[?1015h\033[?1006h" # Enable reporting of mouse position on click and release
OFF = "\033[?1002l" # Disable mouse reporting
DIRECT_ON = "\033[?1003h" # Enable reporting of mouse position at any movement
DIRECT_OFF = "\033[?1003l" # Disable direct mouse reporting
class Cursor():
# from:
# https://superuser.com/questions/607478/how-do-you-change-the-xterm-cursor-to-an-i-beam-or-vertical-bar
# echo -e -n "\x1b[\x30 q" # changes to blinking block
# echo -e -n "\x1b[\x31 q" # changes to blinking block also
# echo -e -n "\x1b[\x32 q" # changes to steady block
# echo -e -n "\x1b[\x33 q" # changes to blinking underline
# echo -e -n "\x1b[\x34 q" # changes to steady underline
# echo -e -n "\x1b[\x35 q" # changes to blinking bar
# echo -e -n "\x1b[\x36 q" # changes to steady bar
BLINKING_BLOCK = "\033[\x30 q"
BLINKING_BLOCK_ALSO = "\033[\x31 q"
STEADY_BLOCK = "\033[\x32 q"
BLINKING_UNDERLINE = "\033[\x33 q"
STEADY_UNDERLINE = "\033[\x34 q"
BLINKING_BAR = "\033[\x35 q"
STEADY_BAR = "\033[\x36 q"
HIDE = "\033[?25l"
SHOW = "\033[?25h"
@staticmethod
def moveTo(y:int,x:int)->str: return f'\033[{y};{x}f'
@staticmethod
def moveRight(n:int)->str: return f'\033[{n}C'
@staticmethod
def moveLeft(n:int)->str: return f'\033[{n}D'
@staticmethod
def modeUp(n:int)->str: return f'\033[{n}A'
@staticmethod
def moveDown(n:int)->str: return f'\033[{n}B'
@staticmethod
def show(cursorType):
TTkTermBase.push(cursorType)
TTkTermBase.push(TTkTermBase.Cursor.SHOW)
@staticmethod
def hide():
TTkTermBase.push(TTkTermBase.Cursor.HIDE)
class Sigmask():
CTRL_C = 0x0001
CTRL_S = 0x0002
CTRL_Z = 0x0004
CTRL_Q = 0x0008
title: str = "TermTk"
mouse: bool = True
width: int = 0
height: int = 0
_sigWinChCb = None
@staticmethod
def init(mouse: bool = True, title: str = "TermTk", sigmask=0):
TTkTermBase.title = title
TTkTermBase.mouse = mouse
TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title))
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
TTkTermBase.setSigmask(sigmask, False)
@staticmethod
def exit():
TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle())
TTkTermBase.setEcho(True)
TTkTermBase.CRNL(True)
@staticmethod
def stop():
TTkTermBase.push(TTkTermBase.Mouse.OFF + TTkTermBase.Mouse.DIRECT_OFF)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.NORMAL_SCREEN + TTkTermBase.Cursor.SHOW + TTkTermBase.escTitle())
TTkTermBase.setEcho(True)
TTkTermBase.CRNL(True)
@staticmethod
def cont():
TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title))
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
@staticmethod
def escTitle(txt = "") -> str:
tt = os.environ.get("TERMINAL_TITLE", "")
if tt and txt:
return f'\033]0;{tt} {txt}\a'
else:
return f'\033]0;{tt}{txt}\a'
# NOTE: Due to "I have no idea how to do it in a better way",
# those methods are supposed to be overwritten with the
# compatible one in "term_unix.py" or "term_pyodide.py"
setSigmask = lambda *args: None
push = lambda *args: None
flush = lambda *args: None
setEcho = lambda *args: None
CRNL = lambda *args: None
getTerminalSize = lambda *args: None
registerResizeCb = lambda *args: None

43
TermTk/TTkCore/TTkTerm/term_pyodide.py

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
import pyodideProxy
from .term_base import TTkTermBase
class TTkTerm(TTkTermBase):
@staticmethod
def _push(*args):
pyodideProxy.termPush(str(*args))
TTkTermBase.push = _push
@staticmethod
def _getTerminalSize():
return pyodideProxy.termSize()
TTkTermBase.getTerminalSize = _getTerminalSize
@staticmethod
def _registerResizeCb(callback):
TTkTerm._sigWinChCb = callback
TTkTermBase.registerResizeCb = _registerResizeCb

133
TermTk/TTkCore/TTkTerm/term_unix.py

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2022 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
import sys, os, signal
try: import termios
except Exception as e:
print(f'ERROR: {e}')
exit(1)
from .term_base import TTkTermBase
class TTkTerm(TTkTermBase):
_sigWinChCb = None
# Save treminal attributes during the initialization in order to
# restore later the original states
_termAttr = termios.tcgetattr(sys.stdin)
_termAttrBk = []
@staticmethod
def saveTermAttr():
TTkTerm._termAttrBk.append(termios.tcgetattr(sys.stdin))
@staticmethod
def restoreTermAttr():
if TTkTerm._termAttrBk:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttrBk.pop())
else:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttr)
@staticmethod
def _setSigmask(mask, value=True):
attr = termios.tcgetattr(sys.stdin)
if mask & TTkTerm.Sigmask.CTRL_C:
attr[6][termios.VINTR]= b'\x03' if value else 0
if mask & TTkTerm.Sigmask.CTRL_S:
attr[6][termios.VSTOP]= b'\x13' if value else 0
if mask & TTkTerm.Sigmask.CTRL_Z:
attr[6][termios.VSUSP]= b'\x1a' if value else 0
if mask & TTkTerm.Sigmask.CTRL_Q:
attr[6][termios.VSTART]= b'\x11' if value else 0
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr)
TTkTermBase.setSigmask = _setSigmask
@staticmethod
def getSigmask():
mask = 0x00
attr = termios.tcgetattr(sys.stdin)
mask |= TTkTerm.Sigmask.CTRL_C if attr[6][termios.VINTR] else 0
mask |= TTkTerm.Sigmask.CTRL_S if attr[6][termios.VSTOP] else 0
mask |= TTkTerm.Sigmask.CTRL_Z if attr[6][termios.VSUSP] else 0
mask |= TTkTerm.Sigmask.CTRL_Q if attr[6][termios.VSTART] else 0
return mask
@staticmethod
def exit():
TTkTermBase.exit()
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, TTkTerm._termAttr)
@staticmethod
def _push(*args):
sys.stdout.write(str(*args))
sys.stdout.flush()
TTkTermBase.push = _push
@staticmethod
def _flush():
sys.stdout.flush()
TTkTermBase.flush = _flush
@staticmethod
def _setEcho(val: bool):
# Set/Unset Terminal Input Echo
(i,o,c,l,isp,osp,cc) = termios.tcgetattr(sys.stdin.fileno())
if val: l |= termios.ECHO
else: l &= ~termios.ECHO
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, [i,o,c,l,isp,osp,cc])
TTkTermBase.setEcho = _setEcho
@staticmethod
def _CRNL(val: bool):
#Translate carriage return to newline on input (unless IGNCR is set).
# '\n' CTRL-J
# '\r' CTRL-M (Enter)
(i,o,c,l,isp,osp,cc) = termios.tcgetattr(sys.stdin.fileno())
if val: i |= termios.ICRNL
else: i &= ~termios.ICRNL
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, [i,o,c,l,isp,osp,cc])
TTkTermBase.CRNL = _CRNL
@staticmethod
def _getTerminalSize():
try:
return os.get_terminal_size()
except OSError as e:
print(f'ERROR: {e}')
TTkTermBase.getTerminalSize = _getTerminalSize
@staticmethod
def _sigWinCh(signum, frame):
TTkTerm.width, TTkTerm.height = TTkTerm.getTerminalSize()
if TTkTerm._sigWinChCb is not None:
TTkTerm._sigWinChCb(TTkTerm.width, TTkTerm.height)
@staticmethod
def _registerResizeCb(callback):
TTkTerm._sigWinChCb = callback
# Dummy call to retrieve the terminal size
TTkTerm._sigWinCh(signal.SIGWINCH, None)
signal.signal(signal.SIGWINCH, TTkTerm._sigWinCh)
TTkTermBase.registerResizeCb = _registerResizeCb

91
TermTk/TTkCore/ttk.py

@ -26,6 +26,7 @@ import os
import signal
import time
import queue
import threading
from TermTk.TTkCore.TTkTerm.input import TTkInput
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
@ -45,30 +46,27 @@ class TTk(TTkWidget):
'_events', '_key_events', '_mouse_events', '_screen_events',
'_title',
'_sigmask',
'_lastMultiTap',
#Signals
'eventKeyPress', 'eventMouse' )
'_drawMutex',
'_lastMultiTap')
def __init__(self, *args, **kwargs):
TTkWidget.__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , 'TTk' )
self.eventKeyPress = pyTTkSignal(TTkKeyEvent)
self.eventMouse = pyTTkSignal(TTkMouseEvent)
self._running = False
self._input = None
self._input = TTkInput()
self._input.inputEvent.connect(self._processInput)
self._events = queue.Queue()
self._key_events = queue.Queue()
self._mouse_events = queue.Queue()
self._screen_events = queue.Queue()
self._title = kwargs.get('title','TermTk')
self._sigmask = kwargs.get('sigmask', TTkK.NONE)
self._drawMutex = threading.Lock()
self.setFocusPolicy(TTkK.ClickFocus)
self.hide()
try:
w,h = TTkTerm.getTerminalSize()
self.setGeometry(0,0,w,h)
except OSError as e:
print(f'ERROR: {e}')
w,h = TTkTerm.getTerminalSize()
self.setGeometry(0,0,w,h)
TTkCfg.theme = TTkTheme()
TTkHelper.registerRootWidget(self)
@ -107,7 +105,7 @@ class TTk(TTkWidget):
TTkLog.debug("Signal Event Registered")
TTkTerm.registerResizeCb(self._win_resize_cb)
threading.Thread(target=self._input_thread, daemon=True).start()
self._timer = TTkTimer()
self._timer.timeout.connect(self._time_event)
self._timer.start(0.1)
@ -117,41 +115,21 @@ class TTk(TTkWidget):
# Keep track of the multiTap to avoid the extra key release
self._lastMultiTap = False
TTkTerm.init(title=self._title, sigmask=self._sigmask)
self._mainloop()
#except Exception as e:
# TTkLog.error(f"{e}")
self._input.start()
finally:
self.quit()
TTkTerm.exit()
def _mainloop(self):
while self._running:
# Main Loop
evt = self._events.get()
if evt is TTkK.MOUSE_EVENT:
self._mouse_event()
elif evt is TTkK.KEY_EVENT:
self._key_event()
elif evt is TTkK.TIME_EVENT:
w,h = TTkTerm.getTerminalSize()
self.setGeometry(0,0,w,h)
TTkHelper.paintAll()
self._timer.start(1/TTkCfg.maxFps)
self._fps()
pass
elif evt is TTkK.SCREEN_EVENT:
self.setGeometry(0,0,TTkGlbl.term_w,TTkGlbl.term_h)
TTkLog.info(f"Resize: w:{TTkGlbl.term_w}, h:{TTkGlbl.term_h}")
elif evt is TTkK.QUIT_EVENT:
TTkLog.debug("Quit.")
break
else:
TTkLog.error(f"Unhandled Event {evt}")
break
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _processInput(self, kevt, mevt):
self._drawMutex.acquire()
if kevt is not None:
self._key_event(kevt)
if mevt is not None:
self._mouse_event(mevt)
self._drawMutex.release()
def _mouse_event(self):
mevt = self._mouse_events.get()
self.eventMouse.emit(mevt)
def _mouse_event(self, mevt):
# Upload the global mouse position
# Mainly used by the drag pixmap display
TTkHelper.setMousePos((mevt.x,mevt.y))
@ -199,9 +177,7 @@ class TTk(TTkWidget):
if mevt.evt == TTkK.Release:
TTkHelper.dndEnd()
def _key_event(self):
kevt = self._key_events.get()
self.eventKeyPress.emit(kevt)
def _key_event(self, kevt):
keyHandled = False
# TTkLog.debug(f"Key: {kevt}")
focusWidget = TTkHelper.getFocus()
@ -223,31 +199,26 @@ class TTk(TTkWidget):
TTkHelper.prevFocus(focusWidget if focusWidget else self)
def _time_event(self):
self._events.put(TTkK.TIME_EVENT)
w,h = TTkTerm.getTerminalSize()
self.setGeometry(0,0,w,h)
self._drawMutex.acquire()
TTkHelper.paintAll()
self._drawMutex.release()
self._timer.start(1/TTkCfg.maxFps)
self._fps()
def _win_resize_cb(self, width, height):
TTkGlbl.term_w = int(width)
TTkGlbl.term_h = int(height)
self._events.put(TTkK.SCREEN_EVENT)
self.setGeometry(0,0,TTkGlbl.term_w,TTkGlbl.term_h)
TTkLog.info(f"Resize: w:{TTkGlbl.term_w}, h:{TTkGlbl.term_h}")
def _input_thread(self):
def _inputCallback(kevt=None, mevt=None):
if kevt is not None:
self._key_events.put(kevt)
self._events.put(TTkK.KEY_EVENT)
if mevt is not None:
self._mouse_events.put(mevt)
self._events.put(TTkK.MOUSE_EVENT)
return self._running
# Start input key loop
self._input = TTkInput()
self._input.get_key(_inputCallback)
self._input.close()
def quit(self):
'''Tells the application to exit with a return code.'''
self._events.put(TTkK.QUIT_EVENT)
TTkTimer.quitAll()
self._input.close()
self._running = False
def _SIGSTOP(self, signum, frame):

8
TermTk/TTkTestWidgets/keypressview.py

@ -39,8 +39,7 @@ class TTkKeyPressView(TTkWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._name = kwargs.get('name' , 'TTkAbstractScrollView')
TTkHelper._rootWidget.eventKeyPress.connect(self._addKey)
TTkHelper._rootWidget.eventMouse.connect(self._addMouse)
TTkHelper._rootWidget._input.inputEvent.connect(self._processInput)
self._keys = []
self._period = 0.1
self._fade = 10
@ -48,6 +47,11 @@ class TTkKeyPressView(TTkWidget):
self._timer.timeout.connect(self._timerEvent)
self._timer.start(self._period)
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _processInput(self, kevt, mevt):
if kevt is not None: self._addKey(kevt)
if mevt is not None: self._addMouse(mevt)
@pyTTkSlot(TTkKeyEvent)
def _addKey(self, evt):
if evt.type == TTkK.Character:

Loading…
Cancel
Save