Browse Source

Merge pull request #296 from ceccopierangiolieugenio/295-support-for-gpm

Gpm Driver
pull/305/head
Pier CeccoPierangioliEugenio 1 year ago committed by GitHub
parent
commit
1b1b0a7933
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      .vscode/launch.json
  2. 16
      TermTk/TTkCore/drivers/__init__.py
  3. 2
      TermTk/TTkCore/drivers/unix.py
  4. 303
      TermTk/TTkCore/drivers/unix_gpm.py
  5. 2
      TermTk/TTkCore/drivers/unix_thread.py
  6. 34
      TermTk/TTkCore/string.py
  7. 78
      TermTk/TTkCore/ttk.py
  8. 4
      TermTk/TTkWidgets/TTkTerminal/terminalhelper.py
  9. 8
      docs/source/info/resources/clipboard.rst
  10. 77
      docs/source/info/resources/experimental.rst
  11. 3
      docs/source/info/resources/index.rst
  12. 208
      tests/t.generic/test.ctypes.01.gpm.01.py
  13. 242
      tests/t.generic/test.ctypes.01.gpm.02.py
  14. 250
      tests/t.generic/test.ctypes.01.gpm.03.thread.py
  15. 263
      tests/t.generic/test.ctypes.01.gpm.04.thread.queue.py
  16. 270
      tests/t.generic/test.ctypes.01.gpm.05.thread.queue.generator.py
  17. 264
      tests/t.generic/test.ctypes.01.gpm.06.getch_rewrite.py
  18. 70
      tests/t.generic/test.ctypes.02.fd.conversion.py
  19. 27
      tests/t.generic/test.curses.001.py
  20. 11
      tests/t.input/test.input.py
  21. 3329
      tests/utf-8/ascii.test.txt
  22. 6
      tools/check.import.sh
  23. 36
      tutorial/000-examples.rst

10
.vscode/launch.json vendored

@ -20,6 +20,16 @@
"console": "integratedTerminal",
"justMyCode": true,
"args": ["tools/webExporter/js/ttkproxy.js"]
},{
"name": "Python: Current File with Env",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"TERMTK_GPM": "1"
},
},{
"name": "Python: Test Player",
"type": "debugpy",

16
TermTk/TTkCore/drivers/__init__.py

@ -4,16 +4,24 @@ import platform
if importlib.util.find_spec('pyodideProxy'):
from .pyodide import *
from .term_pyodide import *
elif platform.system() == 'Linux':
from .unix import *
import os
if os.environ.get("TERMTK_FORCESERIAL",False):
from .term_unix_serial import *
if os.environ.get("TERMTK_GPM",False):
from .unix_gpm import *
from .term_unix import *
else:
from .term_unix import *
from .unix import *
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 *
elif platform.system() == 'Windows':
from .windows import *
from .term_windows import *

2
TermTk/TTkCore/drivers/unix.py

@ -51,7 +51,7 @@ class TTkInputDriver():
def read(self):
rm = re.compile('(\033?[^\033]+)')
while self._readPipe[0] not in (list := select( [sys.stdin, self._readPipe[0]], [], [] )[0]):
while self._readPipe[0] not in (_rlist := select( [sys.stdin, self._readPipe[0]], [], [] )[0]):
# Read all the full input
_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

303
TermTk/TTkCore/drivers/unix_gpm.py

@ -0,0 +1,303 @@
# MIT License
#
# Copyright (c) 2024 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.
__all__ = ['TTkSignalDriver','TTkInputDriver']
import sys
import os
import re
import signal
from select import select
import ctypes
try: import fcntl, termios, tty
except Exception as e:
print(f'ERROR: {e}')
exit(1)
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class _Gpm_Connect(ctypes.Structure):
_fields_ = [
("eventMask", ctypes.c_ushort),
("defaultMask", ctypes.c_ushort),
("minMod", ctypes.c_ushort),
("maxMod", ctypes.c_ushort),
("pid", ctypes.c_int),
("vc", ctypes.c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class _Gpm_Event(ctypes.Structure):
_fields_ = [
("buttons", ctypes.c_ubyte),
("modifiers", ctypes.c_ubyte),
("vc", ctypes.c_short),
("dx", ctypes.c_short),
("dy", ctypes.c_short),
("x", ctypes.c_short),
("y", ctypes.c_short),
("type", ctypes.c_int),
("clicks", ctypes.c_int),
("margin", ctypes.c_int),
("wdx", ctypes.c_short),
("wdy", ctypes.c_short)]
_GPM_HANDLER_FUNC = ctypes.CFUNCTYPE(
ctypes.c_int,
ctypes.POINTER(_Gpm_Event),
ctypes.POINTER(ctypes.c_void_p))
class TTkInputDriver():
__slots__ = ('_readPipe', '_attr',
'_libgpm', '_libc', '_cstdin')
def __init__(self):
self._libgpm = ctypes.CDLL('libgpm.so.2')
self._libc = ctypes.cdll.LoadLibrary('libc.so.6')
self._cstdin = ctypes.c_void_p.in_dll(self._libc, 'stdin')
self._readPipe = os.pipe()
self._attr = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
def close(self):
termios.tcsetattr(sys.stdin, termios.TCSANOW, self._attr)
os.write(self._readPipe[1], b'quit')
def cont(self):
tty.setcbreak(sys.stdin)
def _gpm_handler(self, event:_Gpm_Event) -> str:
code = 0x00
state = 'M'
x = event.x
y = event.y
# wdx = ec.wdx
wdy = event.wdy # mouse wheel
# Types:
# from: <https://github.com/telmich/gpm.git>/src/headers/gpm.h
# https://github.com/telmich/gpm/blob/master/src/headers/gpm.h
# MOVE = 0x0001
# DRAG = 0x0002
# DOWN = 0x0004
# UP = 0x0008
#
# SINGLE = 0x0010
# DOUBLE = 0x0020
# TRIPLE = 0x0040
# MFLAG = 0x0080
# HARD = 0x0100
# ENTER = 0x0200
# LEAVE = 0x0400
# # exactly one of the bare ones is active at a time
# BARE_EVENTS(type) ((type)&(0x0f|ENTER|LEAVE))
etype = event.type
if etype & 0x0008: # UP
state = 'm'
# Buttons:
# from: <https://github.com/telmich/gpm.git>/src/headers/gpm.h
# https://github.com/telmich/gpm/blob/master/src/headers/gpm.h
# DOWN 0x20
# UP 0x10
# FOURTH 0x08
# LEFT 0x04
# MIDDLE 0x02
# RIGHT 0x01
# NONE 0x00
buttons = event.buttons
if wdy == 1: # Wheel UP(1)
code |= 0x40
elif wdy == -1: # Wheel DOWN(-1)
code |= 0x41
elif etype & (0x0004|0x0008): # DOWN/UP
if buttons & 0x04: # LEFT
code |= 0x00
elif buttons & 0x01: # RIGHT
code |= 0x02
elif buttons & 0x02: # MIDDLE
code |= 0x01
elif etype & (0x0002): # MOVE
if buttons & 0x04: # LEFT
code |= 0x20
elif buttons & 0x01: # RIGHT
code |= 0x22
elif buttons & 0x02: # MIDDLE
code |= 0x21
elif etype & (0x0001): # MOVE
code |= 0x23
# Modifiers:
# From: /usr/include/linux/keyboard.h
# SHIFT 0x01 << 0x00 = 0x0001
# CTRL 0x01 << 0x02 = 0x0004
# ALT 0x01 << 0x03 = 0x0008
# ALTGR 0x01 << 0x01 = 0x0002
# SHIFTL 0x01 << 0x04 = 0x0010
# KANASHIFT 0x01 << 0x04 = 0x0010
# SHIFTR 0x01 << 0x05 = 0x0020
# CTRLL 0x01 << 0x06 = 0x0040
# CTRLR 0x01 << 0x07 = 0x0080
# CAPSSHIFT 0x01 << 0x08 = 0x0100
modifiers = event.modifiers
if modifiers & 0x0001: # SHIFT
code |= 0x27
if modifiers & 0x0004: # CTRL
code |= 0x10
if modifiers & (0x0008|0x0002): # ALT/ALTGR
code |= 0x08
return f"\033[<{code};{x};{y}{state}"
def read(self):
rm = re.compile('(\033?[^\033]+)')
_conn = _Gpm_Connect()
_conn.eventMask = ~0 # Want to know about all the events
_conn.defaultMask = 0 # don't handle anything by default
_conn.minMod = 0 # want everything
_conn.maxMod = ~0 # all modifiers included
if (_gpm_fd := self._libgpm.Gpm_Open(ctypes.pointer(_conn), 0)) == -1:
raise Exception("Cannot connect to the mouse server")
if _gpm_fd < 0:
self._libgpm.Gpm_Close()
raise Exception("Xterm GPM driver not supported")
_ev = _Gpm_Event()
with os.fdopen(_gpm_fd, "r") as gpm_file_obj:
while self._readPipe[0] not in (_rlist := select( [sys.stdin, gpm_file_obj, self._readPipe[0]], [], [] )[0]):
if gpm_file_obj in _rlist:
self._libgpm.Gpm_GetEvent(ctypes.pointer(_ev))
yield self._gpm_handler(_ev)
if sys.stdin in _rlist:
# Read all the full input
_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.read()
fcntl.fcntl(sys.stdin, fcntl.F_SETFL, _fl)
# Split all the ansi sequences
# or yield any separate input char
if stdinRead == '\033':
yield '\033'
continue
for sr in rm.findall(stdinRead):
if '\033' == sr[0]:
yield sr
else:
for ch in sr:
yield ch
self._libgpm.Gpm_Close()
class TTkSignalDriver():
sigStop = pyTTkSignal()
sigCont = pyTTkSignal()
sigInt = pyTTkSignal()
@staticmethod
def init():
# Register events
signal.signal(signal.SIGTSTP, TTkSignalDriver._SIGSTOP) # Ctrl-Z
signal.signal(signal.SIGCONT, TTkSignalDriver._SIGCONT) # Resume
signal.signal(signal.SIGINT, TTkSignalDriver._SIGINT) # Ctrl-C
def exit():
signal.signal(signal.SIGINT, signal.SIG_DFL)
def _SIGSTOP(signum, frame): TTkSignalDriver.sigStop.emit()
def _SIGCONT(signum, frame): TTkSignalDriver.sigCont.emit()
def _SIGINT( signum, frame): TTkSignalDriver.sigInt.emit()
def _main():
inputDriver = TTkInputDriver()
for stdinRead in inputDriver.read():
out = stdinRead.replace('\033','<ESC>')
print(f"Input: {out}")
if stdinRead == 'q':
print('Break')
break
inputDriver.close()
if __name__ == "__main__":
_main()

2
TermTk/TTkCore/drivers/unix_thread.py

@ -72,7 +72,7 @@ class ReadInput():
def close(self):
os.write(self._readPipe[1], b'quit')
def _read(self):
def _read_old(self):
_fn = sys.stdin.fileno()
_attr = termios.tcgetattr(_fn)
tty.setcbreak(_fn)

34
TermTk/TTkCore/string.py

@ -22,6 +22,7 @@
__all__ = ['TTkString']
import os
import re
import unicodedata
from types import GeneratorType
@ -674,13 +675,15 @@ class TTkString():
sum(unicodedata.east_asian_width(ch) == 'W' for ch in self._text) -
sum(unicodedata.category(ch) in ('Me','Mn') for ch in self._text) )
def _getDataW(self):
def _getDataW_pts(self):
retTxt = []
retCol = []
for i,ch in enumerate(self._text):
retTxt_append = retTxt.append
retCol_append = retCol.append
for ch,color in zip(self._text,self._colors):
if unicodedata.east_asian_width(ch) == 'W':
retTxt += (ch,'')
retCol += (self._colors[i],self._colors[i])
retTxt += [ch,'']
retCol += [color,color]
elif unicodedata.category(ch) in ('Me','Mn'):
if retTxt:
if len(retTxt)>1 and retTxt[-1] == '':
@ -691,6 +694,25 @@ class TTkString():
# retTxt = [f"{ch}"]
# retCol = [TTkColor.RST]
else:
retTxt.append(ch)
retCol.append(self._colors[i])
retTxt_append(ch)
retCol_append(color)
return (retTxt, retCol)
def _getDataW_tty(self):
retTxt = []
retCol = []
retTxt_append = retTxt.append
retCol_append = retCol.append
for ch,color in zip(self._text,self._colors):
if unicodedata.east_asian_width(ch) == 'W':
retTxt += ['','']
retCol += [color,color]
elif unicodedata.category(ch) not in ('Me','Mn'):
retTxt_append(ch)
retCol_append(color)
return (retTxt, retCol)
if os.environ.get("TERMTK_GPM",False):
_getDataW = _getDataW_tty
else:
_getDataW = _getDataW_pts

78
TermTk/TTkCore/ttk.py

@ -46,42 +46,41 @@ from TermTk.TTkWidgets.about import TTkAbout
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer
class TTk(TTkContainer):
class _mouseCursor(TTkWidget):
__slots__ = ('_cursor','_color')
def __init__(self):
super().__init__(name='MouseCursor')
class _MouseCursor():
__slots__ = ('_cursor','_color', '_pos', 'updated')
def __init__(self):
self.updated = pyTTkSignal()
self._pos = (0,0)
self._cursor = ''
self._color = TTkColor.RST
TTkInput.inputEvent.connect(self._mouseInput)
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _mouseInput(self, _, mevt):
if mevt is not None:
self._cursor = ''
self._color = TTkColor.RST
self.resize(1,1)
TTkInput.inputEvent.connect(self._mouseInput)
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _mouseInput(self, _, mevt):
if mevt is not None:
self._cursor = ''
self._color = TTkColor.RST
if mevt.key == TTkK.Wheel:
if mevt.evt == TTkK.WHEEL_Up:
self._cursor = ''
else:
self._cursor = ''
elif mevt.evt == TTkK.Press:
self._color = TTkColor.bg('#FFFF00') + TTkColor.fg('#000000')
elif mevt.evt == TTkK.Drag:
self._color = TTkColor.bg('#666600') + TTkColor.fg('#FFFF00')
# elif mevt.evt == TTkK.Release:
# self._color = TTkColor.bg('#006600') + TTkColor.fg('#00FFFF')
self.move(mevt.x, mevt.y)
self.update()
self.raiseWidget()
def paintEvent(self, canvas):
canvas.drawChar((0,0), self._cursor, self._color)
#canvas.drawChar((0,0),'✜')
if mevt.key == TTkK.Wheel:
if mevt.evt == TTkK.WHEEL_Up:
self._cursor = ''
else:
self._cursor = ''
elif mevt.evt == TTkK.Press:
self._color = TTkColor.bg('#FFFF00') + TTkColor.fg('#000000')
elif mevt.evt == TTkK.Drag:
self._color = TTkColor.bg('#666600') + TTkColor.fg('#FFFF00')
# elif mevt.evt == TTkK.Release:
# self._color = TTkColor.bg('#006600') + TTkColor.fg('#00FFFF')
self._pos = (mevt.x, mevt.y)
self.updated.emit()
class TTk(TTkContainer):
__slots__ = (
'_termMouse', '_termDirectMouse',
'_title',
'_showMouseCursor',
'_showMouseCursor', '_mouseCursor',
'_sigmask', '_timer',
'_drawMutex',
'_paintEvent',
@ -107,7 +106,8 @@ class TTk(TTkContainer):
self.paintExecuted = pyTTkSignal()
self._termMouse = True
self._termDirectMouse = mouseTrack
self._showMouseCursor = os.environ.get("TTK_MOUSE",mouseCursor)
self._mouseCursor = None
self._showMouseCursor = os.environ.get("TERMTK_MOUSE",mouseCursor)
super().__init__(**kwargs)
TTkInput.inputEvent.connect(self._processInput)
TTkInput.pasteEvent.connect(self._processPaste)
@ -126,6 +126,9 @@ class TTk(TTkContainer):
TTkCfg.doubleBuffer = False
TTkCfg.doubleBufferNew = True
if os.environ.get("TERMTK_GPM",False):
self._showMouseCursor = True
TTkHelper.registerRootWidget(self)
frame = 0
@ -178,9 +181,9 @@ class TTk(TTkContainer):
sigmask=self._sigmask)
if self._showMouseCursor:
TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
m = TTk._mouseCursor()
self.rootLayout().addWidget(m)
self._mouseCursor = _MouseCursor()
self._mouseCursor.updated.connect(self.update)
self.paintChildCanvas = self._mouseCursorPaintChildCanvas
self._mainLoop()
finally:
@ -194,6 +197,13 @@ class TTk(TTkContainer):
self.quit()
TTkTerm.exit()
def _mouseCursorPaintChildCanvas(self) -> None:
super().paintChildCanvas()
ch = self._mouseCursor._cursor
pos = self._mouseCursor._pos
color = self._mouseCursor._color
self.getCanvas().drawChar(char=ch, pos=pos, color=color)
def _mainLoop(self):
if platform.system() == 'Emscripten':
return

4
TermTk/TTkWidgets/TTkTerminal/terminalhelper.py

@ -124,6 +124,10 @@ class TTkTerminalHelper():
if self._pid == 0:
def _spawnTerminal(argv=self._shell, env=os.environ):
env=env.copy()
env.pop("TERMTK_GPM",None)
env.pop("TERMTK_MOUSE",None)
env['TERM']='screen'
os.execvpe(argv[0], argv, env)
# threading.Thread(target=_spawnTerminal).start()
# TTkHelper.quit()

8
docs/source/info/resources/clipboard.rst

@ -9,10 +9,10 @@ Clipboard
pyTermTk_ include a clipboard wrapper :py:class:`TTkClipboard`, around any of the following libraries:
- `copykitten <https://github.com/klavionik/copykitten>`_ - Robust, dependency-free way to use the system clipboard in Python.
- `pyperclip <https://github.com/asweigart/pyperclip>`_ - Python module for cross-platform clipboard functions.
- `pyperclip3 <https://pypi.org/project/pyperclip3>`_ / `pyclip <https://github.com/spyoungtech/pyclip>`_ - Cross-platform Clipboard module for Python with binary support.
- `clipboard <https://github.com/terryyin/clipboard>`_ - A cross platform clipboard operation library of Python. Works for Windows, Mac and Linux.
- `copykitten <https://github.com/klavionik/copykitten>`__ - Robust, dependency-free way to use the system clipboard in Python.
- `pyperclip <https://github.com/asweigart/pyperclip>`__ - Python module for cross-platform clipboard functions.
- `pyperclip3 <https://pypi.org/project/pyperclip3>`__ / `pyclip <https://github.com/spyoungtech/pyclip>`_ - Cross-platform Clipboard module for Python with binary support.
- `clipboard <https://github.com/terryyin/clipboard>`__ - A cross platform clipboard operation library of Python. Works for Windows, Mac and Linux.
.. raw:: html

77
docs/source/info/resources/experimental.rst

@ -0,0 +1,77 @@
.. _experimental_features:
=====================
Experimental Features
=====================
PyTermTk provides several experimental features to enhance functionality and user interaction.
These features are not enabled by default and must be activated via environment variables.
Below is a list of the currently available experimental features.
.. _mouse_visual_feedback:
---------------------
Mouse Visual Feedback
---------------------
Enable mouse visual feedback glyph ('✠') in PyTermTk.
To enable this feature,
set the environment variable **TERMTK_MOUSE** to `1` and run your application:
.. code:: bash
TERMTK_MOUSE=1 demo/demo.py
----------------------------------------------------------------------------
`GPM <https://wiki.archlinux.org/title/General_purpose_mouse>`__ Integration
----------------------------------------------------------------------------
`GPM <https://wiki.archlinux.org/title/General_purpose_mouse>`__ (General Purpose Mouse) support enables mouse interaction in Linux TTY environments without requiring a graphical user interface.
To activate GPM support, set the **TERMTK_GPM** environment variable to `1`:
.. code:: bash
TERMTK_GPM=1 demo/demo.py
.. note::
The :ref:`mouse_visual_feedback` is enabled my default when the GPM driver is loaded
.. note::
GPM must be installed and running on your system for this feature to work.
Install GPM using your system's package manager and ensure it is started with
.. code:: bash
sudo systemctl start gpm
.. seealso::
* https://github.com/telmich/gpm
* https://wiki.archlinux.org/title/General_purpose_mouse
* https://www.geeksforgeeks.org/gpm-command-in-linux-with-examples
--------------
Serial Console
--------------
PyTermTk can detect the terminal size also on a serial console (i.e. ttyUSBx).
To force serial console compatibility,
set the **TERMTK_FORCESERIAL** environment variable to `1`:
.. code:: bash
TERMTK_FORCESERIAL=1 demo/demo.py
--------------------
Feedback and Support
--------------------
Since these features are experimental,
they may not work as expected in all environments.
If you encounter issues or have suggestions,
please report them to the PyTermTk issue tracker or contribute to the project.

3
docs/source/info/resources/index.rst

@ -8,4 +8,5 @@ Resources
clipboard
modal
dragdrop
dragdrop
experimental

208
tests/t.generic/test.ctypes.01.gpm.01.py

@ -0,0 +1,208 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys
from ctypes import CDLL, c_void_p, cdll, CFUNCTYPE
from ctypes import Structure, Union, pointer, POINTER, cast
from ctypes import c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
ec = event.contents
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
gpm_handler = c_void_p.in_dll(libgpm, 'gpm_handler')
gpm_fd = c_void_p.in_dll(libgpm, 'gpm_fd')
print("Open Connection")
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
# RDFM;
# This is one of the rare cases where "Readuing the Fucking Manual" was the only way to solve this issue
# Not just that but a basic knowledge of c casting annd function pointers
# https://docs.python.org/3/library/ctypes.html#type-conversions
gpm_handler.value = cast(HANDLER_FUNC(my_handler),c_void_p).value
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
gpm_fd.value = _gpm_fd
print("Starting Loop")
while c := libgpm.Gpm_Getc(cstdin):
print(f"Key: {c=:04X} ")
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

242
tests/t.generic/test.ctypes.01.gpm.02.py

@ -0,0 +1,242 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys
import termios, tty
from ctypes import ( CDLL, c_void_p, cdll, CFUNCTYPE )
from ctypes import ( Structure, Union, pointer, POINTER, cast )
from ctypes import ( c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p )
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
ec = event.contents
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
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])
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])
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
gpm_handler = c_void_p.in_dll(libgpm, 'gpm_handler')
gpm_fd = c_int.in_dll(libgpm, 'gpm_fd')
print("Open Connection")
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
# RDFM;
# This is one of the rare cases where "Readuing the Fucking Manual" was the only way to solve this issue
# Not just that but a basic knowledge of c casting annd function pointers
# https://docs.python.org/3/library/ctypes.html#type-conversions
gpm_handler.value = cast(HANDLER_FUNC(my_handler),c_void_p).value
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
gpm_fd.value = _gpm_fd
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
print("Starting Loop")
print(f"{sys.stdin=} {cstdin=}")
# setEcho(False)
old_settings = termios.tcgetattr(sys.stdin)
# new_settings = termios.tcgetattr(sys.stdin)
# new_settings[3] &= ~termios.ICANON
# new_settings[3] &= ~termios.ICRNL
# new_settings[3] &= ~termios.ECHO
# termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
tty.setcbreak(sys.stdin)
while (c:=libgpm.Gpm_Getc(cstdin)) and c != ord('q'):
print(f"Key: {c=:04X} ")
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# setEcho(True)
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

250
tests/t.generic/test.ctypes.01.gpm.03.thread.py

@ -0,0 +1,250 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys
import termios, tty
import threading, queue
from ctypes import ( CDLL, c_void_p, cdll, CFUNCTYPE )
from ctypes import ( Structure, Union, pointer, POINTER, cast )
from ctypes import ( c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p )
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
ec = event.contents
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
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])
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])
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
gpm_handler = c_void_p.in_dll(libgpm, 'gpm_handler')
gpm_fd = c_int.in_dll(libgpm, 'gpm_fd')
print("Open Connection")
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
# RDFM;
# This is one of the rare cases where "Readuing the Fucking Manual" was the only way to solve this issue
# Not just that but a basic knowledge of c casting annd function pointers
# https://docs.python.org/3/library/ctypes.html#type-conversions
gpm_handler.value = cast(HANDLER_FUNC(my_handler),c_void_p).value
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
gpm_fd.value = _gpm_fd
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
print("Starting Loop")
print(f"{sys.stdin=} {cstdin=}")
# setEcho(False)
old_settings = termios.tcgetattr(sys.stdin)
# new_settings = termios.tcgetattr(sys.stdin)
# new_settings[3] &= ~termios.ICANON
# new_settings[3] &= ~termios.ICRNL
# new_settings[3] &= ~termios.ECHO
# termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
tty.setcbreak(sys.stdin)
def _processGpm():
print(f"Thread Started")
while (c:=libgpm.Gpm_Getc(cstdin)) and c != ord('q'):
print(f"Key: {c=:04X} - '{chr(c)}'")
t = threading.Thread(target=_processGpm)
t.start()
t.join()
print(f"Thread Joined")
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# setEcho(True)
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

263
tests/t.generic/test.ctypes.01.gpm.04.thread.queue.py

@ -0,0 +1,263 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys
import termios, tty
import threading, queue
from ctypes import ( CDLL, c_void_p, cdll, CFUNCTYPE )
from ctypes import ( Structure, Union, pointer, POINTER, cast )
from ctypes import ( c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p )
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
gpmQueue = queue.Queue()
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
ec = event.contents
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
# print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
gpmQueue.put(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
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])
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])
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
gpm_handler = c_void_p.in_dll(libgpm, 'gpm_handler')
gpm_fd = c_int.in_dll(libgpm, 'gpm_fd')
print("Open Connection")
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
# RDFM;
# This is one of the rare cases where "Readuing the Fucking Manual" was the only way to solve this issue
# Not just that but a basic knowledge of c casting annd function pointers
# https://docs.python.org/3/library/ctypes.html#type-conversions
gpm_handler.value = cast(HANDLER_FUNC(my_handler),c_void_p).value
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
gpm_fd.value = _gpm_fd
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
print("Starting Loop")
print(f"{sys.stdin=} {cstdin=}")
# setEcho(False)
old_settings = termios.tcgetattr(sys.stdin)
# new_settings = termios.tcgetattr(sys.stdin)
# new_settings[3] &= ~termios.ICANON
# new_settings[3] &= ~termios.ICRNL
# new_settings[3] &= ~termios.ECHO
# termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
tty.setcbreak(sys.stdin)
def _processGpm():
print(f"Thread Started")
while (c:=libgpm.Gpm_Getc(cstdin)) and c != ord('q'):
print(f"Key: {c=:04X} - '{chr(c)}'")
gpmQueue.put(chr(c))
gpmQueue.put(None)
t = threading.Thread(target=_processGpm)
t.start()
while True:
item = gpmQueue.get()
if item is None:
break
print(f"{item=}")
gpmQueue.task_done()
t.join()
print(f"Thread Joined")
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# setEcho(True)
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

270
tests/t.generic/test.ctypes.01.gpm.05.thread.queue.generator.py

@ -0,0 +1,270 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys
import termios, tty
import threading, queue
from ctypes import ( CDLL, c_void_p, cdll, CFUNCTYPE )
from ctypes import ( Structure, Union, pointer, POINTER, cast )
from ctypes import ( c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p )
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
gpmQueue = queue.Queue()
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
ec = event.contents
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
# print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
gpmQueue.put(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
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])
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])
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
gpm_handler = c_void_p.in_dll(libgpm, 'gpm_handler')
gpm_fd = c_int.in_dll(libgpm, 'gpm_fd')
print("Open Connection")
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
# RDFM;
# This is one of the rare cases where "Readuing the Fucking Manual" was the only way to solve this issue
# Not just that but a basic knowledge of c casting annd function pointers
# https://docs.python.org/3/library/ctypes.html#type-conversions
gpm_handler.value = cast(HANDLER_FUNC(my_handler),c_void_p).value
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
gpm_fd.value = _gpm_fd
print(f"{libgpm.gpm_handler=} {gpm_handler=} {gpm_handler.value=} {gpm_fd=}")
print("Starting Loop")
print(f"{sys.stdin=} {cstdin=}")
# setEcho(False)
old_settings = termios.tcgetattr(sys.stdin)
# new_settings = termios.tcgetattr(sys.stdin)
# new_settings[3] &= ~termios.ICANON
# new_settings[3] &= ~termios.ICRNL
# new_settings[3] &= ~termios.ECHO
# termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
tty.setcbreak(sys.stdin)
def _processGpm():
print(f"Thread Started")
while (c:=libgpm.Gpm_Getc(cstdin)):
print(f"Key: {c=:04X} - '{chr(c)}'")
gpmQueue.put(chr(c))
if c == ord('q'):
break
gpmQueue.put(None)
def _q_to_gen():
t = threading.Thread(target=_processGpm)
t.start()
while True:
item = gpmQueue.get()
if item is None:
break
print(f"1: {item=}")
gpmQueue.task_done()
yield item
t.join()
print(f"Thread Joined")
for item in _q_to_gen():
print(f"2: {item=}")
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# setEcho(True)
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
print(f"GPM Close()")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

264
tests/t.generic/test.ctypes.01.gpm.06.getch_rewrite.py

@ -0,0 +1,264 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import sys, os
import termios, tty
import threading, queue
try: import fcntl, termios, tty
except Exception as e:
print(f'ERROR: {e}')
exit(1)
from select import select
from ctypes import ( CDLL, c_void_p, cdll, CFUNCTYPE )
from ctypes import ( Structure, Union, pointer, POINTER, cast )
from ctypes import ( c_short, c_int, c_char, c_ushort, c_ubyte, c_void_p )
libgpm = CDLL('libgpm.so.2') # libgpm.so.2
libc = cdll.LoadLibrary('libc.so.6') # libc.so.6
cstdout = c_void_p.in_dll(libc, 'stdout')
cstdin = c_void_p.in_dll(libc, 'stdin')
gpmQueue = queue.Queue()
'''
#define GPM_MAGIC 0x47706D4C /* "GpmL" */
typedef struct Gpm_Connect {
unsigned short eventMask, defaultMask;
unsigned short minMod, maxMod;
int pid;
int vc;
} Gpm_Connect;
'''
class Gpm_Connect(Structure):
_fields_ = [
("eventMask", c_ushort),
("defaultMask", c_ushort),
("minMod", c_ushort),
("maxMod", c_ushort),
("pid", c_int),
("vc", c_int)]
'''
enum Gpm_Etype {
GPM_MOVE=1,
GPM_DRAG=2, /* exactly one of the bare ones is active at a time */
GPM_DOWN=4,
GPM_UP= 8,
#define GPM_BARE_EVENTS(type) ((type)&(0x0f|GPM_ENTER|GPM_LEAVE))
GPM_SINGLE=16, /* at most one in three is set */
GPM_DOUBLE=32,
GPM_TRIPLE=64, /* WARNING: I depend on the values */
GPM_MFLAG=128, /* motion during click? */
GPM_HARD=256, /* if set in the defaultMask, force an already
used event to pass over to another handler */
GPM_ENTER=512, /* enter event, user in Roi's */
GPM_LEAVE=1024 /* leave event, used in Roi's */
};
enum Gpm_Margin {GPM_TOP=1, GPM_BOT=2, GPM_LFT=4, GPM_RGT=8};
typedef struct Gpm_Event {
unsigned char buttons, modifiers; /* try to be a multiple of 4 */
unsigned short vc;
short dx, dy, x, y; /* displacement x,y for this event, and absolute x,y */
enum Gpm_Etype type;
/* clicks e.g. double click are determined by time-based processing */
int clicks;
enum Gpm_Margin margin;
/* wdx/y: displacement of wheels in this event. Absolute values are not
* required, because wheel movement is typically used for scrolling
* or selecting fields, not for cursor positioning. The application
* can determine when the end of file or form is reached, and not
* go any further.
* A single mouse will use wdy, "vertical scroll" wheel. */
short wdx, wdy;
} Gpm_Event;
'''
class Gpm_Event(Structure):
_fields_ = [
("buttons", c_ubyte),
("modifiers", c_ubyte),
("vc", c_short),
("dx", c_short),
("dy", c_short),
("x", c_short),
("y", c_short),
("type", c_int),
("clicks", c_int),
("margin", c_int),
("wdx", c_short),
("wdy", c_short)]
'''
int my_handler(Gpm_Event *event, void *data)
{
printf("Event Type : %d at x=%d y=%d\n", event->type, event->x, event->y);
return 0;
}
'''
# CFUNCTYPE(c_int, POINTER(Gpm_Event), c_void_p)
HANDLER_FUNC = CFUNCTYPE(c_int, POINTER(Gpm_Event), POINTER(c_void_p))
# gpm_handler = POINTER(HANDLER_FUNC).in_dll(libgpm, 'gpm_handler')
def my_handler(event:Gpm_Event, data):
# print(f"{event=} {data=} {dir(event)=} {event.contents=}")
# ec = event.contents
ec = event
buttons = ec.buttons
modifiers = ec.modifiers
vc = ec.vc
dx = ec.dx
dy = ec.dy
x = ec.x
y = ec.y
clicks = ec.clicks
wdx = ec.wdx
wdy = ec.wdy
types = []
for t in (tt:={1:"Move",2:"Drag",4:"Down",8:"Up",
16:"Single",32:"Double",64:"Triple",
128:"MFlag",256:"HARD",512:"ENTER",1024:"LEAVE"}):
if t&ec.type:
types.append(tt[t])
margin = {1:"Top",2:"Bottom",4:"Left",8:"Right"}.get(ec.margin,f"UNDEF ({ec.margin})!!!")
print(f"{buttons=}, {modifiers=}, {vc=}, {dx=}, {dy=}, {x=}, {y=}, {types=}, {clicks=}, {margin=}, {wdx=}, {wdy=}")
return 0
'''
int main()
{ Gpm_Connect conn;
int c;
conn.eventMask = ~0; /* Want to know about all the events */
conn.defaultMask = 0; /* don't handle anything by default */
conn.minMod = 0; /* want everything */
conn.maxMod = ~0; /* all modifiers included */
if(Gpm_Open(&conn, 0) == -1)
printf("Cannot connect to mouse server\n");
gpm_handler = my_handler;
while((c = Gpm_Getc(stdin)) != EOF)
printf("%c", c);
Gpm_Close();
return 0;
}
'''
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])
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])
def main():
conn = Gpm_Connect()
c = 0
conn.eventMask = ~0 # Want to know about all the events
conn.defaultMask = 0 # don't handle anything by default
conn.minMod = 0 # want everything
conn.maxMod = ~0 # all modifiers included
print("Open Connection")
if (_gpm_fd := libgpm.Gpm_Open(pointer(conn), 0)) == -1:
print("Cannot connect to mouse server\n")
print("Starting Loop")
print(f"{sys.stdin.fileno()=} {cstdin=}, {_gpm_fd=}")
# setEcho(False)
old_settings = termios.tcgetattr(sys.stdin)
# new_settings = termios.tcgetattr(sys.stdin)
# new_settings[3] &= ~termios.ICANON
# new_settings[3] &= ~termios.ICRNL
# new_settings[3] &= ~termios.ECHO
# termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
# tty.setcbreak(sys.stdin)
_fn = sys.stdin.fileno()
tty.setcbreak(_fn)
# Convert file descriptor to Python file object
if _gpm_fd <= 0:
print("GPM XTerm Daemon not supported")
else:
with os.fdopen(_gpm_fd, "r") as gpm_file_obj:
print(f"File obj: {gpm_file_obj=} {gpm_file_obj.fileno()=} ")
_ev = Gpm_Event()
while True:
rlist, _, _ = select( [sys.stdin, gpm_file_obj], [], [] )
if gpm_file_obj in rlist:
libgpm.Gpm_GetEvent(pointer(_ev))
my_handler(_ev, None)
if sys.stdin in rlist:
if (stdinRead := sys.stdin.read(1)) == "\033": # Check if the stream start with an escape sequence
_fl = fcntl.fcntl(_fn, fcntl.F_GETFL)
fcntl.fcntl(_fn, fcntl.F_SETFL, _fl | os.O_NONBLOCK) # Set the input as NONBLOCK to read the full sequence
stdinRead += sys.stdin.read(20) # Check if the stream start with an escape sequence
if stdinRead.startswith("\033[<"): # Clear the buffer if this is a mouse code
sys.stdin.read(0x40)
fcntl.fcntl(_fn, fcntl.F_SETFL, _fl)
out = stdinRead.replace('\033','<ESC>')
print(f"{out=}")
termios.tcsetattr(sys.stdin, termios.TCSANOW, old_settings)
# setEcho(True)
# while c := libgpm.Gpm_Getchar():
# print(f"Key: {c=:04X} ")
print(f"GPM Close()")
libgpm.Gpm_Close()
if __name__ == "__main__":
main()

70
tests/t.generic/test.ctypes.02.fd.conversion.py

@ -0,0 +1,70 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 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.
# Example from:
# https://www.linuxjournal.com/article/4600
# https://stackoverflow.com/questions/3794309/python-ctypes-python-file-object-c-file
import os
import sys
import ctypes
libc = ctypes.cdll.LoadLibrary('libc.so.6') # libc.so.6
# libc = ctypes.CDLL(ctypes.util.find_library("c"))
cstdout = ctypes.c_void_p.in_dll(libc, 'stdout')
cstdin = ctypes.c_void_p.in_dll(libc, 'stdin')
print(f"{cstdout=} {cstdin=}")
print(f"{sys.stdout.fileno()=} {sys.stdin.fileno()=}")
print(f"{sys.stdout=} {sys.stdin=}")
# Get the file descriptor of stdin
# fd = sys.stdin.fileno()
# fd = sys.stdout.fileno()
fd = sys.stderr.fileno()
# Define the prototype of fdopen
libc.fdopen.argtypes = [ctypes.c_int, ctypes.c_char_p]
libc.fdopen.restype = ctypes.c_void_p # FILE * is a void pointer
# Convert to FILE *
mode = b"r" # File mode (read)
file_ptr = libc.fdopen(fd, mode)
print(f"FILE * pointer: {file_ptr}")
# Define the prototype of fileno
libc.fileno.argtypes = [ctypes.c_void_p]
libc.fileno.restype = ctypes.c_int
# Convert FILE * back to file descriptor
fd_back = libc.fileno(file_ptr)
print(f"Back to file descriptor: {fd_back}")
# Convert file descriptor to Python file object
with os.fdopen(fd_back, "r") as file_obj:
print(f"File obj: {file_obj=} {file_obj.fileno()=} ")

27
tests/t.generic/test.curses.001.py

@ -0,0 +1,27 @@
import curses
def main(stdscr):
curses.curs_set(0)
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
print("\033[?1003h\n") # allows capturing mouse movement
while True:
c = stdscr.getch()
if c == curses.KEY_MOUSE:
try:
event = curses.getmouse()
x = event[2]
y = event[1]
dims = stdscr.getmaxyx()
stdscr.addstr(0,0,"="*dims[1])
stdscr.addstr(0,dims[1]-len(str(dims)),str(dims))
stdscr.addstr(0,0,str(event))
if event[4] == 4:
stdscr.addstr(x,y,"X")
else:
stdscr.addstr(x,y,"*")
except:
pass
stdscr.refresh()
curses.wrapper(main)

11
tests/t.input/test.input.py

@ -26,8 +26,7 @@ import sys, os
import logging
sys.path.append(os.path.join(sys.path[0],'../..'))
from TermTk import TTkLog, TTkK, TTkTerm
from TermTk.TTkCore.TTkTerm.input import TTkInput
from TermTk import TTkLog, TTkK, TTkTerm, TTkInput
def message_handler(mode, context, message):
log = logging.debug
@ -45,10 +44,10 @@ TTkLog.installMessageHandler(message_handler)
TTkLog.info("Retrieve Keyboard, Mouse press/drag/wheel Events")
TTkLog.info("Press q or <ESC> to exit")
TTkTerm.push(TTkTerm.Mouse.ON)
TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
TTkTerm.push(TTkTerm.SET_BRACKETED_PM)
TTkTerm.setEcho(False)
# TTkTerm.push(TTkTerm.Mouse.ON)
# TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
# TTkTerm.push(TTkTerm.SET_BRACKETED_PM)
# TTkTerm.setEcho(False)
def winCallback(width, height):
TTkLog.info(f"Resize: w:{width}, h:{height}")

3329
tests/utf-8/ascii.test.txt

File diff suppressed because it is too large Load Diff

6
tools/check.import.sh

@ -72,6 +72,12 @@ __check(){
-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/unix_gpm.py:import sys" \
-e "drivers/unix_gpm.py:import os" \
-e "drivers/unix_gpm.py:import re" \
-e "drivers/unix_gpm.py:import ctypes" \
-e "drivers/unix_gpm.py:import signal" \
-e "drivers/unix_gpm.py:from select import select" \
-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" \

36
tutorial/000-examples.rst

@ -11,7 +11,7 @@ TTkLineEdit
Init
----
`TTkLineEdit/Init.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/Init.01.py>`_ -
`TTkLineEdit/Init.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/Init.01.py>`__ -
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkLineEdit/Init.01.py>`__):
.. code:: bash
@ -24,7 +24,7 @@ Init
Set/Get Text
------------
`TTkLineEdit/SetGet.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/SetGet.01.py>`_ -
`TTkLineEdit/SetGet.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/SetGet.01.py>`__ -
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkLineEdit/SetGet.01.py>`__):
.. code:: bash
@ -37,7 +37,7 @@ Set/Get Text
Events
------
`TTkLineEdit/SetGet.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/Events.01.py>`_ -
`TTkLineEdit/SetGet.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkLineEdit/Events.01.py>`__ -
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkLineEdit/Events.01.py>`__):
.. _Examples-Terminal:
@ -48,48 +48,48 @@ TTkTerminal
.. note::
This widget is available only on Linux/Mac
`TTkTerminal/TerminalTab.01.Basic.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.01.Basic.py>`_
`TTkTerminal/TerminalTab.01.Basic.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.01.Basic.py>`__
`TTkTerminal/TerminalTab.02.AddButton.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.02.AddButton.py>`_
`TTkTerminal/TerminalTab.02.AddButton.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.02.AddButton.py>`__
`TTkTerminal/TerminalTab.03.KodeTab.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.03.KodeTab.py>`_
`TTkTerminal/TerminalTab.03.KodeTab.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.03.KodeTab.py>`__
`TTkTerminal/TerminalTab.04.KodeTab.close.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.04.KodeTab.close.py>`_
`TTkTerminal/TerminalTab.04.KodeTab.close.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTerminal/TerminalTab.04.KodeTab.close.py>`__
TTkTable
========
`TTkTable/table.01.basic.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.01.basic.py>`_
`TTkTable/table.01.basic.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.01.basic.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.01.basic.py>`__):
`TTkTable/table.02.custom.model.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.01.py>`_
`TTkTable/table.02.custom.model.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.01.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.02.custom.model.01.py>`__):
`TTkTable/table.02.custom.model.02.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.02.py>`_
`TTkTable/table.02.custom.model.02.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.02.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.02.custom.model.02.py>`__):
`TTkTable/table.02.custom.model.03.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.03.py>`_
`TTkTable/table.02.custom.model.03.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.02.custom.model.03.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.02.custom.model.03.py>`__):
`TTkTable/table.03.theming.color.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.01.py>`_
`TTkTable/table.03.theming.color.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.01.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.03.theming.color.01.py>`__):
`TTkTable/table.03.theming.color.02.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.02.py>`_
`TTkTable/table.03.theming.color.02.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.02.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.03.theming.color.02.py>`__):
`TTkTable/table.03.theming.color.03.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.03.py>`_
`TTkTable/table.03.theming.color.03.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.color.03.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.03.theming.color.03.py>`__):
`TTkTable/table.03.theming.headers.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.headers.01.py>`_
`TTkTable/table.03.theming.headers.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.headers.01.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.03.theming.headers.01.py>`__):
`TTkTable/table.03.theming.lines.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.lines.01.py>`_
`TTkTable/table.03.theming.lines.01.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.03.theming.lines.01.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.03.theming.lines.01.py>`__):
`TTkTable/table.04.methods.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.04.methods.py>`_
`TTkTable/table.04.methods.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.04.methods.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.04.methods.py>`__):
`TTkTable/table.05.events.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.05.events.py>`_
`TTkTable/table.05.events.py <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/examples/TTkTable/table.05.events.py>`__
(`tryItOnline <https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html?filePath=tutorial/examples/TTkTable/table.05.events.py>`__):
TTkTextEdit

Loading…
Cancel
Save