1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//! Switches between Light and Dark [appearances](https://support.apple.com/en-us/HT208976) in macOS.
//!
//! ## Usage
//! There's no configuration required!  `thcon dark` will enable dark mode on macOS, and
//! `thcon light` will disable it, but this behavior can be disabled with `disabled = true`.
//!
//! ## `thcon.toml` Schema
//! Section: `macos`
//!
//! | Key | Type | Description | Default  |
//! | --- | ---- | ----------- | -------- |
//! | `disabled` | boolean | `true` to disable theming of this app, otherwise `false` | `false` |

use crate::config::Config as ThconConfig;
use crate::operation::Operation;
use crate::themeable::{ConfigState, Themeable};
use crate::AppConfig;
use crate::Disableable;

use std::process::Command;

use anyhow::anyhow;
use anyhow::{Context, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize, Disableable, AppConfig)]
pub struct _Config {
    #[serde(default)]
    disabled: bool,
}

pub struct MacOS;

impl Themeable for MacOS {
    fn config_state(&self, config: &ThconConfig) -> ConfigState {
        ConfigState::with_default_config(config.macos.as_ref().map(|c| c.inner.as_ref()))
    }

    fn switch(&self, config: &ThconConfig, operation: &Operation) -> Result<()> {
        match self.config_state(config) {
            ConfigState::NoDefault => unreachable!(),
            ConfigState::Default => (),
            ConfigState::Disabled => return Ok(()),
            ConfigState::Enabled => (),
        };

        let dark_mode = match operation {
            Operation::Lighten => false,
            Operation::Darken => true,
        };

        Command::new("osascript")
            .arg("-e")
            .arg(format!(
                "tell app \"System Events\" to \
                     tell appearance preferences to \
                     set dark mode to {}",
                dark_mode
            ))
            .status()
            .context("Failed to execute 'osascript'")
            .and_then(|status| {
                if status.success() {
                    Ok(())
                } else {
                    Err(anyhow!("Failed to execute 'osascript'"))
                }
            })
    }
}