Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using width and/or height parameters in .screenshot causes an infinite loop #9

Open
mcleantom opened this issue Nov 23, 2023 · 1 comment

Comments

@mcleantom
Copy link

mcleantom commented Nov 23, 2023

I am trying to do a screenshot of a docker container which runs firefox and a vnc, you can run this yourself like:

docker run -p 5900:5900 jlesage/firefox

I then want to take a screenshot and reduce the image size so it hopefully runs faster:

import asyncio, asyncvnc
from PIL import Image


async def get_screenshot():
    host = "localhost"
    port = 5900
    async with asyncvnc.connect(host, port) as client:
        pixels = await client.screenshot(width=320, height=240)
        image  = Image.fromarray(pixels)
        image.save("screenshot.png")


if __name__ == "__main__":
    asyncio.run(get_screenshot())

The code seems to get stuck on an infinite loop somewhere (hard for me to find out where).

On a separate note, what I really want to do is lower the resolution but keep the whole screen size, do you know if this is possible?

Thanks!

@mcleantom
Copy link
Author

mcleantom commented Nov 23, 2023

I think the error is at this block of code:

    def is_complete(self):
        """
        Returns true if the video buffer is entirely opaque.
        """

        if self.data is None:
            return False
        return self.data[:, :, self.mode.index('a')].all()

Where the whole screenshot is not being filled so .all() returns false, then this loop:

    async def screenshot(self, x: int = 0, y: int = 0, width: Optional[int] = None, height: Optional[int] = None):
        """
        Takes a screenshot and returns a 3D RGBA array.
        """

        self.video.data = None
        self.video.refresh(x, y, width, height)
        while True:
            update_type = await self.read()
            if update_type is UpdateType.VIDEO:
                if self.video.is_complete():
                    return self.video.as_rgba()

the if self.video.is_complete() is false, so the while True loop loops again, which goes back to self.read(), which then eventually goes all the way down to streams.py to this bit of code:

    async def _wait_for_data(self, func_name):
        """Wait until feed_data() or feed_eof() is called.

        If stream was paused, automatically resume it.
        """
        # StreamReader uses a future to link the protocol feed_data() method
        # to a read coroutine. Running two read coroutines at the same time
        # would have an unexpected behaviour. It would not possible to know
        # which coroutine would get the next data.
        if self._waiter is not None:
            raise RuntimeError(
                f'{func_name}() called while another coroutine is '
                f'already waiting for incoming data')

        assert not self._eof, '_wait_for_data after EOF'

        # Waiting for data while paused will make deadlock, so prevent it.
        # This is essential for readexactly(n) for case when n > self._limit.
        if self._paused:
            self._paused = False
            self._transport.resume_reading()

        self._waiter = self._loop.create_future()
        try:
            await self._waiter
        finally:
            self._waiter = None

Where the code waits forever at await self._waiter

Confusing bug, but I hope this helps.

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

No branches or pull requests

1 participant