/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <unx/gtk/gtkframe.hxx>
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkgdi.hxx>
#include <unx/gtk/gtksalmenu.hxx>
#include <unx/gtk/hudawareness.h>
#include <vcl/event.hxx>
#include <vcl/i18nhelp.hxx>
#include <vcl/keycodes.hxx>
#include <unx/geninst.h>
#include <headless/svpgdi.hxx>
#include <sal/log.hxx>
#include <tools/diagnose_ex.h>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
#include <vcl/settings.hxx>

#include <gtk/gtk.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unx/gtk/gtkbackend.hxx>

#include <window.h>

#include <basegfx/vector/b2ivector.hxx>

#include <dlfcn.h>

#include <algorithm>

#if OSL_DEBUG_LEVEL > 1
#  include <cstdio>
#endif

#include <i18nlangtag/mslangid.hxx>

#include <cstdlib>
#include <cmath>

#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>

#if !GTK_CHECK_VERSION(4, 0, 0)
#   define GDK_ALT_MASK GDK_MOD1_MASK
#   define GDK_TOPLEVEL_STATE_MAXIMIZED GDK_WINDOW_STATE_MAXIMIZED
#   define GDK_TOPLEVEL_STATE_MINIMIZED GDK_WINDOW_STATE_ICONIFIED
#   define gdk_wayland_surface_get_wl_surface gdk_wayland_window_get_wl_surface
#   define gdk_x11_surface_get_xid gdk_x11_window_get_xid
#endif

using namespace com::sun::star;

int GtkSalFrame::m_nFloats = 0;

static GDBusConnection* pSessionBus = nullptr;

sal_uInt16 GtkSalFrame::GetKeyModCode( guint state )
{
    sal_uInt16 nCode = 0;
    if( state & GDK_SHIFT_MASK )
        nCode |= KEY_SHIFT;
    if( state & GDK_CONTROL_MASK )
        nCode |= KEY_MOD1;
    if (state & GDK_ALT_MASK)
        nCode |= KEY_MOD2;
    if( state & GDK_SUPER_MASK )
        nCode |= KEY_MOD3;
    return nCode;
}

sal_uInt16 GtkSalFrame::GetMouseModCode( guint state )
{
    sal_uInt16 nCode = GetKeyModCode( state );
    if( state & GDK_BUTTON1_MASK )
        nCode |= MOUSE_LEFT;
    if( state & GDK_BUTTON2_MASK )
        nCode |= MOUSE_MIDDLE;
    if( state & GDK_BUTTON3_MASK )
        nCode |= MOUSE_RIGHT;

    return nCode;
}

// KEY_F26 is the last function key known to keycodes.hxx
static bool IsFunctionKeyVal(guint keyval)
{
    return keyval >= GDK_KEY_F1 && keyval <= GDK_KEY_F26;
}

sal_uInt16 GtkSalFrame::GetKeyCode(guint keyval)
{
    sal_uInt16 nCode = 0;
    if( keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9 )
        nCode = KEY_0 + (keyval-GDK_KEY_0);
    else if( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
        nCode = KEY_0 + (keyval-GDK_KEY_KP_0);
    else if( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
        nCode = KEY_A + (keyval-GDK_KEY_A );
    else if( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
        nCode = KEY_A + (keyval-GDK_KEY_a );
    else if (IsFunctionKeyVal(keyval))
    {
        switch( keyval )
        {
            // - - - - - Sun keyboard, see vcl/unx/source/app/saldisp.cxx
            // although GDK_KEY_F1 ... GDK_KEY_L10 are known to
            // gdk/gdkkeysyms.h, they are unlikely to be generated
            // except possibly by Solaris systems
            // this whole section needs review
            case GDK_KEY_L2:
                    nCode = KEY_F12;
                break;
            case GDK_KEY_L3:            nCode = KEY_PROPERTIES; break;
            case GDK_KEY_L4:            nCode = KEY_UNDO;       break;
            case GDK_KEY_L6:            nCode = KEY_COPY;       break; // KEY_F16
            case GDK_KEY_L8:            nCode = KEY_PASTE;      break; // KEY_F18
            case GDK_KEY_L10:           nCode = KEY_CUT;        break; // KEY_F20
            default:
                nCode = KEY_F1 + (keyval-GDK_KEY_F1);           break;
        }
    }
    else
    {
        switch( keyval )
        {
            case GDK_KEY_KP_Down:
            case GDK_KEY_Down:          nCode = KEY_DOWN;       break;
            case GDK_KEY_KP_Up:
            case GDK_KEY_Up:            nCode = KEY_UP;         break;
            case GDK_KEY_KP_Left:
            case GDK_KEY_Left:          nCode = KEY_LEFT;       break;
            case GDK_KEY_KP_Right:
            case GDK_KEY_Right:         nCode = KEY_RIGHT;      break;
            case GDK_KEY_KP_Begin:
            case GDK_KEY_KP_Home:
            case GDK_KEY_Begin:
            case GDK_KEY_Home:          nCode = KEY_HOME;       break;
            case GDK_KEY_KP_End:
            case GDK_KEY_End:           nCode = KEY_END;        break;
            case GDK_KEY_KP_Page_Up:
            case GDK_KEY_Page_Up:       nCode = KEY_PAGEUP;     break;
            case GDK_KEY_KP_Page_Down:
            case GDK_KEY_Page_Down:     nCode = KEY_PAGEDOWN;   break;
            case GDK_KEY_KP_Enter:
            case GDK_KEY_Return:        nCode = KEY_RETURN;     break;
            case GDK_KEY_Escape:        nCode = KEY_ESCAPE;     break;
            case GDK_KEY_ISO_Left_Tab:
            case GDK_KEY_KP_Tab:
            case GDK_KEY_Tab:           nCode = KEY_TAB;        break;
            case GDK_KEY_BackSpace:     nCode = KEY_BACKSPACE;  break;
            case GDK_KEY_KP_Space:
            case GDK_KEY_space:         nCode = KEY_SPACE;      break;
            case GDK_KEY_KP_Insert:
            case GDK_KEY_Insert:        nCode = KEY_INSERT;     break;
            case GDK_KEY_KP_Delete:
            case GDK_KEY_Delete:        nCode = KEY_DELETE;     break;
            case GDK_KEY_plus:
            case GDK_KEY_KP_Add:        nCode = KEY_ADD;        break;
            case GDK_KEY_minus:
            case GDK_KEY_KP_Subtract:   nCode = KEY_SUBTRACT;   break;
            case GDK_KEY_asterisk:
            case GDK_KEY_KP_Multiply:   nCode = KEY_MULTIPLY;   break;
            case GDK_KEY_slash:
            case GDK_KEY_KP_Divide:     nCode = KEY_DIVIDE;     break;
            case GDK_KEY_period:        nCode = KEY_POINT;      break;
            case GDK_KEY_decimalpoint:  nCode = KEY_POINT;      break;
            case GDK_KEY_comma:         nCode = KEY_COMMA;      break;
            case GDK_KEY_less:          nCode = KEY_LESS;       break;
            case GDK_KEY_greater:       nCode = KEY_GREATER;    break;
            case GDK_KEY_KP_Equal:
            case GDK_KEY_equal:         nCode = KEY_EQUAL;      break;
            case GDK_KEY_Find:          nCode = KEY_FIND;       break;
            case GDK_KEY_Menu:          nCode = KEY_CONTEXTMENU;break;
            case GDK_KEY_Help:          nCode = KEY_HELP;       break;
            case GDK_KEY_Undo:          nCode = KEY_UNDO;       break;
            case GDK_KEY_Redo:          nCode = KEY_REPEAT;     break;
            // on a sun keyboard this actually is usually SunXK_Stop = 0x0000FF69 (XK_Cancel),
            // but VCL doesn't have a key definition for that
            case GDK_KEY_Cancel:        nCode = KEY_F11;        break;
            case GDK_KEY_KP_Decimal:
            case GDK_KEY_KP_Separator:  nCode = KEY_DECIMAL;    break;
            case GDK_KEY_asciitilde:    nCode = KEY_TILDE;      break;
            case GDK_KEY_leftsinglequotemark:
            case GDK_KEY_quoteleft:    nCode = KEY_QUOTELEFT;    break;
            case GDK_KEY_bracketleft:  nCode = KEY_BRACKETLEFT;  break;
            case GDK_KEY_bracketright: nCode = KEY_BRACKETRIGHT; break;
            case GDK_KEY_semicolon:    nCode = KEY_SEMICOLON;    break;
            case GDK_KEY_quoteright:   nCode = KEY_QUOTERIGHT;   break;
            // some special cases, also see saldisp.cxx
            // - - - - - - - - - - - - -  Apollo - - - - - - - - - - - - - 0x1000
            // These can be found in ap_keysym.h
            case 0x1000FF02: // apXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1000FF03: // apXK_Cut
                nCode = KEY_CUT;
                break;
            case 0x1000FF04: // apXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1000FF14: // apXK_Repeat
                nCode = KEY_REPEAT;
                break;
            // Exit, Save
            // - - - - - - - - - - - - - - D E C - - - - - - - - - - - - - 0x1000
            // These can be found in DECkeysym.h
            case 0x1000FF00:
                nCode = KEY_DELETE;
                break;
            // - - - - - - - - - - - - - -  H P  - - - - - - - - - - - - - 0x1000
            // These can be found in HPkeysym.h
            case 0x1000FF73: // hpXK_DeleteChar
                nCode = KEY_DELETE;
                break;
            case 0x1000FF74: // hpXK_BackTab
            case 0x1000FF75: // hpXK_KP_BackTab
                nCode = KEY_TAB;
                break;
            // - - - - - - - - - - - - - - I B M - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - O S F - - - - - - - - - - - - - 0x1004
            // These also can be found in HPkeysym.h
            case 0x1004FF02: // osfXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1004FF03: // osfXK_Cut
                nCode = KEY_CUT;
                break;
            case 0x1004FF04: // osfXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1004FF07: // osfXK_BackTab
                nCode = KEY_TAB;
                break;
            case 0x1004FF08: // osfXK_BackSpace
                nCode = KEY_BACKSPACE;
                break;
            case 0x1004FF1B: // osfXK_Escape
                nCode = KEY_ESCAPE;
                break;
            // Up, Down, Left, Right, PageUp, PageDown
            // - - - - - - - - - - - - - - S C O - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - S G I - - - - - - - - - - - - - 0x1007
            // - - - - - - - - - - - - - - S N I - - - - - - - - - - - - -
            // - - - - - - - - - - - - - - S U N - - - - - - - - - - - - - 0x1005
            // These can be found in Sunkeysym.h
            case 0x1005FF10: // SunXK_F36
                nCode = KEY_F11;
                break;
            case 0x1005FF11: // SunXK_F37
                nCode = KEY_F12;
                break;
            case 0x1005FF70: // SunXK_Props
                nCode = KEY_PROPERTIES;
                break;
            case 0x1005FF71: // SunXK_Front
                nCode = KEY_FRONT;
                break;
            case 0x1005FF72: // SunXK_Copy
                nCode = KEY_COPY;
                break;
            case 0x1005FF73: // SunXK_Open
                nCode = KEY_OPEN;
                break;
            case 0x1005FF74: // SunXK_Paste
                nCode = KEY_PASTE;
                break;
            case 0x1005FF75: // SunXK_Cut
                nCode = KEY_CUT;
                break;
            // - - - - - - - - - - - - - X F 8 6 - - - - - - - - - - - - - 0x1008
            // These can be found in XF86keysym.h
            // but more importantly they are also available GTK/Gdk version 3
            // and hence are already provided in gdk/gdkkeysyms.h, and hence
            // in gdk/gdk.h
            case GDK_KEY_Copy:          nCode = KEY_COPY;  break; // 0x1008ff57
            case GDK_KEY_Cut:           nCode = KEY_CUT;   break; // 0x1008ff58
            case GDK_KEY_Open:          nCode = KEY_OPEN;  break; // 0x1008ff6b
            case GDK_KEY_Paste:         nCode = KEY_PASTE; break; // 0x1008ff6d
        }
    }

    return nCode;
}

#if !GTK_CHECK_VERSION(4, 0, 0)
guint GtkSalFrame::GetKeyValFor(GdkKeymap* pKeyMap, guint16 hardware_keycode, guint8 group)
{
    guint updated_keyval = 0;
    gdk_keymap_translate_keyboard_state(pKeyMap, hardware_keycode,
        GdkModifierType(0), group, &updated_keyval, nullptr, nullptr, nullptr);
    return updated_keyval;
}
#endif

namespace {

// F10 means either KEY_F10 or KEY_MENU, which has to be decided
// in the independent part.
struct KeyAlternate
{
    sal_uInt16          nKeyCode;
    sal_Unicode     nCharCode;
    KeyAlternate() : nKeyCode( 0 ), nCharCode( 0 ) {}
    KeyAlternate( sal_uInt16 nKey, sal_Unicode nChar = 0 ) : nKeyCode( nKey ), nCharCode( nChar ) {}
};

}

static KeyAlternate
GetAlternateKeyCode( const sal_uInt16 nKeyCode )
{
    KeyAlternate aAlternate;

    switch( nKeyCode )
    {
        case KEY_F10: aAlternate = KeyAlternate( KEY_MENU );break;
        case KEY_F24: aAlternate = KeyAlternate( KEY_SUBTRACT, '-' );break;
    }

    return aAlternate;
}

#if OSL_DEBUG_LEVEL > 0
static bool dumpframes = false;
#endif

bool GtkSalFrame::doKeyCallback( guint state,
                                 guint keyval,
                                 guint16 hardware_keycode,
                                 guint8 group,
                                 sal_Unicode aOrigCode,
                                 bool bDown,
                                 bool bSendRelease
                                 )
{
    SalKeyEvent aEvent;

    aEvent.mnCharCode       = aOrigCode;
    aEvent.mnRepeat         = 0;

    vcl::DeletionListener aDel( this );

#if OSL_DEBUG_LEVEL > 0
    const char* pKeyDebug = getenv("VCL_GTK3_PAINTDEBUG");

    if (pKeyDebug && *pKeyDebug == '1')
    {
        if (bDown)
        {
            // shift-zero forces a re-draw and event is swallowed
            if (keyval == GDK_KEY_0)
            {
                SAL_INFO("vcl.gtk3", "force widget_queue_draw.");
                gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
                return false;
            }
            else if (keyval == GDK_KEY_1)
            {
                SAL_INFO("vcl.gtk3", "force repaint all.");
                TriggerPaintEvent();
                return false;
            }
            else if (keyval == GDK_KEY_2)
            {
                dumpframes = !dumpframes;
                SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes);
                return false;
            }
        }
    }
#endif

    /*
     *  #i42122# translate all keys with Ctrl and/or Alt to group 0 else
     *  shortcuts (e.g. Ctrl-o) will not work but be inserted by the
     *  application
     *
     *  #i52338# do this for all keys that the independent part has no key code
     *  for
     *
     *  fdo#41169 rather than use group 0, detect if there is a group which can
     *  be used to input Latin text and use that if possible
     */
    aEvent.mnCode = GetKeyCode( keyval );
#if !GTK_CHECK_VERSION(4, 0, 0)
    if( aEvent.mnCode == 0 )
    {
        gint best_group = SAL_MAX_INT32;

        // Try and find Latin layout
        GdkKeymap* keymap = gdk_keymap_get_default();
        GdkKeymapKey *keys;
        gint n_keys;
        if (gdk_keymap_get_entries_for_keyval(keymap, GDK_KEY_A, &keys, &n_keys))
        {
            // Find the lowest group that supports Latin layout
            for (gint i = 0; i < n_keys; ++i)
            {
                if (keys[i].level != 0 && keys[i].level != 1)
                    continue;
                best_group = std::min(best_group, keys[i].group);
                if (best_group == 0)
                    break;
            }
            g_free(keys);
        }

        //Unavailable, go with original group then I suppose
        if (best_group == SAL_MAX_INT32)
            best_group = group;

        guint updated_keyval = GetKeyValFor(keymap, hardware_keycode, best_group);
        aEvent.mnCode = GetKeyCode(updated_keyval);
    }
#else
    (void)hardware_keycode;
    (void)group;
#endif

    aEvent.mnCode   |= GetKeyModCode( state );

    bool bStopProcessingKey;
    if (bDown)
    {
        bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
        // #i46889# copy AlternateKeyCode handling from generic plugin
        if (!bStopProcessingKey)
        {
            KeyAlternate aAlternate = GetAlternateKeyCode( aEvent.mnCode );
            if( aAlternate.nKeyCode )
            {
                aEvent.mnCode = aAlternate.nKeyCode;
                if( aAlternate.nCharCode )
                    aEvent.mnCharCode = aAlternate.nCharCode;
                bStopProcessingKey = CallCallbackExc(SalEvent::KeyInput, &aEvent);
            }
        }
        if( bSendRelease && ! aDel.isDeleted() )
        {
            CallCallbackExc(SalEvent::KeyUp, &aEvent);
        }
    }
    else
        bStopProcessingKey = CallCallbackExc(SalEvent::KeyUp, &aEvent);
    return bStopProcessingKey;
}

GtkSalFrame::GtkSalFrame( SalFrame* pParent, SalFrameStyleFlags nStyle )
    : m_nXScreen( getDisplay()->GetDefaultXScreen() )
    , m_pHeaderBar(nullptr)
    , m_bGraphics(false)
    , m_nSetFocusSignalId(0)
#if !GTK_CHECK_VERSION(4, 0, 0)
    , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
#endif
{
    getDisplay()->registerFrame( this );
    m_bDefaultPos       = true;
    m_bDefaultSize      = ( (nStyle & SalFrameStyleFlags::SIZEABLE) && ! pParent );
    Init( pParent, nStyle );
}

GtkSalFrame::GtkSalFrame( SystemParentData* pSysData )
    : m_nXScreen( getDisplay()->GetDefaultXScreen() )
    , m_pHeaderBar(nullptr)
    , m_bGraphics(false)
    , m_nSetFocusSignalId(0)
#if !GTK_CHECK_VERSION(4, 0, 0)
    , m_aSmoothScrollIdle("GtkSalFrame m_aSmoothScrollIdle")
#endif
{
    getDisplay()->registerFrame( this );
    // permanently ignore errors from our unruly children ...
    GetGenericUnixSalData()->ErrorTrapPush();
    m_bDefaultPos       = true;
    m_bDefaultSize      = true;
    Init( pSysData );
}

// AppMenu watch functions.

static void ObjectDestroyedNotify( gpointer data )
{
    if ( data ) {
        g_object_unref( data );
    }
}

#if !GTK_CHECK_VERSION(4,0,0)
static void hud_activated( gboolean hud_active, gpointer user_data )
{
    if ( hud_active )
    {
        SolarMutexGuard aGuard;
        GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );
        GtkSalMenu* pSalMenu = reinterpret_cast< GtkSalMenu* >( pSalFrame->GetMenu() );

        if ( pSalMenu )
            pSalMenu->UpdateFull();
    }
}
#endif

static void attach_menu_model(GtkSalFrame* pSalFrame)
{
    GtkWidget* pWidget = pSalFrame->getWindow();
    GdkSurface* gdkWindow = widget_get_surface(pWidget);

    if ( gdkWindow == nullptr || g_object_get_data( G_OBJECT( gdkWindow ), "g-lo-menubar" ) != nullptr )
        return;

    // Create menu model and action group attached to this frame.
    GMenuModel* pMenuModel = G_MENU_MODEL( g_lo_menu_new() );
    GActionGroup* pActionGroup = reinterpret_cast<GActionGroup*>(g_lo_action_group_new());

    // Set window properties.
    g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-menubar", pMenuModel, ObjectDestroyedNotify );
    g_object_set_data_full( G_OBJECT( gdkWindow ), "g-lo-action-group", pActionGroup, ObjectDestroyedNotify );

#if !GTK_CHECK_VERSION(4,0,0)
    // Get a DBus session connection.
    if (!pSessionBus)
        pSessionBus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, nullptr);
    if (!pSessionBus)
        return;

    // Generate menu paths.
    sal_uIntPtr windowId = GtkSalFrame::GetNativeWindowHandle(pWidget);
    gchar* aDBusWindowPath = g_strdup_printf( "/org/libreoffice/window/%lu", windowId );
    gchar* aDBusMenubarPath = g_strdup_printf( "/org/libreoffice/window/%lu/menus/menubar", windowId );

    GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
#if defined(GDK_WINDOWING_X11)
    if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
    {
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_ID", "org.libreoffice" );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_MENUBAR_OBJECT_PATH", aDBusMenubarPath );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_WINDOW_OBJECT_PATH", aDBusWindowPath );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_APPLICATION_OBJECT_PATH", "/org/libreoffice" );
        gdk_x11_window_set_utf8_property( gdkWindow, "_GTK_UNIQUE_BUS_NAME", g_dbus_connection_get_unique_name( pSessionBus ) );
    }
#endif
#if defined(GDK_WINDOWING_WAYLAND)
    if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
    {
        gdk_wayland_window_set_dbus_properties_libgtk_only(gdkWindow, "org.libreoffice",
                                                           nullptr,
                                                           aDBusMenubarPath,
                                                           aDBusWindowPath,
                                                           "/org/libreoffice",
                                                           g_dbus_connection_get_unique_name( pSessionBus ));
    }
#endif
    // Publish the menu model and the action group.
    SAL_INFO("vcl.unity", "exporting menu model at " << pMenuModel << " for window " << windowId);
    pSalFrame->m_nMenuExportId = g_dbus_connection_export_menu_model (pSessionBus, aDBusMenubarPath, pMenuModel, nullptr);
    SAL_INFO("vcl.unity", "exporting action group at " << pActionGroup << " for window " << windowId);
    pSalFrame->m_nActionGroupExportId = g_dbus_connection_export_action_group( pSessionBus, aDBusWindowPath, pActionGroup, nullptr);
    pSalFrame->m_nHudAwarenessId = hud_awareness_register( pSessionBus, aDBusMenubarPath, hud_activated, pSalFrame, nullptr, nullptr );

    g_free( aDBusWindowPath );
    g_free( aDBusMenubarPath );
#endif
}

void on_registrar_available( GDBusConnection * /*connection*/,
                             const gchar     * /*name*/,
                             const gchar     * /*name_owner*/,
                             gpointer         user_data )
{
    SolarMutexGuard aGuard;

    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );

    SalMenu* pSalMenu = pSalFrame->GetMenu();

    if ( pSalMenu != nullptr )
    {
        GtkSalMenu* pGtkSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
        pGtkSalMenu->EnableUnity(true);
    }
}

// This is called when the registrar becomes unavailable. It shows the menubar.
void on_registrar_unavailable( GDBusConnection * /*connection*/,
                               const gchar     * /*name*/,
                               gpointer         user_data )
{
    SolarMutexGuard aGuard;

    SAL_INFO("vcl.unity", "on_registrar_unavailable");

    //pSessionBus = NULL;
    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );

    SalMenu* pSalMenu = pSalFrame->GetMenu();

    if ( pSalMenu ) {
        GtkSalMenu* pGtkSalMenu = static_cast< GtkSalMenu* >( pSalMenu );
        pGtkSalMenu->EnableUnity(false);
    }
}

void GtkSalFrame::EnsureAppMenuWatch()
{
    if ( m_nWatcherId )
        return;

    // Get a DBus session connection.
    if ( pSessionBus == nullptr )
    {
        pSessionBus = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, nullptr );

        if ( pSessionBus == nullptr )
            return;
    }

    // Publish the menu only if AppMenu registrar is available.
    m_nWatcherId = g_bus_watch_name_on_connection( pSessionBus,
                                                   "com.canonical.AppMenu.Registrar",
                                                   G_BUS_NAME_WATCHER_FLAGS_NONE,
                                                   on_registrar_available,
                                                   on_registrar_unavailable,
                                                   this,
                                                   nullptr );
}

void GtkSalFrame::InvalidateGraphics()
{
    if( m_pGraphics )
    {
        m_bGraphics = false;
    }
}

GtkSalFrame::~GtkSalFrame()
{
#if !GTK_CHECK_VERSION(4,0,0)
    m_aSmoothScrollIdle.Stop();
    m_aSmoothScrollIdle.ClearInvokeHandler();
#endif

    if (m_pDropTarget)
    {
        m_pDropTarget->deinitialize();
        m_pDropTarget = nullptr;
    }

    if (m_pDragSource)
    {
        m_pDragSource->deinitialize();
        m_pDragSource= nullptr;
    }

    InvalidateGraphics();

    if (m_pParent)
    {
        m_pParent->m_aChildren.remove( this );
    }

    getDisplay()->deregisterFrame( this );

    if( m_pRegion )
    {
        cairo_region_destroy( m_pRegion );
    }

    m_pIMHandler.reset();

    //tdf#108705 remove grabs on event widget before
    //destroying event widget
    while (m_nGrabLevel)
        removeGrabLevel();

    {
        SolarMutexGuard aGuard;

        if(m_nWatcherId)
            g_bus_unwatch_name(m_nWatcherId);
    }

    GtkWidget *pEventWidget = getMouseEventWidget();
    for (auto handler_id : m_aMouseSignalIds)
        g_signal_handler_disconnect(G_OBJECT(pEventWidget), handler_id);
#if !GTK_CHECK_VERSION(4, 0, 0)
    if( m_pFixedContainer )
        gtk_widget_destroy( GTK_WIDGET( m_pFixedContainer ) );
    if( m_pEventBox )
        gtk_widget_destroy( GTK_WIDGET(m_pEventBox) );
    if( m_pTopLevelGrid )
        gtk_widget_destroy( GTK_WIDGET(m_pTopLevelGrid) );
#else
    g_signal_handler_disconnect(G_OBJECT(gtk_widget_get_display(pEventWidget)), m_nSettingChangedSignalId);
#endif
    {
        SolarMutexGuard aGuard;

        if( m_pWindow )
        {
            g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", nullptr );

            if ( pSessionBus )
            {
                if ( m_nHudAwarenessId )
                    hud_awareness_unregister( pSessionBus, m_nHudAwarenessId );
                if ( m_nMenuExportId )
                    g_dbus_connection_unexport_menu_model( pSessionBus, m_nMenuExportId );
                if ( m_nActionGroupExportId )
                    g_dbus_connection_unexport_action_group( pSessionBus, m_nActionGroupExportId );
            }
            m_xFrameWeld.reset();
#if !GTK_CHECK_VERSION(4,0,0)
            gtk_widget_destroy( m_pWindow );
#else
            if (GTK_IS_WINDOW(m_pWindow))
                gtk_window_destroy(GTK_WINDOW(m_pWindow));
            else
                g_clear_pointer(&m_pWindow, gtk_widget_unparent);
#endif
        }
    }

#if !GTK_CHECK_VERSION(4,0,0)
    if( m_pForeignParent )
        g_object_unref( G_OBJECT( m_pForeignParent ) );
    if( m_pForeignTopLevel )
        g_object_unref( G_OBJECT( m_pForeignTopLevel) );
#endif

    m_pGraphics.reset();

    if (m_pSurface)
        cairo_surface_destroy(m_pSurface);
}

void GtkSalFrame::moveWindow( tools::Long nX, tools::Long nY )
{
    if( isChild( false ) )
    {
        GtkWidget* pParent = m_pParent ? gtk_widget_get_parent(m_pWindow) : nullptr;
        // tdf#130414 it's possible that we were reparented and are no longer inside
        // our original GtkFixed parent
        if (pParent && GTK_IS_FIXED(pParent))
        {
            gtk_fixed_move( GTK_FIXED(pParent),
                            m_pWindow,
                            nX - m_pParent->maGeometry.nX, nY - m_pParent->maGeometry.nY );
        }
    }
#if !GTK_CHECK_VERSION(4,0,0)
    else
        gtk_window_move( GTK_WINDOW(m_pWindow), nX, nY );
#endif
}

void GtkSalFrame::widget_set_size_request(tools::Long nWidth, tools::Long nHeight)
{
    gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), nWidth, nHeight );
}

void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
{
    m_nWidthRequest = nWidth;
    m_nHeightRequest = nHeight;
    gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
#if !GTK_CHECK_VERSION(4,0,0)
    if (gtk_widget_get_visible(m_pWindow))
        gtk_window_resize(GTK_WINDOW(m_pWindow), nWidth, nHeight);
#endif
}

void GtkSalFrame::resizeWindow( tools::Long nWidth, tools::Long nHeight )
{
    if( isChild( false ) )
    {
        widget_set_size_request(nWidth, nHeight);
    }
    else if( ! isChild( true, false ) )
        window_resize(nWidth, nHeight);
}

#if !GTK_CHECK_VERSION(4,0,0)
// tdf#124694 GtkFixed takes the max size of all its children as its
// preferred size, causing it to not clip its child, but grow instead.

static void
ooo_fixed_get_preferred_height(GtkWidget*, gint *minimum, gint *natural)
{
    *minimum = 0;
    *natural = 0;
}

static void
ooo_fixed_get_preferred_width(GtkWidget*, gint *minimum, gint *natural)
{
    *minimum = 0;
    *natural = 0;
}
#endif

static void
ooo_fixed_class_init(GtkFixedClass *klass)
{
#if !GTK_CHECK_VERSION(4,0,0)
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
    widget_class->get_accessible = ooo_fixed_get_accessible;
    widget_class->get_preferred_height = ooo_fixed_get_preferred_height;
    widget_class->get_preferred_width = ooo_fixed_get_preferred_width;
#else
    (void)klass;
#endif
}

/*
 * Always use a sub-class of GtkFixed we can tag for a11y. This allows us to
 * utilize GAIL for the toplevel window and toolkit implementation incl.
 * key event listener support ..
 */

GType
ooo_fixed_get_type()
{
    static GType type = 0;

    if (!type) {
        static const GTypeInfo tinfo =
        {
            sizeof (GtkFixedClass),
            nullptr,      /* base init */
            nullptr,  /* base finalize */
            reinterpret_cast<GClassInitFunc>(ooo_fixed_class_init), /* class init */
            nullptr, /* class finalize */
            nullptr,                      /* class data */
            sizeof (GtkFixed),         /* instance size */
            0,                         /* nb preallocs */
            nullptr,  /* instance init */
            nullptr                       /* value table */
        };

        type = g_type_register_static( GTK_TYPE_FIXED, "OOoFixed",
                                       &tinfo, GTypeFlags(0));
    }

    return type;
}

void GtkSalFrame::updateScreenNumber()
{
#if !GTK_CHECK_VERSION(4,0,0)
    int nScreen = 0;
    GdkScreen *pScreen = gtk_widget_get_screen( m_pWindow );
    if( pScreen )
        nScreen = getDisplay()->getSystem()->getScreenMonitorIdx( pScreen, maGeometry.nX, maGeometry.nY );
    maGeometry.nDisplayScreenNumber = nScreen;
#endif
}

GtkWidget *GtkSalFrame::getMouseEventWidget() const
{
#if !GTK_CHECK_VERSION(4,0,0)
    return GTK_WIDGET(m_pEventBox);
#else
    return GTK_WIDGET(m_pFixedContainer);
#endif
}

static void damaged(void *handle,
                    sal_Int32 nExtentsX, sal_Int32 nExtentsY,
                    sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(handle);
    pThis->damaged(nExtentsX, nExtentsY, nExtentsWidth, nExtentsHeight);
}

void GtkSalFrame::InitCommon()
{
    m_pSurface = nullptr;
    m_nGrabLevel = 0;
    m_bSalObjectSetPosSize = false;

    m_aDamageHandler.handle = this;
    m_aDamageHandler.damaged = ::damaged;

#if !GTK_CHECK_VERSION(4,0,0)
    m_aSmoothScrollIdle.SetInvokeHandler(LINK(this, GtkSalFrame, AsyncScroll));
#endif

    m_pTopLevelGrid = GTK_GRID(gtk_grid_new());
    container_add(m_pWindow, GTK_WIDGET(m_pTopLevelGrid));

#if !GTK_CHECK_VERSION(4,0,0)
    m_pEventBox = GTK_EVENT_BOX(gtk_event_box_new());
    gtk_widget_add_events( GTK_WIDGET(m_pEventBox),
                           GDK_ALL_EVENTS_MASK );
    gtk_widget_set_vexpand(GTK_WIDGET(m_pEventBox), true);
    gtk_widget_set_hexpand(GTK_WIDGET(m_pEventBox), true);
    gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pEventBox), 0, 0, 1, 1);
#endif

    // add the fixed container child,
    // fixed is needed since we have to position plugin windows
#if !GTK_CHECK_VERSION(4,0,0)
    m_pFixedContainer = GTK_FIXED(g_object_new( ooo_fixed_get_type(), nullptr ));
    m_pDrawingArea = m_pFixedContainer;
#else
    m_pOverlay = GTK_OVERLAY(gtk_overlay_new());
    m_pFixedContainer = GTK_FIXED(gtk_fixed_new());
    m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
#endif
    gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
    gtk_widget_set_size_request(GTK_WIDGET(m_pFixedContainer), 1, 1);
#if !GTK_CHECK_VERSION(4,0,0)
    gtk_container_add( GTK_CONTAINER(m_pEventBox), GTK_WIDGET(m_pFixedContainer) );
#else
    gtk_widget_set_vexpand(GTK_WIDGET(m_pOverlay), true);
    gtk_widget_set_hexpand(GTK_WIDGET(m_pOverlay), true);
    gtk_grid_attach(m_pTopLevelGrid, GTK_WIDGET(m_pOverlay), 0, 0, 1, 1);
    gtk_overlay_set_child(m_pOverlay, GTK_WIDGET(m_pDrawingArea));
    gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pFixedContainer));
#endif

    GtkWidget *pEventWidget = getMouseEventWidget();
#if !GTK_CHECK_VERSION(4,0,0)
    gtk_widget_set_app_paintable(GTK_WIDGET(m_pFixedContainer), true);
    gtk_widget_set_redraw_on_allocate(GTK_WIDGET(m_pFixedContainer), false);
#endif

#if GTK_CHECK_VERSION(4,0,0)
    m_nSettingChangedSignalId = g_signal_connect(G_OBJECT(gtk_widget_get_display(pEventWidget)), "setting-changed", G_CALLBACK(signalStyleUpdated), this);
#else
    // use pEventWidget instead of m_pWindow to avoid infinite event loop under Linux Mint Mate 18.3
    g_signal_connect(G_OBJECT(pEventWidget), "style-updated", G_CALLBACK(signalStyleUpdated), this);
#endif
    gtk_widget_set_has_tooltip(pEventWidget, true);
    // connect signals
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "query-tooltip", G_CALLBACK(signalTooltipQuery), this ));
#if !GTK_CHECK_VERSION(4,0,0)
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-press-event", G_CALLBACK(signalButton), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "button-release-event", G_CALLBACK(signalButton), this ));

    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "motion-notify-event", G_CALLBACK(signalMotion), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "leave-notify-event", G_CALLBACK(signalCrossing), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "enter-notify-event", G_CALLBACK(signalCrossing), this ));

    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "scroll-event", G_CALLBACK(signalScroll), this ));
#else
    GtkGesture *pClick = gtk_gesture_click_new();
    gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pClick), 0);
    // use GTK_PHASE_TARGET instead of default GTK_PHASE_BUBBLE because I don't
    // want click events in gtk widgets inside the overlay, e.g. the font size
    // combobox GtkEntry in the toolbar, to be propagated down to this widget
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pClick), GTK_PHASE_TARGET);
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pClick));
    g_signal_connect(pClick, "pressed", G_CALLBACK(gesturePressed), this);
    g_signal_connect(pClick, "released", G_CALLBACK(gestureReleased), this);

    GtkEventController* pMotionController = gtk_event_controller_motion_new();
    g_signal_connect(pMotionController, "motion", G_CALLBACK(signalMotion), this);
    g_signal_connect(pMotionController, "enter", G_CALLBACK(signalEnter), this);
    g_signal_connect(pMotionController, "leave", G_CALLBACK(signalLeave), this);
    gtk_event_controller_set_propagation_phase(pMotionController, GTK_PHASE_TARGET);
    gtk_widget_add_controller(pEventWidget, pMotionController);

    GtkEventController* pScrollController = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
    g_signal_connect(pScrollController, "scroll", G_CALLBACK(signalScroll), this);
    gtk_widget_add_controller(pEventWidget, pScrollController);
#endif

    //Drop Target Stuff
#if GTK_CHECK_VERSION(4,0,0)
    GtkDropTargetAsync* pDropTarget = gtk_drop_target_async_new(nullptr, GdkDragAction(GDK_ACTION_ALL));
    g_signal_connect(G_OBJECT(pDropTarget), "drag-enter", G_CALLBACK(signalDragMotion), this);
    g_signal_connect(G_OBJECT(pDropTarget), "drag-motion", G_CALLBACK(signalDragMotion), this);
    g_signal_connect(G_OBJECT(pDropTarget), "drag-leave", G_CALLBACK(signalDragLeave), this);
    g_signal_connect(G_OBJECT(pDropTarget), "drop", G_CALLBACK(signalDragDrop), this);
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pDropTarget));
#else
    gtk_drag_dest_set(GTK_WIDGET(pEventWidget), GtkDestDefaults(0), nullptr, 0, GdkDragAction(0));
    gtk_drag_dest_set_track_motion(GTK_WIDGET(pEventWidget), true);
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-motion", G_CALLBACK(signalDragMotion), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-drop", G_CALLBACK(signalDragDrop), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-received", G_CALLBACK(signalDragDropReceived), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-leave", G_CALLBACK(signalDragLeave), this ));
#endif

#if !GTK_CHECK_VERSION(4,0,0)
    //Drag Source Stuff
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-end", G_CALLBACK(signalDragEnd), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-failed", G_CALLBACK(signalDragFailed), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-delete", G_CALLBACK(signalDragDelete), this ));
    m_aMouseSignalIds.push_back(g_signal_connect( G_OBJECT(pEventWidget), "drag-data-get", G_CALLBACK(signalDragDataGet), this ));
#endif

#if !GTK_CHECK_VERSION(4,0,0)
    g_signal_connect( G_OBJECT(m_pFixedContainer), "draw", G_CALLBACK(signalDraw), this );
    g_signal_connect( G_OBJECT(m_pFixedContainer), "size-allocate", G_CALLBACK(sizeAllocated), this );
#else
    gtk_drawing_area_set_draw_func(m_pDrawingArea, signalDraw, this, nullptr);
    g_signal_connect(G_OBJECT(m_pDrawingArea), "resize", G_CALLBACK(sizeAllocated), this);
#endif

    g_signal_connect(G_OBJECT(m_pFixedContainer), "realize", G_CALLBACK(signalRealize), this);

#if !GTK_CHECK_VERSION(4,0,0)
    GtkGesture *pSwipe = gtk_gesture_swipe_new(pEventWidget);
    g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pSwipe);
#else
    GtkGesture *pSwipe = gtk_gesture_swipe_new();
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pSwipe));
#endif
    g_signal_connect(pSwipe, "swipe", G_CALLBACK(gestureSwipe), this);
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pSwipe), GTK_PHASE_TARGET);

#if !GTK_CHECK_VERSION(4,0,0)
    GtkGesture *pLongPress = gtk_gesture_long_press_new(pEventWidget);
    g_object_weak_ref(G_OBJECT(pEventWidget), reinterpret_cast<GWeakNotify>(g_object_unref), pLongPress);
#else
    GtkGesture *pLongPress = gtk_gesture_long_press_new();
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pLongPress));
#endif
    g_signal_connect(pLongPress, "pressed", G_CALLBACK(gestureLongPress), this);
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER (pLongPress), GTK_PHASE_TARGET);

#if !GTK_CHECK_VERSION(4,0,0)
    g_signal_connect_after( G_OBJECT(m_pWindow), "focus-in-event", G_CALLBACK(signalFocus), this );
    g_signal_connect_after( G_OBJECT(m_pWindow), "focus-out-event", G_CALLBACK(signalFocus), this );
    if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the signal
        m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this );
#else
    GtkEventController* pFocusController = gtk_event_controller_focus_new();
    g_signal_connect(pFocusController, "enter", G_CALLBACK(signalFocusEnter), this);
    g_signal_connect(pFocusController, "leave", G_CALLBACK(signalFocusLeave), this);
    gtk_widget_set_focusable(pEventWidget, true);
    gtk_widget_add_controller(pEventWidget, pFocusController);
    if (GTK_IS_WINDOW(m_pWindow)) // i.e. not if it's a GtkEventBox which doesn't have the property (presumably?)
        m_nSetFocusSignalId = g_signal_connect( G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this );
#endif
#if !GTK_CHECK_VERSION(4,0,0)
    g_signal_connect( G_OBJECT(m_pWindow), "map-event", G_CALLBACK(signalMap), this );
    g_signal_connect( G_OBJECT(m_pWindow), "unmap-event", G_CALLBACK(signalUnmap), this );
    g_signal_connect( G_OBJECT(m_pWindow), "delete-event", G_CALLBACK(signalDelete), this );
#else
    g_signal_connect( G_OBJECT(m_pWindow), "map", G_CALLBACK(signalMap), this );
    g_signal_connect( G_OBJECT(m_pWindow), "unmap", G_CALLBACK(signalUnmap), this );
    if (GTK_IS_WINDOW(m_pWindow))
        g_signal_connect( G_OBJECT(m_pWindow), "close-request", G_CALLBACK(signalDelete), this );
#endif
#if !GTK_CHECK_VERSION(4,0,0)
    g_signal_connect( G_OBJECT(m_pWindow), "configure-event", G_CALLBACK(signalConfigure), this );
#endif

#if !GTK_CHECK_VERSION(4,0,0)
    g_signal_connect( G_OBJECT(m_pWindow), "key-press-event", G_CALLBACK(signalKey), this );
    g_signal_connect( G_OBJECT(m_pWindow), "key-release-event", G_CALLBACK(signalKey), this );
#else
    m_pKeyController = GTK_EVENT_CONTROLLER_KEY(gtk_event_controller_key_new());
    g_signal_connect(m_pKeyController, "key-pressed", G_CALLBACK(signalKeyPressed), this);
    g_signal_connect(m_pKeyController, "key-released", G_CALLBACK(signalKeyReleased), this);
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(m_pKeyController));

#endif
    g_signal_connect( G_OBJECT(m_pWindow), "destroy", G_CALLBACK(signalDestroy), this );

    // init members
    m_nKeyModifiers     = ModKeyFlags::NONE;
    m_bFullscreen       = false;
#if GTK_CHECK_VERSION(4,0,0)
    m_nState            = static_cast<GdkToplevelState>(0);
#else
    m_nState            = GDK_WINDOW_STATE_WITHDRAWN;
#endif
    m_pIMHandler        = nullptr;
    m_pRegion           = nullptr;
    m_pDropTarget       = nullptr;
    m_pDragSource       = nullptr;
    m_bGeometryIsProvisional = false;
    m_bIconSetWhileUnmapped = false;
    m_bTooltipBlocked   = false;
    m_ePointerStyle     = static_cast<PointerStyle>(0xffff);
    m_pSalMenu          = nullptr;
    m_nWatcherId        = 0;
    m_nMenuExportId     = 0;
    m_nActionGroupExportId = 0;
    m_nHudAwarenessId   = 0;

#if !GTK_CHECK_VERSION(4,0,0)
    gtk_widget_add_events( m_pWindow,
                           GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
                           GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK |
                           GDK_SCROLL_MASK
                           );
#endif

    // show the widgets
#if !GTK_CHECK_VERSION(4,0,0)
    gtk_widget_show_all(GTK_WIDGET(m_pTopLevelGrid));
#else
    gtk_widget_show(GTK_WIDGET(m_pTopLevelGrid));
#endif

    // realize the window, we need an XWindow id
    gtk_widget_realize( m_pWindow );

    if (GTK_IS_WINDOW(m_pWindow))
    {
#if !GTK_CHECK_VERSION(4,0,0)
        g_signal_connect(G_OBJECT(m_pWindow), "window-state-event", G_CALLBACK(signalWindowState), this);
#else
        GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
        g_signal_connect(G_OBJECT(gdkWindow), "notify::state", G_CALLBACK(signalWindowState), this);
#endif
    }

    //system data
    m_aSystemData.SetWindowHandle(GetNativeWindowHandle(m_pWindow));
    m_aSystemData.aShellWindow  = reinterpret_cast<sal_IntPtr>(this);
    m_aSystemData.pSalFrame     = this;
    m_aSystemData.pWidget       = m_pWindow;
    m_aSystemData.nScreen       = m_nXScreen.getXScreen();
    m_aSystemData.toolkit       = SystemEnvData::Toolkit::Gtk;

#if defined(GDK_WINDOWING_X11)
    GdkDisplay *pDisplay = getGdkDisplay();
    if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
    {
        m_aSystemData.pDisplay = gdk_x11_display_get_xdisplay(pDisplay);
        m_aSystemData.platform = SystemEnvData::Platform::Xcb;
#if !GTK_CHECK_VERSION(4,0,0)
        GdkScreen* pScreen = gtk_widget_get_screen(m_pWindow);
        GdkVisual* pVisual = gdk_screen_get_system_visual(pScreen);
        m_aSystemData.pVisual = gdk_x11_visual_get_xvisual(pVisual);
#endif
    }
#endif
#if defined(GDK_WINDOWING_WAYLAND)
    if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
    {
        m_aSystemData.pDisplay = gdk_wayland_display_get_wl_display(pDisplay);
        m_aSystemData.platform = SystemEnvData::Platform::Wayland;
    }
#endif

    m_bGraphics = false;
    m_pGraphics = nullptr;

    m_nFloatFlags = FloatWinPopupFlags::NONE;
    m_bFloatPositioned = false;

    m_nWidthRequest = 0;
    m_nHeightRequest = 0;

    // fake an initial geometry, gets updated via configure event or SetPosSize
    if (m_bDefaultPos || m_bDefaultSize)
    {
        Size aDefSize = calcDefaultSize();
        maGeometry.nX                   = -1;
        maGeometry.nY                   = -1;
        maGeometry.nWidth               = aDefSize.Width();
        maGeometry.nHeight              = aDefSize.Height();
        maGeometry.nTopDecoration       = 0;
        maGeometry.nBottomDecoration    = 0;
        maGeometry.nLeftDecoration      = 0;
        maGeometry.nRightDecoration     = 0;
    }
    updateScreenNumber();

    SetIcon(SV_ICON_ID_OFFICE);
}

GtkSalFrame *GtkSalFrame::getFromWindow( GtkWidget *pWindow )
{
    return static_cast<GtkSalFrame *>(g_object_get_data( G_OBJECT( pWindow ), "SalFrame" ));
}

void GtkSalFrame::DisallowCycleFocusOut()
{
    if (!m_nSetFocusSignalId)
        return;
    // don't enable/disable can-focus as control enters and leaves
    // embedded native gtk widgets
    g_signal_handler_disconnect(G_OBJECT(m_pWindow), m_nSetFocusSignalId);
    m_nSetFocusSignalId = 0;

#if !GTK_CHECK_VERSION(4, 0, 0)
    // gtk3: set container without can-focus and focus will tab between
    // the native embedded widgets using the default gtk handling for
    // that
    // gtk4: no need because the native widgets are the only
    // thing in the overlay and the drawing widget is underneath so
    // the natural focus cycle is sufficient
    gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), false);
#endif
}

bool GtkSalFrame::IsCycleFocusOutDisallowed() const
{
    return m_nSetFocusSignalId == 0;
}

void GtkSalFrame::AllowCycleFocusOut()
{
    if (m_nSetFocusSignalId)
        return;
#if !GTK_CHECK_VERSION(4,0,0)
    // enable/disable can-focus as control enters and leaves
    // embedded native gtk widgets
    m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "set-focus", G_CALLBACK(signalSetFocus), this);
#else
    m_nSetFocusSignalId = g_signal_connect(G_OBJECT(m_pWindow), "notify::focus-widget", G_CALLBACK(signalSetFocus), this);
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
    // set container without can-focus and focus will tab between
    // the native embedded widgets using the default gtk handling for
    // that
    // gtk4: no need because the native widgets are the only
    // thing in the overlay and the drawing widget is underneath so
    // the natural focus cycle is sufficient
    gtk_widget_set_can_focus(GTK_WIDGET(m_pFixedContainer), true);
#endif
}


void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )
{
    if( nStyle & SalFrameStyleFlags::DEFAULT ) // ensure default style
    {
        nStyle |= SalFrameStyleFlags::MOVEABLE | SalFrameStyleFlags::SIZEABLE | SalFrameStyleFlags::CLOSEABLE;
        nStyle &= ~SalFrameStyleFlags::FLOAT;
    }

    m_pParent = static_cast<GtkSalFrame*>(pParent);
#if !GTK_CHECK_VERSION(4,0,0)
    m_pForeignParent = nullptr;
    m_aForeignParentWindow = None;
    m_pForeignTopLevel = nullptr;
    m_aForeignTopLevelWindow = None;
#endif
    m_nStyle = nStyle;

    bool bPopup = ((nStyle & SalFrameStyleFlags::FLOAT) &&
                   !(nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION));

    if( nStyle & SalFrameStyleFlags::SYSTEMCHILD )
    {
#if !GTK_CHECK_VERSION(4,0,0)
        m_pWindow = gtk_event_box_new();
#else
        m_pWindow = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#endif
        if( m_pParent )
        {
            // insert into container
            gtk_fixed_put( m_pParent->getFixedContainer(),
                           m_pWindow, 0, 0 );
        }
    }
    else
    {
#if !GTK_CHECK_VERSION(4,0,0)
        m_pWindow = gtk_window_new(bPopup ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
#else
        m_pWindow = gtk_window_new();
#endif

#if !GTK_CHECK_VERSION(4,0,0)
        // hook up F1 to show help for embedded native gtk widgets
        GtkAccelGroup *pGroup = gtk_accel_group_new();
        GClosure* closure = g_cclosure_new(G_CALLBACK(GtkSalFrame::NativeWidgetHelpPressed), GTK_WINDOW(m_pWindow), nullptr);
        gtk_accel_group_connect(pGroup, GDK_KEY_F1, static_cast<GdkModifierType>(0), GTK_ACCEL_LOCKED, closure);
        gtk_window_add_accel_group(GTK_WINDOW(m_pWindow), pGroup);
#endif
    }

    g_object_set_data( G_OBJECT( m_pWindow ), "SalFrame", this );
    g_object_set_data( G_OBJECT( m_pWindow ), "libo-version", const_cast<char *>(LIBO_VERSION_DOTTED));

    // force wm class hint
    if (!isChild())
    {
        if (m_pParent)
            m_sWMClass = m_pParent->m_sWMClass;
        updateWMClass();
    }

    if (GTK_IS_WINDOW(m_pWindow))
    {
        if (m_pParent)
        {
            GtkWidget* pTopLevel = widget_get_toplevel(m_pParent->m_pWindow);
#if !GTK_CHECK_VERSION(4,0,0)
            if (!isChild())
                gtk_window_set_screen(GTK_WINDOW(m_pWindow), gtk_widget_get_screen(pTopLevel));
#endif

            if (!(m_pParent->m_nStyle & SalFrameStyleFlags::PLUG))
                gtk_window_set_transient_for(GTK_WINDOW(m_pWindow), GTK_WINDOW(pTopLevel));
            m_pParent->m_aChildren.push_back( this );
            gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pTopLevel)), GTK_WINDOW(m_pWindow));
        }
        else
        {
            gtk_window_group_add_window(gtk_window_group_new(), GTK_WINDOW(m_pWindow));
            g_object_unref(gtk_window_get_group(GTK_WINDOW(m_pWindow)));
        }
    }

    // set window type
    bool bDecoHandling =
        ! isChild() &&
        ( ! (nStyle & SalFrameStyleFlags::FLOAT) ||
          (nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) );

    if( bDecoHandling )
    {
#if !GTK_CHECK_VERSION(4,0,0)
        GdkWindowTypeHint eType = GDK_WINDOW_TYPE_HINT_NORMAL;
        if( (nStyle & SalFrameStyleFlags::DIALOG) && m_pParent != nullptr )
            eType = GDK_WINDOW_TYPE_HINT_DIALOG;
#endif
        if( nStyle & SalFrameStyleFlags::INTRO )
        {
#if !GTK_CHECK_VERSION(4,0,0)
            gtk_window_set_role( GTK_WINDOW(m_pWindow), "splashscreen" );
            eType = GDK_WINDOW_TYPE_HINT_SPLASHSCREEN;
#endif
        }
        else if( nStyle & SalFrameStyleFlags::TOOLWINDOW )
        {
#if !GTK_CHECK_VERSION(4,0,0)
            eType = GDK_WINDOW_TYPE_HINT_DIALOG;
            gtk_window_set_skip_taskbar_hint( GTK_WINDOW(m_pWindow), true );
#endif
        }
        else if( nStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
        {
#if !GTK_CHECK_VERSION(4,0,0)
            eType = GDK_WINDOW_TYPE_HINT_TOOLBAR;
            gtk_window_set_focus_on_map(GTK_WINDOW(m_pWindow), false);
#endif
            gtk_window_set_decorated(GTK_WINDOW(m_pWindow), false);
        }
#if !GTK_CHECK_VERSION(4,0,0)
        gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), eType );
        gtk_window_set_gravity( GTK_WINDOW(m_pWindow), GDK_GRAVITY_STATIC );
#endif
        gtk_window_set_resizable( GTK_WINDOW(m_pWindow), bool(nStyle & SalFrameStyleFlags::SIZEABLE) );

#if !GTK_CHECK_VERSION(4,0,0)
#if defined(GDK_WINDOWING_WAYLAND)
        //rhbz#1392145 under wayland/csd if we've overridden the default widget direction in order to set LibreOffice's
        //UI to the configured ui language but the system ui locale is a different text direction, then the toplevel
        //built-in close button of the titlebar follows the overridden direction rather than continue in the same
        //direction as every other titlebar on the user's desktop. So if they don't match set an explicit
        //header bar with the desired 'outside' direction
        if ((eType == GDK_WINDOW_TYPE_HINT_NORMAL || eType == GDK_WINDOW_TYPE_HINT_DIALOG) && DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()))
        {
            const bool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage());
            const bool bAppIsRTL = gtk_widget_get_default_direction() == GTK_TEXT_DIR_RTL;
            if (bDesktopIsRTL != bAppIsRTL)
            {
                m_pHeaderBar = GTK_HEADER_BAR(gtk_header_bar_new());
                gtk_widget_set_direction(GTK_WIDGET(m_pHeaderBar), bDesktopIsRTL ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
                gtk_header_bar_set_show_close_button(m_pHeaderBar, true);
                gtk_window_set_titlebar(GTK_WINDOW(m_pWindow), GTK_WIDGET(m_pHeaderBar));
                gtk_widget_show(GTK_WIDGET(m_pHeaderBar));
            }
        }
#endif
#endif
    }
#if !GTK_CHECK_VERSION(4,0,0)
    else if( nStyle & SalFrameStyleFlags::FLOAT )
        gtk_window_set_type_hint( GTK_WINDOW(m_pWindow), GDK_WINDOW_TYPE_HINT_POPUP_MENU );
#endif

    InitCommon();

    if (!bPopup)
    {
        // Enable GMenuModel native menu
        attach_menu_model(this);
    }
}

#if !GTK_CHECK_VERSION(4,0,0)
GdkNativeWindow GtkSalFrame::findTopLevelSystemWindow( GdkNativeWindow )
{
    //FIXME: no findToplevelSystemWindow
    return 0;
}
#endif

void GtkSalFrame::Init( SystemParentData* pSysData )
{
    m_pParent = nullptr;
#if !GTK_CHECK_VERSION(4,0,0)
    m_aForeignParentWindow = pSysData->aWindow;
    m_pForeignParent = nullptr;
    m_aForeignTopLevelWindow = findTopLevelSystemWindow(pSysData->aWindow);
    m_pForeignTopLevel = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignTopLevelWindow );
    gdk_window_set_events( m_pForeignTopLevel, GDK_STRUCTURE_MASK );

    if( pSysData->nSize > sizeof(pSysData->nSize)+sizeof(pSysData->aWindow) && pSysData->bXEmbedSupport )
    {
        m_pWindow = gtk_plug_new_for_display( getGdkDisplay(), pSysData->aWindow );
        gtk_widget_set_can_default(m_pWindow, true);
        gtk_widget_set_can_focus(m_pWindow, true);
        gtk_widget_set_sensitive(m_pWindow, true);
    }
    else
    {
        m_pWindow = gtk_window_new( GTK_WINDOW_POPUP );
    }
#endif
    m_nStyle = SalFrameStyleFlags::PLUG;
    InitCommon();

#if !GTK_CHECK_VERSION(4,0,0)
    m_pForeignParent = gdk_x11_window_foreign_new_for_display( getGdkDisplay(), m_aForeignParentWindow );
    gdk_window_set_events( m_pForeignParent, GDK_STRUCTURE_MASK );
#else
    (void)pSysData;
#endif

    //FIXME: Handling embedded windows, is going to be fun ...
}

void GtkSalFrame::SetExtendedFrameStyle(SalExtStyle)
{
}

SalGraphics* GtkSalFrame::AcquireGraphics()
{
    if( m_bGraphics )
        return nullptr;

    if( !m_pGraphics )
    {
        m_pGraphics.reset( new GtkSalGraphics( this, m_pWindow ) );
        if (!m_pSurface)
        {
            AllocateFrame();
            TriggerPaintEvent();
        }
        m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
    }
    m_bGraphics = true;
    return m_pGraphics.get();
}

void GtkSalFrame::ReleaseGraphics( SalGraphics* pGraphics )
{
    (void) pGraphics;
    assert( pGraphics == m_pGraphics.get() );
    m_bGraphics = false;
}

bool GtkSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
{
    getDisplay()->SendInternalEvent( this, pData.release() );
    return true;
}

void GtkSalFrame::SetTitle( const OUString& rTitle )
{
    if( m_pWindow && ! isChild() )
    {
        OString sTitle(OUStringToOString(rTitle, RTL_TEXTENCODING_UTF8));
        gtk_window_set_title(GTK_WINDOW(m_pWindow), sTitle.getStr());
#if !GTK_CHECK_VERSION(4,0,0)
        if (m_pHeaderBar)
            gtk_header_bar_set_title(m_pHeaderBar, sTitle.getStr());
#endif
    }
}

void GtkSalFrame::SetIcon(const char* appicon)
{
    gtk_window_set_icon_name(GTK_WINDOW(m_pWindow), appicon);

#if defined(GDK_WINDOWING_WAYLAND)
    if (!DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()))
        return;

#if GTK_CHECK_VERSION(4,0,0)
    GdkSurface* gdkWindow = gtk_native_get_surface(gtk_widget_get_native(m_pWindow));
    gdk_wayland_toplevel_set_application_id((GDK_TOPLEVEL(gdkWindow)), appicon);
#else
    static auto set_application_id = reinterpret_cast<void (*) (GdkWindow*, const char*)>(
                                         dlsym(nullptr, "gdk_wayland_window_set_application_id"));
    if (set_application_id)
    {
        GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
        set_application_id(gdkWindow, appicon);
    }
#endif
    // gdk_wayland_window_set_application_id doesn't seem to work before
    // the window is mapped, so set this for real when/if we are mapped
    m_bIconSetWhileUnmapped = !gtk_widget_get_mapped(m_pWindow);
#endif
}

void GtkSalFrame::SetIcon( sal_uInt16 nIcon )
{
    if( (m_nStyle & (SalFrameStyleFlags::PLUG|SalFrameStyleFlags::SYSTEMCHILD|SalFrameStyleFlags::FLOAT|SalFrameStyleFlags::INTRO|SalFrameStyleFlags::OWNERDRAWDECORATION))
        || ! m_pWindow )
        return;

    gchar* appicon;

    if (nIcon == SV_ICON_ID_TEXT)
        appicon = g_strdup ("libreoffice-writer");
    else if (nIcon == SV_ICON_ID_SPREADSHEET)
        appicon = g_strdup ("libreoffice-calc");
    else if (nIcon == SV_ICON_ID_DRAWING)
        appicon = g_strdup ("libreoffice-draw");
    else if (nIcon == SV_ICON_ID_PRESENTATION)
        appicon = g_strdup ("libreoffice-impress");
    else if (nIcon == SV_ICON_ID_DATABASE)
        appicon = g_strdup ("libreoffice-base");
    else if (nIcon == SV_ICON_ID_FORMULA)
        appicon = g_strdup ("libreoffice-math");
    else
        appicon = g_strdup ("libreoffice-startcenter");

    SetIcon(appicon);

    g_free (appicon);
}

void GtkSalFrame::SetMenu( SalMenu* pSalMenu )
{
    m_pSalMenu = static_cast<GtkSalMenu*>(pSalMenu);
}

SalMenu* GtkSalFrame::GetMenu()
{
    return m_pSalMenu;
}

void GtkSalFrame::DrawMenuBar()
{
}

void GtkSalFrame::Center()
{
    if (!GTK_IS_WINDOW(m_pWindow))
        return;
#if !GTK_CHECK_VERSION(4,0,0)
    if (m_pParent)
        gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER_ON_PARENT);
    else
        gtk_window_set_position(GTK_WINDOW(m_pWindow), GTK_WIN_POS_CENTER);
#endif
}

Size GtkSalFrame::calcDefaultSize()
{
    Size aScreenSize(getDisplay()->GetScreenSize(GetDisplayScreen()));
    int scale = gtk_widget_get_scale_factor(m_pWindow);
    aScreenSize.setWidth( aScreenSize.Width() / scale );
    aScreenSize.setHeight( aScreenSize.Height() / scale );
    return bestmaxFrameSizeForScreenSize(aScreenSize);
}

void GtkSalFrame::SetDefaultSize()
{
    Size aDefSize = calcDefaultSize();

    SetPosSize( 0, 0, aDefSize.Width(), aDefSize.Height(),
                SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT );

    if( (m_nStyle & SalFrameStyleFlags::DEFAULT) && m_pWindow )
        gtk_window_maximize( GTK_WINDOW(m_pWindow) );
}

void GtkSalFrame::Show( bool bVisible, bool /*bNoActivate*/ )
{
    if( !m_pWindow )
        return;

    if( bVisible )
    {
        getDisplay()->startupNotificationCompleted();

        if( m_bDefaultPos )
            Center();
        if( m_bDefaultSize )
            SetDefaultSize();
        setMinMaxSize();

        if (isFloatGrabWindow() && !getDisplay()->GetCaptureFrame())
        {
            m_pParent->grabPointer(true, true, true);
            m_pParent->addGrabLevel();
        }

#if defined(GDK_WINDOWING_WAYLAND)
        /*
         rhbz#1334915, gnome#779143, tdf#100158
         https://gitlab.gnome.org/GNOME/gtk/-/issues/767

         before gdk_wayland_window_set_application_id was available gtk
         under wayland lacked a way to change the app_id of a window, so
         brute force everything as a startcenter when initially shown to at
         least get the default LibreOffice icon and not the broken app icon
        */
        static bool bAppIdImmutable = DLSYM_GDK_IS_WAYLAND_DISPLAY(getGdkDisplay()) &&
                                      !dlsym(nullptr, "gdk_wayland_window_set_application_id");
        if (bAppIdImmutable)
        {
            OString sOrigName(g_get_prgname());
            g_set_prgname("libreoffice-startcenter");
            gtk_widget_show(m_pWindow);
            g_set_prgname(sOrigName.getStr());
        }
        else
        {
            gtk_widget_show(m_pWindow);
        }
#else
        gtk_widget_show(m_pWindow);
#endif

        if( isFloatGrabWindow() )
        {
            m_nFloats++;
            if (!getDisplay()->GetCaptureFrame())
            {
                grabPointer(true, true, true);
                addGrabLevel();
            }
            // #i44068# reset parent's IM context
            if( m_pParent )
                m_pParent->EndExtTextInput(EndExtTextInputFlags::NONE);
        }
    }
    else
    {
        if( isFloatGrabWindow() )
        {
            m_nFloats--;
            if (!getDisplay()->GetCaptureFrame())
            {
                removeGrabLevel();
                grabPointer(false, true, false);
                m_pParent->removeGrabLevel();
                bool bParentIsFloatGrabWindow = m_pParent->isFloatGrabWindow();
                m_pParent->grabPointer(bParentIsFloatGrabWindow, true, bParentIsFloatGrabWindow);
            }
        }
        gtk_widget_hide( m_pWindow );
        if( m_pIMHandler )
            m_pIMHandler->focusChanged( false );
    }
}

void GtkSalFrame::setMinMaxSize()
{
    /*  #i34504# metacity (and possibly others) do not treat
     *  _NET_WM_STATE_FULLSCREEN and max_width/height independently;
     *  whether they should is undefined. So don't set the max size hint
     *  for a full screen window.
    */
    if( !m_pWindow || isChild() )
        return;

#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkGeometry aGeo;
    int aHints = 0;
    if( m_nStyle & SalFrameStyleFlags::SIZEABLE )
    {
        if( m_aMinSize.Width() && m_aMinSize.Height() && ! m_bFullscreen )
        {
            aGeo.min_width  = m_aMinSize.Width();
            aGeo.min_height = m_aMinSize.Height();
            aHints |= GDK_HINT_MIN_SIZE;
        }
        if( m_aMaxSize.Width() && m_aMaxSize.Height() && ! m_bFullscreen )
        {
            aGeo.max_width  = m_aMaxSize.Width();
            aGeo.max_height = m_aMaxSize.Height();
            aHints |= GDK_HINT_MAX_SIZE;
        }
    }
    else
    {
        if (!m_bFullscreen && m_nWidthRequest && m_nHeightRequest)
        {
            aGeo.min_width = m_nWidthRequest;
            aGeo.min_height = m_nHeightRequest;
            aHints |= GDK_HINT_MIN_SIZE;

            aGeo.max_width = m_nWidthRequest;
            aGeo.max_height = m_nHeightRequest;
            aHints |= GDK_HINT_MAX_SIZE;
        }
    }

    if( m_bFullscreen && m_aMaxSize.Width() && m_aMaxSize.Height() )
    {
        aGeo.max_width = m_aMaxSize.Width();
        aGeo.max_height = m_aMaxSize.Height();
        aHints |= GDK_HINT_MAX_SIZE;
    }
    if( aHints )
    {
        gtk_window_set_geometry_hints( GTK_WINDOW(m_pWindow),
                                       nullptr,
                                       &aGeo,
                                       GdkWindowHints( aHints ) );
    }
#endif
}

void GtkSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
{
    if( ! isChild() )
    {
        m_aMaxSize = Size( nWidth, nHeight );
        setMinMaxSize();
    }
}
void GtkSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
{
    if( ! isChild() )
    {
        m_aMinSize = Size( nWidth, nHeight );
        if( m_pWindow )
        {
            widget_set_size_request(nWidth, nHeight);
            setMinMaxSize();
        }
    }
}

void GtkSalFrame::AllocateFrame()
{
    basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight );
    if (m_pSurface && m_aFrameSize.getX() == aFrameSize.getX() &&
                      m_aFrameSize.getY() == aFrameSize.getY() )
        return;

    if( aFrameSize.getX() == 0 )
        aFrameSize.setX( 1 );
    if( aFrameSize.getY() == 0 )
        aFrameSize.setY( 1 );

    if (m_pSurface)
        cairo_surface_destroy(m_pSurface);

    m_pSurface = surface_create_similar_surface(widget_get_surface(m_pWindow),
                                                CAIRO_CONTENT_COLOR_ALPHA,
                                                aFrameSize.getX(),
                                                aFrameSize.getY());
    m_aFrameSize = aFrameSize;

    cairo_surface_set_user_data(m_pSurface, SvpSalGraphics::getDamageKey(), &m_aDamageHandler, nullptr);
    SAL_INFO("vcl.gtk3", "allocated Frame size of " << maGeometry.nWidth << " x " << maGeometry.nHeight);

    if (m_pGraphics)
        m_pGraphics->setSurface(m_pSurface, m_aFrameSize);
}

void GtkSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags )
{
    if( !m_pWindow || isChild( true, false ) )
        return;

    if( (nFlags & ( SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT )) &&
        (nWidth > 0 && nHeight > 0 ) // sometimes stupid things happen
            )
    {
        m_bDefaultSize = false;

        // tdf#131031 Just setting maGeometry.nWidth/nHeight will delete
        // the evtl. implicitly existing space at top for the gtk native MenuBar,
        // will make the Window too big and the StatusBar at the bottom vanish
        // and thus breaks the fix in tdf#130841.
        const int nImplicitMenuBarHeight(m_pSalMenu ? m_pSalMenu->GetMenuBarHeight() : 0);
        maGeometry.nWidth = nWidth;
        maGeometry.nHeight = nHeight - nImplicitMenuBarHeight;

        if( isChild( false ) )
            widget_set_size_request(nWidth, nHeight);
        else if( ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) )
            window_resize(nWidth, nHeight);

        setMinMaxSize();
    }
    else if( m_bDefaultSize )
        SetDefaultSize();

    m_bDefaultSize = false;

    if( nFlags & ( SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ) )
    {
        if( m_pParent )
        {
            if( AllSettings::GetLayoutRTL() )
                nX = m_pParent->maGeometry.nWidth-m_nWidthRequest-1-nX;
            nX += m_pParent->maGeometry.nX;
            nY += m_pParent->maGeometry.nY;
        }

        if (nFlags & SAL_FRAME_POSSIZE_X)
            maGeometry.nX = nX;
        if (nFlags & SAL_FRAME_POSSIZE_Y)
            maGeometry.nY = nY;
        m_bGeometryIsProvisional = true;

        m_bDefaultPos = false;

        moveWindow(maGeometry.nX, maGeometry.nY);

        updateScreenNumber();
    }
    else if( m_bDefaultPos )
        Center();

    m_bDefaultPos = false;
}

void GtkSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
{
    if( m_pWindow && !(m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) )
    {
        rWidth = maGeometry.nWidth;
        rHeight = maGeometry.nHeight;
    }
    else
        rWidth = rHeight = 0;
}

void GtkSalFrame::GetWorkArea( tools::Rectangle& rRect )
{
    GdkRectangle aRect;
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkScreen  *pScreen = gtk_widget_get_screen(m_pWindow);
    tools::Rectangle aRetRect;
    int max = gdk_screen_get_n_monitors (pScreen);
    for (int i = 0; i < max; ++i)
    {
        gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
        tools::Rectangle aMonitorRect(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
        aRetRect.Union(aMonitorRect);
    }
    rRect = aRetRect;
#else
    GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
    GdkSurface* gdkWindow = widget_get_surface(m_pWindow);
    GdkMonitor* pMonitor = gdk_display_get_monitor_at_surface(pDisplay, gdkWindow);
    gdk_monitor_get_geometry(pMonitor, &aRect);
    rRect = tools::Rectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
#endif
}

SalFrame* GtkSalFrame::GetParent() const
{
    return m_pParent;
}

void GtkSalFrame::SetWindowState( const SalFrameState* pState )
{
    if( ! m_pWindow || ! pState || isChild( true, false ) )
        return;

    const WindowStateMask nMaxGeometryMask =
        WindowStateMask::X | WindowStateMask::Y |
        WindowStateMask::Width | WindowStateMask::Height |
        WindowStateMask::MaximizedX | WindowStateMask::MaximizedY |
        WindowStateMask::MaximizedWidth | WindowStateMask::MaximizedHeight;

    if( (pState->mnMask & WindowStateMask::State) &&
        ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
        (pState->mnState & WindowStateState::Maximized) &&
        (pState->mnMask & nMaxGeometryMask) == nMaxGeometryMask )
    {
        resizeWindow( pState->mnWidth, pState->mnHeight );
        moveWindow( pState->mnX, pState->mnY );
        m_bDefaultPos = m_bDefaultSize = false;

        updateScreenNumber();

        m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
        m_aRestorePosSize = tools::Rectangle( Point( pState->mnX, pState->mnY ),
                                       Size( pState->mnWidth, pState->mnHeight ) );
    }
    else if( pState->mnMask & (WindowStateMask::X | WindowStateMask::Y |
                               WindowStateMask::Width | WindowStateMask::Height ) )
    {
        sal_uInt16 nPosSizeFlags = 0;
        tools::Long nX         = pState->mnX - (m_pParent ? m_pParent->maGeometry.nX : 0);
        tools::Long nY         = pState->mnY - (m_pParent ? m_pParent->maGeometry.nY : 0);
        if( pState->mnMask & WindowStateMask::X )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
        else
            nX = maGeometry.nX - (m_pParent ? m_pParent->maGeometry.nX : 0);
        if( pState->mnMask & WindowStateMask::Y )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
        else
            nY = maGeometry.nY - (m_pParent ? m_pParent->maGeometry.nY : 0);
        if( pState->mnMask & WindowStateMask::Width )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
        if( pState->mnMask & WindowStateMask::Height )
            nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
        SetPosSize( nX, nY, pState->mnWidth, pState->mnHeight, nPosSizeFlags );
    }

    if( pState->mnMask & WindowStateMask::State && ! isChild() )
    {
        if( pState->mnState & WindowStateState::Maximized )
            gtk_window_maximize( GTK_WINDOW(m_pWindow) );
        else
            gtk_window_unmaximize( GTK_WINDOW(m_pWindow) );
        /* #i42379# there is no rollup state in GDK; and rolled up windows are
        *  (probably depending on the WM) reported as iconified. If we iconify a
        *  window here that was e.g. a dialog, then it will be unmapped but still
        *  not be displayed in the task list, so it's an iconified window that
        *  the user cannot get out of this state. So do not set the iconified state
        *  on windows with a parent (that is transient frames) since these tend
        *  to not be represented in an icon task list.
        */
        bool bMinimize = pState->mnState & WindowStateState::Minimized && !m_pParent;
#if GTK_CHECK_VERSION(4, 0, 0)
        if (bMinimize)
            gtk_window_minimize(GTK_WINDOW(m_pWindow));
        else
            gtk_window_unminimize(GTK_WINDOW(m_pWindow));
#else
        if (bMinimize)
            gtk_window_iconify(GTK_WINDOW(m_pWindow));
        else
            gtk_window_deiconify(GTK_WINDOW(m_pWindow));
#endif
    }
    TriggerPaintEvent();
}

namespace
{
    void GetPosAndSize(GtkWindow *pWindow, tools::Long& rX, tools::Long &rY, tools::Long &rWidth, tools::Long &rHeight)
    {
       gint width, height;
#if !GTK_CHECK_VERSION(4, 0, 0)
       gint root_x, root_y;
       gtk_window_get_position(GTK_WINDOW(pWindow), &root_x, &root_y);
       rX = root_x;
       rY = root_y;

       gtk_window_get_size(GTK_WINDOW(pWindow), &width, &height);
#else
       rX = 0;
       rY = 0;
       gtk_window_get_default_size(GTK_WINDOW(pWindow), &width, &height);
#endif
       rWidth = width;
       rHeight = height;
    }

    tools::Rectangle GetPosAndSize(GtkWindow *pWindow)
    {
        tools::Long nX, nY, nWidth, nHeight;
        GetPosAndSize(pWindow, nX, nY, nWidth, nHeight);
        return tools::Rectangle(nX, nY, nX + nWidth, nY + nHeight);
    }
}

bool GtkSalFrame::GetWindowState( SalFrameState* pState )
{
    pState->mnState = WindowStateState::Normal;
    pState->mnMask  = WindowStateMask::State;

    // rollup ? gtk 2.2 does not seem to support the shaded state
    if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
        pState->mnState |= WindowStateState::Minimized;
    if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
    {
        pState->mnState |= WindowStateState::Maximized;
        pState->mnX                 = m_aRestorePosSize.Left();
        pState->mnY                 = m_aRestorePosSize.Top();
        pState->mnWidth             = m_aRestorePosSize.GetWidth();
        pState->mnHeight            = m_aRestorePosSize.GetHeight();
        GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnMaximizedX, pState->mnMaximizedY,
                                             pState->mnMaximizedWidth, pState->mnMaximizedHeight);
        pState->mnMask  |= WindowStateMask::MaximizedX          |
                           WindowStateMask::MaximizedY          |
                           WindowStateMask::MaximizedWidth      |
                           WindowStateMask::MaximizedHeight;
    }
    else
    {
        GetPosAndSize(GTK_WINDOW(m_pWindow), pState->mnX, pState->mnY,
                                             pState->mnWidth, pState->mnHeight);
    }

    pState->mnMask  |= WindowStateMask::X            |
                       WindowStateMask::Y            |
                       WindowStateMask::Width        |
                       WindowStateMask::Height;

    return true;
}

void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
{
    if( !m_pWindow )
        return;

    if (maGeometry.nDisplayScreenNumber == nNewScreen && eType == SetType::RetainSize)
        return;

#if !GTK_CHECK_VERSION(4, 0, 0)
    int nX = maGeometry.nX, nY = maGeometry.nY,
        nWidth = maGeometry.nWidth, nHeight = maGeometry.nHeight;
    GdkScreen *pScreen = nullptr;
    GdkRectangle aNewMonitor;

    bool bSpanAllScreens = nNewScreen == static_cast<unsigned int>(-1);
    bool bSpanMonitorsWhenFullscreen = bSpanAllScreens && getDisplay()->getSystem()->GetDisplayScreenCount() > 1;
    gint nMonitor = -1;
    if (bSpanMonitorsWhenFullscreen)   //span all screens
    {
        pScreen = gtk_widget_get_screen( m_pWindow );
        aNewMonitor.x = 0;
        aNewMonitor.y = 0;
        aNewMonitor.width = gdk_screen_get_width(pScreen);
        aNewMonitor.height = gdk_screen_get_height(pScreen);
    }
    else
    {
        bool bSameMonitor = false;

        if (!bSpanAllScreens)
        {
            pScreen = getDisplay()->getSystem()->getScreenMonitorFromIdx( nNewScreen, nMonitor );
            if (!pScreen)
            {
                g_warning ("Attempt to move GtkSalFrame to invalid screen %d => "
                           "fallback to current\n", nNewScreen);
            }
        }

        if (!pScreen)
        {
            pScreen = gtk_widget_get_screen( m_pWindow );
            bSameMonitor = true;
        }

        // Heavy lifting, need to move screen ...
        if( pScreen != gtk_widget_get_screen( m_pWindow ))
            gtk_window_set_screen( GTK_WINDOW( m_pWindow ), pScreen );

        gint nOldMonitor = gdk_screen_get_monitor_at_window(
                                pScreen, widget_get_surface( m_pWindow ) );
        if (bSameMonitor)
            nMonitor = nOldMonitor;

    #if OSL_DEBUG_LEVEL > 1
        if( nMonitor == nOldMonitor )
            g_warning( "An apparently pointless SetScreen - should we elide it ?" );
    #endif

        GdkRectangle aOldMonitor;
        gdk_screen_get_monitor_geometry( pScreen, nOldMonitor, &aOldMonitor );
        gdk_screen_get_monitor_geometry( pScreen, nMonitor, &aNewMonitor );

        nX = aNewMonitor.x + nX - aOldMonitor.x;
        nY = aNewMonitor.y + nY - aOldMonitor.y;
    }

    bool bResize = false;
    bool bVisible = gtk_widget_get_mapped( m_pWindow );
    if( bVisible )
        Show( false );

    if( eType == SetType::Fullscreen )
    {
        nX = aNewMonitor.x;
        nY = aNewMonitor.y;
        nWidth = aNewMonitor.width;
        nHeight = aNewMonitor.height;
        m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;
        bResize = true;

        // #i110881# for the benefit of compiz set a max size here
        // else setting to fullscreen fails for unknown reasons
        m_aMaxSize.setWidth( aNewMonitor.width );
        m_aMaxSize.setHeight( aNewMonitor.height );
    }

    if( pSize && eType == SetType::UnFullscreen )
    {
        nX = pSize->Left();
        nY = pSize->Top();
        nWidth = pSize->GetWidth();
        nHeight = pSize->GetHeight();
        m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;
        bResize = true;
    }

    if (bResize)
    {
        // temporarily re-sizeable
        if( !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
            gtk_window_set_resizable( GTK_WINDOW(m_pWindow), true );
        window_resize(nWidth, nHeight);
    }

    gtk_window_move(GTK_WINDOW(m_pWindow), nX, nY);

    GdkFullscreenMode eMode =
        bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;

    gdk_window_set_fullscreen_mode(widget_get_surface(m_pWindow), eMode);

    GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
    if( eType == SetType::Fullscreen )
    {
        if (pMenuBarContainerWidget)
            gtk_widget_hide(pMenuBarContainerWidget);
        if (bSpanMonitorsWhenFullscreen)
            gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
        else
            gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pScreen, nMonitor);

    }
    else if( eType == SetType::UnFullscreen )
    {
        if (pMenuBarContainerWidget)
            gtk_widget_show(pMenuBarContainerWidget);
        gtk_window_unfullscreen( GTK_WINDOW( m_pWindow ) );
    }

    if( eType == SetType::UnFullscreen &&
        !(m_nStyle & SalFrameStyleFlags::SIZEABLE) )
        gtk_window_set_resizable( GTK_WINDOW( m_pWindow ), FALSE );

    // FIXME: we should really let gtk+ handle our widget hierarchy ...
    if( m_pParent && gtk_widget_get_screen( m_pParent->m_pWindow ) != pScreen )
        SetParent( nullptr );

    std::list< GtkSalFrame* > aChildren = m_aChildren;
    for (auto const& child : aChildren)
        child->SetScreen( nNewScreen, SetType::RetainSize );

    m_bDefaultPos = m_bDefaultSize = false;
    updateScreenNumber();

    if( bVisible )
        Show( true );

#else
    (void)pSize; // assume restore will restore the original size without our help

    bool bSpanMonitorsWhenFullscreen = nNewScreen == static_cast<unsigned int>(-1);

    GdkFullscreenMode eMode =
        bSpanMonitorsWhenFullscreen ? GDK_FULLSCREEN_ON_ALL_MONITORS : GDK_FULLSCREEN_ON_CURRENT_MONITOR;

    g_object_set(widget_get_surface(m_pWindow), "fullscreen-mode", eMode, nullptr);

    GtkWidget* pMenuBarContainerWidget = m_pSalMenu ? m_pSalMenu->GetMenuBarContainerWidget() : nullptr;
    if (eType == SetType::Fullscreen)
    {
        if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
        {
            // temp make it resizable, restore when unfullscreened
            gtk_window_set_resizable(GTK_WINDOW(m_pWindow), true);
        }

        m_nStyle |= SalFrameStyleFlags::PARTIAL_FULLSCREEN;

        if (pMenuBarContainerWidget)
            gtk_widget_hide(pMenuBarContainerWidget);
        if (bSpanMonitorsWhenFullscreen)
            gtk_window_fullscreen(GTK_WINDOW(m_pWindow));
        else
        {
            GdkDisplay* pDisplay = gtk_widget_get_display(m_pWindow);
            GListModel* pList = gdk_display_get_monitors(pDisplay);
            GdkMonitor* pMonitor = static_cast<GdkMonitor*>(g_list_model_get_item(pList, nNewScreen));
            if (!pMonitor)
                pMonitor = gdk_display_get_monitor_at_surface(pDisplay, widget_get_surface(m_pWindow));
            gtk_window_fullscreen_on_monitor(GTK_WINDOW(m_pWindow), pMonitor);
        }
    }
    else if (eType == SetType::UnFullscreen)
    {
        m_nStyle &= ~SalFrameStyleFlags::PARTIAL_FULLSCREEN;

        if (pMenuBarContainerWidget)
            gtk_widget_show(pMenuBarContainerWidget);
        gtk_window_unfullscreen(GTK_WINDOW(m_pWindow));

        if (!(m_nStyle & SalFrameStyleFlags::SIZEABLE))
        {
            // restore temp resizability
            gtk_window_set_resizable(GTK_WINDOW(m_pWindow), false);
        }
    }

    for (auto const& child : m_aChildren)
        child->SetScreen(nNewScreen, SetType::RetainSize);

    m_bDefaultPos = m_bDefaultSize = false;
    updateScreenNumber();
#endif
}

void GtkSalFrame::SetScreenNumber( unsigned int nNewScreen )
{
    SetScreen( nNewScreen, SetType::RetainSize );
}

void GtkSalFrame::updateWMClass()
{
    if (!DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
        return;

    if (!gtk_widget_get_realized(m_pWindow))
        return;

    OString aResClass = OUStringToOString(m_sWMClass, RTL_TEXTENCODING_ASCII_US);
    const char *pResClass = !aResClass.isEmpty() ? aResClass.getStr() :
                                                    SalGenericSystem::getFrameClassName();
    XClassHint* pClass = XAllocClassHint();
    OString aResName = SalGenericSystem::getFrameResName();
    pClass->res_name  = const_cast<char*>(aResName.getStr());
    pClass->res_class = const_cast<char*>(pResClass);
    Display *display = gdk_x11_display_get_xdisplay(getGdkDisplay());
    XSetClassHint( display,
                   GtkSalFrame::GetNativeWindowHandle(m_pWindow),
                   pClass );
    XFree( pClass );
}

void GtkSalFrame::SetApplicationID( const OUString &rWMClass )
{
    if( rWMClass != m_sWMClass && ! isChild() )
    {
        m_sWMClass = rWMClass;
        updateWMClass();

        for (auto const& child : m_aChildren)
            child->SetApplicationID(rWMClass);
    }
}

void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
{
    m_bFullscreen = bFullScreen;

    if( !m_pWindow || isChild() )
        return;

    if( bFullScreen )
    {
        m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
        SetScreen( nScreen, SetType::Fullscreen );
    }
    else
    {
        SetScreen( nScreen, SetType::UnFullscreen,
                   !m_aRestorePosSize.IsEmpty() ? &m_aRestorePosSize : nullptr );
        m_aRestorePosSize = tools::Rectangle();
    }
}

void GtkSalFrame::StartPresentation( bool bStart )
{
    std::optional<guint> aWindow;
    std::optional<Display*> aDisplay;

    bool bX11 = DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay());
    if (bX11)
    {
        aWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
        aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
    }

    m_ScreenSaverInhibitor.inhibit( bStart,
                                    u"presentation",
                                    bX11,
                                    aWindow,
                                    aDisplay );
}

void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    if( m_pWindow )
        gtk_window_set_keep_above( GTK_WINDOW( m_pWindow ), bOnTop );
#else
    (void)bOnTop;
#endif
}

static guint32 nLastUserInputTime = GDK_CURRENT_TIME;

guint32 GtkSalFrame::GetLastInputEventTime()
{
    return nLastUserInputTime;
}

void GtkSalFrame::UpdateLastInputEventTime(guint32 nUserInputTime)
{
    //gtk3 can generate a synthetic crossing event with a useless 0
    //(GDK_CURRENT_TIME) timestamp on showing a menu from the main
    //menubar, which is unhelpful, so ignore the 0 timestamps
    if (nUserInputTime == GDK_CURRENT_TIME)
        return;
    nLastUserInputTime = nUserInputTime;
}

void GtkSalFrame::ToTop( SalFrameToTop nFlags )
{
    if( !m_pWindow )
        return;

    if( isChild( false ) )
        GrabFocus();
    else if( gtk_widget_get_mapped( m_pWindow ) )
    {
        auto nTimestamp = GetLastInputEventTime();
#ifdef GDK_WINDOWING_X11
        GdkDisplay *pDisplay = GtkSalFrame::getGdkDisplay();
        if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
            nTimestamp = gdk_x11_display_get_user_time(pDisplay);
#endif
        if (!(nFlags & SalFrameToTop::GrabFocusOnly))
            gtk_window_present_with_time(GTK_WINDOW(m_pWindow), nTimestamp);
#if !GTK_CHECK_VERSION(4, 0, 0)
        else
            gdk_window_focus(widget_get_surface(m_pWindow), nTimestamp);
#endif
        GrabFocus();
    }
    else
    {
        if( nFlags & SalFrameToTop::RestoreWhenMin )
            gtk_window_present( GTK_WINDOW(m_pWindow) );
    }
}

void GtkSalFrame::SetPointer( PointerStyle ePointerStyle )
{
    if( !m_pWindow || ePointerStyle == m_ePointerStyle )
        return;

    m_ePointerStyle = ePointerStyle;
    GdkCursor *pCursor = getDisplay()->getCursor( ePointerStyle );
    widget_set_cursor(GTK_WIDGET(m_pWindow), pCursor);
}

void GtkSalFrame::grabPointer( bool bGrab, bool bKeyboardAlso, bool bOwnerEvents )
{
    if (bGrab)
    {
        // tdf#135779 move focus back inside usual input window out of any
        // other gtk widgets before grabbing the pointer
        GrabFocus();
    }

    static const char* pEnv = getenv( "SAL_NO_MOUSEGRABS" );
    if (pEnv && *pEnv)
        return;

    if (!m_pWindow)
        return;

#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkSeat* pSeat = gdk_display_get_default_seat(getGdkDisplay());
    if (bGrab)
    {
        GdkSeatCapabilities eCapability = bKeyboardAlso ? GDK_SEAT_CAPABILITY_ALL : GDK_SEAT_CAPABILITY_ALL_POINTING;
        gdk_seat_grab(pSeat, widget_get_surface(getMouseEventWidget()), eCapability,
                      bOwnerEvents, nullptr, nullptr, nullptr, nullptr);
    }
    else
    {
        gdk_seat_ungrab(pSeat);
    }
#else
    (void)bKeyboardAlso;
    (void)bOwnerEvents;
#endif
}

void GtkSalFrame::CaptureMouse( bool bCapture )
{
    getDisplay()->CaptureMouse( bCapture ? this : nullptr );
}

void GtkSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkSalFrame* pFrame = this;
    while( pFrame && pFrame->isChild( false ) )
        pFrame = pFrame->m_pParent;
    if( ! pFrame )
        return;

    GdkScreen *pScreen = gtk_widget_get_screen(pFrame->m_pWindow);
    GdkDisplay *pDisplay = gdk_screen_get_display( pScreen );

    /* when the application tries to center the mouse in the dialog the
     * window isn't mapped already. So use coordinates relative to the root window.
     */
    unsigned int nWindowLeft = maGeometry.nX + nX;
    unsigned int nWindowTop  = maGeometry.nY + nY;

    GdkDeviceManager* pManager = gdk_display_get_device_manager(pDisplay);
    gdk_device_warp(gdk_device_manager_get_client_pointer(pManager), pScreen, nWindowLeft, nWindowTop);

    // #i38648# ask for the next motion hint
    gint x, y;
    GdkModifierType mask;
    gdk_window_get_pointer( widget_get_surface(pFrame->m_pWindow) , &x, &y, &mask );
#else
    (void)nX;
    (void)nY;
#endif
}

void GtkSalFrame::Flush()
{
    gdk_display_flush( getGdkDisplay() );
}

void GtkSalFrame::KeyCodeToGdkKey(const vcl::KeyCode& rKeyCode,
    guint* pGdkKeyCode, GdkModifierType *pGdkModifiers)
{
    if ( pGdkKeyCode == nullptr || pGdkModifiers == nullptr )
        return;

    // Get GDK key modifiers
    GdkModifierType nModifiers = GdkModifierType(0);

    if ( rKeyCode.IsShift() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_SHIFT_MASK );

    if ( rKeyCode.IsMod1() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_CONTROL_MASK );

    if ( rKeyCode.IsMod2() )
        nModifiers = static_cast<GdkModifierType>( nModifiers | GDK_ALT_MASK );

    *pGdkModifiers = nModifiers;

    // Get GDK keycode.
    guint nKeyCode = 0;

    guint nCode = rKeyCode.GetCode();

    if ( nCode >= KEY_0 && nCode <= KEY_9 )
        nKeyCode = ( nCode - KEY_0 ) + GDK_KEY_0;
    else if ( nCode >= KEY_A && nCode <= KEY_Z )
        nKeyCode = ( nCode - KEY_A ) + GDK_KEY_A;
    else if ( nCode >= KEY_F1 && nCode <= KEY_F26 )
        nKeyCode = ( nCode - KEY_F1 ) + GDK_KEY_F1;
    else
    {
        switch (nCode)
        {
            case KEY_DOWN:          nKeyCode = GDK_KEY_Down;            break;
            case KEY_UP:            nKeyCode = GDK_KEY_Up;              break;
            case KEY_LEFT:          nKeyCode = GDK_KEY_Left;            break;
            case KEY_RIGHT:         nKeyCode = GDK_KEY_Right;           break;
            case KEY_HOME:          nKeyCode = GDK_KEY_Home;            break;
            case KEY_END:           nKeyCode = GDK_KEY_End;             break;
            case KEY_PAGEUP:        nKeyCode = GDK_KEY_Page_Up;         break;
            case KEY_PAGEDOWN:      nKeyCode = GDK_KEY_Page_Down;       break;
            case KEY_RETURN:        nKeyCode = GDK_KEY_Return;          break;
            case KEY_ESCAPE:        nKeyCode = GDK_KEY_Escape;          break;
            case KEY_TAB:           nKeyCode = GDK_KEY_Tab;             break;
            case KEY_BACKSPACE:     nKeyCode = GDK_KEY_BackSpace;       break;
            case KEY_SPACE:         nKeyCode = GDK_KEY_space;           break;
            case KEY_INSERT:        nKeyCode = GDK_KEY_Insert;          break;
            case KEY_DELETE:        nKeyCode = GDK_KEY_Delete;          break;
            case KEY_ADD:           nKeyCode = GDK_KEY_plus;            break;
            case KEY_SUBTRACT:      nKeyCode = GDK_KEY_minus;           break;
            case KEY_MULTIPLY:      nKeyCode = GDK_KEY_asterisk;        break;
            case KEY_DIVIDE:        nKeyCode = GDK_KEY_slash;           break;
            case KEY_POINT:         nKeyCode = GDK_KEY_period;          break;
            case KEY_COMMA:         nKeyCode = GDK_KEY_comma;           break;
            case KEY_LESS:          nKeyCode = GDK_KEY_less;            break;
            case KEY_GREATER:       nKeyCode = GDK_KEY_greater;         break;
            case KEY_EQUAL:         nKeyCode = GDK_KEY_equal;           break;
            case KEY_FIND:          nKeyCode = GDK_KEY_Find;            break;
            case KEY_CONTEXTMENU:   nKeyCode = GDK_KEY_Menu;            break;
            case KEY_HELP:          nKeyCode = GDK_KEY_Help;            break;
            case KEY_UNDO:          nKeyCode = GDK_KEY_Undo;            break;
            case KEY_REPEAT:        nKeyCode = GDK_KEY_Redo;            break;
            case KEY_DECIMAL:       nKeyCode = GDK_KEY_KP_Decimal;      break;
            case KEY_TILDE:         nKeyCode = GDK_KEY_asciitilde;      break;
            case KEY_QUOTELEFT:     nKeyCode = GDK_KEY_quoteleft;       break;
            case KEY_BRACKETLEFT:   nKeyCode = GDK_KEY_bracketleft;     break;
            case KEY_BRACKETRIGHT:  nKeyCode = GDK_KEY_bracketright;    break;
            case KEY_SEMICOLON:     nKeyCode = GDK_KEY_semicolon;       break;
            case KEY_QUOTERIGHT:    nKeyCode = GDK_KEY_quoteright;      break;

            // Special cases
            case KEY_COPY:          nKeyCode = GDK_KEY_Copy;            break;
            case KEY_CUT:           nKeyCode = GDK_KEY_Cut;             break;
            case KEY_PASTE:         nKeyCode = GDK_KEY_Paste;           break;
            case KEY_OPEN:          nKeyCode = GDK_KEY_Open;            break;
        }
    }

    *pGdkKeyCode = nKeyCode;
}

OUString GtkSalFrame::GetKeyName( sal_uInt16 nKeyCode )
{
    guint nGtkKeyCode;
    GdkModifierType nGtkModifiers;
    KeyCodeToGdkKey(nKeyCode, &nGtkKeyCode, &nGtkModifiers );

    gchar* pName = gtk_accelerator_get_label(nGtkKeyCode, nGtkModifiers);
    OUString aRet(pName, rtl_str_getLength(pName), RTL_TEXTENCODING_UTF8);
    g_free(pName);
    return aRet;
}

GdkDisplay *GtkSalFrame::getGdkDisplay()
{
    return GetGtkSalData()->GetGdkDisplay();
}

GtkSalDisplay *GtkSalFrame::getDisplay()
{
    return GetGtkSalData()->GetGtkDisplay();
}

SalFrame::SalPointerState GtkSalFrame::GetPointerState()
{
    SalPointerState aState;
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkScreen* pScreen;
    gint x, y;
    GdkModifierType aMask;
    gdk_display_get_pointer( getGdkDisplay(), &pScreen, &x, &y, &aMask );
    aState.maPos = Point( x - maGeometry.nX, y - maGeometry.nY );
    aState.mnState = GetMouseModCode( aMask );
#endif
    return aState;
}

KeyIndicatorState GtkSalFrame::GetIndicatorState()
{
    KeyIndicatorState nState = KeyIndicatorState::NONE;

#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkKeymap *pKeyMap = gdk_keymap_get_for_display(getGdkDisplay());

    if (gdk_keymap_get_caps_lock_state(pKeyMap))
        nState |= KeyIndicatorState::CAPSLOCK;
    if (gdk_keymap_get_num_lock_state(pKeyMap))
        nState |= KeyIndicatorState::NUMLOCK;
    if (gdk_keymap_get_scroll_lock_state(pKeyMap))
        nState |= KeyIndicatorState::SCROLLLOCK;
#endif

    return nState;
}

void GtkSalFrame::SimulateKeyPress( sal_uInt16 nKeyCode )
{
    g_warning ("missing simulate keypress %d", nKeyCode);
}

void GtkSalFrame::SetInputContext( SalInputContext* pContext )
{
    if( ! pContext )
        return;

    if( ! (pContext->mnOptions & InputContextFlags::Text) )
        return;

    // create a new im context
    if( ! m_pIMHandler )
        m_pIMHandler.reset( new IMHandler( this ) );
}

void GtkSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
{
    if( m_pIMHandler )
        m_pIMHandler->endExtTextInput( nFlags );
}

bool GtkSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
{
    // not supported yet
    return false;
}

LanguageType GtkSalFrame::GetInputLanguage()
{
    return LANGUAGE_DONTKNOW;
}

void GtkSalFrame::UpdateSettings( AllSettings& rSettings )
{
    if( ! m_pWindow )
        return;

    GtkSalGraphics* pGraphics = m_pGraphics.get();
    bool bFreeGraphics = false;
    if( ! pGraphics )
    {
        pGraphics = static_cast<GtkSalGraphics*>(AcquireGraphics());
        if ( !pGraphics )
        {
            SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings");
            return;
        }
        bFreeGraphics = true;
    }

    pGraphics->UpdateSettings( rSettings );

    if( bFreeGraphics )
        ReleaseGraphics( pGraphics );
}

void GtkSalFrame::Beep()
{
    gdk_display_beep( getGdkDisplay() );
}

const SystemEnvData* GtkSalFrame::GetSystemData() const
{
    return &m_aSystemData;
}

void GtkSalFrame::ResolveWindowHandle(SystemEnvData& rData) const
{
    if (!rData.pWidget)
        return;
    SAL_WARN("vcl.gtk3", "its undesirable to need the NativeWindowHandle, see tdf#139609");
    rData.SetWindowHandle(GetNativeWindowHandle(static_cast<GtkWidget*>(rData.pWidget)));
}

void GtkSalFrame::SetParent( SalFrame* pNewParent )
{
    GtkWindow* pWindow = GTK_IS_WINDOW(m_pWindow) ? GTK_WINDOW(m_pWindow) : nullptr;
    if (m_pParent)
    {
        if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
            gtk_window_group_remove_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
        m_pParent->m_aChildren.remove(this);
    }
    m_pParent = static_cast<GtkSalFrame*>(pNewParent);
    if (m_pParent)
    {
        m_pParent->m_aChildren.push_back(this);
        if (pWindow && GTK_IS_WINDOW(m_pParent->m_pWindow))
            gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(m_pParent->m_pWindow)), pWindow);
    }
    if (!isChild() && pWindow)
        gtk_window_set_transient_for( pWindow,
                                      (m_pParent && ! m_pParent->isChild(true,false)) ? GTK_WINDOW(m_pParent->m_pWindow) : nullptr
                                     );
}

void GtkSalFrame::SetPluginParent( SystemParentData* )
{
    //FIXME: no SetPluginParent impl. for gtk3
}

void GtkSalFrame::ResetClipRegion()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    if( m_pWindow )
        gdk_window_shape_combine_region( widget_get_surface( m_pWindow ), nullptr, 0, 0 );
#endif
}

void GtkSalFrame::BeginSetClipRegion( sal_uInt32 )
{
    if( m_pRegion )
        cairo_region_destroy( m_pRegion );
    m_pRegion = cairo_region_create();
}

void GtkSalFrame::UnionClipRegion( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
{
    if( m_pRegion )
    {
        GdkRectangle aRect;
        aRect.x         = nX;
        aRect.y         = nY;
        aRect.width     = nWidth;
        aRect.height    = nHeight;
        cairo_region_union_rectangle( m_pRegion, &aRect );
    }
}

void GtkSalFrame::EndSetClipRegion()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    if( m_pWindow && m_pRegion )
        gdk_window_shape_combine_region( widget_get_surface(m_pWindow), m_pRegion, 0, 0 );
#endif
}

void GtkSalFrame::PositionByToolkit(const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
{
    if (ImplGetSVData()->maNWFData.mbCanDetermineWindowPosition)
        return;

    m_aFloatRect = rRect;
    m_nFloatFlags = nFlags;
    m_bFloatPositioned = true;
}

void GtkSalFrame::SetModal(bool bModal)
{
    if (!m_pWindow)
        return;
    gtk_window_set_modal(GTK_WINDOW(m_pWindow), bModal);
}

bool GtkSalFrame::GetModal() const
{
    if (!m_pWindow)
        return false;
    return gtk_window_get_modal(GTK_WINDOW(m_pWindow));
}

gboolean GtkSalFrame::signalTooltipQuery(GtkWidget*, gint /*x*/, gint /*y*/,
                                     gboolean /*keyboard_mode*/, GtkTooltip *tooltip,
                                     gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (pThis->m_aTooltip.isEmpty() || pThis->m_bTooltipBlocked)
        return false;
    gtk_tooltip_set_text(tooltip,
        OUStringToOString(pThis->m_aTooltip, RTL_TEXTENCODING_UTF8).getStr());
    GdkRectangle aHelpArea;
    aHelpArea.x = pThis->m_aHelpArea.Left();
    aHelpArea.y = pThis->m_aHelpArea.Top();
    aHelpArea.width = pThis->m_aHelpArea.GetWidth();
    aHelpArea.height = pThis->m_aHelpArea.GetHeight();
    if (AllSettings::GetLayoutRTL())
        aHelpArea.x = pThis->maGeometry.nWidth-aHelpArea.width-1-aHelpArea.x;
    gtk_tooltip_set_tip_area(tooltip, &aHelpArea);
    return true;
}

bool GtkSalFrame::ShowTooltip(const OUString& rHelpText, const tools::Rectangle& rHelpArea)
{
    m_aTooltip = rHelpText;
    m_aHelpArea = rHelpArea;
    gtk_widget_trigger_tooltip_query(getMouseEventWidget());
    return true;
}

void GtkSalFrame::BlockTooltip()
{
    m_bTooltipBlocked = true;
}

void GtkSalFrame::UnblockTooltip()
{
    m_bTooltipBlocked = false;
}

void GtkSalFrame::HideTooltip()
{
    m_aTooltip.clear();
    GtkWidget* pEventWidget = getMouseEventWidget();
    gtk_widget_trigger_tooltip_query(pEventWidget);
}

namespace
{
    void set_pointing_to(GtkPopover *pPopOver, vcl::Window* pParent, const tools::Rectangle& rHelpArea, const SalFrameGeometry& rGeometry)
    {
        GdkRectangle aRect;
        aRect.x = FloatingWindow::ImplConvertToAbsPos(pParent, rHelpArea).Left() - rGeometry.nX;
        aRect.y = rHelpArea.Top();
        aRect.width = 1;
        aRect.height = 1;

        GtkPositionType ePos = gtk_popover_get_position(pPopOver);
        switch (ePos)
        {
            case GTK_POS_BOTTOM:
            case GTK_POS_TOP:
                aRect.width = rHelpArea.GetWidth();
                break;
            case GTK_POS_RIGHT:
            case GTK_POS_LEFT:
                aRect.height = rHelpArea.GetHeight();
                break;
        }

        gtk_popover_set_pointing_to(pPopOver, &aRect);
    }
}

void* GtkSalFrame::ShowPopover(const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea, QuickHelpFlags nFlags)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkWidget *pWidget = gtk_popover_new(getMouseEventWidget());
#else
    GtkWidget *pWidget = gtk_popover_new();
    gtk_widget_set_parent(pWidget, getMouseEventWidget());
#endif
    OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
    GtkWidget *pLabel =  gtk_label_new(sUTF.getStr());
#if !GTK_CHECK_VERSION(4, 0, 0)
    gtk_container_add(GTK_CONTAINER(pWidget), pLabel);
#else
    gtk_popover_set_child(GTK_POPOVER(pWidget), pLabel);
#endif

    if (nFlags & QuickHelpFlags::Top)
        gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_BOTTOM);
    else if (nFlags & QuickHelpFlags::Bottom)
        gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_TOP);
    else if (nFlags & QuickHelpFlags::Left)
        gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_RIGHT);
    else if (nFlags & QuickHelpFlags::Right)
        gtk_popover_set_position(GTK_POPOVER(pWidget), GTK_POS_LEFT);

    set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);

#if !GTK_CHECK_VERSION(4, 0, 0)
    gtk_popover_set_modal(GTK_POPOVER(pWidget), false);
#else
    gtk_popover_set_autohide(GTK_POPOVER(pWidget), false);
#endif

    gtk_widget_show(pLabel);
    gtk_widget_show(pWidget);

    return pWidget;
}

bool GtkSalFrame::UpdatePopover(void* nId, const OUString& rHelpText, vcl::Window* pParent, const tools::Rectangle& rHelpArea)
{
    GtkWidget *pWidget = static_cast<GtkWidget*>(nId);

    set_pointing_to(GTK_POPOVER(pWidget), pParent, rHelpArea, maGeometry);

#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkWidget *pLabel = gtk_bin_get_child(GTK_BIN(pWidget));
#else
    GtkWidget *pLabel = gtk_popover_get_child(GTK_POPOVER(pWidget));
#endif
    OString sUTF = OUStringToOString(rHelpText, RTL_TEXTENCODING_UTF8);
    gtk_label_set_text(GTK_LABEL(pLabel), sUTF.getStr());

    return true;
}

bool GtkSalFrame::HidePopover(void* nId)
{
    GtkWidget *pWidget = static_cast<GtkWidget*>(nId);
#if !GTK_CHECK_VERSION(4, 0, 0)
    gtk_widget_destroy(pWidget);
#else
    g_clear_pointer(&pWidget, gtk_widget_unparent);
#endif
    return true;
}

void GtkSalFrame::addGrabLevel()
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    if (m_nGrabLevel == 0)
        gtk_grab_add(getMouseEventWidget());
#endif
    ++m_nGrabLevel;
}

void GtkSalFrame::removeGrabLevel()
{
    if (m_nGrabLevel > 0)
    {
        --m_nGrabLevel;
#if !GTK_CHECK_VERSION(4, 0, 0)
        if (m_nGrabLevel == 0)
            gtk_grab_remove(getMouseEventWidget());
#endif
    }
}

void GtkSalFrame::closePopup()
{
    if (!m_nFloats)
        return;
    ImplSVData* pSVData = ImplGetSVData();
    if (!pSVData->mpWinData->mpFirstFloat)
        return;
    if (pSVData->mpWinData->mpFirstFloat->ImplGetFrame() != this)
        return;
    pSVData->mpWinData->mpFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
}

#if !GTK_CHECK_VERSION(4, 0, 0)
namespace
{
    //tdf#117981 translate embedded video window mouse events to parent coordinates
    void translate_coords(GdkWindow* pSourceWindow, GtkWidget* pTargetWidget, int& rEventX, int& rEventY)
    {
        gpointer user_data=nullptr;
        gdk_window_get_user_data(pSourceWindow, &user_data);
        GtkWidget* pRealEventWidget = static_cast<GtkWidget*>(user_data);
        if (pRealEventWidget)
        {
            gtk_coord fX(0.0), fY(0.0);
            gtk_widget_translate_coordinates(pRealEventWidget, pTargetWidget, rEventX, rEventY, &fX, &fY);
            rEventX = fX;
            rEventY = fY;
        }
    }
}
#endif

void GtkSalFrame::GrabFocus()
{
    GtkWidget* pGrabWidget;
#if !GTK_CHECK_VERSION(4, 0, 0)
    if (GTK_IS_EVENT_BOX(m_pWindow))
        pGrabWidget = GTK_WIDGET(m_pWindow);
    else
        pGrabWidget = GTK_WIDGET(m_pFixedContainer);
    // m_nSetFocusSignalId is 0 for the DisallowCycleFocusOut case where
    // we don't allow focus to enter the toplevel, but expect it to
    // stay in some embedded native gtk widget
    if (!gtk_widget_get_can_focus(pGrabWidget) && m_nSetFocusSignalId)
        gtk_widget_set_can_focus(pGrabWidget, true);
#else
    pGrabWidget = GTK_WIDGET(m_pFixedContainer);
#endif
    if (!gtk_widget_has_focus(pGrabWidget))
    {
        gtk_widget_grab_focus(pGrabWidget);
        if (m_pIMHandler)
            m_pIMHandler->focusChanged(true);
    }
}

bool GtkSalFrame::DrawingAreaButton(SalEvent nEventType, int nEventX, int nEventY, int nButton, guint32 nTime, guint nState)
{
    UpdateLastInputEventTime(nTime);

    SalMouseEvent aEvent;
    switch(nButton)
    {
        case 1: aEvent.mnButton = MOUSE_LEFT;   break;
        case 2: aEvent.mnButton = MOUSE_MIDDLE; break;
        case 3: aEvent.mnButton = MOUSE_RIGHT;  break;
        default: return false;
    }

    aEvent.mnTime = nTime;
    aEvent.mnX = nEventX;
    aEvent.mnY = nEventY;
    aEvent.mnCode = GetMouseModCode(nState);

    if( AllSettings::GetLayoutRTL() )
        aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;

    CallCallbackExc(nEventType, &aEvent);

    return true;
}

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalButton(GtkWidget*, GdkEventButton* pEvent, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    GtkWidget* pEventWidget = pThis->getMouseEventWidget();
    bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);

    if (pEvent->type == GDK_BUTTON_PRESS)
    {
        // tdf#120764 It isn't allowed under wayland to have two visible popups that share
        // the same top level parent. The problem is that since gtk 3.24 tooltips are also
        // implemented as popups, which means that we cannot show any popup if there is a
        // visible tooltip. In fact, gtk will hide the tooltip by itself after this handler,
        // in case of a button press event. But if we intend to show a popup on button press
        // it will be too late, so just do it here:
        pThis->HideTooltip();

        // focus on click
        if (!bDifferentEventWindow)
            pThis->GrabFocus();
    }

    SalEvent nEventType = SalEvent::NONE;
    switch( pEvent->type )
    {
        case GDK_BUTTON_PRESS:
            nEventType = SalEvent::MouseButtonDown;
            break;
        case GDK_BUTTON_RELEASE:
            nEventType = SalEvent::MouseButtonUp;
            break;
        default:
            return false;
    }

    vcl::DeletionListener aDel( pThis );

    if (pThis->isFloatGrabWindow())
    {
        //rhbz#1505379 if the window that got the event isn't our one, or there's none
        //of our windows under the mouse then close this popup window
        if (bDifferentEventWindow ||
            gdk_device_get_window_at_position(pEvent->device, nullptr, nullptr) == nullptr)
        {
            if (pEvent->type == GDK_BUTTON_PRESS)
                pThis->closePopup();
            else if (pEvent->type == GDK_BUTTON_RELEASE)
                return true;
        }
    }

    int nEventX = pEvent->x;
    int nEventY = pEvent->y;

    if (bDifferentEventWindow)
        translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);

    if (!aDel.isDeleted())
    {
        int frame_x = static_cast<int>(pEvent->x_root - nEventX);
        int frame_y = static_cast<int>(pEvent->y_root - nEventY);
        if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
        {
            pThis->m_bGeometryIsProvisional = false;
            pThis->maGeometry.nX = frame_x;
            pThis->maGeometry.nY = frame_y;
            ImplSVData* pSVData = ImplGetSVData();
            if (pSVData->maNWFData.mbCanDetermineWindowPosition)
                pThis->CallCallbackExc(SalEvent::Move, nullptr);
        }
    }

    bool bRet = false;
    if (!aDel.isDeleted())
    {
        bRet = pThis->DrawingAreaButton(nEventType,
                                        pEvent->x_root - pThis->maGeometry.nX,
                                        pEvent->y_root - pThis->maGeometry.nY,
                                        pEvent->button,
                                        pEvent->time,
                                        pEvent->state);
    }

    return bRet;
}
#else
void GtkSalFrame::gesturePressed(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->gestureButton(pGesture, SalEvent::MouseButtonDown, x, y);
}

void GtkSalFrame::gestureReleased(GtkGestureClick* pGesture, int /*n_press*/, gdouble x, gdouble y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->gestureButton(pGesture, SalEvent::MouseButtonUp, x, y);
}

void GtkSalFrame::gestureButton(GtkGestureClick* pGesture, SalEvent nEventType, gdouble x, gdouble y)
{
    GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pGesture));
    GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pGesture));
    int nButton = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(pGesture));
    DrawingAreaButton(nEventType, x, y, nButton, gdk_event_get_time(pEvent), eType);
}
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::LaunchAsyncScroll(GdkEvent const * pEvent)
{
    //if we don't match previous pending states, flush that queue now
    if (!m_aPendingScrollEvents.empty() && pEvent->scroll.state != m_aPendingScrollEvents.back()->scroll.state)
    {
        m_aSmoothScrollIdle.Stop();
        m_aSmoothScrollIdle.Invoke();
        assert(m_aPendingScrollEvents.empty());
    }
    //add scroll event to queue
    m_aPendingScrollEvents.push_back(gdk_event_copy(pEvent));
    if (!m_aSmoothScrollIdle.IsActive())
        m_aSmoothScrollIdle.Start();
}
#endif

void GtkSalFrame::DrawingAreaScroll(double delta_x, double delta_y, int nEventX, int nEventY, guint32 nTime, guint nState)
{
    SalWheelMouseEvent aEvent;

    aEvent.mnTime = nTime;
    aEvent.mnX = nEventX;
    // --- RTL --- (mirror mouse pos)
    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = maGeometry.nWidth - 1 - aEvent.mnX;
    aEvent.mnY = nEventY;
    aEvent.mnCode = GetMouseModCode(nState);

    // rhbz#1344042 "Traditionally" in gtk3 we tool a single up/down event as
    // equating to 3 scroll lines and a delta of 120. So scale the delta here
    // by 120 where a single mouse wheel click is an incoming delta_x of 1
    // and divide that by 40 to get the number of scroll lines
    if (delta_x != 0.0)
    {
        aEvent.mnDelta = -delta_x * 120;
        aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
        if (aEvent.mnDelta == 0)
            aEvent.mnDelta = aEvent.mnNotchDelta;
        aEvent.mbHorz = true;
        aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
        CallCallbackExc(SalEvent::WheelMouse, &aEvent);
    }

    if (delta_y != 0.0)
    {
        aEvent.mnDelta = -delta_y * 120;
        aEvent.mnNotchDelta = aEvent.mnDelta < 0 ? -1 : +1;
        if (aEvent.mnDelta == 0)
            aEvent.mnDelta = aEvent.mnNotchDelta;
        aEvent.mbHorz = false;
        aEvent.mnScrollLines = std::abs(aEvent.mnDelta) / 40.0;
        CallCallbackExc(SalEvent::WheelMouse, &aEvent);
    }
}

#if !GTK_CHECK_VERSION(4, 0, 0)
IMPL_LINK_NOARG(GtkSalFrame, AsyncScroll, Timer *, void)
{
    assert(!m_aPendingScrollEvents.empty());

    SalWheelMouseEvent aEvent;

    GdkEvent* pEvent = m_aPendingScrollEvents.back();
    auto nEventX = pEvent->scroll.x;
    auto nEventY = pEvent->scroll.y;
    auto nTime = pEvent->scroll.time;
    auto nState = pEvent->scroll.state;

    double delta_x(0.0), delta_y(0.0);
    for (auto pSubEvent : m_aPendingScrollEvents)
    {
        delta_x += pSubEvent->scroll.delta_x;
        delta_y += pSubEvent->scroll.delta_y;
        gdk_event_free(pSubEvent);
    }
    m_aPendingScrollEvents.clear();

    DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, nState);
}
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
SalWheelMouseEvent GtkSalFrame::GetWheelEvent(const GdkEventScroll& rEvent)
{
    SalWheelMouseEvent aEvent;

    aEvent.mnTime = rEvent.time;
    aEvent.mnX = static_cast<sal_uLong>(rEvent.x);
    aEvent.mnY = static_cast<sal_uLong>(rEvent.y);
    aEvent.mnCode = GetMouseModCode(rEvent.state);

    switch (rEvent.direction)
    {
        case GDK_SCROLL_UP:
            aEvent.mnDelta = 120;
            aEvent.mnNotchDelta = 1;
            aEvent.mnScrollLines = 3;
            aEvent.mbHorz = false;
            break;

        case GDK_SCROLL_DOWN:
            aEvent.mnDelta = -120;
            aEvent.mnNotchDelta = -1;
            aEvent.mnScrollLines = 3;
            aEvent.mbHorz = false;
            break;

        case GDK_SCROLL_LEFT:
            aEvent.mnDelta = 120;
            aEvent.mnNotchDelta = 1;
            aEvent.mnScrollLines = 3;
            aEvent.mbHorz = true;
            break;

        case GDK_SCROLL_RIGHT:
            aEvent.mnDelta = -120;
            aEvent.mnNotchDelta = -1;
            aEvent.mnScrollLines = 3;
            aEvent.mbHorz = true;
            break;
        default:
            break;
    }

    return aEvent;
}

gboolean GtkSalFrame::signalScroll(GtkWidget*, GdkEvent* pInEvent, gpointer frame)
{
    GdkEventScroll& rEvent = pInEvent->scroll;

    UpdateLastInputEventTime(rEvent.time);

    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    if (rEvent.direction == GDK_SCROLL_SMOOTH)
    {
        pThis->LaunchAsyncScroll(pInEvent);
        return true;
    }

    //if we have smooth scrolling previous pending states, flush that queue now
    if (!pThis->m_aPendingScrollEvents.empty())
    {
        pThis->m_aSmoothScrollIdle.Stop();
        pThis->m_aSmoothScrollIdle.Invoke();
        assert(pThis->m_aPendingScrollEvents.empty());
    }

    SalWheelMouseEvent aEvent(GetWheelEvent(rEvent));

    // --- RTL --- (mirror mouse pos)
    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = pThis->maGeometry.nWidth - 1 - aEvent.mnX;

    pThis->CallCallbackExc(SalEvent::WheelMouse, &aEvent);

    return true;
}
#else
gboolean GtkSalFrame::signalScroll(GtkEventControllerScroll* pController, double delta_x, double delta_y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
    GdkModifierType eState = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));

    auto nTime = gdk_event_get_time(pEvent);

    UpdateLastInputEventTime(nTime);

    double nEventX(0.0), nEventY(0.0);
    gdk_event_get_position(pEvent, &nEventX, &nEventY);

    pThis->DrawingAreaScroll(delta_x, delta_y, nEventX, nEventY, nTime, eState);

    return true;
}

#endif

void GtkSalFrame::gestureSwipe(GtkGestureSwipe* gesture, gdouble velocity_x, gdouble velocity_y, gpointer frame)
{
    gdouble x, y;
    GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
    //I feel I want the first point of the sequence, not the last point which
    //the docs say this gives, but for the moment assume we start and end
    //within the same vcl window
    if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
    {
        SalSwipeEvent aEvent;
        aEvent.mnVelocityX = velocity_x;
        aEvent.mnVelocityY = velocity_y;
        aEvent.mnX = x;
        aEvent.mnY = y;

        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::Swipe, &aEvent);
    }
}

void GtkSalFrame::gestureLongPress(GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer frame)
{
    GdkEventSequence *sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
    if (gtk_gesture_get_point(GTK_GESTURE(gesture), sequence, &x, &y))
    {
        SalLongPressEvent aEvent;
        aEvent.mnX = x;
        aEvent.mnY = y;

        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::LongPress, &aEvent);
    }
}

void GtkSalFrame::DrawingAreaMotion(int nEventX, int nEventY, guint32 nTime, guint nState)
{
    UpdateLastInputEventTime(nTime);

    SalMouseEvent aEvent;
    aEvent.mnTime = nTime;
    aEvent.mnX = nEventX;
    aEvent.mnY = nEventY;
    aEvent.mnCode = GetMouseModCode(nState);
    aEvent.mnButton = 0;

    if( AllSettings::GetLayoutRTL() )
        aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;

    CallCallbackExc(SalEvent::MouseMove, &aEvent);
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalMotion(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
    GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
    pThis->DrawingAreaMotion(x, y, gdk_event_get_time(pEvent), eType);
}
#else
gboolean GtkSalFrame::signalMotion( GtkWidget*, GdkEventMotion* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    GtkWidget* pEventWidget = pThis->getMouseEventWidget();
    bool bDifferentEventWindow = pEvent->window != widget_get_surface(pEventWidget);

    //If a menu, e.g. font name dropdown, is open, then under wayland moving the
    //mouse in the top left corner of the toplevel window in a
    //0,0,float-width,float-height area generates motion events which are
    //delivered to the dropdown
    if (pThis->isFloatGrabWindow() && bDifferentEventWindow)
        return true;

    vcl::DeletionListener aDel( pThis );

    int nEventX = pEvent->x;
    int nEventY = pEvent->y;

    if (bDifferentEventWindow)
        translate_coords(pEvent->window, pEventWidget, nEventX, nEventY);

    int frame_x = static_cast<int>(pEvent->x_root - nEventX);
    int frame_y = static_cast<int>(pEvent->y_root - nEventY);

    if (pThis->m_bGeometryIsProvisional || frame_x != pThis->maGeometry.nX || frame_y != pThis->maGeometry.nY)
    {
        pThis->m_bGeometryIsProvisional = false;
        pThis->maGeometry.nX = frame_x;
        pThis->maGeometry.nY = frame_y;
        ImplSVData* pSVData = ImplGetSVData();
        if (pSVData->maNWFData.mbCanDetermineWindowPosition)
            pThis->CallCallbackExc(SalEvent::Move, nullptr);
    }

    if (!aDel.isDeleted())
    {
        pThis->DrawingAreaMotion(pEvent->x_root - pThis->maGeometry.nX,
                                 pEvent->y_root - pThis->maGeometry.nY,
                                 pEvent->time, pEvent->state);
    }

    if (!aDel.isDeleted())
    {
        // ask for the next hint
        gint x, y;
        GdkModifierType mask;
        gdk_window_get_pointer( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &x, &y, &mask );
    }

    return true;
}
#endif

void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
{
    UpdateLastInputEventTime(nTime);

    SalMouseEvent aEvent;
    aEvent.mnTime = nTime;
    aEvent.mnX = nEventX;
    aEvent.mnY = nEventY;
    aEvent.mnCode = GetMouseModCode(nState);
    aEvent.mnButton = 0;

    if (AllSettings::GetLayoutRTL())
        aEvent.mnX = maGeometry.nWidth-1-aEvent.mnX;

    CallCallbackExc(nEventType, &aEvent);
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalEnter(GtkEventControllerMotion *pController, double x, double y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
    GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
    pThis->DrawingAreaCrossing(SalEvent::MouseMove, x, y, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
}

void GtkSalFrame::signalLeave(GtkEventControllerMotion *pController, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    GdkEvent* pEvent = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController));
    GdkModifierType eType = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(pController));
    pThis->DrawingAreaCrossing(SalEvent::MouseLeave, -1, -1, pEvent ? gdk_event_get_time(pEvent) : GDK_CURRENT_TIME, eType);
}
#else
gboolean GtkSalFrame::signalCrossing( GtkWidget*, GdkEventCrossing* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaCrossing((pEvent->type == GDK_ENTER_NOTIFY) ? SalEvent::MouseMove : SalEvent::MouseLeave,
                               pEvent->x_root - pThis->maGeometry.nX,
                               pEvent->y_root - pThis->maGeometry.nY,
                               pEvent->time,
                               pEvent->state);
    return true;
}
#endif

cairo_t* GtkSalFrame::getCairoContext() const
{
    cairo_t* cr = cairo_create(m_pSurface);
    assert(cr);
    return cr;
}

void GtkSalFrame::damaged(sal_Int32 nExtentsX, sal_Int32 nExtentsY,
                          sal_Int32 nExtentsWidth, sal_Int32 nExtentsHeight) const
{
#if OSL_DEBUG_LEVEL > 0
    if (dumpframes)
    {
        static int frame;
        OString tmp("/tmp/frame" + OString::number(frame++) + ".png");
        cairo_t* cr = getCairoContext();
        cairo_surface_write_to_png(cairo_get_target(cr), tmp.getStr());
        cairo_destroy(cr);
    }
#endif

    // quite a bit of noise in RTL mode with negative widths
    if (nExtentsWidth <= 0 || nExtentsHeight <= 0)
        return;

#if !GTK_CHECK_VERSION(4, 0, 0)
    gtk_widget_queue_draw_area(GTK_WIDGET(m_pDrawingArea),
                               nExtentsX, nExtentsY,
                               nExtentsWidth, nExtentsHeight);
#else
    gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
    (void)nExtentsX;
    (void)nExtentsY;
#endif
}

// blit our backing cairo surface to the target cairo context
void GtkSalFrame::DrawingAreaDraw(cairo_t *cr)
{
    cairo_set_source_surface(cr, m_pSurface, 0, 0);
    cairo_paint(cr);
}

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalDraw(GtkWidget*, cairo_t *cr, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaDraw(cr);
    return false;
}
#else
void GtkSalFrame::signalDraw(GtkDrawingArea*, cairo_t *cr, int /*width*/, int /*height*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaDraw(cr);
}
#endif

void GtkSalFrame::DrawingAreaResized(GtkWidget* pWidget, int nWidth, int nHeight)
{
    // ignore size-allocations that occur during configuring an embedded SalObject
    if (m_bSalObjectSetPosSize)
        return;
    maGeometry.nWidth = nWidth;
    maGeometry.nHeight = nHeight;
    bool bRealized = gtk_widget_get_realized(pWidget);
    if (bRealized)
        AllocateFrame();
    CallCallbackExc( SalEvent::Resize, nullptr );
    if (bRealized)
        TriggerPaintEvent();
}

#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, GdkRectangle *pAllocation, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaResized(pWidget, pAllocation->width, pAllocation->height);
}
#else
void GtkSalFrame::sizeAllocated(GtkWidget* pWidget, int nWidth, int nHeight, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaResized(pWidget, nWidth, nHeight);
}
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
namespace {

void swapDirection(GdkGravity& gravity)
{
    if (gravity == GDK_GRAVITY_NORTH_WEST)
        gravity = GDK_GRAVITY_NORTH_EAST;
    else if (gravity == GDK_GRAVITY_NORTH_EAST)
        gravity = GDK_GRAVITY_NORTH_WEST;
    else if (gravity == GDK_GRAVITY_SOUTH_WEST)
        gravity = GDK_GRAVITY_SOUTH_EAST;
    else if (gravity == GDK_GRAVITY_SOUTH_EAST)
        gravity = GDK_GRAVITY_SOUTH_WEST;
}

}
#endif

void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->AllocateFrame();
    if (pThis->m_bSalObjectSetPosSize)
        return;
    pThis->TriggerPaintEvent();

#if !GTK_CHECK_VERSION(4, 0, 0)
    if (!pThis->m_bFloatPositioned)
        return;

    static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
                                                                 GdkGravity, GdkAnchorHints, gint, gint)>(
                                                                    dlsym(nullptr, "gdk_window_move_to_rect"));
    if (!window_move_to_rect)
        return;

    GdkGravity rect_anchor = GDK_GRAVITY_SOUTH_WEST, menu_anchor = GDK_GRAVITY_NORTH_WEST;

    if (pThis->m_nFloatFlags & FloatWinPopupFlags::Left)
    {
        rect_anchor = GDK_GRAVITY_NORTH_WEST;
        menu_anchor = GDK_GRAVITY_NORTH_EAST;
    }
    else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Up)
    {
        rect_anchor = GDK_GRAVITY_NORTH_WEST;
        menu_anchor = GDK_GRAVITY_SOUTH_WEST;
    }
    else if (pThis->m_nFloatFlags & FloatWinPopupFlags::Right)
    {
        rect_anchor = GDK_GRAVITY_NORTH_EAST;
    }

    VclPtr<vcl::Window> pVclParent = pThis->GetWindow()->GetParent();
    if (pVclParent->GetOutDev()->HasMirroredGraphics() && pVclParent->IsRTLEnabled())
    {
        swapDirection(rect_anchor);
        swapDirection(menu_anchor);
    }

    tools::Rectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
    if (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)) != GDK_WINDOW_TOPLEVEL)
        aFloatRect.Move(-pThis->m_pParent->maGeometry.nX, -pThis->m_pParent->maGeometry.nY);

    GdkRectangle rect {static_cast<int>(aFloatRect.Left()), static_cast<int>(aFloatRect.Top()),
                       static_cast<int>(aFloatRect.GetWidth()), static_cast<int>(aFloatRect.GetHeight())};

    GdkSurface* gdkWindow = widget_get_surface(pThis->m_pWindow);
    window_move_to_rect(gdkWindow, &rect, rect_anchor, menu_anchor, static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE), 0, 0);
#endif
}

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalConfigure(GtkWidget*, GdkEventConfigure* pEvent, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    bool bMoved = false;
    int x = pEvent->x, y = pEvent->y;

    /* #i31785# claims we cannot trust the x,y members of the event;
     * they are e.g. not set correctly on maximize/demaximize;
     * yet the gdkdisplay-x11.c code handling configure_events has
     * done this XTranslateCoordinates work since the day ~zero.
     */
    if (pThis->m_bGeometryIsProvisional || x != pThis->maGeometry.nX || y != pThis->maGeometry.nY )
    {
        bMoved = true;
        pThis->m_bGeometryIsProvisional = false;
        pThis->maGeometry.nX = x;
        pThis->maGeometry.nY = y;
    }

    // update decoration hints
    GdkRectangle aRect;
    gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
    pThis->maGeometry.nTopDecoration    = y - aRect.y;
    pThis->maGeometry.nBottomDecoration = aRect.y + aRect.height - y - pEvent->height;
    pThis->maGeometry.nLeftDecoration   = x - aRect.x;
    pThis->maGeometry.nRightDecoration  = aRect.x + aRect.width - x - pEvent->width;
    pThis->updateScreenNumber();

    if (bMoved)
    {
        ImplSVData* pSVData = ImplGetSVData();
        if (pSVData->maNWFData.mbCanDetermineWindowPosition)
            pThis->CallCallbackExc(SalEvent::Move, nullptr);
    }

    return false;
}
#endif

void GtkSalFrame::queue_draw()
{
    gtk_widget_queue_draw(GTK_WIDGET(m_pDrawingArea));
}

void GtkSalFrame::TriggerPaintEvent()
{
    //Under gtk2 we can basically paint directly into the XWindow and on
    //additional "expose-event" events we can re-render the missing pieces
    //
    //Under gtk3 we have to keep our own buffer up to date and flush it into
    //the given cairo context on "draw". So we emit a paint event on
    //opportune resize trigger events to initially fill our backbuffer and then
    //keep it up to date with our direct paints and tell gtk those regions
    //have changed and then blit them into the provided cairo context when
    //we get the "draw"
    //
    //The other alternative was to always paint everything on "draw", but
    //that duplicates the amount of drawing and is hideously slow
    SAL_INFO("vcl.gtk3", "force painting" << 0 << "," << 0 << " " << maGeometry.nWidth << "x" << maGeometry.nHeight);
    SalPaintEvent aPaintEvt(0, 0, maGeometry.nWidth, maGeometry.nHeight, true);
    CallCallbackExc(SalEvent::Paint, &aPaintEvt);
    queue_draw();
}

void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
{
    SalGenericInstance* pSalInstance =
        static_cast<SalGenericInstance*>(GetSalData()->m_pInstance);

    // check if printers have changed (analogous to salframe focus handler)
    pSalInstance->updatePrinterUpdate();

    if (nEventType == SalEvent::LoseFocus)
        m_nKeyModifiers = ModKeyFlags::NONE;

    if (m_pIMHandler)
    {
        bool bFocusInAnotherGtkWidget = false;
        if (GTK_IS_WINDOW(m_pWindow))
        {
            GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
            bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
        }
        if (!bFocusInAnotherGtkWidget)
            m_pIMHandler->focusChanged(nEventType == SalEvent::GetFocus);
    }

    // ask for changed printers like generic implementation
    if (nEventType == SalEvent::GetFocus && pSalInstance->isPrinterInit())
        pSalInstance->updatePrinterUpdate();

    CallCallbackExc(nEventType, nullptr);
}

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalFocus( GtkWidget*, GdkEventFocus* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    SalGenericInstance *pSalInstance =
        static_cast< SalGenericInstance* >(GetSalData()->m_pInstance);

    // check if printers have changed (analogous to salframe focus handler)
    pSalInstance->updatePrinterUpdate();

    if( !pEvent->in )
        pThis->m_nKeyModifiers = ModKeyFlags::NONE;

    if( pThis->m_pIMHandler )
    {
        bool bFocusInAnotherGtkWidget = false;
        if (GTK_IS_WINDOW(pThis->m_pWindow))
        {
            GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
            bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
        }
        if (!bFocusInAnotherGtkWidget)
            pThis->m_pIMHandler->focusChanged( pEvent->in != 0 );
    }

    // ask for changed printers like generic implementation
    if( pEvent->in && pSalInstance->isPrinterInit() )
        pSalInstance->updatePrinterUpdate();

    // FIXME: find out who the hell steals the focus from our frame
    // while we have the pointer grabbed, this should not come from
    // the window manager. Is this an event that was still queued ?
    // The focus does not seem to get set inside our process
    // in the meantime do not propagate focus get/lose if floats are open
    if( m_nFloats == 0 )
    {
        GtkWidget* pGrabWidget;
        if (GTK_IS_EVENT_BOX(pThis->m_pWindow))
            pGrabWidget = GTK_WIDGET(pThis->m_pWindow);
        else
            pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);
        bool bHasFocus = gtk_widget_has_focus(pGrabWidget);
        pThis->CallCallbackExc(bHasFocus ? SalEvent::GetFocus : SalEvent::LoseFocus, nullptr);
    }

    return false;
}
#else
void GtkSalFrame::signalFocusEnter(GtkEventControllerFocus*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaFocusInOut(SalEvent::GetFocus);
}

void GtkSalFrame::signalFocusLeave(GtkEventControllerFocus*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->DrawingAreaFocusInOut(SalEvent::LoseFocus);
}
#endif

// change of focus between native widgets within the toplevel
#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalSetFocus(GtkWindow*, GtkWidget* pWidget, gpointer frame)
#else
void GtkSalFrame::signalSetFocus(GtkWindow*, GParamSpec*, gpointer frame)
#endif
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    GtkWidget* pGrabWidget = GTK_WIDGET(pThis->m_pFixedContainer);

    GtkWidget* pTopLevel = widget_get_toplevel(pGrabWidget);
    // see commentary in GtkSalObjectWidgetClip::Show
    if (pTopLevel && g_object_get_data(G_OBJECT(pTopLevel), "g-lo-BlockFocusChange"))
        return;

#if GTK_CHECK_VERSION(4, 0, 0)
    GtkWidget* pWidget = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
#endif

    // tdf#129634 interpret losing focus as focus passing explicitly to another widget
    bool bLoseFocus = pWidget && pWidget != pGrabWidget;

    // do not propagate focus get/lose if floats are open
    pThis->CallCallbackExc(bLoseFocus ? SalEvent::LoseFocus : SalEvent::GetFocus, nullptr);

#if !GTK_CHECK_VERSION(4, 0, 0)
    gtk_widget_set_can_focus(GTK_WIDGET(pThis->m_pFixedContainer), !bLoseFocus);
#endif
}

void GtkSalFrame::WindowMap()
{
    if (m_bIconSetWhileUnmapped)
        SetIcon(gtk_window_get_icon_name(GTK_WINDOW(m_pWindow)));

    CallCallbackExc( SalEvent::Resize, nullptr );
    TriggerPaintEvent();
}

void GtkSalFrame::WindowUnmap()
{
    CallCallbackExc( SalEvent::Resize, nullptr );

    if (m_bFloatPositioned)
    {
        // Unrealize is needed for cases where we reuse the same popup
        // (e.g. the font name control), making the realize signal fire
        // again on next show.
        gtk_widget_unrealize(m_pWindow);
        m_bFloatPositioned = false;
    }
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalMap(GtkWidget*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->WindowMap();
}

void GtkSalFrame::signalUnmap(GtkWidget*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->WindowUnmap();
}
#else
gboolean GtkSalFrame::signalMap(GtkWidget*, GdkEvent*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->WindowMap();
    return false;
}

gboolean GtkSalFrame::signalUnmap(GtkWidget*, GdkEvent*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->WindowUnmap();
    return false;
}
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)

static bool key_forward(GdkEventKey* pEvent, GtkWindow* pDest)
{
    gpointer pClass = g_type_class_ref(GTK_TYPE_WINDOW);
    GtkWidgetClass* pWindowClass = GTK_WIDGET_CLASS(pClass);
    bool bHandled = pEvent->type == GDK_KEY_PRESS
        ? pWindowClass->key_press_event(GTK_WIDGET(pDest), pEvent)
        : pWindowClass->key_release_event(GTK_WIDGET(pDest), pEvent);
    g_type_class_unref(pClass);
    return bHandled;
}

static bool activate_menubar_mnemonic(GtkWidget* pWidget, guint nKeyval)
{
    const char* pLabel = gtk_menu_item_get_label(GTK_MENU_ITEM(pWidget));
    gunichar cAccelChar = 0;
    if (!pango_parse_markup(pLabel, -1, '_', nullptr, nullptr, &cAccelChar, nullptr))
        return false;
    if (!cAccelChar)
        return false;
    auto nMnemonicKeyval = gdk_keyval_to_lower(gdk_unicode_to_keyval(cAccelChar));
    if (nKeyval == nMnemonicKeyval)
        return gtk_widget_mnemonic_activate(pWidget, false);
    return false;
}

bool GtkSalFrame::HandleMenubarMnemonic(guint eState, guint nKeyval)
{
    bool bUsedInMenuBar = false;
    if (eState & GDK_ALT_MASK)
    {
        if (GtkWidget* pMenuBar = m_pSalMenu ? m_pSalMenu->GetMenuBarWidget() : nullptr)
        {
            GList* pChildren = gtk_container_get_children(GTK_CONTAINER(pMenuBar));
            for (GList* pChild = g_list_first(pChildren); pChild; pChild = g_list_next(pChild))
            {
                bUsedInMenuBar = activate_menubar_mnemonic(static_cast<GtkWidget*>(pChild->data), nKeyval);
                if (bUsedInMenuBar)
                    break;
            }
            g_list_free(pChildren);
        }
    }
    return bUsedInMenuBar;
}

gboolean GtkSalFrame::signalKey(GtkWidget* pWidget, GdkEventKey* pEvent, gpointer frame)
{
    UpdateLastInputEventTime(pEvent->time);

    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    bool bFocusInAnotherGtkWidget = false;

    VclPtr<vcl::Window> xTopLevelInterimWindow;

    if (GTK_IS_WINDOW(pThis->m_pWindow))
    {
        GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(pThis->m_pWindow));
        bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(pThis->m_pFixedContainer);
        if (bFocusInAnotherGtkWidget)
        {
            if (!gtk_widget_get_realized(pFocusWindow))
                return true;

            // if the focus is not in our main widget, see if there is a handler
            // for this key stroke in GtkWindow first
            if (key_forward(pEvent, GTK_WINDOW(pThis->m_pWindow)))
                return true;

            // Is focus inside an InterimItemWindow? In which case find that
            // InterimItemWindow and send unconsumed keystrokes to it to
            // support ctrl-q etc shortcuts. Only bother to search for the
            // InterimItemWindow if it is a toplevel that fills its frame, or
            // the keystroke is sufficiently special its worth passing on,
            // e.g. F6 to switch between task-panels or F5 to close a navigator
            if (pThis->IsCycleFocusOutDisallowed() || IsFunctionKeyVal(pEvent->keyval))
            {
                GtkWidget* pSearch = pFocusWindow;
                while (pSearch)
                {
                    void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
                    if (pData)
                    {
                        xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
                        break;
                    }
                    pSearch = gtk_widget_get_parent(pSearch);
                }
            }
        }
    }

    if (pThis->isFloatGrabWindow())
        return signalKey(pWidget, pEvent, pThis->m_pParent);

    vcl::DeletionListener aDel( pThis );

    if (!bFocusInAnotherGtkWidget && pThis->m_pIMHandler && pThis->m_pIMHandler->handleKeyEvent(pEvent))
        return true;

    bool bStopProcessingKey = false;

    // handle modifiers
    if( pEvent->keyval == GDK_KEY_Shift_L || pEvent->keyval == GDK_KEY_Shift_R ||
        pEvent->keyval == GDK_KEY_Control_L || pEvent->keyval == GDK_KEY_Control_R ||
        pEvent->keyval == GDK_KEY_Alt_L || pEvent->keyval == GDK_KEY_Alt_R ||
        pEvent->keyval == GDK_KEY_Meta_L || pEvent->keyval == GDK_KEY_Meta_R ||
        pEvent->keyval == GDK_KEY_Super_L || pEvent->keyval == GDK_KEY_Super_R )
    {
        sal_uInt16 nModCode = GetKeyModCode( pEvent->state );
        ModKeyFlags nExtModMask = ModKeyFlags::NONE;
        sal_uInt16 nModMask = 0;
        // pressing just the ctrl key leads to a keysym of XK_Control but
        // the event state does not contain ControlMask. In the release
        // event it's the other way round: it does contain the Control mask.
        // The modifier mode therefore has to be adapted manually.
        switch( pEvent->keyval )
        {
            case GDK_KEY_Control_L:
                nExtModMask = ModKeyFlags::LeftMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Control_R:
                nExtModMask = ModKeyFlags::RightMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Alt_L:
                nExtModMask = ModKeyFlags::LeftMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Alt_R:
                nExtModMask = ModKeyFlags::RightMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Shift_L:
                nExtModMask = ModKeyFlags::LeftShift;
                nModMask = KEY_SHIFT;
                break;
            case GDK_KEY_Shift_R:
                nExtModMask = ModKeyFlags::RightShift;
                nModMask = KEY_SHIFT;
                break;
            // Map Meta/Super to MOD3 modifier on all Unix systems
            // except macOS
            case GDK_KEY_Meta_L:
            case GDK_KEY_Super_L:
                nExtModMask = ModKeyFlags::LeftMod3;
                nModMask = KEY_MOD3;
                break;
            case GDK_KEY_Meta_R:
            case GDK_KEY_Super_R:
                nExtModMask = ModKeyFlags::RightMod3;
                nModMask = KEY_MOD3;
                break;
        }

        SalKeyModEvent aModEvt;
        aModEvt.mbDown = pEvent->type == GDK_KEY_PRESS;

        if( pEvent->type == GDK_KEY_RELEASE )
        {
            aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
            aModEvt.mnCode = nModCode & ~nModMask;
            pThis->m_nKeyModifiers &= ~nExtModMask;
        }
        else
        {
            aModEvt.mnCode = nModCode | nModMask;
            pThis->m_nKeyModifiers |= nExtModMask;
            aModEvt.mnModKeyCode = pThis->m_nKeyModifiers;
        }

        pThis->CallCallbackExc( SalEvent::KeyModChange, &aModEvt );
    }
    else
    {
        bool bRestoreDisallowCycleFocusOut = false;

        VclPtr<vcl::Window> xOrigFrameFocusWin;
        VclPtr<vcl::Window> xOrigFocusWin;
        if (xTopLevelInterimWindow)
        {
            // Focus is inside an InterimItemWindow so send unconsumed
            // keystrokes to by setting it as the mpFocusWin
            VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
            ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
            xOrigFrameFocusWin = pFrameData->mpFocusWin;
            pFrameData->mpFocusWin = xTopLevelInterimWindow;

            ImplSVData* pSVData = ImplGetSVData();
            xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
            pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;

            if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
            {
                // For F6, allow the focus to leave the InterimItemWindow
                pThis->AllowCycleFocusOut();
                bRestoreDisallowCycleFocusOut = true;
            }
        }

        bStopProcessingKey = pThis->doKeyCallback(pEvent->state,
                                                  pEvent->keyval,
                                                  pEvent->hardware_keycode,
                                                  pEvent->group,
                                                  sal_Unicode(gdk_keyval_to_unicode( pEvent->keyval )),
                                                  (pEvent->type == GDK_KEY_PRESS),
                                                  false);

        // tdf#144846 If this is registered as a menubar mnemonic then ensure
        // that any other widget won't be considered as a candidate by taking
        // over the task of launch the menubar menu outself
        // The code was moved here from its original position at beginning
        // of this function in order to resolve tdf#146174.
        if (!bStopProcessingKey && // module key handler did not process key
            pEvent->type == GDK_KEY_PRESS &&  // module key handler handles only GDK_KEY_PRESS
            GTK_IS_WINDOW(pThis->m_pWindow) &&
            pThis->HandleMenubarMnemonic(pEvent->state, pEvent->keyval))
        {
            return true;
        }

        if (!aDel.isDeleted())
        {
            pThis->m_nKeyModifiers = ModKeyFlags::NONE;

            if (xTopLevelInterimWindow)
            {
                // Focus was inside an InterimItemWindow, restore the original
                // focus win, unless the focus was changed away from the
                // InterimItemWindow which should only be possible with F6
                VclPtr<vcl::Window> xVclWindow = pThis->GetWindow();
                ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
                if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
                    pFrameData->mpFocusWin = xOrigFrameFocusWin;

                ImplSVData* pSVData = ImplGetSVData();
                if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
                    pSVData->mpWinData->mpFocusWin = xOrigFocusWin;

                if (bRestoreDisallowCycleFocusOut)
                {
                    // undo the above AllowCycleFocusOut for F6
                    pThis->DisallowCycleFocusOut();
                }
            }
        }

    }

    if (!bFocusInAnotherGtkWidget && !aDel.isDeleted() && pThis->m_pIMHandler)
        pThis->m_pIMHandler->updateIMSpotLocation();

    return bStopProcessingKey;
}
#else

bool GtkSalFrame::DrawingAreaKey(GtkEventControllerKey* pController, SalEvent nEventType, guint keyval, guint keycode, guint state)
{
    guint32 nTime = gdk_event_get_time(gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(pController)));
    UpdateLastInputEventTime(nTime);

    bool bFocusInAnotherGtkWidget = false;

    VclPtr<vcl::Window> xTopLevelInterimWindow;

    if (GTK_IS_WINDOW(m_pWindow))
    {
        GtkWidget* pFocusWindow = gtk_window_get_focus(GTK_WINDOW(m_pWindow));
        bFocusInAnotherGtkWidget = pFocusWindow && pFocusWindow != GTK_WIDGET(m_pFixedContainer);
        if (bFocusInAnotherGtkWidget)
        {
            if (!gtk_widget_get_realized(pFocusWindow))
                return true;
            // if the focus is not in our main widget, see if there is a handler
            // for this key stroke in GtkWindow first
            bool bHandled = gtk_event_controller_key_forward(pController, m_pWindow);
            if (bHandled)
                return true;

            // Is focus inside an InterimItemWindow? In which case find that
            // InterimItemWindow and send unconsumed keystrokes to it to
            // support ctrl-q etc shortcuts. Only bother to search for the
            // InterimItemWindow if it is a toplevel that fills its frame, or
            // the keystroke is sufficiently special its worth passing on,
            // e.g. F6 to switch between task-panels or F5 to close a navigator
            if (IsCycleFocusOutDisallowed() || IsFunctionKeyVal(keyval))
            {
                GtkWidget* pSearch = pFocusWindow;
                while (pSearch)
                {
                    void* pData = g_object_get_data(G_OBJECT(pSearch), "InterimWindowGlue");
                    if (pData)
                    {
                        xTopLevelInterimWindow = static_cast<vcl::Window*>(pData);
                        break;
                    }
                    pSearch = gtk_widget_get_parent(pSearch);
                }
            }
        }
    }

    vcl::DeletionListener aDel(this);

    bool bStopProcessingKey = false;

    // handle modifiers
    if( keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R ||
        keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R ||
        keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R ||
        keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R ||
        keyval == GDK_KEY_Super_L || keyval == GDK_KEY_Super_R )
    {
        sal_uInt16 nModCode = GetKeyModCode(state);
        ModKeyFlags nExtModMask = ModKeyFlags::NONE;
        sal_uInt16 nModMask = 0;
        // pressing just the ctrl key leads to a keysym of XK_Control but
        // the event state does not contain ControlMask. In the release
        // event it's the other way round: it does contain the Control mask.
        // The modifier mode therefore has to be adapted manually.
        switch (keyval)
        {
            case GDK_KEY_Control_L:
                nExtModMask = ModKeyFlags::LeftMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Control_R:
                nExtModMask = ModKeyFlags::RightMod1;
                nModMask = KEY_MOD1;
                break;
            case GDK_KEY_Alt_L:
                nExtModMask = ModKeyFlags::LeftMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Alt_R:
                nExtModMask = ModKeyFlags::RightMod2;
                nModMask = KEY_MOD2;
                break;
            case GDK_KEY_Shift_L:
                nExtModMask = ModKeyFlags::LeftShift;
                nModMask = KEY_SHIFT;
                break;
            case GDK_KEY_Shift_R:
                nExtModMask = ModKeyFlags::RightShift;
                nModMask = KEY_SHIFT;
                break;
            // Map Meta/Super to MOD3 modifier on all Unix systems
            // except macOS
            case GDK_KEY_Meta_L:
            case GDK_KEY_Super_L:
                nExtModMask = ModKeyFlags::LeftMod3;
                nModMask = KEY_MOD3;
                break;
            case GDK_KEY_Meta_R:
            case GDK_KEY_Super_R:
                nExtModMask = ModKeyFlags::RightMod3;
                nModMask = KEY_MOD3;
                break;
        }

        SalKeyModEvent aModEvt;
        aModEvt.mbDown = nEventType == SalEvent::KeyInput;

        if (!aModEvt.mbDown)
        {
            aModEvt.mnModKeyCode = m_nKeyModifiers;
            aModEvt.mnCode = nModCode & ~nModMask;
            m_nKeyModifiers &= ~nExtModMask;
        }
        else
        {
            aModEvt.mnCode = nModCode | nModMask;
            m_nKeyModifiers |= nExtModMask;
            aModEvt.mnModKeyCode = m_nKeyModifiers;
        }

        CallCallbackExc(SalEvent::KeyModChange, &aModEvt);
    }
    else
    {
        bool bRestoreDisallowCycleFocusOut = false;

        VclPtr<vcl::Window> xOrigFrameFocusWin;
        VclPtr<vcl::Window> xOrigFocusWin;
        if (xTopLevelInterimWindow)
        {
            // Focus is inside an InterimItemWindow so send unconsumed
            // keystrokes to by setting it as the mpFocusWin
            VclPtr<vcl::Window> xVclWindow = GetWindow();
            ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
            xOrigFrameFocusWin = pFrameData->mpFocusWin;
            pFrameData->mpFocusWin = xTopLevelInterimWindow;

            ImplSVData* pSVData = ImplGetSVData();
            xOrigFocusWin = pSVData->mpWinData->mpFocusWin;
            pSVData->mpWinData->mpFocusWin = xTopLevelInterimWindow;

            if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
            {
                // For F6, allow the focus to leave the InterimItemWindow
                AllowCycleFocusOut();
                bRestoreDisallowCycleFocusOut = true;
            }
        }


        bStopProcessingKey = doKeyCallback(state,
                                           keyval,
                                           keycode,
                                           0, // group
                                           sal_Unicode(gdk_keyval_to_unicode(keyval)),
                                           nEventType == SalEvent::KeyInput,
                                           false);

        if (!aDel.isDeleted())
        {
            m_nKeyModifiers = ModKeyFlags::NONE;

            if (xTopLevelInterimWindow)
            {
                // Focus was inside an InterimItemWindow, restore the original
                // focus win, unless the focus was changed away from the
                // InterimItemWindow which should only be possible with F6
                VclPtr<vcl::Window> xVclWindow = GetWindow();
                ImplFrameData* pFrameData = xVclWindow->ImplGetWindowImpl()->mpFrameData;
                if (pFrameData->mpFocusWin == xTopLevelInterimWindow)
                    pFrameData->mpFocusWin = xOrigFrameFocusWin;

                ImplSVData* pSVData = ImplGetSVData();
                if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
                    pSVData->mpWinData->mpFocusWin = xOrigFocusWin;

                if (bRestoreDisallowCycleFocusOut)
                {
                    // undo the above AllowCycleFocusOut for F6
                    DisallowCycleFocusOut();
                }
            }
        }
    }

    if (m_pIMHandler)
        m_pIMHandler->updateIMSpotLocation();

    return bStopProcessingKey;
}

gboolean GtkSalFrame::signalKeyPressed(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    return pThis->DrawingAreaKey(pController, SalEvent::KeyInput, keyval, keycode, state);
}

gboolean GtkSalFrame::signalKeyReleased(GtkEventControllerKey* pController, guint keyval, guint keycode, GdkModifierType state, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    return pThis->DrawingAreaKey(pController, SalEvent::KeyUp, keyval, keycode, state);
}
#endif

bool GtkSalFrame::WindowCloseRequest()
{
    CallCallbackExc(SalEvent::Close, nullptr);
    return true;
}

#if GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalDelete(GtkWidget*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    return pThis->WindowCloseRequest();
}
#else
gboolean GtkSalFrame::signalDelete(GtkWidget*, GdkEvent*, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    return pThis->WindowCloseRequest();
}
#endif

const cairo_font_options_t* GtkSalFrame::get_font_options()
{
    GtkWidget* pWidget = getMouseEventWidget();
#if GTK_CHECK_VERSION(4, 0, 0)
    PangoContext* pContext = gtk_widget_get_pango_context(pWidget);
    assert(pContext);
    return pango_cairo_context_get_font_options(pContext);
#else
    return gdk_screen_get_font_options(gtk_widget_get_screen(pWidget));
#endif
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalStyleUpdated(GtkWidget*, const gchar* /*pSetting*/, gpointer frame)
#else
void GtkSalFrame::signalStyleUpdated(GtkWidget*, gpointer frame)
#endif
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    // note: settings changed for multiple frames is avoided in winproc.cxx ImplHandleSettings
    GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::SettingsChanged );

    // a plausible alternative might be to send SalEvent::FontChanged if pSetting starts with "gtk-xft"

    // fire off font-changed when the system cairo font hints change
    GtkInstance *pInstance = static_cast<GtkInstance*>(GetSalData()->m_pInstance);
    const cairo_font_options_t* pLastCairoFontOptions = pInstance->GetLastSeenCairoFontOptions();
    const cairo_font_options_t* pCurrentCairoFontOptions = pThis->get_font_options();
    bool bFontSettingsChanged = true;
    if (pLastCairoFontOptions && pCurrentCairoFontOptions)
        bFontSettingsChanged = !cairo_font_options_equal(pLastCairoFontOptions, pCurrentCairoFontOptions);
    else if (!pLastCairoFontOptions && !pCurrentCairoFontOptions)
        bFontSettingsChanged = false;
    if (bFontSettingsChanged)
    {
        pInstance->ResetLastSeenCairoFontOptions(pCurrentCairoFontOptions);
        GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::FontChanged );
    }
}

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkSalFrame::signalWindowState( GtkWidget*, GdkEvent* pEvent, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MINIMIZED) )
    {
        GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
        pThis->TriggerPaintEvent();
    }

    if ((pEvent->window_state.new_window_state & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
        !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
    {
        pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
    }

    if ((pEvent->window_state.new_window_state & GDK_WINDOW_STATE_WITHDRAWN) &&
        !(pThis->m_nState & GDK_WINDOW_STATE_WITHDRAWN))
    {
        if (pThis->isFloatGrabWindow())
            pThis->closePopup();
    }

    pThis->m_nState = pEvent->window_state.new_window_state;

    return false;
}
#else
void GtkSalFrame::signalWindowState(GdkToplevel* pSurface, GParamSpec*, gpointer frame)
{
    GdkToplevelState eNewWindowState = gdk_toplevel_get_state(pSurface);

    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if( (pThis->m_nState & GDK_TOPLEVEL_STATE_MINIMIZED) != (eNewWindowState & GDK_TOPLEVEL_STATE_MINIMIZED) )
    {
        GtkSalFrame::getDisplay()->SendInternalEvent( pThis, nullptr, SalEvent::Resize );
        pThis->TriggerPaintEvent();
    }

    if ((eNewWindowState & GDK_TOPLEVEL_STATE_MAXIMIZED) &&
        !(pThis->m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED))
    {
        pThis->m_aRestorePosSize = GetPosAndSize(GTK_WINDOW(pThis->m_pWindow));
    }

    pThis->m_nState = eNewWindowState;
}
#endif

namespace
{
    GdkDragAction VclToGdk(sal_Int8 dragOperation)
    {
        GdkDragAction eRet(static_cast<GdkDragAction>(0));
        if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
            eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_COPY);
        if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
            eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_MOVE);
        if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
            eRet = static_cast<GdkDragAction>(eRet | GDK_ACTION_LINK);
        return eRet;
    }

    sal_Int8 GdkToVcl(GdkDragAction dragOperation)
    {
        sal_Int8 nRet(0);
        if (dragOperation & GDK_ACTION_COPY)
            nRet |= css::datatransfer::dnd::DNDConstants::ACTION_COPY;
        if (dragOperation & GDK_ACTION_MOVE)
            nRet |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
        if (dragOperation & GDK_ACTION_LINK)
            nRet |= css::datatransfer::dnd::DNDConstants::ACTION_LINK;
        return nRet;
    }
}

namespace
{
    GdkDragAction getPreferredDragAction(sal_Int8 dragOperation)
    {
        GdkDragAction eAct(static_cast<GdkDragAction>(0));

        if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_MOVE)
            eAct = GDK_ACTION_MOVE;
        else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_COPY)
            eAct = GDK_ACTION_COPY;
        else if (dragOperation & css::datatransfer::dnd::DNDConstants::ACTION_LINK)
            eAct = GDK_ACTION_LINK;

        return eAct;
    }
}

static bool g_DropSuccessSet = false;
static bool g_DropSuccess = false;

namespace {

#if GTK_CHECK_VERSION(4, 0, 0)

void read_drop_async_completed(GObject* source, GAsyncResult* res, gpointer user_data)
{
    GdkDrop* drop = GDK_DROP(source);
    read_transfer_result* pRes = static_cast<read_transfer_result*>(user_data);

    GInputStream* pResult = gdk_drop_read_finish(drop, res, nullptr, nullptr);

    if (!pResult)
    {
        pRes->bDone = true;
        g_main_context_wakeup(nullptr);
        return;
    }

    pRes->aVector.resize(read_transfer_result::BlockSize);

    g_input_stream_read_async(pResult,
                              pRes->aVector.data(),
                              pRes->aVector.size(),
                              G_PRIORITY_DEFAULT,
                              nullptr,
                              read_transfer_result::read_block_async_completed,
                              user_data);
}
#endif

class GtkDropTargetDropContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDropContext>
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkDragContext *m_pContext;
    guint m_nTime;
#else
    GdkDrop* m_pDrop;
#endif
public:
#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkDropTargetDropContext(GdkDragContext* pContext, guint nTime)
        : m_pContext(pContext)
        , m_nTime(nTime)
#else
    GtkDropTargetDropContext(GdkDrop* pDrop)
        : m_pDrop(pDrop)
#endif
    {
    }

    // XDropTargetDropContext
    virtual void SAL_CALL acceptDrop(sal_Int8 dragOperation) override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
#else
        GdkDragAction eDragAction = getPreferredDragAction(dragOperation);
        gdk_drop_status(m_pDrop,
                        static_cast<GdkDragAction>(eDragAction | gdk_drop_get_actions(m_pDrop)),
                        eDragAction);
#endif
    }

    virtual void SAL_CALL rejectDrop() override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
#else
        gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
#endif
    }

    virtual void SAL_CALL dropComplete(sal_Bool bSuccess) override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        gtk_drag_finish(m_pContext, bSuccess, false, m_nTime);
#else
        // should we do something better here
        gdk_drop_finish(m_pDrop, bSuccess
                        ? gdk_drop_get_actions(m_pDrop)
                        : static_cast<GdkDragAction>(0));
#endif
        if (GtkInstDragSource::g_ActiveDragSource)
        {
            g_DropSuccessSet = true;
            g_DropSuccess = bSuccess;
        }
    }
};

}

class GtkDnDTransferable : public GtkTransferable
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkDragContext *m_pContext;
    guint m_nTime;
    GtkWidget *m_pWidget;
    GtkInstDropTarget* m_pDropTarget;
#else
    GdkDrop* m_pDrop;
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
    GMainLoop *m_pLoop;
    GtkSelectionData *m_pData;
#endif
public:
#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkDnDTransferable(GdkDragContext *pContext, guint nTime, GtkWidget *pWidget, GtkInstDropTarget *pDropTarget)
        : m_pContext(pContext)
        , m_nTime(nTime)
        , m_pWidget(pWidget)
        , m_pDropTarget(pDropTarget)
#else
    GtkDnDTransferable(GdkDrop *pDrop)
        : m_pDrop(pDrop)
#endif
#if !GTK_CHECK_VERSION(4, 0, 0)
        , m_pLoop(nullptr)
        , m_pData(nullptr)
#endif
    {
    }

    virtual css::uno::Any SAL_CALL getTransferData(const css::datatransfer::DataFlavor& rFlavor) override
    {
        css::datatransfer::DataFlavor aFlavor(rFlavor);
        if (aFlavor.MimeType == "text/plain;charset=utf-16")
            aFlavor.MimeType = "text/plain;charset=utf-8";

        auto it = m_aMimeTypeToGtkType.find(aFlavor.MimeType);
        if (it == m_aMimeTypeToGtkType.end())
            return css::uno::Any();

        css::uno::Any aRet;

#if !GTK_CHECK_VERSION(4, 0, 0)
        /* like gtk_clipboard_wait_for_contents run a sub loop
         * waiting for drag-data-received triggered from
         * gtk_drag_get_data
         */
        {
            m_pLoop = g_main_loop_new(nullptr, true);
            m_pDropTarget->SetFormatConversionRequest(this);

            gtk_drag_get_data(m_pWidget, m_pContext, it->second, m_nTime);

            if (g_main_loop_is_running(m_pLoop))
                main_loop_run(m_pLoop);

            g_main_loop_unref(m_pLoop);
            m_pLoop = nullptr;
            m_pDropTarget->SetFormatConversionRequest(nullptr);
        }

        if (aFlavor.MimeType == "text/plain;charset=utf-8")
        {
            OUString aStr;
            gchar *pText = reinterpret_cast<gchar*>(gtk_selection_data_get_text(m_pData));
            if (pText)
                aStr = OUString(pText, rtl_str_getLength(pText), RTL_TEXTENCODING_UTF8);
            g_free(pText);
            aRet <<= aStr.replaceAll("\r\n", "\n");
        }
        else
        {
            gint length(0);
            const guchar *rawdata = gtk_selection_data_get_data_with_length(m_pData,
                                                                            &length);
            // seen here was rawhide == nullptr and length set to -1
            if (rawdata)
            {
                css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(rawdata), length);
                aRet <<= aSeq;
            }
        }

        gtk_selection_data_free(m_pData);
#else
        SalInstance* pInstance = GetSalData()->m_pInstance;
        read_transfer_result aRes;
        const char *mime_types[] = { it->second.getStr(), nullptr };

        gdk_drop_read_async(m_pDrop,
                            mime_types,
                            G_PRIORITY_DEFAULT,
                            nullptr,
                            read_drop_async_completed,
                            &aRes);

        while (!aRes.bDone)
            pInstance->DoYield(true, false);

        if (aFlavor.MimeType == "text/plain;charset=utf-8")
            aRet <<= aRes.get_as_string();
        else
            aRet <<= aRes.get_as_sequence();
#endif
        return aRet;
    }

    virtual std::vector<css::datatransfer::DataFlavor> getTransferDataFlavorsAsVector() override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        std::vector<GdkAtom> targets;
        for (GList* l = gdk_drag_context_list_targets(m_pContext); l; l = l->next)
            targets.push_back(static_cast<GdkAtom>(l->data));
        return GtkTransferable::getTransferDataFlavorsAsVector(targets.data(), targets.size());
#else
        GdkContentFormats* pFormats = gdk_drop_get_formats(m_pDrop);
        gsize n_targets;
        const char * const *targets = gdk_content_formats_get_mime_types(pFormats, &n_targets);
        return GtkTransferable::getTransferDataFlavorsAsVector(targets, n_targets);
#endif
    }

#if !GTK_CHECK_VERSION(4, 0, 0)
    void LoopEnd(GtkSelectionData *pData)
    {
        m_pData = pData;
        g_main_loop_quit(m_pLoop);
    }
#endif
};

// For LibreOffice internal D&D we provide the Transferable without Gtk
// intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
GtkInstDragSource* GtkInstDragSource::g_ActiveDragSource;

#if GTK_CHECK_VERSION(4, 0, 0)

gboolean GtkSalFrame::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return false;
    return pThis->m_pDropTarget->signalDragDrop(context, drop, x, y);
}
#else
gboolean GtkSalFrame::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return false;
    return pThis->m_pDropTarget->signalDragDrop(pWidget, context, x, y, time);
}
#endif

#if !GTK_CHECK_VERSION(4, 0, 0)
gboolean GtkInstDropTarget::signalDragDrop(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, guint time)
#else
gboolean GtkInstDropTarget::signalDragDrop(GtkDropTargetAsync* context, GdkDrop* drop, double x, double y)
#endif
{
    // remove the deferred dragExit, as we'll do a drop
#ifndef NDEBUG
    bool res =
#endif
    g_idle_remove_by_data(this);
#ifndef NDEBUG
#if !GTK_CHECK_VERSION(4, 0, 0)
    assert(res);
#else
    (void)res;
#endif
#endif

    css::datatransfer::dnd::DropTargetDropEvent aEvent;
    aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
#if !GTK_CHECK_VERSION(4, 0, 0)
    aEvent.Context = new GtkDropTargetDropContext(context, time);
#else
    aEvent.Context = new GtkDropTargetDropContext(drop);
#endif
    aEvent.LocationX = x;
    aEvent.LocationY = y;
#if !GTK_CHECK_VERSION(4, 0, 0)
    aEvent.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
#else
    aEvent.DropAction = GdkToVcl(getPreferredDragAction(GdkToVcl(gdk_drop_get_actions(drop))));
#endif
    // ACTION_DEFAULT is documented as...
    // 'This means the user did not press any key during the Drag and Drop operation
    // and the action that was combined with ACTION_DEFAULT is the system default action'
    // in tdf#107031 writer won't insert a link when a heading is dragged from the
    // navigator unless this is set. Its unclear really what ACTION_DEFAULT means,
    // there is a deprecated 'GDK_ACTION_DEFAULT Means nothing, and should not be used'
    // possible equivalent in gtk.
    // So (tdf#109227) set ACTION_DEFAULT if no modifier key is held down
#if !GTK_CHECK_VERSION(4,0,0)
    aEvent.SourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
    GdkModifierType mask;
    gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
#else
    aEvent.SourceActions = GdkToVcl(gdk_drop_get_actions(drop));
    GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
#endif
    if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
        aEvent.DropAction |= css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT;

    css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
    // For LibreOffice internal D&D we provide the Transferable without Gtk
    // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
    if (GtkInstDragSource::g_ActiveDragSource)
        xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable();
    else
    {
#if GTK_CHECK_VERSION(4,0,0)
        xTransferable = new GtkDnDTransferable(drop);
#else
        xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
#endif
    }
    aEvent.Transferable = xTransferable;

    fire_drop(aEvent);

    return true;
}

namespace {

class GtkDropTargetDragContext : public cppu::WeakImplHelper<css::datatransfer::dnd::XDropTargetDragContext>
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkDragContext *m_pContext;
    guint m_nTime;
#else
    GdkDrop* m_pDrop;
#endif
public:
#if !GTK_CHECK_VERSION(4, 0, 0)
    GtkDropTargetDragContext(GdkDragContext *pContext, guint nTime)
        : m_pContext(pContext)
        , m_nTime(nTime)
#else
    GtkDropTargetDragContext(GdkDrop* pDrop)
        : m_pDrop(pDrop)
#endif
    {
    }

    virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_drag_status(m_pContext, getPreferredDragAction(dragOperation), m_nTime);
#else
        gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), getPreferredDragAction(dragOperation));
#endif
    }

    virtual void SAL_CALL rejectDrag() override
    {
#if !GTK_CHECK_VERSION(4, 0, 0)
        gdk_drag_status(m_pContext, static_cast<GdkDragAction>(0), m_nTime);
#else
        gdk_drop_status(m_pDrop, gdk_drop_get_actions(m_pDrop), static_cast<GdkDragAction>(0));
#endif
    }
};

}

#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalDragDropReceived(GtkWidget* pWidget, GdkDragContext* context, gint x, gint y, GtkSelectionData* data, guint ttype, guint time, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return;
    pThis->m_pDropTarget->signalDragDropReceived(pWidget, context, x, y, data, ttype, time);
}

void GtkInstDropTarget::signalDragDropReceived(GtkWidget* /*pWidget*/, GdkDragContext * /*context*/, gint /*x*/, gint /*y*/, GtkSelectionData* data, guint /*ttype*/, guint /*time*/)
{
    /*
     * If we get a drop, then we will call like gtk_clipboard_wait_for_contents
     * with a loop inside a loop to get the right format, so if this is the
     * case return to the outer loop here with a copy of the desired data
     *
     * don't look at me like that.
     */
    if (!m_pFormatConversionRequest)
        return;

    m_pFormatConversionRequest->LoopEnd(gtk_selection_data_copy(data));
}
#endif

#if GTK_CHECK_VERSION(4,0,0)
GdkDragAction GtkSalFrame::signalDragMotion(GtkDropTargetAsync *dest, GdkDrop *drop, double x, double y, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return GdkDragAction(0);
    return pThis->m_pDropTarget->signalDragMotion(dest, drop, x, y);
}
#else
gboolean GtkSalFrame::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return false;
    return pThis->m_pDropTarget->signalDragMotion(pWidget, context, x, y, time);
}
#endif

#if !GTK_CHECK_VERSION(4,0,0)
gboolean GtkInstDropTarget::signalDragMotion(GtkWidget *pWidget, GdkDragContext *context, gint x, gint y, guint time)
#else
GdkDragAction GtkInstDropTarget::signalDragMotion(GtkDropTargetAsync *context, GdkDrop *pDrop, double x, double y)
#endif
{
    if (!m_bInDrag)
    {
#if !GTK_CHECK_VERSION(4,0,0)
        GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
        gtk_drag_highlight(pHighlightWidget);
#else
        GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) :
                gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(context));
        gtk_widget_set_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE, false);
#endif
    }

    css::datatransfer::dnd::DropTargetDragEnterEvent aEvent;
    aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(this);
#if !GTK_CHECK_VERSION(4,0,0)
    rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(context, time);
#else
    rtl::Reference<GtkDropTargetDragContext> pContext = new GtkDropTargetDragContext(pDrop);
#endif
    //preliminary accept the Drag and select the preferred action, the fire_* will
    //inform the original caller of our choice and the callsite can decide
    //to overrule this choice. i.e. typically here we default to ACTION_MOVE
#if !GTK_CHECK_VERSION(4,0,0)
    sal_Int8 nSourceActions = GdkToVcl(gdk_drag_context_get_actions(context));
    GdkModifierType mask;
    gdk_window_get_pointer(widget_get_surface(pWidget), nullptr, nullptr, &mask);
#else
    sal_Int8 nSourceActions = GdkToVcl(gdk_drop_get_actions(pDrop));
    GdkModifierType mask = gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(context));
#endif

    // tdf#124411 default to move if drag originates within LO itself, default
    // to copy if it comes from outside, this is similar to srcAndDestEqual
    // in macosx DropTarget::determineDropAction equivalent
    sal_Int8 nNewDropAction = GtkInstDragSource::g_ActiveDragSource ?
                                css::datatransfer::dnd::DNDConstants::ACTION_MOVE :
                                css::datatransfer::dnd::DNDConstants::ACTION_COPY;

    // tdf#109227 if a modifier is held down, default to the matching
    // action for that modifier combo, otherwise pick the preferred
    // default from the possible source actions
    if ((mask & GDK_SHIFT_MASK) && !(mask & GDK_CONTROL_MASK))
        nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_MOVE;
    else if ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
        nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY;
    else if ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
        nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
    nNewDropAction &= nSourceActions;

    GdkDragAction eAction;
    if (!(mask & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) && !nNewDropAction)
        eAction = getPreferredDragAction(nSourceActions);
    else
        eAction = getPreferredDragAction(nNewDropAction);

#if !GTK_CHECK_VERSION(4,0,0)
    gdk_drag_status(context, eAction, time);
#else
    gdk_drop_status(pDrop,
                    static_cast<GdkDragAction>(eAction | gdk_drop_get_actions(pDrop)),
                    eAction);
#endif
    aEvent.Context = pContext;
    aEvent.LocationX = x;
    aEvent.LocationY = y;
    //under wayland at least, the action selected by gdk_drag_status on the
    //context is not immediately available via gdk_drag_context_get_selected_action
    //so here we set the DropAction from what we selected on the context, not
    //what the context says is selected
    aEvent.DropAction = GdkToVcl(eAction);
    aEvent.SourceActions = nSourceActions;

    if (!m_bInDrag)
    {
        css::uno::Reference<css::datatransfer::XTransferable> xTransferable;
        // For LibreOffice internal D&D we provide the Transferable without Gtk
        // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
        if (GtkInstDragSource::g_ActiveDragSource)
            xTransferable = GtkInstDragSource::g_ActiveDragSource->GetTransferrable();
        else
        {
#if !GTK_CHECK_VERSION(4,0,0)
            xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
#else
            xTransferable = new GtkDnDTransferable(pDrop);
#endif
        }
        css::uno::Sequence<css::datatransfer::DataFlavor> aFormats = xTransferable->getTransferDataFlavors();
        aEvent.SupportedDataFlavors = aFormats;
        fire_dragEnter(aEvent);
        m_bInDrag = true;
    }
    else
    {
        fire_dragOver(aEvent);
    }

#if !GTK_CHECK_VERSION(4,0,0)
    return true;
#else
    return eAction;
#endif
}

#if GTK_CHECK_VERSION(4,0,0)
void GtkSalFrame::signalDragLeave(GtkDropTargetAsync* pDest, GdkDrop* /*drop*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return;
    pThis->m_pDropTarget->signalDragLeave(gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(pDest)));
}
#else
void GtkSalFrame::signalDragLeave(GtkWidget* pWidget, GdkDragContext* /*context*/, guint /*time*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDropTarget)
        return;
    pThis->m_pDropTarget->signalDragLeave(pWidget);
}
#endif

static gboolean lcl_deferred_dragExit(gpointer user_data)
{
    GtkInstDropTarget* pThis = static_cast<GtkInstDropTarget*>(user_data);
    css::datatransfer::dnd::DropTargetEvent aEvent;
    aEvent.Source = static_cast<css::datatransfer::dnd::XDropTarget*>(pThis);
    pThis->fire_dragExit(aEvent);
    return false;
}

void GtkInstDropTarget::signalDragLeave(GtkWidget *pWidget)
{
    m_bInDrag = false;

    GtkWidget* pHighlightWidget = m_pFrame ? GTK_WIDGET(m_pFrame->getFixedContainer()) : pWidget;
#if !GTK_CHECK_VERSION(4,0,0)
    gtk_drag_unhighlight(pHighlightWidget);
#else
    gtk_widget_unset_state_flags(pHighlightWidget, GTK_STATE_FLAG_DROP_ACTIVE);
#endif

    // defer fire_dragExit, since gtk also sends a drag-leave before the drop, while
    // LO expect to either handle the drop or the exit... at least in Writer.
    // but since we don't know there will be a drop following the leave, defer the
    // exit handling to an idle.
    g_idle_add(lcl_deferred_dragExit, this);
}

void GtkSalFrame::signalDestroy( GtkWidget* pObj, gpointer frame )
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if( pObj != pThis->m_pWindow )
        return;

    pThis->m_aDamageHandler.damaged = nullptr;
    pThis->m_aDamageHandler.handle = nullptr;
    if (pThis->m_pSurface)
        cairo_surface_set_user_data(pThis->m_pSurface, SvpSalGraphics::getDamageKey(), nullptr, nullptr);
    pThis->m_pFixedContainer = nullptr;
    pThis->m_pDrawingArea = nullptr;
#if !GTK_CHECK_VERSION(4, 0, 0)
    pThis->m_pEventBox = nullptr;
#endif
    pThis->m_pTopLevelGrid = nullptr;
    pThis->m_pWindow = nullptr;
    pThis->m_xFrameWeld.reset();
    pThis->InvalidateGraphics();
}

// GtkSalFrame::IMHandler

GtkSalFrame::IMHandler::IMHandler( GtkSalFrame* pFrame )
: m_pFrame(pFrame),
  m_nPrevKeyPresses( 0 ),
  m_pIMContext( nullptr ),
  m_bFocused( true ),
  m_bPreeditJustChanged( false )
{
    m_aInputEvent.mpTextAttr = nullptr;
    createIMContext();
}

GtkSalFrame::IMHandler::~IMHandler()
{
    // cancel an eventual event posted to begin preedit again
    GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
    deleteIMContext();
}

void GtkSalFrame::IMHandler::createIMContext()
{
    if(  m_pIMContext )
        return;

    m_pIMContext = gtk_im_multicontext_new ();
    g_signal_connect( m_pIMContext, "commit",
                      G_CALLBACK (signalIMCommit), this );
    g_signal_connect( m_pIMContext, "preedit_changed",
                      G_CALLBACK (signalIMPreeditChanged), this );
    g_signal_connect( m_pIMContext, "retrieve_surrounding",
                      G_CALLBACK (signalIMRetrieveSurrounding), this );
    g_signal_connect( m_pIMContext, "delete_surrounding",
                      G_CALLBACK (signalIMDeleteSurrounding), this );
    g_signal_connect( m_pIMContext, "preedit_start",
                      G_CALLBACK (signalIMPreeditStart), this );
    g_signal_connect( m_pIMContext, "preedit_end",
                      G_CALLBACK (signalIMPreeditEnd), this );

    GetGenericUnixSalData()->ErrorTrapPush();
    im_context_set_client_widget(m_pIMContext, m_pFrame->getMouseEventWidget());
#if GTK_CHECK_VERSION(4, 0, 0)
    gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, m_pIMContext);
#endif
    gtk_im_context_focus_in( m_pIMContext );
    GetGenericUnixSalData()->ErrorTrapPop();
    m_bFocused = true;

}

void GtkSalFrame::IMHandler::deleteIMContext()
{
    if( !m_pIMContext )
        return;

    // first give IC a chance to deinitialize
    GetGenericUnixSalData()->ErrorTrapPush();
#if GTK_CHECK_VERSION(4, 0, 0)
    gtk_event_controller_key_set_im_context(m_pFrame->m_pKeyController, nullptr);
#endif
    im_context_set_client_widget(m_pIMContext, nullptr);
    GetGenericUnixSalData()->ErrorTrapPop();
    // destroy old IC
    g_object_unref( m_pIMContext );
    m_pIMContext = nullptr;
}

void GtkSalFrame::IMHandler::doCallEndExtTextInput()
{
    m_aInputEvent.mpTextAttr = nullptr;
    m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
}

void GtkSalFrame::IMHandler::updateIMSpotLocation()
{
    SalExtTextInputPosEvent aPosEvent;
    m_pFrame->CallCallbackExc( SalEvent::ExtTextInputPos, static_cast<void*>(&aPosEvent) );
    GdkRectangle aArea;
    aArea.x = aPosEvent.mnX;
    aArea.y = aPosEvent.mnY;
    aArea.width = aPosEvent.mnWidth;
    aArea.height = aPosEvent.mnHeight;
    GetGenericUnixSalData()->ErrorTrapPush();
    gtk_im_context_set_cursor_location( m_pIMContext, &aArea );
    GetGenericUnixSalData()->ErrorTrapPop();
}

void GtkSalFrame::IMHandler::sendEmptyCommit()
{
    vcl::DeletionListener aDel( m_pFrame );

    SalExtTextInputEvent aEmptyEv;
    aEmptyEv.mpTextAttr         = nullptr;
    aEmptyEv.maText.clear();
    aEmptyEv.mnCursorPos        = 0;
    aEmptyEv.mnCursorFlags      = 0;
    m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&aEmptyEv) );
    if( ! aDel.isDeleted() )
        m_pFrame->CallCallbackExc( SalEvent::EndExtTextInput, nullptr );
}

void GtkSalFrame::IMHandler::endExtTextInput( EndExtTextInputFlags /*nFlags*/ )
{
    gtk_im_context_reset ( m_pIMContext );

    if( !m_aInputEvent.mpTextAttr )
        return;

    vcl::DeletionListener aDel( m_pFrame );
    // delete preedit in sal (commit an empty string)
    sendEmptyCommit();
    if( ! aDel.isDeleted() )
    {
        // mark previous preedit state again (will e.g. be sent at focus gain)
        m_aInputEvent.mpTextAttr = m_aInputFlags.data();
        if( m_bFocused )
        {
            // begin preedit again
            GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
        }
    }
}

void GtkSalFrame::IMHandler::focusChanged( bool bFocusIn )
{
    m_bFocused = bFocusIn;
    if( bFocusIn )
    {
        GetGenericUnixSalData()->ErrorTrapPush();
        gtk_im_context_focus_in( m_pIMContext );
        GetGenericUnixSalData()->ErrorTrapPop();
        if( m_aInputEvent.mpTextAttr )
        {
            sendEmptyCommit();
            // begin preedit again
            GtkSalFrame::getDisplay()->SendInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
        }
    }
    else
    {
        GetGenericUnixSalData()->ErrorTrapPush();
        gtk_im_context_focus_out( m_pIMContext );
        GetGenericUnixSalData()->ErrorTrapPop();
        // cancel an eventual event posted to begin preedit again
        GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
    }
}

#if !GTK_CHECK_VERSION(4, 0, 0)
bool GtkSalFrame::IMHandler::handleKeyEvent( GdkEventKey* pEvent )
{
    vcl::DeletionListener aDel( m_pFrame );

    if( pEvent->type == GDK_KEY_PRESS )
    {
        // Add this key press event to the list of previous key presses
        // to which we compare key release events.  If a later key release
        // event has a matching key press event in this list, we swallow
        // the key release because some GTK Input Methods don't swallow it
        // for us.
        m_aPrevKeyPresses.emplace_back(pEvent );
        m_nPrevKeyPresses++;

        // Also pop off the earliest key press event if there are more than 10
        // already.
        while (m_nPrevKeyPresses > 10)
        {
            m_aPrevKeyPresses.pop_front();
            m_nPrevKeyPresses--;
        }

        GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );

        // #i51353# update spot location on every key input since we cannot
        // know which key may activate a preedit choice window
        updateIMSpotLocation();
        if( aDel.isDeleted() )
            return true;

        bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
        g_object_unref( pRef );

        if( aDel.isDeleted() )
            return true;

        m_bPreeditJustChanged = false;

        if( bResult )
            return true;
        else
        {
            SAL_WARN_IF( m_nPrevKeyPresses <= 0, "vcl.gtk3", "key press has vanished !" );
            if( ! m_aPrevKeyPresses.empty() ) // sanity check
            {
                // event was not swallowed, do not filter a following
                // key release event
                // note: this relies on gtk_im_context_filter_keypress
                // returning without calling a handler (in the "not swallowed"
                // case ) which might change the previous key press list so
                // we would pop the wrong event here
                m_aPrevKeyPresses.pop_back();
                m_nPrevKeyPresses--;
            }
        }
    }

    // Determine if we got an earlier key press event corresponding to this key release
    if (pEvent->type == GDK_KEY_RELEASE)
    {
        GObject* pRef = G_OBJECT( g_object_ref( G_OBJECT( m_pIMContext ) ) );
        bool bResult = gtk_im_context_filter_keypress( m_pIMContext, pEvent );
        g_object_unref( pRef );

        if( aDel.isDeleted() )
            return true;

        m_bPreeditJustChanged = false;

        auto iter = std::find(m_aPrevKeyPresses.begin(), m_aPrevKeyPresses.end(), pEvent);
        // If we found a corresponding previous key press event, swallow the release
        // and remove the earlier key press from our list
        if (iter != m_aPrevKeyPresses.end())
        {
            m_aPrevKeyPresses.erase(iter);
            m_nPrevKeyPresses--;
            return true;
        }

        if( bResult )
            return true;
    }

    return false;
}

/* FIXME:
* #122282# still more hacking: some IMEs never start a preedit but simply commit
* in this case we cannot commit a single character. Workaround: do not do the
* single key hack for enter or space if the unicode committed does not match
*/

static bool checkSingleKeyCommitHack( guint keyval, sal_Unicode cCode )
{
    bool bRet = true;
    switch( keyval )
    {
        case GDK_KEY_KP_Enter:
        case GDK_KEY_Return:
            if( cCode != '\n' && cCode != '\r' )
                bRet = false;
            break;
        case GDK_KEY_space:
        case GDK_KEY_KP_Space:
            if( cCode != ' ' )
                bRet = false;
            break;
        default:
            break;
    }
    return bRet;
}

void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );
    {
        const bool bWasPreedit =
            (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
            pThis->m_bPreeditJustChanged;

        pThis->m_aInputEvent.mpTextAttr         = nullptr;
        pThis->m_aInputEvent.maText             = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
        pThis->m_aInputEvent.mnCursorPos        = pThis->m_aInputEvent.maText.getLength();
        pThis->m_aInputEvent.mnCursorFlags      = 0;

        pThis->m_aInputFlags.clear();

        /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
         *  which is logical and consequent. But since even simple input like
         *  <space> comes through the commit signal instead of signalKey
         *  and all kinds of windows only implement KeyInput (e.g. PushButtons,
         *  RadioButtons and a lot of other Controls), will send a single
         *  KeyInput/KeyUp sequence instead of an ExtText event if there
         *  never was a preedit and the text is only one character.
         *
         *  In this case there the last ExtText event must have been
         *  SalEvent::EndExtTextInput, either because of a regular commit
         *  or because there never was a preedit.
         */
        bool bSingleCommit = false;
        if( ! bWasPreedit
            && pThis->m_aInputEvent.maText.getLength() == 1
            && ! pThis->m_aPrevKeyPresses.empty()
            )
        {
            const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
            sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];

            if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
            {
                pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
                bSingleCommit = true;
            }
        }
        if( ! bSingleCommit )
        {
            pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
            if( ! aDel.isDeleted() )
                pThis->doCallEndExtTextInput();
        }
        if( ! aDel.isDeleted() )
        {
            // reset input event
            pThis->m_aInputEvent.maText.clear();
            pThis->m_aInputEvent.mnCursorPos = 0;
            pThis->updateIMSpotLocation();
        }
    }
}
#else
void GtkSalFrame::IMHandler::signalIMCommit( GtkIMContext* /*pContext*/, gchar* pText, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );
    {
#if 0
        const bool bWasPreedit =
            (pThis->m_aInputEvent.mpTextAttr != nullptr) ||
            pThis->m_bPreeditJustChanged;
#endif

        pThis->m_aInputEvent.mpTextAttr         = nullptr;
        pThis->m_aInputEvent.maText             = OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 );
        pThis->m_aInputEvent.mnCursorPos        = pThis->m_aInputEvent.maText.getLength();
        pThis->m_aInputEvent.mnCursorFlags      = 0;

        pThis->m_aInputFlags.clear();

        /* necessary HACK: all keyboard input comes in here as soon as an IMContext is set
         *  which is logical and consequent. But since even simple input like
         *  <space> comes through the commit signal instead of signalKey
         *  and all kinds of windows only implement KeyInput (e.g. PushButtons,
         *  RadioButtons and a lot of other Controls), will send a single
         *  KeyInput/KeyUp sequence instead of an ExtText event if there
         *  never was a preedit and the text is only one character.
         *
         *  In this case there the last ExtText event must have been
         *  SalEvent::EndExtTextInput, either because of a regular commit
         *  or because there never was a preedit.
         */
        bool bSingleCommit = false;
#if 0
        // TODO this needs a rethink to work again if necessary
        if( ! bWasPreedit
            && pThis->m_aInputEvent.maText.getLength() == 1
            && ! pThis->m_aPrevKeyPresses.empty()
            )
        {
            const PreviousKeyPress& rKP = pThis->m_aPrevKeyPresses.back();
            sal_Unicode aOrigCode = pThis->m_aInputEvent.maText[0];

            if( checkSingleKeyCommitHack( rKP.keyval, aOrigCode ) )
            {
                pThis->m_pFrame->doKeyCallback( rKP.state, rKP.keyval, rKP.hardware_keycode, rKP.group, aOrigCode, true, true );
                bSingleCommit = true;
            }
        }
#endif
        if( ! bSingleCommit )
        {
            pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
            if( ! aDel.isDeleted() )
                pThis->doCallEndExtTextInput();
        }
        if( ! aDel.isDeleted() )
        {
            // reset input event
            pThis->m_aInputEvent.maText.clear();
            pThis->m_aInputEvent.mnCursorPos = 0;
            pThis->updateIMSpotLocation();
        }
    }
}
#endif

OUString GtkSalFrame::GetPreeditDetails(GtkIMContext* pIMContext, std::vector<ExtTextInputAttr>& rInputFlags, sal_Int32& rCursorPos, sal_uInt8& rCursorFlags)
{
    char*           pText           = nullptr;
    PangoAttrList*  pAttrs          = nullptr;
    gint            nCursorPos      = 0;

    gtk_im_context_get_preedit_string( pIMContext,
                                       &pText,
                                       &pAttrs,
                                       &nCursorPos );

    gint nUtf8Len = pText ? strlen(pText) : 0;
    OUString sText = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();

    std::vector<sal_Int32> aUtf16Offsets;
    for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < sText.getLength(); sText.iterateCodePoints(&nUtf16Offset))
        aUtf16Offsets.push_back(nUtf16Offset);

    sal_Int32 nUtf32Len = aUtf16Offsets.size();
        // from the above loop filling aUtf16Offsets, we know that its size() fits into sal_Int32
    aUtf16Offsets.push_back(sText.getLength());

    // sanitize the CurPos which is in utf-32
    if (nCursorPos < 0)
        nCursorPos = 0;
    else if (nCursorPos > nUtf32Len)
        nCursorPos = nUtf32Len;

    rCursorPos = aUtf16Offsets[nCursorPos];
    rCursorFlags = 0;

    rInputFlags.resize(std::max(1, static_cast<int>(sText.getLength())), ExtTextInputAttr::NONE);

    PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
    do
    {
        GSList *attr_list = nullptr;
        GSList *tmp_list = nullptr;
        gint nUtf8Start, nUtf8End;
        ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;

        // docs say... "Get the range of the current segment ... the stored
        // return values are signed, not unsigned like the values in
        // PangoAttribute", which implies that the units are otherwise the same
        // as that of PangoAttribute whose docs state these units are "in
        // bytes"
        // so this is the utf8 range
        pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);

        // sanitize the utf8 range
        nUtf8Start = std::min(nUtf8Start, nUtf8Len);
        nUtf8End = std::min(nUtf8End, nUtf8Len);
        if (nUtf8Start >= nUtf8End)
            continue;

        // get the utf32 range
        sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
        sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);

        // sanitize the utf32 range
        nUtf32Start = std::min(nUtf32Start, nUtf32Len);
        nUtf32End = std::min(nUtf32End, nUtf32Len);
        if (nUtf32Start >= nUtf32End)
            continue;

        tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
        while (tmp_list)
        {
            PangoAttribute *pango_attr = static_cast<PangoAttribute *>(tmp_list->data);

            switch (pango_attr->klass->type)
            {
                case PANGO_ATTR_BACKGROUND:
                    sal_attr |= ExtTextInputAttr::Highlight;
                    rCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
                    break;
                case PANGO_ATTR_UNDERLINE:
                    sal_attr |= ExtTextInputAttr::Underline;
                    break;
                case PANGO_ATTR_STRIKETHROUGH:
                    sal_attr |= ExtTextInputAttr::RedText;
                    break;
                default:
                    break;
            }
            pango_attribute_destroy (pango_attr);
            tmp_list = tmp_list->next;
        }
        if (sal_attr == ExtTextInputAttr::NONE)
            sal_attr |= ExtTextInputAttr::Underline;
        g_slist_free (attr_list);

        // Set the sal attributes on our text
        // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
        for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
        {
            SAL_WARN_IF(i >= static_cast<int>(rInputFlags.size()),
                "vcl.gtk3", "pango attrib out of range. Broken range: "
                << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
                << rInputFlags.size());
            if (i >= static_cast<int>(rInputFlags.size()))
                continue;
            rInputFlags[i] |= sal_attr;
        }
    } while (pango_attr_iterator_next (iter));
    pango_attr_iterator_destroy(iter);

    g_free( pText );
    pango_attr_list_unref( pAttrs );

    return sText;
}

void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext* pIMContext, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    sal_Int32 nCursorPos(0);
    sal_uInt8 nCursorFlags(0);
    std::vector<ExtTextInputAttr> aInputFlags;
    OUString sText = GtkSalFrame::GetPreeditDetails(pIMContext, aInputFlags, nCursorPos, nCursorFlags);
    if (sText.isEmpty() && pThis->m_aInputEvent.maText.isEmpty())
    {
        // change from nothing to nothing -> do not start preedit
        // e.g. this will activate input into a calc cell without
        // user input
        return;
    }

    pThis->m_bPreeditJustChanged = true;

    bool bEndPreedit = sText.isEmpty() && pThis->m_aInputEvent.mpTextAttr != nullptr;
    pThis->m_aInputEvent.maText = sText;
    pThis->m_aInputEvent.mnCursorPos = nCursorPos;
    pThis->m_aInputEvent.mnCursorFlags = nCursorFlags;
    pThis->m_aInputFlags = aInputFlags;
    pThis->m_aInputEvent.mpTextAttr = pThis->m_aInputFlags.data();

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );

    pThis->m_pFrame->CallCallbackExc( SalEvent::ExtTextInput, static_cast<void*>(&pThis->m_aInputEvent));
    if( bEndPreedit && ! aDel.isDeleted() )
        pThis->doCallEndExtTextInput();
    if( ! aDel.isDeleted() )
        pThis->updateIMSpotLocation();
}

void GtkSalFrame::IMHandler::signalIMPreeditStart( GtkIMContext*, gpointer /*im_handler*/ )
{
}

void GtkSalFrame::IMHandler::signalIMPreeditEnd( GtkIMContext*, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    pThis->m_bPreeditJustChanged = true;

    SolarMutexGuard aGuard;
    vcl::DeletionListener aDel( pThis->m_pFrame );
    pThis->doCallEndExtTextInput();
    if( ! aDel.isDeleted() )
        pThis->updateIMSpotLocation();
}

gboolean GtkSalFrame::IMHandler::signalIMRetrieveSurrounding( GtkIMContext* pContext, gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    SalSurroundingTextRequestEvent aEvt;
    aEvt.maText.clear();
    aEvt.mnStart = aEvt.mnEnd = 0;

    SolarMutexGuard aGuard;
    pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aEvt);

    OString sUTF = OUStringToOString(aEvt.maText, RTL_TEXTENCODING_UTF8);
    OUString sCursorText(aEvt.maText.copy(0, aEvt.mnStart));
    gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
        OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
    return true;
}

Selection GtkSalFrame::CalcDeleteSurroundingSelection(const OUString& rSurroundingText, sal_Int32 nCursorIndex, int nOffset, int nChars)
{
    Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);

    if (nCursorIndex == -1)
        return aInvalid;

    // Note that offset and n_chars are in characters not in bytes
    // which differs from the usage other places in GtkIMContext

    if (nOffset > 0)
    {
        while (nOffset && nCursorIndex < rSurroundingText.getLength())
        {
            rSurroundingText.iterateCodePoints(&nCursorIndex, 1);
            --nOffset;
        }
    }
    else if (nOffset < 0)
    {
        while (nOffset && nCursorIndex > 0)
        {
            rSurroundingText.iterateCodePoints(&nCursorIndex, -1);
            ++nOffset;
        }
    }

    if (nOffset)
    {
        SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to move to offset: " << nOffset);
        return aInvalid;
    }

    sal_Int32 nCursorEndIndex(nCursorIndex);
    sal_Int32 nCount(0);
    while (nCount < nChars && nCursorEndIndex < rSurroundingText.getLength())
    {
        rSurroundingText.iterateCodePoints(&nCursorEndIndex, 1);
        ++nCount;
    }

    if (nCount != nChars)
    {
        SAL_WARN("vcl.gtk", "IM delete-surrounding, unable to select: " << nChars << " characters");
        return aInvalid;
    }

    return Selection(nCursorIndex, nCursorEndIndex);
}

gboolean GtkSalFrame::IMHandler::signalIMDeleteSurrounding( GtkIMContext*, gint offset, gint nchars,
    gpointer im_handler )
{
    GtkSalFrame::IMHandler* pThis = static_cast<GtkSalFrame::IMHandler*>(im_handler);

    // First get the surrounding text
    SalSurroundingTextRequestEvent aSurroundingTextEvt;
    aSurroundingTextEvt.maText.clear();
    aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;

    SolarMutexGuard aGuard;
    pThis->m_pFrame->CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);

    // Turn offset, nchars into a utf-16 selection
    Selection aSelection = GtkSalFrame::CalcDeleteSurroundingSelection(aSurroundingTextEvt.maText,
                                                                       aSurroundingTextEvt.mnStart,
                                                                       offset, nchars);
    Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
    if (aSelection == aInvalid)
        return false;

    SalSurroundingTextSelectionChangeEvent aEvt;
    aEvt.mnStart = aSelection.Min();
    aEvt.mnEnd = aSelection.Max();

    pThis->m_pFrame->CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);

    aSelection = Selection(aEvt.mnStart, aEvt.mnEnd);
    if (aSelection == aInvalid)
        return false;

    return true;
}

Size GtkSalDisplay::GetScreenSize( int nDisplayScreen )
{
    tools::Rectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
    return Size( aRect.GetWidth(), aRect.GetHeight() );
}

sal_uIntPtr GtkSalFrame::GetNativeWindowHandle(GtkWidget *pWidget)
{
    GdkSurface* pSurface = widget_get_surface(pWidget);
    GdkDisplay *pDisplay = getGdkDisplay();

#if defined(GDK_WINDOWING_X11)
    if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
    {
        return gdk_x11_surface_get_xid(pSurface);
    }
#endif

#if defined(GDK_WINDOWING_WAYLAND)
    if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
    {
        return reinterpret_cast<sal_uIntPtr>(gdk_wayland_surface_get_wl_surface(pSurface));
    }
#endif

    return 0;
}

void GtkInstDragSource::set_datatransfer(const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
                                     const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
{
    m_xListener = rListener;
    m_xTrans = rTrans;
}

void GtkInstDragSource::setActiveDragSource()
{
   // For LibreOffice internal D&D we provide the Transferable without Gtk
   // intermediaries as a shortcut, see tdf#100097 for how dbaccess depends on this
   g_ActiveDragSource = this;
   g_DropSuccessSet = false;
   g_DropSuccess = false;
}

#if !GTK_CHECK_VERSION(4, 0, 0)
std::vector<GtkTargetEntry> GtkInstDragSource::FormatsToGtk(const css::uno::Sequence<css::datatransfer::DataFlavor> &rFormats)
{
    return m_aConversionHelper.FormatsToGtk(rFormats);
}
#endif

void GtkInstDragSource::startDrag(const datatransfer::dnd::DragGestureEvent& rEvent,
                                  sal_Int8 sourceActions, sal_Int32 /*cursor*/, sal_Int32 /*image*/,
                                  const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
                                  const css::uno::Reference<css::datatransfer::dnd::XDragSourceListener>& rListener)
{
    set_datatransfer(rTrans, rListener);

    if (m_pFrame)
    {
        setActiveDragSource();

        m_pFrame->startDrag(rEvent, rTrans, m_aConversionHelper ,VclToGdk(sourceActions));
    }
    else
        dragFailed();
}

void GtkSalFrame::startDrag(const css::datatransfer::dnd::DragGestureEvent& rEvent,
                            const css::uno::Reference<css::datatransfer::XTransferable>& rTrans,
                            VclToGtkHelper& rConversionHelper,
                            GdkDragAction sourceActions)
{
    SolarMutexGuard aGuard;

    assert(m_pDragSource);

#if GTK_CHECK_VERSION(4, 0, 0)

    GdkSeat *pSeat = gdk_display_get_default_seat(getGdkDisplay());
    GdkDrag* pDrag = gdk_drag_begin(widget_get_surface(getMouseEventWidget()),
                                    gdk_seat_get_pointer(pSeat),
                                    transerable_content_new(&rConversionHelper, rTrans.get()),
                                    sourceActions,
                                    rEvent.DragOriginX, rEvent.DragOriginY);

    g_signal_connect(G_OBJECT(pDrag), "drop-performed", G_CALLBACK(signalDragEnd), this);
    g_signal_connect(G_OBJECT(pDrag), "cancel", G_CALLBACK(signalDragFailed), this);
    g_signal_connect(G_OBJECT(pDrag), "dnd-finished", G_CALLBACK(signalDragDelete), this);

#else

    auto aFormats = rTrans->getTransferDataFlavors();
    auto aGtkTargets = rConversionHelper.FormatsToGtk(aFormats);

    GtkTargetList *pTargetList = gtk_target_list_new(aGtkTargets.data(), aGtkTargets.size());

    gint nDragButton = 1; // default to left button
    css::awt::MouseEvent aEvent;
    if (rEvent.Event >>= aEvent)
    {
        if (aEvent.Buttons & css::awt::MouseButton::LEFT )
            nDragButton = 1;
        else if (aEvent.Buttons & css::awt::MouseButton::RIGHT)
            nDragButton = 3;
        else if (aEvent.Buttons & css::awt::MouseButton::MIDDLE)
            nDragButton = 2;
    }

    GdkEvent aFakeEvent;
    memset(&aFakeEvent, 0, sizeof(GdkEvent));
    aFakeEvent.type = GDK_BUTTON_PRESS;
    aFakeEvent.button.window = widget_get_surface(getMouseEventWidget());
    aFakeEvent.button.time = GDK_CURRENT_TIME;
    GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
    aFakeEvent.button.device = gdk_device_manager_get_client_pointer(pDeviceManager);

    GdkDragContext *pDrag = gtk_drag_begin_with_coordinates(getMouseEventWidget(),
                                                            pTargetList,
                                                            sourceActions,
                                                            nDragButton,
                                                            &aFakeEvent,
                                                            rEvent.DragOriginX,
                                                            rEvent.DragOriginY);

    gtk_target_list_unref(pTargetList);

    for (auto &a : aGtkTargets)
        g_free(a.target);
#endif

    if (!pDrag)
        m_pDragSource->dragFailed();
}

void GtkInstDragSource::dragFailed()
{
    if (m_xListener.is())
    {
        datatransfer::dnd::DragSourceDropEvent aEv;
        aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_NONE;
        aEv.DropSuccess = false;
        auto xListener = m_xListener;
        m_xListener.clear();
        xListener->dragDropEnd(aEv);
    }
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalDragFailed(GdkDrag* /*drag*/, GdkDragCancelReason /*reason*/, gpointer frame)
#else
gboolean GtkSalFrame::signalDragFailed(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkDragResult /*result*/, gpointer frame)
#endif
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (pThis->m_pDragSource)
        pThis->m_pDragSource->dragFailed();
#if !GTK_CHECK_VERSION(4, 0, 0)
    return false;
#endif
}

void GtkInstDragSource::dragDelete()
{
    if (m_xListener.is())
    {
        datatransfer::dnd::DragSourceDropEvent aEv;
        aEv.DropAction = datatransfer::dnd::DNDConstants::ACTION_MOVE;
        aEv.DropSuccess = true;
        auto xListener = m_xListener;
        m_xListener.clear();
        xListener->dragDropEnd(aEv);
    }
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalDragDelete(GdkDrag* /*context*/, gpointer frame)
#else
void GtkSalFrame::signalDragDelete(GtkWidget* /*widget*/, GdkDragContext* /*context*/, gpointer frame)
#endif
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDragSource)
        return;
    pThis->m_pDragSource->dragDelete();
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkInstDragSource::dragEnd(GdkDrag* context)
#else
void GtkInstDragSource::dragEnd(GdkDragContext* context)
#endif
{
    if (m_xListener.is())
    {
        datatransfer::dnd::DragSourceDropEvent aEv;
#if GTK_CHECK_VERSION(4, 0, 0)
        aEv.DropAction = GdkToVcl(gdk_drag_get_selected_action(context));
#else
        aEv.DropAction = GdkToVcl(gdk_drag_context_get_selected_action(context));
#endif
        // an internal drop can accept the drop but fail with dropComplete( false )
        // this is different than the GTK API
        if (g_DropSuccessSet)
            aEv.DropSuccess = g_DropSuccess;
        else
            aEv.DropSuccess = true;
        auto xListener = m_xListener;
        m_xListener.clear();
        xListener->dragDropEnd(aEv);
    }
    g_ActiveDragSource = nullptr;
}

#if GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::signalDragEnd(GdkDrag* context, gpointer frame)
#else
void GtkSalFrame::signalDragEnd(GtkWidget* /*widget*/, GdkDragContext* context, gpointer frame)
#endif
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDragSource)
        return;
    pThis->m_pDragSource->dragEnd(context);
}

#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkInstDragSource::dragDataGet(GtkSelectionData *data, guint info)
{
    m_aConversionHelper.setSelectionData(m_xTrans, data, info);
}

void GtkSalFrame::signalDragDataGet(GtkWidget* /*widget*/, GdkDragContext* /*context*/, GtkSelectionData *data, guint info,
                                    guint /*time*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    if (!pThis->m_pDragSource)
        return;
    pThis->m_pDragSource->dragDataGet(data, info);
}
#endif

bool GtkSalFrame::CallCallbackExc(SalEvent nEvent, const void* pEvent) const
{
    SolarMutexGuard aGuard;
    bool nRet = false;
    try
    {
        nRet = CallCallback(nEvent, pEvent);
    }
    catch (...)
    {
        GtkSalData *pSalData = static_cast<GtkSalData*>(GetSalData());
        pSalData->setException(std::current_exception());
    }
    return nRet;
}

#if !GTK_CHECK_VERSION(4, 0, 0)
void GtkSalFrame::nopaint_container_resize_children(GtkContainer *pContainer)
{
    bool bOrigSalObjectSetPosSize = m_bSalObjectSetPosSize;
    m_bSalObjectSetPosSize = true;
    gtk_container_resize_children(pContainer);
    m_bSalObjectSetPosSize = bOrigSalObjectSetPosSize;
}
#endif

GdkEvent* GtkSalFrame::makeFakeKeyPress(GtkWidget* pWidget)
{
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkEvent *event = gdk_event_new(GDK_KEY_PRESS);
    event->key.window = GDK_WINDOW(g_object_ref(widget_get_surface(pWidget)));

    GdkSeat *seat = gdk_display_get_default_seat(gtk_widget_get_display(pWidget));
    gdk_event_set_device(event, gdk_seat_get_keyboard(seat));

    event->key.send_event = 1 /* TRUE */;
    event->key.time = gtk_get_current_event_time();
    event->key.state = 0;
    event->key.keyval = 0;
    event->key.length = 0;
    event->key.string = nullptr;
    event->key.hardware_keycode = 0;
    event->key.group = 0;
    event->key.is_modifier = false;
    return event;
#else
    (void)pWidget;
    return nullptr;
#endif
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
