summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormallum <mallum@f5eea0f0-44ea-0310-b729-df5b855dafe5>2005-03-22 18:51:40 +0000
committermallum <mallum@f5eea0f0-44ea-0310-b729-df5b855dafe5>2005-03-22 18:51:40 +0000
commitb989a142a1cedd2691eb6b4ba23f92c4cca6c7ec (patch)
treefe08cde5de5099696c83ab81a1c5d00a74e7f738
move and rename interaction -> xresponse
-rw-r--r--AUTHORS0
-rw-r--r--ChangeLog45
-rw-r--r--Makefile.am7
-rw-r--r--NEWS0
-rw-r--r--README59
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac28
-rw-r--r--xresponse.c421
8 files changed, 563 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/AUTHORS
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..deb95ff
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,45 @@
+2005-03-07 mallum,,, <mallum@openedhand.com>
+
+ * README:
+ * configure.ac:
+ * xresponse.c: (get_server_time), (get_xevent_timed), (fake_event),
+ (wait_response), (usage), (main):
+ Various tweaks.
+
+2005-01-18 mallum,,, <mallum@openedhand.com>
+
+ * README:
+ Populate
+ * configure.ac:
+ Bump up to 0.2
+ * xresponse.c: (get_xevent_timed), (usage), (main):
+ Add support for '-i' switch
+
+2005-01-18 mallum,,, <mallum@openedhand.com>
+
+ * xresponse.c: (handle_xerror), (log_action), (get_server_time),
+ (get_xevent_timed), (setup_display), (eat_damage), (fake_event),
+ (wait_response), (usage), (main):
+ Redo command line parsing with extra opts and loop.
+
+2005-01-18 mallum,,, <mallum@openedhand.com>
+
+ * xresponse.c: (log_action), (wait_response), (usage), (main):
+ Improve option handling, log output.
+
+2005-01-17 mallum,,, <mallum@openedhand.com>
+
+ * Makefile.am:
+ * configure.ac:
+ * interaction.c:
+ * xresponse.c:
+ rename interaction.c -> xresponse.c
+
+2005-01-17 mallum,,, <mallum@openedhand.com>
+
+ * Makefile.am:
+ * autogen.sh:
+ * configure.ac:
+ Autotoolify
+ * interaction.c:
+ Refactor a little
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..2597873
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,7 @@
+AM_CFLAGS = @GCC_FLAGS@ @XLIBS_CFLAGS@
+
+bin_PROGRAMS=xresponse
+
+xresponse_SOURCES = xresponse.c
+
+xresponse_LDADD = @XLIBS_LIBS@
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..b4ebcec
--- /dev/null
+++ b/README
@@ -0,0 +1,59 @@
+xresponse
+====
+
+'xresponse' is a simple tool for meassuring UI response times to a
+full mouse click event. It requires the Xtest, to 'fake' the mouse
+event, and XDamage, to report areas of the display that have changed.
+
+
+Building
+====
+
+Its recommended you have the freedesktop.org autotooled xlibs
+installed with pkg-config (.pc ) files for xlibs, xext, xtst and
+xdamage. Then it should be a case of the usual ./configure, make and
+make install.
+
+
+Usage
+===
+
+xresponse <-o|--logfile output> [commands..]
+
+Commands are any combination/order of;
+
+-c|--click <XxY> Send click and await damage response
+-m|--monitor <WIDTHxHEIGHT+X+Y> Watch area for damage ( default fullscreen )
+-w|--wait <seconds> Max time to wait for damage, set to 0 to
+ monitor for ever.
+ ( default 5 secs)
+-s|--stamp <string> Write 'string' to log file
+
+-i|--inspect Just display damage events
+
+
+Examples
+===
+
+Click mouse at 100,100 and collect max 5 seconds of damage events;
+
+% xresponse -c 100x100
+
+Click the mouse at 100x100, 100x150 and 200x200 colecting a max second
+of damage for each only in 240x320+0+0;
+
+% xresponse -w 1 -m 240x320+0+0 -c 100x100 -c 100x150 -c 200x200
+
+Monitor all damage for ever;
+
+% xresponse -w 0 -i
+
+Monitor only top corner of screen for 10 seconds, then send a click collecting
+5 seconds of damage
+
+% xresponse -w 10 -m 240x320+0+0 -i -w 5 -c 100x100
+
+Tips
+===
+
+- you can use 'xmag' to figure out screen co-ords for window clicks.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..b1376df
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+autoreconf -v --install || exit 1
+./configure --enable-maintainer-mode "$@"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..d024755
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,28 @@
+AC_PREREQ(2.53)
+AC_INIT([xresponse], 0.3.1, [ross@o-hand.com])
+AC_CONFIG_SRCDIR([xresponse.c])
+
+AM_INIT_AUTOMAKE()
+
+AM_CONFIG_HEADER([config.h])
+
+# Checks for programs.
+AC_PROG_CC
+
+#PKG_CHECK_MODULES(X11, x11, [have_libx11pc="yes"], [have_libx11pc="no"])
+
+# Very lazy check, possibly do old way aswell, but damage will be needed
+# whatever so likely will need autoconfed ( fd.o ) xlibs.
+PKG_CHECK_MODULES(XLIBS, x11 xext xtst xdamage)
+
+AC_SUBST(XLIBS_CFLAGS)
+AC_SUBST(XLIBS_LIBS)
+
+if test "x$GCC" = "xyes"; then
+ GCC_FLAGS="-g -Wall"
+fi
+
+AC_SUBST(GCC_FLAGS)
+
+# Checks for header files.
+AC_OUTPUT([Makefile])
diff --git a/xresponse.c b/xresponse.c
new file mode 100644
index 0000000..68c5621
--- /dev/null
+++ b/xresponse.c
@@ -0,0 +1,421 @@
+/*
+ * xresponse - Interaction latency tester,
+ *
+ * Written by Ross Burton & Matthew Allum
+ * <info@openedhand.com>
+ *
+ * Copyright (C) 2005 Nokia
+ *
+ * Licensed under the GPL v2 or greater.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <sys/time.h>
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/extensions/XTest.h>
+#include <X11/extensions/Xdamage.h>
+
+/*
+ * defs
+ */
+
+#define streq(a,b) (strcmp(a,b) == 0)
+
+typedef struct Rectangle { int x,y,width,height; } Rectangle;
+
+/*
+ * Global variables
+ */
+
+FILE *LogFile = NULL; /* The file to output the log output too */
+int DamageEventNum; /* Damage Ext Event ID */
+Atom AtomTimestamp; /* Atom for getting server time */
+int DamageWaitSecs = 5; /* Max time to collect damamge */
+Rectangle InterestedDamageRect; /* Damage rect to monitor */
+
+int
+handle_xerror(Display *dpy, XErrorEvent *e)
+{
+ /* Really only here for debugging, for gdb backtrace */
+ char msg[255];
+ XGetErrorText(dpy, e->error_code, msg, sizeof msg);
+ fprintf(stderr, "X error (%#lx): %s (opcode: %i)\n",
+ e->resourceid, msg, e->request_code);
+
+ exit(1);
+}
+
+
+/**
+ * Perform simple logging with timestamp and diff from last log
+ */
+void
+log_action(Time time, int is_stamp, const char *format, ...)
+{
+ static Time last_time;
+ va_list ap;
+ char *tmp = NULL;
+ static int displayed_header;
+
+ va_start(ap,format);
+ vasprintf(&tmp, format, ap);
+ va_end(ap);
+
+ if (!displayed_header) /* Header */
+ {
+ fprintf(LogFile, "\n"
+ " Server Time : Diff : Info\n"
+ "-----------------------------\n");
+ displayed_header = 1;
+ }
+
+ if (is_stamp)
+ {
+ fprintf(LogFile, "%s\n", tmp);
+ }
+ else
+ {
+ fprintf(LogFile, "%10lums : %5lums : %s",
+ time,
+ (last_time > 0 && time > 0) ? time - last_time : 0,
+ tmp);
+ }
+
+ if (time) last_time = time;
+
+ if (tmp) free(tmp);
+}
+
+/**
+ * Get the current timestamp from the X server.
+ */
+static Time
+get_server_time(Display *dpy)
+{
+ XChangeProperty (dpy, DefaultRootWindow (dpy),
+ AtomTimestamp, AtomTimestamp, 8,
+ PropModeReplace, "a", 1);
+ for (;;)
+ {
+ XEvent xevent;
+
+ XMaskEvent (dpy, PropertyChangeMask, &xevent);
+ if (xevent.xproperty.atom == AtomTimestamp)
+ return xevent.xproperty.time;
+ }
+}
+
+/**
+ * Get an X event with a timeout ( in secs ). The timeout is
+ * updated for the number of secs left.
+ */
+static Bool
+get_xevent_timed(Display *dpy,
+ XEvent *event_return,
+ struct timeval *tv) /* in seconds */
+{
+
+ if (tv == NULL || (tv->tv_sec == 0 && tv->tv_usec == 0))
+ {
+ XNextEvent(dpy, event_return);
+ return True;
+ }
+
+ XFlush(dpy);
+
+ if (XPending(dpy) == 0)
+ {
+ int fd = ConnectionNumber(dpy);
+ fd_set readset;
+
+ FD_ZERO(&readset);
+ FD_SET(fd, &readset);
+
+ if (select(fd+1, &readset, NULL, NULL, tv) == 0)
+ return False;
+ else
+ {
+ XNextEvent(dpy, event_return);
+
+ /* *timeout = tv.tv_sec; */ /* XXX Linux only ? */
+
+ return True;
+ }
+
+ } else {
+ XNextEvent(dpy, event_return);
+ return True;
+ }
+}
+
+/**
+ * Set up Display connection, required extensions and req other X bits
+ */
+static Display*
+setup_display(char *dpy_name)
+{
+ Display *dpy;
+ Damage damage;
+ int unused;
+
+ if ((dpy = XOpenDisplay(dpy_name)) == NULL)
+ {
+ fprintf (stderr, "Unable to connect to DISPLAY.\n");
+ return NULL;
+ }
+
+ /* Check the extensions we need are available */
+
+ if (!XTestQueryExtension (dpy, &unused, &unused, &unused, &unused)) {
+ fprintf (stderr, "No XTest extension found\n");
+ return NULL;
+ }
+
+ if (!XDamageQueryExtension (dpy, &DamageEventNum, &unused)) {
+ fprintf (stderr, "No DAMAGE extension found\n");
+ return NULL;
+ }
+
+ /* Set up our interested rect */
+ InterestedDamageRect.x = 0;
+ InterestedDamageRect.y = 0;
+ InterestedDamageRect.width = DisplayWidth(dpy, DefaultScreen(dpy));
+ InterestedDamageRect.height = DisplayHeight(dpy, DefaultScreen(dpy));
+
+ XSetErrorHandler(handle_xerror);
+
+ XSynchronize(dpy, True);
+
+ /* Needed for get_server_time */
+ AtomTimestamp = XInternAtom (dpy, "_X_LATENCY_TIMESTAMP", False);
+ XSelectInput(dpy, DefaultRootWindow(dpy), PropertyChangeMask);
+
+ /* XXX Return/global this ? */
+ damage = XDamageCreate (dpy,
+ DefaultRootWindow(dpy),
+ XDamageReportBoundingBox);
+ return dpy;
+}
+
+
+/**
+ * Eat all Damage events in the X event queue.
+ */
+static void
+eat_damage(Display *dpy)
+{
+ while (XPending(dpy))
+ {
+ XEvent xev;
+ XDamageNotifyEvent *dev;
+
+ XNextEvent(dpy, &xev);
+
+ if (xev.type == DamageEventNum + XDamageNotify)
+ {
+ dev = (XDamageNotifyEvent*)&xev;
+ XDamageSubtract(dpy, dev->damage, None, None);
+ }
+ }
+}
+
+/**
+ * 'Fakes' a mouse click, returning time sent.
+ */
+static Time
+fake_event(Display *dpy, int x, int y)
+{
+ Time start;
+
+ XTestFakeMotionEvent(dpy, DefaultScreen(dpy), x, y, CurrentTime);
+
+ /* Eat up any damage caused by above pointer move */
+ eat_damage(dpy);
+
+ start = get_server_time(dpy);
+
+ /* Sent click */
+ XTestFakeButtonEvent(dpy, Button1, True, CurrentTime);
+ XTestFakeButtonEvent(dpy, Button1, False, CurrentTime);
+
+ return start;
+}
+
+/**
+ * Waits for a damage 'response' to above click
+ */
+static Bool
+wait_response(Display *dpy)
+{
+ XEvent e;
+ struct timeval tv;
+ int waitsecs, lastsecs;
+
+ tv.tv_sec = lastsecs = DamageWaitSecs;
+ tv.tv_usec = 0;
+
+ while (get_xevent_timed(dpy, &e, &tv))
+ {
+ if (e.type == DamageEventNum + XDamageNotify)
+ {
+ XDamageNotifyEvent *dev = (XDamageNotifyEvent*)&e;
+
+ if (dev->area.x >= InterestedDamageRect.x
+ && dev->area.width <= InterestedDamageRect.width
+ && dev->area.y >= InterestedDamageRect.y
+ && dev->area.height <= InterestedDamageRect.height)
+ {
+ log_action(dev->timestamp, 0, "Got damage event %dx%d+%d+%d\n",
+ dev->area.width, dev->area.height,
+ dev->area.x, dev->area.y);
+ }
+ else waitsecs = lastsecs; /* Reset */
+
+ XDamageSubtract(dpy, dev->damage, None, None);
+ }
+ else
+ {
+ waitsecs = lastsecs; /* Reset */
+ fprintf(stderr, "Got unwanted event type %d\n", e.type);
+ }
+
+ fflush(LogFile);
+
+ lastsecs = waitsecs;
+ }
+
+ return True;
+}
+
+void
+usage(char *progname)
+{
+ fprintf(stderr, "%s: usage, %s <-o|--logfile output> [commands..]\n"
+ "Commands are any combination/order of;\n"
+ "-c|--click <XxY> Send click and await damage response\n"
+ "-m|--monitor <WIDTHxHEIGHT+X+Y> Watch area for damage ( default fullscreen )\n"
+ "-w|--wait <seconds> Max time to wait for damage ( default 5 secs)\n"
+ "-s|--stamp <string> Write 'string' to log file\n\n"
+ "-i|--inspect Just display damage events\n",
+ progname, progname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ Display *dpy;
+ int cnt, x, y, i = 0;
+
+ if (argc == 1)
+ usage(argv[0]);
+
+ if ((dpy = setup_display(getenv("DISPLAY"))) == NULL)
+ exit(1);
+
+ if (streq(argv[1],"-o") || streq(argv[1],"--logfile"))
+ {
+ i++;
+
+ if (++i > argc) usage (argv[0]);
+
+ if ((LogFile = fopen(argv[i], "w")) == NULL)
+ fprintf(stderr, "Failed to create logfile '%s'\n", argv[i]);
+ }
+
+ if (LogFile == NULL)
+ LogFile = stdout;
+
+ while (++i < argc)
+ {
+ if (streq("-c", argv[i]) || streq("--click", argv[i]))
+ {
+ if (++i>=argc) usage (argv[0]);
+
+ cnt = sscanf(argv[i], "%ux%u", &x, &y);
+ if (cnt != 2)
+ {
+ fprintf(stderr, "*** failed to parse '%s'\n", argv[i]);
+ usage(argv[0]);
+ }
+
+ /* Send the event */
+ log_action(fake_event(dpy, x, y), 0, "Clicked %ix%i\n", x, y);
+
+ /* .. and wait for the damage response */
+ wait_response(dpy);
+
+ continue;
+ }
+
+ if (streq("-s", argv[i]) || streq("--stamp", argv[i]))
+ {
+ if (++i>=argc) usage (argv[0]);
+ log_action(0, 1, argv[i]);
+ continue;
+ }
+
+
+ if (streq("-m", argv[i]) || streq("--monitor", argv[i]))
+ {
+ if (++i>=argc) usage (argv[0]);
+
+ if ((cnt = sscanf(argv[i], "%ux%u+%u+%u",
+ &InterestedDamageRect.width,
+ &InterestedDamageRect.height,
+ &InterestedDamageRect.x,
+ &InterestedDamageRect.y)) != 4)
+ {
+ fprintf(stderr, "*** failed to parse '%s'\n", argv[i]);
+ usage(argv[0]);
+ }
+
+ /*
+ printf("Set monitor rect to %ix%i+%i+%i\n",
+ InterestedDamageRect.x,InterestedDamageRect.y,
+ InterestedDamageRect.width,InterestedDamageRect.height);
+ */
+
+ continue;
+ }
+
+ if (streq("-w", argv[i]) || streq("--wait", argv[i]))
+ {
+ if (++i>=argc) usage (argv[0]);
+
+ if ((DamageWaitSecs = atoi(argv[i])) < 0)
+ {
+ fprintf(stderr, "*** failed to parse '%s'\n", argv[i]);
+ usage(argv[0]);
+ }
+ /*
+ log_action(0, "Set event timout to %isecs\n", DamageWaitSecs);
+ */
+ continue;
+ }
+
+ if (streq("-i", argv[i]) || streq("--inspect", argv[i]))
+ {
+ wait_response(dpy);
+ continue;
+ }
+
+ fprintf(stderr, "*** Dont understand %s\n", argv[i]);
+ usage(argv[0]);
+ }
+
+ /* Clean Up */
+
+ XCloseDisplay(dpy);
+ fclose(LogFile);
+
+ return 0;
+}