Merge lp:~unity-2d-team/unity-2d/testability-integration into lp:unity-2d

Proposed by Gerry Boland
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
Reviewer Review Type Date Requested Status
Tiago Salem Herrmann (community) Approve
Review via email: mp+85417@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Gerry Boland (gerboland) wrote :

A quick guide to setting up your machine to for testing this MR:

$ sudo add-apt-repo ppa:gerboland/testability
$ sudo apt-get update
$ sudo apt-get install rubygems testability-qttas \
  ruby-testability-driver-qt-sut-plugin testability-visualizer \
  librmagick-ruby1.8 xdotool xsel xkill xwininfo

Edit /etc/tdriver/tdriver_parameters.xml to just contain:

<parameters>
   <sut id="sut_qt" template="qt">
       <!-- use default values -->
       <parameter name="qttas_server_ip" value="127.0.0.1" />
   </sut>

   <!-- overload default behaviours parameter (see generic.xml in defaults folder) -->
   <!-- parameter name="tmp_folder" value="C:\tdriver\temp_folder"/ -->
   <parameter name="behaviours" value="behaviours.xml"/>
</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

Revision history for this message
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_shell_command). Right now, it is required that we test on the host machine.

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

Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

Just tested in my environment and everything is working fine.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2011-12-09 13:09:46 +0000
+++ CMakeLists.txt 2011-12-15 16:55:31 +0000
@@ -89,6 +89,7 @@
89# Tests89# Tests
90enable_testing()90enable_testing()
91add_custom_target(check make test)91add_custom_target(check make test)
92configure_file(tests/misc/binary_dir.txt.in ${CMAKE_CURRENT_SOURCE_DIR}/tests/misc/binary_dir.txt @ONLY)
9293
93# Source94# Source
94add_subdirectory(libunity-2d-private)95add_subdirectory(libunity-2d-private)
9596
=== modified file 'launcher/Launcher.qml'
--- launcher/Launcher.qml 2011-12-08 14:13:21 +0000
+++ launcher/Launcher.qml 2011-12-15 16:55:31 +0000
@@ -91,7 +91,7 @@
9191
92 LauncherList {92 LauncherList {
93 id: main93 id: main
94 Accessible.name: "main"94 objectName: "main"
9595
96 /* function to position highlighted tile so that the shadow does not cover it */96 /* function to position highlighted tile so that the shadow does not cover it */
97 function positionMainViewForIndex(index) {97 function positionMainViewForIndex(index) {
@@ -153,7 +153,7 @@
153153
154 LauncherList {154 LauncherList {
155 id: shelf155 id: shelf
156 Accessible.name: "shelf"156 objectName: "shelf"
157157
158 anchors.bottom: parent.bottom158 anchors.bottom: parent.bottom
159 anchors.bottomMargin: main.anchors.bottomMargin159 anchors.bottomMargin: main.anchors.bottomMargin
160160
=== modified file 'launcher/LauncherItem.qml'
--- launcher/LauncherItem.qml 2011-11-28 19:40:53 +0000
+++ launcher/LauncherItem.qml 2011-12-15 16:55:31 +0000
@@ -56,6 +56,7 @@
5656
57 property int tileSize57 property int tileSize
58 property int selectionOutlineSize58 property int selectionOutlineSize
59 property alias name: looseItem.objectName
59 property string desktopFile: ""60 property string desktopFile: ""
60 property alias icon: icon.source61 property alias icon: icon.source
61 property alias urgentAnimation: urgentAnimation62 property alias urgentAnimation: urgentAnimation
@@ -131,6 +132,7 @@
131 /* This is the arrow shown at the right of the tile when the application is132 /* This is the arrow shown at the right of the tile when the application is
132 the active one */133 the active one */
133 Image {134 Image {
135 objectName: "active"
134 anchors.right: parent.right136 anchors.right: parent.right
135 y: item.height - item.selectionOutlineSize / 2 - height / 2137 y: item.height - item.selectionOutlineSize / 2 - height / 2
136 mirror: isRightToLeft()138 mirror: isRightToLeft()
@@ -152,6 +154,7 @@
152 Repeater {154 Repeater {
153 model: item.pips155 model: item.pips
154 delegate: Image {156 delegate: Image {
157 objectName: "pips"
155 /* FIXME: It seems that when the image is created (or re-used) by the Repeater158 /* FIXME: It seems that when the image is created (or re-used) by the Repeater
156 for a moment it doesn't have any parent, and therefore warnings are159 for a moment it doesn't have any parent, and therefore warnings are
157 printed for the following two anchor assignements. This fixes the160 printed for the following two anchor assignements. This fixes the
@@ -191,6 +194,7 @@
191 While the application is launching, this will fade out and in. */194 While the application is launching, this will fade out and in. */
192 Image {195 Image {
193 id: tileBackground196 id: tileBackground
197 objectName: "tileBackground"
194 property color color: defaultBackgroundColor198 property color color: defaultBackgroundColor
195 anchors.fill: parent199 anchors.fill: parent
196 smooth: true200 smooth: true
@@ -241,6 +245,7 @@
241 /* This is just the main icon of the tile */245 /* This is just the main icon of the tile */
242 Image {246 Image {
243 id: icon247 id: icon
248 objectName: "icon"
244 anchors.centerIn: parent249 anchors.centerIn: parent
245 smooth: true250 smooth: true
246251
@@ -275,6 +280,7 @@
275280
276 Image {281 Image {
277 id: selectionOutline282 id: selectionOutline
283 objectName: "selectionOutline"
278 anchors.centerIn: parent284 anchors.centerIn: parent
279 smooth: true285 smooth: true
280 source: "artwork/round_selected_66x66.png"286 source: "artwork/round_selected_66x66.png"
@@ -309,6 +315,7 @@
309315
310 Image {316 Image {
311 id: progressBar317 id: progressBar
318 objectName: "progressBar"
312 source: "artwork/progress_bar_trough.png"319 source: "artwork/progress_bar_trough.png"
313 anchors.verticalCenter: parent.verticalCenter320 anchors.verticalCenter: parent.verticalCenter
314 anchors.left: parent.left321 anchors.left: parent.left
315322
=== modified file 'launcher/LauncherList.qml'
--- launcher/LauncherList.qml 2011-12-11 16:36:28 +0000
+++ launcher/LauncherList.qml 2011-12-15 16:55:31 +0000
@@ -21,6 +21,7 @@
2121
22AutoScrollingListView {22AutoScrollingListView {
23 id: list23 id: list
24 Accessible.name: objectName
2425
25 /* The spacing is explicitly set to -8 in order to compensate26 /* The spacing is explicitly set to -8 in order to compensate
26 the space added by selectionOutline and round_corner_54x54.png. */27 the space added by selectionOutline and round_corner_54x54.png. */
@@ -89,6 +90,7 @@
89 }90 }
9091
91 Accessible.name: accessibleDescription()92 Accessible.name: accessibleDescription()
93 name: item.name
9294
93 width: list.width95 width: list.width
94 tileSize: list.tileSize96 tileSize: list.tileSize
9597
=== modified file 'launcher/tests/CMakeLists.txt'
--- launcher/tests/CMakeLists.txt 2011-11-25 15:38:51 +0000
+++ launcher/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000
@@ -2,7 +2,7 @@
2 set(_test_list "")2 set(_test_list "")
3 foreach(_test ${ARGN})3 foreach(_test ${ARGN})
4 add_test(NAME ${_test} 4 add_test(NAME ${_test}
5 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ./${_test}5 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ./${_test}
6 )6 )
7 add_executable(${_test} ${_test}.cpp ${_test}.moc)7 add_executable(${_test} ${_test}.cpp ${_test}.moc)
8 qt4_generate_moc(${_test}.cpp ${_test}.moc)8 qt4_generate_moc(${_test}.cpp ${_test}.moc)
99
=== added file 'libunity-2d-private/src/testabilityinterface.h'
--- libunity-2d-private/src/testabilityinterface.h 1970-01-01 00:00:00 +0000
+++ libunity-2d-private/src/testabilityinterface.h 2011-12-15 16:55:31 +0000
@@ -0,0 +1,37 @@
1/***************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (testabilitydriver@nokia.com)
6**
7** This file is part of TDriver.
8**
9** If you have questions regarding the use of this file, please contact
10** Nokia at testabilitydriver@nokia.com .
11**
12** This library is free software; you can redistribute it and/or
13** modify it under the terms of the GNU Lesser General Public
14** License version 2.1 as published by the Free Software Foundation
15** and appearing in the file LICENSE.LGPL included in the packaging
16** of this file.
17**
18****************************************************************************/
19
20#include <QObject>
21#include <QString>
22
23class TestabilityInterface
24{
25public:
26 virtual ~TestabilityInterface() {}
27
28 /*!
29 Initializes the plugin once loaded.
30 */
31 virtual void Initialize() = 0;
32
33};
34
35 Q_DECLARE_INTERFACE(TestabilityInterface,
36 "com.nokia.testability.TestabilityInterface/1.0")
37
038
=== modified file 'libunity-2d-private/src/unity2dapplication.cpp'
--- libunity-2d-private/src/unity2dapplication.cpp 2011-10-12 19:36:54 +0000
+++ libunity-2d-private/src/unity2dapplication.cpp 2011-12-15 16:55:31 +0000
@@ -41,6 +41,12 @@
41#include <gtk/gtk.h>41#include <gtk/gtk.h>
42#include <pango/pango.h>42#include <pango/pango.h>
4343
44// Testability
45#include <QtPlugin>
46#include <QPluginLoader>
47#include <QLibraryInfo>
48#include "testabilityinterface.h"
49
44// libc50// libc
45#include <stdlib.h>51#include <stdlib.h>
4652
@@ -157,6 +163,11 @@
157: QApplication(argc, argv)163: QApplication(argc, argv)
158, m_platformFontTracker(new PlatformFontTracker)164, m_platformFontTracker(new PlatformFontTracker)
159{165{
166 /* Load Testability Plugin on startup if requested */
167 if (arrayContains(argv, argv + argc, "-testability")) {
168 loadTestabilityPlugin();
169 }
170
160 /* Configure translations */171 /* Configure translations */
161 Unity2dTr::init("unity-2d", INSTALL_PREFIX "/share/locale");172 Unity2dTr::init("unity-2d", INSTALL_PREFIX "/share/locale");
162173
@@ -208,4 +219,35 @@
208 return QApplication::x11EventFilter(event);219 return QApplication::x11EventFilter(event);
209}220}
210221
222/*
223 * Load the Testability Plugin if available
224 *
225 * Testability is a tool required for UI testing. See tests/ directory.
226 */
227void Unity2dApplication::loadTestabilityPlugin()
228{
229 QString testabilityPlugin = "testability/libtestability";
230 QString testabilityPluginPostfix = ".so";
231
232 testabilityPlugin = QLibraryInfo::location(QLibraryInfo::PluginsPath)
233 + QObject::tr("/") + testabilityPlugin + testabilityPluginPostfix;
234 QPluginLoader loader(testabilityPlugin.toLatin1().data());
235
236 QObject *plugin = loader.instance();
237 if (plugin) {
238 qDebug("Testability plugin loaded successfully!");
239 testabilityInterface = qobject_cast<TestabilityInterface *>(plugin);
240
241 if (testabilityInterface) {
242 qDebug("Testability interface obtained!");
243 testabilityInterface->Initialize();
244 } else {
245 qDebug("Failed to get testability interface!");
246 }
247 } else {
248 qDebug("Testability plugin %s load failed with error:%s",
249 testabilityPlugin.toLatin1().data(), loader.errorString().toLatin1().data());
250 }
251}
252
211#include <unity2dapplication.moc>253#include <unity2dapplication.moc>
212254
=== modified file 'libunity-2d-private/src/unity2dapplication.h'
--- libunity-2d-private/src/unity2dapplication.h 2011-07-27 14:07:00 +0000
+++ libunity-2d-private/src/unity2dapplication.h 2011-12-15 16:55:31 +0000
@@ -29,6 +29,8 @@
2929
30class PlatformFontTracker;30class PlatformFontTracker;
3131
32class TestabilityInterface;
33
32class AbstractX11EventFilter34class AbstractX11EventFilter
33{35{
34public:36public:
@@ -66,8 +68,10 @@
66 bool x11EventFilter(XEvent*);68 bool x11EventFilter(XEvent*);
6769
68private:70private:
71 void loadTestabilityPlugin();
69 QList<AbstractX11EventFilter*> m_x11EventFilters;72 QList<AbstractX11EventFilter*> m_x11EventFilters;
70 PlatformFontTracker* m_platformFontTracker;73 PlatformFontTracker* m_platformFontTracker;
74 TestabilityInterface* testabilityInterface;
71};75};
7276
73#endif // UNITY2DAPPLICATION_H77#endif // UNITY2DAPPLICATION_H
7478
=== modified file 'libunity-2d-private/tests/CMakeLists.txt'
--- libunity-2d-private/tests/CMakeLists.txt 2011-11-28 13:44:43 +0000
+++ libunity-2d-private/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000
@@ -4,7 +4,7 @@
4include_directories(4include_directories(
5 ${libunity-2d-private_SOURCE_DIR}/src5 ${libunity-2d-private_SOURCE_DIR}/src
6 ${libunity-2d-private_SOURCE_DIR}/Unity2d6 ${libunity-2d-private_SOURCE_DIR}/Unity2d
7 ${CMAKE_SOURCE_DIR}/tests7 ${CMAKE_SOURCE_DIR}/tests/misc
8 ${CMAKE_CURRENT_BINARY_DIR}8 ${CMAKE_CURRENT_BINARY_DIR}
9 ${GLIB_INCLUDE_DIRS}9 ${GLIB_INCLUDE_DIRS}
10 ${QT_QTTEST_INCLUDE_DIR}10 ${QT_QTTEST_INCLUDE_DIR}
@@ -17,7 +17,7 @@
17 set(_test_list "")17 set(_test_list "")
18 foreach(_test ${ARGN})18 foreach(_test ${ARGN})
19 add_test(NAME ${_test} 19 add_test(NAME ${_test}
20 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ./${_test}20 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ./${_test}
21 )21 )
22 add_executable(${_test} ${_test}.cpp ${_test}.moc)22 add_executable(${_test} ${_test}.cpp ${_test}.moc)
23 qt4_generate_moc(${_test}.cpp ${_test}.moc)23 qt4_generate_moc(${_test}.cpp ${_test}.moc)
2424
=== modified file 'panel/tests/CMakeLists.txt'
--- panel/tests/CMakeLists.txt 2011-11-23 19:57:05 +0000
+++ panel/tests/CMakeLists.txt 2011-12-15 16:55:31 +0000
@@ -2,7 +2,7 @@
2 set(_test_list "")2 set(_test_list "")
3 foreach(_test ${ARGN})3 foreach(_test ${ARGN})
4 add_test(NAME ${_test} 4 add_test(NAME ${_test}
5 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/run-with-xvfb.sh ${_test}5 COMMAND /bin/bash ${CMAKE_SOURCE_DIR}/tests/misc/run-with-xvfb.sh ${_test}
6 )6 )
7 add_executable(${_test} ${_test}.cpp ${_test}.moc)7 add_executable(${_test} ${_test}.cpp ${_test}.moc)
8 qt4_generate_moc(${_test}.cpp ${_test}.moc)8 qt4_generate_moc(${_test}.cpp ${_test}.moc)
99
=== added file 'tests/README'
--- tests/README 1970-01-01 00:00:00 +0000
+++ tests/README 2011-12-15 16:55:31 +0000
@@ -0,0 +1,177 @@
1=======================
2User Acceptance Testing
3=======================
4
5Outline
6-------
7Repeatedly and reliably reproducing the inputs a user and comparing the outputs
8to known good values is an excellent way to maintain the existing functionality
9of a piece of software. However manually performing these tests is tiresome and
10prone to error.
11
12Automation is the key! As we use Qt, we are spoiled with a tool called
13"Testability" which is designed to automate this testing process. We make use of
14this tool for testing Unity 2D.
15
16
17Testability Introduction
18------------------------
19Testability is a UX testing framework, whose core feature for us is that it
20allows inspection of the tree of QObjects in a Qt application to read properties
21(write to some) and call signals, as well as faking mouse/keyboard/gesture
22inputs, grabbing visual outputs, and measuring both CPU and graphical
23performance.
24
25Testability is comprised of the following parts:
26
27On the System Under Test (SUT)
28- A plugin that the Qt-based software to be tested loads - usually with the
29 “-testability” switch.
30- A server app that allows inspection and interaction with the SUT [qttasserver]
31
32On the Host machine (can be same as the SUT)
33- Ruby libraries to act as client for qttasserver, so that testing is scriptable
34 [testability-driver & testability-driver-qt-sut-plugin]
35- GUI to allow easy inspection of SUT and writing tests [tdriver-visualizer],
36 but not needed for running tests.
37
38For the moment, the system is designed assuming that the SUT is also the Host
39machine.
40
41
42Testability installation instructions
43-------------------------------------
44Add this repo to your apt sources:
45
46$ sudo add-apt-repository ppa:gerboland/testability
47$ sudo apt-get update
48
49Testability is comprised of the following parts:
50- testability-qttas
51 The server binary and plugin library for the SUT.
52- ruby-testability-driver
53 Basic client interface for qttasserver
54- ruby-testability-driver-qt-sut-plugin
55 [Depends on ruby-testability-driver]
56 Plugin for testability-driver to add QObject inspection support
57- testability-visualizer
58 User interface - not needed for testing
59
60
61To get started, install all these and additional dependencies with:
62
63$ sudo apt-get install rubygems testability-qttas \
64 ruby-testability-driver-qt-sut-plugin testability-visualizer \
65 librmagick-ruby1.8 xdotool xsel x11-utils
66
67You need to configure Testability for your usage. Assuming you’ll be testing on
68your host machine, this involves editing /etc/tdriver/tdriver_parameters.xml to
69just contain:
70
71<parameters>
72 <sut id="sut_qt" template="qt">
73 <!-- use default values -->
74 <parameter name="qttas_server_ip" value="127.0.0.1" />
75 </sut>
76
77 <!-- overload default behaviours parameter (see generic.xml in defaults folder) -->
78 <parameter name="behaviours" value="behaviours.xml" />
79</parameters>
80
81Also to get log output from these processes, you should create the directory
82/logs/testability [FIXME]
83
84
85Running Tests
86-------------
87Before any test are run, you must start the qttasserver on the SUT:
88$ qttasserver&
89
90To run tests on this source tree, make sure you run cmake & compile. Otherwise
91tests will be run on the installed applications.
92
93You run the entire Unity 2D test suite by executing the run-tests.rb script:
94$ cd tests
95$ ruby run-tests.rb
96
97This script enters specified subdirectories and runs the tests contained in any
98ruby script with particular format.
99
100You can run individual test suites (one per file) by entering the directory and
101calling:
102$ cd launcher
103$ ruby autohide_show_tests.rb
104
105Single test cases ("Position with Empty Desktop") can be run with
106$ ruby autohide_shot_tests.rb --name "test_Position_with_Empty_Desktop"
107
108
109Test Suite Syntax
110-----------------
111Please take a look at this example:
112
113
114
115require '../run-tests.rb' unless $INIT_COMPLETED #include necessary libs, etc
116
117################################# Test Suite ###################################
118context "Test Suite summary" do
119 # Run once at the beginning of this test suite
120 startup do
121 end
122
123 # Run once at the end of this test suite
124 shutdown do
125 end
126
127 # Run before each test case begins
128 setup do
129 # Execute the application
130 @sut = TDriver.sut(:Id => "sut_qt")
131 @app = @sut.run( :name => "/absolute/path/to/application",
132 :arguments => "-testability",
133 :sleeptime => 2 )
134 end
135
136 # Run after each test case completes
137 teardown do
138 #@app.close
139 #Need to kill Launcher as it does not shutdown when politely asked
140 system "pkill -nf unity-2d-launcher"
141 end
142
143 ##############################################################################
144 # Test cases
145
146 test "Short description of first test case" do
147 assert_equal( Integer(@app.Unity2dPanel()['width']), 66,
148 'These two values are not equal' )
149
150 assert( @app.Unity2dPanel()['x_absolute'], \
151 'This quantity is not true' )
152 end
153
154 test "Another test case description" do
155 end
156end
157
158
159Using Testability Visualizer
160----------------------------
161First off, ensure you're running the "qttasserver"
162
163Run
164$ tdriver_visualizer
165
166In the menu bar, open “Applications” and select “Start New Application” and
167enter the absolute path to the Qt binary you want to inspect. The application
168will appear, but also will the Visualizer, with a preview of the application in
169the left pane. In the center is a tree of QObjects in this application, and on
170the right you can inspect the properties, methods and signals of each QObject as
171you drill into the tree.
172
173Note you should go to “View” -> “Docks and Toolbars” -> “Code Editor” to get a
174text editor. See that you can right-click QObjects and you get the option to
175paste a QObject path from the root - saves time!
176
177Hit F9 to execute the test.
0178
=== added directory 'tests/launcher'
=== added file 'tests/launcher/autohide_show_tests.rb'
--- tests/launcher/autohide_show_tests.rb 1970-01-01 00:00:00 +0000
+++ tests/launcher/autohide_show_tests.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,193 @@
1#!/usr/bin/env ruby1.8
2=begin
3/*
4 * This file is part of unity-2d
5 *
6 * Copyright 2011 Canonical Ltd.
7 *
8 * Authors:
9 * - Gerry Boland <gerry.boland@canonical.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; version 3.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23=end
24
25require '../run-tests.rb' unless $INIT_COMPLETED
26require 'xdo/xwindow'
27require 'xdo/keyboard'
28require 'xdo/mouse'
29require 'timeout'
30require 'tmpdir'
31
32# Helper function to open window at certain position
33def open_window_at(x,y)
34 # Open Terminal with position (x,y)
35 Dir.mktmpdir {|dir| # use this to generate unique window title to help Xdo get window ID
36 system "gnome-terminal --geometry=100x30+#{x}+#{y} --working-directory=#{dir} &"
37 Timeout.timeout(3){XDo::XWindow.wait_for_window(dir)}
38 }
39 return XDo::XWindow.from_active
40end
41
42############################# Test Suite #############################
43context "Launcher Autohide and Show Tests" do
44 WIDTH = 65 #launcher bar width
45
46 # Run once at the beginning of this test suite
47 startup do
48 system 'killall unity-2d-launcher > /dev/null 2>&1'
49 system 'killall unity-2d-launcher > /dev/null 2>&1'
50
51 # Minimize all windows
52 XDo::XWindow.toggle_minimize_all
53 end
54
55 # Run once at the end of this test suite
56 shutdown do
57 end
58
59 # Run before each test case begins
60 setup do
61 #Ensure mouse out of the way
62 XDo::Mouse.move(200,200,10,true)
63
64 # Execute the application
65 @sut = TDriver.sut(:Id => "sut_qt")
66 @app = @sut.run( :name => UNITY_2D_LAUNCHER,
67 :arguments => "-testability",
68 :sleeptime => 2 )
69 # Make certain application is ready for testing
70 verify(10){ @app.Unity2dPanel() }
71 end
72
73 # Run after each test case completes
74 teardown do
75 #@app.close
76 #Need to kill Launcher as it does not shutdown when politely asked
77 system "pkill -nf unity-2d-launcher"
78 end
79
80 #####################################################################################
81 # Test cases
82
83 test "Position with Empty Desktop" do
84 # check width before proceeding
85 assert_equal( Integer(@app.Unity2dPanel()['width']), WIDTH,
86 "Launcher is not #{WIDTH} pixels wide on screen!" )
87
88 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
89 'Launcher hiding on empty desktop, should be visible' )
90 end
91
92 test "Position with Window not in the way" do
93 # Open Terminal with position 100x100
94 xid = open_window_at(100,100)
95 sleep 0.5
96 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
97 'Launcher hiding when window not in the way, should be visible' )
98 xid.close!
99 end
100
101 test "Position with Window in the way" do
102 # Open Terminal with position 40x100
103 xid = open_window_at(40,100)
104 sleep 0.5
105 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
106 'Launcher visible when window in the way, should be hidden' )
107 xid.close!
108 end
109
110 test "Move window positioning to check launcher action" do
111 # Open Terminal with position 100x100
112 xid = open_window_at(100,100)
113 sleep 0.5
114 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
115 'Launcher hiding when window not in the way, should be visible' )
116 xid.move(WIDTH-1,100)
117 sleep 1
118 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
119 'Launcher visible when window in the way, should be hidden' )
120 xid.move(WIDTH,100)
121 sleep 0.5
122 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
123 'Launcher hiding when window not in the way, should be visible' )
124 xid.close!
125 end
126
127 test "Reveal hidden Launcher with mouse" do
128 xid = open_window_at(10,100)
129 sleep 0.5
130 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
131 'Launcher visible with window in the way, should be hidden' )
132 XDo::Mouse.move(0,200)
133 sleep 1
134 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
135 'Launcher hiding when mouse at left edge of screen' )
136 XDo::Mouse.move(WIDTH-1,200)
137 sleep 2
138 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
139 'Launcher should still be visible as mouse over it' )
140 XDo::Mouse.move(WIDTH,200)
141 sleep 2
142 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
143 'Launcher visible with window in the way and mouse moved out, should be hidden' )
144 xid.close!
145 end
146
147 test "Hold Super key down to reveal launcher and shortcut keys" do
148 xid = open_window_at(10,100)
149 sleep 0.5
150 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
151 'Launcher visible with window in the way, should be hidden' )
152 XDo::Keyboard.key_down('SUPER')
153 sleep 2
154 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
155 'Launcher hiding when Super Key held, should be visible' )
156
157 assert_equal( @app.LauncherList( :name => 'main' ) \
158 .QDeclarativeItem( :name => 'Home Folder' ) \
159 .QDeclarativeRectangle() \
160 .QDeclarativeText()['visible'], 'true', \
161 'Shortcut on Home Folder icon not displaying with Super key held' )
162 XDo::Keyboard.key_up('SUPER')
163 sleep 2
164 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
165 'Launcher visible with window in the way and mouse moved out, should be hidden' )
166 xid.close!
167 end
168
169=begin
170 # Test disabled due to bug in Xdo::Keyboard where function keys are not accepted
171 test "Press Alt+F1 to focus Launcher" do
172 xid = open_window_at(10,100)
173 sleep 0.5
174 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
175 'Launcher visible with window in the way, should be hidden' )
176 XDo::Keyboard.alt_f1
177 sleep 0.5
178 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), 0, \
179 'Launcher hiding after Alt+F1 pressed, should be visible' )
180
181 assert_equal( @app.LauncherList( :name => 'main' ) \
182 .QDeclarativeItem( :name => 'Dash home' ) \
183 .QDeclarativeImage( :name => 'selectionOutline' )['visible'], 'true', \
184 'Dash icon not highlighted after Alt+F1 pressed' )
185 XDo::Keyboard.esc
186 sleep 2
187 assert_equal( Integer(@app.Unity2dPanel()['x_absolute']), -WIDTH, \
188 'Launcher visible with window in the way and mouse moved out, should be hidden' )
189 xid.close!
190 end
191=end
192
193end
0194
=== added directory 'tests/launcher/verification'
=== added file 'tests/launcher/verification/dash-tile.png'
1Binary 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 differ195Binary 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
=== added file 'tests/launcher/visual_verification.rb'
--- tests/launcher/visual_verification.rb 1970-01-01 00:00:00 +0000
+++ tests/launcher/visual_verification.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,86 @@
1#!/usr/bin/env ruby1.8
2=begin
3/*
4 * This file is part of unity-2d
5 *
6 * Copyright 2011 Canonical Ltd.
7 *
8 * Authors:
9 * - Gerry Boland <gerry.boland@canonical.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; version 3.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23=end
24
25require '../run-tests.rb' unless $INIT_COMPLETED
26
27############################# Test Suite #############################
28context "Launcher Autohide and Show Tests" do
29 pwd = File.expand_path(File.dirname(__FILE__)) + '/'
30
31 # Run once at the beginning of this test suite
32 startup do
33 system 'killall unity-2d-launcher > /dev/null 2>&1'
34 system 'killall unity-2d-launcher > /dev/null 2>&1'
35 end
36
37 # Run once at the end of this test suite
38 shutdown do
39 end
40
41 # Run before each test case begins
42 setup do
43 # Execute the application
44 @sut = TDriver.sut(:Id => "sut_qt")
45 @app = @sut.run( :name => UNITY_2D_LAUNCHER,
46 :arguments => "-testability",
47 :sleeptime => 2 )
48 end
49
50 # Run after each test case completes
51 teardown do
52 #@app.close
53 #Need to kill Launcher as it does not shutdown when politely asked
54 system "pkill -nf unity-2d-launcher"
55 end
56
57 #####################################################################################
58 # Test cases
59
60 test "Visually compare Dash tile with reference" do
61 expected_image = pwd + 'verification/dash-tile.png'
62
63 dash_tile = @app.Unity2dPanel() \
64 .LauncherList( :name => 'main' ) \
65 .QDeclarativeItem( :name => 'Dash home' ) \
66 .QDeclarativeItem()
67
68 #Check tile visual matches reference image, with some tolerance.
69 assert( dash_tile.find_on_screen(expected_image, 4), \
70 'Dash tile not matching reference image' )
71 end
72
73 test "Check Dash Tile location in Launcher" do
74 expected_image = pwd + 'verification/dash-tile.png'
75
76 tile_list = @app.Unity2dPanel().LauncherList( :name => 'main' )
77
78 # Given the reference image, locate the matching visual in the LauncherList
79 coordinates = tile_list.find_on_screen(expected_image, 4)
80 assert( coordinates, 'Unable to find visual matching Dash tile reference image on screen' )
81
82 # Dash tile should have these coordinates in the Launcher
83 assert_equal( coordinates, [7,7], 'Dash tile not at correct coordinates' )
84 end
85
86end
087
=== added directory 'tests/manual-tests'
=== added file 'tests/manual-tests/launcher.txt'
--- tests/manual-tests/launcher.txt 1970-01-01 00:00:00 +0000
+++ tests/manual-tests/launcher.txt 2011-12-15 16:55:31 +0000
@@ -0,0 +1,104 @@
1----
2 * Mouse over a tile in the launcher (which displays tooltip).
3 * Press Super+S
4
5-> Tooltip disappears, spread appears (lp:881458)
6
7----
8 * Have Launcher hidden.
9 * Bring mouse over panel
10 * Move it directly to the left, so it stays over panel
11
12-> Launcher stays hidden - it should not reveal (lp:891636)
13
14----
15 * RTL: Open Dash
16 * perform a simple search
17 * click on an application icon
18
19-> Application launches - should not crash (lp:836498)
20
21----
22 * Press Alt-F1
23
24-> Launcher revealed with Dash button focused - Pressing Alt-F1 again should toggle the focus between launcher and applications (lp:885304)
25
26----
27 * Launcher visible, scrolled down, hit Super
28
29-> Launcher reposition to beginning - and its tooltips/menus should hide. (lp:876632)
30
31----
32 * Press Alt-F1, select the trash icon using the keyboard.
33
34-> Trash icon gets highlighted - The highlight effect should not be truncated on top and bottom (lp:876589)
35
36----
37 * Launch an application. Open the context menu of the same application tile in launcher. Hit Alt-F4
38
39-> The visible context is updated - closing the application should be notified in launcher menu(lp:784541)
40
41----
42 * Right-click on a non running application tile in the launcher. Click the "Remove from Launcher" option.
43
44-> Launcher is still shown for a while - it should not close immediately (lp:884410)
45
46----
47 * Press Right Mouse Button over an application tile in the launcher. Do not release.
48
49-> Menu is shown - it should not show on button release (lp:813036)
50
51----
52 * Press Right Mouse Button over an application tile in the launcher, do not release, move mouse up/down
53
54-> Launcher does not scroll (lp:813041)
55
56----
57 * Press Left Mouse Button over an application tile in the launcher, do not release
58 * Move mouse up/down, release Left Mouse Button
59 * while the icons are going back to their original position press Right Mouse Button and try to continue the dragging
60
61-> Launcher keeps going back to its original position - you should not be able to continue a dragging with Right Mouse Button
62
63----
64 * Have the Launcher hidden. Move the mouse over the panel. Move mouse to the left edge the panel.
65
66-> Launcher is not revealed - moving the cursor in the top left corner of the panel shouldn't reveal launcher(lp:891636)
67
68----
69 * Start the launcher with a maximized window under it.
70 * Watch the launcher autohide.
71 * Alt+Tab to another maximized window.
72 * Move the mouse to the leftmost part of the screen.
73 * Watch launcher show.
74 * Keeping the mouse at the leftmost part of the screen move to the top panel.
75 * Wait one second
76
77-> Launcher is hidden - The launcher should not still be visible when you have the mouse on the top panel (lp:892004)
78
79----
80 * Open an application & maximize the same
81 * Hit show-desktop shortcut [Ctrl+Alt+D?] key to see the Desktop
82
83--> Launcher is revealed - Showing the Desktop should reveal launcher (lp:898161)
84
85----
86
87 * Drag a file(example .txt) onto a tile(gedit) in the launcher
88
89--> 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)
90
91----
92
93 * Reveal launcher
94 * Place mouse over a tile whose application is running
95
96--> Tooltip appears - The tooltip should be displayed at the center of the tile. (lp:898349)
97
98----
99
100 * Show the desktop (Ctrl+Alt+D ?)
101
102The panel title Ubuntu Desktop is shown - The desktop title is changed from 'Desktop to 'Ubuntu Desktop'(lp:869873)
103
104----
0105
=== added file 'tests/manual-tests/panel.txt'
=== added file 'tests/manual-tests/places.txt'
--- tests/manual-tests/places.txt 1970-01-01 00:00:00 +0000
+++ tests/manual-tests/places.txt 2011-12-15 16:55:31 +0000
@@ -0,0 +1,13 @@
1----
2 * Hit super key to Open Dash.
3
4--> Dash have window buttons to maximize/unmaximize - Clicking on maximize/unmaximize
5 button should toggle the dash mode between full-screen and netbook mode. If display is
6 small, Dash should be locked to full-screen mode. (lp:860400)
7
8----
9 * Press Alt+F2
10
11--> Dash opens and "Run a command" is shown in the search field - (lp:883392)
12
13----
014
=== added file 'tests/manual-tests/window-manager.txt'
--- tests/manual-tests/window-manager.txt 1970-01-01 00:00:00 +0000
+++ tests/manual-tests/window-manager.txt 2011-12-15 16:55:31 +0000
@@ -0,0 +1,9 @@
1----
2 * Hit Super+S key. Spread is launched.
3
4-> 1) Mouse hovering and 2) Arrow, Enter and Esc keys - are enabled - Hitting arrow
5 keys or mouse hovering should switch the highlighted workspaces. The Enter key should
6 zoom the highlighted workspace while Esc key should simply exit from spread with no
7 changes.(lp:744978)
8
9----
010
=== added directory 'tests/misc'
=== added file 'tests/misc/binary_dir.txt.in'
--- tests/misc/binary_dir.txt.in 1970-01-01 00:00:00 +0000
+++ tests/misc/binary_dir.txt.in 2011-12-15 16:55:31 +0000
@@ -0,0 +1,1 @@
1@CMAKE_CURRENT_BINARY_DIR@
02
=== added directory 'tests/misc/lib'
=== added file 'tests/misc/lib/testhelper.rb'
--- tests/misc/lib/testhelper.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/testhelper.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,162 @@
1require 'test/unit'
2
3dir = File.dirname(File.expand_path(__FILE__))
4$LOAD_PATH.unshift dir + '/../lib'
5$TEST_DIR = File.dirname(File.expand_path(__FILE__))
6
7# Enable a startup and shutdwn method for each test case
8# From https://github.com/freerange/test_startup
9module TestStartupAndShutdown
10 def startup(&block)
11 install_global_startup
12 @__startup_blocks ||= []
13 @__startup_blocks << block if block_given?
14 @__startup_blocks
15 end
16
17 def shutdown(&block)
18 install_global_startup
19 @__shutdown_blocks ||= []
20 @__shutdown_blocks << block if block_given?
21 @__shutdown_blocks
22 end
23
24 attr_reader :__startup_blocks, :__shutdown_blocks
25
26 def install_global_startup
27 extend(TestSuiteWithGlobalStartup)
28 end
29
30 module TestSuiteWithGlobalStartup
31 def suite(*args)
32 mysuite = super
33 these_startup_blocks = __startup_blocks
34 these_shutdown_blocks = __shutdown_blocks
35 mysuite.instance_eval { @__startup_blocks = these_startup_blocks }
36 mysuite.instance_eval { @__shutdown_blocks = these_shutdown_blocks }
37 def mysuite.run(*args)
38 (@__startup_blocks || []).each { |block| block.call }
39 super
40 (@__shutdown_blocks || []).each { |block| block.call }
41 end
42 mysuite
43 end
44 end
45end
46
47Test::Unit::TestCase.extend(TestStartupAndShutdown)
48
49##
50# Test::Unit runs test in alphabetical order. This class instead runs them
51# sequentially. Can specify ordering with the line
52# execute :sequentially:
53# Taken from http://wiki.openqa.org/download/attachments/804/testcase.rb
54# Copyright: Bret Pettichord
55
56class TestCase < Test::Unit::TestCase
57 @@order = :sequentially
58 def initialize name
59 throw :invalid_test if name == :default_test && self.class == TestCase
60 super
61 end
62 class << self
63 attr_accessor :test_methods, :order
64 def test_methods
65 @test_methods ||= []
66 end
67 def order
68 @order || @@order
69 end
70 def default_order= order
71 @@order = order
72 end
73 def sorted_test_methods
74 case order
75 when :alphabetically: test_methods.sort
76 when :sequentially: test_methods
77 when :reversed_sequentially: test_methods.reverse
78 when :reversed_alphabetically: test_methods.sort.reverse
79 else raise ArgumentError, "Execute option not supported: #{@order}"
80 end
81 end
82 def suite
83 suite = Test::Unit::TestSuite.new(name)
84 sorted_test_methods.each do |test|
85 catch :invalid_test do
86 suite << new(test)
87 end
88 end
89 if (suite.empty?)
90 catch :invalid_test do
91 suite << new(:default_test)
92 end
93 end
94 return suite
95 end
96 def method_added id
97 name = id.id2name
98 test_methods << name if name =~ /^test./
99 end
100 def execute order
101 @order = order
102 end
103 end
104end
105
106
107##
108# Snippit from test/spec/mini 5
109# Allows syntatic sugar for tests in the following form:
110#
111#context "It's test/spec/mini!" do
112# setup do
113# @name = "Chris"
114# end
115#
116# teardown do
117# @name = nil
118# end
119#
120# test "with Test::Unit" do
121# assert (self.class < Test::Unit::TestCase)
122# end
123#
124# test "body-less test cases"
125#
126# test :symbol_test_names do
127# assert true
128# end
129#
130# xtest "disabled tests" do
131# assert disabled!
132# end
133#
134# context "and of course" do
135# test "nested contexts!" do
136# assert_equal "Chris", @name
137# end
138# end
139#end
140#
141# http://gist.github.com/307649 (chris@ozmm.org)
142#
143
144def context(*args, &block)
145 return super unless (name = args.first) && block
146
147 klass = Class.new(TestCase) do
148 def self.test(name, &block)
149 define_method("test_#{name.to_s.gsub(/\W/,'_')}", &block) if block
150 end
151 def self.xtest(*args) end
152 def self.context(*args, &block) instance_eval(&block) end
153 def self.setup(&block)
154 define_method(:setup) { self.class.setups.each { |s| instance_eval(&s) } }
155 setups << block
156 end
157 def self.setups; @setups ||= [] end
158 def self.teardown(&block) define_method(:teardown, &block) end
159 end
160 (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
161 klass.class_eval &block
162end
0163
=== added directory 'tests/misc/lib/xdo'
=== added file 'tests/misc/lib/xdo/README.xdo.rdoc'
--- tests/misc/lib/xdo/README.xdo.rdoc 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/README.xdo.rdoc 2011-12-15 16:55:31 +0000
@@ -0,0 +1,52 @@
1--
2This file is part of Xdo.
3Copyright © 2009, 2010 Marvin Gülker
4 Initia in potestate nostra sunt, de eventu fortuna iudicat.
5++
6=XDo
7XDo is a library to simulate keyboard and mouse input and manipulating windows on the X server.
8It's wrapped around the command-line tools xdotool[http://www.semicomplete.com/projects/xdotool/],
9xsel[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],
10so you will need to have them installed if you want to use Xdo (even if xwininfo, eject and xkill are usually already installed).
11If not, try to install them via your favourite packaging manager.
12After they're installed, install XDo via RubyGems:
13 sudo gem install xdo
14==Usage
15 #Require some of XDo's files
16 require "xdo/keyboard"
17 require "xdo/mouse"
18 require "xdo/xwindow"
19 #Move the cursor
20 XDo::Mouse.move(100, 100)
21 #Simulate text (with special escape sequences!)
22 XDo::Keyboard.simulate("This is{TAB}text.")
23 #Some sequences can be shortened:
24 XDo::Keyboard.simulate("This ist\ttext.")
25 #And this will move a window containing the string "gedit",
26 #unless it's maximized.
27 win = XDo::XWindow.from_title(/gedit/)
28 win.move(200, 200)
29==Files
30You can require the following files in your projects:
31* xdo/clipboard: Clipboard access
32* xdo/keyboard: Pretty self-explaining
33* xdo/mouse: Automate the mouse
34* xdo/xwindow: Manipulate windows in various ways
35As an helpful extra, I created an executable ruby file "xinfo.rb". Thanks to RubyGems,
36you can start this GUI tool right from the command line by typing:
37 xinfo.rb
38It's by far not perfect, maybe not even good, but I think it can be useful sometimes
39(you will need to have wxRuby installed, try <tt>sudo gem install wxruby-ruby19</tt>).
40If you're looking for a more professional program, try the "X window information" tool.
41==Notes
42* If your +xdotool+ seems to reject the --window option, you are not using the current version. Try building the newest one from the source.
43* 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.
44==Fairly incomplete
45* 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.
46* 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. :-)
47==License/Copyright
48 Copyright © 2009, 2010 Marvin Gülker
49 This library is free software; you may redistribute it and/or modify it
50 under the terms of Ruby's license (see http://www.ruby-lang.org/en/LICENSE.txt).
51 You can contact me at sutniuq ät gmx Dot net.
52 Initia in potestate nostra sunt, de eventu fortuna iudicat.
053
=== added file 'tests/misc/lib/xdo/_xdo.rb'
--- tests/misc/lib/xdo/_xdo.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/_xdo.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,35 @@
1#Encoding: UTF-8
2#This file is part of Xdo.
3#Copyright © 2009 Marvin Gülker
4# Initia in potestate nostra sunt, de eventu fortuna iudicat.
5#
6# This file contains shared definitions for all the xdo scripts
7#
8# Modified by Gerry Boland <gerry dot boland at canonical dot com>
9
10require "open3"
11require "strscan"
12
13#The namespace of this library.
14module XDo
15
16 #The command to start xdotool.
17 XDOTOOL = "xdotool"
18
19 #The command to start xsel.
20 XSEL = "xsel"
21
22 #The command to start xwininfo.
23 XWININFO = "xwininfo"
24
25 #The command to start xkill.
26 XKILL = "xkill"
27
28 #Class for errors in this library.
29 class XError < StandardError
30 end
31
32 class ParseError < StandardError
33 end
34
35end #module XDo
036
=== added file 'tests/misc/lib/xdo/clipboard.rb'
--- tests/misc/lib/xdo/clipboard.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/clipboard.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,209 @@
1#Encoding: UTF-8
2#This file is part of Xdo.
3#Copyright © 2009, 2010 Marvin Gülker
4# Initia in potestate nostra sunt, de eventu fortuna iudicat.
5#
6# Modified by Gerry Boland <gerry dot boland at canonical dot com>
7require File.join(File.dirname(__FILE__), '_xdo')
8
9module XDo
10
11 #A module for interaction with the X clipboard. Please note, that the X clipboard
12 #consists of three parts: The PRIMARY clipboard, the CLIPBOARD clipboard, and
13 #the SECONDARY clipboard. The clipboard you access normally via [CTRL]+[C]
14 #or by right-clicking and selecting "copy", is usually the CLIPBOARD clipboard (but that
15 #depends on the application you use). The three main methods of this module (#read, #write
16 #and #clear) take a list symbols of the clipboards to interact with. If you don't want to
17 #pass in the symbols, use the predefined read_xy, write_xy and clear_xy methods. They cannot
18 #access more than one clipboard at a time.
19 #The symbols for the clipboards are:
20 #[PRIMARY] :primary
21 #[SECONDARY] :secondary
22 #[CLIPBOARD] :clipboard
23 #You cannot store complex objects like images via this interface, only strings. However,
24 #you could translate an image into a string (packed pixels maybe?) and put that on the
25 #clipboard -- for your own application this may be fine, but it won't magically allow
26 #a user to paste that image into a graphics program.
27 #
28 #The +xsel+ program used by this module is quite outdated. As far as I can see, it's
29 #last update happened in 2002 and since I do not believe that software exists that
30 #won't break over a period of 8 years without a single modification while updating systems I'm about to
31 #switch to a newer one. +xclip+ is likely, but that one got it's last update in early
32 #2009...
33 module Clipboard
34
35 class << self
36
37 ##
38 # :singleton-method: read_primary
39 #Returns the contents of the PRIMARY clipboard.
40 #See #read for an explanation.
41
42 ##
43 # :singleton-method: read_clipboard
44 #Returns the contents of the CLIPBOARD clipboard.
45 #See #read for an explanation.
46
47 ##
48 # :singleton-method: read_secondary
49 #Returns the contents of the SECONDARY clipboard.
50 #See #read for an explanation.
51
52 ##
53 # :singleton-method: write_primary
54 #Writes to the PRIMARY clipboard.
55 #See #write for an explanation.
56
57 ##
58 # :singleton-method: write_clipboard
59 #Writes to the CLIPBOARD clipboard.
60 #See #write for an explanation.
61
62 ##
63 # :singleton-method: write_secondary
64 #Writes to the SECONDARY clipboard.
65 #See #write for an explanation.
66
67 ##
68 # :singleton-method: clear_primary
69 #Clears the PRIMARY clipboard.
70 #See #clear for an explanation.
71
72 ##
73 # :singleton-method: clear_clipboard
74 #Clears the CLIPBOARD clipboard.
75 #See #clear for an explanation.
76
77 ##
78 # :singleton-method: clear_secondary
79 #Clears the SECONDARY clipboard.
80 #See #clear for an explanation.
81
82 #Reads text from a X clipboard.
83 #===Parameters
84 #[<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>).
85 #===Return value
86 #A hash of form
87 # {:clip_sym => "clipboard_content"}
88 #If you didn't pass any arguments to #read, the hash will contain keys for
89 #all clipboard, i.e. for <tt>:clipboard</tt>, <tt>:primary</tt> and <tt>:secondary</tt>.
90 #If you did, only those symbols will be included you passed. See
91 #the _Example_ section for an example of this.
92 #===Example
93 # XDo::Clipboard.read #| {:clipboard => "...", :primary => "...", :secondary => "..."}
94 # XDo::Clipboard.read(:primary) #| {:primary => "..."}
95 # XDo::Clipboard.read(:clipboard, :secondary) #| {clipboard => "...", :secondary => "..."}
96 #===Remarks
97 #You could also use one of the read_* methods for convenience.
98 def read(*from)
99 if from.first.kind_of? Hash
100 warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!")
101 from = from.first.keys
102 end
103 from.concat([:clipboard, :primary, :secondary]) if from.empty?
104
105 hsh = {}
106 hsh[:primary] = `#{XSEL}` if from.include? :primary
107 hsh[:clipboard] = `#{XSEL} -b` if from.include? :clipboard
108 hsh[:secondary] = `#{XSEL} -s` if from.include? :secondary
109 hsh
110 end
111
112
113 #Writes text to a X clipboard.
114 #===Parameters
115 #[<tt>*to</tt>] (<tt>:clipboard</tt>) Specifies to what clipboards you want to wrote to.
116 #===Return value
117 #The text written.
118 #===Example
119 # XDo::Clipboard.write("I love Ruby") #You can now paste this via [CTRL] + [V]
120 # XDo::Clipboard.write("I love Ruby", :primary) #You can now paste this via a middle-mouse-button click
121 # XDo::Clipboard.write("I love Ruby", :clipboard, :primary) #Both of the above
122 #===Remarks
123 #You could also use one of the write_* methods for convenience.
124 def write(text, *to)
125 if to.first.kind_of? Hash
126 warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!")
127 to = to.first.keys
128 end
129 to << :clipboard if to.empty?
130
131 IO.popen("xsel -i", "w"){|io| io.write(text)} if to.include? :primary
132 IO.popen("xsel -b -i", "w"){|io| io.write(text)} if to.include? :clipboard
133 IO.popen("xsel -s -i", "w"){|io| io.write(text)} if to.include? :secondary
134 text
135 end
136
137 #Appends text to a X clipboard.
138 #===Parameters
139 #[+text+] The text to append.
140 #[<tt>*to</tt>] (<tt>:clipboard</tt>) The clipboards to which you want to append.
141 #===Return value
142 #Undefined.
143 #===Example
144 # XDo::Clipboard.write("I love ")
145 # XDo::Clipboard.append("Ruby")
146 # puts XDo::Clipboard.read(:clipboard)[:clipboard] #=> I love Ruby
147 #
148 # XDo::Clipboard.write("I love", :primary)
149 # XDo::Clipboard.append("Ruby", :primary, :clipboard)
150 # #If you now paste via [CTRL] + [V], you'll get 'Ruby'. If you
151 # #paste via the middle mouse button, you'll get 'I love Ruby'
152 # #(Assuming you didn't execute the first block of code, of course).
153 def append(text, *to)
154 if to.first.kind_of? Hash
155 warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!")
156 to = to.first.keys
157 end
158 to << :clipboard if to.empty?
159
160 IO.popen("xsel -a -i", "w"){|io| io.write(text)} if to.include? :primary
161 IO.popen("xsel -b -a -i", "w"){|io| io.write(text)} if to.include? :clipboard
162 IO.popen("xsel -s -a -i", "w"){|io| io.write(text)} if to.include? :secondary
163 end
164
165 #Clears the specified clipboards.
166 #===Parameters
167 #[<tt>*clips</tt>] (<tt>:primary</tt>, <tt>:clipboard</tt>, <tt>:secondary</tt>) The clipboards you want to clear.
168 #===Return value
169 #nil.
170 #===Example
171 # XDo::Clipboard.write("I love Ruby")
172 # XDo::Clipboard.clear
173 # #Nothing can be pasted anymore
174 #
175 # XDo::Clipboard.write("I love Ruby", :clipboard, :primary)
176 # XDo::Clipboard.clear(:primary)
177 # #You can still paste via [CTRL] + [V], but not with the middle mouse button
178 def clear(*clips)
179 if clips.first.kind_of? Hash
180 warn("#{caller.first}: Deprecation warning: Use symbols as a rest argument now!")
181 clips = clips.first.keys
182 end
183 clips.concat([:primary, :clipboard, :secondary]) if clips.empty?
184
185 `#{XSEL} -c` if clips.include? :primary
186 `#{XSEL} -b -c` if clips.include? :clipboard
187 `#{XSEL} -s -c` if clips.include? :secondary
188 nil
189 end
190
191 [:primary, :clipboard, :secondary].each do |sym|
192
193 define_method(:"read_#{sym}") do
194 read(sym)[sym]
195 end
196
197 define_method(:"write_#{sym}") do |text|
198 write(text, sym)
199 end
200
201 define_method(:"clear_#{sym}") do
202 clear(sym)
203 end
204
205 end
206
207 end
208 end
209end
0210
=== added file 'tests/misc/lib/xdo/keyboard.rb'
--- tests/misc/lib/xdo/keyboard.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/keyboard.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,387 @@
1#Encoding: UTF-8
2#This file is part of Xdo.
3#Copyright © 2009, 2010 Marvin Gülker
4# Initia in potestate nostra sunt, de eventu fortuna iudicat.
5#
6# Modified by Gerry Boland <gerry dot boland at canonical dot com>
7require File.join(File.dirname(__FILE__), '_xdo')
8
9module XDo
10
11 #A namespace encabsulating methods to simulate keyboard input. You can
12 #send input to special windows, just pass in the window's ID or a XWindow
13 #object via the +w_id+ parameter.
14 module Keyboard
15
16 #Aliases for key names in escape sequences.
17 ALIASES = {
18 "BS" => "BackSpace",
19 "BACKSPACE" => "BackSpace",
20 "DEL" => "Delete",
21 "ESC" => "Escape",
22 "INS" => "Insert",
23 "PGUP" => "Prior",
24 "PGDN" => "Next",
25 "NUM1" => "KP_End",
26 "NUM2" => "KP_Down",
27 "NUM3" => "KP_Next",
28 "NUM4" => "KP_Left",
29 "NUM5" => "KP_Begin",
30 "NUM6" => "KP_Right",
31 "NUM7" => "KP_Home",
32 "NUM8" => "KP_Up",
33 "NUM9" => "KP_Prior",
34 "NUM_DIV" => "KP_Divide",
35 "NUM_MUL" => "KP_Multiply",
36 "NUM_SUB" => "KP_Subtract",
37 "NUM_ADD" => "KP_Add",
38 "NUM_ENTER" => "KP_Enter",
39 "NUM_DEL" => "KP_Delete",
40 "NUM_COMMA" => "KP_Separator",
41 "NUM_INS" => "KP_Insert",
42 "NUM0" => "KP_0",
43 "CTRL" => "Control_L",
44 "ALT" => "Alt_L",
45 "ALT_GR" => "ISO_Level3_Shift",
46 "WIN" => "Super_L",
47 "SUPER" => "Super_L"
48 }.freeze
49
50 #The names of some keyboard symbols. The latest release of
51 #xdotool is capable of sending keysymbols directly, i.e.
52 # xdotool key Adiaeresis
53 #results in Ä being sent.
54 #This hash defines how those special characters can be
55 #sent. Feel free to add characters that are missing! You
56 #can use the +xev+ program to obtain their keycodes.
57 SPECIAL_CHARS = {
58 "ä" => "adiaeresis",
59 "Ä" => "Adiaeresis",
60 "ö" => "odiaeresis",
61 "Ö" => "Odiaeresis",
62 "ü" => "udiaeresis",
63 "Ü" => "Udiaeresis",
64 "ë" => "ediaeresis",
65 "Ë" => "Ediaeresis", #Does not work with xdotool
66 "ï" => "idiaeresis",
67 "Ï" => "Idiaeresis", #Does not work with xdotool
68 "ß" => "ssharp",
69 "\n" => "Return",
70 "\t" => "Tab",
71 "\b" => "BackSpace",
72 "§" => "section",
73 "[" => "bracketleft",
74 "]" => "bracketright",
75 "{" => "braceright",
76 "}" => "braceleft",
77 "@" => "at",
78 "€" => "EuroSign",
79 "|" => "bar",
80 "?" => "question"
81 }
82
83 class << self
84
85 #Types a character sequence, but without any special chars.
86 #===Parameters
87 #[+str+] The string to type.
88 #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
89 #===Return value
90 #nil.
91 #===Example
92 # XDo::Keyboard.type("test")
93 # XDo::Keyboard.type("täst") #=> I don't what key produces '�', skipping.
94 #===Remarks
95 #This function is a bit faster then #simulate.
96 def type(str, w_id = 0)
97 out = Open3.popen3("#{XDOTOOL} type #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}'#{str}'") do |stdin, stdout, stderr|
98 stdin.close_write
99 str = stderr.read
100 warn(str) unless str.empty?
101 end
102 nil
103 end
104
105 #Types a character sequence. You can use the escape sequence {...} to send special
106 #keystrokes.
107 #===Parameters
108 #[+str+] The string to simulate.
109 #[+raw+] (false) If true, escape sequences via {...} are disabled. See _Remarks_.
110 #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
111 #===Return value
112 #The string that was simulated.
113 #===Raises
114 #[ParseError] Your string was invalid.
115 #===Example
116 # XDo::Keyboard.simulate("test")
117 # XDo::Keyboard.simulate("täst")
118 # XDo::Keyboard.simulate("tex{BS}st")
119 #===Remarks
120 #This method recognizes many special chars like ? and ä, even if you disable
121 #the escape syntax {..} via setting the +raw+ parameter to true (that's the only way to send the { and } chars).
122 #
123 #+str+ may contain escape sequences in braces { and }. The letters between those two indicate
124 #what special character to send - this way you can simulate non-letter keypresses like [ESC]!
125 #You may use the following escape sequences:
126 # Escape seq. | Keystroke | Comment
127 # ============+===================+=================
128 # ALT | [Alt_L] |
129 # ------------+-------------------------------------
130 # ALT_GR | [ISO_Level3_Shift]| Not on USA
131 # | | keyboard
132 # ------------+-------------------------------------
133 # BS | [BackSpace] |
134 # ------------+-------------------------------------
135 # BACKSPACE | [BackSpace] |
136 # ------------+-------------------------------------
137 # CTRL | [Control_L] |
138 # ------------+-------------------------------------
139 # DEL | [Delete] |
140 # ------------+-------------------------------------
141 # END | [End] |
142 # ------------+-------------------------------------
143 # ESC | [Escape] |
144 # ------------+-------------------------------------
145 # INS | [Insert] |
146 # ------------+-------------------------------------
147 # HOME | [Home] |
148 # ------------+-------------------------------------
149 # MENU | [Menu] | Usually right-
150 # | | click menu
151 # ------------+-------------------------------------
152 # NUM0..NUM9 | [KP_0]..[KP_9] | Numpad keys
153 # ------------+-------------------------------------
154 # NUM_DIV | [KP_Divide] | Numpad key
155 # ------------+-------------------------------------
156 # NUM_MUL | [KP_Multiply] | Numpad key
157 # ------------+-------------------------------------
158 # NUM_SUB | [KP_Subtract] | Numpad key
159 # ------------+-------------------------------------
160 # NUM_ADD | [KP_Add] | Numpad key
161 # ------------+-------------------------------------
162 # NUM_ENTER | [KP_Enter] | Numpad key
163 # ------------+-------------------------------------
164 # NUM_DEL | [KP_Delete] | Numpad key
165 # ------------+-------------------------------------
166 # NUM_COMMA | [KP_Separator] | Numpad key
167 # ------------+-------------------------------------
168 # NUM_INS | [KP_Insert] | Numpad key
169 # ------------+-------------------------------------
170 # PAUSE | [Pause] |
171 # ------------+-------------------------------------
172 # PGUP | [Prior] | Page up
173 # ------------+-------------------------------------
174 # PGDN | [Next] | Page down
175 # ------------+-------------------------------------
176 # PRINT | [Print] |
177 # ------------+-------------------------------------
178 # SUPER | [Super_L] | Windows key
179 # ------------+-------------------------------------
180 # TAB | [Tab] |
181 # ------------+-------------------------------------
182 # WIN | [Super_L] | Windows key
183 def simulate(str, raw = false, w_id = 0)
184 raise(XDo::XError, "Invalid number of open and close braces!") unless str.scan(/\{/).size == str.scan(/\}/).size
185
186 tokens = tokenize(str)
187
188 tokens.each do |sym, s|
189 case sym
190 when :plain then type(s, w_id.to_i)
191 when :esc then
192 if raw
193 type("{#{s}}", w_id.to_i) #The braces should be preserved when using +raw+.
194 else
195 if ALIASES.has_key?(s)
196 key(ALIASES[s])
197 else
198 char(s.split("_").map(&:capitalize).join("_"), w_id.to_i)
199 end
200 end
201 when :special then
202 if SPECIAL_CHARS.has_key?(s)
203 char(SPECIAL_CHARS[s], w_id.to_i)
204 else
205 raise(XDo::ParseError, "No key symbol known for '#{s}'!")
206 end
207 else #Write a bug report if you get here. That really shouldn't happen.
208 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.")
209 end
210 end
211 str
212 end
213
214 #Simulate a single char directly via the +key+ command of +xdotool+.
215 #===Parameters
216 #[+c+] A single char like "a" or a combination like "shift+a".
217 #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
218 #===Return value
219 #The +c+ you passed in.
220 #===Raises
221 #[XError] Invalid keyname.
222 #===Example
223 # XDo::Keyboard.char("a") #=> a
224 # XDo::Keyboard.char("A") #=> A
225 # XDo::Keyboard.char("ctrl+c")
226 def char(c, w_id = 0)
227 Open3.popen3("#{XDOTOOL} key #{w_id.nonzero? ? "--window #{w_id.to_i} " : ""}#{c}") do |stdin, stdout, stderr|
228 stdin.close_write
229 raise(XDo::XError, "Invalid character '#{c}'!") if stderr.read =~ /No such key name/
230 end
231 c
232 end
233 alias key char
234
235 #Holds a key down.
236 #===Parameters
237 #[+key+] The key to hold down.
238 #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
239 #===Return value
240 #+key+.
241 #===Raises
242 #[XError] Invalid keyname.
243 #===Example
244 # XDo::Keyboard.key_down("a")
245 # sleep 2
246 # XDo::Keyboard.key_up("a")
247 #===Remarks
248 #You should release the key sometime via Keyboard.key_up.
249 def key_down(key, w_id = 0)
250 Open3.popen3("#{XDOTOOL} keydown #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr|
251 stdin.close_write
252 raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/
253 end
254 key
255 end
256
257 #Releases a key hold down by #key_down.
258 #===Parameters
259 #[+key+] The key to release.
260 #[+w_id+] (0) The ID of the window you want the input send to (or an XWindow object). 0 means the active window.
261 #===Return value
262 #+key+.
263 #===Raises
264 #[XError] Invalid keyname.
265 #===Example
266 # XDo::Keyboard.key_down("a")
267 # sleep 2
268 # XDo::Keyboard.key_up("a")
269 #===Remarks
270 #This has no effect on already released keys.
271 def key_up(key, w_id = 0)
272 Open3.popen3("#{XDOTOOL} keyup #{w_id.nonzero? ? "--window #{w_id.to_i} " : "" }#{check_for_special_key(key)}") do |stdin, stdout, stderr|
273 stdin.close_write
274 raise(XDo::XError, "Invalid character '#{key}'!") if stderr.read =~ /No such key name/
275 end
276 key
277 end
278
279 #Deletes a char.
280 #===Parameters
281 #[right] (false) If this is true, +del_char+ uses the DEL key for deletion, otherwise the BackSpace key.
282 #===Return value
283 #nil.
284 #===Example
285 # XDo::Keyboard.delete
286 # XDo::Keyboard.delete(true)
287 def delete(right = false)
288 Keyboard.simulate(right ? "\b" : "{DEL}")
289 nil
290 end
291
292 #Allows you to things like this:
293 # XDo::Keyboard.ctrl_c
294 #The string will be capitalized and every _ will be replaced by a + and then passed into #char.
295 #You can't use this way to send whitespace or _ characters.
296 def method_missing(sym, *args, &block)
297 super if args.size > 1 or block
298 char(sym.to_s.capitalize.gsub("_", "+"), args[0].nil? ? 0 : args[0])
299 end
300
301 private
302
303 #Tokenizes a string into an array of form
304 # [[:plain, "nonspecial"], [:special, "a"], [:esc, "INS"], ...]
305 def tokenize(str)
306 tokens = []
307 #We need a binary version of our string as StringScanner isn't able to work
308 #with encodings.
309 ss = StringScanner.new(RUBY_VERSION >= '1.9' ? str.dup.force_encoding("BINARY") : str.dup) #String#force_encoding always returns self
310 until ss.eos?
311 pos = ss.pos
312 if ss.scan_until(/\{/)
313 #Get the string between the last and the recent match. We have to subtract 2 here,
314 #since a StringScanner position is always ahead of the string character by 1 (since 0 in
315 #a SmallScanner means "before the first character") and the matched brace shouldn't be
316 #included.
317 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.
318 pos = ss.pos
319 ss.scan_until(/\}/)
320 tokens << [:esc, ss.string[Range.new(pos, ss.pos - 2)]] #See above for comment on -2
321 else #We're behind the last escape sequence now - there must be some characters left, otherwise this wouldn't be triggered.
322 tokens << [:plain, ss.rest]
323 ss.terminate
324 end
325 end
326 #Now hunt for special character like ä which can't be send using xdotool's type command.
327 regexp = Regexp.union(*SPECIAL_CHARS.keys.map{|st| st}) #Regexp.union escapes automatically, no need for Regexp.escape
328 tokens.map! do |ary|
329 #But first, we have to remedy from that insane forced encoding for StringScanner.
330 #Force every string's encoding back to the original encoding.
331 ary[1].force_encoding(str.encoding) if RUBY_VERSION >= '1.9'
332 next([ary]) unless ary[0] == :plain #Extra array since we flatten(1) it afterwards
333 tokens2 = []
334 ss = StringScanner.new(ary[1])
335 until ss.eos?
336 pos = ss.pos
337 if ss.scan_until(regexp)
338 #Same as for the first StringScanner encoding problem goes here, but since I now have to use a UTF-8 regexp
339 #I have to put the string into the StringScanner as UTF-8, but because the StringScanner returns positions for
340 #a BINARY-encoded string I have to get the string, grep the position from the BINARY version and then reforce
341 #it to the correct encoding.
342 if RUBY_VERSION >= '1.9'
343 tokens2 << [:plain, ss.string.dup.force_encoding("BINARY")[Range.new(pos, ss.pos - 2)].force_encoding(str.encoding)] unless ss.pos == 1
344 else
345 tokens2 << [:plain, ss.string.dup[Range.new(pos, ss.pos - 2)]] unless ss.pos == 1
346 end
347 tokens2 << [:special, ss.matched]
348 pos = ss.pos
349 else
350 tokens2 << [:plain, ss.rest]
351 ss.terminate
352 end
353 end
354 tokens2
355 end
356 #Make the token sequence 1-dimensional
357 tokens.flatten!(1)
358 #Now delete empty :plain tokens, they don't have to be handled.
359 #They are created by strings like "abc{ESC}{ESC}", where they are
360 #recognized between the two escapes.
361 #Empty escape sequences are an error in any case.
362 tokens.delete_if do |sym, st|
363 if st.empty?
364 if sym == :esc
365 raise(XDo::ParseError, "Empty escape sequence found!")
366 else
367 true
368 end
369 end
370 end
371
372 #Return the tokens array.
373 tokens
374 end
375
376 #Checks wheather +key+ is a special character (i.e. contained
377 #in the SPECIAL_CHARS hash) and returns the key symbol for it if so,
378 #otherwise returns +key+.
379 def check_for_special_key(key)
380 SPECIAL_CHARS.has_key?(key) ? SPECIAL_CHARS[key] : key
381 end
382
383 end
384
385 end
386
387end
0388
=== added file 'tests/misc/lib/xdo/mouse.rb'
--- tests/misc/lib/xdo/mouse.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/mouse.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,254 @@
1#Encoding: UTF-8
2#This file is part of Xdo.
3#Copyright © 2009 Marvin Gülker
4# Initia in potestate nostra sunt, de eventu fortuna iudicat.
5#
6# Modified by Gerry Boland <gerry dot boland at canonical dot com>
7require File.join(File.dirname(__FILE__), '_xdo')
8
9module XDo
10
11 #Automate your mouse! You can simulate every click you can do with
12 #your fingers - it's kind of funny, but don't forget to create USEFUL
13 #applications, not ones annoying your users (e. g. you could make
14 #his/her mouse unusable).
15 module Mouse
16
17 #Left mouse button.
18 LEFT = 1
19 #Middle mouse button (as if you click your mouse wheel).
20 MIDDLE = 2
21 #Right mouse button.
22 RIGHT = 3
23 #Mouse wheel up.
24 UP = 4
25 #Mouse wheel down.
26 DOWN = 5
27
28 #Maps the button's symbols to the numbers xdotool uses.
29 BUTTON2XDOTOOL = {
30 :left => 1,
31 :middle => 2,
32 :right => 3,
33 :up => 4,
34 :down => 5
35 }.freeze
36
37 class << self
38
39 #Gets the current cursor position.
40 #===Return value
41 #A two-element array of form <code>[x, y]</code>.
42 #===Example
43 # p XDo::Mouse.position #=> [12, 326]
44 def position
45 out = `#{XDOTOOL} getmouselocation`.match(/x:(\d+) y:(\d+)/)
46 [$1.to_i, $2.to_i]
47 end
48
49 #Moves the mouse cursor to the given position.
50 #===Parameters
51 #[+x+] The goal X coordinate.
52 #[+x+] The goal Y coordinate.
53 #[+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).
54 #[+set+] (false) If true, the +speed+ parameter is ignored and the cursor is directly set to the given position.
55 #[+sync+] (true) If true, this method blocks until the cursor has reached the given position.
56 #===Return value
57 #The position you specified, as a two-dimensional array of form <tt>[x, y]</tt>.
58 #===Raises
59 #[ArgumentError] The value of +speed+ was lower or equal to zero.
60 #===Example
61 # #Move to (10|10)
62 # XDo::Mouse.move(10, 10)
63 # #Move fast to (10|10)
64 # XDo::Mouse.move(10, 10, 10)
65 # #Directly set the cursor to (10|10) without any movement
66 # XDo::Mouse.move(10, 10, 1, true)
67 def move(x, y, speed = 2, set = false, sync = true)
68 if set
69 opts = []
70 opts << "--sync" if sync
71 `#{XDOTOOL} mousemove #{opts.join(" ")} #{x} #{y}`
72 return [x, y]
73 else
74 raise(ArgumentError, "speed has to be > 0 (default is 2), was #{speed}!") if speed <= 0
75 pos = position #Current cursor position
76 act_x = pos[0]
77 act_y = pos[1]
78 aim_x = x
79 aim_y = y
80 #Create the illusion of a fluent movement (hey, that statement sounds better in German, really! ;-))
81 loop do
82 #Change position as indiciated by +speed+
83 if act_x > aim_x
84 act_x -= speed
85 elsif act_x < aim_x
86 act_x += speed
87 end
88 if act_y > aim_y
89 act_y -= speed
90 elsif act_y < aim_y
91 act_y += speed
92 end
93 #Move to computed position
94 move(act_x, act_y, speed, true)
95 #Check wheather the cursor's current position is inside an
96 #acceptable area around the goal position. The size of this
97 #area is defined by +speed+; this check ensures we don't get
98 #an infinite loop for unusual conditions.
99 if ((aim_x - speed)..(aim_x + speed)).include? act_x
100 if ((aim_y - speed)..(aim_y + speed)).include? act_y
101 break
102 end #if in Y-Toleranz
103 end #if in X-Toleranz
104 end #loop
105 #Correct the cursor position to point to the exact point specified.
106 #This is for the case the "acceptable area" condition above triggers.
107 if position != [x, y]
108 move(x, y, 1, true)
109 end #if position != [x, y]
110
111 end #if set
112 [x, y]
113 end #def move
114
115 #Simulates a mouse click. If you don't specify a X AND a Y position,
116 #the click will happen at the current cursor position.
117 #===Parameters
118 #[+x+] (nil) The goal X position. Specify together with +y+.
119 #[+y+] (nil) The goal Y position.
120 #[+button+] (:left) The button to click with. One of the following symbols: <tt>:left</tt>, <tt>:right</tt>, <tt>:middle</tt>.
121 #The other arguments are the same as for #move.
122 #===Return value
123 #Undefined.
124 #===Example
125 # #Click at the current position
126 # XDo::Mouse.click
127 # #Click at (10|10)
128 # XDo::Mouse.click(10, 10)
129 # #Click at the current position with the right mouse button
130 # XDo::Mouse.click(nil, nil, :right)
131 # #Move fast to (10|10) and click with the right mouse button
132 # XDo::Mouse.click(10, 10, :right, 10)
133 # #Directly set the cursor to (10|10) and click with the middle mouse button
134 # XDo::Mouse.click(10, 10, :middle, 1, true)
135 def click(x = nil, y = nil, button = :left, speed = 1, set = false)
136 if button.kind_of?(Numeric)
137 warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.")
138 button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based
139 end
140 if x and y
141 move(x, y, speed, set)
142 end
143 `#{XDOTOOL} click #{BUTTON2XDOTOOL[button]}`
144 end
145
146 #Scroll with the mouse wheel. +amount+ is the time of steps to scroll.
147 #===Parameters
148 #[+dir+] The direction to scroll into. Either :up or :down.
149 #[+amount+] The number of steps to scroll. These are *not* meant to be full wheel rounds.
150 #===Return value
151 #Undefined.
152 #===Example
153 # #Scroll up
154 # XDo::Mouse.wheel(:up, 4)
155 # #Scroll down 4 steps
156 # XDo::Mouse.wheel(:down, 4)
157 #===Remarks
158 #The X Server handles wheel up and wheel down events like mouse click
159 #events and if you take a look in the source code of this function you will see, that
160 #it calls #click passing through the +dir+ parameter. So, if you want to be funny,
161 #write something like
162 # XDo::Mouse.click(nil, nil, :up)
163 #.
164 def wheel(dir, amount)
165 if button.kind_of?(Numeric)
166 warn("#{caller.first}: Deprecation warning: Use symbols such as :up for the dir parameter.")
167 button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based
168 end
169 amount.times{click(nil, nil, dir)}
170 end
171
172 #Holds a mouse button down. Don't forget to release it some time.
173 #===Parameters
174 #[+button+] (:left) The button to hold down.
175 #===Return value
176 #Undefined.
177 #===Example
178 # #Hold down the left mouse button for a second
179 # XDo::Mouse.down
180 # sleep 1
181 # XDo::Mouse.up
182 # #Hold down the right mouse button for a second
183 # XDo::Mouse.down(:right)
184 # sleep 1
185 # XDo::Mouse.up(:right)
186 def down(button = :left)
187 if button.kind_of?(Numeric)
188 warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.")
189 button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based
190 end
191 `#{XDOTOOL} mousedown #{BUTTON2XDOTOOL[button]}`
192 end
193
194 #Releases a mouse button. Probably it's a good idea to call #down first?
195 #===Parameters
196 #[+button+] (:left) The button to release.
197 #===Return value
198 #Undefined.
199 #===Example
200 # #Hold down the left mouse button for a second
201 # XDo::Mouse.down
202 # sleep 1
203 # XDo::Mouse.up
204 # #Hold down the right mouse button for a second
205 # XDo::Mouse.down(:right)
206 # sleep 1
207 # XDo::Mouse.up(:right)
208 def up(button = :left)
209 if button.kind_of?(Numeric)
210 warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.")
211 button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based
212 end
213 `#{XDOTOOL} mouseup #{BUTTON2XDOTOOL[button]}`
214 end
215
216 #Executs a drag&drop operation.
217 #===Parameters
218 #[+x1+] Start X coordinate. Set to the current cursor X coordinate if set to nil. Pass together with +y1+.
219 #[+y1+] Start Y coordinate. Set to the current cursor Y coordinate if set to nil.
220 #[+x2+] Goal X coordinate.
221 #[+y2+] Goal Y coordinate.
222 #[+button+] (:left) The button to hold down.
223 #The rest of the parameters is the same as for #move.
224 #===Return value
225 #nil.
226 #===Example
227 # #Drag from (10|10) to (37|56)
228 # XDo::Mouse.drag(10, 10, 37, 56)
229 # #Drag from (10|10) to (37|56) holding the right mouse button down
230 # XDo::Mouse.drag(10, 10, 37, 56, :right)
231 def drag(x1, y1, x2, y2, button = :left, speed = 2, set = false)
232 if button.kind_of?(Numeric)
233 warn("#{caller.first}: Deprecation warning: Use symbols such as :left for the button parameter.")
234 button = BUTTON2XDOTOOL.keys[button - 1] #indices are 0-based
235 end
236 #If x1 and y1 aren't passed, assume the current position
237 if x1.nil? and y1.nil?
238 x1, y1 = position
239 end
240 #Go to the given start position
241 move(x1, y1, speed, set)
242 #Hold button down
243 down(button)
244 #Move to the goal position
245 move(x2, y2, speed, set)
246 #Release button
247 up(button)
248 nil
249 end
250
251 end #class << self
252
253 end #module Mouse
254end
0255
=== added file 'tests/misc/lib/xdo/simulatable.rb'
--- tests/misc/lib/xdo/simulatable.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/simulatable.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,91 @@
1#!/usr/bin/env ruby
2#Encoding: UTF-8
3#This file is part of Xdo.
4#Copyright © 2009, 2010 Marvin Gülker
5# Initia in potestate nostra sunt, de eventu fortuna iudicat.
6#
7# Modified by Gerry Boland <gerry dot boland at canonical dot com>
8require File.join(File.dirname(__FILE__), '_xdo')
9require File.join(File.dirname(__FILE__), 'keyboard')
10
11module XDo
12
13 #Mixin that allows String-like objects to be directly
14 #simulated. You can use it with Ruby's String class:
15 # require "xdo/simulatable"
16 #
17 # class String
18 # include XDo::Simulatable
19 # def to_xdo
20 # to_s
21 # end
22 # end
23 #
24 # "abc".simulate
25 #Every method in this module calls #to_xdo on +self+
26 #first, so make sure this method returns a xdo-usable
27 #String (i.e. no invisible characters except newline, tab and space).
28 module Simulatable
29
30 #Simulates +self+ as keystrokes. Escape sequences are allowed.
31 #===Parameters
32 #[raw] (+false+) If true, escape sequences are ignored.
33 #[w_id] (+nil+) The window's ID to send the keystrokes to. Either an integer or a XWindow object.
34 #===Return value
35 #self.
36 #===Raises
37 #[ParseError] Your string was invalid.
38 #===Example
39 # "This is an{BS} test".simulate
40 # win = XDo::XWindow.from_title(/gedit/)
41 # "This is an{BS} test".simulate(false, win)
42 def simulate(raw = false, w_id = nil)
43 XDo::Keyboard.simulate(to_xdo, raw, w_id)
44 end
45
46 #Types +self+ as keystrokes. Ignores escape sequences.
47 #===Parameters
48 #[w_id] The window's ID to send the keystrokes to. Either an integer or a XWindow object.
49 #===Return value
50 #nil.
51 #===Example
52 # "Some test text".type
53 # win = XDo::XWindow.from_title(/gedit/)
54 # "Some test text".type(win)
55 def type(w_id = nil)
56 XDo::Keyboard.type(to_xdo, w_id)
57 end
58
59 #Holds the the key corresponding to the first character in +self+ down.
60 #===Parameters
61 #[w_id] The window in which to hold a key down. Either an integer ID or a XWindow object.
62 #===Return value
63 #The character of the key hold down.
64 #===Raises
65 #[XError] Invalid keyname.
66 #===Example
67 # "a".down
68 # win = XDo::XWindow.from_title(/gedit/)
69 # "a".down(win)
70 def down(w_id = nil)
71 XDo::Keyboard.key_down(to_xdo[0], w_id)
72 end
73
74 #Releases the key corresponding to the first character in +self+.
75 #===Parameters
76 #[w_id] The window in which to release a key. Either an integer ID or a XWindow object.
77 #===Return value
78 #The character of the key released.
79 #===Raises
80 #[XError] Invalid keyname.
81 #===Example
82 # "a".up
83 # win = XDo::XWindow.from_title(/gedit/)
84 # "a".up(win)
85 def up(w_id = nil)
86 XDo::Keyboard.key_up(to_xdo[0], w_id)
87 end
88
89 end
90
91end
092
=== added directory 'tests/misc/lib/xdo/test'
=== added file 'tests/misc/lib/xdo/test/test_clipboard.rb'
--- tests/misc/lib/xdo/test/test_clipboard.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/test/test_clipboard.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,49 @@
1#!/usr/bin/env ruby
2#Encoding: UTF-8
3
4require "test/unit"
5require File.join(File.dirname(__FILE__), '../clipboard')
6
7class ClipboardTest < Test::Unit::TestCase
8
9 def test_read
10 #Write something to the clipboard first
11 IO.popen("#{XDo::XSEL} -i", "w"){|io| io.write("Some primary test text")}
12 IO.popen("#{XDo::XSEL} -b -i", "w"){|io| io.write("Some clipboard \ntest text")}
13 IO.popen("#{XDo::XSEL} -s -i", "w"){|io| io.write("Some secondary test text")}
14
15 clip = XDo::Clipboard.read
16 assert_equal("Some primary test text", XDo::Clipboard.read_primary)
17 assert_equal(XDo::Clipboard.read_primary, clip[:primary])
18 assert_equal("Some clipboard \ntest text", XDo::Clipboard.read_clipboard)
19 assert_equal(XDo::Clipboard.read_clipboard, clip[:clipboard])
20 assert_equal("Some secondary test text", XDo::Clipboard.read_secondary)
21 assert_equal(XDo::Clipboard.read_secondary, clip[:secondary])
22 end
23
24 def test_write
25 XDo::Clipboard.write_primary "Primary!"
26 XDo::Clipboard.write_clipboard "Clipboard!\nNewline"
27 XDo::Clipboard.write_secondary "Secondary!"
28
29 assert_equal("Primary!", `#{XDo::XSEL}`)
30 assert_equal("Secondary!", `#{XDo::XSEL} -s`)
31 assert_equal("Clipboard!\nNewline", `#{XDo::XSEL} -b`)
32
33 XDo::Clipboard.write("XYZ", :primary, :secondary, :clipboard)
34 assert_equal({ :primary => "XYZ", :secondary => "XYZ", :clipboard => "XYZ"}, XDo::Clipboard.read)
35 end
36
37 def test_append
38 ["primary", "secondary", "clipboard"].each{|m| XDo::Clipboard.send(:"write_#{m}", "This is... ")}
39 XDo::Clipboard.append("a Test!", :primary, :secondary, :clipboard)
40 ["primary", "secondary", "clipboard"].each{|m| assert_equal(XDo::Clipboard.send(:"read_#{m}"), "This is... a Test!")}
41 end
42
43 def test_clear
44 XDo::Clipboard.write("ABC", :primary, :secondary, :clipboard)
45 ["primary", "secondary", "clipboard"].each{|m| XDo::Clipboard.send("clear_#{m}")}
46 ["primary", "secondary", "clipboard"].each{|m| assert_equal("", XDo::Clipboard.send("read_#{m}"))}
47 end
48
49end
050
=== added file 'tests/misc/lib/xdo/test/test_keyboard.rb'
--- tests/misc/lib/xdo/test/test_keyboard.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/test/test_keyboard.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,114 @@
1#!/usr/bin/env ruby
2#Encoding: UTF-8
3
4require "test/unit"
5require "tempfile"
6require File.join(File.dirname(__FILE__), '../keyboard')
7require File.join(File.dirname(__FILE__), '../clipboard')
8require File.join(File.dirname(__FILE__), '../xwindow')
9require File.join(File.dirname(__FILE__), '../simulatable')
10
11class TestKeyboard < Test::Unit::TestCase
12
13 #Command to start a simple text editor
14 EDITOR_CMD = "gedit -s"
15 EDITOR_NAME = "gedit"
16
17 TESTTEXT = "This is test\ntext."
18 TESTTEXT2 = "WXY"
19 TESTTEXT_RAW = "ä{TAB}?b"
20 TESTTEXT_SPECIAL = "ab{TAB}c{TAB}{TAB}d"
21
22 def setup
23 @editor_pipe = IO.popen(EDITOR_CMD, 'r')
24 sleep 3
25 end
26
27 def teardown
28 Process.kill 'TERM', @editor_pipe.pid
29 @editor_pipe.close
30 end
31
32 def test_char
33 Process.kill 'TERM', @editor_pipe.pid
34 @editor_pipe.close #Special file need to be opened
35 tempfile = Tempfile.open("XDOTEST")
36 tempfile.write(TESTTEXT)
37 tempfile.flush
38 sleep 3 #Wait for the buffer to be written out
39 @editor_pipe = IO.popen(EDITOR_CMD + ' ' + tempfile.path, 'r') #So it's automatically killed by #teardown
40 sleep 3
41 tempfile.close
42 20.times{XDo::Keyboard.char("Shift+Right")}
43 XDo::Keyboard.ctrl_c
44 sleep 0.2
45 assert_equal(TESTTEXT, XDo::Clipboard.read_clipboard)
46 end
47
48 def test_simulate
49 XDo::Keyboard.simulate("A{BS}#{TESTTEXT2}")
50 XDo::Keyboard.ctrl_a
51 XDo::Keyboard.ctrl_c
52 sleep 0.2
53 assert_equal(TESTTEXT2, XDo::Clipboard.read_clipboard)
54
55 XDo::Keyboard.ctrl_a
56 XDo::Keyboard.delete
57 XDo::Keyboard.simulate(TESTTEXT_SPECIAL)
58 XDo::Keyboard.ctrl_a
59 XDo::Keyboard.ctrl_c
60 sleep 0.2
61 assert_equal(TESTTEXT_SPECIAL.gsub("{TAB}", "\t"), XDo::Clipboard.read_clipboard)
62
63 XDo::Keyboard.ctrl_a
64 XDo::Keyboard.delete
65 XDo::Keyboard.simulate(TESTTEXT_RAW, true)
66 XDo::Keyboard.ctrl_a
67 XDo::Keyboard.ctrl_c
68 sleep 0.2
69 assert_equal(TESTTEXT_RAW, XDo::Clipboard.read_clipboard)
70 end
71
72 def test_type
73 XDo::Keyboard.type(TESTTEXT2)
74 XDo::Keyboard.ctrl_a
75 XDo::Keyboard.ctrl_c
76 sleep 0.2
77 assert_equal(TESTTEXT2, XDo::Clipboard.read_clipboard)
78 end
79
80 def test_window_id
81 XDo::XWindow.unfocus #Ensure that the editor hasn't the input focus anymore
82 sleep 1
83 edit_id = XDo::XWindow.search(EDITOR_NAME).last
84 xwin = XDo::XWindow.new(edit_id)
85 XDo::Keyboard.simulate(TESTTEXT_SPECIAL, false, edit_id)
86 sleep 1
87 xwin.activate
88 XDo::Keyboard.ctrl_a
89 XDo::Keyboard.ctrl_c
90 sleep 0.2
91 assert_equal(TESTTEXT_SPECIAL.gsub("{TAB}", "\t"), XDo::Clipboard.read_clipboard)
92 end
93
94 def test_include
95 String.class_eval do
96 include XDo::Simulatable
97
98 def to_xdo
99 to_s
100 end
101 end
102
103 XDo::Keyboard.ctrl_a
104 XDo::Keyboard.delete
105 "Ein String".simulate
106 XDo::Keyboard.ctrl_a
107 sleep 0.2
108 XDo::Keyboard.ctrl_c
109 sleep 0.2
110 clip = XDo::Clipboard.read_clipboard
111 assert_equal("Ein String", clip, "Simulated typed string fails to match")
112 end
113
114end
0115
=== added file 'tests/misc/lib/xdo/test/test_mouse.rb'
--- tests/misc/lib/xdo/test/test_mouse.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/test/test_mouse.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,29 @@
1#!/usr/bin/env ruby
2#Encoding: UTF-8
3
4require "test/unit"
5require File.join(File.dirname(__FILE__), '../mouse')
6
7class MouseTest < Test::Unit::TestCase
8
9 def test_position
10 str = `#{XDo::XDOTOOL} getmouselocation`
11 xdpos = []
12 xdpos << str.match(/x:\s?(\d+)/)[1]
13 xdpos << str.match(/y:\s?(\d+)/)[1]
14 xdpos.collect!{|o| o.to_i}
15 assert_equal(xdpos, XDo::Mouse.position)
16 end
17
18 def test_move
19 XDo::Mouse.move(200, 200)
20 assert_equal([200, 200], XDo::Mouse.position)
21 XDo::Mouse.move(0, 0)
22 assert_equal([0, 0], XDo::Mouse.position)
23 XDo::Mouse.move(0, 200)
24 assert_equal([0, 200], XDo::Mouse.position)
25 XDo::Mouse.move(100, 100)
26 assert_equal([100, 100], XDo::Mouse.position)
27 end
28
29end
030
=== added file 'tests/misc/lib/xdo/test/test_xwindow.rb'
--- tests/misc/lib/xdo/test/test_xwindow.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/test/test_xwindow.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,101 @@
1#!/usr/bin/env ruby
2#Encoding: UTF-8
3
4require "test/unit"
5require File.join(File.dirname(__FILE__), '../xwindow')
6require File.join(File.dirname(__FILE__), '../keyboard')
7
8class WindowTest < Test::Unit::TestCase
9
10 attr_accessor :xwindow
11
12 #The command used to create new windows.
13 #The program MUST NOT start maximized. xdotool has no possibility of
14 #acting on maximized windows.
15 NEW_WINDOW_NAME = "Home"
16 NEW_WINDOW_CMD = "nautilus"
17
18 @@xwin = nil
19
20 def setup
21 @editor_pipe = IO.popen(NEW_WINDOW_CMD, 'r')
22 XDo::XWindow.wait_for_window(Regexp.new(Regexp.escape(NEW_WINDOW_NAME)))
23 @@xwin = XDo::XWindow.from_title(Regexp.new(Regexp.escape(NEW_WINDOW_NAME)))
24 end
25
26 def teardown
27 @@xwin.focus
28 @@xwin.close!
29# Process.kill 'TERM', @editor_pipe.pid
30# @editor_pipe.close
31 end
32
33 def test_ewmh_active_window
34 begin
35 XDo::XWindow.from_active
36 rescue XDo::XError
37 #Standard not available
38 notify $!.message
39 end
40 end
41
42 def test_ewmh_wm_desktop
43 begin
44 XDo::XWindow.desktop_num
45 rescue XDo::XError
46 #Standard not available
47 notify $!.message
48 end
49 end
50
51 def test_ewmh_current_desktop
52 begin
53 XDo::XWindow.desktop
54 rescue XDo::XError
55 #Standard not available
56 notify $!.message
57 end
58 end
59
60 def test_exists
61 assert_equal(true, XDo::XWindow.exists?(@@xwin.title))
62 end
63
64 def test_unfocus
65 XDo::XWindow.unfocus
66 # assert_not_equal(@@xwin.id, XDo::XWindow.from_focused.id) # not supported by WM
67 # assert_raise(XDo::XError){XDo::XWindow.from_active} #Nothing's active anymore
68 end
69
70 def test_active
71 @@xwin.activate
72 assert_equal(@@xwin.id, XDo::XWindow.from_active.id)
73 end
74
75 def test_focused
76 @@xwin.unfocus
77 @@xwin.focus
78 assert_equal(@@xwin.id, XDo::XWindow.from_focused.id)
79 end
80
81 def test_move
82 @@xwin.move(87, 57)
83 assert_in_delta(87, 3, @@xwin.abs_position[0])
84 assert_in_delta(57, 3, @@xwin.abs_position[1])
85 # assert_equal(@@xwin.abs_position, @@xwin.rel_position) - why should this succeed?
86 end
87
88 def test_resize
89 @@xwin.resize(500, 500)
90 assert_equal([500, 500], @@xwin.size)
91 end
92
93 def test_map
94 @@xwin.unmap
95 assert_equal(nil, @@xwin.visible?)
96 @@xwin.map
97 assert_block("Window is not visible."){@@xwin.visible?.kind_of?(Integer)}
98 end
99
100end
101
0102
=== added file 'tests/misc/lib/xdo/xwindow.rb'
--- tests/misc/lib/xdo/xwindow.rb 1970-01-01 00:00:00 +0000
+++ tests/misc/lib/xdo/xwindow.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,1009 @@
1#Encoding: UTF-8
2#This file is part of Xdo.
3#Copyright © 2009, 2010 Marvin Gülker
4# Initia in potestate nostra sunt, de eventu fortuna iudicat.
5#
6# Modified by Gerry Boland <gerry dot boland at canonical dot com>
7require File.join(File.dirname(__FILE__), '_xdo')
8require "open3"
9
10module XDo
11
12 #This class represents a window on the screen. Each window is uniquely identified by
13 #an internal ID; before you can create a reference to a window (a XWindow object) you
14 #have to obtain the internal ID of that window and pass it into XWindow.new.
15 #Or you use the class methods of this class, notably XWindow.active_window.
16 #
17 #Via the XWindow object you get you can manipulate a window in serveral ways, e.g.
18 #you can move or resize it. Some methods are not available on every window
19 #manager: XWindow.active_window, XWindow.desktop_num, XWindow.desktop_num=, XWindow.desktop,
20 #XWindow.desktop=, XWindow.from_active, #raise, #activate, #desktop, #desktop=.
21 #Some of them may be available, some not. On my machine (an Ubuntu Lucid) for
22 #example I can use active_window, desktop_num and #activate, but not #raise or #desktop=.
23 #Those methods are tagged with the sentence "Part of the EWMH standard XY". Not all
24 #parts of the EWMH standard are provided by every window manager.
25 #
26 #As of version 0.0.4 the way to search for window is about to change. The old version
27 #where you passed a hash with symbols has been deprecated (and you get warnings
28 #about this if you use it) in favor of passing those symbols as a rest argument. See
29 #XWindow.search for more details.
30 #
31 #You should also be aware of the fact that XDo is about to support Regexp objects
32 #in XWindow.search. In future versions (i.e. after the next minor release) strings
33 #*always* mean an exact title/class/whatever match. For parts, you have to use
34 #Regular Expressions. There is a culprit, though. +xdotool+ doesn't use Ruby's
35 #Regular Expressions engine Oniguruma and expects C-style regexps. I don't know
36 #about the differences - but if you're absolutely sure your window title matches
37 #that wonderful three-line extended regexp and +xdotool+ doesn't find it, you
38 #may email me at sutniuq@@gmx@net explaining which construct defeats +xdotool+.
39 #I will then setup a list over time which states which constructs don't work.
40 #
41 #Be <i>very careful</i> with the methods that are part of the two desktop EWMH standards.
42 #After I set the number of desktops and changed the current desktop, I had to reboot my
43 #system to get the original configuration back. I don't know if I'm not using +xdotool+ correct,
44 #but neither my library nor +xdotool+ itself could rescue my desktop settings. Btw, that's the
45 #reason why it's not in XDo's unit tests (but it should work; at least in one way...).
46 class XWindow
47 #The internal ID of the window.
48 attr_reader :id
49
50 class << self
51
52 #Checks if a window exists.
53 #===Parameters
54 #[+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.
55 #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search.
56 #===Return value
57 #true or false.
58 #===Example
59 # p XWindow.exists?("gedit") #=> true
60 # p XWindow.exists?(/^gedit/) #=> false
61 #===Remarks
62 #It may be a good idea to pass :onlyvisible as a search parameter.
63 def exists?(name, *opts)
64 if opts.first.kind_of?(Hash)
65 warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.")
66 opts = opts.first.keys
67 end
68
69 !search(name, *opts).empty?
70 end
71
72 #Checks wheather the given ID exists or not.
73 #===Parameters
74 #[+id+] The ID to check for.
75 #===Return value
76 #true or false.
77 #===Example
78 # p XWindow.id_exits?(29360674) #=> true
79 # p XWindow.id_exists?(123456) #=> false
80 def id_exists?(id)
81 err = ""
82 Open3.popen3("#{XDo::XWININFO} -id #{id}"){|stdin, stdout, stderr| err << stderr.read}
83 return false unless err.empty?
84 return true
85 end
86
87 #Waits for a window name to exist.
88 #===Parameters
89 #[+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.
90 #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search.
91 #===Return value
92 #The ID of the newly appeared window.
93 #===Example
94 # #Wait for a window with "gedit" somewhere in it's title:
95 # XDo::XWindow.wait_for_window("gedit")
96 # #Wait for a window that ends with "ends_with_this":
97 # XDo::XWindow.wait_for_window(/ends_with_this$/)
98 # #It's useful to combine this method with the Timeout module:
99 # require "timeout"
100 # Timeout.timeout(3){XDo::XWindow.wait_for_window("gedit")}
101 #===Remarks
102 #Returns immediately if the window does already exist.
103 def wait_for_window(name, *opts)
104 if opts.first.kind_of?(Hash)
105 warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.")
106 opts = opts.first.keys
107 end
108
109 loop{break if exists?(name, *opts);sleep(0.5)}
110 search(name, *opts).first
111 end
112
113 #Waits for a window to close.
114 #===Parameters
115 #[+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.
116 #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search.
117 #===Return value
118 #nil.
119 #===Example
120 # #Wait for a window with "gedit" somewhere in it's title
121 # XDo::XWindow.wait_for_close("gedit")
122 # #Waits for a window whose title ends with "ends_with_this":
123 # XDo::XWindow.wait_for_close(/ends_with_this$/)
124 # #It's quite useful to combine this method with the Timeout module:
125 # require "timeout"
126 # Timeout.timeout(3){XDo::XWindow.wait_for_close("gedit")}
127 def wait_for_close(name, *opts)
128 if opts.first.kind_of?(Hash)
129 warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.")
130 opts = opts.first.keys
131 end
132
133 loop{break if !exists?(name, *opts);sleep(0.5)}
134 nil
135 end
136
137 #Search for a window name to get the internal ID of a window.
138 #===Parameters
139 #[+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.
140 #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters.
141 #====Possible search parameters
142 #Copied from the +xdotool+ manpage:
143 #[class] Match against the window class.
144 #[classname] Match against the window classname.
145 #[name] Match against the window name. This is the same string that is displayed in the window titlebar.
146 #[onlyvisible] Show only visible windows in the results. This means ones with map state IsViewable.
147 #===Return value
148 #An array containing the IDs of all found windows or an empty array
149 #if none was found.
150 #===Example
151 # #Look for every window with "gedit" in it's title, class or classname
152 # XDo::XWindow.search("gedit")
153 # #Look for every window whose title, class or classname ends with "SciTE"
154 # XDo::XWindow.search(/SciTE$/)
155 # #Explicitly only search the titles of visible windows
156 # XDo::XWindow.search("gedit", :name, :onlyvisible)
157 def search(str, *opts)
158 if opts.first.kind_of?(Hash)
159 warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.")
160 opts = opts.first.keys
161 end
162 opts = [:name, :class, :classname] if opts.empty?
163
164 #Allow Regular Expressions. Since I can't pass them directly to the command line,
165 #I need to get their source. Otherwise we want an exact match, therefore the line
166 #begin and line end anchors need to be set around the given string.
167 str = str.source if str.kind_of?(Regexp)
168 #TODO
169 #The following is the new behaviour that will be activated with the next minor version.
170 #See DEPRECATE.rdoc.
171 #str = if str.kind_of?(Regexp)
172 #str.source
173 #else
174 #"^#{str.to_str}$"
175 #end
176
177 cmd = "#{XDo::XDOTOOL} search "
178 opts.each{|sym| cmd << "--#{sym} "}
179 cmd << "'" << str << "'"
180 #Don't handle errors since we want an empty array in case of an error
181 Open3.popen3(cmd){|stdin, stdout, stderr| stdin.close_write; stdout.read}.lines.to_a.collect{|l| l.strip.to_i}
182 end
183
184 #Returns the internal ID of the currently focused window.
185 #===Parameters
186 #[+notice_children+] (false) If true, childwindows are noticed and you may get a child window instead of a toplevel window.
187 #===Return value
188 #The internal ID of the found window.
189 #===Raises
190 #[XError] Error invoking +xdotool+.
191 #===Example
192 # p XDo::XWindow.focused_window #=> 41943073
193 # p XDo::XWindow.focused_window(true) #=> 41943074
194 #===Remarks
195 #This method may find an invisible window, see active_window for a more reliable method.
196 def focused_window(notice_children = false)
197 err = ""
198 out = ""
199 Open3.popen3("#{XDo::XDOTOOL} getwindowfocus #{notice_children ? "-f" : ""}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
200 raise(XDo::XError, err) unless err.empty?
201 return out.to_i
202 end
203
204 #Returns the internal ID of the currently focused window.
205 #===Return value
206 #The ID of the found window.
207 #===Raises
208 #[XError] Error invoking +xdotool+.
209 #===Example
210 # p XDo::XWindow.active_window #=> 41943073
211 #===Remarks
212 #This method is more reliable than #focused_window, but never finds an invisible window.
213 #
214 #Part of the EWMH standard ACTIVE_WINDOW.
215 def active_window
216 err = ""
217 out = ""
218 Open3.popen3("#{XDo::XDOTOOL} getactivewindow"){|stdin, stdout, stderr| out = stdout.read; err = stderr.read}
219 raise(XDo::XError, err) unless err.empty?
220 return Integer(out)
221 end
222
223 #Set the number of working desktops.
224 #===Parameters
225 #[+num+] The number of desktops you want to exist.
226 #===Return value
227 #+num+.
228 #===Raises
229 #[XError] Error invoking +xdotool+.
230 #===Example
231 # XDo::XWindow.desktop_num = 2
232 #===Remarks
233 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
234 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
235 #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?
236 #That's the reason, why the desktop-related methods don't work with Ubuntu.
237 #
238 #Part of the EWMH standard WM_DESKTOP.
239 def desktop_num=(num)
240 err = ""
241 Open3.popen3("#{XDo::XDOTOOL} set_num_desktops #{num}"){|stdin, stdout, stderr| err << stderr.read}
242 raise(XDo::Error, err) unless err.empty?
243 num
244 end
245
246 #Get the number of working desktops.
247 #===Return value
248 #The number of desktops.
249 #===Raises
250 #[XError] Error invoking +xdotool+.
251 #===Example
252 # p XDo::XWindow.desktop_num = 1
253 #===Remarks
254 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
255 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
256 #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?
257 #That's the reason, why the desktop-related methods don't work with Ubuntu.
258 #
259 #Part of the EWMH standard WM_DESKTOP.
260 def desktop_num
261 err = ""
262 out = ""
263 Open3.popen3("#{XDo::XDOTOOL} get_num_desktops"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
264 raise(XDo::XError, err) unless err.empty?
265 Integer(out)
266 end
267
268 #Change the view to desktop +num+.
269 #===Parameters
270 #[+num+] The 0-based index of the desktop you want to switch to.
271 #===Return value
272 #+num+.
273 #===Raises
274 #[XError] Error invoking +xdotool+.
275 #===Example
276 # XDo::XWindow.desktop = 1
277 #===Remarks
278 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
279 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
280 #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?
281 #That's the reason, why the desktop-related methods don't work with Ubuntu.
282 #
283 #Part of the EWMH standard CURRENT_DESKTOP.
284 def desktop=(num)
285 err = ""
286 Open3.popen3("#{XDo::XDOTOOL} set_desktop #{num}"){|stdin, stdout, stderr| err << stderr.read}
287 raise(XDo::XError, err) unless err.empty?
288 num
289 end
290
291 #Returns the number of the active desktop.
292 #===Return value
293 #The number of the currently shown desktop.
294 #===Raises
295 #[XError] Error invoking +xdotool+.
296 #===Example
297 # p XDo::XWindow.desktop #=> 0
298 #===Remarks
299 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
300 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
301 #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?
302 #That's the reason, why the desktop-related methods don't work with Ubuntu.
303 #
304 #Part of the EWMH standard CURRENT_DESKTOP.
305 def desktop
306 err = ""
307 out = ""
308 Open3.popen3("#{XDo::XDOTOOL} get_desktop"){|stdin, stdout, stderr| out = stdout.read; err = stderr.read}
309 raise(XDo::XError, err) unless err.empty?
310 Integer(out)
311 end
312
313 #Creates a XWindow by calling search with the given parameters.
314 #The window is created from the first ID found.
315 #===Parameters
316 #[+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.
317 #[<tt>*opts</tt> (<tt>[:name, :class, :classname]</tt>) Search parameters. See XWindow.search.
318 #===Return value
319 #The created XWindow object.
320 #===Raises
321 #[XError] Error invoking +xdotool+.
322 #===Example
323 # #Exact title/class/classname match
324 # xwin = XDo::XWindow.from_search("xwindow.rb - SciTE")
325 # #Part match via regexp
326 # xwin = XDo::XWindow.from_search(/SciTE/)
327 # #Part match via string - DEPRECATED.
328 # xwin = XDo::XWindow.from_search("SciTE")
329 # #Only search the window classes
330 # xwin = XDo::XWindow.from_search(/SciTE/, :class)
331 def from_search(name, *opts)
332 if opts.first.kind_of?(Hash)
333 warn("#{caller.first}: Deprecation Warning: Using a hash as further arguments is deprecated. Pass the symbols directly.")
334 opts = opts.first.keys
335 end
336
337 ids = search(name, *opts)
338 raise(XDo::XError, "The window '#{name}' wasn't found!") if ids.empty?
339 new(ids.first)
340 end
341
342 #_Deprecated_. Use XWindow.from_search or XWindow.from_title instead.
343 def from_name(name, *opts)
344 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.")
345 from_search(name, *opts)
346 end
347
348 #Same as XWindow.from_search, but only looks for the window's titles to match.
349 #===Parameters
350 #[+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.
351 #===Return value
352 #A XWindow object made up from the first window ID found.
353 #===Raises
354 #[XError] Error invoking +xdotool+.
355 #===Example
356 # #Exact string match
357 # xwin = XDo::XWindow.from_title("xwindow.rb - SciTE")
358 # #Part match via regexp
359 # xwin = XDo::XWindow.from_title(/SciTE/)
360 # #Part match via string - DEPRECATED.
361 # xwin = XDo::XWindow.from_title("SciTE")
362 def from_title(title)
363 from_search(title, :name)
364 end
365
366 #Creates a XWindow by calling XWindow.focused_window with the given parameter.
367 #===Parameters
368 #[+notice_children+] (false) If true, you may get a child window as the active window.
369 #===Return value
370 #The newly created XWindow objects.
371 #===Example
372 # xwin = XDo::XWindow.from_focused
373 #===Remarks
374 #The XWindow.focused_window method is a bit dangerous, since it may
375 #find an invisible window. Use XWindow.from_active if you don't want that.
376 def from_focused(notice_childs = false)
377 new(focused_window(notice_childs))
378 end
379
380 #Creates a XWindow by calling active_window.
381 #===Return value
382 #The newly created XWindow object.
383 #===Example
384 # xwin = XDo::XWindow.from_active
385 #===Remarks
386 #This method does not find invisible nor child windows; if you want that,
387 #you should take a look at XWindow.from_focused.
388 def from_active
389 new(active_window)
390 end
391
392 #Returns the ID of the root window.
393 #===Return value
394 #The ID of the root window.
395 #===Example
396 # p XDo::XWindow.root_id #=> 346
397 def root_id
398 out = ""
399 err = ""
400 Open3.popen3("#{XDo::XWININFO} -root"){|stdin, stdout, stderr| out << stdout.read.strip; err << stderr.read.strip}
401 Kernel.raise(XDo::XError, err) unless err.empty?
402 Integer(out.lines.to_a[0].match(/Window id:(.*?)\(/)[1].strip)
403 end
404
405 #Creates a XWindow refering to the root window.
406 #===Return value
407 #The newly created XWindow object.
408 #===Example
409 # rwin = XDo::XWindow.from_root
410 def from_root
411 new(root_id)
412 end
413
414 #Creates a invalid XWindow.
415 #===Return value
416 #The newly created XWindow object.
417 #===Example
418 # nwin = XDo::XWindow.from_null
419 #===Remarks
420 #The handle the returned XWindow object uses is zero and
421 #therefore invalid. You can't call #move, #resize or other
422 #methods on it, but it may be useful for unsetting focus.
423 #See also the XWindow.unfocus method.
424 def from_null
425 new(0) #Zero never is a valid window ID. Even the root window has another ID.
426 end
427
428 #Unsets the input focus by setting it to the invalid
429 #NULL window.
430 #===Parameters
431 #[+sync+] (true) If true, this method blocks until the input focus has been unset.
432 #===Return value
433 #nil.
434 #===Example
435 # win = XDo::XWindow.from_active
436 # win.focus
437 # XDo::XWindow.unfocus
438 def unfocus(sync = true)
439 from_null.focus(sync)
440 end
441
442 #Deprecated.
443 def desktop_name=(name)
444 warn("#{caller.first}: Deprecation warning: XWindow.desktop_name= doesn't do anything anymore.")
445 end
446
447 #Deprecated.
448 def desktop_name
449 warn("#{caller.first}: Deprecation warning: XWindow.desktop_name doesn't do anything anymore.")
450 "x-nautilus-desktop"
451 end
452
453 #Deprecated. Just calls XWindow.unfocus internally.
454 def focus_desktop
455 warn("#{caller.first}: Deprecation warning: XWindow.focus_desktop is deprecated. Use XWindow.unfocus instead.")
456 unfocus
457 end
458 alias activate_desktop focus_desktop
459
460 #Minimize all windows (or restore, if already) by sending [CTRL]+[ALT]+[D].
461 #Available after requireing "xdo/keyboard".
462 #===Return value
463 #Undefined.
464 #===Raises
465 #[NotImplementedError] You didn't require 'xdo/keyboard'.
466 #===Example
467 # #Everything will be minimized:
468 # XDo::XWindow.toggle_minimize_all
469 # #And now we'll restore everything.
470 # XDo::XWindow.toggle_minimize_all
471 def toggle_minimize_all
472 raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard
473 XDo::Keyboard.ctrl_alt_d
474 end
475
476 #Minimizes the active window. There's no way to restore a specific minimized window.
477 #Available after requireing "xdo/keyboard".
478 #===Return value
479 #Undefined.
480 #===Raises
481 #[NotImplementedError] You didn't require 'xdo/keyboard'.
482 #===Example
483 # XDo::XWindow.minimize
484 def minimize
485 raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard
486 XDo::Keyboard.key("Alt+F9")
487 end
488
489 #Maximize or normalize the active window if already maximized.
490 #Available after requireing "xdo/keyboard".
491 #===Return value
492 #Undefined.
493 #===Raises
494 #[NotImplementedError] You didn't require 'xdo/keyboard'.
495 #===Example
496 # XDo::XWindow.minimize
497 # XDo::XWindow.toggle_maximize
498 def toggle_maximize
499 raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard
500 XDo::Keyboard.key("Alt+F10")
501 end
502
503 end
504
505 ##
506 # :method: title=
507 #call-seq:
508 # title = str
509 # name = str
510 #
511 #Changes a window's title.
512 #===Parameters
513 #[+str+] The new title.
514 #===Return value
515 #+str+.
516 #===Raises
517 #[XError] Error invoking +xdotool+.
518 #===Example
519 # xwin.title = "Ruby is awesome!"
520
521 ##
522 # :method: icon_title=
523 #call-seq:
524 # icon_title = str
525 # icon_name = str
526 #
527 #Changes the window's icon title, i.e. the string that is displayed in
528 #the task bar or panel where all open windows show up.
529 #===Parameters
530 #[+str+] The string you want to set.
531 #===Return value
532 #+str+.
533 #===Raises
534 #[XError] Error invoking +xdotool+.
535 #===Example
536 # xwin.icon_title = "This is minimized."
537
538 ##
539 # :method: classname=
540 #call-seq:
541 # classname = str
542 #
543 #Sets a window's classname.
544 #===Parameters
545 #[+str+] The window's new classname.
546 #===Return value
547 #+str+.
548 #===Raises
549 #[XError] Error invoking +xdotool+.
550 #===Example
551 # xwin.classname = "MyNewClass"
552
553 #Creates a new XWindow object from an internal ID.
554 #===Parameters
555 #[+id+] The internal ID to create the window from.
556 #===Return value
557 #The newly created XWindow object.
558 #===Example
559 # id = XWindow.search(/edit/)[1]
560 # xwin = XWindow.new(id)
561 #===Remarks
562 #See also many class methods of the XWindow class which allow
563 #you to forget about the internal ID of a window.
564 def initialize(id)
565 @id = id.to_i
566 end
567
568 #Human-readable output of form
569 # <XDo::XWindow: "title" (window_id)>
570 def inspect
571 %Q|<#{self.class}: "#{title}" (#{id})>|
572 end
573
574 #Set the size of a window.
575 #===Parameters
576 #[+width+] The new width, usually in pixels.
577 #[+height+] The new height, usually in pixels.
578 #[+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.
579 #[+sync+] (true) If true, this method blocks until the window has finished resizing.
580 #===Return value
581 #Undefined.
582 #===Raises
583 #[XError] Error executing +xdotool+.
584 #===Example
585 # #Resize a window to 400x300px
586 # xwin.resize(400, 300)
587 # #Resize a terminal window to 100 rows and 100 columns
588 # xtermwin.resize(100, 100, true)
589 #===Remarks
590 #This has no effect on maximized winwows.
591 def resize(width, height, use_hints = false, sync = true)
592 err = ""
593 opts = []
594 opts << "--usehints" if use_hints
595 opts << "--sync" if sync
596 Open3.popen3("#{XDo::XDOTOOL} windowsize #{opts.join(" ")} #{@id} #{width} #{height}"){|stdin, stdout, stderr| err << stderr.read}
597 Kernel.raise(XDo::XError, err) unless err.empty?
598 end
599
600 #Moves a window. +xdotool+ is not really exact with the coordinates,
601 #special windows like Ubuntu's panels make it placing wrong.
602 #===Parameters
603 #[+x+] The goal X coordinate.
604 #[+y+] The goal Y coordinate.
605 #[+sync+] (true) If true, this method blocks until the window has finished moving.
606 #===Return value
607 #Undefined.
608 #===Raises
609 #[XError] Error executing +xdotool+.
610 #===Example
611 # xwin.move(100, 100)
612 # p xwin.abs_position #=> [101, 101]
613 def move(x, y, sync = true)
614 err = ""
615 opts = []
616 opts << "--sync" if sync
617 Open3.popen3("#{XDo::XDOTOOL} windowmove #{opts.join(" ")} #{@id} #{x} #{y}"){|stdin, stdout, stderr| err << stderr.read}
618 Kernel.raise(XDo::XError, err) unless err.empty?
619 end
620
621 #Set the input focus to the window (but don't bring it to the front).
622 #===Parameters
623 #[+sync+] (true) If true, this method blocks until the window got the input focus.
624 #===Return value
625 #Undefined.
626 #===Raises
627 #[XError] Error invoking +xdotool+.
628 #===Example
629 # xwin.focus
630 #===Remarks
631 #This method may not work on every window manager. You should use
632 ##activate, which is supported by more window managers.
633 def focus(sync = true)
634 err = ""
635 opts = []
636 opts << "--sync" if sync
637 Open3.popen3("#{XDo::XDOTOOL} windowfocus #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read}
638 Kernel.raise(XDo::XError, err) unless err.empty?
639 end
640
641 #The window loses the input focus by setting it to an invalid window.
642 #Parameters
643 #[+sync+] (true) If true, this method blocks until the focus has been set to nothing.
644 #===Return value
645 #Undefined.
646 #===Example
647 # xwin.focus
648 # xwin.unfocus
649 def unfocus(sync = true)
650 XDo::XWindow.unfocus(sync)
651 end
652
653 #Maps a window to the screen (makes it visible).
654 #===Parameters
655 #[+sync+] (true) If true, this method blocks until the window has been mapped.
656 #===Return value
657 #Undefined.
658 #===Raises
659 #[XError] Error invoking +xdotool+.
660 #===Example
661 # xwin.unmap #Windows are usually mapped
662 # xwin.map
663 def map(sync = true)
664 err = ""
665 opts = []
666 opts << "--sync" if sync
667 Open3.popen3("#{XDo::XDOTOOL} windowmap #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read}
668 Kernel.raise(XDo::XError, err) unless err.empty?
669 end
670
671 #Unmap a window from the screen (make it invisible).
672 #===Parameters
673 #[+sync+] (true) If true, this method blocks until the window has been unmapped.
674 #===Return value
675 #Undefined.
676 #===Raises
677 #[XError] Error executing +xdotool+.
678 #===Example
679 # xwin.unmap
680 def unmap(sync = true)
681 err = ""
682 opts = []
683 opts << "--sync" if sync
684 Open3.popen3("#{XDo::XDOTOOL} windowunmap #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read}
685 Kernel.raise(XDo::XError, err) unless err.empty?
686 end
687
688 #Bring a window to the front (but don't give it the input focus).
689 #Not implemented in all window managers.
690 #===Return value
691 #Undefined.
692 #===Raises
693 #[XError] Error executing +xdotool+.
694 #===Example
695 # xwin.raise
696 def raise
697 err = ""
698 Open3.popen3("#{XDo::XDOTOOL} windowraise #{@id}"){|stdin, stdout, stderr| err << stderr.read}
699 Kernel.raise(XDo::XError, err) unless err.empty?
700 end
701
702 #Activate a window. That is, bring it to top and give it the input focus.
703 #===Parameters
704 #[+sync+] (true) If true, this method blocks until the window has been activated.
705 #===Return value
706 #Undefined.
707 #===Raises
708 #[XError] Error executing +xdotool+.
709 #===Example
710 # xwin.activate
711 #===Remarks
712 #This is the recommanded method to give a window the input focus, since
713 #it works on more window managers than #focus and also works across
714 #desktops.
715 #
716 #Part of the EWMH standard ACTIVE_WINDOW.
717 def activate(sync = true)
718 tried_focus = false
719 begin
720 err = ""
721 opts = []
722 opts << "--sync" if sync
723 Open3.popen3("#{XDo::XDOTOOL} windowactivate #{opts.join(" ")} #{@id}"){|stdin, stdout, stderr| err << stderr.read}
724 Kernel.raise(XDo::XError, err) unless err.empty?
725 rescue XDo::XError
726 #If no window is active, xdotool's windowactivate fails,
727 #because it tries to determine which is the currently active window.
728 unless tried_focus
729 tried_focus = true
730 focus
731 retry
732 else
733 raise
734 end
735 end
736 end
737
738 #Move a window to a desktop.
739 #===Parameters
740 #[+num+] The 0-based index of the desktop you want the window to move to.
741 #===Return value
742 #Undefined.
743 #===Raises
744 #[XError] Error executing +xdotool+.
745 #===Example
746 # xwin.desktop = 3
747 #===Remarks
748 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
749 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
750 #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?
751 #That's the reason, why the desktop-related methods don't work with Ubuntu.
752 #
753 #Part of the EWMH standard CURRENT_DESKTOP.
754 def desktop=(num)
755 err = ""
756 Open3.popen3("#{XDo::XDOTOOL} set_desktop_for_window #{@id} #{num}"){|stdin, stdout, stderr| err << stderr.read}
757 Kernel.raise(XDo::XError, err) unless err.empty?
758 end
759
760 #Get the desktop the window is on.
761 #===Return value
762 #The 0-based index of the desktop this window resides on.
763 #===Raises
764 #[XError] Error executing +xdotool+.
765 #===Example
766 # p xwin.desktop #=> 0
767 #===Remarks
768 #Although Ubuntu systems seem to have several desktops, that isn't completely true. An usual Ubuntu system only
769 #has a single working desktop, on which Ubuntu sets up an arbitrary number of other "desktop views" (usually 4).
770 #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?
771 #That's the reason, why the desktop-related methods don't work with Ubuntu.
772 #
773 #Part of the EWMH standard CURRENT_DESKTOP.
774 def desktop
775 err = ""
776 out = ""
777 Open3.popen3("#{XDo::XDOTOOL} get_desktop_for_window #{@id}"){|stdin, stdout, stderr| out = stdout.read; err << stderr.read}
778 Kernel.raise(XDo::XError, err) unless err.empty?
779 Integer(out)
780 end
781
782 #The title of the window or nil if it doesn't have a title.
783 #===Return value
784 #The window's title, encoded as UTF-8, or nil if the window doesn't have a title.
785 #===Raises
786 #[XError] Error executing +xwininfo+.
787 #===Example
788 # p xwin.title #=> "xwindow.rb SciTE"
789 def title
790 err = ""
791 out = ""
792 if @id == XWindow.root_id #This is the root window
793 return "(the root window)"
794 elsif @id.zero?
795 return "(NULL window)"
796 else
797 Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
798 end
799 Kernel.raise(XDo::XError, err) unless err.empty?
800 title = out.strip.lines.to_a[0].match(/"(.*)"/)[1] rescue Kernel.raise(XDo::XError, "No window with ID #{@id} found!")
801 return title #Kann auch nil sein, dann ist das Fenster namenlos.
802 end
803
804 #The absolute position of the window on the screen.
805 #===Return value
806 #A two-element array of form <tt>[x, y]</tt>.
807 #===Raises
808 #[XError] Error executing +xwininfo+.
809 #===Example
810 # p xwin.abs_position #=> [0, 51]
811 def abs_position
812 out = ""
813 err = ""
814 Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
815 Kernel.raise(XDo::XError, err) unless err.empty?
816 out = out.strip.lines.to_a
817 x = out[2].match(/:\s+(\d+)/)[1]
818 y = out[3].match(/:\s+(\d+)/)[1]
819 [x.to_i, y.to_i]
820 end
821 alias position abs_position
822
823 #The position of the window relative to it's parent window.
824 #===Return value
825 #A two-element array of form <tt>[x, y]</tt>.
826 #===Raises
827 #[XError] Error executing +xdotool+.
828 #===Example
829 # p xwin.rel_position => [0, 51]
830 def rel_position
831 out = ""
832 err = ""
833 Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
834 Kernel.raise(XDo::XError, err) unless err.empty?
835 out = out.strip.lines.to_a
836 x = out[4].match(/:\s+(\d+)/)[1]
837 y = out[5].match(/:\s+(\d+)/)[1]
838 [x.to_i, y.to_i]
839 end
840
841 #The size of the window.
842 #===Return value
843 #A two-element array of form <tt>[width, height]</tt>.
844 #===Raises
845 #[XError] Error executing +xwininfo+.
846 #===Example
847 # p xwin.size #=> [1280, 948]
848 def size
849 out = ""
850 err = ""
851 Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
852 out = out.strip.lines.to_a
853 Kernel.raise(XDo::XError, err) unless err.empty?
854 width = out[6].match(/:\s+(\d+)/)[1]
855 height = out[7].match(/:\s+(\d+)/)[1]
856 [width.to_i, height.to_i]
857 end
858
859 #true if the window is mapped to the screen.
860 #===Return value
861 #nil if the window is not mapped, an integer value otherwise.
862 #===Raises
863 #[XError] Error executing +xwininfo+.
864 #===Example
865 # p xwin.visible? #=> 470
866 # xwin.unmap
867 # p xwin.visible? #=> nil
868 def visible?
869 err = ""
870 out = ""
871 Open3.popen3("#{XDo::XWININFO} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
872 out = out.strip
873 Kernel.raise(XDo::XError, err) unless err.empty?
874 return out =~ /IsViewable/
875 end
876
877 #Returns true if the window exists.
878 #===Return value
879 #true or false.
880 #===Example
881 # p xwin.exists? #=> true
882 def exists?
883 XDo::XWindow.id_exists?(@id)
884 end
885
886 #Closes a window by activating it and then sending [ALT] + [F4].
887 #===Return value
888 #nil.
889 #===Raises
890 #[NotImplementedError] You didn't require "xdo/keyboard".
891 #===Example
892 # xwin.close
893 #===Remarks
894 #A program could ask to save data.
895 #
896 #Use #kill! to kill the process running the window.
897 #
898 #Available after requireing "xdo/keyboard".
899 def close
900 Kernel.raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard
901 activate
902 XDo::Keyboard.char("Alt+F4")
903 sleep 0.5
904 nil
905 end
906
907 #More aggressive variant of #close. Think of +close!+ as
908 #the middle between #close and #kill!. It first tries
909 #to close the window by calling #close and if that
910 #does not succeed (within +timeout+ seconds), it will call #kill!.
911 #===Paramters
912 #[+timeout+] (2) The time to wait before using #kill!, in seconds.
913 #===Return value
914 #Undefined.
915 #===Raises
916 #[XError] Error executing +xkill+.
917 #===Example
918 # xwin.close!
919 #===Remarks
920 #Available after requireing "xdo/keyboard".
921 def close!(timeout = 2)
922 Kernel.raise(NotImplementedError, "You have to require 'xdo/keyboard' before you can use #{__method__}!") unless defined? XDo::Keyboard
923 #Try to close normally
924 close
925 #Check if it's deleted
926 if exists?
927 #If not, wait some seconds and then check again
928 sleep timeout
929 if exists?
930 #If it's not deleted after some time, force it to close.
931 kill!
932 end
933 end
934 end
935
936 #Kills the process that runs a window. The window will be
937 #terminated immediatly, if that isn't what you want, have
938 #a look at #close.
939 #===Return value
940 #nil.
941 #===Raises
942 #[XError] Error executing +xkill+.
943 #===Example
944 # xwin.kill!
945 def kill!
946 out = ""
947 err = ""
948 Open3.popen3("#{XDo::XKILL} -id #{@id}"){|stdin, stdout, stderr| out << stdout.read; err << stderr.read}
949 Kernel.raise(XDo::XError, err) unless err.empty?
950 nil
951 end
952
953 #Returns the window's internal ID.
954 #===Return value
955 #An integer describing the window's internal ID.
956 #===Example
957 # p xwin.to_i #=> 29361095
958 def to_i
959 @id
960 end
961
962 #Returns a window's title.
963 #===Return value
964 #The window's title.
965 #===Example
966 # p xwin.to_s #=> "xwindow.rb * SciTE"
967 def to_s
968 title
969 end
970
971 #true if the internal ID is zero.
972 #===Return value
973 #true or false.
974 #===Example
975 # p xwin.zero? #=> false
976 def zero?
977 @id.zero?
978 end
979
980 #true if the internal ID is not zero.
981 #===Return value
982 #nil or the internal ID.
983 #===Example
984 # p xwin.nonzero? #=> 29361095
985 def nonzero?
986 @id.nonzero?
987 end
988
989 [:"name=", :"icon_name=", :"classname="].each do |sym|
990 define_method(sym) do |str|
991 set_window(sym.to_s[0..-2].gsub("_", "-"), str.encode("UTF-8"))
992 str
993 end
994 end
995 alias title= name=
996 alias icon_title= icon_name=
997
998 private
999
1000 #Calls +xdotool+'s set_window command with the given options.
1001 def set_window(option, value)
1002 err = ""
1003 Open3.popen3("#{XDOTOOL} set_window --#{option} '#{value}' #{@id}"){|stdin, stdout, stderr| err << stderr.read}
1004 Kernel.raise(XDo::XError, err) unless err.empty?
1005 end
1006
1007 end
1008
1009end
01010
=== renamed file 'tests/run-with-xvfb.sh' => 'tests/misc/run-with-xvfb.sh'
=== renamed file 'tests/unitytestmacro.h' => 'tests/misc/unitytestmacro.h'
=== added directory 'tests/other'
=== added directory 'tests/panel'
=== added directory 'tests/places'
=== added file 'tests/run-tests.rb'
--- tests/run-tests.rb 1970-01-01 00:00:00 +0000
+++ tests/run-tests.rb 2011-12-15 16:55:31 +0000
@@ -0,0 +1,74 @@
1#!/usr/bin/env ruby1.8
2=begin
3/*
4 * This file is part of unity-2d
5 *
6 * Copyright 2011 Canonical Ltd.
7 *
8 * Authors:
9 * - Gerry Boland <gerry.boland@canonical.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; version 3.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23=end
24
25# Testability test suite execution script
26#
27# Run all tests with
28# $ ruby run-tests.rb
29
30# Check script being run by Ruby 1.8.x, later versions not supported by TDriver
31abort("Aborted! Ruby 1.9 not supported, use 1.8") unless RUBY_VERSION < '1.9'
32
33# Add ./misc/lib to the list of library locations - need to calculate absolute path
34require 'pathname'
35$library_path = File.expand_path(File.dirname(__FILE__)) + '/misc/lib'
36$LOAD_PATH.unshift $library_path
37
38# If cmake was called, obtain the path to the built binary directory. If not, we test the
39# installed applications instead
40binary_dir_file = $library_path + '/../binary_dir.txt'
41if File.exists?(binary_dir_file)
42 binary_dir = File.open(binary_dir_file).first.strip
43 puts 'Running tests on applications contained within ' + binary_dir
44 UNITY_2D_LAUNCHER = binary_dir + '/launcher/app/unity-2d-launcher'
45 UNITY_2D_PANEL = binary_dir + '/panel/app/unity-2d-panel'
46 UNITY_2D_PLACES = binary_dir + '/places/app/unity-2d-places'
47 UNITY_2D_SPREAD = binary_dir + '/spread/app/unity-2d-spread'
48else
49 puts 'NOTICE: source not configured, tests will be carried out on *installed* applications!'
50 UNITY_2D_LAUNCHER = 'unity-2d-launcher'
51 UNITY_2D_PANEL = 'unity-2d-panel'
52 UNITY_2D_PLACES = 'unity-2d-places'
53 UNITY_2D_SPREAD = 'unity-2d-spread'
54end
55
56# The following line includes the complete tdriver environment
57require 'tdriver'
58include TDriverVerify
59
60# Require unit test framework: This enables execution of test cases and also includes assertions (Test::Unit::Assertions)
61require 'testhelper'
62
63# List of directories in which to search for test cases
64test_directories = ['launcher', 'panel', 'places', 'spread', 'window-manager', 'other']
65
66# Only run scan for tests if this script is directly called
67if __FILE__ == $0
68 $INIT_COMPLETED = true # Prevent this file being included by test cases
69
70 # Scan through the above directories and execute test cases contained.
71 test_directories.each do | directory |
72 Dir["#{directory}/*.rb"].each { |testCase| require testCase}
73 end
74end
075
=== added directory 'tests/spread'
=== added directory 'tests/window-manager'

Subscribers

People subscribed via source and target branches