blob: a1b617de4dbb7b0dbed2ee742a78a6229bf6cd8e [file] [log] [blame] [edit]
// Copyright 2024 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// GEN_BUILD:CONDITION(tint_build_is_linux || tint_build_is_mac)
#include <unistd.h>
#include <termios.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <optional>
#include <string_view>
#include <utility>
#include "src/tint/utils/macros/defer.h"
#include "src/tint/utils/system/env.h"
#include "src/tint/utils/system/terminal.h"
namespace tint {
bool TerminalSupportsColors(FILE* out) {
if (!isatty(fileno(out))) {
return false;
}
if (auto term = GetEnvVar("TERM"); !term.empty()) {
return term == "cygwin" || term == "linux" || term == "rxvt-unicode-256color" ||
term == "rxvt-unicode" || term == "screen-256color" || term == "screen" ||
term == "tmux-256color" || term == "tmux" || term == "xterm-256color" ||
term == "xterm-color" || term == "xterm";
}
return false;
}
/// Probes the terminal using a Device Control escape sequence to get the background color.
/// @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions
std::optional<bool> TerminalIsDark(FILE* out) {
if (!TerminalSupportsColors(out)) {
return std::nullopt;
}
// Get the file descriptor for 'out'
int out_fd = fileno(out);
if (out_fd == -1) {
return std::nullopt;
}
// Store the current attributes for 'out', restore it before returning
termios original_state{};
tcgetattr(out_fd, &original_state);
TINT_DEFER(tcsetattr(out_fd, TCSADRAIN, &original_state));
// Prevent echoing.
termios state = original_state;
state.c_lflag &= ~static_cast<tcflag_t>(ECHO | ICANON);
tcsetattr(out_fd, TCSADRAIN, &state);
// Emit the device control escape sequence to query the terminal colors.
static constexpr std::string_view kQuery = "\x1b]11;?\x07";
fwrite(kQuery.data(), 1, kQuery.length(), out);
fflush(out);
// Timeout for attempting to read the response.
static constexpr auto kTimeout = std::chrono::milliseconds(100);
// Record the start time.
auto start = std::chrono::steady_clock::now();
// Helpers for parsing the response.
std::optional<char> peek;
auto read = [&]() -> std::optional<char> {
if (peek) {
char c = *peek;
peek.reset();
return c;
}
while ((std::chrono::steady_clock::now() - start) < kTimeout) {
char c;
if (fread(&c, 1, 1, stdin) == 1) {
return c;
}
}
return std::nullopt;
};
auto match = [&](std::string_view str) {
for (char c : str) {
if (c != read()) {
return false;
}
}
return true;
};
struct Hex {
uint32_t num = 0; // The parsed hex number
uint32_t len = 0; // Number of hex digits parsed
};
auto hex = [&] {
uint32_t num = 0;
uint32_t len = 0;
while (auto c = read()) {
if (c >= '0' && c <= '9') {
num = num * 16 + static_cast<uint32_t>(*c - '0');
len++;
} else if (c >= 'a' && c <= 'z') {
num = num * 16 + 10 + static_cast<uint32_t>(*c - 'a');
len++;
} else if (c >= 'A' && c <= 'Z') {
num = num * 16 + 10 + static_cast<uint32_t>(*c - 'A');
len++;
} else {
peek = c;
break;
}
}
return Hex{num, len};
};
if (!match("\033]11;rgb:")) {
return std::nullopt;
}
auto r_i = hex();
if (!match("/")) {
return std::nullopt;
}
auto g_i = hex();
if (!match("/")) {
return std::nullopt;
}
auto b_i = hex();
if (r_i.len != g_i.len || g_i.len != b_i.len) {
return std::nullopt;
}
uint32_t max = 0;
switch (r_i.len) {
case 2: // rr/gg/bb
max = 0xff;
break;
case 4: // rrrr/gggg/bbbb
max = 0xffff;
break;
default:
return std::nullopt;
}
// https://en.wikipedia.org/wiki/Relative_luminance
float r = static_cast<float>(r_i.num) / static_cast<float>(max);
float g = static_cast<float>(g_i.num) / static_cast<float>(max);
float b = static_cast<float>(b_i.num) / static_cast<float>(max);
return (0.2126f * r + 0.7152f * g + 0.0722f * b) < 0.5f;
}
} // namespace tint