Crossfire Client, Trunk  R20789
main.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, please
9  * see COPYING and LICENSE.
10  *
11  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
12  */
13 
19 #include "client.h"
20 
21 #include <errno.h>
22 #include <gtk/gtk.h>
23 #include <stdbool.h>
24 
25 #ifndef WIN32
26 #include <signal.h>
27 #endif
28 
29 #include "client-vala.h"
30 #include "image.h"
31 #include "main.h"
32 #include "mapdata.h"
33 #include "metaserver.h"
34 #include "script.h"
35 #include "gtk2proto.h"
36 
37 /* Sets up the basic colors. */
38 static const char *const colorname[NUM_COLORS] = {
39  "Black", /* 0 */
40  "White", /* 1 */
41  "Navy", /* 2 */
42  "Red", /* 3 */
43  "Orange", /* 4 */
44  "DodgerBlue", /* 5 */
45  "DarkOrange2", /* 6 */
46  "SeaGreen", /* 7 */
47  "DarkSeaGreen", /* 8 *//* Used for window background color */
48  "Grey50", /* 9 */
49  "Sienna", /* 10 */
50  "Gold", /* 11 */
51  "Khaki" /* 12 */
52 };
53 
54 static gboolean updatekeycodes = FALSE;
55 
56 /* TODO: Move these declarations to actual header files. */
57 extern bool time_map_redraw;
58 extern bool profile_latency;
59 extern int MINLOG;
60 extern SoundServer* server;
61 
62 static char *connect_server = NULL;
63 
65 static GOptionEntry options[] = {
66  { "server", 's', 0, G_OPTION_ARG_STRING, &connect_server,
67  "Connect to the given server", "SERVER[:PORT]" },
68  { "cache", 0, 0, G_OPTION_ARG_NONE, &want_config[CONFIG_CACHE],
69  "Cache images", NULL },
70  { "prefetch", 0, 0, G_OPTION_ARG_NONE, &want_config[CONFIG_DOWNLOAD],
71  "Download images before playing", NULL },
72  { "faceset", 0, 0, G_OPTION_ARG_STRING, &face_info.want_faceset,
73  "Use the given faceset (if available)", "FACESET" },
74 
75  { "sound_server", 0, 0, G_OPTION_ARG_FILENAME, &sound_server,
76  "Path to the sound server", "PATH" },
77  { "updatekeycodes", 0, 0, G_OPTION_ARG_NONE, &updatekeycodes,
78  "Update the saved bindings for this keyboard", NULL },
79 
80  { "profile-latency", 0, 0, G_OPTION_ARG_NONE, &profile_latency,
81  "Log command acknowledgement latency to stdout", NULL },
82  { "profile-redraw", 0, 0, G_OPTION_ARG_NONE, &time_map_redraw,
83  "Print map redraw times to stdout", NULL },
84  { "verbose", 'v', 0, G_OPTION_ARG_INT, &MINLOG,
85  "Set verbosity (0 is the most verbose)", "LEVEL" },
86  { NULL }
87 };
88 
90 
92 
93 GtkBuilder *dialog_xml, *window_xml;
95 GtkNotebook *main_notebook;
96 
98 bool next_tick = false;
99 
100 #ifdef WIN32 /* Win32 scripting support */
101 static int do_scriptout() {
102  script_process(NULL);
103  return (TRUE);
104 }
105 #endif /* WIN32 */
106 
111 static gboolean do_timeout(gpointer data) {
112  if (cpl.showmagic) {
113  if (gtk_notebook_get_current_page(GTK_NOTEBOOK(map_notebook)) !=
114  MAGIC_MAP_PAGE) {
115  // Stop flashing when the user switches back to the map window.
116  cpl.showmagic = 0;
117  } else {
119  cpl.showmagic ^= 2;
120  }
121  }
122  if (cpl.spells_updated) {
124  }
125  if (!tick) {
126  inventory_tick();
128  }
129 
130  return TRUE;
131 }
132 
136 static gboolean redraw(gpointer data) {
137  // Do not redraw if no tick has arrived since the last redraw.
138  if (next_tick) {
139  next_tick = false;
140  } else {
141  // Sleep for 10 ms to prevent busy wait.
142  g_usleep(10 * 1e3);
143  return TRUE;
144  }
145 
146  if (have_new_image) {
147  if (cpl.container) {
149  }
150  cpl.ob->inv_updated = 1;
151 
152  have_new_image = 0;
153  draw_map(1);
154  draw_lists();
155  } else {
156  draw_map(0);
157  }
158  return TRUE;
159 }
160 
164 void client_tick(guint32 tick) {
166  inventory_tick();
168  next_tick = true;
169 }
173 void on_window_destroy_event(GtkObject *object, gpointer user_data) {
174 #ifdef WIN32
175  script_killall();
176 #endif
177 
178  LOG(LOG_DEBUG, "main.c::client_exit", "Exiting with return value 0.");
179  exit(0);
180 }
181 
185 static gboolean do_network(GObject *stream, gpointer data) {
186  struct timeval timeout = {0, 0};
187  fd_set tmp_read;
188  int pollret;
189 
190  if (!client_is_connected()) {
191  gtk_main_quit();
192  return FALSE;
193  }
194 
195  client_run();
196 
197  FD_ZERO(&tmp_read);
198  script_fdset(&maxfd, &tmp_read);
199  pollret = select(maxfd, &tmp_read, NULL, NULL, &timeout);
200  if (pollret > 0) {
201  script_process(&tmp_read);
202  }
203 
204  draw_lists();
205  return TRUE;
206 }
207 
211 static void event_loop() {
212  guint source_redraw = g_idle_add(redraw, NULL);
213  guint source_timeout = g_timeout_add(100, do_timeout, NULL);
214 
215 #ifdef WIN32
216  g_timeout_add(250, (GtkFunction) do_scriptout, NULL);
217 #endif
218 
219  GSource *net_source = client_get_source();
220  g_assert_nonnull(net_source);
221  g_source_set_callback(net_source, (GSourceFunc)do_network, NULL, NULL);
222  g_source_attach(net_source, NULL);
223  gtk_main();
224 
225  g_source_remove(source_redraw);
226  g_source_remove(source_timeout);
227  LOG(LOG_DEBUG, "event_loop", "Disconnected");
228 }
229 
241 static void parse_args(int argc, char *argv[]) {
242  GOptionContext *context = g_option_context_new("- Crossfire GTKv2 Client");
243  GError *error = NULL;
244 
245  g_option_context_add_main_entries(context, options, NULL);
246  g_option_context_add_group(context, gtk_get_option_group(TRUE));
247 
248  if (!g_option_context_parse(context, &argc, &argv, &error)) {
249  g_print("%s\n", error->message);
250  g_error_free(error);
251  exit(EXIT_FAILURE);
252  }
253 
254  g_option_context_free(context);
255 
256  /*
257  * Move this after the parsing of command line options, since that can
258  * change the default log level.
259  */
260  LOG(LOG_DEBUG, "Client Version", VERSION_INFO);
261  if (MINLOG <= 0) {
262  g_setenv("CF_SOUND_DEBUG", "yes", false);
263  }
264 }
265 
271 void error_dialog(char *error, char *message) {
272  GtkWidget *dialog = gtk_message_dialog_new(
273  NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
274  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error);
275  gtk_window_set_title(GTK_WINDOW(dialog), "Crossfire Client");
276  gtk_message_dialog_format_secondary_markup(
277  GTK_MESSAGE_DIALOG(dialog), "%s", message);
278  gtk_dialog_run(GTK_DIALOG(dialog));
279  gtk_widget_destroy(dialog);
280 }
281 
294 void my_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
295  const gchar *message, gpointer user_data) {
296  g_usleep(1 * 1e6);
297 }
298 
299 static void init_sockets() {
300  /* Use the 'new' login method. */
301  wantloginmethod = 2;
302 
303 #ifdef WIN32
304  maxfd = 0; /* This is ignored on win32 platforms */
305 #else /* def WIN32 */
306  signal(SIGPIPE, SIG_IGN);
307 #ifdef HAVE_SYSCONF
308  maxfd = sysconf(_SC_OPEN_MAX);
309 #else
310  maxfd = getdtablesize();
311 #endif
312 #endif /* def WIN32 */
313 }
314 
318 static char *init_ui_layout(const char *name) {
319  guint retval = gtk_builder_add_from_file(window_xml, name, NULL);
320  if (retval > 0 && strlen(name) > 0) {
321  if (window_xml_file != name) { // FIXME: caught by Valgrind
322  strncpy(window_xml_file, name, sizeof(window_xml_file));
323  }
324  return window_xml_file;
325  } else {
326  return NULL;
327  }
328 }
329 
330 static void init_ui() {
331  GError *error = NULL;
332  GdkGeometry geometry;
333  int i;
334 
335  /* Load dialog windows using GtkBuilder. */
336  dialog_xml = gtk_builder_new();
337  if (!gtk_builder_add_from_file(dialog_xml, DIALOG_FILENAME, &error)) {
338  error_dialog("Couldn't load UI dialogs.", error->message);
339  g_warning("Couldn't load UI dialogs: %s", error->message);
340  g_error_free(error);
341  exit(EXIT_FAILURE);
342  }
343 
344  /* Load main window using GtkBuilder. */
345  window_xml = gtk_builder_new();
346  if (init_ui_layout(window_xml_file) == NULL) {
347  LOG(LOG_DEBUG, "init_ui_layout", "Using default layout");
348  if (init_ui_layout(DEFAULT_UI) == NULL) {
349  g_error("Could not load default layout!");
350  }
351  }
352 
353  connect_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "connect_window"));
354  gtk_window_set_transient_for(GTK_WINDOW(connect_window),
355  GTK_WINDOW(window_root));
356  g_signal_connect(connect_window, "destroy",
357  G_CALLBACK(on_window_destroy_event), NULL);
358  main_notebook =
359  GTK_NOTEBOOK(gtk_builder_get_object(dialog_xml, "main_notebook"));
360 
361  /* Begin connecting signals for the root window. */
362  window_root = GTK_WIDGET(gtk_builder_get_object(window_xml, "window_root"));
363  if (window_root == NULL) {
364  error_dialog("Could not load main window",
365  "Check that your layout files are not corrupt.");
366  exit(EXIT_FAILURE);
367  }
368 
369  /* Request the window to receive focus in and out events */
370  gtk_widget_add_events((gpointer) window_root, GDK_FOCUS_CHANGE_MASK);
371  g_signal_connect((gpointer) window_root, "focus-out-event",
372  G_CALLBACK(focusoutfunc), NULL);
373 
374  g_signal_connect_swapped((gpointer) window_root, "key_press_event",
375  G_CALLBACK(keyfunc), GTK_OBJECT(window_root));
376  g_signal_connect_swapped((gpointer) window_root, "key_release_event",
377  G_CALLBACK(keyrelfunc), GTK_OBJECT(window_root));
378  g_signal_connect((gpointer) window_root, "destroy",
379  G_CALLBACK(on_window_destroy_event), NULL);
380 
381  /* Purely arbitrary min window size */
382  geometry.min_width=640;
383  geometry.min_height=480;
384 
385  gtk_window_set_geometry_hints(GTK_WINDOW(window_root), window_root,
386  &geometry, GDK_HINT_MIN_SIZE);
387 
388  magic_map = GTK_WIDGET(gtk_builder_get_object(window_xml,
389  "drawingarea_magic_map"));
390 
391  g_signal_connect((gpointer) magic_map, "expose_event",
392  G_CALLBACK(on_drawingarea_magic_map_expose_event), NULL);
393 
394  /* Set up colors before doing the other initialization functions */
395  for (i = 0; i < NUM_COLORS; i++) {
396  if (!gdk_color_parse(colorname[i], &root_color[i])) {
397  fprintf(stderr, "gdk_color_parse failed (%s)\n", colorname[i]);
398  }
399  if (!gdk_colormap_alloc_color(gtk_widget_get_colormap(window_root),
400  &root_color[i], FALSE, FALSE)) {
401  fprintf(stderr, "gdk_color_alloc failed\n");
402  }
403  }
404 
405  inventory_init(window_root);
406  info_init(window_root);
407  keys_init(window_root);
408  stats_init(window_root);
409  config_init(window_root);
410  pickup_init(window_root);
411  msgctrl_init(window_root);
414 
415  load_window_positions(window_root);
416 
417  init_theme();
418  load_theme(TRUE);
419  init_menu_items();
420 }
421 
428  gtk_widget_show(window_root);
431 }
432 
438  gtk_widget_hide(window_root);
440  /*
441  * We know the following is the private map structure in item.c. But
442  * we don't have direct access to it, so we still use locate.
443  */
445 
446  if (server != NULL) {
447  sound_server_stop(server);
448  }
449  gtk_widget_show(connect_window);
450 }
451 
455 int main(int argc, char *argv[]) {
456 #ifdef ENABLE_NLS
457  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
458  bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
459  textdomain(GETTEXT_PACKAGE);
460 #endif
461 
462  // Initialize GTK and client library.
463  gtk_init(&argc, &argv);
464  client_init();
465 
466  // Set defaults, load configuration, and parse arguments.
467  config_load();
468  parse_args(argc, argv);
469  config_check();
470  char *layout = g_path_get_basename(window_xml_file);
471  snprintf(VERSION_INFO, MAX_BUF,
472  "GTKv2 Client " FULL_VERSION " (%s)", layout);
473  g_free(layout);
474 
475  // Initialize UI, sockets, and sound server.
476  init_ui();
477  init_sockets();
478 
479  if (!want_config[CONFIG_SOUND] || !init_sounds()) {
480  use_config[CONFIG_SOUND] = FALSE;
481  } else {
482  use_config[CONFIG_SOUND] = TRUE;
483  }
484 
485  /* Load cached pixmaps. */
487 
488  while (true) {
489  gtk_widget_show(connect_window);
490  if (connect_server == NULL) {
492  gtk_main();
493  } else {
495  if (csocket.fd == NULL) {
496  LOG(LOG_ERROR, "main", "Unable to connect to %s!", connect_server);
497  break;
498  }
500  }
501 
503  if (serverloginmethod) {
505  } else {
507  }
508 
509  /* The event_loop will block until connection to the server is lost. */
510  event_loop();
511 
513 
514  /*
515  * Need to reset the images so they match up properly and prevent
516  * memory leaks.
517  */
519  client_reset();
520  }
521 }
522 
534 void get_window_coord(GtkWidget *win, int *x, int *y, int *wx, int *wy,
535  int *w, int *h) {
536  /* Position of a window relative to its parent window. */
537  gdk_window_get_geometry(gtk_widget_get_window(win), x, y, w, h, NULL);
538  /* Position of the window in root window coordinates. */
539  gdk_window_get_origin(gtk_widget_get_window(win), wx, wy);
540  *wx -= *x;
541  *wy -= *y;
542 }
void update_spell_information(void)
Definition: spells.c:190
int maxfd
Definition: client.c:58
void error_dialog(char *error, char *message)
Definition: main.c:271
void client_negotiate(int sound)
Definition: client.c:298
void init_create_character_window()
Definition: create_char.c:791
GtkBuilder * window_xml
Definition: main.c:93
void config_check()
Definition: config.c:311
int MINLOG
Definition: misc.c:32
GSocketConnection * fd
Definition: client.h:120
guint32 tick
Definition: client.c:61
gint16 use_config[CONFIG_NUMS]
Definition: init.c:40
SoundServer * server
Definition: sound.c:25
void mapdata_animation(void)
Definition: mapdata.c:1340
void inventory_init(GtkWidget *window_root)
Definition: inventory.c:453
item * locate_item(gint32 tag)
Definition: item.c:300
static void event_loop()
Definition: main.c:211
static char * connect_server
Definition: main.c:62
void client_init()
Definition: init.c:184
static char * init_ui_layout(const char *name)
Definition: main.c:318
guint16 inv_updated
Definition: item.h:76
item * ob
Definition: client.h:336
void reset_image_data(void)
Definition: image.c:428
void keyfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1556
void load_theme(int reload)
Definition: config.c:138
void show_main_client()
Definition: main.c:426
void stats_init(GtkWidget *window_root)
Definition: stats.c:147
static const char *const colorname[NUM_COLORS]
Definition: main.c:38
void config_load()
Definition: config.c:395
char window_xml_file[MAX_BUF]
Definition: main.c:89
ClientSocket csocket
Definition: client.c:69
int wantloginmethod
Definition: client.c:59
void clear_stat_mapping(void)
Definition: stats.c:771
Face_Information face_info
Definition: image.c:169
int main(int argc, char *argv[])
Definition: main.c:455
void pickup_init(GtkWidget *window_root)
Definition: pickup.c:401
void init_image_cache_data(void)
Definition: image.c:526
void get_window_coord(GtkWidget *win, int *x, int *y, int *wx, int *wy, int *w, int *h)
Definition: main.c:534
int serverloginmethod
Definition: client.c:59
char VERSION_INFO[MAX_BUF]
Definition: client.c:48
static GOptionEntry options[]
Definition: main.c:65
void init_theme()
Definition: config.c:76
static void init_ui()
Definition: main.c:330
#define DIALOG_FILENAME
Definition: main.h:34
void init_menu_items()
Definition: menubar.c:91
bool next_tick
Definition: main.c:98
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:109
bool time_map_redraw
Definition: map.c:50
void hide_main_client()
Definition: main.c:437
bool profile_latency
Definition: player.c:33
static gboolean redraw(gpointer data)
Definition: main.c:136
void msgctrl_init(GtkWidget *window_root)
Definition: info.c:1331
GtkWidget * magic_map
Definition: main.c:94
void draw_lists(void)
Definition: inventory.c:1076
void on_window_destroy_event(GtkObject *object, gpointer user_data)
Definition: main.c:173
Client_Player cpl
Definition: client.c:68
item * container
Definition: client.h:339
static gboolean do_network(GObject *stream, gpointer data)
Definition: main.c:185
GSource * client_get_source()
Definition: client.c:293
GtkWidget * map_notebook
Definition: map.c:44
#define DEFAULT_UI
Definition: main.h:33
#define MAX_BUF
Definition: client.h:40
GdkColor root_color[NUM_COLORS]
Definition: main.c:91
void config_init(GtkWidget *window_root)
Definition: config.c:464
void info_init(GtkWidget *window_root)
Definition: info.c:649
#define CONFIG_CACHE
Definition: client.h:189
gboolean on_drawingarea_magic_map_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
void keys_init(GtkWidget *window_root)
Definition: keys.c:621
gint16 want_config[CONFIG_NUMS]
Definition: init.c:40
void client_tick(guint32 tick)
Definition: main.c:164
GtkWidget * window_root
Definition: main.c:94
#define NUM_COLORS
Definition: main.h:19
#define CONFIG_DOWNLOAD
Definition: client.h:185
void client_run()
Definition: client.c:187
int init_sounds(void)
Definition: sound.c:27
void map_init(GtkWidget *window_root)
Definition: map.c:75
guint8 showmagic
Definition: client.h:364
int have_new_image
Definition: image.c:52
void client_connect(const char hostname[static 1])
Definition: client.c:258
Warning that something definitely didn&#39;t work.
Definition: client.h:444
guint32 spells_updated
Definition: client.h:353
#define MAGIC_MAP_PAGE
Definition: main.h:39
char * sound_server
Definition: client.c:51
void my_log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
Definition: main.c:294
void load_window_positions(GtkWidget *window_root)
Definition: config.c:822
void client_reset()
Definition: init.c:239
void focusoutfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1513
void hide_all_login_windows(void)
Definition: account.c:99
void script_fdset(int *maxfd, fd_set *set)
Definition: script.c:551
void remove_item_inventory(item *op)
Definition: item.c:393
void account_show_login()
Definition: account.c:1306
void info_buffer_tick(void)
Definition: info.c:1086
Input_State input_state
Definition: client.h:341
void metaserver_ui_init()
Definition: metaserver.c:71
void magic_map_flash_pos(void)
Definition: magicmap.c:77
static void init_sockets()
Definition: main.c:299
#define CONFIG_SOUND
Definition: client.h:197
char * name
Definition: image.c:39
static void parse_args(int argc, char *argv[])
Definition: main.c:241
#define FULL_VERSION
Definition: version.h:1
GtkWidget * connect_window
Definition: main.c:94
GtkNotebook * main_notebook
Definition: main.c:95
static gboolean do_timeout(gpointer data)
Definition: main.c:111
GtkBuilder * dialog_xml
Definition: main.c:93
Useful debugging information.
Definition: client.h:441
static gboolean updatekeycodes
Definition: main.c:54
void script_process(fd_set *set)
Definition: script.c:565
bool client_is_connected()
Definition: client.c:289
void draw_map(int redraw)
void keyrelfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1542
void metaserver_show_prompt(void)
Definition: metaserver.c:189
void inventory_tick(void)
Definition: inventory.c:1266