xref: /core/vcl/unx/gtk3/gtkframe.cxx (revision bee080ab)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_version.h>
21 
22 #include <unx/gtk/gtkframe.hxx>
23 #include <unx/gtk/gtkdata.hxx>
24 #include <unx/gtk/gtkinst.hxx>
25 #include <unx/gtk/gtkgdi.hxx>
26 #include <unx/gtk/gtksalmenu.hxx>
27 #include <unx/gtk/hudawareness.h>
28 #include <vcl/event.hxx>
29 #include <vcl/i18nhelp.hxx>
30 #include <vcl/keycodes.hxx>
31 #include <unx/geninst.h>
32 #include <headless/svpgdi.hxx>
33 #include <sal/log.hxx>
34 #include <comphelper/diagnose_ex.hxx>
35 #include <vcl/toolkit/floatwin.hxx>
36 #include <vcl/toolkit/unowrap.hxx>
37 #include <vcl/svapp.hxx>
38 #include <vcl/weld.hxx>
39 #include <vcl/window.hxx>
40 #include <vcl/settings.hxx>
41 
42 #include <gtk/gtk.h>
43 
44 #include <X11/Xlib.h>
45 #include <X11/Xutil.h>
46 #include <unx/gtk/gtkbackend.hxx>
47 
48 #include <strings.hrc>
49 #include <window.h>
50 
51 #include <basegfx/vector/b2ivector.hxx>
52 #include <officecfg/Office/Common.hxx>
53 
54 #include <dlfcn.h>
55 
56 #include <algorithm>
57 
58 #if OSL_DEBUG_LEVEL > 1
59 #  include <cstdio>
60 #endif
61 
62 #include <i18nlangtag/mslangid.hxx>
63 
64 #include <cstdlib>
65 #include <cmath>
66 
67 #include <com/sun/star/awt/MouseButton.hpp>
68 #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
69 #include <com/sun/star/frame/Desktop.hpp>
70 #include <com/sun/star/util/XModifiable.hpp>
71 
72 #if !GTK_CHECK_VERSION(4, 0, 0)
73 #   define GDK_ALT_MASK GDK_MOD1_MASK
74 #   define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED
75 #   define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED
76 #   define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface
77 #   define gdk_x11_surface_get_xid gdk_x11_window_get_xid
78 #endif
79 
80 using namespace com::sun::star;
81 
82 int GtkSalFrame::m_nFloats = 0;
83 
84 static GDBusConnection* pSessionBus = nullptr;
85 
86 static void EnsureSessionBus()
87 {
88     if (!pSessionBus)
89         pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
90 }
91 
92 sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
93 {
94     sal_uInt16 nCode = 0;
95     if( state & GDK_SHIFT_MASK )
96         nCode |= KEY_SHIFT;
97     if( state & GDK_CONTROL_MASK )
98         nCode |= KEY_MOD1;
99     if (state & GDK_ALT_MASK)
100         nCode |= KEY_MOD2;
101     if( state & GDK_SUPER_MASK )
102         nCode |= KEY_MOD3;
103     return nCode;
104 }
105 
106 sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
107 {
108     sal_uInt16 nCode = GetKeyModCode( state );
109     if( state & GDK_BUTTON1_MASK )
110         nCode |= MOUSE_LEFT;
111     if( state & GDK_BUTTON2_MASK )
112         nCode |= MOUSE_MIDDLE;
113     if( state & GDK_BUTTON3_MASK )
114         nCode |= MOUSE_RIGHT;
115 
116     return nCode;
117 }
118 
119 // KEY_F26 is the last function key known to keycodes.hxx
120 static bool IsFunctionKeyVal(guint keyval)
121 {
122     return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
123 }
124 
125 sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
126 {
127     sal_uInt16 nCode = 0;
128     if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
129         nCode = KEY_0 + (keyval-GDK_KEY_0);
130     else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
131         nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
132     else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
133         nCode = KEY_A + (keyval-GDK_KEY_A );
134     else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
135         nCode = KEY_A + (keyval-GDK_KEY_a );
136     else if (IsFunctionKeyVal(keyval))
137     {
138         switch( keyval )
139         {
140             // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
141             // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
142             // gdk/gdkkeysyms.h, they are unlikely to be generated
143             // except possibly by Solaris systems
144             // this whole section needs review
145             case GDK_KEY_L2:
146                     nCode = KEY_F12;
147                 break;
148             case GDK_KEY_L3:            nCode = KEY_PROPERTIES; break;
149             case GDK_KEY_L4:            nCode = KEY_UNDO;       break;
150             case GDK_KEY_L6:            nCode = KEY_COPY;       break; // KEY_F16
151             case GDK_KEY_L8:            nCode = KEY_PASTE;      break; // KEY_F18
152             case GDK_KEY_L10:           nCode = KEY_CUT;        break; // KEY_F20
153             default:
154                 nCode = KEY_F1 + (keyval-GDK_KEY_F1);           break;
155         }
156     }
157     else
158     {
159         switch( keyval )
160         {
161             case GDK_KEY_KP_Down:
162             case GDK_KEY_Down:          nCode = KEY_DOWN;       break;
163             case GDK_KEY_KP_Up:
164             case GDK_KEY_Up:            nCode = KEY_UP;         break;
165             case GDK_KEY_KP_Left:
166             case GDK_KEY_Left:          nCode = KEY_LEFT;       break;
167             case GDK_KEY_KP_Right:
168             case GDK_KEY_Right:         nCode = KEY_RIGHT;      break;
169             case GDK_KEY_KP_Begin:
170             case GDK_KEY_KP_Home:
171             case GDK_KEY_Begin:
172             case GDK_KEY_Home:          nCode = KEY_HOME;       break;
173             case GDK_KEY_KP_End:
174             case GDK_KEY_End:           nCode = KEY_END;        break;
175             case GDK_KEY_KP_Page_Up:
176             case GDK_KEY_Page_Up:       nCode = KEY_PAGEUP;     break;
177             case GDK_KEY_KP_Page_Down:
178             case GDK_KEY_Page_Down:     nCode = KEY_PAGEDOWN;   break;
179             case GDK_KEY_KP_Enter:
180             case GDK_KEY_Return:        nCode = KEY_RETURN;     break;
181             case GDK_KEY_Escape:        nCode = KEY_ESCAPE;     break;
182             case GDK_KEY_ISO_Left_Tab:
183             case GDK_KEY_KP_Tab:
184             case GDK_KEY_Tab:           nCode = KEY_TAB;        break;
185             case GDK_KEY_BackSpace:     nCode = KEY_BACKSPACE;  break;
186             case GDK_KEY_KP_Space:
187             case GDK_KEY_space:         nCode = KEY_SPACE;      break;
188             case GDK_KEY_KP_Insert:
189             case GDK_KEY_Insert:        nCode = KEY_INSERT;     break;
190             case GDK_KEY_KP_Delete:
191             case GDK_KEY_Delete:        nCode = KEY_DELETE;     break;
192             case GDK_KEY_plus:
193             case GDK_KEY_KP_Add:        nCode = KEY_ADD;        break;
194             case GDK_KEY_minus:
195             case GDK_KEY_KP_Subtract:   nCode = KEY_SUBTRACT;   break;
196             case GDK_KEY_asterisk:
197             case GDK_KEY_KP_Multiply:   nCode = KEY_MULTIPLY;   break;
198             case GDK_KEY_slash:
199             case GDK_KEY_KP_Divide:     nCode = KEY_DIVIDE;     break;
200             case GDK_KEY_period:        nCode = KEY_POINT;      break;
201             case GDK_KEY_decimalpoint:  nCode = KEY_POINT;      break;
202             case GDK_KEY_comma:         nCode = KEY_COMMA;      break;
203             case GDK_KEY_less:          nCode = KEY_LESS;       break;
204             case GDK_KEY_greater:       nCode = KEY_GREATER;    break;
205             case GDK_KEY_KP_Equal:
206             case GDK_KEY_equal:         nCode = KEY_EQUAL;      break;
207             case GDK_KEY_Find:          nCode = KEY_FIND;       break;
208             case GDK_KEY_Menu:          nCode = KEY_CONTEXTMENU;break;
209             case GDK_KEY_Help:          nCode = KEY_HELP;       break;
210             case GDK_KEY_Undo:          nCode = KEY_UNDO;       break;
211             case GDK_KEY_Redo:          nCode = KEY_REPEAT;     break;
212             // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
213             // but VCL doesn't have a key definition for that
214             case GDK_KEY_Cancel:        nCode = KEY_F11;        break;
215             case GDK_KEY_KP_Decimal:
216             case GDK_KEY_KP_Separator:  nCode = KEY_DECIMAL;    break;
217             case GDK_KEY_asciitilde:    nCode = KEY_TILDE;      break;
218             case GDK_KEY_leftsinglequotemark:
219             case GDK_KEY_quoteleft:    nCode = KEY_QUOTELEFT;    break;
220             case GDK_KEY_bracketleft:  nCode = KEY_BRACKETLEFT;  break;
221             case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
222             case GDK_KEY_semicolon:    nCode = KEY_SEMICOLON;    break;
223             case GDK_KEY_quoteright:   nCode = KEY_QUOTERIGHT;   break;
224             case GDK_KEY_braceright:   nCode = KEY_RIGHTCURLYBRACKET;   break;
225             case GDK_KEY_numbersign:   nCode = KEY_NUMBERSIGN; break;
226             case GDK_KEY_Forward:      nCode = KEY_XF86FORWARD; break;
227             case GDK_KEY_Back:         nCode = KEY_XF86BACK; break;
228             case GDK_KEY_colon:    nCode = KEY_COLON;    break;
229             // some special cases, also see saldisp.cxx
230             // - - - - - - - - - - - - -  Apollo - - - - - - - - - - - - - 0x1000
231             // These can be found in ap_keysym.h
232             case 0x1000FF02: // apXK_Copy
233                 nCode = KEY_COPY;
234                 break;
235             case 0x1000FF03: // apXK_Cut
236                 nCode = KEY_CUT;
237                 break;
238             case 0x1000FF04: // apXK_Paste
239                 nCode = KEY_PASTE;
240                 break;
241             case 0x1000FF14: // apXK_Repeat
242                 nCode = KEY_REPEAT;
243                 break;
244             // Exit, Save
245             // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
246             // These can be found in DECkeysym.h
247             case 0x1000FF00:
248                 nCode = KEY_DELETE;
249                 break;
250             // - - - - - - - - - - - - - -  H P  - - - - - - - - - - - - - 0x1000
251             // These can be found in HPkeysym.h
252             case 0x1000FF73: // hpXK_DeleteChar
253                 nCode = KEY_DELETE;
254                 break;
255             case 0x1000FF74: // hpXK_BackTab
256             case 0x1000FF75: // hpXK_KP_BackTab
257                 nCode = KEY_TAB;
258                 break;
259             // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
260             // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
261             // These also can be found in HPkeysym.h
262             case 0x1004FF02: // osfXK_Copy
263                 nCode = KEY_COPY;
264                 break;
265             case 0x1004FF03: // osfXK_Cut
266                 nCode = KEY_CUT;
267                 break;
268             case 0x1004FF04: // osfXK_Paste
269                 nCode = KEY_PASTE;
270                 break;
271             case 0x1004FF07: // osfXK_BackTab
272                 nCode = KEY_TAB;
273                 break;
274             case 0x1004FF08: // osfXK_BackSpace
275                 nCode = KEY_BACKSPACE;
276                 break;
277             case 0x1004FF1B: // osfXK_Escape
278                 nCode = KEY_ESCAPE;
279                 break;
280             // Up, Down, Left, Right, PageUp, PageDown
281             // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
282             // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
283             // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
284             // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
285             // These can be found in Sunkeysym.h
286             case 0x1005FF10: // SunXK_F36
287                 nCode = KEY_F11;
288                 break;
289             case 0x1005FF11: // SunXK_F37
290                 nCode = KEY_F12;
291                 break;
292             case 0x1005FF70: // SunXK_Props
293                 nCode = KEY_PROPERTIES;
294                 break;
295             case 0x1005FF71: // SunXK_Front
296                 nCode = KEY_FRONT;
297                 break;
298             case 0x1005FF72: // SunXK_Copy
299                 nCode = KEY_COPY;
300                 break;
301             case 0x1005FF73: // SunXK_Open
302                 nCode = KEY_OPEN;
303                 break;
304             case 0x1005FF74: // SunXK_Paste
305                 nCode = KEY_PASTE;
306                 break;
307             case 0x1005FF75: // SunXK_Cut
308                 nCode = KEY_CUT;
309                 break;
310             // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
311             // These can be found in XF86keysym.h
312             // but more importantly they are also available GTK/Gdk version 3
313             // and hence are already provided in gdk/gdkkeysyms.h, and hence
314             // in gdk/gdk.h
315             case GDK_KEY_Copy:          nCode = KEY_COPY;  break; // 0x1008ff57
316             case GDK_KEY_Cut:           nCode = KEY_CUT;   break; // 0x1008ff58
317             case GDK_KEY_Open:          nCode = KEY_OPEN;  break; // 0x1008ff6b
318             case GDK_KEY_Paste:         nCode = KEY_PASTE; break; // 0x1008ff6d
319         }
320     }
321 
322     return nCode;
323 }
324 
325 #if !GTK_CHECK_VERSION(4, 0, 0)
326 guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
327 {
328     guint updated_keyval = 0;
329     gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
330         GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
331     return updated_keyval;
332 }
333 #endif
334 
335 namespace {
336 
337 // F10 means either KEY_F10 or KEY_MENU, which has to be decided
338 // in the independent part.
339 struct KeyAlternate
340 {
341     sal_uInt16          nKeyCode;
342     sal_Unicode     nCharCode;
343     KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
344     KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
345 };
346 
347 }
348 
349 static KeyAlternate
350 GetAlternateKeyCode( const sal_uInt16 nKeyCode )
351 {
352     KeyAlternate aAlternate;
353 
354     switch( nKeyCode )
355     {
356         case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
357         case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
358     }
359 
360     return aAlternate;
361 }
362 
363 #if OSL_DEBUG_LEVEL > 0
364 static bool dumpframes = false;
365 #endif
366 
367 bool GtkSalFrame::doKeyCallback( guint state,
368                                  guint keyval,
369                                  guint16 hardware_keycode,
370                                  guint8 group,
371                                  sal_Unicode aOrigCode,
372                                  bool bDown,
373                                  bool bSendRelease
374                                  )
375 {
376     SalKeyEvent aEvent;
377 
378     aEvent.mnCharCode       = aOrigCode;
379     aEvent.mnRepeat         = 0;
380 
381     vcl::DeletionListener aDel( this );
382 
383 #if OSL_DEBUG_LEVEL > 0
384     const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");
385 
386     if (pKeyDebug && *pKeyDebug == '1')
387     {
388         if (bDown)
389         {
390             // shift-zero forces a re-draw and event is swallowed
391             if (keyval == GDK_KEY_0)
392             {
393                 SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
394                 gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
395                 return false;
396             }
397             else if (keyval == GDK_KEY_1)
398             {
399                 SAL_INFO("vcl.gtk3", "force repaint all.");
400                 TriggerPaintEvent();
401                 return false;
402             }
403             else if (keyval == GDK_KEY_2)
404             {
405                 dumpframes = !dumpframes;
406                 SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
407                 return false;
408             }
409         }
410     }
411 #endif
412 
413     /*
414      *  #i42122# translate all keys with Ctrl and/or Alt to group 0 else
415      *  shortcuts (e.g. Ctrl-o) will not work but be inserted by the
416      *  application
417      *
418      *  #i52338# do this for all keys that the independent part has no key code
419      *  for
420      *
421      *  fdo#41169 rather than use group 0, detect if there is a group which can
422      *  be used to input Latin text and use that if possible
423      */
424     aEvent.mnCode = GetKeyCode( keyval );
425 #if !GTK_CHECK_VERSION(4, 0, 0)
426     if( aEvent.mnCode == 0 )
427     {
428         gint best_group = SAL_MAX_INT32;
429 
430         // Try and find Latin layout
431         GdkKeymap* keymap = gdk_keymap_get_default();
432         GdkKeymapKey *keys;
433         gint n_keys;
434         if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
435         {
436             // Find the lowest group that supports Latin layout
437             for (gint i = 0; i < n_keys; ++i)
438             {
439                 if (keys[i].level != 0 && keys[i].level != 1)
440                     continue;
441                 best_group = std::min(best_group, keys[i].group);
442                 if (best_group == 0)
443                     break;
444             }
445             g_free(keys);
446         }
447 
448         //Unavailable, go with original group then I suppose
449         if (best_group == SAL_MAX_INT32)
450             best_group = group;
451 
452         guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
453         aEvent.mnCode = GetKeyCode(updated_keyval);
454     }
455 #else
456     (void)hardware_keycode;
457     (void)group;
458 #endif
459 
460     aEvent.mnCode   |= GetKeyModCode( state );
461 
462     bool bStopProcessingKey;
463     if (bDown)
464     {
465         // tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
466         // certain cases such as pressing Control-Alt-C in a Writer document while
467         // there is uncommitted text will call GtkSalFrame::EndExtTextInput() which
468         // will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
469         // event will delete the uncommitted text and then insert the committed text
470         // but LibreOffice will crash when deleting the uncommitted text because
471         // deletion of the text also removes and deletes the newly inserted comment.
472         if (m_pIMHandler && !m_pIMHandler->m_aInputEvent.maText.isEmpty() && (aEvent.mnCode & (KEY_MOD1 | KEY_MOD2)))
473             m_pIMHandler->doCallEndExtTextInput();
474 
475         bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
476         // #i46889# copy AlternateKeyCode handling from generic plugin
477         if (!bStopProcessingKey)
478         {
479             KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
480             if( aAlternate.nKeyCode )
481             {
482                 aEvent.mnCode = aAlternate.nKeyCode;
483                 if( aAlternate.nCharCode )
484                     aEvent.mnCharCode = aAlternate.nCharCode;
485                 bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
486             }
487         }
488         if( bSendRelease && ! aDel.isDeleted() )
489         {
490             CallCallbackExc(SalEvent::KeyUp, &aEvent);
491         }
492     }
493     else
494         bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
495     return bStopProcessingKey;
496 }
497 
498 GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
499     : m_nXScreen( getDisplay()->GetDefaultXScreen() )
500     , m_pHeaderBar(nullptr)
501     , m_bGraphics(false)
502     , m_nSetFocusSignalId(0)
503 #if !GTK_CHECK_VERSION(4, 0, 0)
504     , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
505 #endif
506 {
507     getDisplay()->registerFrame( this );
508     m_bDefaultPos       = true;
509     m_bDefaultSize      = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
510     Init( pParent, nStyle );
511 }
512 
513 GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
514     : m_nXScreen( getDisplay()->GetDefaultXScreen() )
515     , m_pHeaderBar(nullptr)
516     , m_bGraphics(false)
517     , m_nSetFocusSignalId(0)
518 #if !GTK_CHECK_VERSION(4, 0, 0)
519     , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
520 #endif
521 {
522     getDisplay()->registerFrame( this );
523     // permanently ignore errors from our unruly children ...
524     GetGenericUnixSalData()->ErrorTrapPush();
525     m_bDefaultPos       = true;
526     m_bDefaultSize      = true;
527     Init( pSysData );
528 }
529 
530 // AppMenu watch functions.
531 
532 static void ObjectDestroyedNotify( gpointer data )
533 {
534     if ( data ) {
535         g_object_unref( data );
536     }
537 }
538 
539 #if !GTK_CHECK_VERSION(4,0,0)
540 static void hud_activated( gboolean hud_active, gpointer user_data )
541 {
542     if ( hud_active )
543     {
544         SolarMutexGuard aGuard;
545         GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
546         GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );
547 
548         if ( pSalMenu )
549             pSalMenu->UpdateFull();
550     }
551 }
552 #endif
553 
554 static void attach_menu_model(GtkSalFrame* pSalFrame)
555 {
556     GtkWidget* pWidget = pSalFrame->getWindow();
557     GdkSurface* gdkWindow = widget_get_surface(pWidget);
558 
559     if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr )
560         return;
561 
562     // Create menu model and action group attached to this frame.
563     GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
564     GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());
565 
566     // Set window properties.
567     g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
568     g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );
569 
570 #if !GTK_CHECK_VERSION(4,0,0)
571     // Get a DBus session connection.
572     EnsureSessionBus();
573     if (!pSessionBus)
574         return;
575 
576     // Generate menu paths.
577     sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget);
578     gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
579     gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );
580 
581     GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
582 #if defined(GDK_WINDOWING_X11)
583     if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
584     {
585         gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
586         gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
587         gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
588         gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
589         gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
590     }
591 #endif
592 #if defined(GDK_WINDOWING_WAYLAND)
593     if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
594     {
595         gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
596                                                            nullptr,
597                                                            aDBusMenubarPath,
598                                                            aDBusWindowPath,
599                                                            "/org/libreoffice",
600                                                            g_dbus_connection_get_unique_name( pSessionBus ));
601     }
602 #endif
603     // Publish the menu model and the action group.
604     SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
605     pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
606     SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
607     pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
608     pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );
609 
610     g_free( aDBusWindowPath );
611     g_free( aDBusMenubarPath );
612 #endif
613 }
614 
615 void on_registrar_available( GDBusConnection * /*connection*/,
616                              const gchar     * /*name*/,
617                              const gchar     * /*name_owner*/,
618                              gpointer         user_data )
619 {
620     SolarMutexGuard aGuard;
621 
622     GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
623 
624     SalMenu* pSalMenu = pSalFrame->GetMenu();
625 
626     if ( pSalMenu != nullptr )
627     {
628         GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
629         pGtkSalMenu->EnableUnity(true);
630     }
631 }
632 
633 // This is called when the registrar becomes unavailable. It shows the menubar.
634 void on_registrar_unavailable( GDBusConnection * /*connection*/,
635                                const gchar     * /*name*/,
636                                gpointer         user_data )
637 {
638     SolarMutexGuard aGuard;
639 
640     SAL_INFO("vcl.unity", "on_registrar_unavailable");
641 
642     GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
643 
644     SalMenu* pSalMenu = pSalFrame->GetMenu();
645 
646     if ( pSalMenu ) {
647         GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
648         pGtkSalMenu->EnableUnity(false);
649     }
650 }
651 
652 void GtkSalFrame::EnsureAppMenuWatch()
653 {
654     if ( m_nWatcherId )
655         return;
656 
657     // Get a DBus session connection.
658     EnsureSessionBus();
659     if (!pSessionBus)
660         return;
661 
662     // Publish the menu only if AppMenu registrar is available.
663     m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
664                                                    "com.canonical.AppMenu.Registrar",
665                                                    G_BUS_NAME_WATCHER_FLAGS_NONE,
666                                                    on_registrar_available,
667                                                    on_registrar_unavailable,
668                                                    this,
669                                                    nullptr );
670 }
671 
672 void GtkSalFrame::InvalidateGraphics()
673 {
674     if( m_pGraphics )
675     {
676         m_bGraphics = false;
677     }
678 }
679 
680 GtkSalFrame::~GtkSalFrame()
681 {
682 #if !GTK_CHECK_VERSION(4,0,0)
683     m_aSmoothScrollIdle.Stop();
684     m_aSmoothScrollIdle.ClearInvokeHandler();
685 #endif
686 
687     if (m_pDropTarget)
688     {
689         m_pDropTarget->deinitialize();
690         m_pDropTarget = nullptr;
691     }
692 
693     if (m_pDragSource)
694     {
695         m_pDragSource->deinitialize();
696         m_pDragSource= nullptr;
697     }
698 
699     InvalidateGraphics();
700 
701     if (m_pParent)
702     {
703         m_pParent->m_aChildren.remove( this );
704     }
705 
706     getDisplay()->deregisterFrame( this );
707 
708     if( m_pRegion )
709     {
710         cairo_region_destroy( m_pRegion );
711     }
712 
713     m_pIMHandler.reset();
714 
715     //tdf#108705 remove grabs on event widget before
716     //destroying event widget
717     while (m_nGrabLevel)
718         removeGrabLevel();
719 
720     {
721         SolarMutexGuard aGuard;
722 
723         if (m_nWatcherId)
724             g_bus_unwatch_name(m_nWatcherId);
725 
726         if (m_nPortalSettingChangedSignalId)
727             g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
728 
729         if (m_pSettingsPortal)
730             g_object_unref(m_pSettingsPortal);
731 
732         if (m_nSessionClientSignalId)
733             g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);
734 
735         if (m_pSessionClient)
736             g_object_unref(m_pSessionClient);
737 
738         if (m_pSessionManager)
739             g_object_unref(m_pSessionManager);
740     }
741 
742     GtkWidget *pEventWidget = getMouseEventWidget();
743     for (auto handler_id : m_aMouseSignalIds)
744         g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
745 
746 #if !GTK_CHECK_VERSION(4, 0, 0)
747     if( m_pFixedContainer )
748         gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
749     if( m_pEventBox )
750         gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
751     if( m_pTopLevelGrid )
752         gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
753 #else
754     g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId);
755 #endif
756     {
757         SolarMutexGuard aGuard;
758 
759         if( m_pWindow )
760         {
761             g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );
762 
763             if ( pSessionBus )
764             {
765                 if ( m_nHudAwarenessId )
766                     hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
767                 if ( m_nMenuExportId )
768                     g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
769                 if ( m_nActionGroupExportId )
770                     g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
771             }
772             m_xFrameWeld.reset();
773 #if !GTK_CHECK_VERSION(4,0,0)
774             gtk_widget_destroy( m_pWindow );
775 #else
776             if (GTK_IS_WINDOW(m_pWindow))
777                 gtk_window_destroy(GTK_WINDOW(m_pWindow));
778             else
779                 g_clear_pointer(&m_pWindow, gtk_widget_unparent);
780 #endif
781         }
782     }
783 
784 #if !GTK_CHECK_VERSION(4,0,0)
785     if( m_pForeignParent )
786         g_object_unref( G_OBJECT( m_pForeignParent ) );
787     if( m_pForeignTopLevel )
788         g_object_unref( G_OBJECT( m_pForeignTopLevel) );
789 #endif
790 
791     m_pGraphics.reset();
792 
793     if (m_pSurface)
794         cairo_surface_destroy(m_pSurface);
795 }
796 
797 void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
798 {
799     if( isChild( false ) )
800     {
801         GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
802         // tdf#130414 it's possible that we were reparented and are no longer inside
803         // our original GtkFixed parent
804         if (pParent && GTK_IS_FIXED(pParent))
805         {
806             gtk_fixed_move( GTK_FIXED(pParent),
807                             m_pWindow,
808                             nX - m_pParent->maGeometry.x(), nY - m_pParent->maGeometry.y() );
809         }
810         return;
811     }
812 #if GTK_CHECK_VERSION(4,0,0)
813     if (GTK_IS_POPOVER(m_pWindow))
814     {
815         GdkRectangle aRect;
816         aRect.x = nX;
817         aRect.y = nY;
818         aRect.width = 1;
819         aRect.height = 1;
820         gtk_popover_set_pointing_to(GTK_POPOVER(m_pWindow), &aRect);
821         return;
822     }
823 #else
824     gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
825 #endif
826 }
827 
828 void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
829 {
830     gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
831 #if GTK_CHECK_VERSION(4,0,0)
832     gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight );
833 #endif
834 }
835 
836 void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
837 {
838     m_nWidthRequest = nWidth;
839     m_nHeightRequest = nHeight;
840     if (!GTK_IS_WINDOW(m_pWindow))
841     {
842 #if GTK_CHECK_VERSION(4,0,0)
843         gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight);
844 #endif
845         return;
846     }
847     gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
848 #if !GTK_CHECK_VERSION(4,0,0)
849     gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
850 #endif
851 }
852 
853 void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
854 {
855     if( isChild( false ) )
856     {
857         widget_set_size_request(nWidth, nHeight);
858     }
859     else if( ! isChild( true, false ) )
860         window_resize(nWidth, nHeight);
861 }
862 
863 #if !GTK_CHECK_VERSION(4,0,0)
864 // tdf#124694 GtkFixed takes the max size of all its children as its
865 // preferred size, causing it to not clip its child, but grow instead.
866 
867 static void
868 ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
869 {
870     *minimum = 0;
871     *natural = 0;
872 }
873 
874 static void
875 ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
876 {
877     *minimum = 0;
878     *natural = 0;
879 }
880 
881 static void
882 ooo_fixed_class_init(gpointer klass_, gpointer)
883 {
884     auto const klass = static_cast<GtkFixedClass *>(klass_);
885     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
886     widget_class->get_accessible = ooo_fixed_get_accessible;
887     widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
888     widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
889 }
890 
891 /*
892  * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
893  * utilize GAIL for the toplevel window and toolkit implementation incl.
894  * key event listener support ..
895  */
896 
897 GType
898 ooo_fixed_get_type()
899 {
900     static GType type = 0;
901 
902     if (!type) {
903         static const GTypeInfo tinfo =
904         {
905             sizeof (GtkFixedClass),
906             nullptr,      /* base init */
907             nullptr,  /* base finalize */
908             ooo_fixed_class_init, /* class init */
909             nullptr, /* class finalize */
910             nullptr,                      /* class data */
911             sizeof (GtkFixed),         /* instance size */
912             0,                         /* nb preallocs */
913             nullptr,  /* instance init */
914             nullptr                       /* value table */
915         };
916 
917         type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
918                                        &tinfo, GTypeFlags(0));
919     }
920 
921     return type;
922 }
923 
924 #endif
925 
926 void GtkSalFrame::updateScreenNumber()
927 {
928 #if !GTK_CHECK_VERSION(4,0,0)
929     int nScreen = 0;
930     GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
931     if( pScreen )
932         nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.x(), maGeometry.y() );
933     maGeometry.setScreen(nScreen);
934 #endif
935 }
936 
937 GtkWidget *GtkSalFrame::getMouseEventWidget() const
938 {
939 #if !GTK_CHECK_VERSION(4,0,0)
940     return GTK_WIDGET(m_pEventBox);
941 #else
942     return GTK_WIDGET(m_pFixedContainer);
943 #endif
944 }
945 
946 static void damaged(void *handle,
947                     sal_Int32 nExtentsX, sal_Int32 nExtentsY,
948                     sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
949 {
950     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
951     pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
952 }
953 
954 #if !GTK_CHECK_VERSION(4,0,0)
955 static void notifyUnref(gpointer data, GObject *) { g_object_unref(data); }
956 #endif
957 
958 void GtkSalFrame::InitCommon()
959 {
960     m_pSurface = nullptr;
961     m_nGrabLevel = 0;
962     m_bSalObjectSetPosSize = false;
963     m_nPortalSettingChangedSignalId = 0;
964     m_nSessionClientSignalId = 0;
965     m_pSettingsPortal = nullptr;
966     m_pSessionManager = nullptr;
967     m_pSessionClient = nullptr;
968 
969     m_aDamageHandler.handle = this;
970     m_aDamageHandler.damaged = ::damaged;
971 
972 #if !GTK_CHECK_VERSION(4,0,0)
973     m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
974 #endif
975 
976     m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
977     container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid));
978 
979 #if !GTK_CHECK_VERSION(4,0,0)
980     m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
981     gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
982                            GDK_ALL_EVENTS_MASK );
983     gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
984     gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
985     gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
986 #endif
987 
988     // add the fixed container child,
989     // fixed is needed since we have to position plugin windows
990 #if !GTK_CHECK_VERSION(4,0,0)
991     m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
992     m_pDrawingArea = m_pFixedContainer;
993 #else
994     m_pOverlay = GTK_OVERLAY(gtk_overlay_new());
995 #if GTK_CHECK_VERSION(4,9,0)
996     m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
997 #else
998     m_pFixedContainer = GTK_FIXED(gtk_fixed_new());
999 #endif
1000     m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
1001 #endif
1002     if (GTK_IS_WINDOW(m_pWindow))
1003     {
1004         Size aDefWindowSize = calcDefaultSize();
1005         gtk_window_set_default_size(GTK_WINDOW(m_pWindow), aDefWindowSize.Width(), aDefWindowSize.Height());
1006     }
1007     gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
1008     gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
1009 #if !GTK_CHECK_VERSION(4,0,0)
1010     gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
1011 #else
1012     gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true);
1013     gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true);
1014     gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1);
1015     gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea));
1016     gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer));
1017 #endif
1018 
1019     GtkWidget *pEventWidget = getMouseEventWidget();
1020 #if !GTK_CHECK_VERSION(4,0,0)
1021     gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
1022     gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
1023 #endif
1024 
1025 #if GTK_CHECK_VERSION(4,0,0)
1026     m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this);
1027 #else
1028     // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
1029     g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this);
1030 #endif
1031     gtk_widget_set_has_tooltip(pEventWidget, true);
1032     // connect signals
1033     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
1034 #if !GTK_CHECK_VERSION(4,0,0)
1035     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
1036     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));
1037 
1038     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
1039     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
1040     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));
1041 
1042     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
1043 #else
1044     GtkGesture *pClick = gtk_gesture_click_new();
1045     gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
1046     // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't
1047     // want click events in gtk widgets inside the overlay, e.g. the font size
1048     // combobox GtkEntry in the toolbar, to be propagated down to this widget
1049     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET);
1050     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick));
1051     g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this);
1052     g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this);
1053 
1054     GtkEventController* pMotionController = gtk_event_controller_motion_new();
1055     g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
1056     g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this);
1057     g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this);
1058     gtk_widget_add_controller(pEventWidget, pMotionController);
1059 
1060     GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
1061     g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
1062     gtk_widget_add_controller(pEventWidget, pScrollController);
1063 #endif
1064 
1065 #if GTK_CHECK_VERSION(4,0,0)
1066     GtkGesture* pZoomGesture = gtk_gesture_zoom_new();
1067     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pZoomGesture));
1068 #else
1069     GtkGesture* pZoomGesture = gtk_gesture_zoom_new(GTK_WIDGET(pEventWidget));
1070     g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pZoomGesture);
1071 #endif
1072     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pZoomGesture),
1073                                                GTK_PHASE_TARGET);
1074     // Note that the default zoom gesture signal handler needs to run first to setup correct
1075     // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
1076     g_signal_connect_after(pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
1077     g_signal_connect_after(pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
1078     g_signal_connect_after(pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
1079 
1080 #if GTK_CHECK_VERSION(4,0,0)
1081     GtkGesture* pRotateGesture = gtk_gesture_rotate_new();
1082     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pRotateGesture));
1083 #else
1084     GtkGesture* pRotateGesture = gtk_gesture_rotate_new(GTK_WIDGET(pEventWidget));
1085     g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pRotateGesture);
1086 #endif
1087     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pRotateGesture),
1088                                                GTK_PHASE_TARGET);
1089     g_signal_connect(pRotateGesture, "begin", G_CALLBACK(signalRotateBegin), this);
1090     g_signal_connect(pRotateGesture, "update", G_CALLBACK(signalRotateUpdate), this);
1091     g_signal_connect(pRotateGesture, "end", G_CALLBACK(signalRotateEnd), this);
1092 
1093     //Drop Target Stuff
1094 #if GTK_CHECK_VERSION(4,0,0)
1095     GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL));
1096     g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this);
1097     g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this);
1098     g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this);
1099     g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this);
1100     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget));
1101 #else
1102     gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
1103     gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
1104     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
1105     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
1106     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
1107     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
1108 #endif
1109 
1110 #if !GTK_CHECK_VERSION(4,0,0)
1111     //Drag Source Stuff
1112     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
1113     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
1114     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
1115     m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
1116 #endif
1117 
1118 #if !GTK_CHECK_VERSION(4,0,0)
1119     g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
1120     g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
1121 #else
1122     gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
1123     g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this);
1124 #endif
1125 
1126     g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this);
1127 
1128 #if !GTK_CHECK_VERSION(4,0,0)
1129     GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
1130     g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pSwipe);
1131 #else
1132     GtkGesture *pSwipe = gtk_gesture_swipe_new();
1133     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe));
1134 #endif
1135     g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
1136     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);
1137 
1138 #if !GTK_CHECK_VERSION(4,0,0)
1139     GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
1140     g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pLongPress);
1141 #else
1142     GtkGesture *pLongPress = gtk_gesture_long_press_new();
1143     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress));
1144 #endif
1145     g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
1146     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);
1147 
1148 #if !GTK_CHECK_VERSION(4,0,0)
1149     g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
1150     g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
1151     if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
1152         m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
1153 #else
1154     GtkEventController* pFocusController = gtk_event_controller_focus_new();
1155     g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this);
1156     g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this);
1157     gtk_widget_set_focusable(pEventWidget, true);
1158     gtk_widget_add_controller(pEventWidget, pFocusController);
1159     if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?)
1160         m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this );
1161 #endif
1162 #if !GTK_CHECK_VERSION(4,0,0)
1163     g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
1164     g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
1165     g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
1166 #else
1167     g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this );
1168     g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this );
1169     if (GTK_IS_WINDOW(m_pWindow))
1170         g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this );
1171 #endif
1172 #if !GTK_CHECK_VERSION(4,0,0)
1173     g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
1174 #endif
1175 
1176 #if !GTK_CHECK_VERSION(4,0,0)
1177     g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
1178     g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
1179 #else
1180     m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
1181     g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this);
1182     g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this);
1183     gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController));
1184 
1185 #endif
1186     g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );
1187 
1188     // init members
1189     m_nKeyModifiers     = ModKeyFlags::NONE;
1190     m_bFullscreen       = false;
1191 #if GTK_CHECK_VERSION(4,0,0)
1192     m_nState            = static_cast<GdkToplevelState>(0);
1193 #else
1194     m_nState            = GDK_WINDOW_STATE_WITHDRAWN;
1195 #endif
1196     m_pIMHandler        = nullptr;
1197     m_pRegion           = nullptr;
1198     m_pDropTarget       = nullptr;
1199     m_pDragSource       = nullptr;
1200     m_bGeometryIsProvisional = false;
1201     m_bIconSetWhileUnmapped = false;
1202     m_bTooltipBlocked   = false;
1203     m_ePointerStyle     = static_cast<PointerStyle>(0xffff);
1204     m_pSalMenu          = nullptr;
1205     m_nWatcherId        = 0;
1206     m_nMenuExportId     = 0;
1207     m_nActionGroupExportId = 0;
1208     m_nHudAwarenessId   = 0;
1209 
1210 #if !GTK_CHECK_VERSION(4,0,0)
1211     gtk_widget_add_events( m_pWindow,
1212                            GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1213                            GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
1214                            GDK_SCROLL_MASK | GDK_TOUCHPAD_GESTURE_MASK
1215                            );
1216 #endif
1217 
1218     // show the widgets
1219 #if !GTK_CHECK_VERSION(4,0,0)
1220     gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
1221 #else
1222     gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid));
1223 #endif
1224 
1225     // realize the window, we need an XWindow id
1226     gtk_widget_realize( m_pWindow );
1227 
1228     if (GTK_IS_WINDOW(m_pWindow))
1229     {
1230 #if !GTK_CHECK_VERSION(4,0,0)
1231         g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this);
1232 #else
1233         GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
1234         g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this);
1235 #endif
1236     }
1237 
1238     //system data
1239     m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
1240     m_aSystemData.aShellWindow  = reinterpret_cast<sal_IntPtr>(this);
1241     m_aSystemData.pSalFrame     = this;
1242     m_aSystemData.pWidget       = m_pWindow;
1243     m_aSystemData.nScreen       = m_nXScreen.getXScreen();
1244     m_aSystemData.toolkit       = SystemEnvData::Toolkit::Gtk;
1245 
1246 #if defined(GDK_WINDOWING_X11)
1247     GdkDisplay *pDisplay = getGdkDisplay();
1248     if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
1249     {
1250         m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
1251         m_aSystemData.platform = SystemEnvData::Platform::Xcb;
1252 #if !GTK_CHECK_VERSION(4,0,0)
1253         GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
1254         GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
1255         m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
1256 #endif
1257     }
1258 #endif
1259 #if defined(GDK_WINDOWING_WAYLAND)
1260     if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
1261     {
1262         m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
1263         m_aSystemData.platform = SystemEnvData::Platform::Wayland;
1264     }
1265 #endif
1266 
1267     m_bGraphics = false;
1268     m_pGraphics = nullptr;
1269 
1270     m_nFloatFlags = FloatWinPopupFlags::NONE;
1271     m_bFloatPositioned = false;
1272 
1273     m_nWidthRequest = 0;
1274     m_nHeightRequest = 0;
1275 
1276     // fake an initial geometry, gets updated via configure event or SetPosSize
1277     if (m_bDefaultPos || m_bDefaultSize)
1278     {
1279         Size aDefSize = calcDefaultSize();
1280         maGeometry.setPosSize({ -1, -1 }, aDefSize);
1281         maGeometry.setDecorations(0, 0, 0, 0);
1282     }
1283     updateScreenNumber();
1284 
1285     SetIcon(SV_ICON_ID_OFFICE);
1286 }
1287 
1288 GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
1289 {
1290     return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
1291 }
1292 
1293 void GtkSalFrame::DisallowCycleFocusOut()
1294 {
1295     if (!m_nSetFocusSignalId)
1296         return;
1297     // don't enable/disable can-focus as control enters and leaves
1298     // embedded native gtk widgets
1299     g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
1300     m_nSetFocusSignalId = 0;
1301 
1302 #if !GTK_CHECK_VERSION(4, 0, 0)
1303     // gtk3: set container without can-focus and focus will tab between
1304     // the native embedded widgets using the default gtk handling for
1305     // that
1306     // gtk4: no need because the native widgets are the only
1307     // thing in the overlay and the drawing widget is underneath so
1308     // the natural focus cycle is sufficient
1309     gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
1310 #endif
1311 }
1312 
1313 bool GtkSalFrame::IsCycleFocusOutDisallowed() const
1314 {
1315     return m_nSetFocusSignalId == 0;
1316 }
1317 
1318 void GtkSalFrame::AllowCycleFocusOut()
1319 {
1320     if (m_nSetFocusSignalId)
1321         return;
1322 #if !GTK_CHECK_VERSION(4,0,0)
1323     // enable/disable can-focus as control enters and leaves
1324     // embedded native gtk widgets
1325     m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
1326 #else
1327     m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this);
1328 #endif
1329 
1330 #if !GTK_CHECK_VERSION(4, 0, 0)
1331     // set container without can-focus and focus will tab between
1332     // the native embedded widgets using the default gtk handling for
1333     // that
1334     // gtk4: no need because the native widgets are the only
1335     // thing in the overlay and the drawing widget is underneath so
1336     // the natural focus cycle is sufficient
1337     gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
1338 #endif
1339 }
1340 
1341 namespace
1342 {
1343     enum ColorScheme
1344     {
1345         DEFAULT,
1346         PREFER_DARK,
1347         PREFER_LIGHT
1348     };
1349 
1350     void ReadColorScheme(GDBusProxy* proxy, GVariant** out)
1351     {
1352         g_autoptr (GVariant) ret =
1353             g_dbus_proxy_call_sync(proxy, "Read",
1354                                    g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
1355                                    G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
1356         if (!ret)
1357             return;
1358 
1359         g_autoptr (GVariant) child = nullptr;
1360         g_variant_get(ret, "(v)", &child);
1361         g_variant_get(child, "v", out);
1362 
1363         return;
1364     }
1365 }
1366 
1367 void GtkSalFrame::SetColorScheme(GVariant* variant)
1368 {
1369     if (!m_pWindow)
1370         return;
1371 
1372     guint32 color_scheme;
1373 
1374     switch (officecfg::Office::Common::Misc::Appearance::get())
1375     {
1376         default:
1377         case 0: // Auto
1378         {
1379             if (variant)
1380             {
1381                 color_scheme = g_variant_get_uint32(variant);
1382                 if (color_scheme > PREFER_LIGHT)
1383                     color_scheme = DEFAULT;
1384             }
1385             else
1386                 color_scheme = DEFAULT;
1387             break;
1388         }
1389         case 1: // Light
1390             color_scheme = PREFER_LIGHT;
1391             break;
1392         case 2: // Dark
1393             color_scheme = PREFER_DARK;
1394             break;
1395     }
1396 
1397     bool bDarkIconTheme(color_scheme == PREFER_DARK);
1398     GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
1399     g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
1400 }
1401 
1402 bool GtkSalFrame::GetUseDarkMode() const
1403 {
1404     if (!m_pWindow)
1405         return false;
1406     GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
1407     gboolean bDarkIconTheme = false;
1408     g_object_get(pSettings, "gtk-application-prefer-dark-theme", &bDarkIconTheme, nullptr);
1409     return bDarkIconTheme;
1410 }
1411 
1412 bool GtkSalFrame::GetUseReducedAnimation() const
1413 {
1414     if (!m_pWindow)
1415         return false;
1416     GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
1417     gboolean bAnimations;
1418     g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
1419     return !bAnimations;
1420 }
1421 
1422 static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
1423                                        GVariant* parameters, gpointer frame)
1424 {
1425     if (g_strcmp0(signal_name, "SettingChanged"))
1426         return;
1427 
1428     g_autoptr (GVariant) value = nullptr;
1429     const char *name_space;
1430     const char *name;
1431     g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
1432 
1433     if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
1434         g_strcmp0(name, "color-scheme"))
1435       return;
1436 
1437     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
1438     pThis->SetColorScheme(value);
1439 }
1440 
1441 void GtkSalFrame::ListenPortalSettings()
1442 {
1443     EnsureSessionBus();
1444 
1445     if (!pSessionBus)
1446         return;
1447 
1448     m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
1449                                               G_DBUS_PROXY_FLAGS_NONE,
1450                                               nullptr,
1451                                               "org.freedesktop.portal.Desktop",
1452                                               "/org/freedesktop/portal/desktop",
1453                                               "org.freedesktop.portal.Settings",
1454                                               nullptr,
1455                                               nullptr);
1456 
1457     UpdateDarkMode();
1458 
1459     if (!m_pSettingsPortal)
1460         return;
1461 
1462     m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
1463 }
1464 
1465 static void session_client_response(GDBusProxy* client_proxy)
1466 {
1467     g_dbus_proxy_call(client_proxy,
1468                       "EndSessionResponse",
1469                       g_variant_new ("(bs)", true, ""),
1470                       G_DBUS_CALL_FLAGS_NONE,
1471                       G_MAXINT,
1472                       nullptr, nullptr, nullptr);
1473 }
1474 
1475 // unset documents "modify" flag so they won't veto closing
1476 static void clear_modify_and_terminate()
1477 {
1478     css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
1479     uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
1480     uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
1481     while (xComponents->hasMoreElements())
1482     {
1483         css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
1484         if (xModifiable)
1485             xModifiable->setModified(false);
1486     }
1487     xDesktop->terminate();
1488 }
1489 
1490 static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
1491                                   GVariant* /*parameters*/, gpointer frame)
1492 {
1493     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
1494 
1495     if (g_str_equal (signal_name, "QueryEndSession"))
1496     {
1497         css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
1498         uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));
1499 
1500         bool bModified = false;
1501 
1502         // find the XModifiable for this GtkSalFrame
1503         if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
1504         {
1505             VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
1506             css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
1507             sal_Int32 nFrameCount = xList->getCount();
1508             for (sal_Int32 i = 0; i < nFrameCount; ++i)
1509             {
1510                 css::uno::Reference<css::frame::XFrame> xFrame;
1511                 xList->getByIndex(i) >>= xFrame;
1512                 if (!xFrame)
1513                     continue;
1514                 VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
1515                 if (!xWin)
1516                    continue;
1517                 if (xWin->GetFrameWindow() != xThisWindow)
1518                     continue;
1519                 css::uno::Reference<css::frame::XController> xController = xFrame->getController();
1520                 if (!xController)
1521                     break;
1522                 css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
1523                 if (!xModifiable)
1524                     break;
1525                 bModified = xModifiable->isModified();
1526                 break;
1527             }
1528         }
1529 
1530         pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
1531                                      gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
1532 
1533         session_client_response(client_proxy);
1534     }
1535     else if (g_str_equal (signal_name, "CancelEndSession"))
1536     {
1537         // restore back to uninhibited (to set again if queried), so frames
1538         // that go away before the next logout don't affect that logout
1539         pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
1540                                      gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
1541     }
1542     else if (g_str_equal (signal_name, "EndSession"))
1543     {
1544         session_client_response(client_proxy);
1545         clear_modify_and_terminate();
1546     }
1547     else if (g_str_equal (signal_name, "Stop"))
1548     {
1549         clear_modify_and_terminate();
1550     }
1551 }
1552 
1553 void GtkSalFrame::ListenSessionManager()
1554 {
1555     EnsureSessionBus();
1556 
1557     if (!pSessionBus)
1558         return;
1559 
1560     m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
1561                                               G_DBUS_PROXY_FLAGS_NONE,
1562                                               nullptr,
1563                                               "org.gnome.SessionManager",
1564                                               "/org/gnome/SessionManager",
1565                                               "org.gnome.SessionManager",
1566                                               nullptr,
1567                                               nullptr);
1568 
1569     if (!m_pSessionManager)
1570         return;
1571 
1572     GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
1573                                  "RegisterClient",
1574                                  g_variant_new ("(ss)", "org.libreoffice", ""),
1575                                  G_DBUS_CALL_FLAGS_NONE,
1576                                  G_MAXINT,
1577                                  nullptr,
1578                                  nullptr);
1579 
1580     if (!res)
1581         return;
1582 
1583     gchar* client_path;
1584     g_variant_get(res, "(o)", &client_path);
1585     g_variant_unref(res);
1586 
1587     m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
1588                                              G_DBUS_PROXY_FLAGS_NONE,
1589                                              nullptr,
1590                                              "org.gnome.SessionManager",
1591                                              client_path,
1592                                              "org.gnome.SessionManager.ClientPrivate",
1593                                              nullptr,
1594                                              nullptr);
1595 
1596     g_free(client_path);
1597 
1598     if (!m_pSessionClient)
1599         return;
1600 
1601     m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
1602 }
1603 
1604 void GtkSalFrame::UpdateDarkMode()
1605 {
1606     g_autoptr (GVariant) value = nullptr;
1607     if (m_pSettingsPortal)
1608         ReadColorScheme(m_pSettingsPortal, &value);
1609     SetColorScheme(value);
1610 }
1611 
1612 #if GTK_CHECK_VERSION(4,0,0)
1613 static void PopoverClosed(GtkPopover*, GtkSalFrame* pThis)
1614 {
1615     SolarMutexGuard aGuard;
1616     pThis->closePopup();
1617 }
1618 #endif
1619 
1620 void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
1621 {
1622     if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
1623     {
1624         nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
1625         nStyle &= ~SalFrameStyleFlags::FLOAT;
1626     }
1627 
1628     m_pParent = static_cast<GtkSalFrame*>(pParent);
1629 #if !GTK_CHECK_VERSION(4,0,0)
1630     m_pForeignParent = nullptr;
1631     m_aForeignParentWindow = None;
1632     m_pForeignTopLevel = nullptr;
1633     m_aForeignTopLevelWindow = None;
1634 #endif
1635     m_nStyle = nStyle;
1636 
1637     bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) &&
1638                    !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));
1639 
1640     if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
1641     {
1642 #if !GTK_CHECK_VERSION(4,0,0)
1643         m_pWindow = gtk_event_box_new();
1644 #else
1645         m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1646 #endif
1647         if( m_pParent )
1648         {
1649             // insert into container
1650             gtk_fixed_put( m_pParent->getFixedContainer(),
1651                            m_pWindow, 0, 0 );
1652         }
1653     }
1654     else
1655     {
1656 #if !GTK_CHECK_VERSION(4,0,0)
1657         m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
1658 #else
1659         if (!bPopup)
1660             m_pWindow = gtk_window_new();
1661         else
1662         {
1663             m_pWindow = gtk_popover_new();
1664             gtk_popover_set_has_arrow(GTK_POPOVER(m_pWindow), false);
1665             g_signal_connect(m_pWindow, "closed", G_CALLBACK(PopoverClosed), this);
1666         }
1667 #endif
1668 
1669 #if !GTK_CHECK_VERSION(4,0,0)
1670         // hook up F1 to show help for embedded native gtk widgets
1671         GtkAccelGroup *pGroup = gtk_accel_group_new();
1672         GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
1673         gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
1674         gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
1675 #endif
1676     }
1677 
1678     g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
1679     g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));
1680 
1681     // force wm class hint
1682     if (!isChild())
1683     {
1684         if (m_pParent)
1685             m_sWMClass = m_pParent->m_sWMClass;
1686         updateWMClass();
1687     }
1688 
1689     if (GTK_IS_WINDOW(m_pWindow))
1690     {
1691         if (m_pParent)
1692         {
1693             GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow);
1694 #if !GTK_CHECK_VERSION(4,0,0)
1695             if (!isChild())
1696                 gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
1697 #endif
1698 
1699             if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
1700                 gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
1701             m_pParent->m_aChildren.push_back( this );
1702             gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
1703         }
1704         else
1705         {
1706             gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
1707             g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
1708         }
1709     }
1710     else if (GTK_IS_POPOVER(m_pWindow))
1711     {
1712         assert(m_pParent);
1713         gtk_widget_set_parent(m_pWindow, m_pParent->getMouseEventWidget());
1714     }
1715 
1716     // set window type
1717     bool bDecoHandling =
1718         ! isChild() &&
1719         ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
1720           (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );
1721 
1722     if( bDecoHandling )
1723     {
1724 #if !GTK_CHECK_VERSION(4,0,0)
1725         GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
1726         if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
1727             eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1728 #endif
1729         if( nStyle & SalFrameStyleFlags::INTRO )
1730         {
1731 #if !GTK_CHECK_VERSION(4,0,0)
1732             gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
1733             eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
1734 #endif
1735         }
1736         else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
1737         {
1738 #if !GTK_CHECK_VERSION(4,0,0)
1739             eType = GDK_WINDOW_TYPE_HINT_DIALOG;
1740             gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
1741 #endif
1742         }
1743         else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
1744         {
1745 #if !GTK_CHECK_VERSION(4,0,0)
1746             eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
1747             gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
1748 #endif
1749             gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
1750         }
1751 #if !GTK_CHECK_VERSION(4,0,0)
1752         gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
1753         gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
1754 #endif
1755         gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );
1756 
1757 #if !GTK_CHECK_VERSION(4,0,0)
1758 #if defined(GDK_WINDOWING_WAYLAND)
1759         //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
1760         //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
1761         //built-in close button of the titlebar follows the overridden direction rather than continue in the same
1762         //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
1763         //header bar with the desired 'outside' direction
1764         if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
1765         {
1766             const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage());
1767             const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
1768             if (bDesktopIsRTL != bAppIsRTL)
1769             {
1770                 m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
1771                 gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
1772                 gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
1773                 gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
1774                 gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
1775             }
1776         }
1777 #endif
1778 #endif
1779     }
1780 #if !GTK_CHECK_VERSION(4,0,0)
1781     else if( nStyle & SalFrameStyleFlags::FLOAT )
1782         gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
1783 #endif
1784 
1785     InitCommon();
1786 
1787     if (!bPopup)
1788     {
1789         // Enable GMenuModel native menu
1790         attach_menu_model(this);
1791 
1792         // Listen to portal settings for e.g. prefer dark theme
1793         ListenPortalSettings();
1794 
1795         // Listen to session manager for e.g. query-end
1796         ListenSessionManager();
1797     }
1798 }
1799 
1800 #if !GTK_CHECK_VERSION(4,0,0)
1801 GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
1802 {
1803     //FIXME: no findToplevelSystemWindow
1804     return 0;
1805 }
1806 #endif
1807 
1808 void GtkSalFrame::Init( SystemParentData* pSysData )
1809 {
1810     m_pParent = nullptr;
1811 #if !GTK_CHECK_VERSION(4,0,0)
1812     m_aForeignParentWindow = pSysData->aWindow;
1813     m_pForeignParent = nullptr;
1814     m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
1815     m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
1816     gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );
1817 
1818     if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
1819     {
1820         m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
1821         gtk_widget_set_can_default(m_pWindow, true);
1822         gtk_widget_set_can_focus(m_pWindow, true);
1823         gtk_widget_set_sensitive(m_pWindow, true);
1824     }
1825     else
1826     {
1827         m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
1828     }
1829 #endif
1830     m_nStyle = SalFrameStyleFlags::PLUG;
1831     InitCommon();
1832 
1833 #if !GTK_CHECK_VERSION(4,0,0)
1834     m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
1835     gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
1836 #else
1837     (void)pSysData;
1838 #endif
1839 
1840     //FIXME: Handling embedded windows, is going to be fun ...
1841 }
1842 
1843 void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
1844 {
1845 }
1846 
1847 SalGraphics* GtkSalFrame::AcquireGraphics()
1848 {
1849     if( m_bGraphics )
1850         return nullptr;
1851 
1852     if( !m_pGraphics )
1853     {
1854         m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
1855         if (!m_pSurface)
1856         {
1857             AllocateFrame();
1858             TriggerPaintEvent();
1859         }
1860         m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
1861     }
1862     m_bGraphics = true;
1863     return m_pGraphics.get();
1864 }
1865 
1866 void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
1867 {
1868     (void) pGraphics;
1869     assert( pGraphics == m_pGraphics.get() );
1870     m_bGraphics = false;
1871 }
1872 
1873 bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
1874 {
1875     getDisplay()->SendInternalEvent( this, pData.release() );
1876     return true;
1877 }
1878 
1879 void GtkSalFrame::SetTitle( const OUString& rTitle )
1880 {
1881     if (m_pWindow && GTK_IS_WINDOW(m_pWindow) && !isChild())
1882     {
1883         OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
1884         gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
1885 #if !GTK_CHECK_VERSION(4,0,0)
1886         if (m_pHeaderBar)
1887             gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
1888 #endif
1889     }
1890 }
1891 
1892 void GtkSalFrame::SetIcon(const char* appicon)
1893 {
1894     gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);
1895 
1896 #if defined(GDK_WINDOWING_WAYLAND)
1897     if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
1898         return;
1899 
1900 #if GTK_CHECK_VERSION(4,0,0)
1901     GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow));
1902     gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon);
1903 #else
1904     static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
1905                                          dlsym(nullptr, "gdk_wayland_window_set_application_id"));
1906     if (set_application_id)
1907     {
1908         GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
1909         set_application_id(gdkWindow, appicon);
1910     }
1911 #endif
1912     // gdk_wayland_window_set_application_id doesn't seem to work before
1913     // the window is mapped, so set this for real when/if we are mapped
1914     m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
1915 #endif
1916 }
1917 
1918 void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
1919 {
1920     if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
1921         || ! m_pWindow )
1922         return;
1923 
1924     gchar* appicon;
1925 
1926     if (nIcon == SV_ICON_ID_TEXT)
1927         appicon = g_strdup ("libreoffice-writer");
1928     else if (nIcon == SV_ICON_ID_SPREADSHEET)
1929         appicon = g_strdup ("libreoffice-calc");
1930     else if (nIcon == SV_ICON_ID_DRAWING)
1931         appicon = g_strdup ("libreoffice-draw");
1932     else if (nIcon == SV_ICON_ID_PRESENTATION)
1933         appicon = g_strdup ("libreoffice-impress");
1934     else if (nIcon == SV_ICON_ID_DATABASE)
1935         appicon = g_strdup ("libreoffice-base");
1936     else if (nIcon == SV_ICON_ID_FORMULA)
1937         appicon = g_strdup ("libreoffice-math");
1938     else
1939         appicon = g_strdup ("libreoffice-startcenter");
1940 
1941     SetIcon(appicon);
1942 
1943     g_free (appicon);
1944 }
1945 
1946 void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
1947 {
1948     m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
1949 }
1950 
1951 SalMenu* GtkSalFrame::GetMenu()
1952 {
1953     return m_pSalMenu;
1954 }
1955 
1956 void GtkSalFrame::Center()
1957 {
1958     if (!GTK_IS_WINDOW(m_pWindow))
1959         return;
1960 #if !GTK_CHECK_VERSION(4,0,0)
1961     if (m_pParent)
1962         gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
1963     else
1964         gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
1965 #endif
1966 }
1967 
1968 Size GtkSalFrame::calcDefaultSize()
1969 {
1970     Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
1971     int scale = gtk_widget_get_scale_factor(m_pWindow);
1972     aScreenSize.setWidth( aScreenSize.Width() / scale );
1973     aScreenSize.setHeight( aScreenSize.Height() / scale );
1974     return bestmaxFrameSizeForScreenSize(aScreenSize);
1975 }
1976 
1977 void GtkSalFrame::SetDefaultSize()
1978 {
1979     Size aDefSize = calcDefaultSize();
1980 
1981     SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
1982                 SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );
1983 
1984     if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
1985         gtk_window_maximize( GTK_WINDOW(m_pWindow) );
1986 }
1987 
1988 void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
1989 {
1990     if( !m_pWindow )
1991         return;
1992 
1993     if( bVisible )
1994     {
1995         getDisplay()->startupNotificationCompleted();
1996 
1997         if( m_bDefaultPos )
1998             Center();
1999         if( m_bDefaultSize )
2000             SetDefaultSize();
2001         setMinMaxSize();
2002 
2003         if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
2004         {
2005             m_pParent->grabPointer(true, true, true);
2006             m_pParent->addGrabLevel();
2007         }
2008 
2009 #if defined(GDK_WINDOWING_WAYLAND) && !GTK_CHECK_VERSION(4,0,0)
2010         /*
2011          rhbz#1334915, gnome#779143, tdf#100158
2012          https://gitlab.gnome.org/GNOME/gtk/-/issues/767
2013 
2014          before gdk_wayland_window_set_application_id was available gtk
2015          under wayland lacked a way to change the app_id of a window, so
2016          brute force everything as a startcenter when initially shown to at
2017          least get the default LibreOffice icon and not the broken app icon
2018         */
2019         static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
2020                                       !dlsym(nullptr, "gdk_wayland_window_set_application_id");
2021         if (bAppIdImmutable)
2022         {
2023             OString sOrigName(g_get_prgname());
2024             g_set_prgname("libreoffice-startcenter");
2025             gtk_widget_show(m_pWindow);
2026             g_set_prgname(sOrigName.getStr());
2027         }
2028         else
2029         {
2030             gtk_widget_show(m_pWindow);
2031         }
2032 #else
2033         gtk_widget_show(m_pWindow);
2034 #endif
2035 
2036         if( isFloatGrabWindow() )
2037         {
2038             m_nFloats++;
2039             if (!getDisplay()->GetCaptureFrame())
2040             {
2041                 grabPointer(true, true, true);
2042                 addGrabLevel();
2043             }
2044             // #i44068# reset parent's IM context
2045             if( m_pParent )
2046                 m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
2047         }
2048     }
2049     else
2050     {
2051         if( isFloatGrabWindow() )
2052         {
2053             m_nFloats--;
2054             if (!getDisplay()->GetCaptureFrame())
2055             {
2056                 removeGrabLevel();
2057                 grabPointer(false, true, false);
2058                 m_pParent->removeGrabLevel();
2059                 bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
2060                 m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
2061             }
2062         }
2063         gtk_widget_hide( m_pWindow );
2064         if( m_pIMHandler )
2065             m_pIMHandler->focusChanged( false );
2066     }
2067 }
2068 
2069 void GtkSalFrame::setMinMaxSize()
2070 {
2071     /*  #i34504# metacity (and possibly others) do not treat
2072      *  _NET_WM_STATE_FULLSCREEN and max_width/height independently;
2073      *  whether they should is undefined. So don't set the max size hint
2074      *  for a full screen window.
2075     */
2076     if( !m_pWindow || isChild() )
2077         return;
2078 
2079 #if !GTK_CHECK_VERSION(4, 0, 0)
2080     GdkGeometry aGeo;
2081     int aHints = 0;
2082     if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
2083     {
2084         if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
2085         {
2086             aGeo.min_width  = m_aMinSize.Width();
2087             aGeo.min_height = m_aMinSize.Height();
2088             aHints |= GDK_HINT_MIN_SIZE;
2089         }
2090         if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
2091         {
2092             aGeo.max_width  = m_aMaxSize.Width();
2093             aGeo.max_height = m_aMaxSize.Height();
2094             aHints |= GDK_HINT_MAX_SIZE;
2095         }
2096     }
2097     else
2098     {
2099         if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
2100         {
2101             aGeo.min_width = m_nWidthRequest;
2102             aGeo.min_height = m_nHeightRequest;
2103             aHints |= GDK_HINT_MIN_SIZE;
2104 
2105             aGeo.max_width = m_nWidthRequest;
2106             aGeo.max_height = m_nHeightRequest;
2107             aHints |= GDK_HINT_MAX_SIZE;
2108         }
2109     }
2110 
2111     if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
2112     {
2113         aGeo.max_width = m_aMaxSize.Width();
2114         aGeo.max_height = m_aMaxSize.Height();
2115         aHints |= GDK_HINT_MAX_SIZE;
2116     }
2117     if( aHints )
2118     {
2119         gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
2120                                        nullptr,
2121                                        &aGeo,
2122                                        GdkWindowHints( aHints ) );
2123     }
2124 #endif
2125 }
2126 
2127 void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
2128 {
2129     if( ! isChild() )
2130     {
2131         m_aMaxSize = Size( nWidth, nHeight );
2132         setMinMaxSize();
2133     }
2134 }
2135 void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
2136 {
2137     if( ! isChild() )
2138     {
2139         m_aMinSize = Size( nWidth, nHeight );
2140         if( m_pWindow )
2141         {
2142             widget_set_size_request(nWidth, nHeight);
2143             setMinMaxSize();
2144         }
2145     }
2146 }
2147 
2148 void GtkSalFrame::AllocateFrame()
2149 {
2150     basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() );
2151     if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
2152                       m_aFrameSize.getY() == aFrameSize.getY() )
2153         return;
2154 
2155     if( aFrameSize.getX() == 0 )
2156         aFrameSize.setX( 1 );
2157     if( aFrameSize.getY() == 0 )
2158         aFrameSize.setY( 1 );
2159 
2160     if (m_pSurface)
2161         cairo_surface_destroy(m_pSurface);
2162 
2163     m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow),
2164                                                 CAIRO_CONTENT_COLOR_ALPHA,
2165                                                 aFrameSize.getX(),
2166                                                 aFrameSize.getY());
2167     m_aFrameSize = aFrameSize;
2168 
2169     cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
2170     SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.width() << " x " << maGeometry.height());
2171 
2172     if (m_pGraphics)
2173         m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
2174 }
2175 
2176 void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
2177 {
2178     if( !m_pWindow || isChild( true, false ) )
2179         return;
2180 
2181     if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
2182         (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
2183             )
2184     {
2185         m_bDefaultSize = false;
2186 
2187         maGeometry.setSize({ nWidth, nHeight });
2188 
2189         if (isChild(false) || GTK_IS_POPOVER(m_pWindow))
2190             widget_set_size_request(nWidth, nHeight);
2191         else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) )
2192             window_resize(nWidth, nHeight);
2193 
2194         setMinMaxSize();
2195     }
2196     else if( m_bDefaultSize )
2197         SetDefaultSize();
2198 
2199     m_bDefaultSize = false;
2200 
2201     if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
2202     {
2203         if( m_pParent )
2204         {
2205             if( AllSettings::GetLayoutRTL() )
2206                 nX = m_pParent->maGeometry.width()-m_nWidthRequest-1-nX;
2207             nX += m_pParent->maGeometry.x();
2208             nY += m_pParent->maGeometry.y();
2209         }
2210 
2211         if (nFlags & SAL_FRAME_POSSIZE_X)
2212             maGeometry.setX(nX);
2213         if (nFlags & SAL_FRAME_POSSIZE_Y)
2214             maGeometry.setY(nY);
2215         m_bGeometryIsProvisional = true;
2216 
2217         m_bDefaultPos = false;
2218 
2219         moveWindow(maGeometry.x(), maGeometry.y());
2220 
2221         updateScreenNumber();
2222     }
2223     else if( m_bDefaultPos )
2224         Center();
2225 
2226     m_bDefaultPos = false;
2227 }
2228 
2229 void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
2230 {
2231     if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) )
2232     {
2233         rWidth = maGeometry.width();
2234         rHeight = maGeometry.height();
2235     }
2236     else
2237         rWidth = rHeight = 0;
2238 }
2239 
2240 void GtkSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
2241 {
2242     GdkRectangle aRect;
2243 #if !GTK_CHECK_VERSION(4, 0, 0)
2244     GdkScreen  *pScreen = gtk_widget_get_screen(m_pWindow);
2245     AbsoluteScreenPixelRectangle aRetRect;
2246     int max = gdk_screen_get_n_monitors (pScreen);
2247     for (int i = 0; i < max; ++i)
2248     {
2249         gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
2250         AbsoluteScreenPixelRectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
2251         aRetRect.Union(aMonitorRect);
2252     }
2253     rRect = aRetRect;
2254 #else
2255     GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
2256     GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
2257     GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
2258     gdk_monitor_get_geometry(pMonitor, &aRect);
2259     rRect = AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
2260 #endif
2261 }
2262 
2263 SalFrame* GtkSalFrame::GetParent() const
2264 {
2265     return m_pParent;
2266 }
2267 
2268 void GtkSalFrame::SetWindowState(const vcl::WindowData* pState)
2269 {
2270     if( ! m_pWindow || ! pState || isChild( true, false ) )
2271         return;
2272 
2273     const vcl::WindowDataMask nMaxGeometryMask = vcl::WindowDataMask::PosSize |
2274         vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
2275         vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
2276 
2277     if( (pState->mask() & vcl::WindowDataMask::State) &&
2278         ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
2279         (pState->state() & vcl::WindowState::Maximized) &&
2280         (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask )
2281     {
2282         resizeWindow(pState->width(), pState->height());
2283         moveWindow(pState->x(), pState->y());
2284         m_bDefaultPos = m_bDefaultSize = false;
2285 
2286         updateScreenNumber();
2287 
2288         m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
2289         m_aRestorePosSize = pState->posSize();
2290     }
2291     else if (pState->mask() & vcl::WindowDataMask::PosSize)
2292     {
2293         sal_uInt16 nPosSizeFlags = 0;
2294         tools::Long nX = pState->x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
2295         tools::Long nY = pState->y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
2296         if (pState->mask() & vcl::WindowDataMask::X)
2297             nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
2298         else
2299             nX = maGeometry.x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
2300         if (pState->mask() & vcl::WindowDataMask::Y)
2301             nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
2302         else
2303             nY = maGeometry.y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
2304         if (pState->mask() & vcl::WindowDataMask::Width)
2305             nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
2306         if (pState->mask() & vcl::WindowDataMask::Height)
2307             nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
2308         SetPosSize(nX, nY, pState->width(), pState->height(), nPosSizeFlags);
2309     }
2310 
2311     if (pState->mask() & vcl::WindowDataMask::State && !isChild())
2312     {
2313         if (pState->state() & vcl::WindowState::Maximized)
2314             gtk_window_maximize( GTK_WINDOW(m_pWindow) );
2315         else
2316             gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
2317         /* #i42379# there is no rollup state in GDK; and rolled up windows are
2318         *  (probably depending on the WM) reported as iconified. If we iconify a
2319         *  window here that was e.g. a dialog, then it will be unmapped but still
2320         *  not be displayed in the task list, so it's an iconified window that
2321         *  the user cannot get out of this state. So do not set the iconified state
2322         *  on windows with a parent (that is transient frames) since these tend
2323         *  to not be represented in an icon task list.
2324         */
2325         bool bMinimize = pState->state() & vcl::WindowState::Minimized && !m_pParent;
2326 #if GTK_CHECK_VERSION(4, 0, 0)
2327         if (bMinimize)
2328             gtk_window_minimize(GTK_WINDOW(m_pWindow));
2329         else
2330             gtk_window_unminimize(GTK_WINDOW(m_pWindow));
2331 #else
2332         if (bMinimize)
2333             gtk_window_iconify(GTK_WINDOW(m_pWindow));
2334         else
2335             gtk_window_deiconify(GTK_WINDOW(m_pWindow));
2336 #endif
2337     }
2338     TriggerPaintEvent();
2339 }
2340 
2341 namespace
2342 {
2343     void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
2344     {
2345        gint width, height;
2346 #if !GTK_CHECK_VERSION(4, 0, 0)
2347        gint root_x, root_y;
2348        gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
2349        rX = root_x;
2350        rY = root_y;
2351 
2352        gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
2353 #else
2354        rX = 0;
2355        rY = 0;
2356        gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height);
2357 #endif
2358        rWidth = width;
2359        rHeight = height;
2360     }
2361 
2362     tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
2363     {
2364         tools::Long nX, nY, nWidth, nHeight;
2365         GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
2366         return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
2367     }
2368 }
2369 
2370 bool GtkSalFrame::GetWindowState(vcl::WindowData* pState)
2371 {
2372     pState->setState(vcl::WindowState::Normal);
2373     pState->setMask(vcl::WindowDataMask::PosSizeState);
2374 
2375     // rollup ? gtk 2.2 does not seem to support the shaded state
2376     if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
2377         pState->rState() |= vcl::WindowState::Minimized;
2378     if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
2379     {
2380         pState->rState() |= vcl::WindowState::Maximized;
2381         pState->setPosSize(m_aRestorePosSize);
2382         tools::Rectangle aPosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
2383         pState->SetMaximizedX(aPosSize.Left());
2384         pState->SetMaximizedY(aPosSize.Top());
2385         pState->SetMaximizedWidth(aPosSize.GetWidth());
2386         pState->SetMaximizedHeight(aPosSize.GetHeight());
2387         pState->rMask() |= vcl::WindowDataMask::MaximizedX          |
2388                            vcl::WindowDataMask::MaximizedY          |
2389                            vcl::WindowDataMask::MaximizedWidth      |
2390                            vcl::WindowDataMask::MaximizedHeight;
2391     }
2392     else
2393         pState->setPosSize(GetPosAndSize(GTK_WINDOW(m_pWindow)));
2394 
2395     return true;
2396 }
2397 
2398 void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
2399 {
2400     if( !m_pWindow )
2401         return;
2402 
2403     if (maGeometry.screen() == nNewScreen && eType == SetType::RetainSize)
2404         return;
2405 
2406 #if !GTK_CHECK_VERSION(4, 0, 0)
2407     int nX = maGeometry.x(), nY = maGeometry.y(),
2408         nWidth = maGeometry.width(), nHeight = maGeometry.height();
2409     GdkScreen *pScreen = nullptr;
2410     GdkRectangle aNewMonitor;
2411 
2412     bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
2413     bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
2414     gint nMonitor = -1;
2415     if (bSpanMonitorsWhenFullscreen)   //span all screens
2416     {
2417         pScreen = gtk_widget_get_screen( m_pWindow );
2418         aNewMonitor.x = 0;
2419         aNewMonitor.y = 0;
2420         aNewMonitor.width = gdk_screen_get_width(pScreen);
2421         aNewMonitor.height = gdk_screen_get_height(pScreen);
2422     }
2423     else
2424     {
2425         bool bSameMonitor = false;
2426 
2427         if (!bSpanAllScreens)
2428         {
2429             pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
2430             if (!pScreen)
2431             {
2432                 g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
2433                            "fallback to current\n", nNewScreen);
2434             }
2435         }
2436 
2437         if (!pScreen)
2438         {
2439             pScreen = gtk_widget_get_screen( m_pWindow );
2440             bSameMonitor = true;
2441         }
2442 
2443         // Heavy lifting, need to move screen ...
2444         if( pScreen != gtk_widget_get_screen( m_pWindow ))
2445             gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );
2446 
2447         gint nOldMonitor = gdk_screen_get_monitor_at_window(
2448                                 pScreen, widget_get_surface( m_pWindow ) );
2449         if (bSameMonitor)
2450             nMonitor = nOldMonitor;
2451 
2452     #if OSL_DEBUG_LEVEL > 1
2453         if( nMonitor == nOldMonitor )
2454             g_warning( "An apparently pointless SetScreen - should we elide it ?" );
2455     #endif
2456 
2457         GdkRectangle aOldMonitor;
2458         gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
2459         gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );
2460 
2461         nX = aNewMonitor.x + nX - aOldMonitor.x;
2462         nY = aNewMonitor.y + nY - aOldMonitor.y;
2463     }
2464 
2465     bool bResize = false;
2466     bool bVisible = gtk_widget_get_mapped( m_pWindow );
2467     if( bVisible )
2468         Show( false );
2469 
2470     if( eType == SetType::Fullscreen )
2471     {
2472         nX = aNewMonitor.x;
2473         nY = aNewMonitor.y;
2474         nWidth = aNewMonitor.width;
2475         nHeight = aNewMonitor.height;
2476         bResize = true;
2477 
2478         // #i110881# for the benefit of compiz set a max size here
2479         // else setting to fullscreen fails for unknown reasons
2480         m_aMaxSize.setWidth( aNewMonitor.width );
2481         m_aMaxSize.setHeight( aNewMonitor.height );
2482     }
2483 
2484     if( pSize && eType == SetType::UnFullscreen )
2485     {
2486         nX = pSize->Left();
2487         nY = pSize->Top();
2488         nWidth = pSize->GetWidth();
2489         nHeight = pSize->GetHeight();
2490         bResize = true;
2491     }
2492 
2493     if (bResize)
2494     {
2495         // temporarily re-sizeable
2496         if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
2497             gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
2498         window_resize(nWidth, nHeight);
2499     }
2500 
2501     gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);
2502 
2503     GdkFullscreenMode eMode =
2504         bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
2505 
2506     gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode);
2507 
2508     GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
2509     if( eType == SetType::Fullscreen )
2510     {
2511         if (pMenuBarContainerWidget)
2512             gtk_widget_hide(pMenuBarContainerWidget);
2513         if (bSpanMonitorsWhenFullscreen)
2514             gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
2515         else
2516             gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);
2517 
2518     }
2519     else if( eType == SetType::UnFullscreen )
2520     {
2521         if (pMenuBarContainerWidget)
2522             gtk_widget_show(pMenuBarContainerWidget);
2523         gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
2524     }
2525 
2526     if( eType == SetType::UnFullscreen &&
2527         !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
2528         gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );
2529 
2530     // FIXME: we should really let gtk+ handle our widget hierarchy ...
2531     if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
2532         SetParent( nullptr );
2533 
2534     std::list< GtkSalFrame* > aChildren = m_aChildren;
2535     for (auto const& child : aChildren)
2536         child->SetScreen( nNewScreen, SetType::RetainSize );
2537 
2538     m_bDefaultPos = m_bDefaultSize = false;
2539     updateScreenNumber();
2540 
2541     if( bVisible )
2542         Show( true );
2543 
2544 #else
2545     (void)pSize; // assume restore will restore the original size without our help
2546 
2547     bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast<unsigned int>(-1);
2548 
2549     GdkFullscreenMode eMode =
2550         bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;
2551 
2552     g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr);
2553 
2554     GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
2555     if (eType == SetType::Fullscreen)
2556     {
2557         if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
2558         {
2559             // temp make it resizable, restore when unfullscreened
2560             gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true);
2561         }
2562 
2563         if (pMenuBarContainerWidget)
2564             gtk_widget_hide(pMenuBarContainerWidget);
2565         if (bSpanMonitorsWhenFullscreen)
2566             gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
2567         else
2568         {
2569             GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
2570             GListModel* pList = gdk_display_get_monitors(pDisplay);
2571             GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nNewScreen));
2572             if (!pMonitor)
2573                 pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow));
2574             gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor);
2575         }
2576     }
2577     else if (eType == SetType::UnFullscreen)
2578     {
2579         if (pMenuBarContainerWidget)
2580             gtk_widget_show(pMenuBarContainerWidget);
2581         gtk_window_unfullscreen(GTK_WINDOW(m_pWindow));
2582 
2583         if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
2584         {
2585             // restore temp resizability
2586             gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false);
2587         }
2588     }
2589 
2590     for (auto const& child : m_aChildren)
2591         child->SetScreen(nNewScreen, SetType::RetainSize);
2592 
2593     m_bDefaultPos = m_bDefaultSize = false;
2594     updateScreenNumber();
2595 #endif
2596 }
2597 
2598 void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
2599 {
2600     SetScreen( nNewScreen, SetType::RetainSize );
2601 }
2602 
2603 void GtkSalFrame::updateWMClass()
2604 {
2605     if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
2606         return;
2607 
2608     if (!gtk_widget_get_realized(m_pWindow))
2609         return;
2610 
2611     OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
2612     const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
2613                                                     SalGenericSystem::getFrameClassName();
2614     XClassHint* pClass = XAllocClassHint();
2615     OString aResName = SalGenericSystem::getFrameResName();
2616     pClass->res_name  = const_cast<char*>(aResName.getStr());
2617     pClass->res_class = const_cast<char*>(pResClass);
2618     Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay());
2619     XSetClassHint( display,
2620                    GtkSalFrame::GetNativeWindowHandle(m_pWindow),
2621                    pClass );
2622     XFree( pClass );
2623 }
2624 
2625 void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
2626 {
2627     if( rWMClass != m_sWMClass && ! isChild() )
2628     {
2629         m_sWMClass = rWMClass;
2630         updateWMClass();
2631 
2632         for (auto const& child : m_aChildren)
2633             child->SetApplicationID(rWMClass);
2634     }
2635 }
2636 
2637 void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
2638 {
2639     m_bFullscreen = bFullScreen;
2640 
2641     if( !m_pWindow || isChild() )
2642         return;
2643 
2644     if( bFullScreen )
2645     {
2646         m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
2647         SetScreen( nScreen, SetType::Fullscreen );
2648     }
2649     else
2650     {
2651         SetScreen( nScreen, SetType::UnFullscreen,
2652                    !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
2653         m_aRestorePosSize = tools::Rectangle();
2654     }
2655 }
2656 
2657 void GtkSalFrame::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
2658 {
2659     guint nWindow(0);
2660     std::optional<Display*> aDisplay;
2661 
2662     if (DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
2663     {
2664         nWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
2665         aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
2666     }
2667 
2668     m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
2669                                       nWindow, aDisplay, application_id);
2670 }
2671 
2672 void GtkSalFrame::StartPresentation( bool bStart )
2673 {
2674     SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
2675 }
2676 
2677 void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
2678 {
2679 #if !GTK_CHECK_VERSION(4, 0, 0)
2680     if( m_pWindow )
2681         gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
2682 #else
2683     (void)bOnTop;
2684 #endif
2685 }
2686 
2687 static guint32 nLastUserInputTime = GDK_CURRENT_TIME;
2688 
2689 guint32 GtkSalFrame::GetLastInputEventTime()
2690 {
2691     return nLastUserInputTime;
2692 }
2693 
2694 void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
2695 {
2696     //gtk3 can generate a synthetic crossing event with a useless 0
2697     //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
2698     //menubar, which is unhelpful, so ignore the 0 timestamps
2699     if (nUserInputTime == GDK_CURRENT_TIME)
2700         return;
2701     nLastUserInputTime = nUserInputTime;
2702 }
2703 
2704 void GtkSalFrame::ToTop( SalFrameToTop nFlags )
2705 {
2706     if( !m_pWindow )
2707         return;
2708 
2709     if( isChild( false ) )
2710         GrabFocus();
2711     else if( gtk_widget_get_mapped( m_pWindow ) )
2712     {
2713         auto nTimestamp = GetLastInputEventTime();
2714 #ifdef GDK_WINDOWING_X11
2715         GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
2716         if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
2717             nTimestamp = gdk_x11_display_get_user_time(pDisplay);
2718 #endif
2719         if (!(nFlags & SalFrameToTop::GrabFocusOnly))
2720             gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
2721 #if !GTK_CHECK_VERSION(4, 0, 0)
2722         else
2723             gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp);
2724 #endif
2725         GrabFocus();
2726     }
2727     else
2728     {
2729         if( nFlags & SalFrameToTop::RestoreWhenMin )
2730             gtk_window_present( GTK_WINDOW(m_pWindow) );
2731     }
2732 }
2733 
2734 void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
2735 {
2736     if( !m_pWindow || ePointerStyle == m_ePointerStyle )
2737         return;
2738 
2739     m_ePointerStyle = ePointerStyle;
2740     GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
2741     widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor);
2742 }
2743 
2744 void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
2745 {
2746     if (bGrab)
2747     {
2748         // tdf#135779 move focus back inside usual input window out of any
2749         // other gtk widgets before grabbing the pointer
2750         GrabFocus();
2751     }
2752 
2753     static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
2754     if (pEnv && *pEnv)
2755         return;
2756 
2757     if (!m_pWindow)
2758         return;
2759 
2760 #if !GTK_CHECK_VERSION(4, 0, 0)
2761     GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
2762     if (bGrab)
2763     {
2764         GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
2765         gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability,
2766                       bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
2767     }
2768     else
2769     {
2770         gdk_seat_ungrab(pSeat);
2771     }
2772 #else
2773     (void)bKeyboardAlso;
2774     (void)bOwnerEvents;
2775 #endif
2776 }
2777 
2778 void GtkSalFrame::CaptureMouse( bool bCapture )
2779 {
2780     getDisplay()->CaptureMouse( bCapture ? this : nullptr );
2781 }
2782 
2783 void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
2784 {
2785 #if !GTK_CHECK_VERSION(4, 0, 0)
2786     GtkSalFrame* pFrame = this;
2787     while( pFrame && pFrame->isChild( false ) )
2788         pFrame = pFrame->m_pParent;
2789     if( ! pFrame )
2790         return;
2791 
2792     GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
2793     GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );
2794 
2795     /* when the application tries to center the mouse in the dialog the
2796      * window isn't mapped already. So use coordinates relative to the root window.
2797      */
2798     unsigned int nWindowLeft = maGeometry.x() + nX;
2799     unsigned int nWindowTop  = maGeometry.y() + nY;
2800 
2801     GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
2802     gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);
2803 
2804     // #i38648# ask for the next motion hint
2805     gint x, y;
2806     GdkModifierType mask;
2807     gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask );
2808 #else
2809     (void)nX;
2810     (void)nY;
2811 #endif
2812 }
2813 
2814 void GtkSalFrame::Flush()
2815 {
2816     gdk_display_flush( getGdkDisplay() );
2817 }
2818 
2819 void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
2820     guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
2821 {
2822     if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
2823         return;
2824 
2825     // Get GDK key modifiers
2826     GdkModifierType nModifiers = GdkModifierType(0);
2827 
2828     if ( rKeyCode.IsShift() )
2829         nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );
2830 
2831     if ( rKeyCode.IsMod1() )
2832         nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );
2833 
2834     if ( rKeyCode.IsMod2() )
2835         nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_ALT_MASK );
2836 
2837     *pGdkModifiers = nModifiers;
2838 
2839     // Get GDK keycode.
2840     guint nKeyCode = 0;
2841 
2842     guint nCode = rKeyCode.GetCode();
2843 
2844     if ( nCode >= KEY_0 && nCode <= KEY_9 )
2845         nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
2846     else if ( nCode >= KEY_A && nCode <= KEY_Z )
2847         nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
2848     else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
2849         nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
2850     else
2851     {
2852         switch (nCode)
2853         {
2854             case KEY_DOWN:          nKeyCode = GDK_KEY_Down;            break;
2855             case KEY_UP:            nKeyCode = GDK_KEY_Up;              break;
2856             case KEY_LEFT:          nKeyCode = GDK_KEY_Left;            break;
2857             case KEY_RIGHT:         nKeyCode = GDK_KEY_Right;           break;
2858             case KEY_HOME:          nKeyCode = GDK_KEY_Home;            break;
2859             case KEY_END:           nKeyCode = GDK_KEY_End;             break;
2860             case KEY_PAGEUP:        nKeyCode = GDK_KEY_Page_Up;         break;
2861             case KEY_PAGEDOWN:      nKeyCode = GDK_KEY_Page_Down;       break;
2862             case KEY_RETURN:        nKeyCode = GDK_KEY_Return;          break;
2863             case KEY_ESCAPE:        nKeyCode = GDK_KEY_Escape;          break;
2864             case KEY_TAB:           nKeyCode = GDK_KEY_Tab;             break;
2865             case KEY_BACKSPACE:     nKeyCode = GDK_KEY_BackSpace;       break;
2866             case KEY_SPACE:         nKeyCode = GDK_KEY_space;           break;
2867             case KEY_INSERT:        nKeyCode = GDK_KEY_Insert;          break;
2868             case KEY_DELETE:        nKeyCode = GDK_KEY_Delete;          break;
2869             case KEY_ADD:           nKeyCode = GDK_KEY_plus;            break;
2870             case KEY_SUBTRACT:      nKeyCode = GDK_KEY_minus;           break;
2871             case KEY_MULTIPLY:      nKeyCode = GDK_KEY_asterisk;        break;
2872             case KEY_DIVIDE:        nKeyCode = GDK_KEY_slash;           break;
2873             case KEY_POINT:         nKeyCode = GDK_KEY_period;          break;
2874             case KEY_COMMA:         nKeyCode = GDK_KEY_comma;           break;
2875             case KEY_LESS:          nKeyCode = GDK_KEY_less;            break;
2876             case KEY_GREATER:       nKeyCode = GDK_KEY_greater;         break;
2877             case KEY_EQUAL:         nKeyCode = GDK_KEY_equal;           break;
2878             case KEY_FIND:          nKeyCode = GDK_KEY_Find;            break;
2879             case KEY_CONTEXTMENU:   nKeyCode = GDK_KEY_Menu;            break;
2880             case KEY_HELP:          nKeyCode = GDK_KEY_Help;            break;
2881             case KEY_UNDO:          nKeyCode = GDK_KEY_Undo;            break;
2882             case KEY_REPEAT:        nKeyCode = GDK_KEY_Redo;            break;
2883             case KEY_DECIMAL:       nKeyCode = GDK_KEY_KP_Decimal;      break;
2884             case KEY_TILDE:         nKeyCode = GDK_KEY_asciitilde;      break;
2885             case KEY_QUOTELEFT:     nKeyCode = GDK_KEY_quoteleft;       break;
2886             case KEY_BRACKETLEFT:   nKeyCode = GDK_KEY_bracketleft;     break;
2887             case KEY_BRACKETRIGHT:  nKeyCode = GDK_KEY_bracketright;    break;
2888             case KEY_SEMICOLON:     nKeyCode = GDK_KEY_semicolon;       break;
2889             case KEY_QUOTERIGHT:    nKeyCode = GDK_KEY_quoteright;      break;
2890             case KEY_RIGHTCURLYBRACKET: nKeyCode = GDK_KEY_braceright;      break;
2891             case KEY_NUMBERSIGN: nKeyCode = GDK_KEY_numbersign;         break;
2892             case KEY_XF86FORWARD:   nKeyCode = GDK_KEY_Forward;         break;
2893             case KEY_XF86BACK:      nKeyCode = GDK_KEY_Back;            break;
2894             case KEY_COLON:         nKeyCode = GDK_KEY_colon;           break;
2895 
2896             // Special cases
2897             case KEY_COPY:          nKeyCode = GDK_KEY_Copy;            break;
2898             case KEY_CUT:           nKeyCode = GDK_KEY_Cut;             break;
2899             case KEY_PASTE:         nKeyCode = GDK_KEY_Paste;           break;
2900             case KEY_OPEN:          nKeyCode = GDK_KEY_Open;            break;
2901         }
2902     }
2903 
2904     *pGdkKeyCode = nKeyCode;
2905 }
2906 
2907 OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
2908 {
2909     guint nGtkKeyCode;
2910     GdkModifierType nGtkModifiers;
2911     KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );
2912 
2913     gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
2914     OUString aRet = OStringToOUString(pName, RTL_TEXTENCODING_UTF8);
2915     g_free(pName);
2916     return aRet;
2917 }
2918 
2919 GdkDisplay *GtkSalFrame::getGdkDisplay()
2920 {
2921     return GetGtkSalData()->GetGdkDisplay();
2922 }
2923 
2924 GtkSalDisplay *GtkSalFrame::getDisplay()
2925 {
2926     return GetGtkSalData()->GetGtkDisplay();
2927 }
2928 
2929 SalFrame::SalPointerState GtkSalFrame::GetPointerState()
2930 {
2931     SalPointerState aState;
2932 #if !GTK_CHECK_VERSION(4, 0, 0)
2933     GdkScreen* pScreen;
2934     gint x, y;
2935     GdkModifierType aMask;
2936     gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
2937     aState.maPos = Point( x - maGeometry.x(), y - maGeometry.y() );
2938     aState.mnState = GetMouseModCode( aMask );
2939 #endif
2940     return aState;
2941 }
2942 
2943 KeyIndicatorState GtkSalFrame::GetIndicatorState()
2944 {
2945     KeyIndicatorState nState = KeyIndicatorState::NONE;
2946 
2947 #if !GTK_CHECK_VERSION(4, 0, 0)
2948     GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());
2949 
2950     if (gdk_keymap_get_caps_lock_state(pKeyMap))
2951         nState |= KeyIndicatorState::CAPSLOCK;
2952     if (gdk_keymap_get_num_lock_state(pKeyMap))
2953         nState |= KeyIndicatorState::NUMLOCK;
2954     if (gdk_keymap_get_scroll_lock_state(pKeyMap))
2955         nState |= KeyIndicatorState::SCROLLLOCK;
2956 #endif
2957 
2958     return nState;
2959 }
2960 
2961 void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
2962 {
2963     g_warning ("missing simulate keypress %d", nKeyCode);
2964 }
2965 
2966 void GtkSalFrame::SetInputContext( SalInputContext* pContext )
2967 {
2968     if( ! pContext )
2969         return;
2970 
2971     if( ! (pContext->mnOptions & InputContextFlags::Text) )
2972         return;
2973 
2974     // create a new im context
2975     if( ! m_pIMHandler )
2976         m_pIMHandler.reset( new IMHandler( this ) );
2977 }
2978 
2979 void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
2980 {
2981     if( m_pIMHandler )
2982         m_pIMHandler->endExtTextInput( nFlags );
2983 }
2984 
2985 bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
2986 {
2987     // not supported yet
2988     return false;
2989 }
2990 
2991 LanguageType GtkSalFrame::GetInputLanguage()
2992 {
2993     return LANGUAGE_DONTKNOW;
2994 }
2995 
2996 void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
2997 {
2998     if( ! m_pWindow )
2999         return;
3000 
3001     GtkSalGraphics* pGraphics = m_pGraphics.get();
3002     bool bFreeGraphics = false;
3003     if( ! pGraphics )
3004     {
3005         pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
3006         if ( !pGraphics )
3007         {
3008             SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
3009             return;
3010         }
3011         bFreeGraphics = true;
3012     }
3013 
3014     pGraphics->UpdateSettings( rSettings );
3015 
3016     if( bFreeGraphics )
3017         ReleaseGraphics( pGraphics );
3018 }
3019 
3020 void GtkSalFrame::Beep()
3021 {
3022     gdk_display_beep( getGdkDisplay() );
3023 }
3024 
3025 const SystemEnvData* GtkSalFrame::GetSystemData() const
3026 {
3027     return &m_aSystemData;
3028 }
3029 
3030 void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
3031 {
3032     if (!rData.pWidget)
3033         return;
3034     SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
3035     rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
3036 }
3037 
3038 void GtkSalFrame::SetParent( SalFrame* pNewParent )
3039 {
3040     GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
3041     if (m_pParent)
3042     {
3043         if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
3044             gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
3045         m_pParent->m_aChildren.remove(this);
3046     }
3047     m_pParent = static_cast<GtkSalFrame*>(pNewParent);
3048     if (m_pParent)
3049     {
3050         m_pParent->m_aChildren.push_back(this);
3051         if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
3052             gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
3053     }
3054     if (!isChild() && pWindow)
3055         gtk_window_set_transient_for( pWindow,
3056                                       (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
3057                                      );
3058 }
3059 
3060 void GtkSalFrame::SetPluginParent( SystemParentData* )
3061 {
3062     //FIXME: no SetPluginParent impl. for gtk3
3063 }
3064 
3065 void GtkSalFrame::ResetClipRegion()
3066 {
3067 #if !GTK_CHECK_VERSION(4, 0, 0)
3068     if( m_pWindow )
3069         gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 );
3070 #endif
3071 }
3072 
3073 void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
3074 {
3075     if( m_pRegion )
3076         cairo_region_destroy( m_pRegion );
3077     m_pRegion = cairo_region_create();
3078 }
3079 
3080 void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
3081 {
3082     if( m_pRegion )
3083     {
3084         GdkRectangle aRect;
3085         aRect.x         = nX;
3086         aRect.y         = nY;
3087         aRect.width     = nWidth;
3088         aRect.height    = nHeight;
3089         cairo_region_union_rectangle( m_pRegion, &aRect );
3090     }
3091 }
3092 
3093 void GtkSalFrame::EndSetClipRegion()
3094 {
3095 #if !GTK_CHECK_VERSION(4, 0, 0)
3096     if( m_pWindow && m_pRegion )
3097         gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 );
3098 #endif
3099 }
3100 
3101 void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
3102 {
3103     if ( ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition &&
3104         // tdf#152155 cannot determine window positions of popup listboxes on sidebar
3105         nFlags != LISTBOX_FLOATWINPOPUPFLAGS )
3106     {
3107         return;
3108     }
3109 
3110     m_aFloatRect = rRect;
3111     m_nFloatFlags = nFlags;
3112     m_bFloatPositioned = true;
3113 }
3114 
3115 void GtkSalFrame::SetModal(bool bModal)
3116 {
3117     if (!m_pWindow)
3118         return;
3119     gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
3120 }
3121 
3122 bool GtkSalFrame::GetModal() const
3123 {
3124     if (!m_pWindow)
3125         return false;
3126     return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
3127 }
3128 
3129 gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
3130                                      gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
3131                                      gpointer frame)
3132 {
3133     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3134     if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
3135         return false;
3136     gtk_tooltip_set_text(tooltip,
3137         OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
3138     GdkRectangle aHelpArea;
3139     aHelpArea.x = pThis->m_aHelpArea.Left();
3140     aHelpArea.y = pThis->m_aHelpArea.Top();
3141     aHelpArea.width = pThis->m_aHelpArea.GetWidth();
3142     aHelpArea.height = pThis->m_aHelpArea.GetHeight();
3143     if (AllSettings::GetLayoutRTL())
3144         aHelpArea.x = pThis->maGeometry.width()-aHelpArea.width-1-aHelpArea.x;
3145     gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
3146     return true;
3147 }
3148 
3149 bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
3150 {
3151     m_aTooltip = rHelpText;
3152     m_aHelpArea = rHelpArea;
3153     gtk_widget_trigger_tooltip_query(getMouseEventWidget());
3154     return true;
3155 }
3156 
3157 void GtkSalFrame::BlockTooltip()
3158 {
3159     m_bTooltipBlocked = true;
3160 }
3161 
3162 void GtkSalFrame::UnblockTooltip()
3163 {
3164     m_bTooltipBlocked = false;
3165 }
3166 
3167 void GtkSalFrame::HideTooltip()
3168 {
3169     m_aTooltip.clear();
3170     GtkWidget* pEventWidget = getMouseEventWidget();
3171     gtk_widget_trigger_tooltip_query(pEventWidget);
3172 }
3173 
3174 namespace
3175 {
3176     void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
3177     {
3178         GdkRectangle aRect;
3179         aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.x();
3180         aRect.y = rHelpArea.Top();
3181         aRect.width = 1;
3182         aRect.height = 1;
3183 
3184         GtkPositionType ePos = gtk_popover_get_position(pPopOver);
3185         switch (ePos)
3186         {
3187             case GTK_POS_BOTTOM:
3188             case GTK_POS_TOP:
3189                 aRect.width = rHelpArea.GetWidth();
3190                 break;
3191             case GTK_POS_RIGHT:
3192             case GTK_POS_LEFT:
3193                 aRect.height = rHelpArea.GetHeight();
3194                 break;
3195         }
3196 
3197         gtk_popover_set_pointing_to(pPopOver, &aRect);
3198     }
3199 }
3200 
3201 void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
3202 {
3203 #if !GTK_CHECK_VERSION(4, 0, 0)
3204     GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
3205 #else
3206     GtkWidget *pWidget = gtk_popover_new();
3207     gtk_widget_set_parent(pWidget, getMouseEventWidget());
3208 #endif
3209     OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
3210     GtkWidget *pLabel =  gtk_label_new(sUTF.getStr());
3211 #if !GTK_CHECK_VERSION(4, 0, 0)
3212     gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
3213 #else
3214     gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel);
3215 #endif
3216 
3217     if (nFlags & QuickHelpFlags::Top)
3218         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
3219     else if (nFlags & QuickHelpFlags::Bottom)
3220         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
3221     else if (nFlags & QuickHelpFlags::Left)
3222         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
3223     else if (nFlags & QuickHelpFlags::Right)
3224         gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);
3225 
3226     set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
3227 
3228 #if !GTK_CHECK_VERSION(4, 0, 0)
3229     gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
3230 #else
3231     gtk_popover_set_autohide(GTK_POPOVER(pWidget), false);
3232 #endif
3233 
3234     gtk_widget_show(pLabel);
3235     gtk_widget_show(pWidget);
3236 
3237     return pWidget;
3238 }
3239 
3240 bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
3241 {
3242     GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
3243 
3244     set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);
3245 
3246 #if !GTK_CHECK_VERSION(4, 0, 0)
3247     GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
3248 #else
3249     GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget));
3250 #endif
3251     OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
3252     gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());
3253 
3254     return true;
3255 }
3256 
3257 bool GtkSalFrame::HidePopover(void* nId)
3258 {
3259     GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
3260 #if !GTK_CHECK_VERSION(4, 0, 0)
3261     gtk_widget_destroy(pWidget);
3262 #else
3263     g_clear_pointer(&pWidget, gtk_widget_unparent);
3264 #endif
3265     return true;
3266 }
3267 
3268 void GtkSalFrame::addGrabLevel()
3269 {
3270 #if !GTK_CHECK_VERSION(4, 0, 0)
3271     if (m_nGrabLevel == 0)
3272         gtk_grab_add(getMouseEventWidget());
3273 #endif
3274     ++m_nGrabLevel;
3275 }
3276 
3277 void GtkSalFrame::removeGrabLevel()
3278 {
3279     if (m_nGrabLevel > 0)
3280     {
3281         --m_nGrabLevel;
3282 #if !GTK_CHECK_VERSION(4, 0, 0)
3283         if (m_nGrabLevel == 0)
3284             gtk_grab_remove(getMouseEventWidget());
3285 #endif
3286     }
3287 }
3288 
3289 void GtkSalFrame::closePopup()
3290 {
3291     if (!m_nFloats)
3292         return;
3293     ImplSVData* pSVData = ImplGetSVData();
3294     if (!pSVData->mpWinData->mpFirstFloat)
3295         return;
3296     if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
3297         return;
3298     pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
3299 }
3300 
3301 #if !GTK_CHECK_VERSION(4, 0, 0)
3302 namespace
3303 {
3304     //tdf#117981 translate embedded video window mouse events to parent coordinates
3305     void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
3306     {
3307         gpointer user_data=nullptr;
3308         gdk_window_get_user_data(pSourceWindow, &user_data);
3309         GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
3310         if (pRealEventWidget)
3311         {
3312             gtk_coord fX(0.0), fY(0.0);
3313             gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY);
3314             rEventX = fX;
3315             rEventY = fY;
3316         }
3317     }
3318 }
3319 #endif
3320 
3321 void GtkSalFrame::GrabFocus()
3322 {
3323     GtkWidget* pGrabWidget;
3324 #if !GTK_CHECK_VERSION(4, 0, 0)
3325     if (GTK_IS_EVENT_BOX(m_pWindow))
3326         pGrabWidget = GTK_WIDGET(m_pWindow);
3327     else
3328         pGrabWidget = GTK_WIDGET(m_pFixedContainer);
3329     // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
3330     // we don't allow focus to enter the toplevel, but expect it to
3331     // stay in some embedded native gtk widget
3332     if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
3333         gtk_widget_set_can_focus(pGrabWidget, true);
3334 #else
3335     pGrabWidget = GTK_WIDGET(m_pFixedContainer);
3336 #endif
3337     if (!gtk_widget_has_focus(pGrabWidget))
3338     {
3339         gtk_widget_grab_focus(pGrabWidget);
3340         if (m_pIMHandler)
3341             m_pIMHandler->focusChanged(true);
3342     }
3343 }
3344 
3345 bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState)
3346 {
3347     UpdateLastInputEventTime(nTime);
3348 
3349     SalMouseEvent aEvent;
3350     switch(nButton)
3351     {
3352         case 1: aEvent.mnButton = MOUSE_LEFT;   break;
3353         case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
3354         case 3: aEvent.mnButton = MOUSE_RIGHT;  break;
3355         default: return false;
3356     }
3357 
3358     aEvent.mnTime = nTime;
3359     aEvent.mnX = nEventX;
3360     aEvent.mnY = nEventY;
3361     aEvent.mnCode = GetMouseModCode(nState);
3362 
3363     if( AllSettings::GetLayoutRTL() )
3364         aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
3365 
3366     CallCallbackExc(nEventType, &aEvent);
3367 
3368     return true;
3369 }
3370 
3371 #if !GTK_CHECK_VERSION(4, 0, 0)
3372 
3373 void GtkSalFrame::UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY)
3374 {
3375     //tdf#151509 don't overwrite geometry for system children
3376     if (m_nStyle & SalFrameStyleFlags::SYSTEMCHILD)
3377         return;
3378 
3379     int frame_x = x_root - nEventX;
3380     int frame_y = y_root - nEventY;
3381     if (m_bGeometryIsProvisional || frame_x != maGeometry.x() || frame_y != maGeometry.y())
3382     {
3383         m_bGeometryIsProvisional = false;
3384         maGeometry.setPos({ frame_x, frame_y });
3385         ImplSVData* pSVData = ImplGetSVData();
3386         if (pSVData->maNWFData.mbCanDetermineWindowPosition)
3387             CallCallbackExc(SalEvent::Move, nullptr);
3388     }
3389 }
3390 
3391 gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
3392 {
3393     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3394     GtkWidget* pEventWidget = pThis->getMouseEventWidget();
3395     bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
3396 
3397     if (pEvent->type == GDK_BUTTON_PRESS)
3398     {
3399         // tdf#120764 It isn't allowed under wayland to have two visible popups that share
3400         // the same top level parent. The problem is that since gtk 3.24 tooltips are also
3401         // implemented as popups, which means that we cannot show any popup if there is a
3402         // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
3403         // in case of a button press event. But if we intend to show a popup on button press
3404         // it will be too late, so just do it here:
3405         pThis->HideTooltip();
3406 
3407         // focus on click
3408         if (!bDifferentEventWindow)
3409             pThis->GrabFocus();
3410     }
3411 
3412     SalEvent nEventType = SalEvent::NONE;
3413     switch( pEvent->type )
3414     {
3415         case GDK_BUTTON_PRESS:
3416             nEventType = SalEvent::MouseButtonDown;
3417             break;
3418         case GDK_BUTTON_RELEASE:
3419             nEventType = SalEvent::MouseButtonUp;
3420             break;
3421         default:
3422             return false;
3423     }
3424 
3425     vcl::DeletionListener aDel( pThis );
3426 
3427     if (pThis->isFloatGrabWindow())
3428     {
3429         //rhbz#1505379 if the window that got the event isn't our one, or there's none
3430         //of our windows under the mouse then close this popup window
3431         if (bDifferentEventWindow ||
3432             gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
3433         {
3434             if (pEvent->type == GDK_BUTTON_PRESS)
3435                 pThis->closePopup();
3436             else if (pEvent->type == GDK_BUTTON_RELEASE)
3437                 return true;
3438         }
3439     }
3440 
3441     int nEventX = pEvent->x;
3442     int nEventY = pEvent->y;
3443 
3444     if (bDifferentEventWindow)
3445         translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
3446 
3447     if (!aDel.isDeleted())
3448         pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
3449 
3450     bool bRet = false;
3451     if (!aDel.isDeleted())
3452     {
3453         bRet = pThis->DrawingAreaButton(nEventType,
3454                                         nEventX,
3455                                         nEventY,
3456                                         pEvent->button,
3457                                         pEvent->time,
3458                                         pEvent->state);
3459     }
3460 
3461     return bRet;
3462 }
3463 #else
3464 void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
3465 {
3466     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3467     pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y);
3468 }
3469 
3470 void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
3471 {
3472     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3473     pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y);
3474 }
3475 
3476 void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y)
3477 {
3478     GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture));
3479     GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
3480     int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
3481     DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType);
3482 }
3483 #endif
3484 
3485 #if !GTK_CHECK_VERSION(4, 0, 0)
3486 void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
3487 {
3488     //if we don't match previous pending states, flush that queue now
3489     if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
3490     {
3491         m_aSmoothScrollIdle.Stop();
3492         m_aSmoothScrollIdle.Invoke();
3493         assert(m_aPendingScrollEvents.empty());
3494     }
3495     //add scroll event to queue
3496     m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
3497     if (!m_aSmoothScrollIdle.IsActive())
3498         m_aSmoothScrollIdle.Start();
3499 }
3500 #endif
3501 
3502 void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState)
3503 {
3504     SalWheelMouseEvent aEvent;
3505 
3506     aEvent.mnTime = nTime;
3507     aEvent.mnX = nEventX;
3508     // --- RTL --- (mirror mouse pos)
3509     if (AllSettings::GetLayoutRTL())
3510         aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
3511     aEvent.mnY = nEventY;
3512     aEvent.mnCode = GetMouseModCode(nState);
3513 
3514     // rhbz#1344042 "Traditionally" in gtk3 we took a single up/down event as
3515     // equating to 3 scroll lines and a delta of 120. So scale the delta here
3516     // by 120 where a single mouse wheel click is an incoming delta_x of 1
3517     // and divide that by 40 to get the number of scroll lines
3518     if (delta_x != 0.0)
3519     {
3520         aEvent.mnDelta = -delta_x * 120;
3521         aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
3522         if (aEvent.mnDelta == 0)
3523             aEvent.mnDelta = aEvent.mnNotchDelta;
3524         aEvent.mbHorz = true;
3525         aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
3526         CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3527     }
3528 
3529     if (delta_y != 0.0)
3530     {
3531         aEvent.mnDelta = -delta_y * 120;
3532         aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
3533         if (aEvent.mnDelta == 0)
3534             aEvent.mnDelta = aEvent.mnNotchDelta;
3535         aEvent.mbHorz = false;
3536         aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
3537         CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3538     }
3539 }
3540 
3541 #if !GTK_CHECK_VERSION(4, 0, 0)
3542 IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
3543 {
3544     assert(!m_aPendingScrollEvents.empty());
3545 
3546     GdkEvent* pEvent = m_aPendingScrollEvents.back();
3547     auto nEventX = pEvent->scroll.x;
3548     auto nEventY = pEvent->scroll.y;
3549     auto nTime = pEvent->scroll.time;
3550     auto nState = pEvent->scroll.state;
3551 
3552     double delta_x(0.0), delta_y(0.0);
3553     for (auto pSubEvent : m_aPendingScrollEvents)
3554     {
3555         delta_x += pSubEvent->scroll.delta_x;
3556         delta_y += pSubEvent->scroll.delta_y;
3557         gdk_event_free(pSubEvent);
3558     }
3559     m_aPendingScrollEvents.clear();
3560 
3561     DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState);
3562 }
3563 #endif
3564 
3565 #if !GTK_CHECK_VERSION(4, 0, 0)
3566 SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
3567 {
3568     SalWheelMouseEvent aEvent;
3569 
3570     aEvent.mnTime = rEvent.time;
3571     aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
3572     aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
3573     aEvent.mnCode = GetMouseModCode(rEvent.state);
3574 
3575     switch (rEvent.direction)
3576     {
3577         case GDK_SCROLL_UP:
3578             aEvent.mnDelta = 120;
3579             aEvent.mnNotchDelta = 1;
3580             aEvent.mnScrollLines = 3;
3581             aEvent.mbHorz = false;
3582             break;
3583 
3584         case GDK_SCROLL_DOWN:
3585             aEvent.mnDelta = -120;
3586             aEvent.mnNotchDelta = -1;
3587             aEvent.mnScrollLines = 3;
3588             aEvent.mbHorz = false;
3589             break;
3590 
3591         case GDK_SCROLL_LEFT:
3592             aEvent.mnDelta = 120;
3593             aEvent.mnNotchDelta = 1;
3594             aEvent.mnScrollLines = 3;
3595             aEvent.mbHorz = true;
3596             break;
3597 
3598         case GDK_SCROLL_RIGHT:
3599             aEvent.mnDelta = -120;
3600             aEvent.mnNotchDelta = -1;
3601             aEvent.mnScrollLines = 3;
3602             aEvent.mbHorz = true;
3603             break;
3604         default:
3605             break;
3606     }
3607 
3608     return aEvent;
3609 }
3610 
3611 gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
3612 {
3613     GdkEventScroll& rEvent = pInEvent->scroll;
3614 
3615     UpdateLastInputEventTime(rEvent.time);
3616 
3617     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3618 
3619     if (rEvent.direction == GDK_SCROLL_SMOOTH)
3620     {
3621         pThis->LaunchAsyncScroll(pInEvent);
3622         return true;
3623     }
3624 
3625     //if we have smooth scrolling previous pending states, flush that queue now
3626     if (!pThis->m_aPendingScrollEvents.empty())
3627     {
3628         pThis->m_aSmoothScrollIdle.Stop();
3629         pThis->m_aSmoothScrollIdle.Invoke();
3630         assert(pThis->m_aPendingScrollEvents.empty());
3631     }
3632 
3633     SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));
3634 
3635     // --- RTL --- (mirror mouse pos)
3636     if (AllSettings::GetLayoutRTL())
3637         aEvent.mnX = pThis->maGeometry.width() - 1 - aEvent.mnX;
3638 
3639     pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);
3640 
3641     return true;
3642 }
3643 #else
3644 gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame)
3645 {
3646     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3647 
3648     GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3649     GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3650 
3651     auto nTime = gdk_event_get_time(pEvent);
3652 
3653     UpdateLastInputEventTime(nTime);
3654 
3655     double nEventX(0.0), nEventY(0.0);
3656     gdk_event_get_position(pEvent, &nEventX, &nEventY);
3657 
3658     pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState);
3659 
3660     return true;
3661 }
3662 
3663 gboolean GtkSalFrame::event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y)
3664 {
3665     return GtkSalFrame::signalScroll(pController, delta_x, delta_y, this);
3666 }
3667 
3668 #endif
3669 
3670 void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
3671 {
3672     gdouble x, y;
3673     GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
3674     //I feel I want the first point of the sequence, not the last point which
3675     //the docs say this gives, but for the moment assume we start and end
3676     //within the same vcl window
3677     if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
3678     {
3679         SalGestureSwipeEvent aEvent;
3680         aEvent.mnVelocityX = velocity_x;
3681         aEvent.mnVelocityY = velocity_y;
3682         aEvent.mnX = x;
3683         aEvent.mnY = y;
3684 
3685         GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3686         pThis->CallCallbackExc(SalEvent::GestureSwipe, &aEvent);
3687     }
3688 }
3689 
3690 void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
3691 {
3692     GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
3693     if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
3694     {
3695         SalGestureLongPressEvent aEvent;
3696         aEvent.mnX = x;
3697         aEvent.mnY = y;
3698 
3699         GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3700         pThis->CallCallbackExc(SalEvent::GestureLongPress, &aEvent);
3701     }
3702 }
3703 
3704 void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState)
3705 {
3706     UpdateLastInputEventTime(nTime);
3707 
3708     SalMouseEvent aEvent;
3709     aEvent.mnTime = nTime;
3710     aEvent.mnX = nEventX;
3711     aEvent.mnY = nEventY;
3712     aEvent.mnCode = GetMouseModCode(nState);
3713     aEvent.mnButton = 0;
3714 
3715     if( AllSettings::GetLayoutRTL() )
3716         aEvent.mnX = maGeometry.width() - 1 - aEvent.mnX;
3717 
3718     CallCallbackExc(SalEvent::MouseMove, &aEvent);
3719 }
3720 
3721 #if GTK_CHECK_VERSION(4, 0, 0)
3722 void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
3723 {
3724     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3725     GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3726     GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3727     pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType);
3728 }
3729 #else
3730 gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
3731 {
3732     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3733     GtkWidget* pEventWidget = pThis->getMouseEventWidget();
3734     bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);
3735 
3736     //If a menu, e.g. font name dropdown, is open, then under wayland moving the
3737     //mouse in the top left corner of the toplevel window in a
3738     //0,0,float-width,float-height area generates motion events which are
3739     //delivered to the dropdown
3740     if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
3741         return true;
3742 
3743     vcl::DeletionListener aDel( pThis );
3744 
3745     int nEventX = pEvent->x;
3746     int nEventY = pEvent->y;
3747 
3748     if (bDifferentEventWindow)
3749         translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);
3750 
3751     pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
3752 
3753     if (!aDel.isDeleted())
3754         pThis->DrawingAreaMotion(nEventX, nEventY, pEvent->time, pEvent->state);
3755 
3756     if (!aDel.isDeleted())
3757     {
3758         // ask for the next hint
3759         gint x, y;
3760         GdkModifierType mask;
3761         gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
3762     }
3763 
3764     return true;
3765 }
3766 #endif
3767 
3768 void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
3769 {
3770     UpdateLastInputEventTime(nTime);
3771 
3772     SalMouseEvent aEvent;
3773     aEvent.mnTime = nTime;
3774     aEvent.mnX = nEventX;
3775     aEvent.mnY = nEventY;
3776     aEvent.mnCode = GetMouseModCode(nState);
3777     aEvent.mnButton = 0;
3778 
3779     if (AllSettings::GetLayoutRTL())
3780         aEvent.mnX = maGeometry.width()-1-aEvent.mnX;
3781 
3782     CallCallbackExc(nEventType, &aEvent);
3783 }
3784 
3785 #if GTK_CHECK_VERSION(4, 0, 0)
3786 void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
3787 {
3788     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3789     GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3790     GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3791     pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
3792 }
3793 
3794 void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame)
3795 {
3796     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3797     GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
3798     GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
3799     pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
3800 }
3801 #else
3802 gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
3803 {
3804     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3805     pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
3806                                pEvent->x,
3807                                pEvent->y,
3808                                pEvent->time,
3809                                pEvent->state);
3810     return true;
3811 }
3812 #endif
3813 
3814 cairo_t* GtkSalFrame::getCairoContext() const
3815 {
3816     cairo_t* cr = cairo_create(m_pSurface);
3817     assert(cr);
3818     return cr;
3819 }
3820 
3821 void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
3822                           sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
3823 {
3824 #if OSL_DEBUG_LEVEL > 0
3825     if (dumpframes)
3826     {
3827         static int frame;
3828         OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
3829         cairo_t* cr = getCairoContext();
3830         cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
3831         cairo_destroy(cr);
3832     }
3833 #endif
3834 
3835     // quite a bit of noise in RTL mode with negative widths
3836     if (nExtentsWidth <= 0 || nExtentsHeight <= 0)
3837         return;
3838 
3839 #if !GTK_CHECK_VERSION(4, 0, 0)
3840     gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea),
3841                                nExtentsX, nExtentsY,
3842                                nExtentsWidth, nExtentsHeight);
3843 #else
3844     gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
3845     (void)nExtentsX;
3846     (void)nExtentsY;
3847 #endif
3848 }
3849 
3850 // blit our backing cairo surface to the target cairo context
3851 void GtkSalFrame::DrawingAreaDraw(cairo_t *cr)
3852 {
3853     cairo_set_source_surface(cr, m_pSurface, 0, 0);
3854     cairo_paint(cr);
3855 }
3856 
3857 #if !GTK_CHECK_VERSION(4, 0, 0)
3858 gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
3859 {
3860     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3861     pThis->DrawingAreaDraw(cr);
3862     return false;
3863 }
3864 #else
3865 void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
3866 {
3867     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3868     pThis->DrawingAreaDraw(cr);
3869 }
3870 #endif
3871 
3872 void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight)
3873 {
3874     // ignore size-allocations that occur during configuring an embedded SalObject
3875     if (m_bSalObjectSetPosSize)
3876         return;
3877     maGeometry.setSize({ nWidth, nHeight });
3878     bool bRealized = gtk_widget_get_realized(pWidget);
3879     if (bRealized)
3880         AllocateFrame();
3881     CallCallbackExc( SalEvent::Resize, nullptr );
3882     if (bRealized)
3883         TriggerPaintEvent();
3884 }
3885 
3886 #if !GTK_CHECK_VERSION(4, 0, 0)
3887 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
3888 {
3889     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3890     pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
3891 }
3892 #else
3893 void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
3894 {
3895     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3896     pThis->DrawingAreaResized(pWidget, nWidth, nHeight);
3897 }
3898 #endif
3899 
3900 #if !GTK_CHECK_VERSION(4, 0, 0)
3901 namespace {
3902 
3903 void swapDirection(GdkGravity& gravity)
3904 {
3905     if (gravity == GDK_GRAVITY_NORTH_WEST)
3906         gravity = GDK_GRAVITY_NORTH_EAST;
3907     else if (gravity == GDK_GRAVITY_NORTH_EAST)
3908         gravity = GDK_GRAVITY_NORTH_WEST;
3909     else if (gravity == GDK_GRAVITY_SOUTH_WEST)
3910         gravity = GDK_GRAVITY_SOUTH_EAST;
3911     else if (gravity == GDK_GRAVITY_SOUTH_EAST)
3912         gravity = GDK_GRAVITY_SOUTH_WEST;
3913 }
3914 
3915 }
3916 #endif
3917 
3918 void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
3919 {
3920     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3921     pThis->AllocateFrame();
3922     if (pThis->m_bSalObjectSetPosSize)
3923         return;
3924     pThis->TriggerPaintEvent();
3925 
3926 #if !GTK_CHECK_VERSION(4, 0, 0)
3927     if (!pThis->m_bFloatPositioned)
3928         return;
3929 
3930     static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
3931                                                                  GdkGravity, GdkAnchorHints, gint, gint)>(
3932                                                                     dlsym(nullptr, "gdk_window_move_to_rect"));
3933     if (!window_move_to_rect)
3934         return;
3935 
3936     GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;
3937 
3938     if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
3939     {
3940         rect_anchor = GDK_GRAVITY_NORTH_WEST;
3941         menu_anchor = GDK_GRAVITY_NORTH_EAST;
3942     }
3943     else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
3944     {
3945         rect_anchor = GDK_GRAVITY_NORTH_WEST;
3946         menu_anchor = GDK_GRAVITY_SOUTH_WEST;
3947     }
3948     else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
3949     {
3950         rect_anchor = GDK_GRAVITY_NORTH_EAST;
3951     }
3952 
3953     VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
3954     if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
3955     {
3956         swapDirection(rect_anchor);
3957         swapDirection(menu_anchor);
3958     }
3959 
3960     AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
3961     switch (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)))
3962     {
3963         case GDK_WINDOW_TOPLEVEL:
3964             break;
3965         case GDK_WINDOW_CHILD:
3966         {
3967             // See tdf#152155 for an example
3968             gtk_coord nX(0), nY(0.0);
3969             gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
3970             aFloatRect.Move(nX, nY);
3971             break;
3972         }
3973         default:
3974         {
3975             // See tdf#154072 for an example
3976             aFloatRect.Move(-pThis->m_pParent->maGeometry.x(), -pThis->m_pParent->maGeometry.y());
3977             break;
3978         }
3979     }
3980 
3981     GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
3982                        static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};
3983 
3984     GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
3985     window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
3986 #endif
3987 }
3988 
3989 #if !GTK_CHECK_VERSION(4, 0, 0)
3990 gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
3991 {
3992     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
3993 
3994     bool bMoved = false;
3995     int x = pEvent->x, y = pEvent->y;
3996 
3997     /* #i31785# claims we cannot trust the x,y members of the event;
3998      * they are e.g. not set correctly on maximize/demaximize;
3999      * yet the gdkdisplay-x11.c code handling configure_events has
4000      * done this XTranslateCoordinates work since the day ~zero.
4001      */
4002     if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.x() || y != pThis->maGeometry.y() )
4003     {
4004         bMoved = true;
4005         pThis->m_bGeometryIsProvisional = false;
4006         pThis->maGeometry.setPos({ x, y });
4007     }
4008 
4009     // update decoration hints
4010     GdkRectangle aRect;
4011     gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
4012     pThis->maGeometry.setTopDecoration(y - aRect.y);
4013     pThis->maGeometry.setBottomDecoration(aRect.y + aRect.height - y - pEvent->height);
4014     pThis->maGeometry.setLeftDecoration(x - aRect.x);
4015     pThis->maGeometry.setRightDecoration(aRect.x + aRect.width - x - pEvent->width);
4016     pThis->updateScreenNumber();
4017 
4018     if (bMoved)
4019     {
4020         ImplSVData* pSVData = ImplGetSVData();
4021         if (pSVData->maNWFData.mbCanDetermineWindowPosition)
4022             pThis->CallCallbackExc(SalEvent::Move, nullptr);
4023     }
4024 
4025     return false;
4026 }
4027 #endif
4028 
4029 void GtkSalFrame::queue_draw()
4030 {
4031     gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
4032 }
4033 
4034 void GtkSalFrame::TriggerPaintEvent()
4035 {
4036     //Under gtk2 we can basically paint directly into the XWindow and on
4037     //additional "expose-event" events we can re-render the missing pieces
4038     //
4039     //Under gtk3 we have to keep our own buffer up to date and flush it into
4040     //the given cairo context on "draw". So we emit a paint event on
4041     //opportune resize trigger events to initially fill our backbuffer and then
4042     //keep it up to date with our direct paints and tell gtk those regions
4043     //have changed and then blit them into the provided cairo context when
4044     //we get the "draw"
4045     //
4046     //The other alternative was to always paint everything on "draw", but
4047     //that duplicates the amount of drawing and is hideously slow
4048     SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.width() << "x" << maGeometry.height());
4049     SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
4050     CallCallbackExc(SalEvent::Paint, &aPaintEvt);
4051     queue_draw();
4052 }
4053 
4054 void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
4055 {
4056     SalGenericInstance* pSalInstance = GetGenericInstance();
4057 
4058     // check if printers have changed (analogous to salframe focus handler)
4059     pSalInstance->updatePrinterUpdate();
4060 
4061     if (nEventType == SalEvent::LoseFocus)
4062         m_nKeyModifiers = ModKeyFlags::NONE;
4063 
4064     if (m_pIMHandler)
4065     {
4066         bool bFocusInAnotherGtkWidget = false;
4067         if (GTK_IS_WINDOW(m_pWindow))
4068         {
4069             GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
4070             bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
4071         }
4072         if (!bFocusInAnotherGtkWidget)
4073             m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus);
4074     }
4075 
4076     // ask for changed printers like generic implementation
4077     if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit())
4078         pSalInstance->updatePrinterUpdate();
4079 
4080     CallCallbackExc(nEventType, nullptr);
4081 }
4082 
4083 #if !GTK_CHECK_VERSION(4, 0, 0)
4084 gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
4085 {
4086     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4087 
4088     SalGenericInstance *pSalInstance = GetGenericInstance();
4089 
4090     // check if printers have changed (analogous to salframe focus handler)
4091     pSalInstance->updatePrinterUpdate();
4092 
4093     if( !pEvent->in )
4094         pThis->m_nKeyModifiers = ModKeyFlags::NONE;
4095 
4096     if( pThis->m_pIMHandler )
4097     {
4098         bool bFocusInAnotherGtkWidget = false;
4099         if (GTK_IS_WINDOW(pThis->m_pWindow))
4100         {
4101             GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4102             bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
4103         }
4104         if (!bFocusInAnotherGtkWidget)
4105             pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
4106     }
4107 
4108     // ask for changed printers like generic implementation
4109     if( pEvent->in && pSalInstance->isPrinterInit() )
4110         pSalInstance->updatePrinterUpdate();
4111 
4112     // FIXME: find out who the hell steals the focus from our frame
4113     // while we have the pointer grabbed, this should not come from
4114     // the window manager. Is this an event that was still queued ?
4115     // The focus does not seem to get set inside our process
4116     // in the meantime do not propagate focus get/lose if floats are open
4117     if( m_nFloats == 0 )
4118     {
4119         GtkWidget* pGrabWidget;
4120         if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
4121             pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
4122         else
4123             pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
4124         bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
4125         pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
4126     }
4127 
4128     return false;
4129 }
4130 #else
4131 void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame)
4132 {
4133     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4134     pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
4135 }
4136 
4137 void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
4138 {
4139     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4140     pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus);
4141 }
4142 #endif
4143 
4144 // change of focus between native widgets within the toplevel
4145 #if !GTK_CHECK_VERSION(4, 0, 0)
4146 void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
4147 #else
4148 void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame)
4149 #endif
4150 {
4151     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4152 
4153     GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
4154 
4155     GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget);
4156     // see commentary in GtkSalObjectWidgetClip::Show
4157     if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
4158         return;
4159 
4160 #if GTK_CHECK_VERSION(4, 0, 0)
4161     GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4162 #endif
4163 
4164     // tdf#129634 interpret losing focus as focus passing explicitly to another widget
4165     bool bLoseFocus = pWidget && pWidget != pGrabWidget;
4166 
4167     // do not propagate focus get/lose if floats are open
4168     pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);
4169 
4170 #if !GTK_CHECK_VERSION(4, 0, 0)
4171     gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
4172 #endif
4173 }
4174 
4175 void GtkSalFrame::WindowMap()
4176 {
4177     if (m_bIconSetWhileUnmapped)
4178         SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow)));
4179 
4180     CallCallbackExc( SalEvent::Resize, nullptr );
4181     TriggerPaintEvent();
4182 }
4183 
4184 void GtkSalFrame::WindowUnmap()
4185 {
4186     CallCallbackExc( SalEvent::Resize, nullptr );
4187 
4188     if (m_bFloatPositioned)
4189     {
4190         // Unrealize is needed for cases where we reuse the same popup
4191         // (e.g. the font name control), making the realize signal fire
4192         // again on next show.
4193         gtk_widget_unrealize(m_pWindow);
4194         m_bFloatPositioned = false;
4195     }
4196 }
4197 
4198 #if GTK_CHECK_VERSION(4, 0, 0)
4199 void GtkSalFrame::signalMap(GtkWidget*, gpointer frame)
4200 {
4201     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4202     pThis->WindowMap();
4203 }
4204 
4205 void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
4206 {
4207     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4208     pThis->WindowUnmap();
4209 }
4210 #else
4211 gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
4212 {
4213     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4214     pThis->WindowMap();
4215     return false;
4216 }
4217 
4218 gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
4219 {
4220     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4221     pThis->WindowUnmap();
4222     return false;
4223 }
4224 #endif
4225 
4226 #if !GTK_CHECK_VERSION(4, 0, 0)
4227 
4228 static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest)
4229 {
4230     gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
4231     GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
4232     bool bHandled = pEvent->type == GDK_KEY_PRESS
4233         ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent)
4234         : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent);
4235     g_type_class_unref(pClass);
4236     return bHandled;
4237 }
4238 
4239 static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval)
4240 {
4241     const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget));
4242     gunichar cAccelChar = 0;
4243     if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr))
4244         return false;
4245     if (!cAccelChar)
4246         return false;
4247     auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar));
4248     if (nKeyval == nMnemonicKeyval)
4249         return gtk_widget_mnemonic_activate(pWidget, false);
4250     return false;
4251 }
4252 
4253 bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval)
4254 {
4255     bool bUsedInMenuBar = false;
4256     if (eState & GDK_ALT_MASK)
4257     {
4258         if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr)
4259         {
4260             GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar));
4261             for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
4262             {
4263                 bUsedInMenuBar = activate_menubar_mnemonic(static_cast<GtkWidget*>(pChild->data), nKeyval);
4264                 if (bUsedInMenuBar)
4265                     break;
4266             }
4267             g_list_free(pChildren);
4268         }
4269     }
4270     return bUsedInMenuBar;
4271 }
4272 
4273 gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
4274 {
4275     UpdateLastInputEventTime(pEvent->time);
4276 
4277     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4278 
4279     bool bFocusInAnotherGtkWidget = false;
4280 
4281     VclPtr<vcl::Window> xTopLevelInterimWindow;
4282 
4283     if (GTK_IS_WINDOW(pThis->m_pWindow))
4284     {
4285         GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
4286         bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
4287         if (bFocusInAnotherGtkWidget)
4288         {
4289             if (!gtk_widget_get_realized(pFocusWindow))
4290                 return true;
4291 
4292             // if the focus is not in our main widget, see if there is a handler
4293             // for this key stroke in GtkWindow first
4294             if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow)))
4295                 return true;
4296 
4297             // Is focus inside an InterimItemWindow? In which case find that
4298             // InterimItemWindow and send unconsumed keystrokes to it to
4299             // support ctrl-q etc shortcuts. Only bother to search for the
4300             // InterimItemWindow if it is a toplevel that fills its frame, or
4301             // the keystroke is sufficiently special its worth passing on,
4302             // e.g. F6 to switch between task-panels or F5 to close a navigator
4303             if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
4304             {
4305                 GtkWidget* pSearch = pFocusWindow;
4306                 while (pSearch)
4307                 {
4308                     void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
4309                     if (pData)
4310                     {
4311                         xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
4312                         break;
4313                     }
4314                     pSearch = gtk_widget_get_parent(pSearch);
4315                 }
4316             }
4317         }
4318     }
4319 
4320     if (pThis->isFloatGrabWindow())
4321         return signalKey(pWidget, pEvent, pThis->m_pParent);
4322 
4323     vcl::DeletionListener aDel( pThis );
4324 
4325     if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
4326         return true;
4327 
4328     bool bStopProcessingKey = false;
4329 
4330     // handle modifiers
4331     if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
4332         pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
4333         pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
4334         pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
4335         pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
4336     {
4337         sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
4338         ModKeyFlags nExtModMask = ModKeyFlags::NONE;
4339         sal_uInt16 nModMask = 0;
4340         // pressing just the ctrl key leads to a keysym of XK_Control but
4341         // the event state does not contain ControlMask. In the release
4342         // event it's the other way round: it does contain the Control mask.
4343         // The modifier mode therefore has to be adapted manually.
4344         switch( pEvent->keyval )
4345         {
4346             case GDK_KEY_Control_L:
4347                 nExtModMask = ModKeyFlags::LeftMod1;
4348                 nModMask = KEY_MOD1;
4349                 break;
4350             case GDK_KEY_Control_R:
4351                 nExtModMask = ModKeyFlags::RightMod1;
4352                 nModMask = KEY_MOD1;
4353                 break;
4354             case GDK_KEY_Alt_L:
4355                 nExtModMask = ModKeyFlags::LeftMod2;
4356                 nModMask = KEY_MOD2;
4357                 break;
4358             case GDK_KEY_Alt_R:
4359                 nExtModMask = ModKeyFlags::RightMod2;
4360                 nModMask = KEY_MOD2;
4361                 break;
4362             case GDK_KEY_Shift_L:
4363                 nExtModMask = ModKeyFlags::LeftShift;
4364                 nModMask = KEY_SHIFT;
4365                 break;
4366             case GDK_KEY_Shift_R:
4367                 nExtModMask = ModKeyFlags::RightShift;
4368                 nModMask = KEY_SHIFT;
4369                 break;
4370             // Map Meta/Super to MOD3 modifier on all Unix systems
4371             // except macOS
4372             case GDK_KEY_Meta_L:
4373             case GDK_KEY_Super_L:
4374                 nExtModMask = ModKeyFlags::LeftMod3;
4375                 nModMask = KEY_MOD3;
4376                 break;
4377             case GDK_KEY_Meta_R:
4378             case GDK_KEY_Super_R:
4379                 nExtModMask = ModKeyFlags::RightMod3;
4380                 nModMask = KEY_MOD3;
4381                 break;
4382         }
4383 
4384         SalKeyModEvent aModEvt;
4385         aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;
4386 
4387         if( pEvent->type == GDK_KEY_RELEASE )
4388         {
4389             aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
4390             aModEvt.mnCode = nModCode & ~nModMask;
4391             pThis->m_nKeyModifiers &= ~nExtModMask;
4392         }
4393         else
4394         {
4395             aModEvt.mnCode = nModCode | nModMask;
4396             pThis->m_nKeyModifiers |= nExtModMask;
4397             aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
4398         }
4399 
4400         pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
4401     }
4402     else
4403     {
4404         bool bRestoreDisallowCycleFocusOut = false;
4405 
4406         VclPtr<vcl::Window> xOrigFrameFocusWin;
4407         VclPtr<vcl::Window> xOrigFocusWin;
4408         if (xTopLevelInterimWindow)
4409         {
4410             // Focus is inside an InterimItemWindow so send unconsumed
4411             // keystrokes to by setting it as the mpFocusWin
4412             VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
4413             ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4414             xOrigFrameFocusWin = pFrameData->mpFocusWin;
4415             pFrameData->mpFocusWin = xTopLevelInterimWindow;
4416 
4417             ImplSVData* pSVData = ImplGetSVData();
4418             xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
4419             pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
4420 
4421             if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
4422             {
4423                 // For F6, allow the focus to leave the InterimItemWindow
4424                 pThis->AllowCycleFocusOut();
4425                 bRestoreDisallowCycleFocusOut = true;
4426             }
4427         }
4428 
4429         bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
4430                                                   pEvent->keyval,
4431                                                   pEvent->hardware_keycode,
4432                                                   pEvent->group,
4433                                                   sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
4434                                                   (pEvent->type == GDK_KEY_PRESS),
4435                                                   false);
4436 
4437         // tdf#144846 If this is registered as a menubar mnemonic then ensure
4438         // that any other widget won't be considered as a candidate by taking
4439         // over the task of launch the menubar menu outself
4440         // The code was moved here from its original position at beginning
4441         // of this function in order to resolve tdf#146174.
4442         if (!bStopProcessingKey && // module key handler did not process key
4443             pEvent->type == GDK_KEY_PRESS &&  // module key handler handles only GDK_KEY_PRESS
4444             GTK_IS_WINDOW(pThis->m_pWindow) &&
4445             pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval))
4446         {
4447             return true;
4448         }
4449 
4450         if (!aDel.isDeleted())
4451         {
4452             pThis->m_nKeyModifiers = ModKeyFlags::NONE;
4453 
4454             if (xTopLevelInterimWindow)
4455             {
4456                 // Focus was inside an InterimItemWindow, restore the original
4457                 // focus win, unless the focus was changed away from the
4458                 // InterimItemWindow which should only be possible with F6
4459                 VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
4460                 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4461                 if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
4462                     pFrameData->mpFocusWin = xOrigFrameFocusWin;
4463 
4464                 ImplSVData* pSVData = ImplGetSVData();
4465                 if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
4466                     pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
4467 
4468                 if (bRestoreDisallowCycleFocusOut)
4469                 {
4470                     // undo the above AllowCycleFocusOut for F6
4471                     pThis->DisallowCycleFocusOut();
4472                 }
4473             }
4474         }
4475 
4476     }
4477 
4478     if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
4479         pThis->m_pIMHandler->updateIMSpotLocation();
4480 
4481     return bStopProcessingKey;
4482 }
4483 #else
4484 
4485 bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state)
4486 {
4487     guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)));
4488     UpdateLastInputEventTime(nTime);
4489 
4490     bool bFocusInAnotherGtkWidget = false;
4491 
4492     VclPtr<vcl::Window> xTopLevelInterimWindow;
4493 
4494     if (GTK_IS_WINDOW(m_pWindow))
4495     {
4496         GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
4497         bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
4498         if (bFocusInAnotherGtkWidget)
4499         {
4500             if (!gtk_widget_get_realized(pFocusWindow))
4501                 return true;
4502             // if the focus is not in our main widget, see if there is a handler
4503             // for this key stroke in GtkWindow first
4504             bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow);
4505             if (bHandled)
4506                 return true;
4507 
4508             // Is focus inside an InterimItemWindow? In which case find that
4509             // InterimItemWindow and send unconsumed keystrokes to it to
4510             // support ctrl-q etc shortcuts. Only bother to search for the
4511             // InterimItemWindow if it is a toplevel that fills its frame, or
4512             // the keystroke is sufficiently special its worth passing on,
4513             // e.g. F6 to switch between task-panels or F5 to close a navigator
4514             if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval))
4515             {
4516                 GtkWidget* pSearch = pFocusWindow;
4517                 while (pSearch)
4518                 {
4519                     void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
4520                     if (pData)
4521                     {
4522                         xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
4523                         break;
4524                     }
4525                     pSearch = gtk_widget_get_parent(pSearch);
4526                 }
4527             }
4528         }
4529     }
4530 
4531     vcl::DeletionListener aDel(this);
4532 
4533     bool bStopProcessingKey = false;
4534 
4535     // handle modifiers
4536     if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R ||
4537         keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
4538         keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R ||
4539         keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R ||
4540         keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R )
4541     {
4542         sal_uInt16 nModCode = GetKeyModCode(state);
4543         ModKeyFlags nExtModMask = ModKeyFlags::NONE;
4544         sal_uInt16 nModMask = 0;
4545         // pressing just the ctrl key leads to a keysym of XK_Control but
4546         // the event state does not contain ControlMask. In the release
4547         // event it's the other way round: it does contain the Control mask.
4548         // The modifier mode therefore has to be adapted manually.
4549         switch (keyval)
4550         {
4551             case GDK_KEY_Control_L:
4552                 nExtModMask = ModKeyFlags::LeftMod1;
4553                 nModMask = KEY_MOD1;
4554                 break;
4555             case GDK_KEY_Control_R:
4556                 nExtModMask = ModKeyFlags::RightMod1;
4557                 nModMask = KEY_MOD1;
4558                 break;
4559             case GDK_KEY_Alt_L:
4560                 nExtModMask = ModKeyFlags::LeftMod2;
4561                 nModMask = KEY_MOD2;
4562                 break;
4563             case GDK_KEY_Alt_R:
4564                 nExtModMask = ModKeyFlags::RightMod2;
4565                 nModMask = KEY_MOD2;
4566                 break;
4567             case GDK_KEY_Shift_L:
4568                 nExtModMask = ModKeyFlags::LeftShift;
4569                 nModMask = KEY_SHIFT;
4570                 break;
4571             case GDK_KEY_Shift_R:
4572                 nExtModMask = ModKeyFlags::RightShift;
4573                 nModMask = KEY_SHIFT;
4574                 break;
4575             // Map Meta/Super to MOD3 modifier on all Unix systems
4576             // except macOS
4577             case GDK_KEY_Meta_L:
4578             case GDK_KEY_Super_L:
4579                 nExtModMask = ModKeyFlags::LeftMod3;
4580                 nModMask = KEY_MOD3;
4581                 break;
4582             case GDK_KEY_Meta_R:
4583             case GDK_KEY_Super_R:
4584                 nExtModMask = ModKeyFlags::RightMod3;
4585                 nModMask = KEY_MOD3;
4586                 break;
4587         }
4588 
4589         SalKeyModEvent aModEvt;
4590         aModEvt.mbDown = nEventType == SalEvent::KeyInput;
4591 
4592         if (!aModEvt.mbDown)
4593         {
4594             aModEvt.mnModKeyCode = m_nKeyModifiers;
4595             aModEvt.mnCode = nModCode & ~nModMask;
4596             m_nKeyModifiers &= ~nExtModMask;
4597         }
4598         else
4599         {
4600             aModEvt.mnCode = nModCode | nModMask;
4601             m_nKeyModifiers |= nExtModMask;
4602             aModEvt.mnModKeyCode = m_nKeyModifiers;
4603         }
4604 
4605         CallCallbackExc(SalEvent::KeyModChange, &aModEvt);
4606     }
4607     else
4608     {
4609         bool bRestoreDisallowCycleFocusOut = false;
4610 
4611         VclPtr<vcl::Window> xOrigFrameFocusWin;
4612         VclPtr<vcl::Window> xOrigFocusWin;
4613         if (xTopLevelInterimWindow)
4614         {
4615             // Focus is inside an InterimItemWindow so send unconsumed
4616             // keystrokes to by setting it as the mpFocusWin
4617             VclPtr<vcl::Window> xVclWindow = GetWindow();
4618             ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4619             xOrigFrameFocusWin = pFrameData->mpFocusWin;
4620             pFrameData->mpFocusWin = xTopLevelInterimWindow;
4621 
4622             ImplSVData* pSVData = ImplGetSVData();
4623             xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
4624             pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;
4625 
4626             if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
4627             {
4628                 // For F6, allow the focus to leave the InterimItemWindow
4629                 AllowCycleFocusOut();
4630                 bRestoreDisallowCycleFocusOut = true;
4631             }
4632         }
4633 
4634 
4635         bStopProcessingKey = doKeyCallback(state,
4636                                            keyval,
4637                                            keycode,
4638                                            0, // group
4639                                            sal_Unicode(gdk_keyval_to_unicode(keyval)),
4640                                            nEventType == SalEvent::KeyInput,
4641                                            false);
4642 
4643         if (!aDel.isDeleted())
4644         {
4645             m_nKeyModifiers = ModKeyFlags::NONE;
4646 
4647             if (xTopLevelInterimWindow)
4648             {
4649                 // Focus was inside an InterimItemWindow, restore the original
4650                 // focus win, unless the focus was changed away from the
4651                 // InterimItemWindow which should only be possible with F6
4652                 VclPtr<vcl::Window> xVclWindow = GetWindow();
4653                 ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
4654                 if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
4655                     pFrameData->mpFocusWin = xOrigFrameFocusWin;
4656 
4657                 ImplSVData* pSVData = ImplGetSVData();
4658                 if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
4659                     pSVData->mpWinData->mpFocusWin = xOrigFocusWin;
4660 
4661                 if (bRestoreDisallowCycleFocusOut)
4662                 {
4663                     // undo the above AllowCycleFocusOut for F6
4664                     DisallowCycleFocusOut();
4665                 }
4666             }
4667         }
4668     }
4669 
4670     if (m_pIMHandler)
4671         m_pIMHandler->updateIMSpotLocation();
4672 
4673     return bStopProcessingKey;
4674 }
4675 
4676 gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
4677 {
4678     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4679     return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state);
4680 }
4681 
4682 gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
4683 {
4684     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4685     return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state);
4686 }
4687 #endif
4688 
4689 bool GtkSalFrame::WindowCloseRequest()
4690 {
4691     CallCallbackExc(SalEvent::Close, nullptr);
4692     return true;
4693 }
4694 
4695 #if GTK_CHECK_VERSION(4, 0, 0)
4696 gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame)
4697 {
4698     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4699     return pThis->WindowCloseRequest();
4700 }
4701 #else
4702 gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
4703 {
4704     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4705     return pThis->WindowCloseRequest();
4706 }
4707 #endif
4708 
4709 const cairo_font_options_t* GtkSalFrame::get_font_options()
4710 {
4711     GtkWidget* pWidget = getMouseEventWidget();
4712 #if GTK_CHECK_VERSION(4, 0, 0)
4713     PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
4714     assert(pContext);
4715     return pango_cairo_context_get_font_options(pContext);
4716 #else
4717     return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget));
4718 #endif
4719 }
4720 
4721 #if GTK_CHECK_VERSION(4, 0, 0)
4722 void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame)
4723 #else
4724 void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
4725 #endif
4726 {
4727     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4728 
4729     // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
4730     GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );
4731 
4732     // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft"
4733 
4734     // fire off font-changed when the system cairo font hints change
4735     GtkInstance *pInstance = GetGtkInstance();
4736     const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
4737     const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options();
4738     bool bFontSettingsChanged = true;
4739     if (pLastCairoFontOptions && pCurrentCairoFontOptions)
4740         bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
4741     else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
4742         bFontSettingsChanged = false;
4743     if (bFontSettingsChanged)
4744     {
4745         pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
4746         GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
4747     }
4748 }
4749 
4750 #if !GTK_CHECK_VERSION(4, 0, 0)
4751 gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
4752 {
4753     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4754     if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) )
4755     {
4756         GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
4757         pThis->TriggerPaintEvent();
4758     }
4759 
4760     if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
4761         !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
4762     {
4763         pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
4764     }
4765 
4766     if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
4767         !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
4768     {
4769         if (pThis->isFloatGrabWindow())
4770             pThis->closePopup();
4771     }
4772 
4773     pThis->m_nState = pEvent->window_state.new_window_state;
4774 
4775     return false;
4776 }
4777 #else
4778 void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame)
4779 {
4780     GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface);
4781 
4782     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4783     if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) )
4784     {
4785         GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
4786         pThis->TriggerPaintEvent();
4787     }
4788 
4789     if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
4790         !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
4791     {
4792         pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
4793     }
4794 
4795     pThis->m_nState = eNewWindowState;
4796 }
4797 #endif
4798 
4799 namespace
4800 {
4801     bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
4802                           GestureEventZoomType eEventType)
4803     {
4804         gdouble x = 0;
4805         gdouble y = 0;
4806         gtk_gesture_get_point(gesture, sequence, &x, &y);
4807 
4808         SalGestureZoomEvent aEvent;
4809         aEvent.meEventType = eEventType;
4810         aEvent.mnX = x;
4811         aEvent.mnY = y;
4812         aEvent.mfScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
4813         GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4814         pThis->CallCallbackExc(SalEvent::GestureZoom, &aEvent);
4815         return true;
4816     }
4817 
4818     bool handleSignalRotate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
4819                             GestureEventRotateType eEventType)
4820     {
4821         gdouble x = 0;
4822         gdouble y = 0;
4823         gtk_gesture_get_point(gesture, sequence, &x, &y);
4824 
4825         SalGestureRotateEvent aEvent;
4826         aEvent.meEventType = eEventType;
4827         aEvent.mnX = x;
4828         aEvent.mnY = y;
4829         aEvent.mfAngleDelta = gtk_gesture_rotate_get_angle_delta(GTK_GESTURE_ROTATE(gesture));
4830         GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
4831         pThis->CallCallbackExc(SalEvent::GestureRotate, &aEvent);
4832         return true;
4833     }
4834 }
4835 
4836 bool GtkSalFrame::signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4837 {
4838     return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Begin);
4839 }
4840 
4841 bool GtkSalFrame::signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4842 {
4843     return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Update);
4844 }
4845 
4846 bool GtkSalFrame::signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
4847 {
4848     return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::End);
4849 }
4850 
4851 bool GtkSalFrame::signalRotateBegin(GtkGesture* gesture, GdkEventSequence* sequence,
4852                                     gpointer frame)
4853 {
4854     return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Begin);
4855 }
4856 
4857 bool GtkSalFrame::signalRotateUpdate(GtkGesture* gesture, GdkEventSequence* sequence,
4858                                      gpointer frame)
4859 {
4860     return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Update);
4861 }
4862 
4863 bool GtkSalFrame::signalRotateEnd(GtkGesture* gesture, GdkEventSequence* sequence,
4864                                   gpointer frame)
4865 {
4866     return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::End);
4867 }
4868 
4869 namespace
4870 {
4871     GdkDragAction VclToGdk(sal_Int8 dragOperation)
4872     {
4873         GdkDragAction eRet(static_cast<GdkDragAction>(0));
4874         if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
4875             eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
4876         if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
4877             eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
4878         if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
4879             eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
4880         return eRet;
4881     }
4882 
4883     sal_Int8 GdkToVcl(GdkDragAction dragOperation)
4884     {
4885         sal_Int8 nRet(0);
4886         if (dragOperation & GDK_ACTION_COPY)
4887             nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
4888         if (dragOperation & GDK_ACTION_MOVE)
4889             nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
4890         if (dragOperation & GDK_ACTION_LINK)
4891             nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
4892         return nRet;
4893     }
4894 }
4895 
4896 namespace
4897 {
4898     GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
4899     {
4900         GdkDragAction eAct(static_cast<GdkDragAction>(0));
4901 
4902         if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
4903             eAct = GDK_ACTION_MOVE;
4904         else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
4905             eAct = GDK_ACTION_COPY;
4906         else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
4907             eAct = GDK_ACTION_LINK;
4908 
4909         return eAct;
4910     }
4911 }
4912 
4913 static bool g_DropSuccessSet = false;
4914 static bool g_DropSuccess = false;
4915 
4916 namespace {
4917 
4918 #if GTK_CHECK_VERSION(4, 0, 0)
4919 
4920 void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
4921 {
4922     GdkDrop* drop = GDK_DROP(source);
4923     read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);
4924 
4925     GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr);
4926 
4927     if (!pResult)
4928     {
4929         pRes->bDone = true;
4930         g_main_context_wakeup(nullptr);
4931         return;
4932     }
4933 
4934     pRes->aVector.resize(read_transfer_result::BlockSize);
4935 
4936     g_input_stream_read_async(pResult,
4937                               pRes->aVector.data(),
4938                               pRes->aVector.size(),
4939                               G_PRIORITY_DEFAULT,
4940                               nullptr,
4941                               read_transfer_result::read_block_async_completed,
4942                               user_data);
4943 }
4944 #endif
4945 
4946 class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
4947 {
4948 #if !GTK_CHECK_VERSION(4, 0, 0)
4949     GdkDragContext *m_pContext;
4950     guint m_nTime;
4951 #else
4952     GdkDrop* m_pDrop;
4953 #endif
4954 public:
4955 #if !GTK_CHECK_VERSION(4, 0, 0)
4956     GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime)
4957         : m_pContext(pContext)
4958         , m_nTime(nTime)
4959 #else
4960     GtkDropTargetDropContext(GdkDrop* pDrop)
4961         : m_pDrop(pDrop)
4962 #endif
4963     {
4964     }
4965 
4966     // XDropTargetDropContext
4967     virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
4968     {
4969 #if !GTK_CHECK_VERSION(4, 0, 0)
4970         gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
4971 #else
4972         GdkDragAction eDragAction = getPreferredDragAction(dragOperation);
4973         gdk_drop_status(m_pDrop,
4974                         static_cast<GdkDragAction>(eDragAction | gdk_drop_get_actions(m_pDrop)),
4975                         eDragAction);
4976 #endif
4977     }
4978 
4979     virtual void SAL_CALL rejectDrop() override
4980     {
4981 #if !GTK_CHECK_VERSION(4, 0, 0)
4982         gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
4983 #else
4984         gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
4985 #endif
4986     }
4987 
4988     virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
4989     {
4990 #if !GTK_CHECK_VERSION(4, 0, 0)
4991         gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
4992 #else
4993         // should we do something better here
4994         gdk_drop_finish(m_pDrop, bSuccess
4995                         ? gdk_drop_get_actions(m_pDrop)
4996                         : static_cast<GdkDragAction>(0));
4997 #endif
4998         if (GtkInstDragSource::g_ActiveDragSource)
4999         {
5000             g_DropSuccessSet = true;
5001             g_DropSuccess = bSuccess;
5002         }
5003     }
5004 };
5005 
5006 }
5007 
5008 class GtkDnDTransferable : public GtkTransferable
5009 {
5010 #if !GTK_CHECK_VERSION(4, 0, 0)
5011     GdkDragContext *m_pContext;
5012     guint m_nTime;
5013     GtkWidget *m_pWidget;
5014     GtkInstDropTarget* m_pDropTarget;
5015 #else
5016     GdkDrop* m_pDrop;
5017 #endif
5018 #if !GTK_CHECK_VERSION(4, 0, 0)
5019     GMainLoop *m_pLoop;
5020     GtkSelectionData *m_pData;
5021 #endif
5022 public:
5023 #if !GTK_CHECK_VERSION(4, 0, 0)
5024     GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget)
5025         : m_pContext(pContext)
5026         , m_nTime(nTime)
5027         , m_pWidget(pWidget)
5028         , m_pDropTarget(pDropTarget)
5029 #else
5030     GtkDnDTransferable(GdkDrop *pDrop)
5031         : m_pDrop(pDrop)
5032 #endif
5033 #if !GTK_CHECK_VERSION(4, 0, 0)
5034         , m_pLoop(nullptr)
5035         , m_pData(nullptr)
5036 #endif
5037     {
5038     }
5039 
5040     virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
5041     {
5042         css::datatransfer::DataFlavor aFlavor(rFlavor);
5043         if (aFlavor.MimeType == "text/plain;charset=utf-16")
5044             aFlavor.MimeType = "text/plain;charset=utf-8";
5045 
5046         auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
5047         if (it == m_aMimeTypeToGtkType.end())
5048             return css::uno::Any();
5049 
5050         css::uno::Any aRet;
5051 
5052 #if !GTK_CHECK_VERSION(4, 0, 0)
5053         /* like gtk_clipboard_wait_for_contents run a sub loop
5054          * waiting for drag-data-received triggered from
5055          * gtk_drag_get_data
5056          */
5057         {
5058             m_pLoop = g_main_loop_new(nullptr, true);
5059             m_pDropTarget->SetFormatConversionRequest(this);
5060 
5061             gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);
5062 
5063             if (g_main_loop_is_running(m_pLoop))
5064                 main_loop_run(m_pLoop);
5065 
5066             g_main_loop_unref(m_pLoop);
5067             m_pLoop = nullptr;
5068             m_pDropTarget->SetFormatConversionRequest(nullptr);
5069         }
5070 
5071         if (aFlavor.MimeType == "text/plain;charset=utf-8")
5072         {
5073             OUString aStr;
5074             gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
5075             if (pText)
5076                 aStr = OStringToOUString(pText, RTL_TEXTENCODING_UTF8);
5077             g_free(pText);
5078             aRet <<= aStr.replaceAll("\r\n", "\n");
5079         }
5080         else
5081         {
5082             gint length(0);
5083             const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
5084                                                                             &length);
5085             // seen here was rawhide == nullptr and length set to -1
5086             if (rawdata)
5087             {
5088                 css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
5089                 aRet <<= aSeq;
5090             }
5091         }
5092 
5093         gtk_selection_data_free(m_pData);
5094 #else
5095         SalInstance* pInstance = GetSalInstance();
5096         read_transfer_result aRes;
5097         const char *mime_types[] = { it->second.getStr(), nullptr };
5098 
5099         gdk_drop_read_async(m_pDrop,
5100                             mime_types,
5101                             G_PRIORITY_DEFAULT,
5102                             nullptr,
5103                             read_drop_async_completed,
5104                             &aRes);
5105 
5106         while (!aRes.bDone)
5107             pInstance->DoYield(true, false);
5108 
5109         if (aFlavor.MimeType == "text/plain;charset=utf-8")
5110             aRet <<= aRes.get_as_string();
5111         else
5112             aRet <<= aRes.get_as_sequence();
5113 #endif
5114         return aRet;
5115     }
5116 
5117     virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
5118     {
5119 #if !GTK_CHECK_VERSION(4, 0, 0)
5120         std::vector<GdkAtom> targets;
5121         for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
5122             targets.push_back(static_cast<GdkAtom>(l->data));
5123         return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
5124 #else
5125         GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop);
5126         gsize n_targets;
5127         const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
5128         return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
5129 #endif
5130     }
5131 
5132 #if !GTK_CHECK_VERSION(4, 0, 0)
5133     void LoopEnd(GtkSelectionData *pData)
5134     {
5135         m_pData = pData;
5136         g_main_loop_quit(m_pLoop);
5137     }
5138 #endif
5139 };
5140 
5141 // For LibreOffice internal D&D we provide the Transferable without Gtk
5142 // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5143 GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource;
5144 
5145 #if GTK_CHECK_VERSION(4, 0, 0)
5146 
5147 gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame)
5148 {
5149     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5150     if (!pThis->m_pDropTarget)
5151         return false;
5152     return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y);
5153 }
5154 #else
5155 gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
5156 {
5157     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5158     if (!pThis->m_pDropTarget)
5159         return false;
5160     return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
5161 }
5162 #endif
5163 
5164 #if !GTK_CHECK_VERSION(4, 0, 0)
5165 gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
5166 #else
5167 gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y)
5168 #endif
5169 {
5170     // remove the deferred dragExit, as we'll do a drop
5171 #ifndef NDEBUG
5172     bool res =
5173 #endif
5174     g_idle_remove_by_data(this);
5175 #ifndef NDEBUG
5176 #if !GTK_CHECK_VERSION(4, 0, 0)
5177     assert(res);
5178 #else
5179     (void)res;
5180 #endif
5181 #endif
5182 
5183     css::datatransfer::dnd::DropTargetDropEvent aEvent;
5184     aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
5185 #if !GTK_CHECK_VERSION(4, 0, 0)
5186     aEvent.Context = new GtkDropTargetDropContext(context, time);
5187 #else
5188     aEvent.Context = new GtkDropTargetDropContext(drop);
5189 #endif
5190     aEvent.LocationX = x;
5191     aEvent.LocationY = y;
5192 #if !GTK_CHECK_VERSION(4, 0, 0)
5193     aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
5194 #else
5195     aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop))));
5196 #endif
5197     // ACTION_DEFAULT is documented as...
5198     // 'This means the user did not press any key during the Drag and Drop operation
5199     // and the action that was combined with ACTION_DEFAULT is the system default action'
5200     // in tdf#107031 writer won't insert a link when a heading is dragged from the
5201     // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
5202     // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
5203     // possible equivalent in gtk.
5204     // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
5205 #if !GTK_CHECK_VERSION(4,0,0)
5206     aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
5207     GdkModifierType mask;
5208     gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
5209 #else
5210     aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop));
5211     GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
5212 #endif
5213     if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
5214         aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;
5215 
5216     css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
5217     // For LibreOffice internal D&D we provide the Transferable without Gtk
5218     // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5219     if (GtkInstDragSource::g_ActiveDragSource)
5220         xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
5221     else
5222     {
5223 #if GTK_CHECK_VERSION(4,0,0)
5224         xTransferable = new GtkDnDTransferable(drop);
5225 #else
5226         xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
5227 #endif
5228     }
5229     aEvent.Transferable = xTransferable;
5230 
5231     fire_drop(aEvent);
5232 
5233     return true;
5234 }
5235 
5236 namespace {
5237 
5238 class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
5239 {
5240 #if !GTK_CHECK_VERSION(4, 0, 0)
5241     GdkDragContext *m_pContext;
5242     guint m_nTime;
5243 #else
5244     GdkDrop* m_pDrop;
5245 #endif
5246 public:
5247 #if !GTK_CHECK_VERSION(4, 0, 0)
5248     GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
5249         : m_pContext(pContext)
5250         , m_nTime(nTime)
5251 #else
5252     GtkDropTargetDragContext(GdkDrop* pDrop)
5253         : m_pDrop(pDrop)
5254 #endif
5255     {
5256     }
5257 
5258     virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
5259     {
5260 #if !GTK_CHECK_VERSION(4, 0, 0)
5261         gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
5262 #else
5263         gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation));
5264 #endif
5265     }
5266 
5267     virtual void SAL_CALL rejectDrag() override
5268     {
5269 #if !GTK_CHECK_VERSION(4, 0, 0)
5270         gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
5271 #else
5272         gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
5273 #endif
5274     }
5275 };
5276 
5277 }
5278 
5279 #if !GTK_CHECK_VERSION(4, 0, 0)
5280 void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
5281 {
5282     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5283     if (!pThis->m_pDropTarget)
5284         return;
5285     pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
5286 }
5287 
5288 void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
5289 {
5290     /*
5291      * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
5292      * with a loop inside a loop to get the right format, so if this is the
5293      * case return to the outer loop here with a copy of the desired data
5294      *
5295      * don't look at me like that.
5296      */
5297     if (!m_pFormatConversionRequest)
5298         return;
5299 
5300     m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
5301 }
5302 #endif
5303 
5304 #if GTK_CHECK_VERSION(4,0,0)
5305 GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame)
5306 {
5307     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5308     if (!pThis->m_pDropTarget)
5309         return GdkDragAction(0);
5310     return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y);
5311 }
5312 #else
5313 gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
5314 {
5315     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5316     if (!pThis->m_pDropTarget)
5317         return false;
5318     return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
5319 }
5320 #endif
5321 
5322 #if !GTK_CHECK_VERSION(4,0,0)
5323 gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
5324 #else
5325 GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y)
5326 #endif
5327 {
5328     if (!m_bInDrag)
5329     {
5330 #if !GTK_CHECK_VERSION(4,0,0)
5331         GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
5332         gtk_drag_highlight(pHighlightWidget);
5333 #else
5334         GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) :
5335                 gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context));
5336         gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false);
5337 #endif
5338     }
5339 
5340     css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
5341     aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
5342 #if !GTK_CHECK_VERSION(4,0,0)
5343     rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
5344 #else
5345     rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(pDrop);
5346 #endif
5347     //preliminary accept the Drag and select the preferred action, the fire_* will
5348     //inform the original caller of our choice and the callsite can decide
5349     //to overrule this choice. i.e. typically here we default to ACTION_MOVE
5350 #if !GTK_CHECK_VERSION(4,0,0)
5351     sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
5352     GdkModifierType mask;
5353     gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
5354 #else
5355     sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop));
5356     GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
5357 #endif
5358 
5359     // tdf#124411 default to move if drag originates within LO itself, default
5360     // to copy if it comes from outside, this is similar to srcAndDestEqual
5361     // in macosx DropTarget::determineDropAction equivalent
5362     sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ?
5363                                 css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
5364                                 css::datatransfer::dnd::DNDConstants::ACTION_COPY;
5365 
5366     // tdf#109227 if a modifier is held down, default to the matching
5367     // action for that modifier combo, otherwise pick the preferred
5368     // default from the possible source actions
5369     if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
5370         nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
5371     else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
5372         nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
5373     else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
5374         nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
5375     nNewDropAction &= nSourceActions;
5376 
5377     GdkDragAction eAction;
5378     if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
5379         eAction = getPreferredDragAction(nSourceActions);
5380     else
5381         eAction = getPreferredDragAction(nNewDropAction);
5382 
5383 #if !GTK_CHECK_VERSION(4,0,0)
5384     gdk_drag_status(context, eAction, time);
5385 #else
5386     gdk_drop_status(pDrop,
5387                     static_cast<GdkDragAction>(eAction | gdk_drop_get_actions(pDrop)),
5388                     eAction);
5389 #endif
5390     aEvent.Context = pContext;
5391     aEvent.LocationX = x;
5392     aEvent.LocationY = y;
5393     //under wayland at least, the action selected by gdk_drag_status on the
5394     //context is not immediately available via gdk_drag_context_get_selected_action
5395     //so here we set the DropAction from what we selected on the context, not
5396     //what the context says is selected
5397     aEvent.DropAction = GdkToVcl(eAction);
5398     aEvent.SourceActions = nSourceActions;
5399 
5400     if (!m_bInDrag)
5401     {
5402         css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
5403         // For LibreOffice internal D&D we provide the Transferable without Gtk
5404         // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
5405         if (GtkInstDragSource::g_ActiveDragSource)
5406             xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
5407         else
5408         {
5409 #if !GTK_CHECK_VERSION(4,0,0)
5410             xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
5411 #else
5412             xTransferable = new GtkDnDTransferable(pDrop);
5413 #endif
5414         }
5415         aEvent.SupportedDataFlavors = xTransferable->getTransferDataFlavors();
5416         fire_dragEnter(aEvent);
5417         m_bInDrag = true;
5418     }
5419     else
5420     {
5421         fire_dragOver(aEvent);
5422     }
5423 
5424 #if !GTK_CHECK_VERSION(4,0,0)
5425     return true;
5426 #else
5427     return eAction;
5428 #endif
5429 }
5430 
5431 #if GTK_CHECK_VERSION(4,0,0)
5432 void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame)
5433 {
5434     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5435     if (!pThis->m_pDropTarget)
5436         return;
5437     pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest)));
5438 }
5439 #else
5440 void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame)
5441 {
5442     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5443     if (!pThis->m_pDropTarget)
5444         return;
5445     pThis->m_pDropTarget->signalDragLeave(pWidget);
5446 }
5447 #endif
5448 
5449 static gboolean lcl_deferred_dragExit(gpointer user_data)
5450 {
5451     GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
5452     css::datatransfer::dnd::DropTargetEvent aEvent;
5453     aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
5454     pThis->fire_dragExit(aEvent);
5455     return false;
5456 }
5457 
5458 void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget)
5459 {
5460     m_bInDrag = false;
5461 
5462     GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
5463 #if !GTK_CHECK_VERSION(4,0,0)
5464     gtk_drag_unhighlight(pHighlightWidget);
5465 #else
5466     gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE);
5467 #endif
5468 
5469     // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
5470     // LO expect to either handle the drop or the exit... at least in Writer.
5471     // but since we don't know there will be a drop following the leave, defer the
5472     // exit handling to an idle.
5473     g_idle_add(lcl_deferred_dragExit, this);
5474 }
5475 
5476 void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
5477 {
5478     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
5479     if( pObj != pThis->m_pWindow )
5480         return;
5481 
5482     pThis->m_aDamageHandler.damaged = nullptr;
5483     pThis->m_aDamageHandler.handle = nullptr;
5484     if (pThis->m_pSurface)
5485         cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
5486     pThis->m_pFixedContainer = nullptr;
5487     pThis->m_pDrawingArea = nullptr;
5488 #if !GTK_CHECK_VERSION(4, 0, 0)
5489     pThis->m_pEventBox = nullptr;
5490 #endif
5491     pThis->m_pTopLevelGrid = nullptr;
5492     pThis->m_pWindow = nullptr;
5493     pThis->m_xFrameWeld.reset();
5494     pThis->InvalidateGraphics();
5495 }
5496 
5497 // GtkSalFrame::IMHandler
5498 
5499 GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
5500 : m_pFrame(pFrame),
5501   m_nPrevKeyPresses( 0 ),
5502   m_pIMContext( nullptr ),
5503   m_bFocused( true ),
5504   m_bPreeditJustChanged( false )
5505 {
5506     m_aInputEvent.mpTextAttr = nullptr;
5507     createIMContext();
5508 }
5509 
5510 GtkSalFrame::IMHandler::~IMHandler()
5511 {
5512     // cancel an eventual event posted to begin preedit again
5513     GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5514     deleteIMContext();
5515 }
5516 
5517 void GtkSalFrame::IMHandler::createIMContext()
5518 {
5519     if(  m_pIMContext )
5520         return;
5521 
5522     m_pIMContext = gtk_im_multicontext_new ();
5523     g_signal_connect( m_pIMContext, "commit",
5524                       G_CALLBACK (signalIMCommit), this );
5525     g_signal_connect( m_pIMContext, "preedit_changed",
5526                       G_CALLBACK (signalIMPreeditChanged), this );
5527     g_signal_connect( m_pIMContext, "retrieve_surrounding",
5528                       G_CALLBACK (signalIMRetrieveSurrounding), this );
5529     g_signal_connect( m_pIMContext, "delete_surrounding",
5530                       G_CALLBACK (signalIMDeleteSurrounding), this );
5531     g_signal_connect( m_pIMContext, "preedit_start",
5532                       G_CALLBACK (signalIMPreeditStart), this );
5533     g_signal_connect( m_pIMContext, "preedit_end",
5534                       G_CALLBACK (signalIMPreeditEnd), this );
5535 
5536     GetGenericUnixSalData()->ErrorTrapPush();
5537     im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget());
5538 #if GTK_CHECK_VERSION(4, 0, 0)
5539     gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext);
5540 #endif
5541     gtk_im_context_focus_in( m_pIMContext );
5542     GetGenericUnixSalData()->ErrorTrapPop();
5543     m_bFocused = true;
5544 
5545 }
5546 
5547 void GtkSalFrame::IMHandler::deleteIMContext()
5548 {
5549     if( !m_pIMContext )
5550         return;
5551 
5552     // first give IC a chance to deinitialize
5553     GetGenericUnixSalData()->ErrorTrapPush();
5554 #if GTK_CHECK_VERSION(4, 0, 0)
5555     gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr);
5556 #endif
5557     im_context_set_client_widget(m_pIMContext, nullptr);
5558     GetGenericUnixSalData()->ErrorTrapPop();
5559     // destroy old IC
5560     g_object_unref( m_pIMContext );
5561     m_pIMContext = nullptr;
5562 }
5563 
5564 void GtkSalFrame::IMHandler::doCallEndExtTextInput()
5565 {
5566     m_aInputEvent.mpTextAttr = nullptr;
5567     m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
5568 }
5569 
5570 void GtkSalFrame::IMHandler::updateIMSpotLocation()
5571 {
5572     SalExtTextInputPosEvent aPosEvent;
5573     m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
5574     GdkRectangle aArea;
5575     aArea.x = aPosEvent.mnX;
5576     aArea.y = aPosEvent.mnY;
5577     aArea.width = aPosEvent.mnWidth;
5578     aArea.height = aPosEvent.mnHeight;
5579     GetGenericUnixSalData()->ErrorTrapPush();
5580     gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
5581     GetGenericUnixSalData()->ErrorTrapPop();
5582 }
5583 
5584 void GtkSalFrame::IMHandler::sendEmptyCommit()
5585 {
5586     vcl::DeletionListener aDel( m_pFrame );
5587 
5588     SalExtTextInputEvent aEmptyEv;
5589     aEmptyEv.mpTextAttr         = nullptr;
5590     aEmptyEv.maText.clear();
5591     aEmptyEv.mnCursorPos        = 0;
5592     aEmptyEv.mnCursorFlags      = 0;
5593     m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
5594     if( ! aDel.isDeleted() )
5595         m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
5596 }
5597 
5598 void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
5599 {
5600     gtk_im_context_reset ( m_pIMContext );
5601 
5602     if( !m_aInputEvent.mpTextAttr )
5603         return;
5604 
5605     vcl::DeletionListener aDel( m_pFrame );
5606     // delete preedit in sal (commit an empty string)
5607     sendEmptyCommit();
5608     if( ! aDel.isDeleted() )
5609     {
5610         // mark previous preedit state again (will e.g. be sent at focus gain)
5611         m_aInputEvent.mpTextAttr = m_aInputFlags.data();
5612         if( m_bFocused )
5613         {
5614             // begin preedit again
5615             GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5616         }
5617     }
5618 }
5619 
5620 void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
5621 {
5622     m_bFocused = bFocusIn;
5623     if( bFocusIn )
5624     {
5625         GetGenericUnixSalData()->ErrorTrapPush();
5626         gtk_im_context_focus_in( m_pIMContext );
5627         GetGenericUnixSalData()->ErrorTrapPop();
5628         if( m_aInputEvent.mpTextAttr )
5629         {
5630             sendEmptyCommit();
5631             // begin preedit again
5632             GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5633         }
5634     }
5635     else
5636     {
5637         GetGenericUnixSalData()->ErrorTrapPush();
5638         gtk_im_context_focus_out( m_pIMContext );
5639         GetGenericUnixSalData()->ErrorTrapPop();
5640         // cancel an eventual event posted to begin preedit again
5641         GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
5642     }
5643 }
5644 
5645 #if !GTK_CHECK_VERSION(4, 0, 0)
5646 bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
5647 {
5648     vcl::DeletionListener aDel( m_pFrame );
5649 
5650     if( pEvent->type == GDK_KEY_PRESS )
5651     {
5652         // Add this key press event to the list of previous key presses
5653         // to which we compare key release events.  If a later key release
5654         // event has a matching key press event in this list, we swallow
5655         // the key release because some GTK Input Methods don't swallow it
5656         // for us.
5657         m_aPrevKeyPresses.emplace_back(pEvent );
5658         m_nPrevKeyPresses++;
5659 
5660         // Also pop off the earliest key press event if there are more than 10
5661         // already.
5662         while (m_nPrevKeyPresses > 10)
5663         {
5664             m_aPrevKeyPresses.pop_front();
5665             m_nPrevKeyPresses--;
5666         }
5667 
5668         GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
5669 
5670         // #i51353# update spot location on every key input since we cannot
5671         // know which key may activate a preedit choice window
5672         updateIMSpotLocation();
5673         if( aDel.isDeleted() )
5674             return true;
5675 
5676         bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
5677         g_object_unref( pRef );
5678 
5679         if( aDel.isDeleted() )
5680             return true;
5681 
5682         m_bPreeditJustChanged = false;
5683 
5684         if( bResult )
5685             return true;
5686         else
5687         {
5688             SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
5689             if( ! m_aPrevKeyPresses.empty() ) // sanity check
5690             {
5691                 // event was not swallowed, do not filter a following
5692                 // key release event
5693                 // note: this relies on gtk_im_context_filter_keypress
5694                 // returning without calling a handler (in the "not swallowed"
5695                 // case ) which might change the previous key press list so
5696                 // we would pop the wrong event here
5697                 m_aPrevKeyPresses.pop_back();
5698                 m_nPrevKeyPresses--;
5699             }
5700         }
5701     }
5702 
5703     // Determine if we got an earlier key press event corresponding to this key release
5704     if (pEvent->type == GDK_KEY_RELEASE)
5705     {
5706         GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
5707         bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
5708         g_object_unref( pRef );
5709 
5710         if( aDel.isDeleted() )
5711             return true;
5712 
5713         m_bPreeditJustChanged = false;
5714 
5715         auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
5716         // If we found a corresponding previous key press event, swallow the release
5717         // and remove the earlier key press from our list
5718         if (iter != m_aPrevKeyPresses.end())
5719         {
5720             m_aPrevKeyPresses.erase(iter);
5721             m_nPrevKeyPresses--;
5722             return true;
5723         }
5724 
5725         if( bResult )
5726             return true;
5727     }
5728 
5729     return false;
5730 }
5731 
5732 /* FIXME:
5733 * #122282# still more hacking: some IMEs never start a preedit but simply commit
5734 * in this case we cannot commit a single character. Workaround: do not do the
5735 * single key hack for enter or space if the unicode committed does not match
5736 */
5737 
5738 static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
5739 {
5740     bool bRet = true;
5741     switch( keyval )
5742     {
5743         case GDK_KEY_KP_Enter:
5744         case GDK_KEY_Return:
5745             if( cCode != '\n' && cCode != '\r' )
5746                 bRet = false;
5747             break;
5748         case GDK_KEY_space:
5749         case GDK_KEY_KP_Space:
5750             if( cCode != ' ' )
5751                 bRet = false;
5752             break;
5753         default:
5754             break;
5755     }
5756     return bRet;
5757 }
5758 
5759 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
5760 {
5761     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
5762 
5763     SolarMutexGuard aGuard;
5764     vcl::DeletionListener aDel( pThis->m_pFrame );
5765     {
5766         const bool bWasPreedit =
5767             (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
5768             pThis->m_bPreeditJustChanged;
5769 
5770         pThis->m_aInputEvent.mpTextAttr         = nullptr;
5771         pThis->m_aInputEvent.maText             = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
5772         pThis->m_aInputEvent.mnCursorPos        = pThis->m_aInputEvent.maText.getLength();
5773         pThis->m_aInputEvent.mnCursorFlags      = 0;
5774 
5775         pThis->m_aInputFlags.clear();
5776 
5777         /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
5778          *  which is logical and consequent. But since even simple input like
5779          *  <space> comes through the commit signal instead of signalKey
5780          *  and all kinds of windows only implement KeyInput (e.g. PushButtons,
5781          *  RadioButtons and a lot of other Controls), will send a single
5782          *  KeyInput/KeyUp sequence instead of an ExtText event if there
5783          *  never was a preedit and the text is only one character.
5784          *
5785          *  In this case there the last ExtText event must have been
5786          *  SalEvent::EndExtTextInput, either because of a regular commit
5787          *  or because there never was a preedit.
5788          */
5789         bool bSingleCommit = false;
5790         if( ! bWasPreedit
5791             && pThis->m_aInputEvent.maText.getLength() == 1
5792             && ! pThis->m_aPrevKeyPresses.empty()
5793             )
5794         {
5795             const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
5796             sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
5797 
5798             if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
5799             {
5800                 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
5801                 bSingleCommit = true;
5802             }
5803         }
5804         if( ! bSingleCommit )
5805         {
5806             pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
5807             if( ! aDel.isDeleted() )
5808                 pThis->doCallEndExtTextInput();
5809         }
5810         if( ! aDel.isDeleted() )
5811         {
5812             // reset input event
5813             pThis->m_aInputEvent.maText.clear();
5814             pThis->m_aInputEvent.mnCursorPos = 0;
5815             pThis->updateIMSpotLocation();
5816         }
5817     }
5818 }
5819 #else
5820 void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
5821 {
5822     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
5823 
5824     SolarMutexGuard aGuard;
5825     vcl::DeletionListener aDel( pThis->m_pFrame );
5826     {
5827 #if 0
5828         const bool bWasPreedit =
5829             (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
5830             pThis->m_bPreeditJustChanged;
5831 #endif
5832 
5833         pThis->m_aInputEvent.mpTextAttr         = nullptr;
5834         pThis->m_aInputEvent.maText             = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
5835         pThis->m_aInputEvent.mnCursorPos        = pThis->m_aInputEvent.maText.getLength();
5836         pThis->m_aInputEvent.mnCursorFlags      = 0;
5837 
5838         pThis->m_aInputFlags.clear();
5839 
5840         /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
5841          *  which is logical and consequent. But since even simple input like
5842          *  <space> comes through the commit signal instead of signalKey
5843          *  and all kinds of windows only implement KeyInput (e.g. PushButtons,
5844          *  RadioButtons and a lot of other Controls), will send a single
5845          *  KeyInput/KeyUp sequence instead of an ExtText event if there
5846          *  never was a preedit and the text is only one character.
5847          *
5848          *  In this case there the last ExtText event must have been
5849          *  SalEvent::EndExtTextInput, either because of a regular commit
5850          *  or because there never was a preedit.
5851          */
5852         bool bSingleCommit = false;
5853 #if 0
5854         // TODO this needs a rethink to work again if necessary
5855         if( ! bWasPreedit
5856             && pThis->m_aInputEvent.maText.getLength() == 1
5857             && ! pThis->m_aPrevKeyPresses.empty()
5858             )
5859         {
5860             const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
5861             sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];
5862 
5863             if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
5864             {
5865                 pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
5866                 bSingleCommit = true;
5867             }
5868         }
5869 #endif
5870         if( ! bSingleCommit )
5871         {
5872             pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
5873             if( ! aDel.isDeleted() )
5874                 pThis->doCallEndExtTextInput();
5875         }
5876         if( ! aDel.isDeleted() )
5877         {
5878             // reset input event
5879             pThis->m_aInputEvent.maText.clear();
5880             pThis->m_aInputEvent.mnCursorPos = 0;
5881             pThis->updateIMSpotLocation();
5882         }
5883     }
5884 }
5885 #endif
5886 
5887 OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
5888 {
5889     char*           pText           = nullptr;
5890     PangoAttrList*  pAttrs          = nullptr;
5891     gint            nCursorPos      = 0;
5892 
5893     gtk_im_context_get_preedit_string( pIMContext,
5894                                        &pText,
5895                                        &pAttrs,
5896                                        &nCursorPos );
5897 
5898     gint nUtf8Len = pText ? strlen(pText) : 0;
5899     OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
5900 
5901     std::vector<sal_Int32> aUtf16Offsets;
5902     for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
5903         aUtf16Offsets.push_back(nUtf16Offset);
5904 
5905     sal_Int32 nUtf32Len = aUtf16Offsets.size();
5906         // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
5907     aUtf16Offsets.push_back(sText.getLength());
5908 
5909     // sanitize the CurPos which is in utf-32
5910     if (nCursorPos < 0)
5911         nCursorPos = 0;
5912     else if (nCursorPos > nUtf32Len)
5913         nCursorPos = nUtf32Len;
5914 
5915     rCursorPos = aUtf16Offsets[nCursorPos];
5916     rCursorFlags = 0;
5917 
5918     rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);
5919 
5920     PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
5921     do
5922     {
5923         GSList *attr_list = nullptr;
5924         GSList *tmp_list = nullptr;
5925         gint nUtf8Start, nUtf8End;
5926         ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
5927 
5928         // docs say... "Get the range of the current segment ... the stored
5929         // return values are signed, not unsigned like the values in
5930         // PangoAttribute", which implies that the units are otherwise the same
5931         // as that of PangoAttribute whose docs state these units are "in
5932         // bytes"
5933         // so this is the utf8 range
5934         pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
5935 
5936         // sanitize the utf8 range
5937         nUtf8Start = std::min(nUtf8Start, nUtf8Len);
5938         nUtf8End = std::min(nUtf8End, nUtf8Len);
5939         if (nUtf8Start >= nUtf8End)
5940             continue;
5941 
5942         // get the utf32 range
5943         sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
5944         sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
5945 
5946         // sanitize the utf32 range
5947         nUtf32Start = std::min(nUtf32Start, nUtf32Len);
5948         nUtf32End = std::min(nUtf32End, nUtf32Len);
5949         if (nUtf32Start >= nUtf32End)
5950             continue;
5951 
5952         tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
5953         while (tmp_list)
5954         {
5955             PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);
5956 
5957             switch (pango_attr->klass->type)
5958             {
5959                 case PANGO_ATTR_BACKGROUND:
5960                     sal_attr |= ExtTextInputAttr::Highlight;
5961                     rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
5962                     break;
5963                 case PANGO_ATTR_UNDERLINE:
5964                 {
5965                     PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
5966                     switch (pango_underline->value)
5967                     {
5968                         case PANGO_UNDERLINE_NONE:
5969                             break;
5970                         case PANGO_UNDERLINE_DOUBLE:
5971                             sal_attr |= ExtTextInputAttr::DoubleUnderline;
5972                             break;
5973                         default:
5974                             sal_attr |= ExtTextInputAttr::Underline;
5975                             break;
5976                     }
5977                     break;
5978                 }
5979                 case PANGO_ATTR_STRIKETHROUGH:
5980                     sal_attr |= ExtTextInputAttr::RedText;
5981                     break;
5982                 default:
5983                     break;
5984             }
5985             pango_attribute_destroy (pango_attr);
5986             tmp_list = tmp_list->next;
5987         }
5988         if (!attr_list)
5989             sal_attr |= ExtTextInputAttr::Underline;
5990         g_slist_free (attr_list);
5991 
5992         // Set the sal attributes on our text
5993         // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
5994         for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
5995         {
5996             SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
5997                 "vcl.gtk3", "pango attrib out of range. Broken range: "
5998                 << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
5999                 << rInputFlags.size());
6000             if (i >= static_cast<int>(rInputFlags.size()))
6001                 continue;
6002             rInputFlags[i] |= sal_attr;
6003         }
6004     } while (pango_attr_iterator_next (iter));
6005     pango_attr_iterator_destroy(iter);
6006 
6007     g_free( pText );
6008     pango_attr_list_unref( pAttrs );
6009 
6010     return sText;
6011 }
6012 
6013 void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
6014 {
6015     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6016 
6017     sal_Int32 nCursorPos(0);
6018     sal_uInt8 nCursorFlags(0);
6019     std::vector<ExtTextInputAttr> aInputFlags;
6020     OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
6021     if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
6022     {
6023         // change from nothing to nothing -> do not start preedit
6024         // e.g. this will activate input into a calc cell without
6025         // user input
6026         return;
6027     }
6028 
6029     pThis->m_bPreeditJustChanged = true;
6030 
6031     bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
6032     pThis->m_aInputEvent.maText = sText;
6033     pThis->m_aInputEvent.mnCursorPos = nCursorPos;
6034     pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
6035     pThis->m_aInputFlags = aInputFlags;
6036     pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();
6037 
6038     SolarMutexGuard aGuard;
6039     vcl::DeletionListener aDel( pThis->m_pFrame );
6040 
6041     pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
6042     if( bEndPreedit && ! aDel.isDeleted() )
6043         pThis->doCallEndExtTextInput();
6044     if( ! aDel.isDeleted() )
6045         pThis->updateIMSpotLocation();
6046 }
6047 
6048 void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
6049 {
6050 }
6051 
6052 void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
6053 {
6054     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6055 
6056     pThis->m_bPreeditJustChanged = true;
6057 
6058     SolarMutexGuard aGuard;
6059     vcl::DeletionListener aDel( pThis->m_pFrame );
6060     pThis->doCallEndExtTextInput();
6061     if( ! aDel.isDeleted() )
6062         pThis->updateIMSpotLocation();
6063 }
6064 
6065 gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
6066 {
6067     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6068 
6069     SalSurroundingTextRequestEvent aEvt;
6070     aEvt.maText.clear();
6071     aEvt.mnStart = aEvt.mnEnd = 0;
6072 
6073     SolarMutexGuard aGuard;
6074     pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);
6075 
6076     OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
6077     std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
6078     gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
6079         OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
6080     return true;
6081 }
6082 
6083 gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
6084     gpointer im_handler )
6085 {
6086     GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);
6087 
6088     // First get the surrounding text
6089     SalSurroundingTextRequestEvent aSurroundingTextEvt;
6090     aSurroundingTextEvt.maText.clear();
6091     aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
6092 
6093     SolarMutexGuard aGuard;
6094     pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
6095 
6096     // Turn offset, nchars into a utf-16 selection
6097     Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
6098                                                                        aSurroundingTextEvt.mnStart,
6099                                                                        offset, nchars);
6100     Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
6101     if (aSelection == aInvalid)
6102         return false;
6103 
6104     SalSurroundingTextSelectionChangeEvent aEvt;
6105     aEvt.mnStart = aSelection.Min();
6106     aEvt.mnEnd = aSelection.Max();
6107 
6108     pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
6109 
6110     aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
6111     if (aSelection == aInvalid)
6112         return false;
6113 
6114     return true;
6115 }
6116 
6117 AbsoluteScreenPixelSize GtkSalDisplay::GetScreenSize( int nDisplayScreen )
6118 {
6119     AbsoluteScreenPixelRectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
6120     return AbsoluteScreenPixelSize( aRect.GetWidth(), aRect.GetHeight() );
6121 }
6122 
6123 sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
6124 {
6125     GdkSurface* pSurface = widget_get_surface(pWidget);
6126     GdkDisplay *pDisplay = getGdkDisplay();
6127 
6128 #if defined(GDK_WINDOWING_X11)
6129     if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
6130     {
6131         return gdk_x11_surface_get_xid(pSurface);
6132     }
6133 #endif
6134 
6135 #if defined(GDK_WINDOWING_WAYLAND)
6136     if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
6137     {
6138         return reinterpret_cast<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
6139     }
6140 #endif
6141 
6142     return 0;
6143 }
6144 
6145 void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6146                                      const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
6147 {
6148     m_xListener = rListener;
6149     m_xTrans = rTrans;
6150 }
6151 
6152 void GtkInstDragSource::setActiveDragSource()
6153 {
6154    // For LibreOffice internal D&D we provide the Transferable without Gtk
6155    // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
6156    g_ActiveDragSource = this;
6157    g_DropSuccessSet = false;
6158    g_DropSuccess = false;
6159 }
6160 
6161 #if !GTK_CHECK_VERSION(4, 0, 0)
6162 std::vector<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
6163 {
6164     return m_aConversionHelper.FormatsToGtk(rFormats);
6165 }
6166 #endif
6167 
6168 void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
6169                                   sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
6170                                   const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6171                                   const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
6172 {
6173     set_datatransfer(rTrans, rListener);
6174 
6175     if (m_pFrame)
6176     {
6177         setActiveDragSource();
6178 
6179         m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions));
6180     }
6181     else
6182         dragFailed();
6183 }
6184 
6185 void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
6186                             const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
6187                             VclToGtkHelper& rConversionHelper,
6188                             GdkDragAction sourceActions)
6189 {
6190     SolarMutexGuard aGuard;
6191 
6192     assert(m_pDragSource);
6193 
6194 #if GTK_CHECK_VERSION(4, 0, 0)
6195 
6196     GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay());
6197     GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()),
6198                                     gdk_seat_get_pointer(pSeat),
6199                                     transerable_content_new(&rConversionHelper, rTrans.get()),
6200                                     sourceActions,
6201                                     rEvent.DragOriginX, rEvent.DragOriginY);
6202 
6203     g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this);
6204     g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this);
6205     g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this);
6206 
6207 #else
6208 
6209     auto aFormats = rTrans->getTransferDataFlavors();
6210     auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats);
6211 
6212     GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());
6213 
6214     gint nDragButton = 1; // default to left button
6215     css::awt::MouseEvent aEvent;
6216     if (rEvent.Event >>= aEvent)
6217     {
6218         if (aEvent.Buttons & css::awt::MouseButton::LEFT )
6219             nDragButton = 1;
6220         else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
6221             nDragButton = 3;
6222         else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
6223             nDragButton = 2;
6224     }
6225 
6226     GdkEvent aFakeEvent;
6227     memset(&aFakeEvent, 0, sizeof(GdkEvent));
6228     aFakeEvent.type = GDK_BUTTON_PRESS;
6229     aFakeEvent.button.window = widget_get_surface(getMouseEventWidget());
6230     aFakeEvent.button.time = GDK_CURRENT_TIME;
6231 
6232     aFakeEvent.button.device = gtk_get_current_event_device();
6233     // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an
6234     // appropriate device to use.
6235     if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
6236     {
6237         GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
6238         GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER);
6239         for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next)
6240         {
6241             GdkDevice* pDevice = static_cast<GdkDevice*>(pEntry->data);
6242             if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD)
6243                 continue;
6244             if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr))
6245             {
6246                 aFakeEvent.button.device = pDevice;
6247                 break;
6248             }
6249         }
6250         g_list_free(pDevices);
6251     }
6252 
6253     GdkDragContext *pDrag;
6254     if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
6255         pDrag = nullptr;
6256     else
6257         pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
6258                                                 pTargetList,
6259                                                 sourceActions,
6260                                                 nDragButton,
6261                                                 &aFakeEvent,
6262                                                 rEvent.DragOriginX,
6263                                                 rEvent.DragOriginY);
6264 
6265     gtk_target_list_unref(pTargetList);
6266 
6267     for (auto &a : aGtkTargets)
6268         g_free(a.target);
6269 #endif
6270 
6271     if (!pDrag)
6272         m_pDragSource->dragFailed();
6273 }
6274 
6275 void GtkInstDragSource::dragFailed()
6276 {
6277     if (m_xListener.is())
6278     {
6279         datatransfer::dnd::DragSourceDropEvent aEv;
6280         aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
6281         aEv.DropSuccess = false;
6282         auto xListener = m_xListener;
6283         m_xListener.clear();
6284         xListener->dragDropEnd(aEv);
6285     }
6286 }
6287 
6288 #if GTK_CHECK_VERSION(4, 0, 0)
6289 void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame)
6290 #else
6291 gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
6292 #endif
6293 {
6294     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6295     if (pThis->m_pDragSource)
6296         pThis->m_pDragSource->dragFailed();
6297 #if !GTK_CHECK_VERSION(4, 0, 0)
6298     return false;
6299 #endif
6300 }
6301 
6302 void GtkInstDragSource::dragDelete()
6303 {
6304     if (m_xListener.is())
6305     {
6306         datatransfer::dnd::DragSourceDropEvent aEv;
6307         aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
6308         aEv.DropSuccess = true;
6309         auto xListener = m_xListener;
6310         m_xListener.clear();
6311         xListener->dragDropEnd(aEv);
6312     }
6313 }
6314 
6315 #if GTK_CHECK_VERSION(4, 0, 0)
6316 void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame)
6317 #else
6318 void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
6319 #endif
6320 {
6321     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6322     if (!pThis->m_pDragSource)
6323         return;
6324     pThis->m_pDragSource->dragDelete();
6325 }
6326 
6327 #if GTK_CHECK_VERSION(4, 0, 0)
6328 void GtkInstDragSource::dragEnd(GdkDrag* context)
6329 #else
6330 void GtkInstDragSource::dragEnd(GdkDragContext* context)
6331 #endif
6332 {
6333     if (m_xListener.is())
6334     {
6335         datatransfer::dnd::DragSourceDropEvent aEv;
6336 #if GTK_CHECK_VERSION(4, 0, 0)
6337         aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context));
6338 #else
6339         aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
6340 #endif
6341         // an internal drop can accept the drop but fail with dropComplete( false )
6342         // this is different than the GTK API
6343         if (g_DropSuccessSet)
6344             aEv.DropSuccess = g_DropSuccess;
6345         else
6346             aEv.DropSuccess = true;
6347         auto xListener = m_xListener;
6348         m_xListener.clear();
6349         xListener->dragDropEnd(aEv);
6350     }
6351     g_ActiveDragSource = nullptr;
6352 }
6353 
6354 #if GTK_CHECK_VERSION(4, 0, 0)
6355 void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame)
6356 #else
6357 void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
6358 #endif
6359 {
6360     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6361     if (!pThis->m_pDragSource)
6362         return;
6363     pThis->m_pDragSource->dragEnd(context);
6364 }
6365 
6366 #if !GTK_CHECK_VERSION(4, 0, 0)
6367 void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info)
6368 {
6369     m_aConversionHelper.setSelectionData(m_xTrans, data, info);
6370 }
6371 
6372 void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
6373                                     guint /*time*/, gpointer frame)
6374 {
6375     GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
6376     if (!pThis->m_pDragSource)
6377         return;
6378     pThis->m_pDragSource->dragDataGet(data, info);
6379 }
6380 #endif
6381 
6382 bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
6383 {
6384     SolarMutexGuard aGuard;
6385     bool nRet = false;
6386     try
6387     {
6388         nRet = CallCallback(nEvent, pEvent);
6389     }
6390     catch (...)
6391     {
6392         GetGtkSalData()->setException(std::current_exception());
6393     }
6394     return nRet;
6395 }
6396 
6397 #if !GTK_CHECK_VERSION(4, 0, 0)
6398 void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
6399 {
6400     bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
6401     m_bSalObjectSetPosSize = true;
6402     gtk_container_resize_children(pContainer);
6403     m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
6404 }
6405 #endif
6406 
6407 GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
6408 {
6409 #if !GTK_CHECK_VERSION(4, 0, 0)
6410     GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
6411     event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget)));
6412 
6413     GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
6414     gdk_event_set_device(event, gdk_seat_get_keyboard(seat));
6415 
6416     event->key.send_event = 1 /* TRUE */;
6417     event->key.time = gtk_get_current_event_time();
6418     event->key.state = 0;
6419     event->key.keyval = 0;
6420     event->key.length = 0;
6421     event->key.string = nullptr;
6422     event->key.hardware_keycode = 0;
6423     event->key.group = 0;
6424     event->key.is_modifier = false;
6425     return event;
6426 #else
6427     (void)pWidget;
6428     return nullptr;
6429 #endif
6430 }
6431 
6432 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
6433