Crossfire Client, Trunk  R21611
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;
62 
63 static char *connect_server = NULL;
64 
65 static gboolean script_launch(const gchar *option_name, const gchar *value, gpointer data, GError **error);
66 
68 static GOptionEntry options[] = {
69  { "server", 's', 0, G_OPTION_ARG_STRING, &connect_server,
70  "Connect to the given server", "SERVER[:PORT]" },
71  { "cache", 0, 0, G_OPTION_ARG_NONE, &want_config[CONFIG_CACHE],
72  "Cache images", NULL },
73  { "prefetch", 0, 0, G_OPTION_ARG_NONE, &want_config[CONFIG_DOWNLOAD],
74  "Download images before playing", NULL },
75  { "faceset", 0, 0, G_OPTION_ARG_STRING, &face_info.want_faceset,
76  "Use the given faceset (if available)", "FACESET" },
77 
78  { "sound_server", 0, 0, G_OPTION_ARG_FILENAME, &sound_server,
79  "Path to the sound server", "PATH" },
80  { "updatekeycodes", 0, 0, G_OPTION_ARG_NONE, &updatekeycodes,
81  "Update the saved bindings for this keyboard", NULL },
82 
83  { "profile-latency", 0, 0, G_OPTION_ARG_NONE, &profile_latency,
84  "Log command acknowledgement latency to stdout", NULL },
85  { "profile-redraw", 0, 0, G_OPTION_ARG_NONE, &time_map_redraw,
86  "Print map redraw times to stdout", NULL },
87  { "verbose", 'v', 0, G_OPTION_ARG_INT, &MINLOG,
88  "Set verbosity (0 is the most verbose)", "LEVEL" },
89  { "debug-protocol", 0, 0, G_OPTION_ARG_NONE, &debug_protocol,
90  "Print commands to and from the server", NULL },
91  { "script", 0, 0, G_OPTION_ARG_CALLBACK, &script_launch,
92  "Launch client script at start (can be used multiple times)", "SCRIPT_NAME" },
93  { NULL }
94 };
95 
97 
99 
100 GtkBuilder *dialog_xml, *window_xml;
102 GtkNotebook *main_notebook;
103 
104 #ifdef WIN32 /* Win32 scripting support */
105 static int do_scriptout() {
106  script_process(NULL);
107  return (TRUE);
108 }
109 #endif /* WIN32 */
110 
111 static gboolean script_launch(const gchar *option_name, const gchar *value, gpointer data, GError **error)
112 {
113  (void)option_name; // always "--script"
114  (void)data; // Not used
115  (void)error; // Not used
116  script_init(value);
117  return TRUE;
118 }
119 
124 static gboolean redraw(gpointer data) {
125  // Add a check for client_is_connected so that forced socket termination
126  // does not erroneously attempt to redraw the map after it has been destroyed.
127  if (client_is_connected()) {
128  if (have_new_image) {
129  if (cpl.container) {
131  }
132  cpl.ob->inv_updated = 1;
133 
134  have_new_image = 0;
135  draw_map(1);
136  } else {
137  draw_map(0);
138  }
139  draw_lists();
140  }
141  return FALSE;
142 }
143 
147 void client_tick(guint32 tick) {
148  if (cpl.showmagic) {
149  if (gtk_notebook_get_current_page(GTK_NOTEBOOK(map_notebook)) !=
150  MAGIC_MAP_PAGE) {
151  // Stop flashing when the user switches back to the map window.
152  cpl.showmagic = 0;
153  } else {
155  cpl.showmagic ^= 2;
156  }
157  }
158  if (cpl.spells_updated) {
160  }
161 
163  inventory_tick();
165  g_idle_add(redraw, NULL);
166 }
170 void on_window_destroy_event(GtkWidget *object, gpointer user_data) {
171 #ifdef WIN32
172  script_killall();
173 #endif
174 
175  LOG(LOG_DEBUG, "main.c::client_exit", "Exiting with return value 0.");
176  exit(0);
177 }
178 
182 static gboolean do_network(GObject *stream, gpointer data) {
183  struct timeval timeout = {0, 0};
184  fd_set tmp_read;
185  int pollret;
186 
187  if (!client_is_connected()) {
188  gtk_main_quit();
189  LOG(LOG_INFO, "main.c::do_network", "Trying to do network when not connected.");
190  return FALSE;
191  }
192 
193  client_run();
194 
195  FD_ZERO(&tmp_read);
196  script_fdset(&maxfd, &tmp_read);
197  pollret = select(maxfd, &tmp_read, NULL, NULL, &timeout);
198  if (pollret > 0) {
199  script_process(&tmp_read);
200  }
201 
202  return TRUE;
203 }
204 
208 static void event_loop() {
209 #ifdef WIN32
210  g_timeout_add(250, (GtkFunction) do_scriptout, NULL);
211 #endif
212 
213  GSource *net_source = client_get_source();
214  if (net_source == NULL) {
215  error_dialog("Server unexpectedly disconnected",
216  "The server unexpectedly disconnected.");
218  return;
219  }
220  g_source_set_callback(net_source, (GSourceFunc)do_network, NULL, NULL);
221  g_source_attach(net_source, NULL);
222  gtk_main();
223 
224  LOG(LOG_DEBUG, "event_loop", "Disconnected");
225 }
226 
238 static void parse_args(int argc, char *argv[]) {
239  GOptionContext *context = g_option_context_new("- Crossfire GTKv2 Client");
240  GError *error = NULL;
241 
242  g_option_context_add_main_entries(context, options, NULL);
243  g_option_context_add_group(context, gtk_get_option_group(TRUE));
244 
245  if (!g_option_context_parse(context, &argc, &argv, &error)) {
246  g_print("%s\n", error->message);
247  g_error_free(error);
248  exit(EXIT_FAILURE);
249  }
250 
251  g_option_context_free(context);
252 
253  /*
254  * Move this after the parsing of command line options, since that can
255  * change the default log level.
256  */
257  LOG(LOG_DEBUG, "Client Version", VERSION_INFO);
258  if (MINLOG <= 0) {
259  g_setenv("CF_SOUND_DEBUG", "yes", false);
260  }
261 }
262 
268 void error_dialog(char *error, char *message) {
269  GtkWidget *dialog = gtk_message_dialog_new(
270  NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
271  GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error);
272  gtk_window_set_title(GTK_WINDOW(dialog), "Crossfire Client");
273  gtk_message_dialog_format_secondary_markup(
274  GTK_MESSAGE_DIALOG(dialog), "%s", message);
275  gtk_dialog_run(GTK_DIALOG(dialog));
276  gtk_widget_destroy(dialog);
277 }
278 
291 void my_log_handler(const gchar *log_domain, GLogLevelFlags log_level,
292  const gchar *message, gpointer user_data) {
293  g_usleep(1 * 1e6);
294 }
295 
296 static void init_sockets() {
297  /* Use the 'new' login method. */
298  wantloginmethod = 2;
299 
300 #ifdef WIN32
301  maxfd = 0; /* This is ignored on win32 platforms */
302 #else /* def WIN32 */
303  signal(SIGPIPE, SIG_IGN);
304 #ifdef HAVE_SYSCONF
305  maxfd = sysconf(_SC_OPEN_MAX);
306 #else
307  maxfd = getdtablesize();
308 #endif
309 #endif /* def WIN32 */
310 }
311 
315 static char *init_ui_layout(const char *name) {
316  guint retval = gtk_builder_add_from_file(window_xml, name, NULL);
317  if (retval > 0 && strlen(name) > 0) {
318  if (window_xml_file != name) { // FIXME: caught by Valgrind
319  strncpy(window_xml_file, name, sizeof(window_xml_file));
320  }
321  return window_xml_file;
322  } else {
323  return NULL;
324  }
325 }
326 
327 static void init_ui() {
328  GError *error = NULL;
329  GdkGeometry geometry;
330  int i;
331 
332  /* Load dialog windows using GtkBuilder. */
333  dialog_xml = gtk_builder_new();
334  if (!gtk_builder_add_from_file(dialog_xml, DIALOG_FILENAME, &error)) {
335  error_dialog("Couldn't load UI dialogs.", error->message);
336  g_warning("Couldn't load UI dialogs: %s", error->message);
337  g_error_free(error);
338  exit(EXIT_FAILURE);
339  }
340  LOG(LOG_DEBUG, "init_ui", "loaded dialog_xml");
341 
342  /* Load main window using GtkBuilder. */
343  window_xml = gtk_builder_new();
344  if (init_ui_layout(window_xml_file) == NULL) {
345  LOG(LOG_DEBUG, "init_ui_layout", "Using default layout");
346  if (init_ui_layout(DEFAULT_UI) == NULL) {
347  g_error("Could not load default layout!");
348  }
349  }
350  LOG(LOG_DEBUG, "init_ui", "loaded window_xml");
351 
352  connect_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "connect_window"));
353  gtk_window_set_transient_for(GTK_WINDOW(connect_window),
354  GTK_WINDOW(window_root));
355  g_signal_connect(connect_window, "destroy",
356  G_CALLBACK(on_window_destroy_event), NULL);
357  main_notebook =
358  GTK_NOTEBOOK(gtk_builder_get_object(dialog_xml, "main_notebook"));
359 
360  /* Begin connecting signals for the root window. */
361  window_root = GTK_WIDGET(gtk_builder_get_object(window_xml, "window_root"));
362  if (window_root == NULL) {
363  error_dialog("Could not load main window",
364  "Check that your layout files are not corrupt.");
365  exit(EXIT_FAILURE);
366  }
367 
368  /* Request the window to receive focus in and out events */
369  gtk_widget_add_events((gpointer) window_root, GDK_FOCUS_CHANGE_MASK);
370  g_signal_connect((gpointer) window_root, "focus-out-event",
371  G_CALLBACK(focusoutfunc), NULL);
372 
373  g_signal_connect_swapped((gpointer) window_root, "key_press_event",
374  G_CALLBACK(keyfunc), GTK_WIDGET(window_root));
375  g_signal_connect_swapped((gpointer) window_root, "key_release_event",
376  G_CALLBACK(keyrelfunc), GTK_WIDGET(window_root));
377  g_signal_connect((gpointer) window_root, "destroy",
378  G_CALLBACK(on_window_destroy_event), NULL);
379 
380  /* Purely arbitrary min window size */
381  geometry.min_width=640;
382  geometry.min_height=480;
383 
384  gtk_window_set_geometry_hints(GTK_WINDOW(window_root), window_root,
385  &geometry, GDK_HINT_MIN_SIZE);
386 
387  magic_map = GTK_WIDGET(gtk_builder_get_object(window_xml,
388  "drawingarea_magic_map"));
389 
390  g_signal_connect((gpointer) magic_map, "expose_event",
391  G_CALLBACK(on_drawingarea_magic_map_expose_event), NULL);
392 
393  /* Set up colors before doing the other initialization functions */
394  for (i = 0; i < NUM_COLORS; i++) {
395  if (!gdk_color_parse(colorname[i], &root_color[i])) {
396  fprintf(stderr, "gdk_color_parse failed (%s)\n", colorname[i]);
397  }
398  if (!gdk_colormap_alloc_color(gtk_widget_get_colormap(window_root),
399  &root_color[i], FALSE, FALSE)) {
400  fprintf(stderr, "gdk_color_alloc failed\n");
401  }
402  }
403 
404  LOG(LOG_DEBUG, "init_ui", "sub init");
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  LOG(LOG_DEBUG, "init_ui", "window positions");
416  load_window_positions(window_root);
417 
418  LOG(LOG_DEBUG, "init_ui", "init themes");
419  init_theme();
420  LOG(LOG_DEBUG, "init_ui", "load themes");
421  load_theme(TRUE);
422  LOG(LOG_DEBUG, "init_ui", "menu items");
423  init_menu_items();
424 }
425 
432  gtk_widget_show(window_root);
435 }
436 
442  gtk_widget_hide(window_root);
444  /*
445  * We know the following is the private map structure in item.c. But
446  * we don't have direct access to it, so we still use locate.
447  */
449 
450  if (server != NULL) {
451  sound_server_stop(server);
452  }
453  gtk_widget_show(connect_window);
454 }
455 
459 int main(int argc, char *argv[]) {
460  global_time = g_timer_new();
461 #ifdef ENABLE_NLS
462  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
463  bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
464  textdomain(GETTEXT_PACKAGE);
465 #endif
466 
467  // Initialize GTK and client library.
468  gtk_init(&argc, &argv);
469  client_init();
470 
471  // Set defaults, load configuration, and parse arguments.
472  config_load();
473  parse_args(argc, argv);
474  config_check();
475  char *layout = g_path_get_basename(window_xml_file);
476  snprintf(VERSION_INFO, MAX_BUF,
477  "GTKv2 Client " FULL_VERSION " (%s)", layout);
478  g_free(layout);
479 
480  // Initialize UI, sockets, and sound server.
481  LOG(LOG_DEBUG, "main", "init UI");
482  init_ui();
483  LOG(LOG_DEBUG, "main", "init sockets");
484  init_sockets();
485 
486  LOG(LOG_DEBUG, "main", "init sound");
487  if (!want_config[CONFIG_SOUND] || !init_sounds()) {
488  use_config[CONFIG_SOUND] = FALSE;
489  } else {
490  use_config[CONFIG_SOUND] = TRUE;
491  }
492 
493  /* Load cached pixmaps. */
494  LOG(LOG_DEBUG, "main", "init image cache");
496 
497  LOG(LOG_DEBUG, "main", "init done");
498 
499  while (true) {
500  gtk_widget_show(connect_window);
501  if (connect_server == NULL) {
503  gtk_main();
504  } else {
506  if (csocket.fd == NULL) {
507  LOG(LOG_ERROR, "main", "Unable to connect to %s!", connect_server);
508  break;
509  }
511  }
512 
514  if (serverloginmethod) {
516  } else {
518  }
519 
520  /* The event_loop will block until connection to the server is lost. */
521  event_loop();
522 
524 
525  /*
526  * Need to reset the images so they match up properly and prevent
527  * memory leaks.
528  */
530  client_reset();
531  }
532 }
533 
545 void get_window_coord(GtkWidget *win, int *x, int *y, int *wx, int *wy,
546  int *w, int *h) {
547  /* Position of a window relative to its parent window. */
548  gdk_window_get_geometry(gtk_widget_get_window(win), x, y, w, h, NULL);
549  /* Position of the window in root window coordinates. */
550  gdk_window_get_origin(gtk_widget_get_window(win), wx, wy);
551  *wx -= *x;
552  *wy -= *y;
553 }
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:268
void client_negotiate(int sound)
Definition: client.c:309
void init_create_character_window()
Definition: create_char.c:791
GtkBuilder * window_xml
Definition: main.c:100
void config_check()
Definition: config.c:311
int MINLOG
Definition: misc.c:34
GSocketConnection * fd
Definition: client.h:120
gint16 use_config[CONFIG_NUMS]
Definition: init.c:40
SoundServer * server
Definition: sound.c:25
void mapdata_animation(void)
Definition: mapdata.c:1357
void inventory_init(GtkWidget *window_root)
Definition: inventory.c:452
item * locate_item(gint32 tag)
Definition: item.c:300
static void event_loop()
Definition: main.c:208
static char * connect_server
Definition: main.c:63
void client_init()
Definition: init.c:184
static char * init_ui_layout(const char *name)
Definition: main.c:315
guint16 inv_updated
Definition: item.h:76
item * ob
Definition: client.h:337
void reset_image_data(void)
Definition: image.c:426
void keyfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1564
void load_theme(int reload)
Definition: config.c:138
void show_main_client()
Definition: main.c:430
void stats_init(GtkWidget *window_root)
Definition: stats.c:147
static gboolean script_launch(const gchar *option_name, const gchar *value, gpointer data, GError **error)
Definition: main.c:111
static const char *const colorname[NUM_COLORS]
Definition: main.c:38
void config_load()
Definition: config.c:393
char window_xml_file[MAX_BUF]
Definition: main.c:96
ClientSocket csocket
Definition: client.c:67
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:459
void pickup_init(GtkWidget *window_root)
Definition: pickup.c:401
void init_image_cache_data(void)
Definition: image.c:524
void get_window_coord(GtkWidget *win, int *x, int *y, int *wx, int *wy, int *w, int *h)
Definition: main.c:545
int serverloginmethod
Definition: client.c:59
char VERSION_INFO[MAX_BUF]
Definition: client.c:48
static GOptionEntry options[]
Definition: main.c:68
void init_theme()
Definition: config.c:76
static void init_ui()
Definition: main.c:327
#define DIALOG_FILENAME
Definition: main.h:37
void init_menu_items()
Definition: menubar.c:91
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:111
void client_disconnect()
Definition: client.c:178
void on_window_destroy_event(GtkWidget *object, gpointer user_data)
Definition: main.c:170
void script_init(const char *cparams)
Definition: script.c:205
bool time_map_redraw
Definition: map.c:50
void hide_main_client()
Definition: main.c:441
bool profile_latency
Definition: player.c:35
static gboolean redraw(gpointer data)
Definition: main.c:124
void msgctrl_init(GtkWidget *window_root)
Definition: info.c:1331
GtkWidget * magic_map
Definition: main.c:101
void draw_lists(void)
Definition: inventory.c:1058
Client_Player cpl
Definition: client.c:66
item * container
Definition: client.h:340
static gboolean do_network(GObject *stream, gpointer data)
Definition: main.c:182
GSource * client_get_source()
Definition: client.c:304
GtkWidget * map_notebook
Definition: map.c:44
#define DEFAULT_UI
Definition: main.h:36
#define MAX_BUF
Definition: client.h:40
GdkColor root_color[NUM_COLORS]
Definition: main.c:98
void config_init(GtkWidget *window_root)
Definition: config.c:462
void info_init(GtkWidget *window_root)
Definition: info.c:649
#define CONFIG_CACHE
Definition: client.h:190
gboolean on_drawingarea_magic_map_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
void keys_init(GtkWidget *window_root)
Definition: keys.c:628
gint16 want_config[CONFIG_NUMS]
Definition: init.c:40
void client_tick(guint32 tick)
Definition: main.c:147
GtkWidget * window_root
Definition: main.c:101
#define NUM_COLORS
Definition: main.h:22
#define CONFIG_DOWNLOAD
Definition: client.h:186
void client_run()
Definition: client.c:185
int init_sounds(void)
Definition: sound.c:27
void map_init(GtkWidget *window_root)
Definition: map.c:75
guint8 showmagic
Definition: client.h:365
int have_new_image
Definition: image.c:42
void client_connect(const char hostname[static 1])
Definition: client.c:269
Warning that something definitely didn&#39;t work.
Definition: client.h:445
guint32 spells_updated
Definition: client.h:354
#define MAGIC_MAP_PAGE
Definition: main.h:42
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:291
void load_window_positions(GtkWidget *window_root)
Definition: config.c:825
void client_reset()
Definition: init.c:239
void focusoutfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1520
GTimer * global_time
Definition: misc.c:31
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:342
void metaserver_ui_init()
Definition: metaserver.c:73
void magic_map_flash_pos(void)
Definition: magicmap.c:77
static void init_sockets()
Definition: main.c:296
bool debug_protocol
Definition: main.c:61
#define CONFIG_SOUND
Definition: client.h:198
static void parse_args(int argc, char *argv[])
Definition: main.c:238
#define FULL_VERSION
Definition: version.h:1
GtkWidget * connect_window
Definition: main.c:101
GtkNotebook * main_notebook
Definition: main.c:102
GtkBuilder * dialog_xml
Definition: main.c:100
Useful debugging information.
Definition: client.h:442
static gboolean updatekeycodes
Definition: main.c:54
void script_process(fd_set *set)
Definition: script.c:565
bool client_is_connected()
Definition: client.c:300
Minor, non-harmful issues.
Definition: client.h:443
void draw_map(int redraw)
void keyrelfunc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
Definition: keys.c:1549
void metaserver_show_prompt(void)
Definition: metaserver.c:213
void inventory_tick(void)
Definition: inventory.c:1197