From ab15f386e4ddb5ba2d0c7cd80716fa4ad793d05d Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Fri, 14 Aug 2026 17:53:41 +0200 Subject: [PATCH] CAD: type a sketch dimension without clicking the field first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a Wayland session the in-canvas value field never took the keyboard focus, so after drawing a rectangle the first keystrokes went nowhere and the field had to be clicked before a number could be typed. open() already did Show, Raise, SetFocus on both frame and control, SelectAll, and re-asserted all of it in a CallAfter — none of it works here, and no amount of re-asserting would: under Wayland a client cannot focus itself, and mutter ignores gtk_window_present() without an activation token as focus-stealing prevention. The earlier fix recorded in this file (dropping wxFRAME_FLOAT_ON_PARENT, whose GTK _UTILITY_ hint made an xrdp session refuse focus) addressed a different compositor. Stop needing WM focus. The canvas keeps the focus and feeds the field: SketchInlineEditor::feed_key() types into the control directly — Enter commits, Esc cancels, Backspace/Delete edit, digits and '-' '.' ',' are accepted, and anything else is handed back so a stray letter cannot vanish into a numeric field. A m_fresh flag reproduces the SelectAll semantics the field already had, so the first digit replaces the prefill. It returns false when the control genuinely holds the focus, so X11 keeps wx's normal routing and no character is typed twice. The CHAR_HOOK gates on the editor's own is_open(), NOT on inline_busy(). inline_busy is a freeze flag for the sketch tool: cleared on commit, re-set only when the next queued field opens, with a CallAfter between them. Gating on it left a window where the field was on screen and the flag was false — typing worked for a rectangle's Width and not its Height. VERIFIED at the machine on behemoth: typing the first dimension directly, with no click, works. NOT yet confirmed: the Width -> Height handover; the is_open() gate is diagnosed from the handover code, not observed. The hook's SNAPORCA_KEYTRACE=1 switch logs each key with the focused widget if it needs chasing further. Co-Authored-By: Claude Opus 5 (1M context) --- src/slic3r/GUI/DesignCanvas.cpp | 5 ++++ src/slic3r/GUI/DesignCanvas.hpp | 3 ++- src/slic3r/GUI/DesignPanel.cpp | 12 ++++++++++ src/slic3r/GUI/SketchInlineEditor.cpp | 33 +++++++++++++++++++++++++++ src/slic3r/GUI/SketchInlineEditor.hpp | 9 ++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/DesignCanvas.cpp b/src/slic3r/GUI/DesignCanvas.cpp index 8fa0278ecb..579652bad3 100644 --- a/src/slic3r/GUI/DesignCanvas.cpp +++ b/src/slic3r/GUI/DesignCanvas.cpp @@ -1185,6 +1185,11 @@ bool DesignCanvas::inline_busy() const return m_sketch_tool.inline_busy(); } +bool DesignCanvas::feed_inline_key(wxKeyEvent& evt) +{ + return m_inline_editor && m_inline_editor->is_open() && m_inline_editor->feed_key(evt); +} + bool DesignCanvas::live_sketch_has_work() const { return m_sketch_tool.live_sketch_has_work(); diff --git a/src/slic3r/GUI/DesignCanvas.hpp b/src/slic3r/GUI/DesignCanvas.hpp index cc6d1b2184..57508d7dec 100644 --- a/src/slic3r/GUI/DesignCanvas.hpp +++ b/src/slic3r/GUI/DesignCanvas.hpp @@ -217,7 +217,8 @@ public: // taking those over would break two working interactions in order to add a third. void set_on_context_menu(std::function cb); void delete_selected_sketch_entities(); - bool inline_busy() const; // a sketch value field is open (guard keys) + bool inline_busy() const; + bool feed_inline_key(wxKeyEvent& evt); // type into the in-canvas value field without focus // a sketch value field is open (guard keys) bool live_sketch_has_work() const; // the live sketch holds entities a cancel would destroy bool undo_last_sketch_entity(); // Ctrl+Z in a sketch: drop the last entity bool delete_selected_or_last_sketch_entity(); // Delete in a sketch: selected, else last diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 3d7128d560..dcd114aa50 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -3796,6 +3796,18 @@ DesignPanel::DesignPanel(wxWindow* parent) // Delete/Ctrl+Z there must edit the text, not the model. const bool in_text = (dynamic_cast(wxWindow::FindFocus()) != nullptr) || (m_viewport && m_viewport->inline_busy()); + // The in-canvas value field cannot take the keyboard focus on a Wayland session (mutter + // refuses a self-focus request without an activation token), so typing went nowhere and + // the value had to be clicked into first. Feed it from here instead: the canvas keeps + // focus, we hand it the keys, and the behaviour is identical on every compositor. Does + // nothing when the field genuinely does hold focus, so X11 keeps its normal routing. + // Gate on the EDITOR being open, not on inline_busy(). inline_busy is a freeze flag for + // the sketch tool: it is cleared on commit and only re-set when the next queued field + // opens, so across a rectangle's Width -> Height handover there is a window where the + // field is on screen and the flag is false. Typing worked for the first dimension and + // not the second, which is exactly that gap. feed_inline_key() already checks is_open(). + if (m_viewport && m_viewport->feed_inline_key(e)) + return; if (getenv("SNAPORCA_KEYTRACE")) { wxWindow* fw = wxWindow::FindFocus(); diff --git a/src/slic3r/GUI/SketchInlineEditor.cpp b/src/slic3r/GUI/SketchInlineEditor.cpp index ce4cda616d..42c308c506 100644 --- a/src/slic3r/GUI/SketchInlineEditor.cpp +++ b/src/slic3r/GUI/SketchInlineEditor.cpp @@ -111,6 +111,7 @@ void SketchInlineEditor::open(const wxPoint& screen_px, double value, m_ctrl->SetFocus(); m_ctrl->SelectAll(); m_open = true; + m_fresh = true; // the field opens with its value selected: the first digit replaces it // Re-assert on the next tick too: the GL canvas can reclaim focus while it finishes // handling the click/render that opened us, so a single immediate SetFocus may be stolen. m_ctrl->CallAfter([this] { @@ -163,4 +164,36 @@ void SketchInlineEditor::close() m_closing = false; } + +bool SketchInlineEditor::feed_key(wxKeyEvent& evt) +{ + if (!m_open || m_ctrl == nullptr) return false; + // If the field really does hold the focus (X11 sessions, where Raise+SetFocus works), let + // wx route the key normally — forwarding it here as well would type every character twice. + if (wxWindow::FindFocus() == m_ctrl) return false; + + const int key = evt.GetKeyCode(); + if (key == WXK_RETURN || key == WXK_NUMPAD_ENTER) { do_commit(); return true; } + if (key == WXK_ESCAPE) { do_cancel(); return true; } + + wxString v = m_ctrl->GetValue(); + if (key == WXK_BACK) { + if (m_fresh) { v.clear(); m_fresh = false; } + else if (!v.empty()) v.RemoveLast(); + } else if (key == WXK_DELETE) { + v.clear(); m_fresh = false; + } else { + const wxChar c = wxChar(evt.GetUnicodeKey()); + // A numeric field: digits, a sign, and either decimal separator. Everything else is + // left for the canvas — a stray letter must not silently vanish into the field. + const bool numeric = (c >= '0' && c <= '9') || c == '-' || c == '.' || c == ','; + if (!numeric) return false; + if (m_fresh) { v.clear(); m_fresh = false; } + v += c; + } + m_ctrl->ChangeValue(v); // ChangeValue: no EVT_TEXT feedback loop + m_ctrl->SetInsertionPointEnd(); + return true; +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SketchInlineEditor.hpp b/src/slic3r/GUI/SketchInlineEditor.hpp index e3f7670e5e..6e1350a86f 100644 --- a/src/slic3r/GUI/SketchInlineEditor.hpp +++ b/src/slic3r/GUI/SketchInlineEditor.hpp @@ -9,6 +9,7 @@ class wxFrame; class wxTextCtrl; class wxStaticText; class wxPoint; +class wxKeyEvent; namespace Slic3r { namespace GUI { @@ -33,6 +34,13 @@ public: void cancel(); // if open, run the registered cancel (keep-as-drawn) void commit(); // if open, run the registered commit (accept the typed value) bool is_open() const { return m_open; } + // Type into the field WITHOUT owning the keyboard focus. Under Wayland a client cannot + // focus itself: mutter ignores gtk_window_present() without an activation token, so the + // Show/Raise/SetFocus dance in open() is refused and the first keystrokes went nowhere — + // "keyboard focus does not go on the labels and some clicks are wasted to focus them". + // The canvas keeps focus and feeds us instead, which behaves the same on every compositor. + // Returns true if the key was consumed. + bool feed_key(wxKeyEvent& evt); private: void do_commit(); @@ -43,6 +51,7 @@ private: wxStaticText* m_title{nullptr}; std::function m_commit; std::function m_cancel; + bool m_fresh{true}; // next printable key replaces the prefill bool m_open{false}; bool m_closing{false}; };