Screenshot with mss in python says "No X11 Library"

Hi. I’m trying to:

#!/usr/bin/env python3
from mss import mss
with mss() as sct:
    sct.shot()

with

let
  my-python = python3.withPackages (python-packages:
    with python-packages; [
      mss
    ]);
in {
  home.packages = with pkgs; [
    my-python
  ];
}

but I’m getting:

[I] ➜ .config/nixpkgs/bin/minimal.py
Traceback (most recent call last):
  File ".config/nixpkgs/bin/minimal.py", line 5, in <module>
    with mss() as sct:
  File "/nix/store/k296zqidarw3fpwz2qlscxkj7v6bcwla-python3-3.8.9-env/lib/python3.8/site-packages/mss/factory.py", line 41, in mss
    return linux.MSS(**kwargs)
  File "/nix/store/k296zqidarw3fpwz2qlscxkj7v6bcwla-python3-3.8.9-env/lib/python3.8/site-packages/mss/linux.py", line 291, in __init__
    raise ScreenShotError("No X11 library found.")
mss.exception.ScreenShotError: No X11 library found.

Tried installing xlib libX11 libraries but it has to be somehow linked in the lib package if I got that right. Copied that package file and tried pasting xorg.libX11 in different *buildInput locations but not really an idea what I am doing.

If someone could give me a pointer would be great.
Thanks

{ lib, buildPythonPackage, fetchPypi, isPy3k, xorg }:

buildPythonPackage rec {
  pname = "mss";
  version = "6.1.0";
  disabled = !isPy3k;

  src = fetchPypi {
    inherit pname version;
    sha256 = "aebd069f3e05667fe9c7b9fa4b1771fe42a4710ce1058ce0236936ce06fa5394";
  };

  # By default it attempts to build Windows-only functionality
  prePatch = ''
    rm mss/windows.py
  '';

  buildInputs = [ xorg.libX11 ];

  # Skipping tests due to most relying on DISPLAY being set
  pythonImportsCheck = [ "mss" ];

  meta = with lib; {
    description = "Cross-platform multiple screenshots module in pure Python";
    homepage = "https://github.com/BoboTiG/python-mss";
    license = licenses.mit;
    maintainers = with maintainers; [ austinbutler ];
  };
}

Python and linking C libraries is… A story.

Usually this is done at runtime, so your build-time config likely doesn’t matter. Nix will automatically also install build inputs if their files appear in the built binaries, but since python finds your library at runtime this won’t work.

This isn’t true for all python packages though, and often the library resolution is broken in ways that are exposed on NixOS (e.g. because it assumes “Linux” = current Ubuntu LTS and the library is in a specific location with a specific name), so let’s see what this package does.

Easiest way to do that is to look at the package. If it’s a build time failure we inspect the setup.py, but it isn’t, so digging in the package I found your error message here: https://github.com/BoboTiG/python-mss/blob/7b5cff83715b6f2f4c1e46a184c73be2b7657263/mss/linux.py#L289

So, this package uses ctypes.find_library. This is good, since it’s a standard library feature, and in the worst case patched on NixOS. It’s still done at runtime, so we need to make sure the dependency is propagated.

Luckily, this is quite easy, and the python build docs even recommend it somewhere in here (we desperately need to move to readthedocs) - just replace your buildInputs in the package with propagatedBuildInputs :slight_smile:

1 Like

Hi and thank you for your detailed answer. Very much appreciate it.
But unfortunately this also does also not work within propagatedBuildInputs. (though I figured out the difference now :slight_smile:

Tried to use it from the python interpreter and found that this:

#!/usr/bin/env python3
import ctypes.util
x11 = ctypes.util.find_library("X11")
if not x11:
    print("No X11.")

Needs LD_LIBRARY_PATH to be set correctly:

[I] ➜ .config/nixpkgs/bin/minimal.py
No X11.

[I] ➜ LD_LIBRARY_PATH=/nix/store/7gxlcshan16fym2ay98zmpi9gz7sjkdx-libX11-1.7.0/lib .config/nixpkgs/bin/minimal.py

And then i found out running it like this makes it work:

LD_LIBRARY_PATH=$HOME/.nix-profile/lib .config/nixpkgs/bin/minimal.py

I’ll then just set this in the script calling it.
But I’m confused.
Where should this be set?
In the lib package? I tried this but didn’t have an effect:

  propagatedBuildInputs = [
    xorg.libX11
    xorg.xrandr
  ];
  LD_LIBRARY_PATH = "${lib.makeLibraryPath propagatedBuildInputs}";

probably that path is also build time only.

Looks like it’s an open bug: [python] ctypes.util.find_library should return full path to the library · Issue #7307 · NixOS/nixpkgs · GitHub

In that case, the way to fix this currently is something like this:

postPatchPhase = ''
    sed -i 's|ctypes.util.find_library("X11")|"${xorg.libX11}/lib/libX11.so"|' mss/linux.py
'';

This is a bit horrible :frowning:

You can also try setting DYLD_LIBRARY_PATH="${lib.makeLibraryPath propagatedBuildInputs}";, but it seems that people on the issue aren’t getting results from that.

2 Likes

Thanks a lot that worked. :smiley:

1 Like