 a2c54c739e
			
		
	
	a2c54c739e
	
	
	
		
			
			In case of an offscreen window find its onscreen embedder, and NULL
any GdkWindowImpl that's not quartz, checking all return values.
This replaces 16ded683, removing its unnecessary search_for_nearest
functions.
Any unchecked cast of a GdkWindowImpl to GdkWindowImplQuartz risks at
least getting an invalid member reference, not just the ones in
gdk_quartz_window_get_foo, and all calls to gdk_quartz_window_get_foo need
to be checked, not just Gtk's internal calls. The motivation was that
transient_for was getting set on a Gimp offscreen window and that caused a
crash in raise_transient.
		
	
		
			
				
	
	
		
			493 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * gtkimmodulequartz
 | |
|  * Copyright (C) 2011 Hiroyuki Yamamoto
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library 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
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * $Id:$
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| #include <string.h>
 | |
| 
 | |
| #include <gtk/gtk.h>
 | |
| #include "gtk/gtkintl.h"
 | |
| #include "gtk/gtkimmodule.h"
 | |
| 
 | |
| #include <AvailabilityMacros.h>
 | |
| 
 | |
| #define GTK_COMPILATION 1 // For gdkquartz-gtk-only.h
 | |
| 
 | |
| #include "gdk/quartz/gdkinternal-quartz.h"
 | |
| #include "gdk/quartz/gdkquartz-cocoa-access.h"
 | |
| #include "gdk/quartz/GdkQuartzView.h"
 | |
| 
 | |
| #define GTK_IM_CONTEXT_TYPE_QUARTZ (type_quartz)
 | |
| #define GTK_IM_CONTEXT_QUARTZ(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_IM_CONTEXT_TYPE_QUARTZ, GtkIMContextQuartz))
 | |
| #define GTK_IM_CONTEXT_QUARTZ_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_IM_CONTEXT_TYPE_QUARTZ, GtkIMContextQuartzClass))
 | |
| 
 | |
| #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200
 | |
| #define NS_EVENT_KEY_DOWN NSKeyDown
 | |
| #else
 | |
| #define NS_EVENT_KEY_DOWN NSEventTypeKeyDown
 | |
| #endif
 | |
| 
 | |
| typedef struct _GtkIMContextQuartz
 | |
| {
 | |
|   GtkIMContext parent;
 | |
|   GtkIMContext *slave;
 | |
|   GdkWindow *client_window;
 | |
|   gchar *preedit_str;
 | |
|   unsigned int cursor_index;
 | |
|   unsigned int selected_len;
 | |
|   GdkRectangle *cursor_rect;
 | |
|   gboolean focused;
 | |
| } GtkIMContextQuartz;
 | |
| 
 | |
| typedef struct _GtkIMContextQuartzClass
 | |
| {
 | |
|   GtkIMContextClass parent_class;
 | |
| } GtkIMContextQuartzClass;
 | |
| 
 | |
| GType type_quartz = 0;
 | |
| static GObjectClass *parent_class;
 | |
| 
 | |
| static const GtkIMContextInfo imquartz_info =
 | |
| {
 | |
|   "quartz",
 | |
|   NC_("input method menu", "Mac OS X Quartz"),
 | |
|   GETTEXT_PACKAGE,
 | |
|   GTK_LOCALEDIR,
 | |
|   "ja:ko:zh:*",
 | |
| };
 | |
| 
 | |
| static const GtkIMContextInfo *info_list[] =
 | |
| {
 | |
|   &imquartz_info,
 | |
| };
 | |
| 
 | |
| #ifndef INCLUDE_IM_quartz
 | |
| #define MODULE_ENTRY(type,function) G_MODULE_EXPORT type im_module_ ## function
 | |
| #else
 | |
| #define MODULE_ENTRY(type, function) type _gtk_immodule_quartz_ ## function
 | |
| #endif
 | |
| 
 | |
| static void
 | |
| quartz_get_preedit_string (GtkIMContext *context,
 | |
|                            gchar **str,
 | |
|                            PangoAttrList **attrs,
 | |
|                            gint *cursor_pos)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
| 
 | |
|   GTK_NOTE (MISC, g_print ("quartz_get_preedit_string\n"));
 | |
| 
 | |
|   if (str)
 | |
|     *str = qc->preedit_str ? g_strdup (qc->preedit_str) : g_strdup ("");
 | |
| 
 | |
|   if (attrs)
 | |
|     {
 | |
|       *attrs = pango_attr_list_new ();
 | |
|       int len = g_utf8_strlen (*str, -1);
 | |
|       gchar *ch = *str;
 | |
|       if (len > 0)
 | |
|         {
 | |
|           PangoAttribute *attr;
 | |
|           int i = 0;
 | |
|           for (;;)
 | |
|             {
 | |
|               gchar *s = ch;
 | |
|               ch = g_utf8_next_char (ch);
 | |
| 
 | |
|               if (i >= qc->cursor_index &&
 | |
| 		  i < qc->cursor_index + qc->selected_len)
 | |
|                 attr = pango_attr_underline_new (PANGO_UNDERLINE_DOUBLE);
 | |
|               else
 | |
|                 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
 | |
| 
 | |
|               attr->start_index = s - *str;
 | |
|               if (!*ch)
 | |
|                 attr->end_index = attr->start_index + strlen (s);
 | |
|               else
 | |
|                 attr->end_index = ch - *str;
 | |
| 
 | |
|               pango_attr_list_change (*attrs, attr);
 | |
| 
 | |
|               if (!*ch)
 | |
|                 break;
 | |
|               i++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|   if (cursor_pos)
 | |
|     *cursor_pos = qc->cursor_index;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| output_result (GtkIMContext *context,
 | |
|                GdkWindow *win)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
|   gboolean retval = FALSE;
 | |
|   int fixed_str_replace_len;
 | |
|   gchar *fixed_str, *marked_str;
 | |
| 
 | |
|   fixed_str_replace_len = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (win),
 | |
|       TIC_INSERT_TEXT_REPLACE_LEN));
 | |
|   fixed_str = g_strdup (g_object_get_data (G_OBJECT (win), TIC_INSERT_TEXT));
 | |
|   marked_str = g_strdup (g_object_get_data (G_OBJECT (win), TIC_MARKED_TEXT));
 | |
|   if (fixed_str)
 | |
|     {
 | |
|       GTK_NOTE (MISC, g_print ("tic-insert-text: %s\n", fixed_str));
 | |
|       g_free (qc->preedit_str);
 | |
|       qc->preedit_str = NULL;
 | |
|       g_object_set_data (G_OBJECT (win), TIC_INSERT_TEXT, NULL);
 | |
|       if (fixed_str_replace_len)
 | |
|         {
 | |
|           gboolean retval;
 | |
|           g_object_set_data (G_OBJECT (win), TIC_INSERT_TEXT_REPLACE_LEN, 0);
 | |
|           g_signal_emit_by_name (context, "delete-surrounding",
 | |
|               -fixed_str_replace_len, fixed_str_replace_len, &retval);
 | |
|         }
 | |
|       g_signal_emit_by_name (context, "commit", fixed_str);
 | |
|       g_signal_emit_by_name (context, "preedit_changed");
 | |
| 
 | |
|       unsigned int filtered =
 | |
| 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
 | |
| 						GIC_FILTER_KEY));
 | |
|       GTK_NOTE (MISC, g_print ("filtered, %d\n", filtered));
 | |
|       if (filtered)
 | |
|         retval = TRUE;
 | |
|       else
 | |
|         retval = FALSE;
 | |
|     }
 | |
|   if (marked_str)
 | |
|     {
 | |
|       GTK_NOTE (MISC, g_print ("tic-marked-text: %s\n", marked_str));
 | |
|       qc->cursor_index =
 | |
| 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
 | |
| 						TIC_SELECTED_POS));
 | |
|       qc->selected_len =
 | |
| 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
 | |
| 						TIC_SELECTED_LEN));
 | |
|       g_free (qc->preedit_str);
 | |
|       qc->preedit_str = g_strdup (marked_str);
 | |
|       g_object_set_data (G_OBJECT (win), TIC_MARKED_TEXT, NULL);
 | |
|       g_signal_emit_by_name (context, "preedit_changed");
 | |
|       retval = TRUE;
 | |
|     }
 | |
|   if (!fixed_str && !marked_str)
 | |
|     {
 | |
|       unsigned int filtered =
 | |
| 	  GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
 | |
| 					       GIC_FILTER_KEY));
 | |
|       if (filtered)
 | |
|         retval = TRUE;
 | |
|       if (qc->preedit_str && strlen (qc->preedit_str) > 0)
 | |
|         retval = TRUE;
 | |
|     }
 | |
|   g_free (fixed_str);
 | |
|   g_free (marked_str);
 | |
|   return retval;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| quartz_filter_keypress (GtkIMContext *context,
 | |
|                         GdkEventKey *event)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
|   gboolean retval;
 | |
|   NSView *nsview;
 | |
|   GdkWindow *win;
 | |
| 
 | |
|   GTK_NOTE (MISC, g_print ("quartz_filter_keypress\n"));
 | |
| 
 | |
|   if (!GDK_IS_QUARTZ_WINDOW (qc->client_window))
 | |
|     return FALSE;
 | |
| 
 | |
|   NSEvent *nsevent = gdk_quartz_event_get_nsevent ((GdkEvent *)event);
 | |
| 
 | |
|   if (!nsevent)
 | |
|     {
 | |
|       if (event->hardware_keycode == 0 && event->keyval == 0xffffff)
 | |
|         /* update text input changes by mouse events */
 | |
|         return output_result (context, event->window);
 | |
|       else
 | |
|         return gtk_im_context_filter_keypress (qc->slave, event);
 | |
|     }
 | |
| 
 | |
|   nsview = gdk_quartz_window_get_nsview (qc->client_window);
 | |
| 
 | |
|   win = (GdkWindow *)[(GdkQuartzView *)[[nsevent window] contentView] gdkWindow];
 | |
|   GTK_NOTE (MISC, g_print ("client_window: %p, win: %p, nsview: %p\n",
 | |
|                            qc->client_window, win, nsview));
 | |
| 
 | |
|   if (event->type == GDK_KEY_RELEASE)
 | |
|     return FALSE;
 | |
| 
 | |
|   if (event->hardware_keycode == 55)	/* Command */
 | |
|     return FALSE;
 | |
| 
 | |
|   if (event->hardware_keycode == 53) /* Escape */
 | |
|     return FALSE;
 | |
| 
 | |
|   NSEventType etype = [nsevent type];
 | |
|   if (etype == NS_EVENT_KEY_DOWN)
 | |
|     {
 | |
|        g_object_set_data (G_OBJECT (win), TIC_IN_KEY_DOWN,
 | |
|                                           GUINT_TO_POINTER (TRUE));
 | |
|        [nsview keyDown: nsevent];
 | |
|     }
 | |
|   /* JIS_Eisu || JIS_Kana */
 | |
|   if (event->hardware_keycode == 102 || event->hardware_keycode == 104)
 | |
|     return FALSE;
 | |
| 
 | |
|   retval = output_result(context, win);
 | |
|   g_object_set_data (G_OBJECT (win), TIC_IN_KEY_DOWN,
 | |
|                                      GUINT_TO_POINTER (FALSE));
 | |
|   GTK_NOTE (MISC, g_print ("quartz_filter_keypress done\n"));
 | |
| 
 | |
|   return retval;
 | |
| }
 | |
| 
 | |
| static void
 | |
| discard_preedit (GtkIMContext *context)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
| 
 | |
|   if (!qc->client_window)
 | |
|     return;
 | |
| 
 | |
|   if (!GDK_IS_QUARTZ_WINDOW (qc->client_window))
 | |
|     return;
 | |
| 
 | |
|   NSView *nsview = gdk_quartz_window_get_nsview (qc->client_window);
 | |
|   if (!nsview)
 | |
|     return;
 | |
| 
 | |
|   /* reset any partial input for this NSView */
 | |
|   [(GdkQuartzView *)nsview unmarkText];
 | |
| #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
 | |
|   NSInputManager *currentInputManager = [NSInputManager currentInputManager];
 | |
|   [currentInputManager markedTextAbandoned:nsview];
 | |
| #else
 | |
|   [[NSTextInputContext currentInputContext] discardMarkedText];
 | |
| #endif
 | |
|   if (qc->preedit_str && strlen (qc->preedit_str) > 0)
 | |
|     {
 | |
|       g_signal_emit_by_name (context, "commit", qc->preedit_str);
 | |
| 
 | |
|       g_free (qc->preedit_str);
 | |
|       qc->preedit_str = NULL;
 | |
|       g_signal_emit_by_name (context, "preedit_changed");
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_reset (GtkIMContext *context)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("quartz_reset\n"));
 | |
|   discard_preedit (context);
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_set_client_window (GtkIMContext *context, GdkWindow *window)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
| 
 | |
|   GTK_NOTE (MISC, g_print ("quartz_set_client_window: %p\n", window));
 | |
| 
 | |
|   qc->client_window = window;
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_focus_in (GtkIMContext *context)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("quartz_focus_in\n"));
 | |
| 
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
|   qc->focused = TRUE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_focus_out (GtkIMContext *context)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("quartz_focus_out\n"));
 | |
| 
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
|   qc->focused = FALSE;
 | |
| 
 | |
|   /* Commit any partially built strings or it'll mess up other GTK+ widgets in the window */
 | |
|   discard_preedit (context);
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_set_cursor_location (GtkIMContext *context, GdkRectangle *area)
 | |
| {
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
 | |
|   gint x, y;
 | |
|   NSView *nsview;
 | |
|   GdkWindow *win;
 | |
| 
 | |
|   GTK_NOTE (MISC, g_print ("quartz_set_cursor_location\n"));
 | |
| 
 | |
|   if (!qc->client_window)
 | |
|     return;
 | |
| 
 | |
|   if (!qc->focused)
 | |
|     return;
 | |
| 
 | |
|   qc->cursor_rect->x = area->x;
 | |
|   qc->cursor_rect->y = area->y;
 | |
|   qc->cursor_rect->width = area->width;
 | |
|   qc->cursor_rect->height = area->height;
 | |
| 
 | |
|   gdk_window_get_origin (qc->client_window, &x, &y);
 | |
| 
 | |
|   qc->cursor_rect->x = area->x + x;
 | |
|   qc->cursor_rect->y = area->y + y;
 | |
| 
 | |
|   if (!GDK_IS_QUARTZ_WINDOW (qc->client_window))
 | |
|     return;
 | |
| 
 | |
|   nsview = gdk_quartz_window_get_nsview (qc->client_window);
 | |
|   if (nsview == NULL)
 | |
|     return;
 | |
| 
 | |
|   win = (GdkWindow *)[(GdkQuartzView*)nsview gdkWindow];
 | |
|   if (win == NULL)
 | |
|     {
 | |
|       g_warning ("quartz_set_cursor_location received NULL gdkWindow");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   g_object_set_data (G_OBJECT (win), GIC_CURSOR_RECT, qc->cursor_rect);
 | |
| }
 | |
| 
 | |
| static void
 | |
| quartz_set_use_preedit (GtkIMContext *context, gboolean use_preedit)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("quartz_set_use_preedit: %d\n", use_preedit));
 | |
| }
 | |
| 
 | |
| static void
 | |
| commit_cb (GtkIMContext *context, const gchar *str, GtkIMContextQuartz *qc)
 | |
| {
 | |
|   g_signal_emit_by_name (qc, "commit", str);
 | |
| }
 | |
| 
 | |
| static void
 | |
| imquartz_finalize (GObject *obj)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("imquartz_finalize\n"));
 | |
| 
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (obj);
 | |
|   g_free (qc->preedit_str);
 | |
|   qc->preedit_str = NULL;
 | |
|   g_free (qc->cursor_rect);
 | |
|   qc->cursor_rect = NULL;
 | |
| 
 | |
|   g_signal_handlers_disconnect_by_func (qc->slave, (gpointer)commit_cb, qc);
 | |
|   g_object_unref (qc->slave);
 | |
| 
 | |
|   parent_class->finalize (obj);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_im_context_quartz_class_init (GtkIMContextClass *klass)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("gtk_im_context_quartz_class_init\n"));
 | |
| 
 | |
|   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | |
|   parent_class = g_type_class_peek_parent (klass);
 | |
| 
 | |
|   klass->get_preedit_string = quartz_get_preedit_string;
 | |
|   klass->filter_keypress = quartz_filter_keypress;
 | |
|   klass->reset = quartz_reset;
 | |
|   klass->set_client_window = quartz_set_client_window;
 | |
|   klass->focus_in = quartz_focus_in;
 | |
|   klass->focus_out = quartz_focus_out;
 | |
|   klass->set_cursor_location = quartz_set_cursor_location;
 | |
|   klass->set_use_preedit = quartz_set_use_preedit;
 | |
| 
 | |
|   object_class->finalize = imquartz_finalize;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_im_context_quartz_init (GtkIMContext *im_context)
 | |
| {
 | |
|   GTK_NOTE (MISC, g_print ("gtk_im_context_quartz_init\n"));
 | |
| 
 | |
|   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (im_context);
 | |
|   qc->preedit_str = g_strdup ("");
 | |
|   qc->cursor_index = 0;
 | |
|   qc->selected_len = 0;
 | |
|   qc->cursor_rect = g_malloc (sizeof (GdkRectangle));
 | |
|   qc->focused = FALSE;
 | |
| 
 | |
|   qc->slave = g_object_new (GTK_TYPE_IM_CONTEXT_SIMPLE, NULL);
 | |
|   g_signal_connect (G_OBJECT (qc->slave), "commit", G_CALLBACK (commit_cb), qc);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_im_context_quartz_register_type (GTypeModule *module)
 | |
| {
 | |
|   const GTypeInfo object_info =
 | |
|   {
 | |
|     sizeof (GtkIMContextQuartzClass),
 | |
|     (GBaseInitFunc) NULL,
 | |
|     (GBaseFinalizeFunc) NULL,
 | |
|     (GClassInitFunc) gtk_im_context_quartz_class_init,
 | |
|     NULL,           /* class_finalize */
 | |
|     NULL,           /* class_data */
 | |
|     sizeof (GtkIMContextQuartz),
 | |
|     0,
 | |
|     (GInstanceInitFunc) gtk_im_context_quartz_init,
 | |
|   };
 | |
| 
 | |
|   type_quartz =
 | |
|     g_type_module_register_type (module,
 | |
|                                  GTK_TYPE_IM_CONTEXT,
 | |
|                                  "GtkIMContextQuartz",
 | |
|                                  &object_info, 0);
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (void, init) (GTypeModule * module)
 | |
| {
 | |
|   gtk_im_context_quartz_register_type (module);
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (void, exit) (void)
 | |
| {
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (void, list) (const GtkIMContextInfo *** contexts, int *n_contexts)
 | |
| {
 | |
|   *contexts = info_list;
 | |
|   *n_contexts = G_N_ELEMENTS (info_list);
 | |
| }
 | |
| 
 | |
| MODULE_ENTRY (GtkIMContext *, create) (const gchar * context_id)
 | |
| {
 | |
|   g_return_val_if_fail (context_id, NULL);
 | |
| 
 | |
|   if (!strcmp (context_id, "quartz"))
 | |
|     {
 | |
|       GTK_NOTE (MISC, g_print ("immodule_quartz create\n"));
 | |
|       return g_object_new (type_quartz, NULL);
 | |
|     }
 | |
|   else
 | |
|     return NULL;
 | |
| }
 |