Yesterday while surfing the ASCII highways of IRC (yes, IRC) a URL linking to a MacOS application scrolled by my screen. Unclack is a small MacOS utility which silences the microphone of the user when they’re typing. The purpose is to prevent the noise of typing being passed through to other participants when on a Zoom / Skype / Jitsi call. Neat.
They don’t make a Linux version, and I couldn’t see anything similar, so I did what I usually do in this instance, throw the idea towards my friendly local coder, Stuart Langridge. He was, as ever, initially bemused and then dismissive.
Here’s a simulated, completly hyothetical conversation between us:
Alan: I reckon that’s an app which could exist on linux - unclack.app - “Unclack is the small but mighty Mac utility that mutes your keyboard while you type!”
Stuart: But my keyboard doesn’t make any noise?
Stuart: Oh haha! it mutes the mic
Stuart: I thought: the answer here is don’t be popey with the stupid noisy keyboard ;)
He has a point. Hypothetically I might have suggested:
Alan: I reckon a little python thing that sits in the corner and can be toggled on/off would be neat
Stuart: Is this an attempt to nerd-snipe me into building it? :)
Stuart: Ok, if i get a chance I’ll look into it!
Stuart provided me with the first iteration, able to detect keypresses, you know, like a keylogger. We spent approximately 30 seconds brainstorming a name. I came up with
unkeeb. Stuart expressed with some vigor that
unkeeb was under no circumstances going to be the name.
A short while later,
unkeeb was born!
By late afternoon Stuart had the basics of the utility working. We tested over a Google Meet. It would detect keypresses, mute and unmute the microphone, and display the status in the indicator area. This smelled like a Minimum Viable Product!
With Stuarts veto on the
unkeeb name, as the Ideas Man, I had to think of something else. I checked a thesaurus and came up with
calm-ivories which was even dumber than
unkeeb. Then it hit me!
30 seconds of Internet due diligence later, and the name was settled. “Hushboard” it is. Who says naming things is hard? Stuart does. All the time. That’s why he relies on me for these parts of the project lifecycle. That, and packaging. We were getting close to sharing the creation with the world. We decided, with no surprise to anyone, to make a snap!
So here’s what I did to package what’s essentially a tiny bit of python, and a few dependencies as a snap, so Stuart could build and publish it in the Snap Store.
This is straightforward. The
version tag uses
git so whenever a build is published, you can refer back to the tag which it was built from. It’s going to be strictly confined. We don’t want it doing anything narfarious with all those keypresses it’s collecting!
Note: For the humour-challenged, this is a joke. It does not collect keypresses.
core18 which means it’s using packages from Ubuntu 18.04 for
stage-packages later, which are older than the packages used when we tested on our 20.04 / 20.10 systems. Foreshadowing
name: hushboard base: core18 version: 'git' summary: Mute your microphone while typing description: | Save your friends and colleagues from the noise of your keyboard while on audio or video calls. Hushboard detects keypresses and automatically mutes your microphone, unmuting shortly after you finish typing. grade: stable confinement: strict
First is hushboard itself. As I plan to land this in the upstream repo, the
. - the current directory. I’m not using the
python plugin, because the project doesn’t have all the necessary python bits like a
setup.py. We’re literally dumping the python files into the snap and running them.
gir1.2-appindicator3-0.1 so it can place a little indicator icon in the top right of the screen, so you can see what the app is doing. We also need
python3-xlib which is needed by Hushboard to hook into X11 to detect keypresses. Yes, this uses X11, no, it hasn’t been tested on Wayland. No, it almost certainly won’t work there. Patches may be welcome, so long as it doesn’t break X11 stuff.
prime section is pretty neat. This means “only put these things in the snap, and nothing else”. That way we strip the snap down to the absolute minimum needed to make it run. The Xlib library is about the biggest thing in the snap, which ends up being under 200K in size.
parts: hushboard: plugin: dump source: . stage-packages: - gir1.2-appindicator3-0.1 - python3-xlib prime: - usr/lib/girepository-1.0/AppIndicator3-0.1.typelib - usr/lib/*/libappindicator3.so.* - usr/lib/python3/dist-packages/* - hushboard/* - bin/*
We bundle a launcher script so we need a part to pull that in.
launcher: plugin: dump source: snap/local/
Here’s the launcher script. All it does is ensure python can find the dependencies we bundled (xlib) and launches the application. I could probably move the
PYTHONPART line to the
apps section as an
environment stanza, and may well have done by the time you read this.
#!/bin/bash PYTHONPATH=$SNAP/usr/lib/python3/dist-packages:$PYTHONPATH cd $SNAP /usr/bin/python3 -m hushboard
Here we specify the binary we are going to expose to the outside world as
hushboard, which in fact launches
$SNAP/bin/launcher, that we just saw above. I definitely should add that
PYTHONPATH to the
environment section. Hang on. Ok, done.
We’re leveraging the GNOME extension here. It means a ton of stuff is done and bundled for us.
apps: hushboard: extensions: [gnome-3-34] environment: LD_LIBRARY_PATH: $SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio command: bin/launcher plugs: - x11 - audio-playback
That’s it. I threw a PR over to Stuart who hooked it all up to the snapcraft build system. Within an hour or so
When I first built the snap, the yaml looked pretty much as it does now, but without the
prime section. This caused a library conflict between GNOME libraries in the GNOME Platform Snap (gnome-3-34) and what was pulled in by my
stage-packages definition. Thanks to Ken VanDine on the Ubuntu Desktop Team and Chris Patterson from the Snapcraft team at work for helping me better understand this. We ndeeded to bundle the appindicator library, but didn’t want all the other dependencies, which caused the conflict. Using the
prime section we could allowlist only the things we want, which has a side-effect that the snap is nice and tight.
When Stuart first landed the yaml and hooked up the snapcraft build service, we had build failures on the s390x and ppc64el architecture builders. That’s because the
gnome-3-34-1804-sdk component used at build time isn’t built for those architectures. So I added an architectures stanza to limit the builds we’d support. Now the build service doesn’t even try to build s390x, ppc64el. Sorry all of you doing video calls on your IBM mainframes!
At one point in development, once snapped, hushboard would crash with a traceback:
$ snap run hushboard Gtk-Message: 16:12:36.769: Failed to load module "canberra-gtk-module" Traceback (most recent call last): File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.6/runpy.py", line 85, in _run_code exec(code, run_globals) File "/snap/hushboard/x19/hushboard/__main__.py", line 204, in <module> HushboardIndicator().run() File "/snap/hushboard/x19/hushboard/__main__.py", line 150, in __init__ self.queue = queue.SimpleQueue() AttributeError: module 'queue' has no attribute 'SimpleQueue'
A quick search online revealed SimpleQueue which Stuart used in hushboard, was introduced in Python 3.7. We’re building on Ubuntu 18.04 due to the use of
core18 as a
base (callback). I worried this might be a significant problem. Stuart pointed out it was a one line fix to use a different method to achieve the same thing, and be compliant with the features in Python 3.6. Awesomesauce!
I had a couple of meetings today in which I could test the newly minted hushboard. Hushboard has an internal timer which essential starts whenever the user presses a key and the microphone is muted. If you don’t press anything for a certain period, you’re unmuted. We initially settled on 2 seconds as the delay. But this seemed weird, as the typer would have to wait 2 seconds after typing to be able to say anything. It stilted conversations.
We tried 150ms but that was seen as too short a time. People in the meeting could hear my microphone strobing off and on as I typed, because I don’t necessarily type super fast all the time. We finally settled on 250ms but I expect Stuart is open to feedback on this topic 😃.
This was a fun little project. Within about 24 hours we went from idea to prototype, implementation, testing, building for multiple architectures, publishing, made a tutorial video and a promotional blog post about this simple little utility. Do feel free to install it and let us know your thoughts.