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