blob: 59bbe1a4a76727e5ad15a788932672ccff5bcdad [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * Porting to GTK+ was done by:
12 *
Bram Moolenaar0a56cb82005-01-04 21:45:14 +000013 * (C) 1998,1999,2000 by Marcin Dalecki <martin@dalecki.de>
Bram Moolenaar071d4272004-06-13 20:20:40 +000014 *
15 * With GREAT support and continuous encouragements by Andy Kahn and of
16 * course Bram Moolenaar!
17 *
18 * Support for GTK+ 2 was added by:
19 *
20 * (C) 2002,2003 Jason Hildebrand <jason@peaceworks.ca>
21 * Daniel Elstner <daniel.elstner@gmx.net>
Bram Moolenaar98921892016-02-23 17:14:37 +010022 *
23 * Support for GTK+ 3 was added by:
24 *
25 * 2016 Kazunobu Kuriyama <kazunobu.kuriyama@gmail.com>
Bram Moolenaar071d4272004-06-13 20:20:40 +000026 */
27
28#include "vim.h"
Bram Moolenaar36e294c2015-12-29 18:55:46 +010029#ifdef USE_GRESOURCE
30#include "auto/gui_gtk_gresources.h"
31#endif
Bram Moolenaarc93e7912008-07-08 10:46:08 +000032
Bram Moolenaar071d4272004-06-13 20:20:40 +000033#ifdef FEAT_GUI_GNOME
Bram Moolenaar30613902019-12-01 22:11:18 +010034// Gnome redefines _() and N_(). Grrr...
Bram Moolenaar071d4272004-06-13 20:20:40 +000035# ifdef _
36# undef _
37# endif
38# ifdef N_
39# undef N_
40# endif
41# ifdef textdomain
42# undef textdomain
43# endif
44# ifdef bindtextdomain
45# undef bindtextdomain
46# endif
Bram Moolenaara2dd9002007-05-14 17:38:30 +000047# ifdef bind_textdomain_codeset
48# undef bind_textdomain_codeset
Bram Moolenaar49325942007-05-10 19:19:59 +000049# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000050# if defined(FEAT_GETTEXT) && !defined(ENABLE_NLS)
Bram Moolenaar30613902019-12-01 22:11:18 +010051# define ENABLE_NLS // so the texts in the dialog boxes are translated
Bram Moolenaar071d4272004-06-13 20:20:40 +000052# endif
53# include <gnome.h>
54# include "version.h"
Bram Moolenaar30613902019-12-01 22:11:18 +010055// missing prototype in bonobo-dock-item.h
Bram Moolenaardb552d602006-03-23 22:59:57 +000056extern void bonobo_dock_item_set_behavior(BonoboDockItem *dock_item, BonoboDockItemBehavior beh);
Bram Moolenaar071d4272004-06-13 20:20:40 +000057#endif
58
59#if !defined(FEAT_GUI_GTK) && defined(PROTO)
Bram Moolenaar30613902019-12-01 22:11:18 +010060// When generating prototypes we don't want syntax errors.
Bram Moolenaar071d4272004-06-13 20:20:40 +000061# define GdkAtom int
62# define GdkEventExpose int
63# define GdkEventFocus int
64# define GdkEventVisibility int
65# define GdkEventProperty int
66# define GtkContainer int
67# define GtkTargetEntry int
68# define GtkType int
69# define GtkWidget int
70# define gint int
71# define gpointer int
72# define guint int
73# define GdkEventKey int
74# define GdkEventSelection int
75# define GtkSelectionData int
76# define GdkEventMotion int
77# define GdkEventButton int
78# define GdkDragContext int
79# define GdkEventConfigure int
80# define GdkEventClient int
81#else
Bram Moolenaar98921892016-02-23 17:14:37 +010082# if GTK_CHECK_VERSION(3,0,0)
83# include <gdk/gdkkeysyms-compat.h>
84# include <gtk/gtkx.h>
85# else
86# include <gdk/gdkkeysyms.h>
87# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000088# include <gdk/gdk.h>
Bram Moolenaar4f974752019-02-17 17:44:42 +010089# ifdef MSWIN
Bram Moolenaar071d4272004-06-13 20:20:40 +000090# include <gdk/gdkwin32.h>
91# else
92# include <gdk/gdkx.h>
93# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000094# include <gtk/gtk.h>
95# include "gui_gtk_f.h"
96#endif
97
98#ifdef HAVE_X11_SUNKEYSYM_H
99# include <X11/Sunkeysym.h>
100#endif
101
102/*
103 * Easy-to-use macro for multihead support.
104 */
Bram Moolenaarb463e8d2017-06-05 15:07:09 +0200105#define GET_X_ATOM(atom) gdk_x11_atom_to_xatom_for_display( \
Bram Moolenaar071d4272004-06-13 20:20:40 +0000106 gtk_widget_get_display(gui.mainwin), atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000107
Bram Moolenaar30613902019-12-01 22:11:18 +0100108// Selection type distinguishers
Bram Moolenaar071d4272004-06-13 20:20:40 +0000109enum
110{
111 TARGET_TYPE_NONE,
112 TARGET_UTF8_STRING,
113 TARGET_STRING,
114 TARGET_COMPOUND_TEXT,
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +0000115 TARGET_HTML,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000116 TARGET_TEXT,
117 TARGET_TEXT_URI_LIST,
118 TARGET_TEXT_PLAIN,
119 TARGET_VIM,
120 TARGET_VIMENC
121};
122
123/*
124 * Table of selection targets supported by Vim.
125 * Note: Order matters, preferred types should come first.
126 */
127static const GtkTargetEntry selection_targets[] =
128{
129 {VIMENC_ATOM_NAME, 0, TARGET_VIMENC},
130 {VIM_ATOM_NAME, 0, TARGET_VIM},
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +0000131 {"text/html", 0, TARGET_HTML},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000132 {"UTF8_STRING", 0, TARGET_UTF8_STRING},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000133 {"COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT},
134 {"TEXT", 0, TARGET_TEXT},
135 {"STRING", 0, TARGET_STRING}
136};
K.Takataeeec2542021-06-02 13:28:16 +0200137#define N_SELECTION_TARGETS ARRAY_LENGTH(selection_targets)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138
139#ifdef FEAT_DND
140/*
141 * Table of DnD targets supported by Vim.
142 * Note: Order matters, preferred types should come first.
143 */
144static const GtkTargetEntry dnd_targets[] =
145{
146 {"text/uri-list", 0, TARGET_TEXT_URI_LIST},
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +0000147 {"text/html", 0, TARGET_HTML},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000148 {"UTF8_STRING", 0, TARGET_UTF8_STRING},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000149 {"STRING", 0, TARGET_STRING},
150 {"text/plain", 0, TARGET_TEXT_PLAIN}
151};
K.Takataeeec2542021-06-02 13:28:16 +0200152# define N_DND_TARGETS ARRAY_LENGTH(dnd_targets)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000153#endif
154
155
Bram Moolenaar071d4272004-06-13 20:20:40 +0000156/*
157 * "Monospace" is a standard font alias that should be present
158 * on all proper Pango/fontconfig installations.
159 */
160# define DEFAULT_FONT "Monospace 10"
161
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +0200162#if defined(FEAT_GUI_GNOME) && defined(FEAT_SESSION)
163# define USE_GNOME_SESSION
164#endif
165
166#if !defined(FEAT_GUI_GNOME)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000167/*
168 * Atoms used to communicate save-yourself from the X11 session manager. There
169 * is no need to move them into the GUI struct, since they should be constant.
170 */
171static GdkAtom wm_protocols_atom = GDK_NONE;
172static GdkAtom save_yourself_atom = GDK_NONE;
173#endif
174
175/*
176 * Atoms used to control/reference X11 selections.
177 */
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +0000178static GdkAtom html_atom = GDK_NONE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000179static GdkAtom utf8_string_atom = GDK_NONE;
Bram Moolenaar30613902019-12-01 22:11:18 +0100180static GdkAtom vim_atom = GDK_NONE; // Vim's own special selection format
181static GdkAtom vimenc_atom = GDK_NONE; // Vim's extended selection format
Bram Moolenaar071d4272004-06-13 20:20:40 +0000182
183/*
184 * Keycodes recognized by vim.
185 * NOTE: when changing this, the table in gui_x11.c probably needs the same
186 * change!
187 */
188static struct special_key
189{
190 guint key_sym;
191 char_u code0;
192 char_u code1;
193}
194const special_keys[] =
195{
196 {GDK_Up, 'k', 'u'},
197 {GDK_Down, 'k', 'd'},
198 {GDK_Left, 'k', 'l'},
199 {GDK_Right, 'k', 'r'},
200 {GDK_F1, 'k', '1'},
201 {GDK_F2, 'k', '2'},
202 {GDK_F3, 'k', '3'},
203 {GDK_F4, 'k', '4'},
204 {GDK_F5, 'k', '5'},
205 {GDK_F6, 'k', '6'},
206 {GDK_F7, 'k', '7'},
207 {GDK_F8, 'k', '8'},
208 {GDK_F9, 'k', '9'},
209 {GDK_F10, 'k', ';'},
210 {GDK_F11, 'F', '1'},
211 {GDK_F12, 'F', '2'},
212 {GDK_F13, 'F', '3'},
213 {GDK_F14, 'F', '4'},
214 {GDK_F15, 'F', '5'},
215 {GDK_F16, 'F', '6'},
216 {GDK_F17, 'F', '7'},
217 {GDK_F18, 'F', '8'},
218 {GDK_F19, 'F', '9'},
219 {GDK_F20, 'F', 'A'},
220 {GDK_F21, 'F', 'B'},
Bram Moolenaar30613902019-12-01 22:11:18 +0100221 {GDK_Pause, 'F', 'B'}, // Pause == F21 according to netbeans.txt
Bram Moolenaar071d4272004-06-13 20:20:40 +0000222 {GDK_F22, 'F', 'C'},
223 {GDK_F23, 'F', 'D'},
224 {GDK_F24, 'F', 'E'},
225 {GDK_F25, 'F', 'F'},
226 {GDK_F26, 'F', 'G'},
227 {GDK_F27, 'F', 'H'},
228 {GDK_F28, 'F', 'I'},
229 {GDK_F29, 'F', 'J'},
230 {GDK_F30, 'F', 'K'},
231 {GDK_F31, 'F', 'L'},
232 {GDK_F32, 'F', 'M'},
233 {GDK_F33, 'F', 'N'},
234 {GDK_F34, 'F', 'O'},
235 {GDK_F35, 'F', 'P'},
236#ifdef SunXK_F36
237 {SunXK_F36, 'F', 'Q'},
238 {SunXK_F37, 'F', 'R'},
239#endif
240 {GDK_Help, '%', '1'},
241 {GDK_Undo, '&', '8'},
242 {GDK_BackSpace, 'k', 'b'},
243 {GDK_Insert, 'k', 'I'},
244 {GDK_Delete, 'k', 'D'},
245 {GDK_3270_BackTab, 'k', 'B'},
246 {GDK_Clear, 'k', 'C'},
247 {GDK_Home, 'k', 'h'},
248 {GDK_End, '@', '7'},
249 {GDK_Prior, 'k', 'P'},
250 {GDK_Next, 'k', 'N'},
251 {GDK_Print, '%', '9'},
Bram Moolenaar30613902019-12-01 22:11:18 +0100252 // Keypad keys:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000253 {GDK_KP_Left, 'k', 'l'},
254 {GDK_KP_Right, 'k', 'r'},
255 {GDK_KP_Up, 'k', 'u'},
256 {GDK_KP_Down, 'k', 'd'},
257 {GDK_KP_Insert, KS_EXTRA, (char_u)KE_KINS},
258 {GDK_KP_Delete, KS_EXTRA, (char_u)KE_KDEL},
259 {GDK_KP_Home, 'K', '1'},
260 {GDK_KP_End, 'K', '4'},
Bram Moolenaar30613902019-12-01 22:11:18 +0100261 {GDK_KP_Prior, 'K', '3'}, // page up
262 {GDK_KP_Next, 'K', '5'}, // page down
Bram Moolenaar071d4272004-06-13 20:20:40 +0000263
264 {GDK_KP_Add, 'K', '6'},
265 {GDK_KP_Subtract, 'K', '7'},
266 {GDK_KP_Divide, 'K', '8'},
267 {GDK_KP_Multiply, 'K', '9'},
268 {GDK_KP_Enter, 'K', 'A'},
269 {GDK_KP_Decimal, 'K', 'B'},
270
271 {GDK_KP_0, 'K', 'C'},
272 {GDK_KP_1, 'K', 'D'},
273 {GDK_KP_2, 'K', 'E'},
274 {GDK_KP_3, 'K', 'F'},
275 {GDK_KP_4, 'K', 'G'},
276 {GDK_KP_5, 'K', 'H'},
277 {GDK_KP_6, 'K', 'I'},
278 {GDK_KP_7, 'K', 'J'},
279 {GDK_KP_8, 'K', 'K'},
280 {GDK_KP_9, 'K', 'L'},
281
Bram Moolenaar30613902019-12-01 22:11:18 +0100282 // End of list marker:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000283 {0, 0, 0}
284};
285
286/*
287 * Flags for command line options table below.
288 */
289#define ARG_FONT 1
290#define ARG_GEOMETRY 2
291#define ARG_REVERSE 3
292#define ARG_NOREVERSE 4
293#define ARG_BACKGROUND 5
294#define ARG_FOREGROUND 6
295#define ARG_ICONIC 7
296#define ARG_ROLE 8
297#define ARG_NETBEANS 9
Bram Moolenaar30613902019-12-01 22:11:18 +0100298#define ARG_XRM 10 // ignored
299#define ARG_MENUFONT 11 // ignored
Bram Moolenaar071d4272004-06-13 20:20:40 +0000300#define ARG_INDEX_MASK 0x00ff
Bram Moolenaar30613902019-12-01 22:11:18 +0100301#define ARG_HAS_VALUE 0x0100 // a value is expected after the argument
302#define ARG_NEEDS_GUI 0x0200 // need to initialize the GUI for this
303#define ARG_FOR_GTK 0x0400 // argument is handled by GTK+ or GNOME
304#define ARG_COMPAT_LONG 0x0800 // accept -foo but substitute with --foo
305#define ARG_KEEP 0x1000 // don't remove argument from argv[]
Bram Moolenaar071d4272004-06-13 20:20:40 +0000306
307/*
308 * This table holds all the X GUI command line options allowed. This includes
309 * the standard ones so that we can skip them when Vim is started without the
310 * GUI (but the GUI might start up later).
311 *
312 * When changing this, also update doc/gui_x11.txt and the usage message!!!
313 */
314typedef struct
315{
316 const char *name;
317 unsigned int flags;
318}
319cmdline_option_T;
320
321static const cmdline_option_T cmdline_options[] =
322{
Bram Moolenaar30613902019-12-01 22:11:18 +0100323 // We handle these options ourselves
Bram Moolenaar071d4272004-06-13 20:20:40 +0000324 {"-fn", ARG_FONT|ARG_HAS_VALUE},
325 {"-font", ARG_FONT|ARG_HAS_VALUE},
326 {"-geom", ARG_GEOMETRY|ARG_HAS_VALUE},
327 {"-geometry", ARG_GEOMETRY|ARG_HAS_VALUE},
328 {"-rv", ARG_REVERSE},
329 {"-reverse", ARG_REVERSE},
330 {"+rv", ARG_NOREVERSE},
331 {"+reverse", ARG_NOREVERSE},
332 {"-bg", ARG_BACKGROUND|ARG_HAS_VALUE},
333 {"-background", ARG_BACKGROUND|ARG_HAS_VALUE},
334 {"-fg", ARG_FOREGROUND|ARG_HAS_VALUE},
335 {"-foreground", ARG_FOREGROUND|ARG_HAS_VALUE},
336 {"-iconic", ARG_ICONIC},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000337 {"--role", ARG_ROLE|ARG_HAS_VALUE},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000338#ifdef FEAT_NETBEANS_INTG
Bram Moolenaar30613902019-12-01 22:11:18 +0100339 {"-nb", ARG_NETBEANS}, // non-standard value format
340 {"-xrm", ARG_XRM|ARG_HAS_VALUE}, // not implemented
341 {"-mf", ARG_MENUFONT|ARG_HAS_VALUE}, // not implemented
342 {"-menufont", ARG_MENUFONT|ARG_HAS_VALUE}, // not implemented
Bram Moolenaar071d4272004-06-13 20:20:40 +0000343#endif
Bram Moolenaar30613902019-12-01 22:11:18 +0100344 // Arguments handled by GTK (and GNOME) internally.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000345 {"--g-fatal-warnings", ARG_FOR_GTK},
346 {"--gdk-debug", ARG_FOR_GTK|ARG_HAS_VALUE},
347 {"--gdk-no-debug", ARG_FOR_GTK|ARG_HAS_VALUE},
348 {"--gtk-debug", ARG_FOR_GTK|ARG_HAS_VALUE},
349 {"--gtk-no-debug", ARG_FOR_GTK|ARG_HAS_VALUE},
350 {"--gtk-module", ARG_FOR_GTK|ARG_HAS_VALUE},
351 {"--sync", ARG_FOR_GTK},
352 {"--display", ARG_FOR_GTK|ARG_HAS_VALUE|ARG_COMPAT_LONG},
353 {"--name", ARG_FOR_GTK|ARG_HAS_VALUE|ARG_COMPAT_LONG},
354 {"--class", ARG_FOR_GTK|ARG_HAS_VALUE|ARG_COMPAT_LONG},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000355 {"--screen", ARG_FOR_GTK|ARG_HAS_VALUE},
356 {"--gxid-host", ARG_FOR_GTK|ARG_HAS_VALUE},
357 {"--gxid-port", ARG_FOR_GTK|ARG_HAS_VALUE},
Bram Moolenaar071d4272004-06-13 20:20:40 +0000358#ifdef FEAT_GUI_GNOME
359 {"--load-modules", ARG_FOR_GTK|ARG_HAS_VALUE},
360 {"--sm-client-id", ARG_FOR_GTK|ARG_HAS_VALUE},
361 {"--sm-config-prefix", ARG_FOR_GTK|ARG_HAS_VALUE},
362 {"--sm-disable", ARG_FOR_GTK},
363 {"--oaf-ior-fd", ARG_FOR_GTK|ARG_HAS_VALUE},
364 {"--oaf-activate-iid", ARG_FOR_GTK|ARG_HAS_VALUE},
365 {"--oaf-private", ARG_FOR_GTK},
366 {"--enable-sound", ARG_FOR_GTK},
367 {"--disable-sound", ARG_FOR_GTK},
368 {"--espeaker", ARG_FOR_GTK|ARG_HAS_VALUE},
369 {"-?", ARG_FOR_GTK|ARG_NEEDS_GUI},
370 {"--help", ARG_FOR_GTK|ARG_NEEDS_GUI|ARG_KEEP},
371 {"--usage", ARG_FOR_GTK|ARG_NEEDS_GUI},
Bram Moolenaar30613902019-12-01 22:11:18 +0100372# if 0 // conflicts with Vim's own --version argument
Bram Moolenaar071d4272004-06-13 20:20:40 +0000373 {"--version", ARG_FOR_GTK|ARG_NEEDS_GUI},
374# endif
375 {"--disable-crash-dialog", ARG_FOR_GTK},
376#endif
377 {NULL, 0}
378};
379
380static int gui_argc = 0;
381static char **gui_argv = NULL;
382
Bram Moolenaar071d4272004-06-13 20:20:40 +0000383static const char *role_argument = NULL;
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +0200384#if defined(USE_GNOME_SESSION)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000385static const char *restart_command = NULL;
Bram Moolenaar9085f802009-06-03 14:20:21 +0000386static char *abs_restart_command = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000387#endif
388static int found_iconic_arg = FALSE;
389
390#ifdef FEAT_GUI_GNOME
391/*
392 * Can't use Gnome if --socketid given
393 */
394static int using_gnome = 0;
395#else
396# define using_gnome 0
397#endif
398
399/*
Ernie Rael9f53e7b2022-04-17 18:27:49 +0100400 * Keep a short term resize history so that stale gtk responses can be
401 * discarded.
402 * When a gtk_window_resize() request is sent to gtk, the width/height of
403 * the request is saved. Recent stale requests are kept around in a list.
404 * See https://github.com/vim/vim/issues/10123
405 */
406#if 0 // Change to 1 to enable ch_log() calls for debugging.
407# ifdef FEAT_JOB_CHANNEL
408# define ENABLE_RESIZE_HISTORY_LOG
409# endif
410#endif
411
412/*
413 * History item of a resize request.
414 * Width and height are of gui.mainwin.
415 */
416typedef struct resize_history {
417 int used; // If true, can't match for discard. Only matches once.
418 int width;
419 int height;
420#ifdef ENABLE_RESIZE_HISTORY_LOG
421 int seq; // for ch_log messages
422#endif
423 struct resize_history *next;
424} resize_hist_T;
425
426// never NULL during execution
427static resize_hist_T *latest_resize_hist;
428// list of stale resize requests
429static resize_hist_T *old_resize_hists;
430
431/*
432 * Used when calling gtk_window_resize().
433 * Create a resize request history item, put previous request on stale list.
434 * Width/height are the size of the request for the gui.mainwin.
435 */
436 static void
437alloc_resize_hist(int width, int height)
438{
439 // alloc a new resize hist, save current in list of old history
440 resize_hist_T *prev_hist = latest_resize_hist;
441 resize_hist_T *new_hist = ALLOC_CLEAR_ONE(resize_hist_T);
442
443 new_hist->width = width;
444 new_hist->height = height;
445 latest_resize_hist = new_hist;
446
447 // previous hist item becomes head of list
448 prev_hist->next = old_resize_hists;
449 old_resize_hists = prev_hist;
450
451#ifdef ENABLE_RESIZE_HISTORY_LOG
452 new_hist->seq = prev_hist->seq + 1;
453 ch_log(NULL, "gui_gtk: New resize seq %d (%d, %d) [%d, %d]",
454 new_hist->seq, width, height, (int)Columns, (int)Rows);
455#endif
456}
457
458/*
459 * Free everything on the stale resize history list.
460 * This list is empty when there are no outstanding resize requests.
461 */
462 static void
463clear_resize_hists()
464{
465#ifdef ENABLE_RESIZE_HISTORY_LOG
466 int i = 0;
467#endif
468
469 if (latest_resize_hist)
470 latest_resize_hist->used = TRUE;
471 while (old_resize_hists != NULL)
472 {
473 resize_hist_T *next_hist = old_resize_hists->next;
474
475 vim_free(old_resize_hists);
476 old_resize_hists = next_hist;
477#ifdef ENABLE_RESIZE_HISTORY_LOG
478 i++;
479#endif
480 }
481#ifdef ENABLE_RESIZE_HISTORY_LOG
482 ch_log(NULL, "gui_gtk: free %d hists", i);
483#endif
484}
485
486// true if hist item is unused and matches w,h
487#define MATCH_WIDTH_HEIGHT(hist, w, h) \
488 (!hist->used && hist->width == w && hist->height == h)
489
490/*
491 * Search the resize hist list.
492 * Return true if the specified width,height match an item in the list that
493 * has never matched before. Mark the matching item as used so it will
494 * not match again.
495 */
496 static int
497match_stale_width_height(int width, int height)
498{
499 resize_hist_T *hist = old_resize_hists;
500
501 for (hist = old_resize_hists; hist != NULL; hist = hist->next)
502 if (MATCH_WIDTH_HEIGHT(hist, width, height))
503 {
504#ifdef ENABLE_RESIZE_HISTORY_LOG
505 ch_log(NULL, "gui_gtk: discard seq %d, cur seq %d",
506 hist->seq, latest_resize_hist->seq);
507#endif
508 hist->used = TRUE;
509 return TRUE;
510 }
511 return FALSE;
512}
513
514#if defined(EXITFREE)
515 static void
516free_all_resize_hist()
517{
518 clear_resize_hists();
519 vim_free(latest_resize_hist);
520}
521#endif
522
523/*
Chris Daltonee93e322021-11-23 12:27:48 +0000524 * GTK doesn't set the GDK_BUTTON1_MASK state when dragging a touch. Add this
525 * state when dragging.
526 */
527static guint dragging_button_state = 0;
528
529/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000530 * Parse the GUI related command-line arguments. Any arguments used are
531 * deleted from argv, and *argc is decremented accordingly. This is called
532 * when vim is started, whether or not the GUI has been started.
533 */
534 void
535gui_mch_prepare(int *argc, char **argv)
536{
537 const cmdline_option_T *option;
538 int i = 0;
539 int len = 0;
540
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +0200541#if defined(USE_GNOME_SESSION)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000542 /*
543 * Determine the command used to invoke Vim, to be passed as restart
544 * command to the session manager. If argv[0] contains any directory
545 * components try building an absolute path, otherwise leave it as is.
546 */
547 restart_command = argv[0];
548
549 if (strchr(argv[0], G_DIR_SEPARATOR) != NULL)
550 {
551 char_u buf[MAXPATHL];
552
553 if (mch_FullName((char_u *)argv[0], buf, (int)sizeof(buf), TRUE) == OK)
Bram Moolenaar9085f802009-06-03 14:20:21 +0000554 {
555 abs_restart_command = (char *)vim_strsave(buf);
556 restart_command = abs_restart_command;
557 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000558 }
559#endif
560
561 /*
562 * Move all the entries in argv which are relevant to GTK+ and GNOME
563 * into gui_argv. Freed later in gui_mch_init().
564 */
565 gui_argc = 0;
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200566 gui_argv = ALLOC_MULT(char *, *argc + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000567
568 g_return_if_fail(gui_argv != NULL);
569
570 gui_argv[gui_argc++] = argv[i++];
571
572 while (i < *argc)
573 {
Bram Moolenaar30613902019-12-01 22:11:18 +0100574 // Don't waste CPU cycles on non-option arguments.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000575 if (argv[i][0] != '-' && argv[i][0] != '+')
576 {
577 ++i;
578 continue;
579 }
580
Bram Moolenaar30613902019-12-01 22:11:18 +0100581 // Look for argv[i] in cmdline_options[] table.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000582 for (option = &cmdline_options[0]; option->name != NULL; ++option)
583 {
584 len = strlen(option->name);
585
586 if (strncmp(argv[i], option->name, len) == 0)
587 {
588 if (argv[i][len] == '\0')
589 break;
Bram Moolenaar30613902019-12-01 22:11:18 +0100590 // allow --foo=bar style
Bram Moolenaar071d4272004-06-13 20:20:40 +0000591 if (argv[i][len] == '=' && (option->flags & ARG_HAS_VALUE))
592 break;
593#ifdef FEAT_NETBEANS_INTG
Bram Moolenaar30613902019-12-01 22:11:18 +0100594 // darn, -nb has non-standard syntax
Bram Moolenaar071d4272004-06-13 20:20:40 +0000595 if (vim_strchr((char_u *)":=", argv[i][len]) != NULL
596 && (option->flags & ARG_INDEX_MASK) == ARG_NETBEANS)
597 break;
598#endif
599 }
600 else if ((option->flags & ARG_COMPAT_LONG)
601 && strcmp(argv[i], option->name + 1) == 0)
602 {
Bram Moolenaar30613902019-12-01 22:11:18 +0100603 // Replace the standard X arguments "-name" and "-display"
604 // with their GNU-style long option counterparts.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000605 argv[i] = (char *)option->name;
606 break;
607 }
608 }
Bram Moolenaar30613902019-12-01 22:11:18 +0100609 if (option->name == NULL) // no match
Bram Moolenaar071d4272004-06-13 20:20:40 +0000610 {
611 ++i;
612 continue;
613 }
614
615 if (option->flags & ARG_FOR_GTK)
616 {
Bram Moolenaar30613902019-12-01 22:11:18 +0100617 // Move the argument into gui_argv, which
618 // will later be passed to gtk_init_check()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000619 gui_argv[gui_argc++] = argv[i];
620 }
621 else
622 {
623 char *value = NULL;
624
Bram Moolenaar30613902019-12-01 22:11:18 +0100625 // Extract the option's value if there is one.
626 // Accept both "--foo bar" and "--foo=bar" style.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000627 if (option->flags & ARG_HAS_VALUE)
628 {
629 if (argv[i][len] == '=')
630 value = &argv[i][len + 1];
631 else if (i + 1 < *argc && strcmp(argv[i + 1], "--") != 0)
632 value = argv[i + 1];
633 }
634
Bram Moolenaar30613902019-12-01 22:11:18 +0100635 // Check for options handled by Vim itself
Bram Moolenaar071d4272004-06-13 20:20:40 +0000636 switch (option->flags & ARG_INDEX_MASK)
637 {
638 case ARG_REVERSE:
639 found_reverse_arg = TRUE;
640 break;
641 case ARG_NOREVERSE:
642 found_reverse_arg = FALSE;
643 break;
644 case ARG_FONT:
645 font_argument = value;
646 break;
647 case ARG_GEOMETRY:
648 if (value != NULL)
649 gui.geom = vim_strsave((char_u *)value);
650 break;
651 case ARG_BACKGROUND:
652 background_argument = value;
653 break;
654 case ARG_FOREGROUND:
655 foreground_argument = value;
656 break;
657 case ARG_ICONIC:
658 found_iconic_arg = TRUE;
659 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000660 case ARG_ROLE:
Bram Moolenaar30613902019-12-01 22:11:18 +0100661 role_argument = value; // used later in gui_mch_open()
Bram Moolenaar071d4272004-06-13 20:20:40 +0000662 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000663#ifdef FEAT_NETBEANS_INTG
664 case ARG_NETBEANS:
Bram Moolenaar30613902019-12-01 22:11:18 +0100665 gui.dofork = FALSE; // don't fork() when starting GUI
Bram Moolenaar071d4272004-06-13 20:20:40 +0000666 netbeansArg = argv[i];
667 break;
668#endif
669 default:
670 break;
671 }
672 }
673
Bram Moolenaar30613902019-12-01 22:11:18 +0100674 // These arguments make gnome_program_init() print a message and exit.
675 // Must start the GUI for this, otherwise ":gui" will exit later!
676 // Only when the GUI can start.
Bram Moolenaar717e1962016-08-10 21:28:44 +0200677 if ((option->flags & ARG_NEEDS_GUI)
678 && gui_mch_early_init_check(FALSE) == OK)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000679 gui.starting = TRUE;
680
681 if (option->flags & ARG_KEEP)
682 ++i;
683 else
684 {
Bram Moolenaar30613902019-12-01 22:11:18 +0100685 // Remove the flag from the argument vector.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000686 if (--*argc > i)
687 {
688 int n_strip = 1;
689
Bram Moolenaar30613902019-12-01 22:11:18 +0100690 // Move the argument's value as well, if there is one.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000691 if ((option->flags & ARG_HAS_VALUE)
692 && argv[i][len] != '='
693 && strcmp(argv[i + 1], "--") != 0)
694 {
695 ++n_strip;
696 --*argc;
697 if (option->flags & ARG_FOR_GTK)
698 gui_argv[gui_argc++] = argv[i + 1];
699 }
700
701 if (*argc > i)
702 mch_memmove(&argv[i], &argv[i + n_strip],
703 (*argc - i) * sizeof(char *));
704 }
705 argv[*argc] = NULL;
706 }
707 }
708
709 gui_argv[gui_argc] = NULL;
710}
711
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000712#if defined(EXITFREE) || defined(PROTO)
713 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100714gui_mch_free_all(void)
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000715{
716 vim_free(gui_argv);
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +0200717#if defined(USE_GNOME_SESSION)
Bram Moolenaar9085f802009-06-03 14:20:21 +0000718 vim_free(abs_restart_command);
719#endif
Ernie Rael9f53e7b2022-04-17 18:27:49 +0100720 free_all_resize_hist();
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000721}
722#endif
723
Bram Moolenaar98921892016-02-23 17:14:37 +0100724#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000725/*
726 * This should be maybe completely removed.
727 * Doesn't seem possible, since check_copy_area() relies on
728 * this information. --danielk
729 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000730 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000731visibility_event(GtkWidget *widget UNUSED,
732 GdkEventVisibility *event,
733 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000734{
735 gui.visibility = event->state;
736 /*
737 * When we do an gdk_window_copy_area(), and the window is partially
738 * obscured, we want to receive an event to tell us whether it worked
739 * or not.
740 */
741 if (gui.text_gc != NULL)
742 gdk_gc_set_exposures(gui.text_gc,
743 gui.visibility != GDK_VISIBILITY_UNOBSCURED);
744 return FALSE;
745}
Bram Moolenaar30613902019-12-01 22:11:18 +0100746#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000747
748/*
749 * Redraw the corresponding portions of the screen.
750 */
Bram Moolenaar98921892016-02-23 17:14:37 +0100751#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar98921892016-02-23 17:14:37 +0100752 static gboolean
Bram Moolenaar8e31fd52016-06-04 22:18:13 +0200753draw_event(GtkWidget *widget UNUSED,
Bram Moolenaar98921892016-02-23 17:14:37 +0100754 cairo_t *cr,
755 gpointer user_data UNUSED)
756{
Bram Moolenaar30613902019-12-01 22:11:18 +0100757 // Skip this when the GUI isn't set up yet, will redraw later.
Bram Moolenaar98921892016-02-23 17:14:37 +0100758 if (gui.starting)
759 return FALSE;
760
Bram Moolenaar30613902019-12-01 22:11:18 +0100761 out_flush(); // make sure all output has been processed
762 // for GTK+ 3, may induce other draw events.
Bram Moolenaar98921892016-02-23 17:14:37 +0100763
764 cairo_set_source_surface(cr, gui.surface, 0, 0);
765
Bram Moolenaar98921892016-02-23 17:14:37 +0100766 {
767 cairo_rectangle_list_t *list = NULL;
768
Bram Moolenaar98921892016-02-23 17:14:37 +0100769 list = cairo_copy_clip_rectangle_list(cr);
770 if (list->status != CAIRO_STATUS_CLIP_NOT_REPRESENTABLE)
771 {
772 int i;
Bram Moolenaarfe344a92017-02-23 12:20:35 +0100773
Bram Moolenaar98921892016-02-23 17:14:37 +0100774 for (i = 0; i < list->num_rectangles; i++)
775 {
presuku9459b8d2021-11-16 20:03:56 +0000776 const cairo_rectangle_t *rect = &list->rectangles[i];
777 cairo_rectangle(cr, rect->x, rect->y, rect->width, rect->height);
778 cairo_fill(cr);
Bram Moolenaar98921892016-02-23 17:14:37 +0100779 }
780 }
781 cairo_rectangle_list_destroy(list);
Bram Moolenaar98921892016-02-23 17:14:37 +0100782 }
Bram Moolenaar98921892016-02-23 17:14:37 +0100783
784 return FALSE;
785}
Bram Moolenaar30613902019-12-01 22:11:18 +0100786#else // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000787 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000788expose_event(GtkWidget *widget UNUSED,
789 GdkEventExpose *event,
790 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000791{
Bram Moolenaar30613902019-12-01 22:11:18 +0100792 // Skip this when the GUI isn't set up yet, will redraw later.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000793 if (gui.starting)
794 return FALSE;
795
Bram Moolenaar30613902019-12-01 22:11:18 +0100796 out_flush(); // make sure all output has been processed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000797 gui_redraw(event->area.x, event->area.y,
798 event->area.width, event->area.height);
799
Bram Moolenaar30613902019-12-01 22:11:18 +0100800 // Clear the border areas if needed
Bram Moolenaar071d4272004-06-13 20:20:40 +0000801 if (event->area.x < FILL_X(0))
802 gdk_window_clear_area(gui.drawarea->window, 0, 0, FILL_X(0), 0);
803 if (event->area.y < FILL_Y(0))
804 gdk_window_clear_area(gui.drawarea->window, 0, 0, 0, FILL_Y(0));
805 if (event->area.x > FILL_X(Columns))
806 gdk_window_clear_area(gui.drawarea->window,
807 FILL_X((int)Columns), 0, 0, 0);
808 if (event->area.y > FILL_Y(Rows))
809 gdk_window_clear_area(gui.drawarea->window, 0, FILL_Y((int)Rows), 0, 0);
810
811 return FALSE;
812}
Bram Moolenaar30613902019-12-01 22:11:18 +0100813#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000814
815#ifdef FEAT_CLIENTSERVER
816/*
817 * Handle changes to the "Comm" property
818 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000819 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000820property_event(GtkWidget *widget,
821 GdkEventProperty *event,
822 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000823{
824 if (event->type == GDK_PROPERTY_NOTIFY
825 && event->state == (int)GDK_PROPERTY_NEW_VALUE
Bram Moolenaar98921892016-02-23 17:14:37 +0100826 && GDK_WINDOW_XID(event->window) == commWindow
Bram Moolenaar071d4272004-06-13 20:20:40 +0000827 && GET_X_ATOM(event->atom) == commProperty)
828 {
829 XEvent xev;
830
Bram Moolenaar30613902019-12-01 22:11:18 +0100831 // Translate to XLib
Bram Moolenaar071d4272004-06-13 20:20:40 +0000832 xev.xproperty.type = PropertyNotify;
833 xev.xproperty.atom = commProperty;
834 xev.xproperty.window = commWindow;
835 xev.xproperty.state = PropertyNewValue;
Bram Moolenaar98921892016-02-23 17:14:37 +0100836 serverEventProc(GDK_WINDOW_XDISPLAY(gtk_widget_get_window(widget)),
Ernie Rael9f53e7b2022-04-17 18:27:49 +0100837 &xev, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000838 }
839 return FALSE;
840}
Bram Moolenaar30613902019-12-01 22:11:18 +0100841#endif // defined(FEAT_CLIENTSERVER)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000842
Bram Moolenaar7ebf4e12018-08-07 20:01:40 +0200843/*
844 * Handle changes to the "Xft/DPI" setting
845 */
846 static void
847gtk_settings_xft_dpi_changed_cb(GtkSettings *gtk_settings UNUSED,
Ernie Rael9f53e7b2022-04-17 18:27:49 +0100848 GParamSpec *pspec UNUSED,
849 gpointer data UNUSED)
Bram Moolenaar7ebf4e12018-08-07 20:01:40 +0200850{
851 // Create a new PangoContext for this screen, and initialize it
852 // with the current font if necessary.
853 if (gui.text_context != NULL)
854 g_object_unref(gui.text_context);
855
856 gui.text_context = gtk_widget_create_pango_context(gui.mainwin);
857 pango_context_set_base_dir(gui.text_context, PANGO_DIRECTION_LTR);
858
859 if (gui.norm_font != NULL)
860 {
861 // force default font
862 gui_mch_init_font(*p_guifont == NUL ? NULL : p_guifont, FALSE);
863 gui_set_shellsize(TRUE, FALSE, RESIZE_BOTH);
864 }
865}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000866
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200867typedef gboolean timeout_cb_type;
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200868
869/*
870 * Start a timer that will invoke the specified callback.
871 * Returns the ID of the timer.
872 */
873 static guint
874timeout_add(int time, timeout_cb_type (*callback)(gpointer), int *flagp)
875{
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200876 return g_timeout_add((guint)time, (GSourceFunc)callback, flagp);
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200877}
878
879 static void
880timeout_remove(guint timer)
881{
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200882 g_source_remove(timer);
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200883}
884
885
Bram Moolenaar30613902019-12-01 22:11:18 +0100886/////////////////////////////////////////////////////////////////////////////
887// Focus handlers:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000888
889
890/*
891 * This is a simple state machine:
892 * BLINK_NONE not blinking at all
893 * BLINK_OFF blinking, cursor is not shown
894 * BLINK_ON blinking, cursor is shown
895 */
896
897#define BLINK_NONE 0
898#define BLINK_OFF 1
899#define BLINK_ON 2
900
901static int blink_state = BLINK_NONE;
902static long_u blink_waittime = 700;
903static long_u blink_ontime = 400;
904static long_u blink_offtime = 250;
905static guint blink_timer = 0;
906
Bram Moolenaar703a8042016-06-04 16:24:32 +0200907 int
908gui_mch_is_blinking(void)
909{
910 return blink_state != BLINK_NONE;
911}
912
Bram Moolenaar9d5d3c92016-07-07 16:43:02 +0200913 int
914gui_mch_is_blink_off(void)
915{
916 return blink_state == BLINK_OFF;
917}
918
Bram Moolenaar071d4272004-06-13 20:20:40 +0000919 void
920gui_mch_set_blinking(long waittime, long on, long off)
921{
922 blink_waittime = waittime;
923 blink_ontime = on;
924 blink_offtime = off;
925}
926
927/*
928 * Stop the cursor blinking. Show the cursor if it wasn't shown.
929 */
930 void
Bram Moolenaar1dd45fb2018-01-31 21:10:01 +0100931gui_mch_stop_blink(int may_call_gui_update_cursor)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000932{
933 if (blink_timer)
934 {
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200935 timeout_remove(blink_timer);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000936 blink_timer = 0;
937 }
Bram Moolenaar1dd45fb2018-01-31 21:10:01 +0100938 if (blink_state == BLINK_OFF && may_call_gui_update_cursor)
Bram Moolenaarda3a77d2016-07-10 23:16:09 +0200939 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000940 gui_update_cursor(TRUE, FALSE);
presuku9459b8d2021-11-16 20:03:56 +0000941#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaarda3a77d2016-07-10 23:16:09 +0200942 gui_mch_flush();
presuku9459b8d2021-11-16 20:03:56 +0000943#endif
Bram Moolenaarda3a77d2016-07-10 23:16:09 +0200944 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000945 blink_state = BLINK_NONE;
946}
947
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200948 static timeout_cb_type
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000949blink_cb(gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000950{
951 if (blink_state == BLINK_ON)
952 {
953 gui_undraw_cursor();
954 blink_state = BLINK_OFF;
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200955 blink_timer = timeout_add(blink_offtime, blink_cb, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000956 }
957 else
958 {
959 gui_update_cursor(TRUE, FALSE);
960 blink_state = BLINK_ON;
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200961 blink_timer = timeout_add(blink_ontime, blink_cb, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000962 }
presuku9459b8d2021-11-16 20:03:56 +0000963#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaarda3a77d2016-07-10 23:16:09 +0200964 gui_mch_flush();
presuku9459b8d2021-11-16 20:03:56 +0000965#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000966
Bram Moolenaar30613902019-12-01 22:11:18 +0100967 return FALSE; // don't happen again
Bram Moolenaar071d4272004-06-13 20:20:40 +0000968}
969
970/*
971 * Start the cursor blinking. If it was already blinking, this restarts the
972 * waiting time and shows the cursor.
973 */
974 void
975gui_mch_start_blink(void)
976{
977 if (blink_timer)
Bram Moolenaar7bcdb7d2014-04-06 21:08:45 +0200978 {
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200979 timeout_remove(blink_timer);
Bram Moolenaar7bcdb7d2014-04-06 21:08:45 +0200980 blink_timer = 0;
981 }
Bram Moolenaar30613902019-12-01 22:11:18 +0100982 // Only switch blinking on if none of the times is zero
Bram Moolenaar071d4272004-06-13 20:20:40 +0000983 if (blink_waittime && blink_ontime && blink_offtime && gui.in_focus)
984 {
Bram Moolenaar4ab79682017-08-27 14:50:47 +0200985 blink_timer = timeout_add(blink_waittime, blink_cb, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000986 blink_state = BLINK_ON;
987 gui_update_cursor(TRUE, FALSE);
presuku9459b8d2021-11-16 20:03:56 +0000988#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaarda3a77d2016-07-10 23:16:09 +0200989 gui_mch_flush();
presuku9459b8d2021-11-16 20:03:56 +0000990#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000991 }
992}
993
Bram Moolenaar071d4272004-06-13 20:20:40 +0000994 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000995enter_notify_event(GtkWidget *widget UNUSED,
996 GdkEventCrossing *event UNUSED,
997 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000998{
999 if (blink_state == BLINK_NONE)
1000 gui_mch_start_blink();
1001
Bram Moolenaar30613902019-12-01 22:11:18 +01001002 // make sure keyboard input goes there
Bram Moolenaar98921892016-02-23 17:14:37 +01001003 if (gtk_socket_id == 0 || !gtk_widget_has_focus(gui.drawarea))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001004 gtk_widget_grab_focus(gui.drawarea);
1005
1006 return FALSE;
1007}
1008
Bram Moolenaar071d4272004-06-13 20:20:40 +00001009 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001010leave_notify_event(GtkWidget *widget UNUSED,
1011 GdkEventCrossing *event UNUSED,
1012 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001013{
1014 if (blink_state != BLINK_NONE)
Bram Moolenaar1dd45fb2018-01-31 21:10:01 +01001015 gui_mch_stop_blink(TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001016
1017 return FALSE;
1018}
1019
Bram Moolenaar071d4272004-06-13 20:20:40 +00001020 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001021focus_in_event(GtkWidget *widget,
1022 GdkEventFocus *event UNUSED,
1023 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001024{
1025 gui_focus_change(TRUE);
1026
1027 if (blink_state == BLINK_NONE)
1028 gui_mch_start_blink();
1029
Bram Moolenaar30613902019-12-01 22:11:18 +01001030 // make sure keyboard input goes to the draw area (if this is focus for a
1031 // window)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00001032 if (widget != gui.drawarea)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00001033 gtk_widget_grab_focus(gui.drawarea);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001034
1035 return TRUE;
1036}
1037
Bram Moolenaar071d4272004-06-13 20:20:40 +00001038 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001039focus_out_event(GtkWidget *widget UNUSED,
Bram Moolenaarcc448b32010-07-14 16:52:17 +02001040 GdkEventFocus *event UNUSED,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001041 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001042{
1043 gui_focus_change(FALSE);
1044
1045 if (blink_state != BLINK_NONE)
Bram Moolenaar1dd45fb2018-01-31 21:10:01 +01001046 gui_mch_stop_blink(TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001047
1048 return TRUE;
1049}
1050
1051
Bram Moolenaar071d4272004-06-13 20:20:40 +00001052/*
1053 * Translate a GDK key value to UTF-8 independently of the current locale.
1054 * The output is written to string, which must have room for at least 6 bytes
1055 * plus the NUL terminator. Returns the length in bytes.
1056 *
Bram Moolenaarf4ae6b22020-05-30 19:52:46 +02001057 * event->string is evil; see here why:
Bram Moolenaar071d4272004-06-13 20:20:40 +00001058 * http://developer.gnome.org/doc/API/2.0/gdk/gdk-Event-Structures.html#GdkEventKey
1059 */
1060 static int
Bram Moolenaarf4ae6b22020-05-30 19:52:46 +02001061keyval_to_string(unsigned int keyval, char_u *string)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001062{
1063 int len;
1064 guint32 uc;
1065
1066 uc = gdk_keyval_to_unicode(keyval);
1067 if (uc != 0)
1068 {
Bram Moolenaarf4ae6b22020-05-30 19:52:46 +02001069 // Translate a normal key to UTF-8. This doesn't work for dead
1070 // keys of course, you _have_ to use an input method for that.
1071 len = utf_char2bytes((int)uc, string);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001072 }
1073 else
1074 {
Bram Moolenaar30613902019-12-01 22:11:18 +01001075 // Translate keys which are represented by ASCII control codes in Vim.
1076 // There are only a few of those; most control keys are translated to
1077 // special terminal-like control sequences.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001078 len = 1;
1079 switch (keyval)
1080 {
1081 case GDK_Tab: case GDK_KP_Tab: case GDK_ISO_Left_Tab:
1082 string[0] = TAB;
1083 break;
1084 case GDK_Linefeed:
1085 string[0] = NL;
1086 break;
1087 case GDK_Return: case GDK_ISO_Enter: case GDK_3270_Enter:
1088 string[0] = CAR;
1089 break;
1090 case GDK_Escape:
1091 string[0] = ESC;
1092 break;
1093 default:
1094 len = 0;
1095 break;
1096 }
1097 }
1098 string[len] = NUL;
1099
1100 return len;
1101}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001102
Bram Moolenaar19a09a12005-03-04 23:39:37 +00001103 static int
1104modifiers_gdk2vim(guint state)
1105{
1106 int modifiers = 0;
1107
1108 if (state & GDK_SHIFT_MASK)
1109 modifiers |= MOD_MASK_SHIFT;
1110 if (state & GDK_CONTROL_MASK)
1111 modifiers |= MOD_MASK_CTRL;
1112 if (state & GDK_MOD1_MASK)
1113 modifiers |= MOD_MASK_ALT;
Bram Moolenaar580061a2010-08-13 13:57:13 +02001114#if GTK_CHECK_VERSION(2,10,0)
Bram Moolenaar30bb4142010-05-17 22:07:15 +02001115 if (state & GDK_SUPER_MASK)
1116 modifiers |= MOD_MASK_META;
1117#endif
Bram Moolenaar19a09a12005-03-04 23:39:37 +00001118 if (state & GDK_MOD4_MASK)
1119 modifiers |= MOD_MASK_META;
1120
1121 return modifiers;
1122}
1123
1124 static int
1125modifiers_gdk2mouse(guint state)
1126{
1127 int modifiers = 0;
1128
1129 if (state & GDK_SHIFT_MASK)
1130 modifiers |= MOUSE_SHIFT;
1131 if (state & GDK_CONTROL_MASK)
1132 modifiers |= MOUSE_CTRL;
1133 if (state & GDK_MOD1_MASK)
1134 modifiers |= MOUSE_ALT;
1135
1136 return modifiers;
1137}
1138
Bram Moolenaar071d4272004-06-13 20:20:40 +00001139/*
1140 * Main keyboard handler:
1141 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001142 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001143key_press_event(GtkWidget *widget UNUSED,
1144 GdkEventKey *event,
1145 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001146{
Bram Moolenaar30613902019-12-01 22:11:18 +01001147 // For GTK+ 2 we know for sure how large the string might get.
1148 // (That is, up to 6 bytes + NUL + CSI escapes + safety measure.)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001149 char_u string[32], string2[32];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001150 guint key_sym;
1151 int len;
1152 int i;
1153 int modifiers;
1154 int key;
1155 guint state;
1156 char_u *s, *d;
1157
Bram Moolenaar20892c12011-06-26 04:49:00 +02001158 gui.event_time = event->time;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001159 key_sym = event->keyval;
1160 state = event->state;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001161
1162#ifdef FEAT_XIM
1163 if (xim_queue_key_press_event(event, TRUE))
1164 return TRUE;
1165#endif
1166
Bram Moolenaar071d4272004-06-13 20:20:40 +00001167#ifdef SunXK_F36
1168 /*
1169 * These keys have bogus lookup strings, and trapping them here is
1170 * easier than trying to XRebindKeysym() on them with every possible
1171 * combination of modifiers.
1172 */
1173 if (key_sym == SunXK_F36 || key_sym == SunXK_F37)
1174 len = 0;
1175 else
1176#endif
1177 {
Bram Moolenaarf4ae6b22020-05-30 19:52:46 +02001178 len = keyval_to_string(key_sym, string2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001179
Bram Moolenaar30613902019-12-01 22:11:18 +01001180 // Careful: convert_input() doesn't handle the NUL character.
1181 // No need to convert pure ASCII anyway, thus the len > 1 check.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001182 if (len > 1 && input_conv.vc_type != CONV_NONE)
1183 len = convert_input(string2, len, sizeof(string2));
1184
1185 s = string2;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001186 d = string;
1187 for (i = 0; i < len; ++i)
1188 {
1189 *d++ = s[i];
1190 if (d[-1] == CSI && d + 2 < string + sizeof(string))
1191 {
Bram Moolenaar30613902019-12-01 22:11:18 +01001192 // Turn CSI into K_CSI.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001193 *d++ = KS_EXTRA;
1194 *d++ = (int)KE_CSI;
1195 }
1196 }
1197 len = d - string;
1198 }
1199
Bram Moolenaar30613902019-12-01 22:11:18 +01001200 // Shift-Tab results in Left_Tab, but we want <S-Tab>
Bram Moolenaar071d4272004-06-13 20:20:40 +00001201 if (key_sym == GDK_ISO_Left_Tab)
1202 {
1203 key_sym = GDK_Tab;
1204 state |= GDK_SHIFT_MASK;
1205 }
1206
Bram Moolenaar071d4272004-06-13 20:20:40 +00001207#ifdef FEAT_MENU
Bram Moolenaar30613902019-12-01 22:11:18 +01001208 // If there is a menu and 'wak' is "yes", or 'wak' is "menu" and the key
1209 // is a menu shortcut, we ignore everything with the ALT modifier.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001210 if ((state & GDK_MOD1_MASK)
1211 && gui.menu_is_active
1212 && (*p_wak == 'y'
1213 || (*p_wak == 'm'
1214 && len == 1
1215 && gui_is_menu_shortcut(string[0]))))
Bram Moolenaar30613902019-12-01 22:11:18 +01001216 // For GTK2 we return false to signify that we haven't handled the
1217 // keypress, so that gtk will handle the mnemonic or accelerator.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001218 return FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001219#endif
1220
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001221 // We used to apply Alt/Meta to the key here (Mod1Mask), but that is now
1222 // done later, the same as it happens for the terminal. Hopefully that
1223 // works for everybody...
Bram Moolenaar071d4272004-06-13 20:20:40 +00001224
Bram Moolenaar30613902019-12-01 22:11:18 +01001225 // Check for special keys. Also do this when len == 1 (key has an ASCII
1226 // value) to detect backspace, delete and keypad keys.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001227 if (len == 0 || len == 1)
1228 {
1229 for (i = 0; special_keys[i].key_sym != 0; i++)
1230 {
1231 if (special_keys[i].key_sym == key_sym)
1232 {
1233 string[0] = CSI;
1234 string[1] = special_keys[i].code0;
1235 string[2] = special_keys[i].code1;
1236 len = -3;
1237 break;
1238 }
1239 }
1240 }
1241
Bram Moolenaar30613902019-12-01 22:11:18 +01001242 if (len == 0) // Unrecognized key
Bram Moolenaar071d4272004-06-13 20:20:40 +00001243 return TRUE;
1244
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001245 // For some keys a shift modifier is translated into another key code.
1246 if (len == -3)
1247 key = TO_SPECIAL(string[1], string[2]);
1248 else
Bram Moolenaar820ffa52020-06-21 15:09:14 +02001249 {
1250 string[len] = NUL;
1251 key = mb_ptr2char(string);
1252 }
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001253
Bram Moolenaaref6746f2020-06-20 14:43:23 +02001254 // Handle modifiers.
1255 modifiers = modifiers_gdk2vim(state);
1256
1257 // Recognize special keys.
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001258 key = simplify_key(key, &modifiers);
1259 if (key == CSI)
1260 key = K_CSI;
1261 if (IS_SPECIAL(key))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001262 {
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001263 string[0] = CSI;
1264 string[1] = K_SECOND(key);
1265 string[2] = K_THIRD(key);
1266 len = 3;
1267 }
1268 else
1269 {
Bram Moolenaar4e2114e2020-10-07 16:12:37 +02001270 // Some keys need adjustment when the Ctrl modifier is used.
1271 key = may_adjust_key_for_ctrl(modifiers, key);
Bram Moolenaaref6746f2020-06-20 14:43:23 +02001272
Bram Moolenaar4e2114e2020-10-07 16:12:37 +02001273 // May remove the Shift modifier if it's included in the key.
Bram Moolenaaref6746f2020-06-20 14:43:23 +02001274 modifiers = may_remove_shift_modifier(modifiers, key);
1275
Bram Moolenaar820ffa52020-06-21 15:09:14 +02001276 len = mb_char2bytes(key, string);
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001277 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001278
Bram Moolenaarfd615a32020-05-16 14:01:51 +02001279 if (modifiers != 0)
1280 {
1281 string2[0] = CSI;
1282 string2[1] = KS_MODIFIER;
1283 string2[2] = modifiers;
1284 add_to_input_buf(string2, 3);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001285 }
1286
Bram Moolenaar4e1d8bd2020-08-01 13:10:14 +02001287 // Check if the key interrupts.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001288 {
Bram Moolenaar4e1d8bd2020-08-01 13:10:14 +02001289 int int_ch = check_for_interrupt(key, modifiers);
1290
1291 if (int_ch != NUL)
1292 {
1293 trash_input_buf();
1294 string[0] = int_ch;
1295 len = 1;
1296 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001297 }
1298
1299 add_to_input_buf(string, len);
1300
Bram Moolenaar30613902019-12-01 22:11:18 +01001301 // blank out the pointer if necessary
Bram Moolenaar071d4272004-06-13 20:20:40 +00001302 if (p_mh)
1303 gui_mch_mousehide(TRUE);
1304
Bram Moolenaar071d4272004-06-13 20:20:40 +00001305 return TRUE;
1306}
1307
Bram Moolenaar98921892016-02-23 17:14:37 +01001308#if defined(FEAT_XIM) || GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001309 static gboolean
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001310key_release_event(GtkWidget *widget UNUSED,
1311 GdkEventKey *event,
1312 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001313{
Bram Moolenaar98921892016-02-23 17:14:37 +01001314# if defined(FEAT_XIM)
Bram Moolenaar20892c12011-06-26 04:49:00 +02001315 gui.event_time = event->time;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001316 /*
1317 * GTK+ 2 input methods may do fancy stuff on key release events too.
1318 * With the default IM for instance, you can enter any UCS code point
1319 * by holding down CTRL-SHIFT and typing hexadecimal digits.
1320 */
1321 return xim_queue_key_press_event(event, FALSE);
Bram Moolenaar98921892016-02-23 17:14:37 +01001322# else
1323 return TRUE;
1324# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001325}
1326#endif
1327
1328
Bram Moolenaar30613902019-12-01 22:11:18 +01001329/////////////////////////////////////////////////////////////////////////////
1330// Selection handlers:
Bram Moolenaar071d4272004-06-13 20:20:40 +00001331
Bram Moolenaar30613902019-12-01 22:11:18 +01001332// Remember when clip_lose_selection was called from here, we must not call
1333// gtk_selection_owner_set() then.
Bram Moolenaar29dfa5a2018-03-20 21:24:45 +01001334static int in_selection_clear_event = FALSE;
1335
Bram Moolenaar071d4272004-06-13 20:20:40 +00001336 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001337selection_clear_event(GtkWidget *widget UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001338 GdkEventSelection *event,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001339 gpointer user_data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001340{
Bram Moolenaar29dfa5a2018-03-20 21:24:45 +01001341 in_selection_clear_event = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001342 if (event->selection == clip_plus.gtk_sel_atom)
1343 clip_lose_selection(&clip_plus);
1344 else
1345 clip_lose_selection(&clip_star);
Bram Moolenaar29dfa5a2018-03-20 21:24:45 +01001346 in_selection_clear_event = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001347
Bram Moolenaar071d4272004-06-13 20:20:40 +00001348 return TRUE;
1349}
1350
Bram Moolenaar30613902019-12-01 22:11:18 +01001351#define RS_NONE 0 // selection_received_cb() not called yet
1352#define RS_OK 1 // selection_received_cb() called and OK
1353#define RS_FAIL 2 // selection_received_cb() called and failed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001354static int received_selection = RS_NONE;
1355
Bram Moolenaar071d4272004-06-13 20:20:40 +00001356 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001357selection_received_cb(GtkWidget *widget UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001358 GtkSelectionData *data,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001359 guint time_ UNUSED,
1360 gpointer user_data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001361{
Bram Moolenaar0554fa42019-06-14 21:36:54 +02001362 Clipboard_T *cbd;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001363 char_u *text;
1364 char_u *tmpbuf = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001365 guchar *tmpbuf_utf8 = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001366 int len;
Bram Moolenaard44347f2011-06-19 01:14:29 +02001367 int motion_type = MAUTO;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001368
Bram Moolenaar98921892016-02-23 17:14:37 +01001369 if (gtk_selection_data_get_selection(data) == clip_plus.gtk_sel_atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001370 cbd = &clip_plus;
1371 else
1372 cbd = &clip_star;
1373
Bram Moolenaar98921892016-02-23 17:14:37 +01001374 text = (char_u *)gtk_selection_data_get_data(data);
1375 len = gtk_selection_data_get_length(data);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001376
1377 if (text == NULL || len <= 0)
1378 {
1379 received_selection = RS_FAIL;
Bram Moolenaar30613902019-12-01 22:11:18 +01001380 // clip_free_selection(cbd); ???
Bram Moolenaar071d4272004-06-13 20:20:40 +00001381
Bram Moolenaar071d4272004-06-13 20:20:40 +00001382 return;
1383 }
1384
Bram Moolenaar98921892016-02-23 17:14:37 +01001385 if (gtk_selection_data_get_data_type(data) == vim_atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001386 {
1387 motion_type = *text++;
1388 --len;
1389 }
Bram Moolenaar98921892016-02-23 17:14:37 +01001390 else if (gtk_selection_data_get_data_type(data) == vimenc_atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391 {
1392 char_u *enc;
1393 vimconv_T conv;
1394
1395 motion_type = *text++;
1396 --len;
1397
1398 enc = text;
1399 text += STRLEN(text) + 1;
1400 len -= text - enc;
1401
Bram Moolenaar30613902019-12-01 22:11:18 +01001402 // If the encoding of the text is different from 'encoding', attempt
1403 // converting it.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001404 conv.vc_type = CONV_NONE;
1405 convert_setup(&conv, enc, p_enc);
1406 if (conv.vc_type != CONV_NONE)
1407 {
1408 tmpbuf = string_convert(&conv, text, &len);
1409 if (tmpbuf != NULL)
1410 text = tmpbuf;
1411 convert_setup(&conv, NULL, NULL);
1412 }
1413 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001414
Bram Moolenaar30613902019-12-01 22:11:18 +01001415 // gtk_selection_data_get_text() handles all the nasty details
1416 // and targets and encodings etc. This rocks so hard.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001417 else
1418 {
1419 tmpbuf_utf8 = gtk_selection_data_get_text(data);
1420 if (tmpbuf_utf8 != NULL)
1421 {
1422 len = STRLEN(tmpbuf_utf8);
1423 if (input_conv.vc_type != CONV_NONE)
1424 {
1425 tmpbuf = string_convert(&input_conv, tmpbuf_utf8, &len);
1426 if (tmpbuf != NULL)
1427 text = tmpbuf;
1428 }
1429 else
1430 text = tmpbuf_utf8;
1431 }
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001432 else if (len >= 2 && text[0] == 0xff && text[1] == 0xfe)
1433 {
1434 vimconv_T conv;
1435
Bram Moolenaar30613902019-12-01 22:11:18 +01001436 // UTF-16, we get this for HTML
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001437 conv.vc_type = CONV_NONE;
1438 convert_setup_ext(&conv, (char_u *)"utf-16le", FALSE, p_enc, TRUE);
1439
1440 if (conv.vc_type != CONV_NONE)
1441 {
1442 text += 2;
1443 len -= 2;
1444 tmpbuf = string_convert(&conv, text, &len);
1445 convert_setup(&conv, NULL, NULL);
1446 }
1447 if (tmpbuf != NULL)
1448 text = tmpbuf;
1449 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001450 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001451
Bram Moolenaar30613902019-12-01 22:11:18 +01001452 // Chop off any trailing NUL bytes. OpenOffice sends these.
Bram Moolenaara76638f2010-06-05 12:49:46 +02001453 while (len > 0 && text[len - 1] == NUL)
1454 --len;
1455
Bram Moolenaar071d4272004-06-13 20:20:40 +00001456 clip_yank_selection(motion_type, text, (long)len, cbd);
1457 received_selection = RS_OK;
1458 vim_free(tmpbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001459 g_free(tmpbuf_utf8);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001460}
1461
1462/*
1463 * Prepare our selection data for passing it to the external selection
1464 * client.
1465 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001466 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001467selection_get_cb(GtkWidget *widget UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001468 GtkSelectionData *selection_data,
1469 guint info,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001470 guint time_ UNUSED,
1471 gpointer user_data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472{
1473 char_u *string;
1474 char_u *tmpbuf;
1475 long_u tmplen;
1476 int length;
1477 int motion_type;
1478 GdkAtom type;
Bram Moolenaar0554fa42019-06-14 21:36:54 +02001479 Clipboard_T *cbd;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001480
Bram Moolenaar98921892016-02-23 17:14:37 +01001481 if (gtk_selection_data_get_selection(selection_data)
1482 == clip_plus.gtk_sel_atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001483 cbd = &clip_plus;
1484 else
1485 cbd = &clip_star;
1486
1487 if (!cbd->owned)
Bram Moolenaar30613902019-12-01 22:11:18 +01001488 return; // Shouldn't ever happen
Bram Moolenaar071d4272004-06-13 20:20:40 +00001489
1490 if (info != (guint)TARGET_STRING
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001491 && (!clip_html || info != (guint)TARGET_HTML)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001492 && info != (guint)TARGET_UTF8_STRING
1493 && info != (guint)TARGET_VIMENC
Bram Moolenaar071d4272004-06-13 20:20:40 +00001494 && info != (guint)TARGET_VIM
1495 && info != (guint)TARGET_COMPOUND_TEXT
1496 && info != (guint)TARGET_TEXT)
1497 return;
1498
Bram Moolenaar30613902019-12-01 22:11:18 +01001499 // get the selection from the '*'/'+' register
Bram Moolenaar071d4272004-06-13 20:20:40 +00001500 clip_get_selection(cbd);
1501
1502 motion_type = clip_convert_selection(&string, &tmplen, cbd);
1503 if (motion_type < 0 || string == NULL)
1504 return;
Bram Moolenaar30613902019-12-01 22:11:18 +01001505 // Due to int arguments we can't handle more than G_MAXINT. Also
1506 // reserve one extra byte for NUL or the motion type; just in case.
1507 // (Not that pasting 2G of text is ever going to work, but... ;-)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001508 length = MIN(tmplen, (long_u)(G_MAXINT - 1));
1509
1510 if (info == (guint)TARGET_VIM)
1511 {
Bram Moolenaar964b3742019-05-24 18:54:09 +02001512 tmpbuf = alloc(length + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001513 if (tmpbuf != NULL)
1514 {
1515 tmpbuf[0] = motion_type;
1516 mch_memmove(tmpbuf + 1, string, (size_t)length);
1517 }
Bram Moolenaar30613902019-12-01 22:11:18 +01001518 // For our own format, the first byte contains the motion type
Bram Moolenaar071d4272004-06-13 20:20:40 +00001519 ++length;
1520 vim_free(string);
1521 string = tmpbuf;
1522 type = vim_atom;
1523 }
1524
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001525 else if (info == (guint)TARGET_HTML)
1526 {
1527 vimconv_T conv;
1528
Bram Moolenaar30613902019-12-01 22:11:18 +01001529 // Since we get utf-16, we probably should set it as well.
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001530 conv.vc_type = CONV_NONE;
1531 convert_setup_ext(&conv, p_enc, TRUE, (char_u *)"utf-16le", FALSE);
1532 if (conv.vc_type != CONV_NONE)
1533 {
1534 tmpbuf = string_convert(&conv, string, &length);
1535 convert_setup(&conv, NULL, NULL);
1536 vim_free(string);
1537 string = tmpbuf;
1538 }
1539
Bram Moolenaar30613902019-12-01 22:11:18 +01001540 // Prepend the BOM: "fffe"
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001541 if (string != NULL)
1542 {
1543 tmpbuf = alloc(length + 2);
Bram Moolenaar6ee96582019-04-27 22:06:37 +02001544 if (tmpbuf != NULL)
1545 {
1546 tmpbuf[0] = 0xff;
1547 tmpbuf[1] = 0xfe;
1548 mch_memmove(tmpbuf + 2, string, (size_t)length);
1549 vim_free(string);
1550 string = tmpbuf;
1551 length += 2;
1552 }
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001553
Bram Moolenaar98921892016-02-23 17:14:37 +01001554#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01001555 // Looks redundant even for GTK2 because these values are
1556 // overwritten by gtk_selection_data_set() that follows.
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001557 selection_data->type = selection_data->target;
Bram Moolenaar30613902019-12-01 22:11:18 +01001558 selection_data->format = 16; // 16 bits per char
Bram Moolenaar98921892016-02-23 17:14:37 +01001559#endif
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00001560 gtk_selection_data_set(selection_data, html_atom, 16,
1561 string, length);
1562 vim_free(string);
1563 }
1564 return;
1565 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001566 else if (info == (guint)TARGET_VIMENC)
1567 {
1568 int l = STRLEN(p_enc);
1569
Bram Moolenaar30613902019-12-01 22:11:18 +01001570 // contents: motion_type 'encoding' NUL text
Bram Moolenaar964b3742019-05-24 18:54:09 +02001571 tmpbuf = alloc(length + l + 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572 if (tmpbuf != NULL)
1573 {
1574 tmpbuf[0] = motion_type;
1575 STRCPY(tmpbuf + 1, p_enc);
1576 mch_memmove(tmpbuf + l + 2, string, (size_t)length);
Bram Moolenaar6ee96582019-04-27 22:06:37 +02001577 length += l + 2;
1578 vim_free(string);
1579 string = tmpbuf;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001580 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001581 type = vimenc_atom;
1582 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001583
Bram Moolenaar30613902019-12-01 22:11:18 +01001584 // gtk_selection_data_set_text() handles everything for us. This is
1585 // so easy and simple and cool, it'd be insane not to use it.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001586 else
1587 {
1588 if (output_conv.vc_type != CONV_NONE)
1589 {
1590 tmpbuf = string_convert(&output_conv, string, &length);
1591 vim_free(string);
1592 if (tmpbuf == NULL)
1593 return;
1594 string = tmpbuf;
1595 }
Bram Moolenaar30613902019-12-01 22:11:18 +01001596 // Validate the string to avoid runtime warnings
Bram Moolenaar071d4272004-06-13 20:20:40 +00001597 if (g_utf8_validate((const char *)string, (gssize)length, NULL))
1598 {
1599 gtk_selection_data_set_text(selection_data,
1600 (const char *)string, length);
1601 }
1602 vim_free(string);
1603 return;
1604 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001605
1606 if (string != NULL)
1607 {
Bram Moolenaar98921892016-02-23 17:14:37 +01001608#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01001609 // Looks redundant even for GTK2 because these values are
1610 // overwritten by gtk_selection_data_set() that follows.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001611 selection_data->type = selection_data->target;
Bram Moolenaar30613902019-12-01 22:11:18 +01001612 selection_data->format = 8; // 8 bits per char
Bram Moolenaar98921892016-02-23 17:14:37 +01001613#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001614 gtk_selection_data_set(selection_data, type, 8, string, length);
1615 vim_free(string);
1616 }
1617}
1618
1619/*
Bram Moolenaar29695702012-05-18 17:03:18 +02001620 * Check if the GUI can be started. Called before gvimrc is sourced and
1621 * before fork().
1622 * Return OK or FAIL.
1623 */
1624 int
Bram Moolenaar717e1962016-08-10 21:28:44 +02001625gui_mch_early_init_check(int give_message)
Bram Moolenaar29695702012-05-18 17:03:18 +02001626{
1627 char_u *p;
1628
Bram Moolenaar30613902019-12-01 22:11:18 +01001629 // Guess that when $DISPLAY isn't set the GUI can't start.
Bram Moolenaar29695702012-05-18 17:03:18 +02001630 p = mch_getenv((char_u *)"DISPLAY");
1631 if (p == NULL || *p == NUL)
1632 {
1633 gui.dying = TRUE;
Bram Moolenaar717e1962016-08-10 21:28:44 +02001634 if (give_message)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001635 emsg(_((char *)e_cannot_open_display));
Bram Moolenaar29695702012-05-18 17:03:18 +02001636 return FAIL;
1637 }
1638 return OK;
1639}
1640
1641/*
1642 * Check if the GUI can be started. Called before gvimrc is sourced but after
1643 * fork().
Bram Moolenaar071d4272004-06-13 20:20:40 +00001644 * Return OK or FAIL.
1645 */
1646 int
1647gui_mch_init_check(void)
1648{
Bram Moolenaar3a466a82016-01-19 17:47:25 +01001649#ifdef USE_GRESOURCE
1650 static int res_registered = FALSE;
1651
1652 if (!res_registered)
1653 {
Bram Moolenaar30613902019-12-01 22:11:18 +01001654 // Call this function in the GUI process; otherwise, the resources
1655 // won't be available. Don't call it twice.
Bram Moolenaar3a466a82016-01-19 17:47:25 +01001656 res_registered = TRUE;
1657 gui_gtk_register_resource();
1658 }
1659#endif
1660
Bram Moolenaarf80663f2016-04-05 21:56:06 +02001661#if GTK_CHECK_VERSION(3,10,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01001662 // Vim currently assumes that Gtk means X11, so it cannot use native Gtk
1663 // support for other backends such as Wayland.
Bram Moolenaarf80663f2016-04-05 21:56:06 +02001664 gdk_set_allowed_backends ("x11");
1665#endif
1666
Bram Moolenaar071d4272004-06-13 20:20:40 +00001667#ifdef FEAT_GUI_GNOME
1668 if (gtk_socket_id == 0)
1669 using_gnome = 1;
1670#endif
1671
Bram Moolenaar30613902019-12-01 22:11:18 +01001672 // This defaults to argv[0], but we want it to match the name of the
1673 // shipped gvim.desktop so that Vim's windows can be associated with this
1674 // file.
Bram Moolenaar8dd79012013-05-21 12:52:04 +02001675 g_set_prgname("gvim");
1676
Bram Moolenaar30613902019-12-01 22:11:18 +01001677 // Don't use gtk_init() or gnome_init(), it exits on failure.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001678 if (!gtk_init_check(&gui_argc, &gui_argv))
1679 {
1680 gui.dying = TRUE;
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001681 emsg(_((char *)e_cannot_open_display));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001682 return FAIL;
1683 }
1684
1685 return OK;
1686}
1687
Bram Moolenaar30613902019-12-01 22:11:18 +01001688/////////////////////////////////////////////////////////////////////////////
1689// Mouse handling callbacks
Bram Moolenaar071d4272004-06-13 20:20:40 +00001690
1691
1692static guint mouse_click_timer = 0;
1693static int mouse_timed_out = TRUE;
1694
1695/*
1696 * Timer used to recognize multiple clicks of the mouse button
1697 */
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001698 static timeout_cb_type
Bram Moolenaar071d4272004-06-13 20:20:40 +00001699mouse_click_timer_cb(gpointer data)
1700{
Bram Moolenaar30613902019-12-01 22:11:18 +01001701 // we don't use this information currently
Bram Moolenaar071d4272004-06-13 20:20:40 +00001702 int *timed_out = (int *) data;
1703
1704 *timed_out = TRUE;
Bram Moolenaar30613902019-12-01 22:11:18 +01001705 return FALSE; // don't happen again
Bram Moolenaar071d4272004-06-13 20:20:40 +00001706}
1707
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001708static guint motion_repeat_timer = 0;
1709static int motion_repeat_offset = FALSE;
1710static timeout_cb_type motion_repeat_timer_cb(gpointer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001711
1712 static void
1713process_motion_notify(int x, int y, GdkModifierType state)
1714{
1715 int button;
1716 int_u vim_modifiers;
Bram Moolenaar98921892016-02-23 17:14:37 +01001717 GtkAllocation allocation;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001718
Chris Daltonee93e322021-11-23 12:27:48 +00001719 // Need to add GDK_BUTTON1_MASK state when dragging a touch.
1720 state |= dragging_button_state;
1721
Bram Moolenaar071d4272004-06-13 20:20:40 +00001722 button = (state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK |
1723 GDK_BUTTON3_MASK | GDK_BUTTON4_MASK |
1724 GDK_BUTTON5_MASK))
1725 ? MOUSE_DRAG : ' ';
1726
Bram Moolenaar30613902019-12-01 22:11:18 +01001727 // If our pointer is currently hidden, then we should show it.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001728 gui_mch_mousehide(FALSE);
1729
Bram Moolenaar30613902019-12-01 22:11:18 +01001730 // Just moving the rodent above the drawing area without any button
1731 // being pressed.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001732 if (button != MOUSE_DRAG)
1733 {
1734 gui_mouse_moved(x, y);
1735 return;
1736 }
1737
Bram Moolenaar30613902019-12-01 22:11:18 +01001738 // translate modifier coding between the main engine and GTK
Bram Moolenaar19a09a12005-03-04 23:39:37 +00001739 vim_modifiers = modifiers_gdk2mouse(state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001740
Bram Moolenaar30613902019-12-01 22:11:18 +01001741 // inform the editor engine about the occurrence of this event
Bram Moolenaar071d4272004-06-13 20:20:40 +00001742 gui_send_mouse_event(button, x, y, FALSE, vim_modifiers);
1743
Bram Moolenaar071d4272004-06-13 20:20:40 +00001744 /*
1745 * Auto repeat timer handling.
1746 */
Bram Moolenaar98921892016-02-23 17:14:37 +01001747 gtk_widget_get_allocation(gui.drawarea, &allocation);
1748
1749 if (x < 0 || y < 0
1750 || x >= allocation.width
1751 || y >= allocation.height)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001752 {
1753
1754 int dx;
1755 int dy;
1756 int offshoot;
1757 int delay = 10;
1758
Bram Moolenaar30613902019-12-01 22:11:18 +01001759 // Calculate the maximal distance of the cursor from the drawing area.
1760 // (offshoot can't become negative here!).
Bram Moolenaar98921892016-02-23 17:14:37 +01001761 dx = x < 0 ? -x : x - allocation.width;
1762 dy = y < 0 ? -y : y - allocation.height;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001763
1764 offshoot = dx > dy ? dx : dy;
1765
Bram Moolenaar30613902019-12-01 22:11:18 +01001766 // Make a linearly decaying timer delay with a threshold of 5 at a
1767 // distance of 127 pixels from the main window.
1768 //
1769 // One could think endlessly about the most ergonomic variant here.
1770 // For example it could make sense to calculate the distance from the
1771 // drags start instead...
1772 //
1773 // Maybe a parabolic interpolation would suite us better here too...
Bram Moolenaar071d4272004-06-13 20:20:40 +00001774 if (offshoot > 127)
1775 {
Bram Moolenaar30613902019-12-01 22:11:18 +01001776 // 5 appears to be somehow near to my perceptual limits :-).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001777 delay = 5;
1778 }
1779 else
1780 {
1781 delay = (130 * (127 - offshoot)) / 127 + 5;
1782 }
1783
Bram Moolenaar30613902019-12-01 22:11:18 +01001784 // shoot again
Bram Moolenaar071d4272004-06-13 20:20:40 +00001785 if (!motion_repeat_timer)
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001786 motion_repeat_timer = timeout_add(delay, motion_repeat_timer_cb,
1787 NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001788 }
1789}
1790
Bram Moolenaar98921892016-02-23 17:14:37 +01001791#if GTK_CHECK_VERSION(3,0,0)
1792 static GdkDevice *
1793gui_gtk_get_pointer_device(GtkWidget *widget)
1794{
1795 GdkWindow * const win = gtk_widget_get_window(widget);
1796 GdkDisplay * const dpy = gdk_window_get_display(win);
Bram Moolenaar30e12d22016-04-17 20:49:53 +02001797# if GTK_CHECK_VERSION(3,20,0)
1798 GdkSeat * const seat = gdk_display_get_default_seat(dpy);
1799 return gdk_seat_get_pointer(seat);
1800# else
Bram Moolenaar98921892016-02-23 17:14:37 +01001801 GdkDeviceManager * const mngr = gdk_display_get_device_manager(dpy);
1802 return gdk_device_manager_get_client_pointer(mngr);
Bram Moolenaar30e12d22016-04-17 20:49:53 +02001803# endif
Bram Moolenaar98921892016-02-23 17:14:37 +01001804}
1805
1806 static GdkWindow *
1807gui_gtk_get_pointer(GtkWidget *widget,
1808 gint *x,
1809 gint *y,
1810 GdkModifierType *state)
1811{
1812 GdkWindow * const win = gtk_widget_get_window(widget);
1813 GdkDevice * const dev = gui_gtk_get_pointer_device(widget);
1814 return gdk_window_get_device_position(win, dev , x, y, state);
1815}
1816
Bram Moolenaar2369c152016-03-04 23:08:25 +01001817# if defined(FEAT_GUI_TABLINE) || defined(PROTO)
Bram Moolenaar98921892016-02-23 17:14:37 +01001818 static GdkWindow *
1819gui_gtk_window_at_position(GtkWidget *widget,
1820 gint *x,
1821 gint *y)
1822{
1823 GdkDevice * const dev = gui_gtk_get_pointer_device(widget);
1824 return gdk_device_get_window_at_position(dev, x, y);
1825}
Bram Moolenaar2369c152016-03-04 23:08:25 +01001826# endif
Bram Moolenaar30613902019-12-01 22:11:18 +01001827#else // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar664323e2018-09-18 22:30:07 +02001828# define gui_gtk_get_pointer(wid, x, y, s) \
1829 gdk_window_get_pointer((wid)->window, x, y, s)
1830# define gui_gtk_window_at_position(wid, x, y) gdk_window_at_pointer(x, y)
Bram Moolenaar98921892016-02-23 17:14:37 +01001831#endif
1832
Bram Moolenaar071d4272004-06-13 20:20:40 +00001833/*
1834 * Timer used to recognize multiple clicks of the mouse button.
1835 */
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001836 static timeout_cb_type
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001837motion_repeat_timer_cb(gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001838{
1839 int x;
1840 int y;
1841 GdkModifierType state;
1842
Bram Moolenaar98921892016-02-23 17:14:37 +01001843 gui_gtk_get_pointer(gui.drawarea, &x, &y, &state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001844
1845 if (!(state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK |
1846 GDK_BUTTON3_MASK | GDK_BUTTON4_MASK |
1847 GDK_BUTTON5_MASK)))
1848 {
1849 motion_repeat_timer = 0;
1850 return FALSE;
1851 }
1852
Bram Moolenaar30613902019-12-01 22:11:18 +01001853 // If there already is a mouse click in the input buffer, wait another
1854 // time (otherwise we would create a backlog of clicks)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001855 if (vim_used_in_input_buf() > 10)
1856 return TRUE;
1857
1858 motion_repeat_timer = 0;
1859
1860 /*
1861 * Fake a motion event.
1862 * Trick: Pretend the mouse moved to the next character on every other
1863 * event, otherwise drag events will be discarded, because they are still
1864 * in the same character.
1865 */
1866 if (motion_repeat_offset)
1867 x += gui.char_width;
1868
1869 motion_repeat_offset = !motion_repeat_offset;
1870 process_motion_notify(x, y, state);
1871
Bram Moolenaar30613902019-12-01 22:11:18 +01001872 // Don't happen again. We will get reinstalled in the synthetic event
1873 // if needed -- thus repeating should still work.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001874 return FALSE;
1875}
1876
Bram Moolenaar071d4272004-06-13 20:20:40 +00001877 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001878motion_notify_event(GtkWidget *widget,
1879 GdkEventMotion *event,
1880 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001881{
1882 if (event->is_hint)
1883 {
1884 int x;
1885 int y;
1886 GdkModifierType state;
1887
Bram Moolenaar98921892016-02-23 17:14:37 +01001888 gui_gtk_get_pointer(widget, &x, &y, &state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001889 process_motion_notify(x, y, state);
1890 }
1891 else
1892 {
1893 process_motion_notify((int)event->x, (int)event->y,
1894 (GdkModifierType)event->state);
1895 }
1896
Bram Moolenaar30613902019-12-01 22:11:18 +01001897 return TRUE; // handled
Bram Moolenaar071d4272004-06-13 20:20:40 +00001898}
1899
1900
1901/*
1902 * Mouse button handling. Note please that we are capturing multiple click's
1903 * by our own timeout mechanism instead of the one provided by GTK+ itself.
1904 * This is due to the way the generic VIM code is recognizing multiple clicks.
1905 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001906 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001907button_press_event(GtkWidget *widget,
1908 GdkEventButton *event,
1909 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001910{
1911 int button;
1912 int repeated_click = FALSE;
1913 int x, y;
1914 int_u vim_modifiers;
1915
Bram Moolenaar20892c12011-06-26 04:49:00 +02001916 gui.event_time = event->time;
Bram Moolenaar7cfea752010-06-22 06:07:12 +02001917
Bram Moolenaar30613902019-12-01 22:11:18 +01001918 // Make sure we have focus now we've been selected
Bram Moolenaar98921892016-02-23 17:14:37 +01001919 if (gtk_socket_id != 0 && !gtk_widget_has_focus(widget))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001920 gtk_widget_grab_focus(widget);
1921
1922 /*
1923 * Don't let additional events about multiple clicks send by GTK to us
1924 * after the initial button press event confuse us.
1925 */
1926 if (event->type != GDK_BUTTON_PRESS)
1927 return FALSE;
1928
1929 x = event->x;
1930 y = event->y;
1931
Bram Moolenaar30613902019-12-01 22:11:18 +01001932 // Handle multiple clicks
Bram Moolenaar071d4272004-06-13 20:20:40 +00001933 if (!mouse_timed_out && mouse_click_timer)
1934 {
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001935 timeout_remove(mouse_click_timer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001936 mouse_click_timer = 0;
1937 repeated_click = TRUE;
1938 }
1939
1940 mouse_timed_out = FALSE;
Bram Moolenaar4ab79682017-08-27 14:50:47 +02001941 mouse_click_timer = timeout_add(p_mouset, mouse_click_timer_cb,
1942 &mouse_timed_out);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001943
1944 switch (event->button)
1945 {
Bram Moolenaar30613902019-12-01 22:11:18 +01001946 // Keep in sync with gui_x11.c.
1947 // Buttons 4-7 are handled in scroll_event()
Chris Daltonee93e322021-11-23 12:27:48 +00001948 case 1:
1949 button = MOUSE_LEFT;
1950 // needed for touch-drag
1951 dragging_button_state |= GDK_BUTTON1_MASK;
1952 break;
Bram Moolenaar88e484b2015-11-24 15:38:44 +01001953 case 2: button = MOUSE_MIDDLE; break;
1954 case 3: button = MOUSE_RIGHT; break;
1955 case 8: button = MOUSE_X1; break;
1956 case 9: button = MOUSE_X2; break;
1957 default:
Bram Moolenaar30613902019-12-01 22:11:18 +01001958 return FALSE; // Unknown button
Bram Moolenaar071d4272004-06-13 20:20:40 +00001959 }
1960
1961#ifdef FEAT_XIM
Bram Moolenaar30613902019-12-01 22:11:18 +01001962 // cancel any preediting
Bram Moolenaar071d4272004-06-13 20:20:40 +00001963 if (im_is_preediting())
1964 xim_reset();
1965#endif
1966
Bram Moolenaar19a09a12005-03-04 23:39:37 +00001967 vim_modifiers = modifiers_gdk2mouse(event->state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001968
1969 gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001970
1971 return TRUE;
1972}
1973
Bram Moolenaar071d4272004-06-13 20:20:40 +00001974/*
Bram Moolenaar182c5be2010-06-25 05:37:59 +02001975 * GTK+ 2 abstracts scrolling via the GdkEventScroll.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001976 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001977 static gboolean
Bram Moolenaarb85cb212009-05-17 14:24:23 +00001978scroll_event(GtkWidget *widget,
1979 GdkEventScroll *event,
1980 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001981{
1982 int button;
Bram Moolenaar19a09a12005-03-04 23:39:37 +00001983 int_u vim_modifiers;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001984
Bram Moolenaar98921892016-02-23 17:14:37 +01001985 if (gtk_socket_id != 0 && !gtk_widget_has_focus(widget))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001986 gtk_widget_grab_focus(widget);
1987
1988 switch (event->direction)
1989 {
1990 case GDK_SCROLL_UP:
1991 button = MOUSE_4;
1992 break;
1993 case GDK_SCROLL_DOWN:
1994 button = MOUSE_5;
1995 break;
Bram Moolenaar8d9b40e2010-07-25 15:49:07 +02001996 case GDK_SCROLL_LEFT:
1997 button = MOUSE_7;
1998 break;
1999 case GDK_SCROLL_RIGHT:
2000 button = MOUSE_6;
2001 break;
Bram Moolenaar30613902019-12-01 22:11:18 +01002002 default: // This shouldn't happen
Bram Moolenaar071d4272004-06-13 20:20:40 +00002003 return FALSE;
2004 }
2005
2006# ifdef FEAT_XIM
Bram Moolenaar30613902019-12-01 22:11:18 +01002007 // cancel any preediting
Bram Moolenaar071d4272004-06-13 20:20:40 +00002008 if (im_is_preediting())
2009 xim_reset();
2010# endif
2011
Bram Moolenaar19a09a12005-03-04 23:39:37 +00002012 vim_modifiers = modifiers_gdk2mouse(event->state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002013
2014 gui_send_mouse_event(button, (int)event->x, (int)event->y,
2015 FALSE, vim_modifiers);
2016
Bram Moolenaar071d4272004-06-13 20:20:40 +00002017 return TRUE;
2018}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002019
2020
Bram Moolenaar071d4272004-06-13 20:20:40 +00002021 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002022button_release_event(GtkWidget *widget UNUSED,
2023 GdkEventButton *event,
2024 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002025{
2026 int x, y;
2027 int_u vim_modifiers;
2028
Bram Moolenaar20892c12011-06-26 04:49:00 +02002029 gui.event_time = event->time;
Bram Moolenaar7cfea752010-06-22 06:07:12 +02002030
Bram Moolenaar30613902019-12-01 22:11:18 +01002031 // Remove any motion "machine gun" timers used for automatic further
2032 // extension of allocation areas if outside of the applications window
2033 // area .
Bram Moolenaar071d4272004-06-13 20:20:40 +00002034 if (motion_repeat_timer)
2035 {
Bram Moolenaar4ab79682017-08-27 14:50:47 +02002036 timeout_remove(motion_repeat_timer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002037 motion_repeat_timer = 0;
2038 }
2039
2040 x = event->x;
2041 y = event->y;
2042
Bram Moolenaar19a09a12005-03-04 23:39:37 +00002043 vim_modifiers = modifiers_gdk2mouse(event->state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002044
2045 gui_send_mouse_event(MOUSE_RELEASE, x, y, FALSE, vim_modifiers);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002046
Chris Daltonee93e322021-11-23 12:27:48 +00002047 switch (event->button)
2048 {
2049 case 1: // MOUSE_LEFT
2050 dragging_button_state = 0;
2051 break;
2052 }
2053
Bram Moolenaar071d4272004-06-13 20:20:40 +00002054 return TRUE;
2055}
2056
2057
2058#ifdef FEAT_DND
Bram Moolenaar30613902019-12-01 22:11:18 +01002059/////////////////////////////////////////////////////////////////////////////
2060// Drag aNd Drop support handlers.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002061
2062/*
2063 * Count how many items there may be and separate them with a NUL.
2064 * Apparently the items are separated with \r\n. This is not documented,
2065 * thus be careful not to go past the end. Also allow separation with
2066 * NUL characters.
2067 */
2068 static int
2069count_and_decode_uri_list(char_u *out, char_u *raw, int len)
2070{
2071 int i;
2072 char_u *p = out;
2073 int count = 0;
2074
2075 for (i = 0; i < len; ++i)
2076 {
2077 if (raw[i] == NUL || raw[i] == '\n' || raw[i] == '\r')
2078 {
2079 if (p > out && p[-1] != NUL)
2080 {
2081 ++count;
2082 *p++ = NUL;
2083 }
2084 }
2085 else if (raw[i] == '%' && i + 2 < len && hexhex2nr(raw + i + 1) > 0)
2086 {
2087 *p++ = hexhex2nr(raw + i + 1);
2088 i += 2;
2089 }
2090 else
2091 *p++ = raw[i];
2092 }
2093 if (p > out && p[-1] != NUL)
2094 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002095 *p = NUL; // last item didn't have \r or \n
Bram Moolenaar071d4272004-06-13 20:20:40 +00002096 ++count;
2097 }
2098 return count;
2099}
2100
2101/*
2102 * Parse NUL separated "src" strings. Make it an array "outlist" form. On
2103 * this process, URI which protocol is not "file:" are removed. Return
2104 * length of array (less than "max").
2105 */
2106 static int
2107filter_uri_list(char_u **outlist, int max, char_u *src)
2108{
2109 int i, j;
2110
2111 for (i = j = 0; i < max; ++i)
2112 {
2113 outlist[i] = NULL;
2114 if (STRNCMP(src, "file:", 5) == 0)
2115 {
2116 src += 5;
2117 if (STRNCMP(src, "//localhost", 11) == 0)
2118 src += 11;
2119 while (src[0] == '/' && src[1] == '/')
2120 ++src;
2121 outlist[j++] = vim_strsave(src);
2122 }
2123 src += STRLEN(src) + 1;
2124 }
2125 return j;
2126}
2127
2128 static char_u **
2129parse_uri_list(int *count, char_u *data, int len)
2130{
2131 int n = 0;
2132 char_u *tmp = NULL;
Bram Moolenaar945ec092016-06-08 21:17:43 +02002133 char_u **array = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002134
Bram Moolenaarc799fe22019-05-28 23:08:19 +02002135 if (data != NULL && len > 0 && (tmp = alloc(len + 1)) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002136 {
2137 n = count_and_decode_uri_list(tmp, data, len);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02002138 if (n > 0 && (array = ALLOC_MULT(char_u *, n)) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002139 n = filter_uri_list(array, n, tmp);
2140 }
2141 vim_free(tmp);
2142 *count = n;
2143 return array;
2144}
2145
2146 static void
2147drag_handle_uri_list(GdkDragContext *context,
2148 GtkSelectionData *data,
2149 guint time_,
2150 GdkModifierType state,
2151 gint x,
2152 gint y)
2153{
2154 char_u **fnames;
2155 int nfiles = 0;
2156
Bram Moolenaar98921892016-02-23 17:14:37 +01002157 fnames = parse_uri_list(&nfiles,
2158 (char_u *)gtk_selection_data_get_data(data),
2159 gtk_selection_data_get_length(data));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002160
2161 if (fnames != NULL && nfiles > 0)
2162 {
Bram Moolenaar19a09a12005-03-04 23:39:37 +00002163 int_u modifiers;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002164
Bram Moolenaar30613902019-12-01 22:11:18 +01002165 gtk_drag_finish(context, TRUE, FALSE, time_); // accept
Bram Moolenaar071d4272004-06-13 20:20:40 +00002166
Bram Moolenaar19a09a12005-03-04 23:39:37 +00002167 modifiers = modifiers_gdk2mouse(state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002168
2169 gui_handle_drop(x, y, modifiers, fnames, nfiles);
2170 }
Bram Moolenaareb3593b2006-04-22 22:33:57 +00002171 else
2172 vim_free(fnames);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002173}
2174
2175 static void
2176drag_handle_text(GdkDragContext *context,
2177 GtkSelectionData *data,
2178 guint time_,
2179 GdkModifierType state)
2180{
2181 char_u dropkey[6] = {CSI, KS_MODIFIER, 0, CSI, KS_EXTRA, (char_u)KE_DROP};
2182 char_u *text;
2183 int len;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002184 char_u *tmpbuf = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002185
Bram Moolenaar98921892016-02-23 17:14:37 +01002186 text = (char_u *)gtk_selection_data_get_data(data);
2187 len = gtk_selection_data_get_length(data);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002188
Bram Moolenaar98921892016-02-23 17:14:37 +01002189 if (gtk_selection_data_get_data_type(data) == utf8_string_atom)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002190 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00002191 if (input_conv.vc_type != CONV_NONE)
2192 tmpbuf = string_convert(&input_conv, text, &len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002193 if (tmpbuf != NULL)
2194 text = tmpbuf;
2195 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002196
2197 dnd_yank_drag_data(text, (long)len);
Bram Moolenaar30613902019-12-01 22:11:18 +01002198 gtk_drag_finish(context, TRUE, FALSE, time_); // accept
Bram Moolenaar071d4272004-06-13 20:20:40 +00002199 vim_free(tmpbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002200
Bram Moolenaar19a09a12005-03-04 23:39:37 +00002201 dropkey[2] = modifiers_gdk2vim(state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002202
2203 if (dropkey[2] != 0)
2204 add_to_input_buf(dropkey, (int)sizeof(dropkey));
2205 else
2206 add_to_input_buf(dropkey + 3, (int)(sizeof(dropkey) - 3));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002207}
2208
2209/*
2210 * DND receiver.
2211 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002212 static void
2213drag_data_received_cb(GtkWidget *widget,
2214 GdkDragContext *context,
2215 gint x,
2216 gint y,
2217 GtkSelectionData *data,
2218 guint info,
2219 guint time_,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002220 gpointer user_data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002221{
2222 GdkModifierType state;
2223
Bram Moolenaar30613902019-12-01 22:11:18 +01002224 // Guard against trash
Bram Moolenaar98921892016-02-23 17:14:37 +01002225 const guchar * const data_data = gtk_selection_data_get_data(data);
2226 const gint data_length = gtk_selection_data_get_length(data);
2227 const gint data_format = gtk_selection_data_get_format(data);
2228
2229 if (data_data == NULL
2230 || data_length <= 0
2231 || data_format != 8
2232 || data_data[data_length] != '\0')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002233 {
2234 gtk_drag_finish(context, FALSE, FALSE, time_);
2235 return;
2236 }
2237
Bram Moolenaar30613902019-12-01 22:11:18 +01002238 // Get the current modifier state for proper distinguishment between
2239 // different operations later.
Bram Moolenaar98921892016-02-23 17:14:37 +01002240 gui_gtk_get_pointer(widget, NULL, NULL, &state);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002241
Bram Moolenaar30613902019-12-01 22:11:18 +01002242 // Not sure about the role of "text/plain" here...
Bram Moolenaar071d4272004-06-13 20:20:40 +00002243 if (info == (guint)TARGET_TEXT_URI_LIST)
2244 drag_handle_uri_list(context, data, time_, state, x, y);
2245 else
2246 drag_handle_text(context, data, time_, state);
2247
2248}
Bram Moolenaar30613902019-12-01 22:11:18 +01002249#endif // FEAT_DND
Bram Moolenaar071d4272004-06-13 20:20:40 +00002250
2251
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02002252#if defined(USE_GNOME_SESSION)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002253/*
2254 * GnomeClient interact callback. Check for unsaved buffers that cannot
2255 * be abandoned and pop up a dialog asking the user for confirmation if
2256 * necessary.
2257 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002258 static void
Bram Moolenaar30bb4142010-05-17 22:07:15 +02002259sm_client_check_changed_any(GnomeClient *client UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002260 gint key,
Bram Moolenaar30bb4142010-05-17 22:07:15 +02002261 GnomeDialogType type UNUSED,
2262 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002263{
2264 cmdmod_T save_cmdmod;
2265 gboolean shutdown_cancelled;
2266
2267 save_cmdmod = cmdmod;
2268
2269# ifdef FEAT_BROWSE
Bram Moolenaare1004402020-10-24 20:49:43 +02002270 cmdmod.cmod_flags |= CMOD_BROWSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002271# endif
2272# if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
Bram Moolenaare1004402020-10-24 20:49:43 +02002273 cmdmod.cmod_flags |= CMOD_CONFIRM;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002274# endif
2275 /*
2276 * If there are changed buffers, present the user with
2277 * a dialog if possible, otherwise give an error message.
2278 */
Bram Moolenaar027387f2016-01-02 22:25:52 +01002279 shutdown_cancelled = check_changed_any(FALSE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002280
2281 exiting = FALSE;
2282 cmdmod = save_cmdmod;
Bram Moolenaar30613902019-12-01 22:11:18 +01002283 setcursor(); // position the cursor
Bram Moolenaar071d4272004-06-13 20:20:40 +00002284 out_flush();
2285 /*
2286 * If the user hit the [Cancel] button the whole shutdown
2287 * will be cancelled. Wow, quite powerful feature (:
2288 */
2289 gnome_interaction_key_return(key, shutdown_cancelled);
2290}
2291
2292/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002293 * "save_yourself" signal handler. Initiate an interaction to ask the user
2294 * for confirmation if necessary. Save the current editing session and tell
2295 * the session manager how to restart Vim.
2296 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002297 static gboolean
2298sm_client_save_yourself(GnomeClient *client,
Bram Moolenaar30bb4142010-05-17 22:07:15 +02002299 gint phase UNUSED,
2300 GnomeSaveStyle save_style UNUSED,
2301 gboolean shutdown UNUSED,
Bram Moolenaar071d4272004-06-13 20:20:40 +00002302 GnomeInteractStyle interact_style,
Bram Moolenaar30bb4142010-05-17 22:07:15 +02002303 gboolean fast UNUSED,
2304 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002305{
2306 static const char suffix[] = "-session.vim";
2307 char *session_file;
2308 unsigned int len;
2309 gboolean success;
2310
Bram Moolenaar30613902019-12-01 22:11:18 +01002311 // Always request an interaction if possible. check_changed_any()
2312 // won't actually show a dialog unless any buffers have been modified.
2313 // There doesn't seem to be an obvious way to check that without
2314 // automatically firing the dialog. Anyway, it works just fine.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002315 if (interact_style == GNOME_INTERACT_ANY)
2316 gnome_client_request_interaction(client, GNOME_DIALOG_NORMAL,
2317 &sm_client_check_changed_any,
2318 NULL);
2319 out_flush();
Bram Moolenaar30613902019-12-01 22:11:18 +01002320 ml_sync_all(FALSE, FALSE); // preserve all swap files
Bram Moolenaar071d4272004-06-13 20:20:40 +00002321
Bram Moolenaar30613902019-12-01 22:11:18 +01002322 // The path is unique for each session save. We do neither know nor care
2323 // which session script will actually be used later. This decision is in
2324 // the domain of the session manager.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002325 session_file = gnome_config_get_real_path(
2326 gnome_client_get_config_prefix(client));
2327 len = strlen(session_file);
2328
2329 if (len > 0 && session_file[len-1] == G_DIR_SEPARATOR)
Bram Moolenaar30613902019-12-01 22:11:18 +01002330 --len; // get rid of the superfluous trailing '/'
Bram Moolenaar071d4272004-06-13 20:20:40 +00002331
2332 session_file = g_renew(char, session_file, len + sizeof(suffix));
2333 memcpy(session_file + len, suffix, sizeof(suffix));
2334
2335 success = write_session_file((char_u *)session_file);
2336
2337 if (success)
2338 {
2339 const char *argv[8];
2340 int i;
2341
Bram Moolenaar30613902019-12-01 22:11:18 +01002342 // Tell the session manager how to wipe out the stored session data.
2343 // This isn't as dangerous as it looks, don't worry :) session_file
2344 // is a unique absolute filename. Usually it'll be something like
2345 // `/home/user/.gnome2/vim-XXXXXX-session.vim'.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002346 i = 0;
2347 argv[i++] = "rm";
2348 argv[i++] = session_file;
2349 argv[i] = NULL;
2350
2351 gnome_client_set_discard_command(client, i, (char **)argv);
2352
Bram Moolenaar30613902019-12-01 22:11:18 +01002353 // Tell the session manager how to restore the just saved session.
2354 // This is easily done thanks to Vim's -S option. Pass the -f flag
2355 // since there's no need to fork -- it might even cause confusion.
2356 // Also pass the window role to give the WM something to match on.
2357 // The role is set in gui_mch_open(), thus should _never_ be NULL.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002358 i = 0;
2359 argv[i++] = restart_command;
2360 argv[i++] = "-f";
2361 argv[i++] = "-g";
Bram Moolenaar071d4272004-06-13 20:20:40 +00002362 argv[i++] = "--role";
2363 argv[i++] = gtk_window_get_role(GTK_WINDOW(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002364 argv[i++] = "-S";
2365 argv[i++] = session_file;
2366 argv[i] = NULL;
2367
2368 gnome_client_set_restart_command(client, i, (char **)argv);
2369 gnome_client_set_clone_command(client, 0, NULL);
2370 }
2371
2372 g_free(session_file);
2373
2374 return success;
2375}
2376
2377/*
2378 * Called when the session manager wants us to die. There isn't much to save
2379 * here since "save_yourself" has been emitted before (unless serious trouble
2380 * is happening).
2381 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002382 static void
Bram Moolenaar30bb4142010-05-17 22:07:15 +02002383sm_client_die(GnomeClient *client UNUSED, gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002384{
Bram Moolenaar30613902019-12-01 22:11:18 +01002385 // Don't write messages to the GUI anymore
Bram Moolenaar071d4272004-06-13 20:20:40 +00002386 full_screen = FALSE;
2387
Bram Moolenaarc93e7912008-07-08 10:46:08 +00002388 vim_strncpy(IObuff, (char_u *)
Bram Moolenaarce0842a2005-07-18 21:58:11 +00002389 _("Vim: Received \"die\" request from session manager\n"),
Bram Moolenaarc93e7912008-07-08 10:46:08 +00002390 IOSIZE - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002391 preserve_exit();
2392}
2393
2394/*
2395 * Connect our signal handlers to be notified on session save and shutdown.
2396 */
2397 static void
2398setup_save_yourself(void)
2399{
2400 GnomeClient *client;
2401
2402 client = gnome_master_client();
2403
2404 if (client != NULL)
2405 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002406 // Must use the deprecated gtk_signal_connect() for compatibility
2407 // with GNOME 1. Arrgh, zombies!
Bram Moolenaar071d4272004-06-13 20:20:40 +00002408 gtk_signal_connect(GTK_OBJECT(client), "save_yourself",
2409 GTK_SIGNAL_FUNC(&sm_client_save_yourself), NULL);
2410 gtk_signal_connect(GTK_OBJECT(client), "die",
2411 GTK_SIGNAL_FUNC(&sm_client_die), NULL);
2412 }
2413}
2414
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02002415#else // !USE_GNOME_SESSION
Bram Moolenaar071d4272004-06-13 20:20:40 +00002416
2417# ifdef USE_XSMP
2418/*
2419 * GTK tells us that XSMP needs attention
2420 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002421 static gboolean
Bram Moolenaar66f948e2016-01-30 16:39:25 +01002422local_xsmp_handle_requests(
2423 GIOChannel *source UNUSED,
2424 GIOCondition condition,
2425 gpointer data)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002426{
2427 if (condition == G_IO_IN)
2428 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002429 // Do stuff; maybe close connection
Bram Moolenaar071d4272004-06-13 20:20:40 +00002430 if (xsmp_handle_requests() == FAIL)
2431 g_io_channel_unref((GIOChannel *)data);
2432 return TRUE;
2433 }
Bram Moolenaar30613902019-12-01 22:11:18 +01002434 // Error
Bram Moolenaar071d4272004-06-13 20:20:40 +00002435 g_io_channel_unref((GIOChannel *)data);
2436 xsmp_close();
2437 return TRUE;
2438}
Bram Moolenaar30613902019-12-01 22:11:18 +01002439# endif // USE_XSMP
Bram Moolenaar071d4272004-06-13 20:20:40 +00002440
2441/*
2442 * Setup the WM_PROTOCOLS to indicate we want the WM_SAVE_YOURSELF event.
2443 * This is an ugly use of X functions. GTK doesn't offer an alternative.
2444 */
2445 static void
2446setup_save_yourself(void)
2447{
2448 Atom *existing_atoms = NULL;
2449 int count = 0;
2450
Bram Moolenaar98921892016-02-23 17:14:37 +01002451# ifdef USE_XSMP
Bram Moolenaar071d4272004-06-13 20:20:40 +00002452 if (xsmp_icefd != -1)
2453 {
2454 /*
2455 * Use XSMP is preference to legacy WM_SAVE_YOURSELF;
2456 * set up GTK IO monitor
2457 */
2458 GIOChannel *g_io = g_io_channel_unix_new(xsmp_icefd);
2459
2460 g_io_add_watch(g_io, G_IO_IN | G_IO_ERR | G_IO_HUP,
2461 local_xsmp_handle_requests, (gpointer)g_io);
Bram Moolenaar98921892016-02-23 17:14:37 +01002462 g_io_channel_unref(g_io);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002463 }
2464 else
Bram Moolenaar98921892016-02-23 17:14:37 +01002465# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002466 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002467 // Fall back to old method
Bram Moolenaar071d4272004-06-13 20:20:40 +00002468
Bram Moolenaar30613902019-12-01 22:11:18 +01002469 // first get the existing value
Bram Moolenaar98921892016-02-23 17:14:37 +01002470 GdkWindow * const mainwin_win = gtk_widget_get_window(gui.mainwin);
2471
2472 if (XGetWMProtocols(GDK_WINDOW_XDISPLAY(mainwin_win),
2473 GDK_WINDOW_XID(mainwin_win),
2474 &existing_atoms, &count))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002475 {
2476 Atom *new_atoms;
2477 Atom save_yourself_xatom;
2478 int i;
2479
2480 save_yourself_xatom = GET_X_ATOM(save_yourself_atom);
2481
Bram Moolenaar30613902019-12-01 22:11:18 +01002482 // check if WM_SAVE_YOURSELF isn't there yet
Bram Moolenaar071d4272004-06-13 20:20:40 +00002483 for (i = 0; i < count; ++i)
2484 if (existing_atoms[i] == save_yourself_xatom)
2485 break;
2486
2487 if (i == count)
2488 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002489 // allocate an Atoms array which is one item longer
Bram Moolenaarc799fe22019-05-28 23:08:19 +02002490 new_atoms = ALLOC_MULT(Atom, count + 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002491 if (new_atoms != NULL)
2492 {
2493 memcpy(new_atoms, existing_atoms, count * sizeof(Atom));
2494 new_atoms[count] = save_yourself_xatom;
Bram Moolenaar98921892016-02-23 17:14:37 +01002495 XSetWMProtocols(GDK_WINDOW_XDISPLAY(mainwin_win),
2496 GDK_WINDOW_XID(mainwin_win),
Bram Moolenaar071d4272004-06-13 20:20:40 +00002497 new_atoms, count + 1);
2498 vim_free(new_atoms);
2499 }
2500 }
2501 XFree(existing_atoms);
2502 }
2503 }
2504}
2505
Bram Moolenaar071d4272004-06-13 20:20:40 +00002506/*
2507 * Installing a global event filter seems to be the only way to catch
2508 * client messages of type WM_PROTOCOLS without overriding GDK's own
2509 * client message event filter. Well, that's still better than trying
2510 * to guess what the GDK filter had done if it had been invoked instead
Bram Moolenaar071d4272004-06-13 20:20:40 +00002511 *
2512 * GTK2_FIXME: This doesn't seem to work. For some reason we never
2513 * receive WM_SAVE_YOURSELF even though everything is set up correctly.
2514 * I have the nasty feeling modern session managers just don't send this
2515 * deprecated message anymore. Addition: confirmed by several people.
2516 *
2517 * The GNOME session support is much cooler anyway. Unlike this ugly
2518 * WM_SAVE_YOURSELF hack it actually stores the session... And yes,
2519 * it should work with KDE as well.
2520 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002521 static GdkFilterReturn
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002522global_event_filter(GdkXEvent *xev,
2523 GdkEvent *event UNUSED,
2524 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002525{
2526 XEvent *xevent = (XEvent *)xev;
2527
2528 if (xevent != NULL
2529 && xevent->type == ClientMessage
2530 && xevent->xclient.message_type == GET_X_ATOM(wm_protocols_atom)
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002531 && (long_u)xevent->xclient.data.l[0]
2532 == GET_X_ATOM(save_yourself_atom))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002533 {
2534 out_flush();
Bram Moolenaar30613902019-12-01 22:11:18 +01002535 ml_sync_all(FALSE, FALSE); // preserve all swap files
Bram Moolenaar071d4272004-06-13 20:20:40 +00002536 /*
2537 * Set the window's WM_COMMAND property, to let the window manager
2538 * know we are done saving ourselves. We don't want to be
2539 * restarted, thus set argv to NULL.
2540 */
Bram Moolenaar98921892016-02-23 17:14:37 +01002541 XSetCommand(GDK_WINDOW_XDISPLAY(gtk_widget_get_window(gui.mainwin)),
2542 GDK_WINDOW_XID(gtk_widget_get_window(gui.mainwin)),
Bram Moolenaar071d4272004-06-13 20:20:40 +00002543 NULL, 0);
2544 return GDK_FILTER_REMOVE;
2545 }
2546
2547 return GDK_FILTER_CONTINUE;
2548}
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02002549#endif // !USE_GNOME_SESSION
Bram Moolenaar071d4272004-06-13 20:20:40 +00002550
2551
2552/*
2553 * Setup the window icon & xcmdsrv comm after the main window has been realized.
2554 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002555 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002556mainwin_realize(GtkWidget *widget UNUSED, gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002557{
Bram Moolenaar30613902019-12-01 22:11:18 +01002558// If you get an error message here, you still need to unpack the runtime
2559// archive!
Bram Moolenaar071d4272004-06-13 20:20:40 +00002560#ifdef magick
2561# undef magick
2562#endif
Bram Moolenaar30613902019-12-01 22:11:18 +01002563 // A bit hackish, but avoids casting later and allows optimization
Bram Moolenaar071d4272004-06-13 20:20:40 +00002564# define static static const
Bram Moolenaar071d4272004-06-13 20:20:40 +00002565#define magick vim32x32
2566#include "../runtime/vim32x32.xpm"
2567#undef magick
2568#define magick vim16x16
2569#include "../runtime/vim16x16.xpm"
2570#undef magick
2571#define magick vim48x48
2572#include "../runtime/vim48x48.xpm"
2573#undef magick
Bram Moolenaar071d4272004-06-13 20:20:40 +00002574# undef static
Bram Moolenaar071d4272004-06-13 20:20:40 +00002575
Bram Moolenaar98921892016-02-23 17:14:37 +01002576 GdkWindow * const mainwin_win = gtk_widget_get_window(gui.mainwin);
Bram Moolenaar98921892016-02-23 17:14:37 +01002577
Bram Moolenaar30613902019-12-01 22:11:18 +01002578 // When started with "--echo-wid" argument, write window ID on stdout.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002579 if (echo_wid_arg)
2580 {
Bram Moolenaar98921892016-02-23 17:14:37 +01002581 printf("WID: %ld\n", (long)GDK_WINDOW_XID(mainwin_win));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002582 fflush(stdout);
2583 }
2584
2585 if (vim_strchr(p_go, GO_ICON) != NULL)
2586 {
2587 /*
2588 * Add an icon to the main window. For fun and convenience of the user.
2589 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002590 GList *icons = NULL;
2591
2592 icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim16x16));
2593 icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim32x32));
2594 icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim48x48));
2595
2596 gtk_window_set_icon_list(GTK_WINDOW(gui.mainwin), icons);
2597
Bram Moolenaar81a4cf42020-09-09 19:05:13 +02002598 // TODO: is this type cast OK?
2599 g_list_foreach(icons, (GFunc)(void *)&g_object_unref, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002600 g_list_free(icons);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002601 }
2602
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02002603#if !defined(USE_GNOME_SESSION)
Bram Moolenaar30613902019-12-01 22:11:18 +01002604 // Register a handler for WM_SAVE_YOURSELF with GDK's low-level X I/F
Bram Moolenaar071d4272004-06-13 20:20:40 +00002605 gdk_window_add_filter(NULL, &global_event_filter, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002606#endif
Bram Moolenaar30613902019-12-01 22:11:18 +01002607 // Setup to indicate to the window manager that we want to catch the
2608 // WM_SAVE_YOURSELF event. For GNOME, this connects to the session
2609 // manager instead.
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02002610#if defined(USE_GNOME_SESSION)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002611 if (using_gnome)
2612#endif
2613 setup_save_yourself();
2614
2615#ifdef FEAT_CLIENTSERVER
2616 if (serverName == NULL && serverDelayedStartName != NULL)
2617 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002618 // This is a :gui command in a plain vim with no previous server
Bram Moolenaar98921892016-02-23 17:14:37 +01002619 commWindow = GDK_WINDOW_XID(mainwin_win);
2620
2621 (void)serverRegisterName(GDK_WINDOW_XDISPLAY(mainwin_win),
2622 serverDelayedStartName);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002623 }
2624 else
2625 {
2626 /*
2627 * Cannot handle "XLib-only" windows with gtk event routines, we'll
2628 * have to change the "server" registration to that of the main window
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002629 * If we have not registered a name yet, remember the window.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002630 */
Bram Moolenaar98921892016-02-23 17:14:37 +01002631 serverChangeRegisteredWindow(GDK_WINDOW_XDISPLAY(mainwin_win),
2632 GDK_WINDOW_XID(mainwin_win));
Bram Moolenaar071d4272004-06-13 20:20:40 +00002633 }
2634 gtk_widget_add_events(gui.mainwin, GDK_PROPERTY_CHANGE_MASK);
Bram Moolenaar98921892016-02-23 17:14:37 +01002635 g_signal_connect(G_OBJECT(gui.mainwin), "property-notify-event",
2636 G_CALLBACK(property_event), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002637#endif
2638}
2639
2640 static GdkCursor *
2641create_blank_pointer(void)
2642{
2643 GdkWindow *root_window = NULL;
Bram Moolenaar98921892016-02-23 17:14:37 +01002644#if GTK_CHECK_VERSION(3,0,0)
2645 GdkPixbuf *blank_mask;
2646#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002647 GdkPixmap *blank_mask;
Bram Moolenaar98921892016-02-23 17:14:37 +01002648#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002649 GdkCursor *cursor;
Bram Moolenaar36edf062016-07-21 22:10:12 +02002650#if GTK_CHECK_VERSION(3,0,0)
2651 GdkRGBA color = { 0.0, 0.0, 0.0, 1.0 };
2652#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002653 GdkColor color = { 0, 0, 0, 0 };
2654 char blank_data[] = { 0x0 };
Bram Moolenaar98921892016-02-23 17:14:37 +01002655#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002656
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002657#if GTK_CHECK_VERSION(3,12,0)
Bram Moolenaar98921892016-02-23 17:14:37 +01002658 {
2659 GdkWindow * const win = gtk_widget_get_window(gui.mainwin);
2660 GdkScreen * const scrn = gdk_window_get_screen(win);
2661 root_window = gdk_screen_get_root_window(scrn);
2662 }
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002663#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002664 root_window = gtk_widget_get_root_window(gui.mainwin);
2665#endif
2666
Bram Moolenaar30613902019-12-01 22:11:18 +01002667 // Create a pseudo blank pointer, which is in fact one pixel by one pixel
2668 // in size.
Bram Moolenaar98921892016-02-23 17:14:37 +01002669#if GTK_CHECK_VERSION(3,0,0)
2670 {
2671 cairo_surface_t *surf;
2672 cairo_t *cr;
2673
2674 surf = cairo_image_surface_create(CAIRO_FORMAT_A1, 1, 1);
2675 cr = cairo_create(surf);
2676
Bram Moolenaar36edf062016-07-21 22:10:12 +02002677 cairo_set_source_rgba(cr,
2678 color.red,
2679 color.green,
2680 color.blue,
2681 color.alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01002682 cairo_rectangle(cr, 0, 0, 1, 1);
2683 cairo_fill(cr);
2684 cairo_destroy(cr);
2685
2686 blank_mask = gdk_pixbuf_get_from_surface(surf, 0, 0, 1, 1);
2687 cairo_surface_destroy(surf);
2688
2689 cursor = gdk_cursor_new_from_pixbuf(gdk_window_get_display(root_window),
2690 blank_mask, 0, 0);
2691 g_object_unref(blank_mask);
2692 }
2693#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002694 blank_mask = gdk_bitmap_create_from_data(root_window, blank_data, 1, 1);
2695 cursor = gdk_cursor_new_from_pixmap(blank_mask, blank_mask,
2696 &color, &color, 0, 0);
2697 gdk_bitmap_unref(blank_mask);
Bram Moolenaar98921892016-02-23 17:14:37 +01002698#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002699
2700 return cursor;
2701}
2702
Bram Moolenaar071d4272004-06-13 20:20:40 +00002703 static void
2704mainwin_screen_changed_cb(GtkWidget *widget,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002705 GdkScreen *previous_screen UNUSED,
2706 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002707{
2708 if (!gtk_widget_has_screen(widget))
2709 return;
2710
2711 /*
Bram Moolenaar49325942007-05-10 19:19:59 +00002712 * Recreate the invisible mouse cursor.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002713 */
2714 if (gui.blank_pointer != NULL)
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002715#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar98921892016-02-23 17:14:37 +01002716 g_object_unref(G_OBJECT(gui.blank_pointer));
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002717#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002718 gdk_cursor_unref(gui.blank_pointer);
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02002719#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002720
2721 gui.blank_pointer = create_blank_pointer();
2722
Bram Moolenaar98921892016-02-23 17:14:37 +01002723 if (gui.pointer_hidden && gtk_widget_get_window(gui.drawarea) != NULL)
2724 gdk_window_set_cursor(gtk_widget_get_window(gui.drawarea),
2725 gui.blank_pointer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002726
2727 /*
2728 * Create a new PangoContext for this screen, and initialize it
2729 * with the current font if necessary.
2730 */
2731 if (gui.text_context != NULL)
2732 g_object_unref(gui.text_context);
2733
2734 gui.text_context = gtk_widget_create_pango_context(widget);
2735 pango_context_set_base_dir(gui.text_context, PANGO_DIRECTION_LTR);
2736
2737 if (gui.norm_font != NULL)
2738 {
Bram Moolenaar46c9c732004-12-12 11:37:09 +00002739 gui_mch_init_font(p_guifont, FALSE);
Bram Moolenaar8ac44152017-11-09 18:33:29 +01002740 gui_set_shellsize(TRUE, FALSE, RESIZE_BOTH);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002741 }
2742}
Bram Moolenaar071d4272004-06-13 20:20:40 +00002743
2744/*
2745 * After the drawing area comes up, we calculate all colors and create the
2746 * dummy blank cursor.
2747 *
2748 * Don't try to set any VIM scrollbar sizes anywhere here. I'm relying on the
2749 * fact that the main VIM engine doesn't take them into account anywhere.
2750 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002751 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002752drawarea_realize_cb(GtkWidget *widget, gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002753{
2754 GtkWidget *sbar;
Bram Moolenaar98921892016-02-23 17:14:37 +01002755 GtkAllocation allocation;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002756
2757#ifdef FEAT_XIM
2758 xim_init();
2759#endif
2760 gui_mch_new_colors();
Bram Moolenaar98921892016-02-23 17:14:37 +01002761#if GTK_CHECK_VERSION(3,0,0)
2762 gui.surface = gdk_window_create_similar_surface(
2763 gtk_widget_get_window(widget),
2764 CAIRO_CONTENT_COLOR_ALPHA,
2765 gtk_widget_get_allocated_width(widget),
2766 gtk_widget_get_allocated_height(widget));
2767#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002768 gui.text_gc = gdk_gc_new(gui.drawarea->window);
Bram Moolenaar98921892016-02-23 17:14:37 +01002769#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002770
2771 gui.blank_pointer = create_blank_pointer();
2772 if (gui.pointer_hidden)
Bram Moolenaar98921892016-02-23 17:14:37 +01002773 gdk_window_set_cursor(gtk_widget_get_window(widget), gui.blank_pointer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002774
Bram Moolenaar30613902019-12-01 22:11:18 +01002775 // get the actual size of the scrollbars, if they are realized
Bram Moolenaar071d4272004-06-13 20:20:40 +00002776 sbar = firstwin->w_scrollbars[SBAR_LEFT].id;
2777 if (!sbar || (!gui.which_scrollbars[SBAR_LEFT]
2778 && firstwin->w_scrollbars[SBAR_RIGHT].id))
2779 sbar = firstwin->w_scrollbars[SBAR_RIGHT].id;
Bram Moolenaar98921892016-02-23 17:14:37 +01002780 gtk_widget_get_allocation(sbar, &allocation);
2781 if (sbar && gtk_widget_get_realized(sbar) && allocation.width)
2782 gui.scrollbar_width = allocation.width;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002783
2784 sbar = gui.bottom_sbar.id;
Bram Moolenaar98921892016-02-23 17:14:37 +01002785 if (sbar && gtk_widget_get_realized(sbar) && allocation.height)
2786 gui.scrollbar_height = allocation.height;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002787}
2788
2789/*
2790 * Properly clean up on shutdown.
2791 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002792 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002793drawarea_unrealize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002794{
Bram Moolenaar30613902019-12-01 22:11:18 +01002795 // Don't write messages to the GUI anymore
Bram Moolenaar071d4272004-06-13 20:20:40 +00002796 full_screen = FALSE;
2797
2798#ifdef FEAT_XIM
2799 im_shutdown();
2800#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002801 if (gui.ascii_glyphs != NULL)
2802 {
2803 pango_glyph_string_free(gui.ascii_glyphs);
2804 gui.ascii_glyphs = NULL;
2805 }
2806 if (gui.ascii_font != NULL)
2807 {
2808 g_object_unref(gui.ascii_font);
2809 gui.ascii_font = NULL;
2810 }
2811 g_object_unref(gui.text_context);
2812 gui.text_context = NULL;
2813
Bram Moolenaar98921892016-02-23 17:14:37 +01002814#if GTK_CHECK_VERSION(3,0,0)
2815 if (gui.surface != NULL)
2816 {
2817 cairo_surface_destroy(gui.surface);
2818 gui.surface = NULL;
2819 }
2820#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002821 g_object_unref(gui.text_gc);
2822 gui.text_gc = NULL;
Bram Moolenaar98921892016-02-23 17:14:37 +01002823#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002824
Bram Moolenaar98921892016-02-23 17:14:37 +01002825#if GTK_CHECK_VERSION(3,0,0)
2826 g_object_unref(G_OBJECT(gui.blank_pointer));
2827#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002828 gdk_cursor_unref(gui.blank_pointer);
Bram Moolenaar98921892016-02-23 17:14:37 +01002829#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002830 gui.blank_pointer = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002831}
2832
Bram Moolenaara859f042016-11-17 19:11:55 +01002833#if GTK_CHECK_VERSION(3,22,2)
2834 static void
2835drawarea_style_updated_cb(GtkWidget *widget UNUSED,
2836 gpointer data UNUSED)
2837#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002838 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002839drawarea_style_set_cb(GtkWidget *widget UNUSED,
2840 GtkStyle *previous_style UNUSED,
2841 gpointer data UNUSED)
Bram Moolenaara859f042016-11-17 19:11:55 +01002842#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002843{
2844 gui_mch_new_colors();
2845}
2846
Bram Moolenaar98921892016-02-23 17:14:37 +01002847#if GTK_CHECK_VERSION(3,0,0)
2848 static gboolean
2849drawarea_configure_event_cb(GtkWidget *widget,
2850 GdkEventConfigure *event,
2851 gpointer data UNUSED)
2852{
2853 static int cur_width = 0;
2854 static int cur_height = 0;
2855
2856 g_return_val_if_fail(event
2857 && event->width >= 1 && event->height >= 1, TRUE);
2858
Bram Moolenaar182707a2016-11-21 20:55:58 +01002859# if GTK_CHECK_VERSION(3,22,2) && !GTK_CHECK_VERSION(3,22,4)
Bram Moolenaar30613902019-12-01 22:11:18 +01002860 // As of 3.22.2, GdkWindows have started distributing configure events to
2861 // their "native" children (https://git.gnome.org/browse/gtk+/commit/?h=gtk-3-22&id=12579fe71b3b8f79eb9c1b80e429443bcc437dd0).
2862 //
2863 // As can be seen from the implementation of move_native_children() and
2864 // configure_native_child() in gdkwindow.c, those functions actually
2865 // propagate configure events to every child, failing to distinguish
2866 // "native" one from non-native one.
2867 //
2868 // Naturally, configure events propagated to here like that are fallacious
2869 // and, as a matter of fact, they trigger a geometric collapse of
2870 // gui.drawarea in fullscreen and maximized modes.
2871 //
2872 // To filter out such nuisance events, we are making use of the fact that
2873 // the field send_event of such GdkEventConfigures is set to FALSE in
2874 // configure_native_child().
2875 //
2876 // Obviously, this is a terrible hack making GVim depend on GTK's
2877 // implementation details. Therefore, watch out any relevant internal
2878 // changes happening in GTK in the feature (sigh).
2879 //
2880 // Follow-up
2881 // After a few weeks later, the GdkWindow change mentioned above was
2882 // reverted (https://git.gnome.org/browse/gtk+/commit/?h=gtk-3-22&id=f70039cb9603a02d2369fec4038abf40a1711155).
2883 // The corresponding official release is 3.22.4.
Bram Moolenaara859f042016-11-17 19:11:55 +01002884 if (event->send_event == FALSE)
2885 return TRUE;
2886# endif
2887
Bram Moolenaar98921892016-02-23 17:14:37 +01002888 if (event->width == cur_width && event->height == cur_height)
2889 return TRUE;
2890
2891 cur_width = event->width;
2892 cur_height = event->height;
2893
2894 if (gui.surface != NULL)
2895 cairo_surface_destroy(gui.surface);
2896
2897 gui.surface = gdk_window_create_similar_surface(
2898 gtk_widget_get_window(widget),
2899 CAIRO_CONTENT_COLOR_ALPHA,
2900 event->width, event->height);
2901
2902 gtk_widget_queue_draw(widget);
2903
2904 return TRUE;
2905}
2906#endif
2907
Bram Moolenaar071d4272004-06-13 20:20:40 +00002908/*
2909 * Callback routine for the "delete_event" signal on the toplevel window.
2910 * Tries to vim gracefully, or refuses to exit with changed buffers.
2911 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002912 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00002913delete_event_cb(GtkWidget *widget UNUSED,
2914 GdkEventAny *event UNUSED,
2915 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002916{
2917 gui_shell_closed();
2918 return TRUE;
2919}
2920
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002921#if defined(FEAT_MENU) || defined(FEAT_TOOLBAR) || defined(FEAT_GUI_TABLINE)
2922 static int
2923get_item_dimensions(GtkWidget *widget, GtkOrientation orientation)
2924{
Bram Moolenaarc4a249a2017-01-30 22:56:48 +01002925# ifdef FEAT_GUI_GNOME
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002926 GtkOrientation item_orientation = GTK_ORIENTATION_HORIZONTAL;
2927
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002928 if (using_gnome && widget != NULL)
2929 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00002930 GtkWidget *parent;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002931 BonoboDockItem *dockitem;
2932
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00002933 parent = gtk_widget_get_parent(widget);
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00002934 if (G_TYPE_FROM_INSTANCE(parent) == BONOBO_TYPE_DOCK_ITEM)
2935 {
Bram Moolenaar30613902019-12-01 22:11:18 +01002936 // Only menu & toolbar are dock items. Could tabline be?
2937 // Seem to be only the 2 defined in GNOME
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00002938 widget = parent;
2939 dockitem = BONOBO_DOCK_ITEM(widget);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002940
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00002941 if (dockitem == NULL || dockitem->is_floating)
2942 return 0;
2943 item_orientation = bonobo_dock_item_get_orientation(dockitem);
2944 }
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002945 }
Bram Moolenaarc4a249a2017-01-30 22:56:48 +01002946# else
2947# define item_orientation GTK_ORIENTATION_HORIZONTAL
Bram Moolenaar98921892016-02-23 17:14:37 +01002948# endif
Bram Moolenaarc4a249a2017-01-30 22:56:48 +01002949
Bram Moolenaar98921892016-02-23 17:14:37 +01002950 if (widget != NULL
2951 && item_orientation == orientation
2952 && gtk_widget_get_realized(widget)
2953 && gtk_widget_get_visible(widget))
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002954 {
Bram Moolenaar664323e2018-09-18 22:30:07 +02002955# if GTK_CHECK_VERSION(3,0,0) || !defined(FEAT_GUI_GNOME)
Bram Moolenaar98921892016-02-23 17:14:37 +01002956 GtkAllocation allocation;
2957
2958 gtk_widget_get_allocation(widget, &allocation);
Bram Moolenaarc4a249a2017-01-30 22:56:48 +01002959 return allocation.height;
Bram Moolenaar98921892016-02-23 17:14:37 +01002960# else
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002961 if (orientation == GTK_ORIENTATION_HORIZONTAL)
2962 return widget->allocation.height;
2963 else
2964 return widget->allocation.width;
Bram Moolenaar98921892016-02-23 17:14:37 +01002965# endif
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002966 }
2967 return 0;
2968}
2969#endif
2970
2971 static int
2972get_menu_tool_width(void)
2973{
2974 int width = 0;
2975
Bram Moolenaar30613902019-12-01 22:11:18 +01002976#ifdef FEAT_GUI_GNOME // these are never vertical without GNOME
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002977# ifdef FEAT_MENU
2978 width += get_item_dimensions(gui.menubar, GTK_ORIENTATION_VERTICAL);
2979# endif
2980# ifdef FEAT_TOOLBAR
2981 width += get_item_dimensions(gui.toolbar, GTK_ORIENTATION_VERTICAL);
2982# endif
2983# ifdef FEAT_GUI_TABLINE
Bram Moolenaar97b2ad32006-03-18 21:40:56 +00002984 if (gui.tabline != NULL)
2985 width += get_item_dimensions(gui.tabline, GTK_ORIENTATION_VERTICAL);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00002986# endif
2987#endif
2988
2989 return width;
2990}
2991
2992 static int
2993get_menu_tool_height(void)
2994{
2995 int height = 0;
2996
2997#ifdef FEAT_MENU
2998 height += get_item_dimensions(gui.menubar, GTK_ORIENTATION_HORIZONTAL);
2999#endif
3000#ifdef FEAT_TOOLBAR
3001 height += get_item_dimensions(gui.toolbar, GTK_ORIENTATION_HORIZONTAL);
3002#endif
3003#ifdef FEAT_GUI_TABLINE
Bram Moolenaar97b2ad32006-03-18 21:40:56 +00003004 if (gui.tabline != NULL)
3005 height += get_item_dimensions(gui.tabline, GTK_ORIENTATION_HORIZONTAL);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003006#endif
3007
3008 return height;
3009}
3010
Bram Moolenaar30613902019-12-01 22:11:18 +01003011// This controls whether we can set the real window hints at
3012// start-up when in a GtkPlug.
3013// 0 = normal processing (default)
3014// 1 = init. hints set, no-one's tried to reset since last check
3015// 2 = init. hints set, attempt made to change hints
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003016static int init_window_hints_state = 0;
3017
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003018 static void
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003019update_window_manager_hints(int force_width, int force_height)
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003020{
3021 static int old_width = 0;
3022 static int old_height = 0;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003023 static int old_min_width = 0;
3024 static int old_min_height = 0;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003025 static int old_char_width = 0;
3026 static int old_char_height = 0;
3027
3028 int width;
3029 int height;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003030 int min_width;
3031 int min_height;
3032
Bram Moolenaar30613902019-12-01 22:11:18 +01003033 // At start-up, don't try to set the hints until the initial
3034 // values have been used (those that dictate our initial size)
3035 // Let forced (i.e., correct) values through always.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003036 if (!(force_width && force_height) && init_window_hints_state > 0)
3037 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003038 // Don't do it!
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003039 init_window_hints_state = 2;
3040 return;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003041 }
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003042
Bram Moolenaar30613902019-12-01 22:11:18 +01003043 // This also needs to be done when the main window isn't there yet,
3044 // otherwise the hints don't work.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003045 width = gui_get_base_width();
3046 height = gui_get_base_height();
3047# ifdef FEAT_MENU
3048 height += tabline_height() * gui.char_height;
3049# endif
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003050 width += get_menu_tool_width();
3051 height += get_menu_tool_height();
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003052
Bram Moolenaar30613902019-12-01 22:11:18 +01003053 // GtkSockets use GtkPlug's [gui,mainwin] min-size hints to determine
3054 // their actual widget size. When we set our size ourselves (e.g.,
3055 // 'set columns=' or init. -geom) we briefly set the min. to the size
3056 // we wish to be instead of the legitimate minimum so that we actually
3057 // resize correctly.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003058 if (force_width && force_height)
3059 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003060 min_width = force_width;
3061 min_height = force_height;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003062 }
3063 else
3064 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003065 min_width = width + MIN_COLUMNS * gui.char_width;
3066 min_height = height + MIN_LINES * gui.char_height;
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003067 }
3068
Bram Moolenaar30613902019-12-01 22:11:18 +01003069 // Avoid an expose event when the size didn't change.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003070 if (width != old_width
3071 || height != old_height
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003072 || min_width != old_min_width
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003073 || min_height != old_min_height
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003074 || gui.char_width != old_char_width
3075 || gui.char_height != old_char_height)
3076 {
3077 GdkGeometry geometry;
3078 GdkWindowHints geometry_mask;
3079
3080 geometry.width_inc = gui.char_width;
3081 geometry.height_inc = gui.char_height;
3082 geometry.base_width = width;
3083 geometry.base_height = height;
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003084 geometry.min_width = min_width;
3085 geometry.min_height = min_height;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003086 geometry_mask = GDK_HINT_BASE_SIZE|GDK_HINT_RESIZE_INC
3087 |GDK_HINT_MIN_SIZE;
Bram Moolenaar30613902019-12-01 22:11:18 +01003088 // Using gui.formwin as geometry widget doesn't work as expected
3089 // with GTK+ 2 -- dunno why. Presumably all the resizing hacks
3090 // in Vim confuse GTK+.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003091 gtk_window_set_geometry_hints(GTK_WINDOW(gui.mainwin), gui.mainwin,
3092 &geometry, geometry_mask);
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00003093 old_width = width;
3094 old_height = height;
3095 old_min_width = min_width;
3096 old_min_height = min_height;
3097 old_char_width = gui.char_width;
3098 old_char_height = gui.char_height;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003099 }
3100}
3101
Bram Moolenaar50bf7ce2019-09-15 13:17:00 +02003102#if defined(FEAT_GUI_DARKTHEME) || defined(PROTO)
3103 void
3104gui_mch_set_dark_theme(int dark)
3105{
3106# if GTK_CHECK_VERSION(3,0,0)
3107 GtkSettings *gtk_settings;
3108
3109 gtk_settings = gtk_settings_get_for_screen(gdk_screen_get_default());
3110 g_object_set(gtk_settings, "gtk-application-prefer-dark-theme", (gboolean)dark, NULL);
3111# endif
3112}
Bram Moolenaar30613902019-12-01 22:11:18 +01003113#endif // FEAT_GUI_DARKTHEME
Bram Moolenaar50bf7ce2019-09-15 13:17:00 +02003114
Bram Moolenaar071d4272004-06-13 20:20:40 +00003115#ifdef FEAT_TOOLBAR
3116
Bram Moolenaar071d4272004-06-13 20:20:40 +00003117/*
3118 * This extra effort wouldn't be necessary if we only used stock icons in the
3119 * toolbar, as we do for all builtin icons. But user-defined toolbar icons
3120 * shouldn't be treated differently, thus we do need this.
3121 */
3122 static void
3123icon_size_changed_foreach(GtkWidget *widget, gpointer user_data)
3124{
3125 if (GTK_IS_IMAGE(widget))
3126 {
3127 GtkImage *image = (GtkImage *)widget;
3128
Bram Moolenaar98921892016-02-23 17:14:37 +01003129# if GTK_CHECK_VERSION(3,10,0)
3130 if (gtk_image_get_storage_type(image) == GTK_IMAGE_ICON_NAME)
3131 {
3132 const GtkIconSize icon_size = GPOINTER_TO_INT(user_data);
3133 const gchar *icon_name;
3134
3135 gtk_image_get_icon_name(image, &icon_name, NULL);
Bram Moolenaar81a4cf42020-09-09 19:05:13 +02003136 image = (GtkImage *)gtk_image_new_from_icon_name(
3137 icon_name, icon_size);
Bram Moolenaar98921892016-02-23 17:14:37 +01003138 }
3139# else
Bram Moolenaar30613902019-12-01 22:11:18 +01003140 // User-defined icons are stored in a GtkIconSet
Bram Moolenaar071d4272004-06-13 20:20:40 +00003141 if (gtk_image_get_storage_type(image) == GTK_IMAGE_ICON_SET)
3142 {
3143 GtkIconSet *icon_set;
3144 GtkIconSize icon_size;
3145
3146 gtk_image_get_icon_set(image, &icon_set, &icon_size);
3147 icon_size = (GtkIconSize)(long)user_data;
3148
3149 gtk_icon_set_ref(icon_set);
3150 gtk_image_set_from_icon_set(image, icon_set, icon_size);
3151 gtk_icon_set_unref(icon_set);
3152 }
Bram Moolenaar98921892016-02-23 17:14:37 +01003153# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003154 }
3155 else if (GTK_IS_CONTAINER(widget))
3156 {
3157 gtk_container_foreach((GtkContainer *)widget,
3158 &icon_size_changed_foreach,
3159 user_data);
3160 }
3161}
Bram Moolenaar071d4272004-06-13 20:20:40 +00003162
3163 static void
3164set_toolbar_style(GtkToolbar *toolbar)
3165{
3166 GtkToolbarStyle style;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003167 GtkIconSize size;
3168 GtkIconSize oldsize;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003169
Bram Moolenaar071d4272004-06-13 20:20:40 +00003170 if ((toolbar_flags & (TOOLBAR_TEXT | TOOLBAR_ICONS | TOOLBAR_HORIZ))
3171 == (TOOLBAR_TEXT | TOOLBAR_ICONS | TOOLBAR_HORIZ))
3172 style = GTK_TOOLBAR_BOTH_HORIZ;
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003173 else if ((toolbar_flags & (TOOLBAR_TEXT | TOOLBAR_ICONS))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003174 == (TOOLBAR_TEXT | TOOLBAR_ICONS))
3175 style = GTK_TOOLBAR_BOTH;
3176 else if (toolbar_flags & TOOLBAR_TEXT)
3177 style = GTK_TOOLBAR_TEXT;
3178 else
3179 style = GTK_TOOLBAR_ICONS;
3180
3181 gtk_toolbar_set_style(toolbar, style);
Bram Moolenaar98921892016-02-23 17:14:37 +01003182# if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003183 gtk_toolbar_set_tooltips(toolbar, (toolbar_flags & TOOLBAR_TOOLTIPS) != 0);
Bram Moolenaar98921892016-02-23 17:14:37 +01003184# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003185
Bram Moolenaar071d4272004-06-13 20:20:40 +00003186 switch (tbis_flags)
3187 {
3188 case TBIS_TINY: size = GTK_ICON_SIZE_MENU; break;
3189 case TBIS_SMALL: size = GTK_ICON_SIZE_SMALL_TOOLBAR; break;
3190 case TBIS_MEDIUM: size = GTK_ICON_SIZE_BUTTON; break;
3191 case TBIS_LARGE: size = GTK_ICON_SIZE_LARGE_TOOLBAR; break;
Bram Moolenaarbeb003b2016-03-08 22:47:17 +01003192 case TBIS_HUGE: size = GTK_ICON_SIZE_DND; break;
3193 case TBIS_GIANT: size = GTK_ICON_SIZE_DIALOG; break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003194 default: size = GTK_ICON_SIZE_INVALID; break;
3195 }
3196 oldsize = gtk_toolbar_get_icon_size(toolbar);
3197
3198 if (size == GTK_ICON_SIZE_INVALID)
3199 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003200 // Let global user preferences decide the icon size.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003201 gtk_toolbar_unset_icon_size(toolbar);
3202 size = gtk_toolbar_get_icon_size(toolbar);
3203 }
3204 if (size != oldsize)
3205 {
3206 gtk_container_foreach(GTK_CONTAINER(toolbar),
3207 &icon_size_changed_foreach,
3208 GINT_TO_POINTER((int)size));
3209 }
3210 gtk_toolbar_set_icon_size(toolbar, size);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003211}
3212
Bram Moolenaar30613902019-12-01 22:11:18 +01003213#endif // FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00003214
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003215#if defined(FEAT_GUI_TABLINE) || defined(PROTO)
3216static int ignore_tabline_evt = FALSE;
Bram Moolenaara5621492006-02-25 21:55:24 +00003217static GtkWidget *tabline_menu;
Bram Moolenaar98921892016-02-23 17:14:37 +01003218# if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003219static GtkTooltips *tabline_tooltip;
Bram Moolenaar98921892016-02-23 17:14:37 +01003220# endif
Bram Moolenaar30613902019-12-01 22:11:18 +01003221static int clicked_page; // page clicked in tab line
Bram Moolenaara5621492006-02-25 21:55:24 +00003222
3223/*
3224 * Handle selecting an item in the tab line popup menu.
3225 */
Bram Moolenaara5621492006-02-25 21:55:24 +00003226 static void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00003227tabline_menu_handler(GtkMenuItem *item UNUSED, gpointer user_data)
Bram Moolenaara5621492006-02-25 21:55:24 +00003228{
Bram Moolenaar30613902019-12-01 22:11:18 +01003229 // Add the string cmd into input buffer
Bram Moolenaarc6fe9192006-04-09 21:54:49 +00003230 send_tabline_menu_event(clicked_page, (int)(long)user_data);
Bram Moolenaara5621492006-02-25 21:55:24 +00003231}
3232
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003233 static void
3234add_tabline_menu_item(GtkWidget *menu, char_u *text, int resp)
3235{
3236 GtkWidget *item;
3237 char_u *utf_text;
3238
3239 utf_text = CONVERT_TO_UTF8(text);
3240 item = gtk_menu_item_new_with_label((const char *)utf_text);
3241 gtk_widget_show(item);
3242 CONVERT_TO_UTF8_FREE(utf_text);
3243
3244 gtk_container_add(GTK_CONTAINER(menu), item);
Bram Moolenaar98921892016-02-23 17:14:37 +01003245 g_signal_connect(G_OBJECT(item), "activate",
3246 G_CALLBACK(tabline_menu_handler),
3247 GINT_TO_POINTER(resp));
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003248}
3249
Bram Moolenaara5621492006-02-25 21:55:24 +00003250/*
Bram Moolenaara5621492006-02-25 21:55:24 +00003251 * Create a menu for the tab line.
3252 */
3253 static GtkWidget *
3254create_tabline_menu(void)
3255{
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003256 GtkWidget *menu;
Bram Moolenaara5621492006-02-25 21:55:24 +00003257
3258 menu = gtk_menu_new();
Bram Moolenaar29547192018-12-11 20:39:19 +01003259 add_tabline_menu_item(menu, (char_u *)_("Close tab"), TABLINE_MENU_CLOSE);
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003260 add_tabline_menu_item(menu, (char_u *)_("New tab"), TABLINE_MENU_NEW);
3261 add_tabline_menu_item(menu, (char_u *)_("Open Tab..."), TABLINE_MENU_OPEN);
Bram Moolenaara5621492006-02-25 21:55:24 +00003262
3263 return menu;
3264}
3265
3266 static gboolean
3267on_tabline_menu(GtkWidget *widget, GdkEvent *event)
3268{
Bram Moolenaar30613902019-12-01 22:11:18 +01003269 // Was this button press event ?
Bram Moolenaara5621492006-02-25 21:55:24 +00003270 if (event->type == GDK_BUTTON_PRESS)
3271 {
3272 GdkEventButton *bevent = (GdkEventButton *)event;
3273 int x = bevent->x;
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003274 int y = bevent->y;
3275 GtkWidget *tabwidget;
3276 GdkWindow *tabwin;
Bram Moolenaara5621492006-02-25 21:55:24 +00003277
Bram Moolenaar30613902019-12-01 22:11:18 +01003278 // When ignoring events return TRUE so that the selected page doesn't
3279 // change.
Bram Moolenaarf193fff2006-04-27 00:02:13 +00003280 if (hold_gui_events
3281# ifdef FEAT_CMDWIN
3282 || cmdwin_type != 0
3283# endif
3284 )
3285 return TRUE;
3286
Bram Moolenaar98921892016-02-23 17:14:37 +01003287 tabwin = gui_gtk_window_at_position(gui.mainwin, &x, &y);
Bram Moolenaar98921892016-02-23 17:14:37 +01003288
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003289 gdk_window_get_user_data(tabwin, (gpointer)&tabwidget);
Bram Moolenaar98921892016-02-23 17:14:37 +01003290 clicked_page = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tabwidget),
3291 "tab_num"));
Bram Moolenaara5621492006-02-25 21:55:24 +00003292
Bram Moolenaar30613902019-12-01 22:11:18 +01003293 // If the event was generated for 3rd button popup the menu.
Bram Moolenaara5621492006-02-25 21:55:24 +00003294 if (bevent->button == 3)
3295 {
Bram Moolenaara859f042016-11-17 19:11:55 +01003296# if GTK_CHECK_VERSION(3,22,2)
3297 gtk_menu_popup_at_pointer(GTK_MENU(widget), event);
3298# else
Bram Moolenaara5621492006-02-25 21:55:24 +00003299 gtk_menu_popup(GTK_MENU(widget), NULL, NULL, NULL, NULL,
3300 bevent->button, bevent->time);
Bram Moolenaara859f042016-11-17 19:11:55 +01003301# endif
Bram Moolenaar30613902019-12-01 22:11:18 +01003302 // We handled the event.
Bram Moolenaara5621492006-02-25 21:55:24 +00003303 return TRUE;
3304 }
Bram Moolenaar96351572006-05-05 21:16:59 +00003305 else if (bevent->button == 1)
Bram Moolenaareddf53b2006-02-27 00:11:10 +00003306 {
Bram Moolenaar96351572006-05-05 21:16:59 +00003307 if (clicked_page == 0)
3308 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003309 // Click after all tabs moves to next tab page. When "x" is
3310 // small guess it's the left button.
Bram Moolenaara3f41662010-07-11 19:01:06 +02003311 send_tabline_event(x < 50 ? -1 : 0);
Bram Moolenaar96351572006-05-05 21:16:59 +00003312 }
Bram Moolenaareddf53b2006-02-27 00:11:10 +00003313 }
Bram Moolenaara5621492006-02-25 21:55:24 +00003314 }
Bram Moolenaarf193fff2006-04-27 00:02:13 +00003315
Bram Moolenaar30613902019-12-01 22:11:18 +01003316 // We didn't handle the event.
Bram Moolenaara5621492006-02-25 21:55:24 +00003317 return FALSE;
3318}
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003319
3320/*
3321 * Handle selecting one of the tabs.
3322 */
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003323 static void
3324on_select_tab(
Bram Moolenaarb85cb212009-05-17 14:24:23 +00003325 GtkNotebook *notebook UNUSED,
Bram Moolenaar98921892016-02-23 17:14:37 +01003326 gpointer *page UNUSED,
Bram Moolenaar89d40322006-08-29 15:30:07 +00003327 gint idx,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00003328 gpointer data UNUSED)
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003329{
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003330 if (!ignore_tabline_evt)
Bram Moolenaara3f41662010-07-11 19:01:06 +02003331 send_tabline_event(idx + 1);
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003332}
3333
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003334# if GTK_CHECK_VERSION(2,10,0)
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003335/*
3336 * Handle reordering the tabs (using D&D).
3337 */
3338 static void
3339on_tab_reordered(
3340 GtkNotebook *notebook UNUSED,
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003341 gpointer *page UNUSED,
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003342 gint idx,
3343 gpointer data UNUSED)
3344{
3345 if (!ignore_tabline_evt)
3346 {
3347 if ((tabpage_index(curtab) - 1) < idx)
3348 tabpage_move(idx + 1);
3349 else
3350 tabpage_move(idx);
Bram Moolenaareddf53b2006-02-27 00:11:10 +00003351 }
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003352}
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003353# endif
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003354
3355/*
3356 * Show or hide the tabline.
3357 */
3358 void
3359gui_mch_show_tabline(int showit)
3360{
3361 if (gui.tabline == NULL)
3362 return;
3363
3364 if (!showit != !gtk_notebook_get_show_tabs(GTK_NOTEBOOK(gui.tabline)))
3365 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003366 // Note: this may cause a resize event
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003367 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gui.tabline), showit);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003368 update_window_manager_hints(0, 0);
Bram Moolenaar96351572006-05-05 21:16:59 +00003369 if (showit)
Bram Moolenaar98921892016-02-23 17:14:37 +01003370 gtk_widget_set_can_focus(GTK_WIDGET(gui.tabline), FALSE);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003371 }
Bram Moolenaar96351572006-05-05 21:16:59 +00003372
3373 gui_mch_update();
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003374}
3375
3376/*
Bram Moolenaar3517bb12006-03-03 22:58:45 +00003377 * Return TRUE when tabline is displayed.
3378 */
3379 int
3380gui_mch_showing_tabline(void)
3381{
3382 return gui.tabline != NULL
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003383 && gtk_notebook_get_show_tabs(GTK_NOTEBOOK(gui.tabline));
Bram Moolenaar3517bb12006-03-03 22:58:45 +00003384}
3385
3386/*
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003387 * Update the labels of the tabline.
3388 */
3389 void
3390gui_mch_update_tabline(void)
3391{
3392 GtkWidget *page;
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003393 GtkWidget *event_box;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003394 GtkWidget *label;
3395 tabpage_T *tp;
3396 int nr = 0;
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003397 int tab_num;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003398 int curtabidx = 0;
Bram Moolenaarc1e37902006-04-18 21:55:01 +00003399 char_u *labeltext;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003400
3401 if (gui.tabline == NULL)
3402 return;
3403
3404 ignore_tabline_evt = TRUE;
3405
Bram Moolenaar30613902019-12-01 22:11:18 +01003406 // Add a label for each tab page. They all contain the same text area.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003407 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr)
3408 {
3409 if (tp == curtab)
3410 curtabidx = nr;
3411
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003412 tab_num = nr + 1;
3413
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003414 page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(gui.tabline), nr);
3415 if (page == NULL)
3416 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003417 // Add notebook page
Bram Moolenaar98921892016-02-23 17:14:37 +01003418# if GTK_CHECK_VERSION(3,2,0)
3419 page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3420 gtk_box_set_homogeneous(GTK_BOX(page), FALSE);
3421# else
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003422 page = gtk_vbox_new(FALSE, 0);
Bram Moolenaar98921892016-02-23 17:14:37 +01003423# endif
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003424 gtk_widget_show(page);
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003425 event_box = gtk_event_box_new();
3426 gtk_widget_show(event_box);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003427 label = gtk_label_new("-Empty-");
Bram Moolenaar98921892016-02-23 17:14:37 +01003428# if !GTK_CHECK_VERSION(3,14,0)
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003429 gtk_misc_set_padding(GTK_MISC(label), 2, 2);
Bram Moolenaar98921892016-02-23 17:14:37 +01003430# endif
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003431 gtk_container_add(GTK_CONTAINER(event_box), label);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003432 gtk_widget_show(label);
3433 gtk_notebook_insert_page(GTK_NOTEBOOK(gui.tabline),
3434 page,
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003435 event_box,
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003436 nr++);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003437# if GTK_CHECK_VERSION(2,10,0)
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003438 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(gui.tabline),
3439 page,
3440 TRUE);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003441# endif
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003442 }
3443
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003444 event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(gui.tabline), page);
Bram Moolenaar98921892016-02-23 17:14:37 +01003445 g_object_set_data(G_OBJECT(event_box), "tab_num",
3446 GINT_TO_POINTER(tab_num));
Bram Moolenaar98921892016-02-23 17:14:37 +01003447 label = gtk_bin_get_child(GTK_BIN(event_box));
Bram Moolenaar57657d82006-04-21 22:12:41 +00003448 get_tabline_label(tp, FALSE);
Bram Moolenaarc1e37902006-04-18 21:55:01 +00003449 labeltext = CONVERT_TO_UTF8(NameBuff);
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003450 gtk_label_set_text(GTK_LABEL(label), (const char *)labeltext);
Bram Moolenaarc1e37902006-04-18 21:55:01 +00003451 CONVERT_TO_UTF8_FREE(labeltext);
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003452
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003453 get_tabline_label(tp, TRUE);
3454 labeltext = CONVERT_TO_UTF8(NameBuff);
Bram Moolenaar98921892016-02-23 17:14:37 +01003455# if GTK_CHECK_VERSION(3,0,0)
3456 gtk_widget_set_tooltip_text(event_box, (const gchar *)labeltext);
3457# else
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003458 gtk_tooltips_set_tip(GTK_TOOLTIPS(tabline_tooltip), event_box,
3459 (const char *)labeltext, NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01003460# endif
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003461 CONVERT_TO_UTF8_FREE(labeltext);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003462 }
3463
Bram Moolenaar30613902019-12-01 22:11:18 +01003464 // Remove any old labels.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003465 while (gtk_notebook_get_nth_page(GTK_NOTEBOOK(gui.tabline), nr) != NULL)
3466 gtk_notebook_remove_page(GTK_NOTEBOOK(gui.tabline), nr);
3467
Bram Moolenaar98921892016-02-23 17:14:37 +01003468 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(gui.tabline)) != curtabidx)
3469 gtk_notebook_set_current_page(GTK_NOTEBOOK(gui.tabline), curtabidx);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003470
Bram Moolenaar30613902019-12-01 22:11:18 +01003471 // Make sure everything is in place before drawing text.
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003472 gui_mch_update();
3473
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003474 ignore_tabline_evt = FALSE;
3475}
3476
3477/*
3478 * Set the current tab to "nr". First tab is 1.
3479 */
3480 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01003481gui_mch_set_curtab(int nr)
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003482{
3483 if (gui.tabline == NULL)
3484 return;
3485
3486 ignore_tabline_evt = TRUE;
Bram Moolenaar98921892016-02-23 17:14:37 +01003487 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(gui.tabline)) != nr - 1)
3488 gtk_notebook_set_current_page(GTK_NOTEBOOK(gui.tabline), nr - 1);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003489 ignore_tabline_evt = FALSE;
3490}
3491
Bram Moolenaar30613902019-12-01 22:11:18 +01003492#endif // FEAT_GUI_TABLINE
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003493
Bram Moolenaar071d4272004-06-13 20:20:40 +00003494/*
Bram Moolenaara76638f2010-06-05 12:49:46 +02003495 * Add selection targets for PRIMARY and CLIPBOARD selections.
3496 */
3497 void
3498gui_gtk_set_selection_targets(void)
3499{
3500 int i, j = 0;
3501 int n_targets = N_SELECTION_TARGETS;
3502 GtkTargetEntry targets[N_SELECTION_TARGETS];
3503
3504 for (i = 0; i < (int)N_SELECTION_TARGETS; ++i)
3505 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003506 // OpenOffice tries to use TARGET_HTML and fails when we don't
3507 // return something, instead of trying another target. Therefore only
3508 // offer TARGET_HTML when it works.
Bram Moolenaara76638f2010-06-05 12:49:46 +02003509 if (!clip_html && selection_targets[i].info == TARGET_HTML)
3510 n_targets--;
3511 else
Bram Moolenaara76638f2010-06-05 12:49:46 +02003512 targets[j++] = selection_targets[i];
3513 }
3514
3515 gtk_selection_clear_targets(gui.drawarea, (GdkAtom)GDK_SELECTION_PRIMARY);
3516 gtk_selection_clear_targets(gui.drawarea, (GdkAtom)clip_plus.gtk_sel_atom);
3517 gtk_selection_add_targets(gui.drawarea,
3518 (GdkAtom)GDK_SELECTION_PRIMARY,
3519 targets, n_targets);
3520 gtk_selection_add_targets(gui.drawarea,
3521 (GdkAtom)clip_plus.gtk_sel_atom,
3522 targets, n_targets);
3523}
3524
3525/*
3526 * Set up for receiving DND items.
3527 */
3528 void
3529gui_gtk_set_dnd_targets(void)
3530{
3531 int i, j = 0;
3532 int n_targets = N_DND_TARGETS;
3533 GtkTargetEntry targets[N_DND_TARGETS];
3534
3535 for (i = 0; i < (int)N_DND_TARGETS; ++i)
3536 {
Bram Moolenaarb931d742011-10-26 11:36:25 +02003537 if (!clip_html && dnd_targets[i].info == TARGET_HTML)
Bram Moolenaara76638f2010-06-05 12:49:46 +02003538 n_targets--;
3539 else
Bram Moolenaara76638f2010-06-05 12:49:46 +02003540 targets[j++] = dnd_targets[i];
3541 }
3542
3543 gtk_drag_dest_unset(gui.drawarea);
3544 gtk_drag_dest_set(gui.drawarea,
3545 GTK_DEST_DEFAULT_ALL,
3546 targets, n_targets,
Bram Moolenaarba7cc9f2011-02-25 17:10:27 +01003547 GDK_ACTION_COPY | GDK_ACTION_MOVE);
Bram Moolenaara76638f2010-06-05 12:49:46 +02003548}
3549
3550/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00003551 * Initialize the GUI. Create all the windows, set up all the callbacks etc.
3552 * Returns OK for success, FAIL when the GUI can't be started.
3553 */
3554 int
3555gui_mch_init(void)
3556{
3557 GtkWidget *vbox;
3558
3559#ifdef FEAT_GUI_GNOME
Bram Moolenaar30613902019-12-01 22:11:18 +01003560 // Initialize the GNOME libraries. gnome_program_init()/gnome_init()
3561 // exits on failure, but that's a non-issue because we already called
3562 // gtk_init_check() in gui_mch_init_check().
Bram Moolenaar071d4272004-06-13 20:20:40 +00003563 if (using_gnome)
Bram Moolenaar3be71ce2013-01-23 16:00:11 +01003564 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003565 gnome_program_init(VIMPACKAGE, VIM_VERSION_SHORT,
3566 LIBGNOMEUI_MODULE, gui_argc, gui_argv, NULL);
Bram Moolenaar3be71ce2013-01-23 16:00:11 +01003567# if defined(FEAT_FLOAT) && defined(LC_NUMERIC)
Bram Moolenaar1ff32c52014-04-29 15:11:43 +02003568 {
3569 char *p = setlocale(LC_NUMERIC, NULL);
3570
Bram Moolenaar30613902019-12-01 22:11:18 +01003571 // Make sure strtod() uses a decimal point, not a comma. Gnome
3572 // init may change it.
Bram Moolenaar1ff32c52014-04-29 15:11:43 +02003573 if (p == NULL || strcmp(p, "C") != 0)
3574 setlocale(LC_NUMERIC, "C");
3575 }
Bram Moolenaar3be71ce2013-01-23 16:00:11 +01003576# endif
3577 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003578#endif
Bram Moolenaard23a8232018-02-10 18:45:26 +01003579 VIM_CLEAR(gui_argv);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003580
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003581#if GLIB_CHECK_VERSION(2,1,3)
Bram Moolenaar30613902019-12-01 22:11:18 +01003582 // Set the human-readable application name
Bram Moolenaar071d4272004-06-13 20:20:40 +00003583 g_set_application_name("Vim");
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003584#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003585 /*
3586 * Force UTF-8 output no matter what the value of 'encoding' is.
3587 * did_set_string_option() in option.c prohibits changing 'termencoding'
3588 * to something else than UTF-8 if the GUI is in use.
3589 */
Bram Moolenaar31e5c602022-04-15 13:53:33 +01003590 set_option_value_give_err((char_u *)"termencoding",
3591 0L, (char_u *)"utf-8", 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003592
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003593#ifdef FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00003594 gui_gtk_register_stock_icons();
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003595#endif
Bram Moolenaar30613902019-12-01 22:11:18 +01003596 // FIXME: Need to install the classic icons and a gtkrc.classic file.
3597 // The hard part is deciding install locations and the Makefile magic.
Bram Moolenaar98921892016-02-23 17:14:37 +01003598#if !GTK_CHECK_VERSION(3,0,0)
3599# if 0
Bram Moolenaar071d4272004-06-13 20:20:40 +00003600 gtk_rc_parse("gtkrc");
Bram Moolenaar98921892016-02-23 17:14:37 +01003601# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003602#endif
3603
Bram Moolenaar30613902019-12-01 22:11:18 +01003604 // Initialize values
Bram Moolenaar071d4272004-06-13 20:20:40 +00003605 gui.border_width = 2;
3606 gui.scrollbar_width = SB_DEFAULT_WIDTH;
3607 gui.scrollbar_height = SB_DEFAULT_WIDTH;
Bram Moolenaar36edf062016-07-21 22:10:12 +02003608#if GTK_CHECK_VERSION(3,0,0)
3609 gui.fgcolor = g_new(GdkRGBA, 1);
3610 gui.bgcolor = g_new(GdkRGBA, 1);
3611 gui.spcolor = g_new(GdkRGBA, 1);
3612#else
Bram Moolenaar30613902019-12-01 22:11:18 +01003613 // LINTED: avoid warning: conversion to 'unsigned long'
Bram Moolenaar071d4272004-06-13 20:20:40 +00003614 gui.fgcolor = g_new0(GdkColor, 1);
Bram Moolenaar30613902019-12-01 22:11:18 +01003615 // LINTED: avoid warning: conversion to 'unsigned long'
Bram Moolenaar071d4272004-06-13 20:20:40 +00003616 gui.bgcolor = g_new0(GdkColor, 1);
Bram Moolenaar30613902019-12-01 22:11:18 +01003617 // LINTED: avoid warning: conversion to 'unsigned long'
Bram Moolenaarf36d3692005-03-15 22:48:14 +00003618 gui.spcolor = g_new0(GdkColor, 1);
Bram Moolenaar36edf062016-07-21 22:10:12 +02003619#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003620
Bram Moolenaar30613902019-12-01 22:11:18 +01003621 // Initialise atoms
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00003622 html_atom = gdk_atom_intern("text/html", FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003623 utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003624
Bram Moolenaar30613902019-12-01 22:11:18 +01003625 // Set default foreground and background colors.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003626 gui.norm_pixel = gui.def_norm_pixel;
3627 gui.back_pixel = gui.def_back_pixel;
3628
3629 if (gtk_socket_id != 0)
3630 {
3631 GtkWidget *plug;
3632
Bram Moolenaar30613902019-12-01 22:11:18 +01003633 // Use GtkSocket from another app.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003634 plug = gtk_plug_new_for_display(gdk_display_get_default(),
3635 gtk_socket_id);
Bram Moolenaar98921892016-02-23 17:14:37 +01003636 if (plug != NULL && gtk_plug_get_socket_window(GTK_PLUG(plug)) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003637 {
3638 gui.mainwin = plug;
3639 }
3640 else
3641 {
3642 g_warning("Connection to GTK+ socket (ID %u) failed",
3643 (unsigned int)gtk_socket_id);
Bram Moolenaar30613902019-12-01 22:11:18 +01003644 // Pretend we never wanted it if it failed (get own window)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003645 gtk_socket_id = 0;
3646 }
3647 }
3648
3649 if (gtk_socket_id == 0)
3650 {
3651#ifdef FEAT_GUI_GNOME
3652 if (using_gnome)
3653 {
3654 gui.mainwin = gnome_app_new("Vim", NULL);
3655# ifdef USE_XSMP
Bram Moolenaar30613902019-12-01 22:11:18 +01003656 // Use the GNOME save-yourself functionality now.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003657 xsmp_close();
3658# endif
3659 }
3660 else
3661#endif
3662 gui.mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
3663 }
3664
3665 gtk_widget_set_name(gui.mainwin, "vim-main-window");
3666
Bram Moolenaar30613902019-12-01 22:11:18 +01003667 // Create the PangoContext used for drawing all text.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003668 gui.text_context = gtk_widget_create_pango_context(gui.mainwin);
3669 pango_context_set_base_dir(gui.text_context, PANGO_DIRECTION_LTR);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003670
Bram Moolenaar98921892016-02-23 17:14:37 +01003671 gtk_container_set_border_width(GTK_CONTAINER(gui.mainwin), 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003672 gtk_widget_add_events(gui.mainwin, GDK_VISIBILITY_NOTIFY_MASK);
3673
Bram Moolenaar98921892016-02-23 17:14:37 +01003674 g_signal_connect(G_OBJECT(gui.mainwin), "delete-event",
3675 G_CALLBACK(&delete_event_cb), NULL);
3676
3677 g_signal_connect(G_OBJECT(gui.mainwin), "realize",
3678 G_CALLBACK(&mainwin_realize), NULL);
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02003679
3680 g_signal_connect(G_OBJECT(gui.mainwin), "screen-changed",
Bram Moolenaar071d4272004-06-13 20:20:40 +00003681 G_CALLBACK(&mainwin_screen_changed_cb), NULL);
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02003682
Bram Moolenaar071d4272004-06-13 20:20:40 +00003683 gui.accel_group = gtk_accel_group_new();
3684 gtk_window_add_accel_group(GTK_WINDOW(gui.mainwin), gui.accel_group);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003685
Bram Moolenaar30613902019-12-01 22:11:18 +01003686 // A vertical box holds the menubar, toolbar and main text window.
Bram Moolenaar98921892016-02-23 17:14:37 +01003687#if GTK_CHECK_VERSION(3,2,0)
3688 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3689 gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
3690#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00003691 vbox = gtk_vbox_new(FALSE, 0);
Bram Moolenaar98921892016-02-23 17:14:37 +01003692#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003693
3694#ifdef FEAT_GUI_GNOME
3695 if (using_gnome)
3696 {
Bram Moolenaar182c5be2010-06-25 05:37:59 +02003697# if defined(FEAT_MENU)
Bram Moolenaar30613902019-12-01 22:11:18 +01003698 // automagically restore menubar/toolbar placement
Bram Moolenaar071d4272004-06-13 20:20:40 +00003699 gnome_app_enable_layout_config(GNOME_APP(gui.mainwin), TRUE);
3700# endif
3701 gnome_app_set_contents(GNOME_APP(gui.mainwin), vbox);
3702 }
3703 else
3704#endif
3705 {
3706 gtk_container_add(GTK_CONTAINER(gui.mainwin), vbox);
3707 gtk_widget_show(vbox);
3708 }
3709
3710#ifdef FEAT_MENU
3711 /*
3712 * Create the menubar and handle
3713 */
3714 gui.menubar = gtk_menu_bar_new();
3715 gtk_widget_set_name(gui.menubar, "vim-menubar");
3716
Bram Moolenaar30613902019-12-01 22:11:18 +01003717 // Avoid that GTK takes <F10> away from us.
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00003718 {
3719 GtkSettings *gtk_settings;
3720
3721 gtk_settings = gtk_settings_get_for_screen(gdk_screen_get_default());
3722 g_object_set(gtk_settings, "gtk-menu-bar-accel", NULL, NULL);
3723 }
Bram Moolenaar293ee4d2004-12-09 21:34:53 +00003724
3725
Bram Moolenaar071d4272004-06-13 20:20:40 +00003726# ifdef FEAT_GUI_GNOME
3727 if (using_gnome)
3728 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003729 BonoboDockItem *dockitem;
3730
3731 gnome_app_set_menus(GNOME_APP(gui.mainwin), GTK_MENU_BAR(gui.menubar));
3732 dockitem = gnome_app_get_dock_item_by_name(GNOME_APP(gui.mainwin),
3733 GNOME_APP_MENUBAR_NAME);
Bram Moolenaar30613902019-12-01 22:11:18 +01003734 // We don't want the menu to float.
Bram Moolenaardb552d602006-03-23 22:59:57 +00003735 bonobo_dock_item_set_behavior(dockitem,
3736 bonobo_dock_item_get_behavior(dockitem)
3737 | BONOBO_DOCK_ITEM_BEH_NEVER_FLOATING);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003738 gui.menubar_h = GTK_WIDGET(dockitem);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003739 }
3740 else
Bram Moolenaar30613902019-12-01 22:11:18 +01003741# endif // FEAT_GUI_GNOME
Bram Moolenaar071d4272004-06-13 20:20:40 +00003742 {
Bram Moolenaar30613902019-12-01 22:11:18 +01003743 // Always show the menubar, otherwise <F10> doesn't work. It may be
3744 // disabled in gui_init() later.
Bram Moolenaar18144c82006-04-12 21:52:12 +00003745 gtk_widget_show(gui.menubar);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003746 gtk_box_pack_start(GTK_BOX(vbox), gui.menubar, FALSE, FALSE, 0);
3747 }
Bram Moolenaar30613902019-12-01 22:11:18 +01003748#endif // FEAT_MENU
Bram Moolenaar071d4272004-06-13 20:20:40 +00003749
3750#ifdef FEAT_TOOLBAR
3751 /*
3752 * Create the toolbar and handle
3753 */
Bram Moolenaar30613902019-12-01 22:11:18 +01003754 // some aesthetics on the toolbar
Bram Moolenaar98921892016-02-23 17:14:37 +01003755# ifdef USE_GTK3
Bram Moolenaar30613902019-12-01 22:11:18 +01003756 // TODO: Add GTK+ 3 code here using GtkCssProvider if necessary.
3757 // N.B. Since the default value of GtkToolbar::button-relief is
3758 // GTK_RELIEF_NONE, there's no need to specify that, probably.
Bram Moolenaar98921892016-02-23 17:14:37 +01003759# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00003760 gtk_rc_parse_string(
3761 "style \"vim-toolbar-style\" {\n"
3762 " GtkToolbar::button_relief = GTK_RELIEF_NONE\n"
3763 "}\n"
3764 "widget \"*.vim-toolbar\" style \"vim-toolbar-style\"\n");
Bram Moolenaar98921892016-02-23 17:14:37 +01003765# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003766 gui.toolbar = gtk_toolbar_new();
3767 gtk_widget_set_name(gui.toolbar, "vim-toolbar");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003768 set_toolbar_style(GTK_TOOLBAR(gui.toolbar));
3769
3770# ifdef FEAT_GUI_GNOME
3771 if (using_gnome)
3772 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003773 BonoboDockItem *dockitem;
3774
3775 gnome_app_set_toolbar(GNOME_APP(gui.mainwin), GTK_TOOLBAR(gui.toolbar));
3776 dockitem = gnome_app_get_dock_item_by_name(GNOME_APP(gui.mainwin),
3777 GNOME_APP_TOOLBAR_NAME);
3778 gui.toolbar_h = GTK_WIDGET(dockitem);
Bram Moolenaar30613902019-12-01 22:11:18 +01003779 // When the toolbar is floating it gets stuck. So long as that isn't
3780 // fixed let's disallow floating.
Bram Moolenaare580b0c2006-03-21 21:33:03 +00003781 bonobo_dock_item_set_behavior(dockitem,
Bram Moolenaardb552d602006-03-23 22:59:57 +00003782 bonobo_dock_item_get_behavior(dockitem)
3783 | BONOBO_DOCK_ITEM_BEH_NEVER_FLOATING);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003784 gtk_container_set_border_width(GTK_CONTAINER(gui.toolbar), 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003785 }
3786 else
Bram Moolenaar30613902019-12-01 22:11:18 +01003787# endif // FEAT_GUI_GNOME
Bram Moolenaar071d4272004-06-13 20:20:40 +00003788 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003789 if (vim_strchr(p_go, GO_TOOLBAR) != NULL
3790 && (toolbar_flags & (TOOLBAR_TEXT | TOOLBAR_ICONS)))
3791 gtk_widget_show(gui.toolbar);
3792 gtk_box_pack_start(GTK_BOX(vbox), gui.toolbar, FALSE, FALSE, 0);
3793 }
Bram Moolenaar30613902019-12-01 22:11:18 +01003794#endif // FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00003795
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003796#ifdef FEAT_GUI_TABLINE
Bram Moolenaar97b2ad32006-03-18 21:40:56 +00003797 /*
3798 * Use a Notebook for the tab pages labels. The labels are hidden by
3799 * default.
Bram Moolenaar97b2ad32006-03-18 21:40:56 +00003800 */
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003801 gui.tabline = gtk_notebook_new();
3802 gtk_widget_show(gui.tabline);
3803 gtk_box_pack_start(GTK_BOX(vbox), gui.tabline, FALSE, FALSE, 0);
3804 gtk_notebook_set_show_border(GTK_NOTEBOOK(gui.tabline), FALSE);
3805 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gui.tabline), FALSE);
Bram Moolenaar4a85b412006-04-23 22:40:29 +00003806 gtk_notebook_set_scrollable(GTK_NOTEBOOK(gui.tabline), TRUE);
Bram Moolenaar98921892016-02-23 17:14:37 +01003807# if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar96351572006-05-05 21:16:59 +00003808 gtk_notebook_set_tab_border(GTK_NOTEBOOK(gui.tabline), FALSE);
Bram Moolenaar98921892016-02-23 17:14:37 +01003809# endif
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003810
Bram Moolenaar98921892016-02-23 17:14:37 +01003811# if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003812 tabline_tooltip = gtk_tooltips_new();
3813 gtk_tooltips_enable(GTK_TOOLTIPS(tabline_tooltip));
Bram Moolenaar98921892016-02-23 17:14:37 +01003814# endif
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003815
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003816 {
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003817 GtkWidget *page, *label, *event_box;
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003818
Bram Moolenaar30613902019-12-01 22:11:18 +01003819 // Add the first tab.
Bram Moolenaar98921892016-02-23 17:14:37 +01003820# if GTK_CHECK_VERSION(3,2,0)
3821 page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3822 gtk_box_set_homogeneous(GTK_BOX(page), FALSE);
3823# else
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003824 page = gtk_vbox_new(FALSE, 0);
Bram Moolenaar98921892016-02-23 17:14:37 +01003825# endif
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003826 gtk_widget_show(page);
3827 gtk_container_add(GTK_CONTAINER(gui.tabline), page);
3828 label = gtk_label_new("-Empty-");
3829 gtk_widget_show(label);
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003830 event_box = gtk_event_box_new();
3831 gtk_widget_show(event_box);
Bram Moolenaar98921892016-02-23 17:14:37 +01003832 g_object_set_data(G_OBJECT(event_box), "tab_num", GINT_TO_POINTER(1L));
Bram Moolenaar98921892016-02-23 17:14:37 +01003833# if !GTK_CHECK_VERSION(3,14,0)
Bram Moolenaar8ea91232006-04-28 22:41:43 +00003834 gtk_misc_set_padding(GTK_MISC(label), 2, 2);
Bram Moolenaar98921892016-02-23 17:14:37 +01003835# endif
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003836 gtk_container_add(GTK_CONTAINER(event_box), label);
Bram Moolenaar437df8f2006-04-27 21:47:44 +00003837 gtk_notebook_set_tab_label(GTK_NOTEBOOK(gui.tabline), page, event_box);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003838# if GTK_CHECK_VERSION(2,10,0)
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003839 gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(gui.tabline), page, TRUE);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003840# endif
Bram Moolenaar97b2ad32006-03-18 21:40:56 +00003841 }
Bram Moolenaar54a709e2006-05-04 21:57:11 +00003842
Bram Moolenaar98921892016-02-23 17:14:37 +01003843 g_signal_connect(G_OBJECT(gui.tabline), "switch-page",
3844 G_CALLBACK(on_select_tab), NULL);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003845# if GTK_CHECK_VERSION(2,10,0)
Bram Moolenaarca05aa22017-10-22 15:36:14 +02003846 g_signal_connect(G_OBJECT(gui.tabline), "page-reordered",
3847 G_CALLBACK(on_tab_reordered), NULL);
Bram Moolenaar92cbf622018-09-19 22:40:03 +02003848# endif
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003849
Bram Moolenaar30613902019-12-01 22:11:18 +01003850 // Create a popup menu for the tab line and connect it.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003851 tabline_menu = create_tabline_menu();
Bram Moolenaar98921892016-02-23 17:14:37 +01003852 g_signal_connect_swapped(G_OBJECT(gui.tabline), "button-press-event",
3853 G_CALLBACK(on_tabline_menu), G_OBJECT(tabline_menu));
Bram Moolenaar30613902019-12-01 22:11:18 +01003854#endif // FEAT_GUI_TABLINE
Bram Moolenaar32466aa2006-02-24 23:53:04 +00003855
Bram Moolenaar8a99e662020-10-21 16:10:21 +02003856 gui.formwin = gui_gtk_form_new();
Bram Moolenaar98921892016-02-23 17:14:37 +01003857 gtk_container_set_border_width(GTK_CONTAINER(gui.formwin), 0);
Bram Moolenaar98921892016-02-23 17:14:37 +01003858#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003859 gtk_widget_set_events(gui.formwin, GDK_EXPOSURE_MASK);
Bram Moolenaar98921892016-02-23 17:14:37 +01003860#endif
Bram Moolenaar023fd5d2020-12-08 20:31:16 +01003861#if GTK_CHECK_VERSION(3,22,2)
3862 gtk_widget_set_name(gui.formwin, "vim-gtk-form");
3863#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003864
3865 gui.drawarea = gtk_drawing_area_new();
Bram Moolenaar98921892016-02-23 17:14:37 +01003866#if GTK_CHECK_VERSION(3,0,0)
3867 gui.surface = NULL;
Bram Moolenaar98921892016-02-23 17:14:37 +01003868#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003869
Bram Moolenaar30613902019-12-01 22:11:18 +01003870 // Determine which events we will filter.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003871 gtk_widget_set_events(gui.drawarea,
3872 GDK_EXPOSURE_MASK |
3873 GDK_ENTER_NOTIFY_MASK |
3874 GDK_LEAVE_NOTIFY_MASK |
3875 GDK_BUTTON_PRESS_MASK |
3876 GDK_BUTTON_RELEASE_MASK |
Bram Moolenaar071d4272004-06-13 20:20:40 +00003877 GDK_SCROLL_MASK |
Bram Moolenaar071d4272004-06-13 20:20:40 +00003878 GDK_KEY_PRESS_MASK |
3879 GDK_KEY_RELEASE_MASK |
3880 GDK_POINTER_MOTION_MASK |
3881 GDK_POINTER_MOTION_HINT_MASK);
3882
3883 gtk_widget_show(gui.drawarea);
Bram Moolenaar8a99e662020-10-21 16:10:21 +02003884 gui_gtk_form_put(GTK_FORM(gui.formwin), gui.drawarea, 0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003885 gtk_widget_show(gui.formwin);
3886 gtk_box_pack_start(GTK_BOX(vbox), gui.formwin, TRUE, TRUE, 0);
3887
Bram Moolenaar30613902019-12-01 22:11:18 +01003888 // For GtkSockets, key-presses must go to the focus widget (drawarea)
3889 // and not the window.
Bram Moolenaar98921892016-02-23 17:14:37 +01003890 g_signal_connect((gtk_socket_id == 0) ? G_OBJECT(gui.mainwin)
3891 : G_OBJECT(gui.drawarea),
3892 "key-press-event",
3893 G_CALLBACK(key_press_event), NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01003894#if defined(FEAT_XIM) || GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01003895 // Also forward key release events for the benefit of GTK+ 2 input
3896 // modules. Try CTRL-SHIFT-xdigits to enter a Unicode code point.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003897 g_signal_connect((gtk_socket_id == 0) ? G_OBJECT(gui.mainwin)
3898 : G_OBJECT(gui.drawarea),
Bram Moolenaar98921892016-02-23 17:14:37 +01003899 "key-release-event",
Bram Moolenaar071d4272004-06-13 20:20:40 +00003900 G_CALLBACK(&key_release_event), NULL);
3901#endif
Bram Moolenaar98921892016-02-23 17:14:37 +01003902 g_signal_connect(G_OBJECT(gui.drawarea), "realize",
3903 G_CALLBACK(drawarea_realize_cb), NULL);
3904 g_signal_connect(G_OBJECT(gui.drawarea), "unrealize",
3905 G_CALLBACK(drawarea_unrealize_cb), NULL);
Bram Moolenaar664323e2018-09-18 22:30:07 +02003906#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar98921892016-02-23 17:14:37 +01003907 g_signal_connect(G_OBJECT(gui.drawarea), "configure-event",
3908 G_CALLBACK(drawarea_configure_event_cb), NULL);
Bram Moolenaar664323e2018-09-18 22:30:07 +02003909#endif
3910#if GTK_CHECK_VERSION(3,22,2)
Bram Moolenaara859f042016-11-17 19:11:55 +01003911 g_signal_connect_after(G_OBJECT(gui.drawarea), "style-updated",
3912 G_CALLBACK(&drawarea_style_updated_cb), NULL);
Bram Moolenaar664323e2018-09-18 22:30:07 +02003913#else
Bram Moolenaar98921892016-02-23 17:14:37 +01003914 g_signal_connect_after(G_OBJECT(gui.drawarea), "style-set",
3915 G_CALLBACK(&drawarea_style_set_cb), NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01003916#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003917
Bram Moolenaar98921892016-02-23 17:14:37 +01003918#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003919 gui.visibility = GDK_VISIBILITY_UNOBSCURED;
Bram Moolenaar98921892016-02-23 17:14:37 +01003920#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003921
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02003922#if !defined(USE_GNOME_SESSION)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003923 wm_protocols_atom = gdk_atom_intern("WM_PROTOCOLS", FALSE);
3924 save_yourself_atom = gdk_atom_intern("WM_SAVE_YOURSELF", FALSE);
3925#endif
3926
3927 if (gtk_socket_id != 0)
Bram Moolenaar30613902019-12-01 22:11:18 +01003928 // make sure keyboard input can go to the drawarea
Bram Moolenaar98921892016-02-23 17:14:37 +01003929 gtk_widget_set_can_focus(gui.drawarea, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003930
3931 /*
3932 * Set clipboard specific atoms
3933 */
3934 vim_atom = gdk_atom_intern(VIM_ATOM_NAME, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003935 vimenc_atom = gdk_atom_intern(VIMENC_ATOM_NAME, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003936 clip_star.gtk_sel_atom = GDK_SELECTION_PRIMARY;
3937 clip_plus.gtk_sel_atom = gdk_atom_intern("CLIPBOARD", FALSE);
3938
3939 /*
3940 * Start out by adding the configured border width into the border offset.
3941 */
3942 gui.border_offset = gui.border_width;
3943
Bram Moolenaar98921892016-02-23 17:14:37 +01003944#if GTK_CHECK_VERSION(3,0,0)
3945 g_signal_connect(G_OBJECT(gui.drawarea), "draw",
3946 G_CALLBACK(draw_event), NULL);
3947#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00003948 gtk_signal_connect(GTK_OBJECT(gui.mainwin), "visibility_notify_event",
3949 GTK_SIGNAL_FUNC(visibility_event), NULL);
3950 gtk_signal_connect(GTK_OBJECT(gui.drawarea), "expose_event",
3951 GTK_SIGNAL_FUNC(expose_event), NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01003952#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003953
3954 /*
3955 * Only install these enter/leave callbacks when 'p' in 'guioptions'.
3956 * Only needed for some window managers.
3957 */
3958 if (vim_strchr(p_go, GO_POINTER) != NULL)
3959 {
Bram Moolenaar98921892016-02-23 17:14:37 +01003960 g_signal_connect(G_OBJECT(gui.drawarea), "leave-notify-event",
3961 G_CALLBACK(leave_notify_event), NULL);
3962 g_signal_connect(G_OBJECT(gui.drawarea), "enter-notify-event",
3963 G_CALLBACK(enter_notify_event), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003964 }
3965
Bram Moolenaar30613902019-12-01 22:11:18 +01003966 // Real windows can get focus ... GtkPlug, being a mere container can't,
3967 // only its widgets. Arguably, this could be common code and we not use
3968 // the window focus at all, but let's be safe.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003969 if (gtk_socket_id == 0)
3970 {
Bram Moolenaar98921892016-02-23 17:14:37 +01003971 g_signal_connect(G_OBJECT(gui.mainwin), "focus-out-event",
3972 G_CALLBACK(focus_out_event), NULL);
3973 g_signal_connect(G_OBJECT(gui.mainwin), "focus-in-event",
3974 G_CALLBACK(focus_in_event), NULL);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003975 }
3976 else
3977 {
Bram Moolenaar98921892016-02-23 17:14:37 +01003978 g_signal_connect(G_OBJECT(gui.drawarea), "focus-out-event",
3979 G_CALLBACK(focus_out_event), NULL);
3980 g_signal_connect(G_OBJECT(gui.drawarea), "focus-in-event",
3981 G_CALLBACK(focus_in_event), NULL);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003982#ifdef FEAT_GUI_TABLINE
Bram Moolenaar98921892016-02-23 17:14:37 +01003983 g_signal_connect(G_OBJECT(gui.tabline), "focus-out-event",
3984 G_CALLBACK(focus_out_event), NULL);
3985 g_signal_connect(G_OBJECT(gui.tabline), "focus-in-event",
3986 G_CALLBACK(focus_in_event), NULL);
Bram Moolenaar30613902019-12-01 22:11:18 +01003987#endif // FEAT_GUI_TABLINE
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00003988 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003989
Bram Moolenaar98921892016-02-23 17:14:37 +01003990 g_signal_connect(G_OBJECT(gui.drawarea), "motion-notify-event",
3991 G_CALLBACK(motion_notify_event), NULL);
3992 g_signal_connect(G_OBJECT(gui.drawarea), "button-press-event",
3993 G_CALLBACK(button_press_event), NULL);
3994 g_signal_connect(G_OBJECT(gui.drawarea), "button-release-event",
3995 G_CALLBACK(button_release_event), NULL);
3996 g_signal_connect(G_OBJECT(gui.drawarea), "scroll-event",
3997 G_CALLBACK(&scroll_event), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003998
3999 /*
4000 * Add selection handler functions.
4001 */
Bram Moolenaar98921892016-02-23 17:14:37 +01004002 g_signal_connect(G_OBJECT(gui.drawarea), "selection-clear-event",
4003 G_CALLBACK(selection_clear_event), NULL);
4004 g_signal_connect(G_OBJECT(gui.drawarea), "selection-received",
4005 G_CALLBACK(selection_received_cb), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004006
Bram Moolenaara76638f2010-06-05 12:49:46 +02004007 gui_gtk_set_selection_targets();
Bram Moolenaar071d4272004-06-13 20:20:40 +00004008
Bram Moolenaar98921892016-02-23 17:14:37 +01004009 g_signal_connect(G_OBJECT(gui.drawarea), "selection-get",
4010 G_CALLBACK(selection_get_cb), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004011
Bram Moolenaar30613902019-12-01 22:11:18 +01004012 // Pretend we don't have input focus, we will get an event if we do.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004013 gui.in_focus = FALSE;
4014
Bram Moolenaar7ebf4e12018-08-07 20:01:40 +02004015 // Handle changes to the "Xft/DPI" setting.
4016 {
4017 GtkSettings *gtk_settings =
4018 gtk_settings_get_for_screen(gdk_screen_get_default());
4019
4020 g_signal_connect(gtk_settings, "notify::gtk-xft-dpi",
4021 G_CALLBACK(gtk_settings_xft_dpi_changed_cb), NULL);
4022 }
4023
Bram Moolenaar071d4272004-06-13 20:20:40 +00004024 return OK;
4025}
4026
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02004027#if defined(USE_GNOME_SESSION) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004028/*
4029 * This is called from gui_start() after a fork() has been done.
4030 * We have to tell the session manager our new PID.
4031 */
4032 void
4033gui_mch_forked(void)
4034{
4035 if (using_gnome)
4036 {
4037 GnomeClient *client;
4038
4039 client = gnome_master_client();
4040
4041 if (client != NULL)
4042 gnome_client_set_process_id(client, getpid());
4043 }
4044}
Bram Moolenaarf96ae0b2019-07-28 15:21:55 +02004045#endif // USE_GNOME_SESSION
Bram Moolenaar071d4272004-06-13 20:20:40 +00004046
Bram Moolenaar98921892016-02-23 17:14:37 +01004047#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +02004048 static GdkRGBA
4049color_to_rgba(guicolor_T color)
Bram Moolenaar98921892016-02-23 17:14:37 +01004050{
Bram Moolenaar36edf062016-07-21 22:10:12 +02004051 GdkRGBA rgba;
4052 rgba.red = ((color & 0xff0000) >> 16) / 255.0;
4053 rgba.green = ((color & 0xff00) >> 8) / 255.0;
4054 rgba.blue = ((color & 0xff)) / 255.0;
4055 rgba.alpha = 1.0;
4056 return rgba;
Bram Moolenaar98921892016-02-23 17:14:37 +01004057}
4058
4059 static void
Bram Moolenaar36edf062016-07-21 22:10:12 +02004060set_cairo_source_rgba_from_color(cairo_t *cr, guicolor_T color)
Bram Moolenaar98921892016-02-23 17:14:37 +01004061{
Bram Moolenaar36edf062016-07-21 22:10:12 +02004062 const GdkRGBA rgba = color_to_rgba(color);
4063 cairo_set_source_rgba(cr, rgba.red, rgba.green, rgba.blue, rgba.alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01004064}
Bram Moolenaar30613902019-12-01 22:11:18 +01004065#endif // GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar98921892016-02-23 17:14:37 +01004066
Bram Moolenaar071d4272004-06-13 20:20:40 +00004067/*
4068 * Called when the foreground or background color has been changed.
4069 * This used to change the graphics contexts directly but we are
4070 * currently manipulating them where desired.
4071 */
4072 void
4073gui_mch_new_colors(void)
4074{
Bram Moolenaar7988a6f2020-12-09 15:53:27 +01004075 if (gui.drawarea != NULL
4076#if GTK_CHECK_VERSION(3,22,2)
4077 && gui.formwin != NULL
4078#endif
4079 && gtk_widget_get_window(gui.drawarea) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004080 {
Bram Moolenaar664323e2018-09-18 22:30:07 +02004081#if !GTK_CHECK_VERSION(3,22,2)
Bram Moolenaar7988a6f2020-12-09 15:53:27 +01004082 GdkWindow * const da_win = gtk_widget_get_window(gui.drawarea);
Bram Moolenaar664323e2018-09-18 22:30:07 +02004083#endif
Bram Moolenaara859f042016-11-17 19:11:55 +01004084#if GTK_CHECK_VERSION(3,22,2)
Bram Moolenaar7988a6f2020-12-09 15:53:27 +01004085 GtkStyleContext * const context =
4086 gtk_widget_get_style_context(gui.formwin);
Bram Moolenaara859f042016-11-17 19:11:55 +01004087 GtkCssProvider * const provider = gtk_css_provider_new();
4088 gchar * const css = g_strdup_printf(
Bram Moolenaar023fd5d2020-12-08 20:31:16 +01004089 "widget#vim-gtk-form {\n"
Bram Moolenaara859f042016-11-17 19:11:55 +01004090 " background-color: #%.2lx%.2lx%.2lx;\n"
4091 "}\n",
4092 (gui.back_pixel >> 16) & 0xff,
4093 (gui.back_pixel >> 8) & 0xff,
4094 gui.back_pixel & 0xff);
4095
4096 gtk_css_provider_load_from_data(provider, css, -1, NULL);
4097 gtk_style_context_add_provider(context,
4098 GTK_STYLE_PROVIDER(provider), G_MAXUINT);
4099
4100 g_free(css);
4101 g_object_unref(provider);
Bram Moolenaar30613902019-12-01 22:11:18 +01004102#elif GTK_CHECK_VERSION(3,4,0) // !GTK_CHECK_VERSION(3,22,2)
Bram Moolenaar36edf062016-07-21 22:10:12 +02004103 GdkRGBA rgba;
Bram Moolenaar98921892016-02-23 17:14:37 +01004104
Bram Moolenaar36edf062016-07-21 22:10:12 +02004105 rgba = color_to_rgba(gui.back_pixel);
Bram Moolenaar98921892016-02-23 17:14:37 +01004106 {
4107 cairo_pattern_t * const pat = cairo_pattern_create_rgba(
Bram Moolenaar36edf062016-07-21 22:10:12 +02004108 rgba.red, rgba.green, rgba.blue, rgba.alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01004109 if (pat != NULL)
4110 {
4111 gdk_window_set_background_pattern(da_win, pat);
4112 cairo_pattern_destroy(pat);
4113 }
4114 else
Bram Moolenaar36edf062016-07-21 22:10:12 +02004115 gdk_window_set_background_rgba(da_win, &rgba);
Bram Moolenaar98921892016-02-23 17:14:37 +01004116 }
Bram Moolenaar30613902019-12-01 22:11:18 +01004117#else // !GTK_CHECK_VERSION(3,4,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004118 GdkColor color = { 0, 0, 0, 0 };
4119
4120 color.pixel = gui.back_pixel;
Bram Moolenaar98921892016-02-23 17:14:37 +01004121 gdk_window_set_background(da_win, &color);
Bram Moolenaar30613902019-12-01 22:11:18 +01004122#endif // !GTK_CHECK_VERSION(3,22,2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004123 }
4124}
4125
Bram Moolenaar071d4272004-06-13 20:20:40 +00004126/*
4127 * This signal informs us about the need to rearrange our sub-widgets.
4128 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004129 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004130form_configure_event(GtkWidget *widget UNUSED,
4131 GdkEventConfigure *event,
4132 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004133{
Ernie Rael9f53e7b2022-04-17 18:27:49 +01004134 int usable_height = event->height;
4135 // Resize requests are made for gui.mainwin,
4136 // get it's dimensions for searching if this event
4137 // is a response to a vim request.
4138 GdkWindow *win = gtk_widget_get_window(gui.mainwin);
4139 int w = gdk_window_get_width(win);
4140 int h = gdk_window_get_height(win);
4141
4142#ifdef ENABLE_RESIZE_HISTORY_LOG
4143 ch_log(NULL, "gui_gtk: form_configure_event: (%d, %d) [%d, %d]",
4144 w, h, (int)Columns, (int)Rows);
4145#endif
4146
4147 // Look through history of recent vim resize reqeusts.
4148 // If this event matches:
4149 // - "latest resize hist" We're caught up;
4150 // clear the history and process this event.
4151 // If history is, old to new, 100, 99, 100, 99. If this event is
4152 // 99 for the stale, it is matched against the current. History
4153 // is cleared, we my bounce, but no worse than before.
4154 // - "older/stale hist" If match an unused event in history,
4155 // then discard this event, and mark the matching event as used.
4156 // - "no match" Figure it's a user resize event, clear history.
4157 // NOTE: clear history is default, then all incoming events are processed
4158
4159 if (!MATCH_WIDTH_HEIGHT(latest_resize_hist, w, h)
4160 && match_stale_width_height(w, h))
4161 // discard stale event
4162 return TRUE;
4163 clear_resize_hists();
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004164
Bram Moolenaar182707a2016-11-21 20:55:58 +01004165#if GTK_CHECK_VERSION(3,22,2) && !GTK_CHECK_VERSION(3,22,4)
Bram Moolenaar30613902019-12-01 22:11:18 +01004166 // As of 3.22.2, GdkWindows have started distributing configure events to
4167 // their "native" children (https://git.gnome.org/browse/gtk+/commit/?h=gtk-3-22&id=12579fe71b3b8f79eb9c1b80e429443bcc437dd0).
4168 //
4169 // As can be seen from the implementation of move_native_children() and
4170 // configure_native_child() in gdkwindow.c, those functions actually
4171 // propagate configure events to every child, failing to distinguish
4172 // "native" one from non-native one.
4173 //
4174 // Naturally, configure events propagated to here like that are fallacious
4175 // and, as a matter of fact, they trigger a geometric collapse of
4176 // gui.formwin.
4177 //
4178 // To filter out such fallacious events, check if the given event is the
4179 // one that was sent out to the right place. Ignore it if not.
4180 //
4181 // Follow-up
4182 // After a few weeks later, the GdkWindow change mentioned above was
4183 // reverted (https://git.gnome.org/browse/gtk+/commit/?h=gtk-3-22&id=f70039cb9603a02d2369fec4038abf40a1711155).
4184 // The corresponding official release is 3.22.4.
Bram Moolenaara859f042016-11-17 19:11:55 +01004185 if (event->window != gtk_widget_get_window(gui.formwin))
4186 return TRUE;
4187#endif
4188
Bram Moolenaar30613902019-12-01 22:11:18 +01004189 // When in a GtkPlug, we can't guarantee valid heights (as a round
4190 // no. of char-heights), so we have to manually sanitise them.
4191 // Widths seem to sort themselves out, don't ask me why.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004192 if (gtk_socket_id != 0)
Bram Moolenaar30613902019-12-01 22:11:18 +01004193 usable_height -= (gui.char_height - (gui.char_height/2)); // sic.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004194
Bram Moolenaar8a99e662020-10-21 16:10:21 +02004195 gui_gtk_form_freeze(GTK_FORM(gui.formwin));
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004196 gui_resize_shell(event->width, usable_height);
Bram Moolenaar8a99e662020-10-21 16:10:21 +02004197 gui_gtk_form_thaw(GTK_FORM(gui.formwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004198
4199 return TRUE;
4200}
4201
4202/*
4203 * Function called when window already closed.
4204 * We can't do much more here than to trying to preserve what had been done,
4205 * since the window is already inevitably going away.
4206 */
Bram Moolenaar98921892016-02-23 17:14:37 +01004207 static void
4208mainwin_destroy_cb(GObject *object UNUSED, gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004209{
Bram Moolenaar30613902019-12-01 22:11:18 +01004210 // Don't write messages to the GUI anymore
Bram Moolenaar071d4272004-06-13 20:20:40 +00004211 full_screen = FALSE;
4212
4213 gui.mainwin = NULL;
4214 gui.drawarea = NULL;
4215
Bram Moolenaar30613902019-12-01 22:11:18 +01004216 if (!exiting) // only do anything if the destroy was unexpected
Bram Moolenaar071d4272004-06-13 20:20:40 +00004217 {
Bram Moolenaarce0842a2005-07-18 21:58:11 +00004218 vim_strncpy(IObuff,
4219 (char_u *)_("Vim: Main window unexpectedly destroyed\n"),
4220 IOSIZE - 1);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004221 preserve_exit();
4222 }
Bram Moolenaar36e294c2015-12-29 18:55:46 +01004223#ifdef USE_GRESOURCE
4224 gui_gtk_unregister_resource();
4225#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00004226}
4227
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004228 void
4229gui_gtk_get_screen_geom_of_win(
4230 GtkWidget *wid,
4231 int point_x, // x position of window if not initialized
4232 int point_y, // y position of window if not initialized
4233 int *screen_x,
4234 int *screen_y,
4235 int *width,
4236 int *height)
4237{
4238 GdkRectangle geometry;
4239 GdkWindow *win = gtk_widget_get_window(wid);
4240#if GTK_CHECK_VERSION(3,22,0)
Bram Moolenaar24001432021-03-20 12:36:46 +01004241 GdkDisplay *dpy;
4242 GdkMonitor *monitor;
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004243
Bram Moolenaar24001432021-03-20 12:36:46 +01004244 if (wid != NULL && gtk_widget_get_realized(wid))
4245 dpy = gtk_widget_get_display(wid);
4246 else
4247 dpy = gdk_display_get_default();
4248 if (win != NULL)
4249 monitor = gdk_display_get_monitor_at_window(dpy, win);
4250 else
4251 monitor = gdk_display_get_monitor_at_point(dpy, point_x, point_y);
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004252 gdk_monitor_get_geometry(monitor, &geometry);
4253#else
4254 GdkScreen* screen;
4255 int monitor;
4256
4257 if (wid != NULL && gtk_widget_has_screen(wid))
4258 screen = gtk_widget_get_screen(wid);
4259 else
4260 screen = gdk_screen_get_default();
Bram Moolenaar24001432021-03-20 12:36:46 +01004261 if (win != NULL)
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004262 monitor = gdk_screen_get_monitor_at_window(screen, win);
Bram Moolenaar24001432021-03-20 12:36:46 +01004263 else
4264 monitor = gdk_screen_get_monitor_at_point(screen, point_x, point_y);
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004265 gdk_screen_get_monitor_geometry(screen, monitor, &geometry);
4266#endif
4267 *screen_x = geometry.x;
4268 *screen_y = geometry.y;
4269 *width = geometry.width;
4270 *height = geometry.height;
4271}
4272
4273/*
4274 * The screen size is used to make sure the initial window doesn't get bigger
4275 * than the screen. This subtracts some room for menubar, toolbar and window
4276 * decorations.
4277 */
4278 static void
4279gui_gtk_get_screen_dimensions(
4280 int point_x,
4281 int point_y,
4282 int *screen_w,
4283 int *screen_h)
4284{
4285 int x, y;
4286
4287 gui_gtk_get_screen_geom_of_win(gui.mainwin, point_x, point_y,
4288 &x, &y, screen_w, screen_h);
4289
4290 // Subtract 'guiheadroom' from the height to allow some room for the
4291 // window manager (task list and window title bar).
4292 *screen_h -= p_ghr;
4293
4294 /*
4295 * FIXME: dirty trick: Because the gui_get_base_height() doesn't include
4296 * the toolbar and menubar for GTK, we subtract them from the screen
4297 * height, so that the window size can be made to fit on the screen.
4298 * This should be completely changed later.
4299 */
4300 *screen_w -= get_menu_tool_width();
4301 *screen_h -= get_menu_tool_height();
4302}
4303
4304 void
4305gui_mch_get_screen_dimensions(int *screen_w, int *screen_h)
4306{
4307 gui_gtk_get_screen_dimensions(0, 0, screen_w, screen_h);
4308}
4309
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004310
4311/*
4312 * Bit of a hack to ensure we start GtkPlug windows with the correct window
4313 * hints (and thus the required size from -geom), but that after that we
4314 * put the hints back to normal (the actual minimum size) so we may
4315 * subsequently be resized smaller. GtkSocket (the parent end) uses the
Bram Moolenaarc4568ab2018-11-16 16:21:05 +01004316 * plug's window 'min hints to set *its* minimum size, but that's also the
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004317 * only way we have of making ourselves bigger (by set lines/columns).
4318 * Thus set hints at start-up to ensure correct init. size, then a
Bram Moolenaar3f2a5d82016-02-27 22:08:16 +01004319 * second after the final attempt to reset the real minimum hints (done by
4320 * scrollbar init.), actually do the standard hints and stop the timer.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004321 * We'll not let the default hints be set while this timer's active.
4322 */
Bram Moolenaar4ab79682017-08-27 14:50:47 +02004323 static timeout_cb_type
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004324check_startup_plug_hints(gpointer data UNUSED)
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004325{
4326 if (init_window_hints_state == 1)
4327 {
Bram Moolenaar30613902019-12-01 22:11:18 +01004328 // Safe to use normal hints now
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004329 init_window_hints_state = 0;
4330 update_window_manager_hints(0, 0);
Bram Moolenaar30613902019-12-01 22:11:18 +01004331 return FALSE; // stop timer
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004332 }
4333
Bram Moolenaar30613902019-12-01 22:11:18 +01004334 // Keep on trying
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004335 init_window_hints_state = 1;
4336 return TRUE;
4337}
4338
Bram Moolenaar071d4272004-06-13 20:20:40 +00004339/*
4340 * Open the GUI window which was created by a call to gui_mch_init().
4341 */
4342 int
4343gui_mch_open(void)
4344{
4345 guicolor_T fg_pixel = INVALCOLOR;
4346 guicolor_T bg_pixel = INVALCOLOR;
Bram Moolenaar79ef6d62009-09-23 15:35:48 +00004347 guint pixel_width;
4348 guint pixel_height;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004349
Bram Moolenaar071d4272004-06-13 20:20:40 +00004350 /*
4351 * Allow setting a window role on the command line, or invent one
4352 * if none was specified. This is mainly useful for GNOME session
4353 * support; allowing the WM to restore window placement.
4354 */
4355 if (role_argument != NULL)
4356 {
4357 gtk_window_set_role(GTK_WINDOW(gui.mainwin), role_argument);
4358 }
4359 else
4360 {
4361 char *role;
4362
Bram Moolenaar30613902019-12-01 22:11:18 +01004363 // Invent a unique-enough ID string for the role
Bram Moolenaar071d4272004-06-13 20:20:40 +00004364 role = g_strdup_printf("vim-%u-%u-%u",
4365 (unsigned)mch_get_pid(),
4366 (unsigned)g_random_int(),
4367 (unsigned)time(NULL));
4368
4369 gtk_window_set_role(GTK_WINDOW(gui.mainwin), role);
4370 g_free(role);
4371 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004372
4373 if (gui_win_x != -1 && gui_win_y != -1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004374 gtk_window_move(GTK_WINDOW(gui.mainwin), gui_win_x, gui_win_y);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004375
Bram Moolenaar30613902019-12-01 22:11:18 +01004376 // Determine user specified geometry, if present.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004377 if (gui.geom != NULL)
4378 {
4379 int mask;
4380 unsigned int w, h;
4381 int x = 0;
4382 int y = 0;
4383
4384 mask = XParseGeometry((char *)gui.geom, &x, &y, &w, &h);
4385
4386 if (mask & WidthValue)
4387 Columns = w;
4388 if (mask & HeightValue)
Bram Moolenaard68071d2006-05-02 22:08:30 +00004389 {
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004390 if (p_window > (long)h - 1 || !option_was_set((char_u *)"window"))
Bram Moolenaard68071d2006-05-02 22:08:30 +00004391 p_window = h - 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004392 Rows = h;
Bram Moolenaard68071d2006-05-02 22:08:30 +00004393 }
Bram Moolenaare057d402013-06-30 17:51:51 +02004394 limit_screen_size();
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004395
4396 pixel_width = (guint)(gui_get_base_width() + Columns * gui.char_width);
4397 pixel_height = (guint)(gui_get_base_height() + Rows * gui.char_height);
4398
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004399 pixel_width += get_menu_tool_width();
4400 pixel_height += get_menu_tool_height();
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004401
Bram Moolenaar071d4272004-06-13 20:20:40 +00004402 if (mask & (XValue | YValue))
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004403 {
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00004404 int ww, hh;
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004405
4406#ifdef FEAT_GUI_GTK
4407 gui_gtk_get_screen_dimensions(x, y, &ww, &hh);
4408#else
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00004409 gui_mch_get_screen_dimensions(&ww, &hh);
Bram Moolenaara555e6f2021-03-18 22:28:57 +01004410#endif
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00004411 hh += p_ghr + get_menu_tool_height();
4412 ww += get_menu_tool_width();
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004413 if (mask & XNegative)
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00004414 x += ww - pixel_width;
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004415 if (mask & YNegative)
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00004416 y += hh - pixel_height;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004417 gtk_window_move(GTK_WINDOW(gui.mainwin), x, y);
Bram Moolenaar2dd8b522007-10-19 12:33:44 +00004418 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01004419 VIM_CLEAR(gui.geom);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004420
Bram Moolenaar30613902019-12-01 22:11:18 +01004421 // From now until everyone's stopped trying to set the window hints
4422 // to their correct minimum values, stop them being set as we need
4423 // them to remain at our required size for the parent GtkSocket to
4424 // give us the right initial size.
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004425 if (gtk_socket_id != 0 && (mask & WidthValue || mask & HeightValue))
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004426 {
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004427 update_window_manager_hints(pixel_width, pixel_height);
4428 init_window_hints_state = 1;
Bram Moolenaar4ab79682017-08-27 14:50:47 +02004429 timeout_add(1000, check_startup_plug_hints, NULL);
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004430 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004431 }
4432
Bram Moolenaar79ef6d62009-09-23 15:35:48 +00004433 pixel_width = (guint)(gui_get_base_width() + Columns * gui.char_width);
4434 pixel_height = (guint)(gui_get_base_height() + Rows * gui.char_height);
Bram Moolenaar30613902019-12-01 22:11:18 +01004435 // For GTK2 changing the size of the form widget doesn't cause window
4436 // resizing.
Bram Moolenaardebe25a2010-06-06 17:41:24 +02004437 if (gtk_socket_id == 0)
Bram Moolenaar79ef6d62009-09-23 15:35:48 +00004438 gtk_window_resize(GTK_WINDOW(gui.mainwin), pixel_width, pixel_height);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004439 update_window_manager_hints(0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004440
4441 if (foreground_argument != NULL)
4442 fg_pixel = gui_get_color((char_u *)foreground_argument);
4443 if (fg_pixel == INVALCOLOR)
4444 fg_pixel = gui_get_color((char_u *)"Black");
4445
4446 if (background_argument != NULL)
4447 bg_pixel = gui_get_color((char_u *)background_argument);
4448 if (bg_pixel == INVALCOLOR)
4449 bg_pixel = gui_get_color((char_u *)"White");
4450
4451 if (found_reverse_arg)
4452 {
4453 gui.def_norm_pixel = bg_pixel;
4454 gui.def_back_pixel = fg_pixel;
4455 }
4456 else
4457 {
4458 gui.def_norm_pixel = fg_pixel;
4459 gui.def_back_pixel = bg_pixel;
4460 }
4461
Bram Moolenaar30613902019-12-01 22:11:18 +01004462 // Get the colors from the "Normal" and "Menu" group (set in syntax.c or
4463 // in a vimrc file)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004464 set_normal_colors();
4465
Bram Moolenaar30613902019-12-01 22:11:18 +01004466 // Check that none of the colors are the same as the background color
Bram Moolenaar071d4272004-06-13 20:20:40 +00004467 gui_check_colors();
4468
Bram Moolenaar30613902019-12-01 22:11:18 +01004469 // Get the colors for the highlight groups (gui_check_colors() might have
4470 // changed them).
4471 highlight_gui_started(); // re-init colors and fonts
Bram Moolenaar071d4272004-06-13 20:20:40 +00004472
Bram Moolenaar98921892016-02-23 17:14:37 +01004473 g_signal_connect(G_OBJECT(gui.mainwin), "destroy",
4474 G_CALLBACK(mainwin_destroy_cb), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004475
Bram Moolenaar071d4272004-06-13 20:20:40 +00004476 /*
4477 * Notify the fixed area about the need to resize the contents of the
4478 * gui.formwin, which we use for random positioning of the included
4479 * components.
4480 *
4481 * We connect this signal deferred finally after anything is in place,
4482 * since this is intended to handle resizements coming from the window
4483 * manager upon us and should not interfere with what VIM is requesting
4484 * upon startup.
4485 */
Ernie Rael9f53e7b2022-04-17 18:27:49 +01004486 latest_resize_hist = ALLOC_CLEAR_ONE(resize_hist_T);
Bram Moolenaar98921892016-02-23 17:14:37 +01004487 g_signal_connect(G_OBJECT(gui.formwin), "configure-event",
Ernie Rael9f53e7b2022-04-17 18:27:49 +01004488 G_CALLBACK(form_configure_event), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004489
4490#ifdef FEAT_DND
Bram Moolenaar30613902019-12-01 22:11:18 +01004491 // Set up for receiving DND items.
Bram Moolenaara76638f2010-06-05 12:49:46 +02004492 gui_gtk_set_dnd_targets();
Bram Moolenaar071d4272004-06-13 20:20:40 +00004493
Bram Moolenaar98921892016-02-23 17:14:37 +01004494 g_signal_connect(G_OBJECT(gui.drawarea), "drag-data-received",
Ernie Rael9f53e7b2022-04-17 18:27:49 +01004495 G_CALLBACK(drag_data_received_cb), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004496#endif
4497
Bram Moolenaar30613902019-12-01 22:11:18 +01004498 // With GTK+ 2, we need to iconify the window before calling show()
4499 // to avoid mapping the window for a short time.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004500 if (found_iconic_arg && gtk_socket_id == 0)
4501 gui_mch_iconify();
Bram Moolenaar071d4272004-06-13 20:20:40 +00004502
4503 {
Bram Moolenaar182c5be2010-06-25 05:37:59 +02004504#if defined(FEAT_GUI_GNOME) && defined(FEAT_MENU)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004505 unsigned long menu_handler = 0;
4506# ifdef FEAT_TOOLBAR
4507 unsigned long tool_handler = 0;
4508# endif
4509 /*
4510 * Urgh hackish :/ For some reason BonoboDockLayout always forces a
4511 * show when restoring the saved layout configuration. We can't just
4512 * hide the widgets again after gtk_widget_show(gui.mainwin) since it's
4513 * a toplevel window and thus will be realized immediately. Instead,
4514 * connect signal handlers to hide the widgets just after they've been
4515 * marked visible, but before the main window is realized.
4516 */
4517 if (using_gnome && vim_strchr(p_go, GO_MENUS) == NULL)
4518 menu_handler = g_signal_connect_after(gui.menubar_h, "show",
4519 G_CALLBACK(&gtk_widget_hide),
4520 NULL);
4521# ifdef FEAT_TOOLBAR
4522 if (using_gnome && vim_strchr(p_go, GO_TOOLBAR) == NULL
4523 && (toolbar_flags & (TOOLBAR_TEXT | TOOLBAR_ICONS)))
4524 tool_handler = g_signal_connect_after(gui.toolbar_h, "show",
4525 G_CALLBACK(&gtk_widget_hide),
4526 NULL);
4527# endif
4528#endif
4529 gtk_widget_show(gui.mainwin);
4530
Bram Moolenaar182c5be2010-06-25 05:37:59 +02004531#if defined(FEAT_GUI_GNOME) && defined(FEAT_MENU)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004532 if (menu_handler != 0)
4533 g_signal_handler_disconnect(gui.menubar_h, menu_handler);
4534# ifdef FEAT_TOOLBAR
4535 if (tool_handler != 0)
4536 g_signal_handler_disconnect(gui.toolbar_h, tool_handler);
4537# endif
4538#endif
4539 }
4540
Bram Moolenaar071d4272004-06-13 20:20:40 +00004541 return OK;
4542}
4543
Bram Moolenaar32fbc4f2020-09-29 22:16:09 +02004544/*
4545 * Clean up for when exiting Vim.
4546 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004547 void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004548gui_mch_exit(int rc UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004549{
Bram Moolenaar32fbc4f2020-09-29 22:16:09 +02004550 // Clean up, unless we don't want to invoke free().
4551 if (gui.mainwin != NULL && !really_exiting)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004552 gtk_widget_destroy(gui.mainwin);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004553}
4554
4555/*
4556 * Get the position of the top left corner of the window.
4557 */
4558 int
4559gui_mch_get_winpos(int *x, int *y)
4560{
Bram Moolenaar071d4272004-06-13 20:20:40 +00004561 gtk_window_get_position(GTK_WINDOW(gui.mainwin), x, y);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004562 return OK;
4563}
4564
4565/*
4566 * Set the position of the top left corner of the window to the given
4567 * coordinates.
4568 */
4569 void
4570gui_mch_set_winpos(int x, int y)
4571{
Bram Moolenaar071d4272004-06-13 20:20:40 +00004572 gtk_window_move(GTK_WINDOW(gui.mainwin), x, y);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004573}
4574
Bram Moolenaar98921892016-02-23 17:14:37 +01004575#if !GTK_CHECK_VERSION(3,0,0)
4576# if 0
Bram Moolenaar071d4272004-06-13 20:20:40 +00004577static int resize_idle_installed = FALSE;
4578/*
4579 * Idle handler to force resize. Used by gui_mch_set_shellsize() to ensure
4580 * the shell size doesn't exceed the window size, i.e. if the window manager
4581 * ignored our size request. Usually this happens if the window is maximized.
4582 *
4583 * FIXME: It'd be nice if we could find a little more orthodox solution.
4584 * See also the remark below in gui_mch_set_shellsize().
4585 *
4586 * DISABLED: When doing ":set lines+=1" this function would first invoke
4587 * gui_resize_shell() with the old size, then the normal callback would
4588 * report the new size through form_configure_event(). That caused the window
4589 * layout to be messed up.
4590 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004591 static gboolean
4592force_shell_resize_idle(gpointer data)
4593{
4594 if (gui.mainwin != NULL
4595 && GTK_WIDGET_REALIZED(gui.mainwin)
4596 && GTK_WIDGET_VISIBLE(gui.mainwin))
4597 {
4598 int width;
4599 int height;
4600
4601 gtk_window_get_size(GTK_WINDOW(gui.mainwin), &width, &height);
4602
4603 width -= get_menu_tool_width();
4604 height -= get_menu_tool_height();
4605
4606 gui_resize_shell(width, height);
4607 }
4608
4609 resize_idle_installed = FALSE;
Bram Moolenaar30613902019-12-01 22:11:18 +01004610 return FALSE; // don't call me again
Bram Moolenaar071d4272004-06-13 20:20:40 +00004611}
Bram Moolenaar98921892016-02-23 17:14:37 +01004612# endif
Bram Moolenaar30613902019-12-01 22:11:18 +01004613#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004614
Bram Moolenaar09736232009-09-23 16:14:49 +00004615/*
4616 * Return TRUE if the main window is maximized.
4617 */
4618 int
Bram Moolenaar66f948e2016-01-30 16:39:25 +01004619gui_mch_maximized(void)
Bram Moolenaar09736232009-09-23 16:14:49 +00004620{
Bram Moolenaar98921892016-02-23 17:14:37 +01004621 return (gui.mainwin != NULL && gtk_widget_get_window(gui.mainwin) != NULL
4622 && (gdk_window_get_state(gtk_widget_get_window(gui.mainwin))
4623 & GDK_WINDOW_STATE_MAXIMIZED));
Bram Moolenaar09736232009-09-23 16:14:49 +00004624}
4625
4626/*
4627 * Unmaximize the main window
4628 */
4629 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01004630gui_mch_unmaximize(void)
Bram Moolenaar09736232009-09-23 16:14:49 +00004631{
4632 if (gui.mainwin != NULL)
4633 gtk_window_unmaximize(GTK_WINDOW(gui.mainwin));
4634}
Bram Moolenaar09736232009-09-23 16:14:49 +00004635
Bram Moolenaar071d4272004-06-13 20:20:40 +00004636/*
Bram Moolenaar8ac44152017-11-09 18:33:29 +01004637 * Called when the font changed while the window is maximized or GO_KEEPWINSIZE
4638 * is set. Compute the new Rows and Columns. This is like resizing the
4639 * window.
Bram Moolenaar12bc1b52011-08-10 17:44:45 +02004640 */
4641 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01004642gui_mch_newfont(void)
Bram Moolenaar12bc1b52011-08-10 17:44:45 +02004643{
4644 int w, h;
4645
4646 gtk_window_get_size(GTK_WINDOW(gui.mainwin), &w, &h);
4647 w -= get_menu_tool_width();
4648 h -= get_menu_tool_height();
4649 gui_resize_shell(w, h);
4650}
4651
4652/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00004653 * Set the windows size.
4654 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004655 void
4656gui_mch_set_shellsize(int width, int height,
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004657 int min_width UNUSED, int min_height UNUSED,
4658 int base_width UNUSED, int base_height UNUSED,
4659 int direction UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004660{
Bram Moolenaar30613902019-12-01 22:11:18 +01004661 // give GTK+ a chance to put all widget's into place
Bram Moolenaar071d4272004-06-13 20:20:40 +00004662 gui_mch_update();
4663
Bram Moolenaar30613902019-12-01 22:11:18 +01004664 // this will cause the proper resizement to happen too
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004665 if (gtk_socket_id == 0)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004666 update_window_manager_hints(0, 0);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004667
Bram Moolenaar30613902019-12-01 22:11:18 +01004668 // With GTK+ 2, changing the size of the form widget doesn't resize
4669 // the window. So let's do it the other way around and resize the
4670 // main window instead.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004671 width += get_menu_tool_width();
4672 height += get_menu_tool_height();
4673
Ernie Rael9f53e7b2022-04-17 18:27:49 +01004674 alloc_resize_hist(width, height); // track the resize request
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004675 if (gtk_socket_id == 0)
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004676 gtk_window_resize(GTK_WINDOW(gui.mainwin), width, height);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004677 else
Bram Moolenaarc9b4b052006-04-30 18:54:39 +00004678 update_window_manager_hints(width, height);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004679
Bram Moolenaar98921892016-02-23 17:14:37 +01004680# if !GTK_CHECK_VERSION(3,0,0)
4681# if 0
Bram Moolenaar071d4272004-06-13 20:20:40 +00004682 if (!resize_idle_installed)
4683 {
4684 g_idle_add_full(GDK_PRIORITY_EVENTS + 10,
4685 &force_shell_resize_idle, NULL, NULL);
4686 resize_idle_installed = TRUE;
4687 }
Bram Moolenaar98921892016-02-23 17:14:37 +01004688# endif
Bram Moolenaar30613902019-12-01 22:11:18 +01004689# endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004690 /*
4691 * Wait until all events are processed to prevent a crash because the
4692 * real size of the drawing area doesn't reflect Vim's internal ideas.
4693 *
4694 * This is a bit of a hack, since Vim is a terminal application with a GUI
4695 * on top, while the GUI expects to be the boss.
4696 */
4697 gui_mch_update();
Bram Moolenaar071d4272004-06-13 20:20:40 +00004698}
4699
Bram Moolenaar071d4272004-06-13 20:20:40 +00004700 void
Bram Moolenaarb85cb212009-05-17 14:24:23 +00004701gui_mch_settitle(char_u *title, char_u *icon UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004702{
Bram Moolenaar071d4272004-06-13 20:20:40 +00004703 if (title != NULL && output_conv.vc_type != CONV_NONE)
4704 title = string_convert(&output_conv, title, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004705
4706 gtk_window_set_title(GTK_WINDOW(gui.mainwin), (const char *)title);
4707
Bram Moolenaar071d4272004-06-13 20:20:40 +00004708 if (output_conv.vc_type != CONV_NONE)
4709 vim_free(title);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004710}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004711
4712#if defined(FEAT_MENU) || defined(PROTO)
4713 void
4714gui_mch_enable_menu(int showit)
4715{
4716 GtkWidget *widget;
4717
4718# ifdef FEAT_GUI_GNOME
4719 if (using_gnome)
4720 widget = gui.menubar_h;
4721 else
4722# endif
4723 widget = gui.menubar;
4724
Bram Moolenaar30613902019-12-01 22:11:18 +01004725 // Do not disable the menu while starting up, otherwise F10 doesn't work.
Bram Moolenaar98921892016-02-23 17:14:37 +01004726 if (!showit != !gtk_widget_get_visible(widget) && !gui.starting)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004727 {
4728 if (showit)
4729 gtk_widget_show(widget);
4730 else
4731 gtk_widget_hide(widget);
4732
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004733 update_window_manager_hints(0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004734 }
4735}
Bram Moolenaar30613902019-12-01 22:11:18 +01004736#endif // FEAT_MENU
Bram Moolenaar071d4272004-06-13 20:20:40 +00004737
4738#if defined(FEAT_TOOLBAR) || defined(PROTO)
4739 void
4740gui_mch_show_toolbar(int showit)
4741{
4742 GtkWidget *widget;
4743
4744 if (gui.toolbar == NULL)
4745 return;
4746
4747# ifdef FEAT_GUI_GNOME
4748 if (using_gnome)
4749 widget = gui.toolbar_h;
4750 else
4751# endif
4752 widget = gui.toolbar;
4753
4754 if (showit)
4755 set_toolbar_style(GTK_TOOLBAR(gui.toolbar));
4756
Bram Moolenaar98921892016-02-23 17:14:37 +01004757 if (!showit != !gtk_widget_get_visible(widget))
Bram Moolenaar071d4272004-06-13 20:20:40 +00004758 {
4759 if (showit)
4760 gtk_widget_show(widget);
4761 else
4762 gtk_widget_hide(widget);
4763
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00004764 update_window_manager_hints(0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004765 }
4766}
Bram Moolenaar30613902019-12-01 22:11:18 +01004767#endif // FEAT_TOOLBAR
Bram Moolenaar071d4272004-06-13 20:20:40 +00004768
Bram Moolenaar071d4272004-06-13 20:20:40 +00004769/*
4770 * Check if a given font is a CJK font. This is done in a very crude manner. It
4771 * just see if U+04E00 for zh and ja and U+AC00 for ko are covered in a given
4772 * font. Consequently, this function cannot be used as a general purpose check
4773 * for CJK-ness for which fontconfig APIs should be used. This is only used by
4774 * gui_mch_init_font() to deal with 'CJK fixed width fonts'.
4775 */
4776 static int
4777is_cjk_font(PangoFontDescription *font_desc)
4778{
4779 static const char * const cjk_langs[] =
4780 {"zh_CN", "zh_TW", "zh_HK", "ja", "ko"};
4781
4782 PangoFont *font;
4783 unsigned i;
4784 int is_cjk = FALSE;
4785
4786 font = pango_context_load_font(gui.text_context, font_desc);
4787
4788 if (font == NULL)
4789 return FALSE;
4790
4791 for (i = 0; !is_cjk && i < G_N_ELEMENTS(cjk_langs); ++i)
4792 {
4793 PangoCoverage *coverage;
4794 gunichar uc;
4795
Bram Moolenaar14285cb2020-03-27 20:58:37 +01004796 // Valgrind reports a leak for pango_language_from_string(), but the
4797 // documentation says "This is owned by Pango and should not be freed".
Bram Moolenaar071d4272004-06-13 20:20:40 +00004798 coverage = pango_font_get_coverage(
4799 font, pango_language_from_string(cjk_langs[i]));
4800
4801 if (coverage != NULL)
4802 {
4803 uc = (cjk_langs[i][0] == 'k') ? 0xAC00 : 0x4E00;
4804 is_cjk = (pango_coverage_get(coverage, uc) == PANGO_COVERAGE_EXACT);
4805 pango_coverage_unref(coverage);
4806 }
4807 }
4808
4809 g_object_unref(font);
4810
4811 return is_cjk;
4812}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004813
Bram Moolenaar231334e2005-07-25 20:46:57 +00004814/*
4815 * Adjust gui.char_height (after 'linespace' was changed).
4816 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004817 int
Bram Moolenaar231334e2005-07-25 20:46:57 +00004818gui_mch_adjust_charheight(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004819{
Bram Moolenaar071d4272004-06-13 20:20:40 +00004820 PangoFontMetrics *metrics;
4821 int ascent;
4822 int descent;
4823
4824 metrics = pango_context_get_metrics(gui.text_context, gui.norm_font,
4825 pango_context_get_language(gui.text_context));
4826 ascent = pango_font_metrics_get_ascent(metrics);
4827 descent = pango_font_metrics_get_descent(metrics);
4828
4829 pango_font_metrics_unref(metrics);
4830
Bram Moolenaar70cf4582020-10-27 20:43:26 +01004831 // Round up when the value is more than about 1/16 of a pixel above a whole
4832 // pixel (12.0624 becomes 12, 12.07 becomes 13). Then add 'linespace'.
4833 gui.char_height = (ascent + descent + (PANGO_SCALE * 15) / 16)
4834 / PANGO_SCALE + p_linespace;
Bram Moolenaar30613902019-12-01 22:11:18 +01004835 // LINTED: avoid warning: bitwise operation on signed value
Bram Moolenaar071d4272004-06-13 20:20:40 +00004836 gui.char_ascent = PANGO_PIXELS(ascent + p_linespace * PANGO_SCALE / 2);
4837
Bram Moolenaar30613902019-12-01 22:11:18 +01004838 // A not-positive value of char_height may crash Vim. Only happens
4839 // if 'linespace' is negative (which does make sense sometimes).
Bram Moolenaar071d4272004-06-13 20:20:40 +00004840 gui.char_ascent = MAX(gui.char_ascent, 0);
4841 gui.char_height = MAX(gui.char_height, gui.char_ascent + 1);
4842
4843 return OK;
4844}
4845
Bram Moolenaar98921892016-02-23 17:14:37 +01004846#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01004847// Callback function used in gui_mch_font_dialog()
Bram Moolenaar98921892016-02-23 17:14:37 +01004848 static gboolean
4849font_filter(const PangoFontFamily *family,
4850 const PangoFontFace *face UNUSED,
4851 gpointer data UNUSED)
4852{
4853 return pango_font_family_is_monospace((PangoFontFamily *)family);
4854}
4855#endif
4856
Bram Moolenaar071d4272004-06-13 20:20:40 +00004857/*
4858 * Put up a font dialog and return the selected font name in allocated memory.
4859 * "oldval" is the previous value. Return NULL when cancelled.
4860 * This should probably go into gui_gtk.c. Hmm.
4861 * FIXME:
4862 * The GTK2 font selection dialog has no filtering API. So we could either
4863 * a) implement our own (possibly copying the code from somewhere else) or
4864 * b) just live with it.
4865 */
4866 char_u *
4867gui_mch_font_dialog(char_u *oldval)
4868{
4869 GtkWidget *dialog;
4870 int response;
4871 char_u *fontname = NULL;
4872 char_u *oldname;
4873
Bram Moolenaar98921892016-02-23 17:14:37 +01004874#if GTK_CHECK_VERSION(3,2,0)
4875 dialog = gtk_font_chooser_dialog_new(NULL, NULL);
4876 gtk_font_chooser_set_filter_func(GTK_FONT_CHOOSER(dialog), font_filter,
4877 NULL, NULL);
4878#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00004879 dialog = gtk_font_selection_dialog_new(NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01004880#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00004881
4882 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(gui.mainwin));
4883 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
4884
4885 if (oldval != NULL && oldval[0] != NUL)
4886 {
4887 if (output_conv.vc_type != CONV_NONE)
4888 oldname = string_convert(&output_conv, oldval, NULL);
4889 else
4890 oldname = oldval;
4891
Bram Moolenaar30613902019-12-01 22:11:18 +01004892 // Annoying bug in GTK (or Pango): if the font name does not include a
4893 // size, zero is used. Use default point size ten.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004894 if (!vim_isdigit(oldname[STRLEN(oldname) - 1]))
4895 {
4896 char_u *p = vim_strnsave(oldname, STRLEN(oldname) + 3);
4897
4898 if (p != NULL)
4899 {
4900 STRCPY(p + STRLEN(p), " 10");
4901 if (oldname != oldval)
4902 vim_free(oldname);
4903 oldname = p;
4904 }
4905 }
4906
Bram Moolenaar98921892016-02-23 17:14:37 +01004907#if GTK_CHECK_VERSION(3,2,0)
4908 gtk_font_chooser_set_font(
4909 GTK_FONT_CHOOSER(dialog), (const gchar *)oldname);
4910#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00004911 gtk_font_selection_dialog_set_font_name(
4912 GTK_FONT_SELECTION_DIALOG(dialog), (const char *)oldname);
Bram Moolenaar98921892016-02-23 17:14:37 +01004913#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00004914
4915 if (oldname != oldval)
Bram Moolenaar8c711452005-01-14 21:53:12 +00004916 vim_free(oldname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004917 }
Bram Moolenaarbef9d832009-09-11 13:46:41 +00004918 else
Bram Moolenaar98921892016-02-23 17:14:37 +01004919#if GTK_CHECK_VERSION(3,2,0)
4920 gtk_font_chooser_set_font(
4921 GTK_FONT_CHOOSER(dialog), DEFAULT_FONT);
4922#else
Bram Moolenaarbef9d832009-09-11 13:46:41 +00004923 gtk_font_selection_dialog_set_font_name(
4924 GTK_FONT_SELECTION_DIALOG(dialog), DEFAULT_FONT);
Bram Moolenaar98921892016-02-23 17:14:37 +01004925#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00004926
4927 response = gtk_dialog_run(GTK_DIALOG(dialog));
4928
4929 if (response == GTK_RESPONSE_OK)
4930 {
4931 char *name;
4932
Bram Moolenaar98921892016-02-23 17:14:37 +01004933#if GTK_CHECK_VERSION(3,2,0)
4934 name = gtk_font_chooser_get_font(GTK_FONT_CHOOSER(dialog));
4935#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00004936 name = gtk_font_selection_dialog_get_font_name(
4937 GTK_FONT_SELECTION_DIALOG(dialog));
Bram Moolenaar98921892016-02-23 17:14:37 +01004938#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00004939 if (name != NULL)
4940 {
Bram Moolenaar362e1a32006-03-06 23:29:24 +00004941 char_u *p;
4942
Bram Moolenaar30613902019-12-01 22:11:18 +01004943 // Apparently some font names include a comma, need to escape
4944 // that, because in 'guifont' it separates names.
Bram Moolenaar362e1a32006-03-06 23:29:24 +00004945 p = vim_strsave_escaped((char_u *)name, (char_u *)",");
Bram Moolenaar071d4272004-06-13 20:20:40 +00004946 g_free(name);
Bram Moolenaareb3593b2006-04-22 22:33:57 +00004947 if (p != NULL && input_conv.vc_type != CONV_NONE)
Bram Moolenaar362e1a32006-03-06 23:29:24 +00004948 {
4949 fontname = string_convert(&input_conv, p, NULL);
4950 vim_free(p);
4951 }
4952 else
4953 fontname = p;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004954 }
4955 }
4956
4957 if (response != GTK_RESPONSE_NONE)
4958 gtk_widget_destroy(dialog);
4959
4960 return fontname;
4961}
4962
4963/*
4964 * Some monospace fonts don't support a bold weight, and fall back
4965 * silently to the regular weight. But this is no good since our text
4966 * drawing function can emulate bold by overstriking. So let's try
4967 * to detect whether bold weight is actually available and emulate it
4968 * otherwise.
4969 *
4970 * Note that we don't need to check for italic style since Xft can
4971 * emulate italic on its own, provided you have a proper fontconfig
4972 * setup. We wouldn't be able to emulate it in Vim anyway.
4973 */
4974 static void
4975get_styled_font_variants(void)
4976{
4977 PangoFontDescription *bold_font_desc;
4978 PangoFont *plain_font;
4979 PangoFont *bold_font;
4980
4981 gui.font_can_bold = FALSE;
4982
4983 plain_font = pango_context_load_font(gui.text_context, gui.norm_font);
4984
4985 if (plain_font == NULL)
4986 return;
4987
4988 bold_font_desc = pango_font_description_copy_static(gui.norm_font);
4989 pango_font_description_set_weight(bold_font_desc, PANGO_WEIGHT_BOLD);
4990
4991 bold_font = pango_context_load_font(gui.text_context, bold_font_desc);
4992 /*
4993 * The comparison relies on the unique handle nature of a PangoFont*,
4994 * i.e. it's assumed that a different PangoFont* won't refer to the
4995 * same font. Seems to work, and failing here isn't critical anyway.
4996 */
4997 if (bold_font != NULL)
4998 {
4999 gui.font_can_bold = (bold_font != plain_font);
5000 g_object_unref(bold_font);
5001 }
5002
5003 pango_font_description_free(bold_font_desc);
5004 g_object_unref(plain_font);
5005}
5006
Bram Moolenaar071d4272004-06-13 20:20:40 +00005007static PangoEngineShape *default_shape_engine = NULL;
5008
5009/*
5010 * Create a map from ASCII characters in the range [32,126] to glyphs
5011 * of the current font. This is used by gui_gtk2_draw_string() to skip
5012 * the itemize and shaping process for the most common case.
5013 */
5014 static void
5015ascii_glyph_table_init(void)
5016{
Bram Moolenaar16350cb2016-08-14 20:27:34 +02005017 char_u ascii_chars[2 * 128];
Bram Moolenaar071d4272004-06-13 20:20:40 +00005018 PangoAttrList *attr_list;
5019 GList *item_list;
5020 int i;
5021
5022 if (gui.ascii_glyphs != NULL)
5023 pango_glyph_string_free(gui.ascii_glyphs);
5024 if (gui.ascii_font != NULL)
5025 g_object_unref(gui.ascii_font);
5026
5027 gui.ascii_glyphs = NULL;
5028 gui.ascii_font = NULL;
5029
Bram Moolenaar30613902019-12-01 22:11:18 +01005030 // For safety, fill in question marks for the control characters.
5031 // Put a space between characters to avoid shaping.
Bram Moolenaar16350cb2016-08-14 20:27:34 +02005032 for (i = 0; i < 128; ++i)
5033 {
5034 if (i >= 32 && i < 127)
5035 ascii_chars[2 * i] = i;
5036 else
5037 ascii_chars[2 * i] = '?';
5038 ascii_chars[2 * i + 1] = ' ';
5039 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00005040
5041 attr_list = pango_attr_list_new();
5042 item_list = pango_itemize(gui.text_context, (const char *)ascii_chars,
5043 0, sizeof(ascii_chars), attr_list, NULL);
5044
Bram Moolenaar30613902019-12-01 22:11:18 +01005045 if (item_list != NULL && item_list->next == NULL) // play safe
Bram Moolenaar071d4272004-06-13 20:20:40 +00005046 {
5047 PangoItem *item;
5048 int width;
5049
5050 item = (PangoItem *)item_list->data;
5051 width = gui.char_width * PANGO_SCALE;
5052
Bram Moolenaar30613902019-12-01 22:11:18 +01005053 // Remember the shape engine used for ASCII.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005054 default_shape_engine = item->analysis.shape_engine;
5055
5056 gui.ascii_font = item->analysis.font;
5057 g_object_ref(gui.ascii_font);
5058
5059 gui.ascii_glyphs = pango_glyph_string_new();
5060
5061 pango_shape((const char *)ascii_chars, sizeof(ascii_chars),
5062 &item->analysis, gui.ascii_glyphs);
5063
5064 g_return_if_fail(gui.ascii_glyphs->num_glyphs == sizeof(ascii_chars));
5065
5066 for (i = 0; i < gui.ascii_glyphs->num_glyphs; ++i)
5067 {
5068 PangoGlyphGeometry *geom;
5069
5070 geom = &gui.ascii_glyphs->glyphs[i].geometry;
5071 geom->x_offset += MAX(0, width - geom->width) / 2;
5072 geom->width = width;
5073 }
5074 }
5075
Bram Moolenaar81a4cf42020-09-09 19:05:13 +02005076 // TODO: is this type cast OK?
5077 g_list_foreach(item_list, (GFunc)(void *)&pango_item_free, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005078 g_list_free(item_list);
5079 pango_attr_list_unref(attr_list);
5080}
Bram Moolenaar071d4272004-06-13 20:20:40 +00005081
5082/*
5083 * Initialize Vim to use the font or fontset with the given name.
5084 * Return FAIL if the font could not be loaded, OK otherwise.
5085 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00005086 int
Bram Moolenaarb85cb212009-05-17 14:24:23 +00005087gui_mch_init_font(char_u *font_name, int fontset UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00005088{
Bram Moolenaar071d4272004-06-13 20:20:40 +00005089 PangoFontDescription *font_desc;
5090 PangoLayout *layout;
5091 int width;
5092
Bram Moolenaar30613902019-12-01 22:11:18 +01005093 // If font_name is NULL, this means to use the default, which should
5094 // be present on all proper Pango/fontconfig installations.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005095 if (font_name == NULL)
5096 font_name = (char_u *)DEFAULT_FONT;
5097
5098 font_desc = gui_mch_get_font(font_name, FALSE);
5099
5100 if (font_desc == NULL)
5101 return FAIL;
5102
5103 gui_mch_free_font(gui.norm_font);
5104 gui.norm_font = font_desc;
5105
5106 pango_context_set_font_description(gui.text_context, font_desc);
5107
5108 layout = pango_layout_new(gui.text_context);
5109 pango_layout_set_text(layout, "MW", 2);
5110 pango_layout_get_size(layout, &width, NULL);
5111 /*
5112 * Set char_width to half the width obtained from pango_layout_get_size()
5113 * for CJK fixed_width/bi-width fonts. An unpatched version of Xft leads
5114 * Pango to use the same width for both non-CJK characters (e.g. Latin
5115 * letters and numbers) and CJK characters. This results in 's p a c e d
5116 * o u t' rendering when a CJK 'fixed width' font is used. To work around
5117 * that, divide the width returned by Pango by 2 if cjk_width is equal to
5118 * width for CJK fonts.
5119 *
5120 * For related bugs, see:
5121 * http://bugzilla.gnome.org/show_bug.cgi?id=106618
5122 * http://bugzilla.gnome.org/show_bug.cgi?id=106624
5123 *
5124 * With this, for all four of the following cases, Vim works fine:
5125 * guifont=CJK_fixed_width_font
5126 * guifont=Non_CJK_fixed_font
5127 * guifont=Non_CJK_fixed_font,CJK_Fixed_font
5128 * guifont=Non_CJK_fixed_font guifontwide=CJK_fixed_font
5129 */
5130 if (is_cjk_font(gui.norm_font))
5131 {
5132 int cjk_width;
5133
Bram Moolenaar30613902019-12-01 22:11:18 +01005134 // Measure the text extent of U+4E00 and U+4E8C
Bram Moolenaar071d4272004-06-13 20:20:40 +00005135 pango_layout_set_text(layout, "\344\270\200\344\272\214", -1);
5136 pango_layout_get_size(layout, &cjk_width, NULL);
5137
Bram Moolenaar30613902019-12-01 22:11:18 +01005138 if (width == cjk_width) // Xft not patched
Bram Moolenaar071d4272004-06-13 20:20:40 +00005139 width /= 2;
5140 }
5141 g_object_unref(layout);
5142
5143 gui.char_width = (width / 2 + PANGO_SCALE - 1) / PANGO_SCALE;
5144
Bram Moolenaar30613902019-12-01 22:11:18 +01005145 // A zero width may cause a crash. Happens for semi-invalid fontsets.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005146 if (gui.char_width <= 0)
5147 gui.char_width = 8;
5148
Bram Moolenaar231334e2005-07-25 20:46:57 +00005149 gui_mch_adjust_charheight();
Bram Moolenaar071d4272004-06-13 20:20:40 +00005150
Bram Moolenaar30613902019-12-01 22:11:18 +01005151 // Set the fontname, which will be used for information purposes
Bram Moolenaar071d4272004-06-13 20:20:40 +00005152 hl_set_font_name(font_name);
5153
5154 get_styled_font_variants();
5155 ascii_glyph_table_init();
5156
Bram Moolenaar30613902019-12-01 22:11:18 +01005157 // Avoid unnecessary overhead if 'guifontwide' is equal to 'guifont'.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005158 if (gui.wide_font != NULL
5159 && pango_font_description_equal(gui.norm_font, gui.wide_font))
5160 {
5161 pango_font_description_free(gui.wide_font);
5162 gui.wide_font = NULL;
5163 }
5164
Bram Moolenaare161c792009-11-03 17:13:59 +00005165 if (gui_mch_maximized())
5166 {
Bram Moolenaar30613902019-12-01 22:11:18 +01005167 // Update lines and columns in accordance with the new font, keep the
5168 // window maximized.
Bram Moolenaar12bc1b52011-08-10 17:44:45 +02005169 gui_mch_newfont();
Bram Moolenaare161c792009-11-03 17:13:59 +00005170 }
5171 else
Bram Moolenaare161c792009-11-03 17:13:59 +00005172 {
Bram Moolenaar30613902019-12-01 22:11:18 +01005173 // Preserve the logical dimensions of the screen.
Bram Moolenaare161c792009-11-03 17:13:59 +00005174 update_window_manager_hints(0, 0);
5175 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00005176
5177 return OK;
5178}
5179
5180/*
5181 * Get a reference to the font "name".
5182 * Return zero for failure.
5183 */
5184 GuiFont
5185gui_mch_get_font(char_u *name, int report_error)
5186{
Bram Moolenaar071d4272004-06-13 20:20:40 +00005187 PangoFontDescription *font;
Bram Moolenaar071d4272004-06-13 20:20:40 +00005188
Bram Moolenaar30613902019-12-01 22:11:18 +01005189 // can't do this when GUI is not running
Bram Moolenaar071d4272004-06-13 20:20:40 +00005190 if (!gui.in_use || name == NULL)
5191 return NULL;
5192
Bram Moolenaar071d4272004-06-13 20:20:40 +00005193 if (output_conv.vc_type != CONV_NONE)
5194 {
5195 char_u *buf;
5196
5197 buf = string_convert(&output_conv, name, NULL);
5198 if (buf != NULL)
5199 {
5200 font = pango_font_description_from_string((const char *)buf);
5201 vim_free(buf);
5202 }
5203 else
5204 font = NULL;
5205 }
5206 else
5207 font = pango_font_description_from_string((const char *)name);
5208
5209 if (font != NULL)
5210 {
5211 PangoFont *real_font;
5212
Bram Moolenaar30613902019-12-01 22:11:18 +01005213 // pango_context_load_font() bails out if no font size is set
Bram Moolenaar071d4272004-06-13 20:20:40 +00005214 if (pango_font_description_get_size(font) <= 0)
5215 pango_font_description_set_size(font, 10 * PANGO_SCALE);
5216
5217 real_font = pango_context_load_font(gui.text_context, font);
5218
5219 if (real_font == NULL)
5220 {
5221 pango_font_description_free(font);
5222 font = NULL;
5223 }
5224 else
5225 g_object_unref(real_font);
5226 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00005227
5228 if (font == NULL)
5229 {
5230 if (report_error)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005231 semsg(_((char *)e_unknown_font_str), name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005232 return NULL;
5233 }
5234
Bram Moolenaar071d4272004-06-13 20:20:40 +00005235 return font;
5236}
5237
Bram Moolenaar1cd871b2004-12-19 22:46:22 +00005238#if defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005239/*
5240 * Return the name of font "font" in allocated memory.
5241 */
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005242 char_u *
Bram Moolenaarb85cb212009-05-17 14:24:23 +00005243gui_mch_get_fontname(GuiFont font, char_u *name UNUSED)
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005244{
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005245 if (font != NOFONT)
5246 {
Bram Moolenaar89d40322006-08-29 15:30:07 +00005247 char *pangoname = pango_font_description_to_string(font);
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005248
Bram Moolenaar89d40322006-08-29 15:30:07 +00005249 if (pangoname != NULL)
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005250 {
Bram Moolenaar89d40322006-08-29 15:30:07 +00005251 char_u *s = vim_strsave((char_u *)pangoname);
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005252
Bram Moolenaar89d40322006-08-29 15:30:07 +00005253 g_free(pangoname);
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005254 return s;
5255 }
5256 }
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005257 return NULL;
5258}
Bram Moolenaar1cd871b2004-12-19 22:46:22 +00005259#endif
Bram Moolenaar46c9c732004-12-12 11:37:09 +00005260
Bram Moolenaar071d4272004-06-13 20:20:40 +00005261/*
5262 * If a font is not going to be used, free its structure.
5263 */
5264 void
5265gui_mch_free_font(GuiFont font)
5266{
5267 if (font != NOFONT)
Bram Moolenaar071d4272004-06-13 20:20:40 +00005268 pango_font_description_free(font);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005269}
5270
Bram Moolenaar071d4272004-06-13 20:20:40 +00005271/*
Bram Moolenaar36edf062016-07-21 22:10:12 +02005272 * Return the Pixel value (color) for the given color name.
5273 *
Bram Moolenaar071d4272004-06-13 20:20:40 +00005274 * Return INVALCOLOR for error.
5275 */
5276 guicolor_T
5277gui_mch_get_color(char_u *name)
5278{
Bram Moolenaar2e324952018-04-14 14:37:07 +02005279 guicolor_T color = INVALCOLOR;
5280
Bram Moolenaar30613902019-12-01 22:11:18 +01005281 if (!gui.in_use) // can't do this when GUI not running
Bram Moolenaar2e324952018-04-14 14:37:07 +02005282 return color;
5283
5284 if (name != NULL)
5285 color = gui_get_color_cmn(name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005286
Bram Moolenaar98921892016-02-23 17:14:37 +01005287#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar2e324952018-04-14 14:37:07 +02005288 return color;
Bram Moolenaar98921892016-02-23 17:14:37 +01005289#else
Bram Moolenaar36edf062016-07-21 22:10:12 +02005290 if (color == INVALCOLOR)
5291 return INVALCOLOR;
Bram Moolenaar071d4272004-06-13 20:20:40 +00005292
Bram Moolenaar26af85d2017-07-23 16:45:10 +02005293 return gui_mch_get_rgb_color(
5294 (color & 0xff0000) >> 16,
5295 (color & 0xff00) >> 8,
5296 color & 0xff);
5297#endif
5298}
5299
5300/*
5301 * Return the Pixel value (color) for the given RGB values.
5302 * Return INVALCOLOR for error.
5303 */
5304 guicolor_T
5305gui_mch_get_rgb_color(int r, int g, int b)
5306{
5307#if GTK_CHECK_VERSION(3,0,0)
5308 return gui_get_rgb_color_cmn(r, g, b);
5309#else
5310 GdkColor gcolor;
5311 int ret;
5312
5313 gcolor.red = (guint16)(r / 255.0 * 65535 + 0.5);
5314 gcolor.green = (guint16)(g / 255.0 * 65535 + 0.5);
5315 gcolor.blue = (guint16)(b / 255.0 * 65535 + 0.5);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005316
Bram Moolenaar36edf062016-07-21 22:10:12 +02005317 ret = gdk_colormap_alloc_color(gtk_widget_get_colormap(gui.drawarea),
5318 &gcolor, FALSE, TRUE);
5319
5320 return ret != 0 ? (guicolor_T)gcolor.pixel : INVALCOLOR;
5321#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005322}
5323
5324/*
5325 * Set the current text foreground color.
5326 */
5327 void
5328gui_mch_set_fg_color(guicolor_T color)
5329{
Bram Moolenaar36edf062016-07-21 22:10:12 +02005330#if GTK_CHECK_VERSION(3,0,0)
5331 *gui.fgcolor = color_to_rgba(color);
5332#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005333 gui.fgcolor->pixel = (unsigned long)color;
Bram Moolenaar36edf062016-07-21 22:10:12 +02005334#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005335}
5336
5337/*
5338 * Set the current text background color.
5339 */
5340 void
5341gui_mch_set_bg_color(guicolor_T color)
5342{
Bram Moolenaar36edf062016-07-21 22:10:12 +02005343#if GTK_CHECK_VERSION(3,0,0)
5344 *gui.bgcolor = color_to_rgba(color);
5345#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005346 gui.bgcolor->pixel = (unsigned long)color;
Bram Moolenaar36edf062016-07-21 22:10:12 +02005347#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005348}
5349
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005350/*
5351 * Set the current text special color.
5352 */
5353 void
5354gui_mch_set_sp_color(guicolor_T color)
5355{
Bram Moolenaar36edf062016-07-21 22:10:12 +02005356#if GTK_CHECK_VERSION(3,0,0)
5357 *gui.spcolor = color_to_rgba(color);
5358#else
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005359 gui.spcolor->pixel = (unsigned long)color;
Bram Moolenaar36edf062016-07-21 22:10:12 +02005360#endif
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005361}
5362
Bram Moolenaar071d4272004-06-13 20:20:40 +00005363/*
5364 * Function-like convenience macro for the sake of efficiency.
5365 */
5366#define INSERT_PANGO_ATTR(Attribute, AttrList, Start, End) \
5367 G_STMT_START{ \
5368 PangoAttribute *tmp_attr_; \
5369 tmp_attr_ = (Attribute); \
5370 tmp_attr_->start_index = (Start); \
5371 tmp_attr_->end_index = (End); \
5372 pango_attr_list_insert((AttrList), tmp_attr_); \
5373 }G_STMT_END
5374
5375 static void
5376apply_wide_font_attr(char_u *s, int len, PangoAttrList *attr_list)
5377{
5378 char_u *start = NULL;
5379 char_u *p;
5380 int uc;
5381
5382 for (p = s; p < s + len; p += utf_byte2len(*p))
5383 {
5384 uc = utf_ptr2char(p);
5385
5386 if (start == NULL)
5387 {
5388 if (uc >= 0x80 && utf_char2cells(uc) == 2)
5389 start = p;
5390 }
Bram Moolenaar30613902019-12-01 22:11:18 +01005391 else if (uc < 0x80 // optimization shortcut
Bram Moolenaar071d4272004-06-13 20:20:40 +00005392 || (utf_char2cells(uc) != 2 && !utf_iscomposing(uc)))
5393 {
5394 INSERT_PANGO_ATTR(pango_attr_font_desc_new(gui.wide_font),
5395 attr_list, start - s, p - s);
5396 start = NULL;
5397 }
5398 }
5399
5400 if (start != NULL)
5401 INSERT_PANGO_ATTR(pango_attr_font_desc_new(gui.wide_font),
5402 attr_list, start - s, len);
5403}
5404
5405 static int
5406count_cluster_cells(char_u *s, PangoItem *item,
5407 PangoGlyphString* glyphs, int i,
5408 int *cluster_width,
5409 int *last_glyph_rbearing)
5410{
5411 char_u *p;
Bram Moolenaar30613902019-12-01 22:11:18 +01005412 int next; // glyph start index of next cluster
5413 int start, end; // string segment of current cluster
5414 int width; // real cluster width in Pango units
Bram Moolenaar071d4272004-06-13 20:20:40 +00005415 int uc;
5416 int cellcount = 0;
5417
5418 width = glyphs->glyphs[i].geometry.width;
5419
5420 for (next = i + 1; next < glyphs->num_glyphs; ++next)
5421 {
5422 if (glyphs->glyphs[next].attr.is_cluster_start)
5423 break;
5424 else if (glyphs->glyphs[next].geometry.width > width)
5425 width = glyphs->glyphs[next].geometry.width;
5426 }
5427
5428 start = item->offset + glyphs->log_clusters[i];
5429 end = item->offset + ((next < glyphs->num_glyphs) ?
5430 glyphs->log_clusters[next] : item->length);
5431
5432 for (p = s + start; p < s + end; p += utf_byte2len(*p))
5433 {
5434 uc = utf_ptr2char(p);
5435 if (uc < 0x80)
5436 ++cellcount;
5437 else if (!utf_iscomposing(uc))
5438 cellcount += utf_char2cells(uc);
5439 }
5440
5441 if (last_glyph_rbearing != NULL
5442 && cellcount > 0 && next == glyphs->num_glyphs)
5443 {
5444 PangoRectangle ink_rect;
5445 /*
5446 * If a certain combining mark had to be taken from a non-monospace
5447 * font, we have to compensate manually by adapting x_offset according
5448 * to the ink extents of the previous glyph.
5449 */
5450 pango_font_get_glyph_extents(item->analysis.font,
5451 glyphs->glyphs[i].glyph,
5452 &ink_rect, NULL);
5453
5454 if (PANGO_RBEARING(ink_rect) > 0)
5455 *last_glyph_rbearing = PANGO_RBEARING(ink_rect);
5456 }
5457
5458 if (cellcount > 0)
5459 *cluster_width = width;
5460
5461 return cellcount;
5462}
5463
5464/*
5465 * If there are only combining characters in the cluster, we cannot just
5466 * change the width of the previous glyph since there is none. Therefore
5467 * some guesswork is needed.
5468 *
5469 * If ink_rect.x is negative Pango apparently has taken care of the composing
5470 * by itself. Actually setting x_offset = 0 should be sufficient then, but due
5471 * to problems with composing from different fonts we still need to fine-tune
Bram Moolenaar64404472010-06-26 06:24:45 +02005472 * x_offset to avoid ugliness.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005473 *
5474 * If ink_rect.x is not negative, force overstriking by pointing x_offset to
5475 * the position of the previous glyph. Apparently this happens only with old
5476 * X fonts which don't provide the special combining information needed by
5477 * Pango.
5478 */
5479 static void
5480setup_zero_width_cluster(PangoItem *item, PangoGlyphInfo *glyph,
5481 int last_cellcount, int last_cluster_width,
5482 int last_glyph_rbearing)
5483{
5484 PangoRectangle ink_rect;
5485 PangoRectangle logical_rect;
5486 int width;
5487
5488 width = last_cellcount * gui.char_width * PANGO_SCALE;
5489 glyph->geometry.x_offset = -width + MAX(0, width - last_cluster_width) / 2;
5490 glyph->geometry.width = 0;
5491
5492 pango_font_get_glyph_extents(item->analysis.font,
5493 glyph->glyph,
5494 &ink_rect, &logical_rect);
5495 if (ink_rect.x < 0)
5496 {
5497 glyph->geometry.x_offset += last_glyph_rbearing;
5498 glyph->geometry.y_offset = logical_rect.height
5499 - (gui.char_height - p_linespace) * PANGO_SCALE;
5500 }
Bram Moolenaar706e84b2010-08-07 15:46:45 +02005501 else
Bram Moolenaar30613902019-12-01 22:11:18 +01005502 // If the accent width is smaller than the cluster width, position it
5503 // in the middle.
Bram Moolenaar706e84b2010-08-07 15:46:45 +02005504 glyph->geometry.x_offset = -width + MAX(0, width - ink_rect.width) / 2;
Bram Moolenaar071d4272004-06-13 20:20:40 +00005505}
5506
Bram Moolenaar98921892016-02-23 17:14:37 +01005507#if GTK_CHECK_VERSION(3,0,0)
5508 static void
5509draw_glyph_string(int row, int col, int num_cells, int flags,
5510 PangoFont *font, PangoGlyphString *glyphs,
5511 cairo_t *cr)
5512#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005513 static void
5514draw_glyph_string(int row, int col, int num_cells, int flags,
5515 PangoFont *font, PangoGlyphString *glyphs)
Bram Moolenaar98921892016-02-23 17:14:37 +01005516#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005517{
5518 if (!(flags & DRAW_TRANSP))
5519 {
Bram Moolenaar98921892016-02-23 17:14:37 +01005520#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +02005521 cairo_set_source_rgba(cr,
5522 gui.bgcolor->red, gui.bgcolor->green, gui.bgcolor->blue,
5523 gui.bgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01005524 cairo_rectangle(cr,
5525 FILL_X(col), FILL_Y(row),
5526 num_cells * gui.char_width, gui.char_height);
5527 cairo_fill(cr);
5528#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005529 gdk_gc_set_foreground(gui.text_gc, gui.bgcolor);
5530
5531 gdk_draw_rectangle(gui.drawarea->window,
5532 gui.text_gc,
5533 TRUE,
5534 FILL_X(col),
5535 FILL_Y(row),
5536 num_cells * gui.char_width,
5537 gui.char_height);
Bram Moolenaar98921892016-02-23 17:14:37 +01005538#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005539 }
5540
Bram Moolenaar98921892016-02-23 17:14:37 +01005541#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +02005542 cairo_set_source_rgba(cr,
5543 gui.fgcolor->red, gui.fgcolor->green, gui.fgcolor->blue,
5544 gui.fgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01005545 cairo_move_to(cr, TEXT_X(col), TEXT_Y(row));
5546 pango_cairo_show_glyph_string(cr, font, glyphs);
5547#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005548 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
5549
5550 gdk_draw_glyphs(gui.drawarea->window,
5551 gui.text_gc,
5552 font,
5553 TEXT_X(col),
5554 TEXT_Y(row),
5555 glyphs);
Bram Moolenaar98921892016-02-23 17:14:37 +01005556#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005557
Bram Moolenaar30613902019-12-01 22:11:18 +01005558 // redraw the contents with an offset of 1 to emulate bold
Bram Moolenaar071d4272004-06-13 20:20:40 +00005559 if ((flags & DRAW_BOLD) && !gui.font_can_bold)
Bram Moolenaar98921892016-02-23 17:14:37 +01005560#if GTK_CHECK_VERSION(3,0,0)
5561 {
Bram Moolenaar36edf062016-07-21 22:10:12 +02005562 cairo_set_source_rgba(cr,
5563 gui.fgcolor->red, gui.fgcolor->green, gui.fgcolor->blue,
5564 gui.fgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01005565 cairo_move_to(cr, TEXT_X(col) + 1, TEXT_Y(row));
5566 pango_cairo_show_glyph_string(cr, font, glyphs);
5567 }
5568#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005569 gdk_draw_glyphs(gui.drawarea->window,
5570 gui.text_gc,
5571 font,
5572 TEXT_X(col) + 1,
5573 TEXT_Y(row),
5574 glyphs);
Bram Moolenaar98921892016-02-23 17:14:37 +01005575#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005576}
5577
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005578/*
5579 * Draw underline and undercurl at the bottom of the character cell.
5580 */
Bram Moolenaar98921892016-02-23 17:14:37 +01005581#if GTK_CHECK_VERSION(3,0,0)
5582 static void
5583draw_under(int flags, int row, int col, int cells, cairo_t *cr)
5584#else
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005585 static void
5586draw_under(int flags, int row, int col, int cells)
Bram Moolenaar98921892016-02-23 17:14:37 +01005587#endif
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005588{
5589 int i;
5590 int offset;
Bram Moolenaarb85cb212009-05-17 14:24:23 +00005591 static const int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 };
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005592 int y = FILL_Y(row + 1) - 1;
5593
Bram Moolenaar30613902019-12-01 22:11:18 +01005594 // Undercurl: draw curl at the bottom of the character cell.
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005595 if (flags & DRAW_UNDERC)
5596 {
Bram Moolenaar98921892016-02-23 17:14:37 +01005597#if GTK_CHECK_VERSION(3,0,0)
5598 cairo_set_line_width(cr, 1.0);
5599 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
Bram Moolenaar36edf062016-07-21 22:10:12 +02005600 cairo_set_source_rgba(cr,
5601 gui.spcolor->red, gui.spcolor->green, gui.spcolor->blue,
5602 gui.spcolor->alpha);
Yamagi9cd93852021-11-20 20:42:29 +00005603 cairo_move_to(cr, FILL_X(col) + 1, y - 2 + 0.5);
5604 for (i = FILL_X(col) + 1; i < FILL_X(col + cells); ++i)
Bram Moolenaar98921892016-02-23 17:14:37 +01005605 {
5606 offset = val[i % 8];
5607 cairo_line_to(cr, i, y - offset + 0.5);
5608 }
5609 cairo_stroke(cr);
5610#else
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005611 gdk_gc_set_foreground(gui.text_gc, gui.spcolor);
5612 for (i = FILL_X(col); i < FILL_X(col + cells); ++i)
5613 {
5614 offset = val[i % 8];
5615 gdk_draw_point(gui.drawarea->window, gui.text_gc, i, y - offset);
5616 }
5617 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
Bram Moolenaar98921892016-02-23 17:14:37 +01005618#endif
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005619 }
5620
Bram Moolenaar30613902019-12-01 22:11:18 +01005621 // Draw a strikethrough line
Bram Moolenaarcf4b00c2017-09-02 18:33:56 +02005622 if (flags & DRAW_STRIKE)
5623 {
5624#if GTK_CHECK_VERSION(3,0,0)
5625 cairo_set_line_width(cr, 1.0);
5626 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
5627 cairo_set_source_rgba(cr,
5628 gui.spcolor->red, gui.spcolor->green, gui.spcolor->blue,
5629 gui.spcolor->alpha);
5630 cairo_move_to(cr, FILL_X(col), y + 1 - gui.char_height/2 + 0.5);
5631 cairo_line_to(cr, FILL_X(col + cells), y + 1 - gui.char_height/2 + 0.5);
5632 cairo_stroke(cr);
5633#else
5634 gdk_gc_set_foreground(gui.text_gc, gui.spcolor);
5635 gdk_draw_line(gui.drawarea->window, gui.text_gc,
5636 FILL_X(col), y + 1 - gui.char_height/2,
5637 FILL_X(col + cells), y + 1 - gui.char_height/2);
5638 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
5639#endif
5640 }
5641
Bram Moolenaar30613902019-12-01 22:11:18 +01005642 // Underline: draw a line at the bottom of the character cell.
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005643 if (flags & DRAW_UNDERL)
5644 {
Bram Moolenaar30613902019-12-01 22:11:18 +01005645 // When p_linespace is 0, overwrite the bottom row of pixels.
5646 // Otherwise put the line just below the character.
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005647 if (p_linespace > 1)
5648 y -= p_linespace - 1;
Bram Moolenaar98921892016-02-23 17:14:37 +01005649#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaarcf4b00c2017-09-02 18:33:56 +02005650 cairo_set_line_width(cr, 1.0);
5651 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
5652 cairo_set_source_rgba(cr,
5653 gui.fgcolor->red, gui.fgcolor->green, gui.fgcolor->blue,
5654 gui.fgcolor->alpha);
5655 cairo_move_to(cr, FILL_X(col), y + 0.5);
5656 cairo_line_to(cr, FILL_X(col + cells), y + 0.5);
5657 cairo_stroke(cr);
Bram Moolenaar98921892016-02-23 17:14:37 +01005658#else
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005659 gdk_draw_line(gui.drawarea->window, gui.text_gc,
5660 FILL_X(col), y,
5661 FILL_X(col + cells) - 1, y);
Bram Moolenaar98921892016-02-23 17:14:37 +01005662#endif
Bram Moolenaarf36d3692005-03-15 22:48:14 +00005663 }
5664}
5665
Bram Moolenaar071d4272004-06-13 20:20:40 +00005666 int
5667gui_gtk2_draw_string(int row, int col, char_u *s, int len, int flags)
5668{
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005669 char_u *conv_buf = NULL; // result of UTF-8 conversion
5670 char_u *new_conv_buf;
5671 int convlen;
5672 char_u *sp, *bp;
5673 int plen;
5674 int len_sum; // return value needs to add up since we are
5675 // printing substrings
5676 int byte_sum; // byte position in string
5677 char_u *cs; // current *s pointer
5678 int needs_pango; // look ahead, 0=ascii 1=unicode/ligatures
Bram Moolenaar9d4b8ca2021-10-17 11:33:47 +01005679 int should_need_pango = FALSE;
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005680 int slen;
5681 int is_ligature;
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005682 int is_utf8;
5683 char_u backup_ch;
Bram Moolenaar071d4272004-06-13 20:20:40 +00005684
Bram Moolenaar98921892016-02-23 17:14:37 +01005685 if (gui.text_context == NULL || gtk_widget_get_window(gui.drawarea) == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00005686 return len;
5687
5688 if (output_conv.vc_type != CONV_NONE)
5689 {
5690 /*
5691 * Convert characters from 'encoding' to 'termencoding', which is set
5692 * to UTF-8 by gui_mch_init(). did_set_string_option() in option.c
5693 * prohibits changing this to something else than UTF-8 if the GUI is
5694 * in use.
5695 */
5696 convlen = len;
5697 conv_buf = string_convert(&output_conv, s, &convlen);
5698 g_return_val_if_fail(conv_buf != NULL, len);
5699
Bram Moolenaar30613902019-12-01 22:11:18 +01005700 // Correct for differences in char width: some chars are
5701 // double-wide in 'encoding' but single-wide in utf-8. Add a space to
5702 // compensate for that.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005703 for (sp = s, bp = conv_buf; sp < s + len && bp < conv_buf + convlen; )
5704 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00005705 plen = utf_ptr2len(bp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005706 if ((*mb_ptr2cells)(sp) == 2 && utf_ptr2cells(bp) == 1)
5707 {
5708 new_conv_buf = alloc(convlen + 2);
5709 if (new_conv_buf == NULL)
5710 return len;
5711 plen += bp - conv_buf;
5712 mch_memmove(new_conv_buf, conv_buf, plen);
5713 new_conv_buf[plen] = ' ';
5714 mch_memmove(new_conv_buf + plen + 1, conv_buf + plen,
5715 convlen - plen + 1);
5716 vim_free(conv_buf);
5717 conv_buf = new_conv_buf;
5718 ++convlen;
5719 bp = conv_buf + plen;
5720 plen = 1;
5721 }
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00005722 sp += (*mb_ptr2len)(sp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00005723 bp += plen;
5724 }
5725 s = conv_buf;
5726 len = convlen;
5727 }
5728
5729 /*
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005730 * Ligature support and complex utf-8 char optimization:
5731 * String received to output to screen can print using pre-cached glyphs
5732 * (fast) or Pango (slow). Ligatures and multibype utf-8 must use Pango.
5733 * Since we receive mixed content string, split it into logical segments
5734 * that are guaranteed to go trough glyphs as much as possible. Since
5735 * single ligature char prints as ascii, print it that way.
5736 */
5737 len_sum = 0; // return value needs to add up since we are printing
5738 // substrings
5739 byte_sum = 0;
5740 cs = s;
Dusan Popovic3c19b502021-11-20 22:03:30 +00005741 // First char decides starting needs_pango mode, 0=ascii 1=utf8/ligatures.
5742 // Even if it is ligature char, two chars or more make ligature.
5743 // Ascii followed by utf8 is also going trough pango.
5744 is_utf8 = (*cs & 0x80);
5745 is_ligature = gui.ligatures_map[*cs] && (len > 1);
5746 if (is_ligature)
5747 is_ligature = gui.ligatures_map[*(cs + 1)];
5748 if (!is_utf8 && len > 1)
5749 is_utf8 = (*(cs + 1) & 0x80) != 0;
5750 needs_pango = is_utf8 || is_ligature;
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005751
5752 // split string into ascii and non-ascii (ligatures + utf-8) substrings,
5753 // print glyphs or use Pango
5754 while (cs < s + len)
5755 {
5756 slen = 0;
5757 while (slen < (len - byte_sum))
5758 {
5759 is_ligature = gui.ligatures_map[*(cs + slen)];
5760 // look ahead, single ligature char between ascii is ascii
5761 if (is_ligature && !needs_pango)
5762 {
5763 if ((slen + 1) < (len - byte_sum))
Dusan Popovic3c19b502021-11-20 22:03:30 +00005764 is_ligature = gui.ligatures_map[*(cs + slen + 1)];
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005765 else
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005766 is_ligature = 0;
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005767 }
5768 is_utf8 = *(cs + slen) & 0x80;
Dusan Popovic3c19b502021-11-20 22:03:30 +00005769 // ascii followed by utf8 could be combining
5770 // if so send it trough pango
5771 if ((!is_utf8) && ((slen + 1) < (len - byte_sum)))
5772 is_utf8 = (*(cs + slen + 1) & 0x80);
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005773 should_need_pango = (is_ligature || is_utf8);
5774 if (needs_pango != should_need_pango) // mode switch
5775 break;
5776 if (needs_pango)
5777 {
5778 if (is_ligature)
5779 {
5780 slen++; // ligature char by char
5781 }
Bram Moolenaar2c236702021-11-21 11:15:49 +00005782 else // is_utf8
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005783 {
5784 if ((*(cs + slen) & 0xC0) == 0x80)
5785 {
5786 // a continuation, find next 0xC0 != 0x80 but don't
5787 // include it
5788 while ((slen < (len - byte_sum))
5789 && ((*(cs + slen) & 0xC0) == 0x80))
5790 {
5791 slen++;
5792 }
5793 }
5794 else if ((*(cs + slen) & 0xE0) == 0xC0)
5795 {
5796 // + one byte utf8
5797 slen++;
5798 }
5799 else if ((*(cs + slen) & 0xF0) == 0xE0)
5800 {
5801 // + two bytes utf8
5802 slen += 2;
5803 }
5804 else if ((*(cs + slen) & 0xF8) == 0xF0)
5805 {
5806 // + three bytes utf8
5807 slen += 3;
5808 }
5809 else
5810 {
5811 // this should not happen, try moving forward, Pango
5812 // will catch it
5813 slen++;
5814 }
5815 }
5816 }
5817 else
5818 {
5819 slen++; // ascii
5820 }
5821 }
Bram Moolenaar9d4b8ca2021-10-17 11:33:47 +01005822
Bram Moolenaard68a0042021-10-20 23:08:11 +01005823 if (slen < len)
5824 {
5825 // temporarily zero terminate substring, print, restore char, wrap
5826 backup_ch = *(cs + slen);
5827 *(cs + slen) = NUL;
5828 }
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005829 len_sum += gui_gtk2_draw_string_ext(row, col + len_sum,
5830 cs, slen, flags, needs_pango);
Bram Moolenaard68a0042021-10-20 23:08:11 +01005831 if (slen < len)
5832 *(cs + slen) = backup_ch;
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005833 cs += slen;
5834 byte_sum += slen;
5835 needs_pango = should_need_pango;
5836 }
5837 vim_free(conv_buf);
5838 return len_sum;
5839}
5840
5841 int
5842gui_gtk2_draw_string_ext(
5843 int row,
5844 int col,
5845 char_u *s,
5846 int len,
5847 int flags,
5848 int force_pango)
5849{
5850 GdkRectangle area; // area for clip mask
5851 PangoGlyphString *glyphs; // glyphs of current item
5852 int column_offset = 0; // column offset in cells
5853 int i;
5854#if GTK_CHECK_VERSION(3,0,0)
5855 cairo_t *cr;
5856#endif
5857
5858 /*
Bram Moolenaar071d4272004-06-13 20:20:40 +00005859 * Restrict all drawing to the current screen line in order to prevent
5860 * fuzzy font lookups from messing up the screen.
5861 */
5862 area.x = gui.border_offset;
5863 area.y = FILL_Y(row);
5864 area.width = gui.num_cols * gui.char_width;
5865 area.height = gui.char_height;
5866
Bram Moolenaar98921892016-02-23 17:14:37 +01005867#if GTK_CHECK_VERSION(3,0,0)
5868 cr = cairo_create(gui.surface);
5869 cairo_rectangle(cr, area.x, area.y, area.width, area.height);
5870 cairo_clip(cr);
5871#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005872 gdk_gc_set_clip_origin(gui.text_gc, 0, 0);
5873 gdk_gc_set_clip_rectangle(gui.text_gc, &area);
Bram Moolenaar98921892016-02-23 17:14:37 +01005874#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005875
5876 glyphs = pango_glyph_string_new();
5877
5878 /*
5879 * Optimization hack: If possible, skip the itemize and shaping process
5880 * for pure ASCII strings. This optimization is particularly effective
5881 * because Vim draws space characters to clear parts of the screen.
5882 */
5883 if (!(flags & DRAW_ITALIC)
5884 && !((flags & DRAW_BOLD) && gui.font_can_bold)
Dusan Popovic4eeedc02021-10-16 20:52:05 +01005885 && gui.ascii_glyphs != NULL
5886 && !force_pango)
Bram Moolenaar071d4272004-06-13 20:20:40 +00005887 {
5888 char_u *p;
5889
5890 for (p = s; p < s + len; ++p)
5891 if (*p & 0x80)
5892 goto not_ascii;
5893
5894 pango_glyph_string_set_size(glyphs, len);
5895
5896 for (i = 0; i < len; ++i)
5897 {
Bram Moolenaar16350cb2016-08-14 20:27:34 +02005898 glyphs->glyphs[i] = gui.ascii_glyphs->glyphs[2 * s[i]];
Bram Moolenaar071d4272004-06-13 20:20:40 +00005899 glyphs->log_clusters[i] = i;
5900 }
5901
Bram Moolenaar98921892016-02-23 17:14:37 +01005902#if GTK_CHECK_VERSION(3,0,0)
5903 draw_glyph_string(row, col, len, flags, gui.ascii_font, glyphs, cr);
5904#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00005905 draw_glyph_string(row, col, len, flags, gui.ascii_font, glyphs);
Bram Moolenaar98921892016-02-23 17:14:37 +01005906#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005907
5908 column_offset = len;
5909 }
5910 else
5911not_ascii:
5912 {
5913 PangoAttrList *attr_list;
5914 GList *item_list;
5915 int cluster_width;
5916 int last_glyph_rbearing;
Bram Moolenaar30613902019-12-01 22:11:18 +01005917 int cells = 0; // cells occupied by current cluster
Bram Moolenaar071d4272004-06-13 20:20:40 +00005918
Bram Moolenaar30613902019-12-01 22:11:18 +01005919 // Safety check: pango crashes when invoked with invalid utf-8
5920 // characters.
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +00005921 if (!utf_valid_string(s, s + len))
5922 {
5923 column_offset = len;
5924 goto skipitall;
5925 }
5926
Bram Moolenaar30613902019-12-01 22:11:18 +01005927 // original width of the current cluster
Bram Moolenaar071d4272004-06-13 20:20:40 +00005928 cluster_width = PANGO_SCALE * gui.char_width;
5929
Bram Moolenaar30613902019-12-01 22:11:18 +01005930 // right bearing of the last non-composing glyph
Bram Moolenaar071d4272004-06-13 20:20:40 +00005931 last_glyph_rbearing = PANGO_SCALE * gui.char_width;
5932
5933 attr_list = pango_attr_list_new();
5934
Bram Moolenaar30613902019-12-01 22:11:18 +01005935 // If 'guifontwide' is set then use that for double-width characters.
5936 // Otherwise just go with 'guifont' and let Pango do its thing.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005937 if (gui.wide_font != NULL)
5938 apply_wide_font_attr(s, len, attr_list);
5939
5940 if ((flags & DRAW_BOLD) && gui.font_can_bold)
5941 INSERT_PANGO_ATTR(pango_attr_weight_new(PANGO_WEIGHT_BOLD),
5942 attr_list, 0, len);
5943 if (flags & DRAW_ITALIC)
5944 INSERT_PANGO_ATTR(pango_attr_style_new(PANGO_STYLE_ITALIC),
5945 attr_list, 0, len);
5946 /*
5947 * Break the text into segments with consistent directional level
5948 * and shaping engine. Pure Latin text needs only a single segment,
5949 * so there's no need to worry about the loop's efficiency. Better
5950 * try to optimize elsewhere, e.g. reducing exposes and stuff :)
5951 */
5952 item_list = pango_itemize(gui.text_context,
5953 (const char *)s, 0, len, attr_list, NULL);
5954
5955 while (item_list != NULL)
5956 {
5957 PangoItem *item;
Bram Moolenaar30613902019-12-01 22:11:18 +01005958 int item_cells = 0; // item length in cells
Bram Moolenaar071d4272004-06-13 20:20:40 +00005959
5960 item = (PangoItem *)item_list->data;
5961 item_list = g_list_delete_link(item_list, item_list);
5962 /*
5963 * Increment the bidirectional embedding level by 1 if it is not
5964 * even. An odd number means the output will be RTL, but we don't
5965 * want that since Vim handles right-to-left text on its own. It
5966 * would probably be sufficient to just set level = 0, but you can
5967 * never know :)
5968 *
5969 * Unfortunately we can't take advantage of Pango's ability to
5970 * render both LTR and RTL at the same time. In order to support
5971 * that, Vim's main screen engine would have to make use of Pango
5972 * functionality.
5973 */
5974 item->analysis.level = (item->analysis.level + 1) & (~1U);
5975
Bram Moolenaar30613902019-12-01 22:11:18 +01005976 // HACK: Overrule the shape engine, we don't want shaping to be
5977 // done, because drawing the cursor would change the display.
Bram Moolenaar071d4272004-06-13 20:20:40 +00005978 item->analysis.shape_engine = default_shape_engine;
5979
Bram Moolenaar3cbe0c02015-09-08 20:00:22 +02005980#ifdef HAVE_PANGO_SHAPE_FULL
Bram Moolenaar7e2ec002015-09-08 16:31:06 +02005981 pango_shape_full((const char *)s + item->offset, item->length,
5982 (const char *)s, len, &item->analysis, glyphs);
Bram Moolenaar3cbe0c02015-09-08 20:00:22 +02005983#else
5984 pango_shape((const char *)s + item->offset, item->length,
5985 &item->analysis, glyphs);
5986#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00005987 /*
5988 * Fixed-width hack: iterate over the array and assign a fixed
5989 * width to each glyph, thus overriding the choice made by the
5990 * shaping engine. We use utf_char2cells() to determine the
5991 * number of cells needed.
5992 *
5993 * Also perform all kind of dark magic to get composing
5994 * characters right (and pretty too of course).
5995 */
5996 for (i = 0; i < glyphs->num_glyphs; ++i)
5997 {
5998 PangoGlyphInfo *glyph;
5999
6000 glyph = &glyphs->glyphs[i];
6001
6002 if (glyph->attr.is_cluster_start)
6003 {
6004 int cellcount;
6005
6006 cellcount = count_cluster_cells(
6007 s, item, glyphs, i, &cluster_width,
6008 (item_list != NULL) ? &last_glyph_rbearing : NULL);
6009
6010 if (cellcount > 0)
6011 {
6012 int width;
6013
6014 width = cellcount * gui.char_width * PANGO_SCALE;
6015 glyph->geometry.x_offset +=
6016 MAX(0, width - cluster_width) / 2;
6017 glyph->geometry.width = width;
6018 }
6019 else
6020 {
Bram Moolenaar30613902019-12-01 22:11:18 +01006021 // If there are only combining characters in the
6022 // cluster, we cannot just change the width of the
6023 // previous glyph since there is none. Therefore
6024 // some guesswork is needed.
Bram Moolenaar071d4272004-06-13 20:20:40 +00006025 setup_zero_width_cluster(item, glyph, cells,
6026 cluster_width,
6027 last_glyph_rbearing);
6028 }
6029
6030 item_cells += cellcount;
6031 cells = cellcount;
6032 }
6033 else if (i > 0)
6034 {
6035 int width;
6036
Bram Moolenaar30613902019-12-01 22:11:18 +01006037 // There is a previous glyph, so we deal with combining
6038 // characters the canonical way.
6039 // In some circumstances Pango uses a positive x_offset,
6040 // then use the width of the previous glyph for this one
6041 // and set the previous width to zero.
6042 // Otherwise we get a negative x_offset, Pango has already
6043 // positioned the combining char, keep the widths as they
6044 // are.
6045 // For both adjust the x_offset to position the glyph in
6046 // the middle.
Bram Moolenaar706e84b2010-08-07 15:46:45 +02006047 if (glyph->geometry.x_offset >= 0)
Bram Moolenaar96118f32010-08-08 14:40:37 +02006048 {
6049 glyphs->glyphs[i].geometry.width =
6050 glyphs->glyphs[i - 1].geometry.width;
Bram Moolenaar706e84b2010-08-07 15:46:45 +02006051 glyphs->glyphs[i - 1].geometry.width = 0;
Bram Moolenaar96118f32010-08-08 14:40:37 +02006052 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00006053 width = cells * gui.char_width * PANGO_SCALE;
6054 glyph->geometry.x_offset +=
6055 MAX(0, width - cluster_width) / 2;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006056 }
Bram Moolenaar30613902019-12-01 22:11:18 +01006057 else // i == 0 "cannot happen"
Bram Moolenaar071d4272004-06-13 20:20:40 +00006058 {
6059 glyph->geometry.width = 0;
6060 }
6061 }
6062
Bram Moolenaar30613902019-12-01 22:11:18 +01006063 //// Aaaaand action! **
Bram Moolenaar98921892016-02-23 17:14:37 +01006064#if GTK_CHECK_VERSION(3,0,0)
6065 draw_glyph_string(row, col + column_offset, item_cells,
6066 flags, item->analysis.font, glyphs,
6067 cr);
6068#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006069 draw_glyph_string(row, col + column_offset, item_cells,
6070 flags, item->analysis.font, glyphs);
Bram Moolenaar98921892016-02-23 17:14:37 +01006071#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006072
6073 pango_item_free(item);
6074
6075 column_offset += item_cells;
6076 }
6077
6078 pango_attr_list_unref(attr_list);
6079 }
6080
Bram Moolenaar3fdfa4a2004-10-07 21:02:47 +00006081skipitall:
Bram Moolenaar30613902019-12-01 22:11:18 +01006082 // Draw underline and undercurl.
Bram Moolenaar98921892016-02-23 17:14:37 +01006083#if GTK_CHECK_VERSION(3,0,0)
6084 draw_under(flags, row, col, column_offset, cr);
6085#else
Bram Moolenaarf36d3692005-03-15 22:48:14 +00006086 draw_under(flags, row, col, column_offset);
Bram Moolenaar98921892016-02-23 17:14:37 +01006087#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006088
6089 pango_glyph_string_free(glyphs);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006090
Bram Moolenaar98921892016-02-23 17:14:37 +01006091#if GTK_CHECK_VERSION(3,0,0)
6092 cairo_destroy(cr);
presuku9459b8d2021-11-16 20:03:56 +00006093 gtk_widget_queue_draw_area(gui.drawarea, area.x, area.y,
6094 area.width, area.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006095#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006096 gdk_gc_set_clip_rectangle(gui.text_gc, NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01006097#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006098
6099 return column_offset;
6100}
Bram Moolenaar071d4272004-06-13 20:20:40 +00006101
6102/*
6103 * Return OK if the key with the termcap name "name" is supported.
6104 */
6105 int
6106gui_mch_haskey(char_u *name)
6107{
6108 int i;
6109
6110 for (i = 0; special_keys[i].key_sym != 0; i++)
6111 if (name[0] == special_keys[i].code0
6112 && name[1] == special_keys[i].code1)
6113 return OK;
6114 return FAIL;
6115}
6116
Bram Moolenaarb2c5a5a2013-02-14 22:11:39 +01006117#if defined(FEAT_TITLE) || defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006118/*
6119 * Return the text window-id and display. Only required for X-based GUI's
6120 */
6121 int
6122gui_get_x11_windis(Window *win, Display **dis)
6123{
Bram Moolenaar98921892016-02-23 17:14:37 +01006124 if (gui.mainwin != NULL && gtk_widget_get_window(gui.mainwin) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006125 {
Bram Moolenaar98921892016-02-23 17:14:37 +01006126 *dis = GDK_WINDOW_XDISPLAY(gtk_widget_get_window(gui.mainwin));
6127 *win = GDK_WINDOW_XID(gtk_widget_get_window(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00006128 return OK;
6129 }
6130
6131 *dis = NULL;
6132 *win = 0;
6133 return FAIL;
6134}
6135#endif
6136
6137#if defined(FEAT_CLIENTSERVER) \
6138 || (defined(FEAT_X11) && defined(FEAT_CLIPBOARD)) || defined(PROTO)
6139
6140 Display *
6141gui_mch_get_display(void)
6142{
Bram Moolenaar98921892016-02-23 17:14:37 +01006143 if (gui.mainwin != NULL && gtk_widget_get_window(gui.mainwin) != NULL)
6144 return GDK_WINDOW_XDISPLAY(gtk_widget_get_window(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00006145 else
6146 return NULL;
6147}
6148#endif
6149
6150 void
6151gui_mch_beep(void)
6152{
Bram Moolenaar071d4272004-06-13 20:20:40 +00006153 GdkDisplay *display;
6154
Bram Moolenaar98921892016-02-23 17:14:37 +01006155 if (gui.mainwin != NULL && gtk_widget_get_realized(gui.mainwin))
Bram Moolenaar071d4272004-06-13 20:20:40 +00006156 display = gtk_widget_get_display(gui.mainwin);
6157 else
6158 display = gdk_display_get_default();
6159
6160 if (display != NULL)
6161 gdk_display_beep(display);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006162}
6163
6164 void
6165gui_mch_flash(int msec)
6166{
Bram Moolenaar98921892016-02-23 17:14:37 +01006167#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar30613902019-12-01 22:11:18 +01006168 // TODO Replace GdkGC with Cairo
Bram Moolenaar98921892016-02-23 17:14:37 +01006169 (void)msec;
6170#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006171 GdkGCValues values;
6172 GdkGC *invert_gc;
6173
6174 if (gui.drawarea->window == NULL)
6175 return;
6176
6177 values.foreground.pixel = gui.norm_pixel ^ gui.back_pixel;
6178 values.background.pixel = gui.norm_pixel ^ gui.back_pixel;
6179 values.function = GDK_XOR;
6180 invert_gc = gdk_gc_new_with_values(gui.drawarea->window,
6181 &values,
6182 GDK_GC_FOREGROUND |
6183 GDK_GC_BACKGROUND |
6184 GDK_GC_FUNCTION);
6185 gdk_gc_set_exposures(invert_gc,
6186 gui.visibility != GDK_VISIBILITY_UNOBSCURED);
6187 /*
6188 * Do a visual beep by changing back and forth in some undetermined way,
6189 * the foreground and background colors. This is due to the fact that
6190 * there can't be really any prediction about the effects of XOR on
6191 * arbitrary X11 servers. However this seems to be enough for what we
6192 * intend it to do.
6193 */
6194 gdk_draw_rectangle(gui.drawarea->window, invert_gc,
6195 TRUE,
6196 0, 0,
6197 FILL_X((int)Columns) + gui.border_offset,
6198 FILL_Y((int)Rows) + gui.border_offset);
6199
6200 gui_mch_flush();
Bram Moolenaar30613902019-12-01 22:11:18 +01006201 ui_delay((long)msec, TRUE); // wait so many msec
Bram Moolenaar071d4272004-06-13 20:20:40 +00006202
6203 gdk_draw_rectangle(gui.drawarea->window, invert_gc,
6204 TRUE,
6205 0, 0,
6206 FILL_X((int)Columns) + gui.border_offset,
6207 FILL_Y((int)Rows) + gui.border_offset);
6208
6209 gdk_gc_destroy(invert_gc);
Bram Moolenaar98921892016-02-23 17:14:37 +01006210#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006211}
6212
6213/*
6214 * Invert a rectangle from row r, column c, for nr rows and nc columns.
6215 */
6216 void
6217gui_mch_invert_rectangle(int r, int c, int nr, int nc)
6218{
Bram Moolenaar98921892016-02-23 17:14:37 +01006219#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar4fc563b2016-03-12 12:40:58 +01006220 const GdkRectangle rect = {
6221 FILL_X(c), FILL_Y(r), nc * gui.char_width, nr * gui.char_height
6222 };
6223 cairo_t * const cr = cairo_create(gui.surface);
6224
Bram Moolenaar36edf062016-07-21 22:10:12 +02006225 set_cairo_source_rgba_from_color(cr, gui.norm_pixel ^ gui.back_pixel);
Bram Moolenaar4fc563b2016-03-12 12:40:58 +01006226# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,9,2)
6227 cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
6228# else
Bram Moolenaar30613902019-12-01 22:11:18 +01006229 // Give an implementation for older cairo versions if necessary.
Bram Moolenaar4fc563b2016-03-12 12:40:58 +01006230# endif
presuku9459b8d2021-11-16 20:03:56 +00006231 cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
Bram Moolenaar4fc563b2016-03-12 12:40:58 +01006232 cairo_fill(cr);
6233
6234 cairo_destroy(cr);
6235
presuku9459b8d2021-11-16 20:03:56 +00006236 gtk_widget_queue_draw_area(gui.drawarea, rect.x, rect.y,
6237 rect.width, rect.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006238#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006239 GdkGCValues values;
6240 GdkGC *invert_gc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006241
6242 if (gui.drawarea->window == NULL)
6243 return;
6244
Bram Moolenaar89d40322006-08-29 15:30:07 +00006245 values.foreground.pixel = gui.norm_pixel ^ gui.back_pixel;
6246 values.background.pixel = gui.norm_pixel ^ gui.back_pixel;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006247 values.function = GDK_XOR;
6248 invert_gc = gdk_gc_new_with_values(gui.drawarea->window,
6249 &values,
6250 GDK_GC_FOREGROUND |
6251 GDK_GC_BACKGROUND |
6252 GDK_GC_FUNCTION);
Bram Moolenaar89d40322006-08-29 15:30:07 +00006253 gdk_gc_set_exposures(invert_gc, gui.visibility !=
6254 GDK_VISIBILITY_UNOBSCURED);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006255 gdk_draw_rectangle(gui.drawarea->window, invert_gc,
6256 TRUE,
6257 FILL_X(c), FILL_Y(r),
6258 (nc) * gui.char_width, (nr) * gui.char_height);
6259 gdk_gc_destroy(invert_gc);
Bram Moolenaar98921892016-02-23 17:14:37 +01006260#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006261}
6262
6263/*
6264 * Iconify the GUI window.
6265 */
6266 void
6267gui_mch_iconify(void)
6268{
Bram Moolenaar071d4272004-06-13 20:20:40 +00006269 gtk_window_iconify(GTK_WINDOW(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00006270}
6271
6272#if defined(FEAT_EVAL) || defined(PROTO)
6273/*
6274 * Bring the Vim window to the foreground.
6275 */
6276 void
6277gui_mch_set_foreground(void)
6278{
Bram Moolenaar071d4272004-06-13 20:20:40 +00006279 gtk_window_present(GTK_WINDOW(gui.mainwin));
Bram Moolenaar071d4272004-06-13 20:20:40 +00006280}
6281#endif
6282
6283/*
6284 * Draw a cursor without focus.
6285 */
6286 void
6287gui_mch_draw_hollow_cursor(guicolor_T color)
6288{
6289 int i = 1;
Bram Moolenaar98921892016-02-23 17:14:37 +01006290#if GTK_CHECK_VERSION(3,0,0)
6291 cairo_t *cr;
6292#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006293
Bram Moolenaar98921892016-02-23 17:14:37 +01006294 if (gtk_widget_get_window(gui.drawarea) == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006295 return;
6296
Bram Moolenaar98921892016-02-23 17:14:37 +01006297#if GTK_CHECK_VERSION(3,0,0)
6298 cr = cairo_create(gui.surface);
6299#endif
6300
Bram Moolenaar071d4272004-06-13 20:20:40 +00006301 gui_mch_set_fg_color(color);
6302
Bram Moolenaar98921892016-02-23 17:14:37 +01006303#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +02006304 cairo_set_source_rgba(cr,
6305 gui.fgcolor->red, gui.fgcolor->green, gui.fgcolor->blue,
6306 gui.fgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01006307#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006308 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
Bram Moolenaar98921892016-02-23 17:14:37 +01006309#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006310 if (mb_lefthalve(gui.row, gui.col))
6311 i = 2;
Bram Moolenaar98921892016-02-23 17:14:37 +01006312#if GTK_CHECK_VERSION(3,0,0)
6313 cairo_set_line_width(cr, 1.0);
6314 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
6315 cairo_rectangle(cr,
6316 FILL_X(gui.col) + 0.5, FILL_Y(gui.row) + 0.5,
6317 i * gui.char_width - 1, gui.char_height - 1);
6318 cairo_stroke(cr);
6319 cairo_destroy(cr);
6320#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006321 gdk_draw_rectangle(gui.drawarea->window, gui.text_gc,
6322 FALSE,
6323 FILL_X(gui.col), FILL_Y(gui.row),
6324 i * gui.char_width - 1, gui.char_height - 1);
Bram Moolenaar98921892016-02-23 17:14:37 +01006325#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006326}
6327
6328/*
6329 * Draw part of a cursor, "w" pixels wide, and "h" pixels high, using
6330 * color "color".
6331 */
6332 void
6333gui_mch_draw_part_cursor(int w, int h, guicolor_T color)
6334{
Bram Moolenaar98921892016-02-23 17:14:37 +01006335 if (gtk_widget_get_window(gui.drawarea) == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006336 return;
6337
6338 gui_mch_set_fg_color(color);
6339
Bram Moolenaar98921892016-02-23 17:14:37 +01006340#if GTK_CHECK_VERSION(3,0,0)
6341 {
6342 cairo_t *cr;
6343
6344 cr = cairo_create(gui.surface);
Bram Moolenaar36edf062016-07-21 22:10:12 +02006345 cairo_set_source_rgba(cr,
6346 gui.fgcolor->red, gui.fgcolor->green, gui.fgcolor->blue,
6347 gui.fgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01006348 cairo_rectangle(cr,
6349# ifdef FEAT_RIGHTLEFT
Bram Moolenaar30613902019-12-01 22:11:18 +01006350 // vertical line should be on the right of current point
Bram Moolenaar98921892016-02-23 17:14:37 +01006351 CURSOR_BAR_RIGHT ? FILL_X(gui.col + 1) - w :
6352# endif
6353 FILL_X(gui.col), FILL_Y(gui.row) + gui.char_height - h,
6354 w, h);
6355 cairo_fill(cr);
6356 cairo_destroy(cr);
6357 }
Bram Moolenaar30613902019-12-01 22:11:18 +01006358#else // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006359 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
6360 gdk_draw_rectangle(gui.drawarea->window, gui.text_gc,
6361 TRUE,
Bram Moolenaar98921892016-02-23 17:14:37 +01006362# ifdef FEAT_RIGHTLEFT
Bram Moolenaar30613902019-12-01 22:11:18 +01006363 // vertical line should be on the right of current point
Bram Moolenaar071d4272004-06-13 20:20:40 +00006364 CURSOR_BAR_RIGHT ? FILL_X(gui.col + 1) - w :
Bram Moolenaar98921892016-02-23 17:14:37 +01006365# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006366 FILL_X(gui.col),
6367 FILL_Y(gui.row) + gui.char_height - h,
6368 w, h);
Bram Moolenaar30613902019-12-01 22:11:18 +01006369#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006370}
6371
6372
6373/*
6374 * Catch up with any queued X11 events. This may put keyboard input into the
6375 * input buffer, call resize call-backs, trigger timers etc. If there is
6376 * nothing in the X11 event queue (& no timers pending), then we return
6377 * immediately.
6378 */
6379 void
6380gui_mch_update(void)
6381{
Bram Moolenaara3f41662010-07-11 19:01:06 +02006382 while (g_main_context_pending(NULL) && !vim_is_input_buf_full())
6383 g_main_context_iteration(NULL, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006384}
6385
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006386 static timeout_cb_type
Bram Moolenaar071d4272004-06-13 20:20:40 +00006387input_timer_cb(gpointer data)
6388{
6389 int *timed_out = (int *) data;
6390
Bram Moolenaar30613902019-12-01 22:11:18 +01006391 // Just inform the caller about the occurrence of it
Bram Moolenaar071d4272004-06-13 20:20:40 +00006392 *timed_out = TRUE;
6393
Bram Moolenaar30613902019-12-01 22:11:18 +01006394 return FALSE; // don't happen again
Bram Moolenaar071d4272004-06-13 20:20:40 +00006395}
6396
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006397#ifdef FEAT_JOB_CHANNEL
6398 static timeout_cb_type
6399channel_poll_cb(gpointer data UNUSED)
6400{
Bram Moolenaar30613902019-12-01 22:11:18 +01006401 // Using an event handler for a channel that may be disconnected does
6402 // not work, it hangs. Instead poll for messages.
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006403 channel_handle_events(TRUE);
6404 parse_queued_messages();
6405
Bram Moolenaar30613902019-12-01 22:11:18 +01006406 return TRUE; // repeat
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006407}
6408#endif
6409
Bram Moolenaar071d4272004-06-13 20:20:40 +00006410/*
6411 * GUI input routine called by gui_wait_for_chars(). Waits for a character
6412 * from the keyboard.
6413 * wtime == -1 Wait forever.
6414 * wtime == 0 This should never happen.
6415 * wtime > 0 Wait wtime milliseconds for a character.
6416 * Returns OK if a character was found to be available within the given time,
6417 * or FAIL otherwise.
6418 */
6419 int
6420gui_mch_wait_for_chars(long wtime)
6421{
Bram Moolenaar4231da42016-06-02 14:30:04 +02006422 int focus;
6423 guint timer;
6424 static int timed_out;
6425 int retval = FAIL;
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006426#ifdef FEAT_JOB_CHANNEL
6427 guint channel_timer = 0;
6428#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006429
6430 timed_out = FALSE;
6431
Bram Moolenaar12dfc9e2019-01-28 22:32:58 +01006432 // This timeout makes sure that we will return if no characters arrived in
6433 // time. If "wtime" is zero just use one.
6434 if (wtime >= 0)
Bram Moolenaar34a58742019-02-03 15:28:28 +01006435 timer = timeout_add(wtime == 0 ? 1L : wtime,
Bram Moolenaar12dfc9e2019-01-28 22:32:58 +01006436 input_timer_cb, &timed_out);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006437 else
6438 timer = 0;
6439
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006440#ifdef FEAT_JOB_CHANNEL
Bram Moolenaar30613902019-12-01 22:11:18 +01006441 // If there is a channel with the keep_open flag we need to poll for input
6442 // on them.
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006443 if (channel_any_keep_open())
6444 channel_timer = timeout_add(20, channel_poll_cb, NULL);
6445#endif
6446
Bram Moolenaar071d4272004-06-13 20:20:40 +00006447 focus = gui.in_focus;
6448
6449 do
6450 {
Bram Moolenaar30613902019-12-01 22:11:18 +01006451 // Stop or start blinking when focus changes
Bram Moolenaar071d4272004-06-13 20:20:40 +00006452 if (gui.in_focus != focus)
6453 {
6454 if (gui.in_focus)
6455 gui_mch_start_blink();
6456 else
Bram Moolenaar1dd45fb2018-01-31 21:10:01 +01006457 gui_mch_stop_blink(TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006458 focus = gui.in_focus;
6459 }
6460
Bram Moolenaar93c88e02015-09-15 14:12:05 +02006461#ifdef MESSAGE_QUEUE
Bram Moolenaar4231da42016-06-02 14:30:04 +02006462# ifdef FEAT_TIMERS
6463 did_add_timer = FALSE;
6464# endif
Bram Moolenaar93c88e02015-09-15 14:12:05 +02006465 parse_queued_messages();
Bram Moolenaar4231da42016-06-02 14:30:04 +02006466# ifdef FEAT_TIMERS
6467 if (did_add_timer)
Bram Moolenaar30613902019-12-01 22:11:18 +01006468 // Need to recompute the waiting time.
Bram Moolenaar4231da42016-06-02 14:30:04 +02006469 goto theend;
6470# endif
Bram Moolenaar8c8de832008-06-24 22:58:06 +00006471#endif
6472
Bram Moolenaar071d4272004-06-13 20:20:40 +00006473 /*
6474 * Loop in GTK+ processing until a timeout or input occurs.
Bram Moolenaard4755bb2004-09-02 19:12:26 +00006475 * Skip this if input is available anyway (can happen in rare
6476 * situations, sort of race condition).
Bram Moolenaar071d4272004-06-13 20:20:40 +00006477 */
Bram Moolenaard4755bb2004-09-02 19:12:26 +00006478 if (!input_available())
Bram Moolenaara3f41662010-07-11 19:01:06 +02006479 g_main_context_iteration(NULL, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006480
Bram Moolenaar30613902019-12-01 22:11:18 +01006481 // Got char, return immediately
Bram Moolenaar071d4272004-06-13 20:20:40 +00006482 if (input_available())
6483 {
Bram Moolenaar4231da42016-06-02 14:30:04 +02006484 retval = OK;
6485 goto theend;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006486 }
6487 } while (wtime < 0 || !timed_out);
6488
6489 /*
6490 * Flush all eventually pending (drawing) events.
6491 */
6492 gui_mch_update();
6493
Bram Moolenaar4231da42016-06-02 14:30:04 +02006494theend:
6495 if (timer != 0 && !timed_out)
Bram Moolenaar4ab79682017-08-27 14:50:47 +02006496 timeout_remove(timer);
6497#ifdef FEAT_JOB_CHANNEL
6498 if (channel_timer != 0)
6499 timeout_remove(channel_timer);
Bram Moolenaar4231da42016-06-02 14:30:04 +02006500#endif
6501
6502 return retval;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006503}
6504
6505
Bram Moolenaar30613902019-12-01 22:11:18 +01006506/////////////////////////////////////////////////////////////////////////////
6507// Output drawing routines.
6508//
Bram Moolenaar071d4272004-06-13 20:20:40 +00006509
6510
Bram Moolenaar30613902019-12-01 22:11:18 +01006511// Flush any output to the screen
Bram Moolenaar071d4272004-06-13 20:20:40 +00006512 void
6513gui_mch_flush(void)
6514{
Bram Moolenaar98921892016-02-23 17:14:37 +01006515 if (gui.mainwin != NULL && gtk_widget_get_realized(gui.mainwin))
Bram Moolenaar92cbf622018-09-19 22:40:03 +02006516#if GTK_CHECK_VERSION(2,4,0)
Bram Moolenaarb463e8d2017-06-05 15:07:09 +02006517 gdk_display_flush(gtk_widget_get_display(gui.mainwin));
Bram Moolenaar92cbf622018-09-19 22:40:03 +02006518#else
6519 gdk_display_sync(gtk_widget_get_display(gui.mainwin));
6520#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006521}
6522
6523/*
6524 * Clear a rectangular region of the screen from text pos (row1, col1) to
6525 * (row2, col2) inclusive.
6526 */
6527 void
Bram Moolenaarfe344a92017-02-23 12:20:35 +01006528gui_mch_clear_block(int row1arg, int col1arg, int row2arg, int col2arg)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006529{
Bram Moolenaarfe344a92017-02-23 12:20:35 +01006530
6531 int col1 = check_col(col1arg);
6532 int col2 = check_col(col2arg);
6533 int row1 = check_row(row1arg);
6534 int row2 = check_row(row2arg);
6535
Bram Moolenaar98921892016-02-23 17:14:37 +01006536#if GTK_CHECK_VERSION(3,0,0)
6537 if (gtk_widget_get_window(gui.drawarea) == NULL)
6538 return;
6539#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006540 GdkColor color;
6541
6542 if (gui.drawarea->window == NULL)
6543 return;
6544
6545 color.pixel = gui.back_pixel;
Bram Moolenaar98921892016-02-23 17:14:37 +01006546#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006547
Bram Moolenaar98921892016-02-23 17:14:37 +01006548#if GTK_CHECK_VERSION(3,0,0)
6549 {
Bram Moolenaar30613902019-12-01 22:11:18 +01006550 // Add one pixel to the far right column in case a double-stroked
6551 // bold glyph may sit there.
Bram Moolenaar98921892016-02-23 17:14:37 +01006552 const GdkRectangle rect = {
6553 FILL_X(col1), FILL_Y(row1),
6554 (col2 - col1 + 1) * gui.char_width + (col2 == Columns - 1),
6555 (row2 - row1 + 1) * gui.char_height
6556 };
Bram Moolenaar98921892016-02-23 17:14:37 +01006557 cairo_t * const cr = cairo_create(gui.surface);
Bram Moolenaara859f042016-11-17 19:11:55 +01006558# if GTK_CHECK_VERSION(3,22,2)
6559 set_cairo_source_rgba_from_color(cr, gui.back_pixel);
6560# else
presuku9459b8d2021-11-16 20:03:56 +00006561 GdkWindow * const win = gtk_widget_get_window(gui.drawarea);
Bram Moolenaar98921892016-02-23 17:14:37 +01006562 cairo_pattern_t * const pat = gdk_window_get_background_pattern(win);
6563 if (pat != NULL)
6564 cairo_set_source(cr, pat);
6565 else
Bram Moolenaar36edf062016-07-21 22:10:12 +02006566 set_cairo_source_rgba_from_color(cr, gui.back_pixel);
Bram Moolenaara859f042016-11-17 19:11:55 +01006567# endif
presuku9459b8d2021-11-16 20:03:56 +00006568 cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006569 cairo_fill(cr);
6570 cairo_destroy(cr);
6571
presuku9459b8d2021-11-16 20:03:56 +00006572 gtk_widget_queue_draw_area(gui.drawarea,
6573 rect.x, rect.y, rect.width, rect.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006574 }
Bram Moolenaar30613902019-12-01 22:11:18 +01006575#else // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006576 gdk_gc_set_foreground(gui.text_gc, &color);
6577
Bram Moolenaar30613902019-12-01 22:11:18 +01006578 // Clear one extra pixel at the far right, for when bold characters have
6579 // spilled over to the window border.
Bram Moolenaar071d4272004-06-13 20:20:40 +00006580 gdk_draw_rectangle(gui.drawarea->window, gui.text_gc, TRUE,
6581 FILL_X(col1), FILL_Y(row1),
6582 (col2 - col1 + 1) * gui.char_width
6583 + (col2 == Columns - 1),
6584 (row2 - row1 + 1) * gui.char_height);
Bram Moolenaar30613902019-12-01 22:11:18 +01006585#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006586}
6587
Bram Moolenaar98921892016-02-23 17:14:37 +01006588#if GTK_CHECK_VERSION(3,0,0)
6589 static void
6590gui_gtk_window_clear(GdkWindow *win)
6591{
6592 const GdkRectangle rect = {
6593 0, 0, gdk_window_get_width(win), gdk_window_get_height(win)
6594 };
6595 cairo_t * const cr = cairo_create(gui.surface);
Bram Moolenaara859f042016-11-17 19:11:55 +01006596# if GTK_CHECK_VERSION(3,22,2)
6597 set_cairo_source_rgba_from_color(cr, gui.back_pixel);
6598# else
Bram Moolenaar98921892016-02-23 17:14:37 +01006599 cairo_pattern_t * const pat = gdk_window_get_background_pattern(win);
6600 if (pat != NULL)
6601 cairo_set_source(cr, pat);
6602 else
Bram Moolenaar36edf062016-07-21 22:10:12 +02006603 set_cairo_source_rgba_from_color(cr, gui.back_pixel);
Bram Moolenaara859f042016-11-17 19:11:55 +01006604# endif
presuku9459b8d2021-11-16 20:03:56 +00006605 cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006606 cairo_fill(cr);
6607 cairo_destroy(cr);
6608
presuku9459b8d2021-11-16 20:03:56 +00006609 gtk_widget_queue_draw_area(gui.drawarea,
6610 rect.x, rect.y, rect.width, rect.height);
Bram Moolenaar98921892016-02-23 17:14:37 +01006611}
Bram Moolenaar25328e32018-09-11 21:30:09 +02006612#else
6613# define gui_gtk_window_clear(win) gdk_window_clear(win)
Bram Moolenaar98921892016-02-23 17:14:37 +01006614#endif
6615
Bram Moolenaar071d4272004-06-13 20:20:40 +00006616 void
6617gui_mch_clear_all(void)
6618{
Bram Moolenaar98921892016-02-23 17:14:37 +01006619 if (gtk_widget_get_window(gui.drawarea) != NULL)
6620 gui_gtk_window_clear(gtk_widget_get_window(gui.drawarea));
Bram Moolenaar071d4272004-06-13 20:20:40 +00006621}
6622
Bram Moolenaar98921892016-02-23 17:14:37 +01006623#if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006624/*
6625 * Redraw any text revealed by scrolling up/down.
6626 */
6627 static void
6628check_copy_area(void)
6629{
6630 GdkEvent *event;
6631 int expose_count;
6632
6633 if (gui.visibility != GDK_VISIBILITY_PARTIAL)
6634 return;
6635
Bram Moolenaar30613902019-12-01 22:11:18 +01006636 // Avoid redrawing the cursor while scrolling or it'll end up where
6637 // we don't want it to be. I'm not sure if it's correct to call
6638 // gui_dont_update_cursor() at this point but it works as a quick
6639 // fix for now.
Bram Moolenaar107abd22016-08-12 14:08:25 +02006640 gui_dont_update_cursor(TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006641
6642 do
6643 {
Bram Moolenaar30613902019-12-01 22:11:18 +01006644 // Wait to check whether the scroll worked or not.
Bram Moolenaar071d4272004-06-13 20:20:40 +00006645 event = gdk_event_get_graphics_expose(gui.drawarea->window);
6646
6647 if (event == NULL)
Bram Moolenaar30613902019-12-01 22:11:18 +01006648 break; // received NoExpose event
Bram Moolenaar071d4272004-06-13 20:20:40 +00006649
6650 gui_redraw(event->expose.area.x, event->expose.area.y,
6651 event->expose.area.width, event->expose.area.height);
6652
6653 expose_count = event->expose.count;
6654 gdk_event_free(event);
6655 }
Bram Moolenaar30613902019-12-01 22:11:18 +01006656 while (expose_count > 0); // more events follow
Bram Moolenaar071d4272004-06-13 20:20:40 +00006657
6658 gui_can_update_cursor();
6659}
Bram Moolenaar30613902019-12-01 22:11:18 +01006660#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar98921892016-02-23 17:14:37 +01006661
6662#if GTK_CHECK_VERSION(3,0,0)
6663 static void
6664gui_gtk_surface_copy_rect(int dest_x, int dest_y,
6665 int src_x, int src_y,
6666 int width, int height)
6667{
6668 cairo_t * const cr = cairo_create(gui.surface);
6669
6670 cairo_rectangle(cr, dest_x, dest_y, width, height);
6671 cairo_clip(cr);
6672 cairo_push_group(cr);
6673 cairo_set_source_surface(cr, gui.surface, dest_x - src_x, dest_y - src_y);
6674 cairo_paint(cr);
6675 cairo_pop_group_to_source(cr);
6676 cairo_paint(cr);
6677
6678 cairo_destroy(cr);
6679}
6680#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006681
6682/*
6683 * Delete the given number of lines from the given row, scrolling up any
6684 * text further down within the scroll region.
6685 */
6686 void
6687gui_mch_delete_lines(int row, int num_lines)
6688{
Bram Moolenaar98921892016-02-23 17:14:37 +01006689#if GTK_CHECK_VERSION(3,0,0)
6690 const int ncols = gui.scroll_region_right - gui.scroll_region_left + 1;
6691 const int nrows = gui.scroll_region_bot - row + 1;
6692 const int src_nrows = nrows - num_lines;
6693
6694 gui_gtk_surface_copy_rect(
6695 FILL_X(gui.scroll_region_left), FILL_Y(row),
6696 FILL_X(gui.scroll_region_left), FILL_Y(row + num_lines),
6697 gui.char_width * ncols + 1, gui.char_height * src_nrows);
6698 gui_clear_block(
6699 gui.scroll_region_bot - num_lines + 1, gui.scroll_region_left,
6700 gui.scroll_region_bot, gui.scroll_region_right);
presuku9459b8d2021-11-16 20:03:56 +00006701 gtk_widget_queue_draw_area(gui.drawarea,
Bram Moolenaar98921892016-02-23 17:14:37 +01006702 FILL_X(gui.scroll_region_left), FILL_Y(row),
presuku9459b8d2021-11-16 20:03:56 +00006703 gui.char_width * ncols + 1, gui.char_height * nrows);
Bram Moolenaar98921892016-02-23 17:14:37 +01006704#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006705 if (gui.visibility == GDK_VISIBILITY_FULLY_OBSCURED)
Bram Moolenaar30613902019-12-01 22:11:18 +01006706 return; // Can't see the window
Bram Moolenaar071d4272004-06-13 20:20:40 +00006707
6708 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
6709 gdk_gc_set_background(gui.text_gc, gui.bgcolor);
6710
Bram Moolenaar30613902019-12-01 22:11:18 +01006711 // copy one extra pixel, for when bold has spilled over
Bram Moolenaar071d4272004-06-13 20:20:40 +00006712 gdk_window_copy_area(gui.drawarea->window, gui.text_gc,
6713 FILL_X(gui.scroll_region_left), FILL_Y(row),
6714 gui.drawarea->window,
6715 FILL_X(gui.scroll_region_left),
6716 FILL_Y(row + num_lines),
6717 gui.char_width * (gui.scroll_region_right
6718 - gui.scroll_region_left + 1) + 1,
6719 gui.char_height * (gui.scroll_region_bot - row - num_lines + 1));
6720
6721 gui_clear_block(gui.scroll_region_bot - num_lines + 1,
6722 gui.scroll_region_left,
6723 gui.scroll_region_bot, gui.scroll_region_right);
6724 check_copy_area();
Bram Moolenaar30613902019-12-01 22:11:18 +01006725#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006726}
6727
6728/*
6729 * Insert the given number of lines before the given row, scrolling down any
6730 * following text within the scroll region.
6731 */
6732 void
6733gui_mch_insert_lines(int row, int num_lines)
6734{
Bram Moolenaar98921892016-02-23 17:14:37 +01006735#if GTK_CHECK_VERSION(3,0,0)
6736 const int ncols = gui.scroll_region_right - gui.scroll_region_left + 1;
6737 const int nrows = gui.scroll_region_bot - row + 1;
6738 const int src_nrows = nrows - num_lines;
6739
6740 gui_gtk_surface_copy_rect(
6741 FILL_X(gui.scroll_region_left), FILL_Y(row + num_lines),
6742 FILL_X(gui.scroll_region_left), FILL_Y(row),
6743 gui.char_width * ncols + 1, gui.char_height * src_nrows);
presuku9459b8d2021-11-16 20:03:56 +00006744 gui_clear_block(
Bram Moolenaar98921892016-02-23 17:14:37 +01006745 row, gui.scroll_region_left,
6746 row + num_lines - 1, gui.scroll_region_right);
presuku9459b8d2021-11-16 20:03:56 +00006747 gtk_widget_queue_draw_area(gui.drawarea,
Bram Moolenaar98921892016-02-23 17:14:37 +01006748 FILL_X(gui.scroll_region_left), FILL_Y(row),
presuku9459b8d2021-11-16 20:03:56 +00006749 gui.char_width * ncols + 1, gui.char_height * nrows);
Bram Moolenaar98921892016-02-23 17:14:37 +01006750#else
Bram Moolenaar071d4272004-06-13 20:20:40 +00006751 if (gui.visibility == GDK_VISIBILITY_FULLY_OBSCURED)
Bram Moolenaar30613902019-12-01 22:11:18 +01006752 return; // Can't see the window
Bram Moolenaar071d4272004-06-13 20:20:40 +00006753
6754 gdk_gc_set_foreground(gui.text_gc, gui.fgcolor);
6755 gdk_gc_set_background(gui.text_gc, gui.bgcolor);
6756
Bram Moolenaar30613902019-12-01 22:11:18 +01006757 // copy one extra pixel, for when bold has spilled over
Bram Moolenaar071d4272004-06-13 20:20:40 +00006758 gdk_window_copy_area(gui.drawarea->window, gui.text_gc,
6759 FILL_X(gui.scroll_region_left), FILL_Y(row + num_lines),
6760 gui.drawarea->window,
6761 FILL_X(gui.scroll_region_left), FILL_Y(row),
6762 gui.char_width * (gui.scroll_region_right
6763 - gui.scroll_region_left + 1) + 1,
6764 gui.char_height * (gui.scroll_region_bot - row - num_lines + 1));
6765
6766 gui_clear_block(row, gui.scroll_region_left,
6767 row + num_lines - 1, gui.scroll_region_right);
6768 check_copy_area();
Bram Moolenaar30613902019-12-01 22:11:18 +01006769#endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006770}
6771
6772/*
6773 * X Selection stuff, for cutting and pasting text to other windows.
6774 */
6775 void
Bram Moolenaar0554fa42019-06-14 21:36:54 +02006776clip_mch_request_selection(Clipboard_T *cbd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006777{
6778 GdkAtom target;
6779 unsigned i;
Bram Moolenaar51b5ab92008-01-06 14:17:07 +00006780 time_t start;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006781
6782 for (i = 0; i < N_SELECTION_TARGETS; ++i)
6783 {
Bram Moolenaar3a6eaa52009-06-16 13:23:06 +00006784 if (!clip_html && selection_targets[i].info == TARGET_HTML)
6785 continue;
Bram Moolenaar071d4272004-06-13 20:20:40 +00006786 received_selection = RS_NONE;
6787 target = gdk_atom_intern(selection_targets[i].target, FALSE);
6788
6789 gtk_selection_convert(gui.drawarea,
6790 cbd->gtk_sel_atom, target,
6791 (guint32)GDK_CURRENT_TIME);
6792
Bram Moolenaar30613902019-12-01 22:11:18 +01006793 // Hack: Wait up to three seconds for the selection. A hang was
6794 // noticed here when using the netrw plugin combined with ":gui"
6795 // during the FocusGained event.
Bram Moolenaar51b5ab92008-01-06 14:17:07 +00006796 start = time(NULL);
6797 while (received_selection == RS_NONE && time(NULL) < start + 3)
Bram Moolenaar30613902019-12-01 22:11:18 +01006798 g_main_context_iteration(NULL, TRUE); // wait for selection_received_cb
Bram Moolenaar071d4272004-06-13 20:20:40 +00006799
6800 if (received_selection != RS_FAIL)
6801 return;
6802 }
6803
Bram Moolenaar30613902019-12-01 22:11:18 +01006804 // Final fallback position - use the X CUT_BUFFER0 store
Bram Moolenaar98921892016-02-23 17:14:37 +01006805 yank_cut_buffer0(GDK_WINDOW_XDISPLAY(gtk_widget_get_window(gui.mainwin)),
6806 cbd);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006807}
6808
6809/*
6810 * Disown the selection.
6811 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00006812 void
Bram Moolenaar0554fa42019-06-14 21:36:54 +02006813clip_mch_lose_selection(Clipboard_T *cbd UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006814{
Bram Moolenaar29dfa5a2018-03-20 21:24:45 +01006815 if (!in_selection_clear_event)
6816 {
6817 gtk_selection_owner_set(NULL, cbd->gtk_sel_atom, gui.event_time);
6818 gui_mch_update();
6819 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00006820}
6821
6822/*
6823 * Own the selection and return OK if it worked.
6824 */
6825 int
Bram Moolenaar0554fa42019-06-14 21:36:54 +02006826clip_mch_own_selection(Clipboard_T *cbd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006827{
6828 int success;
6829
6830 success = gtk_selection_owner_set(gui.drawarea, cbd->gtk_sel_atom,
Bram Moolenaar20892c12011-06-26 04:49:00 +02006831 gui.event_time);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006832 gui_mch_update();
6833 return (success) ? OK : FAIL;
6834}
6835
6836/*
6837 * Send the current selection to the clipboard. Do nothing for X because we
6838 * will fill in the selection only when requested by another app.
6839 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00006840 void
Bram Moolenaar0554fa42019-06-14 21:36:54 +02006841clip_mch_set_selection(Clipboard_T *cbd UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006842{
6843}
6844
Bram Moolenaar113e1072019-01-20 15:30:40 +01006845#if (defined(FEAT_XCLIPBOARD) && defined(USE_SYSTEM)) || defined(PROTO)
Bram Moolenaar1a0316c2013-03-13 17:50:25 +01006846 int
Bram Moolenaar0554fa42019-06-14 21:36:54 +02006847clip_gtk_owner_exists(Clipboard_T *cbd)
Bram Moolenaar1a0316c2013-03-13 17:50:25 +01006848{
6849 return gdk_selection_owner_get(cbd->gtk_sel_atom) != NULL;
6850}
Bram Moolenaar113e1072019-01-20 15:30:40 +01006851#endif
Bram Moolenaar1a0316c2013-03-13 17:50:25 +01006852
Bram Moolenaar071d4272004-06-13 20:20:40 +00006853
6854#if defined(FEAT_MENU) || defined(PROTO)
6855/*
6856 * Make a menu item appear either active or not active (grey or not grey).
6857 */
6858 void
6859gui_mch_menu_grey(vimmenu_T *menu, int grey)
6860{
6861 if (menu->id == NULL)
6862 return;
6863
6864 if (menu_is_separator(menu->name))
6865 grey = TRUE;
6866
6867 gui_mch_menu_hidden(menu, FALSE);
Bram Moolenaar30613902019-12-01 22:11:18 +01006868 // Be clever about bitfields versus true booleans here!
Bram Moolenaar98921892016-02-23 17:14:37 +01006869 if (!gtk_widget_get_sensitive(menu->id) == !grey)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006870 {
6871 gtk_widget_set_sensitive(menu->id, !grey);
6872 gui_mch_update();
6873 }
6874}
6875
6876/*
6877 * Make menu item hidden or not hidden.
6878 */
6879 void
6880gui_mch_menu_hidden(vimmenu_T *menu, int hidden)
6881{
6882 if (menu->id == 0)
6883 return;
6884
6885 if (hidden)
6886 {
Bram Moolenaar98921892016-02-23 17:14:37 +01006887 if (gtk_widget_get_visible(menu->id))
Bram Moolenaar071d4272004-06-13 20:20:40 +00006888 {
6889 gtk_widget_hide(menu->id);
6890 gui_mch_update();
6891 }
6892 }
6893 else
6894 {
Bram Moolenaar98921892016-02-23 17:14:37 +01006895 if (!gtk_widget_get_visible(menu->id))
Bram Moolenaar071d4272004-06-13 20:20:40 +00006896 {
6897 gtk_widget_show(menu->id);
6898 gui_mch_update();
6899 }
6900 }
6901}
6902
6903/*
6904 * This is called after setting all the menus to grey/hidden or not.
6905 */
6906 void
6907gui_mch_draw_menubar(void)
6908{
Bram Moolenaar30613902019-12-01 22:11:18 +01006909 // just make sure that the visual changes get effect immediately
Bram Moolenaar071d4272004-06-13 20:20:40 +00006910 gui_mch_update();
6911}
Bram Moolenaar30613902019-12-01 22:11:18 +01006912#endif // FEAT_MENU
Bram Moolenaar071d4272004-06-13 20:20:40 +00006913
6914/*
6915 * Scrollbar stuff.
6916 */
6917 void
6918gui_mch_enable_scrollbar(scrollbar_T *sb, int flag)
6919{
6920 if (sb->id == NULL)
6921 return;
6922
Bram Moolenaar98921892016-02-23 17:14:37 +01006923 gtk_widget_set_visible(sb->id, flag);
Bram Moolenaarb3656ed2006-03-20 21:59:49 +00006924 update_window_manager_hints(0, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006925}
6926
6927
6928/*
6929 * Return the RGB value of a pixel as long.
6930 */
Bram Moolenaar1b58cdd2016-08-22 23:04:33 +02006931 guicolor_T
Bram Moolenaar071d4272004-06-13 20:20:40 +00006932gui_mch_get_rgb(guicolor_T pixel)
6933{
Bram Moolenaar98921892016-02-23 17:14:37 +01006934#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +02006935 return (long_u)pixel;
Bram Moolenaar98921892016-02-23 17:14:37 +01006936#else
Bram Moolenaar36edf062016-07-21 22:10:12 +02006937 GdkColor color;
6938
Bram Moolenaar071d4272004-06-13 20:20:40 +00006939 gdk_colormap_query_color(gtk_widget_get_colormap(gui.drawarea),
6940 (unsigned long)pixel, &color);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006941
Bram Moolenaar1b58cdd2016-08-22 23:04:33 +02006942 return (guicolor_T)(
6943 (((unsigned)color.red & 0xff00) << 8)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006944 | ((unsigned)color.green & 0xff00)
Bram Moolenaar1b58cdd2016-08-22 23:04:33 +02006945 | (((unsigned)color.blue & 0xff00) >> 8));
Bram Moolenaar36edf062016-07-21 22:10:12 +02006946#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00006947}
6948
6949/*
Bram Moolenaar6cc16192005-01-08 21:49:45 +00006950 * Get current mouse coordinates in text window.
Bram Moolenaar071d4272004-06-13 20:20:40 +00006951 */
Bram Moolenaar6cc16192005-01-08 21:49:45 +00006952 void
6953gui_mch_getmouse(int *x, int *y)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006954{
Bram Moolenaar98921892016-02-23 17:14:37 +01006955 gui_gtk_get_pointer(gui.drawarea, x, y, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006956}
6957
6958 void
6959gui_mch_setmouse(int x, int y)
6960{
Bram Moolenaar30613902019-12-01 22:11:18 +01006961 // Sorry for the Xlib call, but we can't avoid it, since there is no
6962 // internal GDK mechanism present to accomplish this. (and for good
6963 // reason...)
Bram Moolenaar98921892016-02-23 17:14:37 +01006964 XWarpPointer(GDK_WINDOW_XDISPLAY(gtk_widget_get_window(gui.drawarea)),
6965 (Window)0, GDK_WINDOW_XID(gtk_widget_get_window(gui.drawarea)),
6966 0, 0, 0U, 0U, x, y);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006967}
6968
6969
6970#ifdef FEAT_MOUSESHAPE
Bram Moolenaar30613902019-12-01 22:11:18 +01006971// The last set mouse pointer shape is remembered, to be used when it goes
6972// from hidden to not hidden.
Bram Moolenaar071d4272004-06-13 20:20:40 +00006973static int last_shape = 0;
6974#endif
6975
6976/*
6977 * Use the blank mouse pointer or not.
6978 *
6979 * hide: TRUE = use blank ptr, FALSE = use parent ptr
6980 */
6981 void
6982gui_mch_mousehide(int hide)
6983{
6984 if (gui.pointer_hidden != hide)
6985 {
6986 gui.pointer_hidden = hide;
Bram Moolenaar98921892016-02-23 17:14:37 +01006987 if (gtk_widget_get_window(gui.drawarea) && gui.blank_pointer != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00006988 {
6989 if (hide)
Bram Moolenaar98921892016-02-23 17:14:37 +01006990 gdk_window_set_cursor(gtk_widget_get_window(gui.drawarea),
6991 gui.blank_pointer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006992 else
6993#ifdef FEAT_MOUSESHAPE
6994 mch_set_mouse_shape(last_shape);
6995#else
Bram Moolenaar25328e32018-09-11 21:30:09 +02006996 gdk_window_set_cursor(gtk_widget_get_window(gui.drawarea), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00006997#endif
6998 }
6999 }
7000}
7001
7002#if defined(FEAT_MOUSESHAPE) || defined(PROTO)
7003
Bram Moolenaar30613902019-12-01 22:11:18 +01007004// Table for shape IDs. Keep in sync with the mshape_names[] table in
7005// misc2.c!
Bram Moolenaar071d4272004-06-13 20:20:40 +00007006static const int mshape_ids[] =
7007{
Bram Moolenaar30613902019-12-01 22:11:18 +01007008 GDK_LEFT_PTR, // arrow
7009 GDK_CURSOR_IS_PIXMAP, // blank
7010 GDK_XTERM, // beam
7011 GDK_SB_V_DOUBLE_ARROW, // updown
7012 GDK_SIZING, // udsizing
7013 GDK_SB_H_DOUBLE_ARROW, // leftright
7014 GDK_SIZING, // lrsizing
7015 GDK_WATCH, // busy
7016 GDK_X_CURSOR, // no
7017 GDK_CROSSHAIR, // crosshair
7018 GDK_HAND1, // hand1
7019 GDK_HAND2, // hand2
7020 GDK_PENCIL, // pencil
7021 GDK_QUESTION_ARROW, // question
7022 GDK_RIGHT_PTR, // right-arrow
7023 GDK_CENTER_PTR, // up-arrow
7024 GDK_LEFT_PTR // last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00007025};
7026
7027 void
7028mch_set_mouse_shape(int shape)
7029{
7030 int id;
7031 GdkCursor *c;
7032
Bram Moolenaar98921892016-02-23 17:14:37 +01007033 if (gtk_widget_get_window(gui.drawarea) == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00007034 return;
7035
7036 if (shape == MSHAPE_HIDE || gui.pointer_hidden)
Bram Moolenaar98921892016-02-23 17:14:37 +01007037 gdk_window_set_cursor(gtk_widget_get_window(gui.drawarea),
7038 gui.blank_pointer);
Bram Moolenaar071d4272004-06-13 20:20:40 +00007039 else
7040 {
7041 if (shape >= MSHAPE_NUMBERED)
7042 {
7043 id = shape - MSHAPE_NUMBERED;
7044 if (id >= GDK_LAST_CURSOR)
7045 id = GDK_LEFT_PTR;
7046 else
Bram Moolenaar30613902019-12-01 22:11:18 +01007047 id &= ~1; // they are always even (why?)
Bram Moolenaar071d4272004-06-13 20:20:40 +00007048 }
K.Takataeeec2542021-06-02 13:28:16 +02007049 else if (shape < (int)ARRAY_LENGTH(mshape_ids))
Bram Moolenaar071d4272004-06-13 20:20:40 +00007050 id = mshape_ids[shape];
Bram Moolenaarf9393ef2006-04-24 19:47:27 +00007051 else
7052 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00007053 c = gdk_cursor_new_for_display(
Bram Moolenaarb71ec9f2005-01-25 22:22:02 +00007054 gtk_widget_get_display(gui.drawarea), (GdkCursorType)id);
Bram Moolenaar98921892016-02-23 17:14:37 +01007055 gdk_window_set_cursor(gtk_widget_get_window(gui.drawarea), c);
Bram Moolenaar98921892016-02-23 17:14:37 +01007056# if GTK_CHECK_VERSION(3,0,0)
7057 g_object_unref(G_OBJECT(c));
7058# else
Bram Moolenaar30613902019-12-01 22:11:18 +01007059 gdk_cursor_destroy(c); // Unref, actually. Bloody GTK+ 1.
Bram Moolenaar98921892016-02-23 17:14:37 +01007060# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00007061 }
7062 if (shape != MSHAPE_HIDE)
7063 last_shape = shape;
7064}
Bram Moolenaar30613902019-12-01 22:11:18 +01007065#endif // FEAT_MOUSESHAPE
Bram Moolenaar071d4272004-06-13 20:20:40 +00007066
7067
7068#if defined(FEAT_SIGN_ICONS) || defined(PROTO)
7069/*
7070 * Signs are currently always 2 chars wide. With GTK+ 2, the image will be
7071 * scaled down if the current font is not big enough, or scaled up if the image
7072 * size is less than 3/4 of the maximum sign size. With GTK+ 1, the pixmap
7073 * will be cut off if the current font is not big enough, or centered if it's
7074 * too small.
7075 */
7076# define SIGN_WIDTH (2 * gui.char_width)
7077# define SIGN_HEIGHT (gui.char_height)
7078# define SIGN_ASPECT ((double)SIGN_HEIGHT / (double)SIGN_WIDTH)
7079
Bram Moolenaar071d4272004-06-13 20:20:40 +00007080 void
7081gui_mch_drawsign(int row, int col, int typenr)
7082{
7083 GdkPixbuf *sign;
7084
7085 sign = (GdkPixbuf *)sign_get_image(typenr);
7086
Bram Moolenaar98921892016-02-23 17:14:37 +01007087 if (sign != NULL && gui.drawarea != NULL
7088 && gtk_widget_get_window(gui.drawarea) != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00007089 {
7090 int width;
7091 int height;
7092 int xoffset;
7093 int yoffset;
7094 int need_scale;
7095
7096 width = gdk_pixbuf_get_width(sign);
7097 height = gdk_pixbuf_get_height(sign);
7098 /*
7099 * Decide whether we need to scale. Allow one pixel of border
7100 * width to be cut off, in order to avoid excessive scaling for
7101 * tiny differences in font size.
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007102 * Do scale to fit the height to avoid gaps because of linespacing.
Bram Moolenaar071d4272004-06-13 20:20:40 +00007103 */
7104 need_scale = (width > SIGN_WIDTH + 2
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007105 || height != SIGN_HEIGHT
Bram Moolenaar071d4272004-06-13 20:20:40 +00007106 || (width < 3 * SIGN_WIDTH / 4
7107 && height < 3 * SIGN_HEIGHT / 4));
7108 if (need_scale)
7109 {
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007110 double aspect;
7111 int w = width;
7112 int h = height;
Bram Moolenaar071d4272004-06-13 20:20:40 +00007113
Bram Moolenaar30613902019-12-01 22:11:18 +01007114 // Keep the original aspect ratio
Bram Moolenaar071d4272004-06-13 20:20:40 +00007115 aspect = (double)height / (double)width;
7116 width = (double)SIGN_WIDTH * SIGN_ASPECT / aspect;
7117 width = MIN(width, SIGN_WIDTH);
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007118 if (((double)(MAX(height, SIGN_HEIGHT)) /
7119 (double)(MIN(height, SIGN_HEIGHT))) < 1.15)
7120 {
Bram Moolenaar30613902019-12-01 22:11:18 +01007121 // Change the aspect ratio by at most 15% to fill the
7122 // available space completely.
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007123 height = (double)SIGN_HEIGHT * SIGN_ASPECT / aspect;
7124 height = MIN(height, SIGN_HEIGHT);
7125 }
7126 else
7127 height = (double)width * aspect;
Bram Moolenaar071d4272004-06-13 20:20:40 +00007128
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007129 if (w == width && h == height)
7130 {
Bram Moolenaar30613902019-12-01 22:11:18 +01007131 // no change in dimensions; don't decrease reference counter
7132 // (below)
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007133 need_scale = FALSE;
7134 }
7135 else
7136 {
Bram Moolenaar30613902019-12-01 22:11:18 +01007137 // This doesn't seem to be worth caching, and doing so would
7138 // complicate the code quite a bit.
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007139 sign = gdk_pixbuf_scale_simple(sign, width, height,
7140 GDK_INTERP_BILINEAR);
7141 if (sign == NULL)
Bram Moolenaar30613902019-12-01 22:11:18 +01007142 return; // out of memory
Bram Moolenaar58cbc912014-06-17 18:47:02 +02007143 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00007144 }
7145
Bram Moolenaar30613902019-12-01 22:11:18 +01007146 // The origin is the upper-left corner of the pixmap. Therefore
7147 // these offset may become negative if the pixmap is smaller than
7148 // the 2x1 cells reserved for the sign icon.
Bram Moolenaar071d4272004-06-13 20:20:40 +00007149 xoffset = (width - SIGN_WIDTH) / 2;
7150 yoffset = (height - SIGN_HEIGHT) / 2;
7151
Bram Moolenaar98921892016-02-23 17:14:37 +01007152# if GTK_CHECK_VERSION(3,0,0)
7153 {
7154 cairo_t *cr;
7155 cairo_surface_t *bg_surf;
7156 cairo_t *bg_cr;
7157 cairo_surface_t *sign_surf;
7158 cairo_t *sign_cr;
7159
7160 cr = cairo_create(gui.surface);
7161
7162 bg_surf = cairo_surface_create_similar(gui.surface,
7163 cairo_surface_get_content(gui.surface),
7164 SIGN_WIDTH, SIGN_HEIGHT);
7165 bg_cr = cairo_create(bg_surf);
Bram Moolenaar36edf062016-07-21 22:10:12 +02007166 cairo_set_source_rgba(bg_cr,
7167 gui.bgcolor->red, gui.bgcolor->green, gui.bgcolor->blue,
7168 gui.bgcolor->alpha);
Bram Moolenaar98921892016-02-23 17:14:37 +01007169 cairo_paint(bg_cr);
7170
7171 sign_surf = cairo_surface_create_similar(gui.surface,
7172 cairo_surface_get_content(gui.surface),
7173 SIGN_WIDTH, SIGN_HEIGHT);
7174 sign_cr = cairo_create(sign_surf);
7175 gdk_cairo_set_source_pixbuf(sign_cr, sign, -xoffset, -yoffset);
7176 cairo_paint(sign_cr);
7177
7178 cairo_set_operator(sign_cr, CAIRO_OPERATOR_DEST_OVER);
7179 cairo_set_source_surface(sign_cr, bg_surf, 0, 0);
7180 cairo_paint(sign_cr);
7181
7182 cairo_set_source_surface(cr, sign_surf, FILL_X(col), FILL_Y(row));
7183 cairo_paint(cr);
7184
7185 cairo_destroy(sign_cr);
7186 cairo_surface_destroy(sign_surf);
7187 cairo_destroy(bg_cr);
7188 cairo_surface_destroy(bg_surf);
7189 cairo_destroy(cr);
7190
presuku9459b8d2021-11-16 20:03:56 +00007191 gtk_widget_queue_draw_area(gui.drawarea,
7192 FILL_X(col), FILL_Y(col), width, height);
Bram Moolenaar98921892016-02-23 17:14:37 +01007193
7194 }
Bram Moolenaar30613902019-12-01 22:11:18 +01007195# else // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00007196 gdk_gc_set_foreground(gui.text_gc, gui.bgcolor);
7197
7198 gdk_draw_rectangle(gui.drawarea->window,
7199 gui.text_gc,
7200 TRUE,
7201 FILL_X(col),
7202 FILL_Y(row),
7203 SIGN_WIDTH,
7204 SIGN_HEIGHT);
7205
Bram Moolenaar071d4272004-06-13 20:20:40 +00007206 gdk_pixbuf_render_to_drawable_alpha(sign,
7207 gui.drawarea->window,
7208 MAX(0, xoffset),
7209 MAX(0, yoffset),
7210 FILL_X(col) - MIN(0, xoffset),
7211 FILL_Y(row) - MIN(0, yoffset),
7212 MIN(width, SIGN_WIDTH),
7213 MIN(height, SIGN_HEIGHT),
7214 GDK_PIXBUF_ALPHA_BILEVEL,
7215 127,
7216 GDK_RGB_DITHER_NORMAL,
7217 0, 0);
Bram Moolenaar30613902019-12-01 22:11:18 +01007218# endif // !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00007219 if (need_scale)
7220 g_object_unref(sign);
7221 }
7222}
7223
7224 void *
7225gui_mch_register_sign(char_u *signfile)
7226{
7227 if (signfile[0] != NUL && signfile[0] != '-' && gui.in_use)
7228 {
7229 GdkPixbuf *sign;
7230 GError *error = NULL;
7231 char_u *message;
7232
7233 sign = gdk_pixbuf_new_from_file((const char *)signfile, &error);
7234
7235 if (error == NULL)
7236 return sign;
7237
7238 message = (char_u *)error->message;
7239
7240 if (message != NULL && input_conv.vc_type != CONV_NONE)
7241 message = string_convert(&input_conv, message, NULL);
7242
7243 if (message != NULL)
7244 {
Bram Moolenaar30613902019-12-01 22:11:18 +01007245 // The error message is already translated and will be more
7246 // descriptive than anything we could possibly do ourselves.
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01007247 semsg("E255: %s", message);
Bram Moolenaar071d4272004-06-13 20:20:40 +00007248
7249 if (input_conv.vc_type != CONV_NONE)
7250 vim_free(message);
7251 }
7252 g_error_free(error);
7253 }
7254
7255 return NULL;
7256}
7257
7258 void
7259gui_mch_destroy_sign(void *sign)
7260{
7261 if (sign != NULL)
7262 g_object_unref(sign);
7263}
7264
Bram Moolenaar30613902019-12-01 22:11:18 +01007265#endif // FEAT_SIGN_ICONS