Skip to content

Commit 7e05d3f

Browse files
committed
Fix ascii scrap on sdl2 backend, docs/tests fixes
1 parent 9f52742 commit 7e05d3f

File tree

5 files changed

+55
-172
lines changed

5 files changed

+55
-172
lines changed

docs/reST/ref/scrap.rst

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -77,43 +77,21 @@ data (MIME) types are defined and registered:
7777

7878
::
7979

80-
pygame string
81-
constant value description
82-
--------------------------------------------------
83-
SCRAP_TEXT "text/plain" plain text
84-
SCRAP_BMP "image/bmp" BMP encoded image data
85-
SCRAP_PBM "image/pbm" PBM encoded image data
86-
SCRAP_PPM "image/ppm" PPM encoded image data
87-
88-
``pygame.SCRAP_PPM``, ``pygame.SCRAP_PBM`` and ``pygame.SCRAP_BMP`` are
89-
suitable for surface buffers to be shared with other applications.
90-
``pygame.SCRAP_TEXT`` is an alias for the plain text clipboard type.
91-
92-
Depending on the platform, additional types are automatically registered when
93-
data is placed into the clipboard to guarantee a consistent sharing behaviour
94-
with other applications. The following listed types can be used as strings to
95-
be passed to the respective :mod:`pygame.scrap` module functions.
80+
"text/plain" Plain text (also accessible via the SCRAP_TEXT constant)
81+
"text/plain;charset=utf-8" UTF-8 encoded text
9682

9783
For **Windows** platforms, these additional types are supported automatically
9884
and resolve to their internal definitions:
9985

10086
::
10187

102-
"text/plain;charset=utf-8" UTF-8 encoded text
88+
"image/bmp" BMP encoded image data (also accessible via the SCRAP_BMP constant)
10389
"audio/wav" WAV encoded audio
10490
"image/tiff" TIFF encoded image data
10591

106-
For **X11** platforms, these additional types are supported automatically and
107-
resolve to their internal definitions:
108-
109-
::
110-
111-
"text/plain;charset=utf-8" UTF-8 encoded text
112-
"UTF8_STRING" UTF-8 encoded text
113-
"COMPOUND_TEXT" COMPOUND text
11492

115-
User defined types can be used, but the data might not be accessible by other
116-
applications unless they know what data type to look for.
93+
User defined types can be used on **Windows**, but the data might not be
94+
accessible by other applications unless they know what data type to look for.
11795
Example: Data placed into the clipboard by
11896
``pygame.scrap.put("my_data_type", byte_data)`` can only be accessed by
11997
applications which query the clipboard for the ``"my_data_type"`` data type.
@@ -125,9 +103,7 @@ For an example of how the scrap module works refer to the examples page
125103
.. versionaddedold:: 1.8
126104

127105
.. note::
128-
The scrap module is currently only supported for Windows, X11 and Mac OS X.
129-
On Mac OS X only text works at the moment - other types may be supported in
130-
future releases.
106+
Non-text data is only supported on Windows. On other platforms only text is supported.
131107

132108
.. function:: init
133109

@@ -221,8 +197,8 @@ For an example of how the scrap module works refer to the examples page
221197
Places data for a given clipboard type into the clipboard. The data must
222198
be a string buffer. The type is a string identifying the type of data to be
223199
placed into the clipboard. This can be one of the predefined
224-
``pygame.SCRAP_PBM``, ``pygame.SCRAP_PPM``, ``pygame.SCRAP_BMP`` or
225-
``pygame.SCRAP_TEXT`` values or a user defined string identifier.
200+
``pygame.SCRAP_BMP`` or ``pygame.SCRAP_TEXT`` values or a user defined
201+
string identifier.
226202

227203
:param string type: type identifier of the data to be placed into the
228204
clipboard

src_c/scrap_sdl2.c

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,55 @@
88

99
#define PYGAME_SCRAP_FREE_STRING 1
1010

11-
char *pygame_scrap_plaintext_type = "text/plain;charset=utf-8";
11+
char *pygame_scrap_plaintext_type = "text/plain";
12+
char *pygame_scrap_utf8text_type = "text/plain;charset=utf-8";
1213
char **pygame_scrap_types;
1314

1415
int
1516
pygame_scrap_contains(char *type)
1617
{
17-
return (strcmp(type, pygame_scrap_plaintext_type) == 0) &&
18+
return (strcmp(type, pygame_scrap_plaintext_type) == 0 ||
19+
strcmp(type, pygame_scrap_utf8text_type) == 0) &&
1820
SDL_HasClipboardText();
1921
}
2022

23+
/* Returns 1 if str is valid ascii, 0 otherwise */
24+
static int
25+
_pg_validate_ascii_str(const char *str)
26+
{
27+
while (*str) {
28+
if ((unsigned char)*str > 127) {
29+
return 0;
30+
}
31+
str++;
32+
}
33+
return 1;
34+
}
35+
2136
char *
2237
pygame_scrap_get(char *type, size_t *count)
2338
{
2439
char *retval = NULL;
2540
char *clipboard = NULL;
2641

42+
if (count) {
43+
*count = 0;
44+
}
45+
2746
if (!pygame_scrap_initialized()) {
2847
return RAISE(pgExc_SDLError, "scrap system not initialized.");
2948
}
3049

31-
if (strcmp(type, pygame_scrap_plaintext_type) == 0) {
50+
int is_utf8 = strcmp(type, pygame_scrap_utf8text_type) == 0;
51+
if (is_utf8 || strcmp(type, pygame_scrap_plaintext_type) == 0) {
3252
clipboard = SDL_GetClipboardText();
3353
if (clipboard != NULL) {
34-
*count = strlen(clipboard);
35-
retval = strdup(clipboard);
54+
if (is_utf8 || _pg_validate_ascii_str(clipboard)) {
55+
if (count) {
56+
*count = strlen(clipboard);
57+
}
58+
retval = strdup(clipboard);
59+
}
3660
SDL_free(clipboard);
3761
return retval;
3862
}
@@ -56,13 +80,14 @@ pygame_scrap_init(void)
5680
{
5781
SDL_Init(SDL_INIT_VIDEO);
5882

59-
pygame_scrap_types = malloc(sizeof(char *) * 2);
83+
pygame_scrap_types = malloc(sizeof(char *) * 3);
6084
if (!pygame_scrap_types) {
6185
return 0;
6286
}
6387

6488
pygame_scrap_types[0] = pygame_scrap_plaintext_type;
65-
pygame_scrap_types[1] = NULL;
89+
pygame_scrap_types[1] = pygame_scrap_utf8text_type;
90+
pygame_scrap_types[2] = NULL;
6691

6792
_scrapinitialized = 1;
6893
return _scrapinitialized;
@@ -82,7 +107,8 @@ pygame_scrap_put(char *type, Py_ssize_t srclen, char *src)
82107
return 0;
83108
}
84109

85-
if (strcmp(type, pygame_scrap_plaintext_type) == 0) {
110+
if (strcmp(type, pygame_scrap_plaintext_type) == 0 ||
111+
strcmp(type, pygame_scrap_utf8text_type) == 0) {
86112
if (SDL_SetClipboardText(src) == 0) {
87113
return 1;
88114
}

test/meson.build

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ test_files = files(
3737
'rect_test.py',
3838
'render_test.py',
3939
'rwobject_test.py',
40-
'scrap_tags.py',
4140
'scrap_test.py',
4241
'sndarray_tags.py',
4342
'sndarray_test.py',

test/scrap_tags.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

test/scrap_test.py

Lines changed: 13 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import platform
23
import sys
34
import unittest
45

@@ -51,6 +52,10 @@ def todo_test_get(self):
5152
"""Ensures get works as expected."""
5253
self.fail()
5354

55+
@unittest.skipIf(
56+
platform.system() != "Windows",
57+
"scrap features are broken on non windows platforms",
58+
)
5459
def test_get__owned_empty_type(self):
5560
"""Ensures get works when there is no data of the requested type
5661
in the clipboard and the clipboard is owned by the pygame application.
@@ -95,7 +100,10 @@ def test_put__text(self):
95100

96101
self.assertEqual(scrap.get(pygame.SCRAP_TEXT), b"Another String")
97102

98-
@unittest.skipIf("pygame.image" not in sys.modules, "requires pygame.image module")
103+
@unittest.skipIf(
104+
platform.system() != "Windows",
105+
"scrap features are broken on non windows platforms",
106+
)
99107
def test_put__bmp_image(self):
100108
"""Ensures put can place a BMP image into the clipboard."""
101109
sf = pygame.image.load(trunk_relative_path("examples/data/asprite.bmp"))
@@ -104,6 +112,10 @@ def test_put__bmp_image(self):
104112

105113
self.assertEqual(scrap.get(pygame.SCRAP_BMP), expected_bytes)
106114

115+
@unittest.skipIf(
116+
platform.system() != "Windows",
117+
"scrap features are broken on non windows platforms",
118+
)
107119
def test_put(self):
108120
"""Ensures put can place data into the clipboard
109121
when using a user defined type identifier.
@@ -205,109 +217,5 @@ def test_lost__not_owned(self):
205217
self.assertTrue(lost)
206218

207219

208-
class X11InteractiveTest(unittest.TestCase):
209-
__tags__ = ["ignore", "subprocess_ignore"]
210-
try:
211-
pygame.display.init()
212-
except Exception:
213-
pass
214-
else:
215-
if pygame.display.get_driver() == "x11":
216-
__tags__ = ["interactive"]
217-
pygame.display.quit()
218-
219-
def test_issue_223(self):
220-
"""PATCH: pygame.scrap on X11, fix copying into PRIMARY selection
221-
222-
Copying into theX11 PRIMARY selection (mouse copy/paste) would not
223-
work due to a confusion between content type and clipboard type.
224-
225-
"""
226-
227-
from pygame import display, event, freetype
228-
from pygame.locals import KEYDOWN, QUIT, SCRAP_SELECTION, SCRAP_TEXT, K_y
229-
230-
success = False
231-
freetype.init()
232-
font = freetype.Font(None, 24)
233-
display.init()
234-
display.set_caption("Interactive X11 Paste Test")
235-
screen = display.set_mode((600, 200))
236-
screen.fill(pygame.Color("white"))
237-
text = "Scrap put() succeeded."
238-
msg = (
239-
"Some text has been placed into the X11 clipboard."
240-
" Please click the center mouse button in an open"
241-
" text window to retrieve it."
242-
'\n\nDid you get "{}"? (y/n)'
243-
).format(text)
244-
word_wrap(screen, msg, font, 6)
245-
display.flip()
246-
event.pump()
247-
scrap.init()
248-
scrap.set_mode(SCRAP_SELECTION)
249-
scrap.put(SCRAP_TEXT, text.encode("UTF-8"))
250-
while True:
251-
e = event.wait()
252-
if e.type == QUIT:
253-
break
254-
if e.type == KEYDOWN:
255-
success = e.key == K_y
256-
break
257-
pygame.display.quit()
258-
self.assertTrue(success)
259-
260-
261-
def word_wrap(surf, text, font, margin=0, color=(0, 0, 0)):
262-
font.origin = True
263-
surf_width, surf_height = surf.get_size()
264-
width = surf_width - 2 * margin
265-
height = surf_height - 2 * margin
266-
line_spacing = int(1.25 * font.get_sized_height())
267-
x, y = margin, margin + line_spacing
268-
space = font.get_rect(" ")
269-
for word in iwords(text):
270-
if word == "\n":
271-
x, y = margin, y + line_spacing
272-
else:
273-
bounds = font.get_rect(word)
274-
if x + bounds.width + bounds.x >= width:
275-
x, y = margin, y + line_spacing
276-
if x + bounds.width + bounds.x >= width:
277-
raise ValueError("word too wide for the surface")
278-
if y + bounds.height - bounds.y >= height:
279-
raise ValueError("text to long for the surface")
280-
font.render_to(surf, (x, y), None, color)
281-
x += bounds.width + space.width
282-
return x, y
283-
284-
285-
def iwords(text):
286-
# r"\n|[^ ]+"
287-
#
288-
head = 0
289-
tail = head
290-
end = len(text)
291-
while head < end:
292-
if text[head] == " ":
293-
head += 1
294-
tail = head + 1
295-
elif text[head] == "\n":
296-
head += 1
297-
yield "\n"
298-
tail = head + 1
299-
elif tail == end:
300-
yield text[head:]
301-
head = end
302-
elif text[tail] == "\n":
303-
yield text[head:tail]
304-
head = tail
305-
elif text[tail] == " ":
306-
yield text[head:tail]
307-
head = tail
308-
else:
309-
tail += 1
310-
311-
312220
if __name__ == "__main__":
313221
unittest.main()

0 commit comments

Comments
 (0)