Skip to content

Instantly share code, notes, and snippets.

@jamesmacfie
Created October 22, 2019 02:53
Show Gist options
  • Save jamesmacfie/2061023e5365e8b6bfbbc20792ac90f8 to your computer and use it in GitHub Desktop.
Save jamesmacfie/2061023e5365e8b6bfbbc20792ac90f8 to your computer and use it in GitHub Desktop.
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)
@Climbatize
Copy link

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
Copy link

elis commented Jan 29, 2021

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

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

@alfredomtzrmz
Copy link

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
Copy link

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
Copy link

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.

@plivox
Copy link

plivox commented Jun 19, 2021

Hello, here is my version if anyone is interested.

Automatic iTerm2 preset switching on MacOS

#!/usr/bin/env python3

import asyncio
import iterm2

THEME_LIGHT = "Tango Light"
THEME_DARK = "Tango Dark"


class AutoSwtichTheme:
    def __init__(self, connection, light="Light Background", dark="Dark Background"):
        self.connection = connection
        self.light = light
        self.dark = dark

    async def get_app(self):
        return await iterm2.async_get_app(self.connection)

    async def get_theme(self) -> str:
        parts = await (await self.get_app()).async_get_theme()
        if len(parts) <= 1:
            return parts[0]
        return ""

    async def set_color_preset(self, theme):
        preset = await iterm2.ColorPreset.async_get(
            self.connection, self.light if theme == "light" else self.dark
        )

        profiles = await iterm2.PartialProfile.async_query(self.connection)
        for partial in profiles:
            await (await partial.async_get_full_profile()).async_set_color_preset(
                preset
            )


async def quit(connection):
    while True:
        if not connection.websocket.open:
            exit(0)
        await asyncio.sleep(1)


async def main(connection):
    asyncio.ensure_future(quit(connection), loop=asyncio.get_event_loop())

    ast = AutoSwtichTheme(connection, THEME_LIGHT, THEME_DARK)
    await ast.set_color_preset(await ast.get_theme())

    async with iterm2.VariableMonitor(
        connection, iterm2.VariableScopes.APP, "effectiveTheme", None
    ) as mon:
        while True:
            # Block until theme changes
            theme = await mon.async_get()

            # Set preset if theme has changed
            await ast.set_color_preset(theme)


try:
    iterm2.run_forever(main)
except:
    print("Unable to connect on iTerm2 application")

@kassi
Copy link

kassi commented Jun 21, 2021

@plivox good rewrite. Just a perfectionists nitpick: name it AutoSwitchTheme (typo)

However, regardless which version I use (they basically all do the same), I have 2 problems (on Catalina).

  1. It only works if I switch the theme inside iTerm's preferences, not the MacOS system preferences. (iTerm 3.4.8)
  2. It's super slow. I did some debugging and while the color preset is applied, iTerm is not responsive at all. Each call to async_set_color_preset takes 2-3 seconds - and I have a couple of profiles (> 40), So when switching theme, the computer is blocked for over 2 minutes.

@plivox
Copy link

plivox commented Jun 21, 2021

@plivox good rewrite. Just a perfectionists nitpick: name it AutoSwitchTheme (typo)

Thanks a lot @kassi, stupid mistake ;) it is corrected in the gist.

However, regardless which version I use (they basically all do the same), I have 2 problems (on Catalina).

  1. It only works if I switch the theme inside iTerm's preferences, not the MacOS system preferences. (iTerm 3.4.8)
  2. It's super slow. I did some debugging and while the color preset is applied, iTerm is not responsive at all. Each call to async_set_color_preset takes 2-3 seconds - and I have a couple of profiles (> 40), So when switching theme, the computer is blocked for over 2 minutes.

@kassi, I use MacOS 11.4 (Big Sur) and iTerm2 Build 3.4.9beta1 with the Python 3.8.6 environment. On my side, no worries about performance. But I don't have an easy way to test my script with your versions.

@kassi
Copy link

kassi commented Jun 21, 2021

@plivox No worries. I could try to update to iTerm beta, but BigSur is not as easy. How many profiles do you have? And how long does the above mentioned call take on your side?
I'm currently investigating whether I can get rid of the amount of profiles, but it's not as easy, either. I'm using them mainly for dev set up, on for each pane with different starting command and badge - since I moved away from tmux, it all went into different profiles, which I then use in arrangements. I would need one profile for each project I'm working on I guess due to some automation I have in place.

@plivox
Copy link

plivox commented Jun 21, 2021

I'm currently investigating whether I can get rid of the amount of profiles, but it's not as easy, either. I'm using them mainly for dev set up, on for each pane with different starting command and badge - since I moved away from tmux, it all went into different profiles, which I then use in arrangements. I would need one profile for each project I'm working on I guess due to some automation I have in place.

@kassi, Maybe that's why I only have two profiles "default" and "asciinema". I will test by adding profiles.

@Olshansk
Copy link

@gnachman Any chance iTerm2 will support this natively similar to vscode? https://apple.stackexchange.com/a/381963/185231

@Kamik423
Copy link

In the past they argued that since this can be easily done with scripting it does not need to be a native feature.

I however disagree and hope for it to become native.

@gnachman
Copy link

The current nightly build has separate light/dark mode color settings.

@madhukar93
Copy link

The current nightly build has separate light/dark mode color settings.

which switches according to system theme?

@Kamik423
Copy link

Where can I find this setting?

@gnachman
Copy link

gnachman commented Aug 3, 2021

Where can I find this setting?

image

@Kamik423
Copy link

Kamik423 commented Aug 4, 2021

So this is in the nightly build but not in the beta or the official release. When will it be available in the (brew) beta?

@raszi
Copy link

raszi commented Aug 24, 2021

It does appear in nightly as the screenshot shows, although it is not switching for me. It keeps using the "Dark Mode".

@raszi
Copy link

raszi commented Aug 25, 2021

Okay, I figured it out. You need to select the "Regular" theme in the Appearance menu to make the switching work.

Screen Shot 2021-08-25 at 15 14 45

@fabianSorn
Copy link

The current nightly build has separate light/dark mode color settings.

Can you already say what version this will be released with? I am really looking forward to have this built in.

@gnachman
Copy link

@fabianSorn Go get the beta and give it a try and report any bugs you find. I'll release it when there are no more significant bugs :)

@Kamik423
Copy link

I have been using it since the day it was released. So far I have had no bugs. Seems rock solid to me, instant response; beautiful.

@doublethefish
Copy link

Thanks for this. It didn't update my iTerm colors immediately, so I've created a version that updates all Sessions and Profiles.

I don't use Profiles much so my version makes all terminal sessions update immediately.

It also has a bit more error handling and documentation.

Hope it helps someone:
https://gist.github.com/doublethefish/c339b659738fdb652e820e3b40f97f36

@fredrikaverpil
Copy link

Does auto-switching between light/dark themes (based on OS setting) still require beta or nightly?

@tshu-w
Copy link

tshu-w commented Jun 17, 2022

Does auto-switching between light/dark themes (based on OS setting) still require beta or nightly?

Not anymore.

@bartekpacia
Copy link

bartekpacia commented Jun 23, 2022

Does auto-switching between light/dark themes (based on OS setting) still require beta or nightly?

Not anymore.

Do you mean that this feature is available on stable? I can't find it.

@tshu-w
Copy link

tshu-w commented Jun 23, 2022

Does auto-switching between light/dark themes (based on OS setting) still require beta or nightly?

Not anymore.

Do you mean that this feature is available on stable? I can't find it.

Yes, I'm using iTerm2 Build 3.4.15 download by homebrew (not iterm-beta cask).

Update:
Sorry I may have misunderstood, snippets is working fine, "separate light/dark mode color settings." is not there yet.

@gnachman
Copy link

No, not on stable; only in beta.

@petrmvala
Copy link

This is great! Does iTerm export some env variable indicating in which mode it is? I'm asking because I use bat for browsing code and want to set syntax highlight based on the current background.

Thanks!

@majutsushi
Copy link

Since the mode is based on the system-wide mode you can use defaults read -g AppleInterfaceStyle to determine if dark mode is enabled. The command will return Dark when in dark mode and nothing when not in dark mode.

@fosemberg
Copy link

fosemberg commented Sep 14, 2022

If you want the script to run itself when opening iterm2, you need to put it in the folder, that is not exist by default:
~/Library/ApplicationSupport/iTerm2/Scripts/AutoLaunch

mkdir -p ~/Library/ApplicationSupport/iTerm2/Scripts/AutoLaunch
mv ~/Library/ApplicationSupport/iTerm2/Scripts/auto_dark_mode.py ~/Library/ApplicationSupport/iTerm2/Scripts/AutoLaunch/auto_dark_mode.py

https://iterm2.com/python-api/tutorial/running.html#auto-run-scripts

@lpolon
Copy link

lpolon commented Dec 4, 2023

Thank you!

@wotori
Copy link

wotori commented Mar 20, 2024

Thank you for sharing. I managed to make it work.

For the first time, I encountered this error:

iterm2.colorpresets.ListPresetsException: PRESET_NOT_FOUND

However, since I had created custom profiles before, I was surprised by this occurrence. I decided to list all the presets:

presets = await iterm2.ColorPreset.async_get_list(connection)
print("Presets: ", [preset for preset in presets])

To my surprise, I discovered that my custom presets were missing. The list of I obtained was:

Presets: ['Dark Background', 'Tango Light', 'Smoooooth', 'Light Background', 'Pastel (Dark Background)', 'Solarized Dark', 'Tango Dark', 'Solarized Light']

Now, I understand that this issue is related to color presets rather than the profile, which allows for hot reloading without restarting the console. This feature is truly cool!

@malaiam
Copy link

malaiam commented Mar 20, 2024

Guys, this is an integrated feature in v3.5 (official beta release currently). So this script is not necessary anymore.

image

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