source: owl.c @ 6eaafb0

Last change on this file since 6eaafb0 was 6eaafb0, checked in by David Benjamin <davidben@mit.edu>, 11 years ago
Cleanly destroy the signal thread on shutdown This isn't strictly necessary here, but we should probably be in the habit of safely cleaning up our helper threads. Switch the implementation from GThread to pthreads directly. The cleanest way to kill a signaling thread is to send it SIGTERM with pthread_kill, but GThread doesn't expose that and gives no way to get at the pthread_t.
  • Property mode set to 100644
File size: 19.0 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_char(owl_input j)
300{
301  int ret;
302
303  owl_global_set_lastinputtime(&g, time(NULL));
304  ret = owl_keyhandler_process(owl_global_get_keyhandler(&g), j);
305  if (ret!=0 && ret!=1) {
306    owl_function_makemsg("Unable to handle keypress");
307  }
308}
309
310void owl_process_input(const owl_io_dispatch *d, void *data)
311{
312  owl_input j;
313
314  while (1) {
315    j.ch = wgetch(g.input_pad);
316    if (j.ch == ERR) return;
317
318    j.uch = '\0';
319    if (j.ch >= KEY_MIN && j.ch <= KEY_MAX) {
320      /* This is a curses control character. */
321    }
322    else if (j.ch > 0x7f && j.ch < 0xfe) {
323      /* Pull in a full utf-8 character. */
324      int bytes, i;
325      char utf8buf[7];
326      memset(utf8buf, '\0', 7);
327     
328      utf8buf[0] = j.ch;
329     
330      if ((j.ch & 0xc0) && (~j.ch & 0x20)) bytes = 2;
331      else if ((j.ch & 0xe0) && (~j.ch & 0x10)) bytes = 3;
332      else if ((j.ch & 0xf0) && (~j.ch & 0x08)) bytes = 4;
333      else if ((j.ch & 0xf8) && (~j.ch & 0x04)) bytes = 5;
334      else if ((j.ch & 0xfc) && (~j.ch & 0x02)) bytes = 6;
335      else bytes = 1;
336     
337      for (i = 1; i < bytes; i++) {
338        int tmp = wgetch(g.input_pad);
339        /* If what we got was not a byte, or not a continuation byte */
340        if (tmp > 0xff || !(tmp & 0x80 && ~tmp & 0x40)) {
341          /* ill-formed UTF-8 code unit subsequence, put back the
342             char we just got. */
343          ungetch(tmp);
344          j.ch = ERR;
345          break;
346        }
347        utf8buf[i] = tmp;
348      }
349     
350      if (j.ch != ERR) {
351        if (g_utf8_validate(utf8buf, -1, NULL)) {
352          j.uch = g_utf8_get_char(utf8buf);
353        }
354        else {
355          j.ch = ERR;
356        }
357      }
358    }
359    else if (j.ch <= 0x7f) {
360      j.uch = j.ch;
361    }
362
363    owl_process_input_char(j);
364  }
365}
366
367static gboolean sig_handler_main_thread(gpointer data) {
368  int sig = GPOINTER_TO_INT(data);
369
370  owl_function_debugmsg("Got signal %d", sig);
371  if (sig == SIGWINCH) {
372    owl_function_resize();
373  } else if (sig == SIGTERM || sig == SIGHUP) {
374    owl_function_quit();
375  } else if (sig == SIGINT && owl_global_take_interrupt(&g)) {
376    owl_input in;
377    in.ch = in.uch = owl_global_get_startup_tio(&g)->c_cc[VINTR];
378    owl_process_input_char(in);
379  }
380  return FALSE;
381}
382
383static void sig_handler(int sig, void *data) {
384  GMainContext *context = data;
385  GSource *source;
386
387  /* If it was an interrupt, set a flag so we can handle it earlier if
388   * needbe. sig_handler_main_thread will check the flag to make sure
389   * no one else took it. */
390  if (sig == SIGINT) {
391    owl_global_add_interrupt(&g);
392  }
393  /* Send a message to the main thread. */
394  source = g_idle_source_new();
395  g_source_set_priority(source, G_PRIORITY_DEFAULT);
396  g_source_set_callback(source, sig_handler_main_thread,
397                        GINT_TO_POINTER(sig), NULL);
398  g_source_attach(source, context);
399  g_source_unref(source);
400}
401
402#define CHECK_RESULT(s, syscall) \
403  G_STMT_START {                 \
404    if ((syscall) != 0) {        \
405      perror((s));               \
406      exit(1);                   \
407    }                            \
408  } G_STMT_END
409
410void owl_register_signal_handlers(void) {
411  struct sigaction sig_ignore = { .sa_handler = SIG_IGN };
412  struct sigaction sig_default = { .sa_handler = SIG_DFL };
413  sigset_t sigset;
414  int ret, i;
415  const int signals[] = { SIGABRT, SIGBUS, SIGCHLD, SIGFPE, SIGHUP, SIGILL,
416                          SIGINT, SIGQUIT, SIGSEGV, SIGTERM, SIGWINCH };
417
418  /* Sanitize our signals; the mask and dispositions from our parent
419   * aren't really useful. Signal list taken from equivalent code in
420   * Chromium. */
421  CHECK_RESULT("sigemptyset", sigemptyset(&sigset));
422  if ((ret = pthread_sigmask(SIG_SETMASK, &sigset, NULL)) != 0) {
423    errno = ret;
424    perror("pthread_sigmask");
425  }
426  for (i = 0; i < G_N_ELEMENTS(signals); i++) {
427    CHECK_RESULT("sigaction", sigaction(signals[i], &sig_default, NULL));
428  }
429
430  /* Turn off SIGPIPE; we check the return value of write. */
431  CHECK_RESULT("sigaction", sigaction(SIGPIPE, &sig_ignore, NULL));
432
433  /* Register some signals with the signal thread. */
434  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGWINCH));
435  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGTERM));
436  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGHUP));
437  CHECK_RESULT("sigaddset", sigaddset(&sigset, SIGINT));
438  owl_signal_init(&sigset, sig_handler, g_main_context_default());
439}
440
441#if OWL_STDERR_REDIR
442
443/* Replaces stderr with a pipe so that we can read from it.
444 * Returns the fd of the pipe from which stderr can be read. */
445int stderr_replace(void)
446{
447  int pipefds[2];
448  if (0 != pipe(pipefds)) {
449    perror("pipe");
450    owl_function_debugmsg("stderr_replace: pipe FAILED\n");
451    return -1;
452  }
453    owl_function_debugmsg("stderr_replace: pipe: %d,%d\n", pipefds[0], pipefds[1]);
454  if (-1 == dup2(pipefds[1], 2 /*stderr*/)) {
455    owl_function_debugmsg("stderr_replace: dup2 FAILED (%s)\n", strerror(errno));
456    perror("dup2");
457    return -1;
458  }
459  return pipefds[0];
460}
461
462/* Sends stderr (read from rfd) messages to the error console */
463void stderr_redirect_handler(const owl_io_dispatch *d, void *data)
464{
465  int navail, bread;
466  char buf[4096];
467  int rfd = d->fd;
468  char *err;
469
470  if (rfd<0) return;
471  if (-1 == ioctl(rfd, FIONREAD, &navail)) {
472    return;
473  }
474  /*owl_function_debugmsg("stderr_redirect: navail = %d\n", navail);*/
475  if (navail <= 0) return;
476  if (navail > sizeof(buf)-1) {
477    navail = sizeof(buf)-1;
478  }
479  bread = read(rfd, buf, navail);
480  if (buf[navail-1] != '\0') {
481    buf[navail] = '\0';
482  }
483
484  err = g_strdup_printf("[stderr]\n%s", buf);
485
486  owl_function_log_err(err);
487  g_free(err);
488}
489
490#endif /* OWL_STDERR_REDIR */
491
492int main(int argc, char **argv, char **env)
493{
494  int argc_copy;
495  char **argv_copy;
496  char *perlout, *perlerr;
497  const owl_style *s;
498  const char *dir;
499  owl_options opts;
500  GSource *source;
501
502  if (!GLIB_CHECK_VERSION (2, 12, 0))
503    g_error ("GLib version 2.12.0 or above is needed.");
504
505  argc_copy = argc;
506  argv_copy = g_strdupv(argv);
507
508  setlocale(LC_ALL, "");
509
510  memset(&opts, 0, sizeof opts);
511  opts.load_initial_subs = 1;
512  owl_parse_options(argc, argv, &opts);
513  g.load_initial_subs = opts.load_initial_subs;
514
515  owl_start_curses();
516
517  /* owl global init */
518  owl_global_init(&g);
519  if (opts.rm_debug) unlink(OWL_DEBUG_FILE);
520  if (opts.debug) owl_global_set_debug_on(&g);
521  if (opts.confdir) owl_global_set_confdir(&g, opts.confdir);
522  owl_function_debugmsg("startup: first available debugging message");
523  owl_global_set_startupargs(&g, argc_copy, argv_copy);
524  g_strfreev(argv_copy);
525  owl_global_set_haveaim(&g);
526
527  owl_register_signal_handlers();
528
529  /* register STDIN dispatch; throw away return, we won't need it */
530  owl_select_add_io_dispatch(STDIN_FILENO, OWL_IO_READ, &owl_process_input, NULL, NULL);
531  owl_zephyr_initialize();
532
533#if OWL_STDERR_REDIR
534  /* Do this only after we've started curses up... */
535  owl_function_debugmsg("startup: doing stderr redirection");
536  owl_select_add_io_dispatch(stderr_replace(), OWL_IO_READ, &stderr_redirect_handler, NULL, NULL);
537#endif
538
539  /* create the owl directory, in case it does not exist */
540  owl_function_debugmsg("startup: creating owl directory, if not present");
541  dir=owl_global_get_confdir(&g);
542  mkdir(dir, S_IRWXU);
543
544  /* set the tty, either from the command line, or by figuring it out */
545  owl_function_debugmsg("startup: setting tty name");
546  if (opts.tty) {
547    owl_global_set_tty(&g, opts.tty);
548  } else {
549    char *tty = owl_util_get_default_tty();
550    owl_global_set_tty(&g, tty);
551    g_free(tty);
552  }
553
554  /* Initialize perl */
555  owl_function_debugmsg("startup: processing config file");
556
557  owl_global_pop_context(&g);
558  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL, NULL);
559
560  perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
561  if (perlerr) {
562    endwin();
563    fprintf(stderr, "Internal perl error: %s\n", perlerr);
564    fflush(stderr);
565    printf("Internal perl error: %s\n", perlerr);
566    fflush(stdout);
567    exit(1);
568  }
569
570  owl_global_complete_setup(&g);
571
572  owl_global_setup_default_filters(&g);
573
574  /* set the current view */
575  owl_function_debugmsg("startup: setting the current view");
576  owl_view_create(owl_global_get_current_view(&g), "main",
577                  owl_global_get_filter(&g, "all"),
578                  owl_global_get_style_by_name(&g, "default"));
579
580  /* AIM init */
581  owl_function_debugmsg("startup: doing AIM initialization");
582  owl_aim_init();
583
584  /* execute the startup function in the configfile */
585  owl_function_debugmsg("startup: executing perl startup, if applicable");
586  perlout = owl_perlconfig_execute("BarnOwl::Hooks::_startup();");
587  if (perlout) g_free(perlout);
588
589  /* welcome message */
590  owl_function_debugmsg("startup: creating splash message");
591  owl_function_adminmsg("",
592    "-----------------------------------------------------------------------\n"
593    "Welcome to barnowl version " OWL_VERSION_STRING ".\n"
594    "To see a quick introduction, type ':show quickstart'.                  \n"
595    "Press 'h' for on-line help.                                            \n"
596    "                                                                       \n"
597    "BarnOwl is free software. Type ':show license' for more                \n"
598    "information.                                                     ^ ^   \n"
599    "                                                                 OvO   \n"
600    "Please report any bugs or suggestions to bug-barnowl@mit.edu    (   )  \n"
601    "-----------------------------------------------------------------m-m---\n"
602  );
603
604  /* process the startup file */
605  owl_function_debugmsg("startup: processing startup file");
606  owl_function_source(NULL);
607
608  owl_function_debugmsg("startup: set style for the view: %s", owl_global_get_default_style(&g));
609  s = owl_global_get_style_by_name(&g, owl_global_get_default_style(&g));
610  if(s)
611      owl_view_set_style(owl_global_get_current_view(&g), s);
612  else
613      owl_function_error("No such style: %s", owl_global_get_default_style(&g));
614
615  owl_function_debugmsg("startup: setting context interactive");
616
617  owl_global_pop_context(&g);
618  owl_global_push_context(&g, OWL_CTX_INTERACTIVE|OWL_CTX_RECV, NULL, "recv", NULL);
619
620  source = owl_window_redraw_source_new();
621  g_source_attach(source, NULL);
622  g_source_unref(source);
623
624  source = g_source_new(&owl_process_messages_funcs, sizeof(GSource));
625  g_source_attach(source, NULL);
626  g_source_unref(source);
627
628  owl_function_debugmsg("startup: entering main loop");
629  owl_select_run_loop();
630
631  /* Shut down everything. */
632  owl_zephyr_shutdown();
633  owl_signal_shutdown();
634  owl_shutdown_curses();
635  return 0;
636}
Note: See TracBrowser for help on using the repository browser.