mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
win32 support
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from ctypes import windll
|
||||||
|
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
||||||
from codecs import getincrementaldecoder
|
from codecs import getincrementaldecoder
|
||||||
|
|
||||||
import msvcrt
|
import msvcrt
|
||||||
import os
|
import os
|
||||||
import selectors
|
import selectors
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
from typing import TYPE_CHECKING
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from ..driver import Driver
|
from ..driver import Driver
|
||||||
from ..geometry import Size
|
from ..geometry import Size
|
||||||
@@ -22,6 +25,38 @@ from .._xterm_parser import XTermParser
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from textual.app import App
|
||||||
|
|
||||||
|
WAIT_TIMEOUT = 0x00000102
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:
|
||||||
|
"""
|
||||||
|
Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
|
||||||
|
Returns `None` on timeout.
|
||||||
|
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
|
||||||
|
Note that handles should be a list of `HANDLE` objects, not integers. See
|
||||||
|
this comment in the patch by @quark-zju for the reason why:
|
||||||
|
''' Make sure HANDLE on Windows has a correct size
|
||||||
|
Previously, the type of various HANDLEs are native Python integer
|
||||||
|
types. The ctypes library will treat them as 4-byte integer when used
|
||||||
|
in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually
|
||||||
|
a small integer. Depending on whether the extra 4 bytes are zero-ed out
|
||||||
|
or not, things can happen to work, or break. '''
|
||||||
|
This function returns either `None` or one of the given `HANDLE` objects.
|
||||||
|
(The return value can be tested with the `is` operator.)
|
||||||
|
"""
|
||||||
|
arrtype = HANDLE * len(handles)
|
||||||
|
handle_array = arrtype(*handles)
|
||||||
|
|
||||||
|
ret: int = windll.kernel32.WaitForMultipleObjects(
|
||||||
|
len(handle_array), handle_array, BOOL(False), DWORD(timeout)
|
||||||
|
)
|
||||||
|
|
||||||
|
if ret == WAIT_TIMEOUT:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return handles[ret]
|
||||||
|
|
||||||
|
|
||||||
class WindowsDriver(Driver):
|
class WindowsDriver(Driver):
|
||||||
@@ -64,17 +99,20 @@ class WindowsDriver(Driver):
|
|||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
filehandle = msvcrt.get_osfhandle(self.out_fileno)
|
win32.enable_vt_mode(msvcrt.get_osfhandle(self.out_fileno))
|
||||||
win32.enable_vt_mode(filehandle)
|
win32.setraw(msvcrt.get_osfhandle(self.in_fileno))
|
||||||
|
|
||||||
self.console.set_alt_screen(True)
|
self.console.set_alt_screen(True)
|
||||||
self._enable_mouse_support()
|
self._enable_mouse_support()
|
||||||
self.console.show_cursor(False)
|
self.console.show_cursor(False)
|
||||||
self.console.file.write("\033[?1003h\n")
|
self.console.file.write("\033[?1003h\n")
|
||||||
win32.setraw(msvcrt.get_osfhandle(self.in_fileno))
|
|
||||||
|
from .._context import active_app
|
||||||
|
|
||||||
|
app = active_app.get()
|
||||||
|
|
||||||
self._key_thread = Thread(
|
self._key_thread = Thread(
|
||||||
target=self.run_input_thread, args=(asyncio.get_event_loop(),)
|
target=self.run_input_thread, args=(asyncio.get_event_loop(), app)
|
||||||
)
|
)
|
||||||
|
|
||||||
width, height = win32.get_terminal_size(self.out_fileno)
|
width, height = win32.get_terminal_size(self.out_fileno)
|
||||||
@@ -83,7 +121,9 @@ class WindowsDriver(Driver):
|
|||||||
self._target.post_message(events.Resize(self._target, Size(width, height))),
|
self._target.post_message(events.Resize(self._target, Size(width, height))),
|
||||||
loop=loop,
|
loop=loop,
|
||||||
)
|
)
|
||||||
log("starting key thread")
|
|
||||||
|
from .._context import active_app
|
||||||
|
|
||||||
self._key_thread.start()
|
self._key_thread.start()
|
||||||
|
|
||||||
def disable_input(self) -> None:
|
def disable_input(self) -> None:
|
||||||
@@ -104,45 +144,33 @@ class WindowsDriver(Driver):
|
|||||||
self.console.set_alt_screen(False)
|
self.console.set_alt_screen(False)
|
||||||
self.console.show_cursor(True)
|
self.console.show_cursor(True)
|
||||||
|
|
||||||
def run_input_thread(self, loop) -> None:
|
def run_input_thread(self, loop, app: App) -> None:
|
||||||
try:
|
try:
|
||||||
self._run_input_thread(loop)
|
self._run_input_thread(loop, app)
|
||||||
except Exception:
|
except Exception as error:
|
||||||
pass # TODO: log
|
app.log(error)
|
||||||
|
|
||||||
def _run_input_thread(self, loop) -> None:
|
def _run_input_thread(self, loop, app: App) -> None:
|
||||||
log("input thread")
|
app.log("input thread")
|
||||||
|
|
||||||
selector = selectors.DefaultSelector()
|
parser = XTermParser(self._target, lambda: False)
|
||||||
selector.register(self.in_fileno, selectors.EVENT_READ)
|
|
||||||
|
|
||||||
fileno = self.in_fileno
|
|
||||||
|
|
||||||
def more_data() -> bool:
|
|
||||||
"""Check if there is more data to parse."""
|
|
||||||
for key, events in selector.select(0.01):
|
|
||||||
if events:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
parser = XTermParser(self._target, more_data)
|
|
||||||
|
|
||||||
utf8_decoder = getincrementaldecoder("utf-8")().decode
|
utf8_decoder = getincrementaldecoder("utf-8")().decode
|
||||||
decode = utf8_decoder
|
decode = utf8_decoder
|
||||||
read = os.read
|
read = os.read
|
||||||
|
|
||||||
log("starting thread")
|
input_handle = msvcrt.get_osfhandle(self.in_fileno)
|
||||||
|
app.log("input_handle", input_handle)
|
||||||
|
app.log("starting thread")
|
||||||
try:
|
try:
|
||||||
while not self.exit_event.is_set():
|
while not self.exit_event.is_set():
|
||||||
selector_events = selector.select(0.1)
|
if wait_for_handles([input_handle], 100) is None:
|
||||||
for _selector_key, mask in selector_events:
|
continue
|
||||||
log(mask)
|
unicode_data = decode(read(self.in_fileno, 1024))
|
||||||
if mask | selectors.EVENT_READ:
|
app.log("key", repr(unicode_data))
|
||||||
unicode_data = decode(read(fileno, 1024))
|
for event in parser.feed(unicode_data):
|
||||||
log("ket", unicode_data)
|
self.process_event(event)
|
||||||
for event in parser.feed(unicode_data):
|
|
||||||
self.process_event(event)
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
log(error)
|
app.log(error)
|
||||||
finally:
|
finally:
|
||||||
selector.close()
|
app.log("input thread finished")
|
||||||
|
|||||||
Reference in New Issue
Block a user