Repository: barneygale/asyncvnc (language: python)

Issue:
Sec type `2` unsupported? Auth with password fails..
```python
Traceback (most recent call last):
  File "/home/goodboy/repos/piker/snippets/async_vnc.py", line 21, in <module>
    asyncio.run(run_client())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/home/goodboy/repos/piker/snippets/async_vnc.py", line 5, in run_client
    async with asyncvnc.connect(
  File "/usr/lib/python3.10/contextlib.py", line 199, in __aenter__
    return await anext(self.gen)
  File "/home/goodboy/repos/piker/310/lib/python3.10/site-packages/asyncvnc.py", line 501, in connect
    client = await Client.create(reader, writer, username, password)
  File "/home/goodboy/repos/piker/310/lib/python3.10/site-packages/asyncvnc.py", line 420, in create
    raise ValueError(f'unsupported security types: {security_types}')
ValueError: unsupported security types: {2}
```

Is there a way to know what each sec type maps to and how you must change your server config to make the connection work?
Add a `force_video_mode: str` passthrough kwarg
Since it seems not all video formats are correctly detected yet (eg.
with `x11vnc` basic usage) this allows a user to force a video mode
they know works if wanted.

Pertains to #2.

#### TODO:
- [x] a couple tests showing how things work using this passthrough with `x11vnc` -> **see #4**
- [x] ideally tests in CI (if we can get `x11vnc` on an actions instance - i presume this has been done) and then verifying all the recent bugs i've hit. -> **probably we can complete in #4** or another follow up PR? (**see #4 which completes this**)
Add `x11vnc` to actions instance
In an effort to get #4 (which helped get the fix to #2 🥳) running in CI we need to figure out getting `x11vnc` installed on the vm instance.

There are some details in #4 but likely we'll want to just do this:
- SO answer: https://stackoverflow.com/a/58002908
- actions docs: https://docs.github.com/en/actions/using-github-hosted-runners/customizing-github-hosted-runners

Actions config will just need something like:
```yaml
- name: Install x11vnc
  run: sudo apt-get install -y x11vnc
  ```
Unsupported video mode: `b' \x18\x00\xff\x00\xff\x00\xff\x00\xff\x10\x08\x00'`
When running the following script against the following server command:
`x11vnc -display :0 -noipv6 -forever -noxdamage -ncache_cr`

target script:
```python
async def run_client():
    async with asyncvnc.connect(
        'localhost',
        port=5901,
    ) as client:
        print(client)

    asyncio.run(run_client())
```
output error:

```python
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/home/goodboy/repos/piker/snippets/async_vnc.py", line 8, in run_client
    async with asyncvnc.connect(
  File "/usr/lib/python3.10/contextlib.py", line 199, in __aenter__
    return await anext(self.gen)
  File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 563, in connect
    client = await Client.create(reader, writer, username, password, host_key)
  File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 514, in create
    video=await Video.create(reader, writer))
  File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 292, in create
    mode = video_modes[await reader.readexactly(13)]
KeyError: b' \x18\x00\xff\x00\xff\x00\xff\x00\xff\x10\x08\x00'
```

Feels like a full "system test" where you spawn `x11vnc` and run a client against it would be handy 😄 
Add `x11vnc` to actions instance
In an effort to get #4 (which helped get the fix to #2 🥳) running in CI we need to figure out getting `x11vnc` installed on the vm instance.

There are some details in #4 but likely we'll want to just do this:
- SO answer: https://stackoverflow.com/a/58002908
- actions docs: https://docs.github.com/en/actions/using-github-hosted-runners/customizing-github-hosted-runners

Actions config will just need something like:
```yaml
- name: Install x11vnc
  run: sudo apt-get install -y x11vnc
  ```


Discussion / hints:
Silly error on my part - try upgrading to asyncvnc 1.1.0?
I'm actually on master, will pull and test shortly 🏄🏼 

Yup that fixed it I think 👍🏼 

@barneygale would you take some tests that use `x11vnc` to test against an actual implementation - like full system type tests?
:o that would be amazing!
yeah i'm thinking we could make this client pretty robust with that.

my main use case is driving gui apps inside containers that i don't want to deal with native, so maybe a little docker err sumthing could work here too?

I've done end-to-end tests like that before and they usually serve in getting things pretty correct 🏄🏼 
Ok so I think the first issue is solved but now auth isn't working correctly?

`x11vnc -display :0 -noipv6 -forever -noxdamage -ncache_cr  -passwd doggy`

Run the std connect block with `password='doggy'` gets:

```python
 File "/home/goodboy/repos/piker/snippets/async_vnc.py", line 25, in asyncvnc_main
    asyncio.run(run_client())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/home/goodboy/repos/piker/snippets/async_vnc.py", line 8, in run_client
    async with asyncvnc.connect(
  File "/usr/lib/python3.10/contextlib.py", line 199, in __aenter__
    return await anext(self.gen)
  File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 566, in connect
    client = await Client.create(reader, writer, username, password, host_key)
  File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 519, in create
    raise PermissionError('Auth failed')
PermissionError: Auth failed
```

Not sure what's up, but if i remove the `-passwd` line it works.


I hacked in a `force_video_mode:str = 'rgba'` that gets passed on through to `Video.create()` and that seemed to make everything work. Not sure if that's the solution you want though 😂 
Try 45c311729b330f92ff92dee28ad9f18c0ea093ee?
Ah nice, will do now.
Yeah don't think it fixed it.
not really sure i follow that patch but the output is now:

```python
File "/home/goodboy/repos/asyncvnc/asyncvnc.py", line 295, in create
    mode = video_modes[bytes(mode_data)]
KeyError: b'\x10\x10\x00\x01\x00\x1f\x00?\x00\x1f\x0b\x05\x00'
```

like i said, just forcing `Video.mode = 'rgba'` makes it work.
That video mode uses 16 bits per pixel, which my code can't/won't support. It's different from your original exception!
@barneygale yeah i haven't changed anything. you can see the command line i'm using.

I suggest you run the `x11vnc -display :0 -noipv6 -forever -noxdamage -ncache_cr` and try to run your code.

If you don't expect that to work it'd be somewhat odd to me,
- this is a super popular server setup on linux
- the code works fine if i just force `rgba` (as mentioned)

So i'm not sure what you'd like to do. I can put up my force mode PR and you can see it work?
Ahh ok I take it back, the [latest error](https://github.com/barneygale/asyncvnc/issues/2#issuecomment-1135250766) is actually from an `x11vnc` running in docker setup - so you prolly are right about it being different (i think i connected to the wrong server by accident on that last manual test).

I'm putting up a small test suite shortly that shows your current code works on a local server.
I guess it might be worth it to also show a small container using the settings i have and showing that it works when forcing `'rgba'` mode even though you said:

> That video mode uses 16 bits per pixel, which my code can't/won't support. 
  


Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..e403501
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,58 @@
+name: CI
+
+
+on:
+  # Triggers the workflow on push or pull request events but only for the master branch
+  push:
+    branches: [ main ]
+  pull_request:
+    branches: [ main ]
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+
+jobs:
+
+  testing:
+    name: 'install + test-suite'
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: sh
+
+    env:
+      DISPLAY: ':1'
+
+    steps:
+
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Setup python
+        uses: actions/setup-python@v3
+        with:
+          python-version: '3.10'
+
+      - name: Install x11vnc and deps
+        run: |
+          sudo apt-get install -y xvfb \
+            libxslt-dev \
+            libxrender1 \
+            libxtst6 \
+            libxi6 \
+            libgtk2.0-bin \
+            socat \
+            x11vnc
+
+      - name: Install dependencies
+        run: pip install -U . -r requirements-test.txt --upgrade-strategy eager
+
+      - name: Remove Xorg tmp file(s)
+        run: rm -f /tmp/.X1-lock
+
+      - name: start Xvfb in background
+        run: Xvfb :1 -ac -screen 0 1024x768x16 &
+
+      - name: Test suite
+        run: pytest tests -srvv
diff --git a/README.rst b/README.rst
index 15136ad..1f8e186 100644
--- a/README.rst
+++ b/README.rst
@@ -10,6 +10,10 @@ AsyncVNC: Asynchronous VNC for Python
 .. image:: https://img.shields.io/pypi/v/asyncvnc?style=flat-square
     :target: https://pypi.org/project/asyncvnc
 
+.. image:: https://github.com/barneygale/asyncvnc/actions/workflows/ci.yml/badge.svg
+    :target: https://github.com/barneygale/asyncvnc/actions
+
+
 
 AsyncVNC is a Python package which provides an asynchronous client implementation of the VNC (RFB) protocol on top of
 the asyncio framework.


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..e079f8a
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1,1 @@
+pytest
diff --git a/tests/test_x11vnc.py b/tests/test_x11vnc.py
new file mode 100644
index 0000000..eb2833d
--- /dev/null
+++ b/tests/test_x11vnc.py
@@ -0,0 +1,139 @@
+import asyncio
+import platform
+import signal
+import socket
+import subprocess
+import sys
+import time
+
+import asyncvnc
+import pytest
+
+
+pytest_plugins = ['pytester']
+
+
+# Sending signal.SIGINT on subprocess fails on windows. Use CTRL_* alternatives
+if platform.system() == 'Windows':
+    _KILL_SIGNAL = signal.CTRL_BREAK_EVENT
+    _INT_SIGNAL = signal.CTRL_C_EVENT
+    _PROC_SPAWN_WAIT = 6
+else:
+    _KILL_SIGNAL = signal.SIGKILL
+    _INT_SIGNAL = signal.SIGINT
+    _PROC_SPAWN_WAIT = 0.6 if sys.version_info < (3, 7) else 0.4
+
+
+def sig_prog(proc, sig):
+    '''
+    Kill the process with ``sig``.
+
+    '''
+    proc.send_signal(sig)
+    time.sleep(0.1)
+
+    if not proc.poll():
+        proc.send_signal(_KILL_SIGNAL)
+
+    ret = proc.wait()
+    assert ret in (-9, -2, 0)
+
+
+@pytest.fixture(
+    # password parameteriztion
+    params=[None, 'doggy'],
+    ids=lambda param: f'password={param}',
+)
+def x11vnc(
+    request,
+    testdir,
+):
+    '''
+    Run a ``x11vnc`` server as subproc.
+
+    '''
+    port = 5900
+    pw = request.param
+    cmdargs = [
+        'x11vnc',
+        '-display :1',
+        '-noxdamage',
+        '-noipv6',
+        '-forever',
+        '-noxdamage',
+        '-ncache_cr',
+        f'-rfbport {port}',
+    ]
+    if pw:
+        cmdargs.append(f'-passwd {pw}')
+
+    cli_cmd = ' '.join(cmdargs)
+    print(f'\nspawning x11vnc with: {cli_cmd}\n')
+
+    # TODO: x11 doesn't run on windows right so we don't need this?
+    # i guess it depends on whether we want a test for an equivalent
+    # server on windows? no clue what that project would be..
+    spkwargs = {}
+    if platform.system() == 'Windows':
+        # without this, tests can hang on windows forever..
+        spkwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
+
+    proc = testdir.popen(
+        cmdargs,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        shell=True,
+        **spkwargs,
+    )
+
+    assert not proc.returncode
+
+    timeout = 10
+    start = time.time()
+
+    while (
+        time.time() - start < timeout
+    ):
+        s = socket.socket()
+        sockaddr = ('127.0.0.1', port)
+        try:
+            s.connect(sockaddr)
+            break
+        except Exception:
+            print(f'waiting for vnc server socket to come up: {sockaddr}')
+            time.sleep(0.5)
+
+            # XXX: for debugging these tests if necessary
+            # line = proc.stderr.readline()
+            # while line:
+            #     print(line)
+            #     line = proc.stderr.readline()
+            # else:
+            #     break
+
+        finally:
+            s.close()
+
+    else:
+        raise TimeoutError('`x11vnc` never started up?')
+
+    # time.sleep(_PROC_SPAWN_WAIT)
+    yield proc, port, pw
+    sig_prog(proc, _INT_SIGNAL)
+
+
+def test_basic_connection_maybe_auth(
+    x11vnc,
+):
+    proc, port, pw = x11vnc
+
+    async def run_client():
+        async with asyncvnc.connect(
+            'localhost',
+            port=port,
+            password=pw,
+
+        ) as client:
+            print(client)
+
+    asyncio.run(run_client())