imitator_x 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING.LESSER.rdoc +169 -0
- data/COPYING.rdoc +678 -0
- data/README.rdoc +70 -0
- data/Rakefile.rb +99 -0
- data/TODO.rdoc +8 -0
- data/ext/clipboard.c +429 -0
- data/ext/clipboard.h +61 -0
- data/ext/extconf.rb +55 -0
- data/ext/keyboard.c +711 -0
- data/ext/keyboard.h +29 -0
- data/ext/mouse.c +456 -0
- data/ext/mouse.h +28 -0
- data/ext/x.c +84 -0
- data/ext/x.h +48 -0
- data/ext/xwindow.c +1434 -0
- data/ext/xwindow.h +38 -0
- data/lib/imitator/x/drive.rb +179 -0
- data/lib/imitator/x.rb +29 -0
- data/lib/imitator_x_special_chars.yml +179 -0
- data/test/test_clipboard.rb +46 -0
- data/test/test_keyboard.rb +117 -0
- data/test/test_mouse.rb +40 -0
- data/test/test_xdrive.rb +47 -0
- data/test/test_xwindow.rb +106 -0
- metadata +123 -0
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
|