main.c
1 /* 2 Gpredict: Real-time satellite tracking and orbit prediction program 3 4 Copyright (C) 2001-2017 Alexandru Csete, OZ9AEC. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, visit http://www.fsf.org/ 18 */ 19 #ifdef HAVE_CONFIG_H 20 #include <build-config.h> 21 #endif 22 23 #include <glib/gi18n.h> 24 #include <glib/gstdio.h> 25 #include <gtk/gtk.h> 26 #include <signal.h> 27 #include <stdlib.h> 28 #ifdef WIN32 29 #include <winsock2.h> 30 #endif 31 32 #include "compat.h" 33 #include "gtk-sat-selector.h" 34 #include "gui.h" 35 #include "first-time.h" 36 #include "tle-update.h" 37 #include "mod-mgr.h" 38 #include "sat-cfg.h" 39 #include "sat-log.h" 40 41 42 /* Main application widget. */ 43 GtkWidget *app; 44 45 /* Command line flag for cleaning TLE data. */ 46 static gboolean cleantle = FALSE; 47 48 /* Command line flag for cleaning TRSP data */ 49 static gboolean cleantrsp = FALSE; 50 51 /* Start application in fullscreen mode */ 52 static gboolean fullscreen = FALSE; 53 54 /* Command line options. */ 55 static GOptionEntry entries[] = { 56 {"clean-tle", 0, 0, G_OPTION_ARG_NONE, &cleantle, 57 "Clean the TLE data in user's configuration directory", NULL}, 58 {"clean-trsp", 0, 0, G_OPTION_ARG_NONE, &cleantrsp, 59 "Clean the transponder data in user's configuration directory", NULL}, 60 {"fullscreen", 0, 0, G_OPTION_ARG_NONE, &fullscreen, 61 "Start gpredict in fullscreen mode.", NULL}, 62 {NULL} 63 }; 64 65 const gchar *dummy = N_("just to have a pot"); 66 67 /* ID of TLE monitoring task */ 68 static guint tle_mon_id = 0; 69 70 /* flag indicating whether TLE update is running */ 71 static gboolean tle_upd_running = FALSE; 72 73 /* flag indicating whether user has been notified of TLE update */ 74 static gboolean tle_upd_note_sent = FALSE; 75 76 77 /* private function prototypes */ 78 static void gpredict_app_create(void); 79 static gint gpredict_app_delete(GtkWidget *, GdkEvent *, gpointer); 80 static void gpredict_app_destroy(GtkWidget *, gpointer); 81 static gboolean gpredict_app_config(GtkWidget *, GdkEventConfigure *, 82 gpointer); 83 static void gpredict_sig_handler(int sig); 84 static gboolean tle_mon_task(gpointer data); 85 static void tle_mon_stop(void); 86 static gpointer update_tle_thread(gpointer data); 87 static void clean_tle(void); 88 static void clean_trsp(void); 89 90 #ifdef G_OS_WIN32 91 static void InitWinSock2(void); 92 static void CloseWinSock2(void); 93 #endif 94 95 96 int main(int argc, char *argv[]) 97 { 98 GError *err = NULL; 99 GOptionContext *context; 100 guint error = 0; 101 102 103 #ifdef ENABLE_NLS 104 bindtextdomain(PACKAGE, PACKAGE_LOCALE_DIR); 105 bind_textdomain_codeset(PACKAGE, "UTF-8"); 106 textdomain(PACKAGE); 107 #endif 108 gtk_init(&argc, &argv); 109 110 context = g_option_context_new(""); 111 g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE); 112 g_option_context_set_summary(context, 113 _("Gpredict is a graphical real-time satellite " 114 "tracking and orbit prediction program.\n" 115 "Gpredict does not require any command line " 116 "options for nominal operation.")); 117 g_option_context_add_group(context, gtk_get_option_group(TRUE)); 118 if (!g_option_context_parse(context, &argc, &argv, &err)) 119 g_print(_("Option parsing failed: %s\n"), err->message); 120 121 sat_log_init(); 122 sat_cfg_load(); 123 sat_log_set_level(sat_cfg_get_int(SAT_CFG_INT_LOG_LEVEL)); 124 125 if (cleantle) 126 clean_tle(); 127 128 if (cleantrsp) 129 clean_trsp(); 130 131 /* check that user settings are ok */ 132 error = first_time_check_run(); 133 if (error) 134 { 135 sat_log_log(SAT_LOG_LEVEL_ERROR, 136 _("User config check failed (code %d). This is fatal.\n" 137 "A possible solution would be to remove the " 138 ".config/Gpredict data dir in your home directory"), 139 error); 140 141 return 1; 142 } 143 144 /* create application */ 145 gpredict_app_create(); 146 gtk_widget_show_all(app); 147 if (fullscreen) 148 gtk_window_fullscreen(GTK_WINDOW(app)); 149 150 //sat_debugger_run (); 151 152 /* launch TLE monitoring task; 10 min interval */ 153 tle_mon_id = g_timeout_add(600000, tle_mon_task, NULL); 154 155 #ifdef WIN32 156 // Initializing Windozze Sockets 157 InitWinSock2(); 158 #endif 159 160 gtk_main(); 161 162 g_option_context_free(context); 163 164 sat_cfg_save(); 165 sat_log_close(); 166 sat_cfg_close(); 167 168 #ifdef WIN32 169 CloseWinSock2(); 170 #endif 171 172 return 0; 173 } 174 175 #ifdef WIN32 176 /* This code was given from MSDN */ 177 static void InitWinSock2(void) 178 { 179 WORD wVersionRequested; 180 WSADATA wsaData; 181 int err; 182 183 wVersionRequested = MAKEWORD(2, 2); 184 185 err = WSAStartup(wVersionRequested, &wsaData); 186 if (err != 0) 187 { 188 /* Tell the user that we could not find a usable */ 189 /* WinSock DLL. */ 190 return; 191 } 192 193 /* Confirm that the WinSock DLL supports 2.2. */ 194 /* Note that if the DLL supports versions later */ 195 /* than 2.2 in addition to 2.2, it will still return */ 196 /* 2.2 in wVersion since that is the version we */ 197 /* requested. */ 198 199 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 200 { 201 /* Tell the user that we could not find a usable */ 202 /* WinSock DLL. */ 203 WSACleanup(); 204 return; 205 } 206 } 207 208 static void CloseWinSock2(void) 209 { 210 WSACleanup(); 211 } 212 #endif 213 214 /** 215 * Create main application window. 216 * 217 * @return A new top level window as as GtkWidget. 218 * 219 * This function creates and initialises the main application window. 220 * This function does not create any contents; that part is done in the 221 * gpredict_gui package. 222 * 223 */ 224 static void gpredict_app_create() 225 { 226 gchar *title; 227 gchar *icon; 228 229 /* create window title and file name for window icon */ 230 title = g_strdup(_("Gpredict")); 231 icon = logo_file_name("gpredict_icon_color.svg"); 232 233 /* create window, add title and icon, restore size and position */ 234 app = gtk_window_new(GTK_WINDOW_TOPLEVEL); 235 236 gtk_window_set_title(GTK_WINDOW(app), title); 237 238 /* restore window position and size if requested by config */ 239 /* trunk/gtk/gtkblist.c */ 240 /* size is always restored */ 241 gtk_window_set_default_size(GTK_WINDOW(app), 242 sat_cfg_get_int(SAT_CFG_INT_WINDOW_WIDTH), 243 sat_cfg_get_int(SAT_CFG_INT_WINDOW_HEIGHT)); 244 245 /* position restored only if requested in config */ 246 if (sat_cfg_get_bool(SAT_CFG_BOOL_MAIN_WIN_POS)) 247 { 248 gtk_window_move(GTK_WINDOW(app), 249 sat_cfg_get_int(SAT_CFG_INT_WINDOW_POS_X), 250 sat_cfg_get_int(SAT_CFG_INT_WINDOW_POS_Y)); 251 } 252 253 gtk_container_add(GTK_CONTAINER(app), gui_create(app)); 254 if (g_file_test(icon, G_FILE_TEST_EXISTS)) 255 gtk_window_set_icon_from_file(GTK_WINDOW(app), icon, NULL); 256 257 g_free(title); 258 g_free(icon); 259 260 /* connect delete and destroy signals */ 261 g_signal_connect(G_OBJECT(app), "delete_event", 262 G_CALLBACK(gpredict_app_delete), NULL); 263 g_signal_connect(G_OBJECT(app), "configure_event", 264 G_CALLBACK(gpredict_app_config), NULL); 265 g_signal_connect(G_OBJECT(app), "destroy", 266 G_CALLBACK(gpredict_app_destroy), NULL); 267 268 signal(SIGTERM, gpredict_sig_handler); 269 signal(SIGINT, gpredict_sig_handler); 270 signal(SIGABRT, gpredict_sig_handler); 271 } 272 273 /** 274 * Handle terminate signals. 275 * 276 * @param sig The signal that has been received. 277 * 278 * This function is used to handle termination signals received by the program. 279 * The currently caught signals are SIGTERM, SIGINT and SIGABRT. When one of these 280 * signals is received, the function sends an error message to logger and tries 281 * to make a clean exit. 282 */ 283 static void gpredict_sig_handler(int sig) 284 { 285 g_print("Received signal: %d\n", sig); 286 gtk_widget_destroy(app); 287 } 288 289 /** 290 * Handle delete events. 291 * 292 * @param widget The widget which received the delete event signal. 293 * @param event Data structure describing the event. 294 * @param data User data (NULL). 295 * @param return Always FALSE to indicate that the app should be destroyed. 296 * 297 * This function handles the delete event received by the main application 298 * window (eg. when the window is closed by the WM). This function simply 299 * returns FALSE indicating that the main application window should be 300 * destroyed by emitting the destroy signal. 301 * 302 */ 303 static gint gpredict_app_delete(GtkWidget * widget, GdkEvent * event, 304 gpointer data) 305 { 306 (void)widget; 307 (void)event; 308 (void)data; 309 return FALSE; 310 } 311 312 /** 313 * Handle destroy signals. 314 * 315 * @param widget The widget which received the signal. 316 * @param data User data (NULL). 317 * 318 * This function is called when the main application window receives the 319 * destroy signal, ie. it is destroyed. This function signals all daemons 320 * and other threads to stop and exits the Gtk+ main loop. 321 */ 322 static void gpredict_app_destroy(GtkWidget * widget, gpointer data) 323 { 324 (void)widget; 325 (void)data; 326 327 /* stop TLE monitoring task */ 328 tle_mon_stop(); 329 330 /* GUI timers are stopped automatically */ 331 mod_mgr_save_state(); 332 333 /* not good, have to use configure event instead (see API doc) */ 334 /* gtk_window_get_size (GTK_WINDOW (app), &w, &h); 335 sat_cfg_set_int (SAT_CFG_INT_WINDOW_WIDTH, w); 336 sat_cfg_set_int (SAT_CFG_INT_WINDOW_HEIGHT, h); 337 */ 338 339 gtk_main_quit(); 340 } 341 342 /** 343 * Snoop window position and size when main window receives configure event. 344 * 345 * @param widget Pointer to the gpredict main window. 346 * @param event Pointer to the even structure. 347 * @param data Pointer to user data (always NULL). 348 * 349 * This function is used to trap configure events in order to store the current 350 * position and size of the main window. 351 * 352 * @note unfortunately GdkEventConfigure ignores the window gravity, while 353 * the only way we have of setting the position doesn't. We have to 354 * call get_position because it does pay attention to the gravity. 355 * 356 * @note The logic in the code has been borrowed from gaim/pidgin http://pidgin.im/ 357 * 358 */ 359 static gboolean gpredict_app_config(GtkWidget * widget, 360 GdkEventConfigure * event, 361 gpointer data) 362 { 363 gint x, y, w, h; 364 365 (void)data; 366 367 /* data is only useful when window is visible */ 368 if (gtk_widget_get_visible(widget)) 369 gtk_window_get_position(GTK_WINDOW(widget), &x, &y); 370 else 371 return FALSE; 372 373 #ifdef G_OS_WIN32 374 /* Workaround for GTK+ bug # 169811 - "configure_event" is fired 375 when the window is being maximized */ 376 if (gdk_window_get_state(gtk_widget_get_window(widget)) & GDK_WINDOW_STATE_MAXIMIZED) 377 { 378 return FALSE; 379 } 380 #endif 381 382 /* don't save off-screen positioning */ 383 /* gtk_menu_popup got deprecated in 3.22, first available in Ubuntu 18.04 */ 384 #if GTK_MINOR_VERSION < 22 385 w = gdk_screen_width(); 386 h = gdk_screen_height(); 387 #else 388 GdkWindow *window; 389 GdkDisplay *display; 390 GdkMonitor *monitor; 391 GdkRectangle work_area; 392 393 /* https://gitlab.gnome.org/GNOME/gtk/-/issues/1028 */ 394 window = gtk_widget_get_window(widget); 395 display = gtk_widget_get_display(widget); 396 monitor = gdk_display_get_monitor_at_window(display, window); 397 gdk_monitor_get_workarea(monitor, &work_area); 398 399 w = work_area.width; 400 h = work_area.height; 401 #endif 402 403 if (x < 0 || y < 0 || x + event->width > w || y + event->height > h) 404 { 405 return FALSE; 406 } 407 408 /* store the position and size */ 409 sat_cfg_set_int(SAT_CFG_INT_WINDOW_POS_X, x); 410 sat_cfg_set_int(SAT_CFG_INT_WINDOW_POS_Y, y); 411 sat_cfg_set_int(SAT_CFG_INT_WINDOW_WIDTH, event->width); 412 sat_cfg_set_int(SAT_CFG_INT_WINDOW_HEIGHT, event->height); 413 414 /* continue to handle event normally */ 415 return FALSE; 416 } 417 418 /** 419 * Monitor TLE age. 420 * 421 * This function is called periodically in order to check 422 * whether it is time to update the TLE elements. 423 * 424 * If the time to update the TLE has come, it will either notify 425 * the user, or fork a separate task which will update the TLE data 426 * in the background (depending on user settings). 427 * 428 * In case of notification, the task will be removed in order to 429 * avoid a new notification the next time the taks would be run. 430 */ 431 static gboolean tle_mon_task(gpointer data) 432 { 433 /*GtkWidget *selector; */ 434 glong last, thrld; 435 gint64 now; 436 GtkWidget *dialog; 437 GError *err = NULL; 438 439 if (data != NULL) 440 { 441 sat_log_log(SAT_LOG_LEVEL_ERROR, 442 _ 443 ("%s: Passed a non-null pointer which should never happen.\n"), 444 __func__); 445 } 446 447 /* get time of last update */ 448 last = sat_cfg_get_int(SAT_CFG_INT_TLE_LAST_UPDATE); 449 450 /* get current time */ 451 now = g_get_real_time() / G_USEC_PER_SEC; 452 453 /* threshold */ 454 switch (sat_cfg_get_int(SAT_CFG_INT_TLE_AUTO_UPD_FREQ)) 455 { 456 case TLE_AUTO_UPDATE_MONTHLY: 457 thrld = 2592000; 458 break; 459 460 case TLE_AUTO_UPDATE_WEEKLY: 461 thrld = 604800; 462 break; 463 464 case TLE_AUTO_UPDATE_DAILY: 465 thrld = 86400; 466 break; 467 468 /* set default to "infinite" */ 469 default: 470 thrld = G_MAXLONG; 471 break; 472 } 473 474 if ((now - last) < thrld) 475 { 476 /* too early */ 477 /* sat_log_log (SAT_LOG_LEVEL_DEBUG, */ 478 /* _("%s: Threshold has not been passed yet."), */ 479 /* __func__, last, now, thrld); */ 480 } 481 else 482 { 483 /* time to update */ 484 sat_log_log(SAT_LOG_LEVEL_DEBUG, 485 _("%s: Time threshold has been passed."), __func__); 486 487 /* find out what to do */ 488 if (sat_cfg_get_int(SAT_CFG_INT_TLE_AUTO_UPD_ACTION) == 489 TLE_AUTO_UPDATE_GOAHEAD) 490 { 491 492 /* start update process in separate thread */ 493 sat_log_log(SAT_LOG_LEVEL_DEBUG, 494 _("%s: Starting new update thread."), __func__); 495 496 /** FIXME: store thread and destroy on exit? **/ 497 g_thread_try_new(_("gpredict_tle_update"), update_tle_thread, NULL, 498 &err); 499 500 if (err != NULL) 501 sat_log_log(SAT_LOG_LEVEL_ERROR, 502 _("%s: Failed to create TLE update thread (%s)"), 503 __func__, err->message); 504 505 } 506 else if (!tle_upd_note_sent) 507 { 508 /* notify user */ 509 dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(app), 510 GTK_DIALOG_DESTROY_WITH_PARENT, 511 GTK_MESSAGE_INFO, 512 GTK_BUTTONS_OK, 513 _ 514 ("Your TLE files are getting out of date.\n" 515 "You can update them by selecting\n" 516 "<b>Edit -> Update TLE</b>\n" 517 "in the menubar.")); 518 519 /* Destroy the dialog when the user responds to it (e.g. clicks a button) */ 520 g_signal_connect_swapped(dialog, "response", 521 G_CALLBACK(gtk_widget_destroy), dialog); 522 523 gtk_widget_show_all(dialog); 524 525 tle_upd_note_sent = TRUE; 526 } 527 } 528 529 return TRUE; 530 } 531 532 /* Stop TLE monitoring and any pending updates. */ 533 static void tle_mon_stop() 534 { 535 gboolean retcode; 536 537 if (tle_mon_id) 538 { 539 retcode = g_source_remove(tle_mon_id); 540 541 if (!retcode) 542 sat_log_log(SAT_LOG_LEVEL_ERROR, 543 _("%s: Could not find TLE monitoring task (ID = %d)"), 544 __func__, tle_mon_id); 545 546 } 547 548 /* if TLE update is running wait until it is finished */ 549 while (tle_upd_running) 550 { 551 g_usleep(1000); 552 } 553 } 554 555 /* Thread function which invokes TLE update */ 556 static gpointer update_tle_thread(gpointer data) 557 { 558 (void)data; 559 560 tle_upd_running = TRUE; 561 tle_update_from_network(TRUE, NULL, NULL, NULL); 562 tle_upd_running = FALSE; 563 564 return NULL; 565 } 566 567 /* 568 * Clean TLE data. 569 * 570 * This function removes all .sat files from the user's configuration directory. 571 * The function is called when gpreidict is executed with the --clean-tle 572 * command line option. 573 */ 574 static void clean_tle(void) 575 { 576 GDir *targetdir; 577 gchar *targetdirname, *path; 578 const gchar *filename; 579 580 /* Get trsp directory */ 581 targetdirname = get_satdata_dir(); 582 targetdir = g_dir_open(targetdirname, 0, NULL); 583 584 sat_log_log(SAT_LOG_LEVEL_INFO, 585 _("%s: Cleaning TLE data in %s"), __func__, targetdirname); 586 587 while ((filename = g_dir_read_name(targetdir))) 588 { 589 if (g_str_has_suffix(filename, ".sat")) 590 { 591 /* remove .sat file */ 592 path = sat_file_name(filename); 593 if G_UNLIKELY 594 (g_unlink(path)) 595 { 596 sat_log_log(SAT_LOG_LEVEL_ERROR, 597 _("%s: Failed to delete %s"), __func__, filename); 598 } 599 else 600 { 601 sat_log_log(SAT_LOG_LEVEL_INFO, 602 _("%s: Removed %s"), __func__, filename); 603 } 604 g_free(path); 605 } 606 } 607 g_free(targetdirname); 608 } 609 610 /* 611 * Clean transponder data. 612 * 613 * This function removes all .trsp files from the user's configuration directory. 614 * The function is called when gpredict is executed with the --clean-trsp 615 * command line option. 616 */ 617 static void clean_trsp(void) 618 { 619 GDir *targetdir; 620 gchar *targetdirname, *path; 621 const gchar *filename; 622 623 /* Get trsp directory */ 624 targetdirname = get_trsp_dir(); 625 targetdir = g_dir_open(targetdirname, 0, NULL); 626 627 sat_log_log(SAT_LOG_LEVEL_INFO, 628 _("%s: Cleaning transponder data in %s"), __func__, 629 targetdirname); 630 631 while ((filename = g_dir_read_name(targetdir))) 632 { 633 if (g_str_has_suffix(filename, ".trsp")) 634 { 635 /* remove .trsp file */ 636 path = trsp_file_name(filename); 637 if G_UNLIKELY 638 (g_unlink(path)) 639 { 640 sat_log_log(SAT_LOG_LEVEL_ERROR, 641 _("%s: Failed to delete %s"), __func__, filename); 642 } 643 else 644 { 645 sat_log_log(SAT_LOG_LEVEL_INFO, 646 _("%s: Removed %s"), __func__, filename); 647 } 648 g_free(path); 649 } 650 } 651 g_free(targetdirname); 652 }