Merge lp:~unity-2d-team/unity-2d/testability-integration into lp:unity-2d
- testability-integration
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tiago Salem Herrmann |
Approved revision: | 845 |
Merged at revision: | 831 |
Proposed branch: | lp:~unity-2d-team/unity-2d/testability-integration |
Merge into: | lp:unity-2d |
Diff against target: |
3559 lines (+3248/-6) 30 files modified
CMakeLists.txt (+1/-0) launcher/Launcher.qml (+2/-2) launcher/LauncherItem.qml (+7/-0) launcher/LauncherList.qml (+2/-0) launcher/tests/CMakeLists.txt (+1/-1) libunity-2d-private/src/testabilityinterface.h (+37/-0) libunity-2d-private/src/unity2dapplication.cpp (+42/-0) libunity-2d-private/src/unity2dapplication.h (+4/-0) libunity-2d-private/tests/CMakeLists.txt (+2/-2) panel/tests/CMakeLists.txt (+1/-1) tests/README (+177/-0) tests/launcher/autohide_show_tests.rb (+193/-0) tests/launcher/visual_verification.rb (+86/-0) tests/manual-tests/launcher.txt (+104/-0) tests/manual-tests/places.txt (+13/-0) tests/manual-tests/window-manager.txt (+9/-0) tests/misc/binary_dir.txt.in (+1/-0) tests/misc/lib/testhelper.rb (+162/-0) tests/misc/lib/xdo/README.xdo.rdoc (+52/-0) tests/misc/lib/xdo/_xdo.rb (+35/-0) tests/misc/lib/xdo/clipboard.rb (+209/-0) tests/misc/lib/xdo/keyboard.rb (+387/-0) tests/misc/lib/xdo/mouse.rb (+254/-0) tests/misc/lib/xdo/simulatable.rb (+91/-0) tests/misc/lib/xdo/test/test_clipboard.rb (+49/-0) tests/misc/lib/xdo/test/test_keyboard.rb (+114/-0) tests/misc/lib/xdo/test/test_mouse.rb (+29/-0) tests/misc/lib/xdo/test/test_xwindow.rb (+101/-0) tests/misc/lib/xdo/xwindow.rb (+1009/-0) tests/run-tests.rb (+74/-0) |
To merge this branch: | bzr merge lp:~unity-2d-team/unity-2d/testability-integration |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tiago Salem Herrmann (community) | Approve | ||
Review via email: mp+85417@code.launchpad.net |
Commit message
Description of the change
[tests] Add Automated User Experience testing using "Testability"
Add support for Testability, a Qt application automated testing framework, with some boilerplate code to allow easy writing of tests using Ruby's Test::Util library. Also added is a Ruby library called XDo which allows control of the X server and fake mouse, keyboard and window management.
A couple of sample tests for the launcher have been written. This required the addition of some objectName definitions to the launcher.
Gerry Boland (gerboland) wrote : | # |
Gerry Boland (gerboland) wrote : | # |
Why Xdo?
- Best Ruby library I can find to control X. Emulates mouse & keyboard inputs, has basic clipboard support, and excellent abilities to control the window manager.
- Makes use of existing X test-applications (xdotool, xsel..)
- Easily extendible to support host & system-under-test separation (using @sut.execute_
Why a copy in the tree?
- Rubygems are developer packages, we cannot depend on their stability
- As above, want to modify the library for our own needs. Good to push any fixes to original project however.
- 838. By Gerry Boland
-
Fix required packages and command in README
- 839. By Gerry Boland
-
Improve window ID detection. Passing gnome-terminal a (temporary) unique
directory to work from, which sets the window title to contain a known
string we can then reliably search for. - 840. By Gerry Boland
-
Rebase with trunk
- 841. By Gerry Boland
-
Launcher is 65 pixels wide, not 66. Update test cases
- 842. By Gerry Boland
-
Open gnome-terminal in background for tests.
If no instance of gnome-terminal was running, the system call would block and hang the tests.
- 843. By Gerry Boland
-
Ensure binart_dir.txt generated in source directory
- 844. By Gerry Boland
-
Use WIDTH value in test comment
- 845. By Gerry Boland
-
Additional checks to ensure Launcher ready to be tested
Tiago Salem Herrmann (tiagosh) wrote : | # |
Just tested in my environment and everything is working fine.
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2011-12-09 13:09:46 +0000 |
3 | +++ CMakeLists.txt 2011-12-15 16:55:31 +0000 |
4 | @@ -89,6 +89,7 @@ |
5 | # Tests |
6 | enable_testing() |
7 | add_custom_target(check make test) |
8 | +configure_file(tests/misc/binary_dir.txt.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/misc/binary_dir.txt @ONLY) |
9 | |
10 | # Source |
11 | add_subdirectory(libunity-2d-private) |
12 | |
13 | === modified file 'launcher/Launcher.qml' |
14 | --- launcher/Launcher.qml 2011-12-08 14:13:21 +0000 |
15 | +++ launcher/Launcher.qml 2011-12-15 16:55:31 +0000 |
16 | @@ -91,7 +91,7 @@ |
17 | |
18 | LauncherList { |
19 | id: main |
20 | - Accessible.name: "main" |
21 | + objectName: "main" |
22 | |
23 | /* function to position highlighted tile so that the shadow does not cover it */ |
24 | function positionMainViewForIndex(index) { |
25 | @@ -153,7 +153,7 @@ |
26 | |
27 | LauncherList { |
28 | id: shelf |
29 | - Accessible.name: "shelf" |
30 | + objectName: "shelf" |
31 | |
32 | anchors.bottom: parent.bottom |
33 | anchors.bottomMargin: main.anchors.bottomMargin |
34 | |
35 | === modified file 'launcher/LauncherItem.qml' |
36 | --- launcher/LauncherItem.qml 2011-11-28 19:40:53 +0000 |
37 | +++ launcher/LauncherItem.qml 2011-12-15 16:55:31 +0000 |
38 | @@ -56,6 +56,7 @@ |
39 | |
40 | property int tileSize |
41 | property int selectionOutlineSize |
42 | + property alias name: looseItem.objectName |
43 | property string desktopFile: "" |
44 | property alias icon: icon.source |
45 | property alias urgentAnimation: urgentAnimation |
46 | @@ -131,6 +132,7 @@ |
47 | /* This is the arrow shown at the right of the tile when the application is |
48 | the active one */ |
49 | Image { |
50 | + objectName: "active" |
51 | anchors.right: parent.right |
52 | y: item.height - item.selectionOutlineSize / 2 - height / 2 |
53 | mirror: isRightToLeft() |
54 | @@ -152,6 +154,7 @@ |
55 | Repeater { |
56 | model: item.pips |
57 | delegate: Image { |
58 | + objectName: "pips" |
59 | /* FIXME: It seems that when the image is created (or re-used) by the Repeater |
60 | for a moment it doesn't have any parent, and therefore warnings are |
61 | printed for the following two anchor assignements. This fixes the |
62 | @@ -191,6 +194,7 @@ |
63 | While the application is launching, this will fade out and in. */ |
64 | Image { |
65 | id: tileBackground |
66 | + objectName: "tileBackground" |
67 | property color color: defaultBackgroundColor |
68 | anchors.fill: parent |
69 | smooth: true |
70 | @@ -241,6 +245,7 @@ |
71 | /* This is just the main icon of the tile */ |
72 | Image { |
73 | id: icon |
74 | + objectName: "icon" |
75 | anchors.centerIn: parent |
76 | smooth: true |
77 | |
78 | @@ -275,6 +280,7 @@ |
79 | |
80 | Image { |
81 | id: selectionOutline |
82 | + objectName: "selectionOutline" |
83 | anchors.centerIn: parent |
84 | smooth: true |
85 | source: "artwork/round_selected_66x66.png" |
86 | @@ -309,6 +315,7 @@ |
87 | |
88 | Image { |
89 | id: progressBar |
90 | + objectName: "progressBar" |
91 | source: "artwork/progress_bar_trough.png" |
92 | anchors.verticalCenter: parent.verticalCenter |
93 | anchors.left: parent.left |
94 | |
95 | === modified file 'launcher/LauncherList.qml' |
96 | --- launcher/LauncherList.qml 2011-12-11 16:36:28 +0000 |
97 | +++ launcher/LauncherList.qml 2011-12-15 16:55:31 +0000 |
98 | @@ -21,6 +21,7 @@ |
99 | |
100 | AutoScrollingListView { |
101 | id: list |
102 | + Accessible.name: objectName |
103 | |
104 | /* The spacing is explicitly set to -8 in order to compensate |
105 | the space added by selectionOutline and round_corner_54x54.png. */ |
106 | @@ -89,6 +90,7 @@ |
107 | } |
108 | |
109 | Accessible.name: accessibleDescription() |
110 | + name: item.name |
111 | |
112 | width: list.width |
113 | tileSize: list.tileSize |
114 | |
115 | === modified file 'launcher/tests/CMakeLists.txt' |
116 | --- launcher/tests/CMakeLists.txt 2011-11-25 15:38:51 +0000 |
117 | +++ launcher/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000 |
118 | @@ -2,7 +2,7 @@ |
119 | set(_test_list "") |
120 | foreach(_test ${ARGN}) |
121 | add_test(NAME ${_test} |
122 | - COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ./${_test} |
123 | + COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ./${_test} |
124 | ) |
125 | add_executable(${_test} ${_test}.cpp ${_test}.moc) |
126 | qt4_generate_moc(${_test}.cpp ${_test}.moc) |
127 | |
128 | === added file 'libunity-2d-private/src/testabilityinterface.h' |
129 | --- libunity-2d-private/src/testabilityinterface.h 1970-01-01 00:00:00 +0000 |
130 | +++ libunity-2d-private/src/testabilityinterface.h 2011-12-15 16:55:31 +0000 |
131 | @@ -0,0 +1,37 @@ |
132 | +/*************************************************************************** |
133 | +** |
134 | +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
135 | +** All rights reserved. |
136 | +** Contact: Nokia Corporation (testabilitydriver@nokia.com) |
137 | +** |
138 | +** This file is part of TDriver. |
139 | +** |
140 | +** If you have questions regarding the use of this file, please contact |
141 | +** Nokia at testabilitydriver@nokia.com . |
142 | +** |
143 | +** This library is free software; you can redistribute it and/or |
144 | +** modify it under the terms of the GNU Lesser General Public |
145 | +** License version 2.1 as published by the Free Software Foundation |
146 | +** and appearing in the file LICENSE.LGPL included in the packaging |
147 | +** of this file. |
148 | +** |
149 | +****************************************************************************/ |
150 | + |
151 | +#include <QObject> |
152 | +#include <QString> |
153 | + |
154 | +class TestabilityInterface |
155 | +{ |
156 | +public: |
157 | + virtual ~TestabilityInterface() {} |
158 | + |
159 | + /*! |
160 | + Initializes the plugin once loaded. |
161 | + */ |
162 | + virtual void Initialize() = 0; |
163 | + |
164 | +}; |
165 | + |
166 | + Q_DECLARE_INTERFACE(TestabilityInterface, |
167 | + "com.nokia.testability.TestabilityInterface/1.0") |
168 | + |
169 | |
170 | === modified file 'libunity-2d-private/src/unity2dapplication.cpp' |
171 | --- libunity-2d-private/src/unity2dapplication.cpp 2011-10-12 19:36:54 +0000 |
172 | +++ libunity-2d-private/src/unity2dapplication.cpp 2011-12-15 16:55:31 +0000 |
173 | @@ -41,6 +41,12 @@ |
174 | #include <gtk/gtk.h> |
175 | #include <pango/pango.h> |
176 | |
177 | +// Testability |
178 | +#include <QtPlugin> |
179 | +#include <QPluginLoader> |
180 | +#include <QLibraryInfo> |
181 | +#include "testabilityinterface.h" |
182 | + |
183 | // libc |
184 | #include <stdlib.h> |
185 | |
186 | @@ -157,6 +163,11 @@ |
187 | : QApplication(argc, argv) |
188 | , m_platformFontTracker(new PlatformFontTracker) |
189 | { |
190 | + /* Load Testability Plugin on startup if requested */ |
191 | + if (arrayContains(argv, argv + argc, "-testability")) { |
192 | + loadTestabilityPlugin(); |
193 | + } |
194 | + |
195 | /* Configure translations */ |
196 | Unity2dTr::init("unity-2d", INSTALL_PREFIX "/share/locale"); |
197 | |
198 | @@ -208,4 +219,35 @@ |
199 | return QApplication::x11EventFilter(event); |
200 | } |
201 | |
202 | +/* |
203 | + * Load the Testability Plugin if available |
204 | + * |
205 | + * Testability is a tool required for UI testing. See tests/ directory. |
206 | + */ |
207 | +void Unity2dApplication::loadTestabilityPlugin() |
208 | +{ |
209 | + QString testabilityPlugin = "testability/libtestability"; |
210 | + QString testabilityPluginPostfix = ".so"; |
211 | + |
212 | + testabilityPlugin = QLibraryInfo::location(QLibraryInfo::PluginsPath) |
213 | + + QObject::tr("/") + testabilityPlugin + testabilityPluginPostfix; |
214 | + QPluginLoader loader(testabilityPlugin.toLatin1().data()); |
215 | + |
216 | + QObject *plugin = loader.instance(); |
217 | + if (plugin) { |
218 | + qDebug("Testability plugin loaded successfully!"); |
219 | + testabilityInterface = qobject_cast<TestabilityInterface *>(plugin); |
220 | + |
221 | + if (testabilityInterface) { |
222 | + qDebug("Testability interface obtained!"); |
223 | + testabilityInterface->Initialize(); |
224 | + } else { |
225 | + qDebug("Failed to get testability interface!"); |
226 | + } |
227 | + } else { |
228 | + qDebug("Testability plugin %s load failed with error:%s", |
229 | + testabilityPlugin.toLatin1().data(), loader.errorString().toLatin1().data()); |
230 | + } |
231 | +} |
232 | + |
233 | #include <unity2dapplication.moc> |
234 | |
235 | === modified file 'libunity-2d-private/src/unity2dapplication.h' |
236 | --- libunity-2d-private/src/unity2dapplication.h 2011-07-27 14:07:00 +0000 |
237 | +++ libunity-2d-private/src/unity2dapplication.h 2011-12-15 16:55:31 +0000 |
238 | @@ -29,6 +29,8 @@ |
239 | |
240 | class PlatformFontTracker; |
241 | |
242 | +class TestabilityInterface; |
243 | + |
244 | class AbstractX11EventFilter |
245 | { |
246 | public: |
247 | @@ -66,8 +68,10 @@ |
248 | bool x11EventFilter(XEvent*); |
249 | |
250 | private: |
251 | + void loadTestabilityPlugin(); |
252 | QList<AbstractX11EventFilter*> m_x11EventFilters; |
253 | PlatformFontTracker* m_platformFontTracker; |
254 | + TestabilityInterface* testabilityInterface; |
255 | }; |
256 | |
257 | #endif // UNITY2DAPPLICATION_H |
258 | |
259 | === modified file 'libunity-2d-private/tests/CMakeLists.txt' |
260 | --- libunity-2d-private/tests/CMakeLists.txt 2011-11-28 13:44:43 +0000 |
261 | +++ libunity-2d-private/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000 |
262 | @@ -4,7 +4,7 @@ |
263 | include_directories( |
264 | ${libunity-2d-private_SOURCE_DIR}/src |
265 | ${libunity-2d-private_SOURCE_DIR}/Unity2d |
266 | - ${CMAKE_SOURCE_DIR}/tests |
267 | + ${CMAKE_SOURCE_DIR}/tests/misc |
268 | ${CMAKE_CURRENT_BINARY_DIR} |
269 | ${GLIB_INCLUDE_DIRS} |
270 | ${QT_QTTEST_INCLUDE_DIR} |
271 | @@ -17,7 +17,7 @@ |
272 | set(_test_list "") |
273 | foreach(_test ${ARGN}) |
274 | add_test(NAME ${_test} |
275 | - COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ./${_test} |
276 | + COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ./${_test} |
277 | ) |
278 | add_executable(${_test} ${_test}.cpp ${_test}.moc) |
279 | qt4_generate_moc(${_test}.cpp ${_test}.moc) |
280 | |
281 | === modified file 'panel/tests/CMakeLists.txt' |
282 | --- panel/tests/CMakeLists.txt 2011-11-23 19:57:05 +0000 |
283 | +++ panel/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000 |
284 | @@ -2,7 +2,7 @@ |
285 | set(_test_list "") |
286 | foreach(_test ${ARGN}) |
287 | add_test(NAME ${_test} |
288 | - COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ${_test} |
289 | + COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ${_test} |
290 | ) |
291 | add_executable(${_test} ${_test}.cpp ${_test}.moc) |
292 | qt4_generate_moc(${_test}.cpp ${_test}.moc) |
293 | |
294 | === added file 'tests/README' |
295 | --- tests/README 1970-01-01 00:00:00 +0000 |
296 | +++ tests/README 2011-12-15 16:55:31 +0000 |
297 | @@ -0,0 +1,177 @@ |
298 | +======================= |
299 | +User Acceptance Testing |
300 | +======================= |
301 | + |
302 | +Outline |
303 | +------- |
304 | +Repeatedly and reliably reproducing the inputs a user and comparing the outputs |
305 | +to known good values is an excellent way to maintain the existing functionality |
306 | +of a piece of software. However manually performing these tests is tiresome and |
307 | +prone to error. |
308 | + |
309 | +Automation is the key! As we use Qt, we are spoiled with a tool called |
310 | +"Testability" which is designed to automate this testing process. We make use of |
311 | +this tool for testing Unity 2D. |
312 | + |
313 | + |
314 | +Testability Introduction |
315 | +------------------------ |
316 | +Testability is a UX testing framework, whose core feature for us is that it |
317 | +allows inspection of the tree of QObjects in a Qt application to read properties |
318 | +(write to some) and call signals, as well as faking mouse/keyboard/gesture |
319 | +inputs, grabbing visual outputs, and measuring both CPU and graphical |
320 | +performance. |
321 | + |
322 | +Testability is comprised of the following parts: |
323 | + |
324 | +On the System Under Test (SUT) |
325 | +- A plugin that the Qt-based software to be tested loads - usually with the |
326 | + “-testability” switch. |
327 | +- A server app that allows inspection and interaction with the SUT [qttasserver] |
328 | + |
329 | +On the Host machine (can be same as the SUT) |
330 | +- Ruby libraries to act as client for qttasserver, so that testing is scriptable |
331 | + [testability-driver & testability-driver-qt-sut-plugin] |
332 | +- GUI to allow easy inspection of SUT and writing tests [tdriver-visualizer], |
333 | + but not needed for running tests. |
334 | + |
335 | +For the moment, the system is designed assuming that the SUT is also the Host |
336 | +machine. |
337 | + |
338 | + |
339 | +Testability installation instructions |
340 | +------------------------------------- |
341 | +Add this repo to your apt sources: |
342 | + |
343 | +$ sudo add-apt-repository ppa:gerboland/testability |
344 | +$ sudo apt-get update |
345 | + |
346 | +Testability is comprised of the following parts: |
347 | +- testability-qttas |
348 | + The server binary and plugin library for the SUT. |
349 | +- ruby-testability-driver |
350 | + Basic client interface for qttasserver |
351 | +- ruby-testability-driver-qt-sut-plugin |
352 | + [Depends on ruby-testability-driver] |
353 | + Plugin for testability-driver to add QObject inspection support |
354 | +- testability-visualizer |
355 | + User interface - not needed for testing |
356 | + |
357 | + |
358 | +To get started, install all these and additional dependencies with: |
359 | + |
360 | +$ sudo apt-get install rubygems testability-qttas \ |
361 | + ruby-testability-driver-qt-sut-plugin testability-visualizer \ |
362 | + librmagick-ruby1.8 xdotool xsel x11-utils |
363 | + |
364 | +You need to configure Testability for your usage. Assuming you’ll be testing on |
365 | +your host machine, this involves editing /etc/tdriver/tdriver_parameters.xml to |
366 | +just contain: |
367 | + |
368 | +<parameters> |
369 | + <sut id="sut_qt" template="qt"> |
370 | + <!-- use default values --> |
371 | + <parameter name="qttas_server_ip" value="127.0.0.1" /> |
372 | + </sut> |
373 | + |
374 | + <!-- overload default behaviours parameter (see generic.xml in defaults folder) --> |
375 | + <parameter name="behaviours" value="behaviours.xml" /> |
376 | +</parameters> |
377 | + |
378 | +Also to get log output from these processes, you should create the directory |
379 | +/logs/testability [FIXME] |
380 | + |
381 | + |
382 | +Running Tests |
383 | +------------- |
384 | +Before any test are run, you must start the qttasserver on the SUT: |
385 | +$ qttasserver& |
386 | + |
387 | +To run tests on this source tree, make sure you run cmake & compile. Otherwise |
388 | +tests will be run on the installed applications. |
389 | + |
390 | +You run the entire Unity 2D test suite by executing the run-tests.rb script: |
391 | +$ cd tests |
392 | +$ ruby run-tests.rb |
393 | + |
394 | +This script enters specified subdirectories and runs the tests contained in any |
395 | +ruby script with particular format. |
396 | + |
397 | +You can run individual test suites (one per file) by entering the directory and |
398 | +calling: |
399 | +$ cd launcher |
400 | +$ ruby autohide_show_tests.rb |
401 | + |
402 | +Single test cases ("Position with Empty Desktop") can be run with |
403 | +$ ruby autohide_shot_tests.rb --name "test_Position_with_Empty_Desktop" |
404 | + |
405 | + |
406 | +Test Suite Syntax |
407 | +----------------- |
408 | +Please take a look at this example: |
409 | + |
410 | + |
411 | + |
412 | +require '../run-tests.rb' unless $INIT_COMPLETED #include necessary libs, etc |
413 | + |
414 | +################################# Test Suite ################################### |
415 | +context "Test Suite summary" do |
416 | + # Run once at the beginning of this test suite |
417 | + startup do |
418 | + end |
419 | + |
420 | + # Run once at the end of this test suite |
421 | + shutdown do |
422 | + end |
423 | + |
424 | + # Run before each test case begins |
425 | + setup do |
426 | + # Execute the application |
427 | + @sut = TDriver.sut(:Id => "sut_qt") |
428 | + @app = @sut.run( :name => "/absolute/path/to/application", |
429 | + :arguments => "-testability", |
430 | + :sleeptime => 2 ) |
431 | + end |
432 | + |
433 | + # Run after each test case completes |
434 | + teardown do |
435 | + #@app.close |
436 | + #Need to kill Launcher as it does not shutdown when politely asked |
437 | + system "pkill -nf unity-2d-launcher" |
438 | + end |
439 | + |
440 | + ############################################################################## |
441 | + # Test cases |
442 | + |
443 | + test "Short description of first test case" do |
444 | + assert_equal( Integer(@app.Unity2dPanel()['width']), 66, |
445 | + 'These two values are not equal' ) |
446 | + |
447 | + assert( @app.Unity2dPanel()['x_absolute'], \ |
448 | + 'This quantity is not true' ) |
449 | + end |
450 | + |
451 | + test "Another test case description" do |
452 | + end |
453 | +end |
454 | + |
455 | + |
456 | +Using Testability Visualizer |
457 | +---------------------------- |
458 | +First off, ensure you're running the "qttasserver" |
459 | + |
460 | +Run |
461 | +$ tdriver_visualizer |
462 | + |
463 | +In the menu bar, open “Applications” and select “Start New Application” and |
464 | +enter the absolute path to the Qt binary you want to inspect. The application |
465 | +will appear, but also will the Visualizer, with a preview of the application in |
466 | +the left pane. In the center is a tree of QObjects in this application, and on |
467 | +the right you can inspect the properties, methods and signals of each QObject as |
468 | +you drill into the tree. |
469 | + |
470 | +Note you should go to “View” -> “Docks and Toolbars” -> “Code Editor” to get a |
471 | +text editor. See that you can right-click QObjects and you get the option to |
472 | +paste a QObject path from the root - saves time! |
473 | + |
474 | +Hit F9 to execute the test. |
475 | |
476 | === added directory 'tests/launcher' |
477 | === added file 'tests/launcher/autohide_show_tests.rb' |
478 | --- tests/launcher/autohide_show_tests.rb 1970-01-01 00:00:00 +0000 |
479 | +++ tests/launcher/autohide_show_tests.rb 2011-12-15 16:55:31 +0000 |
480 | @@ -0,0 +1,193 @@ |
481 | +#!/usr/bin/env ruby1.8 |
482 | +=begin |
483 | +/* |
484 | + * This file is part of unity-2d |
485 | + * |
486 | + * Copyright 2011 Canonical Ltd. |
487 | + * |
488 | + * Authors: |
489 | + * - Gerry Boland <gerry.boland@canonical.com> |
490 | + * |
491 | + * This program is free software; you can redistribute it and/or modify |
492 | + * it under the terms of the GNU General Public License as published by |
493 | + * the Free Software Foundation; version 3. |
494 | + * |
495 | + * This program is distributed in the hope that it will be useful, |
496 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
497 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
498 | + * GNU General Public License for more details. |
499 | + * |
500 | + * You should have received a copy of the GNU General Public License |
501 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
502 | + */ |
503 | +=end |
504 | + |
505 | +require '../run-tests.rb' unless $INIT_COMPLETED |
506 | +require 'xdo/xwindow' |
507 | +require 'xdo/keyboard' |
508 | +require 'xdo/mouse' |
509 | +require 'timeout' |
510 | +require 'tmpdir' |
511 | + |
512 | +# Helper function to open window at certain position |
513 | +def open_window_at(x,y) |
514 | + # Open Terminal with position (x,y) |
515 | + Dir.mktmpdir {|dir| # use this to generate unique window title to help Xdo get window ID |
516 | + system "gnome-terminal --geometry=100x30+#{x}+#{y} --working-directory=#{dir} &" |
517 | + Timeout.timeout(3){XDo::XWindow.wait_for_window(dir)} |
518 | + } |
519 | + return XDo::XWindow.from_active |
520 | +end |
521 | + |
522 | +############################# Test Suite ############################# |
523 | +context "Launcher Autohide and Show Tests" do |
524 | + WIDTH = 65 #launcher bar width |
525 | + |
526 | + # Run once at the beginning of this test suite |
527 | + startup do |
528 | + system 'killall unity-2d-launcher > /dev/null 2>&1' |
529 | + system 'killall unity-2d-launcher > /dev/null 2>&1' |
530 | + |
531 | + # Minimize all windows |
532 | + XDo::XWindow.toggle_minimize_all |
533 | + end |
534 | + |
535 | + # Run once at the end of this test suite |
536 | + shutdown do |
537 | + end |
538 | + |
539 | + # Run before each test case begins |
540 | + setup do |
541 | + #Ensure mouse out of the way |
542 | + XDo::Mouse.move(200,200,10,true) |
543 | + |
544 | + # Execute the application |
545 | + @sut = TDriver.sut(:Id => "sut_qt") |
546 | + @app = @sut.run( :name => UNITY_2D_LAUNCHER, |
547 | + :arguments => "-testability", |
548 | + :sleeptime => 2 ) |
549 | + # Make certain application is ready for testing |
550 | + verify(10){ @app.Unity2dPanel() } |
551 | + end |
552 | + |
553 | + # Run after each test case completes |
554 | + teardown do |
555 | + #@app.close |
556 | + #Need to kill Launcher as it does not shutdown when politely asked |
557 | + system "pkill -nf unity-2d-launcher" |
558 | + end |
559 | + |
560 | + ##################################################################################### |
561 | + # Test cases |
562 | + |
563 | + test "Position with Empty Desktop" do |
564 | + # check width before proceeding |
565 | + assert_equal( Integer(@app.Unity2dPanel()['width']), WIDTH, |
566 | + "Launcher is not #{WIDTH} pixels wide on screen!" ) |
567 | + |
568 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
569 | + 'Launcher hiding on empty desktop, should be visible' ) |
570 | + end |
571 | + |
572 | + test "Position with Window not in the way" do |
573 | + # Open Terminal with position 100x100 |
574 | + xid = open_window_at(100,100) |
575 | + sleep 0.5 |
576 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
577 | + 'Launcher hiding when window not in the way, should be visible' ) |
578 | + xid.close! |
579 | + end |
580 | + |
581 | + test "Position with Window in the way" do |
582 | + # Open Terminal with position 40x100 |
583 | + xid = open_window_at(40,100) |
584 | + sleep 0.5 |
585 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
586 | + 'Launcher visible when window in the way, should be hidden' ) |
587 | + xid.close! |
588 | + end |
589 | + |
590 | + test "Move window positioning to check launcher action" do |
591 | + # Open Terminal with position 100x100 |
592 | + xid = open_window_at(100,100) |
593 | + sleep 0.5 |
594 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
595 | + 'Launcher hiding when window not in the way, should be visible' ) |
596 | + xid.move(WIDTH-1,100) |
597 | + sleep 1 |
598 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
599 | + 'Launcher visible when window in the way, should be hidden' ) |
600 | + xid.move(WIDTH,100) |
601 | + sleep 0.5 |
602 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
603 | + 'Launcher hiding when window not in the way, should be visible' ) |
604 | + xid.close! |
605 | + end |
606 | + |
607 | + test "Reveal hidden Launcher with mouse" do |
608 | + xid = open_window_at(10,100) |
609 | + sleep 0.5 |
610 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
611 | + 'Launcher visible with window in the way, should be hidden' ) |
612 | + XDo::Mouse.move(0,200) |
613 | + sleep 1 |
614 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
615 | + 'Launcher hiding when mouse at left edge of screen' ) |
616 | + XDo::Mouse.move(WIDTH-1,200) |
617 | + sleep 2 |
618 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
619 | + 'Launcher should still be visible as mouse over it' ) |
620 | + XDo::Mouse.move(WIDTH,200) |
621 | + sleep 2 |
622 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
623 | + 'Launcher visible with window in the way and mouse moved out, should be hidden' ) |
624 | + xid.close! |
625 | + end |
626 | + |
627 | + test "Hold Super key down to reveal launcher and shortcut keys" do |
628 | + xid = open_window_at(10,100) |
629 | + sleep 0.5 |
630 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
631 | + 'Launcher visible with window in the way, should be hidden' ) |
632 | + XDo::Keyboard.key_down('SUPER') |
633 | + sleep 2 |
634 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
635 | + 'Launcher hiding when Super Key held, should be visible' ) |
636 | + |
637 | + assert_equal( @app.LauncherList( :name => 'main' ) \ |
638 | + .QDeclarativeItem( :name => 'Home Folder' ) \ |
639 | + .QDeclarativeRectangle() \ |
640 | + .QDeclarativeText()['visible'], 'true', \ |
641 | + 'Shortcut on Home Folder icon not displaying with Super key held' ) |
642 | + XDo::Keyboard.key_up('SUPER') |
643 | + sleep 2 |
644 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
645 | + 'Launcher visible with window in the way and mouse moved out, should be hidden' ) |
646 | + xid.close! |
647 | + end |
648 | + |
649 | +=begin |
650 | + # Test disabled due to bug in Xdo::Keyboard where function keys are not accepted |
651 | + test "Press Alt+F1 to focus Launcher" do |
652 | + xid = open_window_at(10,100) |
653 | + sleep 0.5 |
654 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
655 | + 'Launcher visible with window in the way, should be hidden' ) |
656 | + XDo::Keyboard.alt_f1 |
657 | + sleep 0.5 |
658 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \ |
659 | + 'Launcher hiding after Alt+F1 pressed, should be visible' ) |
660 | + |
661 | + assert_equal( @app.LauncherList( :name => 'main' ) \ |
662 | + .QDeclarativeItem( :name => 'Dash home' ) \ |
663 | + .QDeclarativeImage( :name => 'selectionOutline' )['visible'], 'true', \ |
664 | + 'Dash icon not highlighted after Alt+F1 pressed' ) |
665 | + XDo::Keyboard.esc |
666 | + sleep 2 |
667 | + assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \ |
668 | + 'Launcher visible with window in the way and mouse moved out, should be hidden' ) |
669 | + xid.close! |
670 | + end |
671 | +=end |
672 | + |
673 | +end |
674 | |
675 | === added directory 'tests/launcher/verification' |
676 | === added file 'tests/launcher/verification/dash-tile.png' |
677 | Binary files tests/launcher/verification/dash-tile.png 1970-01-01 00:00:00 +0000 and tests/launcher/verification/dash-tile.png 2011-12-15 16:55:31 +0000 differ |
678 | === added file 'tests/launcher/visual_verification.rb' |
679 | --- tests/launcher/visual_verification.rb 1970-01-01 00:00:00 +0000 |
680 | +++ tests/launcher/visual_verification.rb 2011-12-15 16:55:31 +0000 |
681 | @@ -0,0 +1,86 @@ |
682 | +#!/usr/bin/env ruby1.8 |
683 | +=begin |
684 | +/* |
685 | + * This file is part of unity-2d |
686 | + * |
687 | + * Copyright 2011 Canonical Ltd. |
688 | + * |
689 | + * Authors: |
690 | + * - Gerry Boland <gerry.boland@canonical.com> |
691 | + * |
692 | + * This program is free software; you can redistribute it and/or modify |
693 | + * it under the terms of the GNU General Public License as published by |
694 | + * the Free Software Foundation; version 3. |
695 | + * |
696 | + * This program is distributed in the hope that it will be useful, |
697 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
698 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
699 | + * GNU General Public License for more details. |
700 | + * |
701 | + * You should have received a copy of the GNU General Public License |
702 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
703 | + */ |
704 | +=end |
705 | + |
706 | +require '../run-tests.rb' unless $INIT_COMPLETED |
707 | + |
708 | +############################# Test Suite ############################# |
709 | +context "Launcher Autohide and Show Tests" do |
710 | + pwd = File.expand_path(File.dirname(__FILE__)) + '/' |
711 | + |
712 | + # Run once at the beginning of this test suite |
713 | + startup do |
714 | + system 'killall unity-2d-launcher > /dev/null 2>&1' |
715 | + system 'killall unity-2d-launcher > /dev/null 2>&1' |
716 | + end |
717 | + |
718 | + # Run once at the end of this test suite |
719 | + shutdown do |
720 | + end |
721 | + |
722 | + # Run before each test case begins |
723 | + setup do |
724 | + # Execute the application |
725 | + @sut = TDriver.sut(:Id => "sut_qt") |
726 | + @app = @sut.run( :name => UNITY_2D_LAUNCHER, |
727 | + :arguments => "-testability", |
728 | + :sleeptime => 2 ) |
729 | + end |
730 | + |
731 | + # Run after each test case completes |
732 | + teardown do |
733 | + #@app.close |
734 | + #Need to kill Launcher as it does not shutdown when politely asked |
735 | + system "pkill -nf unity-2d-launcher" |
736 | + end |
737 | + |
738 | + ##################################################################################### |
739 | + # Test cases |
740 | + |
741 | + test "Visually compare Dash tile with reference" do |
742 | + expected_image = pwd + 'verification/dash-tile.png' |
743 | + |
744 | + dash_tile = @app.Unity2dPanel() \ |
745 | + .LauncherList( :name => 'main' ) \ |
746 | + .QDeclarativeItem( :name => 'Dash home' ) \ |
747 | + .QDeclarativeItem() |
748 | + |
749 | + #Check tile visual matches reference image, with some tolerance. |
750 | + assert( dash_tile.find_on_screen(expected_image, 4), \ |
751 | + 'Dash tile not matching reference image' ) |
752 | + end |
753 | + |
754 | + test "Check Dash Tile location in Launcher" do |
755 | + expected_image = pwd + 'verification/dash-tile.png' |
756 | + |
757 | + tile_list = @app.Unity2dPanel().LauncherList( :name => 'main' ) |
758 | + |
759 | + # Given the reference image, locate the matching visual in the LauncherList |
760 | + coordinates = tile_list.find_on_screen(expected_image, 4) |
761 | + assert( coordinates, 'Unable to find visual matching Dash tile reference image on screen' ) |
762 | + |
763 | + # Dash tile should have these coordinates in the Launcher |
764 | + assert_equal( coordinates, [7,7], 'Dash tile not at correct coordinates' ) |
765 | + end |
766 | + |
767 | +end |
768 | |
769 | === added directory 'tests/manual-tests' |
770 | === added file 'tests/manual-tests/launcher.txt' |
771 | --- tests/manual-tests/launcher.txt 1970-01-01 00:00:00 +0000 |
772 | +++ tests/manual-tests/launcher.txt 2011-12-15 16:55:31 +0000 |
773 | @@ -0,0 +1,104 @@ |
774 | +---- |
775 | + * Mouse over a tile in the launcher (which displays tooltip). |
776 | + * Press Super+S |
777 | + |
778 | +-> Tooltip disappears, spread appears (lp:881458) |
779 | + |
780 | +---- |
781 | + * Have Launcher hidden. |
782 | + * Bring mouse over panel |
783 | + * Move it directly to the left, so it stays over panel |
784 | + |
785 | +-> Launcher stays hidden - it should not reveal (lp:891636) |
786 | + |
787 | +---- |
788 | + * RTL: Open Dash |
789 | + * perform a simple search |
790 | + * click on an application icon |
791 | + |
792 | +-> Application launches - should not crash (lp:836498) |
793 | + |
794 | +---- |
795 | + * Press Alt-F1 |
796 | + |
797 | +-> Launcher revealed with Dash button focused - Pressing Alt-F1 again should toggle the focus between launcher and applications (lp:885304) |
798 | + |
799 | +---- |
800 | + * Launcher visible, scrolled down, hit Super |
801 | + |
802 | +-> Launcher reposition to beginning - and its tooltips/menus should hide. (lp:876632) |
803 | + |
804 | +---- |
805 | + * Press Alt-F1, select the trash icon using the keyboard. |
806 | + |
807 | +-> Trash icon gets highlighted - The highlight effect should not be truncated on top and bottom (lp:876589) |
808 | + |
809 | +---- |
810 | + * Launch an application. Open the context menu of the same application tile in launcher. Hit Alt-F4 |
811 | + |
812 | +-> The visible context is updated - closing the application should be notified in launcher menu(lp:784541) |
813 | + |
814 | +---- |
815 | + * Right-click on a non running application tile in the launcher. Click the "Remove from Launcher" option. |
816 | + |
817 | +-> Launcher is still shown for a while - it should not close immediately (lp:884410) |
818 | + |
819 | +---- |
820 | + * Press Right Mouse Button over an application tile in the launcher. Do not release. |
821 | + |
822 | +-> Menu is shown - it should not show on button release (lp:813036) |
823 | + |
824 | +---- |
825 | + * Press Right Mouse Button over an application tile in the launcher, do not release, move mouse up/down |
826 | + |
827 | +-> Launcher does not scroll (lp:813041) |
828 | + |
829 | +---- |
830 | + * Press Left Mouse Button over an application tile in the launcher, do not release |
831 | + * Move mouse up/down, release Left Mouse Button |
832 | + * while the icons are going back to their original position press Right Mouse Button and try to continue the dragging |
833 | + |
834 | +-> Launcher keeps going back to its original position - you should not be able to continue a dragging with Right Mouse Button |
835 | + |
836 | +---- |
837 | + * Have the Launcher hidden. Move the mouse over the panel. Move mouse to the left edge the panel. |
838 | + |
839 | +-> Launcher is not revealed - moving the cursor in the top left corner of the panel shouldn't reveal launcher(lp:891636) |
840 | + |
841 | +---- |
842 | + * Start the launcher with a maximized window under it. |
843 | + * Watch the launcher autohide. |
844 | + * Alt+Tab to another maximized window. |
845 | + * Move the mouse to the leftmost part of the screen. |
846 | + * Watch launcher show. |
847 | + * Keeping the mouse at the leftmost part of the screen move to the top panel. |
848 | + * Wait one second |
849 | + |
850 | +-> Launcher is hidden - The launcher should not still be visible when you have the mouse on the top panel (lp:892004) |
851 | + |
852 | +---- |
853 | + * Open an application & maximize the same |
854 | + * Hit show-desktop shortcut [Ctrl+Alt+D?] key to see the Desktop |
855 | + |
856 | +--> Launcher is revealed - Showing the Desktop should reveal launcher (lp:898161) |
857 | + |
858 | +---- |
859 | + |
860 | + * Drag a file(example .txt) onto a tile(gedit) in the launcher |
861 | + |
862 | +--> The application should be launched - Launch the application, if it can handle the file(s) dragged and Open the files. If the application can't handle the files then ignore the drag-n-drop action. (lp:676549) |
863 | + |
864 | +---- |
865 | + |
866 | + * Reveal launcher |
867 | + * Place mouse over a tile whose application is running |
868 | + |
869 | +--> Tooltip appears - The tooltip should be displayed at the center of the tile. (lp:898349) |
870 | + |
871 | +---- |
872 | + |
873 | + * Show the desktop (Ctrl+Alt+D ?) |
874 | + |
875 | +The panel title Ubuntu Desktop is shown - The desktop title is changed from 'Desktop to 'Ubuntu Desktop'(lp:869873) |
876 | + |
877 | +---- |
878 | |
879 | === added file 'tests/manual-tests/panel.txt' |
880 | === added file 'tests/manual-tests/places.txt' |
881 | --- tests/manual-tests/places.txt 1970-01-01 00:00:00 +0000 |
882 | +++ tests/manual-tests/places.txt 2011-12-15 16:55:31 +0000 |
883 | @@ -0,0 +1,13 @@ |
884 | +---- |
885 | + * Hit super key to Open Dash. |
886 | + |
887 | +--> Dash have window buttons to maximize/unmaximize - Clicking on maximize/unmaximize |
888 | + button should toggle the dash mode between full-screen and netbook mode. If display is |
889 | + small, Dash should be locked to full-screen mode. (lp:860400) |
890 | + |
891 | +---- |
892 | + * Press Alt+F2 |
893 | + |
894 | +--> Dash opens and "Run a command" is shown in the search field - (lp:883392) |
895 | + |
896 | +---- |
897 | |
898 | === added file 'tests/manual-tests/window-manager.txt' |
899 | --- tests/manual-tests/window-manager.txt 1970-01-01 00:00:00 +0000 |
900 | +++ tests/manual-tests/window-manager.txt 2011-12-15 16:55:31 +0000 |
901 | @@ -0,0 +1,9 @@ |
902 | +---- |
903 | + * Hit Super+S key. Spread is launched. |
904 | + |
905 | +-> 1) Mouse hovering and 2) Arrow, Enter and Esc keys - are enabled - Hitting arrow |
906 | + keys or mouse hovering should switch the highlighted workspaces. The Enter key should |
907 | + zoom the highlighted workspace while Esc key should simply exit from spread with no |
908 | + changes.(lp:744978) |
909 | + |
910 | +---- |
911 | |
912 | === added directory 'tests/misc' |
913 | === added file 'tests/misc/binary_dir.txt.in' |
914 | --- tests/misc/binary_dir.txt.in 1970-01-01 00:00:00 +0000 |
915 | +++ tests/misc/binary_dir.txt.in 2011-12-15 16:55:31 +0000 |
916 | @@ -0,0 +1,1 @@ |
917 | +@CMAKE_CURRENT_BINARY_DIR@ |
918 | |
919 | === added directory 'tests/misc/lib' |
920 | === added file 'tests/misc/lib/testhelper.rb' |
921 | --- tests/misc/lib/testhelper.rb 1970-01-01 00:00:00 +0000 |
922 | +++ tests/misc/lib/testhelper.rb 2011-12-15 16:55:31 +0000 |
923 | @@ -0,0 +1,162 @@ |
924 | +require 'test/unit' |
925 | + |
926 | +dir = File.dirname(File.expand_path(__FILE__)) |
927 | +$LOAD_PATH.unshift dir + '/../lib' |
928 | +$TEST_DIR = File.dirname(File.expand_path(__FILE__)) |
929 | + |
930 | +# Enable a startup and shutdwn method for each test case |
931 | +# From https://github.com/freerange/test_startup |
932 | +module TestStartupAndShutdown |
933 | + def startup(&block) |
934 | + install_global_startup |
935 | + @__startup_blocks ||= [] |
936 | + @__startup_blocks << block if block_given? |
937 | + @__startup_blocks |
938 | + end |
939 | + |
940 | + def shutdown(&block) |
941 | + install_global_startup |
942 | + @__shutdown_blocks ||= [] |
943 | + @__shutdown_blocks << block if block_given? |
944 | + @__shutdown_blocks |
945 | + end |
946 | + |
947 | + attr_reader :__startup_blocks, :__shutdown_blocks |
948 | + |
949 | + def install_global_startup |
950 | + extend(TestSuiteWithGlobalStartup) |
951 | + end |
952 | + |
953 | + module TestSuiteWithGlobalStartup |
954 | + def suite(*args) |
955 | + mysuite = super |
956 | + these_startup_blocks = __startup_blocks |
957 | + these_shutdown_blocks = __shutdown_blocks |
958 | + mysuite.instance_eval { @__startup_blocks = these_startup_blocks } |
959 | + mysuite.instance_eval { @__shutdown_blocks = these_shutdown_blocks } |
960 | + def mysuite.run(*args) |
961 | + (@__startup_blocks || []).each { |block| block.call } |
962 | + super |
963 | + (@__shutdown_blocks || []).each { |block| block.call } |
964 | + end |
965 | + mysuite |
966 | + end |
967 | + end |
968 | +end |
969 | + |
970 | +Test::Unit::TestCase.extend(TestStartupAndShutdown) |
971 | + |
972 | +## |
973 | +# Test::Unit runs test in alphabetical order. This class instead runs them |
974 | +# sequentially. Can specify ordering with the line |
975 | +# execute :sequentially: |
976 | +# Taken from http://wiki.openqa.org/download/attachments/804/testcase.rb |
977 | +# Copyright: Bret Pettichord |
978 | + |
979 | +class TestCase < Test::Unit::TestCase |
980 | + @@order = :sequentially |
981 | + def initialize name |
982 | + throw :invalid_test if name == :default_test && self.class == TestCase |
983 | + super |
984 | + end |
985 | + class << self |
986 | + attr_accessor :test_methods, :order |
987 | + def test_methods |
988 | + @test_methods ||= [] |
989 | + end |
990 | + def order |
991 | + @order || @@order |
992 | + end |
993 | + def default_order= order |
994 | + @@order = order |
995 | + end |
996 | + def sorted_test_methods |
997 | + case order |
998 | + when :alphabetically: test_methods.sort |
999 | + when :sequentially: test_methods |
1000 | + when :reversed_sequentially: test_methods.reverse |
1001 | + when :reversed_alphabetically: test_methods.sort.reverse |
1002 | + else raise ArgumentError, "Execute option not supported: #{@order}" |
1003 | + end |
1004 | + end |
1005 | + def suite |
1006 | + suite = Test::Unit::TestSuite.new(name) |
1007 | + sorted_test_methods.each do |test| |
1008 | + catch :invalid_test do |
1009 | + suite << new(test) |
1010 | + end |
1011 | + end |
1012 | + if (suite.empty?) |
1013 | + catch :invalid_test do |
1014 | + suite << new(:default_test) |
1015 | + end |
1016 | + end |
1017 | + return suite |
1018 | + end |
1019 | + def method_added id |
1020 | + name = id.id2name |
1021 | + test_methods << name if name =~ /^test./ |
1022 | + end |
1023 | + def execute order |
1024 | + @order = order |
1025 | + end |
1026 | + end |
1027 | +end |
1028 | + |
1029 | + |
1030 | +## |
1031 | +# Snippit from test/spec/mini 5 |
1032 | +# Allows syntatic sugar for tests in the following form: |
1033 | +# |
1034 | +#context "It's test/spec/mini!" do |
1035 | +# setup do |
1036 | +# @name = "Chris" |
1037 | +# end |
1038 | +# |
1039 | +# teardown do |
1040 | +# @name = nil |
1041 | +# end |
1042 | +# |
1043 | +# test "with Test::Unit" do |
1044 | +# assert (self.class < Test::Unit::TestCase) |
1045 | +# end |
1046 | +# |
1047 | +# test "body-less test cases" |
1048 | +# |
1049 | +# test :symbol_test_names do |
1050 | +# assert true |
1051 | +# end |
1052 | +# |
1053 | +# xtest "disabled tests" do |
1054 | +# assert disabled! |
1055 | +# end |
1056 | +# |
1057 | +# context "and of course" do |
1058 | +# test "nested contexts!" do |
1059 | +# assert_equal "Chris", @name |
1060 | +# end |
1061 | +# end |
1062 | +#end |
1063 | +# |
1064 | +# http://gist.github.com/307649 (chris@ozmm.org) |
1065 | +# |
1066 | + |
1067 | +def context(*args, &block) |
1068 | + return super unless (name = args.first) && block |
1069 | + |
1070 | + klass = Class.new(TestCase) do |
1071 | + def self.test(name, &block) |
1072 | + define_method("test_#{name.to_s.gsub(/\W/,'_')}", &block) if block |
1073 | + end |
1074 | + def self.xtest(*args) end |
1075 | + def self.context(*args, &block) instance_eval(&block) end |
1076 | + def self.setup(&block) |
1077 | + define_method(:setup) { self.class.setups.each { |s| instance_eval(&s) } } |
1078 | + setups << block |
1079 | + end |
1080 | + def self.setups; @setups ||= [] end |
1081 | + def self.teardown(&block) define_method(:teardown, &block) end |
1082 | + end |
1083 | + (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') } |
1084 | + klass.class_eval &block |
1085 | +end |
1086 | |
1087 | === added directory 'tests/misc/lib/xdo' |
1088 | === added file 'tests/misc/lib/xdo/README.xdo.rdoc' |
1089 | --- tests/misc/lib/xdo/README.xdo.rdoc 1970-01-01 00:00:00 +0000 |
1090 | +++ tests/misc/lib/xdo/README.xdo.rdoc 2011-12-15 16:55:31 +0000 |
1091 | @@ -0,0 +1,52 @@ |
1092 | +-- |
1093 | +This file is part of Xdo. |
1094 | +Copyright © 2009, 2010 Marvin Gülker |
1095 | + Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1096 | +++ |
1097 | +=XDo |
1098 | +XDo is a library to simulate keyboard and mouse input and manipulating windows on the X server. |
1099 | +It's wrapped around the command-line tools xdotool[http://www.semicomplete.com/projects/xdotool/], |
1100 | +xsel[http://linux.die.net/man/1/xsel], xwininfo[http://linux.die.net/man/1/xwininfo], eject[http://linux.die.net/man/1/eject] and xkill[http://linux.die.net/man/1/xkill], |
1101 | +so you will need to have them installed if you want to use Xdo (even if xwininfo, eject and xkill are usually already installed). |
1102 | +If not, try to install them via your favourite packaging manager. |
1103 | +After they're installed, install XDo via RubyGems: |
1104 | + sudo gem install xdo |
1105 | +==Usage |
1106 | + #Require some of XDo's files |
1107 | + require "xdo/keyboard" |
1108 | + require "xdo/mouse" |
1109 | + require "xdo/xwindow" |
1110 | + #Move the cursor |
1111 | + XDo::Mouse.move(100, 100) |
1112 | + #Simulate text (with special escape sequences!) |
1113 | + XDo::Keyboard.simulate("This is{TAB}text.") |
1114 | + #Some sequences can be shortened: |
1115 | + XDo::Keyboard.simulate("This ist\ttext.") |
1116 | + #And this will move a window containing the string "gedit", |
1117 | + #unless it's maximized. |
1118 | + win = XDo::XWindow.from_title(/gedit/) |
1119 | + win.move(200, 200) |
1120 | +==Files |
1121 | +You can require the following files in your projects: |
1122 | +* xdo/clipboard: Clipboard access |
1123 | +* xdo/keyboard: Pretty self-explaining |
1124 | +* xdo/mouse: Automate the mouse |
1125 | +* xdo/xwindow: Manipulate windows in various ways |
1126 | +As an helpful extra, I created an executable ruby file "xinfo.rb". Thanks to RubyGems, |
1127 | +you can start this GUI tool right from the command line by typing: |
1128 | + xinfo.rb |
1129 | +It's by far not perfect, maybe not even good, but I think it can be useful sometimes |
1130 | +(you will need to have wxRuby installed, try <tt>sudo gem install wxruby-ruby19</tt>). |
1131 | +If you're looking for a more professional program, try the "X window information" tool. |
1132 | +==Notes |
1133 | +* If your +xdotool+ seems to reject the --window option, you are not using the current version. Try building the newest one from the source. |
1134 | +* I recommand the "X window information" tool to get infos about your windows if you aren't satisfied by the xinfo.rb shipped with this package. |
1135 | +==Fairly incomplete |
1136 | +* I'm sure there are several things I didn't notice that can be automated somehow. If you know about, email me! Please add a description of the possibilities and a sample script. |
1137 | +* Another interesting thing are the samples. There are many Linux distrubitions out there, and even many of them rely on X. I cannot test with another than a recent Ubuntu machine, but if you want to contribute and send samples for another OS, I want to encourage you to - I surely won't reject your work. :-) |
1138 | +==License/Copyright |
1139 | + Copyright © 2009, 2010 Marvin Gülker |
1140 | + This library is free software; you may redistribute it and/or modify it |
1141 | + under the terms of Ruby's license (see http://www.ruby-lang.org/en/LICENSE.txt). |
1142 | + You can contact me at sutniuq ät gmx Dot net. |
1143 | + Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1144 | |
1145 | === added file 'tests/misc/lib/xdo/_xdo.rb' |
1146 | --- tests/misc/lib/xdo/_xdo.rb 1970-01-01 00:00:00 +0000 |
1147 | +++ tests/misc/lib/xdo/_xdo.rb 2011-12-15 16:55:31 +0000 |
1148 | @@ -0,0 +1,35 @@ |
1149 | +#Encoding: UTF-8 |
1150 | +#This file is part of Xdo. |
1151 | +#Copyright © 2009 Marvin Gülker |
1152 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1153 | +# |
1154 | +# This file contains shared definitions for all the xdo scripts |
1155 | +# |
1156 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
1157 | + |
1158 | +require "open3" |
1159 | +require "strscan" |
1160 | + |
1161 | +#The namespace of this library. |
1162 | +module XDo |
1163 | + |
1164 | + #The command to start xdotool. |
1165 | + XDOTOOL = "xdotool" |
1166 | + |
1167 | + #The command to start xsel. |
1168 | + XSEL = "xsel" |
1169 | + |
1170 | + #The command to start xwininfo. |
1171 | + XWININFO = "xwininfo" |
1172 | + |
1173 | + #The command to start xkill. |
1174 | + XKILL = "xkill" |
1175 | + |
1176 | + #Class for errors in this library. |
1177 | + class XError < StandardError |
1178 | + end |
1179 | + |
1180 | + class ParseError < StandardError |
1181 | + end |
1182 | + |
1183 | +end #module XDo |
1184 | |
1185 | === added file 'tests/misc/lib/xdo/clipboard.rb' |
1186 | --- tests/misc/lib/xdo/clipboard.rb 1970-01-01 00:00:00 +0000 |
1187 | +++ tests/misc/lib/xdo/clipboard.rb 2011-12-15 16:55:31 +0000 |
1188 | @@ -0,0 +1,209 @@ |
1189 | +#Encoding: UTF-8 |
1190 | +#This file is part of Xdo. |
1191 | +#Copyright © 2009, 2010 Marvin Gülker |
1192 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1193 | +# |
1194 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
1195 | +require File.join(File.dirname(__FILE__), '_xdo') |
1196 | + |
1197 | +module XDo |
1198 | + |
1199 | + #A module for interaction with the X clipboard. Please note, that the X clipboard |
1200 | + #consists of three parts: The PRIMARY clipboard, the CLIPBOARD clipboard, and |
1201 | + #the SECONDARY clipboard. The clipboard you access normally via [CTRL]+[C] |
1202 | + #or by right-clicking and selecting "copy", is usually the CLIPBOARD clipboard (but that |
1203 | + #depends on the application you use). The three main methods of this module (#read, #write |
1204 | + #and #clear) take a list symbols of the clipboards to interact with. If you don't want to |
1205 | + #pass in the symbols, use the predefined read_xy, write_xy and clear_xy methods. They cannot |
1206 | + #access more than one clipboard at a time. |
1207 | + #The symbols for the clipboards are: |
1208 | + #[PRIMARY] :primary |
1209 | + #[SECONDARY] :secondary |
1210 | + #[CLIPBOARD] :clipboard |
1211 | + #You cannot store complex objects like images via this interface, only strings. However, |
1212 | + #you could translate an image into a string (packed pixels maybe?) and put that on the |
1213 | + #clipboard -- for your own application this may be fine, but it won't magically allow |
1214 | + #a user to paste that image into a graphics program. |
1215 | + # |
1216 | + #The +xsel+ program used by this module is quite outdated. As far as I can see, it's |
1217 | + #last update happened in 2002 and since I do not believe that software exists that |
1218 | + #won't break over a period of 8 years without a single modification while updating systems I'm about to |
1219 | + #switch to a newer one. +xclip+ is likely, but that one got it's last update in early |
1220 | + #2009... |
1221 | + module Clipboard |
1222 | + |
1223 | + class << self |
1224 | + |
1225 | + ## |
1226 | + # :singleton-method: read_primary |
1227 | + #Returns the contents of the PRIMARY clipboard. |
1228 | + #See #read for an explanation. |
1229 | + |
1230 | + ## |
1231 | + # :singleton-method: read_clipboard |
1232 | + #Returns the contents of the CLIPBOARD clipboard. |
1233 | + #See #read for an explanation. |
1234 | + |
1235 | + ## |
1236 | + # :singleton-method: read_secondary |
1237 | + #Returns the contents of the SECONDARY clipboard. |
1238 | + #See #read for an explanation. |
1239 | + |
1240 | + ## |
1241 | + # :singleton-method: write_primary |
1242 | + #Writes to the PRIMARY clipboard. |
1243 | + #See #write for an explanation. |
1244 | + |
1245 | + ## |
1246 | + # :singleton-method: write_clipboard |
1247 | + #Writes to the CLIPBOARD clipboard. |
1248 | + #See #write for an explanation. |
1249 | + |
1250 | + ## |
1251 | + # :singleton-method: write_secondary |
1252 | + #Writes to the SECONDARY clipboard. |
1253 | + #See #write for an explanation. |
1254 | + |
1255 | + ## |
1256 | + # :singleton-method: clear_primary |
1257 | + #Clears the PRIMARY clipboard. |
1258 | + #See #clear for an explanation. |
1259 | + |
1260 | + ## |
1261 | + # :singleton-method: clear_clipboard |
1262 | + #Clears the CLIPBOARD clipboard. |
1263 | + #See #clear for an explanation. |
1264 | + |
1265 | + ## |
1266 | + # :singleton-method: clear_secondary |
1267 | + #Clears the SECONDARY clipboard. |
1268 | + #See #clear for an explanation. |
1269 | + |
1270 | + #Reads text from a X clipboard. |
1271 | + #===Parameters |
1272 | + #[<tt>*from</tt>] (<tt>:clipboard</tt>, <tt>:primary</tt>, <tt>:secondary</tt>) Specifies from which clipboards you want to read (in 70% of all cases you want to read from <tt>:clipboard</tt>). |
1273 | + #===Return value |
1274 | + #A hash of form |
1275 | + # {:clip_sym => "clipboard_content"} |
1276 | + #If you didn't pass any arguments to #read, the hash will contain keys for |
1277 | + #all clipboard, i.e. for <tt>:clipboard</tt>, <tt>:primary</tt> and <tt>:secondary</tt>. |
1278 | + #If you did, only those symbols will be included you passed. See |
1279 | + #the _Example_ section for an example of this. |
1280 | + #===Example |
1281 | + # XDo::Clipboard.read #| {:clipboard => "...", :primary => "...", :secondary => "..."} |
1282 | + # XDo::Clipboard.read(:primary) #| {:primary => "..."} |
1283 | + # XDo::Clipboard.read(:clipboard, :secondary) #| {clipboard => "...", :secondary => "..."} |
1284 | + #===Remarks |
1285 | + #You could also use one of the read_* methods for convenience. |
1286 | + def read(*from) |
1287 | + if from.first.kind_of? Hash |
1288 | + warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!") |
1289 | + from = from.first.keys |
1290 | + end |
1291 | + from.concat([:clipboard, :primary, :secondary]) if from.empty? |
1292 | + |
1293 | + hsh = {} |
1294 | + hsh[:primary] = `#{XSEL}` if from.include? :primary |
1295 | + hsh[:clipboard] = `#{XSEL} -b` if from.include? :clipboard |
1296 | + hsh[:secondary] = `#{XSEL} -s` if from.include? :secondary |
1297 | + hsh |
1298 | + end |
1299 | + |
1300 | + |
1301 | + #Writes text to a X clipboard. |
1302 | + #===Parameters |
1303 | + #[<tt>*to</tt>] (<tt>:clipboard</tt>) Specifies to what clipboards you want to wrote to. |
1304 | + #===Return value |
1305 | + #The text written. |
1306 | + #===Example |
1307 | + # XDo::Clipboard.write("I love Ruby") #You can now paste this via [CTRL] + [V] |
1308 | + # XDo::Clipboard.write("I love Ruby", :primary) #You can now paste this via a middle-mouse-button click |
1309 | + # XDo::Clipboard.write("I love Ruby", :clipboard, :primary) #Both of the above |
1310 | + #===Remarks |
1311 | + #You could also use one of the write_* methods for convenience. |
1312 | + def write(text, *to) |
1313 | + if to.first.kind_of? Hash |
1314 | + warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!") |
1315 | + to = to.first.keys |
1316 | + end |
1317 | + to << :clipboard if to.empty? |
1318 | + |
1319 | + IO.popen("xsel -i", "w"){|io| io.write(text)} if to.include? :primary |
1320 | + IO.popen("xsel -b -i", "w"){|io| io.write(text)} if to.include? :clipboard |
1321 | + IO.popen("xsel -s -i", "w"){|io| io.write(text)} if to.include? :secondary |
1322 | + text |
1323 | + end |
1324 | + |
1325 | + #Appends text to a X clipboard. |
1326 | + #===Parameters |
1327 | + #[+text+] The text to append. |
1328 | + #[<tt>*to</tt>] (<tt>:clipboard</tt>) The clipboards to which you want to append. |
1329 | + #===Return value |
1330 | + #Undefined. |
1331 | + #===Example |
1332 | + # XDo::Clipboard.write("I love ") |
1333 | + # XDo::Clipboard.append("Ruby") |
1334 | + # puts XDo::Clipboard.read(:clipboard)[:clipboard] #=> I love Ruby |
1335 | + # |
1336 | + # XDo::Clipboard.write("I love", :primary) |
1337 | + # XDo::Clipboard.append("Ruby", :primary, :clipboard) |
1338 | + # #If you now paste via [CTRL] + [V], you'll get 'Ruby'. If you |
1339 | + # #paste via the middle mouse button, you'll get 'I love Ruby' |
1340 | + # #(Assuming you didn't execute the first block of code, of course). |
1341 | + def append(text, *to) |
1342 | + if to.first.kind_of? Hash |
1343 | + warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!") |
1344 | + to = to.first.keys |
1345 | + end |
1346 | + to << :clipboard if to.empty? |
1347 | + |
1348 | + IO.popen("xsel -a -i", "w"){|io| io.write(text)} if to.include? :primary |
1349 | + IO.popen("xsel -b -a -i", "w"){|io| io.write(text)} if to.include? :clipboard |
1350 | + IO.popen("xsel -s -a -i", "w"){|io| io.write(text)} if to.include? :secondary |
1351 | + end |
1352 | + |
1353 | + #Clears the specified clipboards. |
1354 | + #===Parameters |
1355 | + #[<tt>*clips</tt>] (<tt>:primary</tt>, <tt>:clipboard</tt>, <tt>:secondary</tt>) The clipboards you want to clear. |
1356 | + #===Return value |
1357 | + #nil. |
1358 | + #===Example |
1359 | + # XDo::Clipboard.write("I love Ruby") |
1360 | + # XDo::Clipboard.clear |
1361 | + # #Nothing can be pasted anymore |
1362 | + # |
1363 | + # XDo::Clipboard.write("I love Ruby", :clipboard, :primary) |
1364 | + # XDo::Clipboard.clear(:primary) |
1365 | + # #You can still paste via [CTRL] + [V], but not with the middle mouse button |
1366 | + def clear(*clips) |
1367 | + if clips.first.kind_of? Hash |
1368 | + warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!") |
1369 | + clips = clips.first.keys |
1370 | + end |
1371 | + clips.concat([:primary, :clipboard, :secondary]) if clips.empty? |
1372 | + |
1373 | + `#{XSEL} -c` if clips.include? :primary |
1374 | + `#{XSEL} -b -c` if clips.include? :clipboard |
1375 | + `#{XSEL} -s -c` if clips.include? :secondary |
1376 | + nil |
1377 | + end |
1378 | + |
1379 | + [:primary, :clipboard, :secondary].each do |sym| |
1380 | + |
1381 | + define_method(:"read_#{sym}") do |
1382 | + read(sym)[sym] |
1383 | + end |
1384 | + |
1385 | + define_method(:"write_#{sym}") do |text| |
1386 | + write(text, sym) |
1387 | + end |
1388 | + |
1389 | + define_method(:"clear_#{sym}") do |
1390 | + clear(sym) |
1391 | + end |
1392 | + |
1393 | + end |
1394 | + |
1395 | + end |
1396 | + end |
1397 | +end |
1398 | |
1399 | === added file 'tests/misc/lib/xdo/keyboard.rb' |
1400 | --- tests/misc/lib/xdo/keyboard.rb 1970-01-01 00:00:00 +0000 |
1401 | +++ tests/misc/lib/xdo/keyboard.rb 2011-12-15 16:55:31 +0000 |
1402 | @@ -0,0 +1,387 @@ |
1403 | +#Encoding: UTF-8 |
1404 | +#This file is part of Xdo. |
1405 | +#Copyright © 2009, 2010 Marvin Gülker |
1406 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1407 | +# |
1408 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
1409 | +require File.join(File.dirname(__FILE__), '_xdo') |
1410 | + |
1411 | +module XDo |
1412 | + |
1413 | + #A namespace encabsulating methods to simulate keyboard input. You can |
1414 | + #send input to special windows, just pass in the window's ID or a XWindow |
1415 | + #object via the +w_id+ parameter. |
1416 | + module Keyboard |
1417 | + |
1418 | + #Aliases for key names in escape sequences. |
1419 | + ALIASES = { |
1420 | + "BS" => "BackSpace", |
1421 | + "BACKSPACE" => "BackSpace", |
1422 | + "DEL" => "Delete", |
1423 | + "ESC" => "Escape", |
1424 | + "INS" => "Insert", |
1425 | + "PGUP" => "Prior", |
1426 | + "PGDN" => "Next", |
1427 | + "NUM1" => "KP_End", |
1428 | + "NUM2" => "KP_Down", |
1429 | + "NUM3" => "KP_Next", |
1430 | + "NUM4" => "KP_Left", |
1431 | + "NUM5" => "KP_Begin", |
1432 | + "NUM6" => "KP_Right", |
1433 | + "NUM7" => "KP_Home", |
1434 | + "NUM8" => "KP_Up", |
1435 | + "NUM9" => "KP_Prior", |
1436 | + "NUM_DIV" => "KP_Divide", |
1437 | + "NUM_MUL" => "KP_Multiply", |
1438 | + "NUM_SUB" => "KP_Subtract", |
1439 | + "NUM_ADD" => "KP_Add", |
1440 | + "NUM_ENTER" => "KP_Enter", |
1441 | + "NUM_DEL" => "KP_Delete", |
1442 | + "NUM_COMMA" => "KP_Separator", |
1443 | + "NUM_INS" => "KP_Insert", |
1444 | + "NUM0" => "KP_0", |
1445 | + "CTRL" => "Control_L", |
1446 | + "ALT" => "Alt_L", |
1447 | + "ALT_GR" => "ISO_Level3_Shift", |
1448 | + "WIN" => "Super_L", |
1449 | + "SUPER" => "Super_L" |
1450 | + }.freeze |
1451 | + |
1452 | + #The names of some keyboard symbols. The latest release of |
1453 | + #xdotool is capable of sending keysymbols directly, i.e. |
1454 | + # xdotool key Adiaeresis |
1455 | + #results in Ä being sent. |
1456 | + #This hash defines how those special characters can be |
1457 | + #sent. Feel free to add characters that are missing! You |
1458 | + #can use the +xev+ program to obtain their keycodes. |
1459 | + SPECIAL_CHARS = { |
1460 | + "ä" => "adiaeresis", |
1461 | + "Ä" => "Adiaeresis", |
1462 | + "ö" => "odiaeresis", |
1463 | + "Ö" => "Odiaeresis", |
1464 | + "ü" => "udiaeresis", |
1465 | + "Ü" => "Udiaeresis", |
1466 | + "ë" => "ediaeresis", |
1467 | + "Ë" => "Ediaeresis", #Does not work with xdotool |
1468 | + "ï" => "idiaeresis", |
1469 | + "Ï" => "Idiaeresis", #Does not work with xdotool |
1470 | + "ß" => "ssharp", |
1471 | + "\n" => "Return", |
1472 | + "\t" => "Tab", |
1473 | + "\b" => "BackSpace", |
1474 | + "§" => "section", |
1475 | + "[" => "bracketleft", |
1476 | + "]" => "bracketright", |
1477 | + "{" => "braceright", |
1478 | + "}" => "braceleft", |
1479 | + "@" => "at", |
1480 | + "€" => "EuroSign", |
1481 | + "|" => "bar", |
1482 | + "?" => "question" |
1483 | + } |
1484 | + |
1485 | + class << self |
1486 | + |
1487 | + #Types a character sequence, but without any special chars. |
1488 | + #===Parameters |
1489 | + #[+str+] The string to type. |
1490 | + #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window. |
1491 | + #===Return value |
1492 | + #nil. |
1493 | + #===Example |
1494 | + # XDo::Keyboard.type("test") |
1495 | + # XDo::Keyboard.type("täst") #=> I don't what key produces '�', skipping. |
1496 | + #===Remarks |
1497 | + #This function is a bit faster then #simulate. |
1498 | + def type(str, w_id = 0) |
1499 | + out = Open3.popen3("#{XDOTOOL} type #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}'#{str}'") do |stdin, stdout, stderr| |
1500 | + stdin.close_write |
1501 | + str = stderr.read |
1502 | + warn(str) unless str.empty? |
1503 | + end |
1504 | + nil |
1505 | + end |
1506 | + |
1507 | + #Types a character sequence. You can use the escape sequence {...} to send special |
1508 | + #keystrokes. |
1509 | + #===Parameters |
1510 | + #[+str+] The string to simulate. |
1511 | + #[+raw+] (false) If true, escape sequences via {...} are disabled. See _Remarks_. |
1512 | + #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window. |
1513 | + #===Return value |
1514 | + #The string that was simulated. |
1515 | + #===Raises |
1516 | + #[ParseError] Your string was invalid. |
1517 | + #===Example |
1518 | + # XDo::Keyboard.simulate("test") |
1519 | + # XDo::Keyboard.simulate("täst") |
1520 | + # XDo::Keyboard.simulate("tex{BS}st") |
1521 | + #===Remarks |
1522 | + #This method recognizes many special chars like ? and ä, even if you disable |
1523 | + #the escape syntax {..} via setting the +raw+ parameter to true (that's the only way to send the { and } chars). |
1524 | + # |
1525 | + #+str+ may contain escape sequences in braces { and }. The letters between those two indicate |
1526 | + #what special character to send - this way you can simulate non-letter keypresses like [ESC]! |
1527 | + #You may use the following escape sequences: |
1528 | + # Escape seq. | Keystroke | Comment |
1529 | + # ============+===================+================= |
1530 | + # ALT | [Alt_L] | |
1531 | + # ------------+------------------------------------- |
1532 | + # ALT_GR | [ISO_Level3_Shift]| Not on USA |
1533 | + # | | keyboard |
1534 | + # ------------+------------------------------------- |
1535 | + # BS | [BackSpace] | |
1536 | + # ------------+------------------------------------- |
1537 | + # BACKSPACE | [BackSpace] | |
1538 | + # ------------+------------------------------------- |
1539 | + # CTRL | [Control_L] | |
1540 | + # ------------+------------------------------------- |
1541 | + # DEL | [Delete] | |
1542 | + # ------------+------------------------------------- |
1543 | + # END | [End] | |
1544 | + # ------------+------------------------------------- |
1545 | + # ESC | [Escape] | |
1546 | + # ------------+------------------------------------- |
1547 | + # INS | [Insert] | |
1548 | + # ------------+------------------------------------- |
1549 | + # HOME | [Home] | |
1550 | + # ------------+------------------------------------- |
1551 | + # MENU | [Menu] | Usually right- |
1552 | + # | | click menu |
1553 | + # ------------+------------------------------------- |
1554 | + # NUM0..NUM9 | [KP_0]..[KP_9] | Numpad keys |
1555 | + # ------------+------------------------------------- |
1556 | + # NUM_DIV | [KP_Divide] | Numpad key |
1557 | + # ------------+------------------------------------- |
1558 | + # NUM_MUL | [KP_Multiply] | Numpad key |
1559 | + # ------------+------------------------------------- |
1560 | + # NUM_SUB | [KP_Subtract] | Numpad key |
1561 | + # ------------+------------------------------------- |
1562 | + # NUM_ADD | [KP_Add] | Numpad key |
1563 | + # ------------+------------------------------------- |
1564 | + # NUM_ENTER | [KP_Enter] | Numpad key |
1565 | + # ------------+------------------------------------- |
1566 | + # NUM_DEL | [KP_Delete] | Numpad key |
1567 | + # ------------+------------------------------------- |
1568 | + # NUM_COMMA | [KP_Separator] | Numpad key |
1569 | + # ------------+------------------------------------- |
1570 | + # NUM_INS | [KP_Insert] | Numpad key |
1571 | + # ------------+------------------------------------- |
1572 | + # PAUSE | [Pause] | |
1573 | + # ------------+------------------------------------- |
1574 | + # PGUP | [Prior] | Page up |
1575 | + # ------------+------------------------------------- |
1576 | + # PGDN | [Next] | Page down |
1577 | + # ------------+------------------------------------- |
1578 | + # PRINT | [Print] | |
1579 | + # ------------+------------------------------------- |
1580 | + # SUPER | [Super_L] | Windows key |
1581 | + # ------------+------------------------------------- |
1582 | + # TAB | [Tab] | |
1583 | + # ------------+------------------------------------- |
1584 | + # WIN | [Super_L] | Windows key |
1585 | + def simulate(str, raw = false, w_id = 0) |
1586 | + raise(XDo::XError, "Invalid number of open and close braces!") unless str.scan(/\{/).size == str.scan(/\}/).size |
1587 | + |
1588 | + tokens = tokenize(str) |
1589 | + |
1590 | + tokens.each do |sym, s| |
1591 | + case sym |
1592 | + when :plain then type(s, w_id.to_i) |
1593 | + when :esc then |
1594 | + if raw |
1595 | + type("{#{s}}", w_id.to_i) #The braces should be preserved when using +raw+. |
1596 | + else |
1597 | + if ALIASES.has_key?(s) |
1598 | + key(ALIASES[s]) |
1599 | + else |
1600 | + char(s.split("_").map(&:capitalize).join("_"), w_id.to_i) |
1601 | + end |
1602 | + end |
1603 | + when :special then |
1604 | + if SPECIAL_CHARS.has_key?(s) |
1605 | + char(SPECIAL_CHARS[s], w_id.to_i) |
1606 | + else |
1607 | + raise(XDo::ParseError, "No key symbol known for '#{s}'!") |
1608 | + end |
1609 | + else #Write a bug report if you get here. That really shouldn't happen. |
1610 | + raise(XDo::ParseError, "Invalid token named #{sym.inspect}! This is an internal error - please write a bug report at http://github.com/Quintus/Automations/issues or email me at sutniuq@@gmx@net.") |
1611 | + end |
1612 | + end |
1613 | + str |
1614 | + end |
1615 | + |
1616 | + #Simulate a single char directly via the +key+ command of +xdotool+. |
1617 | + #===Parameters |
1618 | + #[+c+] A single char like "a" or a combination like "shift+a". |
1619 | + #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window. |
1620 | + #===Return value |
1621 | + #The +c+ you passed in. |
1622 | + #===Raises |
1623 | + #[XError] Invalid keyname. |
1624 | + #===Example |
1625 | + # XDo::Keyboard.char("a") #=> a |
1626 | + # XDo::Keyboard.char("A") #=> A |
1627 | + # XDo::Keyboard.char("ctrl+c") |
1628 | + def char(c, w_id = 0) |
1629 | + Open3.popen3("#{XDOTOOL} key #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}#{c}") do |stdin, stdout, stderr| |
1630 | + stdin.close_write |
1631 | + raise(XDo::XError, "Invalid character '#{c}'!") if stderr.read =~ /No such key name/ |
1632 | + end |
1633 | + c |
1634 | + end |
1635 | + alias key char |
1636 | + |
1637 | + #Holds a key down. |
1638 | + #===Parameters |
1639 | + #[+key+] The key to hold down. |
1640 | + #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window. |
1641 | + #===Return value |
1642 | + #+key+. |
1643 | + #===Raises |
1644 | + #[XError] Invalid keyname. |
1645 | + #===Example |
1646 | + # XDo::Keyboard.key_down("a") |
1647 | + # sleep 2 |
1648 | + # XDo::Keyboard.key_up("a") |
1649 | + #===Remarks |
1650 | + #You should release the key sometime via Keyboard.key_up. |
1651 | + def key_down(key, w_id = 0) |
1652 | + Open3.popen3("#{XDOTOOL} keydown #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr| |
1653 | + stdin.close_write |
1654 | + raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/ |
1655 | + end |
1656 | + key |
1657 | + end |
1658 | + |
1659 | + #Releases a key hold down by #key_down. |
1660 | + #===Parameters |
1661 | + #[+key+] The key to release. |
1662 | + #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window. |
1663 | + #===Return value |
1664 | + #+key+. |
1665 | + #===Raises |
1666 | + #[XError] Invalid keyname. |
1667 | + #===Example |
1668 | + # XDo::Keyboard.key_down("a") |
1669 | + # sleep 2 |
1670 | + # XDo::Keyboard.key_up("a") |
1671 | + #===Remarks |
1672 | + #This has no effect on already released keys. |
1673 | + def key_up(key, w_id = 0) |
1674 | + Open3.popen3("#{XDOTOOL} keyup #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr| |
1675 | + stdin.close_write |
1676 | + raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/ |
1677 | + end |
1678 | + key |
1679 | + end |
1680 | + |
1681 | + #Deletes a char. |
1682 | + #===Parameters |
1683 | + #[right] (false) If this is true, +del_char+ uses the DEL key for deletion, otherwise the BackSpace key. |
1684 | + #===Return value |
1685 | + #nil. |
1686 | + #===Example |
1687 | + # XDo::Keyboard.delete |
1688 | + # XDo::Keyboard.delete(true) |
1689 | + def delete(right = false) |
1690 | + Keyboard.simulate(right ? "\b" : "{DEL}") |
1691 | + nil |
1692 | + end |
1693 | + |
1694 | + #Allows you to things like this: |
1695 | + # XDo::Keyboard.ctrl_c |
1696 | + #The string will be capitalized and every _ will be replaced by a + and then passed into #char. |
1697 | + #You can't use this way to send whitespace or _ characters. |
1698 | + def method_missing(sym, *args, &block) |
1699 | + super if args.size > 1 or block |
1700 | + char(sym.to_s.capitalize.gsub("_", "+"), args[0].nil? ? 0 : args[0]) |
1701 | + end |
1702 | + |
1703 | + private |
1704 | + |
1705 | + #Tokenizes a string into an array of form |
1706 | + # [[:plain, "nonspecial"], [:special, "a"], [:esc, "INS"], ...] |
1707 | + def tokenize(str) |
1708 | + tokens = [] |
1709 | + #We need a binary version of our string as StringScanner isn't able to work |
1710 | + #with encodings. |
1711 | + ss = StringScanner.new(RUBY_VERSION >= '1.9' ? str.dup.force_encoding("BINARY") : str.dup) #String#force_encoding always returns self |
1712 | + until ss.eos? |
1713 | + pos = ss.pos |
1714 | + if ss.scan_until(/\{/) |
1715 | + #Get the string between the last and the recent match. We have to subtract 2 here, |
1716 | + #since a StringScanner position is always ahead of the string character by 1 (since 0 in |
1717 | + #a SmallScanner means "before the first character") and the matched brace shouldn't be |
1718 | + #included. |
1719 | + tokens << [:plain, ss.string[Range.new(pos, ss.pos - 2)]] unless ss.pos == 1 #This means, the escape sequence is at the beginning of the string - no :plain text before. |
1720 | + pos = ss.pos |
1721 | + ss.scan_until(/\}/) |
1722 | + tokens << [:esc, ss.string[Range.new(pos, ss.pos - 2)]] #See above for comment on -2 |
1723 | + else #We're behind the last escape sequence now - there must be some characters left, otherwise this wouldn't be triggered. |
1724 | + tokens << [:plain, ss.rest] |
1725 | + ss.terminate |
1726 | + end |
1727 | + end |
1728 | + #Now hunt for special character like ä which can't be send using xdotool's type command. |
1729 | + regexp = Regexp.union(*SPECIAL_CHARS.keys.map{|st| st}) #Regexp.union escapes automatically, no need for Regexp.escape |
1730 | + tokens.map! do |ary| |
1731 | + #But first, we have to remedy from that insane forced encoding for StringScanner. |
1732 | + #Force every string's encoding back to the original encoding. |
1733 | + ary[1].force_encoding(str.encoding) if RUBY_VERSION >= '1.9' |
1734 | + next([ary]) unless ary[0] == :plain #Extra array since we flatten(1) it afterwards |
1735 | + tokens2 = [] |
1736 | + ss = StringScanner.new(ary[1]) |
1737 | + until ss.eos? |
1738 | + pos = ss.pos |
1739 | + if ss.scan_until(regexp) |
1740 | + #Same as for the first StringScanner encoding problem goes here, but since I now have to use a UTF-8 regexp |
1741 | + #I have to put the string into the StringScanner as UTF-8, but because the StringScanner returns positions for |
1742 | + #a BINARY-encoded string I have to get the string, grep the position from the BINARY version and then reforce |
1743 | + #it to the correct encoding. |
1744 | + if RUBY_VERSION >= '1.9' |
1745 | + tokens2 << [:plain, ss.string.dup.force_encoding("BINARY")[Range.new(pos, ss.pos - 2)].force_encoding(str.encoding)] unless ss.pos == 1 |
1746 | + else |
1747 | + tokens2 << [:plain, ss.string.dup[Range.new(pos, ss.pos - 2)]] unless ss.pos == 1 |
1748 | + end |
1749 | + tokens2 << [:special, ss.matched] |
1750 | + pos = ss.pos |
1751 | + else |
1752 | + tokens2 << [:plain, ss.rest] |
1753 | + ss.terminate |
1754 | + end |
1755 | + end |
1756 | + tokens2 |
1757 | + end |
1758 | + #Make the token sequence 1-dimensional |
1759 | + tokens.flatten!(1) |
1760 | + #Now delete empty :plain tokens, they don't have to be handled. |
1761 | + #They are created by strings like "abc{ESC}{ESC}", where they are |
1762 | + #recognized between the two escapes. |
1763 | + #Empty escape sequences are an error in any case. |
1764 | + tokens.delete_if do |sym, st| |
1765 | + if st.empty? |
1766 | + if sym == :esc |
1767 | + raise(XDo::ParseError, "Empty escape sequence found!") |
1768 | + else |
1769 | + true |
1770 | + end |
1771 | + end |
1772 | + end |
1773 | + |
1774 | + #Return the tokens array. |
1775 | + tokens |
1776 | + end |
1777 | + |
1778 | + #Checks wheather +key+ is a special character (i.e. contained |
1779 | + #in the SPECIAL_CHARS hash) and returns the key symbol for it if so, |
1780 | + #otherwise returns +key+. |
1781 | + def check_for_special_key(key) |
1782 | + SPECIAL_CHARS.has_key?(key) ? SPECIAL_CHARS[key] : key |
1783 | + end |
1784 | + |
1785 | + end |
1786 | + |
1787 | + end |
1788 | + |
1789 | +end |
1790 | |
1791 | === added file 'tests/misc/lib/xdo/mouse.rb' |
1792 | --- tests/misc/lib/xdo/mouse.rb 1970-01-01 00:00:00 +0000 |
1793 | +++ tests/misc/lib/xdo/mouse.rb 2011-12-15 16:55:31 +0000 |
1794 | @@ -0,0 +1,254 @@ |
1795 | +#Encoding: UTF-8 |
1796 | +#This file is part of Xdo. |
1797 | +#Copyright © 2009 Marvin Gülker |
1798 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
1799 | +# |
1800 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
1801 | +require File.join(File.dirname(__FILE__), '_xdo') |
1802 | + |
1803 | +module XDo |
1804 | + |
1805 | + #Automate your mouse! You can simulate every click you can do with |
1806 | + #your fingers - it's kind of funny, but don't forget to create USEFUL |
1807 | + #applications, not ones annoying your users (e. g. you could make |
1808 | + #his/her mouse unusable). |
1809 | + module Mouse |
1810 | + |
1811 | + #Left mouse button. |
1812 | + LEFT = 1 |
1813 | + #Middle mouse button (as if you click your mouse wheel). |
1814 | + MIDDLE = 2 |
1815 | + #Right mouse button. |
1816 | + RIGHT = 3 |
1817 | + #Mouse wheel up. |
1818 | + UP = 4 |
1819 | + #Mouse wheel down. |
1820 | + DOWN = 5 |
1821 | + |
1822 | + #Maps the button's symbols to the numbers xdotool uses. |
1823 | + BUTTON2XDOTOOL = { |
1824 | + :left => 1, |
1825 | + :middle => 2, |
1826 | + :right => 3, |
1827 | + :up => 4, |
1828 | + :down => 5 |
1829 | + }.freeze |
1830 | + |
1831 | + class << self |
1832 | + |
1833 | + #Gets the current cursor position. |
1834 | + #===Return value |
1835 | + #A two-element array of form <code>[x, y]</code>. |
1836 | + #===Example |
1837 | + # p XDo::Mouse.position #=> [12, 326] |
1838 | + def position |
1839 | + out = `#{XDOTOOL} getmouselocation`.match(/x:(\d+) y:(\d+)/) |
1840 | + [$1.to_i, $2.to_i] |
1841 | + end |
1842 | + |
1843 | + #Moves the mouse cursor to the given position. |
1844 | + #===Parameters |
1845 | + #[+x+] The goal X coordinate. |
1846 | + #[+x+] The goal Y coordinate. |
1847 | + #[+speed+] (2) Cursor move speed, in pixels per iteration. The higher the value, the more inaccurate the movement (but you can be sure the cursor is always at the position you specified at the end). |
1848 | + #[+set+] (false) If true, the +speed+ parameter is ignored and the cursor is directly set to the given position. |
1849 | + #[+sync+] (true) If true, this method blocks until the cursor has reached the given position. |
1850 | + #===Return value |
1851 | + #The position you specified, as a two-dimensional array of form <tt>[x, y]</tt>. |
1852 | + #===Raises |
1853 | + #[ArgumentError] The value of +speed+ was lower or equal to zero. |
1854 | + #===Example |
1855 | + # #Move to (10|10) |
1856 | + # XDo::Mouse.move(10, 10) |
1857 | + # #Move fast to (10|10) |
1858 | + # XDo::Mouse.move(10, 10, 10) |
1859 | + # #Directly set the cursor to (10|10) without any movement |
1860 | + # XDo::Mouse.move(10, 10, 1, true) |
1861 | + def move(x, y, speed = 2, set = false, sync = true) |
1862 | + if set |
1863 | + opts = [] |
1864 | + opts << "--sync" if sync |
1865 | + `#{XDOTOOL} mousemove #{opts.join(" ")} #{x} #{y}` |
1866 | + return [x, y] |
1867 | + else |
1868 | + raise(ArgumentError, "speed has to be > 0 (default is 2), was #{speed}!") if speed <= 0 |
1869 | + pos = position #Current cursor position |
1870 | + act_x = pos[0] |
1871 | + act_y = pos[1] |
1872 | + aim_x = x |
1873 | + aim_y = y |
1874 | + #Create the illusion of a fluent movement (hey, that statement sounds better in German, really! ;-)) |
1875 | + loop do |
1876 | + #Change position as indiciated by +speed+ |
1877 | + if act_x > aim_x |
1878 | + act_x -= speed |
1879 | + elsif act_x < aim_x |
1880 | + act_x += speed |
1881 | + end |
1882 | + if act_y > aim_y |
1883 | + act_y -= speed |
1884 | + elsif act_y < aim_y |
1885 | + act_y += speed |
1886 | + end |
1887 | + #Move to computed position |
1888 | + move(act_x, act_y, speed, true) |
1889 | + #Check wheather the cursor's current position is inside an |
1890 | + #acceptable area around the goal position. The size of this |
1891 | + #area is defined by +speed+; this check ensures we don't get |
1892 | + #an infinite loop for unusual conditions. |
1893 | + if ((aim_x - speed)..(aim_x + speed)).include? act_x |
1894 | + if ((aim_y - speed)..(aim_y + speed)).include? act_y |
1895 | + break |
1896 | + end #if in Y-Toleranz |
1897 | + end #if in X-Toleranz |
1898 | + end #loop |
1899 | + #Correct the cursor position to point to the exact point specified. |
1900 | + #This is for the case the "acceptable area" condition above triggers. |
1901 | + if position != [x, y] |
1902 | + move(x, y, 1, true) |
1903 | + end #if position != [x, y] |
1904 | + |
1905 | + end #if set |
1906 | + [x, y] |
1907 | + end #def move |
1908 | + |
1909 | + #Simulates a mouse click. If you don't specify a X AND a Y position, |
1910 | + #the click will happen at the current cursor position. |
1911 | + #===Parameters |
1912 | + #[+x+] (nil) The goal X position. Specify together with +y+. |
1913 | + #[+y+] (nil) The goal Y position. |
1914 | + #[+button+] (:left) The button to click with. One of the following symbols: <tt>:left</tt>, <tt>:right</tt>, <tt>:middle</tt>. |
1915 | + #The other arguments are the same as for #move. |
1916 | + #===Return value |
1917 | + #Undefined. |
1918 | + #===Example |
1919 | + # #Click at the current position |
1920 | + # XDo::Mouse.click |
1921 | + # #Click at (10|10) |
1922 | + # XDo::Mouse.click(10, 10) |
1923 | + # #Click at the current position with the right mouse button |
1924 | + # XDo::Mouse.click(nil, nil, :right) |
1925 | + # #Move fast to (10|10) and click with the right mouse button |
1926 | + # XDo::Mouse.click(10, 10, :right, 10) |
1927 | + # #Directly set the cursor to (10|10) and click with the middle mouse button |
1928 | + # XDo::Mouse.click(10, 10, :middle, 1, true) |
1929 | + def click(x = nil, y = nil, button = :left, speed = 1, set = false) |
1930 | + if button.kind_of?(Numeric) |
1931 | + warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.") |
1932 | + button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based |
1933 | + end |
1934 | + if x and y |
1935 | + move(x, y, speed, set) |
1936 | + end |
1937 | + `#{XDOTOOL} click #{BUTTON2XDOTOOL[button]}` |
1938 | + end |
1939 | + |
1940 | + #Scroll with the mouse wheel. +amount+ is the time of steps to scroll. |
1941 | + #===Parameters |
1942 | + #[+dir+] The direction to scroll into. Either :up or :down. |
1943 | + #[+amount+] The number of steps to scroll. These are *not* meant to be full wheel rounds. |
1944 | + #===Return value |
1945 | + #Undefined. |
1946 | + #===Example |
1947 | + # #Scroll up |
1948 | + # XDo::Mouse.wheel(:up, 4) |
1949 | + # #Scroll down 4 steps |
1950 | + # XDo::Mouse.wheel(:down, 4) |
1951 | + #===Remarks |
1952 | + #The X Server handles wheel up and wheel down events like mouse click |
1953 | + #events and if you take a look in the source code of this function you will see, that |
1954 | + #it calls #click passing through the +dir+ parameter. So, if you want to be funny, |
1955 | + #write something like |
1956 | + # XDo::Mouse.click(nil, nil, :up) |
1957 | + #. |
1958 | + def wheel(dir, amount) |
1959 | + if button.kind_of?(Numeric) |
1960 | + warn("#{caller.first}: Deprecation warning: Use symbols such as :up for the dir parameter.") |
1961 | + button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based |
1962 | + end |
1963 | + amount.times{click(nil, nil, dir)} |
1964 | + end |
1965 | + |
1966 | + #Holds a mouse button down. Don't forget to release it some time. |
1967 | + #===Parameters |
1968 | + #[+button+] (:left) The button to hold down. |
1969 | + #===Return value |
1970 | + #Undefined. |
1971 | + #===Example |
1972 | + # #Hold down the left mouse button for a second |
1973 | + # XDo::Mouse.down |
1974 | + # sleep 1 |
1975 | + # XDo::Mouse.up |
1976 | + # #Hold down the right mouse button for a second |
1977 | + # XDo::Mouse.down(:right) |
1978 | + # sleep 1 |
1979 | + # XDo::Mouse.up(:right) |
1980 | + def down(button = :left) |
1981 | + if button.kind_of?(Numeric) |
1982 | + warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.") |
1983 | + button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based |
1984 | + end |
1985 | + `#{XDOTOOL} mousedown #{BUTTON2XDOTOOL[button]}` |
1986 | + end |
1987 | + |
1988 | + #Releases a mouse button. Probably it's a good idea to call #down first? |
1989 | + #===Parameters |
1990 | + #[+button+] (:left) The button to release. |
1991 | + #===Return value |
1992 | + #Undefined. |
1993 | + #===Example |
1994 | + # #Hold down the left mouse button for a second |
1995 | + # XDo::Mouse.down |
1996 | + # sleep 1 |
1997 | + # XDo::Mouse.up |
1998 | + # #Hold down the right mouse button for a second |
1999 | + # XDo::Mouse.down(:right) |
2000 | + # sleep 1 |
2001 | + # XDo::Mouse.up(:right) |
2002 | + def up(button = :left) |
2003 | + if button.kind_of?(Numeric) |
2004 | + warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.") |
2005 | + button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based |
2006 | + end |
2007 | + `#{XDOTOOL} mouseup #{BUTTON2XDOTOOL[button]}` |
2008 | + end |
2009 | + |
2010 | + #Executs a drag&drop operation. |
2011 | + #===Parameters |
2012 | + #[+x1+] Start X coordinate. Set to the current cursor X coordinate if set to nil. Pass together with +y1+. |
2013 | + #[+y1+] Start Y coordinate. Set to the current cursor Y coordinate if set to nil. |
2014 | + #[+x2+] Goal X coordinate. |
2015 | + #[+y2+] Goal Y coordinate. |
2016 | + #[+button+] (:left) The button to hold down. |
2017 | + #The rest of the parameters is the same as for #move. |
2018 | + #===Return value |
2019 | + #nil. |
2020 | + #===Example |
2021 | + # #Drag from (10|10) to (37|56) |
2022 | + # XDo::Mouse.drag(10, 10, 37, 56) |
2023 | + # #Drag from (10|10) to (37|56) holding the right mouse button down |
2024 | + # XDo::Mouse.drag(10, 10, 37, 56, :right) |
2025 | + def drag(x1, y1, x2, y2, button = :left, speed = 2, set = false) |
2026 | + if button.kind_of?(Numeric) |
2027 | + warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.") |
2028 | + button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based |
2029 | + end |
2030 | + #If x1 and y1 aren't passed, assume the current position |
2031 | + if x1.nil? and y1.nil? |
2032 | + x1, y1 = position |
2033 | + end |
2034 | + #Go to the given start position |
2035 | + move(x1, y1, speed, set) |
2036 | + #Hold button down |
2037 | + down(button) |
2038 | + #Move to the goal position |
2039 | + move(x2, y2, speed, set) |
2040 | + #Release button |
2041 | + up(button) |
2042 | + nil |
2043 | + end |
2044 | + |
2045 | + end #class << self |
2046 | + |
2047 | + end #module Mouse |
2048 | +end |
2049 | |
2050 | === added file 'tests/misc/lib/xdo/simulatable.rb' |
2051 | --- tests/misc/lib/xdo/simulatable.rb 1970-01-01 00:00:00 +0000 |
2052 | +++ tests/misc/lib/xdo/simulatable.rb 2011-12-15 16:55:31 +0000 |
2053 | @@ -0,0 +1,91 @@ |
2054 | +#!/usr/bin/env ruby |
2055 | +#Encoding: UTF-8 |
2056 | +#This file is part of Xdo. |
2057 | +#Copyright © 2009, 2010 Marvin Gülker |
2058 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
2059 | +# |
2060 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
2061 | +require File.join(File.dirname(__FILE__), '_xdo') |
2062 | +require File.join(File.dirname(__FILE__), 'keyboard') |
2063 | + |
2064 | +module XDo |
2065 | + |
2066 | + #Mixin that allows String-like objects to be directly |
2067 | + #simulated. You can use it with Ruby's String class: |
2068 | + # require "xdo/simulatable" |
2069 | + # |
2070 | + # class String |
2071 | + # include XDo::Simulatable |
2072 | + # def to_xdo |
2073 | + # to_s |
2074 | + # end |
2075 | + # end |
2076 | + # |
2077 | + # "abc".simulate |
2078 | + #Every method in this module calls #to_xdo on +self+ |
2079 | + #first, so make sure this method returns a xdo-usable |
2080 | + #String (i.e. no invisible characters except newline, tab and space). |
2081 | + module Simulatable |
2082 | + |
2083 | + #Simulates +self+ as keystrokes. Escape sequences are allowed. |
2084 | + #===Parameters |
2085 | + #[raw] (+false+) If true, escape sequences are ignored. |
2086 | + #[w_id] (+nil+) The window's ID to send the keystrokes to. Either an integer or a XWindow object. |
2087 | + #===Return value |
2088 | + #self. |
2089 | + #===Raises |
2090 | + #[ParseError] Your string was invalid. |
2091 | + #===Example |
2092 | + # "This is an{BS} test".simulate |
2093 | + # win = XDo::XWindow.from_title(/gedit/) |
2094 | + # "This is an{BS} test".simulate(false, win) |
2095 | + def simulate(raw = false, w_id = nil) |
2096 | + XDo::Keyboard.simulate(to_xdo, raw, w_id) |
2097 | + end |
2098 | + |
2099 | + #Types +self+ as keystrokes. Ignores escape sequences. |
2100 | + #===Parameters |
2101 | + #[w_id] The window's ID to send the keystrokes to. Either an integer or a XWindow object. |
2102 | + #===Return value |
2103 | + #nil. |
2104 | + #===Example |
2105 | + # "Some test text".type |
2106 | + # win = XDo::XWindow.from_title(/gedit/) |
2107 | + # "Some test text".type(win) |
2108 | + def type(w_id = nil) |
2109 | + XDo::Keyboard.type(to_xdo, w_id) |
2110 | + end |
2111 | + |
2112 | + #Holds the the key corresponding to the first character in +self+ down. |
2113 | + #===Parameters |
2114 | + #[w_id] The window in which to hold a key down. Either an integer ID or a XWindow object. |
2115 | + #===Return value |
2116 | + #The character of the key hold down. |
2117 | + #===Raises |
2118 | + #[XError] Invalid keyname. |
2119 | + #===Example |
2120 | + # "a".down |
2121 | + # win = XDo::XWindow.from_title(/gedit/) |
2122 | + # "a".down(win) |
2123 | + def down(w_id = nil) |
2124 | + XDo::Keyboard.key_down(to_xdo[0], w_id) |
2125 | + end |
2126 | + |
2127 | + #Releases the key corresponding to the first character in +self+. |
2128 | + #===Parameters |
2129 | + #[w_id] The window in which to release a key. Either an integer ID or a XWindow object. |
2130 | + #===Return value |
2131 | + #The character of the key released. |
2132 | + #===Raises |
2133 | + #[XError] Invalid keyname. |
2134 | + #===Example |
2135 | + # "a".up |
2136 | + # win = XDo::XWindow.from_title(/gedit/) |
2137 | + # "a".up(win) |
2138 | + def up(w_id = nil) |
2139 | + XDo::Keyboard.key_up(to_xdo[0], w_id) |
2140 | + end |
2141 | + |
2142 | + end |
2143 | + |
2144 | +end |
2145 | |
2146 | === added directory 'tests/misc/lib/xdo/test' |
2147 | === added file 'tests/misc/lib/xdo/test/test_clipboard.rb' |
2148 | --- tests/misc/lib/xdo/test/test_clipboard.rb 1970-01-01 00:00:00 +0000 |
2149 | +++ tests/misc/lib/xdo/test/test_clipboard.rb 2011-12-15 16:55:31 +0000 |
2150 | @@ -0,0 +1,49 @@ |
2151 | +#!/usr/bin/env ruby |
2152 | +#Encoding: UTF-8 |
2153 | + |
2154 | +require "test/unit" |
2155 | +require File.join(File.dirname(__FILE__), '../clipboard') |
2156 | + |
2157 | +class ClipboardTest < Test::Unit::TestCase |
2158 | + |
2159 | + def test_read |
2160 | + #Write something to the clipboard first |
2161 | + IO.popen("#{XDo::XSEL} -i", "w"){|io| io.write("Some primary test text")} |
2162 | + IO.popen("#{XDo::XSEL} -b -i", "w"){|io| io.write("Some clipboard \ntest text")} |
2163 | + IO.popen("#{XDo::XSEL} -s -i", "w"){|io| io.write("Some secondary test text")} |
2164 | + |
2165 | + clip = XDo::Clipboard.read |
2166 | + assert_equal("Some primary test text", XDo::Clipboard.read_primary) |
2167 | + assert_equal(XDo::Clipboard.read_primary, clip[:primary]) |
2168 | + assert_equal("Some clipboard \ntest text", XDo::Clipboard.read_clipboard) |
2169 | + assert_equal(XDo::Clipboard.read_clipboard, clip[:clipboard]) |
2170 | + assert_equal("Some secondary test text", XDo::Clipboard.read_secondary) |
2171 | + assert_equal(XDo::Clipboard.read_secondary, clip[:secondary]) |
2172 | + end |
2173 | + |
2174 | + def test_write |
2175 | + XDo::Clipboard.write_primary "Primary!" |
2176 | + XDo::Clipboard.write_clipboard "Clipboard!\nNewline" |
2177 | + XDo::Clipboard.write_secondary "Secondary!" |
2178 | + |
2179 | + assert_equal("Primary!", `#{XDo::XSEL}`) |
2180 | + assert_equal("Secondary!", `#{XDo::XSEL} -s`) |
2181 | + assert_equal("Clipboard!\nNewline", `#{XDo::XSEL} -b`) |
2182 | + |
2183 | + XDo::Clipboard.write("XYZ", :primary, :secondary, :clipboard) |
2184 | + assert_equal({ :primary => "XYZ", :secondary => "XYZ", :clipboard => "XYZ"}, XDo::Clipboard.read) |
2185 | + end |
2186 | + |
2187 | + def test_append |
2188 | + ["primary", "secondary", "clipboard"].each{|m| XDo::Clipboard.send(:"write_#{m}", "This is... ")} |
2189 | + XDo::Clipboard.append("a Test!", :primary, :secondary, :clipboard) |
2190 | + ["primary", "secondary", "clipboard"].each{|m| assert_equal(XDo::Clipboard.send(:"read_#{m}"), "This is... a Test!")} |
2191 | + end |
2192 | + |
2193 | + def test_clear |
2194 | + XDo::Clipboard.write("ABC", :primary, :secondary, :clipboard) |
2195 | + ["primary", "secondary", "clipboard"].each{|m| XDo::Clipboard.send("clear_#{m}")} |
2196 | + ["primary", "secondary", "clipboard"].each{|m| assert_equal("", XDo::Clipboard.send("read_#{m}"))} |
2197 | + end |
2198 | + |
2199 | +end |
2200 | |
2201 | === added file 'tests/misc/lib/xdo/test/test_keyboard.rb' |
2202 | --- tests/misc/lib/xdo/test/test_keyboard.rb 1970-01-01 00:00:00 +0000 |
2203 | +++ tests/misc/lib/xdo/test/test_keyboard.rb 2011-12-15 16:55:31 +0000 |
2204 | @@ -0,0 +1,114 @@ |
2205 | +#!/usr/bin/env ruby |
2206 | +#Encoding: UTF-8 |
2207 | + |
2208 | +require "test/unit" |
2209 | +require "tempfile" |
2210 | +require File.join(File.dirname(__FILE__), '../keyboard') |
2211 | +require File.join(File.dirname(__FILE__), '../clipboard') |
2212 | +require File.join(File.dirname(__FILE__), '../xwindow') |
2213 | +require File.join(File.dirname(__FILE__), '../simulatable') |
2214 | + |
2215 | +class TestKeyboard < Test::Unit::TestCase |
2216 | + |
2217 | + #Command to start a simple text editor |
2218 | + EDITOR_CMD = "gedit -s" |
2219 | + EDITOR_NAME = "gedit" |
2220 | + |
2221 | + TESTTEXT = "This is test\ntext." |
2222 | + TESTTEXT2 = "WXY" |
2223 | + TESTTEXT_RAW = "ä{TAB}?b" |
2224 | + TESTTEXT_SPECIAL = "ab{TAB}c{TAB}{TAB}d" |
2225 | + |
2226 | + def setup |
2227 | + @editor_pipe = IO.popen(EDITOR_CMD, 'r') |
2228 | + sleep 3 |
2229 | + end |
2230 | + |
2231 | + def teardown |
2232 | + Process.kill 'TERM', @editor_pipe.pid |
2233 | + @editor_pipe.close |
2234 | + end |
2235 | + |
2236 | + def test_char |
2237 | + Process.kill 'TERM', @editor_pipe.pid |
2238 | + @editor_pipe.close #Special file need to be opened |
2239 | + tempfile = Tempfile.open("XDOTEST") |
2240 | + tempfile.write(TESTTEXT) |
2241 | + tempfile.flush |
2242 | + sleep 3 #Wait for the buffer to be written out |
2243 | + @editor_pipe = IO.popen(EDITOR_CMD + ' ' + tempfile.path, 'r') #So it's automatically killed by #teardown |
2244 | + sleep 3 |
2245 | + tempfile.close |
2246 | + 20.times{XDo::Keyboard.char("Shift+Right")} |
2247 | + XDo::Keyboard.ctrl_c |
2248 | + sleep 0.2 |
2249 | + assert_equal(TESTTEXT, XDo::Clipboard.read_clipboard) |
2250 | + end |
2251 | + |
2252 | + def test_simulate |
2253 | + XDo::Keyboard.simulate("A{BS}#{TESTTEXT2}") |
2254 | + XDo::Keyboard.ctrl_a |
2255 | + XDo::Keyboard.ctrl_c |
2256 | + sleep 0.2 |
2257 | + assert_equal(TESTTEXT2, XDo::Clipboard.read_clipboard) |
2258 | + |
2259 | + XDo::Keyboard.ctrl_a |
2260 | + XDo::Keyboard.delete |
2261 | + XDo::Keyboard.simulate(TESTTEXT_SPECIAL) |
2262 | + XDo::Keyboard.ctrl_a |
2263 | + XDo::Keyboard.ctrl_c |
2264 | + sleep 0.2 |
2265 | + assert_equal(TESTTEXT_SPECIAL.gsub("{TAB}", "\t"), XDo::Clipboard.read_clipboard) |
2266 | + |
2267 | + XDo::Keyboard.ctrl_a |
2268 | + XDo::Keyboard.delete |
2269 | + XDo::Keyboard.simulate(TESTTEXT_RAW, true) |
2270 | + XDo::Keyboard.ctrl_a |
2271 | + XDo::Keyboard.ctrl_c |
2272 | + sleep 0.2 |
2273 | + assert_equal(TESTTEXT_RAW, XDo::Clipboard.read_clipboard) |
2274 | + end |
2275 | + |
2276 | + def test_type |
2277 | + XDo::Keyboard.type(TESTTEXT2) |
2278 | + XDo::Keyboard.ctrl_a |
2279 | + XDo::Keyboard.ctrl_c |
2280 | + sleep 0.2 |
2281 | + assert_equal(TESTTEXT2, XDo::Clipboard.read_clipboard) |
2282 | + end |
2283 | + |
2284 | + def test_window_id |
2285 | + XDo::XWindow.unfocus #Ensure that the editor hasn't the input focus anymore |
2286 | + sleep 1 |
2287 | + edit_id = XDo::XWindow.search(EDITOR_NAME).last |
2288 | + xwin = XDo::XWindow.new(edit_id) |
2289 | + XDo::Keyboard.simulate(TESTTEXT_SPECIAL, false, edit_id) |
2290 | + sleep 1 |
2291 | + xwin.activate |
2292 | + XDo::Keyboard.ctrl_a |
2293 | + XDo::Keyboard.ctrl_c |
2294 | + sleep 0.2 |
2295 | + assert_equal(TESTTEXT_SPECIAL.gsub("{TAB}", "\t"), XDo::Clipboard.read_clipboard) |
2296 | + end |
2297 | + |
2298 | + def test_include |
2299 | + String.class_eval do |
2300 | + include XDo::Simulatable |
2301 | + |
2302 | + def to_xdo |
2303 | + to_s |
2304 | + end |
2305 | + end |
2306 | + |
2307 | + XDo::Keyboard.ctrl_a |
2308 | + XDo::Keyboard.delete |
2309 | + "Ein String".simulate |
2310 | + XDo::Keyboard.ctrl_a |
2311 | + sleep 0.2 |
2312 | + XDo::Keyboard.ctrl_c |
2313 | + sleep 0.2 |
2314 | + clip = XDo::Clipboard.read_clipboard |
2315 | + assert_equal("Ein String", clip, "Simulated typed string fails to match") |
2316 | + end |
2317 | + |
2318 | +end |
2319 | |
2320 | === added file 'tests/misc/lib/xdo/test/test_mouse.rb' |
2321 | --- tests/misc/lib/xdo/test/test_mouse.rb 1970-01-01 00:00:00 +0000 |
2322 | +++ tests/misc/lib/xdo/test/test_mouse.rb 2011-12-15 16:55:31 +0000 |
2323 | @@ -0,0 +1,29 @@ |
2324 | +#!/usr/bin/env ruby |
2325 | +#Encoding: UTF-8 |
2326 | + |
2327 | +require "test/unit" |
2328 | +require File.join(File.dirname(__FILE__), '../mouse') |
2329 | + |
2330 | +class MouseTest < Test::Unit::TestCase |
2331 | + |
2332 | + def test_position |
2333 | + str = `#{XDo::XDOTOOL} getmouselocation` |
2334 | + xdpos = [] |
2335 | + xdpos << str.match(/x:\s?(\d+)/)[1] |
2336 | + xdpos << str.match(/y:\s?(\d+)/)[1] |
2337 | + xdpos.collect!{|o| o.to_i} |
2338 | + assert_equal(xdpos, XDo::Mouse.position) |
2339 | + end |
2340 | + |
2341 | + def test_move |
2342 | + XDo::Mouse.move(200, 200) |
2343 | + assert_equal([200, 200], XDo::Mouse.position) |
2344 | + XDo::Mouse.move(0, 0) |
2345 | + assert_equal([0, 0], XDo::Mouse.position) |
2346 | + XDo::Mouse.move(0, 200) |
2347 | + assert_equal([0, 200], XDo::Mouse.position) |
2348 | + XDo::Mouse.move(100, 100) |
2349 | + assert_equal([100, 100], XDo::Mouse.position) |
2350 | + end |
2351 | + |
2352 | +end |
2353 | |
2354 | === added file 'tests/misc/lib/xdo/test/test_xwindow.rb' |
2355 | --- tests/misc/lib/xdo/test/test_xwindow.rb 1970-01-01 00:00:00 +0000 |
2356 | +++ tests/misc/lib/xdo/test/test_xwindow.rb 2011-12-15 16:55:31 +0000 |
2357 | @@ -0,0 +1,101 @@ |
2358 | +#!/usr/bin/env ruby |
2359 | +#Encoding: UTF-8 |
2360 | + |
2361 | +require "test/unit" |
2362 | +require File.join(File.dirname(__FILE__), '../xwindow') |
2363 | +require File.join(File.dirname(__FILE__), '../keyboard') |
2364 | + |
2365 | +class WindowTest < Test::Unit::TestCase |
2366 | + |
2367 | + attr_accessor :xwindow |
2368 | + |
2369 | + #The command used to create new windows. |
2370 | + #The program MUST NOT start maximized. xdotool has no possibility of |
2371 | + #acting on maximized windows. |
2372 | + NEW_WINDOW_NAME = "Home" |
2373 | + NEW_WINDOW_CMD = "nautilus" |
2374 | + |
2375 | + @@xwin = nil |
2376 | + |
2377 | + def setup |
2378 | + @editor_pipe = IO.popen(NEW_WINDOW_CMD, 'r') |
2379 | + XDo::XWindow.wait_for_window(Regexp.new(Regexp.escape(NEW_WINDOW_NAME))) |
2380 | + @@xwin = XDo::XWindow.from_title(Regexp.new(Regexp.escape(NEW_WINDOW_NAME))) |
2381 | + end |
2382 | + |
2383 | + def teardown |
2384 | + @@xwin.focus |
2385 | + @@xwin.close! |
2386 | +# Process.kill 'TERM', @editor_pipe.pid |
2387 | +# @editor_pipe.close |
2388 | + end |
2389 | + |
2390 | + def test_ewmh_active_window |
2391 | + begin |
2392 | + XDo::XWindow.from_active |
2393 | + rescue XDo::XError |
2394 | + #Standard not available |
2395 | + notify $!.message |
2396 | + end |
2397 | + end |
2398 | + |
2399 | + def test_ewmh_wm_desktop |
2400 | + begin |
2401 | + XDo::XWindow.desktop_num |
2402 | + rescue XDo::XError |
2403 | + #Standard not available |
2404 | + notify $!.message |
2405 | + end |
2406 | + end |
2407 | + |
2408 | + def test_ewmh_current_desktop |
2409 | + begin |
2410 | + XDo::XWindow.desktop |
2411 | + rescue XDo::XError |
2412 | + #Standard not available |
2413 | + notify $!.message |
2414 | + end |
2415 | + end |
2416 | + |
2417 | + def test_exists |
2418 | + assert_equal(true, XDo::XWindow.exists?(@@xwin.title)) |
2419 | + end |
2420 | + |
2421 | + def test_unfocus |
2422 | + XDo::XWindow.unfocus |
2423 | + # assert_not_equal(@@xwin.id, XDo::XWindow.from_focused.id) # not supported by WM |
2424 | + # assert_raise(XDo::XError){XDo::XWindow.from_active} #Nothing's active anymore |
2425 | + end |
2426 | + |
2427 | + def test_active |
2428 | + @@xwin.activate |
2429 | + assert_equal(@@xwin.id, XDo::XWindow.from_active.id) |
2430 | + end |
2431 | + |
2432 | + def test_focused |
2433 | + @@xwin.unfocus |
2434 | + @@xwin.focus |
2435 | + assert_equal(@@xwin.id, XDo::XWindow.from_focused.id) |
2436 | + end |
2437 | + |
2438 | + def test_move |
2439 | + @@xwin.move(87, 57) |
2440 | + assert_in_delta(87, 3, @@xwin.abs_position[0]) |
2441 | + assert_in_delta(57, 3, @@xwin.abs_position[1]) |
2442 | + # assert_equal(@@xwin.abs_position, @@xwin.rel_position) - why should this succeed? |
2443 | + end |
2444 | + |
2445 | + def test_resize |
2446 | + @@xwin.resize(500, 500) |
2447 | + assert_equal([500, 500], @@xwin.size) |
2448 | + end |
2449 | + |
2450 | + def test_map |
2451 | + @@xwin.unmap |
2452 | + assert_equal(nil, @@xwin.visible?) |
2453 | + @@xwin.map |
2454 | + assert_block("Window is not visible."){@@xwin.visible?.kind_of?(Integer)} |
2455 | + end |
2456 | + |
2457 | +end |
2458 | + |
2459 | |
2460 | === added file 'tests/misc/lib/xdo/xwindow.rb' |
2461 | --- tests/misc/lib/xdo/xwindow.rb 1970-01-01 00:00:00 +0000 |
2462 | +++ tests/misc/lib/xdo/xwindow.rb 2011-12-15 16:55:31 +0000 |
2463 | @@ -0,0 +1,1009 @@ |
2464 | +#Encoding: UTF-8 |
2465 | +#This file is part of Xdo. |
2466 | +#Copyright © 2009, 2010 Marvin Gülker |
2467 | +# Initia in potestate nostra sunt, de eventu fortuna iudicat. |
2468 | +# |
2469 | +# Modified by Gerry Boland <gerry dot boland at canonical dot com> |
2470 | +require File.join(File.dirname(__FILE__), '_xdo') |
2471 | +require "open3" |
2472 | + |
2473 | +module XDo |
2474 | + |
2475 | + #This class represents a window on the screen. Each window is uniquely identified by |
2476 | + #an internal ID; before you can create a reference to a window (a XWindow object) you |
2477 | + #have to obtain the internal ID of that window and pass it into XWindow.new. |
2478 | + #Or you use the class methods of this class, notably XWindow.active_window. |
2479 | + # |
2480 | + #Via the XWindow object you get you can manipulate a window in serveral ways, e.g. |
2481 | + #you can move or resize it. Some methods are not available on every window |
2482 | + #manager: XWindow.active_window, XWindow.desktop_num, XWindow.desktop_num=, XWindow.desktop, |
2483 | + #XWindow.desktop=, XWindow.from_active, #raise, #activate, #desktop, #desktop=. |
2484 | + #Some of them may be available, some not. On my machine (an Ubuntu Lucid) for |
2485 | + #example I can use active_window, desktop_num and #activate, but not #raise or #desktop=. |
2486 | + #Those methods are tagged with the sentence "Part of the EWMH standard XY". Not all |
2487 | + #parts of the EWMH standard are provided by every window manager. |
2488 | + # |
2489 | + #As of version 0.0.4 the way to search for window is about to change. The old version |
2490 | + #where you passed a hash with symbols has been deprecated (and you get warnings |
2491 | + #about this if you use it) in favor of passing those symbols as a rest argument. See |
2492 | + #XWindow.search for more details. |
2493 | + # |
2494 | + #You should also be aware of the fact that XDo is about to support Regexp objects |
2495 | + #in XWindow.search. In future versions (i.e. after the next minor release) strings |
2496 | + #*always* mean an exact title/class/whatever match. For parts, you have to use |
2497 | + #Regular Expressions. There is a culprit, though. +xdotool+ doesn't use Ruby's |
2498 | + #Regular Expressions engine Oniguruma and expects C-style regexps. I don't know |
2499 | + #about the differences - but if you're absolutely sure your window title matches |
2500 | + #that wonderful three-line extended regexp and +xdotool+ doesn't find it, you |
2501 | + #may email me at sutniuq@@gmx@net explaining which construct defeats +xdotool+. |
2502 | + #I will then setup a list over time which states which constructs don't work. |
2503 | + # |
2504 | + #Be <i>very careful</i> with the methods that are part of the two desktop EWMH standards. |
2505 | + #After I set the number of desktops and changed the current desktop, I had to reboot my |
2506 | + #system to get the original configuration back. I don't know if I'm not using +xdotool+ correct, |
2507 | + #but neither my library nor +xdotool+ itself could rescue my desktop settings. Btw, that's the |
2508 | + #reason why it's not in XDo's unit tests (but it should work; at least in one way...). |
2509 | + class XWindow |
2510 | + #The internal ID of the window. |
2511 | + attr_reader :id |
2512 | + |
2513 | + class << self |
2514 | + |
2515 | + #Checks if a window exists. |
2516 | + #===Parameters |
2517 | + #[+name+] The name of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2518 | + #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search. |
2519 | + #===Return value |
2520 | + #true or false. |
2521 | + #===Example |
2522 | + # p XWindow.exists?("gedit") #=> true |
2523 | + # p XWindow.exists?(/^gedit/) #=> false |
2524 | + #===Remarks |
2525 | + #It may be a good idea to pass :onlyvisible as a search parameter. |
2526 | + def exists?(name, *opts) |
2527 | + if opts.first.kind_of?(Hash) |
2528 | + warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.") |
2529 | + opts = opts.first.keys |
2530 | + end |
2531 | + |
2532 | + !search(name, *opts).empty? |
2533 | + end |
2534 | + |
2535 | + #Checks wheather the given ID exists or not. |
2536 | + #===Parameters |
2537 | + #[+id+] The ID to check for. |
2538 | + #===Return value |
2539 | + #true or false. |
2540 | + #===Example |
2541 | + # p XWindow.id_exits?(29360674) #=> true |
2542 | + # p XWindow.id_exists?(123456) #=> false |
2543 | + def id_exists?(id) |
2544 | + err = "" |
2545 | + Open3.popen3("#{XDo::XWININFO} -id #{id}"){|stdin, stdout, stderr| err << stderr.read} |
2546 | + return false unless err.empty? |
2547 | + return true |
2548 | + end |
2549 | + |
2550 | + #Waits for a window name to exist. |
2551 | + #===Parameters |
2552 | + #[+name+] The name of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2553 | + #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search. |
2554 | + #===Return value |
2555 | + #The ID of the newly appeared window. |
2556 | + #===Example |
2557 | + # #Wait for a window with "gedit" somewhere in it's title: |
2558 | + # XDo::XWindow.wait_for_window("gedit") |
2559 | + # #Wait for a window that ends with "ends_with_this": |
2560 | + # XDo::XWindow.wait_for_window(/ends_with_this$/) |
2561 | + # #It's useful to combine this method with the Timeout module: |
2562 | + # require "timeout" |
2563 | + # Timeout.timeout(3){XDo::XWindow.wait_for_window("gedit")} |
2564 | + #===Remarks |
2565 | + #Returns immediately if the window does already exist. |
2566 | + def wait_for_window(name, *opts) |
2567 | + if opts.first.kind_of?(Hash) |
2568 | + warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.") |
2569 | + opts = opts.first.keys |
2570 | + end |
2571 | + |
2572 | + loop{break if exists?(name, *opts);sleep(0.5)} |
2573 | + search(name, *opts).first |
2574 | + end |
2575 | + |
2576 | + #Waits for a window to close. |
2577 | + #===Parameters |
2578 | + #[+name+] The name of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2579 | + #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search. |
2580 | + #===Return value |
2581 | + #nil. |
2582 | + #===Example |
2583 | + # #Wait for a window with "gedit" somewhere in it's title |
2584 | + # XDo::XWindow.wait_for_close("gedit") |
2585 | + # #Waits for a window whose title ends with "ends_with_this": |
2586 | + # XDo::XWindow.wait_for_close(/ends_with_this$/) |
2587 | + # #It's quite useful to combine this method with the Timeout module: |
2588 | + # require "timeout" |
2589 | + # Timeout.timeout(3){XDo::XWindow.wait_for_close("gedit")} |
2590 | + def wait_for_close(name, *opts) |
2591 | + if opts.first.kind_of?(Hash) |
2592 | + warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.") |
2593 | + opts = opts.first.keys |
2594 | + end |
2595 | + |
2596 | + loop{break if !exists?(name, *opts);sleep(0.5)} |
2597 | + nil |
2598 | + end |
2599 | + |
2600 | + #Search for a window name to get the internal ID of a window. |
2601 | + #===Parameters |
2602 | + #[+str+] The name of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2603 | + #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. |
2604 | + #====Possible search parameters |
2605 | + #Copied from the +xdotool+ manpage: |
2606 | + #[class] Match against the window class. |
2607 | + #[classname] Match against the window classname. |
2608 | + #[name] Match against the window name. This is the same string that is displayed in the window titlebar. |
2609 | + #[onlyvisible] Show only visible windows in the results. This means ones with map state IsViewable. |
2610 | + #===Return value |
2611 | + #An array containing the IDs of all found windows or an empty array |
2612 | + #if none was found. |
2613 | + #===Example |
2614 | + # #Look for every window with "gedit" in it's title, class or classname |
2615 | + # XDo::XWindow.search("gedit") |
2616 | + # #Look for every window whose title, class or classname ends with "SciTE" |
2617 | + # XDo::XWindow.search(/SciTE$/) |
2618 | + # #Explicitly only search the titles of visible windows |
2619 | + # XDo::XWindow.search("gedit", :name, :onlyvisible) |
2620 | + def search(str, *opts) |
2621 | + if opts.first.kind_of?(Hash) |
2622 | + warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.") |
2623 | + opts = opts.first.keys |
2624 | + end |
2625 | + opts = [:name, :class, :classname] if opts.empty? |
2626 | + |
2627 | + #Allow Regular Expressions. Since I can't pass them directly to the command line, |
2628 | + #I need to get their source. Otherwise we want an exact match, therefore the line |
2629 | + #begin and line end anchors need to be set around the given string. |
2630 | + str = str.source if str.kind_of?(Regexp) |
2631 | + #TODO |
2632 | + #The following is the new behaviour that will be activated with the next minor version. |
2633 | + #See DEPRECATE.rdoc. |
2634 | + #str = if str.kind_of?(Regexp) |
2635 | + #str.source |
2636 | + #else |
2637 | + #"^#{str.to_str}$" |
2638 | + #end |
2639 | + |
2640 | + cmd = "#{XDo::XDOTOOL} search " |
2641 | + opts.each{|sym| cmd << "--#{sym} "} |
2642 | + cmd << "'" << str << "'" |
2643 | + #Don't handle errors since we want an empty array in case of an error |
2644 | + Open3.popen3(cmd){|stdin, stdout, stderr| stdin.close_write; stdout.read}.lines.to_a.collect{|l| l.strip.to_i} |
2645 | + end |
2646 | + |
2647 | + #Returns the internal ID of the currently focused window. |
2648 | + #===Parameters |
2649 | + #[+notice_children+] (false) If true, childwindows are noticed and you may get a child window instead of a toplevel window. |
2650 | + #===Return value |
2651 | + #The internal ID of the found window. |
2652 | + #===Raises |
2653 | + #[XError] Error invoking +xdotool+. |
2654 | + #===Example |
2655 | + # p XDo::XWindow.focused_window #=> 41943073 |
2656 | + # p XDo::XWindow.focused_window(true) #=> 41943074 |
2657 | + #===Remarks |
2658 | + #This method may find an invisible window, see active_window for a more reliable method. |
2659 | + def focused_window(notice_children = false) |
2660 | + err = "" |
2661 | + out = "" |
2662 | + Open3.popen3("#{XDo::XDOTOOL} getwindowfocus #{notice_children ? "-f" : ""}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
2663 | + raise(XDo::XError, err) unless err.empty? |
2664 | + return out.to_i |
2665 | + end |
2666 | + |
2667 | + #Returns the internal ID of the currently focused window. |
2668 | + #===Return value |
2669 | + #The ID of the found window. |
2670 | + #===Raises |
2671 | + #[XError] Error invoking +xdotool+. |
2672 | + #===Example |
2673 | + # p XDo::XWindow.active_window #=> 41943073 |
2674 | + #===Remarks |
2675 | + #This method is more reliable than #focused_window, but never finds an invisible window. |
2676 | + # |
2677 | + #Part of the EWMH standard ACTIVE_WINDOW. |
2678 | + def active_window |
2679 | + err = "" |
2680 | + out = "" |
2681 | + Open3.popen3("#{XDo::XDOTOOL} getactivewindow"){|stdin, stdout, stderr| out = stdout.read; err = stderr.read} |
2682 | + raise(XDo::XError, err) unless err.empty? |
2683 | + return Integer(out) |
2684 | + end |
2685 | + |
2686 | + #Set the number of working desktops. |
2687 | + #===Parameters |
2688 | + #[+num+] The number of desktops you want to exist. |
2689 | + #===Return value |
2690 | + #+num+. |
2691 | + #===Raises |
2692 | + #[XError] Error invoking +xdotool+. |
2693 | + #===Example |
2694 | + # XDo::XWindow.desktop_num = 2 |
2695 | + #===Remarks |
2696 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
2697 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
2698 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
2699 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
2700 | + # |
2701 | + #Part of the EWMH standard WM_DESKTOP. |
2702 | + def desktop_num=(num) |
2703 | + err = "" |
2704 | + Open3.popen3("#{XDo::XDOTOOL} set_num_desktops #{num}"){|stdin, stdout, stderr| err << stderr.read} |
2705 | + raise(XDo::Error, err) unless err.empty? |
2706 | + num |
2707 | + end |
2708 | + |
2709 | + #Get the number of working desktops. |
2710 | + #===Return value |
2711 | + #The number of desktops. |
2712 | + #===Raises |
2713 | + #[XError] Error invoking +xdotool+. |
2714 | + #===Example |
2715 | + # p XDo::XWindow.desktop_num = 1 |
2716 | + #===Remarks |
2717 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
2718 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
2719 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
2720 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
2721 | + # |
2722 | + #Part of the EWMH standard WM_DESKTOP. |
2723 | + def desktop_num |
2724 | + err = "" |
2725 | + out = "" |
2726 | + Open3.popen3("#{XDo::XDOTOOL} get_num_desktops"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
2727 | + raise(XDo::XError, err) unless err.empty? |
2728 | + Integer(out) |
2729 | + end |
2730 | + |
2731 | + #Change the view to desktop +num+. |
2732 | + #===Parameters |
2733 | + #[+num+] The 0-based index of the desktop you want to switch to. |
2734 | + #===Return value |
2735 | + #+num+. |
2736 | + #===Raises |
2737 | + #[XError] Error invoking +xdotool+. |
2738 | + #===Example |
2739 | + # XDo::XWindow.desktop = 1 |
2740 | + #===Remarks |
2741 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
2742 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
2743 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
2744 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
2745 | + # |
2746 | + #Part of the EWMH standard CURRENT_DESKTOP. |
2747 | + def desktop=(num) |
2748 | + err = "" |
2749 | + Open3.popen3("#{XDo::XDOTOOL} set_desktop #{num}"){|stdin, stdout, stderr| err << stderr.read} |
2750 | + raise(XDo::XError, err) unless err.empty? |
2751 | + num |
2752 | + end |
2753 | + |
2754 | + #Returns the number of the active desktop. |
2755 | + #===Return value |
2756 | + #The number of the currently shown desktop. |
2757 | + #===Raises |
2758 | + #[XError] Error invoking +xdotool+. |
2759 | + #===Example |
2760 | + # p XDo::XWindow.desktop #=> 0 |
2761 | + #===Remarks |
2762 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
2763 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
2764 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
2765 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
2766 | + # |
2767 | + #Part of the EWMH standard CURRENT_DESKTOP. |
2768 | + def desktop |
2769 | + err = "" |
2770 | + out = "" |
2771 | + Open3.popen3("#{XDo::XDOTOOL} get_desktop"){|stdin, stdout, stderr| out = stdout.read; err = stderr.read} |
2772 | + raise(XDo::XError, err) unless err.empty? |
2773 | + Integer(out) |
2774 | + end |
2775 | + |
2776 | + #Creates a XWindow by calling search with the given parameters. |
2777 | + #The window is created from the first ID found. |
2778 | + #===Parameters |
2779 | + #[+name+] The name of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2780 | + #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search. |
2781 | + #===Return value |
2782 | + #The created XWindow object. |
2783 | + #===Raises |
2784 | + #[XError] Error invoking +xdotool+. |
2785 | + #===Example |
2786 | + # #Exact title/class/classname match |
2787 | + # xwin = XDo::XWindow.from_search("xwindow.rb - SciTE") |
2788 | + # #Part match via regexp |
2789 | + # xwin = XDo::XWindow.from_search(/SciTE/) |
2790 | + # #Part match via string - DEPRECATED. |
2791 | + # xwin = XDo::XWindow.from_search("SciTE") |
2792 | + # #Only search the window classes |
2793 | + # xwin = XDo::XWindow.from_search(/SciTE/, :class) |
2794 | + def from_search(name, *opts) |
2795 | + if opts.first.kind_of?(Hash) |
2796 | + warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.") |
2797 | + opts = opts.first.keys |
2798 | + end |
2799 | + |
2800 | + ids = search(name, *opts) |
2801 | + raise(XDo::XError, "The window '#{name}' wasn't found!") if ids.empty? |
2802 | + new(ids.first) |
2803 | + end |
2804 | + |
2805 | + #_Deprecated_. Use XWindow.from_search or XWindow.from_title instead. |
2806 | + def from_name(name, *opts) |
2807 | + warn("#{caller.first}: Deprecation Warning: ::from_name is deprecated. Use ::from_search if you want the old behaviour with the ability to specify all search parameters, or ::from_title if you just want to look through the window titles.") |
2808 | + from_search(name, *opts) |
2809 | + end |
2810 | + |
2811 | + #Same as XWindow.from_search, but only looks for the window's titles to match. |
2812 | + #===Parameters |
2813 | + #[+title+] The title of the window to look for. Either a string or a Regular Expression; however, there's no guaranty that +xdotool+ gets the regexp right. Simple ones should work, though. |
2814 | + #===Return value |
2815 | + #A XWindow object made up from the first window ID found. |
2816 | + #===Raises |
2817 | + #[XError] Error invoking +xdotool+. |
2818 | + #===Example |
2819 | + # #Exact string match |
2820 | + # xwin = XDo::XWindow.from_title("xwindow.rb - SciTE") |
2821 | + # #Part match via regexp |
2822 | + # xwin = XDo::XWindow.from_title(/SciTE/) |
2823 | + # #Part match via string - DEPRECATED. |
2824 | + # xwin = XDo::XWindow.from_title("SciTE") |
2825 | + def from_title(title) |
2826 | + from_search(title, :name) |
2827 | + end |
2828 | + |
2829 | + #Creates a XWindow by calling XWindow.focused_window with the given parameter. |
2830 | + #===Parameters |
2831 | + #[+notice_children+] (false) If true, you may get a child window as the active window. |
2832 | + #===Return value |
2833 | + #The newly created XWindow objects. |
2834 | + #===Example |
2835 | + # xwin = XDo::XWindow.from_focused |
2836 | + #===Remarks |
2837 | + #The XWindow.focused_window method is a bit dangerous, since it may |
2838 | + #find an invisible window. Use XWindow.from_active if you don't want that. |
2839 | + def from_focused(notice_childs = false) |
2840 | + new(focused_window(notice_childs)) |
2841 | + end |
2842 | + |
2843 | + #Creates a XWindow by calling active_window. |
2844 | + #===Return value |
2845 | + #The newly created XWindow object. |
2846 | + #===Example |
2847 | + # xwin = XDo::XWindow.from_active |
2848 | + #===Remarks |
2849 | + #This method does not find invisible nor child windows; if you want that, |
2850 | + #you should take a look at XWindow.from_focused. |
2851 | + def from_active |
2852 | + new(active_window) |
2853 | + end |
2854 | + |
2855 | + #Returns the ID of the root window. |
2856 | + #===Return value |
2857 | + #The ID of the root window. |
2858 | + #===Example |
2859 | + # p XDo::XWindow.root_id #=> 346 |
2860 | + def root_id |
2861 | + out = "" |
2862 | + err = "" |
2863 | + Open3.popen3("#{XDo::XWININFO} -root"){|stdin, stdout, stderr| out << stdout.read.strip; err << stderr.read.strip} |
2864 | + Kernel.raise(XDo::XError, err) unless err.empty? |
2865 | + Integer(out.lines.to_a[0].match(/Window id:(.*?)\(/)[1].strip) |
2866 | + end |
2867 | + |
2868 | + #Creates a XWindow refering to the root window. |
2869 | + #===Return value |
2870 | + #The newly created XWindow object. |
2871 | + #===Example |
2872 | + # rwin = XDo::XWindow.from_root |
2873 | + def from_root |
2874 | + new(root_id) |
2875 | + end |
2876 | + |
2877 | + #Creates a invalid XWindow. |
2878 | + #===Return value |
2879 | + #The newly created XWindow object. |
2880 | + #===Example |
2881 | + # nwin = XDo::XWindow.from_null |
2882 | + #===Remarks |
2883 | + #The handle the returned XWindow object uses is zero and |
2884 | + #therefore invalid. You can't call #move, #resize or other |
2885 | + #methods on it, but it may be useful for unsetting focus. |
2886 | + #See also the XWindow.unfocus method. |
2887 | + def from_null |
2888 | + new(0) #Zero never is a valid window ID. Even the root window has another ID. |
2889 | + end |
2890 | + |
2891 | + #Unsets the input focus by setting it to the invalid |
2892 | + #NULL window. |
2893 | + #===Parameters |
2894 | + #[+sync+] (true) If true, this method blocks until the input focus has been unset. |
2895 | + #===Return value |
2896 | + #nil. |
2897 | + #===Example |
2898 | + # win = XDo::XWindow.from_active |
2899 | + # win.focus |
2900 | + # XDo::XWindow.unfocus |
2901 | + def unfocus(sync = true) |
2902 | + from_null.focus(sync) |
2903 | + end |
2904 | + |
2905 | + #Deprecated. |
2906 | + def desktop_name=(name) |
2907 | + warn("#{caller.first}: Deprecation warning: XWindow.desktop_name= doesn't do anything anymore.") |
2908 | + end |
2909 | + |
2910 | + #Deprecated. |
2911 | + def desktop_name |
2912 | + warn("#{caller.first}: Deprecation warning: XWindow.desktop_name doesn't do anything anymore.") |
2913 | + "x-nautilus-desktop" |
2914 | + end |
2915 | + |
2916 | + #Deprecated. Just calls XWindow.unfocus internally. |
2917 | + def focus_desktop |
2918 | + warn("#{caller.first}: Deprecation warning: XWindow.focus_desktop is deprecated. Use XWindow.unfocus instead.") |
2919 | + unfocus |
2920 | + end |
2921 | + alias activate_desktop focus_desktop |
2922 | + |
2923 | + #Minimize all windows (or restore, if already) by sending [CTRL]+[ALT]+[D]. |
2924 | + #Available after requireing "xdo/keyboard". |
2925 | + #===Return value |
2926 | + #Undefined. |
2927 | + #===Raises |
2928 | + #[NotImplementedError] You didn't require 'xdo/keyboard'. |
2929 | + #===Example |
2930 | + # #Everything will be minimized: |
2931 | + # XDo::XWindow.toggle_minimize_all |
2932 | + # #And now we'll restore everything. |
2933 | + # XDo::XWindow.toggle_minimize_all |
2934 | + def toggle_minimize_all |
2935 | + raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard |
2936 | + XDo::Keyboard.ctrl_alt_d |
2937 | + end |
2938 | + |
2939 | + #Minimizes the active window. There's no way to restore a specific minimized window. |
2940 | + #Available after requireing "xdo/keyboard". |
2941 | + #===Return value |
2942 | + #Undefined. |
2943 | + #===Raises |
2944 | + #[NotImplementedError] You didn't require 'xdo/keyboard'. |
2945 | + #===Example |
2946 | + # XDo::XWindow.minimize |
2947 | + def minimize |
2948 | + raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard |
2949 | + XDo::Keyboard.key("Alt+F9") |
2950 | + end |
2951 | + |
2952 | + #Maximize or normalize the active window if already maximized. |
2953 | + #Available after requireing "xdo/keyboard". |
2954 | + #===Return value |
2955 | + #Undefined. |
2956 | + #===Raises |
2957 | + #[NotImplementedError] You didn't require 'xdo/keyboard'. |
2958 | + #===Example |
2959 | + # XDo::XWindow.minimize |
2960 | + # XDo::XWindow.toggle_maximize |
2961 | + def toggle_maximize |
2962 | + raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard |
2963 | + XDo::Keyboard.key("Alt+F10") |
2964 | + end |
2965 | + |
2966 | + end |
2967 | + |
2968 | + ## |
2969 | + # :method: title= |
2970 | + #call-seq: |
2971 | + # title = str |
2972 | + # name = str |
2973 | + # |
2974 | + #Changes a window's title. |
2975 | + #===Parameters |
2976 | + #[+str+] The new title. |
2977 | + #===Return value |
2978 | + #+str+. |
2979 | + #===Raises |
2980 | + #[XError] Error invoking +xdotool+. |
2981 | + #===Example |
2982 | + # xwin.title = "Ruby is awesome!" |
2983 | + |
2984 | + ## |
2985 | + # :method: icon_title= |
2986 | + #call-seq: |
2987 | + # icon_title = str |
2988 | + # icon_name = str |
2989 | + # |
2990 | + #Changes the window's icon title, i.e. the string that is displayed in |
2991 | + #the task bar or panel where all open windows show up. |
2992 | + #===Parameters |
2993 | + #[+str+] The string you want to set. |
2994 | + #===Return value |
2995 | + #+str+. |
2996 | + #===Raises |
2997 | + #[XError] Error invoking +xdotool+. |
2998 | + #===Example |
2999 | + # xwin.icon_title = "This is minimized." |
3000 | + |
3001 | + ## |
3002 | + # :method: classname= |
3003 | + #call-seq: |
3004 | + # classname = str |
3005 | + # |
3006 | + #Sets a window's classname. |
3007 | + #===Parameters |
3008 | + #[+str+] The window's new classname. |
3009 | + #===Return value |
3010 | + #+str+. |
3011 | + #===Raises |
3012 | + #[XError] Error invoking +xdotool+. |
3013 | + #===Example |
3014 | + # xwin.classname = "MyNewClass" |
3015 | + |
3016 | + #Creates a new XWindow object from an internal ID. |
3017 | + #===Parameters |
3018 | + #[+id+] The internal ID to create the window from. |
3019 | + #===Return value |
3020 | + #The newly created XWindow object. |
3021 | + #===Example |
3022 | + # id = XWindow.search(/edit/)[1] |
3023 | + # xwin = XWindow.new(id) |
3024 | + #===Remarks |
3025 | + #See also many class methods of the XWindow class which allow |
3026 | + #you to forget about the internal ID of a window. |
3027 | + def initialize(id) |
3028 | + @id = id.to_i |
3029 | + end |
3030 | + |
3031 | + #Human-readable output of form |
3032 | + # <XDo::XWindow: "title" (window_id)> |
3033 | + def inspect |
3034 | + %Q|<#{self.class}: "#{title}" (#{id})>| |
3035 | + end |
3036 | + |
3037 | + #Set the size of a window. |
3038 | + #===Parameters |
3039 | + #[+width+] The new width, usually in pixels. |
3040 | + #[+height+] The new height, usually in pixels. |
3041 | + #[+use_hints+] (false) If true, window sizing hints are used if they're available. This is usually done when resizing terminal windows to a specific number of rows and columns. |
3042 | + #[+sync+] (true) If true, this method blocks until the window has finished resizing. |
3043 | + #===Return value |
3044 | + #Undefined. |
3045 | + #===Raises |
3046 | + #[XError] Error executing +xdotool+. |
3047 | + #===Example |
3048 | + # #Resize a window to 400x300px |
3049 | + # xwin.resize(400, 300) |
3050 | + # #Resize a terminal window to 100 rows and 100 columns |
3051 | + # xtermwin.resize(100, 100, true) |
3052 | + #===Remarks |
3053 | + #This has no effect on maximized winwows. |
3054 | + def resize(width, height, use_hints = false, sync = true) |
3055 | + err = "" |
3056 | + opts = [] |
3057 | + opts << "--usehints" if use_hints |
3058 | + opts << "--sync" if sync |
3059 | + Open3.popen3("#{XDo::XDOTOOL} windowsize #{opts.join(" ")} #{@id} #{width} #{height}"){|stdin, stdout, stderr| err << stderr.read} |
3060 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3061 | + end |
3062 | + |
3063 | + #Moves a window. +xdotool+ is not really exact with the coordinates, |
3064 | + #special windows like Ubuntu's panels make it placing wrong. |
3065 | + #===Parameters |
3066 | + #[+x+] The goal X coordinate. |
3067 | + #[+y+] The goal Y coordinate. |
3068 | + #[+sync+] (true) If true, this method blocks until the window has finished moving. |
3069 | + #===Return value |
3070 | + #Undefined. |
3071 | + #===Raises |
3072 | + #[XError] Error executing +xdotool+. |
3073 | + #===Example |
3074 | + # xwin.move(100, 100) |
3075 | + # p xwin.abs_position #=> [101, 101] |
3076 | + def move(x, y, sync = true) |
3077 | + err = "" |
3078 | + opts = [] |
3079 | + opts << "--sync" if sync |
3080 | + Open3.popen3("#{XDo::XDOTOOL} windowmove #{opts.join(" ")} #{@id} #{x} #{y}"){|stdin, stdout, stderr| err << stderr.read} |
3081 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3082 | + end |
3083 | + |
3084 | + #Set the input focus to the window (but don't bring it to the front). |
3085 | + #===Parameters |
3086 | + #[+sync+] (true) If true, this method blocks until the window got the input focus. |
3087 | + #===Return value |
3088 | + #Undefined. |
3089 | + #===Raises |
3090 | + #[XError] Error invoking +xdotool+. |
3091 | + #===Example |
3092 | + # xwin.focus |
3093 | + #===Remarks |
3094 | + #This method may not work on every window manager. You should use |
3095 | + ##activate, which is supported by more window managers. |
3096 | + def focus(sync = true) |
3097 | + err = "" |
3098 | + opts = [] |
3099 | + opts << "--sync" if sync |
3100 | + Open3.popen3("#{XDo::XDOTOOL} windowfocus #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3101 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3102 | + end |
3103 | + |
3104 | + #The window loses the input focus by setting it to an invalid window. |
3105 | + #Parameters |
3106 | + #[+sync+] (true) If true, this method blocks until the focus has been set to nothing. |
3107 | + #===Return value |
3108 | + #Undefined. |
3109 | + #===Example |
3110 | + # xwin.focus |
3111 | + # xwin.unfocus |
3112 | + def unfocus(sync = true) |
3113 | + XDo::XWindow.unfocus(sync) |
3114 | + end |
3115 | + |
3116 | + #Maps a window to the screen (makes it visible). |
3117 | + #===Parameters |
3118 | + #[+sync+] (true) If true, this method blocks until the window has been mapped. |
3119 | + #===Return value |
3120 | + #Undefined. |
3121 | + #===Raises |
3122 | + #[XError] Error invoking +xdotool+. |
3123 | + #===Example |
3124 | + # xwin.unmap #Windows are usually mapped |
3125 | + # xwin.map |
3126 | + def map(sync = true) |
3127 | + err = "" |
3128 | + opts = [] |
3129 | + opts << "--sync" if sync |
3130 | + Open3.popen3("#{XDo::XDOTOOL} windowmap #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3131 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3132 | + end |
3133 | + |
3134 | + #Unmap a window from the screen (make it invisible). |
3135 | + #===Parameters |
3136 | + #[+sync+] (true) If true, this method blocks until the window has been unmapped. |
3137 | + #===Return value |
3138 | + #Undefined. |
3139 | + #===Raises |
3140 | + #[XError] Error executing +xdotool+. |
3141 | + #===Example |
3142 | + # xwin.unmap |
3143 | + def unmap(sync = true) |
3144 | + err = "" |
3145 | + opts = [] |
3146 | + opts << "--sync" if sync |
3147 | + Open3.popen3("#{XDo::XDOTOOL} windowunmap #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3148 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3149 | + end |
3150 | + |
3151 | + #Bring a window to the front (but don't give it the input focus). |
3152 | + #Not implemented in all window managers. |
3153 | + #===Return value |
3154 | + #Undefined. |
3155 | + #===Raises |
3156 | + #[XError] Error executing +xdotool+. |
3157 | + #===Example |
3158 | + # xwin.raise |
3159 | + def raise |
3160 | + err = "" |
3161 | + Open3.popen3("#{XDo::XDOTOOL} windowraise #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3162 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3163 | + end |
3164 | + |
3165 | + #Activate a window. That is, bring it to top and give it the input focus. |
3166 | + #===Parameters |
3167 | + #[+sync+] (true) If true, this method blocks until the window has been activated. |
3168 | + #===Return value |
3169 | + #Undefined. |
3170 | + #===Raises |
3171 | + #[XError] Error executing +xdotool+. |
3172 | + #===Example |
3173 | + # xwin.activate |
3174 | + #===Remarks |
3175 | + #This is the recommanded method to give a window the input focus, since |
3176 | + #it works on more window managers than #focus and also works across |
3177 | + #desktops. |
3178 | + # |
3179 | + #Part of the EWMH standard ACTIVE_WINDOW. |
3180 | + def activate(sync = true) |
3181 | + tried_focus = false |
3182 | + begin |
3183 | + err = "" |
3184 | + opts = [] |
3185 | + opts << "--sync" if sync |
3186 | + Open3.popen3("#{XDo::XDOTOOL} windowactivate #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3187 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3188 | + rescue XDo::XError |
3189 | + #If no window is active, xdotool's windowactivate fails, |
3190 | + #because it tries to determine which is the currently active window. |
3191 | + unless tried_focus |
3192 | + tried_focus = true |
3193 | + focus |
3194 | + retry |
3195 | + else |
3196 | + raise |
3197 | + end |
3198 | + end |
3199 | + end |
3200 | + |
3201 | + #Move a window to a desktop. |
3202 | + #===Parameters |
3203 | + #[+num+] The 0-based index of the desktop you want the window to move to. |
3204 | + #===Return value |
3205 | + #Undefined. |
3206 | + #===Raises |
3207 | + #[XError] Error executing +xdotool+. |
3208 | + #===Example |
3209 | + # xwin.desktop = 3 |
3210 | + #===Remarks |
3211 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
3212 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
3213 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
3214 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
3215 | + # |
3216 | + #Part of the EWMH standard CURRENT_DESKTOP. |
3217 | + def desktop=(num) |
3218 | + err = "" |
3219 | + Open3.popen3("#{XDo::XDOTOOL} set_desktop_for_window #{@id} #{num}"){|stdin, stdout, stderr| err << stderr.read} |
3220 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3221 | + end |
3222 | + |
3223 | + #Get the desktop the window is on. |
3224 | + #===Return value |
3225 | + #The 0-based index of the desktop this window resides on. |
3226 | + #===Raises |
3227 | + #[XError] Error executing +xdotool+. |
3228 | + #===Example |
3229 | + # p xwin.desktop #=> 0 |
3230 | + #===Remarks |
3231 | + #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only |
3232 | + #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4). |
3233 | + #That's kind of cheating, but I have not yet find out why it is like that. Maybe it's due to the nice cube rotating effect? |
3234 | + #That's the reason, why the desktop-related methods don't work with Ubuntu. |
3235 | + # |
3236 | + #Part of the EWMH standard CURRENT_DESKTOP. |
3237 | + def desktop |
3238 | + err = "" |
3239 | + out = "" |
3240 | + Open3.popen3("#{XDo::XDOTOOL} get_desktop_for_window #{@id}"){|stdin, stdout, stderr| out = stdout.read; err << stderr.read} |
3241 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3242 | + Integer(out) |
3243 | + end |
3244 | + |
3245 | + #The title of the window or nil if it doesn't have a title. |
3246 | + #===Return value |
3247 | + #The window's title, encoded as UTF-8, or nil if the window doesn't have a title. |
3248 | + #===Raises |
3249 | + #[XError] Error executing +xwininfo+. |
3250 | + #===Example |
3251 | + # p xwin.title #=> "xwindow.rb SciTE" |
3252 | + def title |
3253 | + err = "" |
3254 | + out = "" |
3255 | + if @id == XWindow.root_id #This is the root window |
3256 | + return "(the root window)" |
3257 | + elsif @id.zero? |
3258 | + return "(NULL window)" |
3259 | + else |
3260 | + Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3261 | + end |
3262 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3263 | + title = out.strip.lines.to_a[0].match(/"(.*)"/)[1] rescue Kernel.raise(XDo::XError, "No window with ID #{@id} found!") |
3264 | + return title #Kann auch nil sein, dann ist das Fenster namenlos. |
3265 | + end |
3266 | + |
3267 | + #The absolute position of the window on the screen. |
3268 | + #===Return value |
3269 | + #A two-element array of form <tt>[x, y]</tt>. |
3270 | + #===Raises |
3271 | + #[XError] Error executing +xwininfo+. |
3272 | + #===Example |
3273 | + # p xwin.abs_position #=> [0, 51] |
3274 | + def abs_position |
3275 | + out = "" |
3276 | + err = "" |
3277 | + Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3278 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3279 | + out = out.strip.lines.to_a |
3280 | + x = out[2].match(/:\s+(\d+)/)[1] |
3281 | + y = out[3].match(/:\s+(\d+)/)[1] |
3282 | + [x.to_i, y.to_i] |
3283 | + end |
3284 | + alias position abs_position |
3285 | + |
3286 | + #The position of the window relative to it's parent window. |
3287 | + #===Return value |
3288 | + #A two-element array of form <tt>[x, y]</tt>. |
3289 | + #===Raises |
3290 | + #[XError] Error executing +xdotool+. |
3291 | + #===Example |
3292 | + # p xwin.rel_position => [0, 51] |
3293 | + def rel_position |
3294 | + out = "" |
3295 | + err = "" |
3296 | + Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3297 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3298 | + out = out.strip.lines.to_a |
3299 | + x = out[4].match(/:\s+(\d+)/)[1] |
3300 | + y = out[5].match(/:\s+(\d+)/)[1] |
3301 | + [x.to_i, y.to_i] |
3302 | + end |
3303 | + |
3304 | + #The size of the window. |
3305 | + #===Return value |
3306 | + #A two-element array of form <tt>[width, height]</tt>. |
3307 | + #===Raises |
3308 | + #[XError] Error executing +xwininfo+. |
3309 | + #===Example |
3310 | + # p xwin.size #=> [1280, 948] |
3311 | + def size |
3312 | + out = "" |
3313 | + err = "" |
3314 | + Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3315 | + out = out.strip.lines.to_a |
3316 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3317 | + width = out[6].match(/:\s+(\d+)/)[1] |
3318 | + height = out[7].match(/:\s+(\d+)/)[1] |
3319 | + [width.to_i, height.to_i] |
3320 | + end |
3321 | + |
3322 | + #true if the window is mapped to the screen. |
3323 | + #===Return value |
3324 | + #nil if the window is not mapped, an integer value otherwise. |
3325 | + #===Raises |
3326 | + #[XError] Error executing +xwininfo+. |
3327 | + #===Example |
3328 | + # p xwin.visible? #=> 470 |
3329 | + # xwin.unmap |
3330 | + # p xwin.visible? #=> nil |
3331 | + def visible? |
3332 | + err = "" |
3333 | + out = "" |
3334 | + Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3335 | + out = out.strip |
3336 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3337 | + return out =~ /IsViewable/ |
3338 | + end |
3339 | + |
3340 | + #Returns true if the window exists. |
3341 | + #===Return value |
3342 | + #true or false. |
3343 | + #===Example |
3344 | + # p xwin.exists? #=> true |
3345 | + def exists? |
3346 | + XDo::XWindow.id_exists?(@id) |
3347 | + end |
3348 | + |
3349 | + #Closes a window by activating it and then sending [ALT] + [F4]. |
3350 | + #===Return value |
3351 | + #nil. |
3352 | + #===Raises |
3353 | + #[NotImplementedError] You didn't require "xdo/keyboard". |
3354 | + #===Example |
3355 | + # xwin.close |
3356 | + #===Remarks |
3357 | + #A program could ask to save data. |
3358 | + # |
3359 | + #Use #kill! to kill the process running the window. |
3360 | + # |
3361 | + #Available after requireing "xdo/keyboard". |
3362 | + def close |
3363 | + Kernel.raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard |
3364 | + activate |
3365 | + XDo::Keyboard.char("Alt+F4") |
3366 | + sleep 0.5 |
3367 | + nil |
3368 | + end |
3369 | + |
3370 | + #More aggressive variant of #close. Think of +close!+ as |
3371 | + #the middle between #close and #kill!. It first tries |
3372 | + #to close the window by calling #close and if that |
3373 | + #does not succeed (within +timeout+ seconds), it will call #kill!. |
3374 | + #===Paramters |
3375 | + #[+timeout+] (2) The time to wait before using #kill!, in seconds. |
3376 | + #===Return value |
3377 | + #Undefined. |
3378 | + #===Raises |
3379 | + #[XError] Error executing +xkill+. |
3380 | + #===Example |
3381 | + # xwin.close! |
3382 | + #===Remarks |
3383 | + #Available after requireing "xdo/keyboard". |
3384 | + def close!(timeout = 2) |
3385 | + Kernel.raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard |
3386 | + #Try to close normally |
3387 | + close |
3388 | + #Check if it's deleted |
3389 | + if exists? |
3390 | + #If not, wait some seconds and then check again |
3391 | + sleep timeout |
3392 | + if exists? |
3393 | + #If it's not deleted after some time, force it to close. |
3394 | + kill! |
3395 | + end |
3396 | + end |
3397 | + end |
3398 | + |
3399 | + #Kills the process that runs a window. The window will be |
3400 | + #terminated immediatly, if that isn't what you want, have |
3401 | + #a look at #close. |
3402 | + #===Return value |
3403 | + #nil. |
3404 | + #===Raises |
3405 | + #[XError] Error executing +xkill+. |
3406 | + #===Example |
3407 | + # xwin.kill! |
3408 | + def kill! |
3409 | + out = "" |
3410 | + err = "" |
3411 | + Open3.popen3("#{XDo::XKILL} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read} |
3412 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3413 | + nil |
3414 | + end |
3415 | + |
3416 | + #Returns the window's internal ID. |
3417 | + #===Return value |
3418 | + #An integer describing the window's internal ID. |
3419 | + #===Example |
3420 | + # p xwin.to_i #=> 29361095 |
3421 | + def to_i |
3422 | + @id |
3423 | + end |
3424 | + |
3425 | + #Returns a window's title. |
3426 | + #===Return value |
3427 | + #The window's title. |
3428 | + #===Example |
3429 | + # p xwin.to_s #=> "xwindow.rb * SciTE" |
3430 | + def to_s |
3431 | + title |
3432 | + end |
3433 | + |
3434 | + #true if the internal ID is zero. |
3435 | + #===Return value |
3436 | + #true or false. |
3437 | + #===Example |
3438 | + # p xwin.zero? #=> false |
3439 | + def zero? |
3440 | + @id.zero? |
3441 | + end |
3442 | + |
3443 | + #true if the internal ID is not zero. |
3444 | + #===Return value |
3445 | + #nil or the internal ID. |
3446 | + #===Example |
3447 | + # p xwin.nonzero? #=> 29361095 |
3448 | + def nonzero? |
3449 | + @id.nonzero? |
3450 | + end |
3451 | + |
3452 | + [:"name=", :"icon_name=", :"classname="].each do |sym| |
3453 | + define_method(sym) do |str| |
3454 | + set_window(sym.to_s[0..-2].gsub("_", "-"), str.encode("UTF-8")) |
3455 | + str |
3456 | + end |
3457 | + end |
3458 | + alias title= name= |
3459 | + alias icon_title= icon_name= |
3460 | + |
3461 | + private |
3462 | + |
3463 | + #Calls +xdotool+'s set_window command with the given options. |
3464 | + def set_window(option, value) |
3465 | + err = "" |
3466 | + Open3.popen3("#{XDOTOOL} set_window --#{option} '#{value}' #{@id}"){|stdin, stdout, stderr| err << stderr.read} |
3467 | + Kernel.raise(XDo::XError, err) unless err.empty? |
3468 | + end |
3469 | + |
3470 | + end |
3471 | + |
3472 | +end |
3473 | |
3474 | === renamed file 'tests/run-with-xvfb.sh' => 'tests/misc/run-with-xvfb.sh' |
3475 | === renamed file 'tests/unitytestmacro.h' => 'tests/misc/unitytestmacro.h' |
3476 | === added directory 'tests/other' |
3477 | === added directory 'tests/panel' |
3478 | === added directory 'tests/places' |
3479 | === added file 'tests/run-tests.rb' |
3480 | --- tests/run-tests.rb 1970-01-01 00:00:00 +0000 |
3481 | +++ tests/run-tests.rb 2011-12-15 16:55:31 +0000 |
3482 | @@ -0,0 +1,74 @@ |
3483 | +#!/usr/bin/env ruby1.8 |
3484 | +=begin |
3485 | +/* |
3486 | + * This file is part of unity-2d |
3487 | + * |
3488 | + * Copyright 2011 Canonical Ltd. |
3489 | + * |
3490 | + * Authors: |
3491 | + * - Gerry Boland <gerry.boland@canonical.com> |
3492 | + * |
3493 | + * This program is free software; you can redistribute it and/or modify |
3494 | + * it under the terms of the GNU General Public License as published by |
3495 | + * the Free Software Foundation; version 3. |
3496 | + * |
3497 | + * This program is distributed in the hope that it will be useful, |
3498 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
3499 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3500 | + * GNU General Public License for more details. |
3501 | + * |
3502 | + * You should have received a copy of the GNU General Public License |
3503 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
3504 | + */ |
3505 | +=end |
3506 | + |
3507 | +# Testability test suite execution script |
3508 | +# |
3509 | +# Run all tests with |
3510 | +# $ ruby run-tests.rb |
3511 | + |
3512 | +# Check script being run by Ruby 1.8.x, later versions not supported by TDriver |
3513 | +abort("Aborted! Ruby 1.9 not supported, use 1.8") unless RUBY_VERSION < '1.9' |
3514 | + |
3515 | +# Add ./misc/lib to the list of library locations - need to calculate absolute path |
3516 | +require 'pathname' |
3517 | +$library_path = File.expand_path(File.dirname(__FILE__)) + '/misc/lib' |
3518 | +$LOAD_PATH.unshift $library_path |
3519 | + |
3520 | +# If cmake was called, obtain the path to the built binary directory. If not, we test the |
3521 | +# installed applications instead |
3522 | +binary_dir_file = $library_path + '/../binary_dir.txt' |
3523 | +if File.exists?(binary_dir_file) |
3524 | + binary_dir = File.open(binary_dir_file).first.strip |
3525 | + puts 'Running tests on applications contained within ' + binary_dir |
3526 | + UNITY_2D_LAUNCHER = binary_dir + '/launcher/app/unity-2d-launcher' |
3527 | + UNITY_2D_PANEL = binary_dir + '/panel/app/unity-2d-panel' |
3528 | + UNITY_2D_PLACES = binary_dir + '/places/app/unity-2d-places' |
3529 | + UNITY_2D_SPREAD = binary_dir + '/spread/app/unity-2d-spread' |
3530 | +else |
3531 | + puts 'NOTICE: source not configured, tests will be carried out on *installed* applications!' |
3532 | + UNITY_2D_LAUNCHER = 'unity-2d-launcher' |
3533 | + UNITY_2D_PANEL = 'unity-2d-panel' |
3534 | + UNITY_2D_PLACES = 'unity-2d-places' |
3535 | + UNITY_2D_SPREAD = 'unity-2d-spread' |
3536 | +end |
3537 | + |
3538 | +# The following line includes the complete tdriver environment |
3539 | +require 'tdriver' |
3540 | +include TDriverVerify |
3541 | + |
3542 | +# Require unit test framework: This enables execution of test cases and also includes assertions (Test::Unit::Assertions) |
3543 | +require 'testhelper' |
3544 | + |
3545 | +# List of directories in which to search for test cases |
3546 | +test_directories = ['launcher', 'panel', 'places', 'spread', 'window-manager', 'other'] |
3547 | + |
3548 | +# Only run scan for tests if this script is directly called |
3549 | +if __FILE__ == $0 |
3550 | + $INIT_COMPLETED = true # Prevent this file being included by test cases |
3551 | + |
3552 | + # Scan through the above directories and execute test cases contained. |
3553 | + test_directories.each do | directory | |
3554 | + Dir["#{directory}/*.rb"].each { |testCase| require testCase} |
3555 | + end |
3556 | +end |
3557 | |
3558 | === added directory 'tests/spread' |
3559 | === added directory 'tests/window-manager' |
A quick guide to setting up your machine to for testing this MR:
$ sudo add-apt-repo ppa:gerboland/ testability testability- driver- qt-sut- plugin testability- visualizer \ ruby1.8 xdotool xsel xkill xwininfo
$ sudo apt-get update
$ sudo apt-get install rubygems testability-qttas \
ruby-
librmagick-
Edit /etc/tdriver/ tdriver_ parameters. xml to just contain:
<parameters> server_ ip" value="127.0.0.1" />
<sut id="sut_qt" template="qt">
<!-- use default values -->
<parameter name="qttas_
</sut>
<!-- overload default behaviours parameter (see generic.xml in defaults folder) --> C:\tdriver\ temp_folder" / --> behaviours. xml"/>
<!-- parameter name="tmp_folder" value="
<parameter name="behaviours" value="
</parameters>
Run Testability server first
$ qttasserver&
To run tests on this source tree, make sure you run cmake & compile. Otherwise
tests will be run on the installed applications.
You run the entire Unity 2D test suite by executing the run-tests.rb script:
$ cd tests
$ ruby run-tests.rb
Consult tests/README for more details