diff --git a/CHANGELOG.md b/CHANGELOG.md index 9728e8f50c..0ee7b9a673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.9.1] - 2022-12-30 + +### Added + +- Added textual._win_sleep for Python on Windows < 3.11 https://github.com/Textualize/textual/pull/1457 + ## [0.9.0] - 2022-12-30 ### Added @@ -316,6 +322,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040 - New handler system for messages that doesn't require inheritance - Improved traceback handling +[0.9.1]: https://github.com/Textualize/textual/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/Textualize/textual/compare/v0.8.2...v0.9.0 [0.8.2]: https://github.com/Textualize/textual/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/Textualize/textual/compare/v0.8.0...v0.8.1 diff --git a/docs/blog/posts/better-sleep-on-windows.md b/docs/blog/posts/better-sleep-on-windows.md index 351a9ed2dd..65236d8d48 100644 --- a/docs/blog/posts/better-sleep-on-windows.md +++ b/docs/blog/posts/better-sleep-on-windows.md @@ -43,8 +43,7 @@ async def sleep(sleep_for: float) -> None: Args: sleep_for (float): Seconds to sleep for. - """ - print("sleep") + """ await get_running_loop().run_in_executor(None, time_sleep, sleep_for) ``` diff --git a/pyproject.toml b/pyproject.toml index 700eaad447..444e5461c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "textual" -version = "0.9.0" +version = "0.9.1" homepage = "https://github.com/Textualize/textual" description = "Modern Text User Interface framework" authors = ["Will McGugan "] diff --git a/src/textual/_time.py b/src/textual/_time.py index f332469468..8b82936d3f 100644 --- a/src/textual/_time.py +++ b/src/textual/_time.py @@ -1,4 +1,5 @@ import platform +import sys from asyncio import sleep as asyncio_sleep, get_running_loop from time import monotonic, perf_counter, sleep as time_sleep @@ -16,15 +17,23 @@ if WINDOWS: - async def sleep(sleep_for: float) -> None: - """An asyncio sleep. + if sys.version_info >= (3, 11, 0): - On Windows this achieves a better granularity that asyncio.sleep + async def sleep(sleep_for: float) -> None: + """An asyncio sleep. - Args: - sleep_for (float): Seconds to sleep for. - """ - await get_running_loop().run_in_executor(None, time_sleep, sleep_for) + On Windows this achieves a better granularity that asyncio.sleep + + Args: + sleep_for (float): Seconds to sleep for. + """ + await get_running_loop().run_in_executor(None, time_sleep, sleep_for) + + else: + from ._win_sleep import sleep as win_sleep + + async def sleep(sleep_for: float) -> None: + await get_running_loop().run_in_executor(None, win_sleep, sleep_for) else: sleep = asyncio_sleep diff --git a/src/textual/_win_sleep.py b/src/textual/_win_sleep.py new file mode 100644 index 0000000000..e704cef078 --- /dev/null +++ b/src/textual/_win_sleep.py @@ -0,0 +1,48 @@ +import ctypes +from ctypes.wintypes import LARGE_INTEGER +from time import sleep as time_sleep + +__all__ = ["sleep"] + +kernel32 = ctypes.windll.kernel32 + +INFINITE = 0xFFFFFFFF +WAIT_FAILED = 0xFFFFFFFF +CREATE_WAITABLE_TIMER_HIGH_RESOLUTION = 0x00000002 + + +def sleep(sleep_for: float) -> None: + """A replacement sleep for Windows. + + Python 3.11 added a more accurate sleep. This may be used on < Python 3.11 + + Args: + sleep_for (float): Seconds to sleep for. + """ + handle = kernel32.CreateWaitableTimerExW( + None, + None, + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + 0x1F0003, + ) + if not handle: + time_sleep(sleep_for) + return + + sleep_for -= 1 / 1000 + if not kernel32.SetWaitableTimer( + handle, + ctypes.byref(LARGE_INTEGER(int(sleep_for * -10_000_000))), + 0, + None, + None, + 0, + ): + kernel32.CloseHandle(handle) + print("error") + time_sleep(sleep_for) + return + + if kernel32.WaitForSingleObject(handle, INFINITE) == WAIT_FAILED: + time_sleep(sleep_for) + kernel32.CloseHandle(handle) diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index a2c67595ef..9eda4b045f 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -71,7 +71,8 @@ def test_input_and_focus(snap_compare): "tab", *"Darren", # Focus first input, write "Darren" "tab", - *"Burns", # Tab focus to second input, write "Burns" + *"Burns", + "_", # Tab focus to second input, write "Burns" ] assert snap_compare(WIDGET_EXAMPLES_DIR / "input.py", press=press) @@ -178,6 +179,6 @@ def test_demo(snap_compare): """Test the demo app (python -m textual)""" assert snap_compare( Path("../../src/textual/demo.py"), - press=["down", "down", "down", "_"], + press=["down", "down", "down", "_", "_"], terminal_size=(100, 30), )