Switching OS color mode

Posted 02.07.2022 ยท 4 min read

Most of the time I prefer the color mode of my devices to be dark. I do not have a good answer to why. However, sometimes I switch for different reasons, especially in the summer times to deal with glare or when I am working outside ๐Ÿ˜Ž

To switch I use a neat little app called NightOwl. It allows to have hotkeys for switching, which I have set up to toggle based on ctrl + option + command. It is also schedule for switching or you can switch color mode by clicking the owl in the status bar.

Visual Studio Code

Vscode have support for setting a theme per color mode and automatically switch. To enable it add this to the settings.json and change to your preferred themes.

{
"window.autoDetectColorScheme": true,
"workbench.preferredDarkColorTheme": "GitHub Dark",
"workbench.preferredLightColorTheme": "GitHub Light"
}

iTerm

To get iTerm to switch when the system color mode switches we need to write a script. Below is a script based on this gist. Put the script in $(HOME)/Library/Application\ Support/iTerm2/Scripts/AutoLaunch/color-switch.py and go to Scripts -> AutoLaunch in the iTerm menu and check it. If you haven't used any iTerm scripts before it will prompt to download the python runtime. After that is done it should be working. In the case that it does not update you can check the logs by going to Scripts -> Manage -> Console to see the logs.

#!/usr/bin/env python3
import asyncio
import iterm2
LIGHT_THEME = "papercolor-light"
DARK_THEME = "papercolor-dark"
async def main(connection):
async with iterm2.VariableMonitor(
connection, iterm2.VariableScopes.APP, "effectiveTheme", None
) as mon:
while True:
# Block until theme changes
theme = await mon.async_get()
preset = await iterm2.ColorPreset.async_get(
connection,
DARK_THEME if "dark" in theme.split(" ") else LIGHT_THEME,
)
# Update the list of all profiles and iterate over them.
profiles = await iterm2.PartialProfile.async_query(connection)
for partial in profiles:
profile = await partial.async_get_full_profile()
await profile.async_set_color_preset(preset)
iterm2.run_forever(main)

Shell script for detecting color mode

I created this tiny script that will print light or dark. I put it on path the name color-mode. This will be referred to by other things like my vim setup.

#!/bin/bash
mode="$(defaults read -g AppleInterfaceStyle 2>/dev/null || echo "light")"
echo $mode | tr '[:upper:]' '[:lower:]'

Vim

In the vim setup I use the utility script color-mode described in the section above. This setup is maybe the part of this post that could be smoother, because the switch is not automatic. Below there is a function that will use the color-mode command to get the color mode and set the background variable in vim when it is running in iTerm. If it is not running in iterm it will use the COLOR_MODE environment variable, thus, enabling this to work on remote sessions through ssh. See the ssh section for how to pass the environment variable.

function! SetBackgroundMode(...)
let s:new_bg = "dark"
if $TERM_PROGRAM ==? "iTerm.app"
let s:mode = systemlist("color-mode")
if len(s:mode) > 0
if s:mode[0] ==? "light"
let s:new_bg = "light"
else
let s:new_bg = "dark"
endif
endif
else
if $COLOR_MODE ==? "light"
let s:new_bg = "light"
else
let s:new_bg = "dark"
endif
endif
if &background !=? s:new_bg
let &background = s:new_bg
endif
endfunction

I set the background variable since the colorscheme I use, Papercolor, supports light and dark mode through that vairable. You could change this part to set colorscheme instead.

To enable this I call this function on vim startup in my vimrc file, and I have mapped it to cc so that I can quickly sync the color mode when I change the system one. I have tried to do this automatically with a timer that would call the function every second, but that caused some weird behaviour while typing.

colorscheme Papercolor
call SetBackgroundMode()
nnoremap cc :call SetBackgroundMode()<cr>

SSH

The ssh setup is mostly for vim and other programs that have color schemes, because the regular terminal will follow the colorscheme of the client where it is rendered. To enable vim and other pograms to know the color mode of the client I have set this environment variable in my zsh config.

export COLOR_MODE=$(color-mode)

Then with SendEnv COLOR_MODE in the .ssh/config on the client and AcceptEnv COLOR_MODE in /etc/ssh/sshd_config on the server this environment variable will be accesible on the server. However, this will not automatically update. I don't use it that much so in the current state it is still nice to have.