How to get an X11 Window from a Process ID?

X11

X11 Problem Overview


Under Linux, my C++ application is using fork() and execv() to launch multiple instances of OpenOffice so as to view some powerpoint slide shows. This part works.

Next I want to be able to move the OpenOffice windows to specific locations on the display. I can do that with the XMoveResizeWindow() function but I need to find the Window for each instance.

I have the process ID of each instance, how can I find the X11 Window from that ?


UPDATE - Thanks to Andy's suggestion, I have pulled this off. I'm posting the code here to share it with the Stack Overflow community.

Unfortunately Open Office does not seem to set the _NET_WM_PID property so this doesn't ultimately solve my problem but it does answer the question.

// Attempt to identify a window by name or attribute.
// by Adam Pierce <[email protected]>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <iostream>
#include <list>

using namespace std;

class WindowsMatchingPid
{
public:
	WindowsMatchingPid(Display *display, Window wRoot, unsigned long pid)
		: _display(display)
		, _pid(pid)
	{
	// Get the PID property atom.
		_atomPID = XInternAtom(display, "_NET_WM_PID", True);
		if(_atomPID == None)
		{
			cout << "No such atom" << endl;
			return;
		}

		search(wRoot);
	}

	const list<Window> &result() const { return _result; }

private:
	unsigned long  _pid;
	Atom           _atomPID;
	Display       *_display;
	list<Window>   _result;

	void search(Window w)
	{
	// Get the PID for the current Window.
		Atom           type;
		int            format;
		unsigned long  nItems;
		unsigned long  bytesAfter;
		unsigned char *propPID = 0;
		if(Success == XGetWindowProperty(_display, w, _atomPID, 0, 1, False, XA_CARDINAL,
		                                 &type, &format, &nItems, &bytesAfter, &propPID))
		{
			if(propPID != 0)
			{
			// If the PID matches, add this window to the result set.
				if(_pid == *((unsigned long *)propPID))
					_result.push_back(w);

				XFree(propPID);
			}
		}

	// Recurse into child windows.
		Window    wRoot;
		Window    wParent;
		Window   *wChild;
		unsigned  nChildren;
		if(0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren))
		{
			for(unsigned i = 0; i < nChildren; i++)
				search(wChild[i]);
		}
	}
};

int main(int argc, char **argv)
{
	if(argc < 2)
		return 1;

	int pid = atoi(argv[1]);
	cout << "Searching for windows associated with PID " << pid << endl;

// Start with the root window.
	Display *display = XOpenDisplay(0);

	WindowsMatchingPid match(display, XDefaultRootWindow(display), pid);

// Print the result.
	const list<Window> &result = match.result();
	for(list<Window>::const_iterator it = result.begin(); it != result.end(); it++)
		cout << "Window #" << (unsigned long)(*it) << endl;

	return 0;
}

X11 Solutions


Solution 1 - X11

The only way I know to do this is to traverse the tree of windows until you find what you're looking for. Traversing isn't hard (just see what xwininfo -root -tree does by looking at xwininfo.c if you need an example).

But how do you identify the window you are looking for? Some applications set a window property called _NET_WM_PID.

I believe that OpenOffice is one of the applications that sets that property (as do most Gnome apps), so you're in luck.

Solution 2 - X11

Check if /proc/PID/environ contains a variable called WINDOWID

Solution 3 - X11

Bit late to the party. However: Back in 2004, Harald Welte posted a code snippet that wraps the XCreateWindow() call via LD_PRELOAD and stores the process id in _NET_WM_PID. This makes sure that each window created has a PID entry.

http://www.mail-archive.com/[email protected]/msg05806.html

Solution 4 - X11

Try installing xdotool, then:

#!/bin/bash
# --any and --name present only as a work-around, see: https://github.com/jordansissel/xdotool/issues/14
ids=$(xdotool search --any --pid "$1" --name "dummy")

I do get a lot of ids. I use this to set a terminal window as urgent when it is done with a long command, with the program seturgent. I just loop through all the ids I get from xdotool and run seturgent on them.

Solution 5 - X11

There is no good way. The only real options I see, are:

  1. You could look around in the process's address space to find the connection information and window ID.
  2. You could try to use netstat or lsof or ipcs to map the connections to the Xserver, and then (somehow! you'll need root at least) look at its connection info to find them.
  3. When spawning an instance you can wait until another window is mapped, assume it's the right one, and `move on.

Solution 6 - X11

I took the freedom to re-implement the OP's code using some modern C++ features. It maintains the same functionalities but I think that it reads a bit better. Also it does not leak even if the vector insertion happens to throw.

// Attempt to identify a window by name or attribute.
// originally written by Adam Pierce <[email protected]>
// revised by Dario Pellegrini <[email protected]>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <iostream>
#include <vector>


std::vector<Window> pid2windows(pid_t pid, Display* display, Window w) {
  struct implementation {
    struct FreeWrapRAII {
      void * data;
      FreeWrapRAII(void * data): data(data) {}
      ~FreeWrapRAII(){ XFree(data); }
    };

    std::vector<Window> result;
    pid_t pid;
    Display* display;
    Atom atomPID;

    implementation(pid_t pid, Display* display): pid(pid), display(display) {
      // Get the PID property atom
      atomPID = XInternAtom(display, "_NET_WM_PID", True);
      if(atomPID == None) {
        throw std::runtime_error("pid2windows: no such atom");
      }
    }

    std::vector<Window> getChildren(Window w) {
      Window    wRoot;
      Window    wParent;
      Window   *wChild;
      unsigned  nChildren;
      std::vector<Window> children;
      if(0 != XQueryTree(display, w, &wRoot, &wParent, &wChild, &nChildren)) {
        FreeWrapRAII tmp( wChild );
        children.insert(children.end(), wChild, wChild+nChildren);
      }
      return children;
    }

    void emplaceIfMatches(Window w) {
      // Get the PID for the given Window
      Atom           type;
      int            format;
      unsigned long  nItems;
      unsigned long  bytesAfter;
      unsigned char *propPID = 0;
      if(Success == XGetWindowProperty(display, w, atomPID, 0, 1, False, XA_CARDINAL,
                                       &type, &format, &nItems, &bytesAfter, &propPID)) {
        if(propPID != 0) {
          FreeWrapRAII tmp( propPID );
          if(pid == *reinterpret_cast<pid_t*>(propPID)) {
            result.emplace_back(w);
          }
        }
      }
    }

    void recurse( Window w) {
      emplaceIfMatches(w);
      for (auto & child: getChildren(w)) {
        recurse(child);
      }
    }

    std::vector<Window> operator()( Window w ) {
      result.clear();
      recurse(w);
      return result;
    }
  };
  //back to pid2windows function
  return implementation{pid, display}(w);
}

std::vector<Window> pid2windows(const size_t pid, Display* display) {
  return pid2windows(pid, display, XDefaultRootWindow(display));
}


int main(int argc, char **argv) {
  if(argc < 2)
    return 1;

  int pid = atoi(argv[1]);
  std::cout << "Searching for windows associated with PID " << pid << std::endl;

  // Start with the root window.
  Display *display = XOpenDisplay(0);
  auto res = pid2windows(pid, display);

  // Print the result.
  for( auto & w: res) {
    std::cout << "Window #" << static_cast<unsigned long>(w) << std::endl;
  }

  XCloseDisplay(display);
  return 0;
}

Solution 7 - X11

Are you sure you have the process ID of each instance? My experience with OOo has been that trying to run a second instance of OOo merely converses with the first instance of OOo, and tells that to open the additional file.

I think you're going to need to use the message-sending capabilities of X to ask it nicely for its window. I would hope that OOo documents its coversations somewhere.

Solution 8 - X11

If you use python, I found a way here, the idea is from BurntSushi

If you launched the application, then you should know its cmd string, with which you can reduce calls to xprop, you can always loop through all the xids and check if the pid is the same as the pid you want

import subprocess
import re

import struct
import xcffib as xcb
import xcffib.xproto

def get_property_value(property_reply):
    assert isinstance(property_reply, xcb.xproto.GetPropertyReply)

    if property_reply.format == 8:
        if 0 in property_reply.value:
            ret = []
            s = ''
            for o in property_reply.value:
                if o == 0:
                    ret.append(s)
                    s = ''
                else:
                    s += chr(o)
        else:
            ret = str(property_reply.value.buf())

        return ret
    elif property_reply.format in (16, 32):
        return list(struct.unpack('I' * property_reply.value_len,
                                  property_reply.value.buf()))

    return None

def getProperty(connection, ident, propertyName):

    propertyType = eval(' xcb.xproto.Atom.%s' % propertyName)

    try:
        return connection.core.GetProperty(False, ident, propertyType,
                                        xcb.xproto.GetPropertyType.Any,
                                        0, 2 ** 32 - 1)
    except:
        return None


c = xcb.connect()
root = c.get_setup().roots[0].root

_NET_CLIENT_LIST = c.core.InternAtom(True, len('_NET_CLIENT_LIST'),
                                     '_NET_CLIENT_LIST').reply().atom


raw_clientlist = c.core.GetProperty(False, root, _NET_CLIENT_LIST,
                                    xcb.xproto.GetPropertyType.Any,
                                    0, 2 ** 32 - 1).reply()

clientlist = get_property_value(raw_clientlist)

cookies = {}

for ident in clientlist:
    wm_command = getProperty(c, ident, 'WM_COMMAND')
    cookies[ident] = (wm_command)

xids=[]

for ident in cookies:
    cmd = get_property_value(cookies[ident].reply())
    if cmd and spref in cmd:
        xids.append(ident)

for xid in xids:
    pid = subprocess.check_output('xprop -id %s _NET_WM_PID' % xid, shell=True)
    pid = re.search('(?<=\s=\s)\d+', pid).group()

    if int(pid) == self.pid:
        print 'found pid:', pid
        break

print 'your xid:', xid

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAdam PierceView Question on Stackoverflow
Solution 1 - X11andyView Answer on Stackoverflow
Solution 2 - X11hohoView Answer on Stackoverflow
Solution 3 - X11Raphael WimmerView Answer on Stackoverflow
Solution 4 - X11GauthierView Answer on Stackoverflow
Solution 5 - X11wnoiseView Answer on Stackoverflow
Solution 6 - X11DarioPView Answer on Stackoverflow
Solution 7 - X11TanktalusView Answer on Stackoverflow
Solution 8 - X11ShumanView Answer on Stackoverflow