Skip to content

Commit

Permalink
Add improved support for macOS.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexhuszagh committed Sep 7, 2024
1 parent e3b3fe7 commit 1a4d72e
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
lint-cpp:
strategy:
matrix:
os: [ubuntu-latest, windows-latest] # TODO: Restore macos-latest
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/theme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
theme-cpp:
strategy:
matrix:
os: [ubuntu-latest] # TODO: Restore macos-latest
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down
123 changes: 117 additions & 6 deletions example/breeze_theme.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@

#include <algorithm>
#include <array>
#include <cctype>
#include <cstdio>
#include <cstdint>
#include <filesystem>
#include <iostream>
#include <locale>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>

Expand Down Expand Up @@ -156,6 +159,8 @@ namespace breeze_stylesheets
start = end + 1;
end = value.find(delimiter, start);
}
if (start != end)
result.emplace_back(value.substr(start, end - start));

return result;
}
Expand All @@ -169,6 +174,41 @@ namespace breeze_stylesheets
return (c <= 'Z' && c >= 'A') ? c - ('Z' - 'z') : c;
}

/// @brief Trim from the start (in place).
/// @param s The string to trim.
inline void
_ltrim(::std::string &s)
{
s.erase(s.begin(), ::std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !::std::isspace(ch); }));
}

/// @brief Trim from the end (in place).
/// @param s The string to trim.
inline void
_rtrim(::std::string &s)
{
s.erase(::std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !::std::isspace(ch); }).base(), s.end());
}

/// @brief Trim from both ends (in place).
/// @param s The string to trim.
inline void
_trim(::std::string &s)
{
_rtrim(s);
_ltrim(s);
}

/// @brief Check if a string contains a substring.
/// @param value The full string to search.
/// @param substr The substring to search for.
/// @return If value contains substr.
inline bool
_contains(const ::std::string &value, const ::std::string &substr)
{
return value.find(substr) != ::std::string::npos;
}

#if __APPLE__ || __linux__

/// @brief Determine if the file is an executable.
Expand Down Expand Up @@ -362,7 +402,7 @@ namespace breeze_stylesheets
return ::std::make_tuple(
int32_t(major),
int32_t(minor),
std::stoi(build),
::std::stoi(build),
int32_t(platformId),
int32_t(0),
int32_t(0)
Expand Down Expand Up @@ -408,10 +448,82 @@ namespace breeze_stylesheets
}

#elif __APPLE__
# error "macOS not yet supported."
#elif __linux__

/// @brief Get the current system theme. This requires Windows 10+.
/// @brief Get if the macOS version supports theme detection.
/// @return If the macOS version supports theme detection.
inline bool
_macos_supported_version()
{
// This gives an output similar to:
// ProductName: Mac OS X
// ProductVersion: 10.12.5
// BuildVersion: 16F73
auto [stdout, code] = _run_command("sw_vers r");
if (code != EXIT_SUCCESS)
return false;
auto lines = _split(stdout.value(), '\n');
const ::std::string version_key = "ProductVersion:";
for (auto &line : lines)
{
// find if we have a matching line and get the right value.
if (line.rfind(version_key, 0) != 0)
continue;
auto version = line.substr(version_key.length());
_trim(version);
auto parts = _split(version, '.');
auto major = ::std::stoi(parts.at(0));
auto minor = ::std::stoi(parts.at(1));
auto patch = ::std::stoi(parts.at(2));
if (major < 10)
return false;
if (major >= 11)
return true;
return minor >= 14;
}
return false;
}

/// @brief Get the current system theme.
/// @return The type of the system theme.
inline ::breeze_stylesheets::theme
get_theme()
{
// old macOS versions were always light
if (!_macos_supported_version())
return ::breeze_stylesheets::theme::light;

// we could technically work through the Obj-C API but this is
// a lot of work for a machine I can't test on when we can directly
// get the results anyway via popen. It's way easier, no worries about
// segfaults. there's no good way to capture stderr so we just redirect
// it to stdout, knowing we get stdout on success (dark) and stderr
// otherwise.
auto [stdout, code] = _run_command("defaults read -globalDomain AppleInterfaceStyle 2>&1");

// if we had an error getting our response, leave early.
if (!stdout.has_value())
return ::breeze_stylesheets::theme::unknown;

// had a successful response. normally this only occurs on a dark theme
auto output = stdout.value();
_trim(output);
if (code == EXIT_SUCCESS)
return output == "Dark" ? ::breeze_stylesheets::theme::dark : ::breeze_stylesheets::theme::light;

// if we've had an error, it's generally because the key pair wasn't set.
auto not_exist = _contains(output, "does not exist");
auto any_app = _contains(output, "kCFPreferencesAnyApplication");
auto interface_style = _contains(output, "AppleInterfaceStyle");
if (not_exist && any_app && interface_style)
return ::breeze_stylesheets::theme::light;

// no idea, fall back to unknown
return ::breeze_stylesheets::theme::unknown;
}

#elif __linux__

/// @brief Get the current system theme.
/// @return The type of the system theme.
inline ::breeze_stylesheets::theme
get_theme()
Expand Down Expand Up @@ -439,8 +551,7 @@ namespace breeze_stylesheets

auto value = stdout.value();
::std::transform(value.begin(), value.end(), value.begin(), _to_ascii_lowercase);
auto is_dark = value.find("-dark") != ::std::string::npos;
return is_dark ? ::breeze_stylesheets::theme::dark : ::breeze_stylesheets::theme::light;
return _contains(value, "-dark") ? ::breeze_stylesheets::theme::dark : ::breeze_stylesheets::theme::light;
}

#else
Expand Down
12 changes: 9 additions & 3 deletions example/breeze_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def _initialize_advapi32() -> ctypes.CDLL:
# region macos


def macos_supported_version() -> bool:
def _macos_supported_version() -> bool:
'''Determine if we use a support macOS version.'''

# NOTE: This is typically 10.14.2 or 12.3
Expand All @@ -292,8 +292,14 @@ def macos_supported_version() -> bool:
def _get_theme_macos() -> Theme:
'''Get the current theme, as light or dark, for the system on macOS.'''

# old macOS versions were always light
if not _macos_supported_version():
return Theme.LIGHT

# NOTE: This can segfault on M1 and M2 Macs on Big Sur 11.4+. So, we also
# try reading directly using subprocess.
# try reading directly using subprocess. Specifically, it's documented that
# if dark mode is set, this command returns `Dark`, otherwise it returns
# that the key pair doesn't exist.
try:
command = ['defaults', 'read', '-globalDomain', 'AppleInterfaceStyle']
process = subprocess.run(command, capture_output=True, check=True)
Expand Down Expand Up @@ -538,7 +544,7 @@ def listener(callback: CallbackFn) -> None:
def register_functions() -> tuple[ThemeFn, ListenerFn]:
'''Register our global functions for our themes and listeners.'''

if sys.platform == 'darwin' and macos_supported_version():
if sys.platform == 'darwin':
return (_get_theme_macos, _listener_macos)
if sys.platform == 'win32' and platform.release().isdigit() and int(platform.release()) >= 10:
# Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER.
Expand Down

0 comments on commit 1a4d72e

Please sign in to comment.