source: owl.c @ a5a9572

release-1.7release-1.8release-1.9
Last change on this file since a5a9572 was 385fda9, checked in by David Benjamin <davidben@mit.edu>, 11 years ago
A fairly hacky job of porting sepbar to owl_window We now use the new framework, but the sepbar isn't being marked dirty at the appropriate times. It'll want to be redone later to take advantage of the new signals. The 'in' parameter was also dropped. It was being unused. At best it provided a strange bug when resizing with appendtosepbar set. Also revert the attempt at cleaning up the cursor. We still have a hack, but using owl_window. The cursor probably needs integration with the context stack or some such.
  • Property mode set to 100644
File size: 18.7 KB
Line 
1/*  Copyright (c) 2006-2010 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
35#define STDIN 0
36
37owl_global g;
38
39typedef struct _owl_options {
40  bool load_initial_subs;
41  char *configfile;
42  char *tty;
43  char *confdir;
44  bool debug;
45  bool rm_debug;
46} owl_options;
47
48void usage(void)
49{
50  fprintf(stderr, "Barnowl version %s\n", OWL_VERSION_STRING);
51  fprintf(stderr, "Usage: barnowl [-n] [-d] [-D] [-v] [-h] [-c <configfile>] [-s <confdir>] [-t <ttyname>]\n");
52  fprintf(stderr, "  -n,--no-subs        don't load zephyr subscriptions\n");
53  fprintf(stderr, "  -d,--debug          enable debugging\n");
54  fprintf(stderr, "  -D,--remove-debug   enable debugging and delete previous debug file\n");
55  fprintf(stderr, "  -v,--version        print the Barnowl version number and exit\n");
56  fprintf(stderr, "  -h,--help           print this help message\n");
57  fprintf(stderr, "  -c,--config-file    specify an alternate config file\n");
58  fprintf(stderr, "  -s,--config-dir     specify an alternate config dir (default ~/.owl)\n");
59  fprintf(stderr, "  -t,--tty            set the tty name\n");
60}
61
62/* TODO: free owl_options after init is done? */
63void owl_parse_options(int argc, char *argv[], owl_options *opts) {
64  static const struct option long_options[] = {
65    { "no-subs",         0, 0, 'n' },
66    { "config-file",     1, 0, 'c' },
67    { "config-dir",      1, 0, 's' },
68    { "tty",             1, 0, 't' },
69    { "debug",           0, 0, 'd' },
70    { "remove-debug",    0, 0, 'D' },
71    { "version",         0, 0, 'v' },
72    { "help",            0, 0, 'h' },
73    { NULL, 0, NULL, 0}
74  };
75  char c;
76
77  while((c = getopt_long(argc, argv, "nc:t:s:dDvh",
78                         long_options, NULL)) != -1) {
79    switch(c) {
80    case 'n':
81      opts->load_initial_subs = 0;
82      break;
83    case 'c':
84      opts->configfile = owl_strdup(optarg);
85      break;
86    case 's':
87      opts->confdir = owl_strdup(optarg);
88      break;
89    case 't':
90      opts->tty = owl_strdup(optarg);
91      break;
92    case 'D':
93      opts->rm_debug = 1;
94      /* fallthrough */
95    case 'd':
96      opts->debug = 1;
97      break;
98    case 'v':
99      printf("This is barnowl version %s\n", OWL_VERSION_STRING);
100      exit(0);
101    case 'h':
102    default:
103      usage();
104      exit(1);
105    }
106  }
107}
108
109void owl_start_color(void) {
110  start_color();
111#ifdef HAVE_USE_DEFAULT_COLORS
112  use_default_colors();
113#endif
114
115  /* define simple color pairs */
116  if (has_colors() && COLOR_PAIRS>=8) {
117    int bg = COLOR_BLACK;
118#ifdef HAVE_USE_DEFAULT_COLORS
119    bg = -1;
120#endif
121    init_pair(OWL_COLOR_BLACK,   COLOR_BLACK,   bg);
122    init_pair(OWL_COLOR_RED,     COLOR_RED,     bg);
123    init_pair(OWL_COLOR_GREEN,   COLOR_GREEN,   bg);
124    init_pair(OWL_COLOR_YELLOW,  COLOR_YELLOW,  bg);
125    init_pair(OWL_COLOR_BLUE,    COLOR_BLUE,    bg);
126    init_pair(OWL_COLOR_MAGENTA, COLOR_MAGENTA, bg);
127    init_pair(OWL_COLOR_CYAN,    COLOR_CYAN,    bg);
128    init_pair(OWL_COLOR_WHITE,   COLOR_WHITE,   bg);
129  }
130}
131
132void owl_start_curses(void) {
133  struct termios tio;
134  /* save initial terminal settings */
135  tcgetattr(0, owl_global_get_startup_tio(&g));
136
137  tcgetattr(0, &tio);
138  tio.c_iflag &= ~(ISTRIP|IEXTEN);
139  tio.c_cc[VQUIT] = 0;
140  tio.c_cc[VSUSP] = 0;
141  tcsetattr(0, TCSAFLUSH, &tio);
142
143  /* screen init */
144  initscr();
145  cbreak();
146  noecho();
147
148  owl_start_color();
149}
150
151static void owl_setup_default_filters(void)
152{
153  int i;
154  static const struct {
155    const char *name;
156    const char *desc;
157  } filters[] = {
158    { "personal",
159      "isprivate ^true$ and ( not type ^zephyr$ or ( class ^message  ) )" },
160    { "trash",
161      "class ^mail$ or opcode ^ping$ or type ^admin$ or ( not login ^none$ )" },
162    { "wordwrap", "not ( type ^admin$ or type ^zephyr$ )" },
163    { "ping", "opcode ^ping$" },
164    { "auto", "opcode ^auto$" },
165    { "login", "not login ^none$" },
166    { "reply-lockout", "class ^noc or class ^mail$" },
167    { "out", "direction ^out$" },
168    { "aim", "type ^aim$" },
169    { "zephyr", "type ^zephyr$" },
170    { "none", "false" },
171    { "all", "true" },
172    { NULL, NULL }
173  };
174
175  owl_function_debugmsg("startup: creating default filters");
176
177  for (i = 0; filters[i].name != NULL; i++)
178    owl_global_add_filter(&g, owl_filter_new_fromstring(filters[i].name,
179                                                        filters[i].desc));
180}
181
182/*
183 * Process a new message passed to us on the message queue from some
184 * protocol. This includes adding it to the message list, updating the
185 * view and scrolling if appropriate, logging it, and so on.
186 *
187 * Either a pointer is kept to the message internally, or it is freed
188 * if unneeded. The caller no longer ``owns'' the message's memory.
189 *
190 * Returns 1 if the message was added to the message list, and 0 if it
191 * was ignored due to user settings or otherwise.
192 */
193int owl_process_message(owl_message *m) {
194  const owl_filter *f;
195  /* if this message it on the puntlist, nuke it and continue */
196  if (owl_global_message_is_puntable(&g, m)) {
197    owl_message_delete(m);
198    return 0;
199  }
200
201  /*  login or logout that should be ignored? */
202  if (owl_global_is_ignorelogins(&g)
203      && owl_message_is_loginout(m)) {
204    owl_message_delete(m);
205    return 0;
206  }
207
208  if (!owl_global_is_displayoutgoing(&g)
209      && owl_message_is_direction_out(m)) {
210    owl_message_delete(m);
211    return 0;
212  }
213
214  /* add it to the global list */
215  owl_messagelist_append_element(owl_global_get_msglist(&g), m);
216  /* add it to any necessary views; right now there's only the current view */
217  owl_view_consider_message(owl_global_get_current_view(&g), m);
218
219  if(owl_message_is_direction_in(m)) {
220    /* let perl know about it*/
221    owl_perlconfig_getmsg(m, NULL);
222
223    /* do we need to autoreply? */
224    if (owl_global_is_zaway(&g) && !owl_message_get_attribute_value(m, "isauto")) {
225      if (owl_message_is_type_zephyr(m)) {
226        owl_zephyr_zaway(m);
227      } else if (owl_message_is_type_aim(m)) {
228        if (owl_message_is_private(m)) {
229          owl_function_send_aimawymsg(owl_message_get_sender(m), owl_global_get_zaway_msg(&g));
230        }
231      }
232    }
233
234    /* ring the bell if it's a personal */
235    if (!strcmp(owl_global_get_personalbell(&g), "on")) {
236      if (!owl_message_is_loginout(m) &&
237          !owl_message_is_mail(m) &&
238          owl_message_is_personal(m)) {
239        owl_function_beep();
240      }
241    } else if (!strcmp(owl_global_get_personalbell(&g), "off")) {
242      /* do nothing */
243    } else {
244      f=owl_global_get_filter(&g, owl_global_get_personalbell(&g));
245      if (f && owl_filter_message_match(f, m)) {
246        owl_function_beep();
247      }
248    }
249
250    /* if it matches the alert filter, do the alert action */
251    f=owl_global_get_filter(&g, owl_global_get_alert_filter(&g));
252    if (f && owl_filter_message_match(f, m)) {
253      owl_function_command(owl_global_get_alert_action(&g));
254    }
255
256    /* if it's a zephyr login or logout, update the zbuddylist */
257    if (owl_message_is_type_zephyr(m) && owl_message_is_loginout(m)) {
258      if (owl_message_is_login(m)) {
259        owl_zbuddylist_adduser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
260      } else if (owl_message_is_logout(m)) {
261        owl_zbuddylist_deluser(owl_global_get_zephyr_buddylist(&g), owl_message_get_sender(m));
262      } else {
263        owl_function_error("Internal error: received login notice that is neither login nor logout");
264      }
265    }
266  }
267
268  /* let perl know about it */
269  owl_perlconfig_newmsg(m, NULL);
270  /* log the message if we need to */
271  owl_log_message(m);
272
273  return 1;
274}
275
276/*
277 * Process any new messages we have waiting in the message queue.
278 * Returns 1 if any messages were added to the message list, and 0 otherwise.
279 */
280int owl_process_messages(owl_ps_action *d, void *p)
281{
282  int newmsgs=0;
283  int followlast = owl_global_should_followlast(&g);
284  owl_message *m;
285
286  /* Grab incoming messages. */
287  while (owl_global_messagequeue_pending(&g)) {
288    m = owl_global_messagequeue_popmsg(&g);
289    if (owl_process_message(m))
290      newmsgs = 1;
291  }
292
293  if (newmsgs) {
294    /* follow the last message if we're supposed to */
295    if (followlast)
296      owl_function_lastmsg_noredisplay();
297
298    /* do the newmsgproc thing */
299    owl_function_do_newmsgproc();
300
301    /* redisplay if necessary */
302    /* this should be optimized to not run if the new messages won't be displayed */
303    owl_mainwin_redisplay(owl_global_get_mainwin(&g));
304    sepbar_dirty();
305  }
306  return newmsgs;
307}
308
309void owl_process_input(const owl_io_dispatch *d, void *data)
310{
311  owl_input j;
312
313  while (1) {
314    j.ch = wgetch(g.input_pad);
315    if (j.ch == ERR) return;
316
317    j.uch = '\0';
318    if (j.ch >= KEY_MIN && j.ch <= KEY_MAX) {
319      /* This is a curses control character. */
320    }
321    else if (j.ch > 0x7f && j.ch < 0xfe) {
322      /* Pull in a full utf-8 character. */
323      int bytes, i;
324      char utf8buf[7];
325      memset(utf8buf, '\0', 7);
326     
327      utf8buf[0] = j.ch;
328     
329      if ((j.ch & 0xc0) && (~j.ch & 0x20)) bytes = 2;
330      else if ((j.ch & 0xe0) && (~j.ch & 0x10)) bytes = 3;
331      else if ((j.ch & 0xf0) && (~j.ch & 0x08)) bytes = 4;
332      else if ((j.ch & 0xf8) && (~j.ch & 0x04)) bytes = 5;
333      else if ((j.ch & 0xfc) && (~j.ch & 0x02)) bytes = 6;
334      else bytes = 1;
335     
336      for (i = 1; i < bytes; i++) {
337        int tmp = wgetch(g.input_pad);
338        /* If what we got was not a byte, or not a continuation byte */
339        if (tmp > 0xff || !(tmp & 0x80 && ~tmp & 0x40)) {
340          /* ill-formed UTF-8 code unit subsequence, put back the
341             char we just got. */
342          ungetch(tmp);
343          j.ch = ERR;
344          break;
345        }
346        utf8buf[i] = tmp;
347      }
348     
349      if (j.ch != ERR) {
350        if (g_utf8_validate(utf8buf, -1, NULL)) {
351          j.uch = g_utf8_get_char(utf8buf);
352        }
353        else {
354          j.ch = ERR;
355        }
356      }
357    }
358    else if (j.ch <= 0x7f) {
359      j.uch = j.ch;
360    }
361
362    owl_process_input_char(j);
363  }
364}
365
366void sig_handler(int sig, siginfo_t *si, void *data)
367{
368  if (sig==SIGWINCH) {
369    /* we can't inturrupt a malloc here, so it just sets a flag
370     * schedulding a resize for later
371     */
372    owl_function_resize();
373  } else if (sig==SIGPIPE || sig==SIGCHLD) {
374    /* Set a flag and some info that we got the sigpipe
375     * so we can record that we got it and why... */
376    owl_global_set_errsignal(&g, sig, si);
377  } else if (sig==SIGTERM || sig==SIGHUP) {
378    owl_function_quit();
379  }
380}
381
382void sigint_handler(int sig, siginfo_t *si, void *data)
383{
384  owl_global_set_interrupted(&g);
385}
386
387void owl_register_signal_handlers(void) {
388  struct sigaction sigact;
389
390  /* signal handler */
391  /*sigact.sa_handler=sig_handler;*/
392  sigact.sa_sigaction=sig_handler;
393  sigemptyset(&sigact.sa_mask);
394  sigact.sa_flags=SA_SIGINFO;
395  sigaction(SIGWINCH, &sigact, NULL);
396  sigaction(SIGALRM, &sigact, NULL);
397  sigaction(SIGPIPE, &sigact, NULL);
398  sigaction(SIGTERM, &sigact, NULL);
399  sigaction(SIGHUP, &sigact, NULL);
400
401  sigact.sa_sigaction=sigint_handler;
402  sigaction(SIGINT, &sigact, NULL);
403}
404
405#if OWL_STDERR_REDIR
406
407/* Replaces stderr with a pipe so that we can read from it.
408 * Returns the fd of the pipe from which stderr can be read. */
409int stderr_replace(void)
410{
411  int pipefds[2];
412  if (0 != pipe(pipefds)) {
413    perror("pipe");
414    owl_function_debugmsg("stderr_replace: pipe FAILED\n");
415    return -1;
416  }
417    owl_function_debugmsg("stderr_replace: pipe: %d,%d\n", pipefds[0], pipefds[1]);
418  if (-1 == dup2(pipefds[1], 2 /*stderr*/)) {
419    owl_function_debugmsg("stderr_replace: dup2 FAILED (%s)\n", strerror(errno));
420    perror("dup2");
421    return -1;
422  }
423  return pipefds[0];
424}
425
426/* Sends stderr (read from rfd) messages to the error console */
427void stderr_redirect_handler(const owl_io_dispatch *d, void *data)
428{
429  int navail, bread;
430  char buf[4096];
431  int rfd = d->fd;
432  char *err;
433
434  if (rfd<0) return;
435  if (-1 == ioctl(rfd, FIONREAD, &navail)) {
436    return;
437  }
438  /*owl_function_debugmsg("stderr_redirect: navail = %d\n", navail);*/
439  if (navail <= 0) return;
440  if (navail > sizeof(buf)-1) {
441    navail = sizeof(buf)-1;
442  }
443  bread = read(rfd, buf, navail);
444  if (buf[navail-1] != '\0') {
445    buf[navail] = '\0';
446  }
447
448  err = owl_sprintf("[stderr]\n%s", buf);
449
450  owl_function_log_err(err);
451  owl_free(err);
452}
453
454#endif /* OWL_STDERR_REDIR */
455
456static int owl_refresh_pre_select_action(owl_ps_action *a, void *data)
457{
458  /* if a resize has been scheduled, deal with it */
459  owl_global_resize(&g, 0, 0);
460  /* also handle relayouts */
461  owl_global_relayout(&g);
462
463  /* update the terminal if we need to */
464  if (owl_global_is_needrefresh(&g)) {
465    /* Redraw the screen */
466
467    /* HACK: move the cursor to unmanaged window if necessary; these should be
468     * associated with the context or something. */
469    if (!owl_popwin_is_active(owl_global_get_popwin(&g))
470        && owl_global_get_typwin(&g)) {
471      owl_window_set_cursor(owl_global_get_curs_typwin(&g));
472    } else {
473      owl_window_set_cursor(owl_global_get_curs_sepwin(&g));
474    }
475
476    owl_window_redraw_scheduled();
477    doupdate();
478    owl_global_set_noneedrefresh(&g);
479  }
480  return 0;
481}
482
483
484int main(int argc, char **argv, char **env)
485{
486  int argcsave;
487  const char *const *argvsave;
488  char *perlout, *perlerr;
489  const owl_style *s;
490  const char *dir;
491  owl_options opts;
492
493  if (!GLIB_CHECK_VERSION (2, 12, 0))
494    g_error ("GLib version 2.12.0 or above is needed.");
495
496  argcsave=argc;
497  argvsave=strs(argv);
498
499  setlocale(LC_ALL, "");
500
501  g_type_init ();
502
503  memset(&opts, 0, sizeof opts);
504  opts.load_initial_subs = 1;
505  owl_parse_options(argc, argv, &opts);
506  g.load_initial_subs = opts.load_initial_subs;
507
508  owl_function_debugmsg("startup: Finished parsing arguments");
509
510  owl_register_signal_handlers();
511  owl_start_curses();
512
513  /* owl global init */
514  owl_global_init(&g);
515  if (opts.rm_debug) unlink(OWL_DEBUG_FILE);
516  if (opts.debug) owl_global_set_debug_on(&g);
517  if (opts.confdir) owl_global_set_confdir(&g, opts.confdir);
518  owl_function_debugmsg("startup: first available debugging message");
519  owl_global_set_startupargs(&g, argcsave, argvsave);
520  owl_global_set_haveaim(&g);
521
522  /* register STDIN dispatch; throw away return, we won't need it */
523  owl_select_add_io_dispatch(STDIN, OWL_IO_READ, &owl_process_input, NULL, NULL);
524  owl_zephyr_initialize();
525
526#if OWL_STDERR_REDIR
527  /* Do this only after we've started curses up... */
528  owl_function_debugmsg("startup: doing stderr redirection");
529  owl_select_add_io_dispatch(stderr_replace(), OWL_IO_READ, &stderr_redirect_handler, NULL, NULL);
530#endif
531
532  /* create the owl directory, in case it does not exist */
533  owl_function_debugmsg("startup: creating owl directory, if not present");
534  dir=owl_global_get_confdir(&g);
535  mkdir(dir, S_IRWXU);
536
537  /* set the tty, either from the command line, or by figuring it out */
538  owl_function_debugmsg("startup: setting tty name");
539  if (opts.tty) {
540    owl_global_set_tty(&g, opts.tty);
541  } else {
542    char *tty = owl_util_get_default_tty();
543    owl_global_set_tty(&g, tty);
544    owl_free(tty);
545  }
546
547  /* Initialize perl */
548  owl_function_debugmsg("startup: processing config file");
549
550  owl_global_pop_context(&g);
551  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL);
552
553  perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
554  if (perlerr) {
555    endwin();
556    fprintf(stderr, "Internal perl error: %s\n", perlerr);
557    fflush(stderr);
558    printf("Internal perl error: %s\n", perlerr);
559    fflush(stdout);
560    exit(1);
561  }
562
563  owl_global_complete_setup(&g);
564
565  owl_setup_default_filters();
566
567  /* set the current view */
568  owl_function_debugmsg("startup: setting the current view");
569  owl_view_create(owl_global_get_current_view(&g), "main",
570                  owl_global_get_filter(&g, "all"),
571                  owl_global_get_style_by_name(&g, "default"));
572
573  /* AIM init */
574  owl_function_debugmsg("startup: doing AIM initialization");
575  owl_aim_init();
576
577  /* execute the startup function in the configfile */
578  owl_function_debugmsg("startup: executing perl startup, if applicable");
579  perlout = owl_perlconfig_execute("BarnOwl::Hooks::_startup();");
580  if (perlout) owl_free(perlout);
581
582  /* welcome message */
583  owl_function_debugmsg("startup: creating splash message");
584  owl_function_adminmsg("",
585    "-----------------------------------------------------------------------\n"
586    "Welcome to barnowl version " OWL_VERSION_STRING ".  Press 'h' for on-line help.\n"
587    "To see a quick introduction, type ':show quickstart'.                  \n"
588    "                                                                       \n"
589    "BarnOwl is free software. Type ':show license' for more                \n"
590    "information.                                                     ^ ^   \n"
591    "                                                                 OvO   \n"
592    "Please report any bugs or suggestions to bug-barnowl@mit.edu    (   )  \n"
593    "-----------------------------------------------------------------m-m---\n"
594  );
595
596  /* process the startup file */
597  owl_function_debugmsg("startup: processing startup file");
598  owl_function_source(NULL);
599
600  /* Set the default style */
601  owl_function_debugmsg("startup: setting startup and default style");
602  if (0 != strcmp(owl_global_get_default_style(&g), "__unspecified__")) {
603    /* the style was set by the user: leave it alone */
604  } else {
605    owl_global_set_default_style(&g, "default");
606  }
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_READCONFIG|OWL_CTX_RECV, NULL, "recv");
619
620  owl_select_add_pre_select_action(owl_refresh_pre_select_action, NULL, NULL);
621  owl_select_add_pre_select_action(owl_process_messages, NULL, NULL);
622
623  owl_function_debugmsg("startup: entering main loop");
624  /* main loop */
625  while (1) {
626    /* select on FDs we know about. */
627    owl_select();
628
629    /* Log any error signals */
630    {
631      siginfo_t si;
632      int signum;
633      if ((signum = owl_global_get_errsignal_and_clear(&g, &si)) > 0) {
634        owl_function_error("Got unexpected signal: %d %s  (code: %d band: %ld  errno: %d)",
635                           signum, signum==SIGPIPE?"SIGPIPE":"SIG????",
636                           si.si_code, si.si_band, si.si_errno);
637      }
638    }
639
640  }
641}
Note: See TracBrowser for help on using the repository browser.