root/projects/WinDictator/trunk/windictator/linux/typist.py

Revision 19, 7.0 kB (checked in by edsuom, 1 year ago)

Importing windictator into the global FOSS repo

Line 
1 # WinDictator:
2 # Dictate in Windows, have the text typed in Linux via X faked keystroke events
3 #
4 # Copyright (C) 2005-2006 by Edwin A. Suominen, http://www.eepatents.com
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free Software
8 # Foundation; either version 2 of the License, or (at your option) any later
9 # version.
10 #
11 # This program is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 # FOR A PARTICULAR PURPOSE.  See the file COPYING for more details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51
17 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18
19
20 """
21 A virtual typist
22 """
23
24 from twisted.internet import defer
25
26 import observer, keyer, keycodes
27
28
29 class Typist(object):
30     """
31     I type what you send. Give me some text and I'll see that the right fake
32     keystrokes are generated
33
34     """
35     def __init__(self):
36         self.history = observer.KeyHistory()
37         self.observer = observer.Observer(self.history)
38         self.keyer = keyer.Keyer()
39         self.keyCoder = keycodes.KeyCoder()
40
41     def startup(self, *null):
42         """
43         Starts up all my helpers, returning a C{Deferred} that fires when all
44         are ready.
45         """
46         d1 = self.keyer.startup()
47         d2 = self.observer.startup()
48         return defer.DeferredList([d1,d2])
49
50     def shutdown(self, *null):
51         """
52         Shuts down up all my helpers, returning a C{Deferred} that fires when
53         all are stopped.
54         """
55         d1 = self.keyer.shutdown()
56         d2 = self.observer.shutdown()
57         return defer.DeferredList([d1,d2])
58
59     @defer.deferredGenerator
60     def pressAndReleaseKeys(self, keyList):
61         """
62         This method is decorated with L{defer.deferredGenerator} to immediately
63         return a C{Deferred} that fires upon completion of an iteration over
64         key combinations, which are supplied as sub-sequences of the I{keyList}
65         sequence. The deferred fires with C{True} if all keys were observed to
66         have been pressed and reseased, C{False} otherwise.
67
68         The generator sends fake X key events for each key of each
69         sub-sequence, a keypress followed by a release, and waits for
70         confirmation of each faked key event before proceeding with the next
71         one.
72         """
73         running = True
74        
75         def gotConfirmation(confirmed):
76             running = confirmed
77             return confirmed
78        
79         def keyAndConfirm(func, key):
80             d = func(key)
81             d.addCallback(self.observer.confirm)
82             d.addCallback(gotConfirmation)
83             return defer.waitForDeferred(d)
84        
85         stack = []
86         while running and keyList:
87             keySequence = keyList.pop(0)
88             # Depress each key in order
89             for key in keySequence:
90                 stack.append(key)
91                 wfd = keyAndConfirm(self.keyer.pressKey, key)
92                 yield wfd
93                 if not wfd.getResult():
94                     # Key depression not noted, try one more time
95                     yield defer.waitForDeferred(self.keyer.releaseKey(key))
96                     wfd = keyAndConfirm(self.keyer.pressKey, key)
97                     yield wfd
98                     if not wfd.getResult():
99                         # Key depression still not noted, bail out
100                         yield defer.waitForDeferred(self.keyer.releaseKey(key))
101                         running = False
102                         break
103             # Release each key in reverse order
104             while running and stack:
105                 key = stack.pop()
106                 wfd = keyAndConfirm(self.keyer.releaseKey, key)
107                 yield wfd
108                 if not wfd.getResult():
109                     # Key release not noted, bail out
110                     running = False
111         # Final yield of a Boolean, rather than a wfd, is supplied to the
112         # deferredGenerator's callback
113         yield running
114
115     def backspace(self, N):
116         """
117         Deletes backwards I{N} characters using the C{BackSpace} keysym.
118         """
119         if self.typingEnabled():
120             self.history.backspace(N)
121             keyList = [("BackSpace",)] * N
122             d = self.pressAndReleaseKeys(keyList)
123         else:
124             # Typing isn't enabled, just return a Deferred that immediately
125             # fires with False typing-failed status
126             d = defer.succeed(False)
127         return d
128
129     def insert(self, text):
130         """
131         Parses the supplied I{text} and sends chunks to the appropriate methods
132         of the keyer, returning C{True} if entry of all text was noted and
133         C{False} if not.
134         """
135         if self.typingEnabled:
136             text = self.contextAdjust(text)
137             keyList = self.keyCoder.textToKeys(text)
138             d = self.pressAndReleaseKeys(keyList)
139         else:
140             # Typing isn't enabled, just return a Deferred that immediately
141             # fires with False typing-failed status
142             d = defer.succeed(False)
143         return d
144
145     def typingEnabled(self, text=None):
146         """
147         Indicates whether typing is currently enabled based on the last text
148         supplied and a recent history of observed keystrokes. Typing is B{not}
149         enabled if any of the following conditions are met:
150
151             - the last observed keystroke was recently typed and implemented
152               cursor movement;
153
154             - the last observed keystroke implemented cursor movement and the
155               last-supplied text consists of single word;
156
157             - the dictation mode is 'sleep' instead of 'active'
158
159         """
160         # TODO
161         return True
162    
163     def contextAdjust(self, text):
164         """
165         Adjusts the supplied I{text} based on a recent history of observed
166         keystrokes, excluding I{charsBackwards} characters that are being
167         skipped over or trimmed off the tail end of the history to reach the
168         insertion point. Here are the rules:
169
170             - If a word, comma, or semicolon followed by zero or one space was
171               previously entered, and the beginning of this text is another
172               word, this text will be adjusted as needed to have a single space
173               present.
174
175             - If a period, exclamation point, or question mark followed by zero
176               through two spaces was previously entered, and the beginning of
177               this text is another word, this text will be adjusted as needed
178               to have two spaces present and have the new text start with a
179               capital letter.
180
181             - If a hyphen was previously entered with no spaces thereafter, any
182               spaces at the beginning of this text will be deleted and the
183               first letter will be forced to lowercase.
184             
185         """
186         # TODO
187         return text
188        
Note: See TracBrowser for help on using the browser.