imitator_x 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,70 @@
1
+ =Imitator for X
2
+ The Imitator for X library brings input automation to Linux by a C extension written for Ruby.
3
+ You can fake keyboard and mouse input, just as if an user sits in front of a computer and presses and
4
+ releases keys or moves the mouse. Additionally, you get control of the CD/DVD devices and the windows displayed
5
+ on the screen, properly wrapped as Ruby objects. As an useful extra, clipboard access is provided.
6
+ ==Installation
7
+ Just as always.
8
+ # gem install imitator_x
9
+ You need to have the X11 libraries (should be available on every system using X11) and the development
10
+ headers of the XTEST extension or compiling will fail. On Ubuntu, you do a
11
+ sudo apt-get install libxtst-dev
12
+ or use +aptitude+ or Synaptic or whatever package manager you like. It shouldn't be hard to obtain the package for other systems.
13
+ ==Usage
14
+ require "imitator/x"
15
+
16
+ #Move the mouse somewhere
17
+ Imitator::X::Mouse.move(100, 100)
18
+ #Send some keystrokes
19
+ Imitator::X::Keyboard.simulate("This is testtext!")
20
+ #Or kill an opened window
21
+ xwin = Imitator::X::XWindow.from_title(/gedit/)
22
+ xwin.kill
23
+ ==Note for non-gem use and debugging
24
+ If you don't want to use Imitator for X as a gem, for example for debugging purposes, you will have to set the
25
+ gobal variable $imitator_x_charfile_path to the path of your "imitator_x_special_chars.yml"
26
+ file (which contains the key combinations for creating characters like @) _before_ you require "imitator/x",
27
+ because otherwise Imitator for X isn't able to find that file (or if you have a non-gem version *and*
28
+ the gem installed you'll get the gem's file).
29
+ ==Author
30
+ Marvin G�lker
31
+ You can contact me at sutniuq<>gmx<>net.
32
+
33
+ Initia in potestate nostra sunt, de eventu fortuna iudicat.
34
+ ==License & Copyright
35
+ Copyright � 2010 Marvin G�lker
36
+
37
+ Imitator for X is free software: you can redistribute it and/or modify
38
+ it under the terms of the GNU Lesser General Public License as published by
39
+ the Free Software Foundation, either version 3 of the License, or
40
+ (at your option) any later version.
41
+
42
+ Imitator for X is distributed in the hope that it will be useful,
43
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
44
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45
+ GNU Lesser General Public License for more details.
46
+
47
+ You should have received a copy of the GNU Lesser General Public License
48
+ along with Imitator for X. If not, see <http://www.gnu.org/licenses/>.
49
+
50
+ ==Disclaimer of Warranty
51
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
52
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
53
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
54
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
55
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
57
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
58
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
59
+
60
+ ==Limitation of Liability
61
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
62
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
63
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
64
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
65
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
66
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
67
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
68
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
69
+ SUCH DAMAGES.
70
+
data/Rakefile.rb ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ #Encoding: UTF-8
3
+ =begin
4
+ --
5
+ Imitator for X is a library allowing you to fake input to systems using X11.
6
+ Copyright © 2010 Marvin Gülker
7
+
8
+ This file is part of Imitator for X.
9
+
10
+ Imitator for X is free software: you can redistribute it and/or modify
11
+ it under the terms of the GNU Lesser General Public License as published by
12
+ the Free Software Foundation, either version 3 of the License, or
13
+ (at your option) any later version.
14
+
15
+ Imitator for X is distributed in the hope that it will be useful,
16
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ GNU Lesser General Public License for more details.
19
+
20
+ You should have received a copy of the GNU Lesser General Public License
21
+ along with Imitator for X. If not, see <http://www.gnu.org/licenses/>.
22
+ ++
23
+ =end
24
+
25
+ require "rake/gempackagetask"
26
+ require "rake/clean"
27
+ require "rake/testtask"
28
+ begin
29
+ require "hanna/rdoctask"
30
+ rescue LoadError
31
+ require "rake/rdoctask"
32
+ end
33
+
34
+ $stdout.sync = true
35
+
36
+ CLEAN.include("ext/*.o")
37
+ CLEAN.include("ext/Makefile")
38
+ CLEAN.include("ext/mkmf.log")
39
+ CLOBBER.include("ext/*.so")
40
+
41
+ Rake::RDocTask.new do |rt|
42
+ rt.options = ["--charset=ISO-8859-1"] #Needed for correct displaying of chars like ä.
43
+ rt.rdoc_files.include("README.rdoc", "TODO.rdoc", "COPYING.rdoc", "COPYING.LESSER.rdoc")
44
+ rt.rdoc_files.include("ext/x.c")
45
+ rt.rdoc_files.include("ext/xwindow.c")
46
+ rt.rdoc_files.include("ext/mouse.c")
47
+ rt.rdoc_files.include("ext/clipboard.c")
48
+ rt.rdoc_files.include("ext/keyboard.c")
49
+ rt.rdoc_files.include("lib/imitator/x.rb")
50
+ rt.rdoc_files.include("lib/imitator/x/drive.rb")
51
+ rt.title = "Imitator for X: RDocs"
52
+ rt.main = "README.rdoc"
53
+ rt.rdoc_dir = "doc"
54
+ end
55
+
56
+ spec = Gem::Specification.new do |s|
57
+ s.name = "imitator_x"
58
+ s.summary = "Simulate keyboard and mouse input directly via Ruby to a Linux X server system"
59
+ s.description =<<DESC
60
+ The Imitator for X library allows you to simulate key and
61
+ mouse input to any Linux system that uses the X server version 11.
62
+ Features also include CD/DVD drive control and direct interaction
63
+ with Windows, wrapped as Rubyish objects.
64
+ It's a Ruby 1.9-only library. Please also note that this is
65
+ a C extension that will be compiled during installing.
66
+ DESC
67
+ s.add_development_dependency("test-unit", ">= 2.0")
68
+ s.requirements = ["Linux with X11", "eject command (usually installed)"]
69
+ s.version = "0.0.1"
70
+ s.author = "Marvin Gülker"
71
+ s.email = "sutniuq<>gmx<>net"
72
+ s.platform = Gem::Platform::RUBY #BUT it's Linux-only
73
+ s.required_ruby_version = ">=1.9"
74
+ s.files = [Dir["lib/**/*.rb"], Dir["ext/**/**.c"], Dir["ext/**/*.h"], Dir["test/*.rb"], "ext/extconf.rb", "lib/imitator_x_special_chars.yml", "Rakefile.rb", "README.rdoc", "TODO.rdoc", "COPYING.rdoc", "COPYING.LESSER.rdoc"].flatten
75
+ s.extensions << "ext/extconf.rb"
76
+ s.has_rdoc = true
77
+ s.extra_rdoc_files = %w[README.rdoc TODO.rdoc COPYING.rdoc COPYING.LESSER.rdoc ext/x.c ext/xwindow.c ext/mouse.c ext/clipboard.c ext/keyboard.c] #Why doesn't RDoc document the C files automatically?
78
+ s.rdoc_options << "-t" << "Imitator for X: RDocs" << "-m" << "README.rdoc" << "-c" << "ISO-8859-1"
79
+ s.test_files = Dir["test/test_*.rb"]
80
+ #s.rubyforge_project =
81
+ s.homepage = "http://github.com/Quintus/imitator"
82
+ end
83
+ Rake::GemPackageTask.new(spec).define
84
+
85
+ Rake::TestTask.new("test") do |t|
86
+ t.pattern = "test/test_*.rb"
87
+ t.warning = true
88
+ end
89
+
90
+ desc "Compile Imitator for X"
91
+ task :compile do
92
+ Dir.chdir("ext") do
93
+ ruby "extconf.rb"
94
+ sh "make"
95
+ end
96
+ end
97
+
98
+ desc "Compiles, tests and then creates the gem file."
99
+ task :default => [:clobber, :compile, :test, :gem]
data/TODO.rdoc ADDED
@@ -0,0 +1,8 @@
1
+ =TODO list
2
+ This is the list of things planned but not done yet.
3
+
4
+ * Make XWindow#children return XWindow objects [Segfault problems]
5
+ * Make XWindow.search searching recursive all children layers
6
+ * Make Mouse.wheel accept the same parameters as Mouse.move
7
+ * Implement for Clipboard.write the TIMESTAMP request
8
+ * Take care of swapped mouse buttons for left-handed people
data/ext/clipboard.c ADDED
@@ -0,0 +1,429 @@
1
+ /*********************************************************************************
2
+ Imitator for X is a library allowing you to fake input to systems using X11.
3
+ Copyright � 2010 Marvin G�lker
4
+
5
+ This file is part of Imitator for X.
6
+
7
+ Imitator for X is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Imitator for X is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with Imitator for X. If not, see <http://www.gnu.org/licenses/>.
19
+ *********************************************************************************/
20
+ #include "x.h"
21
+ #include "clipboard.h"
22
+
23
+ /*
24
+ *It was an awful lot of work to get into the X clipboard stuff.
25
+ *I don't think I'll ever forget this, but just in case, here are the
26
+ *important things:
27
+ ** What the X standard calls a "selection" is a clipboard.
28
+ ** An atom is for X what a symbol is for Ruby. Just an internal storage thing convertable to strings.
29
+ ** X maintains 3 clipboards:
30
+ ***PRIMARY (Atom: XA_PRIMARY): The middle-mouse-button-clipboard. Mildly deprecated.
31
+ ***SECONDARY (Atom: XA_SECONDARY): This clipboard isn't used by anyone. It's just there and stands around.
32
+ *** CLIPBOARD (Atom: XInternAtom(p_display, "CLIPBOARD", True): The main clipboard. It acts as the Mac or Windows clipboard via [CTRL]+[C] and the like.
33
+ **XConvertSelection() is sparely described in the X standard. For whatever reason, after 10 times reading it's documentation I
34
+ * did not understand what's actually _converted_ here. After I looked into xsel's and xclip's sources many times I finally got it:
35
+ * this damned function converts the clipboard data from X's internal storage to the specified encoding. The word "encoding" wasn't ever
36
+ * mentioned in the function's description, just "target atom". Why? It's a riddle, solve it if you like...
37
+ ** XA_STRING is the atom for the ISO-8859-1 encoding.
38
+ * The atom for UTF-8 is: XInternAtom(p_display, "UTF8_STRING", True).
39
+ * The atom for binary and ASCII stuff is XA_TEXT.
40
+ **XConvertSelection() fails for the CLIPBOARD selection if None is specified as the "property" parameter; just another thing not documented.
41
+ * It's absolutly... We Germans say "wurschtegal", I don't know how to translate this - it's unimportant in any way - what you pass in. Just use an atom you like, as e.g. XInternAtom(p_display, "DAMNED_FUNCTION", False).
42
+ ** If a X function wants a delay, you shouldn't pass in 0 if you want to make it executed immediately. Pass in CurrentTime instead.
43
+ **Since X doesn't maintain a central clipboard application (but thank God, approaches are made for a shared library in order to simplify the interaction with the selections!) you must
44
+ * create a requestor window whose "xselection" property will get set. That works even if the window isn't mapped.
45
+ ** Data in the PRIMARY clipboard is set in that very moment you select text. You don't have to press any button or click any menu - it's just made. If you deselect the text,
46
+ * the PRIMARY clipboard's content goes away.
47
+ ** The requestor window will get a SelectionNotify event after a call to XConvertSelection(), regardless of that function's success. If XConvertSelection() failed, the SelectionNotify's
48
+ * "property" member will be None.
49
+ */
50
+
51
+ /*Document-module: Imitator::X::Clipboard
52
+ *Yeah, finally, this is the Clipboard module. During development I thought more than once I should stop
53
+ *this, because to deal with X's selection functions is awkward. And still, the Clipboard.write method doesn't work
54
+ *well, so don't rely on it (although Clipboard.read should do a good job).
55
+ *Also, the methods defined here can't be used to set or access non-textual data.
56
+ *
57
+ *So, here's what you need to know about X and the clipboard:
58
+ *
59
+ ** <b>X doesn't have clipboards. </b>
60
+ * Yes, that's true! X does not use the term "clipboard", they're always called "selections".
61
+ *
62
+ ** <b>X has 3 selections. </b>
63
+ * Every X server maintains three selections: The PRIMARY, the SECONDARY and the CLIPBOARD selection.
64
+ *
65
+ ** The <b>PRIMARY</b> selection is set when you select some text - really, you don't need anything else. Just
66
+ * open a terminal window, select some text and then press the middle mouse button (or your mouse wheel)
67
+ * and the selected text gets pasted. Of course this data can't survive the application that sets it.
68
+ *
69
+ ** The <b>SECONDARY</b> selection... well, it's used by nobody. Just ignore it.
70
+ *
71
+ ** The <b>CLIPBOARD</b> selection is to an extend of ~95% the one you're looking for. When you press [CTRL]+[C]
72
+ * or select text and then choose "copy" from a pull-down menu, your data is stored here. Depending on your
73
+ * system, the data stored here remains accessible even after the application that set it terminated. To achieve this,
74
+ * a program called a "clipboard manager" that is running in the background silently copies data applications store
75
+ * in the CLIPBOARD selection into it's own buffer and then acquires ownership of CLIPBOARD.
76
+ *
77
+ *In conclusion, just remember that you should probably try the PRIMARY selection if CLIPBOARD doesn't contain your data.
78
+ *
79
+ *This module operates on UTF-8-encoded strings; that means, you should pass UTF-8-encoded strings to Clipboard.write and
80
+ *you get back UTF-8-encoded strings from Clipboard.read.
81
+ */
82
+
83
+ /*
84
+ *call-seq:
85
+ * Clipboard.read(selection = :clipboard) ==> aString
86
+ *
87
+ *Reads the value of the specified +clipboard+.
88
+ *===Parameters
89
+ *[+selection+] The selection to read from. One of <tt>:clipboard</tt>, <tt>:primary</tt> and <tt>:secondary</tt>.
90
+ *===Return value
91
+ *A string containing the selection's content or an empty string if there is no data in the selection.
92
+ *===Raises
93
+ *[ArgumentError] Incorrect +selection+ argument given.
94
+ *[XError] Could not retrieve the selection data for some reason. It's likely that there's data in the selection, but it isn't textual data; such as an image, for example.
95
+ *===Example
96
+ * puts Imitator::X::Clipboard.read(:clipboard) #=> I love Ruby!
97
+ */
98
+ static VALUE m_read(int argc, VALUE argv[], VALUE self)
99
+ {
100
+ Display * p_display;
101
+ Atom clipboard, actual_type;
102
+ Window win;
103
+ XEvent xevt;
104
+ int actual_format;
105
+ unsigned long nitems, bytes_after_return;
106
+ unsigned char * property;
107
+ char * cp;
108
+ VALUE rclipboard, result;
109
+
110
+ rb_scan_args(argc, argv, "01", &rclipboard);
111
+
112
+ if (NIL_P(rclipboard))
113
+ rclipboard = ID2SYM(rb_intern("clipboard"));
114
+
115
+ p_display = XOpenDisplay(NULL);
116
+ XSetErrorHandler(handle_x_errors);
117
+
118
+ rclipboard = rb_funcall(rclipboard, rb_intern("to_s"), 0); /*Symbol -> String*/
119
+ rclipboard = rb_funcall(rclipboard, rb_intern("upcase"), 0); /*String -> STRING*/
120
+ clipboard = XInternAtom(p_display, StringValuePtr(rclipboard), True); /*STRING -> Atom(STRING)*/
121
+ if (clipboard == None)
122
+ rb_raise(rb_eArgError, "Invalid clipboard specified!");
123
+
124
+ /*Check wheather there is a clipboard owner, and if not, just return an empty string. */
125
+ if (XGetSelectionOwner(p_display, clipboard) == None)
126
+ return rb_str_new2("");
127
+
128
+ /*Create a window for selection interaction*/
129
+ win = CREATE_REQUESTOR_WIN;
130
+
131
+ /*Order the selection content from the current selection owner*/
132
+ XConvertSelection(p_display, clipboard, UTF8_ATOM, IMITATOR_X_CLIP_ATOM, win, CurrentTime);
133
+ /*Wait until our requestor window gets the message that it has received the selection content*/
134
+ for (;;)
135
+ {
136
+ XNextEvent(p_display, &xevt);
137
+ if (xevt.type == SelectionNotify)
138
+ break;
139
+ }
140
+ if (xevt.xselection.property == None)
141
+ rb_raise(XError, "Could not retrieve selection (XConvertSelection() failed)! Is there non-text data in the clipboard?");
142
+
143
+ /*Read the selection property of our requestor window, just in order to get the selection content's size. We don't read the actual data here. */
144
+ XGetWindowProperty(xevt.xselection.display, xevt.xselection.requestor, xevt.xselection.property, 0, 0, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after_return, &property);
145
+ cp = (char *) malloc(bytes_after_return); /*Nice - we already get the number of bytes to allocate from X*/
146
+ /*This is the exciting moment. Reads the data from our requestor window. */
147
+ XGetWindowProperty(xevt.xselection.display, xevt.xselection.requestor, xevt.xselection.property, 0, bytes_after_return, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after_return, &property);
148
+ /*Now copy it away from X, since X wants to XFree() it's data*/
149
+ strcpy(cp, (char *) property);
150
+ /*Convert it to a Ruby UTF-8 string*/
151
+ result = rb_enc_str_new(cp, strlen(cp), rb_utf8_encoding());
152
+
153
+ /*Clean-up actions*/
154
+ XFree(property);
155
+ free(cp);
156
+ XDestroyWindow(p_display, win); /*We don't need the window anymore, a new request will create a new window*/
157
+ XSetErrorHandler(NULL);
158
+ XCloseDisplay(p_display);
159
+
160
+ return result;
161
+ }
162
+
163
+ /*
164
+ *call-seq:
165
+ * Clipboard.write( text ) ==> text
166
+ *
167
+ *Writes +text+ to the CLIPBOARD selection.
168
+ *===Parameters
169
+ *[+text+] The text to store.
170
+ *===Return value
171
+ *The +text+ argument.
172
+ *===Raises
173
+ *[XError] No clipboard manager available, or it doesn't signal it's existance via CLIPBOARD_MANAGER. Also indicates failure in acquiring ownership of the CLIPBOARD selection or failure in setting the clipboard manager as it's owner.
174
+ *===Example
175
+ * Imitator::X::Clipboard.write("I love Ruby")
176
+ * puts Imitator::X::Clipboard.read #=> I love Ruby
177
+ *===Remarks
178
+ *This method doesn't work well in all cases. It works on my Ubuntu Karmic machine, but didn't work
179
+ *with Xubuntu and openSUSE when I tested it in VirtualBox. Therefore, don't rely on this method.
180
+ */
181
+ static VALUE m_write(VALUE self, VALUE rtext)
182
+ {
183
+ Display * p_display;
184
+ Window win, clipboard_owner;
185
+ XEvent xevt, xevt2;
186
+ Atom targets[6], target_sizes[12], save_targets[2];
187
+ VALUE rtext_utf8, rtext_iso_latin1;
188
+ int utf8_len, iso_latin1_len;
189
+
190
+ /*Get neccessary information*/
191
+ rtext_utf8 = rb_str_export_to_enc(rtext, rb_utf8_encoding());
192
+ rtext_iso_latin1 = rb_str_export_to_enc(rtext, rb_enc_find("ISO-8859-1"));
193
+ /*rtext_utf8.bytes.to_a.length - otherwise we lose data due to multibyte characters*/
194
+ utf8_len = NUM2INT(rb_funcall(rb_funcall(rb_funcall(rtext_utf8, rb_intern("bytes"), 0), rb_intern("to_a"), 0), rb_intern("length"), 0));
195
+ iso_latin1_len = NUM2INT(rb_funcall(rtext_iso_latin1, rb_intern("length"), 0));
196
+
197
+ /*Open default display*/
198
+ p_display = XOpenDisplay(NULL);
199
+ /*Let Ruby handle protocol errors*/
200
+ XSetErrorHandler(handle_x_errors);
201
+ /*This are the TARGETS we support*/
202
+ targets[0] = TARGETS_ATOM;
203
+ targets[1] = UTF8_ATOM;
204
+ targets[2] = XA_STRING;
205
+ targets[3] = XInternAtom(p_display, "TIMESTAMP", True); /*TODO: Implement this request*/
206
+ targets[4] = SAVE_TARGETS_ATOM;
207
+ targets[5] = TARGET_SIZES_ATOM;
208
+ /*These are the target's sizes*/
209
+ target_sizes[0] = TARGETS_ATOM;
210
+ target_sizes[1] = 6 * sizeof(Atom);
211
+
212
+ target_sizes[2] = UTF8_ATOM;
213
+ target_sizes[3] = utf8_len;
214
+
215
+ target_sizes[4] = XA_STRING;
216
+ target_sizes[5] = iso_latin1_len;
217
+
218
+ target_sizes[6] = XInternAtom(p_display, "TIMESTAMP", True);
219
+ target_sizes[7] = -1;
220
+
221
+ target_sizes[8] = SAVE_TARGETS_ATOM;
222
+ target_sizes[9] = -1;
223
+
224
+ target_sizes[10] = TARGET_SIZES_ATOM;
225
+ target_sizes[11] = 12 * sizeof(Atom);
226
+ /*We want our data have stored as UTF-8*/
227
+ save_targets[0] = UTF8_ATOM;
228
+ save_targets[1] = XA_STRING;
229
+ if (CLIPBOARD_MANAGER_ATOM == None)
230
+ {
231
+ XSetErrorHandler(NULL);
232
+ XCloseDisplay(p_display);
233
+ rb_raise(XError, "No clipboard manager available!");
234
+ }
235
+
236
+ /*Create a window for copying into CLIPBOARD*/
237
+ win = CREATE_REQUESTOR_WIN;
238
+
239
+ /*Make sure that there's a clipboard manager which can process our request*/
240
+ if ( (clipboard_owner = XGetSelectionOwner(p_display, CLIPBOARD_MANAGER_ATOM)) == None)
241
+ {
242
+ XDestroyWindow(p_display, win);
243
+ XSetErrorHandler(NULL);
244
+ XCloseDisplay(p_display);
245
+ rb_raise(XError, "No owner for the CLIPBOARD_MANAGER selection!");
246
+ }
247
+
248
+ /*Get control of the CLIPBOARD*/
249
+ XSetSelectionOwner(p_display, CLIPBOARD_ATOM, win, CurrentTime);
250
+ if (XGetSelectionOwner(p_display, CLIPBOARD_ATOM) != win)
251
+ {
252
+ XDestroyWindow(p_display, win);
253
+ XSetErrorHandler(NULL);
254
+ XCloseDisplay(p_display);
255
+ rb_raise(XError, "Could not acquire ownership of the CLIPBOARD selection!");
256
+ }
257
+
258
+ /*Our application "needs to exit"*/
259
+ XChangeProperty(p_display, win, IMITATOR_X_CLIP_ATOM, XA_ATOM, 32, PropModeReplace, (unsigned char *) save_targets, 1);
260
+ XConvertSelection(p_display, CLIPBOARD_MANAGER_ATOM, SAVE_TARGETS_ATOM, IMITATOR_X_CLIP_ATOM, win, CurrentTime);
261
+ for (;;)
262
+ {
263
+ XNextEvent(p_display, &xevt);
264
+ if (xevt.type == SelectionRequest) /*selection-related event*/
265
+ {
266
+ /*This is for all anserwing events the same (except "not supported")*/
267
+ xevt2.xselection.type = SelectionNotify;
268
+ xevt2.xselection.display = xevt.xselectionrequest.display;
269
+ xevt2.xselection.requestor = xevt.xselectionrequest.requestor;
270
+ xevt2.xselection.selection = xevt.xselectionrequest.selection;
271
+ xevt2.xselection.target = xevt.xselectionrequest.target;
272
+ xevt2.xselection.time = xevt.xselectionrequest.time;
273
+ xevt2.xselection.property = xevt.xselectionrequest.property;
274
+
275
+ /*Handle individual selection requests*/
276
+ if (xevt.xselectionrequest.target == XInternAtom(p_display, "TARGETS", True)) /*TARGETS information requested*/
277
+ {
278
+ /*Write supported TARGETS into the requestor*/
279
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, (unsigned char *) targets, 6);
280
+ /*Notify the requestor we have set its requested property*/
281
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
282
+ }
283
+ else if (xevt.xselectionrequest.target == TARGET_SIZES_ATOM)
284
+ {
285
+ /*Answer how much data we want to store*/
286
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, ATOM_PAIR_ATOM, 32, PropModeReplace, (unsigned char *) target_sizes, 6);
287
+ /*Notify the requestor we have set its requested property*/
288
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
289
+ }
290
+ else if (xevt.xselectionrequest.target == MULTIPLE_ATOM) /*Makes multiple requests at once*/
291
+ { /*I know, allocating variables in inner blocks is bad style, but I didn't want to
292
+ *declare this amount of variables at the function's top, beacuse that would decrease readability*/
293
+ Atom actual_type, requested = None;
294
+ int actual_format, i;
295
+ unsigned long nitems, bytes;
296
+ unsigned char * prop;
297
+ Atom * wanted_atoms;
298
+ /*See how much data is there and allocate this amount*/
299
+ XGetWindowProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, 0, 0, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes, &prop);
300
+ wanted_atoms = (Atom *) malloc(sizeof(Atom) * nitems);
301
+ /*Now get the data and copy it to our variable*/
302
+ XGetWindowProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, 0, 1000000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes, &prop);
303
+ memcpy(wanted_atoms, prop, sizeof(Atom) * nitems);
304
+ /*Now handle each single request by it's own*/
305
+ for(i = 0;i < nitems; i++)
306
+ {
307
+ if (requested == None) /*This means we'll get a target atom*/
308
+ requested = wanted_atoms[i];
309
+ else /*This means we'll get a property atom*/
310
+ {
311
+ /*OK, I see. I could have made the event handling a separate function and then call it recursively here,
312
+ *but since I only support two targets that's unneccessary I think*/
313
+ if (requested == UTF8_ATOM)
314
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, wanted_atoms[i], requested, 8, PropModeReplace, (unsigned char *) StringValuePtr(rtext_utf8), utf8_len);
315
+ else if (requested == XA_STRING)
316
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, wanted_atoms[i], requested, 8, PropModeReplace, (unsigned char *) StringValuePtr(rtext_iso_latin1), iso_latin1_len);
317
+ else /*Not supported request*/
318
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, wanted_atoms[i], requested, 32, PropModeReplace, (unsigned char *) None, 1);
319
+ requested = None; /*The next iteration will be a target atom again*/
320
+ }
321
+ }
322
+ /*Notify the requestor we have finished*/
323
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
324
+ /*Free the allocated memory*/
325
+ free(wanted_atoms);
326
+ XFree(prop);
327
+ }
328
+ //~ else if (xevt.xselectionrequest.target == UTF8_ATOM || xevt.xselectionrequest.target == XA_STRING) /*UTF-8 or ASCII requested*/
329
+ else if (xevt.xselectionrequest.target == UTF8_ATOM)
330
+ {
331
+ /*Write the string into the requestor*/
332
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, xevt.xselectionrequest.target, 8, PropModeReplace, (unsigned char *) StringValuePtr(rtext_utf8), utf8_len);
333
+ /*Notify the requestor we've finished*/
334
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
335
+ }
336
+ else if (xevt.xselectionrequest.target == XA_STRING)
337
+ {
338
+ XChangeProperty(p_display, xevt.xselectionrequest.requestor, xevt.xselectionrequest.property, xevt.xselectionrequest.target, 8, PropModeReplace, (unsigned char *) StringValuePtr(rtext_iso_latin1), iso_latin1_len);
339
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
340
+ }
341
+ else /*No supported request. SAVE_TARGETS is included here, since it's only a marker*/
342
+ {
343
+ /*Notify the requestor we don't support what it wants*/
344
+ xevt2.xselection.target = None; /*This indicates we don't support what it wants*/
345
+ XSendEvent(p_display, xevt.xselectionrequest.requestor, False, NoEventMask, &xevt2);
346
+ }
347
+ }
348
+ else if (xevt.type == SelectionNotify) /*OK, our request to the clipboard manager has completed*/
349
+ {
350
+ if (xevt.xselection.property == None) /*Ooops - conversion failed, we're still the owner of CLIPBOARD*/
351
+ {
352
+ XDestroyWindow(p_display, win);
353
+ XSetErrorHandler(NULL);
354
+ XCloseDisplay(p_display);
355
+ rb_raise(XError, "Unable to request the clipboard manager to acquire the CLIPBOARD selection!");
356
+ }
357
+ else if (xevt.xselection.property == IMITATOR_X_CLIP_ATOM) /*Success - we're out of responsibility now and can safely exit*/
358
+ break;
359
+ }
360
+ }
361
+
362
+ /*Cleanup actions*/
363
+ XDestroyWindow(p_display, win);
364
+ XSetErrorHandler(NULL);
365
+ XCloseDisplay(p_display);
366
+
367
+ return rtext;
368
+ }
369
+
370
+ /*
371
+ *call-seq:
372
+ * Clipboard.clear( *selections = :clipboard ) ==> nil
373
+ *
374
+ *Clears the specified selection(s).
375
+ *===Parameters
376
+ *[<tt>*selections</tt>] (<tt>:clipboard</tt>) Specifies one or more of <tt>:primary</tt>, <tt>:secondary</tt> and/or <tt>:clipboard</tt>.
377
+ *===Return value
378
+ *nil.
379
+ *===Raises
380
+ *[XError] Invalid selection specified.
381
+ *===Example
382
+ * #Clear the CLIPBOARD's contents
383
+ * Imitator::X::Clipboard.clear
384
+ * #Clear PRIMARY
385
+ * Imitator::X::Clipboard.clear(:primary)
386
+ * #Clear PRIMARY and CLIPBOARD
387
+ * Imitator::X::Clipboard.clear(:primary, :clipboard)
388
+ */
389
+ VALUE m_clear(VALUE self, VALUE args)
390
+ {
391
+ VALUE rtemp;
392
+ Display * p_display;
393
+ int length, i;
394
+ Atom selection;
395
+
396
+ if (RTEST(rb_funcall(args, rb_intern("empty?"), 0)))
397
+ rb_ary_push(args, ID2SYM(rb_intern("clipboard")));
398
+
399
+ p_display = XOpenDisplay(NULL);
400
+ XSetErrorHandler(handle_x_errors);
401
+
402
+ length = NUM2INT(rb_funcall(args, rb_intern("length"), 0));
403
+ for(i = 0; i < length; i++)
404
+ {
405
+ rtemp = rb_funcall(rb_ary_entry(args, i), rb_intern("upcase"), 0);
406
+ selection = XInternAtom(p_display, StringValuePtr(rtemp), True);
407
+ if (selection == None)
408
+ {
409
+ XSetErrorHandler(NULL);
410
+ XCloseDisplay(p_display);
411
+ rb_raise(XError, "Invalid selection specified!");
412
+ }
413
+ XSetSelectionOwner(p_display, selection, None, CurrentTime);
414
+ }
415
+
416
+ XSetErrorHandler(NULL);
417
+ XCloseDisplay(p_display);
418
+ return Qnil;
419
+ }
420
+
421
+ /********************Init function**********************/
422
+
423
+ void Init_clipboard(void)
424
+ {
425
+ Clipboard = rb_define_module_under(X, "Clipboard");
426
+ rb_define_module_function(Clipboard, "read", m_read, -1);
427
+ rb_define_module_function(Clipboard, "write", m_write, 1);
428
+ rb_define_module_function(Clipboard, "clear", m_clear, -2);
429
+ }
data/ext/clipboard.h ADDED
@@ -0,0 +1,61 @@
1
+ /*********************************************************************************
2
+ Imitator for X is a library allowing you to fake input to systems using X11.
3
+ Copyright � 2010 Marvin G�lker
4
+
5
+ This file is part of Imitator for X.
6
+
7
+ Imitator for X is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Imitator for X is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with Imitator for X. If not, see <http://www.gnu.org/licenses/>.
19
+ *********************************************************************************/
20
+ #ifndef IMITATOR_CLIPBOARD_HEADER
21
+ #define IMITATOR_CLIPBOARD_HEADER
22
+
23
+ /*All the macros defined here assume that you have an open X server connection stored
24
+ *in a variable p_display. */
25
+
26
+ /*UTF-8 X-Encoding atom*/
27
+ #define UTF8_ATOM XInternAtom(p_display, "UTF8_STRING", True)
28
+
29
+ /*Atom for the CLIPBOARD selection*/
30
+ #define CLIPBOARD_ATOM XInternAtom(p_display, "CLIPBOARD", True)
31
+
32
+ /*CLIPBOARD_MANAGER atom*/
33
+ #define CLIPBOARD_MANAGER_ATOM XInternAtom(p_display, "CLIPBOARD_MANAGER", True)
34
+
35
+ /*ATOM_PAIR atom*/
36
+ #define ATOM_PAIR_ATOM XInternAtom(p_display, "ATOM_PAIR", True)
37
+
38
+ /*SAVE_TARGETS atom*/
39
+ #define SAVE_TARGETS_ATOM XInternAtom(p_display, "SAVE_TARGETS", True)
40
+
41
+ /*TARGET_SIZES atom*/
42
+ #define TARGET_SIZES_ATOM XInternAtom(p_display, "TARGET_SIZES", True)
43
+
44
+ /*TARGETS atom*/
45
+ #define TARGETS_ATOM XInternAtom(p_display, "TARGETS", True)
46
+
47
+ /*MULTIPLE request atom*/
48
+ #define MULTIPLE_ATOM XInternAtom(p_display, "MULTIPLE", True)
49
+
50
+ /*Atom for storing properties used by this library*/
51
+ #define IMITATOR_X_CLIP_ATOM XInternAtom(p_display, "IMITATOR_X_CLIP", False)
52
+
53
+ /*In order to work with the X selections, we need a window.
54
+ *This macro just creates a simple, unmapped window that is used for
55
+ *the X selection interaction*/
56
+ #define CREATE_REQUESTOR_WIN XCreateSimpleWindow(p_display, XDefaultRootWindow(p_display), 0, 0, 1, 1, 0, 0, 0)
57
+
58
+ VALUE Clipboard;
59
+ void Init_clipboard(void);
60
+
61
+ #endif