/* * Copyright © 2008, 2009, 2010, 2018, 2019, 2020, 2021 Christian Persch * Copyright © 2001-2004,2009,2010 Red Hat, Inc. * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include "config.h" #include "widget.hh" #include // for W_EXITCODE #include #include #include #include #include "vtegtk.hh" #include "vteptyinternal.hh" #include "debug.h" #include "gobject-glue.hh" #if VTE_GTK == 3 #define VTE_STYLE_CLASS_MONOSPACE GTK_STYLE_CLASS_MONOSPACE #elif VTE_GTK == 4 #define VTE_STYLE_CLASS_MONOSPACE "monospace" #endif using namespace std::literals; namespace vte { namespace platform { static void vadjustment_value_changed_cb(vte::platform::Widget* that) noexcept; static void im_commit_cb(GtkIMContext* im_context, char const* text, Widget* that) noexcept try { if (text == nullptr) return; that->terminal()->im_commit(text); } catch (...) { vte::log_exception(); } static void im_preedit_start_cb(GtkIMContext* im_context, Widget* that) noexcept try { _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit started.\n"); that->terminal()->im_preedit_set_active(true); } catch (...) { vte::log_exception(); } static void im_preedit_end_cb(GtkIMContext* im_context, Widget* that) noexcept try { _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit ended.\n"); that->terminal()->im_preedit_set_active(false); } catch (...) { vte::log_exception(); } static void im_preedit_changed_cb(GtkIMContext* im_context, Widget* that) noexcept try { that->im_preedit_changed(); } catch (...) { vte::log_exception(); } static gboolean im_retrieve_surrounding_cb(GtkIMContext* im_context, Widget* that) noexcept try { _vte_debug_print(VTE_DEBUG_EVENTS, "Input method retrieve-surrounding.\n"); return that->terminal()->im_retrieve_surrounding(); } catch (...) { vte::log_exception(); return false; } static gboolean im_delete_surrounding_cb(GtkIMContext* im_context, int offset, int n_chars, Widget* that) noexcept try { _vte_debug_print(VTE_DEBUG_EVENTS, "Input method delete-surrounding offset %d n-chars %d.\n", offset, n_chars); return that->terminal()->im_delete_surrounding(offset, n_chars); } catch (...) { vte::log_exception(); return false; } static void settings_notify_cb(GtkSettings* settings, GParamSpec* pspec, vte::platform::Widget* that) noexcept try { that->settings_changed(); } catch (...) { vte::log_exception(); } #if VTE_GTK == 4 /* Callbacks for event controllers */ static gboolean key_pressed_cb(GtkEventControllerKey* controller, unsigned key, unsigned keycode, unsigned modifiers, Widget* that) noexcept try { return that->event_key_pressed(controller, key, keycode, modifiers); } catch (...) { vte::log_exception(); return false; } static void key_released_cb(GtkEventControllerKey* controller, unsigned key, unsigned keycode, unsigned modifiers, Widget* that) noexcept try { that->event_key_released(controller, key, keycode, modifiers); } catch (...) { vte::log_exception(); } static gboolean key_modifiers_cb(GtkEventControllerKey* controller, unsigned modifiers, Widget* that) noexcept try { return that->event_key_modifiers(controller, modifiers); } catch (...) { vte::log_exception(); return false; } static void focus_enter_cb(GtkEventControllerFocus* controller, Widget* that) noexcept try { that->event_focus_enter(controller); } catch (...) { vte::log_exception(); } static void focus_leave_cb(GtkEventControllerFocus* controller, Widget* that) noexcept try { that->event_focus_leave(controller); } catch (...) { vte::log_exception(); } static void motion_enter_cb(GtkEventControllerMotion* controller, double x, double y, Widget* that) noexcept try { that->event_motion_enter(controller, x, y); } catch (...) { vte::log_exception(); } static void motion_leave_cb(GtkEventControllerMotion* controller, Widget* that) noexcept try { that->event_motion_leave(controller); } catch (...) { vte::log_exception(); } static void motion_motion_cb(GtkEventControllerMotion* controller, double x, double y, Widget* that) noexcept try { that->event_motion(controller, x, y); } catch (...) { vte::log_exception(); } static void motion_notify_is_pointer_cb(GtkEventControllerMotion* controller, GParamSpec* pspec, Widget* that) noexcept try { that->event_motion_notify_is_pointer(controller); } catch (...) { vte::log_exception(); } static void motion_notify_contains_pointer_cb(GtkEventControllerMotion* controller, GParamSpec* pspec, Widget* that) noexcept try { that->event_motion_notify_contains_pointer(controller); } catch (...) { vte::log_exception(); } static void scroll_begin_cb(GtkEventControllerScroll* controller, Widget* that) noexcept try { that->event_scroll_begin(controller); } catch (...) { vte::log_exception(); } static gboolean scroll_scroll_cb(GtkEventControllerScroll* controller, double dx, double dy, Widget* that) noexcept try { return that->event_scroll(controller, dx, dy); } catch (...) { vte::log_exception(); return false; } static void scroll_end_cb(GtkEventControllerScroll* controller, Widget* that) noexcept try { that->event_scroll_end(controller); } catch (...) { vte::log_exception(); } static void scroll_decelerate_cb(GtkEventControllerScroll* controller, double vx, double vy, Widget* that) noexcept try { that->event_scroll_decelerate(controller, vx, vy); } catch (...) { vte::log_exception(); } static void click_pressed_cb(GtkGestureClick* gesture, int press_count, double x, double y, Widget* that) noexcept try { that->gesture_click_pressed(gesture, press_count, x, y); } catch (...) { vte::log_exception(); } static void click_released_cb(GtkGestureClick* gesture, int press_count, double x, double y, Widget* that) noexcept try { that->gesture_click_released(gesture, press_count, x, y); } catch (...) { vte::log_exception(); } static void click_stopped_cb(GtkGestureClick* gesture, Widget* that) noexcept try { that->gesture_click_stopped(gesture); } catch (...) { vte::log_exception(); } static void click_unpaired_release_cb(GtkGestureClick* gesture, double x, double y, unsigned button, GdkEventSequence* sequence, Widget* that) noexcept try { that->gesture_click_unpaired_release(gesture, x, y, button, sequence); } catch (...) { vte::log_exception(); } #endif /* VTE_GTK == 4 */ Widget::Widget(VteTerminal* t) : m_widget{&t->widget} { // Create a default adjustment set_vadjustment({}); #if VTE_GTK == 3 gtk_widget_set_can_focus(gtk(), true); #endif #if VTE_GTK == 4 gtk_widget_set_focusable(gtk(), true); #endif #if VTE_GTK == 3 /* We do our own redrawing. */ // FIXMEchpe is this still necessary? gtk_widget_set_redraw_on_allocate(gtk(), false); #endif /* Until Terminal init is completely fixed, use zero'd memory */ auto place = g_malloc0(sizeof(vte::terminal::Terminal)); m_terminal = new (place) vte::terminal::Terminal(this, t); } Widget::~Widget() noexcept try { g_signal_handlers_disconnect_matched(m_settings.get(), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this); if (m_vadjustment) { g_signal_handlers_disconnect_by_func(m_vadjustment.get(), (void*)vadjustment_value_changed_cb, this); } m_widget = nullptr; m_terminal->~Terminal(); g_free(m_terminal); } catch (...) { vte::log_exception(); } void Widget::beep() noexcept { if (realized()) gtk_widget_error_bell(gtk()); } #if VTE_GTK == 4 bool Widget::contains(double x, double y) { return false; } #endif /* VTE_GTK == 4 */ vte::glib::RefPtr Widget::create_cursor(std::string const& name) const noexcept { #if VTE_GTK == 3 return vte::glib::take_ref(gdk_cursor_new_from_name(gtk_widget_get_display(m_widget), name.c_str())); #elif VTE_GTK == 4 return vte::glib::take_ref(gdk_cursor_new_from_name(name.c_str(), nullptr /* fallback */)); #endif } void Widget::set_cursor(GdkCursor* cursor) noexcept { #if VTE_GTK == 3 gdk_window_set_cursor(m_event_window, cursor); #elif VTE_GTK == 4 gtk_widget_set_cursor(gtk(), cursor); #endif } void Widget::set_cursor(Cursor const& cursor) noexcept { if (!realized()) return; GdkCursor* gdk_cursor{nullptr}; switch (cursor.index()) { case 0: #if VTE_GTK == 3 gdk_cursor = gdk_cursor_new_from_name(gtk_widget_get_display(gtk()), std::get<0>(cursor).c_str()); #elif VTE_GTK == 4 gdk_cursor = gdk_cursor_new_from_name(std::get<0>(cursor).c_str(), nullptr /* fallback */); #endif /* VTE_GTK */ break; case 1: gdk_cursor = std::get<1>(cursor).get(); if (gdk_cursor != nullptr #if VTE_GTK == 3 && gdk_cursor_get_display(gdk_cursor) == gtk_widget_get_display(gtk()) #endif ) { g_object_ref(gdk_cursor); } else { gdk_cursor = nullptr; } break; #if VTE_GTK == 3 case 2: gdk_cursor = gdk_cursor_new_for_display(gtk_widget_get_display(gtk()), std::get<2>(cursor)); break; #endif } set_cursor(gdk_cursor); if (gdk_cursor) g_object_unref(gdk_cursor); } Clipboard& Widget::clipboard_get(ClipboardType type) const { switch (type) { case ClipboardType::CLIPBOARD: return *m_clipboard; case ClipboardType::PRIMARY: return *m_primary_clipboard; default: g_assert_not_reached(); throw std::runtime_error{""}; break; } } std::optional Widget::clipboard_data_get_cb(Clipboard const& clipboard, ClipboardFormat format) { return terminal()->widget_clipboard_data_get(clipboard, format); } void Widget::clipboard_data_clear_cb(Clipboard const& clipboard) { terminal()->widget_clipboard_data_clear(clipboard); } void Widget::clipboard_request_received_cb(Clipboard const& clipboard, std::string_view const& text) { terminal()->widget_paste(text); } void Widget::clipboard_request_failed_cb(Clipboard const& clipboard) { gtk_widget_error_bell(gtk()); } void Widget::clipboard_offer_data(ClipboardType type, ClipboardFormat format) noexcept { try { clipboard_get(type).offer_data(format, &Widget::clipboard_data_get_cb, &Widget::clipboard_data_clear_cb); } catch (...) { /* Let the caller know the request failed */ terminal()->widget_clipboard_data_clear(clipboard_get(type)); } } void Widget::clipboard_request_text(ClipboardType type) noexcept { try { clipboard_get(type).request_text(&Widget::clipboard_request_received_cb, &Widget::clipboard_request_failed_cb); } catch (...) { /* Let the caller know the request failed */ clipboard_request_failed_cb(clipboard_get(type)); } } void Widget::clipboard_set_text(ClipboardType type, std::string_view const& str) noexcept { clipboard_get(type).set_text(str); } #if VTE_GTK == 4 std::pair Widget::compute_expand() { return {true, true}; } #endif /* VTE_GTK == 4 */ void Widget::constructed() noexcept { #if VTE_GTK == 3 auto context = gtk_widget_get_style_context(m_widget); gtk_style_context_add_class (context, VTE_STYLE_CLASS_MONOSPACE); #elif VTE_GTK == 4 gtk_widget_add_css_class(gtk(), VTE_STYLE_CLASS_MONOSPACE); #endif /* VTE_GTK */ #if VTE_GTK == 4 connect_settings(); /* Add event controllers */ auto controller = vte::glib::take_ref(gtk_event_controller_key_new()); g_signal_connect(controller.get(), "key-pressed", G_CALLBACK(key_pressed_cb), this); g_signal_connect(controller.get(), "key-released", G_CALLBACK(key_released_cb), this); g_signal_connect(controller.get(), "modifiers", G_CALLBACK(key_modifiers_cb), this); gtk_event_controller_set_name(controller.get(), "vte-key-controller"); gtk_widget_add_controller(m_widget, controller.release()); controller = vte::glib::take_ref(gtk_event_controller_focus_new()); g_signal_connect(controller.get(), "enter", G_CALLBACK(focus_enter_cb), this); g_signal_connect(controller.get(), "leave", G_CALLBACK(focus_leave_cb), this); gtk_event_controller_set_name(controller.get(), "vte-focus-controller"); gtk_widget_add_controller(m_widget, controller.release()); controller = vte::glib::take_ref(gtk_event_controller_motion_new()); g_signal_connect(controller.get(), "enter", G_CALLBACK(motion_enter_cb), this); g_signal_connect(controller.get(), "leave", G_CALLBACK(motion_leave_cb), this); g_signal_connect(controller.get(), "motion", G_CALLBACK(motion_motion_cb), this); g_signal_connect(controller.get(), "notify::is-pointer", G_CALLBACK(motion_notify_is_pointer_cb), this); g_signal_connect(controller.get(), "notify::contains-pointer", G_CALLBACK(motion_notify_contains_pointer_cb), this); gtk_event_controller_set_name(controller.get(), "vte-motion-controller"); gtk_widget_add_controller(m_widget, controller.release()); auto const scroll_flags = GtkEventControllerScrollFlags(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL); controller = vte::glib::take_ref(gtk_event_controller_scroll_new(scroll_flags)); g_signal_connect(controller.get(), "scroll-begin", G_CALLBACK(scroll_begin_cb), this); g_signal_connect(controller.get(), "scroll-end", G_CALLBACK(scroll_end_cb), this); g_signal_connect(controller.get(), "scroll", G_CALLBACK(scroll_scroll_cb), this); g_signal_connect(controller.get(), "decelerate", G_CALLBACK(scroll_decelerate_cb), this); gtk_event_controller_set_name(controller.get(), "vte-scroll-controller"); gtk_widget_add_controller(m_widget, controller.release()); auto gesture = vte::glib::take_ref(gtk_gesture_click_new()); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture.get()), 0 /* any button */); gtk_gesture_single_set_exclusive(GTK_GESTURE_SINGLE(gesture.get()), true); g_signal_connect(gesture.get(), "pressed", G_CALLBACK(click_pressed_cb), this); g_signal_connect(gesture.get(), "released", G_CALLBACK(click_released_cb), this); g_signal_connect(gesture.get(), "stopped", G_CALLBACK(click_stopped_cb), this); g_signal_connect(gesture.get(), "unpaired-release", G_CALLBACK(click_unpaired_release_cb), this); gtk_widget_add_controller(m_widget, GTK_EVENT_CONTROLLER(gesture.release())); #endif /* VTE_GTK == 4 */ #if VTE_GTK == 3 /* Set the style as early as possible, before GTK+ starts * invoking various callbacks. This is needed in order to * compute the initial geometry correctly in presence of * non-default padding, see bug 787710. */ style_updated(); #elif VTE_GTK == 4 padding_changed(); #endif /* VTE_GTK */ } #if VTE_GTK == 4 void Widget::css_changed(GtkCssStyleChange* change) { /* This function is inefficient, since there's no public API * for GtkCssStyleChange to see what exactly changed, and if * we do need to queue the resize for it or not. */ auto need_resize = padding_changed(); if (need_resize) gtk_widget_queue_resize(gtk()); } #endif /* VTE_GTK == 4 */ void Widget::direction_changed(GtkTextDirection old_direction) noexcept { // FIXME: does this need to feed to BiDi somehow? } void Widget::dispose() noexcept { #ifdef WITH_A11Y m_terminal->set_accessible(nullptr); #endif if (m_terminal->terminate_child()) { int status = W_EXITCODE(0, SIGKILL); emit_child_exited(status); } } void Widget::emit_child_exited(int status) noexcept { _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `child-exited'.\n"); g_signal_emit(object(), signals[SIGNAL_CHILD_EXITED], 0, status); } void Widget::emit_eof() noexcept { _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `eof'.\n"); g_signal_emit(object(), signals[SIGNAL_EOF], 0); } bool Widget::im_filter_keypress(KeyEvent const& event) noexcept { // FIXMEchpe this can only be called when realized, so the m_im_context check is redundant return m_im_context && gtk_im_context_filter_keypress(m_im_context.get(), #if VTE_GTK == 3 reinterpret_cast(event.platform_event()) #elif VTE_GTK == 4 event.platform_event() #endif ); } #if VTE_GTK == 3 void Widget::event_focus_in(GdkEventFocus *event) { _vte_debug_print(VTE_DEBUG_EVENTS, "Focus In"); m_terminal->widget_focus_in(); } void Widget::event_focus_out(GdkEventFocus *event) { _vte_debug_print(VTE_DEBUG_EVENTS, "Focus Out"); m_terminal->widget_focus_out(); } bool Widget::event_key_press(GdkEventKey *event) { auto key_event = key_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Key press key=%x keycode=%x modifiers=%x\n", key_event.keyval(), key_event.keycode(), key_event.modifiers()); return m_terminal->widget_key_press(key_event); } bool Widget::event_key_release(GdkEventKey *event) { auto key_event = key_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Key release key=%x keycode=%x modifiers=%x\n", key_event.keyval(), key_event.keycode(), key_event.modifiers()); return m_terminal->widget_key_release(key_event); } bool Widget::event_button_press(GdkEventButton *event) { auto mouse_event = mouse_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Click press button=%d press_count=%d x=%.3f y=%.3f\n", mouse_event.button_value(), mouse_event.press_count(), mouse_event.x(), mouse_event.y()); return m_terminal->widget_mouse_press(mouse_event); } bool Widget::event_button_release(GdkEventButton *event) { auto mouse_event = mouse_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Click release button=%d x=%.3f y=%.3f\n", mouse_event.button_value(), mouse_event.x(), mouse_event.y()); return m_terminal->widget_mouse_release(mouse_event); } void Widget::event_enter(GdkEventCrossing *event) { auto mouse_event = mouse_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Motion enter x=%.3f y=%.3f\n", mouse_event.x(), mouse_event.y()); m_terminal->widget_mouse_enter(mouse_event); } void Widget::event_leave(GdkEventCrossing *event) { auto mouse_event = mouse_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Motion leave x=%.3f y=%.3f\n", mouse_event.x(), mouse_event.y()); m_terminal->widget_mouse_leave(mouse_event); } bool Widget::event_scroll(GdkEventScroll *event) { auto scroll_event = scroll_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll delta_x=%.3f delta_y=%.3f\n", scroll_event.dx(), scroll_event.dy()); return m_terminal->widget_mouse_scroll(scroll_event); } bool Widget::event_motion_notify(GdkEventMotion *event) { auto mouse_event = mouse_event_from_gdk(reinterpret_cast(event)); _vte_debug_print(VTE_DEBUG_EVENTS, "Motion x=%.3f y=%.3f\n", mouse_event.x(), mouse_event.y()); return m_terminal->widget_mouse_motion(mouse_event); } #endif /* VTE_GTK == 3 */ #if VTE_GTK == 4 bool Widget::event_key_pressed(GtkEventControllerKey* controller, unsigned key, unsigned keycode, unsigned modifiers) { _vte_debug_print(VTE_DEBUG_EVENTS, "Key press key=%x keycode=%x modifiers=%x\n", key, keycode, modifiers); auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return false; return terminal()->widget_key_press(key_event_from_gdk(event)); } void Widget::event_key_released(GtkEventControllerKey* controller, unsigned key, unsigned keycode, unsigned modifiers) { _vte_debug_print(VTE_DEBUG_EVENTS, "Key release key=%x keycode=%x modifiers=%x\n", key, keycode, modifiers); auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return; terminal()->widget_key_release(key_event_from_gdk(event)); } bool Widget::event_key_modifiers(GtkEventControllerKey* controller, unsigned modifiers) { _vte_debug_print(VTE_DEBUG_EVENTS, "Key modifiers=%x\n", modifiers); return terminal()->widget_key_modifiers(modifiers); } void Widget::event_focus_enter(GtkEventControllerFocus* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Focus In"); terminal()->widget_focus_in(); } void Widget::event_focus_leave(GtkEventControllerFocus* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Focus Out"); terminal()->widget_focus_out(); } void Widget::event_motion_enter(GtkEventControllerMotion* controller, double x, double y) { _vte_debug_print(VTE_DEBUG_EVENTS, "Motion enter x=%.3f y=%.3f\n", x, y); #if 0 // FIXMEgtk4 this always returns nullptr, so how do we get the modifiers? auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return; #endif terminal()->widget_mouse_enter({EventBase::Type::eMOUSE_MOTION, 1, // press count, 0, // gdk_event_get_modifier_state(event), MouseEvent::Button::eNONE, x, y}); } void Widget::event_motion_leave(GtkEventControllerMotion* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Motion leave\n"); #if 0 // FIXMEgtk4 this always returns nullptr, so how do we get the modifiers? auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return; #endif // FIXMEgtk4 how to get the coordinates here? GtkEventControllerMotion::update_pointer_focus // has them, but the signal doesn't carry them. File a gtk bug? terminal()->widget_mouse_leave({EventBase::Type::eMOUSE_MOTION, 1, // press count, 0, // gdk_event_get_modifier_state(event), MouseEvent::Button::eNONE, -1, -1 /* FIXMEgtk4 bogus!!! */}); } void Widget::event_motion(GtkEventControllerMotion* controller, double x, double y) { _vte_debug_print(VTE_DEBUG_EVENTS, "Motion x=%.3f y=%.3f\n", x, y); auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return; // FIXMEgtk4 could this also be a touch event?? terminal()->widget_mouse_motion({EventBase::Type::eMOUSE_MOTION, 1, // press count gdk_event_get_modifier_state(event), MouseEvent::Button::eNONE, x, y}); } void Widget::event_motion_notify_is_pointer(GtkEventControllerMotion* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Motion is-pointer now %s\n", _vte_debug_tf(gtk_event_controller_motion_is_pointer(controller))); // FIXMEgtk4 } void Widget::event_motion_notify_contains_pointer(GtkEventControllerMotion* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Motion contains-pointer now %s\n", _vte_debug_tf(gtk_event_controller_motion_contains_pointer(controller))); // FIXMEgtk4 } void Widget::event_scroll_begin(GtkEventControllerScroll* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll begin\n"); // FIXMEgtk4 } bool Widget::event_scroll(GtkEventControllerScroll* controller, double dx, double dy) { _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll delta_x=%.3f delta_y=%.3f\n", dx, dy); auto event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(controller)); if (!event) return false; return terminal()->widget_mouse_scroll({gdk_event_get_modifier_state(event), dx, dy}); } void Widget::event_scroll_end(GtkEventControllerScroll* controller) { _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll end\n"); // FIXMEgtk4 } void Widget::event_scroll_decelerate(GtkEventControllerScroll* controller, double vx, double vy) { _vte_debug_print(VTE_DEBUG_EVENTS, "Scroll decelerate v_x=%.3f v_y=%.3f\n", vx, vy); // FIXMEgtk4 } void Widget::gesture_click_pressed(GtkGestureClick* gesture, int press_count, double x, double y) { _vte_debug_print(VTE_DEBUG_EVENTS, "Click gesture pressed press_count=%d x=%.3f y=%.3f\n", press_count, x, y); // FIXMEgtk4 why does gtk4 not do that automatically? gtk_widget_grab_focus(gtk()); auto const event = mouse_event_from_gesture_click(EventBase::Type::eMOUSE_PRESS, gesture, press_count, x, y); if (terminal()->widget_mouse_press(event)) gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); else gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_DENIED); // FIXMEgtk4 GtkLabel does // if (press_count >= 3) // gtk_event_controller_reset(GTK_EVENT_CONTROLLER(gesture)); // but this makes triple-click 'sticky' } void Widget::gesture_click_released(GtkGestureClick* gesture, int press_count, double x, double y) { _vte_debug_print(VTE_DEBUG_EVENTS, "Click gesture released press_count=%d x=%.3f y=%.3f\n", press_count, x, y); // FIXMEgtk4 why does gtk4 not do that automatically? gtk_widget_grab_focus(gtk()); auto const sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture)); if (!gtk_gesture_handles_sequence(GTK_GESTURE(gesture), sequence)) // FIXMEgtk4 why!? return; auto const event = mouse_event_from_gesture_click(EventBase::Type::eMOUSE_RELEASE, gesture, press_count, x, y); if (terminal()->widget_mouse_release(event)) gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); } void Widget::gesture_click_stopped(GtkGestureClick* gesture) { _vte_debug_print(VTE_DEBUG_EVENTS, "Click gesture stopped\n"); // FIXMEgtk4 what's the right thing to do here??? // Should probably stop selection expansion mode, reset stored buttons, ...? } void Widget::gesture_click_unpaired_release(GtkGestureClick* gesture, double x, double y, unsigned button, GdkEventSequence* sequence) { _vte_debug_print(VTE_DEBUG_EVENTS, "Click gesture unpaired release button=%d x=%.3f y=%.3f\n", button, x, y); // FIXMEgtk4 what's the right thing to do here??? // FIXMEgtk4 why does gtk4 not do that automatically? gtk_widget_grab_focus(gtk()); if (!gtk_gesture_handles_sequence(GTK_GESTURE(gesture), sequence)) // why!? return; auto const event = mouse_event_from_gesture_click(EventBase::Type::eMOUSE_RELEASE, gesture, 1, // press_count x, y); if (terminal()->widget_mouse_release(event)) gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED); } #endif /* VTE_GTK == 4 */ void Widget::im_focus_in() noexcept { gtk_im_context_focus_in(m_im_context.get()); } void Widget::im_focus_out() noexcept { gtk_im_context_focus_out(m_im_context.get()); } void Widget::im_preedit_changed() noexcept { auto str = vte::glib::StringPtr{}; auto attrs = vte::Freeable{}; auto cursorpos = 0; gtk_im_context_get_preedit_string(m_im_context.get(), vte::glib::StringGetter{str}, vte::get_freeable(attrs), &cursorpos); _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit changed (%s,%d).\n", str.get(), cursorpos); if (str) m_terminal->im_preedit_changed(str.get(), cursorpos, std::move(attrs)); } void Widget::im_set_cursor_location(cairo_rectangle_int_t const* rect) noexcept { gtk_im_context_set_cursor_location(m_im_context.get(), rect); } #if VTE_GTK == 3 unsigned Widget::read_modifiers_from_gdk(GdkEvent* event) const noexcept { /* Read the modifiers. See bug #663779 for more information on why we do this. */ auto mods = GdkModifierType{}; if (!gdk_event_get_state(event, &mods)) return 0; /* HACK! Treat META as ALT; see bug #663779. */ if (mods & GDK_META_MASK) mods = GdkModifierType(mods | GDK_MOD1_MASK); /* Map non-virtual modifiers to virtual modifiers (Super, Hyper, Meta) */ auto display = gdk_window_get_display(gdk_event_get_window(event)); auto keymap = gdk_keymap_get_for_display(display); gdk_keymap_add_virtual_modifiers(keymap, &mods); return unsigned(mods); } #endif /* VTE_GTK == 3 */ unsigned Widget::key_event_translate_ctrlkey(KeyEvent const& event) const noexcept { #if VTE_GTK == 3 if (event.keyval() < 128) return event.keyval(); auto display = gdk_window_get_display(gdk_event_get_window(event.platform_event())); auto keymap = gdk_keymap_get_for_display(display); auto keyval = unsigned{event.keyval()}; /* Try groups in order to find one mapping the key to ASCII */ for (auto i = unsigned{0}; i < 4; i++) { auto consumed_modifiers = GdkModifierType{}; gdk_keymap_translate_keyboard_state (keymap, event.keycode(), GdkModifierType(event.modifiers()), i, &keyval, NULL, NULL, &consumed_modifiers); if (keyval < 128) { _vte_debug_print (VTE_DEBUG_EVENTS, "ctrl+Key, group=%d de-grouped into keyval=0x%x\n", event.group(), keyval); break; } } return keyval; #elif VTE_GTK == 4 // FIXMEgtk4: find a way to do this on gtk4 return event.keyval(); #endif } KeyEvent Widget::key_event_from_gdk(GdkEvent* event) const { auto type = EventBase::Type{}; switch (gdk_event_get_event_type(event)) { case GDK_KEY_PRESS: type = KeyEvent::Type::eKEY_PRESS; break; case GDK_KEY_RELEASE: type = KeyEvent::Type::eKEY_RELEASE; break; default: g_assert_not_reached(); return {}; } #if VTE_GTK == 3 auto keyval = unsigned{}; gdk_event_get_keyval(event, &keyval); auto const scancode = unsigned(reinterpret_cast(event)->hardware_keycode); auto const group = reinterpret_cast(event)->group; auto const is_modifier = reinterpret_cast(event)->is_modifier != 0; #elif VTE_GTK == 4 auto keyval = gdk_key_event_get_keyval(event); auto scancode = gdk_key_event_get_keycode(event); auto const group = gdk_key_event_get_level(event); auto const is_modifier = gdk_key_event_is_modifier(event) != false; #endif /* VTE_GTK */ return {event, type, #if VTE_GTK == 3 read_modifiers_from_gdk(event), #elif VTE_GTK == 4 gdk_event_get_modifier_state(event), #endif keyval, scancode, group, is_modifier}; } #if VTE_GTK == 3 MouseEvent Widget::mouse_event_from_gdk(GdkEvent* event) const /* throws */ { auto type = EventBase::Type{}; auto press_count = 0; switch (gdk_event_get_event_type(event)) { case GDK_2BUTTON_PRESS: type = MouseEvent::Type::eMOUSE_PRESS; press_count = 2; break; case GDK_3BUTTON_PRESS: type = MouseEvent::Type::eMOUSE_PRESS; press_count = 3; break; case GDK_BUTTON_PRESS: type = MouseEvent::Type::eMOUSE_PRESS; press_count = 1; break; case GDK_BUTTON_RELEASE: type = MouseEvent::Type::eMOUSE_RELEASE; press_count = 1; break; case GDK_ENTER_NOTIFY: type = MouseEvent::Type::eMOUSE_ENTER; break; case GDK_LEAVE_NOTIFY: type = MouseEvent::Type::eMOUSE_LEAVE; break; case GDK_MOTION_NOTIFY: type = MouseEvent::Type::eMOUSE_MOTION; break; case GDK_SCROLL: type = MouseEvent::Type::eMOUSE_SCROLL; press_count = 1; break; default: throw std::runtime_error{"Unexpected event type"}; } auto x = double{}; auto y = double{}; if (gdk_event_get_window(event) != m_event_window || !gdk_event_get_coords(event, &x, &y)) x = y = -1.; // FIXMEchpe or throw? auto button = 0u; (void)gdk_event_get_button(event, &button); return {type, press_count, read_modifiers_from_gdk(event), MouseEvent::Button(button), x, y}; } #endif /* VTE_GTK == 3 */ void Widget::notify_char_size_changed(int width, int height) { if (scroll_unit_is_pixels()) [[unlikely]] { /* When using pixels as adjustment values, changing the * char size means we need to adjust the scroll bounds * and value to keep the actual scroll position constant. */ notify_scroll_bounds_changed(true); } _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `char-size-changed'.\n"); /* FIXME on next API break, change the signature */ g_signal_emit(gtk(), signals[SIGNAL_CHAR_SIZE_CHANGED], 0, guint(width), guint(height)); } void Widget::notify_scroll_bounds_changed(bool value_changed) { _vte_debug_print(VTE_DEBUG_ADJ, "Updating scroll adjustment\n"); auto const freezer = vte::glib::FreezeObjectNotify{m_vadjustment.get()}; auto changed = false; auto const lower = terminal()->scroll_limit_lower(); auto const upper = terminal()->scroll_limit_upper(); auto dlower = 0.; auto dupper = double(upper - lower); auto dline = 1.; auto row_count = terminal()->row_count(); if (scroll_unit_is_pixels()) [[unlikely]] { auto const factor = m_terminal->get_cell_height(); dupper *= factor; dline *= factor; row_count *= factor; } auto current = gtk_adjustment_get_lower(m_vadjustment.get()); if (!_vte_double_equal(current, dlower)) { _vte_debug_print(VTE_DEBUG_ADJ, "Changing lower bound from %.0f to %f\n", current, dlower); gtk_adjustment_set_lower(m_vadjustment.get(), dlower); changed = true; } current = gtk_adjustment_get_upper(m_vadjustment.get()); if (!_vte_double_equal(current, dupper)) { _vte_debug_print(VTE_DEBUG_ADJ, "Changing upper bound from %.0f to %f\n", current, dupper); gtk_adjustment_set_upper(m_vadjustment.get(), dupper); changed = true; } /* The step increment should always be one. */ current = gtk_adjustment_get_step_increment(m_vadjustment.get()); if (!_vte_double_equal(current, dline)) { _vte_debug_print(VTE_DEBUG_ADJ, "Changing step increment from %.0lf to 1.0\n", current); gtk_adjustment_set_step_increment(m_vadjustment.get(), dline); changed = true; } current = gtk_adjustment_get_page_size(m_vadjustment.get()); if (!_vte_double_equal(current, row_count)) { _vte_debug_print(VTE_DEBUG_ADJ, "Changing page size from %.0f to %ld\n", current, row_count); gtk_adjustment_set_page_size(m_vadjustment.get(), row_count); changed = true; } /* Clicking in the empty area should scroll exactly one screen, * so set the page size to the number of visible rows. */ current = gtk_adjustment_get_page_increment(m_vadjustment.get()); if (!_vte_double_equal(current, row_count)) { _vte_debug_print(VTE_DEBUG_ADJ, "Changing page increment from " "%.0f to %ld\n", current, row_count); gtk_adjustment_set_page_increment(m_vadjustment.get(), row_count); changed = true; } if (value_changed) notify_scroll_value_changed(); if (changed) _vte_debug_print(VTE_DEBUG_SIGNALS, "Adjustment changed.\n"); } void Widget::notify_scroll_value_changed() { _vte_debug_print(VTE_DEBUG_ADJ, "Updating scroll adjustment value\n"); auto const lower = terminal()->scroll_limit_lower(); auto value = terminal()->scroll_position() - lower; if (scroll_unit_is_pixels()) [[unlikely]] { auto const factor = m_terminal->get_cell_height(); value *= factor; } auto const v = gtk_adjustment_get_value(m_vadjustment.get()); if (!_vte_double_equal(v, value)) { m_changing_scroll_position = true; gtk_adjustment_set_value(m_vadjustment.get(), value); m_changing_scroll_position = false; } } #if VTE_GTK == 3 ScrollEvent Widget::scroll_event_from_gdk(GdkEvent* event) const /* throws */ { auto dx = double{}, dy = double{}; if (!gdk_event_get_scroll_deltas(event, &dx, &dy)) { auto dir = GdkScrollDirection{}; if (!gdk_event_get_scroll_direction(event, &dir)) __builtin_unreachable(); switch (dir) { case GDK_SCROLL_UP: dx = 0.; dy = -1.; break; case GDK_SCROLL_DOWN: dx = 0.; dy = 1.; break; case GDK_SCROLL_LEFT: dx = -1.; dy = 0.; break; case GDK_SCROLL_RIGHT: dx = 1.; dy = 0.; break; case GDK_SCROLL_SMOOTH: break; default: __builtin_unreachable(); } } return {read_modifiers_from_gdk(event), dx, dy}; } #endif /* VTE_GTK == 3 */ #if VTE_GTK == 4 MouseEvent Widget::mouse_event_from_gesture_click(EventBase::Type type, GtkGestureClick* gesture, int press_count, double x, double y) const /* throws */ { auto const gesture_single = GTK_GESTURE_SINGLE(gesture); auto const button = gtk_gesture_single_get_current_button(gesture_single); auto const sequence = gtk_gesture_single_get_current_sequence(gesture_single); auto const event = gtk_gesture_get_last_event(GTK_GESTURE(gesture), sequence); if (!event) throw std::runtime_error{"No last event!?"}; return {type, press_count, gdk_event_get_modifier_state(event), MouseEvent::Button(button), x, y}; } #endif /* VTE_GTK == 4 */ void Widget::map() noexcept { #if VTE_GTK == 3 if (m_event_window) gdk_window_show_unraised(m_event_window); #endif } #if VTE_GTK == 4 void Widget::measure(GtkOrientation orientation, int for_size, int* minimum, int* natural, int* minimum_baseline, int* natural_baseline) noexcept { _vte_debug_print(VTE_DEBUG_WIDGET_SIZE, "Widget measure for_size=%d orientation=%s\n", for_size, orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical"); switch (orientation) { case GTK_ORIENTATION_HORIZONTAL: terminal()->widget_measure_width(minimum, natural); break; case GTK_ORIENTATION_VERTICAL: *minimum_baseline = *natural_baseline = -1; terminal()->widget_measure_height(minimum, natural); break; } } #endif /* VTE_GTK == 4 */ bool Widget::padding_changed() noexcept { auto padding = GtkBorder{}; auto const context = gtk_widget_get_style_context(gtk()); gtk_style_context_get_padding(context, #if VTE_GTK == 3 gtk_style_context_get_state(context), #endif &padding); /* FIXMEchpe: do we need to add the border from * gtk_style_context_get_border() to the padding? */ return terminal()->set_style_border(padding); } bool Widget::primary_paste_enabled() const noexcept { auto primary_paste = gboolean{}; g_object_get(m_settings.get(), "gtk-enable-primary-paste", &primary_paste, nullptr); return primary_paste != false; } bool Widget::query_tooltip(int x, int y, bool keyboard, GtkTooltip* tooltip) noexcept { return false; } void Widget::realize() noexcept { // m_mouse_cursor_over_widget = false; /* We'll receive an enter_notify_event if the window appears under the cursor. */ /* Create stock cursors */ m_default_cursor = create_cursor(VTE_DEFAULT_CURSOR); m_invisible_cursor = create_cursor("none"s); m_mousing_cursor = create_cursor(VTE_MOUSING_CURSOR); if (_vte_debug_on(VTE_DEBUG_HYPERLINK)) /* Differ from the standard regex match cursor in debug mode. */ m_hyperlink_cursor = create_cursor(VTE_HYPERLINK_CURSOR_DEBUG); else m_hyperlink_cursor = create_cursor(VTE_HYPERLINK_CURSOR); #if VTE_GTK == 3 /* Create an input window for the widget. */ auto allocation = m_terminal->get_allocated_rect(); GdkWindowAttr attributes; attributes.window_type = GDK_WINDOW_CHILD; attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; attributes.wclass = GDK_INPUT_ONLY; attributes.visual = gtk_widget_get_visual(m_widget); attributes.event_mask = gtk_widget_get_events(m_widget) | GDK_EXPOSURE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_SCROLL_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON1_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK; attributes.cursor = m_default_cursor.get(); guint attributes_mask = GDK_WA_X | GDK_WA_Y | (attributes.visual ? GDK_WA_VISUAL : 0) | GDK_WA_CURSOR; m_event_window = gdk_window_new(gtk_widget_get_parent_window (m_widget), &attributes, attributes_mask); gtk_widget_register_window(m_widget, m_event_window); #endif /* VTE_GTK == 3 */ assert(!m_im_context); m_im_context = vte::glib::take_ref(gtk_im_multicontext_new()); #if (VTE_GTK == 3 && GTK_CHECK_VERSION (3, 24, 14)) || VTE_GTK == 4 g_object_set(m_im_context.get(), "input-purpose", GTK_INPUT_PURPOSE_TERMINAL, nullptr); #endif #if VTE_GTK == 3 gtk_im_context_set_client_window(m_im_context.get(), m_event_window); #elif VTE_GTK == 4 gtk_im_context_set_client_widget(m_im_context.get(), gtk()); #endif g_signal_connect(m_im_context.get(), "commit", G_CALLBACK(im_commit_cb), this); g_signal_connect(m_im_context.get(), "preedit-start", G_CALLBACK(im_preedit_start_cb), this); g_signal_connect(m_im_context.get(), "preedit-changed", G_CALLBACK(im_preedit_changed_cb), this); g_signal_connect(m_im_context.get(), "preedit-end", G_CALLBACK(im_preedit_end_cb), this); g_signal_connect(m_im_context.get(), "retrieve-surrounding", G_CALLBACK(im_retrieve_surrounding_cb), this); g_signal_connect(m_im_context.get(), "delete-surrounding", G_CALLBACK(im_delete_surrounding_cb), this); gtk_im_context_set_use_preedit(m_im_context.get(), true); m_clipboard = std::make_shared(*this, ClipboardType::CLIPBOARD); m_primary_clipboard = std::make_shared(*this, ClipboardType::PRIMARY); m_terminal->widget_realize(); } #if VTE_GTK == 4 void Widget::root() { } #endif /* VTE_GTK == 4 */ #if VTE_GTK == 3 void Widget::screen_changed(GdkScreen *previous_screen) noexcept { auto gdk_screen = gtk_widget_get_screen(m_widget); if (gdk_screen == previous_screen || gdk_screen == nullptr) return; connect_settings(); } #elif VTE_GTK == 4 void Widget::display_changed() noexcept { /* There appears to be no way to retrieve the previous display */ connect_settings(); } #endif /* VTE_GTK */ void Widget::connect_settings() { auto settings = vte::glib::make_ref(gtk_widget_get_settings(m_widget)); if (settings == m_settings) return; if (m_settings) g_signal_handlers_disconnect_matched(m_settings.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this); m_settings = std::move(settings); settings_changed(); g_signal_connect(m_settings.get(), "notify::gtk-cursor-blink", G_CALLBACK(settings_notify_cb), this); g_signal_connect(m_settings.get(), "notify::gtk-cursor-blink-time", G_CALLBACK(settings_notify_cb), this); g_signal_connect(m_settings.get(), "notify::gtk-cursor-blink-timeout", G_CALLBACK(settings_notify_cb), this); #if VTE_GTK == 4 g_signal_connect(m_settings.get(), "notify::gtk-cursor-aspect-ratio", G_CALLBACK(settings_notify_cb), this); #endif } void Widget::settings_changed() { auto blink = gboolean{}; auto blink_time = int{}; auto blink_timeout = int{}; #if VTE_GTK == 4 auto aspect = double{}; #endif g_object_get(m_settings.get(), "gtk-cursor-blink", &blink, "gtk-cursor-blink-time", &blink_time, "gtk-cursor-blink-timeout", &blink_timeout, #if VTE_GTK == 4 "gtk-cursor-aspect-ratio", &aspect, #endif nullptr); _vte_debug_print(VTE_DEBUG_MISC, "Cursor blinking settings: blink=%d time=%d timeout=%d\n", blink, blink_time, blink_timeout); m_terminal->set_blink_settings(blink, blink_time, blink_timeout); #if VTE_GTK == 4 m_terminal->set_cursor_aspect(aspect); #endif } void Widget::set_cursor(CursorType type) noexcept { switch (type) { case CursorType::eDefault: return set_cursor(m_default_cursor.get()); case CursorType::eInvisible: return set_cursor(m_invisible_cursor.get()); case CursorType::eMousing: return set_cursor(m_mousing_cursor.get()); case CursorType::eHyperlink: return set_cursor(m_hyperlink_cursor.get()); } } void Widget::set_hscroll_policy(GtkScrollablePolicy policy) { m_hscroll_policy = policy; #if VTE_GTK == 3 gtk_widget_queue_resize_no_redraw(gtk()); #elif VTE_GTK == 4 gtk_widget_queue_resize(gtk()); #endif } void Widget::set_vscroll_policy(GtkScrollablePolicy policy) { m_vscroll_policy = policy; #if VTE_GTK == 3 gtk_widget_queue_resize_no_redraw(gtk()); #elif VTE_GTK == 4 gtk_widget_queue_resize(gtk()); #endif } bool Widget::set_pty(VtePty* pty_obj) noexcept { if (pty() == pty_obj) return false; m_pty = vte::glib::make_ref(pty_obj); terminal()->set_pty(_vte_pty_get_impl(pty())); return true; } static void vadjustment_value_changed_cb(vte::platform::Widget* that) noexcept try { that->vadjustment_value_changed(); } catch (...) { vte::log_exception(); } void Widget::set_vadjustment(vte::glib::RefPtr adjustment) { if (adjustment && adjustment == m_vadjustment) return; if (!adjustment && m_vadjustment) return; if (m_vadjustment) { g_signal_handlers_disconnect_by_func(m_vadjustment.get(), (void*)vadjustment_value_changed_cb, this); } if (adjustment) m_vadjustment = std::move(adjustment); else m_vadjustment = vte::glib::make_ref_sink(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0))); /* We care about the offset only, not the top or bottom. */ g_signal_connect_swapped(m_vadjustment.get(), "value-changed", G_CALLBACK(vadjustment_value_changed_cb), this); } bool Widget::set_word_char_exceptions(std::optional stropt) { if (m_word_char_exceptions == stropt) return false; if (terminal()->set_word_char_exceptions(stropt)) { m_word_char_exceptions = stropt; return true; } return false; } void Widget::unset_pty() noexcept { if (!pty()) return; /* This is only called from Terminal, so we need * to explicitly notify the VteTerminal:pty property, * but we do NOT need to call Terminal::set_pty(nullptr). */ m_pty.reset(); g_object_notify_by_pspec(object(), pspecs[PROP_PTY]); } #if VTE_GTK == 3 void Widget::size_allocate(GtkAllocation* allocation) { _vte_debug_print(VTE_DEBUG_WIDGET_SIZE, "Widget size allocate width=%d height=%d x=%d y=%d\n", allocation->width, allocation->height, allocation->x, allocation->y); m_terminal->widget_size_allocate(allocation->x, allocation->y, allocation->width, allocation->height, -1, vte::terminal::Terminal::Alignment(m_xalign), vte::terminal::Terminal::Alignment(m_yalign), m_xfill, m_yfill); gtk_widget_set_allocation(gtk(), allocation); if (realized()) gdk_window_move_resize(m_event_window, allocation->x, allocation->y, allocation->width, allocation->height); } #elif VTE_GTK == 4 void Widget::size_allocate(int width, int height, int baseline) { _vte_debug_print(VTE_DEBUG_WIDGET_SIZE, "Widget size allocate width=%d height=%d baseline=%d\n", width, height, baseline); terminal()->widget_size_allocate(width, height, baseline, vte::terminal::Terminal::Alignment(m_xalign), vte::terminal::Terminal::Alignment(m_yalign), m_xfill, m_yfill); } #endif /* VTE_GTK */ bool Widget::should_emit_signal(int id) noexcept { return g_signal_has_handler_pending(object(), signals[id], 0 /* detail */, false /* not interested in blocked handlers */) != FALSE; } void Widget::state_flags_changed(GtkStateFlags old_flags) { _vte_debug_print(VTE_DEBUG_STYLE, "Widget state flags changed\n"); } #if VTE_GTK == 3 void Widget::style_updated() noexcept { _vte_debug_print(VTE_DEBUG_STYLE, "Widget style changed\n"); auto need_resize = padding_changed(); auto aspect = float{}; gtk_widget_style_get(gtk(), "cursor-aspect-ratio", &aspect, nullptr); m_terminal->set_cursor_aspect(aspect); m_terminal->widget_style_updated(); if (need_resize) gtk_widget_queue_resize(m_widget); } #endif /* VTE_GTK == 3 */ #if VTE_GTK == 4 void Widget::system_setting_changed(GtkSystemSetting setting) { _vte_debug_print(VTE_DEBUG_STYLE, "Widget system settings %d changed\n", int(setting)); switch (setting) { case GTK_SYSTEM_SETTING_DISPLAY: display_changed(); break; case GTK_SYSTEM_SETTING_DPI: break; case GTK_SYSTEM_SETTING_FONT_CONFIG: break; case GTK_SYSTEM_SETTING_FONT_NAME: break; case GTK_SYSTEM_SETTING_ICON_THEME: break; default: break; } } #endif /* VTE_GTK == 4 */ void Widget::unmap() noexcept { m_terminal->widget_unmap(); #if VTE_GTK == 3 if (m_event_window) gdk_window_hide(m_event_window); #endif } void Widget::unrealize() noexcept { m_terminal->widget_unrealize(); // FIXMEgtk4 only withdraw content from clipboard, not unselect? if (m_clipboard) { terminal()->widget_clipboard_data_clear(*m_clipboard); m_clipboard->disown(); m_clipboard.reset(); } if (m_primary_clipboard) { terminal()->widget_clipboard_data_clear(*m_primary_clipboard); m_primary_clipboard->disown(); m_primary_clipboard.reset(); } m_default_cursor.reset(); m_invisible_cursor.reset(); m_mousing_cursor.reset(); m_hyperlink_cursor.reset(); /* Shut down input methods. */ assert(m_im_context); g_signal_handlers_disconnect_matched(m_im_context.get(), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, this); m_terminal->im_preedit_reset(); #if VTE_GTK == 3 gtk_im_context_set_client_window(m_im_context.get(), nullptr); #elif VTE_GTK == 4 gtk_im_context_set_client_widget(m_im_context.get(), nullptr); #endif m_im_context.reset(); #if VTE_GTK == 3 /* Destroy input window */ gtk_widget_unregister_window(m_widget, m_event_window); gdk_window_destroy(m_event_window); m_event_window = nullptr; #endif /* VTE_GTK == 3 */ } #if VTE_GTK == 4 void Widget::unroot() { } #endif /* VTE_GTK == 4 */ void Widget::vadjustment_value_changed() { if (!m_terminal) return; if (m_changing_scroll_position) return; auto adj = gtk_adjustment_get_value(m_vadjustment.get()); if (scroll_unit_is_pixels()) [[unlikely]] { auto const factor = m_terminal->get_cell_height(); adj /= factor; } /* Add offset */ auto const lower = terminal()->scroll_limit_lower(); adj += lower; m_terminal->set_scroll_value(adj); } } // namespace platform } // namespace vte