At the beginning of COVID lockdown and multiple people working from home it was obvious there was a need to let others know when I’m in a meeting or on a live webcam. So naturally it took me one year to finally do something about it. Now I’m here to share what I learned along the way. You too can have your very own “do not disturb” sign automatically light up outside your door to tell people not to walk in half-dressed on laundry day.
At first I was surprised Zoom doesn’t have this kind of feature built in. But then again I might use Teams, Meet, Hangouts, WebEx, Bluejeans, or any number of future video collaboration apps. Wouldn’t it make sense to just use a system-wide watch for active webcams or microphones? Like most problems in life, this one can be helped with the Linux kernel. A simple check of the uvcvideo module will show if a video device is in use. Without using events all that is left is to poll it for changes. I chose to build a taskbar icon for this. I would normally do this with my trusty C++. But I decided to step out of my usual comfort zone and use Python in case someone wanted to port it to other platforms. I also wanted to renew my lesser Python-fu and face my inner white space demons. I came up with the following ~90 lines of practical and simple but insecure Python:
https://github.com/jboero/livewebcam/blob/main/livewebcam
Aside from the icon bits, a daemon thread performs the following basic check every 1s, calling scripts as changed:
def run(self): while True: val=subprocess.check_output(['lsmod | grep \'^uvcvideo\' | awk \'{print $3}\''], shell=True, text=True).strip() if val != self.status: self.status = val if val == '0': val=subprocess.check_output(['~/bin/webcam_deactivated.sh']) else: val=subprocess.check_output(['~/bin/webcam_activated.sh']) time.sleep(1)
Rather than implement the parsing of modules, just using a hard-coded shell command got the job done. Now whatever scripts you choose to put in ~/bin/ will be used when at least one webcam activates or deactivates. I recently had a futile go at the kernel maintainers regarding a bug in usb_core triggered by uvcvideo. I would just as soon not go a step further and attempt an events patch to uvcvideo. Also, this leaves room for Mac or Windows users to port their own simple checks.
Now that I had a happy icon that sits in my KDE system tray I could implement scripts for on and off. This is where things got complicated. At first I was going to stick a magnetic bluetooth LED badge on my door to flash “LIVE” whenvever I was in a call. These things are ubiquitous on the internet and cost about $10 for basically an embedded ARM Cortex-M0 with an LED screen, bluetooth, and battery. They are basically a full Raspberry Pi Pico kit but soldered onto the board.
Unfortunately these badges use a fixed firmware that is either listening to Bluetooth transmissions or showing your message – it doesn’t do both which is silly. Many people have posted feedback that they should be so much more. Sure enough someone has already tinkered with custom firmware. Unfortunately the firmware was for older USB variants and I’m not about to de-solder or buy an ISP programmer to flash eeprom just for this. That would be a super interesting project for later and would be a great Rpi alternative but all I want right now is a remote controlled light outside my door. I looked at everything including WiFi smart bulbs to replace my recessed lighting bulbs, to BTLE candles which are an interesting option. Along the way I learned a lot about Bluetooth Low Energy including how a kernel update can waste 4 hours of weekend with bluetooth stack crashes. BTLE is really interesting and makes a lot more sense after reading up on it. Sure enough there is Python that can set the display message on your LED badge across the room, but once it is set, Bluetooth will stop listening for you to change it or shut it off. Darn. I guess I should just make do with USB, which actually has a standard command to control power to ports. Let’s see if something exists for this already.
It looked like there are options out there even if they’re not ideal. Then suddenly I found it. Neon sign “ON AIR” for £15 and it’s as dumb as they come – just using 5v from USB power. Perfect.
The command to control USB power is uhubctl which is in Fedora repos. Unfortunately most USB hubs don’t support this command. In fact very few support it going back 20 years which seems silly. Hubs will happily report that power has been disconnected even though no such disconnection has been made. I assume it’s just a few cents extra to build in this feature but I’m not a USB hub manufacturer. Therefore I needed to source a pre-owned one. In the end I found a BYTECC BT-UH340 from the US. This was all I needed to finalize it. Adding udev rules to allow the wheel group to control USB power, I can now perform a simple uhubctl -a off -l 1-1 -p 1 to turn anything off.
Now with a spare USB extension cable lead to my door I finally have a complete solution. There is an “ON AIR” sign on the outside of my door that lights up automatically whenever any of my webcams are in use. I would love to see a Mac port or improvements in pull requests. I’m sure it can all be better. Even further I would love to hone my IoT skills and sort out flashing those Bluetooth badges. If anybody wants to replicate this please be my guest, and suggestions are always welcome.
Joakim
I did something very similar, code/image here:
https://github.com/Tethik/on-air
My partner built the sign, using a cheap wifi-controllable LED for the actual lamp. I wrote the code.
Instead of continuously polling, I use inotifywait to determine when the lamp is in use or not:
https://github.com/Tethik/on-air/blob/master/lib/onair.go#L52
John Boero
Nice! It’s fun to see it in Go.
Joachim Schröder
Hi John,
great article, thanks for the inspiration! With e.g. running OBS simply checking for processes using the module doesn’t work, as OBS will be using the module all the time. But one could check for any processes except “obs” using the video device that OBS is using as it’s virtual cam (/dev/video10) or all video devices, like this:
lsof -w /dev/video* | grep -v “^COMMAND|^obs”
So, replacing line 41 with
val=subprocess.check_output([‘lsof -w /dev/video* | grep -v \”^COMMAND\|^obs\” && echo 1 || echo 0’], shell=True, text=True).strip()
solves the issue for me and now livewebcam.py works like a charm for me!
Thanks again for your work, John!
/Joachim
Chris Bergeron
For OBS you could poll the rest api easily using curl and jq and a bash script.
Stuart Gathman
That USB problem has been annoying a lot of people for 20 years. AllPortsOff is a required feature to be called a “USB” hub – and yet, it isn’t implemented. (PerPortSwitching is an optional feature.) I had to find a working hub to workaround a UPS with braindead USB: https://gathman.org/2016/07/30/Standard_Schmandard/
Actually, the power control is implemented in the USB controller ICs used by all the hub manufacturers. That is why they report that the power is off. What they leave out is the power transistor to actually switch the power – that might cost 10 cents a port! The IC just toggles a control line.
Some people solder in a power transistor to make a hub work: https://hackaday.com/2014/02/05/software-controlled-per-port-power-switching-for-usb-hubs/
Blob
That’s great for me, but my wife is the one who really needs it, and she uses a Windows laptop provided by her job. Anyone have any ideas how to determine if a webcam is in use in Windows?
Bob
Armando
Hey John do you have a link to where you found the led sign, really like the aesthetic.
John Boero
Hi Armando
I just found it on Amazon UK. It should probably turn up in a search for your local Amazon region.
Hanku Lee
A great idea put into home projects.
Steven MacDonald
Great example. I combined this info with another linux blog for for using a Wii Lego Dimension pad… and presto lights! (easy enough to find that one)
Launched via systemd for me. Left the centre one controlld by the NFC figure. The other two to warn the kids 😀