Skip to content

Instantly share code, notes, and snippets.

Forked from jamesmacfie/
Last active Apr 13, 2022
What would you like to do?
iTerm 2 - script to change theme depending on Mac OS dark mode

My fork of , adapted to also switch Emacs.

How to use

Copy the Python script to the following location:

$HOME/Library/Application Support/iTerm2/Scripts/AutoLaunch/

Create the directory if it doesn't exist. Reboot iTerm2, and say "Yes" if it asks you to download a Python runtime.


This will automatically update Emacs themes, as well, through emacsclient. Make sure you have Emacs running as a server in your init.el ((server-start)).

If Emacs isn't running, this script will skip it silently, no problem. But the emacsclient binary must exist.

For context, here's what I use for my init.el:

;; Start emacs in server mode, unless there already is one
(when (and (require 'server nil :noerror)
           (not (server-running-p)))

(defun hly/disable-themes ()
  (mapc 'disable-theme custom-enabled-themes))

(defun hly/light-mode ()
  ;; I like the default theme.

(defun hly/dark-mode ()
  (load-theme 'tango-dark))

Changing what themes this uses

Note the constants at the top of the file. These are iTerm2 colour profiles, and Emacs functions (which you must define in your Emacs config) to actually switch the theme.

#!/usr/bin/env python3
# Originally from:
import asyncio
import iterm2
EMACSCLIENT_BINARY = '/Applications/'
EMACS_DARK_THEME = 'hly/dark-mode'
EMACS_LIGHT_THEME = 'hly/light-mode'
ITERM_DARK_THEME = "Dark Background"
def is_dark(theme: str) -> bool:
# Themes have space-delimited attributes, one of which will be light or dark.
return "dark" in theme.split(" ")
async def sync_to_theme_iterm(connection, dark_mode: bool):
itheme = ITERM_DARK_THEME if dark_mode else ITERM_LIGHT_THEME
preset = await iterm2.ColorPreset.async_get(connection, itheme)
# Update the list of all profiles and iterate over them.
profiles=await iterm2.PartialProfile.async_query(connection)
for partial in profiles:
# Fetch the full profile and then set the color preset in it.
profile = await partial.async_get_full_profile()
await profile.async_set_color_preset(preset)
async def sync_to_theme_emacs(dark_mode: bool):
etheme = EMACS_DARK_THEME if dark_mode else EMACS_LIGHT_THEME
proc = await asyncio.create_subprocess_exec(
'--eval', f'({etheme})',
'-a', 'true')
await proc.communicate()
# Ignore return code
async def sync_to_theme(connection, theme: str):
dark_mode = is_dark(theme)
await asyncio.gather(
sync_to_theme_iterm(connection, dark_mode),
async def main(connection):
app = await iterm2.async_get_app(connection)
initial_theme = await app.async_get_theme()
await sync_to_theme(connection, initial_theme[0])
async with iterm2.VariableMonitor(connection, iterm2.VariableScopes.APP, "effectiveTheme", None) as mon:
while True:
# Block until theme changes
await sync_to_theme(connection, await mon.async_get())
Copy link

varenc commented Apr 13, 2022

I appreciate the iTerm script refactoring in this! 👌🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment