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

Subscribers

People subscribed via source and target branches