Skip to content

Implement an XShmGetImage-based backend#431

Merged
BoboTiG merged 12 commits intoBoboTiG:mainfrom
jholveck:feat-xshmgetimage
Dec 10, 2025
Merged

Implement an XShmGetImage-based backend#431
BoboTiG merged 12 commits intoBoboTiG:mainfrom
jholveck:feat-xshmgetimage

Conversation

@jholveck
Copy link
Contributor

@jholveck jholveck commented Nov 22, 2025

Changes proposed in this PR

Following on to #426's introduction of XCB backends, this change introduces a backend based on XShmGetImage (technically, xcb_shm_get_image). This avoids the need to send the pixel data across the socket. Instead, the X11 server saves the pixel data into a memory buffer that's shared between the server and MSS, and MSS can use that buffer directly.

This is quite performant: on my computer, the new backend can take 4k screenshots at 30-34 fps, while the XGetImage backend could only run at 11-14 fps.

This backend requires the client and server to be on the same machine (so they can share a memory buffer). It often can't be used across separate containers on the same machine (depending on the container configuration). Because of these limitations, the existing XGetImage implementation is still available. It can be explicitly requested by the user, or the new implementation will use it automatically if XShmGetImage isn't possible.

It is very important to keep up to date tests and documentation.

  • Tests added/updated
  • Documentation updated

Is your code right?

  • ./check.sh passed

This only adds the support for the XCB MIT-SHM extension to mss's
internal xcb libraries. The actual usage of shared memory for
screenshots will be done in a future commit.
This is close to complete, but there's a few things that need to be
chased down: notably, the test_thread_safety test is failing, for some
reason.  It also currently doesn't work correctly if the root window
size increases.

That said, this is quite promising: on my computer, the new backend
can take 4k screenshots at 30-34 fps, while the XGetImage backend
could only run at 11-14 fps.
Previously, I had been trying to close the memfd in _shutdown_shm, and
ignoring EBADF.  It turns out that XCB will close the memfd when you
send it to the X server.

I think this was one potential cause of the issues I saw in
test_thread_safety: the two threads would be reallocated each others'
fds, leading to thread A closing an fd that thread B was using,
thinking that it was thread A's memfd.

Fix so that the memfd is only explicitly closed in an error situation.
Under X11, when the last client disconnects, the server resets.  If
a new client tries to connect before the reset is complete, it may fail.
Since we often run the tests under Xvfb, they're frequently the only
clients.  Since our tests run in rapid succession, this combination
can lead to intermittent failures.

To avoid this, we open a connection at the start of the test session
and keep it open until the end.
@BoboTiG
Copy link
Owner

BoboTiG commented Dec 7, 2025

Tested: it works fine, and is indeed faster than the Xlib backend 💪

@jholveck
Copy link
Contributor Author

I'm working on the user docs, so we should talk about how we want to present this.

Earlier, I think we considered making a release with the new XCB-based stuff experimental and non-default, to give advanced users a chance to try it out and report issues. Do you think that's what you want to do? The alternative I see is to make XShmGetImage the default backend. I think it's safe to make the new XCB-based XShmGetImage the default, but it's up to you.

Once I write the docs, I don't think there's anything else we need to do before committing it, and then you can make a release. What do you think?

@BoboTiG
Copy link
Owner

BoboTiG commented Dec 10, 2025

(...) The alternative I see is to make XShmGetImage the default backend. I think it's safe to make the new XCB-based XShmGetImage the default, but it's up to you.

I think too, lets use XShmGetImage as the default backend right now.

Once I write the docs, I don't think there's anything else we need to do before committing it, and then you can make a release. What do you think?

Maybe adding a CLI argument to be able to choose the backend would be interesting?

Also, before marking XShmGetImage as unavailable if the first grab()
fails, also test to see if the fallback XGetImage also fails (such as
if the user gave an out-of-bounds rect).  In that case, just reraise
the exception and try XShmGetImage again with the next grab().
Also, fix a minor error in the docs formatting.
@jholveck jholveck marked this pull request as ready for review December 10, 2025 07:39
@jholveck
Copy link
Contributor Author

I've changed the default to use XShmGetImage on Linux, and added a --backend flag. Let me know if there's anything else you come across during your review!

filename.unlink()

def test_invalid_backend_option(self, with_cursor: bool, capsys: pytest.CaptureFixture) -> None:
backend = "chuck_norris"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

@BoboTiG BoboTiG merged commit 712503a into BoboTiG:main Dec 10, 2025
21 checks passed
@BoboTiG
Copy link
Owner

BoboTiG commented Dec 10, 2025

That's terrific, thank you @jholveck 🍾

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve speed on GNU/Linux with XShm

2 participants