diff --git a/qt/src/TabWidget.h b/qt/src/TabWidget.h index fe449e407..a450e987c 100644 --- a/qt/src/TabWidget.h +++ b/qt/src/TabWidget.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -8,10 +10,20 @@ class QDragEnterEvent; class QDropEvent; class QMouseEvent; -// MIME type marking a Ghostty tab tear-off drag. Recognised by tab bars +// MIME type marking a Ghastty tab tear-off drag. Recognised by tab bars // (to cancel the tear-off) and by terminal surfaces (to accept the drag -// so no "forbidden" cursor is shown over a Ghostty window). -inline constexpr char kGhosttyTabMime[] = "application/x-ghostty-tab"; +// so no "forbidden" cursor is shown over a Ghastty window). +inline constexpr char kGhosttyTabMime[] = "application/x-ghastty-tab"; + +// Per-tab data stored in QTabBar::tabData. `base` is the terminal-set +// title (libghostty SET_TITLE); `override` is a manual user-set title +// (libghostty SET_TAB_TITLE). updateTabText shows override when set, +// otherwise base. +struct TabData { + QString base; + QString override_; // `override` is a reserved C++ identifier +}; +Q_DECLARE_METATYPE(TabData) // A QTabBar that tears a tab off into its own window when it is dragged // clear of the bar. QTabBar's built-in movable behaviour still handles @@ -41,6 +53,7 @@ private: int m_pressIndex = -1; // tab under the press, or -1 QPoint m_pressPos; // press point, for the drag hot spot bool m_tearing = false; // a tear-off QDrag is in progress + bool m_dropHandled = false; // a TabBar dropEvent caught our tear-off }; // A QTabWidget wired to the tear-off-aware TabBar. diff --git a/qt/src/Util.cpp b/qt/src/Util.cpp new file mode 100644 index 000000000..01876d817 --- /dev/null +++ b/qt/src/Util.cpp @@ -0,0 +1,43 @@ +#include "Util.h" + +#include +#include + +QString triggerKeyName(const ghostty_input_trigger_s &t) { + switch (t.tag) { + case GHOSTTY_TRIGGER_UNICODE: + if (t.key.unicode) return QString(QChar(t.key.unicode)).toUpper(); + return {}; + case GHOSTTY_TRIGGER_PHYSICAL: { + const ghostty_input_key_e k = t.key.physical; + if (k >= GHOSTTY_KEY_DIGIT_0 && k <= GHOSTTY_KEY_DIGIT_9) + return QChar('0' + (k - GHOSTTY_KEY_DIGIT_0)); + if (k >= GHOSTTY_KEY_A && k <= GHOSTTY_KEY_Z) + return QChar('A' + (k - GHOSTTY_KEY_A)); + if (k == GHOSTTY_KEY_ENTER) return QStringLiteral("Return"); + if (k == GHOSTTY_KEY_SPACE) return QStringLiteral("Space"); + if (k == GHOSTTY_KEY_TAB) return QStringLiteral("Tab"); + return {}; + } + default: + return {}; + } +} + +QString formatTrigger(const ghostty_input_trigger_s &t) { + QString s; + if (t.mods & GHOSTTY_MODS_CTRL) s += QStringLiteral("Ctrl+"); + if (t.mods & GHOSTTY_MODS_ALT) s += QStringLiteral("Alt+"); + if (t.mods & GHOSTTY_MODS_SHIFT) s += QStringLiteral("Shift+"); + if (t.mods & GHOSTTY_MODS_SUPER) s += QStringLiteral("Super+"); + + const QString name = triggerKeyName(t); + if (!name.isEmpty()) { + s += name; + } else if (t.tag == GHOSTTY_TRIGGER_PHYSICAL) { + s += QStringLiteral("•"); // an unmapped physical key + } else { + s += QStringLiteral("…"); // CATCH_ALL etc. + } + return s; +} diff --git a/qt/src/Util.h b/qt/src/Util.h new file mode 100644 index 000000000..08caee115 --- /dev/null +++ b/qt/src/Util.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "ghostty.h" + +// Shared helpers used across the Qt frontend. Kept header-only where +// possible so trivial wrappers stay inlined. + +// bell-features is a packed struct returned by ghostty_config_get as a +// bitfield. Layout is fixed by the libghostty C ABI; do not reorder. +enum BellFeature : unsigned int { + BellSystem = 1u << 0, + BellAudio = 1u << 1, + BellAttention = 1u << 2, + BellTitle = 1u << 3, + BellBorder = 1u << 4, +}; + +// Translate Qt keyboard modifiers into libghostty's modifier bitfield. +inline ghostty_input_mods_e translateMods(Qt::KeyboardModifiers m) { + int r = GHOSTTY_MODS_NONE; + if (m & Qt::ShiftModifier) r |= GHOSTTY_MODS_SHIFT; + if (m & Qt::ControlModifier) r |= GHOSTTY_MODS_CTRL; + if (m & Qt::AltModifier) r |= GHOSTTY_MODS_ALT; + if (m & Qt::MetaModifier) r |= GHOSTTY_MODS_SUPER; + return static_cast(r); +} + +// Render the printable letter/digit/named key portion of a libghostty +// trigger. Returns an empty string if the key is not displayable +// (CATCH_ALL, an unmapped physical key, etc.). +QString triggerKeyName(const ghostty_input_trigger_s &t); + +// Format a libghostty trigger as a human-readable chord (e.g. "Ctrl+B"). +// Used for context-menu shortcut hints and the key-sequence overlay. +// Unmapped physical keys render as "•"; trigger.tag CATCH_ALL renders +// as "…". +QString formatTrigger(const ghostty_input_trigger_s &t); + +// Wrapper around ghostty_config_get that infers the value's length +// from a string literal, so call sites stop repeating qstrlen(). +template +inline bool configGet(ghostty_config_t cfg, T *out, const char (&key)[N]) { + return cfg && ghostty_config_get(cfg, out, key, N - 1); +}