I’ve been working from home for a long time now (ever since COVID-19) and one of the complaints I’ve had is that I sit facing away from a window (monitors and webcam facing the window). This means my colleagues get a really dark view of my face after auto brightness accounts for the window or my ceiling lights.
To fix this, I bought a cheap IKEA TERTIAL lamp[0] which retails for 20 bucks, and aimed it at the wall in front of me to diffuse the light. I used a spare Phillips Hue bulb that I had lying around giving me easy controls from Apple Home.
That’s still too many actions for my liking because I’d have to open up control center, go to Home, and turn on the light. Not to mention doing the same thing one my meeting is over.
It’s pretty straightforward to automate this, we need to
- detect if the webcam is enabled
- (in my case) setup a proxy over another computer to avoid storing creds on my corp laptop
- control the light over the Hue bridge using its API
Detecting if the webcam is enabled is surprisingly easy. All the links on Google
point to checking for processing using AppleCamera
or VDC
but I don’t think
that works anymore (at least, it does not work for me on Catalina). Instead,
we’ll use the Console.app
program or the command line equivalent log
that
comes built-in to MacOS.
Detecting webcam use
1 | sudo log show --predicate 'subsystem == "com.apple.VDCAssistant" && \ |
which tells us if events like kCameraStreamStart
and kCameraStreamStop
occur, which seems to occur each time the camera starts and stops. Note that
this command does not seem to work as a normal, unpriviledged user and instead,
root access. There’s probably a way to work around this, but this is a hack
cough short term solution cough.
Controlling lights over the Hue bridge with it’s API is relatively
straightforward, we just need to get a token from the hue developer portal. Once
you have the token you can then control the light with a simple PUT
query like
so:
1 |
|
I have made the script read for events like bulb:on
from stdin that’ll be then
used to toggle the state.
Proxying the command
I keep this running on a personal computer in my household and expose it over a TCP port with socat. It’s kept daemonized with start-stop-daemon and OpenRC.
1 |
|
Each time there’s anything sent to tcp:6338 it runs the script which then controls the bulb. With this, all the corp laptop sees is a string sent to a local computer using netcat.
Tying it all together
(this section is slightly updated, to remove the use of cron)
The script to detect the webcam state is put in the root users crontab on the
laptop. It then sends the appropriate string to the proxy script to toggle the
physical state of the bulb.
The script to detect the status of the webcam uses log stream
instead of log show
, which eliminates the need for a minutely cron. 60 seconds without a lamp
is still quite a noticeable delay in a meeting. Using the stream, it’s put in a
script as so
1 |
|
and launched using launchd
. The launch daemon is dumped into
/Library/LaunchDaemons/
as com.alex.bulb
.
1 |
|
It can then be launched with launchctl load com.alex.bulb
and launchctl start ...
.
The user experience is pretty good, I hop on a call with Google Meet, which enables the camera, which triggers the lamp, and which automatically turns it off after I’m done.
Ref
[0] https://www.ikea.com/sg/en/p/tertial-work-lamp-dark-grey-50355395/