File: keyboard_controller_tests.py

package info (click to toggle)
pynput 1.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 596 kB
  • sloc: python: 6,309; makefile: 10; sh: 1
file content (303 lines) | stat: -rw-r--r-- 10,524 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# coding=utf-8
# pystray
# Copyright (C) 2015-2024 Moses Palmér
#
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

import contextlib
import locale
import sys
import threading

import pynput.keyboard

from six.moves import input

from . import EventTest


class KeyboardControllerTest(EventTest):
    NOTIFICATION = (
        'This test case is non-interactive, so you must not use the '
        'keyboard.\n'
        'You must, however, keep this window focused.')
    CONTROLLER_CLASS = pynput.keyboard.Controller
    LISTENER_CLASS = pynput.keyboard.Listener

    def decode(self, string):
        """Decodes a string read from ``stdin``.

        :param str string: The string to decode.
        """
        if sys.version_info.major >= 3:
            yield string
        else:
            for encoding in (
                    'utf-8',
                    locale.getpreferredencoding(),
                    sys.stdin.encoding):
                if encoding:
                    try:
                        yield string.decode(encoding)
                    except:
                        pass

    @contextlib.contextmanager
    def capture(self):
        """Captures a string in a code block.

        :returns: a callable which returns the actual data read
        """
        data = []

        #: The thread body that reads a line from stdin and appends it to data
        def reader():
            while reader.running:
                data.append(sys.stdin.readline()[:-1])
        reader.running = True

        # Start the thread
        thread = threading.Thread(target=reader)
        thread.start()

        # Run the code block
        try:
            yield lambda: tuple(self.decode(''.join(data)))

        finally:
            # Send a newline to let sys.stdin.readline return in reader
            reader.running = False
            self.controller.tap(pynput.keyboard.Key.enter)
            thread.join()

    def assert_input(self, failure_message, expected):
        """Asserts that a specific text is generated when typing.

        :param str failure_message: The message to display upon failure.

        :param text: The text to type and expect.
        """
        with self.capture() as collect:
            self.controller.type(expected)

        self.assertIn(expected, collect(), failure_message)

    def test_keys(self):
        """Asserts that all keys defined for the base keyboard interface are
        defined for the current platform"""
        from pynput.keyboard._base import Key
        for key in Key:
            self.assertTrue(
                hasattr(pynput.keyboard.Key, key.name),
                '%s is not defined for the current platform' % key.name)

    def test_press_invalid(self):
        with self.assertRaises(self.controller.InvalidKeyException):
            self.controller.press(True)

    def test_release_invalid(self):
        with self.assertRaises(self.controller.InvalidKeyException):
            self.controller.release(True)

    def test_press_release(self):
        """Asserts that a press followed by a release generates a typed string
        for an ascii character"""
        with self.capture() as collect:
            self.controller.tap(pynput.keyboard.Key.space)

        self.assertIn(
            u' ',
            collect(),
            'Failed to press and release space')

    def test_touch(self):
        """Asserts that the touch shortcut behaves as expected"""
        with self.capture() as collect:
            self.controller.touch(pynput.keyboard.Key.space, True)
            self.controller.touch(pynput.keyboard.Key.space, False)

        self.assertIn(
            u' ',
            collect(),
            'Failed to press and release space')

    def test_touch_dead(self):
        """Asserts that pressing dead keys generate combined characters"""
        with self.capture() as collect:
            dead = pynput.keyboard.KeyCode.from_dead(u'~')
            self.controller.tap(dead)
            self.controller.tap(u'a')

        self.assertIn(
            u'ã',
            collect(),
            'Failed to apply dead key')

    def test_touch_dead_space(self):
        """Asserts that pressing dead keys followed by space yields the
        non-dead version"""
        with self.capture() as collect:
            dead = pynput.keyboard.KeyCode.from_dead(u'~')
            self.controller.tap(dead)
            self.controller.tap(pynput.keyboard.Key.space)

        self.assertIn(
            u'~',
            collect(),
            'Failed to apply dead key')

    def test_touch_dead_twice(self):
        """Asserts that pressing dead keys twice yields the non-dead version"""
        with self.capture() as collect:
            dead = pynput.keyboard.KeyCode.from_dead(u'~')
            self.controller.tap(dead)
            self.controller.tap(dead)

        self.assertIn(
            u'~',
            collect(),
            'Failed to apply dead key')

    def test_alt_pressed(self):
        """Asserts that alt_pressed works"""
        # We do not test alt_r, since that does not necessarily exist on the
        # keyboard
        for key in (
                pynput.keyboard.Key.alt,
                pynput.keyboard.Key.alt_l):
            self.controller.press(key)
            self.assertTrue(
                self.controller.alt_pressed,
                'alt_pressed was not set with %s down' % key.name)
            self.controller.release(key)
            self.assertFalse(
                self.controller.alt_pressed,
                'alt_pressed was incorrectly set')

    def test_ctrl_pressed(self):
        """Asserts that ctrl_pressed works"""
        for key in (
                pynput.keyboard.Key.ctrl,
                pynput.keyboard.Key.ctrl_l,
                pynput.keyboard.Key.ctrl_r):
            self.controller.press(key)
            self.assertTrue(
                self.controller.ctrl_pressed,
                'ctrl_pressed was not set with %s down' % key.name)
            self.controller.release(key)
            self.assertFalse(
                self.controller.ctrl_pressed,
                'ctrl_pressed was incorrectly set')

    def test_shift_pressed(self):
        """Asserts that shift_pressed works with normal presses"""
        for key in (
                pynput.keyboard.Key.shift,
                pynput.keyboard.Key.shift_l,
                pynput.keyboard.Key.shift_r):
            self.controller.press(key)
            self.assertTrue(
                self.controller.shift_pressed,
                'shift_pressed was not set with %s down' % key.name)
            self.controller.release(key)
            self.assertFalse(
                self.controller.shift_pressed,
                'shift_pressed was incorrectly set')

    def test_shift_pressed_caps_lock(self):
        """Asserts that shift_pressed is True when caps lock is toggled"""
        self.controller.tap(pynput.keyboard.Key.caps_lock)
        self.assertTrue(
            self.controller.shift_pressed,
            'shift_pressed was not set with caps lock toggled')

        self.controller.tap(pynput.keyboard.Key.caps_lock)
        self.assertFalse(
            self.controller.shift_pressed,
            'shift_pressed was not deactivated with caps lock toggled')

    def test_pressed_shift(self):
        """Asserts that pressing and releasing a Latin character while pressing
        shift causes it to shift to upper case"""
        with self.capture() as collect:
            with self.controller.pressed(pynput.keyboard.Key.shift):
                self.controller.tap(u'a')

                with self.controller.modifiers as modifiers:
                    self.assertIn(
                        pynput.keyboard.Key.shift,
                        modifiers)

        self.assertIn(
            u'A',
            collect(),
            'shift+a did not yield "A"')

    def test_pressed_is_release(self):
        """Asserts that pressed actually releases the key"""
        with self.capture() as collect:
            with self.controller.pressed(pynput.keyboard.Key.shift):
                self.controller.tap(u'a')

            self.controller.tap(u'a')

            with self.controller.pressed(pynput.keyboard.Key.shift):
                self.controller.tap(u'a')


        self.assertIn(
            u'AaA',
            collect(),
            'Keys were not properly released')

    def test_type_latin(self):
        """Asserts that type works for a Latin string"""
        self.assert_input(
            'Failed to type latin string',
            u'Hello World')

    def test_type_ascii(self):
        """Asserts that type works for an ascii string"""
        self.assert_input(
            'Failed to type ascii string',
            u'abc123, "quoted!"')

    def test_type_nonascii(self):
        """Asserts that type works for a non-ascii strings"""
        self.assert_input(
            'Failed to type Spanish string',
            u'Teclado (informática)')
        self.assert_input(
            'Failed to type Russian string',
            u'Компьютерная клавиатура')

    def test_type_control_codes(self):
        """Asserts that type works for a string containing control codes"""
        self.assert_input(
            'Failed to type latin string',
            u'Hello\tworld')

    def test_controller_events(self):
        """Tests that events sent by a controller are received correctly"""
        with self.assert_event(
                'Failed to send press',
                on_press=lambda k: getattr(k, 'char', None) == u'a'):
            self.controller.press(u'a')
        with self.assert_event(
                'Failed to send release',
                on_release=lambda k: getattr(k, 'char', None) == u'a'):
            self.controller.release(u'a')

        self.controller.tap(pynput.keyboard.Key.enter)
        input()