How to kill headless X server started via Python?

2019-04-14 17:00发布

问题:

I want to get screenshots of a webpage in Python. For this I am using http://github.com/AdamN/python-webkit2png/ .

    newArgs = ["xvfb-run", "--server-args=-screen 0, 640x480x24", sys.argv[0]]
    for i in range(1, len(sys.argv)):
        if sys.argv[i] not in ["-x", "--xvfb"]:
            newArgs.append(sys.argv[i])
    logging.debug("Executing %s" % " ".join(newArgs))
    os.execvp(newArgs[0], newArgs)

Basically calls xvfb-run with the correct args. But man xvfb says:

Note that the demo X clients used in the above examples will not exit on their own, so they will have to be killed before xvfb-run will exit.

So that means that this script will <????> if this whole thing is in a loop, (To get multiple screenshots) unless the X server is killed. How can I do that?

回答1:

The documentation for os.execvp states:

These functions all execute a new program, replacing the current process; they do not return. [..]

So after calling os.execvp no other statement in the program will be executed. You may want to use subprocess.Popen instead:

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several other, older modules and functions, such as:

Using subprocess.Popen, the code to run xlogo in the virtual framebuffer X server becomes:

import subprocess
xvfb_args = ['xvfb-run', '--server-args=-screen 0, 640x480x24', 'xlogo']
process = subprocess.Popen(xvfb_args)

Now the problem is that xvfb-run launches Xvfb in a background process. Calling process.kill() will not kill Xvfb (at least not on my machine...). I have been fiddling around with this a bit, and so far the only thing that works for me is:

import os
import signal
import subprocess

SERVER_NUM = 99  # 99 is the default used by xvfb-run; you can leave this out.

xvfb_args = ['xvfb-run', '--server-num=%d' % SERVER_NUM,
             '--server-args=-screen 0, 640x480x24', 'xlogo']
subprocess.Popen(xvfb_args)

# ... do whatever you want to do here...

pid = int(open('/tmp/.X%s-lock' % SERVER_NUM).read().strip())
os.kill(pid, signal.SIGINT)

So this code reads the process ID of Xvfb from /tmp/.X99-lock and sends the process an interrupt. It works, but does yield an error message every now and then (I suppose you can ignore it, though). Hopefully somebody else can provide a more elegant solution. Cheers.