A Weird Imagination

Minimal WebRTC

The problem#

I wanted to stream video of myself and my screen at the same time. My plan was to put the video of myself on my screen and stream the entire screen, but I do not have a camera on my desktop. On the other hand, I do have a smartphone with a camera, so I needed a way to show the video from my phone's camera on my desktop's screen.

There are a few Android apps that promise to do so, but none of the ones I tried worked very well. But I know that video chat works just fine on my phone, including in a web browser using WebRTC which supports peer-to-peer video chat between two web browsers, so it should be easy to show the video from my phone's camera in a browser window on my desktop. Unfortunately, I couldn't find any straightforward solution for setting up just a peer-to-peer video link.

The solution#

Open minimal-webrtc on the computer you want the video streamed to. A QR code will appear; use your smartphone to read it and after approving access to the camera, the video should appear in the first browser window. This is intended to be used for local connections, so it may not work if the two devices are not on the same network. Only the signaling to set up the connection goes to the minimal-webrtc server, the actual video will be sent peer-to-peer over the local network.

To get just the video as a bare window with no decorations, use chromium --app=uri to get rid of the address bar, etc., and this script to remove the rest.

To host it yourself, download the source code1 and use the included run_daphne.sh script (which assumes daphne is installed) and nginx configuration. As WebRTC requires HTTPS, to run it on your local network, you may need to set up a self-signed certificate.

The details#

Read more…

Extracting slides from video presentations

The problem#

Washington state has been holding a lot of press conferences with updates about the COVID-19 situation recently. The information has always been summarized in a few slides during the video, but those slides and explanatory text are only posted separately several hours to a day later.

The solution#

youtube-dl will download videos off Twitter just given the URL of the tweet like this one. Then clone and run slide-detector:

./slide-detector.py video.mp4 473 105 727 397

(requires opencv-python) where video.mp4 is the filename of the video and the relevant section of the video is a 727x397 rectangle whose top-left corner is at the coordinates (473, 105), which is the correct rectangle to crop the linked video to just the main video section (i.e. omitting the ASL interpreter who is always on screen). Omit the numbers to not crop the video.

The script will output the slides as image files in the current directory with names like static_at_3:55.jpg for the slide that appears on the screen 3 minutes and 55 seconds into the video.

The details#

Read more…

100% CPU usage in games with Nvidia Linux drivers

The problem#

Every game, no matter how old and simple, I run on my computer constantly uses an entire CPU thread even when idling at a menu. (Except for some newer multi-threaded games that do the same with multiple threads!) To raise this from a curiosity to a problem, this means that my computer's fans are on at full blast whenever I have a game going, so I notice.

The solution#

To be clear, that symptom could be the result of many different possible causes, others of which I may explore in future blog posts.1 But specifically for systems with Nvidia GPUs using the Nvidia proprietary driver (as opposed to nouveau), setting the environmental variable __GL_YIELD to USLEEP fixed the issue in some games for me. To do so when running a single game, run __GL_YIELD="USLEEP" /path/to/game or to do so permanently, add the line

export __GL_YIELD="USLEEP"

to ~/.profile and restart X.

The details#

Read more…

Change title based on visible section

The problem#

In the computer game Keep Talking and Nobody Explodes, the "bomb expert" players are looking at a fictional "bomb manual", often frantically searching for the right page. While the intention is for this document to be printed out—and physical paper makes it relatively easy to keep the headings at the top of each page visible—there is also a web version if you prefer to view it on a screen (or don't have access to a printer). Scrolling through the web version feels a lot more awkward than flipping through the paper version; one workaround I found was to open different pages in different browser tabs or windows, but then identifying which page is in which window is still awkward.

The solution#

I created a userscript, "BombManual.com Tab Title and TOC" that automatically updates the tab title to match the title of the currently visible page. It additionally adds a table of contents to make it easy to quickly open all of the pages in separate tabs or windows.

If you do not already have one, you will need to install a userscript manager extension for your browser to use it. Alternatively, you could bookmark this bookmarklet, but as you would have to click that bookmark on every instance of the page you opened, that's less convenient.

The details#

Read more…

Long Polling in Django Channels

The problem#

In a web app where the display should be constantly up-to-date, the client needs some way to get up-to-date information from the server. One of the simplest ways to do so is to regularly (every few seconds) query the server asking if there is new information. This involves making a lot of requests and is wasteful of bandwidth and processor time on both the client and server (the latter can be improved with caching).

If updates are rare, it makes much more sense for the server to notify the client when they occur, but HTTP is designed around the client making requests to the server, not the other way around. And, furthermore, the Django web framework (like many web frameworks) is built around that model.

The solution#

Of course, this is a well-understood problem and there are a wide variety of APIs and libraries to solve it discussed on the Wikipedia page for Comet. The main workarounds are WebSockets which is a very flexible technology for two-way communication in a web browser and long polling which is a simpler technique which involves merely having the server not answer a request immediately and instead wait until it actually has an update to reply with.

In the rest of this blog post, I discuss the changes I made to convert a Django-based web app that I originally wrote to use a basic polling pattern and hosted using uWSGI to instead use long polling and be hosted using Gunicorn/Uvicorn. I also cover nginx configuration including hosting the app in a subdirectory.

The details#

Read more…

Eliminating Control.Monad.Error

The problem#

Compiling the Haskell package language-python (a dependency of xcffib), I got the following warning stating that the typeclass Error is deprecated:

language-python/src/Language/Python/Common/ParseError.hs:25:10: warning: [-Wdeprecations]
    In the use of type constructor or class Error
    (imported from Control.Monad.Error.Class, but defined in Control.Monad.Trans.Error):
    Deprecated: "Use Control.Monad.Trans.Except instead"
   |                                 
25 | instance Error ParseError where 
   |          ^^^^^                  

I wasn't sure how to "Use Control.Monad.Trans.Except instead", as Except is not a drop-in replacement for Error.

The solution#

As this StackOverflow answer recommended,

Short answer is: Replace Error by nothing at all

The code used throwError, which I replaced with

throwError = lift . Left

Other than that, I just removed the imports of Control.Monad.Error and the typeclass instance of Error. The full diff is in this pull request.

The details#

Read more…

Troubleshooting python-xcffib

The problem#

The monitor-lock.py script in my previous blog post uses python-xlib, which currently mainly relies on manually porting Xlib functions to Python. This is why it is missing the barrier-related functions I needed in that post. There is work on automating this process, but it appears to be abandoned. I started trying to pick up where they had left off before finding the python-xcffib project which provides auto-generated bindings for libxcb and therefore gives full support for interacting with X at a low level from Python.

python-xcffib (named after the cffi library it uses for binding to the C XCB library) gives a slightly lower-level API than python-xlib, but they are both fairly thin wrappers over the X protocol, so the differences are minor. It was fairly straightforward to port my script from the previous post to use python-xcffib, available as monitor-lock-xcb.py.

Unfortunately, I ran into a bug in python-xcffib:

Traceback (most recent call last):
...
  File "./monitor-lock-xcb.py", line 38, in main
    devices = conn.xinput.XIQueryDevice(xcffib.xinput.Device.AllMaster).reply().infos
...
  File "/usr/lib/python3/dist-packages/xcffib/__init__.py", line 139, in _resize
    assert self.size + increment <= self.known_max
AssertionError

The solution#

I've submitted the fix upstream, so most likely you will not encounter this error. Updating to the latest version (after v0.8.1) should be sufficient to fix the problem.

The fix I applied was to modify the module's __init__.py (the location, which may be different on your machine, is in the stack trace). Specifically, on line 108 in the function Unpacker.unpack(), in the call to struct.calcsize(), change fmt to "=" + fmt.

The details#

Read more…

Lightweight multiseat X

Posted in

The problem#

I hosted a LAN party1 a little while ago and ended up needing to loan out multiple computers to guests in the interest of having no one try to lug their desktop over. As it turns out, I don't keep multiple of spare gaming-ready laptops around, so I needed to get more computers somehow.

The solution#

My desktop has three screens attached to it (two monitors plus a projector), so given an extra keyboard and mouse (or two), it should be possible to run multiple instances of the game on it at the same time to let multiple people play using the same computer.

The script from this forum post makes it easy to set up multi-pointer X so a second keyboard and mouse will get its own mouse cursor. Then each keyboard and mouse pair can interact with its own instance of the game.

As an additional aid, I wrote monitor-lock.py which allows you to assign a mouse to a monitor, so it cannot be moved off that monitor to prevent accidentally interacting with the other player's instance of the game.

The basic usage is that you first run it with no arguments to get the available screens and pointers getting an output something like this:

$ ./monitor-lock.py 
...
Available screens:
screen 0: {'x': 0, 'y': 0, 'width': 3840, 'height': 2160}
screen 1: {'x': 3840, 'y': 0, 'width': 1920, 'height': 1200}
screen 2: {'x': 3840, 'y': 1200, 'width': 1920, 'height': 1080}

Available pointers:
device 2: Virtual core pointer
device 17: second pointer

USAGE: ./monitor-lock.py [device] [screen]

and then in a screen session (so you don't have to worry about accidentally doing this on a monitor you've locked your pointer away from), run

./monitor-lock.py 2 0

and

./monitor-lock.py 17 1

to lock the primary pointer to the first screen and the second pointer to the second screen.

Just use Ctrl+C to kill the process when you want the pointer to be able to move freely again.

The details#

Read more…

Emulating Xbox controllers using GameCube controllers

The problem#

I previously wrote about making different controllers act like Xbox 360 controllers. While it's a useful general-purpose solution, it's can be a bit clunky to have to explicitly set the mappings for each controller. More importantly, the remapping leaves the original controller entries in /dev/input/, although they don't do anything, and some games1 assume that the four players are controlled by the first four controllers. This is no longer true if js0 is the real first controller and js1 is the copy made by xboxdrv to look like an Xbox 360 controller. Or, worse, if js0-js3 are the four real controllers and js4-js7 are the ones we want the game to actually use.

The specific reason I'm remapping the controllers, is that the gamepads I'm actually using are GameCube controllers connected via the Nintendo GameCube controller Adapter for Wii U, which connects up to four GameCube controllers to a USB port. wii-u-gc-adapter makes them usable as controllers, but they appear different enough from Xbox 360 controllers that remapping them is necessary for most games.

The solution#

Just build and use the version of wii-u-gc-adapter in my feature/mimic-xpad branch and your GameCube controllers will show up as Xbox controllers.

The details#

Read more…

User scripts on iPad

The problem#

Google Forms is a tool that allows for easily setting up simple structured data entry. But it's designed to make it easy to analyze a lot of data that has been entered, not to view a single entry. There is a view to show individual entries, but it's very cluttered due to including all of the options that were not selected as well as those that were selected. A display that showed only the entries that were selected could be used as a quick and dirty way to make a form letter-like website.

To make this problem harder, the solution has to run on iPad, a platform not exactly known for its user programmability.

The solution#

Bookmark this link: hide unselected items in Google Form. Then select that bookmark when on the appropriate Google Forms page. Note that in addition to hiding unselected entries, if the entry that is not select has a value of "Yes", then its entire section will be hidden. If you don't want that behavior, bookmark this variant of the script instead.

The details#

Read more…