Browse Source

Initial drop of the platform specific routines, TTkInput is now a static class

pull/192/head
Eugenio Parodi 2 years ago
parent
commit
38ee81f855
  1. 141
      TermTk/TTkCore/TTkTerm/input.py
  2. 36
      TermTk/TTkCore/TTkTerm/readinputwindows.py
  3. 28
      TermTk/TTkCore/TTkTerm/term_base.py
  4. 0
      TermTk/TTkCore/drivers/__init__.py
  5. 0
      TermTk/TTkCore/drivers/pyodide.py
  6. 4
      TermTk/TTkCore/drivers/unix.py
  7. 0
      TermTk/TTkCore/drivers/unix_thread.py
  8. 344
      TermTk/TTkCore/drivers/windows.py
  9. 30
      TermTk/TTkCore/ttk.py
  10. 3
      TermTk/TTkTestWidgets/keypressview.py
  11. 2
      docs/MDNotes/msWindows/Resources.md
  12. 17
      tests/test.input.py
  13. 8
      tests/test.input.raw.py
  14. 0
      tests/test.input.win.01.py
  15. 427
      tests/test.input.win.02.py

141
TermTk/TTkCore/TTkTerm/input.py

@ -28,71 +28,76 @@ from time import time
import platform
if platform.system() == 'Linux':
from .readinputlinux import ReadInput
# from .readinputlinux_thread import ReadInput
from ..drivers.unix import TTkInputDriver
elif platform.system() == 'Darwin':
from .readinputlinux import ReadInput
from ..drivers.unix import TTkInputDriver
elif platform.system() == 'Windows':
from .readinputwindows import ReadInput
from ..drivers.windows import TTkInputDriver
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.signal import pyTTkSignal
from TermTk.TTkCore.TTkTerm.term import TTkTerm
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
class TTkInput:
__slots__ = (
'_readInput',
'_leftLastTime', '_midLastTime', '_rightLastTime',
'_leftTap', '_midTap', '_rightTap',
'_pasteBuffer', '_bracketedPaste',
# Signals
'inputEvent', 'pasteEvent'
)
def __init__(self):
self.inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent)
self.pasteEvent = pyTTkSignal(str)
self._pasteBuffer = ""
self._bracketedPaste = False
self._readInput = None
self._leftLastTime = 0
self._midLastTime = 0
self._rightLastTime = 0
self._leftTap = 0
self._midTap = 0
self._rightTap = 0
def close(self):
if self._readInput:
self._readInput.close()
def stop(self):
inputEvent = pyTTkSignal(TTkKeyEvent, TTkMouseEvent)
pasteEvent = pyTTkSignal(str)
_pasteBuffer = ""
_bracketedPaste = False
_readInput = None
_leftLastTime = 0
_midLastTime = 0
_rightLastTime = 0
_leftTap = 0
_midTap = 0
_rightTap = 0
_mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
class Mouse(int):
ON = 0x01
DIRECT = 0x02
@staticmethod
def init(mouse:bool=False, directMouse:bool=False) -> None:
TTkInput._readInput = TTkInputDriver()
TTkTerm.setMouse(mouse, directMouse)
@staticmethod
def close() -> None:
TTkTerm.setMouse(False, False)
if TTkInput._readInput:
TTkInput._readInput.close()
@staticmethod
def stop() -> None:
pass
def cont(self):
if self._readInput:
self._readInput.cont()
@staticmethod
def cont() -> None:
if TTkInput._readInput:
TTkInput._readInput.cont()
def start(self):
self._readInput = ReadInput()
for stdinRead in self._readInput.read():
self.key_process(stdinRead)
@staticmethod
def start() -> None:
for stdinRead in TTkInput._readInput.read():
TTkInput.key_process(stdinRead)
TTkLog.debug("Close TTkInput")
mouse_re = re.compile(r"\033\[<(\d+);(\d+);(\d+)([mM])")
def key_process(self, stdinRead):
if self._bracketedPaste:
@staticmethod
def key_process(stdinRead:str) -> None:
if TTkInput._bracketedPaste:
if stdinRead.endswith("\033[201~"):
self._pasteBuffer += stdinRead[:-6]
self._bracketedPaste = False
TTkInput._pasteBuffer += stdinRead[:-6]
TTkInput._bracketedPaste = False
# due to the CRNL methos (don't ask me why) the terminal
# is substituting all the \n with \r
self.pasteEvent.emit(self._pasteBuffer.replace('\r','\n'))
self._pasteBuffer = ""
TTkInput.pasteEvent.emit(TTkInput._pasteBuffer.replace('\r','\n'))
TTkInput._pasteBuffer = ""
else:
self._pasteBuffer += stdinRead
TTkInput._pasteBuffer += stdinRead
return
mevt,kevt = None, None
@ -102,7 +107,7 @@ class TTkInput:
kevt = TTkKeyEvent.parse(stdinRead)
else:
# Mouse Event
m = self.mouse_re.match(stdinRead)
m = TTkInput._mouse_re.match(stdinRead)
if not m:
# TODO: Return Error
hex = [f"0x{ord(x):02x}" for x in stdinRead]
@ -134,18 +139,18 @@ class TTkInput:
mod |= TTkK.AltModifier
if code == 0x00:
self._leftLastTime, self._leftTap = _checkTap(self._leftLastTime, self._leftTap)
tap = self._leftTap
TTkInput._leftLastTime, TTkInput._leftTap = _checkTap(TTkInput._leftLastTime, TTkInput._leftTap)
tap = TTkInput._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
TTkInput._midLastTime, TTkInput._midTap = _checkTap(TTkInput._midLastTime, TTkInput._midTap)
tap = TTkInput._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
TTkInput._rightLastTime, TTkInput._rightTap = _checkTap(TTkInput._rightLastTime, TTkInput._rightTap)
tap = TTkInput._rightTap
key = TTkMouseEvent.RightButton
evt = TTkMouseEvent.Press if state=="M" else TTkMouseEvent.Release
elif code == 0x20:
@ -171,37 +176,13 @@ class TTkInput:
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, m.group(0).replace("\033", "<ESC>"))
if kevt or mevt:
self.inputEvent.emit(kevt, mevt)
TTkInput.inputEvent.emit(kevt, mevt)
return
if stdinRead.startswith("\033[200~"):
self._pasteBuffer = stdinRead[6:]
self._bracketedPaste = True
TTkInput._pasteBuffer = stdinRead[6:]
TTkInput._bracketedPaste = True
return
hex = [f"0x{ord(x):02x}" for x in stdinRead]
TTkLog.error("UNHANDLED: "+stdinRead.replace("\033","<ESC>") + " - "+",".join(hex))
def main():
print("Retrieve Keyboard, Mouse press/drag/wheel Events")
print("Press q or <ESC> to exit")
from term import TTkTerm
TTkTerm.push(TTkTerm.Mouse.ON)
TTkTerm.setEcho(False)
def callback(kevt=None, mevt=None):
if kevt is not None:
print(f"Key Event: {kevt}")
if mevt is not None:
print(f"Mouse Event: {mevt}")
testInput = TTkInput()
testInput.get_key(callback)
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkTerm.setEcho(True)
if __name__ == "__main__":
main()

36
TermTk/TTkCore/TTkTerm/readinputwindows.py

@ -1,36 +0,0 @@
# 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.
class ReadInput():
def __init__(self):
pass
def close(self):
pass
def cont(self):
pass
def read(self):
from time import sleep
sleep(5)
yield ""

28
TermTk/TTkCore/TTkTerm/term_base.py

@ -30,13 +30,13 @@ class TTkTermBase():
SET_BRACKETED_PM = "\033[?2004h" # Ps = 2 0 0 4 ⇒ Set bracketed paste mode, xterm.
RESET_BRACKETED_PM = "\033[?2004l" # Ps = 2 0 0 4 ⇒ Reset bracketed paste mode, xterm.
class Mouse():
class Mouse(str):
ON = "\033[?1002h\033[?1006h" # Enable reporting of mouse position on click and release
OFF = "\033[?1002l\033[?1006l" # 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():
class Cursor(str):
# 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
@ -91,22 +91,29 @@ class TTkTermBase():
_sigWinChCb = None
@staticmethod
def init(mouse: bool = True, directMouse: bool = False, title: str = "TermTk", sigmask=0):
def init(title: str = "TermTk", sigmask=0):
TTkTermBase.title = title
TTkTermBase.mouse = mouse | directMouse
TTkTermBase.directMouse = directMouse
TTkTermBase.Cursor.hide()
TTkTermBase.push(TTkTermBase.escTitle(TTkTermBase.title))
TTkTermBase.push(TTkTermBase.ALT_SCREEN)
TTkTermBase.push(TTkTermBase.SET_BRACKETED_PM)
TTkTermBase.push(TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
TTkTermBase.setSigmask(sigmask, False)
@staticmethod
def setMouse(mouse:bool=False, directMouse:bool=False) -> None:
TTkTermBase.mouse = mouse | directMouse
TTkTermBase.directMouse = directMouse
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
else:
TTkTermBase.push(TTkTermBase.Mouse.OFF)
if TTkTermBase.directMouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)
TTkTermBase.setSigmask(sigmask, False)
else:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_OFF)
@staticmethod
def exit():
@ -125,10 +132,7 @@ class TTkTermBase():
@staticmethod
def cont():
TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.SET_BRACKETED_PM + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title))
if TTkTermBase.mouse:
TTkTermBase.push(TTkTermBase.Mouse.ON)
if TTkTermBase.directMouse:
TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON)
TTkTermBase.setMouse(TTkTermBase.mouse, TTkTermBase.directMouse)
TTkTermBase.setEcho(False)
TTkTermBase.CRNL(False)

0
TermTk/TTkCore/drivers/__init__.py

0
TermTk/TTkCore/drivers/pyodide.py

4
TermTk/TTkCore/TTkTerm/readinputlinux.py → TermTk/TTkCore/drivers/unix.py

@ -20,6 +20,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['']
import sys, os, re
from select import select
@ -29,7 +31,7 @@ except Exception as e:
exit(1)
class ReadInput():
class TTkInputDriver():
__slots__ = ('_readPipe','_attr')
def __init__(self):

0
TermTk/TTkCore/TTkTerm/readinputlinux_thread.py → TermTk/TTkCore/drivers/unix_thread.py

344
TermTk/TTkCore/drivers/windows.py

@ -0,0 +1,344 @@
# MIT License
#
# Copyright (c) 2023 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__ = ['']
import sys
from ctypes import Structure, Union, byref, wintypes, windll
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
# Based on the example ported from:
# https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
# https://github.com/ceccopierangiolieugenio/pyTermTk -> tests/test.input.win.01.py
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
STD_INPUT_HANDLE = wintypes.DWORD(-10) # The standard input device. Initially, this is the console input buffer, CONIN$.
STD_OUTPUT_HANDLE = wintypes.DWORD(-11) # The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE = wintypes.DWORD(-12) # The standard error device. Initially, this is the active console screen buffer, CONOUT$.
INVALID_HANDLE_VALUE = -1 # WinBase.h
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
ENABLE_ECHO_INPUT = 0x0004 # Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are typed into the console. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_INSERT_MODE = 0x0020 # When enabled, text entered in a console window will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
ENABLE_LINE_INPUT = 0x0002 # The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disabled, the functions return when one or more characters are available.
ENABLE_MOUSE_INPUT = 0x0010 # If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from the input buffer.
ENABLE_PROCESSED_INPUT = 0x0001 # CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and line feed characters are handled by the system.
ENABLE_QUICK_EDIT_MODE = 0x0040 # This flag enables the user to use the mouse to select and edit text. To enable this mode, use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use ENABLE_EXTENDED_FLAGS without this flag.
ENABLE_WINDOW_INPUT = 0x0008 # User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about these events can be read from the input buffer by applications using the ReadConsoleInput function, but not by those using ReadFile or ReadConsole.
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 # Setting this flag directs the Virtual Terminal processing engine to convert user input received by the console window into Console Virtual Terminal Sequences that can be retrieved by a supporting application through ReadFile or ReadConsole functions.
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
# https://learn.microsoft.com/en-us/windows/console/input-record-str
FOCUS_EVENT = 0x0010 # The Event member contains a FOCUS_EVENT_RECORD structure. These events are used internally and should be ignored.
KEY_EVENT = 0x0001 # The Event member contains a KEY_EVENT_RECORD structure with information about a keyboard event.
MENU_EVENT = 0x0008 # The Event member contains a MENU_EVENT_RECORD structure. These events are used internally and should be ignored.
MOUSE_EVENT = 0x0002 # The Event member contains a MOUSE_EVENT_RECORD structure with information about a mouse movement or button press event.
WINDOW_BUFFER_SIZE_EVENT = 0x0004 # The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with information about the new size of the console screen buffer.
# https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
# dwButtonState
FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 # The leftmost mouse button.
FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 # The second button fom the left.
FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 # The third button from the left.
FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 # The fourth button from the left.
RIGHTMOST_BUTTON_PRESSED = 0x0002 # The rightmost mouse button.
# dwControlKeyState
CAPSLOCK_ON = 0x0080 # The CAPS LOCK light is on.
ENHANCED_KEY = 0x0100 # The key is enhanced. See remarks.
LEFT_ALT_PRESSED = 0x0002 # The left ALT key is pressed.
LEFT_CTRL_PRESSED = 0x0008 # The left CTRL key is pressed.
NUMLOCK_ON = 0x0020 # The NUM LOCK light is on.
RIGHT_ALT_PRESSED = 0x0001 # The right ALT key is pressed.
RIGHT_CTRL_PRESSED = 0x0004 # The right CTRL key is pressed.
SCROLLLOCK_ON = 0x0040 # The SCROLL LOCK light is on.
SHIFT_PRESSED = 0x0010 # The SHIFT key is pressed.
# dwEventFlags
DOUBLE_CLICK = 0x0002 # The second click (button press) of a double-click occurred. The first click is returned as a regular button-press event.
MOUSE_HWHEELED = 0x0008 # The horizontal mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated to the right. Otherwise, the wheel was rotated to the left.
MOUSE_MOVED = 0x0001 # A change in mouse position occurred.
MOUSE_WHEELED = 0x0004 # The vertical mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user. Otherwise, the wheel was rotated backward, toward the user.
# https://docs.microsoft.com/en-us/windows/console/coord-str
#
# typedef struct _COORD {
# SHORT X;
# SHORT Y;
# } COORD, *PCOORD;
class COORD(Structure):
_fields_ = [
("X", wintypes.SHORT),
("Y", wintypes.SHORT)]
# https://docs.microsoft.com/en-us/windows/console/key-event-record-str
#
# typedef struct _KEY_EVENT_RECORD {
# BOOL bKeyDown;
# WORD wRepeatCount;
# WORD wVirtualKeyCode;
# WORD wVirtualScanCode;
# union {
# WCHAR UnicodeChar;
# CHAR AsciiChar;
# } uChar;
# DWORD dwControlKeyState;
# } KEY_EVENT_RECORD;
class KEY_EVENT_RECORD(Structure):
class _uChar(Union):
_fields_ = [
("UnicodeChar", wintypes.WCHAR) ,
("AsciiChar" , wintypes.CHAR ) ]
_fields_ = [
("bKeyDown" , wintypes.BOOL ),
("wRepeatCount" , wintypes.WORD ),
("wVirtualKeyCode" , wintypes.WORD ),
("wVirtualScanCode" , wintypes.WORD ),
("uChar" , _uChar ),
("dwControlKeyState", wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
#
# typedef struct _MOUSE_EVENT_RECORD {
# COORD dwMousePosition;
# DWORD dwButtonState;
# DWORD dwControlKeyState;
# DWORD dwEventFlags;
# } MOUSE_EVENT_RECORD;
class MOUSE_EVENT_RECORD(Structure):
_fields_ = [
("dwMousePosition" , COORD),
("dwButtonState" , wintypes.DWORD),
("dwControlKeyState", wintypes.DWORD),
("dwEventFlags" , wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str
#
# typedef struct _WINDOW_BUFFER_SIZE_RECORD {
# COORD dwSize;
# } WINDOW_BUFFER_SIZE_RECORD;
class WINDOW_BUFFER_SIZE_RECORD(Structure):
_fields_ = [("dwSize", COORD)]
# https://docs.microsoft.com/en-us/windows/console/menu-event-record-str
#
# typedef struct _MENU_EVENT_RECORD {
# UINT dwCommandId;
# } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
class MENU_EVENT_RECORD(Structure):
_fields_ = [("dwCommandId", wintypes.UINT)]
# https://docs.microsoft.com/en-us/windows/console/focus-event-record-str
#
# typedef struct _FOCUS_EVENT_RECORD {
# BOOL bSetFocus;
# } FOCUS_EVENT_RECORD;
class FOCUS_EVENT_RECORD(Structure):
_fields_ = [("bSetFocus", wintypes.BOOL)]
# https://docs.microsoft.com/en-us/windows/console/input-record-str
#
# typedef struct _INPUT_RECORD {
# WORD EventType;
# union {
# KEY_EVENT_RECORD KeyEvent;
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
# } Event;
# } INPUT_RECORD;
class INPUT_RECORD(Structure):
class _Event(Union):
_fields_ = [
("KeyEvent" , KEY_EVENT_RECORD ),
("MouseEvent" , MOUSE_EVENT_RECORD ),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent" , MENU_EVENT_RECORD ),
("FocusEvent" , FOCUS_EVENT_RECORD )]
_fields_ = [
("EventType", wintypes.WORD),
("Event" , _Event )]
class TTkInputDriver():
def __init__(self):
self._run = True
self._initTerminal()
def _initTerminal(self):
# Get the standard input handle.
# From:
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
#
# HANDLE WINAPI GetStdHandle(
# _In_ DWORD nStdHandle
# );
GetStdHandle = windll.kernel32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
self._hStdIn = GetStdHandle(STD_INPUT_HANDLE)
if self._hStdIn == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
self._hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
if self._hStdOut == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
# Save the current input mode, to be restored on exit.
# From:
# https://learn.microsoft.com/en-us/windows/console/GetConsoleMode
#
# BOOL WINAPI GetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _Out_ LPDWORD lpMode
# );
self._GetConsoleMode = windll.kernel32.GetConsoleMode
self._GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
self._GetConsoleMode.restype = wintypes.BOOL
self._fdwSaveOldModeIn = wintypes.DWORD()
if not self._GetConsoleMode(self._hStdIn, byref(self._fdwSaveOldModeIn)):
raise Exception("GetConsoleMode")
self._fdwSaveOldModeOut = wintypes.DWORD()
if not self._GetConsoleMode(self._hStdOut, byref(self._fdwSaveOldModeOut)):
raise Exception("GetConsoleMode")
# TTkLog.debug(f"{fdwSaveOldModeIn.value=:02x}")
# TTkLog.debug(f"{fdwSaveOldModeOut.value=:02x}")
# Enable the window and mouse input events.
# From:
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
#
# BOOL WINAPI SetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _In_ DWORD dwMode
# );
self._SetConsoleMode = windll.kernel32.SetConsoleMode
self._SetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.DWORD]
self._SetConsoleMode.restype = wintypes.BOOL
fdwModeIn = ENABLE_VIRTUAL_TERMINAL_INPUT
# fdwModeIn = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT
# fdwModeIn = 0x0218
if not self._SetConsoleMode(self._hStdIn, fdwModeIn):
raise Exception("SetConsoleMode")
fdwModeOut = self._fdwSaveOldModeOut.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
# fdwModeIn = 0x0218
if not self._SetConsoleMode(self._hStdOut, fdwModeOut):
raise Exception("SetConsoleMode")
# TTkLog.debug(f"{fdwModeIn=:02x}")
# TTkLog.debug(f"{fdwModeOut=:02x}")
def close(self):
self._run = False
# Restore input mode on exit.
if not self._SetConsoleMode(self._hStdIn, self._fdwSaveOldModeIn):
raise Exception("SetConsoleMode")
if not self._SetConsoleMode(self._hStdOut, self._fdwSaveOldModeOut):
raise Exception("SetConsoleMode")
def cont(self):
pass
def read(self) -> str|None:
# From:
# https://learn.microsoft.com/en-us/windows/console/ReadConsoleInput
#
# BOOL WINAPI ReadConsoleInput(
# _In_ HANDLE hConsoleInput,
# _Out_ PINPUT_RECORD lpBuffer,
# _In_ DWORD nLength,
# _Out_ LPDWORD lpNumberOfEventsRead
# );
ReadConsoleInput = windll.kernel32.ReadConsoleInputW # Unicode
# ReadConsoleInput = windll.kernel32.ReadConsoleInputA # ANSII
# ReadConsoleInput.argtypes = [wintypes.HANDLE,
# wintypes.LPINT,
# wintypes.DWORD,
# wintypes.LPWORD]
ReadConsoleInput.restype = wintypes.BOOL
# DWORD cNumRead;
# INPUT_RECORD irInBuf[128];
cNumRead = wintypes.DWORD(0)
irInBuf = (INPUT_RECORD * 256)()
# Loop to read and handle the next 100 input events.
while self._run:
# Wait for the events.
if not ReadConsoleInput(
self._hStdIn, # input buffer handle
byref(irInBuf), # buffer to read into
256, # size of read buffer
byref(cNumRead)): # number of records read
raise Exception("ReadConsoleInput")
# TTkLog.debug(f"{self._hStdIn=} {irInBuf=} {cNumRead=}")
# TTkLog.debug(f"{cNumRead=}")
# Dispatch the events to the appropriate handler.
saveKey = ""
for bb in irInBuf[:cNumRead.value]:
# if not bb.EventType: continue
# TTkLog.debug(f"{bb=} {bb.EventType=} {cNumRead.value=}")
if bb.EventType == KEY_EVENT:
if not bb.Event.KeyEvent.bKeyDown:
continue
saveKey += bb.Event.KeyEvent.uChar.UnicodeChar
elif bb.EventType == MOUSE_EVENT:
# It is not supposed to receive Mouse Events
# due to ENABLE_VIRTUAL_TERMINAL_PROCESSING
# everything is received as ANSI sequence
pass
elif bb.EventType == WINDOW_BUFFER_SIZE_EVENT:
TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent=}")
TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent.dwSize.X=}")
TTkLog.debug(f"{bb.Event.WindowBufferSizeEvent.dwSize.Y=}")
yield saveKey

30
TermTk/TTkCore/ttk.py

@ -40,19 +40,18 @@ from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl
from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.timer import TTkTimer
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkTheme.theme import TTkTheme
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.container import TTkContainer
class TTk(TTkContainer):
class _mouseCursor(TTkWidget):
__slots__ = ('_cursor','_color')
def __init__(self, input):
def __init__(self):
super().__init__(name='MouseCursor')
self._cursor = ''
self._color = TTkColor.RST
self.resize(1,1)
input.inputEvent.connect(self._mouseInput)
TTkInput.inputEvent.connect(self._mouseInput)
@pyTTkSlot(TTkKeyEvent, TTkMouseEvent)
def _mouseInput(self, _, mevt):
if mevt is not None:
@ -99,9 +98,8 @@ class TTk(TTkContainer):
super().__init__(*args, **kwargs)
self._termMouse = True
self._termDirectMouse = kwargs.get('mouseTrack',False)
self._input = TTkInput()
self._input.inputEvent.connect(self._processInput)
self._input.pasteEvent.connect(self._processPaste)
TTkInput.inputEvent.connect(self._processInput)
TTkInput.pasteEvent.connect(self._processPaste)
self._title = kwargs.get('title','TermTk')
self._sigmask = kwargs.get('sigmask', TTkK.NONE)
self._showMouseCursor = os.environ.get("TTK_MOUSE",kwargs.get('mouseCursor', False))
@ -145,6 +143,7 @@ class TTk(TTkContainer):
TTkLog.debug(f" Version: {TTkCfg.version}" )
TTkLog.debug( "" )
TTkLog.debug( "Starting Main Loop..." )
TTkLog.debug(f"screen = ({TTkTerm.getTerminalSize()})")
# Register events
signal.signal(signal.SIGTSTP, self._SIGSTOP) # Ctrl-Z
@ -162,15 +161,16 @@ class TTk(TTkContainer):
# Keep track of the multiTap to avoid the extra key release
self._lastMultiTap = False
TTkInput.init(
mouse=self._termMouse,
directMouse=self._termDirectMouse)
TTkTerm.init(
title=self._title,
sigmask=self._sigmask,
mouse=self._termMouse,
directMouse=self._termDirectMouse )
sigmask=self._sigmask)
if self._showMouseCursor:
TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
m = TTk._mouseCursor(self._input)
m = TTk._mouseCursor()
self.rootLayout().addWidget(m)
self._mainLoop()
@ -183,7 +183,7 @@ class TTk(TTkContainer):
def _mainLoop(self):
if platform.system() == 'Emscripten':
return
self._input.start()
TTkInput.start()
@pyTTkSlot(str)
def _processPaste(self, txt:str):
@ -325,15 +325,15 @@ class TTk(TTkContainer):
'''Tells the application to exit with a return code.'''
if self._timer:
self._timer.timeout.disconnect(self._time_event)
self._input.inputEvent.clear()
TTkInput.inputEvent.clear()
self._paintEvent.set()
self._input.close()
TTkInput.close()
def _SIGSTOP(self, signum, frame):
"""Reset terminal settings and stop background input read before putting to sleep"""
TTkLog.debug("Captured SIGSTOP <CTRL-z>")
TTkTerm.stop()
self._input.stop()
TTkInput.stop()
# TODO: stop the threads
os.kill(os.getpid(), signal.SIGSTOP)
@ -341,7 +341,7 @@ class TTk(TTkContainer):
"""Set terminal settings and restart background input read"""
TTkLog.debug("Captured SIGCONT 'fg/bg'")
TTkTerm.cont()
self._input.cont()
TTkInput.cont()
TTkHelper.rePaintAll()
# TODO: Restart threads
# TODO: Redraw the screen

3
TermTk/TTkTestWidgets/keypressview.py

@ -22,6 +22,7 @@
__all__ = ['TTkKeyPressView']
from TermTk.TTkCore.TTkTerm.input import TTkInput
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent, mod2str, key2str
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
from TermTk.TTkCore.helper import TTkHelper
@ -38,7 +39,7 @@ class TTkKeyPressView(TTkWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
TTkHelper._rootWidget._input.inputEvent.connect(self._processInput)
TTkInput.inputEvent.connect(self._processInput)
self._keys = []
self._fadeDuration = 2.5
self._anim = TTkPropertyAnimation(self, '_pushFade')

2
docs/MDNotes/msWindows/Resources.md

@ -12,6 +12,7 @@
C:
cd C:\users\one\AppData\Local\Programs\Python\Python310-32
python.exe demo/demo.py
python.exe tests/test.input.win.py
```
# termios wrappers
@ -20,6 +21,7 @@ python.exe demo/demo.py
# Competitors with MS-Win support
### Textual -> https://github.com/Textualize/textual
https://github.com/Textualize/textual/blob/main/src/textual/drivers/win32.py
### TheVTPyProject -> https://github.com/srccircumflex/TheVTPyProject

17
tests/test.input.py

@ -26,7 +26,8 @@ import sys, os
import logging
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk import TTkLog, TTkK, TTkInput, TTkTerm
from TermTk import TTkLog, TTkK, TTkTerm
from TermTk.TTkCore.TTkTerm.input import TTkInput
def message_handler(mode, context, message):
log = logging.debug
@ -54,7 +55,6 @@ def winCallback(width, height):
TTkTerm.registerResizeCb(winCallback)
input = TTkInput()
def keyCallback(kevt=None, mevt=None):
if mevt is not None:
@ -65,7 +65,7 @@ def keyCallback(kevt=None, mevt=None):
else:
TTkLog.info(f"Key Event: Special '{kevt}'")
if kevt.key == "q":
input.close()
TTkInput.close()
return False
return True
@ -73,11 +73,12 @@ def pasteCallback(txt:str):
TTkLog.info(f"PASTE = {txt}")
return True
input.inputEvent.connect(keyCallback)
input.pasteEvent.connect(pasteCallback)
TTkInput.inputEvent.connect(keyCallback)
TTkInput.pasteEvent.connect(pasteCallback)
TTkInput.init(mouse=True, directMouse=True)
# TTkInput.init(mouse=True, directMouse=False)
try:
input.start()
TTkInput.start()
finally:
TTkTerm.push(TTkTerm.Mouse.OFF + TTkTerm.Mouse.DIRECT_OFF)
TTkInput.close()
TTkTerm.setEcho(True)

8
tests/test.input.raw.py

@ -40,6 +40,7 @@ def reset():
# Reset
TTkTerm.push("\033[?1000l")
TTkTerm.push("\033[?1002l")
TTkTerm.push("\033[?1003l")
TTkTerm.push("\033[?1015l")
TTkTerm.push("\033[?1006l")
TTkTerm.push("\033[?1049l") # Switch to normal screen
@ -49,10 +50,11 @@ reset()
TTkTerm.push("\033[?2004h") # Paste Bracketed mode
# TTkTerm.push("\033[?1000h")
# TTkTerm.push("\033[?1002h")
# TTkTerm.push("\033[?1006h")
TTkTerm.push("\033[?1002h")
# TTkTerm.push("\033[?1003h")
TTkTerm.push("\033[?1006h")
# TTkTerm.push("\033[?1015h")
TTkTerm.push("\033[?1049h") # Switch to alternate screen
# TTkTerm.push("\033[?1049h") # Switch to alternate screen
# TTkTerm.push(TTkTerm.Mouse.ON)
# TTkTerm.push(TTkTerm.Mouse.DIRECT_ON)
TTkTerm.setEcho(False)

0
tests/test.input.win.py → tests/test.input.win.01.py

427
tests/test.input.win.02.py

@ -0,0 +1,427 @@
# MIT License
#
# Copyright (c) 2023 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
from ctypes import Structure, Union, byref, wintypes, windll
sys.path.append(os.path.join(sys.path[0],'..'))
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
# Example ported from:
# https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
STD_INPUT_HANDLE = wintypes.DWORD(-10) # The standard input device. Initially, this is the console input buffer, CONIN$.
STD_OUTPUT_HANDLE = wintypes.DWORD(-11) # The standard output device. Initially, this is the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE = wintypes.DWORD(-12) # The standard error device. Initially, this is the active console screen buffer, CONOUT$.
INVALID_HANDLE_VALUE = -1 # WinBase.h
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
ENABLE_ECHO_INPUT = 0x0004 # Characters read by the ReadFile or ReadConsole function are written to the active screen buffer as they are typed into the console. This mode can be used only if the ENABLE_LINE_INPUT mode is also enabled.
ENABLE_INSERT_MODE = 0x0020 # When enabled, text entered in a console window will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
ENABLE_LINE_INPUT = 0x0002 # The ReadFile or ReadConsole function returns only when a carriage return character is read. If this mode is disabled, the functions return when one or more characters are available.
ENABLE_MOUSE_INPUT = 0x0010 # If the mouse pointer is within the borders of the console window and the window has the keyboard focus, mouse events generated by mouse movement and button presses are placed in the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from the input buffer.
ENABLE_PROCESSED_INPUT = 0x0001 # CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer is being read by ReadFile or ReadConsole, other control keys are processed by the system and are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also enabled, backspace, carriage return, and line feed characters are handled by the system.
ENABLE_QUICK_EDIT_MODE = 0x0040 # This flag enables the user to use the mouse to select and edit text. To enable this mode, use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use ENABLE_EXTENDED_FLAGS without this flag.
ENABLE_WINDOW_INPUT = 0x0008 # User interactions that change the size of the console screen buffer are reported in the console's input buffer. Information about these events can be read from the input buffer by applications using the ReadConsoleInput function, but not by those using ReadFile or ReadConsole.
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 # Setting this flag directs the Virtual Terminal processing engine to convert user input received by the console window into Console Virtual Terminal Sequences that can be retrieved by a supporting application through ReadFile or ReadConsole functions.
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
# https://learn.microsoft.com/en-us/windows/console/input-record-str
FOCUS_EVENT = 0x0010 # The Event member contains a FOCUS_EVENT_RECORD structure. These events are used internally and should be ignored.
KEY_EVENT = 0x0001 # The Event member contains a KEY_EVENT_RECORD structure with information about a keyboard event.
MENU_EVENT = 0x0008 # The Event member contains a MENU_EVENT_RECORD structure. These events are used internally and should be ignored.
MOUSE_EVENT = 0x0002 # The Event member contains a MOUSE_EVENT_RECORD structure with information about a mouse movement or button press event.
WINDOW_BUFFER_SIZE_EVENT = 0x0004 # The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with information about the new size of the console screen buffer.
# https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
# dwButtonState
FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 # The leftmost mouse button.
FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 # The second button fom the left.
FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 # The third button from the left.
FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 # The fourth button from the left.
RIGHTMOST_BUTTON_PRESSED = 0x0002 # The rightmost mouse button.
# dwControlKeyState
CAPSLOCK_ON = 0x0080 # The CAPS LOCK light is on.
ENHANCED_KEY = 0x0100 # The key is enhanced. See remarks.
LEFT_ALT_PRESSED = 0x0002 # The left ALT key is pressed.
LEFT_CTRL_PRESSED = 0x0008 # The left CTRL key is pressed.
NUMLOCK_ON = 0x0020 # The NUM LOCK light is on.
RIGHT_ALT_PRESSED = 0x0001 # The right ALT key is pressed.
RIGHT_CTRL_PRESSED = 0x0004 # The right CTRL key is pressed.
SCROLLLOCK_ON = 0x0040 # The SCROLL LOCK light is on.
SHIFT_PRESSED = 0x0010 # The SHIFT key is pressed.
# dwEventFlags
DOUBLE_CLICK = 0x0002 # The second click (button press) of a double-click occurred. The first click is returned as a regular button-press event.
MOUSE_HWHEELED = 0x0008 # The horizontal mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated to the right. Otherwise, the wheel was rotated to the left.
MOUSE_MOVED = 0x0001 # A change in mouse position occurred.
MOUSE_WHEELED = 0x0004 # The vertical mouse wheel was moved.
# If the high word of the dwButtonState member contains a positive value, the wheel was rotated forward, away from the user. Otherwise, the wheel was rotated backward, toward the user.
# https://docs.microsoft.com/en-us/windows/console/coord-str
#
# typedef struct _COORD {
# SHORT X;
# SHORT Y;
# } COORD, *PCOORD;
class COORD(Structure):
_fields_ = [
("X", wintypes.SHORT),
("Y", wintypes.SHORT)]
# https://docs.microsoft.com/en-us/windows/console/key-event-record-str
#
# typedef struct _KEY_EVENT_RECORD {
# BOOL bKeyDown;
# WORD wRepeatCount;
# WORD wVirtualKeyCode;
# WORD wVirtualScanCode;
# union {
# WCHAR UnicodeChar;
# CHAR AsciiChar;
# } uChar;
# DWORD dwControlKeyState;
# } KEY_EVENT_RECORD;
class KEY_EVENT_RECORD(Structure):
class _uChar(Union):
_fields_ = [
("UnicodeChar", wintypes.WCHAR) ,
("AsciiChar" , wintypes.CHAR ) ]
_fields_ = [
("bKeyDown" , wintypes.BOOL ),
("wRepeatCount" , wintypes.WORD ),
("wVirtualKeyCode" , wintypes.WORD ),
("wVirtualScanCode" , wintypes.WORD ),
("uChar" , _uChar ),
("dwControlKeyState", wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
#
# typedef struct _MOUSE_EVENT_RECORD {
# COORD dwMousePosition;
# DWORD dwButtonState;
# DWORD dwControlKeyState;
# DWORD dwEventFlags;
# } MOUSE_EVENT_RECORD;
class MOUSE_EVENT_RECORD(Structure):
_fields_ = [
("dwMousePosition" , COORD),
("dwButtonState" , wintypes.DWORD),
("dwControlKeyState", wintypes.DWORD),
("dwEventFlags" , wintypes.DWORD)]
# https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str
#
# typedef struct _WINDOW_BUFFER_SIZE_RECORD {
# COORD dwSize;
# } WINDOW_BUFFER_SIZE_RECORD;
class WINDOW_BUFFER_SIZE_RECORD(Structure):
_fields_ = [("dwSize", COORD)]
# https://docs.microsoft.com/en-us/windows/console/menu-event-record-str
#
# typedef struct _MENU_EVENT_RECORD {
# UINT dwCommandId;
# } MENU_EVENT_RECORD, *PMENU_EVENT_RECORD;
class MENU_EVENT_RECORD(Structure):
_fields_ = [("dwCommandId", wintypes.UINT)]
# https://docs.microsoft.com/en-us/windows/console/focus-event-record-str
#
# typedef struct _FOCUS_EVENT_RECORD {
# BOOL bSetFocus;
# } FOCUS_EVENT_RECORD;
class FOCUS_EVENT_RECORD(Structure):
_fields_ = [("bSetFocus", wintypes.BOOL)]
# https://docs.microsoft.com/en-us/windows/console/input-record-str
#
# typedef struct _INPUT_RECORD {
# WORD EventType;
# union {
# KEY_EVENT_RECORD KeyEvent;
# MOUSE_EVENT_RECORD MouseEvent;
# WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
# MENU_EVENT_RECORD MenuEvent;
# FOCUS_EVENT_RECORD FocusEvent;
# } Event;
# } INPUT_RECORD;
class INPUT_RECORD(Structure):
class _Event(Union):
_fields_ = [
("KeyEvent" , KEY_EVENT_RECORD ),
("MouseEvent" , MOUSE_EVENT_RECORD ),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent" , MENU_EVENT_RECORD ),
("FocusEvent" , FOCUS_EVENT_RECORD )]
_fields_ = [
("EventType", wintypes.WORD),
("Event" , _Event )]
def reset():
# Reset
sys.stdout.write("\033[?1000l")
# sys.stdout.write("\033[?1002l")
sys.stdout.write("\033[?1003l")
sys.stdout.write("\x1b[?1004l") # UnSet Bracheted Paste Mode
sys.stdout.write("\033[?1006l")
sys.stdout.write("\033[?1015l")
# sys.stdout.write("\033[?1049l") # Switch to normal screen
# sys.stdout.write("\033[?2004l") # Paste Bracketed mode
sys.stdout.flush()
def init():
sys.stdout.write("\x1b[?1000h")
sys.stdout.write("\x1b[?1003h")
sys.stdout.write("\x1b[?1004h") # Set Bracheted Paste Mode
sys.stdout.write("\x1b[?1006h")
sys.stdout.write("\x1b[?1015h")
sys.stdout.flush()
# Get the standard input handle.
# From:
# https://learn.microsoft.com/en-us/windows/console/getstdhandle
#
# HANDLE WINAPI GetStdHandle(
# _In_ DWORD nStdHandle
# );
GetStdHandle = windll.kernel32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
hStdIn = GetStdHandle(STD_INPUT_HANDLE)
if hStdIn == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
if hStdOut == INVALID_HANDLE_VALUE:
raise Exception("GetStdHandle")
# Save the current input mode, to be restored on exit.
# From:
# https://learn.microsoft.com/en-us/windows/console/GetConsoleMode
#
# BOOL WINAPI GetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _Out_ LPDWORD lpMode
# );
GetConsoleMode = windll.kernel32.GetConsoleMode
GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]
GetConsoleMode.restype = wintypes.BOOL
fdwSaveOldModeIn = wintypes.DWORD()
if not GetConsoleMode(hStdIn, byref(fdwSaveOldModeIn)):
raise Exception("GetConsoleMode")
fdwSaveOldModeOut = wintypes.DWORD()
if not GetConsoleMode(hStdOut, byref(fdwSaveOldModeOut)):
raise Exception("GetConsoleMode")
print(f"{fdwSaveOldModeIn.value=:02x}")
print(f"{fdwSaveOldModeOut.value=:02x}")
# Enable the window and mouse input events.
# From:
# https://learn.microsoft.com/en-us/windows/console/SetConsoleMode
#
# BOOL WINAPI SetConsoleMode(
# _In_ HANDLE hConsoleHandle,
# _In_ DWORD dwMode
# );
SetConsoleMode = windll.kernel32.SetConsoleMode
SetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.DWORD]
SetConsoleMode.restype = wintypes.BOOL
fdwModeIn = ENABLE_VIRTUAL_TERMINAL_INPUT
# fdwModeIn = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdIn, fdwModeIn):
raise Exception("SetConsoleMode")
fdwModeOut = fdwSaveOldModeOut.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
# fdwModeIn = 0x0218
if not SetConsoleMode(hStdOut, fdwModeOut):
raise Exception("SetConsoleMode")
print(f"{fdwModeIn=:02x}")
print(f"{fdwModeOut=:02x}")
init()
# From:
# https://learn.microsoft.com/en-us/windows/console/ReadConsoleInput
#
# BOOL WINAPI ReadConsoleInput(
# _In_ HANDLE hConsoleInput,
# _Out_ PINPUT_RECORD lpBuffer,
# _In_ DWORD nLength,
# _Out_ LPDWORD lpNumberOfEventsRead
# );
ReadConsoleInput = windll.kernel32.ReadConsoleInputW # Unicode
# ReadConsoleInput = windll.kernel32.ReadConsoleInputA # ANSII
# ReadConsoleInput.argtypes = [wintypes.HANDLE,
# wintypes.LPINT,
# wintypes.DWORD,
# wintypes.LPWORD]
ReadConsoleInput.restype = wintypes.BOOL
# DWORD cNumRead;
# INPUT_RECORD irInBuf[128];
cNumRead = wintypes.DWORD(0)
irInBuf = (INPUT_RECORD * 256)()
# Loop to read and handle the next 100 input events.
for _ in range(50):
# Wait for the events.
if not ReadConsoleInput(
hStdIn, # input buffer handle
byref(irInBuf), # buffer to read into
256, # size of read buffer
byref(cNumRead)): # number of records read
raise Exception("ReadConsoleInput")
# print(f"{hStdIn=} {irInBuf=} {cNumRead=}")
print(f"{cNumRead=}")
# Dispatch the events to the appropriate handler.
lastMousePress = 0
saveKey = ""
for bb in irInBuf[:cNumRead.value]:
# if not bb.EventType: continue
print(f"{bb=} {bb.EventType=} {cNumRead.value=}")
if bb.EventType == KEY_EVENT:
saveKey += bb.Event.KeyEvent.uChar.UnicodeChar
#continue
print(
# f" evt:{bb.Event.KeyEvent}" +
f" kd:{bb.Event.KeyEvent.bKeyDown}" +
f" rc:{bb.Event.KeyEvent.wRepeatCount}" +
f" VKC:{bb.Event.KeyEvent.wVirtualKeyCode}" +
f" VSC:{bb.Event.KeyEvent.wVirtualScanCode}" +
f" UC:{bb.Event.KeyEvent.uChar.UnicodeChar}" +
f" AC:{bb.Event.KeyEvent.uChar.AsciiChar}" +
f" CKS:{bb.Event.KeyEvent.dwControlKeyState} -> {saveKey=}"
)
# print(f"{bb.Event.KeyEvent=}")
# print(f"{bb.Event.KeyEvent.bKeyDown=}")
# print(f"{bb.Event.KeyEvent.wRepeatCount=}")
# print(f"{bb.Event.KeyEvent.wVirtualKeyCode=}")
# print(f"{bb.Event.KeyEvent.wVirtualScanCode=}")
# print(f"{bb.Event.KeyEvent.uChar.UnicodeChar=}")
# print(f"{bb.Event.KeyEvent.uChar.AsciiChar=}")
# print(f"{bb.Event.KeyEvent.dwControlKeyState=}")
elif bb.EventType == MOUSE_EVENT:
x = bb.Event.MouseEvent.dwMousePosition.X
y = bb.Event.MouseEvent.dwMousePosition.Y
print(f"{bb.Event.MouseEvent.dwControlKeyState=}")
print(f"{bb.Event.MouseEvent.dwEventFlags=}")
bstate = bb.Event.MouseEvent.dwButtonState
cstate = bb.Event.MouseEvent.dwControlKeyState
key = TTkMouseEvent.NoButton
evt = TTkMouseEvent.Move
mod = TTkK.NoModifier
tap = 0
# Release the mouse
if not bstate and lastMousePress:
pass
# Ignore the input if another button is pressed while holding the previous
if lastMousePress and lastMousePress & bstate:
continue
# Release the mouse if another button is pressed
# while still holding the first one
if lastMousePress and lastMousePress != (bstate&lastMousePress):
pass
if cstate & CAPSLOCK_ON: pass
if cstate & ENHANCED_KEY: pass
if cstate & LEFT_ALT_PRESSED: mod |= TTkK.AltModifier
if cstate & LEFT_CTRL_PRESSED: mod |= TTkK.ControlModifier
if cstate & NUMLOCK_ON: pass
if cstate & RIGHT_ALT_PRESSED: mod |= TTkK.AltModifier
if cstate & RIGHT_CTRL_PRESSED: mod |= TTkK.ControlModifier
if cstate & SCROLLLOCK_ON: pass
if cstate & SHIFT_PRESSED: mod |= TTkK.ShiftModifier
# Exclude extra button pressed at the same time
if bstate & RIGHTMOST_BUTTON_PRESSED:
key = TTkMouseEvent.RightButton
lastMousePress = RIGHTMOST_BUTTON_PRESSED
elif bstate & FROM_LEFT_1ST_BUTTON_PRESSED:
key = TTkMouseEvent.LeftButton
lastMousePress = FROM_LEFT_1ST_BUTTON_PRESSED
elif bstate & FROM_LEFT_2ND_BUTTON_PRESSED:
key = TTkMouseEvent.MidButton
lastMousePress = FROM_LEFT_2ND_BUTTON_PRESSED
elif bstate & FROM_LEFT_3RD_BUTTON_PRESSED:
lastMousePress = 0 # FROM_LEFT_3RD_BUTTON_PRESSED
elif bstate & FROM_LEFT_4TH_BUTTON_PRESSED:
lastMousePress = 0 # FROM_LEFT_4TH_BUTTON_PRESSED
mevt = TTkMouseEvent(x, y, key, evt, mod, tap, "")
print(f"{str(mevt)=}")
elif bb.EventType == WINDOW_BUFFER_SIZE_EVENT:
print(f"{bb.Event.WindowBufferSizeEvent=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.X=}")
print(f"{bb.Event.WindowBufferSizeEvent.dwSize.Y=}")
print(f"{saveKey=}")
# Restore input mode on exit.
if not SetConsoleMode(hStdIn, fdwSaveOldModeIn):
raise Exception("SetConsoleMode")
if not SetConsoleMode(hStdOut, fdwSaveOldModeOut):
raise Exception("SetConsoleMode")
# return 0;
#
reset()
print('OK')
Loading…
Cancel
Save