/* -*- 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 <config_version.h>
 
#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 <comphelper/diagnose_ex.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/toolkit/unowrap.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 <strings.hrc>
#include <window.h>
 
#include <basegfx/vector/b2ivector.hxx>
#include <officecfg/Office/Common.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>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/util/XModifiable.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;
 
static void EnsureSessionBus()
{
    if (!pSessionBus)
        pSessionBus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, 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;
            case GDK_KEY_braceright:   nCode = KEY_RIGHTCURLYBRACKET;   break;
            case GDK_KEY_numbersign:   nCode = KEY_NUMBERSIGN; break;
            case GDK_KEY_Forward:      nCode = KEY_XF86FORWARD; break;
            case GDK_KEY_Back:         nCode = KEY_XF86BACK; break;
            case GDK_KEY_colon:    nCode = KEY_COLON;    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)
    {
        // tdf#152404 Commit uncommitted text before dispatching key shortcuts. In
        // certain cases such as pressing Control-Alt-C in a Writer document while
        // there is uncommitted text will call GtkSalFrame::EndExtTextInput() which
        // will dispatch a SalEvent::EndExtTextInput event. Writer's handler for that
        // event will delete the uncommitted text and then insert the committed text
        // but LibreOffice will crash when deleting the uncommitted text because
        // deletion of the text also removes and deletes the newly inserted comment.
        if (m_pIMHandler && !m_pIMHandler->m_aInputEvent.maText.isEmpty() && (aEvent.mnCode & (KEY_MOD1 | KEY_MOD2)))
            m_pIMHandler->doCallEndExtTextInput();
 
        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.
    EnsureSessionBus();
    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");
 
    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.
    EnsureSessionBus();
    if (!pSessionBus)
        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);
 
        if (m_nPortalSettingChangedSignalId)
            g_signal_handler_disconnect(m_pSettingsPortal, m_nPortalSettingChangedSignalId);
 
        if (m_pSettingsPortal)
            g_object_unref(m_pSettingsPortal);
 
        if (m_nSessionClientSignalId)
            g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);
 
        if (m_pSessionClient)
            g_object_unref(m_pSessionClient);
 
        if (m_pSessionManager)
            g_object_unref(m_pSessionManager);
    }
 
    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.x(), nY - m_pParent->maGeometry.y() );
        }
        return;
    }
#if GTK_CHECK_VERSION(4,0,0)
    if (GTK_IS_POPOVER(m_pWindow))
    {
        GdkRectangle aRect;
        aRect.x = nX;
        aRect.y = nY;
        aRect.width = 1;
        aRect.height = 1;
        gtk_popover_set_pointing_to(GTK_POPOVER(m_pWindow), &aRect);
        return;
    }
#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 );
#if GTK_CHECK_VERSION(4,0,0)
    gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight );
#endif
}
 
void GtkSalFrame::window_resize(tools::Long nWidth, tools::Long nHeight)
{
    m_nWidthRequest = nWidth;
    m_nHeightRequest = nHeight;
    if (!GTK_IS_WINDOW(m_pWindow))
    {
#if GTK_CHECK_VERSION(4,0,0)
        gtk_widget_set_size_request(GTK_WIDGET(m_pDrawingArea), nWidth, nHeight);
#endif
        return;
    }
    gtk_window_set_default_size(GTK_WINDOW(m_pWindow), nWidth, nHeight);
#if !GTK_CHECK_VERSION(4,0,0)
    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;
}
 
static void
ooo_fixed_class_init(gpointer klass_, gpointer)
{
    auto const klass = static_cast<GtkFixedClass *>(klass_);
    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;
}
 
/*
 * 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 */
            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;
}
 
#endif
 
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.x(), maGeometry.y() );
    maGeometry.setScreen(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);
}
 
#if !GTK_CHECK_VERSION(4,0,0)
static void notifyUnref(gpointer data, GObject *) { g_object_unref(data); }
#endif
 
void GtkSalFrame::InitCommon()
{
    m_pSurface = nullptr;
    m_nGrabLevel = 0;
    m_bSalObjectSetPosSize = false;
    m_nPortalSettingChangedSignalId = 0;
    m_nSessionClientSignalId = 0;
    m_pSettingsPortal = nullptr;
    m_pSessionManager = nullptr;
    m_pSessionClient = nullptr;
 
    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(g_object_new( ooo_fixed_get_type(), nullptr ));
    m_pDrawingArea = GTK_DRAWING_AREA(gtk_drawing_area_new());
#endif
    if (GTK_IS_WINDOW(m_pWindow))
    {
        Size aDefWindowSize = calcDefaultSize();
        gtk_window_set_default_size(GTK_WINDOW(m_pWindow), aDefWindowSize.Width(), aDefWindowSize.Height());
    }
    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_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
 
#if GTK_CHECK_VERSION(4,0,0)
    GtkGesture* pZoomGesture = gtk_gesture_zoom_new();
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pZoomGesture));
#else
    GtkGesture* pZoomGesture = gtk_gesture_zoom_new(GTK_WIDGET(pEventWidget));
    g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pZoomGesture);
#endif
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pZoomGesture),
                                               GTK_PHASE_TARGET);
    // Note that the default zoom gesture signal handler needs to run first to setup correct
    // scale delta. Otherwise the first "begin" event will always contain scale delta of infinity.
    g_signal_connect_after(pZoomGesture, "begin", G_CALLBACK(signalZoomBegin), this);
    g_signal_connect_after(pZoomGesture, "update", G_CALLBACK(signalZoomUpdate), this);
    g_signal_connect_after(pZoomGesture, "end", G_CALLBACK(signalZoomEnd), this);
 
#if GTK_CHECK_VERSION(4,0,0)
    GtkGesture* pRotateGesture = gtk_gesture_rotate_new();
    gtk_widget_add_controller(pEventWidget, GTK_EVENT_CONTROLLER(pRotateGesture));
#else
    GtkGesture* pRotateGesture = gtk_gesture_rotate_new(GTK_WIDGET(pEventWidget));
    g_object_weak_ref(G_OBJECT(pEventWidget), notifyUnref, pRotateGesture);
#endif
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(pRotateGesture),
                                               GTK_PHASE_TARGET);
    g_signal_connect(pRotateGesture, "begin", G_CALLBACK(signalRotateBegin), this);
    g_signal_connect(pRotateGesture, "update", G_CALLBACK(signalRotateUpdate), this);
    g_signal_connect(pRotateGesture, "end", G_CALLBACK(signalRotateEnd), this);
 
    //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), notifyUnref, 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), notifyUnref, 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 | GDK_TOUCHPAD_GESTURE_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.setPosSize({ -1, -1 }, aDefSize);
        maGeometry.setDecorations(0, 0, 0, 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
}
 
namespace
{
    enum ColorScheme
    {
        DEFAULT,
        PREFER_DARK,
        PREFER_LIGHT
    };
 
    void ReadColorScheme(GDBusProxy* proxy, GVariant** out)
    {
        g_autoptr (GVariant) ret =
            g_dbus_proxy_call_sync(proxy, "Read",
                                   g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
                                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT, nullptr, nullptr);
        if (!ret)
            return;
 
        g_autoptr (GVariant) child = nullptr;
        g_variant_get(ret, "(v)", &child);
        g_variant_get(child, "v", out);
 
        return;
    }
}
 
void GtkSalFrame::SetColorScheme(GVariant* variant)
{
    if (!m_pWindow)
        return;
 
    guint32 color_scheme;
 
    switch (officecfg::Office::Common::Misc::Appearance::get())
    {
        default:
        case 0: // Auto
        {
            if (variant)
            {
                color_scheme = g_variant_get_uint32(variant);
                if (color_scheme > PREFER_LIGHT)
                    color_scheme = DEFAULT;
            }
            else
                color_scheme = DEFAULT;
            break;
        }
        case 1: // Light
            color_scheme = PREFER_LIGHT;
            break;
        case 2: // Dark
            color_scheme = PREFER_DARK;
            break;
    }
 
    bool bDarkIconTheme(color_scheme == PREFER_DARK);
    GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
    g_object_set(pSettings, "gtk-application-prefer-dark-theme", bDarkIconTheme, nullptr);
}
 
bool GtkSalFrame::GetUseDarkMode() const
{
    if (!m_pWindow)
        return false;
    GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
    gboolean bDarkIconTheme = false;
    g_object_get(pSettings, "gtk-application-prefer-dark-theme", &bDarkIconTheme, nullptr);
    return bDarkIconTheme;
}
 
bool GtkSalFrame::GetUseReducedAnimation() const
{
    if (!m_pWindow)
        return false;
    GtkSettings* pSettings = gtk_widget_get_settings(m_pWindow);
    gboolean bAnimations;
    g_object_get(pSettings, "gtk-enable-animations", &bAnimations, nullptr);
    return !bAnimations;
}
 
static void settings_portal_changed_cb(GDBusProxy*, const char*, const char* signal_name,
                                       GVariant* parameters, gpointer frame)
{
    if (g_strcmp0(signal_name, "SettingChanged"))
        return;
 
    g_autoptr (GVariant) value = nullptr;
    const char *name_space;
    const char *name;
    g_variant_get(parameters, "(&s&sv)", &name_space, &name, &value);
 
    if (g_strcmp0(name_space, "org.freedesktop.appearance") ||
        g_strcmp0(name, "color-scheme"))
      return;
 
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
    pThis->SetColorScheme(value);
}
 
void GtkSalFrame::ListenPortalSettings()
{
    EnsureSessionBus();
 
    if (!pSessionBus)
        return;
 
    m_pSettingsPortal = g_dbus_proxy_new_sync(pSessionBus,
                                              G_DBUS_PROXY_FLAGS_NONE,
                                              nullptr,
                                              "org.freedesktop.portal.Desktop",
                                              "/org/freedesktop/portal/desktop",
                                              "org.freedesktop.portal.Settings",
                                              nullptr,
                                              nullptr);
 
    UpdateDarkMode();
 
    if (!m_pSettingsPortal)
        return;
 
    m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
}
 
static void session_client_response(GDBusProxy* client_proxy)
{
    g_dbus_proxy_call(client_proxy,
                      "EndSessionResponse",
                      g_variant_new ("(bs)", true, ""),
                      G_DBUS_CALL_FLAGS_NONE,
                      G_MAXINT,
                      nullptr, nullptr, nullptr);
}
 
// unset documents "modify" flag so they won't veto closing
static void clear_modify_and_terminate()
{
    const css::uno::Reference<css::uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
    uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
    uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
    while (xComponents->hasMoreElements())
    {
        css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
        if (xModifiable)
            xModifiable->setModified(false);
    }
    xDesktop->terminate();
}
 
static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
                                  GVariant* /*parameters*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
 
    if (g_str_equal (signal_name, "QueryEndSession"))
    {
        const css::uno::Reference<css::uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
        uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));
 
        bool bModified = false;
 
        // find the XModifiable for this GtkSalFrame
        if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
        {
            VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
            css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
            sal_Int32 nFrameCount = xList->getCount();
            for (sal_Int32 i = 0; i < nFrameCount; ++i)
            {
                css::uno::Reference<css::frame::XFrame> xFrame;
                xList->getByIndex(i) >>= xFrame;
                if (!xFrame)
                    continue;
                VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
                if (!xWin)
                   continue;
                if (xWin->GetFrameWindow() != xThisWindow)
                    continue;
                css::uno::Reference<css::frame::XController> xController = xFrame->getController();
                if (!xController)
                    break;
                css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
                if (!xModifiable)
                    break;
                bModified = xModifiable->isModified();
                break;
            }
        }
 
        pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
                                     gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
 
        session_client_response(client_proxy);
    }
    else if (g_str_equal (signal_name, "CancelEndSession"))
    {
        // restore back to uninhibited (to set again if queried), so frames
        // that go away before the next logout don't affect that logout
        pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
                                     gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
    }
    else if (g_str_equal (signal_name, "EndSession"))
    {
        session_client_response(client_proxy);
        clear_modify_and_terminate();
    }
    else if (g_str_equal (signal_name, "Stop"))
    {
        clear_modify_and_terminate();
    }
}
 
void GtkSalFrame::ListenSessionManager()
{
    EnsureSessionBus();
 
    if (!pSessionBus)
        return;
 
    m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
                                              G_DBUS_PROXY_FLAGS_NONE,
                                              nullptr,
                                              "org.gnome.SessionManager",
                                              "/org/gnome/SessionManager",
                                              "org.gnome.SessionManager",
                                              nullptr,
                                              nullptr);
 
    if (!m_pSessionManager)
        return;
 
    GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
                                 "RegisterClient",
                                 g_variant_new ("(ss)", "org.libreoffice", ""),
                                 G_DBUS_CALL_FLAGS_NONE,
                                 G_MAXINT,
                                 nullptr,
                                 nullptr);
 
    if (!res)
        return;
 
    gchar* client_path;
    g_variant_get(res, "(o)", &client_path);
    g_variant_unref(res);
 
    m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
                                             G_DBUS_PROXY_FLAGS_NONE,
                                             nullptr,
                                             "org.gnome.SessionManager",
                                             client_path,
                                             "org.gnome.SessionManager.ClientPrivate",
                                             nullptr,
                                             nullptr);
 
    g_free(client_path);
 
    if (!m_pSessionClient)
        return;
 
    m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
}
 
void GtkSalFrame::UpdateDarkMode()
{
    g_autoptr (GVariant) value = nullptr;
    if (m_pSettingsPortal)
        ReadColorScheme(m_pSettingsPortal, &value);
    SetColorScheme(value);
}
 
#if GTK_CHECK_VERSION(4,0,0)
static void PopoverClosed(GtkPopover*, GtkSalFrame* pThis)
{
    SolarMutexGuard aGuard;
    pThis->closePopup();
}
#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
        if (!bPopup)
            m_pWindow = gtk_window_new();
        else
        {
            m_pWindow = gtk_popover_new();
            gtk_popover_set_has_arrow(GTK_POPOVER(m_pWindow), false);
            g_signal_connect(m_pWindow, "closed", G_CALLBACK(PopoverClosed), this);
        }
#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)));
        }
    }
    else if (GTK_IS_POPOVER(m_pWindow))
    {
        assert(m_pParent);
        gtk_widget_set_parent(m_pWindow, m_pParent->getMouseEventWidget());
    }
 
    // 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);
 
        // Listen to portal settings for e.g. prefer dark theme
        ListenPortalSettings();
 
        // Listen to session manager for e.g. query-end
        ListenSessionManager();
    }
}
 
#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 && GTK_IS_WINDOW(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::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) && !GTK_CHECK_VERSION(4,0,0)
        /*
         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.width(), maGeometry.height() );
    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.width() << " x " << maGeometry.height());
 
    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;
 
        maGeometry.setSize({ nWidth, nHeight });
 
        if (isChild(false) || GTK_IS_POPOVER(m_pWindow))
            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.width()-m_nWidthRequest-1-nX;
            nX += m_pParent->maGeometry.x();
            nY += m_pParent->maGeometry.y();
        }
 
        if (nFlags & SAL_FRAME_POSSIZE_X)
            maGeometry.setX(nX);
        if (nFlags & SAL_FRAME_POSSIZE_Y)
            maGeometry.setY(nY);
        m_bGeometryIsProvisional = true;
 
        m_bDefaultPos = false;
 
        moveWindow(maGeometry.x(), maGeometry.y());
 
        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.width();
        rHeight = maGeometry.height();
    }
    else
        rWidth = rHeight = 0;
}
 
void GtkSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
{
    GdkRectangle aRect;
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkScreen  *pScreen = gtk_widget_get_screen(m_pWindow);
    AbsoluteScreenPixelRectangle aRetRect;
    int max = gdk_screen_get_n_monitors (pScreen);
    for (int i = 0; i < max; ++i)
    {
        gdk_screen_get_monitor_workarea(pScreen, i, &aRect);
        AbsoluteScreenPixelRectangle 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 = AbsoluteScreenPixelRectangle(aRect.x, aRect.y, aRect.x+aRect.width, aRect.y+aRect.height);
#endif
}
 
SalFrame* GtkSalFrame::GetParent() const
{
    return m_pParent;
}
 
void GtkSalFrame::SetWindowState(const vcl::WindowData* pState)
{
    if( ! m_pWindow || ! pState || isChild( true, false ) )
        return;
 
    const vcl::WindowDataMask nMaxGeometryMask = vcl::WindowDataMask::PosSize |
        vcl::WindowDataMask::MaximizedX | vcl::WindowDataMask::MaximizedY |
        vcl::WindowDataMask::MaximizedWidth | vcl::WindowDataMask::MaximizedHeight;
 
    if( (pState->mask() & vcl::WindowDataMask::State) &&
        ! ( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED ) &&
        (pState->state() & vcl::WindowState::Maximized) &&
        (pState->mask() & nMaxGeometryMask) == nMaxGeometryMask )
    {
        resizeWindow(pState->width(), pState->height());
        moveWindow(pState->x(), pState->y());
        m_bDefaultPos = m_bDefaultSize = false;
 
        updateScreenNumber();
 
        m_nState = GdkToplevelState(m_nState | GDK_TOPLEVEL_STATE_MAXIMIZED);
        m_aRestorePosSize = pState->posSize();
    }
    else if (pState->mask() & vcl::WindowDataMask::PosSize)
    {
        sal_uInt16 nPosSizeFlags = 0;
        tools::Long nX = pState->x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
        tools::Long nY = pState->y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
        if (pState->mask() & vcl::WindowDataMask::X)
            nPosSizeFlags |= SAL_FRAME_POSSIZE_X;
        else
            nX = maGeometry.x() - (m_pParent ? m_pParent->maGeometry.x() : 0);
        if (pState->mask() & vcl::WindowDataMask::Y)
            nPosSizeFlags |= SAL_FRAME_POSSIZE_Y;
        else
            nY = maGeometry.y() - (m_pParent ? m_pParent->maGeometry.y() : 0);
        if (pState->mask() & vcl::WindowDataMask::Width)
            nPosSizeFlags |= SAL_FRAME_POSSIZE_WIDTH;
        if (pState->mask() & vcl::WindowDataMask::Height)
            nPosSizeFlags |= SAL_FRAME_POSSIZE_HEIGHT;
        SetPosSize(nX, nY, pState->width(), pState->height(), nPosSizeFlags);
    }
 
    if (pState->mask() & vcl::WindowDataMask::State && !isChild())
    {
        if (pState->state() & vcl::WindowState::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->state() & vcl::WindowState::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(vcl::WindowData* pState)
{
    pState->setState(vcl::WindowState::Normal);
    pState->setMask(vcl::WindowDataMask::PosSizeState);
 
    // rollup ? gtk 2.2 does not seem to support the shaded state
    if( m_nState & GDK_TOPLEVEL_STATE_MINIMIZED )
        pState->rState() |= vcl::WindowState::Minimized;
    if( m_nState & GDK_TOPLEVEL_STATE_MAXIMIZED )
    {
        pState->rState() |= vcl::WindowState::Maximized;
        pState->setPosSize(m_aRestorePosSize);
        tools::Rectangle aPosSize = GetPosAndSize(GTK_WINDOW(m_pWindow));
        pState->SetMaximizedX(aPosSize.Left());
        pState->SetMaximizedY(aPosSize.Top());
        pState->SetMaximizedWidth(aPosSize.GetWidth());
        pState->SetMaximizedHeight(aPosSize.GetHeight());
        pState->rMask() |= vcl::WindowDataMask::MaximizedX          |
                           vcl::WindowDataMask::MaximizedY          |
                           vcl::WindowDataMask::MaximizedWidth      |
                           vcl::WindowDataMask::MaximizedHeight;
    }
    else
        pState->setPosSize(GetPosAndSize(GTK_WINDOW(m_pWindow)));
 
    return true;
}
 
void GtkSalFrame::SetScreen( unsigned int nNewScreen, SetType eType, tools::Rectangle const *pSize )
{
    if( !m_pWindow )
        return;
 
    if (maGeometry.screen() == nNewScreen && eType == SetType::RetainSize)
        return;
 
#if !GTK_CHECK_VERSION(4, 0, 0)
    int nX = maGeometry.x(), nY = maGeometry.y(),
        nWidth = maGeometry.width(), nHeight = maGeometry.height();
    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;
        bResize = true;
 
        // #i110881# for the benefit of compiz set a max size here
        // else setting to fullscreen fails for unknown reasons
        //
        // tdf#161479 With fractional scaling on wayland the monitor
        // sizes here are reported effectively with the fractional
        // scaling factor rounded up to the next integer, so,
        // 1920 x 1080 at 125% scaling which appears like,
        // 1536 x 864 is reported the same as 200% scaling, i.e.
        // 960 x 540 which causes a problem on trying to set
        // fullscreen on fractional scaling under wayland. Drop
        // this old workaround when under wayland.
#if defined(GDK_WINDOWING_WAYLAND)
        const bool bWayland = DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay());
#else
        const bool bWayland = false;
#endif
        if (!bWayland)
        {
            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();
        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);
        }
 
        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)
    {
        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::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
{
    guint nWindow(0);
    std::optional<Display*> aDisplay;
 
    if (DLSYM_GDK_IS_X11_DISPLAY(getGdkDisplay()))
    {
        nWindow = GtkSalFrame::GetNativeWindowHandle(m_pWindow);
        aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
    }
 
    m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
                                      nWindow, aDisplay, application_id);
}
 
void GtkSalFrame::StartPresentation( bool bStart )
{
    SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
}
 
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.x() + nX;
    unsigned int nWindowTop  = maGeometry.y() + 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;
            case KEY_RIGHTCURLYBRACKET: nKeyCode = GDK_KEY_braceright;      break;
            case KEY_NUMBERSIGN: nKeyCode = GDK_KEY_numbersign;         break;
            case KEY_XF86FORWARD:   nKeyCode = GDK_KEY_Forward;         break;
            case KEY_XF86BACK:      nKeyCode = GDK_KEY_Back;            break;
            case KEY_COLON:         nKeyCode = GDK_KEY_colon;           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 = OStringToOUString(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.x(), y - maGeometry.y() );
    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 &&
        // tdf#152155 cannot determine window positions of popup listboxes on sidebar
        nFlags != LISTBOX_FLOATWINPOPUPFLAGS )
    {
        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.width()-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.x();
        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.width()-1-aEvent.mnX;
 
    CallCallbackExc(nEventType, &aEvent);
 
    return true;
}
 
#if !GTK_CHECK_VERSION(4, 0, 0)
 
void GtkSalFrame::UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY)
{
    //tdf#151509 don't overwrite geometry for system children
    if (m_nStyle & SalFrameStyleFlags::SYSTEMCHILD)
        return;
 
    int frame_x = x_root - nEventX;
    int frame_y = y_root - nEventY;
    if (m_bGeometryIsProvisional || frame_x != maGeometry.x() || frame_y != maGeometry.y())
    {
        m_bGeometryIsProvisional = false;
        maGeometry.setPos({ frame_x, frame_y });
        ImplSVData* pSVData = ImplGetSVData();
        if (pSVData->maNWFData.mbCanDetermineWindowPosition)
            CallCallbackExc(SalEvent::Move, nullptr);
    }
}
 
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())
        pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
 
    bool bRet = false;
    if (!aDel.isDeleted())
    {
        bRet = pThis->DrawingAreaButton(nEventType,
                                        nEventX,
                                        nEventY,
                                        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.width() - 1 - aEvent.mnX;
    aEvent.mnY = nEventY;
    aEvent.mnCode = GetMouseModCode(nState);
 
    // rhbz#1344042 "Traditionally" in gtk3 we took 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());
 
    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.width() - 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;
}
 
gboolean GtkSalFrame::event_controller_scroll_forward(GtkEventControllerScroll* pController, double delta_x, double delta_y)
{
    return GtkSalFrame::signalScroll(pController, delta_x, delta_y, this);
}
 
#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))
    {
        SalGestureSwipeEvent aEvent;
        aEvent.mnVelocityX = velocity_x;
        aEvent.mnVelocityY = velocity_y;
        aEvent.mnX = x;
        aEvent.mnY = y;
 
        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::GestureSwipe, &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))
    {
        SalGestureLongPressEvent aEvent;
        aEvent.mnX = x;
        aEvent.mnY = y;
 
        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::GestureLongPress, &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.width() - 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);
 
    pThis->UpdateGeometryFromEvent(pEvent->x_root, pEvent->y_root, nEventX, nEventY);
 
    if (!aDel.isDeleted())
        pThis->DrawingAreaMotion(nEventX, nEventY, 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.width()-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,
                               pEvent->y,
                               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.setSize({ nWidth, 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);
    }
 
    AbsoluteScreenPixelRectangle aFloatRect = FloatingWindow::ImplConvertToAbsPos(pVclParent, pThis->m_aFloatRect);
    switch (gdk_window_get_window_type(widget_get_surface(pThis->m_pParent->m_pWindow)))
    {
        case GDK_WINDOW_TOPLEVEL:
            break;
        case GDK_WINDOW_CHILD:
        {
            // See tdf#152155 for an example
            gtk_coord nX(0), nY(0.0);
            gtk_widget_translate_coordinates(pThis->m_pParent->m_pWindow, widget_get_toplevel(pThis->m_pParent->m_pWindow), 0, 0, &nX, &nY);
            aFloatRect.Move(nX, nY);
            break;
        }
        default:
        {
            // See tdf#154072 for an example
            aFloatRect.Move(-pThis->m_pParent->maGeometry.x(), -pThis->m_pParent->maGeometry.y());
            break;
        }
    }
 
    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.x() || y != pThis->maGeometry.y() )
    {
        bMoved = true;
        pThis->m_bGeometryIsProvisional = false;
        pThis->maGeometry.setPos({ x, y });
    }
 
    // update decoration hints
    GdkRectangle aRect;
    gdk_window_get_frame_extents( widget_get_surface(GTK_WIDGET(pThis->m_pWindow)), &aRect );
    pThis->maGeometry.setTopDecoration(y - aRect.y);
    pThis->maGeometry.setBottomDecoration(aRect.y + aRect.height - y - pEvent->height);
    pThis->maGeometry.setLeftDecoration(x - aRect.x);
    pThis->maGeometry.setRightDecoration(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.width() << "x" << maGeometry.height());
    SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
    CallCallbackExc(SalEvent::Paint, &aPaintEvt);
    queue_draw();
}
 
void GtkSalFrame::DrawingAreaFocusInOut(SalEvent nEventType)
{
    SalGenericInstance* pSalInstance = GetGenericInstance();
 
    // 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 = GetGenericInstance();
 
    // 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 = std::move(xOrigFrameFocusWin);
 
                ImplSVData* pSVData = ImplGetSVData();
                if (pSVData->mpWinData->mpFocusWin == xTopLevelInterimWindow)
                    pSVData->mpWinData->mpFocusWin = std::move(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 = GetGtkInstance();
    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
{
    bool handleSignalZoom(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
                          GestureEventZoomType eEventType)
    {
        gdouble x = 0;
        gdouble y = 0;
        gtk_gesture_get_point(gesture, sequence, &x, &y);
 
        SalGestureZoomEvent aEvent;
        aEvent.meEventType = eEventType;
        aEvent.mnX = x;
        aEvent.mnY = y;
        aEvent.mfScaleDelta = gtk_gesture_zoom_get_scale_delta(GTK_GESTURE_ZOOM(gesture));
        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::GestureZoom, &aEvent);
        return true;
    }
 
    bool handleSignalRotate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame,
                            GestureEventRotateType eEventType)
    {
        gdouble x = 0;
        gdouble y = 0;
        gtk_gesture_get_point(gesture, sequence, &x, &y);
 
        SalGestureRotateEvent aEvent;
        aEvent.meEventType = eEventType;
        aEvent.mnX = x;
        aEvent.mnY = y;
        aEvent.mfAngleDelta = gtk_gesture_rotate_get_angle_delta(GTK_GESTURE_ROTATE(gesture));
        GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
        pThis->CallCallbackExc(SalEvent::GestureRotate, &aEvent);
        return true;
    }
}
 
bool GtkSalFrame::signalZoomBegin(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
    return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Begin);
}
 
bool GtkSalFrame::signalZoomUpdate(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
    return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::Update);
}
 
bool GtkSalFrame::signalZoomEnd(GtkGesture* gesture, GdkEventSequence* sequence, gpointer frame)
{
    return handleSignalZoom(gesture, sequence, frame, GestureEventZoomType::End);
}
 
bool GtkSalFrame::signalRotateBegin(GtkGesture* gesture, GdkEventSequence* sequence,
                                    gpointer frame)
{
    return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Begin);
}
 
bool GtkSalFrame::signalRotateUpdate(GtkGesture* gesture, GdkEventSequence* sequence,
                                     gpointer frame)
{
    return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::Update);
}
 
bool GtkSalFrame::signalRotateEnd(GtkGesture* gesture, GdkEventSequence* sequence,
                                  gpointer frame)
{
    return handleSignalRotate(gesture, sequence, frame, GestureEventRotateType::End);
}
 
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 = OStringToOUString(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 = GetSalInstance();
        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;
 
    // 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)
        aEvent.Transferable = GtkInstDragSource::g_ActiveDragSource->GetTransferable();
    else
    {
#if GTK_CHECK_VERSION(4,0,0)
        aEvent.Transferable = new GtkDnDTransferable(drop);
#else
        aEvent.Transferable = new GtkDnDTransferable(context, time, pWidget, this);
#endif
    }
 
    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->GetTransferable();
        else
        {
#if !GTK_CHECK_VERSION(4,0,0)
            xTransferable = new GtkDnDTransferable(context, time, pWidget, this);
#else
            xTransferable = new GtkDnDTransferable(pDrop);
#endif
        }
        aEvent.SupportedDataFlavors = xTransferable->getTransferDataFlavors();
        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:
                {
                    PangoAttrInt* pango_underline = reinterpret_cast<PangoAttrInt*>(pango_attr);
                    switch (pango_underline->value)
                    {
                        case PANGO_UNDERLINE_NONE:
                            break;
                        case PANGO_UNDERLINE_DOUBLE:
                            sal_attr |= ExtTextInputAttr::DoubleUnderline;
                            break;
                        default:
                            sal_attr |= ExtTextInputAttr::Underline;
                            break;
                    }
                    break;
                }
                case PANGO_ATTR_STRIKETHROUGH:
                    sal_attr |= ExtTextInputAttr::RedText;
                    break;
                default:
                    break;
            }
            pango_attribute_destroy (pango_attr);
            tmp_list = tmp_list->next;
        }
        if (!attr_list)
            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 = std::move(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);
    std::u16string_view sCursorText(aEvt.maText.subView(0, aEvt.mnStart));
    gtk_im_context_set_surrounding(pContext, sUTF.getStr(), sUTF.getLength(),
        OUStringToOString(sCursorText, RTL_TEXTENCODING_UTF8).getLength());
    return true;
}
 
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 = SalFrame::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;
}
 
AbsoluteScreenPixelSize GtkSalDisplay::GetScreenSize( int nDisplayScreen )
{
    AbsoluteScreenPixelRectangle aRect = m_pSys->GetDisplayScreenPosSizePixel( nDisplayScreen );
    return AbsoluteScreenPixelSize( 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;
 
    aFakeEvent.button.device = gtk_get_current_event_device();
    // if no current event to determine device, or (tdf#140272) the device will be unsuitable then find an
    // appropriate device to use.
    if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
    {
        GdkDeviceManager* pDeviceManager = gdk_display_get_device_manager(getGdkDisplay());
        GList* pDevices = gdk_device_manager_list_devices(pDeviceManager, GDK_DEVICE_TYPE_MASTER);
        for (GList* pEntry = pDevices; pEntry; pEntry = pEntry->next)
        {
            GdkDevice* pDevice = static_cast<GdkDevice*>(pEntry->data);
            if (gdk_device_get_source(pDevice) == GDK_SOURCE_KEYBOARD)
                continue;
            if (gdk_device_get_window_at_position(pDevice, nullptr, nullptr))
            {
                aFakeEvent.button.device = pDevice;
                break;
            }
        }
        g_list_free(pDevices);
    }
 
    GdkDragContext *pDrag;
    if (!aFakeEvent.button.device || !gdk_device_get_window_at_position(aFakeEvent.button.device, nullptr, nullptr))
        pDrag = nullptr;
    else
        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 (...)
    {
        GetGtkSalData()->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: */

V530 The return value of function 'Union' is required to be utilized.

V1053 Calling the 'UpdateDarkMode' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'gtkframe.cxx:510', 'gtkframe.cxx:1789', 'gtkframe.cxx:1453', 'gtkframe.hxx:618'.

V519 The 'm_bDefaultSize' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2181, 2195.

V519 The 'm_bDefaultPos' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 2213, 2222.

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 3766, 3769.

V1037 Two or more case-branches perform the same actions. Check lines: 181, 278

V1037 Two or more case-branches perform the same actions. Check lines: 184, 257, 272

V1037 Two or more case-branches perform the same actions. Check lines: 185, 275

V1037 Two or more case-branches perform the same actions. Check lines: 191, 248, 253

V1037 Two or more case-branches perform the same actions. Check lines: 211, 242

V1037 Two or more case-branches perform the same actions. Check lines: 214, 287

V1037 Two or more case-branches perform the same actions. Check lines: 302, 317

V1048 The 'nDragButton' variable was assigned the same value.

V1051 Consider checking for misprints. It's possible that the 'nOldMonitor' should be checked here.