1 /*
2  * Copyright © 2008, 2009, 2010, 2018 Christian Persch
3  * Copyright © 2001-2004,2009,2010 Red Hat, Inc.
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include "widget.hh"
22 
23 #include <sys/wait.h> // for W_EXITCODE
24 
25 #include <exception>
26 #include <new>
27 #include <stdexcept>
28 #include <string>
29 
30 #include "cxx-utils.hh"
31 #include "vtegtk.hh"
32 #include "vteptyinternal.hh"
33 #include "debug.h"
34 
35 using namespace std::literals;
36 
37 namespace vte {
38 
39 namespace platform {
40 
41 static void
im_commit_cb(GtkIMContext * im_context,char const * text,Widget * that)42 im_commit_cb(GtkIMContext* im_context,
43              char const* text,
44              Widget* that) noexcept
45 try
46 {
47         if (text == nullptr)
48                 return;
49 
50         that->terminal()->im_commit(text);
51 }
52 catch (...)
53 {
54         vte::log_exception();
55 }
56 
57 static void
im_preedit_start_cb(GtkIMContext * im_context,Widget * that)58 im_preedit_start_cb(GtkIMContext* im_context,
59                     Widget* that) noexcept
60 try
61 {
62         _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit started.\n");
63         that->terminal()->im_preedit_set_active(true);
64 }
65 catch (...)
66 {
67         vte::log_exception();
68 }
69 
70 static void
im_preedit_end_cb(GtkIMContext * im_context,Widget * that)71 im_preedit_end_cb(GtkIMContext* im_context,
72                   Widget* that) noexcept
73 try
74 {
75         _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit ended.\n");
76         that->terminal()->im_preedit_set_active(false);
77 }
78 catch (...)
79 {
80         vte::log_exception();
81 }
82 
83 static void
im_preedit_changed_cb(GtkIMContext * im_context,Widget * that)84 im_preedit_changed_cb(GtkIMContext* im_context,
85                       Widget* that) noexcept
86 try
87 {
88         that->im_preedit_changed();
89 }
90 catch (...)
91 {
92         vte::log_exception();
93 }
94 
95 static gboolean
im_retrieve_surrounding_cb(GtkIMContext * im_context,Widget * that)96 im_retrieve_surrounding_cb(GtkIMContext* im_context,
97                            Widget* that) noexcept
98 try
99 {
100         _vte_debug_print(VTE_DEBUG_EVENTS, "Input method retrieve-surrounding.\n");
101         return that->terminal()->im_retrieve_surrounding();
102 }
103 catch (...)
104 {
105         vte::log_exception();
106         return false;
107 }
108 
109 static gboolean
im_delete_surrounding_cb(GtkIMContext * im_context,int offset,int n_chars,Widget * that)110 im_delete_surrounding_cb(GtkIMContext* im_context,
111                          int offset,
112                          int n_chars,
113                          Widget* that) noexcept
114 try
115 {
116         _vte_debug_print(VTE_DEBUG_EVENTS,
117                          "Input method delete-surrounding offset %d n-chars %d.\n",
118                          offset, n_chars);
119         return that->terminal()->im_delete_surrounding(offset, n_chars);
120 }
121 catch (...)
122 {
123         vte::log_exception();
124         return false;
125 }
126 
127 static void
settings_notify_cb(GtkSettings * settings,GParamSpec * pspec,vte::platform::Widget * that)128 settings_notify_cb(GtkSettings* settings,
129                    GParamSpec* pspec,
130                    vte::platform::Widget* that) noexcept
131 try
132 {
133         that->settings_changed();
134 }
135 catch (...)
136 {
137         vte::log_exception();
138 }
139 
Widget(VteTerminal * t)140 Widget::Widget(VteTerminal* t)
141         : m_widget{&t->widget},
142           m_hscroll_policy{GTK_SCROLL_NATURAL},
143           m_vscroll_policy{GTK_SCROLL_NATURAL}
144 {
145         gtk_widget_set_can_focus(gtk(), true);
146 
147         /* We do our own redrawing. */
148         // FIXMEchpe is this still necessary?
149         gtk_widget_set_redraw_on_allocate(gtk(), false);
150 
151         /* Until Terminal init is completely fixed, use zero'd memory */
152         auto place = g_malloc0(sizeof(vte::terminal::Terminal));
153         m_terminal = new (place) vte::terminal::Terminal(this, t);
154 }
155 
~Widget()156 Widget::~Widget() noexcept
157 try
158 {
159         g_signal_handlers_disconnect_matched(gtk_widget_get_settings(m_widget),
160                                              G_SIGNAL_MATCH_DATA,
161                                              0, 0, NULL, NULL,
162                                              this);
163 
164         m_widget = nullptr;
165 
166         m_terminal->~Terminal();
167         g_free(m_terminal);
168 }
169 catch (...)
170 {
171         vte::log_exception();
172 }
173 
174 void
beep()175 Widget::beep() noexcept
176 {
177         if (realized())
178                 gdk_window_beep(gtk_widget_get_window(m_widget));
179 }
180 
181 vte::glib::RefPtr<GdkCursor>
create_cursor(std::string const & name) const182 Widget::create_cursor(std::string const& name) const noexcept
183 {
184 	return vte::glib::take_ref(gdk_cursor_new_from_name(gtk_widget_get_display(m_widget), name.c_str()));
185 }
186 
187 void
set_cursor(GdkCursor * cursor)188 Widget::set_cursor(GdkCursor* cursor) noexcept
189 {
190         gdk_window_set_cursor(m_event_window, cursor);
191 }
192 
193 void
set_cursor(Cursor const & cursor)194 Widget::set_cursor(Cursor const& cursor) noexcept
195 {
196         if (!realized())
197                 return;
198 
199         auto display = gtk_widget_get_display(m_widget);
200         GdkCursor* gdk_cursor{nullptr};
201         switch (cursor.index()) {
202         case 0:
203                 gdk_cursor = gdk_cursor_new_from_name(display, std::get<0>(cursor).c_str());
204                 break;
205         case 1:
206                 gdk_cursor = std::get<1>(cursor).get();
207                 if (gdk_cursor != nullptr &&
208                     gdk_cursor_get_display(gdk_cursor) == display) {
209                         g_object_ref(gdk_cursor);
210                 } else {
211                         gdk_cursor = nullptr;
212                 }
213                 break;
214         case 2:
215                 gdk_cursor = gdk_cursor_new_for_display(display, std::get<2>(cursor));
216                 break;
217         }
218 
219         set_cursor(gdk_cursor);
220         if (gdk_cursor)
221                 g_object_unref(gdk_cursor);
222 }
223 
224 Clipboard&
clipboard_get(ClipboardType type) const225 Widget::clipboard_get(ClipboardType type) const
226 {
227         switch (type) {
228         case ClipboardType::CLIPBOARD: return *m_clipboard;
229         case ClipboardType::PRIMARY: return *m_primary_clipboard;
230         default: g_assert_not_reached(); throw std::runtime_error{""}; break;
231         }
232 }
233 
234 std::optional<std::string_view>
clipboard_data_get_cb(Clipboard const & clipboard,ClipboardFormat format)235 Widget::clipboard_data_get_cb(Clipboard const& clipboard,
236                               ClipboardFormat format)
237 {
238         return terminal()->widget_clipboard_data_get(clipboard, format);
239 }
240 
241 void
clipboard_data_clear_cb(Clipboard const & clipboard)242 Widget::clipboard_data_clear_cb(Clipboard const& clipboard)
243 {
244         terminal()->widget_clipboard_data_clear(clipboard);
245 }
246 
247 void
clipboard_request_received_cb(Clipboard const & clipboard,std::string_view const & text)248 Widget::clipboard_request_received_cb(Clipboard const& clipboard,
249                                       std::string_view const& text)
250 {
251         terminal()->widget_clipboard_text_received(clipboard, text);
252 }
253 
254 void
clipboard_request_failed_cb(Clipboard const & clipboard)255 Widget::clipboard_request_failed_cb(Clipboard const& clipboard)
256 {
257         gtk_widget_error_bell(gtk());
258 }
259 
260 void
clipboard_offer_data(ClipboardType type,ClipboardFormat format)261 Widget::clipboard_offer_data(ClipboardType type,
262                              ClipboardFormat format) noexcept
263 {
264         try {
265                 clipboard_get(type).offer_data(format,
266                                                &Widget::clipboard_data_get_cb,
267                                                &Widget::clipboard_data_clear_cb);
268         } catch (...) {
269                 /* Let the caller know the request failed */
270                 terminal()->widget_clipboard_data_clear(clipboard_get(type));
271         }
272 }
273 
274 void
clipboard_request_text(ClipboardType type)275 Widget::clipboard_request_text(ClipboardType type) noexcept
276 {
277         try {
278                 clipboard_get(type).request_text(&Widget::clipboard_request_received_cb,
279                                                  &Widget::clipboard_request_failed_cb);
280         } catch (...) {
281                 /* Let the caller know the request failed */
282                 clipboard_request_failed_cb(clipboard_get(type));
283         }
284 }
285 
286 void
clipboard_set_text(ClipboardType type,std::string_view const & str)287 Widget::clipboard_set_text(ClipboardType type,
288                            std::string_view const& str) noexcept
289 {
290         clipboard_get(type).set_text(str);
291 }
292 
293 void
constructed()294 Widget::constructed() noexcept
295 {
296         /* Set the style as early as possible, before GTK+ starts
297          * invoking various callbacks. This is needed in order to
298          * compute the initial geometry correctly in presence of
299          * non-default padding, see bug 787710.
300          */
301         style_updated();
302 }
303 
304 void
dispose()305 Widget::dispose() noexcept
306 {
307         if (m_terminal->terminate_child()) {
308                 int status = W_EXITCODE(0, SIGKILL);
309                 emit_child_exited(status);
310         }
311 }
312 
313 void
emit_child_exited(int status)314 Widget::emit_child_exited(int status) noexcept
315 {
316         _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `child-exited'.\n");
317         g_signal_emit(object(), signals[SIGNAL_CHILD_EXITED], 0, status);
318 }
319 
320 void
emit_eof()321 Widget::emit_eof() noexcept
322 {
323         _vte_debug_print(VTE_DEBUG_SIGNALS, "Emitting `eof'.\n");
324         g_signal_emit(object(), signals[SIGNAL_EOF], 0);
325 }
326 
327 bool
im_filter_keypress(KeyEvent const & event)328 Widget::im_filter_keypress(KeyEvent const& event) noexcept
329 {
330         // FIXMEchpe this can only be called when realized, so the m_im_context check is redundant
331         return m_im_context &&
332                 gtk_im_context_filter_keypress(m_im_context.get(),
333                                                reinterpret_cast<GdkEventKey*>(event.platform_event()));
334 }
335 
336 void
im_focus_in()337 Widget::im_focus_in() noexcept
338 {
339         gtk_im_context_focus_in(m_im_context.get());
340 }
341 
342 void
im_focus_out()343 Widget::im_focus_out() noexcept
344 {
345         gtk_im_context_focus_out(m_im_context.get());
346 }
347 
348 void
im_preedit_changed()349 Widget::im_preedit_changed() noexcept
350 {
351         auto str = vte::glib::StringPtr{};
352         auto attrs = vte::Freeable<PangoAttrList>{};
353         auto cursorpos = 0;
354         gtk_im_context_get_preedit_string(m_im_context.get(),
355                                           vte::glib::StringGetter{str},
356                                           vte::get_freeable(attrs),
357                                           &cursorpos);
358         _vte_debug_print(VTE_DEBUG_EVENTS, "Input method pre-edit changed (%s,%d).\n",
359                          str.get(), cursorpos);
360 
361         if (str)
362                 m_terminal->im_preedit_changed(str.get(), cursorpos, std::move(attrs));
363 }
364 
365 void
im_set_cursor_location(cairo_rectangle_int_t const * rect)366 Widget::im_set_cursor_location(cairo_rectangle_int_t const* rect) noexcept
367 {
368         gtk_im_context_set_cursor_location(m_im_context.get(), rect);
369 }
370 
371 unsigned
read_modifiers_from_gdk(GdkEvent * event) const372 Widget::read_modifiers_from_gdk(GdkEvent* event) const noexcept
373 {
374         /* Read the modifiers. See bug #663779 for more information on why we do this. */
375         auto mods = GdkModifierType{};
376         if (!gdk_event_get_state(event, &mods))
377                 return 0;
378 
379         #if 1
380         /* HACK! Treat META as ALT; see bug #663779. */
381         if (mods & GDK_META_MASK)
382                 mods = GdkModifierType(mods | GDK_MOD1_MASK);
383         #endif
384 
385         /* Map non-virtual modifiers to virtual modifiers (Super, Hyper, Meta) */
386         auto display = gdk_window_get_display(gdk_event_get_window(event));
387         auto keymap = gdk_keymap_get_for_display(display);
388         gdk_keymap_add_virtual_modifiers(keymap, &mods);
389 
390         return unsigned(mods);
391 }
392 
393 unsigned
key_event_translate_ctrlkey(KeyEvent const & event) const394 Widget::key_event_translate_ctrlkey(KeyEvent const& event) const noexcept
395 {
396 	if (event.keyval() < 128)
397 		return event.keyval();
398 
399         auto display = gdk_window_get_display(gdk_event_get_window(event.platform_event()));
400         auto keymap = gdk_keymap_get_for_display(display);
401         auto keyval = unsigned{event.keyval()};
402 
403 	/* Try groups in order to find one mapping the key to ASCII */
404 	for (auto i = unsigned{0}; i < 4; i++) {
405 		auto consumed_modifiers = GdkModifierType{};
406 		gdk_keymap_translate_keyboard_state (keymap,
407                                                      event.keycode(),
408                                                      GdkModifierType(event.modifiers()),
409                                                      i,
410                                                      &keyval, NULL, NULL, &consumed_modifiers);
411 		if (keyval < 128) {
412 			_vte_debug_print (VTE_DEBUG_EVENTS,
413                                           "ctrl+Key, group=%d de-grouped into keyval=0x%x\n",
414                                           event.group(), keyval);
415                         break;
416 		}
417 	}
418 
419         return keyval;
420 }
421 
422 KeyEvent
key_event_from_gdk(GdkEventKey * event) const423 Widget::key_event_from_gdk(GdkEventKey* event) const
424 {
425         auto type = EventBase::Type{};
426         switch (gdk_event_get_event_type(reinterpret_cast<GdkEvent*>(event))) {
427         case GDK_KEY_PRESS: type = KeyEvent::Type::eKEY_PRESS;     break;
428         case GDK_KEY_RELEASE: type = KeyEvent::Type::eKEY_RELEASE; break;
429         default: g_assert_not_reached(); return {};
430         }
431 
432         auto base_event = reinterpret_cast<GdkEvent*>(event);
433         return {base_event,
434                 type,
435                 read_modifiers_from_gdk(base_event),
436                 event->keyval,
437                 event->hardware_keycode, // gdk_event_get_scancode(event),
438                 event->group,
439                 event->is_modifier != 0};
440 }
441 
442 MouseEvent
mouse_event_from_gdk(GdkEvent * event) const443 Widget::mouse_event_from_gdk(GdkEvent* event) const /* throws */
444 {
445         auto type = EventBase::Type{};
446         auto press_count = 0u;
447         switch (gdk_event_get_event_type(event)) {
448         case GDK_2BUTTON_PRESS:
449                 type = MouseEvent::Type::eMOUSE_PRESS;
450                 press_count = 2;
451                 break;
452         case GDK_3BUTTON_PRESS:
453                 type = MouseEvent::Type::eMOUSE_PRESS;
454                 press_count = 3;
455                 break;
456         case GDK_BUTTON_PRESS:
457                 type = MouseEvent::Type::eMOUSE_PRESS;
458                 press_count = 1;
459                 break;
460         case GDK_BUTTON_RELEASE:
461                 type = MouseEvent::Type::eMOUSE_RELEASE;
462                 press_count = 1;
463                 break;
464         case GDK_ENTER_NOTIFY:   type = MouseEvent::Type::eMOUSE_ENTER;        break;
465         case GDK_LEAVE_NOTIFY:   type = MouseEvent::Type::eMOUSE_LEAVE;        break;
466         case GDK_MOTION_NOTIFY:  type = MouseEvent::Type::eMOUSE_MOTION;       break;
467         case GDK_SCROLL:
468                 type = MouseEvent::Type::eMOUSE_SCROLL;
469                 press_count = 1;
470                 break;
471         default:
472                 throw std::runtime_error{"Unexpected event type"};
473         }
474 
475         auto x = double{};
476         auto y = double{};
477         if (gdk_event_get_window(event) != m_event_window ||
478             !gdk_event_get_coords(event, &x, &y))
479                 x = y = -1.; // FIXMEchpe or throw?
480 
481         auto button = unsigned{0};
482         (void)gdk_event_get_button(event, &button);
483 
484         return {type,
485                 press_count,
486                 read_modifiers_from_gdk(event),
487                 MouseEvent::Button(button),
488                 x,
489                 y};
490 }
491 
492 ScrollEvent
scroll_event_from_gdk(GdkEvent * event) const493 Widget::scroll_event_from_gdk(GdkEvent* event) const /* throws */
494 {
495         auto dx = double{}, dy = double{};
496         if (!gdk_event_get_scroll_deltas(event, &dx, &dy)) {
497                 auto dir = GdkScrollDirection{};
498                 if (!gdk_event_get_scroll_direction(event, &dir))
499                         __builtin_unreachable();
500 
501                 switch (dir) {
502                 case GDK_SCROLL_UP:     dx =  0.; dy = -1.; break;
503                 case GDK_SCROLL_DOWN:   dx =  0.; dy =  1.; break;
504                 case GDK_SCROLL_LEFT:   dx = -1.; dy =  0.; break;
505                 case GDK_SCROLL_RIGHT:  dx =  1.; dy =  0.; break;
506                 case GDK_SCROLL_SMOOTH: break;
507                 default: __builtin_unreachable();
508                 }
509         }
510 
511         return {read_modifiers_from_gdk(event),
512                 dx, dy};
513 }
514 
515 void
map()516 Widget::map() noexcept
517 {
518         if (m_event_window)
519                 gdk_window_show_unraised(m_event_window);
520 }
521 
522 bool
primary_paste_enabled() const523 Widget::primary_paste_enabled() const noexcept
524 {
525         auto primary_paste = gboolean{};
526         g_object_get(gtk_widget_get_settings(gtk()),
527                      "gtk-enable-primary-paste", &primary_paste,
528                      nullptr);
529 
530         return primary_paste != false;
531 }
532 
533 void
realize()534 Widget::realize() noexcept
535 {
536         //        m_mouse_cursor_over_widget = false;  /* We'll receive an enter_notify_event if the window appears under the cursor. */
537 
538 	/* Create stock cursors */
539 	m_default_cursor = create_cursor(VTE_DEFAULT_CURSOR);
540 	m_invisible_cursor = create_cursor("none"s);
541 	m_mousing_cursor = create_cursor(VTE_MOUSING_CURSOR);
542         if (_vte_debug_on(VTE_DEBUG_HYPERLINK))
543                 /* Differ from the standard regex match cursor in debug mode. */
544                 m_hyperlink_cursor = create_cursor(VTE_HYPERLINK_CURSOR_DEBUG);
545         else
546                 m_hyperlink_cursor = create_cursor(VTE_HYPERLINK_CURSOR);
547 
548 	/* Create an input window for the widget. */
549         auto allocation = m_terminal->get_allocated_rect();
550 	GdkWindowAttr attributes;
551 	attributes.window_type = GDK_WINDOW_CHILD;
552 	attributes.x = allocation.x;
553 	attributes.y = allocation.y;
554 	attributes.width = allocation.width;
555 	attributes.height = allocation.height;
556 	attributes.wclass = GDK_INPUT_ONLY;
557 	attributes.visual = gtk_widget_get_visual(m_widget);
558 	attributes.event_mask =
559                 gtk_widget_get_events(m_widget) |
560                 GDK_EXPOSURE_MASK |
561                 GDK_FOCUS_CHANGE_MASK |
562                 GDK_SMOOTH_SCROLL_MASK |
563                 GDK_SCROLL_MASK |
564                 GDK_BUTTON_PRESS_MASK |
565                 GDK_BUTTON_RELEASE_MASK |
566                 GDK_POINTER_MOTION_MASK |
567                 GDK_BUTTON1_MOTION_MASK |
568                 GDK_ENTER_NOTIFY_MASK |
569                 GDK_LEAVE_NOTIFY_MASK |
570                 GDK_KEY_PRESS_MASK |
571                 GDK_KEY_RELEASE_MASK;
572 	attributes.cursor = m_default_cursor.get();
573 	guint attributes_mask =
574                 GDK_WA_X |
575                 GDK_WA_Y |
576                 (attributes.visual ? GDK_WA_VISUAL : 0) |
577                 GDK_WA_CURSOR;
578 
579 	m_event_window = gdk_window_new(gtk_widget_get_parent_window (m_widget),
580                                         &attributes, attributes_mask);
581         gtk_widget_register_window(m_widget, m_event_window);
582 
583         assert(!m_im_context);
584 	m_im_context.reset(gtk_im_multicontext_new());
585 #if GTK_CHECK_VERSION (3, 24, 14)
586         g_object_set(m_im_context.get(),
587                      "input-purpose", GTK_INPUT_PURPOSE_TERMINAL,
588                      nullptr);
589 #endif
590 	gtk_im_context_set_client_window(m_im_context.get(), m_event_window);
591 	g_signal_connect(m_im_context.get(), "commit",
592 			 G_CALLBACK(im_commit_cb), this);
593 	g_signal_connect(m_im_context.get(), "preedit-start",
594 			 G_CALLBACK(im_preedit_start_cb), this);
595 	g_signal_connect(m_im_context.get(), "preedit-changed",
596 			 G_CALLBACK(im_preedit_changed_cb), this);
597 	g_signal_connect(m_im_context.get(), "preedit-end",
598 			 G_CALLBACK(im_preedit_end_cb), this);
599 	g_signal_connect(m_im_context.get(), "retrieve-surrounding",
600 			 G_CALLBACK(im_retrieve_surrounding_cb), this);
601 	g_signal_connect(m_im_context.get(), "delete-surrounding",
602 			 G_CALLBACK(im_delete_surrounding_cb), this);
603 	gtk_im_context_set_use_preedit(m_im_context.get(), true);
604 
605         m_clipboard = std::make_shared<Clipboard>(*this, ClipboardType::CLIPBOARD);
606         m_primary_clipboard = std::make_shared<Clipboard>(*this, ClipboardType::PRIMARY);
607 
608         m_terminal->widget_realize();
609 }
610 
611 void
screen_changed(GdkScreen * previous_screen)612 Widget::screen_changed(GdkScreen *previous_screen) noexcept
613 {
614         auto gdk_screen = gtk_widget_get_screen(m_widget);
615         if (previous_screen != nullptr &&
616             (gdk_screen != previous_screen || gdk_screen == nullptr)) {
617                 auto settings = gtk_settings_get_for_screen(previous_screen);
618                 g_signal_handlers_disconnect_matched(settings, G_SIGNAL_MATCH_DATA,
619                                                      0, 0, nullptr, nullptr,
620                                                      this);
621         }
622 
623         if (gdk_screen == previous_screen || gdk_screen == nullptr)
624                 return;
625 
626         settings_changed();
627 
628         auto settings = gtk_widget_get_settings(m_widget);
629         g_signal_connect (settings, "notify::gtk-cursor-blink",
630                           G_CALLBACK(settings_notify_cb), this);
631         g_signal_connect (settings, "notify::gtk-cursor-blink-time",
632                           G_CALLBACK(settings_notify_cb), this);
633         g_signal_connect (settings, "notify::gtk-cursor-blink-timeout",
634                           G_CALLBACK(settings_notify_cb), this);
635 }
636 
637 void
settings_changed()638 Widget::settings_changed() noexcept
639 {
640         auto blink = gboolean{};
641         auto blink_time = int{};
642         auto blink_timeout = int{};
643         g_object_get(gtk_widget_get_settings(m_widget),
644                      "gtk-cursor-blink", &blink,
645                      "gtk-cursor-blink-time", &blink_time,
646                      "gtk-cursor-blink-timeout", &blink_timeout,
647                      nullptr);
648 
649         _vte_debug_print(VTE_DEBUG_MISC,
650                          "Cursor blinking settings: blink=%d time=%d timeout=%d\n",
651                          blink, blink_time, blink_timeout);
652 
653         m_terminal->set_blink_settings(blink, blink_time, blink_timeout);
654 }
655 
656 void
set_cursor(CursorType type)657 Widget::set_cursor(CursorType type) noexcept
658 {
659         switch (type) {
660         case CursorType::eDefault:   return set_cursor(m_default_cursor.get());
661         case CursorType::eInvisible: return set_cursor(m_invisible_cursor.get());
662         case CursorType::eMousing:   return set_cursor(m_mousing_cursor.get());
663         case CursorType::eHyperlink: return set_cursor(m_hyperlink_cursor.get());
664         }
665 }
666 
667 bool
set_pty(VtePty * pty_obj)668 Widget::set_pty(VtePty* pty_obj) noexcept
669 {
670         if (pty() == pty_obj)
671                 return false;
672 
673         m_pty = vte::glib::make_ref(pty_obj);
674         terminal()->set_pty(_vte_pty_get_impl(pty()));
675 
676         return true;
677 }
678 
679 bool
set_word_char_exceptions(std::optional<std::string_view> stropt)680 Widget::set_word_char_exceptions(std::optional<std::string_view> stropt)
681 {
682         if (m_word_char_exceptions == stropt)
683                 return false;
684 
685         if (terminal()->set_word_char_exceptions(stropt)) {
686                 m_word_char_exceptions = stropt;
687                 return true;
688         }
689 
690         return false;
691 }
692 
693 void
unset_pty()694 Widget::unset_pty() noexcept
695 {
696         if (!pty())
697                 return;
698 
699         /* This is only called from Terminal, so we need
700          * to explicitly notify the VteTerminal:pty property,
701          * but we do NOT need to call Terminal::set_pty(nullptr).
702          */
703         m_pty.reset();
704         g_object_notify_by_pspec(object(), pspecs[PROP_PTY]);
705 }
706 
707 void
size_allocate(GtkAllocation * allocation)708 Widget::size_allocate(GtkAllocation* allocation) noexcept
709 {
710         m_terminal->widget_size_allocate(allocation);
711 
712         if (realized())
713 		gdk_window_move_resize(m_event_window,
714                                        allocation->x,
715                                        allocation->y,
716                                        allocation->width,
717                                        allocation->height);
718 }
719 
720 bool
should_emit_signal(int id)721 Widget::should_emit_signal(int id) noexcept
722 {
723         return g_signal_has_handler_pending(object(),
724                                             signals[id],
725                                             0 /* detail */,
726                                             false /* not interested in blocked handlers */) != FALSE;
727 }
728 
729 void
style_updated()730 Widget::style_updated() noexcept
731 {
732         auto padding = GtkBorder{};
733         auto context = gtk_widget_get_style_context(gtk());
734         gtk_style_context_get_padding(context, gtk_style_context_get_state(context),
735                                       &padding);
736         m_terminal->set_border_padding(&padding);
737 
738         auto aspect = float{};
739         gtk_widget_style_get(gtk(), "cursor-aspect-ratio", &aspect, nullptr);
740         m_terminal->set_cursor_aspect(aspect);
741 
742         m_terminal->widget_style_updated();
743 }
744 
745 void
unmap()746 Widget::unmap() noexcept
747 {
748         m_terminal->widget_unmap();
749 
750         if (m_event_window)
751                 gdk_window_hide(m_event_window);
752 }
753 
754 void
unrealize()755 Widget::unrealize() noexcept
756 {
757         m_terminal->widget_unrealize();
758 
759         if (m_clipboard) {
760                 terminal()->widget_clipboard_data_clear(*m_clipboard);
761                 m_clipboard->disown();
762         }
763         if (m_primary_clipboard) {
764                 terminal()->widget_clipboard_data_clear(*m_primary_clipboard);
765                 m_primary_clipboard->disown();
766         }
767         m_clipboard.reset();
768         m_primary_clipboard.reset();
769 
770         m_default_cursor.reset();
771         m_invisible_cursor.reset();
772         m_mousing_cursor.reset();
773         m_hyperlink_cursor.reset();
774 
775 	/* Shut down input methods. */
776         assert(m_im_context);
777         g_signal_handlers_disconnect_matched(m_im_context.get(),
778                                              G_SIGNAL_MATCH_DATA,
779                                              0, 0, NULL, NULL,
780                                              this);
781         m_terminal->im_preedit_reset();
782         gtk_im_context_set_client_window(m_im_context.get(), nullptr);
783         m_im_context.reset();
784 
785         /* Destroy input window */
786         gtk_widget_unregister_window(m_widget, m_event_window);
787         gdk_window_destroy(m_event_window);
788         m_event_window = nullptr;
789 }
790 
791 } // namespace platform
792 
793 } // namespace vte
794