/ src / main.c
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  }