bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / unx / gtk3 / gtkdata.cxx
blobf595369f8b5384abb411366db3d4b693792ed9bf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <unistd.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #if defined(FREEBSD) || defined(NETBSD)
25 #include <sys/types.h>
26 #include <sys/time.h>
27 #endif
28 #include <unx/gtk/gtkbackend.hxx>
29 #include <unx/gtk/gtkdata.hxx>
30 #include <unx/gtk/gtkinst.hxx>
31 #include <unx/gtk/gtkframe.hxx>
32 #include <bitmaps.hlst>
33 #include <cursor_hotspots.hxx>
34 #include <o3tl/safeint.hxx>
35 #include <o3tl/string_view.hxx>
36 #include <osl/thread.h>
37 #include <osl/process.h>
39 #include <vcl/svapp.hxx>
40 #include <sal/log.hxx>
42 #include <chrono>
44 using namespace vcl_sal;
46 /***************************************************************
47 * class GtkSalDisplay *
48 ***************************************************************/
50 GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay ) :
51 m_pSys( GtkSalSystem::GetSingleton() ),
52 m_pGdkDisplay( pDisplay ),
53 m_bStartupCompleted( false )
55 for(GdkCursor* & rpCsr : m_aCursors)
56 rpCsr = nullptr;
58 if ( getenv( "SAL_IGNOREXERRORS" ) )
59 GetGenericUnixSalData()->ErrorTrapPush(); // and leak the trap
61 gtk_widget_set_default_direction(AllSettings::GetLayoutRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
64 GtkSalDisplay::~GtkSalDisplay()
66 #if !GTK_CHECK_VERSION(4, 0, 0)
67 if( !m_bStartupCompleted )
68 gdk_notify_startup_complete();
70 for(GdkCursor* & rpCsr : m_aCursors)
71 if( rpCsr )
72 gdk_cursor_unref( rpCsr );
73 #endif
76 #if GTK_CHECK_VERSION(4, 0, 0)
78 static void signalMonitorsChanged(GListModel*, gpointer data)
80 GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
81 pDisp->emitDisplayChanged();
84 #else
86 static void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data )
88 GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
89 pDisp->screenSizeChanged( pScreen );
92 static void signalMonitorsChanged( GdkScreen* pScreen, gpointer data )
94 GtkSalDisplay* pDisp = static_cast<GtkSalDisplay*>(data);
95 pDisp->monitorsChanged( pScreen );
98 void GtkSalDisplay::screenSizeChanged( GdkScreen const * pScreen )
100 m_pSys->countScreenMonitors();
101 if (pScreen)
102 emitDisplayChanged();
105 void GtkSalDisplay::monitorsChanged( GdkScreen const * pScreen )
107 m_pSys->countScreenMonitors();
108 if (pScreen)
109 emitDisplayChanged();
111 #endif
113 GdkCursor* GtkSalDisplay::getFromSvg(OUString const & name, int nXHot, int nYHot)
115 GdkPixbuf* pPixBuf = load_icon_by_name(name);
116 assert(pPixBuf && "missing image?");
117 if (!pPixBuf)
118 return nullptr;
120 #if !GTK_CHECK_VERSION(4, 0, 0)
121 guint nDefaultCursorSize = gdk_display_get_default_cursor_size( m_pGdkDisplay );
122 int nPixWidth = gdk_pixbuf_get_width(pPixBuf);
123 int nPixHeight = gdk_pixbuf_get_height(pPixBuf);
124 double fScalefactor = static_cast<double>(nDefaultCursorSize) / std::max(nPixWidth, nPixHeight);
125 GdkPixbuf* pScaledPixBuf = gdk_pixbuf_scale_simple(pPixBuf,
126 nPixWidth * fScalefactor,
127 nPixHeight * fScalefactor,
128 GDK_INTERP_HYPER);
129 g_object_unref(pPixBuf);
130 return gdk_cursor_new_from_pixbuf(m_pGdkDisplay, pScaledPixBuf,
131 nXHot * fScalefactor, nYHot * fScalefactor);
132 #else
133 GdkTexture* pTexture = gdk_texture_new_for_pixbuf(pPixBuf);
134 g_object_unref(pPixBuf);
135 return gdk_cursor_new_from_texture(pTexture, nXHot, nYHot, nullptr);
136 #endif
139 #define MAKE_CURSOR( vcl_name, name, name2 ) \
140 case vcl_name: \
141 pCursor = getFromSvg(name2, name##curs_x_hot, name##curs_y_hot); \
142 break
144 #if !GTK_CHECK_VERSION(4, 0, 0)
145 #define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
146 case vcl_name: \
147 pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk3_name ); \
148 break
149 #else
150 #define MAP_BUILTIN( vcl_name, gdk3_name, css_name ) \
151 case vcl_name: \
152 pCursor = gdk_cursor_new_from_name(css_name, nullptr); \
153 break
154 #endif
156 GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
158 if ( !m_aCursors[ ePointerStyle ] )
160 GdkCursor *pCursor = nullptr;
162 switch( ePointerStyle )
164 MAP_BUILTIN( PointerStyle::Arrow, GDK_LEFT_PTR, "default" );
165 MAP_BUILTIN( PointerStyle::Text, GDK_XTERM, "text" );
166 MAP_BUILTIN( PointerStyle::Help, GDK_QUESTION_ARROW, "help" );
167 MAP_BUILTIN( PointerStyle::Cross, GDK_CROSSHAIR, "crosshair" );
168 MAP_BUILTIN( PointerStyle::Wait, GDK_WATCH, "wait" );
170 MAP_BUILTIN( PointerStyle::NSize, GDK_SB_V_DOUBLE_ARROW, "n-resize" );
171 MAP_BUILTIN( PointerStyle::SSize, GDK_SB_V_DOUBLE_ARROW, "s-resize" );
172 MAP_BUILTIN( PointerStyle::WSize, GDK_SB_H_DOUBLE_ARROW, "w-resize" );
173 MAP_BUILTIN( PointerStyle::ESize, GDK_SB_H_DOUBLE_ARROW, "e-resize" );
175 MAP_BUILTIN( PointerStyle::NWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
176 MAP_BUILTIN( PointerStyle::NESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
177 MAP_BUILTIN( PointerStyle::SWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
178 MAP_BUILTIN( PointerStyle::SESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
180 MAP_BUILTIN( PointerStyle::WindowNSize, GDK_TOP_SIDE, "n-resize" );
181 MAP_BUILTIN( PointerStyle::WindowSSize, GDK_BOTTOM_SIDE, "s-resize" );
182 MAP_BUILTIN( PointerStyle::WindowWSize, GDK_LEFT_SIDE, "w-resize" );
183 MAP_BUILTIN( PointerStyle::WindowESize, GDK_RIGHT_SIDE, "e-resize" );
185 MAP_BUILTIN( PointerStyle::WindowNWSize, GDK_TOP_LEFT_CORNER, "nw-resize" );
186 MAP_BUILTIN( PointerStyle::WindowNESize, GDK_TOP_RIGHT_CORNER, "ne-resize" );
187 MAP_BUILTIN( PointerStyle::WindowSWSize, GDK_BOTTOM_LEFT_CORNER, "sw-resize" );
188 MAP_BUILTIN( PointerStyle::WindowSESize, GDK_BOTTOM_RIGHT_CORNER, "se-resize" );
190 MAP_BUILTIN( PointerStyle::HSizeBar, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
191 MAP_BUILTIN( PointerStyle::VSizeBar, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
193 MAP_BUILTIN( PointerStyle::RefHand, GDK_HAND2, "grab" );
194 MAP_BUILTIN( PointerStyle::Hand, GDK_HAND2, "grab" );
196 #if !GTK_CHECK_VERSION(4, 0, 0)
197 MAP_BUILTIN( PointerStyle::Pen, GDK_PENCIL, "" );
198 #else
199 MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN );
200 #endif
202 MAP_BUILTIN( PointerStyle::HSplit, GDK_SB_H_DOUBLE_ARROW, "col-resize" );
203 MAP_BUILTIN( PointerStyle::VSplit, GDK_SB_V_DOUBLE_ARROW, "row-resize" );
205 MAP_BUILTIN( PointerStyle::Move, GDK_FLEUR, "move" );
207 #if !GTK_CHECK_VERSION(4, 0, 0)
208 MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
209 #else
210 MAP_BUILTIN( PointerStyle::Null, 0, "none" );
211 #endif
213 MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY );
214 MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL );
215 MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA );
216 MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA );
217 MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE );
218 MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE );
219 MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES );
220 MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES );
222 #if !GTK_CHECK_VERSION(4, 0, 0)
223 MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
224 #else
225 MAP_BUILTIN( PointerStyle::NotAllowed, 0, "not-allowed" );
226 #endif
228 MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE );
229 MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR );
230 MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR );
231 MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE );
232 MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT );
233 MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON );
234 MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER );
235 MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC );
236 MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE );
237 MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT );
238 MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE );
239 MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT );
240 MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT );
241 MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR );
242 MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK );
243 MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP );
244 MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT );
245 MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT );
246 MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND );
247 MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION );
248 MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA );
249 MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK );
250 MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK );
251 MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE );
252 MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK );
253 MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK );
254 MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART );
255 MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE );
256 MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN );
257 MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW );
258 MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD );
259 MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE );
260 MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN );
261 MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED );
262 MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N );
263 MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S );
264 MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W );
265 MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E );
266 MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW );
267 MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE );
268 MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW );
269 MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE );
270 MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS );
271 MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE );
272 MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE );
273 MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL );
275 // #i32329#
276 MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S );
277 MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E );
278 MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE );
279 MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W );
280 MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW );
282 MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE );
283 MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE );
284 MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS );
286 default:
287 SAL_WARN( "vcl.gtk", "pointer " << static_cast<int>(ePointerStyle) << "not implemented" );
288 break;
290 if( !pCursor )
292 #if !GTK_CHECK_VERSION(4, 0, 0)
293 pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );
294 #else
295 pCursor = gdk_cursor_new_from_name("normal", nullptr);
296 #endif
299 m_aCursors[ ePointerStyle ] = pCursor;
302 return m_aCursors[ ePointerStyle ];
305 int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame )
307 GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame);
309 if( !pFrame )
311 if( m_pCapture )
312 static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
313 m_pCapture = nullptr;
314 return 0;
317 if( m_pCapture )
319 if( pFrame == m_pCapture )
320 return 1;
321 static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
324 m_pCapture = pFrame;
325 pFrame->grabPointer( true, false, false );
326 return 1;
329 /**********************************************************************
330 * class GtkSalData *
331 **********************************************************************/
333 GtkSalData::GtkSalData()
334 : GenericUnixSalData()
336 m_pUserEvent = nullptr;
339 #if defined(GDK_WINDOWING_X11)
340 static XIOErrorHandler aOrigXIOErrorHandler = nullptr;
342 extern "C" {
344 static int XIOErrorHdl(Display *)
346 fprintf(stderr, "X IO Error\n");
347 _exit(1);
348 // avoid crashes in unrelated threads that still run while atexit
349 // handlers are in progress
353 #endif
355 GtkSalData::~GtkSalData()
357 // sanity check: at this point nobody should be yielding, but wake them
358 // up anyway before the condition they're waiting on gets destroyed.
359 m_aDispatchCondition.set();
361 osl::MutexGuard g( m_aDispatchMutex );
362 if (m_pUserEvent)
364 g_source_destroy (m_pUserEvent);
365 g_source_unref (m_pUserEvent);
366 m_pUserEvent = nullptr;
368 #if defined(GDK_WINDOWING_X11)
369 if (DLSYM_GDK_IS_X11_DISPLAY(gdk_display_get_default()))
370 XSetIOErrorHandler(aOrigXIOErrorHandler);
371 #endif
374 void GtkSalData::Dispose()
376 deInitNWF();
379 /// Allows events to be processed, returns true if we processed an event.
380 bool GtkSalData::Yield( bool bWait, bool bHandleAllCurrentEvents )
382 /* #i33212# only enter g_main_context_iteration in one thread at any one
383 * time, else one of them potentially will never end as long as there is
384 * another thread in there. Having only one yielding thread actually dispatch
385 * fits the vcl event model (see e.g. the generic plugin).
387 bool bDispatchThread = false;
388 bool bWasEvent = false;
390 // release YieldMutex (and re-acquire at block end)
391 SolarMutexReleaser aReleaser;
392 if( m_aDispatchMutex.tryToAcquire() )
393 bDispatchThread = true;
394 else if( ! bWait )
396 return false; // someone else is waiting already, return
399 if( bDispatchThread )
401 int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
402 bool wasOneEvent = true;
403 while( nMaxEvents-- && wasOneEvent )
405 wasOneEvent = g_main_context_iteration( nullptr, bWait && !bWasEvent );
406 if( wasOneEvent )
407 bWasEvent = true;
409 if (m_aException)
410 std::rethrow_exception(m_aException);
412 else if( bWait )
414 /* #i41693# in case the dispatch thread hangs in join
415 * for this thread the condition will never be set
416 * workaround: timeout of 1 second an emergency exit
418 // we are the dispatch thread
419 m_aDispatchCondition.reset();
420 m_aDispatchCondition.wait(std::chrono::seconds(1));
424 if( bDispatchThread )
426 m_aDispatchMutex.release();
427 if( bWasEvent )
428 m_aDispatchCondition.set(); // trigger non dispatch thread yields
431 return bWasEvent;
434 static GtkStyleProvider* CreateStyleProvider()
437 set a provider to:
439 1) allow certain widgets to have no padding
441 1.a) little close button in menubar to close back to start-center
442 1.b) and small buttons in view->data sources (button.small-button)
443 1.c.1) gtk3 small toolbar button in infobars (toolbar.small-button button)
444 1.c.2) gtk4 small toolbar button in infobars (box.small-button button)
445 1.d) comboboxes in the data browser for tdf#137695 (box#combobox button.small-button,
446 which would instead be combobox button.small-button if we didn't replace GtkComboBox,
447 see GtkInstanceComboBox for an explanation for why we do that)
448 1.e) entry in the data browser for tdf#137695 (entry.small-button)
449 1.f) spinbutton in the data browser tdf#141633 (spinbutton.small-button)
451 2) hide the unwanted active tab in an 'overflow' notebook of double-decker notebooks.
452 (tdf#122623) it's nigh impossible to have a GtkNotebook without an active (checked) tab,
453 so theme the unwanted tab into invisibility
455 GtkCssProvider* pStyleProvider = gtk_css_provider_new();
456 static const gchar data[] =
457 "button.small-button, toolbar.small-button button, box.small-button button, "
458 "combobox.small-button *.combo, box#combobox.small-button *.combo, entry.small-button, "
459 "spinbutton.small-button, spinbutton.small-button entry, spinbutton.small-button button { "
460 "padding: 0; margin-left: 0; margin-right: 0; margin-top: 0; margin-bottom: 0;"
461 "border-width: 0; min-height: 0; min-width: 0; }"
462 #if GTK_CHECK_VERSION(4, 0, 0)
463 // we basically assumed during dialog design that the frame's were invisible, because
464 // they used to be in the default theme during gtk3
465 "frame { border-style: none; }"
466 #endif
467 "notebook.overflow > header.top > tabs > tab:checked { "
468 "box-shadow: none; padding: 0 0 0 0; margin: 0 0 0 0;"
469 "border-image: none; border-image-width: 0 0 0 0;"
470 "background-image: none; background-color: transparent;"
471 "border-radius: 0 0 0 0; border-width: 0 0 0 0;"
472 "border-style: none; border-color: transparent;"
473 "opacity: 0; min-height: 0; min-width: 0; }"
474 // https://css-tricks.com/restart-css-animation/
475 // This animation appears twice with two different names so we can change
476 // the class from "call_attention_1" to "call_attention_2" to restart the
477 // animation
478 "@keyframes shinkandrestore1 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
479 "@keyframes shinkandrestore2 { 50% { margin-left: 15px; margin-right: 15px; opacity: 0.5; } }"
480 " *.call_attention_1 {"
481 "animation-name: shinkandrestore1; animation-duration: 1s; "
482 "animation-timing-function: linear; animation-iteration-count: 2; }"
483 " *.call_attention_2 {"
484 "animation-name: shinkandrestore2; animation-duration: 1s; "
485 "animation-timing-function: linear; animation-iteration-count: 2; }";
486 css_provider_load_from_data(pStyleProvider, data, -1);
487 return GTK_STYLE_PROVIDER(pStyleProvider);
490 void GtkSalData::Init()
492 SAL_INFO( "vcl.gtk", "GtkMainloop::Init()" );
495 * open connection to X11 Display
496 * try in this order:
497 * o -display command line parameter,
498 * o $DISPLAY environment variable
499 * o default display
502 GdkDisplay *pGdkDisp = nullptr;
504 // is there a -display command line parameter?
505 rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
506 int nParams = osl_getCommandArgCount();
507 OString aDisplay;
508 OUString aParam, aBin;
509 char** pCmdLineAry = new char*[ nParams+1 ];
510 osl_getExecutableFile( &aParam.pData );
511 osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
512 pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() );
513 for (int i = 0; i < nParams; ++i)
515 osl_getCommandArg(i, &aParam.pData );
516 OString aBParam( OUStringToOString( aParam, aEnc ) );
518 if( aParam == "-display" || aParam == "--display" )
520 pCmdLineAry[i+1] = g_strdup( "--display" );
521 osl_getCommandArg(i+1, &aParam.pData );
522 aDisplay = OUStringToOString( aParam, aEnc );
524 else
525 pCmdLineAry[i+1] = g_strdup( aBParam.getStr() );
527 // add executable
528 nParams++;
530 g_set_application_name(SalGenericSystem::getFrameClassName());
532 // Set consistent name of the root accessible
533 OUString aAppName = Application::GetAppName();
534 if( !aAppName.isEmpty() )
536 OString aPrgName = OUStringToOString(aAppName, aEnc);
537 g_set_prgname(aPrgName.getStr());
540 // init gtk/gdk
541 #if GTK_CHECK_VERSION(4, 0, 0)
542 gtk_init_check();
543 #else
544 gtk_init_check( &nParams, &pCmdLineAry );
545 #endif
547 for (int i = 0; i < nParams; ++i)
548 g_free( pCmdLineAry[i] );
549 delete [] pCmdLineAry;
551 #if OSL_DEBUG_LEVEL > 1
552 if (g_getenv("SAL_DEBUG_UPDATES"))
553 gdk_window_set_debug_updates (TRUE);
554 #endif
556 pGdkDisp = gdk_display_get_default();
557 if ( !pGdkDisp )
559 OUString aProgramFileURL;
560 osl_getExecutableFile( &aProgramFileURL.pData );
561 OUString aProgramSystemPath;
562 osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
563 OString aProgramName = OUStringToOString(
564 aProgramSystemPath,
565 osl_getThreadTextEncoding() );
566 fprintf( stderr, "%s X11 error: Can't open display: %s\n",
567 aProgramName.getStr(), aDisplay.getStr());
568 fprintf( stderr, " Set DISPLAY environment variable, use -display option\n");
569 fprintf( stderr, " or check permissions of your X-Server\n");
570 fprintf( stderr, " (See \"man X\" resp. \"man xhost\" for details)\n");
571 fflush( stderr );
572 exit(0);
575 ErrorTrapPush();
577 #if defined(GDK_WINDOWING_X11)
578 if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
579 aOrigXIOErrorHandler = XSetIOErrorHandler(XIOErrorHdl);
580 #endif
582 GtkSalDisplay *pDisplay = new GtkSalDisplay( pGdkDisp );
583 SetDisplay( pDisplay );
585 #if GTK_CHECK_VERSION(4, 0, 0)
586 pDisplay->emitDisplayChanged();
587 GListModel *pMonitors = gdk_display_get_monitors(pGdkDisp);
588 g_signal_connect(pMonitors, "items-changed", G_CALLBACK(signalMonitorsChanged), pDisplay);
590 gtk_style_context_add_provider_for_display(pGdkDisp, CreateStyleProvider(),
591 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
592 #else
593 int nScreens = gdk_display_get_n_screens( pGdkDisp );
594 for( int n = 0; n < nScreens; n++ )
596 GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n );
597 if (!pScreen)
598 continue;
600 pDisplay->screenSizeChanged( pScreen );
601 pDisplay->monitorsChanged( pScreen );
602 // add signal handler to notify screen size changes
603 g_signal_connect( G_OBJECT(pScreen), "size-changed",
604 G_CALLBACK(signalScreenSizeChanged), pDisplay );
605 g_signal_connect( G_OBJECT(pScreen), "monitors-changed",
606 G_CALLBACK(signalMonitorsChanged), pDisplay );
608 gtk_style_context_add_provider_for_screen(pScreen, CreateStyleProvider(),
609 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
611 #endif
614 void GtkSalData::ErrorTrapPush()
616 #if GTK_CHECK_VERSION(4, 0, 0)
617 # if defined(GDK_WINDOWING_X11)
618 GdkDisplay* pGdkDisp = gdk_display_get_default();
619 if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
620 gdk_x11_display_error_trap_push(pGdkDisp);
621 # endif
622 #else
623 gdk_error_trap_push();
624 #endif
627 bool GtkSalData::ErrorTrapPop( bool bIgnoreError )
629 #if GTK_CHECK_VERSION(4, 0, 0)
630 # if defined(GDK_WINDOWING_X11)
631 GdkDisplay* pGdkDisp = gdk_display_get_default();
632 if (DLSYM_GDK_IS_X11_DISPLAY(pGdkDisp))
634 if (bIgnoreError)
636 gdk_x11_display_error_trap_pop_ignored(pGdkDisp); // faster
637 return false;
639 return gdk_x11_display_error_trap_pop(pGdkDisp) != 0;
641 # endif
642 return false;
643 #else
644 if (bIgnoreError)
646 gdk_error_trap_pop_ignored (); // faster
647 return false;
649 return gdk_error_trap_pop () != 0;
650 #endif
653 #if !GLIB_CHECK_VERSION(2,32,0)
654 #define G_SOURCE_REMOVE FALSE
655 #endif
657 extern "C" {
659 struct SalGtkTimeoutSource {
660 GSource aParent;
661 GTimeVal aFireTime;
662 GtkSalTimer *pInstance;
665 static void sal_gtk_timeout_defer( SalGtkTimeoutSource *pTSource )
667 g_get_current_time( &pTSource->aFireTime );
668 g_time_val_add( &pTSource->aFireTime, pTSource->pInstance->m_nTimeoutMS * 1000 );
671 static gboolean sal_gtk_timeout_expired( SalGtkTimeoutSource *pTSource,
672 gint *nTimeoutMS, GTimeVal const *pTimeNow )
674 glong nDeltaSec = pTSource->aFireTime.tv_sec - pTimeNow->tv_sec;
675 glong nDeltaUSec = pTSource->aFireTime.tv_usec - pTimeNow->tv_usec;
676 if( nDeltaSec < 0 || ( nDeltaSec == 0 && nDeltaUSec < 0) )
678 *nTimeoutMS = 0;
679 return true;
681 if( nDeltaUSec < 0 )
683 nDeltaUSec += 1000000;
684 nDeltaSec -= 1;
686 // if the clock changes backwards we need to cope ...
687 if( o3tl::make_unsigned(nDeltaSec) > 1 + ( pTSource->pInstance->m_nTimeoutMS / 1000 ) )
689 sal_gtk_timeout_defer( pTSource );
690 return true;
693 *nTimeoutMS = MIN( G_MAXINT, ( nDeltaSec * 1000 + (nDeltaUSec + 999) / 1000 ) );
695 return *nTimeoutMS == 0;
698 static gboolean sal_gtk_timeout_prepare( GSource *pSource, gint *nTimeoutMS )
700 SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
702 GTimeVal aTimeNow;
703 g_get_current_time( &aTimeNow );
705 return sal_gtk_timeout_expired( pTSource, nTimeoutMS, &aTimeNow );
708 static gboolean sal_gtk_timeout_check( GSource *pSource )
710 SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
712 GTimeVal aTimeNow;
713 g_get_current_time( &aTimeNow );
715 return ( pTSource->aFireTime.tv_sec < aTimeNow.tv_sec ||
716 ( pTSource->aFireTime.tv_sec == aTimeNow.tv_sec &&
717 pTSource->aFireTime.tv_usec < aTimeNow.tv_usec ) );
720 static gboolean sal_gtk_timeout_dispatch( GSource *pSource, GSourceFunc, gpointer )
722 SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
724 if( !pTSource->pInstance )
725 return FALSE;
727 SolarMutexGuard aGuard;
729 sal_gtk_timeout_defer( pTSource );
731 ImplSVData* pSVData = ImplGetSVData();
732 if( pSVData->maSchedCtx.mpSalTimer )
733 pSVData->maSchedCtx.mpSalTimer->CallCallback();
735 return G_SOURCE_REMOVE;
738 static GSourceFuncs sal_gtk_timeout_funcs =
740 sal_gtk_timeout_prepare,
741 sal_gtk_timeout_check,
742 sal_gtk_timeout_dispatch,
743 nullptr, nullptr, nullptr
747 static SalGtkTimeoutSource *
748 create_sal_gtk_timeout( GtkSalTimer *pTimer )
750 GSource *pSource = g_source_new( &sal_gtk_timeout_funcs, sizeof( SalGtkTimeoutSource ) );
751 SalGtkTimeoutSource *pTSource = reinterpret_cast<SalGtkTimeoutSource *>(pSource);
752 pTSource->pInstance = pTimer;
754 // #i36226# timers should be executed with lower priority
755 // than XEvents like in generic plugin
756 g_source_set_priority( pSource, G_PRIORITY_LOW );
757 g_source_set_can_recurse( pSource, true );
758 g_source_set_callback( pSource,
759 /* unused dummy */ g_idle_remove_by_data,
760 nullptr, nullptr );
761 g_source_attach( pSource, g_main_context_default() );
762 #ifdef DBG_UTIL
763 g_source_set_name( pSource, "VCL timeout source" );
764 #endif
766 sal_gtk_timeout_defer( pTSource );
768 return pTSource;
771 GtkSalTimer::GtkSalTimer()
772 : m_pTimeout(nullptr)
773 , m_nTimeoutMS(0)
777 GtkSalTimer::~GtkSalTimer()
779 GetGtkInstance()->RemoveTimer();
780 Stop();
783 bool GtkSalTimer::Expired()
785 if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) )
786 return false;
788 gint nDummy = 0;
789 GTimeVal aTimeNow;
790 g_get_current_time( &aTimeNow );
791 return !!sal_gtk_timeout_expired( m_pTimeout, &nDummy, &aTimeNow);
794 void GtkSalTimer::Start( sal_uInt64 nMS )
796 // glib is not 64bit safe in this regard.
797 assert( nMS <= G_MAXINT );
798 if ( nMS > G_MAXINT )
799 nMS = G_MAXINT;
800 m_nTimeoutMS = nMS; // for restarting
801 Stop(); // FIXME: ideally re-use an existing m_pTimeout
802 m_pTimeout = create_sal_gtk_timeout( this );
805 void GtkSalTimer::Stop()
807 if( m_pTimeout )
809 g_source_destroy( &m_pTimeout->aParent );
810 g_source_unref( &m_pTimeout->aParent );
811 m_pTimeout = nullptr;
815 extern "C" {
816 static gboolean call_userEventFn( void *data )
818 SolarMutexGuard aGuard;
819 const SalGenericDisplay *pDisplay = GetGenericUnixSalData()->GetDisplay();
820 if ( pDisplay )
822 GtkSalDisplay *pThisDisplay = static_cast<GtkSalData *>(data)->GetGtkDisplay();
823 assert(static_cast<const SalGenericDisplay *>(pThisDisplay) == pDisplay);
824 pThisDisplay->DispatchInternalEvent();
826 return true;
830 void GtkSalData::TriggerUserEventProcessing()
832 if (m_pUserEvent)
833 g_main_context_wakeup (nullptr); // really needed ?
834 else // nothing pending anyway
836 m_pUserEvent = g_idle_source_new();
837 // tdf#110737 set user-events to a lower priority than system redraw
838 // events, which is G_PRIORITY_HIGH_IDLE + 20, so presentations
839 // queue-redraw has a chance to be fulfilled
840 g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH_IDLE + 30);
841 g_source_set_can_recurse (m_pUserEvent, true);
842 g_source_set_callback (m_pUserEvent, call_userEventFn,
843 static_cast<gpointer>(this), nullptr);
844 g_source_attach (m_pUserEvent, g_main_context_default ());
848 void GtkSalData::TriggerAllUserEventsProcessed()
850 assert( m_pUserEvent );
851 g_source_destroy( m_pUserEvent );
852 g_source_unref( m_pUserEvent );
853 m_pUserEvent = nullptr;
856 void GtkSalDisplay::TriggerUserEventProcessing()
858 GetGtkSalData()->TriggerUserEventProcessing();
861 void GtkSalDisplay::TriggerAllUserEventsProcessed()
863 GetGtkSalData()->TriggerAllUserEventsProcessed();
866 GtkWidget* GtkSalDisplay::findGtkWidgetForNativeHandle(sal_uIntPtr hWindow) const
868 for (auto pSalFrame : m_aFrames )
870 const SystemEnvData* pEnvData = pSalFrame->GetSystemData();
871 if (pEnvData->GetWindowHandle(pSalFrame) == hWindow)
872 return GTK_WIDGET(pEnvData->pWidget);
874 return nullptr;
877 void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
879 if( m_pCapture == pFrame )
881 static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( false, false, false );
882 m_pCapture = nullptr;
884 SalGenericDisplay::deregisterFrame( pFrame );
887 namespace {
889 struct ButtonOrder
891 std::u16string_view m_aType;
892 int m_nPriority;
897 int getButtonPriority(std::u16string_view rType)
899 static const size_t N_TYPES = 8;
900 static const ButtonOrder aDiscardCancelSave[N_TYPES] =
902 { u"discard", 0 },
903 { u"cancel", 1 },
904 { u"close", 1 },
905 { u"no", 2 },
906 { u"open", 3 },
907 { u"save", 3 },
908 { u"yes", 3 },
909 { u"ok", 3 }
912 static const ButtonOrder aSaveDiscardCancel[N_TYPES] =
914 { u"open", 0 },
915 { u"save", 0 },
916 { u"yes", 0 },
917 { u"ok", 0 },
918 { u"discard", 1 },
919 { u"no", 1 },
920 { u"cancel", 2 },
921 { u"close", 2 }
924 const ButtonOrder* pOrder = &aDiscardCancelSave[0];
926 const OUString &rEnv = Application::GetDesktopEnvironment();
928 if (rEnv.equalsIgnoreAsciiCase("windows") ||
929 rEnv.equalsIgnoreAsciiCase("tde") ||
930 rEnv.startsWithIgnoreAsciiCase("kde"))
932 pOrder = &aSaveDiscardCancel[0];
935 for (size_t i = 0; i < N_TYPES; ++i, ++pOrder)
937 if (rType == pOrder->m_aType)
938 return pOrder->m_nPriority;
941 return -1;
944 void container_remove(GtkWidget* pContainer, GtkWidget* pChild)
946 #if !GTK_CHECK_VERSION(4, 0, 0)
947 gtk_container_remove(GTK_CONTAINER(pContainer), pChild);
948 #else
949 assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
950 GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
951 if (GTK_IS_BOX(pContainer))
952 gtk_box_remove(GTK_BOX(pContainer), pChild);
953 else if (GTK_IS_GRID(pContainer))
954 gtk_grid_remove(GTK_GRID(pContainer), pChild);
955 else if (GTK_IS_POPOVER(pContainer))
956 gtk_popover_set_child(GTK_POPOVER(pContainer), nullptr);
957 else if (GTK_IS_WINDOW(pContainer))
958 gtk_window_set_child(GTK_WINDOW(pContainer), nullptr);
959 else if (GTK_IS_FIXED(pContainer))
960 gtk_fixed_remove(GTK_FIXED(pContainer), pChild);
961 #endif
964 void container_add(GtkWidget* pContainer, GtkWidget* pChild)
966 #if !GTK_CHECK_VERSION(4, 0, 0)
967 gtk_container_add(GTK_CONTAINER(pContainer), pChild);
968 #else
969 assert(GTK_IS_BOX(pContainer) || GTK_IS_GRID(pContainer) || GTK_IS_POPOVER(pContainer) ||
970 GTK_IS_FIXED(pContainer) || GTK_IS_WINDOW(pContainer));
971 if (GTK_IS_BOX(pContainer))
972 gtk_box_append(GTK_BOX(pContainer), pChild);
973 else if (GTK_IS_GRID(pContainer))
974 gtk_grid_attach(GTK_GRID(pContainer), pChild, 0, 0, 1, 1);
975 else if (GTK_IS_POPOVER(pContainer))
976 gtk_popover_set_child(GTK_POPOVER(pContainer), pChild);
977 else if (GTK_IS_WINDOW(pContainer))
978 gtk_window_set_child(GTK_WINDOW(pContainer), pChild);
979 else if (GTK_IS_FIXED(pContainer))
980 gtk_fixed_put(GTK_FIXED(pContainer), pChild, 0, 0);
981 #endif
984 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */