Skip to content

Commit

Permalink
Merge pull request #329 from dictation-toolbox/feat/clipboard-improve…
Browse files Browse the repository at this point in the history
…ments

Clipboard toolkit improvements
  • Loading branch information
drmfinlay committed Apr 16, 2021
2 parents 517ad44 + a6731a4 commit 59630b3
Show file tree
Hide file tree
Showing 10 changed files with 613 additions and 37 deletions.
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ replace :code:`dragonfly` with :code:`dragonfly2` or remove lines like this
altogether.

If you are installing this on Linux, you will also need to install the
`xdotool <https://www.semicomplete.com/projects/xdotool/>`__ and `wmctrl
<https://www.freedesktop.org/wiki/Software/wmctrl/>`__ programs. You may
`wmctrl <https://www.freedesktop.org/wiki/Software/wmctrl/>`__, `xdotool
<https://www.semicomplete.com/projects/xdotool/>`__ and `xsel
<http://www.vergenet.net/~conrad/software/xsel/>`__ programs. You may
also need to manually set the ``XDG_SESSION_TYPE`` environment variable to
``x11``.

Expand Down
13 changes: 13 additions & 0 deletions documentation/clipboard.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ on the platform:
* :class:`dragonfly.windows.win32_clipboard.Win32Clipboard` is used on
Windows.

* :class:`dragonfly.windows.x11_clipboard.XselClipboard` is used on
X11/Linux. This class requires the ``xclip`` program.

* :class:`dragonfly.windows.pyperclip_clipboard.PyperclipClipboard` is used
on other platforms, such as macOS or Linux.

Expand Down Expand Up @@ -103,6 +106,16 @@ Windows Clipboard context manager function
.. autofunction:: dragonfly.windows.win32_clipboard.win32_clipboard_ctx


X11 Clipboard classes
----------------------------------------------------------------------------

.. autoclass:: dragonfly.windows.x11_clipboard.BaseX11Clipboard
:members:

.. autoclass:: dragonfly.windows.x11_clipboard.XselClipboard
:members:


Pyperclip Clipboard class
----------------------------------------------------------------------------

Expand Down
7 changes: 4 additions & 3 deletions documentation/installation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ To be able to use the dragonfly, you will need the following:
* **Natlink** *(only for Dragon users)* -- latest
versions available from `SourceForge
<https://sourceforge.net/projects/natlink/files/natlink/natlinktest4.1/>`_.
* `Xdotool <https://www.semicomplete.com/projects/xdotool/>`__ and `wmctrl
<https://www.freedesktop.org/wiki/Software/wmctrl/>`__ programs *(only
* `wmctrl <https://www.freedesktop.org/wiki/Software/wmctrl/>`__, `xdotool
<https://www.semicomplete.com/projects/xdotool/>`__ and `xsel
<http://www.vergenet.net/~conrad/software/xsel/>`__ programs *(only
for Linux/X11 users)* -- usually available from your system's package
manager
manager.

**Note for Linux users**: Dragonfly is only fully functional in an X11
session. You may also need to manually set the ``XDG_SESSION_TYPE``
Expand Down
8 changes: 4 additions & 4 deletions documentation/kaldi_engine.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ from `kaldi-active-grammar
<https://github.com/daanzu/kaldi-active-grammar>`_. Unzip it into a
directory within the directory containing your grammar modules.

**Note for Linux:** Before proceeding, you'll need ``wmctrl`` and
``xdotool``. Under ``apt``-based distributions, you can get them by
running::
**Note for Linux:** Before proceeding, you'll need to install the
``wmctrl``, ``xdotool`` and ``xsel`` programs. Under ``apt``-based
distributions, you can get them by running::

sudo apt install wmctrl xdotool
sudo apt install wmctrl xdotool xsel

You may also need to manually set the ``XDG_SESSION_TYPE`` environment
variable to ``x11``.
Expand Down
128 changes: 128 additions & 0 deletions dragonfly/test/test_clipboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@
# <http://www.gnu.org/licenses/>.
#

import os
from tempfile import NamedTemporaryFile, mkdtemp
import unittest

from dragonfly import Clipboard


format_unicode = Clipboard.format_unicode
format_text = Clipboard.format_text
format_hdrop = Clipboard.format_hdrop


class TestClipboard(unittest.TestCase):
Expand All @@ -35,6 +38,8 @@ class TestClipboard(unittest.TestCase):
These should pass on all supported platforms.
"""

#-----------------------------------------------------------------------

@classmethod
def setUpClass(cls):
# Save the current contents of the clipboard.
Expand All @@ -49,6 +54,8 @@ def setUp(self):
# Clear the clipboard before each test.
Clipboard.clear_clipboard()

#-----------------------------------------------------------------------

def test_get_set_system_text(self):
# Test with an empty system clipboard.
Clipboard.clear_clipboard()
Expand Down Expand Up @@ -79,6 +86,92 @@ def test_clear_clipboard(self):
# Then test that it has been cleared.
self.assertEqual(Clipboard.get_system_text(), "")

def test_convert_format_content(self):
# Define a convenient shorthand function for testing the
# Clipboard.convert_format_content() class method.
def convert(format, content): # pylint: disable=redefined-builtin
return Clipboard.convert_format_content(format, content)

# Text strings used with format_text (ANSI) are converted.
self.assertEqual(convert(format_text, u"text"), b"text")

# Text strings used with format_unicode are not changed.
self.assertEqual(convert(format_unicode, u"text"), u"text")

# Binary strings used with format_unicode are converted.
self.assertEqual(convert(format_unicode, b"text"), u"text")

# Binary strings used with format_text are not changed.
self.assertEqual(convert(format_text, b"text"), b"text")

# Test format_hdrop with existing file paths.
with NamedTemporaryFile() as f1, NamedTemporaryFile() as f2:
# Strings are converted to tuples containing file paths. Test
# using Unicode and binary strings.
# First, test using one existing file.
for test_string in [u"%s\0\0", u"%s\n\n", u"%s\0", u"%s\n"]:
test_string = test_string % f1.name
f1name = u"%s" % f1.name
self.assertEqual(convert(format_hdrop, test_string),
(f1name,))
test_string = test_string.encode()
f1name = f1name.encode()
self.assertEqual(convert(format_hdrop, test_string),
(f1name,))

# Then, test using two.
for test_string in ["%s\0%s\0\0", "%s\n%s\0", "%s\n%s\n"]:
test_string = test_string % (f1.name, f2.name)
f1name, f2name = u"%s" % f1.name, u"%s" % f2.name
self.assertEqual(convert(format_hdrop, test_string),
(f1name, f2name))
test_string = test_string.encode()
f1name, f2name = f1name.encode(), f2name.encode()
self.assertEqual(convert(format_hdrop, test_string),
(f1name, f2name))

# Tuples and lists containing strings are accepted. Lists are
# converted to tuples.
self.assertEqual(convert(format_hdrop, (f1.name,)), (f1.name,))
self.assertEqual(convert(format_hdrop, [f1.name]), (f1.name,))

# Existing relative file paths are not accepted.
cwd = os.getcwd()
try:
# Make a temporary directory and enter it.
tempdir = mkdtemp()
os.chdir(tempdir)

# Test the relative file path.
with NamedTemporaryFile(dir=tempdir) as f:
basename = u"%s" % os.path.basename(f.name)
for test_obj in [basename, basename.encode(), [basename],
(basename,)]:
self.assertRaises(ValueError, convert, format_hdrop,
test_obj)
finally:
# Return to the original working directory and delete tempdir.
os.chdir(cwd)
os.rmdir(tempdir)

# Errors are raised when unacceptable content is used with the text
# formats or with format_hdrop.
for test_obj in [object(), None, 0]:
self.assertRaises(TypeError, convert, format_text, test_obj)
self.assertRaises(TypeError, convert, format_unicode, test_obj)
self.assertRaises(TypeError, convert, format_hdrop, test_obj)
self.assertRaises(TypeError, convert, format_hdrop, (test_obj,))
self.assertRaises(TypeError, convert, format_hdrop, [test_obj])
for test_string in [u"", u"\n", u"\0", u"test\n", u"test\0"]:
self.assertRaises(ValueError, convert, format_hdrop,
test_string)
self.assertRaises(ValueError, convert, format_hdrop,
test_string.encode())

# Empty lists/tuples are not acceptable format_hdrop content.
self.assertRaises(ValueError, convert, format_hdrop, [])
self.assertRaises(ValueError, convert, format_hdrop, ())

def test_from_system_argument(self):
# Test the optional from_system argument of Clipboard.__init__
text = "something"
Expand Down Expand Up @@ -202,6 +295,7 @@ def test_has_format(self):
c = Clipboard()
self.assertFalse(c.has_format(format_unicode))
self.assertFalse(c.has_format(format_text))
self.assertFalse(c.has_format(format_hdrop))

# Test with one format.
c = Clipboard(contents={format_unicode: u"unicode text"})
Expand All @@ -219,19 +313,29 @@ def test_get_format(self):
c = Clipboard()
self.assertRaises(ValueError, c.get_format, format_unicode)
self.assertRaises(ValueError, c.get_format, format_text)
self.assertRaises(ValueError, c.get_format, format_hdrop)

# Test with one format.
text1 = u"unicode text"
c = Clipboard(contents={format_unicode: text1})
self.assertEqual(c.get_format(format_unicode), text1)
self.assertRaises(ValueError, c.get_format, format_text)
self.assertRaises(ValueError, c.get_format, format_hdrop)

# Test with two formats.
text2 = b"text"
c = Clipboard(contents={format_unicode: text1,
format_text: text2})
self.assertEqual(c.get_format(format_unicode), text1)
self.assertEqual(c.get_format(format_text), text2)
self.assertRaises(ValueError, c.get_format, format_hdrop)

# Test with format_hdrop.
with NamedTemporaryFile() as f:
c = Clipboard(contents={format_hdrop: f.name})
self.assertRaises(ValueError, c.get_format, format_unicode)
self.assertRaises(ValueError, c.get_format, format_text)
self.assertEqual(c.get_format(format_hdrop), (f.name,))

def test_set_format(self):
# Test with one format.
Expand All @@ -251,14 +355,28 @@ def test_set_format(self):
self.assertTrue(c.has_format(format_text))
self.assertEqual(c.get_format(format_text), text2)

# Test with format_hdrop.
with NamedTemporaryFile() as f:
c.set_format(format_hdrop, f.name)
self.assertTrue(c.has_format(format_unicode))
self.assertTrue(c.has_format(format_text))
self.assertTrue(c.has_format(format_hdrop))
self.assertEqual(c.get_format(format_hdrop), (f.name,))

# Setting a format to None removes the format's content from the
# instance.
c.set_format(format_text, None)
self.assertFalse(c.has_format(format_text))
self.assertRaises(ValueError, c.get_format, format_text)
self.assertTrue(c.has_format(format_unicode))
self.assertTrue(c.has_format(format_hdrop))
c.set_format(format_unicode, None)
self.assertFalse(c.has_format(format_unicode))
self.assertRaises(ValueError, c.get_format, format_unicode)
self.assertTrue(c.has_format(format_hdrop))
c.set_format(format_hdrop, None)
self.assertFalse(c.has_format(format_hdrop))
self.assertRaises(ValueError, c.get_format, format_hdrop)

def test_has_text(self):
# Test with an empty Clipboard instance.
Expand All @@ -278,6 +396,11 @@ def test_has_text(self):
c = Clipboard(contents={format_text: b"text"})
self.assertTrue(c.has_text())

# Test with format_hdrop only.
with NamedTemporaryFile() as f:
c = Clipboard(contents={format_hdrop: f.name})
self.assertFalse(c.has_text())

def test_set_text(self):
c = Clipboard()
text = "test"
Expand Down Expand Up @@ -316,6 +439,11 @@ def test_get_text(self):
c.set_text(text2)
self.assertEqual(c.get_text(), text2)

# Test with format_hdrop.
with NamedTemporaryFile() as f:
c = Clipboard(contents={format_hdrop: f.name})
self.assertIsNone(c.get_text())

def test_flexible_string_types(self):
# This is similar to the clipboard format conversion that Windows
# performs when necessary. The Clipboard class should do this
Expand Down

0 comments on commit 59630b3

Please sign in to comment.