source: owl.c @ 8eb6068

Last change on this file since 8eb6068 was 8eb6068, checked in by David Benjamin <davidben@mit.edu>, 11 years ago
Don't deliver SIGALRM through the signal thread We don't do anything with it, and code that uses alarm(), of which we have none, wouldn't expect it to be set to something useful anyway.
  • Property mode set to 100644
File size: 17.8 KB
Line 
1/*  Copyright (c) 2006-2011 The BarnOwl Developers. All rights reserved.
2 *  Copyright (c) 2004 James Kretchmar. All rights reserved.
3 *
4 *  This program is free software. You can redistribute it and/or
5 *  modify under the terms of the Sleepycat License. See the COPYING
6 *  file included with the distribution for more information.
7 */
8
9#include <stdio.h>
10#include <unistd.h>
11#include <getopt.h>
12#include <stdlib.h>
13#include <string.h>
14#include <signal.h>
15#include <time.h>
16#include <sys/param.h>
17#include <sys/types.h>
18#include <sys/time.h>
19#include <termios.h>
20#include <sys/stat.h>
21#include <locale.h>
22#include "owl.h"
23
24
25#if OWL_STDERR_REDIR
26#ifdef HAVE_SYS_IOCTL_H
27#include <sys/ioctl.h>
28#endif
29#ifdef HAVE_SYS_FILIO_H
30#include <sys/filio.h>
31#endif
32int stderr_replace(void);
33#endif
34
35owl_global g;
36
37typedef struct _owl_options {
38  bool load_initial_subs;
39  char *configfile;
40  char *tty;
41  char *confdir;
42  bool debug;
43  bool rm_debug;
44} owl_options;
45
46void usage(void)
47{
48  fprintf(stderr, "Barnowl version %s\n", OWL_VERSION_STRING);
49  fprintf(stderr, "Usage: barnowl [-n] [-d] [-D] [-v] [-h] [-c <configfile>] [-s <confdir>] [-t <ttyname>]\n");
50  fprintf(stderr, "  -n,--no-subs        don't load zephyr subscriptions\n");
51  fprintf(stderr, "  -d,--debug          enable debugging\n");
52  fprintf(stderr, "  -D,--remove-debug   enable debugging and delete previous debug file\n");
53  fprintf(stderr, "  -v,--version        print the Barnowl version number and exit\n");
54  fprintf(stderr, "  -h,--help           print this help message\n");
55  fprintf(stderr, "  -c,--config-file    specify an alternate config file\n");
56  fprintf(stderr, "  -s,--config-dir     specify an alternate config dir (default ~/.owl)\n");
57  fprintf(stderr, "  -t,--tty            set the tty name\n");
58}
59
60/* TODO: free owl_options after init is done? */
61void owl_parse_options(int argc, char *argv[], owl_options *opts) {
62  static const struct option long_options[] = {
63    { "no-subs",         0, 0, 'n' },
64    { "config-file",     1, 0, 'c' },
65    { "config-dir",      1, 0, 's' },
66    { "tty",             1, 0, 't' },
67    { "debug",           0, 0, 'd' },
68    { "remove-debug",    0, 0, 'D' },
69    { "version",         0, 0, 'v' },
70    { "help",            0, 0, 'h' },
71    { NULL, 0, NULL, 0}
72  };
73  char c;
74
75  while((c = getopt_long(argc, argv, "nc:t:s:dDvh",
76                         long_options, NULL)) != -1) {
77    switch(c) {
78    case 'n':
79      opts->load_initial_subs = 0;
80      break;
81    case 'c':
82      opts->configfile = g_strdup(optarg);
83      break;
84    case 's':
85      opts->confdir = g_strdup(optarg);
86      break;
87    case 't':
88      opts->tty = g_strdup(optarg);
89      break;
90    case 'D':
91      opts->rm_debug = 1;
92      /* fallthrough */
93    case 'd':
94      opts->debug = 1;
95      break;
96    case 'v':
97      printf("This is barnowl version %s\n", OWL_VERSION_STRING);
98      exit(0);
99    case 'h':
100    default:
101      usage();
102      exit(1);
103    }
104  }
105}
106
107void owl_start_color(void) {
108  start_color();
109#ifdef HAVE_USE_DEFAULT_COLORS
110  use_default_colors();
111#endif
112
113  /* define simple color pairs */
114  if (has_colors() && COLOR_PAIRS>=8) {
115    int bg = COLOR_BLACK;
116#ifdef HAVE_USE_DEFAULT_COLORS
117    bg = -1;
118#endif
119    init_pair(OWL_COLOR_BLACK,   COLOR_BLACK,   bg);
120    init_pair(OWL_COLOR_RED,     COLOR_RED,     bg);
121    init_pair(OWL_COLOR_GREEN,   COLOR_GREEN,   bg);
122    init_pair(OWL_COLOR_YELLOW,  COLOR_YELLOW,  bg);
123    init_pair(OWL_COLOR_BLUE,    COLOR_BLUE,    bg);
124    init_pair(OWL_COLOR_MAGENTA, COLOR_MAGENTA, bg);
125    init_pair(OWL_COLOR_CYAN,    COLOR_CYAN,    bg);
126    init_pair(OWL_COLOR_WHITE,   COLOR_WHITE,   bg);
127  }
128}
129
130void owl_start_curses(void) {
131  struct termios tio;
132  /* save initial terminal settings */
133  tcgetattr(STDIN_FILENO, owl_global_get_startup_tio(&g));
134
135  tcgetattr(STDIN_FILENO, &tio);
136  tio.c_iflag &= ~(ISTRIP|IEXTEN);
137  tio.c_cc[VQUIT] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
138  tio.c_cc[VSUSP] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
139  tio.c_cc[VSTART] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
140  tio.c_cc[VSTOP] = fpathconf(STDIN_FILENO, _PC_VDISABLE);
141  tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
142
143  /* screen init */
144  initscr();
145  cbreak();
146  noecho();
147
148  owl_start_color();
149}
150
151void owl_shutdown_curses(void) {
152  endwin();
153  /* restore terminal settings */
154  tcsetattr(STDIN_FILENO, TCSAFLUSH, owl_global_get_startup_tio(&g));
155}
156
157/*
158 * Process a new message passed to us on the message queue from some
159 * protocol. This includes adding it to the message list, updating the
160 * view and scrolling if appropriate, logging it, and so on.
161 *
162 * Either a pointer is kept to the message internally, or it is freed
163 * if unneeded. The caller no longer ``owns'' the message's memory.
164 *
165 * Returns 1 if the message was added to the message list, and 0 if it
166 * was ignored due to user settings or otherwise.
167 */
168static int owl_process_message(owl_message *m) {
169  const owl_filter *f;
170  /* if this message it on the puntlist, nuke it and continue */
171  if (owl_global_message_is_puntable(&g, m)) {
172    owl_message_delete(m);
173    return 0;
174  }
175
176  /*  login or logout that should be ignored? */
177  if (owl_global_is_ignorelogins(&g)
178      && owl_message_is_loginout(m)) {
179    owl_message_delete(m);
180    return 0;
181  }
182
183  if (!owl_global_is_displayoutgoing(&g)
184      && owl_message_is_direction_out(m)) {
185    owl_message_delete(m);
186    return 0;
187  }
188
189  /* add it to the global list */
190  owl_messagelist_append_element(owl_global_get_msglist(&g), m);
191  /* add it to any necessary views; right now there's only the current view */
192  owl_view_consider_message(owl_global_get_current_view(&g), m);
193
194  if(owl_message_is_direction_in(m)) {
195    /* let perl know about it*/
196    owl_perlconfig_getmsg(m, NULL);
197
198    /* do we need to autoreply? */
199    if (owl_global_is_zaway(&g) && !owl_message_get_attribute_value(m, "isauto")) {
200      if (owl_message_is_type_zephyr(m)) {
201        owl_zephyr_zaway(m);
202      } else if (owl_message_is_type_aim(m)) {
203        if (owl_message_is_private(m)) {
204          owl_function_send_aimawymsg(owl_message_get_sender(m), owl_global_get_zaway_msg(&g));
205        }
206      }
207    }
208
209    /* ring the bell if it's a personal */
210    if (!strcmp(owl_global_get_personalbell(&g), "on")) {
211      if (!owl_message_is_loginout(m) &&
212          !owl_message_is_mail(m) &&
213          owl_message_is_personal(m)) {
214        owl_function_beep();
215      }
216    } else if (!strcmp(owl_global_get_personalbell(&g), "off")) {
217      /* do nothing */
218    } else {
219      f=owl_global_get_filter(&g, owl_global_get_personalbell(&g));
220      if (f && owl_filter_message_match(f, m)) {
221        owl_function_beep();
222      }
223    }
224
225    /* if it matches the alert filter, do the alert action */
226    f=owl_global_get_filter(&g, owl_global_get_alert_filter(&g));
227    if (f && owl_filter_message_match(f, m)) {
228      owl_function_command_norv(owl_global_get_alert_action(&g));
229    }
230
231    /* if it's a zephyr login or logout, update the zbuddylist */
232    if (owl_message_is_type_zephyr(m) && owl_message_is_loginout(m)) {
233      if (owl_message_is_login(m)) {
234        owl_zbuddylist_adduser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
235      } else if (owl_message_is_logout(m)) {
236        owl_zbuddylist_deluser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
237      } else {
238        owl_function_error("Internal error: received login notice that is neither login nor logout");
239      }
240    }
241  }
242
243  /* let perl know about it */
244  owl_perlconfig_newmsg(m, NULL);
245  /* log the message if we need to */
246  owl_log_message(m);
247  /* redraw the sepbar; TODO: don't violate layering */
248  owl_global_sepbar_dirty(&g);
249
250  return 1;
251}
252
253static gboolean owl_process_messages_prepare(GSource *source, int *timeout) {
254  *timeout = -1;
255  return owl_global_messagequeue_pending(&g);
256}
257
258static gboolean owl_process_messages_check(GSource *source) {
259  return owl_global_messagequeue_pending(&g);
260}
261
262/*
263 * Process any new messages we have waiting in the message queue.
264 */
265static gboolean owl_process_messages_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) {
266  int newmsgs=0;
267  int followlast = owl_global_should_followlast(&g);
268  owl_message *m;
269
270  /* Grab incoming messages. */
271  while (owl_global_messagequeue_pending(&g)) {
272    m = owl_global_messagequeue_popmsg(&g);
273    if (owl_process_message(m))
274      newmsgs = 1;
275  }
276
277  if (newmsgs) {
278    /* follow the last message if we're supposed to */
279    if (followlast)
280      owl_function_lastmsg_noredisplay();
281
282    /* do the newmsgproc thing */
283    owl_function_do_newmsgproc();
284
285    /* redisplay if necessary */
286    /* this should be optimized to not run if the new messages won't be displayed */
287    owl_mainwin_redisplay(owl_global_get_mainwin(&g));
288  }
289  return TRUE;
290}
291
292static GSourceFuncs owl_process_messages_funcs = {
293  owl_process_messages_prepare,
294  owl_process_messages_check,
295  owl_process_messages_dispatch,
296  NULL
297};
298
299void owl_process_input(const owl_io_dispatch *d, void *data)
300{
301  owl_input j;
302
303  while (1) {
304    j.ch = wgetch(g.input_pad);
305    if (j.ch == ERR) return;
306
307    j.uch = '\0';
308    if (j.ch >= KEY_MIN && j.ch <= KEY_MAX) {
309      /* This is a curses control character. */
310    }
311    else if (j.ch > 0x7f && j.ch < 0xfe) {
312      /* Pull in a full utf-8 character. */
313      int bytes, i;
314      char utf8buf[7];
315      memset(utf8buf, '\0', 7);
316     
317      utf8buf[0] = j.ch;
318     
319      if ((j.ch & 0xc0) && (~j.ch & 0x20)) bytes = 2;
320      else if ((j.ch & 0xe0) && (~j.ch & 0x10)) bytes = 3;
321      else if ((j.ch & 0xf0) && (~j.ch & 0x08)) bytes = 4;
322      else if ((j.ch & 0xf8) && (~j.ch & 0x04)) bytes = 5;
323      else if ((j.ch & 0xfc) && (~j.ch & 0x02)) bytes = 6;
324      else bytes = 1;
325     
326      for (i = 1; i < bytes; i++) {
327        int tmp = wgetch(g.input_pad);
328        /* If what we got was not a byte, or not a continuation byte */
329        if (tmp > 0xff || !(tmp & 0x80 && ~tmp & 0x40)) {
330          /* ill-formed UTF-8 code unit subsequence, put back the
331             char we just got. */
332          ungetch(tmp);
333          j.ch = ERR;
334          break;
335        }
336        utf8buf[i] = tmp;
337      }
338     
339      if (j.ch != ERR) {
340        if (g_utf8_validate(utf8buf, -1, NULL)) {
341          j.uch = g_utf8_get_char(utf8buf);
342        }
343        else {
344          j.ch = ERR;
345        }
346      }
347    }
348    else if (j.ch <= 0x7f) {
349      j.uch = j.ch;
350    }
351
352    owl_process_input_char(j);
353  }
354}
355
356static gboolean sig_handler_main_thread(gpointer data) {
357  int sig = GPOINTER_TO_INT(data);
358
359  owl_function_debugmsg("Got signal %d", sig);
360  if (sig == SIGWINCH) {
361    owl_function_resize();
362  } else if (sig == SIGTERM || sig == SIGHUP) {
363    owl_function_quit();
364  } else if (sig == SIGINT && owl_global_take_interrupt(&g)) {
365    owl_input in;
366    in.ch = in.uch = owl_global_get_startup_tio(&g)->c_cc[VINTR];
367    owl_process_input_char(in);
368  }
369  return FALSE;
370}
371
372static void sig_handler(int sig, void *data) {
373  GMainContext *context = data;
374  GSource *source;
375
376  /* If it was an interrupt, set a flag so we can handle it earlier if
377   * needbe. sig_handler_main_thread will check the flag to make sure
378   * no one else took it. */
379  if (sig == SIGINT) {
380    owl_global_add_interrupt(&g);
381  }
382  /* Send a message to the main thread. */
383  source = g_idle_source_new();
384  g_source_set_priority(source, G_PRIORITY_DEFAULT);
385  g_source_set_callback(source, sig_handler_main_thread,
386                        GINT_TO_POINTER(sig), NULL);
387  g_source_attach(source, context);
388  g_source_unref(source);
389}
390
391void owl_register_signal_handlers(void) {
392  struct sigaction ignore = { .sa_handler = SIG_IGN };
393  sigset_t sigset;
394
395  /* Turn off SIGPIPE; we check the return value of write. */
396  sigaction(SIGPIPE, &ignore, NULL);
397
398  /* Register some signals with the signal thread. */
399  sigemptyset(&sigset);
400  sigaddset(&sigset, SIGWINCH);
401  sigaddset(&sigset, SIGTERM);
402  sigaddset(&sigset, SIGHUP);
403  sigaddset(&sigset, SIGINT);
404  owl_signal_init(&sigset, sig_handler, g_main_context_default());
405}
406
407#if OWL_STDERR_REDIR
408
409/* Replaces stderr with a pipe so that we can read from it.
410 * Returns the fd of the pipe from which stderr can be read. */
411int stderr_replace(void)
412{
413  int pipefds[2];
414  if (0 != pipe(pipefds)) {
415    perror("pipe");
416    owl_function_debugmsg("stderr_replace: pipe FAILED\n");
417    return -1;
418  }
419    owl_function_debugmsg("stderr_replace: pipe: %d,%d\n", pipefds[0], pipefds[1]);
420  if (-1 == dup2(pipefds[1], 2 /*stderr*/)) {
421    owl_function_debugmsg("stderr_replace: dup2 FAILED (%s)\n", strerror(errno));
422    perror("dup2");
423    return -1;
424  }
425  return pipefds[0];
426}
427
428/* Sends stderr (read from rfd) messages to the error console */
429void stderr_redirect_handler(const owl_io_dispatch *d, void *data)
430{
431  int navail, bread;
432  char buf[4096];
433  int rfd = d->fd;
434  char *err;
435
436  if (rfd<0) return;
437  if (-1 == ioctl(rfd, FIONREAD, &navail)) {
438    return;
439  }
440  /*owl_function_debugmsg("stderr_redirect: navail = %d\n", navail);*/
441  if (navail <= 0) return;
442  if (navail > sizeof(buf)-1) {
443    navail = sizeof(buf)-1;
444  }
445  bread = read(rfd, buf, navail);
446  if (buf[navail-1] != '\0') {
447    buf[navail] = '\0';
448  }
449
450  err = g_strdup_printf("[stderr]\n%s", buf);
451
452  owl_function_log_err(err);
453  g_free(err);
454}
455
456#endif /* OWL_STDERR_REDIR */
457
458int main(int argc, char **argv, char **env)
459{
460  int argc_copy;
461  char **argv_copy;
462  char *perlout, *perlerr;
463  const owl_style *s;
464  const char *dir;
465  owl_options opts;
466  GSource *source;
467
468  if (!GLIB_CHECK_VERSION (2, 12, 0))
469    g_error ("GLib version 2.12.0 or above is needed.");
470
471  argc_copy = argc;
472  argv_copy = g_strdupv(argv);
473
474  setlocale(LC_ALL, "");
475
476  memset(&opts, 0, sizeof opts);
477  opts.load_initial_subs = 1;
478  owl_parse_options(argc, argv, &opts);
479  g.load_initial_subs = opts.load_initial_subs;
480
481  owl_start_curses();
482
483  /* owl global init */
484  owl_global_init(&g);
485  if (opts.rm_debug) unlink(OWL_DEBUG_FILE);
486  if (opts.debug) owl_global_set_debug_on(&g);
487  if (opts.confdir) owl_global_set_confdir(&g, opts.confdir);
488  owl_function_debugmsg("startup: first available debugging message");
489  owl_global_set_startupargs(&g, argc_copy, argv_copy);
490  g_strfreev(argv_copy);
491  owl_global_set_haveaim(&g);
492
493  owl_register_signal_handlers();
494
495  /* register STDIN dispatch; throw away return, we won't need it */
496  owl_select_add_io_dispatch(STDIN_FILENO, OWL_IO_READ, &owl_process_input, NULL, NULL);
497  owl_zephyr_initialize();
498
499#if OWL_STDERR_REDIR
500  /* Do this only after we've started curses up... */
501  owl_function_debugmsg("startup: doing stderr redirection");
502  owl_select_add_io_dispatch(stderr_replace(), OWL_IO_READ, &stderr_redirect_handler, NULL, NULL);
503#endif
504
505  /* create the owl directory, in case it does not exist */
506  owl_function_debugmsg("startup: creating owl directory, if not present");
507  dir=owl_global_get_confdir(&g);
508  mkdir(dir, S_IRWXU);
509
510  /* set the tty, either from the command line, or by figuring it out */
511  owl_function_debugmsg("startup: setting tty name");
512  if (opts.tty) {
513    owl_global_set_tty(&g, opts.tty);
514  } else {
515    char *tty = owl_util_get_default_tty();
516    owl_global_set_tty(&g, tty);
517    g_free(tty);
518  }
519
520  /* Initialize perl */
521  owl_function_debugmsg("startup: processing config file");
522
523  owl_global_pop_context(&g);
524  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL, NULL);
525
526  perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
527  if (perlerr) {
528    endwin();
529    fprintf(stderr, "Internal perl error: %s\n", perlerr);
530    fflush(stderr);
531    printf("Internal perl error: %s\n", perlerr);
532    fflush(stdout);
533    exit(1);
534  }
535
536  owl_global_complete_setup(&g);
537
538  owl_global_setup_default_filters(&g);
539
540  /* set the current view */
541  owl_function_debugmsg("startup: setting the current view");
542  owl_view_create(owl_global_get_current_view(&g), "main",
543                  owl_global_get_filter(&g, "all"),
544                  owl_global_get_style_by_name(&g, "default"));
545
546  /* AIM init */
547  owl_function_debugmsg("startup: doing AIM initialization");
548  owl_aim_init();
549
550  /* execute the startup function in the configfile */
551  owl_function_debugmsg("startup: executing perl startup, if applicable");
552  perlout = owl_perlconfig_execute("BarnOwl::Hooks::_startup();");
553  if (perlout) g_free(perlout);
554
555  /* welcome message */
556  owl_function_debugmsg("startup: creating splash message");
557  owl_function_adminmsg("",
558    "-----------------------------------------------------------------------\n"
559    "Welcome to barnowl version " OWL_VERSION_STRING ".\n"
560    "To see a quick introduction, type ':show quickstart'.                  \n"
561    "Press 'h' for on-line help.                                            \n"
562    "                                                                       \n"
563    "BarnOwl is free software. Type ':show license' for more                \n"
564    "information.                                                     ^ ^   \n"
565    "                                                                 OvO   \n"
566    "Please report any bugs or suggestions to bug-barnowl@mit.edu    (   )  \n"
567    "-----------------------------------------------------------------m-m---\n"
568  );
569
570  /* process the startup file */
571  owl_function_debugmsg("startup: processing startup file");
572  owl_function_source(NULL);
573
574  owl_function_debugmsg("startup: set style for the view: %s", owl_global_get_default_style(&g));
575  s = owl_global_get_style_by_name(&g, owl_global_get_default_style(&g));
576  if(s)
577      owl_view_set_style(owl_global_get_current_view(&g), s);
578  else
579      owl_function_error("No such style: %s", owl_global_get_default_style(&g));
580
581  owl_function_debugmsg("startup: setting context interactive");
582
583  owl_global_pop_context(&g);
584  owl_global_push_context(&g, OWL_CTX_INTERACTIVE|OWL_CTX_RECV, NULL, "recv", NULL);
585
586  source = owl_window_redraw_source_new();
587  g_source_attach(source, NULL);
588  g_source_unref(source);
589
590  source = g_source_new(&owl_process_messages_funcs, sizeof(GSource));
591  g_source_attach(source, NULL);
592  g_source_unref(source);
593
594  owl_function_debugmsg("startup: entering main loop");
595  owl_select_run_loop();
596
597  /* Shut down everything. */
598  owl_zephyr_shutdown();
599  owl_shutdown_curses();
600  return 0;
601}
Note: See TracBrowser for help on using the repository browser.