1 /**************************************************************************/
2 /*  Klavaro - a flexible touch typing tutor                               */
3 /*  Copyright (C) from 2005 until 2008 Felipe Castro                      */
4 /*  Copyright (C) from 2009 until 2019 The Free Software Foundation       */
5 /*                                                                        */
6 /*  This program is free software, licensed under the terms of the GNU    */
7 /*  General Public License as published by the Free Software Foundation,  */
8 /*  either version 3 of the License, or (at your option) any later        */
9 /*  version. You should have received a copy of the GNU General Public    */
10 /*  License along with this program. If not,                              */
11 /*  see <https://www.gnu.org/licenses/>.                                  */
12 /**************************************************************************/
13 
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <glib.h>
18 #include <glib/gstdio.h>
19 #include <pango/pango-attributes.h>
20 #include <gtk/gtk.h>
21 #include <curl/curl.h>
22 
23 #include "auxiliar.h"
24 #include "callbacks.h"
25 #include "translation.h"
26 #include "keyboard.h"
27 #include "tutor.h"
28 #include "accuracy.h"
29 #include "top10.h"
30 #include "main.h"
31 
32 /*******************************************************************************
33  * Variables
34  */
35 GtkBuilder *gui;
36 
37 gchar *KEYB_CUSTOM;
38 gchar *KEYB_EDIT;
39 gchar *OTHER_DEFAULT;
40 
41 static GKeyFile *preferences = NULL;
42 static GKeyFile *altcolor = NULL;
43 static gboolean curl_ok;
44 static gboolean velo_txt;
45 static struct
46 {
47 	gchar *user;
48 	gchar *stats;
49 	gchar *data;
50 	gchar *score;
51 } path;
52 
53 /*******************************************************************************
54  * Interface functions
55  */
56 gchar *
main_path_user()57 main_path_user ()
58 {
59 	return (path.user);
60 }
61 
62 gchar *
main_path_stats()63 main_path_stats ()
64 {
65 	return (path.stats);
66 }
67 
68 gchar *
main_path_data()69 main_path_data ()
70 {
71 	return (path.data);
72 }
73 
74 gchar *
main_path_score()75 main_path_score ()
76 {
77 	return (path.score);
78 }
79 
80 gboolean
main_curl_ok()81 main_curl_ok ()
82 {
83 	return (curl_ok);
84 }
85 
86 gboolean
main_velo_txt()87 main_velo_txt ()
88 {
89 	return (velo_txt);
90 }
91 
92 gboolean
main_preferences_exist(gchar * group,gchar * key)93 main_preferences_exist (gchar * group, gchar * key)
94 {
95 	return (g_key_file_has_key (preferences, group, key, NULL));
96 }
97 
98 void
main_preferences_remove(gchar * group,gchar * key)99 main_preferences_remove (gchar * group, gchar * key)
100 {
101 	g_key_file_remove_key (preferences, group, key, NULL);
102 }
103 
104 gchar *
main_preferences_get_string(gchar * group,gchar * key)105 main_preferences_get_string (gchar * group, gchar * key)
106 {
107 	return (g_key_file_get_string (preferences, group, key, NULL));
108 }
109 
110 void
main_preferences_set_string(gchar * group,gchar * key,gchar * value)111 main_preferences_set_string (gchar * group, gchar * key, gchar * value)
112 {
113 	g_key_file_set_string (preferences, group, key, value);
114 }
115 
116 gint
main_preferences_get_int(gchar * group,gchar * key)117 main_preferences_get_int (gchar * group, gchar * key)
118 {
119 	return (g_key_file_get_integer (preferences, group, key, NULL));
120 }
121 
122 void
main_preferences_set_int(gchar * group,gchar * key,gint value)123 main_preferences_set_int (gchar * group, gchar * key, gint value)
124 {
125 	g_key_file_set_integer (preferences, group, key, value);
126 }
127 
128 gboolean
main_preferences_get_boolean(gchar * group,gchar * key)129 main_preferences_get_boolean (gchar * group, gchar * key)
130 {
131 	return (g_key_file_get_boolean (preferences, group, key, NULL));
132 }
133 
134 void
main_preferences_set_boolean(gchar * group,gchar * key,gboolean value)135 main_preferences_set_boolean (gchar * group, gchar * key, gboolean value)
136 {
137 	g_key_file_set_boolean (preferences, group, key, value);
138 }
139 
140 gboolean
main_altcolor_exist(gchar * group,gchar * key)141 main_altcolor_exist (gchar * group, gchar * key)
142 {
143 	return (g_key_file_has_key (altcolor, group, key, NULL));
144 }
145 
146 gchar *
main_altcolor_get_string(gchar * group,gchar * key)147 main_altcolor_get_string (gchar * group, gchar * key)
148 {
149 	return (g_key_file_get_string (altcolor, group, key, NULL));
150 }
151 
152 
153 gboolean
main_altcolor_get_boolean(gchar * group,gchar * key)154 main_altcolor_get_boolean (gchar * group, gchar * key)
155 {
156 	return (g_key_file_get_boolean (altcolor, group, key, NULL));
157 }
158 
159 void
main_altcolor_set_boolean(gchar * group,gchar * key,gboolean value)160 main_altcolor_set_boolean (gchar * group, gchar * key, gboolean value)
161 {
162 	g_key_file_set_boolean (altcolor, group, key, value);
163 }
164 
165 void
main_preferences_save()166 main_preferences_save ()
167 {
168 	gchar *tmp_name;
169 	FILE *fh;
170 
171 	/* Save preferences
172 	 */
173 	assert_user_dir ();
174 	tmp_name = g_build_filename (main_path_user (), "preferences.ini", NULL);
175 	fh = (FILE *) g_fopen (tmp_name, "w");
176 	if (fh == NULL)
177 		g_warning ("couldn't save your preferences at the user folder:\n %s", tmp_name);
178 	else
179 	{
180 		g_free (tmp_name);
181 		tmp_name = NULL;
182 		tmp_name = g_key_file_to_data (preferences, NULL, NULL);
183 		if (tmp_name != NULL)
184 			fputs (tmp_name, fh);
185 		else
186 			g_warning ("no preferences to be saved!");
187 		fclose (fh);
188 	}
189 	g_free (tmp_name);
190 
191 	/* Save altcolor
192 	 */
193 	tmp_name = g_build_filename (main_path_user (), "altcolor.ini", NULL);
194 	fh = (FILE *) g_fopen (tmp_name, "w");
195 	if (fh == NULL)
196 		g_warning ("couldn't save your altcolor file at the user folder:\n %s", tmp_name);
197 	else
198 	{
199 		g_free (tmp_name);
200 		tmp_name = NULL;
201 		tmp_name = g_key_file_to_data (altcolor, NULL, NULL);
202 		if (tmp_name != NULL)
203 			fputs (tmp_name, fh);
204 		else
205 			g_warning ("no altcolors to be saved!");
206 		fclose (fh);
207 	}
208 	g_free (tmp_name);
209 }
210 
211 /*
212  * End of interface, start of private functions
213  */
214 
215 /**********************************************************************
216  * Initialize the value of the global variables
217  */
218 static void
main_initialize_global_variables()219 main_initialize_global_variables ()
220 {
221 	gchar *tmp;
222 	FILE *fh;
223 
224 	/* Set the home user dir
225 	 */
226 	path.user = g_build_filename (g_get_user_config_dir (), "klavaro", NULL);
227 	if (!g_file_test (path.user, G_FILE_TEST_IS_DIR))
228 	{
229 		if (g_mkdir_with_parents (path.user, DIR_PERM) == -1)
230 			g_error ("Sorry, not able to create the user config dir: %s", path.user);
231 	}
232 
233 	/* Set the home user stats dir (~/.local/share dir)
234 	 */
235 	if (UNIX_OK)
236 		path.stats = g_build_filename (g_get_user_data_dir (), "klavaro", NULL);
237 	else
238 		path.stats = g_build_filename (g_get_user_config_dir (), "klavaro", NULL);
239 	if (!g_file_test (path.stats, G_FILE_TEST_IS_DIR))
240 	{
241 		g_mkdir_with_parents (path.stats, DIR_PERM);
242 	}
243 
244 	/* Get a valid data path.
245 	 * First searches the path at the source directory.
246 	 */
247 	path.data = g_build_filename ("..", "data", NULL);
248 	tmp = g_build_filename (path.data, "basic_lessons.txt", NULL);
249 	if (!g_file_test (tmp, G_FILE_TEST_EXISTS))
250 	{
251 		g_free (path.data);
252 		g_free (tmp);
253 		path.data = g_build_filename (PACKAGE_DATA_DIR, PACKAGE, NULL);
254 		tmp = g_build_filename (path.data, "basic_lessons.txt", NULL);
255 	}
256 	if (! g_file_test (tmp, G_FILE_TEST_EXISTS))
257 		g_error ("couldn't find a data file at the data path:\n %s", tmp);
258 	g_free (tmp);
259 
260 	/* Get a valid scoring path.
261 	 */
262 	path.score = g_build_filename (path.stats, "ksc", NULL);
263 	if (!g_file_test (path.score, G_FILE_TEST_IS_DIR))
264 	{
265 		g_mkdir_with_parents (path.score, DIR_PERM);
266 	}
267 
268 	/* Retrieve saved preferences or initial one
269 	 */
270 	preferences = g_key_file_new ();
271 	tmp = g_build_filename (main_path_user (), "preferences.ini", NULL);
272 	if (!g_file_test (tmp, G_FILE_TEST_EXISTS))
273 	{
274 		g_free (tmp);
275 		tmp = g_strdup ("/etc/klavaro/preferences.ini");
276 	}
277 	g_key_file_load_from_file (preferences, tmp, G_KEY_FILE_NONE, NULL);
278 	g_free (tmp);
279 
280 	/* Retrieve saved alternative colors or initial ones
281 	 */
282 	altcolor = g_key_file_new ();
283 	tmp = g_build_filename (main_path_user (), "altcolor.ini", NULL);
284 	if (!g_file_test (tmp, G_FILE_TEST_EXISTS))
285 	{
286 		g_free (tmp);
287 		tmp = g_build_filename ("/etc/klavaro/altcolor.ini", NULL);
288 	}
289 	if (!g_file_test (tmp, G_FILE_TEST_EXISTS))
290 	{
291 		g_free (tmp);
292 		tmp = g_build_filename (main_path_data (), "altcolor.ini", NULL);
293 	}
294 	g_key_file_load_from_file (altcolor, tmp, G_KEY_FILE_NONE, NULL);
295 	g_free (tmp);
296 
297 	/* Other initializations
298 	 */
299 	trans_init_lang_name_code ();
300 	trans_init_language_env ();
301 	srand (time (0));
302 	tutor_init_timers ();
303 
304 	KEYB_CUSTOM = g_strdup (_("(Custom)"));
305 	KEYB_EDIT = g_strdup (_("(Edit custom)"));
306 	OTHER_DEFAULT = g_strdup (_("(Default)"));
307 }
308 
309 static void
main_gtkbuilder_translation_workaround()310 main_gtkbuilder_translation_workaround ()
311 {
312 	gchar *lb;
313 	GSList *ls;
314 	GSList *it;
315 	GtkWidget *wg;
316 
317 	ls = gtk_builder_get_objects (gui);
318 	it = ls;
319 	do
320 	{
321 		if (G_OBJECT_TYPE (it->data) == GTK_TYPE_LABEL)
322 		{
323 			lb = (gchar *) gtk_label_get_label (GTK_LABEL (it->data));
324 			if (lb[0] != '\0')
325 				gtk_label_set_text_with_mnemonic (GTK_LABEL (it->data), _(lb));
326 		}
327 		else if (G_OBJECT_TYPE (it->data) == GTK_TYPE_ENTRY)
328 		{
329 			lb = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (it->data), GTK_ENTRY_ICON_SECONDARY);
330 			if (lb)
331 			{
332 				if (lb[0] != '\0')
333 					gtk_entry_set_icon_tooltip_text (GTK_ENTRY (it->data), GTK_ENTRY_ICON_SECONDARY, _(lb));
334 			}
335 		}
336 		else if (G_OBJECT_TYPE (it->data) == GTK_TYPE_WINDOW)
337 			gtk_window_set_title (GTK_WINDOW (it->data), _(gtk_window_get_title (GTK_WINDOW (it->data))));
338 
339 		if (GTK_IS_WIDGET (it->data))
340 		{
341 			wg = GTK_WIDGET (it->data);
342 			if (gtk_widget_get_has_tooltip (wg))
343 				gtk_widget_set_tooltip_text (wg, _(gtk_widget_get_tooltip_text (wg)));
344 		}
345 
346 		it = g_slist_next (it);
347 	} while (it);
348 
349 	g_slist_free (ls);
350 }
351 
352 /*******************************************************************************
353  * Initialize some interface widgets
354  */
355 static void
main_window_init()356 main_window_init ()
357 {
358 	gchar *tmp = NULL;
359 	gchar *ttip = NULL;
360 	PangoAttrList *palist;
361 
362 	/* Workaround to make GtkBuilder translate all text, on Windows :-(
363 	 */
364 	if (! UNIX_OK)
365 		main_gtkbuilder_translation_workaround ();
366 
367 	/* Set the language
368 	 */
369 	trans_set_combo_language ();
370 
371 	/* Set keyboard
372 	 */
373 	keyb_create_virtual_keys ();
374 	hints_init ();
375 
376 	/* Set if speech is enabled
377 	 */
378 	callbacks_shield_set (TRUE);
379 	if (!main_preferences_exist ("interface", "speech"))
380 		main_preferences_set_boolean ("interface", "speech", TRUE);
381 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("checkbutton_speech")),
382 		       	main_preferences_get_boolean ("interface", "speech"));
383 	tmp = g_strdup (dngettext (PACKAGE, "Dictation mode (depends on this speech synthesizer: %s)",
384 					    "Dictation mode (depends on one of these speech synthesizers: %s)", 1));
385 	ttip = g_strdup_printf (tmp, "Espeak");
386 	g_free (tmp);
387 	gtk_widget_set_tooltip_text (get_wg ("checkbutton_speech"), ttip);
388 	callbacks_shield_set (FALSE);
389 
390 	/* Set the initial keyboard to use
391 	 */
392 	keyb_set_combo_kbd ("combobox_kbd_country", "combobox_kbd_variant");
393 	keyb_set_combo_kbd ("combobox_keyboard_country", "combobox_keyboard_variant");
394 
395 	/* Set window icons
396 	 */
397 	gtk_window_set_default_icon_name ("klavaro");
398 
399 	/* Set pixmaps
400 	 */
401 	set_pixmap ("image_altcolor", "altcolor.png");
402 	set_pixmap ("image_fluid", "fluid.png");
403 	set_pixmap ("image_keyboard", "key.png");
404 	set_pixmap ("image_other", "other.png");
405 	set_pixmap ("image_progress", "progress.png");
406 	set_pixmap ("image_top10", "top10.png");
407 
408 	/* Set Top10 TreeViews and Combo
409 	 */
410 	top10_init ();
411 
412 	/* Set big and bold labels
413 	 */
414 	pango_parse_markup ("<big><b>----------------------------------------------------------------</b></big>",
415 			    -1, 0, &palist, NULL, NULL, NULL);
416 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_keyboard_title")), palist);
417 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_main_intro")), palist);
418 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_main_basic")), palist);
419 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_main_adapt")), palist);
420 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_main_velo")), palist);
421 	gtk_label_set_attributes (GTK_LABEL (get_wg ("label_main_fluid")), palist);
422 
423 	/* Set main labels (for translation)
424 	 */
425 	tmp = g_strdup_printf ("0 - %s", _("Introduction"));
426 	gtk_label_set_text (GTK_LABEL (get_wg ("label_main_intro")), tmp);
427 	g_free (tmp);
428 	tmp = g_strdup_printf ("1 - %s", _("Basic course"));
429 	gtk_label_set_text (GTK_LABEL (get_wg ("label_main_basic")), tmp);
430 	g_free (tmp);
431 	tmp = g_strdup_printf ("2 - %s", _("Adaptability"));
432 	gtk_label_set_text (GTK_LABEL (get_wg ("label_main_adapt")), tmp);
433 	g_free (tmp);
434 	if (velo_txt)
435 		tmp = g_strdup_printf ("3 - %s (TXT)", _("Speed"));
436 	else
437 		tmp = g_strdup_printf ("3 - %s", _("Speed"));
438 	gtk_label_set_text (GTK_LABEL (get_wg ("label_main_velo")), tmp);
439 	g_free (tmp);
440 	tmp = g_strdup_printf ("4 - %s", _("Fluidity"));
441 	gtk_label_set_text (GTK_LABEL (get_wg ("label_main_fluid")), tmp);
442 	g_free (tmp);
443 
444 	/* Set version
445 	 */
446 	gtk_about_dialog_set_version (GTK_ABOUT_DIALOG (get_obj ("aboutdialog_klavaro")), VERSION);
447 
448 	/* For remembering the window's position
449 	 */
450 	gtk_widget_show (get_wg ("window_main"));
451 	window_restore ("main");
452 
453 	/* Altcolor toggle button
454 	 */
455 	if (!main_altcolor_exist ("colors", "altcolor"))
456 		main_altcolor_set_boolean ("colors", "altcolor", FALSE);
457 	if (main_altcolor_get_boolean ("colors", "altcolor") == TRUE)
458 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_wg ("togglebutton_altcolor")), TRUE);
459 
460 	/* Run last used module
461 	 */
462 	if (!main_preferences_exist ("interface", "autostart"))
463 		main_preferences_set_boolean ("interface", "autostart", FALSE);
464 	if (!main_preferences_exist ("interface", "exercise"))
465 		main_preferences_set_int ("interface", "exercise", 1);
466 	if (main_preferences_get_boolean ("interface", "autostart"))
467 	{
468 		switch (main_preferences_get_int ("interface", "exercise"))
469 		{
470 		case TT_BASIC:
471 			on_button_basic_clicked (NULL, NULL);
472 			break;
473 		case TT_ADAPT:
474 			on_button_adapt_clicked (NULL, NULL);
475 			break;
476 		case TT_VELO:
477 			on_button_velo_clicked (NULL, NULL);
478 			break;
479 		case TT_FLUID:
480 			on_button_fluid_clicked (NULL, NULL);
481 			break;
482 		}
483 	}
484 
485 	/* Set accuracy accumulators
486 	 */
487 	accur_init ();
488 }
489 
490 /*******************************************************************************
491  * Main program
492  */
493 int
main(int argc,char * argv[])494 main (int argc, char *argv[])
495 {
496 	gchar *tmp;
497 	gboolean success = FALSE;
498 	gboolean show_version = FALSE;
499 	GOptionContext *opct;
500 	GOptionEntry option[] = {
501 		{"version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Version", NULL},
502 		{"velotxt", 'x', 0, G_OPTION_ARG_NONE, &velo_txt, "Speed TXT", NULL},
503 		{NULL}
504 	};
505 	GError *gerr = NULL;
506 
507 	/* Localization
508 	 */
509 #ifdef ENABLE_NLS
510 	bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
511 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
512 	textdomain (GETTEXT_PACKAGE);
513 #endif
514 
515 	/* Command-line arguments
516 	 */
517 	opct = g_option_context_new ("\t");
518 	g_option_context_set_translation_domain (opct, GETTEXT_PACKAGE);
519 	g_option_context_add_main_entries (opct, option, GETTEXT_PACKAGE);
520 	g_option_context_add_group (opct, gtk_get_option_group (TRUE));
521 	g_setenv ("NO_AT_BRIDGE", "1", FALSE); /* to eliminate annoying accessibility bus warning */
522 	if (g_option_context_parse (opct, &argc, &argv, &gerr) == FALSE)
523 	{
524 		g_printf ("%s\n", gerr->message);
525 		g_printf ("klavaro -h\n");
526 		return 0;
527 	}
528 
529 	if (show_version)
530 	{
531 		g_printf (VERSION"\n");
532 		//g_printf (PACKAGE_LOCALE_DIR"\n");
533 		return 0;
534 	}
535 
536 	curl_ok = curl_global_init (CURL_GLOBAL_WIN32) == CURLE_OK ? TRUE : FALSE;
537 
538 	main_initialize_global_variables ();	/* Here the locale is got. */
539 
540 	/* Create all the interface stuff
541 	 */
542 	gui = gtk_builder_new ();
543 	gtk_builder_set_translation_domain (gui, NULL);
544 
545 	tmp = g_build_filename (main_path_data (), "klavaro.glade", NULL);
546 	if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR))
547 		success = gtk_builder_add_from_file (gui, tmp, NULL);
548 	else
549 		g_error ("GUI file not found. Aborting.\n %s", tmp);
550 	if (!success)
551 		g_error ("GUI file found but couldn't create the GUI. Aborting.");
552 	if (!g_module_supported ())
553 		g_error ("GUI created but can't connect signals.");
554 	g_free (tmp);
555 	gtk_builder_connect_signals (gui, NULL);
556 
557 	main_window_init ();	/* and initialize its parameters */
558 
559 	gtk_main ();
560 
561 	return 0;
562 }
563 
564 /*******************************************************************************
565  * Quit the application
566  */
567 void
main_window_pass_away()568 main_window_pass_away ()
569 {
570 	main_preferences_save ();
571 	accur_close ();
572 	g_rmdir ("tmp/klavaro");
573 	if (curl_ok) curl_global_cleanup ();
574 	g_print ("\nAdiaux!\n");
575 	exit (0);
576 }
577