diff --git a/dragonfly/os_dependent_mock.py b/dragonfly/os_dependent_mock.py index 246a033..535bdfc 100755 --- a/dragonfly/os_dependent_mock.py +++ b/dragonfly/os_dependent_mock.py @@ -61,10 +61,6 @@ def __init__(self, *args, **kwargs): pass -class Clipboard(MockBase): - pass - - class HardwareInput(MockBase): pass diff --git a/dragonfly/test/test_clipboard.py b/dragonfly/test/test_clipboard.py new file mode 100644 index 0000000..5307af0 --- /dev/null +++ b/dragonfly/test/test_clipboard.py @@ -0,0 +1,96 @@ +# +# This file is part of Dragonfly. +# (c) Copyright 2007, 2008 by Christo Butcher +# Licensed under the LGPL. +# +# Dragonfly is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Dragonfly is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Dragonfly. If not, see +# . +# + +import unittest + +from dragonfly import Clipboard + + +class TestClipboard(unittest.TestCase): + def setUp(self): + # Clear the clipboard before each test. + Clipboard.clear_clipboard() + + def test_system_text_methods(self): + text = "testing testing" + Clipboard.set_system_text(text) + self.assertEqual(Clipboard.get_system_text(), text) + + def test_clear_clipboard(self): + # Put something on the system clipboard. + Clipboard.set_system_text("something") + + # Clear it. + Clipboard.clear_clipboard() + + # Then test that it has been cleared. + self.assertEqual(Clipboard.get_system_text(), "") + + def test_empty(self): + # A new clipboard has no content for the unicode format. + c = Clipboard() + self.assertFalse(c.has_format(Clipboard.format_unicode)) + + # Neither does a new clipboard have text. + self.assertFalse(c.has_text()) + + def test_from_system_argument(self): + # Test the optional from_system argument of Clipboard.__init__ + text = "something" + Clipboard.set_system_text(text) + c = Clipboard(from_system=True) + self.assertEqual(c.text, text) + self.assertTrue(c.has_format(Clipboard.format_unicode)) + self.assertTrue(c.has_text()) + + def test_copy_from_system(self): + text = "testing" + Clipboard.set_system_text(text) + c = Clipboard() + + # Test the method with clear=False (default) + c.copy_from_system(clear=False) + self.assertEqual(c.text, text) + self.assertEqual(Clipboard.get_system_text(), text) + + # Test again with clear=True + c = Clipboard() + c.copy_from_system(clear=True) + self.assertEqual(c.text, text) + self.assertEqual(Clipboard.get_system_text(), "") + + def test_copy_to_system(self): + text = "testing" + c = Clipboard(text=text) + c.copy_to_system() + self.assertEqual(Clipboard.get_system_text(), text) + + def test_set_text(self): + c = Clipboard() + text = "test" + c.set_text(text) + self.assertTrue(c.has_text()) + self.assertTrue(c.has_format(Clipboard.format_unicode)) + self.assertEqual(c.get_text(), text) + self.assertEqual(c.text, text) + + +if __name__ == '__main__': + unittest.main() diff --git a/dragonfly/windows/__init__.py b/dragonfly/windows/__init__.py index d1c0ebd..3dcac06 100644 --- a/dragonfly/windows/__init__.py +++ b/dragonfly/windows/__init__.py @@ -23,13 +23,12 @@ # OS agnostic imports from rectangle import Rectangle, unit from point import Point +from .clipboard import Clipboard # Windows-specific if sys.platform.startswith("win"): from window import Window from monitor import Monitor, monitors - from clipboard import Clipboard else: # Mock imports from ..os_dependent_mock import Window from ..os_dependent_mock import Monitor, monitors - from ..os_dependent_mock import Clipboard diff --git a/dragonfly/windows/clipboard.py b/dragonfly/windows/clipboard.py index 3d302a2..d2b1a82 100644 --- a/dragonfly/windows/clipboard.py +++ b/dragonfly/windows/clipboard.py @@ -19,166 +19,98 @@ # """ -This file implements an interface to the Windows system clipboard. +This file implements an interface to the system clipboard using pyperclip +and should work on Windows, Mac OS and Linux-based operating systems. """ -import win32clipboard -import win32con - +import pyperclip #=========================================================================== class Clipboard(object): + """ + This class provides multi-platform clipboard support through pyperclip. + The only currently supported Windows clipboard format is Unicode text. + """ #----------------------------------------------------------------------- - format_text = win32con.CF_TEXT - format_oemtext = win32con.CF_OEMTEXT - format_unicode = win32con.CF_UNICODETEXT - format_locale = win32con.CF_LOCALE - format_hdrop = win32con.CF_HDROP - format_names = { - format_text: "text", - format_oemtext: "oemtext", - format_unicode: "unicode", - format_locale: "locale", - format_hdrop: "hdrop", - } + format_unicode = 13 # retrieved from pyperclip.init_windows_clipboard + format_names = {format_unicode: "unicode"} @classmethod def get_system_text(cls): - win32clipboard.OpenClipboard() - try: - content = win32clipboard.GetClipboardData(cls.format_unicode) - finally: - win32clipboard.CloseClipboard() - return content + return pyperclip.paste() @classmethod def set_system_text(cls, content): - content = unicode(content) - win32clipboard.OpenClipboard() - try: - win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardData(cls.format_unicode, content) - finally: - win32clipboard.CloseClipboard() + pyperclip.copy(content) + @classmethod + def clear_clipboard(cls): + # TODO Perhaps keep a Windows-only implementation for this; + # setting to "" is not technically the same as using + # win32clipboard.EmptyClipboard. + pyperclip.copy("") #----------------------------------------------------------------------- def __init__(self, contents=None, text=None, from_system=False): - self._contents = {} + # Handle a dictionary argument for contents. + if contents and isinstance(contents, dict): + content = None + for k in contents.keys(): + # Only accept the unicode format for the moment. + if k == self.format_unicode: + content = contents[k] + else: + content = None + + self._content = content # If requested, retrieve current system clipboard contents. if from_system: self.copy_from_system() - # Process given contents for this Clipboard instance. - if contents: - try: - self._contents = dict(contents) - except Exception, e: - raise TypeError("Invalid contents: %s (%r)" % (e, contents)) - - # Handle special case of text content. - if not text is None: - self._contents[self.format_unicode] = unicode(text) + # Handle text content. + if text is not None: + self._content = unicode(text) def __str__(self): - arguments = [] - skip = [] - if self.format_unicode in self._contents: - arguments.append("unicode=%r" - % self._contents[self.format_unicode]) - skip.append(self.format_unicode) - elif self.format_text in self._contents: - arguments.append("text=%r" % self._contents[self.format_text]) - skip.append(self.format_text) - for format in sorted(self._contents.keys()): - if format in skip: - continue - if format in self.format_names: - arguments.append(self.format_names[format]) - else: - arguments.append(repr(format)) - arguments = ", ".join(str(a) for a in arguments) - return "%s(%s)" % (self.__class__.__name__, arguments) + return "%s(%s)" % (self.__class__.__name__, self._content) def copy_from_system(self, formats=None, clear=False): """ - Copy the Windows system clipboard contents into this instance. + Copy the system clipboard contents into this instance. Arguments: - - *formats* (iterable, default: None) -- if not None, only the - given content formats will be retrieved. If None, all - available formats will be retrieved. - - *clear* (boolean, default: False) -- if true, the Windows - system clipboard will be cleared after its contents have been + - *formats* (iterable, default: None) -- this argument has + been *deprecated* and has no effect. + - *clear* (boolean, default: False) -- if true, the system + clipboard will be cleared after its contents have been retrieved. """ - win32clipboard.OpenClipboard() - try: - # Determine which formats to retrieve. - if not formats: - format = 0 - formats = [] - while 1: - format = win32clipboard.EnumClipboardFormats(format) - if not format: - break - formats.append(format) - elif isinstance(formats, int): - formats = (formats,) - - # Verify that the given formats are valid. - try: - for format in formats: - if not isinstance(format, int): - raise TypeError("Invalid clipboard format: %r" - % format) - except Exception, e: - raise - - # Retrieve Windows system clipboard content. - contents = {} - for format in formats: - content = win32clipboard.GetClipboardData(format) - contents[format] = content - self._contents = contents - - # Clear the system clipboard, if requested, and close it. - if clear: - win32clipboard.EmptyClipboard() - - finally: - win32clipboard.CloseClipboard() + # Retrieve the system clipboard content. + self._content = pyperclip.paste() + + # Then clear the system clipboard, if requested. + if clear: + self.clear_clipboard() def copy_to_system(self, clear=True): """ - Copy the contents of this instance into the Windows system - clipboard. + Copy the contents of this instance into the system clipboard. Arguments: - - *clear* (boolean, default: True) -- if true, the Windows - system clipboard will be cleared before this instance's - contents are transferred. + - *clear* (boolean, default: True) -- this argument has been + *deprecated*: the pyperclip implementation for Windows + always clears the clipboard before copying. """ - win32clipboard.OpenClipboard() - try: - # Clear the system clipboard, if requested. - if clear: - win32clipboard.EmptyClipboard() - - # Transfer content to Windows system clipboard. - for format, content in self._contents.items(): - win32clipboard.SetClipboardData(format, content) - - finally: - win32clipboard.CloseClipboard() + # Transfer content to system clipboard. + pyperclip.copy(self._content) def has_format(self, format): """ @@ -189,32 +121,43 @@ def has_format(self, format): - *format* (int) -- the clipboard format to look for. """ - return (format in self._contents) + return format == self.format_unicode and self._content def get_format(self, format): """ Retrieved this instance's content for the given *format*. - Arguments: - - *format* (int) -- the clipboard format to retrieve. - + Only Unicode format (13) is currently supported. If the given *format* is not available, a *ValueError* is raised. + Arguments: + - *format* (int) -- the clipboard format to retrieve. """ - try: - return self._contents[format] - except KeyError: + if format == self.format_unicode: + return self._content + else: raise ValueError("Clipboard format not available: %r" % format) def set_format(self, format, content): - self._contents[format] = content + """ + + Set this instance's content for the given *format*. + + Only Unicode format (13) is currently supported. + If the given *format* is not available, a *ValueError* + is raised. + + Arguments: + - *content* (string) -- the clipboard contents to set. + - *format* (int) -- the clipboard format to set. + """ + pass def has_text(self): """ Determine whether this instance has text content. """ - return (self.format_unicode in self._contents - or self.format_text in self._contents) + return bool(self._content) def get_text(self): """ @@ -222,17 +165,12 @@ def get_text(self): is available, this method returns *None*. """ - if self.format_unicode in self._contents: - return self._contents[self.format_unicode] - elif self.format_text in self._contents: - return self._contents[self.format_text] - else: - return None + return None if not self._content else self._content def set_text(self, content): - self._contents[self.format_unicode] = unicode(content) + self._content = unicode(content) - text = property( - lambda self: self.get_text(), - lambda self, d: self.set_text(d) - ) + text = property( + lambda self: self.get_text(), + lambda self, d: self.set_text(d) + ) diff --git a/setup.py b/setup.py index d5ce527..ec74a51 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,8 @@ def read(*names): install_requires=[ "setuptools >= 0.6c7", - "pywin32;platform_system=='Windows'" + "pywin32;platform_system=='Windows'", + "pyperclip" ], extras_require={