commit
7b33076bea
|
|
@ -23,7 +23,7 @@ project(ghastty LANGUAGES CXX C)
|
|||
# ./qt/build/ghastty
|
||||
# cmake --install qt/build --prefix ~/.local
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
|
|
|
|||
|
|
@ -317,8 +317,10 @@ void GhosttySurface::paintEvent(QPaintEvent *) {
|
|||
// Unfocused-split dimming: a translucent fill over an inactive pane.
|
||||
// Only split panes (a QSplitter parent) are dimmed, matching GTK.
|
||||
if (!hasFocus() && qobject_cast<QSplitter *>(parentWidget())) {
|
||||
double opacity = 0.7;
|
||||
config::get(&opacity, "unfocused-split-opacity");
|
||||
double opacity = 0.7; // default: 70% opaque
|
||||
// On read failure opacity keeps the default; the success bit
|
||||
// isn't load-bearing.
|
||||
(void)config::get(&opacity, "unfocused-split-opacity");
|
||||
if (opacity < 1.0) {
|
||||
QColor fill(0, 0, 0); // default: dim toward black
|
||||
ghostty_config_color_s c{};
|
||||
|
|
@ -714,35 +716,36 @@ void GhosttySurface::sendKey(QKeyEvent *ev, ghostty_input_action_e action) {
|
|||
// likewise reports the XKB keycode, which is libghostty's Linux native.
|
||||
const uint32_t keycode = ev->nativeScanCode();
|
||||
|
||||
ghostty_input_key_s k = {};
|
||||
k.action = action;
|
||||
k.mods = translateMods(ev->modifiers());
|
||||
// OR in any right-side bit for this keycode (e.g. Right-Shift sets
|
||||
// SHIFT_RIGHT alongside SHIFT) and the live Caps/Num lock state
|
||||
// from XkbTracker. macOS + GTK populate all of these; without
|
||||
// them, keybinds like `right_shift+x` can't distinguish from
|
||||
// `left_shift+x` and the kitty CSI-u encoding loses the lock bits.
|
||||
k.mods = static_cast<ghostty_input_mods_e>(
|
||||
k.mods | XkbState::instance().sideBitsForKeycode(keycode) |
|
||||
const ghostty_input_mods_e mods = static_cast<ghostty_input_mods_e>(
|
||||
translateMods(ev->modifiers()) |
|
||||
XkbState::instance().sideBitsForKeycode(keycode) |
|
||||
XkbState::instance().lockMods());
|
||||
k.keycode = keycode;
|
||||
k.text = printable ? text.constData() : nullptr;
|
||||
// XKB lookups: unshifted codepoint (what this physical key would
|
||||
// produce with no mods, e.g. ';' for the Shift+; → ':' event) and the
|
||||
// modifiers the layout consumed to produce the event's text. Without
|
||||
// consumed_mods, Shift+punctuation is emitted as a kitty CSI sequence
|
||||
// the shell can't decode; with it set, libghostty's encoder falls
|
||||
// back to plain text correctly.
|
||||
k.unshifted_codepoint = XkbState::instance().unshiftedCodepoint(keycode);
|
||||
// consumed_mods is computed for every event, not just printable ones.
|
||||
// Function/keypad/Backspace/arrows can also have layout-consumed
|
||||
// modifiers (e.g. Caps Lock affecting case for letter keys, Mode_Switch
|
||||
// for layout shifts on Backspace) that the kitty encoder needs to
|
||||
// strip. macOS + GTK both compute it unconditionally; gating on
|
||||
// printable lost that info on non-text keys.
|
||||
k.consumed_mods = XkbState::instance().consumedMods(keycode, k.mods);
|
||||
k.composing = false;
|
||||
|
||||
// XKB lookups:
|
||||
// unshifted_codepoint — what this physical key would produce with
|
||||
// no mods (e.g. ';' for the Shift+; → ':' event). Without it
|
||||
// libghostty's kitty encoder mis-handles punctuation release
|
||||
// events.
|
||||
// consumed_mods — modifiers the layout consumed to produce the
|
||||
// event's text. Computed for every event, not just printable
|
||||
// ones: function / keypad / Backspace / arrows can have layout-
|
||||
// consumed mods (Caps Lock for letter case, Mode_Switch for
|
||||
// layout shifts on Backspace) the encoder needs to strip. macOS
|
||||
// + GTK both compute it unconditionally.
|
||||
const ghostty_input_key_s k{
|
||||
.action = action,
|
||||
.mods = mods,
|
||||
.consumed_mods = XkbState::instance().consumedMods(keycode, mods),
|
||||
.keycode = keycode,
|
||||
.text = printable ? text.constData() : nullptr,
|
||||
.unshifted_codepoint = XkbState::instance().unshiftedCodepoint(keycode),
|
||||
.composing = false,
|
||||
};
|
||||
ghostty_surface_key(m_surface, k);
|
||||
}
|
||||
|
||||
|
|
@ -1097,14 +1100,15 @@ void GhosttySurface::focusOutEvent(QFocusEvent *) {
|
|||
void GhosttySurface::commitText(const QString &text) {
|
||||
if (!m_surface || text.isEmpty()) return;
|
||||
const QByteArray utf8 = text.toUtf8();
|
||||
ghostty_input_key_s k = {};
|
||||
k.action = GHOSTTY_ACTION_PRESS;
|
||||
k.mods = GHOSTTY_MODS_NONE;
|
||||
k.consumed_mods = GHOSTTY_MODS_NONE;
|
||||
k.keycode = 0;
|
||||
k.text = utf8.constData();
|
||||
k.unshifted_codepoint = 0;
|
||||
k.composing = false;
|
||||
const ghostty_input_key_s k{
|
||||
.action = GHOSTTY_ACTION_PRESS,
|
||||
.mods = GHOSTTY_MODS_NONE,
|
||||
.consumed_mods = GHOSTTY_MODS_NONE,
|
||||
.keycode = 0,
|
||||
.text = utf8.constData(),
|
||||
.unshifted_codepoint = 0,
|
||||
.composing = false,
|
||||
};
|
||||
ghostty_surface_key(m_surface, k);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,8 @@ MainWindow *MainWindow::newWindow(ghostty_surface_t parent) {
|
|||
if (!s_initialWindowConsumed) {
|
||||
s_initialWindowConsumed = true;
|
||||
bool initialWindow = true;
|
||||
config::get(&initialWindow, "initial-window");
|
||||
// Default-on; the success bit isn't load-bearing.
|
||||
(void)config::get(&initialWindow, "initial-window");
|
||||
wantsShow = initialWindow;
|
||||
}
|
||||
if (wantsShow) w->show();
|
||||
|
|
@ -1068,7 +1069,8 @@ void MainWindow::refreshChrome() {
|
|||
// runtime; mirrors the calculation in initialize().
|
||||
if (GhosttyApp::instance().config()) {
|
||||
bool quitAfter = true;
|
||||
config::get(&quitAfter, "quit-after-last-window-closed");
|
||||
// Default-on; the success bit isn't load-bearing.
|
||||
(void)config::get(&quitAfter, "quit-after-last-window-closed");
|
||||
// Same Duration-decode workaround as initialize().
|
||||
const uint64_t delayNs =
|
||||
config::durationNs("quit-after-last-window-closed-delay", 0);
|
||||
|
|
@ -1370,9 +1372,10 @@ void MainWindow::toggleCommandPalette(GhosttySurface *surface) {
|
|||
void MainWindow::applyBlur() {
|
||||
// background-blur is a union whose C value is an i16: 0 (and the
|
||||
// macOS-only negatives) means off, a positive radius means on. KWin
|
||||
// uses its own configured radius, so only on/off matters here.
|
||||
// uses its own configured radius, so only on/off matters here. On
|
||||
// read failure blur stays 0 (off).
|
||||
short blur = 0;
|
||||
config::get(&blur, "background-blur");
|
||||
(void)config::get(&blur, "background-blur");
|
||||
applyWindowBlur(this, blur > 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,17 +20,17 @@ namespace config {
|
|||
// The live ghostty_config_t. Returns nullptr before the singleton has
|
||||
// finished ensureInitialized — callers that read config during early
|
||||
// startup (before the first MainWindow::initialize) must check.
|
||||
ghostty_config_t handle();
|
||||
[[nodiscard]] ghostty_config_t handle();
|
||||
|
||||
// Read a string-valued config key (or an enum, which libghostty
|
||||
// returns as its tag-name string). Empty if absent or the call
|
||||
// fails.
|
||||
QString string(const char *key);
|
||||
[[nodiscard]] QString string(const char *key);
|
||||
|
||||
// Read a bool-valued config key. Returns `fallback` when the key is
|
||||
// absent or the call fails. Note: libghostty's bool config keys are
|
||||
// strict bools, NOT packed bitfields — see bitfield<>() for those.
|
||||
bool boolean(const char *key, bool fallback);
|
||||
[[nodiscard]] bool boolean(const char *key, bool fallback);
|
||||
|
||||
// Parse a duration config key as nanoseconds via the on-disk
|
||||
// fallback. Use this for `?Duration` (optional) keys: c_get.zig
|
||||
|
|
@ -41,18 +41,18 @@ bool boolean(const char *key, bool fallback);
|
|||
// with `unsigned long long` and a manual ms→ns multiplication, NOT
|
||||
// this wrapper, to avoid a redundant disk re-scan on every read.
|
||||
// Returns `fallbackNs` on parse failure or absent key.
|
||||
uint64_t durationNs(const char *key, uint64_t fallbackNs);
|
||||
[[nodiscard]] uint64_t durationNs(const char *key, uint64_t fallbackNs);
|
||||
|
||||
// Scan the user's primary on-disk config file for `key = value`
|
||||
// directly. Used for keys ghostty_config_get can't decode (Duration,
|
||||
// repeating paths). Returns the last matching value, or empty.
|
||||
QString diskValue(const char *key);
|
||||
[[nodiscard]] QString diskValue(const char *key);
|
||||
|
||||
// True if the live config has any custom-shader entry. Drives
|
||||
// GhosttySurface's premultiply pass — `custom-shader` is a
|
||||
// repeatable path that ghostty_config_get can't expose, so we scan
|
||||
// the on-disk config text directly.
|
||||
bool hasCustomShader();
|
||||
[[nodiscard]] bool hasCustomShader();
|
||||
|
||||
// Read a packed-bitfield config key. libghostty serializes packed
|
||||
// structs as a c_uint via c_get.zig (`ptr.* = @intCast(@as(Backing,
|
||||
|
|
@ -61,14 +61,14 @@ bool hasCustomShader();
|
|||
// for a one-field packed struct) under-sizes the write and corrupts
|
||||
// adjacent stack — always go through this. Returns `fallbackBits`
|
||||
// when ghostty_config_get fails.
|
||||
unsigned int bitfield(const char *key, unsigned int fallbackBits);
|
||||
[[nodiscard]] unsigned int bitfield(const char *key, unsigned int fallbackBits);
|
||||
|
||||
// Read a path-valued disk config and expand a leading `~/` to the
|
||||
// user's home directory. Returns empty when the key is absent.
|
||||
// Path-valued keys are read off-disk (libghostty doesn't surface
|
||||
// them through ghostty_config_get) so this is just diskValue() with
|
||||
// a tilde-expansion pass.
|
||||
QString expandedPath(const char *key);
|
||||
[[nodiscard]] QString expandedPath(const char *key);
|
||||
|
||||
// Wrapper around ghostty_config_get that infers the value's length
|
||||
// from a string literal so call sites stop repeating qstrlen(). The
|
||||
|
|
@ -79,9 +79,11 @@ QString expandedPath(const char *key);
|
|||
// `out` must point to the type ghostty.h documents for the key
|
||||
// (bool* for bool keys, ghostty_config_color_s* for colors, etc.).
|
||||
// Returns false when the key is absent or the underlying call
|
||||
// fails.
|
||||
// fails. The success bit MUST be checked — `out` is left untouched
|
||||
// on failure, so dropping the return masks an unread / unwritten
|
||||
// access.
|
||||
template <typename T, size_t N>
|
||||
inline bool get(T *out, const char (&key)[N]) {
|
||||
[[nodiscard]] inline bool get(T *out, const char (&key)[N]) {
|
||||
static_assert(N > 1, "config::get requires a non-empty key literal");
|
||||
ghostty_config_t cfg = handle();
|
||||
return cfg && ghostty_config_get(cfg, out, key, N - 1);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ constexpr const char *kAnimProperty = "ghastty.quickterm.anim";
|
|||
// and a very large value doesn't lock the user out.
|
||||
int animationMs() {
|
||||
double secs = 0.2; // matches Config.zig default
|
||||
config::get(&secs, "quick-terminal-animation-duration");
|
||||
// On read failure secs keeps the default; the success bit isn't
|
||||
// load-bearing.
|
||||
(void)config::get(&secs, "quick-terminal-animation-duration");
|
||||
if (secs <= 0.0) return 0;
|
||||
return std::clamp(static_cast<int>(secs * 1000.0), 1, 1000);
|
||||
}
|
||||
|
|
@ -117,8 +119,10 @@ void setupLayerShell(QWidget *window) {
|
|||
const QSize scr = screen ? screen->size() : QSize(1920, 1080);
|
||||
|
||||
// quick-terminal-size: primary is the edge-perpendicular extent.
|
||||
// On read failure qsz stays zero-initialized and toPx falls back to
|
||||
// its `fallback` argument; the success bit isn't load-bearing.
|
||||
ghostty_config_quick_terminal_size_s qsz = {};
|
||||
config::get(&qsz, "quick-terminal-size");
|
||||
(void)config::get(&qsz, "quick-terminal-size");
|
||||
const auto toPx = [](const ghostty_quick_terminal_size_s &s, int dim,
|
||||
int fallback) -> int {
|
||||
switch (s.tag) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue