imitator_x 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|