gtkutil.c 163 KB
Newer Older
Jan Djärv's avatar
Jan Djärv committed
1
/* Functions for creating and updating GTK widgets.
2

Paul Eggert's avatar
Paul Eggert committed
3
Copyright (C) 2003-2019 Free Software Foundation, Inc.
Jan Djärv's avatar
Jan Djärv committed
4 5 6

This file is part of GNU Emacs.

7
GNU Emacs is free software: you can redistribute it and/or modify
Jan Djärv's avatar
Jan Djärv committed
8
it under the terms of the GNU General Public License as published by
9 10
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
Jan Djärv's avatar
Jan Djärv committed
11 12 13 14 15 16 17

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
18
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
Jan Djärv's avatar
Jan Djärv committed
19

20
#include <config.h>
Jan Djärv's avatar
Jan Djärv committed
21 22

#ifdef USE_GTK
23
#include <float.h>
24
#include <stdio.h>
25 26 27

#include <c-ctype.h>

Jan Djärv's avatar
Jan Djärv committed
28
#include "lisp.h"
29 30 31
#include "dispextern.h"
#include "frame.h"
#include "systime.h"
Jan Djärv's avatar
Jan Djärv committed
32 33 34 35 36
#include "xterm.h"
#include "blockinput.h"
#include "window.h"
#include "gtkutil.h"
#include "termhooks.h"
37 38
#include "keyboard.h"
#include "coding.h"
39

Jan Djärv's avatar
Jan Djärv committed
40 41
#include <gdk/gdkkeysyms.h>

42 43 44
#ifdef HAVE_XFT
#include <X11/Xft/Xft.h>
#endif
45

46 47
#ifdef HAVE_GTK3
#include <gtk/gtkx.h>
48
#include "emacsgtkfixed.h"
49 50
#endif

51 52 53 54
#ifdef HAVE_XDBE
#include <X11/extensions/Xdbe.h>
#endif

55 56 57 58
#ifdef HAVE_GTK3
#define XG_TEXT_CANCEL "Cancel"
#define XG_TEXT_OK     "OK"
#define XG_TEXT_OPEN   "Open"
59
#else
60 61 62
#define XG_TEXT_CANCEL GTK_STOCK_CANCEL
#define XG_TEXT_OK     GTK_STOCK_OK
#define XG_TEXT_OPEN   GTK_STOCK_OPEN
63 64
#endif

65
#ifndef HAVE_GTK3
Jan D's avatar
Jan D committed
66

67
#ifdef HAVE_FREETYPE
68 69 70 71 72 73 74
#define gtk_font_chooser_dialog_new(x, y) \
  gtk_font_selection_dialog_new (x)
#undef GTK_FONT_CHOOSER
#define GTK_FONT_CHOOSER(x) GTK_FONT_SELECTION_DIALOG (x)
#define  gtk_font_chooser_set_font(x, y) \
  gtk_font_selection_dialog_set_font_name (x, y)
#endif
75

76
#define gdk_window_get_geometry(w, a, b, c, d) \
77
  gdk_window_get_geometry (w, a, b, c, d, 0)
78 79 80 81 82 83 84
#define gtk_box_new(ori, spacing)                                       \
  ((ori) == GTK_ORIENTATION_HORIZONTAL                                  \
   ? gtk_hbox_new (FALSE, (spacing)) : gtk_vbox_new (FALSE, (spacing)))
#define gtk_scrollbar_new(ori, spacing)                                 \
  ((ori) == GTK_ORIENTATION_HORIZONTAL                                  \
   ? gtk_hscrollbar_new ((spacing)) : gtk_vscrollbar_new ((spacing)))
#endif /* HAVE_GTK3 */
85

86 87
#define XG_BIN_CHILD(x) gtk_bin_get_child (GTK_BIN (x))

88
static void update_theme_scrollbar_width (void);
89
static void update_theme_scrollbar_height (void);
90

91 92 93 94 95 96 97 98 99 100
#define TB_INFO_KEY "xg_frame_tb_info"
struct xg_frame_tb_info
{
  Lisp_Object last_tool_bar;
  Lisp_Object style;
  int n_last_items;
  int hmargin, vmargin;
  GtkTextDirection dir;
};

101
#ifdef HAVE_XWIDGETS
102
bool xg_gtk_initialized;        /* Used to make sure xwidget calls are possible */
103
#endif
104

105 106
static GtkWidget * xg_get_widget_from_map (ptrdiff_t idx);

107 108 109 110 111

/***********************************************************************
                      Display handling functions
 ***********************************************************************/

112 113
/* Keep track of the default display, or NULL if there is none.  Emacs
   may close all its displays.  */
Jan Djärv's avatar
Jan Djärv committed
114 115 116

static GdkDisplay *gdpy_def;

117 118 119
/* When the GTK widget W is to be created on a display for F that
   is not the default display, set the display for W.
   W can be a GtkMenu or a GtkWindow widget.  */
120

121
static void
Dmitry Antipov's avatar
Dmitry Antipov committed
122
xg_set_screen (GtkWidget *w, struct frame *f)
123
{
124
  if (FRAME_X_DISPLAY (f) != DEFAULT_GDK_DISPLAY ())
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    {
      GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
      GdkScreen *gscreen = gdk_display_get_default_screen (gdpy);

      if (GTK_IS_MENU (w))
        gtk_menu_set_screen (GTK_MENU (w), gscreen);
      else
        gtk_window_set_screen (GTK_WINDOW (w), gscreen);
    }
}


/* Open a display named by DISPLAY_NAME.  The display is returned in *DPY.
   *DPY is set to NULL if the display can't be opened.

   Returns non-zero if display could be opened, zero if display could not
   be opened, and less than zero if the GTK version doesn't support
Paul Eggert's avatar
Paul Eggert committed
142
   multiple displays.  */
143

144
void
145
xg_display_open (char *display_name, Display **dpy)
146 147 148
{
  GdkDisplay *gdpy;

149
  unrequest_sigio ();  /* See comment in x_display_ok, xterm.c.  */
150
  gdpy = gdk_display_open (display_name);
Jan D's avatar
Jan D committed
151
  request_sigio ();
152 153 154 155 156 157
  if (!gdpy_def && gdpy)
    {
      gdpy_def = gdpy;
      gdk_display_manager_set_default_display (gdk_display_manager_get (),
					       gdpy);
    }
158

159
  *dpy = gdpy ? GDK_DISPLAY_XDISPLAY (gdpy) : NULL;
160 161
}

Lars Ingebrigtsen's avatar
Lars Ingebrigtsen committed
162
/* Scaling/HiDPI functions. */
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
static int
xg_get_gdk_scale (void)
{
  const char *sscale = getenv ("GDK_SCALE");

  if (sscale)
    {
      long scale = atol (sscale);
      if (0 < scale)
	return min (scale, INT_MAX);
    }

  return 1;
}

178 179 180
int
xg_get_scale (struct frame *f)
{
181
#ifdef HAVE_GTK3
182
  if (FRAME_GTK_WIDGET (f))
183
    return gtk_widget_get_scale_factor (FRAME_GTK_WIDGET (f));
184 185
#endif
  return xg_get_gdk_scale ();
186
}
187

188 189
/* Close display DPY.  */

190 191 192 193 194
void
xg_display_close (Display *dpy)
{
  GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);

195 196 197
  /* If this is the default display, try to change it before closing.
     If there is no other display to use, gdpy_def is set to NULL, and
     the next call to xg_display_open resets the default display.  */
198 199 200
  if (gdk_display_get_default () == gdpy)
    {
      struct x_display_info *dpyinfo;
201
      GdkDisplay *gdpy_new = NULL;
202 203 204 205 206

      /* Find another display.  */
      for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next)
        if (dpyinfo->display != dpy)
          {
207 208 209
	    gdpy_new = gdk_x11_lookup_xdisplay (dpyinfo->display);
	    gdk_display_manager_set_default_display (gdk_display_manager_get (),
						     gdpy_new);
210 211
            break;
          }
212
      gdpy_def = gdpy_new;
213 214 215 216
    }

  gdk_display_close (gdpy);
}
217

Jan Djärv's avatar
Jan Djärv committed
218 219 220 221 222

/***********************************************************************
                      Utility functions
 ***********************************************************************/

223 224
/* Create and return the cursor to be used for popup menus and
   scroll bars on display DPY.  */
225

226
GdkCursor *
227
xg_create_default_cursor (Display *dpy)
228 229 230 231 232
{
  GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);
  return gdk_cursor_new_for_display (gdpy, GDK_LEFT_PTR);
}

233 234
/* Apply GMASK to GPIX and return a GdkPixbuf with an alpha channel.  */

235
static GdkPixbuf *
Dmitry Antipov's avatar
Dmitry Antipov committed
236
xg_get_pixbuf_from_pix_and_mask (struct frame *f,
237 238
                                 Pixmap pix,
                                 Pixmap mask)
239
{
240 241 242 243
  GdkPixbuf *icon_buf = 0;
  int iunused;
  Window wunused;
  unsigned int width, height, depth, uunused;
244

245 246 247 248 249 250 251 252 253
  if (FRAME_DISPLAY_INFO (f)->red_bits != 8)
    return 0;
  XGetGeometry (FRAME_X_DISPLAY (f), pix, &wunused, &iunused, &iunused,
                &width, &height, &uunused, &depth);
  if (depth != 24)
    return 0;
  XImage *xim = XGetImage (FRAME_X_DISPLAY (f), pix, 0, 0, width, height,
			   ~0, XYPixmap);
  if (xim)
254
    {
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
      XImage *xmm = (! mask ? 0
		     : XGetImage (FRAME_X_DISPLAY (f), mask, 0, 0,
				  width, height, ~0, XYPixmap));
      icon_buf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
      if (icon_buf)
	{
	  guchar *pixels = gdk_pixbuf_get_pixels (icon_buf);
	  int rowjunkwidth = gdk_pixbuf_get_rowstride (icon_buf) - width * 4;
	  for (int y = 0; y < height; y++, pixels += rowjunkwidth)
	    for (int x = 0; x < width; x++)
	      {
		unsigned long rgb = XGetPixel (xim, x, y);
		*pixels++ = (rgb >> 16) & 255;
		*pixels++ = (rgb >> 8) & 255;
		*pixels++ = rgb & 255;
		*pixels++ = xmm && !XGetPixel (xmm, x, y) ? 0 : 255;
	      }
	}

      if (xmm)
	XDestroyImage (xmm);
      XDestroyImage (xim);
277 278 279 280 281
    }

  return icon_buf;
}

282 283
#if defined USE_CAIRO && !defined HAVE_GTK3
static GdkPixbuf *
284
xg_get_pixbuf_from_surface (cairo_surface_t *surface)
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
{
  int width = cairo_image_surface_get_width (surface);
  int height = cairo_image_surface_get_height (surface);
  GdkPixbuf *icon_buf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
					width, height);
  if (icon_buf)
    {
      guchar *pixels = gdk_pixbuf_get_pixels (icon_buf);
      int rowstride = gdk_pixbuf_get_rowstride (icon_buf);
      cairo_surface_t *icon_surface
	= cairo_image_surface_create_for_data (pixels, CAIRO_FORMAT_ARGB32,
					       width, height, rowstride);
      cairo_t *cr = cairo_create (icon_surface);
      cairo_surface_destroy (icon_surface);
      cairo_set_source_surface (cr, surface, 0, 0);
      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
      cairo_paint (cr);
      cairo_destroy (cr);

      for (int y = 0; y < height; y++)
	{
	  for (int x = 0; x < width; x++)
	    {
	      guint32 argb = ((guint32 *) pixels)[x];
309 310 311 312 313 314 315 316 317 318 319 320 321 322
	      int alpha = argb >> 24;

	      if (alpha == 0)
		((guint32 *) pixels)[x] = 0;
	      else
		{
		  int red = (argb >> 16) & 0xff, green = (argb >> 8) & 0xff;
		  int blue = argb & 0xff;

		  pixels[x * 4    ] = red   * 0xff / alpha;
		  pixels[x * 4 + 1] = green * 0xff / alpha;
		  pixels[x * 4 + 2] = blue  * 0xff / alpha;
		  pixels[x * 4 + 3] = alpha;
		}
323 324 325 326 327 328 329 330 331
	    }
	  pixels += rowstride;
	}
    }

  return icon_buf;
}
#endif	/* USE_CAIRO && !HAVE_GTK3 */

332
static Lisp_Object
333
file_for_image (Lisp_Object image)
334 335 336 337 338 339 340 341 342 343 344 345 346
{
  Lisp_Object specified_file = Qnil;
  Lisp_Object tail;

  for (tail = XCDR (image);
       NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
       tail = XCDR (XCDR (tail)))
    if (EQ (XCAR (tail), QCfile))
      specified_file = XCAR (XCDR (tail));

  return specified_file;
}

347 348
/* For the image defined in IMG, make and return a GtkImage.  For displays with
   8 planes or less we must make a GdkPixbuf and apply the mask manually.
Paul Eggert's avatar
Paul Eggert committed
349
   Otherwise the highlighting and dimming the tool bar code in GTK does
350 351 352 353 354 355 356
   will look bad.  For display with more than 8 planes we just use the
   pixmap and mask directly.  For monochrome displays, GTK doesn't seem
   able to use external pixmaps, it looks bad whatever we do.
   The image is defined on the display where frame F is.
   WIDGET is used to find the GdkColormap to use for the GdkPixbuf.
   If OLD_WIDGET is NULL, a new widget is constructed and returned.
   If OLD_WIDGET is not NULL, that widget is modified.  */
357

358
static GtkWidget *
Dmitry Antipov's avatar
Dmitry Antipov committed
359
xg_get_image_for_pixmap (struct frame *f,
360 361 362
                         struct image *img,
                         GtkWidget *widget,
                         GtkImage *old_widget)
363
{
364
#ifdef USE_CAIRO
365 366
  cairo_surface_t *surface;
#else
367
  GdkPixbuf *icon_buf;
368
#endif
369

370
  /* If we have a file, let GTK do all the image handling.
371
     This seems to be the only way to make insensitive and activated icons
372
     look good in all cases.  */
373
  Lisp_Object specified_file = file_for_image (img->spec);
374
  Lisp_Object file;
375

376 377 378
  /* We already loaded the image once before calling this
     function, so this only fails if the image file has been removed.
     In that case, use the pixmap already loaded.  */
379

380
  if (STRINGP (specified_file)
381
      && STRINGP (file = image_find_image_file (specified_file)))
382
    {
383
      char *encoded_file = SSDATA (ENCODE_FILE (file));
384
      if (! old_widget)
385
        old_widget = GTK_IMAGE (gtk_image_new_from_file (encoded_file));
386
      else
387
        gtk_image_set_from_file (old_widget, encoded_file);
388 389

      return GTK_WIDGET (old_widget);
390
    }
391

392 393 394 395
  /* No file, do the image handling ourselves.  This will look very bad
     on a monochrome display, and sometimes bad on all displays with
     certain themes.  */

396
#ifdef USE_CAIRO
397 398 399 400
  if (cairo_pattern_get_type (img->cr_data) == CAIRO_PATTERN_TYPE_SURFACE)
    cairo_pattern_get_surface (img->cr_data, &surface);
  else
    surface = NULL;
401 402 403

  if (surface)
    {
404
#ifdef HAVE_GTK3
405 406 407 408
      if (! old_widget)
        old_widget = GTK_IMAGE (gtk_image_new_from_surface (surface));
      else
        gtk_image_set_from_surface (old_widget, surface);
409
#else  /* !HAVE_GTK3 */
410
      GdkPixbuf *icon_buf = xg_get_pixbuf_from_surface (surface);
411 412 413 414 415 416 417 418 419 420 421

      if (icon_buf)
	{
	  if (! old_widget)
	    old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf));
	  else
	    gtk_image_set_from_pixbuf (old_widget, icon_buf);

	  g_object_unref (G_OBJECT (icon_buf));
	}
#endif	/* !HAVE_GTK3 */
422 423
    }
#else
424 425 426 427 428 429 430 431 432
  /* This is a workaround to make icons look good on pseudo color
     displays.  Apparently GTK expects the images to have an alpha
     channel.  If they don't, insensitive and activated icons will
     look bad.  This workaround does not work on monochrome displays,
     and is strictly not needed on true color/static color displays (i.e.
     16 bits and higher).  But we do it anyway so we get a pixbuf that is
     not associated with the img->pixmap.  The img->pixmap may be removed
     by clearing the image cache and then the tool bar redraw fails, since
     Gtk+ assumes the pixmap is always there.  */
433
  icon_buf = xg_get_pixbuf_from_pix_and_mask (f, img->pixmap, img->mask);
434

435
  if (icon_buf)
436 437 438 439 440
    {
      if (! old_widget)
        old_widget = GTK_IMAGE (gtk_image_new_from_pixbuf (icon_buf));
      else
        gtk_image_set_from_pixbuf (old_widget, icon_buf);
441

442 443
      g_object_unref (G_OBJECT (icon_buf));
    }
444
#endif
445

446
  return GTK_WIDGET (old_widget);
447 448 449 450 451 452
}


/* Set CURSOR on W and all widgets W contain.  We must do like this
   for scroll bars and menu because they create widgets internally,
   and it is those widgets that are visible.  */
453

454
static void
455
xg_set_cursor (GtkWidget *w, GdkCursor *cursor)
Jan Djärv's avatar
Jan Djärv committed
456
{
Juanma Barranquero's avatar
Juanma Barranquero committed
457
  GdkWindow *window = gtk_widget_get_window (w);
458
  GList *children = gdk_window_peek_children (window);
Jan Djärv's avatar
Jan Djärv committed
459

460
  gdk_window_set_cursor (window, cursor);
Jan Djärv's avatar
Jan Djärv committed
461 462 463 464 465 466 467

  /* The scroll bar widget has more than one GDK window (had to look at
     the source to figure this out), and there is no way to set cursor
     on widgets in GTK.  So we must set the cursor for all GDK windows.
     Ditto for menus.  */

  for ( ; children; children = g_list_next (children))
468
    gdk_window_set_cursor (GDK_WINDOW (children->data), cursor);
Jan Djärv's avatar
Jan Djärv committed
469 470 471
}

/* Insert NODE into linked LIST.  */
472

Jan Djärv's avatar
Jan Djärv committed
473 474 475 476
static void
xg_list_insert (xg_list_node *list, xg_list_node *node)
{
  xg_list_node *list_start = list->next;
477

Jan Djärv's avatar
Jan Djärv committed
478 479 480 481 482 483 484
  if (list_start) list_start->prev = node;
  node->next = list_start;
  node->prev = 0;
  list->next = node;
}

/* Remove NODE from linked LIST.  */
485

Jan Djärv's avatar
Jan Djärv committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
static void
xg_list_remove (xg_list_node *list, xg_list_node *node)
{
  xg_list_node *list_start = list->next;
  if (node == list_start)
    {
      list->next = node->next;
      if (list->next) list->next->prev = 0;
    }
  else
    {
      node->prev->next = node->next;
      if (node->next) node->next->prev = node->prev;
    }
}

/* Allocate and return a utf8 version of STR.  If STR is already
503 504
   utf8 or NULL, just return a copy of STR.
   A new string is allocated and the caller must free the result
Jan Djärv's avatar
Jan Djärv committed
505
   with g_free.  */
506

Jan Djärv's avatar
Jan Djärv committed
507
static char *
508
get_utf8_string (const char *str)
Jan Djärv's avatar
Jan Djärv committed
509
{
510
  char *utf8_str;
511

512 513
  if (!str) return NULL;

Jan Djärv's avatar
Jan Djärv committed
514
  /* If not UTF-8, try current locale.  */
515
  if (!g_utf8_validate (str, -1, NULL))
Jan Djärv's avatar
Jan Djärv committed
516
    utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0);
517 518
  else
    return g_strdup (str);
Jan Djärv's avatar
Jan Djärv committed
519

Juanma Barranquero's avatar
Juanma Barranquero committed
520
  if (!utf8_str)
521 522
    {
      /* Probably some control characters in str.  Escape them. */
523 524
      ptrdiff_t len;
      ptrdiff_t nr_bad = 0;
525 526 527 528
      gsize bytes_read;
      gsize bytes_written;
      unsigned char *p = (unsigned char *)str;
      char *cp, *up;
529
      GError *err = NULL;
530

531
      while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
532 533
                                       &bytes_written, &err))
             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
534 535 536
        {
          ++nr_bad;
          p += bytes_written+1;
537 538
          g_error_free (err);
          err = NULL;
539 540
        }

541
      if (err)
542
        {
543 544
          g_error_free (err);
          err = NULL;
545 546 547
        }
      if (cp) g_free (cp);

548
      len = strlen (str);
549 550 551 552
      ptrdiff_t alloc;
      if (INT_MULTIPLY_WRAPV (nr_bad, 4, &alloc)
	  || INT_ADD_WRAPV (len + 1, alloc, &alloc)
	  || SIZE_MAX < alloc)
553
	memory_full (SIZE_MAX);
554
      up = utf8_str = xmalloc (alloc);
555
      p = (unsigned char *)str;
556

557
      while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
558 559
                                       &bytes_written, &err))
             && err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
560
        {
561
          memcpy (up, p, bytes_written);
562 563 564
          up += bytes_written;
          up += sprintf (up, "\\%03o", p[bytes_written]);
          p += bytes_written + 1;
565 566
          g_error_free (err);
          err = NULL;
567 568
        }

Juanma Barranquero's avatar
Juanma Barranquero committed
569
      if (cp)
570
        {
571
          strcpy (up, cp);
572 573
          g_free (cp);
        }
574
      if (err)
575
        {
576 577
          g_error_free (err);
          err = NULL;
578 579
        }
    }
Jan Djärv's avatar
Jan Djärv committed
580 581 582
  return utf8_str;
}

583 584
/* Check for special colors used in face spec for region face.
   The colors are fetched from the Gtk+ theme.
585
   Return true if color was found, false if not.  */
586

587
bool
588 589
xg_check_special_colors (struct frame *f,
                         const char *color_name,
590
                         Emacs_Color *color)
591
{
592 593 594
  bool success_p = 0;
  bool get_bg = strcmp ("gtk_selection_bg_color", color_name) == 0;
  bool get_fg = !get_bg && strcmp ("gtk_selection_fg_color", color_name) == 0;
595 596 597 598

  if (! FRAME_GTK_WIDGET (f) || ! (get_bg || get_fg))
    return success_p;

599
  block_input ();
600 601 602 603 604
  {
#ifdef HAVE_GTK3
    GtkStyleContext *gsty
      = gtk_widget_get_style_context (FRAME_GTK_OUTER_WIDGET (f));
    GdkRGBA col;
605
    char buf[sizeof "rgb://rrrr/gggg/bbbb"];
606 607 608 609
    int state = GTK_STATE_FLAG_SELECTED|GTK_STATE_FLAG_FOCUSED;
    if (get_fg)
      gtk_style_context_get_color (gsty, state, &col);
    else
610 611 612
      {
        GdkRGBA *c;
        /* FIXME: Retrieving the background color is deprecated in
613 614
           GTK+ 3.16.  New versions of GTK+ don't use the concept of a
           single background color any more, so we shouldn't query for
615 616 617 618 619 620 621
           it.  */
        gtk_style_context_get (gsty, state,
                               GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &c,
                               NULL);
        col = *c;
        gdk_rgba_free (c);
      }
622

623 624 625 626 627
    unsigned short
      r = col.red * 65535,
      g = col.green * 65535,
      b = col.blue * 65535;
    sprintf (buf, "rgb:%04x/%04x/%04x", r, g, b);
628
    success_p = x_parse_color (f, buf, color) != 0;
629 630 631 632 633 634 635 636 637 638 639 640
#else
    GtkStyle *gsty = gtk_widget_get_style (FRAME_GTK_WIDGET (f));
    GdkColor *grgb = get_bg
      ? &gsty->bg[GTK_STATE_SELECTED]
      : &gsty->fg[GTK_STATE_SELECTED];

    color->red = grgb->red;
    color->green = grgb->green;
    color->blue = grgb->blue;
    color->pixel = grgb->pixel;
    success_p = 1;
#endif
641

642
  }
643
  unblock_input ();
644 645 646
  return success_p;
}

Jan Djärv's avatar
Jan Djärv committed
647

648 649 650 651 652 653 654 655 656 657 658 659 660

/***********************************************************************
                              Tooltips
 ***********************************************************************/
/* Gtk+ calls this callback when the parent of our tooltip dummy changes.
   We use that to pop down the tooltip.  This happens if Gtk+ for some
   reason wants to change or hide the tooltip.  */

static void
hierarchy_ch_cb (GtkWidget *widget,
                 GtkWidget *previous_toplevel,
                 gpointer   user_data)
{
Paul Eggert's avatar
Paul Eggert committed
661
  struct frame *f = user_data;
662 663
  struct x_output *x = f->output_data.x;
  GtkWidget *top = gtk_widget_get_toplevel (x->ttip_lbl);
Chong Yidong's avatar
Chong Yidong committed
664

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
  if (! top || ! GTK_IS_WINDOW (top))
      gtk_widget_hide (previous_toplevel);
}

/* Callback called when Gtk+ thinks a tooltip should be displayed.
   We use it to get the tooltip window and the tooltip widget so
   we can manipulate the ourselves.

   Return FALSE ensures that the tooltip is not shown.  */

static gboolean
qttip_cb (GtkWidget  *widget,
          gint        xpos,
          gint        ypos,
          gboolean    keyboard_mode,
          GtkTooltip *tooltip,
          gpointer    user_data)
{
Paul Eggert's avatar
Paul Eggert committed
683
  struct frame *f = user_data;
684
  struct x_output *x = f->output_data.x;
Chong Yidong's avatar
Chong Yidong committed
685
  if (x->ttip_widget == NULL)
686
    {
687 688 689
      GtkWidget *p;
      GList *list, *iter;

690 691 692 693 694 695 696
      g_object_set (G_OBJECT (widget), "has-tooltip", FALSE, NULL);
      x->ttip_widget = tooltip;
      g_object_ref (G_OBJECT (tooltip));
      x->ttip_lbl = gtk_label_new ("");
      g_object_ref (G_OBJECT (x->ttip_lbl));
      gtk_tooltip_set_custom (tooltip, x->ttip_lbl);
      x->ttip_window = GTK_WINDOW (gtk_widget_get_toplevel (x->ttip_lbl));
697 698 699 700 701 702 703 704 705 706 707 708

      /* Change stupid Gtk+ default line wrapping.  */
      p = gtk_widget_get_parent (x->ttip_lbl);
      list = gtk_container_get_children (GTK_CONTAINER (p));
      for (iter = list; iter; iter = g_list_next (iter))
        {
          GtkWidget *w = GTK_WIDGET (iter->data);
          if (GTK_IS_LABEL (w))
            gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
        }
      g_list_free (list);

709 710
      /* ATK needs an empty title for some reason.  */
      gtk_window_set_title (x->ttip_window, "");
711 712 713 714 715 716 717
      /* Realize so we can safely get screen later on.  */
      gtk_widget_realize (GTK_WIDGET (x->ttip_window));
      gtk_widget_realize (x->ttip_lbl);

      g_signal_connect (x->ttip_lbl, "hierarchy-changed",
                        G_CALLBACK (hierarchy_ch_cb), f);
    }
718

719 720 721 722
  return FALSE;
}

/* Prepare a tooltip to be shown, i.e. calculate WIDTH and HEIGHT.
723
   Return true if a system tooltip is available.  */
724

725
bool
Dmitry Antipov's avatar
Dmitry Antipov committed
726
xg_prepare_tooltip (struct frame *f,
727 728
                    Lisp_Object string,
                    int *width,
729 730 731 732 733 734 735 736 737 738 739
                    int *height)
{
  struct x_output *x = f->output_data.x;
  GtkWidget *widget;
  GdkWindow *gwin;
  GdkScreen *screen;
  GtkSettings *settings;
  gboolean tt_enabled = TRUE;
  GtkRequisition req;
  Lisp_Object encoded_string;

740 741
  if (!x->ttip_lbl)
    return FALSE;
742

743
  block_input ();
744 745 746
  encoded_string = ENCODE_UTF_8 (string);
  widget = GTK_WIDGET (x->ttip_lbl);
  gwin = gtk_widget_get_window (GTK_WIDGET (x->ttip_window));
747
  screen = gdk_window_get_screen (gwin);
748 749
  settings = gtk_settings_get_for_screen (screen);
  g_object_get (settings, "gtk-enable-tooltips", &tt_enabled, NULL);
Chong Yidong's avatar
Chong Yidong committed
750
  if (tt_enabled)
751 752 753 754 755 756
    {
      g_object_set (settings, "gtk-enable-tooltips", FALSE, NULL);
      /* Record that we disabled it so it can be enabled again.  */
      g_object_set_data (G_OBJECT (x->ttip_window), "restore-tt",
                         (gpointer)f);
    }
Chong Yidong's avatar
Chong Yidong committed
757

758 759 760 761 762
  /* Prevent Gtk+ from hiding tooltip on mouse move and such.  */
  g_object_set_data (G_OBJECT
                     (gtk_widget_get_display (GTK_WIDGET (x->ttip_window))),
                     "gdk-display-current-tooltip", NULL);

763
  /* Put our dummy widget in so we can get callbacks for unrealize and
764 765
     hierarchy-changed.  */
  gtk_tooltip_set_custom (x->ttip_widget, widget);
766
  gtk_tooltip_set_text (x->ttip_widget, SSDATA (encoded_string));
767
  gtk_widget_get_preferred_size (GTK_WIDGET (x->ttip_window), NULL, &req);
768 769
  if (width) *width = req.width;
  if (height) *height = req.height;
Chong Yidong's avatar
Chong Yidong committed
770

771
  unblock_input ();
772

773
  return TRUE;
774 775 776 777 778 779
}

/* Show the tooltip at ROOT_X and ROOT_Y.
   xg_prepare_tooltip must have been called before this function.  */

void
Dmitry Antipov's avatar
Dmitry Antipov committed
780
xg_show_tooltip (struct frame *f, int root_x, int root_y)
781 782 783 784
{
  struct x_output *x = f->output_data.x;
  if (x->ttip_window)
    {
785
      block_input ();
786 787
      gtk_window_move (x->ttip_window, root_x / xg_get_scale (f),
		       root_y / xg_get_scale (f));
788
      gtk_widget_show (GTK_WIDGET (x->ttip_window));
789
      unblock_input ();
790 791 792
    }
}

793

794
/* Hide tooltip if shown.  Do nothing if not shown.
795
   Return true if tip was hidden, false if not (i.e. not using
796
   system tooltips).  */
797
bool
Dmitry Antipov's avatar
Dmitry Antipov committed
798
xg_hide_tooltip (struct frame *f)
799
{
John Wiegley's avatar
John Wiegley committed
800
  if (f->output_data.x->ttip_window)
801 802
    {
      GtkWindow *win = f->output_data.x->ttip_window;
803

804
      block_input ();
805 806 807 808 809
      gtk_widget_hide (GTK_WIDGET (win));

      if (g_object_get_data (G_OBJECT (win), "restore-tt"))
        {
          GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET (win));
810
          GdkScreen *screen = gdk_window_get_screen (gwin);
811 812 813
          GtkSettings *settings = gtk_settings_get_for_screen (screen);
          g_object_set (settings, "gtk-enable-tooltips", TRUE, NULL);
        }
814
      unblock_input ();
Dmitry Antipov's avatar
Dmitry Antipov committed
815

816
      return TRUE;
John Wiegley's avatar
John Wiegley committed
817
    }
818
  return FALSE;
819 820
}

Jan Djärv's avatar
Jan Djärv committed
821 822 823 824 825

/***********************************************************************
    General functions for creating widgets, resizing, events, e.t.c.
 ***********************************************************************/

826
#if ! GTK_CHECK_VERSION (3, 22, 0)
827 828 829 830 831 832 833
static void
my_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
		const gchar *msg, gpointer user_data)
{
  if (!strstr (msg, "visible children"))
    fprintf (stderr, "XX %s-WARNING **: %s\n", log_domain, msg);
}
834
#endif
835

Jan Djärv's avatar
Jan Djärv committed
836 837 838 839
/* Make a geometry string and pass that to GTK.  It seems this is the
   only way to get geometry position right if the user explicitly
   asked for a position when starting Emacs.
   F is the frame we shall set geometry for.  */
840

Jan Djärv's avatar
Jan Djärv committed
841
static void
Dmitry Antipov's avatar
Dmitry Antipov committed
842
xg_set_geometry (struct frame *f)
Jan Djärv's avatar
Jan Djärv committed
843
{
844
  if (f->size_hint_flags & (USPosition | PPosition))
845
    {
846
      int scale = xg_get_scale (f);
847
#if ! GTK_CHECK_VERSION (3, 22, 0)
848 849
      if (x_gtk_use_window_move)
	{
850
#endif
851 852 853 854 855 856 857 858 859 860 861
	  /* Handle negative positions without consulting
	     gtk_window_parse_geometry (Bug#25851).  The position will
	     be off by scrollbar width + window manager decorations.  */
	  if (f->size_hint_flags & XNegative)
	    f->left_pos = (x_display_pixel_width (FRAME_DISPLAY_INFO (f))
			   - FRAME_PIXEL_WIDTH (f) + f->left_pos);

	  if (f->size_hint_flags & YNegative)
	    f->top_pos = (x_display_pixel_height (FRAME_DISPLAY_INFO (f))
			  - FRAME_PIXEL_HEIGHT (f) + f->top_pos);

862
	  /* GTK works in scaled pixels, so convert from X pixels.  */
863
	  gtk_window_move (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
864
			   f->left_pos / scale, f->top_pos / scale);
865 866 867

	  /* Reset size hint flags.  */
	  f->size_hint_flags &= ~ (XNegative | YNegative);
868
# if ! GTK_CHECK_VERSION (3, 22, 0)
869 870 871
	}
      else
	{
872 873
          /* GTK works in scaled pixels, so convert from X pixels.  */
	  int left = f->left_pos / scale;
874
	  int xneg = f->size_hint_flags & XNegative;
875
	  int top = f->top_pos / scale;
876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
	  int yneg = f->size_hint_flags & YNegative;
	  char geom_str[sizeof "=x--" + 4 * INT_STRLEN_BOUND (int)];
	  guint id;

	  if (xneg)
	    left = -left;
	  if (yneg)
	    top = -top;

	  sprintf (geom_str, "=%dx%d%c%d%c%d",
		   FRAME_PIXEL_WIDTH (f),
		   FRAME_PIXEL_HEIGHT (f),
		   (xneg ? '-' : '+'), left,
		   (yneg ? '-' : '+'), top);

	  /* Silence warning about visible children.  */
	  id = g_log_set_handler ("Gtk", G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL
				  | G_LOG_FLAG_RECURSION, my_log_handler, NULL);

	  if (!gtk_window_parse_geometry (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
					  geom_str))
	    fprintf (stderr, "Failed to parse: '%s'\n", geom_str);

	  g_log_remove_handler ("Gtk", id);
	}
901
#endif
902
    }
Jan Djärv's avatar
Jan Djärv committed
903 904
}

905 906 907
/* Function to handle resize of our frame.  As we have a Gtk+ tool bar
   and a Gtk+ menu bar, we get resize events for the edit part of the
   frame only.  We let Gtk+ deal with the Gtk+ parts.
Jan Djärv's avatar
Jan Djärv committed
908 909
   F is the frame to resize.
   PIXELWIDTH, PIXELHEIGHT is the new size in pixels.  */
910

Jan Djärv's avatar
Jan Djärv committed
911
void
Dmitry Antipov's avatar
Dmitry Antipov committed
912
xg_frame_resized (struct frame *f, int pixelwidth, int pixelheight)
Jan Djärv's avatar
Jan Djärv committed
913
{
914
  int width, height;
915 916 917

  if (pixelwidth == -1 && pixelheight == -1)
    {
918
      if (FRAME_GTK_WIDGET (f) && gtk_widget_get_mapped (FRAME_GTK_WIDGET (f)))
919 920 921 922
	gdk_window_get_geometry (gtk_widget_get_window (FRAME_GTK_WIDGET (f)),
				 0, 0, &pixelwidth, &pixelheight);
      else
	return;
923
    }
Chong Yidong's avatar
Chong Yidong committed
924

925 926
  width = FRAME_PIXEL_TO_TEXT_WIDTH (f, pixelwidth);
  height = FRAME_PIXEL_TO_TEXT_HEIGHT (f, pixelheight);
927

928 929 930
  frame_size_history_add
    (f, Qxg_frame_resized, width, height, Qnil);

931 932
  if (width != FRAME_TEXT_WIDTH (f)
      || height != FRAME_TEXT_HEIGHT (f)
933 934
      || pixelwidth != FRAME_PIXEL_WIDTH (f)
      || pixelheight != FRAME_PIXEL_HEIGHT (f))
Jan Djärv's avatar
Jan Djärv committed
935
    {
936
      FRAME_RIF (f)->clear_under_internal_border (f);
937
      change_frame_size (f, width, height, 0, 1, 0, 1);
938 939
      SET_FRAME_GARBAGED (f);
      cancel_mouse_face (f);
940 941
    }
}
Jan Djärv's avatar
Jan Djärv committed
942

Paul Eggert's avatar
Paul Eggert committed
943
/* Resize the outer window of frame F after changing the height.
944
   COLUMNS/ROWS is the size the edit area shall have after the resize.  */
945

Jan Djärv's avatar
Jan Djärv committed
946
void
947
xg_frame_set_char_size (struct frame *f, int width, int height)
Jan Djärv's avatar
Jan Djärv committed
948
{
949
  int pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
950
  int pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height);
951 952
  Lisp_Object fullscreen = get_frame_param (f, Qfullscreen);
  gint gwidth, gheight;
953 954
  int totalheight
    = pixelheight + FRAME_TOOLBAR_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f);
Jan D's avatar
Jan D committed
955
  int totalwidth = pixelwidth + FRAME_TOOLBAR_WIDTH (f);
956

957 958 959
  if (FRAME_PIXEL_HEIGHT (f) == 0)
    return;

960 961 962
  gtk_window_get_size (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
		       &gwidth, &gheight);

963
  /* Do this before resize, as we don't know yet if we will be resized.  */
964
  FRAME_RIF (f)->clear_under_internal_border (f);
965

966 967
  totalheight /= xg_get_scale (f);
  totalwidth /= xg_get_scale (f);
Jan D's avatar
Jan D committed
968

969 970
  x_wm_set_size_hint (f, 0, 0);

971 972 973 974 975 976 977 978 979 980 981 982 983
  /* Resize the top level widget so rows and columns remain constant.

     When the frame is fullheight and we only want to change the width
     or it is fullwidth and we only want to change the height we should
     be able to preserve the fullscreen property.  However, due to the
     fact that we have to send a resize request anyway, the window
     manager will abolish it.  At least the respective size should
     remain unchanged but giving the frame back its normal size will
     be broken ... */
  if (EQ (fullscreen, Qfullwidth) && width == FRAME_TEXT_WIDTH (f))
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_1, width, height,
984
	 list2i (gheight, totalheight));
985 986

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
987
			 gwidth, totalheight);
988 989 990 991 992
    }
  else if (EQ (fullscreen, Qfullheight) && height == FRAME_TEXT_HEIGHT (f))
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_2, width, height,
993
	 list2i (gwidth, totalwidth));
994 995

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
996
			 totalwidth, gheight);
997 998 999 1000 1001
    }
  else
    {
      frame_size_history_add
	(f, Qxg_frame_set_char_size_3, width, height,
1002
	 list2i (totalwidth, totalheight));
1003 1004

      gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
1005
			 totalwidth, totalheight);
1006 1007
      fullscreen = Qnil;
    }
1008

1009 1010 1011
  SET_FRAME_GARBAGED (f);
  cancel_mouse_face (f);

1012 1013 1014 1015 1016 1017 1018
  /* We can not call change_frame_size for a mapped frame,
     we can not set pixel width/height either.  The window manager may
     override our resize request, XMonad does this all the time.
     The best we can do is try to sync, so lisp code sees the updated
     size as fast as possible.
     For unmapped windows, we can set rows/cols.  When
     the frame is mapped again we will (hopefully) get the correct size.  */
1019
  if (FRAME_VISIBLE_P (f))
1020 1021 1022 1023 1024
    {
      /* Must call this to flush out events */
      (void)gtk_events_pending ();
      gdk_flush ();
      x_wait_for_event (f, ConfigureNotify);
1025 1026 1027 1028 1029

      if (!NILP (fullscreen))
	/* Try to restore fullscreen state.  */
	{
	  store_frame_param (f, Qfullscreen, fullscreen);
1030
	  gui_set_fullscreen (f, fullscreen, fullscreen);
1031
	}
1032
    }
1033
  else
1034 1035
    adjust_frame_size (f, width, height, 5, 0, Qxg_frame_set_char_size);

1036 1037
}

1038
/* Handle height/width changes (i.e. add/remove/move menu/toolbar).
1039 1040
   The policy is to keep the number of editable lines.  */

1041
#if 0
1042
static void
Dmitry Antipov's avatar
Dmitry Antipov committed
1043
xg_height_or_width_changed (struct frame *f)
1044 1045
{
  gtk_window_resize (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
1046 1047
                     FRAME_TOTAL_PIXEL_WIDTH (f),
                     FRAME_TOTAL_PIXEL_HEIGHT (f));
1048
  f->output_data.x->hint_flags = 0;
1049
  x_wm_set_size_hint (f, 0, 0);
Jan Djärv's avatar
Jan Djärv committed
1050
}
1051
#endif
Jan Djärv's avatar
Jan Djärv committed
1052

1053
/* Convert an X Window WSESC on display DPY to its corresponding GtkWidget.
Jan Djärv's avatar
Jan Djärv committed
1054 1055 1056 1057
   Must be done like this, because GtkWidget:s can have "hidden"
   X Window that aren't accessible.

   Return 0 if no widget match WDESC.  */
1058

Jan Djärv's avatar
Jan Djärv committed
1059
GtkWidget *
1060
xg_win_to_widget (Display *dpy, Window wdesc)
Jan Djärv's avatar
Jan Djärv committed
1061 1062 1063 1064
{
  gpointer gdkwin;
  GtkWidget *gwdesc = 0;

1065
  block_input ();
1066

1067 1068
  gdkwin = gdk_x11_window_lookup_for_display (gdk_x11_lookup_xdisplay (dpy),
                                              wdesc);
Jan Djärv's avatar
Jan Djärv committed
1069 1070 1071 1072
  if (gdkwin)
    {
      GdkEvent event;
      event.any.window = gdkwin;
1073
      event.any.type = GDK_NOTHING;
Jan Djärv's avatar
Jan Djärv committed
1074 1075
      gwdesc = gtk_get_event_widget (&event);
    }
1076

1077
  unblock_input ();
Jan Djärv's avatar
Jan Djärv committed
1078 1079 1080
  return gwdesc;
}

1081
/* Set the background of widget W to PIXEL.  */
1082

Jan Djärv's avatar
Jan Djärv committed
1083
static void
1084
xg_set_widget_bg (struct frame *f, GtkWidget *w, unsigned long pixel)
Jan Djärv's avatar
Jan Djärv committed
1085
{
1086 1087 1088 1089 1090
#ifdef HAVE_GTK3
  XColor xbg;
  xbg.pixel = pixel;
  if (XQueryColor (FRAME_X_DISPLAY (f), FRAME_X_COLORMAP (f), &xbg))
    {
1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103
      const char format[] = "* { background-color: #%02x%02x%02x; }";
      /* The format is always longer than the resulting string.  */
      char buffer[sizeof format];
      int n = snprintf(buffer, sizeof buffer, format,
                       xbg.red >> 8, xbg.green >> 8, xbg.blue >> 8);
      eassert (n > 0);
      eassert (n < sizeof buffer);
      GtkCssProvider *provider = gtk_css_provider_new ();
      gtk_css_provider_load_from_data (provider, buffer, -1, NULL);
      gtk_style_context_add_provider (gtk_widget_get_style_context(w),
                                      GTK_STYLE_PROVIDER (provider),
                                      GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
      g_clear_object (&provider);
1104 1105 1106
    }
#else
  GdkColor bg;
Jan Djärv's avatar
Jan Djärv committed
1107
  GdkColormap *map = gtk_widget_get_colormap (w);
1108 1109 1110
  gdk_colormap_query_color (map, pixel, &bg);
  gtk_widget_modify_bg (FRAME_GTK_WIDGET (f), GTK_STATE_NORMAL, &bg);
#endif
Jan Djärv's avatar
Jan Djärv committed
1111 1112
}

1113 1114 1115 1116 1117 1118 1119 1120 1121
/* Callback called when the gtk theme changes.
   We notify lisp code so it can fix faces used for region for example.  */

static void
style_changed_cb (GObject <