/* -*- 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 .
*/
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 staticbool 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); elseif( keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9 )
nCode = KEY_0 + (keyval-GDK_KEY_KP_0); elseif( keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z )
nCode = KEY_A + (keyval-GDK_KEY_A ); elseif( keyval >= GDK_KEY_a && keyval <= GDK_KEY_z )
nCode = KEY_A + (keyval-GDK_KEY_a ); elseif (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
}
}
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)); returnfalse;
} elseif (keyval == GDK_KEY_1)
{
SAL_INFO("vcl.gtk3", "force repaint all.");
TriggerPaintEvent(); returnfalse;
} elseif (keyval == GDK_KEY_2)
{
dumpframes = !dumpframes;
SAL_INFO("vcl.gtk3", "toggle dump frames to " << dumpframes); returnfalse;
}
}
} #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;
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();
// 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());
// 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;
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 );
}
#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.
/* * 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) { staticconst 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));
}
#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);
#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
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
}
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
}
session_client_response(client_proxy);
} elseif (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())));
} elseif (g_str_equal (signal_name, "EndSession"))
{
session_client_response(client_proxy);
clear_modify_and_terminate();
} elseif (g_str_equal (signal_name, "Stop"))
{
clear_modify_and_terminate();
}
}
#if !GTK_CHECK_VERSION(4,0,0) #ifdefined(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()))
{ constbool bDesktopIsRTL = MsLangId::isRightToLeft(MsLangId::getConfiguredSystemUILanguage()); constbool 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_set_visible(GTK_WIDGET(m_pHeaderBar), true);
}
} #endif #endif
} #if !GTK_CHECK_VERSION(4,0,0) elseif( 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();
}
}
#ifdefined(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 staticauto set_application_id = reinterpret_cast<void (*) (GdkWindow*, constchar*)>(
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
}
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
*/ staticbool 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_set_visible(m_pWindow, true);
g_set_prgname(sOrigName.getStr());
} else
{
gtk_widget_set_visible(m_pWindow, true);
} #else
gtk_widget_set_visible(m_pWindow, true); #endif
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 (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();
}
// #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. #ifdefined(GDK_WINDOWING_WAYLAND) constbool bWayland = DLSYM_GDK_IS_WAYLAND_DISPLAY(GtkSalFrame::getGdkDisplay()); #else constbool bWayland = false; #endif if (!bWayland)
{
m_aMaxSize.setWidth( aNewMonitor.width );
m_aMaxSize.setHeight( aNewMonitor.height );
}
}
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::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();
}
/* 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.
*/ unsignedint nWindowLeft = maGeometry.x() + nX; unsignedint nWindowTop = maGeometry.y() + nY;
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::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: returnfalse;
}
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);
}
}
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();
}
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(); elseif (pEvent->type == GDK_BUTTON_RELEASE) returntrue;
}
}
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);
// 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);
}
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;
if (rEvent.direction == GDK_SCROLL_SMOOTH)
{
pThis->LaunchAsyncScroll(pInEvent); returntrue;
}
//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());
}
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;
//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) returntrue;
vcl::DeletionListener aDel( pThis );
int nEventX = pEvent->x; int nEventY = pEvent->y;
if (bDifferentEventWindow)
translate_coords(pEvent->window, pEventWidget, 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 );
}
returntrue;
} #endif
void GtkSalFrame::DrawingAreaCrossing(SalEvent nEventType, int nEventX, int nEventY, guint32 nTime, guint nState)
{
UpdateLastInputEventTime(nTime);
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();
}
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())};
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 });
}
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();
}
// 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();
// 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);
}
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 (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_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)) returntrue;
// 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))) returntrue;
// 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)) returntrue;
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;
}
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;
if (pEvent->keyval == GDK_KEY_F6 && pThis->IsCycleFocusOutDisallowed())
{ // For F6, allow the focus to leave the InterimItemWindow
pThis->AllowCycleFocusOut();
bRestoreDisallowCycleFocusOut = true;
}
}
// 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))
{ returntrue;
}
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);
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)) returntrue; // 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) returntrue;
// 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;
}
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;
if (keyval == GDK_KEY_F6 && IsCycleFocusOutDisallowed())
{ // For F6, allow the focus to leave the InterimItemWindow
AllowCycleFocusOut();
bRestoreDisallowCycleFocusOut = true;
}
}
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;
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);
// 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) returnfalse; 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) returnfalse; 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
}
#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;
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; elseif ((mask & GDK_CONTROL_MASK) && !(mask & GDK_SHIFT_MASK))
nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_COPY; elseif ((mask & GDK_SHIFT_MASK) && (mask & GDK_CONTROL_MASK) )
nNewDropAction = css::datatransfer::dnd::DNDConstants::ACTION_LINK;
nNewDropAction &= nSourceActions;
#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);
}
// 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);
}
GtkSalFrame::IMHandler::~IMHandler()
{ // cancel an eventual event posted to begin preedit again
GtkSalFrame::getDisplay()->CancelInternalEvent( m_pFrame, &m_aInputEvent, SalEvent::ExtTextInput );
deleteIMContext();
}
// 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;
}
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( 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--;
}
// #i51353# update spot location on every key input since we cannot // know which key may activate a preedit choice window
updateIMSpotLocation(); if( aDel.isDeleted() ) returntrue;
if( bResult ) returntrue; 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() ) returntrue;
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--; returntrue;
}
if( bResult ) returntrue;
}
returnfalse;
}
/* 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
*/
/* 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];
/* 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];
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; elseif (nCursorPos > nUtf32Len)
nCursorPos = nUtf32Len;
// 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;
// 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);
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;
}
// First get the surrounding text
SalSurroundingTextRequestEvent aSurroundingTextEvt;
aSurroundingTextEvt.maText.clear();
aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
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;
}
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);
}
#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;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.