1 /*
2  * Copyright © 2004 Noah Levitt
3  * Copyright © 2007, 2008 Christian Persch
4  * Copyright © 2012 Red Hat, Inc.
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
19  */
20 
21 #include <config.h>
22 
23 #include <stdlib.h>
24 
25 #include <glib/gi18n.h>
26 #include <gtk/gtk.h>
27 
28 #include <gucharmap/gucharmap.h>
29 #include "gucharmap-window.h"
30 
31 #define UI_RESOURCE "/org/gnome/charmap/ui/menus.ui"
32 
33 /* BEGIN HACK
34  *
35  * Gucharmap is a *character map*, not an emoji picker.
36  * Consequently, we want to show character glyphs in black
37  * and white, not colour emojis.
38  *
39  * However, there currently is no way in the pango API to
40  * suppress use of colour fonts.
41  * Internally, cairo *always* (!) calls FT_Load_Glyph with
42  * the FT_LOAD_COLOR flag. Interpose the function and strip
43  * that flag.
44  *
45  * This still doesn't get the desired display since the
46  * emoji colour fonts (Noto Color Emoji) that's hardcoded
47  * (see bug 787365) has greyscale fallback; I see no way
48  * to skip the font altogether.
49  */
50 
51 #include <dlfcn.h>
52 #include <ft2build.h>
53 #include FT_FREETYPE_H
54 
55 _GUCHARMAP_PUBLIC
56 FT_Error
57 FT_Load_Glyph(FT_Face face,
58 	      FT_UInt glyph_index,
59 	      FT_Int32 load_flags);
60 
61 _GUCHARMAP_PUBLIC
62 FT_Error
63 FT_Load_Char(FT_Face face,
64 	     FT_ULong char_code,
65 	     FT_Int32 load_flags);
66 
67 _GUCHARMAP_PUBLIC
68 FT_Error
FT_Load_Glyph(FT_Face face,FT_UInt glyph_index,FT_Int32 load_flags)69 FT_Load_Glyph(FT_Face face,
70 	      FT_UInt glyph_index,
71 	      FT_Int32 load_flags)
72 {
73   static FT_Error (*original)(FT_Face face,
74 			      FT_UInt glyph_index,
75 			      FT_Int32 load_flags) = NULL;
76   if (!original)
77     original = dlsym(RTLD_NEXT, "FT_Load_Glyph");
78 
79   return original(face, glyph_index, load_flags & ~FT_LOAD_COLOR);
80 }
81 
82 _GUCHARMAP_PUBLIC
83 FT_Error
FT_Load_Char(FT_Face face,FT_ULong char_code,FT_Int32 load_flags)84 FT_Load_Char( FT_Face face,
85 	      FT_ULong char_code,
86 	      FT_Int32 load_flags)
87 {
88   static FT_Error (*original)(FT_Face face,
89 			      FT_ULong char_code,
90 			      FT_Int32 load_flags) = NULL;
91   if (!original)
92     original = dlsym(RTLD_NEXT, "FT_Load_Char");
93 
94   return original(face, char_code, load_flags & ~FT_LOAD_COLOR);
95 }
96 
97 /* END HACK */
98 
99 static gboolean
option_version_cb(const gchar * option_name,const gchar * value,gpointer data,GError ** error)100 option_version_cb (const gchar *option_name,
101                    const gchar *value,
102                    gpointer     data,
103                    GError     **error)
104 {
105   g_print ("%s %s\n", _("GNOME Character Map"), VERSION);
106 
107   exit (EXIT_SUCCESS);
108   return FALSE;
109 }
110 
111 static gboolean
option_print_cb(const gchar * option_name,const gchar * value,gpointer data,GError ** error)112 option_print_cb (const gchar *option_name,
113                  const gchar *value,
114                  gpointer     data,
115                  GError     **error)
116 {
117   const char *p;
118 
119   for (p = value; *p; p = g_utf8_next_char (p)) {
120     gunichar c;
121     char utf[7];
122 
123     c = g_utf8_get_char (p);
124     if (c == (gunichar)-1)
125       continue;
126 
127     utf[g_unichar_to_utf8 (c, utf)] = '\0';
128 
129     g_print("%s\tU+%04X\t%s\n",
130             utf, c,
131             gucharmap_get_unicode_name (c));
132   }
133 
134   exit (EXIT_SUCCESS);
135   return FALSE;
136 }
137 
138 static GAction *
get_corresponding_window_action(GtkApplication * app,GAction * action)139 get_corresponding_window_action (GtkApplication *app,
140                                  GAction        *action)
141 {
142   GList *windows = gtk_application_get_windows (app);
143   const char *name;
144 
145   name = g_action_get_name (G_ACTION (action));
146   return g_action_map_lookup_action (G_ACTION_MAP (windows->data), name);
147 }
148 
149 static void
activate_action(GSimpleAction * action,GVariant * parameter,gpointer data)150 activate_action (GSimpleAction *action,
151                  GVariant      *parameter,
152                  gpointer       data)
153 {
154   GAction *win_action = get_corresponding_window_action (GTK_APPLICATION (data),
155                                                          G_ACTION (action));
156   g_action_activate (win_action, parameter);
157 
158   if (parameter)
159     g_action_change_state (G_ACTION (action), parameter);
160 }
161 
162 static void
activate_toggle_action(GSimpleAction * action,GVariant * parameter,gpointer data)163 activate_toggle_action (GSimpleAction *action,
164                         GVariant      *parameter,
165                         gpointer       data)
166 {
167   GVariant *state = g_action_get_state (G_ACTION (action));
168   gboolean value = g_variant_get_boolean (state);
169   GAction *win_action;
170 
171   win_action = get_corresponding_window_action (GTK_APPLICATION (data),
172                                                 G_ACTION (action));
173   g_action_change_state (win_action, g_variant_new_boolean (!value));
174   g_action_change_state (G_ACTION (action), g_variant_new_boolean (!value));
175   g_variant_unref (state);
176 }
177 
178 static void
change_toggle_state(GSimpleAction * action,GVariant * state,gpointer data)179 change_toggle_state (GSimpleAction *action,
180                      GVariant      *state,
181                      gpointer       data)
182 {
183   g_simple_action_set_state (action, state);
184 }
185 
186 static void
activate_close(GSimpleAction * action,GVariant * parameter,gpointer user_data)187 activate_close (GSimpleAction *action,
188                 GVariant      *parameter,
189                 gpointer       user_data)
190 {
191   GList *windows, *l;
192 
193   /* FIXME: use gtk_application_get_active_window() once it exists */
194   windows = gtk_application_get_windows (GTK_APPLICATION (user_data));
195   for (l = windows; l != NULL; l = l->next) {
196     GtkWidget *window = l->data;
197 
198     if (!GTK_IS_APPLICATION_WINDOW (window))
199       continue;
200 
201     gtk_widget_destroy (window);
202     break;
203   }
204 }
205 
206 static void
startup_cb(GApplication * application,gpointer data)207 startup_cb (GApplication *application,
208             gpointer      data)
209 {
210   GtkBuilder *builder = gtk_builder_new ();
211   GMenuModel *model;
212   gboolean show_app_menu;
213 
214   const GActionEntry app_entries[] =
215   {
216     { "group-by", activate_action, "s", "\"script\"", NULL },
217 
218     { "show-only-glyphs-in-font", activate_toggle_action, NULL, "false",
219       change_toggle_state },
220 
221     { "zoom-in", activate_action, NULL, NULL, NULL },
222     { "zoom-out", activate_action, NULL, NULL, NULL },
223     { "normal-size", activate_action, NULL, NULL, NULL },
224 
225     { "find", activate_action, NULL, NULL, NULL },
226 
227     { "help", activate_action, NULL, NULL, NULL },
228     { "about", activate_action, NULL, NULL, NULL },
229     { "close", activate_close, NULL, NULL, NULL },
230   };
231 
232   g_action_map_add_action_entries (G_ACTION_MAP (application),
233                                    app_entries, G_N_ELEMENTS (app_entries),
234                                    application);
235 
236 
237   gtk_builder_add_from_resource (builder, UI_RESOURCE, NULL);
238 
239   /* app menu */
240   g_object_get (gtk_settings_get_default (),
241                 "gtk-shell-shows-app-menu", &show_app_menu,
242                 NULL);
243   if (show_app_menu) {
244     model = G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"));
245     gtk_application_set_app_menu (GTK_APPLICATION (application), model);
246   }
247 
248   /* window menu */
249 
250 #ifdef ENABLE_PRINTING
251   model = G_MENU_MODEL (gtk_builder_get_object (builder, "printing"));
252 
253   g_menu_append (G_MENU (model), _("Page _Setup"), "win.page-setup");
254 /* g_menu_append (G_MENU (model), _("Print Preview"), "win.print-preview"); */
255   g_menu_append (G_MENU (model), _("_Print"), "win.print");
256 #endif
257 
258   model = G_MENU_MODEL (gtk_builder_get_object (builder, "go-chapter"));
259   g_object_set_data (G_OBJECT (application), "go-chapter-menu", model);
260 
261   model = G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"));
262   gtk_application_set_menubar (GTK_APPLICATION (application), model);
263 
264   gtk_application_add_accelerator (GTK_APPLICATION (application),
265                                    "<Primary>Page_Down", "win.next-chapter",
266                                    NULL);
267   gtk_application_add_accelerator (GTK_APPLICATION (application),
268                                    "<Primary>Page_Up", "win.previous-chapter",
269                                    NULL);
270   gtk_application_add_accelerator (GTK_APPLICATION (application),
271                                    "F1", "app.help", NULL);
272   gtk_application_add_accelerator (GTK_APPLICATION (application),
273                                    "<Primary>q", "app.close", NULL);
274   gtk_application_add_accelerator (GTK_APPLICATION (application),
275                                    "<Primary>w", "app.close", NULL);
276 
277 
278   g_object_unref (builder);
279 }
280 
281 static void
gucharmap_activate(GApplication * application,gpointer unused)282 gucharmap_activate (GApplication *application,
283                     gpointer      unused)
284 {
285   GList *windows = gtk_application_get_windows (GTK_APPLICATION (application));
286   gtk_window_present (GTK_WINDOW (windows->data));
287 }
288 
289 int
main(int argc,char ** argv)290 main (int argc, char **argv)
291 {
292   GtkWidget *window;
293   GdkScreen *screen;
294   int monitor;
295   GdkRectangle rect;
296   GError *error = NULL;
297   char *font = NULL;
298   char **remaining = NULL;
299   GtkApplication *application;
300   guint status;
301   GOptionEntry goptions[] =
302   {
303     { "font", 0, 0, G_OPTION_ARG_STRING, &font,
304       N_("Font to start with; ex: 'Serif 27'"), N_("FONT") },
305     { "version", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG,
306       G_OPTION_ARG_CALLBACK, option_version_cb, NULL, NULL },
307     { "print", 'p', 0, G_OPTION_ARG_CALLBACK, option_print_cb,
308       "Print characters in string", "STRING" },
309     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining,
310       NULL, N_("[STRING…]") },
311     { NULL }
312   };
313 
314   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
315   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
316   textdomain (GETTEXT_PACKAGE);
317 
318   /* Not interested in silly debug spew polluting the journal, bug #749195 */
319   if (g_getenv ("G_ENABLE_DIAGNOSTIC") == NULL)
320     g_setenv ("G_ENABLE_DIAGNOSTIC", "0", TRUE);
321 
322   /* Set programme name explicitly (see bug #653115) */
323   g_set_prgname("gucharmap");
324 
325   if (!gtk_init_with_args (&argc, &argv, NULL, goptions, GETTEXT_PACKAGE, &error))
326     {
327       g_printerr ("%s\n", error->message);
328       g_error_free (error);
329 
330       exit (1);
331     }
332 
333   g_set_application_name (_("Character Map"));
334   gtk_window_set_default_icon_name (GUCHARMAP_ICON_NAME);
335 
336   application = gtk_application_new ("org.gnome.Charmap",
337                                      G_APPLICATION_NON_UNIQUE);
338   g_signal_connect (application, "startup", G_CALLBACK (startup_cb), NULL);
339   g_signal_connect (application, "activate",
340                     G_CALLBACK (gucharmap_activate), NULL);
341 
342   g_application_register (G_APPLICATION (application), NULL, NULL);
343 
344   /* Gucharmap doesn't work right with the dark theme, see #741939.
345    * Apparently this got fixed in gtk+ some time before 3.22, so
346    * only work around this on older versions.
347    */
348   if (gtk_check_version (3, 22, 0) != NULL /* < 3.22.0 */)
349     g_object_set (gtk_settings_get_default (), "gtk-application-prefer-dark-theme", FALSE, NULL);
350 
351   window = gucharmap_window_new (application);
352 
353   screen = gtk_window_get_screen (GTK_WINDOW (window));
354   monitor = gdk_screen_get_monitor_at_point (screen, 0, 0);
355 #if GTK_CHECK_VERSION (3, 3, 5)
356   gdk_screen_get_monitor_workarea (screen, monitor, &rect);
357 #else
358   gdk_screen_get_monitor_geometry (screen, monitor, &rect);
359 #endif
360   gtk_window_set_default_size (GTK_WINDOW (window), rect.width * 9/16, rect.height * 9/16);
361 
362   if (font)
363     {
364       gucharmap_window_set_font (GUCHARMAP_WINDOW (window), font);
365       g_free (font);
366     }
367 
368   gtk_window_present (GTK_WINDOW (window));
369 
370   if (remaining) {
371     char *str = g_strjoinv (" ", remaining);
372     gucharmap_window_search (GUCHARMAP_WINDOW (window), str);
373     g_free (str);
374     g_strfreev (remaining);
375   }
376 
377   status = g_application_run (G_APPLICATION (application), argc, argv);
378   g_object_unref (application);
379 
380   return status;
381 }
382