Merge master into the macOS front branch

This commit is contained in:
Gökhan
2026-08-25 00:16:20 +03:00
48 changed files with 3523 additions and 258 deletions
+148
View File
@@ -13,6 +13,7 @@ is checked on a Mac and the macOS half on Linux, and a change to the chooser
cannot quietly break the platform nobody is sitting at.
"""
import ctypes
import os
import pathlib
import subprocess
@@ -56,6 +57,9 @@ class Chooser(DikteTest):
def test_a_mac(self):
self.assertIs(self.under("darwin"), paste.MACOS)
def test_windows(self):
self.assertIs(self.under("win32"), paste.WINDOWS)
def test_a_mac_running_an_x_server_is_still_a_mac(self):
"""XQuartz sets DISPLAY, and none of X's programs are what pastes here."""
self.assertIs(self.under("darwin", DISPLAY=":0"), paste.MACOS)
@@ -504,5 +508,149 @@ class MacClipboardSnapshot(DikteTest):
self.assertFalse(os.path.exists(directory))
class FakeWin32:
"""user32 and kernel32, as much of both as paste.py calls.
The clipboard is a string held here. A read materialises it as this
machine's own wide characters, which is what wstring_at reads wherever the
test runs; a write arrives as the UTF-16 the real clipboard is handed, so
what the code sent is exactly what is checked.
"""
def __init__(self):
self.text = None
self.buffers = {}
self.next_handle = 1
self.pressed = [] # (virtual key, flags), in the order sent
self.send_result = None # None: report every event as delivered
self.held = False # another program has the clipboard open
self.out_of_memory = False
def _keep(self, buffer):
handle = self.next_handle
self.next_handle += 1
self.buffers[handle] = buffer
return handle
# --- user32
def OpenClipboard(self, owner):
return 0 if self.held else 1
def CloseClipboard(self):
return 1
def EmptyClipboard(self):
self.text = None
return 1
def GetClipboardData(self, fmt):
if self.text is None:
return 0
return self._keep(ctypes.create_unicode_buffer(self.text))
def SetClipboardData(self, fmt, handle):
raw = self.buffers[handle].raw
self.text = raw.decode("utf-16-le").split("\x00", 1)[0]
return handle
def SendInput(self, count, inputs, size):
self.pressed.extend((entry.union.ki.wVk, entry.union.ki.dwFlags)
for entry in inputs)
return count if self.send_result is None else self.send_result
# --- kernel32
def GlobalAlloc(self, flags, size):
if self.out_of_memory:
return 0
return self._keep(ctypes.create_string_buffer(size))
def GlobalLock(self, handle):
buffer = self.buffers.get(handle)
return ctypes.addressof(buffer) if buffer else 0
def GlobalUnlock(self, handle):
return 1
def GlobalFree(self, handle):
self.buffers.pop(handle, None)
return 1
class Windows(Standing, DikteTest):
"""Windows shells out to nothing: both halves are calls into the system."""
platform = "win32"
here = paste.WINDOWS
def setUp(self):
super().setUp()
self.api = FakeWin32()
self.patch_attr(paste, "_win_api", lambda: (self.api, self.api))
self.patch_attr(paste.time, "sleep", lambda seconds: None)
def test_what_is_copied_is_what_reads_back(self):
paste.copy("ığüşöç İ")
self.assertEqual(paste.read_clipboard(), "ığüşöç İ".encode("utf-8"))
def test_an_empty_clipboard_reads_as_empty_text(self):
self.assertEqual(paste.read_clipboard(), b"")
def test_what_was_saved_goes_back_after_the_paste(self):
paste.copy("mine")
saved = paste.read_clipboard()
paste.copy("the dictation")
paste.copy_bytes(saved)
self.assertEqual(self.api.text, "mine")
def test_a_copy_that_fails_leaves_what_was_there(self):
"""EmptyClipboard is the point of no return, so nothing runs after it."""
paste.copy("mine")
for failure in ("out_of_memory", "held"):
with self.subTest(failure=failure):
setattr(self.api, failure, True)
with self.assertRaises(paste.PasteError):
paste.copy("the dictation")
self.assertEqual(self.api.text, "mine")
setattr(self.api, failure, False)
def test_the_handle_is_not_leaked_when_the_copy_fails(self):
self.api.held = True
with self.assertRaises(paste.PasteError):
paste.copy("the dictation")
self.assertEqual(self.api.buffers, {})
def test_readiness_asks_for_no_program_and_no_permission(self):
with only_these_tools():
self.assertTrue(paste.paste_ready())
def test_the_keys_go_down_in_order_and_up_in_reverse(self):
paste.press("ctrl+v")
keyup = 0x0002
self.assertEqual(self.api.pressed,
[(0x11, 0), (0x56, 0), (0x56, keyup), (0x11, keyup)])
def test_three_keys(self):
paste.press("ctrl+shift+v")
self.assertEqual([code for code, _ in self.api.pressed],
[0x11, 0x10, 0x56, 0x56, 0x10, 0x11])
def test_the_other_spellings_land_on_the_same_keys(self):
paste.press("super+enter")
first, self.api.pressed = self.api.pressed, []
paste.press("meta+return")
self.assertEqual(self.api.pressed, first)
def test_a_key_nobody_mapped_is_refused_before_anything_is_sent(self):
with self.assertRaises(paste.PasteError):
paste.press("ctrl+f13")
self.assertEqual(self.api.pressed, [])
def test_a_press_the_system_did_not_take_says_so(self):
self.api.send_result = 0
with self.assertRaises(paste.PasteError) as caught:
paste.press("ctrl+v")
self.assertIn("SendInput", str(caught.exception))
if __name__ == "__main__":
unittest.main()