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