Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
iTerm 2 - script to change theme depending on Mac OS dark mode

How to use

In iTerm2, in the menu bar go to Scripts > Manage > New Python Script

Select Basic. Select Long-Running Daemon

Give the script a decent name (I chose auto_dark_mode.py)

Save and open the script in your editor of choice.

Copy and paste the script below and save

Go back to iTerm2, go to Scripts in the menu bar and select the script you just saved.

Try toggling Dark mode to see what happens! Reminder it's under Appearance in the System Settings

Changing what themes this uses

Note in the script below on lines 15 and 17 that we have a string that we're passing to iTerm2. Change these to whatever you like. The text is whatever appears in the dropdown in the iTerm2 settings under Profile/Color for when you'd want to change the theme manually.

After you change the script you'll have to stop and start the script if it's running already

#!/usr/bin/env python3
import asyncio
import iterm2
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()
# Themes have space-delimited attributes, one of which will be light or dark.
parts = theme.split(" ")
if "dark" in parts:
preset = await iterm2.ColorPreset.async_get(connection, "Solarized Dark")
else:
preset = await iterm2.ColorPreset.async_get(connection, "Light Background")
# 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)
iterm2.run_forever(main)
@jpkbeddu

This comment has been minimized.

Copy link

@jpkbeddu jpkbeddu commented Apr 2, 2020

This script works great. Thanks.

https://www.iterm2.com/python-api/tutorial/running.html#auto-run-scripts
Add this auto start config for the script.

@jirikrepl

This comment has been minimized.

Copy link

@jirikrepl jirikrepl commented Jun 2, 2020

Work great!

First-time installation steps for me:

  1. Download the script from GitHub by right-clicking on Raw button and save as...
  2. copy the script to $HOME/Library/ApplicationSupport/iTerm2/Scripts/AutoLaunch
  3. create AutoLaunch folder if it does not exist
  4. go to iTerm2 > Scripts > AutoLaunch
  5. You will be prompted to download the Python runtime, do it.
  6. Switch the dark/light mode in macOS, iTerm2 should change color

if your script does not work after you restart iTerm2,
make sure that Preferences -> Magic -> Enable Python API is checked and this setting is saved

@Gulivertx

This comment has been minimized.

Copy link

@Gulivertx Gulivertx commented Jun 9, 2020

Nice ! But the script is not switching the preset at launch ?
It works but only when I switch from Dark to Light or inverse.

@j0ner0n

This comment has been minimized.

Copy link

@j0ner0n j0ner0n commented Jul 1, 2020

Nice ! But the script is not switching the preset at launch ?
It works but only when I switch from Dark to Light or inverse.

@Gulivertx That is because the script first waits for theme change before applying the light/dark theme styling. You would fetch the theme right away and wait for change see line below

# Block until theme changes
theme = await mon.async_get()

at the end of the while True loop

@j0ner0n

This comment has been minimized.

@sphynx

This comment has been minimized.

Copy link

@sphynx sphynx commented Jul 9, 2020

The script doesn't seem to work for me on macOS Catalina. I can see in the iTerm Python Console log (iTerm2 -> Scripts -> Manage -> Console) that the script subscribed to variable changes, but when I switch between dark and light mode in preferences - nothing happens (and no new entries are added to the log):

Script → iTerm2 9/7, 22:24:58.378:
<ITMClientOriginatedMessage 0x7f92fd80ba50>: {
    id: 0
    notification_request {
      session: "all"
      subscribe: true
      notification_type: NOTIFY_ON_VARIABLE_CHANGE
      variable_monitor_request {
        name: "effectiveTheme"
        scope: APP
        identifier: ""
      }
    }
}


Script ← iTerm2 9/7, 22:24:58.379:
<ITMServerOriginatedMessage 0x7f92fd825f00>: {
    id: 0
    notification_response {
      status: OK
    }
}
@derekju

This comment has been minimized.

Copy link

@derekju derekju commented Jul 18, 2020

Worked perfectly for me in Catalina. Thanks!

@derwent-m

This comment has been minimized.

Copy link

@derwent-m derwent-m commented Sep 15, 2020

This works well if I enable the script each time i start iTerm2, and then switch modes, but I can't get it to automatically run each time iterm is started, or detect the mode when it starts. Any ideas? Is it possible to enable a user-defined python script using AppleScript?

iTerm2 Build 3.3.12
macOS Mojave 10.14.6

@bladewangpro

This comment has been minimized.

Copy link

@bladewangpro bladewangpro commented Oct 4, 2020

This works perfectly after the following instruments wrote by jamesmacfie, jirikrepl. Thanks a lot.

@Kamik423

This comment has been minimized.

Copy link

@Kamik423 Kamik423 commented Oct 13, 2020

Can this be modified to execute once when iTerm starts? For example my Mac is in light mode but after opening iTerm for the first time today it is in dark mode, until I toggle modes once.

this is annoying.

@malaiam

This comment has been minimized.

Copy link

@malaiam malaiam commented Nov 16, 2020

Works well with instructions from jirikrepl. Thanks!

@klamouri

This comment has been minimized.

Copy link

@klamouri klamouri commented Dec 8, 2020

Thank you for sharing this gist. I tweaked it to also check if the theme is correct at iTem2 startup. Not the most elegant code as I am not very familiar with Python async
Edit: Make sure the theme is the same as your usual theme (Preferences > Profiles > Colors > Color Presets...). I personally use Solarized Dark and Solarized Light

#!/usr/bin/env python3.7

import asyncio
import iterm2
import logging

async def main(connection):

    logger = logging.getLogger('macos_theme_sync')
    app = await iterm2.async_get_app(connection)
    window = app.current_window
    initial_theme = await app.async_get_theme()
    initial_parts = initial_theme[0].split(" ")

    if "dark" in initial_parts:
        initial_preset = await iterm2.ColorPreset.async_get(connection, "Solarized Dark")
        logger.info('Initial theme is dark')
    else:
        initial_preset = await iterm2.ColorPreset.async_get(connection, "Solarized Light")
        logger.info('Initial theme is light')
    
    # Update the list of all profiles and iterate over them.
    initial_profiles=await iterm2.PartialProfile.async_query(connection)
    for partial in initial_profiles:
        # Fetch the full profile and then set the color preset in it.
        initial_profile = await partial.async_get_full_profile()
        await initial_profile.async_set_color_preset(initial_preset)
    
    async with iterm2.VariableMonitor(connection, iterm2.VariableScopes.APP, "effectiveTheme", None) as mon:
        while True:
            # Block until theme changes
            theme = await mon.async_get()
            print(theme)
            # Themes have space-delimited attributes, one of which will be light or dark.
            parts = theme.split(" ")
            if "dark" in parts:
                preset = await iterm2.ColorPreset.async_get(connection, "Solarized Dark")
            else:
                preset = await iterm2.ColorPreset.async_get(connection, "Solarized Light")

            # 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)


iterm2.run_forever(main)
@pcarn

This comment has been minimized.

Copy link

@pcarn pcarn commented Dec 8, 2020

@klamouri you have an extra paren on the logger line.

@klamouri

This comment has been minimized.

Copy link

@klamouri klamouri commented Dec 8, 2020

Thank you @pcarn, I just updated my comment, must have messed it up somehow

@rkulla

This comment has been minimized.

Copy link

@rkulla rkulla commented Dec 21, 2020

FYI, this script overwrote all of my customized color profiles with Solarized Dark's colors, on Catalina. Noticed after I uninstalled it for not supporting iTerm's 'Dedicated HotKey Window' which I was wanting to keep a different color even during dark mode.

@Climbatize

This comment has been minimized.

Copy link

@Climbatize Climbatize commented Jan 20, 2021

Thank you for sharing this gist. I tweaked it to also check if the theme is correct at iTem2 startup. Not the most elegant code as I am not very familiar with Python async
Edit: Make sure the theme is the same as your usual theme (Preferences > Profiles > Colors > Color Presets...). I personally use Solarized Dark and Solarized Light

I cleaned up your script so it's easier to maintain :)

#!/usr/bin/env python3

import asyncio
import iterm2

async def changeTheme(connection,parts):
    theme_dark = "Night Owl"
    theme_light = "Tango Light"
    print(parts)
    
    if "dark" in parts:
        preset = await iterm2.ColorPreset.async_get(connection, theme_dark)
    else:
        preset = await iterm2.ColorPreset.async_get(connection, theme_light)

    # 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 main(connection):

    app = await iterm2.async_get_app(connection)
    window = app.current_window
    initial_theme = await app.async_get_theme()
    await changeTheme(connection,initial_theme)

    async with iterm2.VariableMonitor(connection, iterm2.VariableScopes.APP, "effectiveTheme", None) as mon:
        while True:
            # Block until theme changes
            theme = await mon.async_get()
            # Themes have space-delimited attributes, one of which will be light or dark.
            parts = theme.split(" ")
    
            await changeTheme(connection,parts)
            

iterm2.run_forever(main)
@elis

This comment has been minimized.

Copy link

@elis elis commented Jan 29, 2021

I cleaned up your script so it's easier to maintain :)

@Climbatize - Thanks for the cleaned and updated code! 👍

@IsidroMar95

This comment has been minimized.

Copy link

@IsidroMar95 IsidroMar95 commented Apr 17, 2021

Thank you for sharing this gist. I tweaked it to also check if the theme is correct at iTem2 startup. Not the most elegant code as I am not very familiar with Python async
Edit: Make sure the theme is the same as your usual theme (Preferences > Profiles > Colors > Color Presets...). I personally use Solarized Dark and Solarized Light

I cleaned up your script so it's easier to maintain :)

#!/usr/bin/env python3

import asyncio
import iterm2

async def changeTheme(connection,parts):
    theme_dark = "Night Owl"
    theme_light = "Tango Light"
    print(parts)
    
    if "dark" in parts:
        preset = await iterm2.ColorPreset.async_get(connection, theme_dark)
    else:
        preset = await iterm2.ColorPreset.async_get(connection, theme_light)

    # 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 main(connection):

    app = await iterm2.async_get_app(connection)
    window = app.current_window
    initial_theme = await app.async_get_theme()
    await changeTheme(connection,initial_theme)

    async with iterm2.VariableMonitor(connection, iterm2.VariableScopes.APP, "effectiveTheme", None) as mon:
        while True:
            # Block until theme changes
            theme = await mon.async_get()
            # Themes have space-delimited attributes, one of which will be light or dark.
            parts = theme.split(" ")
    
            await changeTheme(connection,parts)
            

iterm2.run_forever(main)

it seems does not work anymore, i got the error message "auto_dark_mode" ended unexpectedly

@sp4c38

This comment has been minimized.

Copy link

@sp4c38 sp4c38 commented Apr 19, 2021

it seems does not work anymore, i got the error message "auto_dark_mode" ended unexpectedly

Just installed the script by following the steps from the README and from @jirikrepl, works for me (iTerm v.3.4.5).

@Climbatize

This comment has been minimized.

Copy link

@Climbatize Climbatize commented Apr 20, 2021

it seems does not work anymore, i got the error message "auto_dark_mode" ended unexpectedly

Still working to me. 😔
Have you changed the themes names to match the own in your own settings? "Night Owl" for example is not a default theme, I added it by myself.

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