nix-community / nix-on-droid

Nix-enabled environment for your Android device. [maintainers=@t184256,@Gerschtli]
https://nix-on-droid.unboiled.info
MIT License
1.16k stars 65 forks source link

X11 with VNC, Fatal server error: (EE) Failed to activate virtual core keyboard: 2(EE) , termux workaround #75

Open deliciouslytyped opened 3 years ago

deliciouslytyped commented 3 years ago

Update: termux not needed with https://github.com/t184256/nix-on-droid/issues/75#issuecomment-682234708

I've been fiddling with https://github.com/t184256/nix-on-droid/issues/34 , and made something akin to progress, but there are some issues;

cc @shamrocklee

As a temporary workaround for #34, I tried installed and running X11 in termux. I successfully installed and ran Xvfb, and connected nix-built applications to the remote display via the $DISPLAY variable over the loopback device. So a hybrid termux/nix-on-droid approach seems to work*.

A thrown together tutorial (no authentication, etc, the point was just to see if I can even get this to work):

TODO: script that sshes to the termux and handles everythig in one place

deliciouslytyped commented 3 years ago

With regards to the apparent "hanging", I failed to notice that xclock didn't have a second hand, of course it looked stuck. :P So there is probably some sort of virtual input device issue. xclock can be started with an update time < 30 seconds and a second hand will be added.

deliciouslytyped commented 3 years ago

xinput shows:

$ DISPLAY=127.0.0.1:0 xinput
⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ Xvfb mouse                                id=6    [slave  pointer  (2)]
⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Xvfb keyboard                             id=7    [slave  keyboard (3)]
deliciouslytyped commented 3 years ago

See also https://wiki.archlinux.org/index.php/TigerVNC and https://wiki.archlinux.org/index.php/X11vnc . I'm going to try with x11vnc, maybe it will do something better resulting in things working.

deliciouslytyped commented 3 years ago

x11vnc works for the above! You just have to disable the shmem feature (* see error below and noshm in https://jlk.fjfi.cvut.cz/arch/manpages/man/x11vnc.1) to be able to use the display over the network (remember, the X11 server is running on the termux side).

*shmem error when trying to access a remote display:

26/08/2020 05:19:39 shmget(scanline) failed.                                                                          
26/08/2020 05:19:39 shmget: No such file or directory 

It remains an open question what went wrong with no input with x0vncserver.

t184256 commented 3 years ago

That's some determination! I suggest you to make it into a wiki article, even if you don't consider that finished.

Couple of notes:

strace doesnt work

AFAIK, strace or anything using ptrace doesn't work in proot in general, something about ptrace not being able to nest. There is a chance of something has changed since, but I don't recall that changing.

install a VNC app like VNC Viewer (by RealVNC) from the play store, or MultiVNC from F-Droid.

My favourite has always been bVNC. Just look at this beauty --- active since forever and still going strong.

Xvfb + x11vnc

Sorry, I didn't exactly get that: why this combination instead of a single vnc server like tigervnc?

deliciouslytyped commented 3 years ago

I think the Xvfb + x11vnc combination was kind of accidental. I didn't figure out if I can pass arguments on to X11 when starting the vnc server (probably yes?) - so I just started Xvfb appropriately and ran x0vncserver; but then there were the input problems so I tried switching to x11vnc because it seemed fancier, and then it worked.

Edit: thanks for the client suggestion, I'll check it out! Always happy to have tool recommendations instead of haphazard searching.

Edit2: the vnc server and x11 are separate because the current variant needs the vnc server started from nix (x11vnc isnt in termux), while x11 needs to run in termux - but it might work if I try going back to vncserver with my new knowledge, let's see what happens.

Edit3: OK, I'm not sure right now where this leaves me, but it seems using vncserver in termux (without a separately prepared Xvfb session) works properly. However bVNC says something about encryption problems but VNC Viewer works.

Edit4: I rediscovered the first reason that I separated the VNC and X components: I hoped it would help ease debugging, back when I thought I might find a way to debug the X11 error. Running vncviewer (on nix) also yields the X error in the title, presumably through Xvnc.

deliciouslytyped commented 3 years ago

By the way, xterm doesn't work for some reason (window doesn't show up) - but other terminals like urxvt (rxvt-unicode package) work.

deliciouslytyped commented 3 years ago

With regards to debugging, guided by the hint of https://unix.stackexchange.com/questions/314335/how-to-run-xvfb-without-root/316240 ( https://ericdraken.com/running-xvfb-on-a-shared-host-without-x/ ), which has the same error, but different goal - and from which as of yet I haven't been able to gain much insight - I realized that we should be able to intercept the input and output of xkbcomp by writing a wrapper script that captures these to some file. Hopefully this will lead to diagnosing some fixable issue. I'm waiting for the build on my phone. (The build failed. I'm trying again, hopefully it was nondeterministic.)

Alternative ideas were:

But I finally figured out how to get past some Issues I had with multiprocess/threaded GDB debugging*, which does work**. This would have worked if it was just a matter of getting the commandline for the xkbmap call, but data seems to be passed on stdin and I wasn't sure how to dump that without a lot of hassle, which made me think of the wrapper script alternative.

*https://sourceware.org/gdb/onlinedocs/gdb/Background-Execution.html https://web.stanford.edu/class/cs107/resources/gdb_coredump1.pdf https://www-zeuthen.desy.de/unix/unixguide/infohtml/gdb/Non_002dStop-Mode.html

*The general procedure is to set a breakpoint on Popen, set the breakpoint command to `print (char )$x0` to print the first function argument per ARM ABI (https://en.wikipedia.org/wiki/Calling_convention#ARM_(A64) , https://stackoverflow.com/a/4264953 , https://stackoverflow.com/a/233339), set gdb to not detach from child processes, and then to run through the application manually continuing a thread where necessary.

Code (and a cloneable repo) is at https://github.com/freedesktop/xorg-xserver/blob/f33cb4264387ed14a586ba080885b4d21e4aa48b/xkb/ddxLoad.c#L139

Tangentially, The Xserver man page is mentioned as the base for Xorg and Xvfbboth. Strangely enough only Xorg actually seems to provide functionality(namely -logverbose and -logfile) for changing log levels and log file locations. Does this mean Xvfb simply has nothing worth changing, or just that the interface is, irritatingly, crippled?

deliciouslytyped commented 3 years ago

TODO: figure out why Xvfb + x0vncserver had input issues. It's probably some mis- or lack of, configuration. Someone suggested that perhaps evdev isn't loaded by default.

deliciouslytyped commented 3 years ago

TL;DR: wasted too much time thinking xkbcomp was even being run at all.

After some more GDB shenanigans; it turns out x11 has it's own Popen function, which causing a premature exit at https://github.com/freedesktop/xorg-xserver/blob/2902b78535ecc6821cc027351818b28a5c7fdbdc/os/utils.c#L1433 .

Running nix-shell -p python37 --run "python -c ''import os; os.setgid(os,getgid())" yields

Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 38] Function not implemented

Which is probably a proot issue(TODO proot docs?). I will try to stub out setgid with a noop and LD_PRELOAD to get past this. Similarly for the UID functions.

Edit: Hey! I think it works! I suppose I should have looked into my hunch earlier - that Popen was being called but not getting as far as exec (but it seemed absurd and tedious to debug at the time). To add to the above, it was a bit less of a pain after running gef (https://github.com/hugsy/gef), and I just nexti through the function after a Popen breakpoint. This allowed me to match the execution path to the source (maybe I should have recompiled with debug symbols), showing the premature exit after the setgid(). Based on the previously mentioned SO post, I wrote nop stubs for LD_PRELOAD in the form of the following:

id.c:

#include <sys/types.h>
#include <stdio.h>

int setgid(gid_t gid){ printf("WARNING: setgid stubbed\n"); return 0; };
int setuid(uid_t uid){ printf("WARNING: setuid stubbed\n"); return 0; };

compiled with: gcc id.c -std=c99 -o id.so -shared -fPIC and loaded via: LD_PRELOAD=./id.so Xvfb, yielding:

[nix-shell:~]$ LD_PRELOAD=./id.so Xvfb :8
_XSERVTransmkdir: Owner of /tmp/.X11-unix should be set to root
/tmp/comp.9Dl3dMgJh/
The XKEYBOARD keymap compiler (xkbcomp) reports:
> Warning:          Unsupported high keycode 372 for name <I372> ignored
>                   X11 cannot support keycodes above 255.
>                   This warning only shows for the first high keycode.
Errors from xkbcomp are not fatal to the X server
^C

Edit 2: for some reason the printfs don't seem to actually show up anywhere. Anyway, here's the wrapper script for posterity:

let
  ov = self: super: {
    xorg = super.xorg // {
      xkbcomp = super.writeShellScriptBin "xkbcomp" ''
        tbasedir=$(mktemp -d comp.XXXXXXXXX -p /tmp )/
        echo $tbasedir
        tee ''${tbasedir}stdin.txt | {
        ${super.xorg.xkbcomp}/bin/xkbcomp "$@"
        } 1> >(tee ''${tbasedir}stdout.txt ) 2> >(tee ''${tbasedir}stderr.txt >&2 )
        '';
      };
    };
  #I didn't check but Xvfb probably uses an xkbcomp built with it since they are the same codebase, so we have to do a two-staged build to get one with a wrapper
  ov2 = self: super: {
    xorg = super.xorg // {
      xorgserver = super.xorg.xorgserver.overrideAttrs (old: {
        configureFlags = old.configureFlags ++ [ "--with-xkb-bin-directory=${super.xorg.xkbcomp}/bin" ];
        });
      };
    };
in
with import <nixpkgs> { overlays = [ ov ov2 ]; };
  mkShell {
    buildInputs = [ rxvt-unicode xorg.xorgserver xorg.xkbcomp xorg.xinput xorg.xclock awesome xterm xdotool x11vnc nano gdb which ];
    }

TODO: why does this lead to that particularly unhelpful error message?

deliciouslytyped commented 3 years ago

Updated tut:

Demo:

deliciouslytyped commented 3 years ago

It could have been a bit of a shortcut to check the termux packaging, which in fact involves some similar patches: https://github.com/termux/x11-packages/blob/3dcfccb30af147709c1e650aec9926b87cb2336f/packages/xorg-server-xvfb/xorg-server-1.20.1_os_utils.c.patch#L26

deliciouslytyped commented 3 years ago

This can probably be closed after adding an overlay with the patches or something?

Gerschtli commented 3 years ago

Great work @deliciouslytyped! Yes I think documenting your results in a wiki page would be nice. And of course contributing an overlay would be good idea as long as the fix is as robust as it can be. I have to admit, I don't have such in deep knowledge about what you did here so I am not able to give feedback to the nix expression you mentioned earlier..

deliciouslytyped commented 3 years ago

If anyone wants to pick my brain for clarifications please do so. but it's actually pretty simple once you get rid of all the "work in progress" cruft.

There's two variants

I do the first one, the termux patches do the second one. Beyond that it's just running x11 and vnc normally I think.

lypanov commented 3 years ago

I'm not sure how they've done it but FWIW TermuxArch pretty much works out of the box with tightvnc for me. I can't imagine they have any patches so there must be a difference in the proot technique.

M-I commented 2 years ago

I'm not sure how they've done it but FWIW TermuxArch pretty much works out of the box with tightvnc for me. I can't imagine they have any patches so there must be a difference in the proot technique.

It looks like TermuxArch has it's own workaround https://github.com/TermuxArch/TermuxArch/issues/34

lypanov commented 2 years ago

IMHO TermuxArch has a fix via it's proot work not a workaround like those that Termux uses (package modifications). But I'm willing to be convinced otherwise. Alas doesn't seem that nix-on-droid supports this use case so I've long since given up on it and switched to TermuxArch full time.

deliciouslytyped commented 2 years ago

That's a very long thread - do you have any idea what they do?

Gooberpatrol66 commented 1 year ago

Updated tut:

* install nix-on-droid from fdroid

* run `nix-shell -p x11.nix --run "myx :1"` where x11.nix is:
with import <nixpkgs> {};
let
  id_c = writeText "id.c" ''
    #include <sys/types.h>
    #include <stdio.h>

    int setgid(gid_t gid){ printf("WARNING: setgid stubbed"); return 0; }
    int setuid(uid_t uid){ printf("WARNING: setuid stubbed"); return 0; }
    '';

  id_so = runCommand "id.so" { buildInputs = [ gcc ]; } ''
    mkdir -p $out
    gcc -std=c99 -shared -fPIC ${id_c} -o $out/id.so
    '';

  myx = writeShellScriptBin "myx" ''
    export DISPLAY=$1 #TODO dynamic?
    LD_PRELOAD=${id_so}/id.so ${xorg.xorgserver}/bin/Xvfb $1 -ac -listen tcp &
    sleep 5
    ${x11vnc}/bin/x11vnc -display $1 -passwd test -rfbport 5902 -noshm -forever &  #not sure why noshm still needed
    awesome &
    urxvt -e env TERM=xterm tmux & #TODO probably wrong
    '';
in mkShell { buildInputs = [ myx awesome rxvt-unicode tmux ]; }
* be happy, with the applications that dont break in this environment tada

Demo:

I get error: 'x11' has been renamed to/replaced by 'xlibsWrapper' with that command

deliciouslytyped commented 1 year ago

Sorry, I don't have time to look into this right now, but that error message looks like it should give you a string to pull on. What nixpkgs version are you using? Try explicitly setting -I nixpkgs=channel:somenixpkgschannel?

Gooberpatrol66 commented 12 months ago

Sorry, I don't have time to look into this right now, but that error message looks like it should give you a string to pull on. What nixpkgs version are you using? Try explicitly setting -I nixpkgs=channel:somenixpkgschannel?

I get the same error with nixpkgs-unstable

Gooberpatrol66 commented 12 months ago
nix-shell -p x11.nix --run "myx :1" -I nixpkgs=channel:nixos-19.03
error: anonymous function at /nix/store/hirlp67xzg55z23q07dvyczklxzn7j9n-source/pkgs/top-level/default.nix:20:1 called with unexpected argument 'inNixShell'

       at /nix/store/hirlp67xzg55z23q07dvyczklxzn7j9n-source/pkgs/top-level/impure.nix:82:1:

           81|
           82| import ./. (builtins.removeAttrs args [ "system" "platform" ] // {
             | ^
           83|   inherit config overlays crossSystem;
deliciouslytyped commented 12 months ago

For the inNixShell error, this looks relevant https://discourse.nixos.org/t/nix-shell-error-called-with-unexpected-argument-innixshell/20356 .

Gooberpatrol66 commented 12 months ago
nix-shell -p x11.nix --run "myx :1" -I nixpkgs=channel:nixos-20.09 --show-trace
error: attribute 'nix' missing

       at «string»:1:107:

            1| {...}@args: with import <nixpkgs> args; (pkgs.runCommandCC or pkgs.runCommand) "shell" { buildInputs = [ (x11.nix) ]; } ""
             |                                                                                                           ^

       … while evaluating anonymous lambda

       at /nix/store/jpnz9szr04vwv523vicxymp18iddzrav-source/pkgs/stdenv/generic/make-derivation.nix:143:17:

          142|           (map (drv: drv.__spliced.hostHost or drv) depsHostHost)
          143|           (map (drv: drv.crossDrv or drv) buildInputs)
             |                 ^
          144|         ]

       … from call site

       … while evaluating 'getOutput'

       at /nix/store/jpnz9szr04vwv523vicxymp18iddzrav-source/lib/attrsets.nix:464:23:

          463|   */
          464|   getOutput = output: pkg:
             |                       ^
          465|     if pkg.outputUnspecified or false

       … from call site

       … while evaluating the attribute 'buildInputs' of the derivation 'shell'

       at /nix/store/jpnz9szr04vwv523vicxymp18iddzrav-source/pkgs/build-support/trivial-builders.nix:7:7:

            6|     stdenv.mkDerivation ({
            7|       name = lib.strings.sanitizeDerivationName name;
             |       ^
            8|       inherit buildCommand;
ls x11.nix
x11.nix
deliciouslytyped commented 11 months ago

Ok, I wasn't paying attention to the command line. It looks like there might have been a mistake in my original post; nix-shell -p x11.nix looks wrong. Try without the -p. -p on nix-shell is for passing a list of derivations in the scope -p uses, to put into the environment, and we just (I think) want to use the x11.nix file name.

If this fix works I'll edit the original post.

Gooberpatrol66 commented 11 months ago

Ok, I wasn't paying attention to the command line. It looks like there might have been a mistake in my original post; nix-shell -p x11.nix looks wrong. Try without the -p. -p on nix-shell is for passing a list of derivations in the scope -p uses, to put into the environment, and we just (I think) want to use the x11.nix file name.

If this fix works I'll edit the original post.

Yes, that made it work. Thank you!

expenses commented 4 weeks ago

Updated tut:

Thanks for this! I've managed to get tigervnc working as well. In combination with #203 I've managed to write a nice little service module:

{ pkgs, config, lib, ... }:
let
  id_c = pkgs.writeText "id.c" ''
    #include <sys/types.h>
    #include <stdio.h>

    int setgid(gid_t gid){ printf("WARNING: setgid stubbed"); return 0; }
    int setuid(uid_t uid){ printf("WARNING: setuid stubbed"); return 0; }
  '';

  id_so = pkgs.runCommand "id.so" { buildInputs = [ pkgs.gcc ]; } ''
    mkdir -p $out
    gcc -std=c99 -shared -fPIC ${id_c} -o $out/id.so
  '';

  xvnc-wrapped = pkgs.writeShellScriptBin "Xvnc" ''
    LD_PRELOAD=${id_so}/id.so ${pkgs.tigervnc}/bin/Xvnc $@
  '';

  password-file = password:
    pkgs.runCommand "password-file" { } ''
      echo "${password}" | ${pkgs.tigervnc}/bin/vncpasswd -f > $out
    '';

  inherit (lib) types options;
  cfg = config.services.tigervnc;
in {
  options.services.tigervnc = {
    enable = lib.mkEnableOption "tigervnc";
    autostart = lib.mkOption {
      type = types.bool;
      default = false;
    };
    password = lib.mkOption {
      type = types.str;
      default = "password";
    };
  };

  config = lib.mkIf cfg.enable {
    supervisord.programs.tigervnc = {
      path = [ xvnc-wrapped ];
      command = "Xvnc :1 -PasswordFile ${password-file cfg.password}";
      autostart = cfg.autostart;
    };
  };
}