diff options
author | mallum <mallum@f5eea0f0-44ea-0310-b729-df5b855dafe5> | 2005-03-22 18:51:40 +0000 |
---|---|---|
committer | mallum <mallum@f5eea0f0-44ea-0310-b729-df5b855dafe5> | 2005-03-22 18:51:40 +0000 |
commit | b989a142a1cedd2691eb6b4ba23f92c4cca6c7ec (patch) | |
tree | fe08cde5de5099696c83ab81a1c5d00a74e7f738 |
move and rename interaction -> xresponse
-rw-r--r-- | AUTHORS | 0 | ||||
-rw-r--r-- | ChangeLog | 45 | ||||
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | NEWS | 0 | ||||
-rw-r--r-- | README | 59 | ||||
-rwxr-xr-x | autogen.sh | 3 | ||||
-rw-r--r-- | configure.ac | 28 | ||||
-rw-r--r-- | xresponse.c | 421 |
8 files changed, 563 insertions, 0 deletions
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@ @@ -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; +} |