#include <X11/Xlib.h>#include <X11/Xatom.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <errno.h>// Globals, yay!Window rootWindow = None;Atom atomClassName = 0;Atom atomPID = 0;// Resolves a PID to an executable, using /procchar *resolvePID(long lPID) { // If we've reached this function, we're not on OS X // It's fairly safe to assume that we have /proc char szPath[1024]; char *szExePath = malloc(sizeof(char)*1024);; int iChars = 0; snprintf(szPath, 1023, "/proc/%ld/exe", lPID); iChars = readlink(szPath, szExePath, 1023); if(iChars == -1) { free(szExePath); return NULL; } else { szExePath[iChars] = '\0'; return szExePath; }}// Returns the direct parent of a windowWindow getParentWindow(Display *dpy, Window child) { Window rootWindow; Window parentWindow; Window *childWindows; unsigned int iChildCount; XQueryTree(dpy, child, &rootWindow, &parentWindow, &childWindows, &iChildCount); if(iChildCount != 0) XFree(childWindows); return parentWindow;}// Look up the executable's PID, then resolve that to an executable// This should really also check the WM_CLIENT_MACHINE property// as per http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html#id2552250// but I can't be arsed. If you're spending all your time focused on// dbus-daemon, this is why.char *getWindowExecutableByPID(Display *dpy, Window queryWindow) { Atom outType; int outFmt; unsigned long outItems; unsigned long outBytes; long *outPID; long PID; Window currentWindow = queryWindow; while((currentWindow != None) && (currentWindow != rootWindow)) { int success = XGetWindowProperty(dpy, currentWindow, atomPID, 0, 1, False, XA_CARDINAL, &outType, &outFmt, &outItems, &outBytes, (unsigned char **)&outPID); if(success == Success && outType != None) { PID = *outPID; XFree(outPID); return resolvePID(PID); } currentWindow = getParentWindow(dpy, currentWindow); } return NULL;}// Try to use the WM_CLASS property as executable name (dodgy)char *getWindowExecutableByClassName(Display *dpy, Window queryWindow) { Atom outType; int outFmt; unsigned long outItems; unsigned long outBytes; unsigned char *outClassName; char *szClassName = NULL; Window currentWindow = queryWindow; while((currentWindow != None) && (currentWindow != rootWindow)) { int success = XGetWindowProperty(dpy, currentWindow, atomClassName, 0, 1024, False, XA_STRING, &outType, &outFmt, &outItems, &outBytes, &outClassName); if(success == Success && outType != None) { szClassName = strdup((const char *)outClassName); XFree(outClassName); return szClassName; } currentWindow = getParentWindow(dpy, currentWindow); } return NULL; }// Try several methods of finding the executablechar *getWindowExecutable(Display *dpy, Window queryWindow) { char *executable = NULL; // There's no libproc or procfs on macs.#ifndef __APPLE__ executable = getWindowExecutableByPID(dpy, queryWindow);#endif if(!executable) executable = getWindowExecutableByClassName(dpy, queryWindow); if(!executable) executable = strdup("None"); return executable;}char *getFocusWindow(Display *dpy){ Window focus; int revert; // Find the focus window XGetInputFocus(dpy, &focus, &revert); // If nothing has focus, return "None" if(focus == None || focus == rootWindow) return strdup("None"); else return getWindowExecutable(dpy, focus);}int main(int argc, char **argv) { Display *pDisplay = NULL; FILE *pLog = NULL; float flIntervalMult = 1.0; int iInterval = 1000000; char *szPrevFocus = NULL; long lPrevFocusPeriod = 0; long lRewindPosition = 0; if(argc != 3) { printf("%s [interval in seconds] [logfile]\n", argv[0]); exit(1); } if(sscanf(argv[1], "%f", &flIntervalMult) != 1) { printf("Usage: %s [interval in seconds] [logfile]\n", argv[0]); exit(1); } // Init XLib pDisplay = XOpenDisplay(NULL); rootWindow = XDefaultRootWindow(pDisplay); atomPID = XInternAtom(pDisplay, "_NET_WM_PID", False); atomClassName = XInternAtom(pDisplay, "WM_CLASS", False); // Set up defaults szPrevFocus = strdup(""); iInterval *= flIntervalMult; // And open our log file pLog = fopen(argv[2], "w+"); if(pLog == NULL) { printf("Could not open %s for writing: %s\n", argv[2], strerror(errno)); exit(1); } // Print the file header (consists solely of a version specifier, \t, and interval in microseconds) fprintf(pLog, "FOCUSLOG1\t%d\n", iInterval); while(1) { char *szCurrFocus = getFocusWindow(pDisplay); // Check whether focus has changed if(strcmp(szPrevFocus, szCurrFocus) != 0) { // If so, move on to the new line, and print our current focus fprintf(pLog, "\n%s\t", szCurrFocus); // Store where to rewind to, to print the period lRewindPosition = ftell(pLog); // Then print 1 to ensure it's logged even if only 1 interval passes fprintf(pLog, "1"); // Reset the period lPrevFocusPeriod = 1L; // And free and replace the old focus name free(szPrevFocus); szPrevFocus = szCurrFocus; } else { // If it hasn't, free the new focus free(szCurrFocus); // Rewind to the insertion position fseek(pLog, lRewindPosition, SEEK_SET); // And print the new period lPrevFocusPeriod++; fprintf(pLog, "%ld", lPrevFocusPeriod); } // And sleep for a while usleep(iInterval); }}