diff --git a/docs/reST/ref/scrap.rst b/docs/reST/ref/scrap.rst index e44f6e28f3..27d1cc281a 100644 --- a/docs/reST/ref/scrap.rst +++ b/docs/reST/ref/scrap.rst @@ -77,43 +77,21 @@ data (MIME) types are defined and registered: :: - pygame string - constant value description - -------------------------------------------------- - SCRAP_TEXT "text/plain" plain text - SCRAP_BMP "image/bmp" BMP encoded image data - SCRAP_PBM "image/pbm" PBM encoded image data - SCRAP_PPM "image/ppm" PPM encoded image data - -``pygame.SCRAP_PPM``, ``pygame.SCRAP_PBM`` and ``pygame.SCRAP_BMP`` are -suitable for surface buffers to be shared with other applications. -``pygame.SCRAP_TEXT`` is an alias for the plain text clipboard type. - -Depending on the platform, additional types are automatically registered when -data is placed into the clipboard to guarantee a consistent sharing behaviour -with other applications. The following listed types can be used as strings to -be passed to the respective :mod:`pygame.scrap` module functions. + "text/plain" Plain text (also accessible via the SCRAP_TEXT constant) + "text/plain;charset=utf-8" UTF-8 encoded text For **Windows** platforms, these additional types are supported automatically and resolve to their internal definitions: :: - "text/plain;charset=utf-8" UTF-8 encoded text + "image/bmp" BMP encoded image data (also accessible via the SCRAP_BMP constant) "audio/wav" WAV encoded audio "image/tiff" TIFF encoded image data -For **X11** platforms, these additional types are supported automatically and -resolve to their internal definitions: - -:: - - "text/plain;charset=utf-8" UTF-8 encoded text - "UTF8_STRING" UTF-8 encoded text - "COMPOUND_TEXT" COMPOUND text -User defined types can be used, but the data might not be accessible by other -applications unless they know what data type to look for. +User defined types can be used on **Windows**, but the data might not be +accessible by other applications unless they know what data type to look for. Example: Data placed into the clipboard by ``pygame.scrap.put("my_data_type", byte_data)`` can only be accessed by 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 .. versionaddedold:: 1.8 .. note:: - The scrap module is currently only supported for Windows, X11 and Mac OS X. - On Mac OS X only text works at the moment - other types may be supported in - future releases. + Non-text data is only supported on Windows. On other platforms only text is supported. .. function:: init @@ -221,8 +197,8 @@ For an example of how the scrap module works refer to the examples page Places data for a given clipboard type into the clipboard. The data must be a string buffer. The type is a string identifying the type of data to be placed into the clipboard. This can be one of the predefined - ``pygame.SCRAP_PBM``, ``pygame.SCRAP_PPM``, ``pygame.SCRAP_BMP`` or - ``pygame.SCRAP_TEXT`` values or a user defined string identifier. + ``pygame.SCRAP_BMP`` or ``pygame.SCRAP_TEXT`` values or a user defined + string identifier. :param string type: type identifier of the data to be placed into the clipboard diff --git a/src_c/scrap_sdl2.c b/src_c/scrap_sdl2.c index 5de1f4cdbc..754edaf1bf 100644 --- a/src_c/scrap_sdl2.c +++ b/src_c/scrap_sdl2.c @@ -8,31 +8,55 @@ #define PYGAME_SCRAP_FREE_STRING 1 -char *pygame_scrap_plaintext_type = "text/plain;charset=utf-8"; +char *pygame_scrap_plaintext_type = "text/plain"; +char *pygame_scrap_utf8text_type = "text/plain;charset=utf-8"; char **pygame_scrap_types; int pygame_scrap_contains(char *type) { - return (strcmp(type, pygame_scrap_plaintext_type) == 0) && + return (strcmp(type, pygame_scrap_plaintext_type) == 0 || + strcmp(type, pygame_scrap_utf8text_type) == 0) && SDL_HasClipboardText(); } +/* Returns 1 if str is valid ascii, 0 otherwise */ +static int +_pg_validate_ascii_str(const char *str) +{ + while (*str) { + if ((unsigned char)*str > 127) { + return 0; + } + str++; + } + return 1; +} + char * pygame_scrap_get(char *type, size_t *count) { char *retval = NULL; char *clipboard = NULL; + if (count) { + *count = 0; + } + if (!pygame_scrap_initialized()) { return RAISE(pgExc_SDLError, "scrap system not initialized."); } - if (strcmp(type, pygame_scrap_plaintext_type) == 0) { + int is_utf8 = strcmp(type, pygame_scrap_utf8text_type) == 0; + if (is_utf8 || strcmp(type, pygame_scrap_plaintext_type) == 0) { clipboard = SDL_GetClipboardText(); if (clipboard != NULL) { - *count = strlen(clipboard); - retval = strdup(clipboard); + if (is_utf8 || _pg_validate_ascii_str(clipboard)) { + if (count) { + *count = strlen(clipboard); + } + retval = strdup(clipboard); + } SDL_free(clipboard); return retval; } @@ -56,13 +80,14 @@ pygame_scrap_init(void) { SDL_Init(SDL_INIT_VIDEO); - pygame_scrap_types = malloc(sizeof(char *) * 2); + pygame_scrap_types = malloc(sizeof(char *) * 3); if (!pygame_scrap_types) { return 0; } pygame_scrap_types[0] = pygame_scrap_plaintext_type; - pygame_scrap_types[1] = NULL; + pygame_scrap_types[1] = pygame_scrap_utf8text_type; + pygame_scrap_types[2] = NULL; _scrapinitialized = 1; return _scrapinitialized; @@ -82,7 +107,8 @@ pygame_scrap_put(char *type, Py_ssize_t srclen, char *src) return 0; } - if (strcmp(type, pygame_scrap_plaintext_type) == 0) { + if (strcmp(type, pygame_scrap_plaintext_type) == 0 || + strcmp(type, pygame_scrap_utf8text_type) == 0) { if (SDL_SetClipboardText(src) == 0) { return 1; } diff --git a/test/meson.build b/test/meson.build index 1e2cadfa7d..7afcc64e2e 100644 --- a/test/meson.build +++ b/test/meson.build @@ -37,7 +37,6 @@ test_files = files( 'rect_test.py', 'render_test.py', 'rwobject_test.py', - 'scrap_tags.py', 'scrap_test.py', 'sndarray_tags.py', 'sndarray_test.py', diff --git a/test/scrap_tags.py b/test/scrap_tags.py deleted file mode 100644 index 66a18ec2cc..0000000000 --- a/test/scrap_tags.py +++ /dev/null @@ -1,26 +0,0 @@ -# __tags__ = ["ignore", "subprocess_ignore"] - -# TODO: make scrap_test.py work -# This test used to work only on linux and windows. -# Currently it only work in windows, and in linux it throws: -# `pygame.error: content could not be placed in clipboard.` -# The old test and tags kept here for reference when fixing. - -# import sys -# -# exclude = False -# -# if sys.platform == "win32" or sys.platform.startswith("linux"): -# try: -# import pygame -# -# pygame.scrap._NOT_IMPLEMENTED_ -# except AttributeError: -# pass -# else: -# exclude = True -# else: -# exclude = True -# -# if exclude: -# __tags__.extend(["ignore", "subprocess_ignore"]) diff --git a/test/scrap_test.py b/test/scrap_test.py index a62d31c6ea..92f7dbffb5 100644 --- a/test/scrap_test.py +++ b/test/scrap_test.py @@ -1,4 +1,5 @@ import os +import platform import sys import unittest @@ -51,6 +52,10 @@ def todo_test_get(self): """Ensures get works as expected.""" self.fail() + @unittest.skipIf( + platform.system() != "Windows", + "scrap features are broken on non windows platforms", + ) def test_get__owned_empty_type(self): """Ensures get works when there is no data of the requested type in the clipboard and the clipboard is owned by the pygame application. @@ -95,7 +100,10 @@ def test_put__text(self): self.assertEqual(scrap.get(pygame.SCRAP_TEXT), b"Another String") - @unittest.skipIf("pygame.image" not in sys.modules, "requires pygame.image module") + @unittest.skipIf( + platform.system() != "Windows", + "scrap features are broken on non windows platforms", + ) def test_put__bmp_image(self): """Ensures put can place a BMP image into the clipboard.""" sf = pygame.image.load(trunk_relative_path("examples/data/asprite.bmp")) @@ -104,6 +112,10 @@ def test_put__bmp_image(self): self.assertEqual(scrap.get(pygame.SCRAP_BMP), expected_bytes) + @unittest.skipIf( + platform.system() != "Windows", + "scrap features are broken on non windows platforms", + ) def test_put(self): """Ensures put can place data into the clipboard when using a user defined type identifier. @@ -205,109 +217,5 @@ def test_lost__not_owned(self): self.assertTrue(lost) -class X11InteractiveTest(unittest.TestCase): - __tags__ = ["ignore", "subprocess_ignore"] - try: - pygame.display.init() - except Exception: - pass - else: - if pygame.display.get_driver() == "x11": - __tags__ = ["interactive"] - pygame.display.quit() - - def test_issue_223(self): - """PATCH: pygame.scrap on X11, fix copying into PRIMARY selection - - Copying into theX11 PRIMARY selection (mouse copy/paste) would not - work due to a confusion between content type and clipboard type. - - """ - - from pygame import display, event, freetype - from pygame.locals import KEYDOWN, QUIT, SCRAP_SELECTION, SCRAP_TEXT, K_y - - success = False - freetype.init() - font = freetype.Font(None, 24) - display.init() - display.set_caption("Interactive X11 Paste Test") - screen = display.set_mode((600, 200)) - screen.fill(pygame.Color("white")) - text = "Scrap put() succeeded." - msg = ( - "Some text has been placed into the X11 clipboard." - " Please click the center mouse button in an open" - " text window to retrieve it." - '\n\nDid you get "{}"? (y/n)' - ).format(text) - word_wrap(screen, msg, font, 6) - display.flip() - event.pump() - scrap.init() - scrap.set_mode(SCRAP_SELECTION) - scrap.put(SCRAP_TEXT, text.encode("UTF-8")) - while True: - e = event.wait() - if e.type == QUIT: - break - if e.type == KEYDOWN: - success = e.key == K_y - break - pygame.display.quit() - self.assertTrue(success) - - -def word_wrap(surf, text, font, margin=0, color=(0, 0, 0)): - font.origin = True - surf_width, surf_height = surf.get_size() - width = surf_width - 2 * margin - height = surf_height - 2 * margin - line_spacing = int(1.25 * font.get_sized_height()) - x, y = margin, margin + line_spacing - space = font.get_rect(" ") - for word in iwords(text): - if word == "\n": - x, y = margin, y + line_spacing - else: - bounds = font.get_rect(word) - if x + bounds.width + bounds.x >= width: - x, y = margin, y + line_spacing - if x + bounds.width + bounds.x >= width: - raise ValueError("word too wide for the surface") - if y + bounds.height - bounds.y >= height: - raise ValueError("text to long for the surface") - font.render_to(surf, (x, y), None, color) - x += bounds.width + space.width - return x, y - - -def iwords(text): - # r"\n|[^ ]+" - # - head = 0 - tail = head - end = len(text) - while head < end: - if text[head] == " ": - head += 1 - tail = head + 1 - elif text[head] == "\n": - head += 1 - yield "\n" - tail = head + 1 - elif tail == end: - yield text[head:] - head = end - elif text[tail] == "\n": - yield text[head:tail] - head = tail - elif text[tail] == " ": - yield text[head:tail] - head = tail - else: - tail += 1 - - if __name__ == "__main__": unittest.main()