diff options
Diffstat (limited to '')
-rw-r--r-- | vcl/unx/gtk3/gtkdata.cxx | 984 |
1 files changed, 984 insertions, 0 deletions
diff --git a/vcl/unx/gtk3/gtkdata.cxx b/vcl/unx/gtk3/gtkdata.cxx new file mode 100644 index 000000000..cc8535ff2 --- /dev/null +++ b/vcl/unx/gtk3/gtkdata.cxx @@ -0,0 +1,984 @@ +/* -*- 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 <unistd.h> + +#include <stdio.h> +#include <stdlib.h> +#if defined(FREEBSD) || defined(NETBSD) +#include <sys/types.h> +#include <sys/time.h> +#endif +#include <unx/gtk/gtkbackend.hxx> +#include <unx/gtk/gtkdata.hxx> +#include <unx/gtk/gtkinst.hxx> +#include <unx/gtk/gtkframe.hxx> +#include <bitmaps.hlst> +#include <cursor_hotspots.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <osl/thread.h> +#include <osl/process.h> + +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +#include <chrono> + +using namespace vcl_sal; + +/*************************************************************** + * class GtkSalDisplay * + ***************************************************************/ + +GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) : + m_pSys( GtkSalSystem::GetSingleton() ), + m_pGdkDisplay( pDisplay ), + m_bStartupCompleted( false ) +{ + for(GdkCursor* & rpCsr : m_aCursors) + rpCsr = nullptr; + + if ( getenv( "SAL_IGNOREXERRORS" ) ) + GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap + + gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR); +} + +GtkSalDisplay::~GtkSalDisplay() +{ +#if !GTK_CHECK_VERSION(4, 0, 0) + if( !m_bStartupCompleted ) + gdk_notify_startup_complete(); + + for(GdkCursor* & rpCsr : m_aCursors) + if( rpCsr ) + gdk_cursor_unref( rpCsr ); +#endif +} + +#if GTK_CHECK_VERSION(4, 0, 0) + +static void signalMonitorsChanged(GListModel*, gpointer data) +{ + GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data); + pDisp->emitDisplayChanged(); +} + +#else + +static void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data ) +{ + GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data); + pDisp->screenSizeChanged( pScreen ); +} + +static void signalMonitorsChanged( GdkScreen* pScreen, gpointer data ) +{ + GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data); + pDisp->monitorsChanged( pScreen ); +} + +void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen ) +{ + m_pSys->countScreenMonitors(); + if (pScreen) + emitDisplayChanged(); +} + +void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen ) +{ + m_pSys->countScreenMonitors(); + if (pScreen) + emitDisplayChanged(); +} +#endif + +GdkCursor* GtkSalDisplay::getFromSvg(OUString const & name, int nXHot, int nYHot) +{ + GdkPixbuf* pPixBuf = load_icon_by_name(name); + assert(pPixBuf && "missing image?"); + if (!pPixBuf) + return nullptr; + +#if !GTK_CHECK_VERSION(4, 0, 0) + guint nDefaultCursorSize = gdk_display_get_default_cursor_size( m_pGdkDisplay ); + int nPixWidth = gdk_pixbuf_get_width(pPixBuf); + int nPixHeight = gdk_pixbuf_get_height(pPixBuf); + double fScalefactor = static_cast<double>(nDefaultCursorSize) / std::max(nPixWidth, nPixHeight); + GdkPixbuf* pScaledPixBuf = gdk_pixbuf_scale_simple(pPixBuf, + nPixWidth * fScalefactor, + nPixHeight * fScalefactor, + GDK_INTERP_HYPER); + g_object_unref(pPixBuf); + return gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf, + nXHot * fScalefactor, nYHot * fScalefactor); +#else + GdkTexture* pTexture = gdk_texture_new_for_pixbuf(pPixBuf); + g_object_unref(pPixBuf); + return gdk_cursor_new_from_texture(pTexture, nXHot, nYHot, nullptr); +#endif +} + +#define MAKE_CURSOR( vcl_name, name, name2 ) \ + case vcl_name: \ + pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \ + break + +#if !GTK_CHECK_VERSION(4, 0, 0) +#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \ + case vcl_name: \ + pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk3_name ); \ + break +#else +#define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \ + case vcl_name: \ + pCursor = gdk_cursor_new_from_name(css_name, nullptr); \ + break +#endif + +GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle ) +{ + if ( !m_aCursors[ ePointerStyle ] ) + { + GdkCursor *pCursor = nullptr; + + switch( ePointerStyle ) + { + MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR, "default" ); + MAP_BUILTIN( PointerStyle::Text, GDK_XTERM, "text" ); + MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW, "help" ); + MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR, "crosshair" ); + MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH, "wait" ); + + MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW, "n-resize" ); + MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW, "s-resize" ); + MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW, "w-resize" ); + MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW, "e-resize" ); + + MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER, "nw-resize" ); + MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER, "ne-resize" ); + MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" ); + MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" ); + + MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE, "n-resize" ); + MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE, "s-resize" ); + MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE, "w-resize" ); + MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE, "e-resize" ); + + MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER, "nw-resize" ); + MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER, "ne-resize" ); + MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" ); + MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" ); + + MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW, "col-resize" ); + MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW, "row-resize" ); + + MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2, "grab" ); + MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2, "grab" ); + +#if !GTK_CHECK_VERSION(4, 0, 0) + MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL, "" ); +#else + MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN ); +#endif + + MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW, "col-resize" ); + MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW, "row-resize" ); + + MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR, "move" ); + +#if !GTK_CHECK_VERSION(4, 0, 0) + MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL ); +#else + MAP_BUILTIN( PointerStyle::Null, 0, "none" ); +#endif + + MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY ); + MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL ); + MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA ); + MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA ); + MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE ); + MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE ); + MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES ); + MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES ); + +#if !GTK_CHECK_VERSION(4, 0, 0) + MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED ); +#else + MAP_BUILTIN( PointerStyle::NotAllowed, 0, "not-allowed" ); +#endif + + MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE ); + MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR ); + MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR ); + MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE ); + MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT ); + MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON ); + MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER ); + MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC ); + MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE ); + MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT ); + MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE ); + MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT ); + MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT ); + MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR ); + MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK ); + MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP ); + MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT ); + MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT ); + MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND ); + MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION ); + MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA ); + MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK ); + MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK ); + MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE ); + MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK ); + MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK ); + MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART ); + MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE ); + MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN ); + MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW ); + MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD ); + MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE ); + MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN ); + MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED ); + MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N ); + MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S ); + MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W ); + MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E ); + MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW ); + MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE ); + MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW ); + MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE ); + MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS ); + MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE ); + MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE ); + MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL ); + + // #i32329# + MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S ); + MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E ); + MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE ); + MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W ); + MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW ); + + MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE ); + MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE ); + MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS ); + + default: + SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" ); + break; + } + if( !pCursor ) + { +#if !GTK_CHECK_VERSION(4, 0, 0) + pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR ); +#else + pCursor = gdk_cursor_new_from_name("normal", nullptr); +#endif + } + + m_aCursors[ ePointerStyle ] = pCursor; + } + + return m_aCursors[ ePointerStyle ]; +} + +int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame ) +{ + GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame); + + if( !pFrame ) + { + if( m_pCapture ) + static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false ); + m_pCapture = nullptr; + return 0; + } + + if( m_pCapture ) + { + if( pFrame == m_pCapture ) + return 1; + static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false ); + } + + m_pCapture = pFrame; + pFrame->grabPointer( true, false, false ); + return 1; +} + +/********************************************************************** + * class GtkSalData * + **********************************************************************/ + +GtkSalData::GtkSalData() + : GenericUnixSalData() +{ + m_pUserEvent = nullptr; +} + +#if defined(GDK_WINDOWING_X11) +static XIOErrorHandler aOrigXIOErrorHandler = nullptr; + +extern "C" { + +static int XIOErrorHdl(Display *) +{ + fprintf(stderr, "X IO Error\n"); + _exit(1); + // avoid crashes in unrelated threads that still run while atexit + // handlers are in progress +} + +} +#endif + +GtkSalData::~GtkSalData() +{ + // sanity check: at this point nobody should be yielding, but wake them + // up anyway before the condition they're waiting on gets destroyed. + m_aDispatchCondition.set(); + + osl::MutexGuard g( m_aDispatchMutex ); + if (m_pUserEvent) + { + g_source_destroy (m_pUserEvent); + g_source_unref (m_pUserEvent); + m_pUserEvent = nullptr; + } +#if defined(GDK_WINDOWING_X11) + if (DLSYM_GDK_IS_X11_DISPLAY(gdk_display_get_default())) + XSetIOErrorHandler(aOrigXIOErrorHandler); +#endif +} + +void GtkSalData::Dispose() +{ + deInitNWF(); +} + +/// Allows events to be processed, returns true if we processed an event. +bool GtkSalData::Yield( bool bWait, bool bHandleAllCurrentEvents ) +{ + /* #i33212# only enter g_main_context_iteration in one thread at any one + * time, else one of them potentially will never end as long as there is + * another thread in there. Having only one yielding thread actually dispatch + * fits the vcl event model (see e.g. the generic plugin). + */ + bool bDispatchThread = false; + bool bWasEvent = false; + { + // release YieldMutex (and re-acquire at block end) + SolarMutexReleaser aReleaser; + if( m_aDispatchMutex.tryToAcquire() ) + bDispatchThread = true; + else if( ! bWait ) + { + return false; // someone else is waiting already, return + } + + if( bDispatchThread ) + { + int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1; + bool wasOneEvent = true; + while( nMaxEvents-- && wasOneEvent ) + { + wasOneEvent = g_main_context_iteration( nullptr, bWait && !bWasEvent ); + if( wasOneEvent ) + bWasEvent = true; + } + if (m_aException) + std::rethrow_exception(m_aException); + } + else if( bWait ) + { + /* #i41693# in case the dispatch thread hangs in join + * for this thread the condition will never be set + * workaround: timeout of 1 second an emergency exit + */ + // we are the dispatch thread + m_aDispatchCondition.reset(); + m_aDispatchCondition.wait(std::chrono::seconds(1)); + } + } + + if( bDispatchThread ) + { + m_aDispatchMutex.release(); + if( bWasEvent ) + m_aDispatchCondition.set(); // trigger non dispatch thread yields + } + + return bWasEvent; +} + +static GtkStyleProvider* CreateStyleProvider() +{ + /* + set a provider to: + + 1) allow certain widgets to have no padding + + 1.a) little close button in menubar to close back to start-center + 1.b) and small buttons in view->data sources (button.small-button) + 1.c.1) gtk3 small toolbar button in infobars (toolbar.small-button button) + 1.c.2) gtk4 small toolbar button in infobars (box.small-button button) + 1.d) comboboxes in the data browser for tdf#137695 (box#combobox button.small-button, + which would instead be combobox button.small-button if we didn't replace GtkComboBox, + see GtkInstanceComboBox for an explanation for why we do that) + 1.e) entry in the data browser for tdf#137695 (entry.small-button) + 1.f) spinbutton in the data browser tdf#141633 (spinbutton.small-button) + + 2) hide the unwanted active tab in an 'overflow' notebook of double-decker notebooks. + (tdf#122623) it's nigh impossible to have a GtkNotebook without an active (checked) tab, + so theme the unwanted tab into invisibility + */ + GtkCssProvider* pStyleProvider = gtk_css_provider_new(); + static const gchar data[] = + "button.small-button, toolbar.small-button button, box.small-button button, " + "combobox.small-button *.combo, box#combobox.small-button *.combo, entry.small-button, " + "spinbutton.small-button, spinbutton.small-button entry, spinbutton.small-button button { " + "padding: 0; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0;" + "border-width: 0; min-height: 0; min-width: 0; }" +#if GTK_CHECK_VERSION(4, 0, 0) + // we basically assumed during dialog design that the frame's were invisible, because + // they used to be in the default theme during gtk3 + "frame { border-style: none; }" +#endif + "notebook.overflow > header.top > tabs > tab:checked { " + "box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0;" + "border-image: none; border-image-width: 0 0 0 0;" + "background-image: none; background-color: transparent;" + "border-radius: 0 0 0 0; border-width: 0 0 0 0;" + "border-style: none; border-color: transparent;" + "opacity: 0; min-height: 0; min-width: 0; }" + // https://css-tricks.com/restart-css-animation/ + // This animation appears twice with two different names so we can change + // the class from "call_attention_1" to "call_attention_2" to restart the + // animation + "@keyframes shinkandrestore1 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }" + "@keyframes shinkandrestore2 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }" + " *.call_attention_1 {" + "animation-name: shinkandrestore1; animation-duration: 1s; " + "animation-timing-function: linear; animation-iteration-count: 2; }" + " *.call_attention_2 {" + "animation-name: shinkandrestore2; animation-duration: 1s; " + "animation-timing-function: linear; animation-iteration-count: 2; }"; + css_provider_load_from_data(pStyleProvider, data, -1); + return GTK_STYLE_PROVIDER(pStyleProvider); +} + +void GtkSalData::Init() +{ + SAL_INFO( "vcl.gtk", "GtkMainloop::Init()" ); + + /* + * open connection to X11 Display + * try in this order: + * o -display command line parameter, + * o $DISPLAY environment variable + * o default display + */ + + GdkDisplay *pGdkDisp = nullptr; + + // is there a -display command line parameter? + rtl_TextEncoding aEnc = osl_getThreadTextEncoding(); + int nParams = osl_getCommandArgCount(); + OString aDisplay; + OUString aParam, aBin; + char** pCmdLineAry = new char*[ nParams+1 ]; + osl_getExecutableFile( &aParam.pData ); + osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData ); + pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() ); + for (int i = 0; i < nParams; ++i) + { + osl_getCommandArg(i, &aParam.pData ); + OString aBParam( OUStringToOString( aParam, aEnc ) ); + + if( aParam == "-display" || aParam == "--display" ) + { + pCmdLineAry[i+1] = g_strdup( "--display" ); + osl_getCommandArg(i+1, &aParam.pData ); + aDisplay = OUStringToOString( aParam, aEnc ); + } + else + pCmdLineAry[i+1] = g_strdup( aBParam.getStr() ); + } + // add executable + nParams++; + + g_set_application_name(SalGenericSystem::getFrameClassName()); + + // Set consistent name of the root accessible + OUString aAppName = Application::GetAppName(); + if( !aAppName.isEmpty() ) + { + OString aPrgName = OUStringToOString(aAppName, aEnc); + g_set_prgname(aPrgName.getStr()); + } + + // init gtk/gdk +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_init_check(); +#else + gtk_init_check( &nParams, &pCmdLineAry ); +#endif + + for (int i = 0; i < nParams; ++i) + g_free( pCmdLineAry[i] ); + delete [] pCmdLineAry; + +#if OSL_DEBUG_LEVEL > 1 + if (g_getenv("SAL_DEBUG_UPDATES")) + gdk_window_set_debug_updates (TRUE); +#endif + + pGdkDisp = gdk_display_get_default(); + if ( !pGdkDisp ) + { + OUString aProgramFileURL; + osl_getExecutableFile( &aProgramFileURL.pData ); + OUString aProgramSystemPath; + osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData); + OString aProgramName = OUStringToOString( + aProgramSystemPath, + osl_getThreadTextEncoding() ); + fprintf( stderr, "%s X11 error: Can't open display: %s\n", + aProgramName.getStr(), aDisplay.getStr()); + fprintf( stderr, " Set DISPLAY environment variable, use -display option\n"); + fprintf( stderr, " or check permissions of your X-Server\n"); + fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n"); + fflush( stderr ); + exit(0); + } + + ErrorTrapPush(); + +#if defined(GDK_WINDOWING_X11) + if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp)) + aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl); +#endif + + GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp ); + SetDisplay( pDisplay ); + +#if GTK_CHECK_VERSION(4, 0, 0) + pDisplay->emitDisplayChanged(); + GListModel *pMonitors = gdk_display_get_monitors(pGdkDisp); + g_signal_connect(pMonitors, "items-changed", G_CALLBACK(signalMonitorsChanged), pDisplay); + + gtk_style_context_add_provider_for_display(pGdkDisp, CreateStyleProvider(), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); +#else + int nScreens = gdk_display_get_n_screens( pGdkDisp ); + for( int n = 0; n < nScreens; n++ ) + { + GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n ); + if (!pScreen) + continue; + + pDisplay->screenSizeChanged( pScreen ); + pDisplay->monitorsChanged( pScreen ); + // add signal handler to notify screen size changes + g_signal_connect( G_OBJECT(pScreen), "size-changed", + G_CALLBACK(signalScreenSizeChanged), pDisplay ); + g_signal_connect( G_OBJECT(pScreen), "monitors-changed", + G_CALLBACK(signalMonitorsChanged), pDisplay ); + + gtk_style_context_add_provider_for_screen(pScreen, CreateStyleProvider(), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + } +#endif +} + +void GtkSalData::ErrorTrapPush() +{ +#if GTK_CHECK_VERSION(4, 0, 0) +# if defined(GDK_WINDOWING_X11) + GdkDisplay* pGdkDisp = gdk_display_get_default(); + if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp)) + gdk_x11_display_error_trap_push(pGdkDisp); +# endif +#else + gdk_error_trap_push(); +#endif +} + +bool GtkSalData::ErrorTrapPop( bool bIgnoreError ) +{ +#if GTK_CHECK_VERSION(4, 0, 0) +# if defined(GDK_WINDOWING_X11) + GdkDisplay* pGdkDisp = gdk_display_get_default(); + if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp)) + { + if (bIgnoreError) + { + gdk_x11_display_error_trap_pop_ignored(pGdkDisp); // faster + return false; + } + return gdk_x11_display_error_trap_pop(pGdkDisp) != 0; + } +# endif + return false; +#else + if (bIgnoreError) + { + gdk_error_trap_pop_ignored (); // faster + return false; + } + return gdk_error_trap_pop () != 0; +#endif +} + +#if !GLIB_CHECK_VERSION(2,32,0) +#define G_SOURCE_REMOVE FALSE +#endif + +extern "C" { + + struct SalGtkTimeoutSource { + GSource aParent; + GTimeVal aFireTime; + GtkSalTimer *pInstance; + }; + + static void sal_gtk_timeout_defer( SalGtkTimeoutSource *pTSource ) + { + g_get_current_time( &pTSource->aFireTime ); + g_time_val_add( &pTSource->aFireTime, pTSource->pInstance->m_nTimeoutMS * 1000 ); + } + + static gboolean sal_gtk_timeout_expired( SalGtkTimeoutSource *pTSource, + gint *nTimeoutMS, GTimeVal const *pTimeNow ) + { + glong nDeltaSec = pTSource->aFireTime.tv_sec - pTimeNow->tv_sec; + glong nDeltaUSec = pTSource->aFireTime.tv_usec - pTimeNow->tv_usec; + if( nDeltaSec < 0 || ( nDeltaSec == 0 && nDeltaUSec < 0) ) + { + *nTimeoutMS = 0; + return true; + } + if( nDeltaUSec < 0 ) + { + nDeltaUSec += 1000000; + nDeltaSec -= 1; + } + // if the clock changes backwards we need to cope ... + if( o3tl::make_unsigned(nDeltaSec) > 1 + ( pTSource->pInstance->m_nTimeoutMS / 1000 ) ) + { + sal_gtk_timeout_defer( pTSource ); + return true; + } + + *nTimeoutMS = MIN( G_MAXINT, ( nDeltaSec * 1000 + (nDeltaUSec + 999) / 1000 ) ); + + return *nTimeoutMS == 0; + } + + static gboolean sal_gtk_timeout_prepare( GSource *pSource, gint *nTimeoutMS ) + { + SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource); + + GTimeVal aTimeNow; + g_get_current_time( &aTimeNow ); + + return sal_gtk_timeout_expired( pTSource, nTimeoutMS, &aTimeNow ); + } + + static gboolean sal_gtk_timeout_check( GSource *pSource ) + { + SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource); + + GTimeVal aTimeNow; + g_get_current_time( &aTimeNow ); + + return ( pTSource->aFireTime.tv_sec < aTimeNow.tv_sec || + ( pTSource->aFireTime.tv_sec == aTimeNow.tv_sec && + pTSource->aFireTime.tv_usec < aTimeNow.tv_usec ) ); + } + + static gboolean sal_gtk_timeout_dispatch( GSource *pSource, GSourceFunc, gpointer ) + { + SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource); + + if( !pTSource->pInstance ) + return FALSE; + + SolarMutexGuard aGuard; + + sal_gtk_timeout_defer( pTSource ); + + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->maSchedCtx.mpSalTimer ) + pSVData->maSchedCtx.mpSalTimer->CallCallback(); + + return G_SOURCE_REMOVE; + } + + static GSourceFuncs sal_gtk_timeout_funcs = + { + sal_gtk_timeout_prepare, + sal_gtk_timeout_check, + sal_gtk_timeout_dispatch, + nullptr, nullptr, nullptr + }; +} + +static SalGtkTimeoutSource * +create_sal_gtk_timeout( GtkSalTimer *pTimer ) +{ + GSource *pSource = g_source_new( &sal_gtk_timeout_funcs, sizeof( SalGtkTimeoutSource ) ); + SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource); + pTSource->pInstance = pTimer; + + // #i36226# timers should be executed with lower priority + // than XEvents like in generic plugin + g_source_set_priority( pSource, G_PRIORITY_LOW ); + g_source_set_can_recurse( pSource, true ); + g_source_set_callback( pSource, + /* unused dummy */ g_idle_remove_by_data, + nullptr, nullptr ); + g_source_attach( pSource, g_main_context_default() ); +#ifdef DBG_UTIL + g_source_set_name( pSource, "VCL timeout source" ); +#endif + + sal_gtk_timeout_defer( pTSource ); + + return pTSource; +} + +GtkSalTimer::GtkSalTimer() + : m_pTimeout(nullptr) + , m_nTimeoutMS(0) +{ +} + +GtkSalTimer::~GtkSalTimer() +{ + GetGtkInstance()->RemoveTimer(); + Stop(); +} + +bool GtkSalTimer::Expired() +{ + if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) ) + return false; + + gint nDummy = 0; + GTimeVal aTimeNow; + g_get_current_time( &aTimeNow ); + return !!sal_gtk_timeout_expired( m_pTimeout, &nDummy, &aTimeNow); +} + +void GtkSalTimer::Start( sal_uInt64 nMS ) +{ + // glib is not 64bit safe in this regard. + assert( nMS <= G_MAXINT ); + if ( nMS > G_MAXINT ) + nMS = G_MAXINT; + m_nTimeoutMS = nMS; // for restarting + Stop(); // FIXME: ideally re-use an existing m_pTimeout + m_pTimeout = create_sal_gtk_timeout( this ); +} + +void GtkSalTimer::Stop() +{ + if( m_pTimeout ) + { + g_source_destroy( &m_pTimeout->aParent ); + g_source_unref( &m_pTimeout->aParent ); + m_pTimeout = nullptr; + } +} + +extern "C" { + static gboolean call_userEventFn( void *data ) + { + SolarMutexGuard aGuard; + const SalGenericDisplay *pDisplay = GetGenericUnixSalData()->GetDisplay(); + if ( pDisplay ) + { + GtkSalDisplay *pThisDisplay = static_cast<GtkSalData *>(data)->GetGtkDisplay(); + assert(static_cast<const SalGenericDisplay *>(pThisDisplay) == pDisplay); + pThisDisplay->DispatchInternalEvent(); + } + return true; + } +} + +void GtkSalData::TriggerUserEventProcessing() +{ + if (m_pUserEvent) + g_main_context_wakeup (nullptr); // really needed ? + else // nothing pending anyway + { + m_pUserEvent = g_idle_source_new(); + // tdf#110737 set user-events to a lower priority than system redraw + // events, which is G_PRIORITY_HIGH_IDLE + 20, so presentations + // queue-redraw has a chance to be fulfilled + g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH_IDLE + 30); + g_source_set_can_recurse (m_pUserEvent, true); + g_source_set_callback (m_pUserEvent, call_userEventFn, + static_cast<gpointer>(this), nullptr); + g_source_attach (m_pUserEvent, g_main_context_default ()); + } +} + +void GtkSalData::TriggerAllUserEventsProcessed() +{ + assert( m_pUserEvent ); + g_source_destroy( m_pUserEvent ); + g_source_unref( m_pUserEvent ); + m_pUserEvent = nullptr; +} + +void GtkSalDisplay::TriggerUserEventProcessing() +{ + GetGtkSalData()->TriggerUserEventProcessing(); +} + +void GtkSalDisplay::TriggerAllUserEventsProcessed() +{ + GetGtkSalData()->TriggerAllUserEventsProcessed(); +} + +GtkWidget* GtkSalDisplay::findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const +{ + for (auto pSalFrame : m_aFrames ) + { + const SystemEnvData* pEnvData = pSalFrame->GetSystemData(); + if (pEnvData->GetWindowHandle(pSalFrame) == hWindow) + return GTK_WIDGET(pEnvData->pWidget); + } + return nullptr; +} + +void GtkSalDisplay::deregisterFrame( SalFrame* pFrame ) +{ + if( m_pCapture == pFrame ) + { + static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false ); + m_pCapture = nullptr; + } + SalGenericDisplay::deregisterFrame( pFrame ); +} + +namespace { + +struct ButtonOrder +{ + std::string_view m_aType; + int m_nPriority; +}; + +} + +int getButtonPriority(std::string_view rType) +{ + static const size_t N_TYPES = 8; + static const ButtonOrder aDiscardCancelSave[N_TYPES] = + { + { "discard", 0 }, + { "cancel", 1 }, + { "close", 1 }, + { "no", 2 }, + { "open", 3 }, + { "save", 3 }, + { "yes", 3 }, + { "ok", 3 } + }; + + static const ButtonOrder aSaveDiscardCancel[N_TYPES] = + { + { "open", 0 }, + { "save", 0 }, + { "yes", 0 }, + { "ok", 0 }, + { "discard", 1 }, + { "no", 1 }, + { "cancel", 2 }, + { "close", 2 } + }; + + const ButtonOrder* pOrder = &aDiscardCancelSave[0]; + + const OUString &rEnv = Application::GetDesktopEnvironment(); + + if (rEnv.equalsIgnoreAsciiCase("windows") || + rEnv.equalsIgnoreAsciiCase("tde") || + rEnv.startsWithIgnoreAsciiCase("kde")) + { + pOrder = &aSaveDiscardCancel[0]; + } + + for (size_t i = 0; i < N_TYPES; ++i, ++pOrder) + { + if (rType == pOrder->m_aType) + return pOrder->m_nPriority; + } + + return -1; +} + +void container_remove(GtkWidget* pContainer, GtkWidget* pChild) +{ +#if !GTK_CHECK_VERSION(4, 0, 0) + gtk_container_remove(GTK_CONTAINER(pContainer), pChild); +#else + assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) || + GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer)); + if (GTK_IS_BOX(pContainer)) + gtk_box_remove(GTK_BOX(pContainer), pChild); + else if (GTK_IS_GRID(pContainer)) + gtk_grid_remove(GTK_GRID(pContainer), pChild); + else if (GTK_IS_POPOVER(pContainer)) + gtk_popover_set_child(GTK_POPOVER(pContainer), nullptr); + else if (GTK_IS_WINDOW(pContainer)) + gtk_window_set_child(GTK_WINDOW(pContainer), nullptr); + else if (GTK_IS_FIXED(pContainer)) + gtk_fixed_remove(GTK_FIXED(pContainer), pChild); +#endif +} + +void container_add(GtkWidget* pContainer, GtkWidget* pChild) +{ +#if !GTK_CHECK_VERSION(4, 0, 0) + gtk_container_add(GTK_CONTAINER(pContainer), pChild); +#else + assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) || + GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer)); + if (GTK_IS_BOX(pContainer)) + gtk_box_append(GTK_BOX(pContainer), pChild); + else if (GTK_IS_GRID(pContainer)) + gtk_grid_attach(GTK_GRID(pContainer), pChild, 0, 0, 1, 1); + else if (GTK_IS_POPOVER(pContainer)) + gtk_popover_set_child(GTK_POPOVER(pContainer), pChild); + else if (GTK_IS_WINDOW(pContainer)) + gtk_window_set_child(GTK_WINDOW(pContainer), pChild); + else if (GTK_IS_FIXED(pContainer)) + gtk_fixed_put(GTK_FIXED(pContainer), pChild, 0, 0); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |