hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

main.c (5830B)


      1 /*
      2  * src/main.c from hirc
      3  *
      4  * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <stdio.h>
     21 #include <wchar.h>
     22 #include <errno.h>
     23 #include <stdlib.h>
     24 #include <libgen.h>
     25 #include <limits.h>
     26 #include <string.h>
     27 #include <unistd.h>
     28 #include <stdarg.h>
     29 #include <signal.h>
     30 #include <poll.h>
     31 #include "hirc.h"
     32 
     33 struct Server *servers = NULL;
     34 struct HistInfo *main_buf;
     35 
     36 void
     37 die(int code, char *format, ...) {
     38 	static int dying = 0;
     39 	va_list ap;
     40 
     41 	/* prevent loop if a function in cleanup() calls die() */
     42 	if (!dying) {
     43 		dying = 1;
     44 		cleanup("Client error");
     45 		dying = 0;
     46 
     47 		fprintf(stderr, "Fatal: ");
     48 		va_start(ap, format);
     49 		vfprintf(stderr, format, ap);
     50 		va_end(ap);
     51 #ifdef DIE_CORE
     52 		raise(SIGABRT);
     53 #else
     54 		exit(code);
     55 #endif /* DIE_CORE */
     56 	}
     57 }
     58 
     59 void
     60 cleanup(char *quitmsg) {
     61 	struct Server *sp, *prev;
     62 
     63 	for (sp = servers, prev = NULL; sp; sp = sp->next) {
     64 		if (prev)
     65 			serv_free(prev);
     66 		serv_disconnect(sp, 0, quitmsg);
     67 		prev = sp;
     68 	}
     69 
     70 	serv_free(prev);
     71 	ui_deinit();
     72 }
     73 
     74 int
     75 main(int argc, char *argv[]) {
     76 	struct Selected oldselected;
     77 	struct Server *sp;
     78 	int i, j, refreshed, inputrefreshed;
     79 	long pinginact, reconnectinterval, maxreconnectinterval;
     80 
     81 	if (argc > 2) {
     82 		fprintf(stderr, "usage: %s [configfile]\n", basename(argv[0]));
     83 		fprintf(stderr, "       %s -d\n", basename(argv[0]));
     84 		return EXIT_FAILURE;
     85 	}
     86 
     87 	if (argc == 2 && strcmp(argv[1], "-d") == 0) {
     88 		printf(".Bl -tag\n");
     89 		for (i=0; config[i].name; i++) {
     90 			printf(".It Ic %s\n", config[i].name);
     91 			printf(".Bd -literal -compact\n");
     92 			printf("Default value: %s\n", config_get_pretty(&config[i], 1));
     93 			for (j=0; config[i].description[j]; j++)
     94 				printf("%s\n", config[i].description[j]);
     95 			printf(".Ed\n");
     96 		}
     97 		printf(".El\n");
     98 		printf(".Sh COMMANDS\n");
     99 		printf(".Bl -tag\n");
    100 		for (i=0; commands[i].name && commands[i].func; i++) {
    101 			printf(".It Ic /%s\n", commands[i].name);
    102 			printf(".Bd -literal -compact\n");
    103 			for (j=0; commands[i].description[j]; j++)
    104 				printf("%s\n", commands[i].description[j]);
    105 			printf(".Ed\n");
    106 		}
    107 		printf(".El\n");
    108 		return 0;
    109 	}
    110 
    111 	main_buf = emalloc(sizeof(struct HistInfo));
    112 	main_buf->activity = Activity_none;
    113 	main_buf->unread = main_buf->ignored = 0;
    114 	main_buf->server = NULL;
    115 	main_buf->channel = NULL;
    116 	main_buf->history = NULL;
    117 
    118 	ui_init();
    119 
    120 	if (argc == 2)
    121 		if (config_read(argv[1]) == -1)
    122 			die(1, "cannot read config file '%s': %s\n", argv[1], strerror(errno));
    123 
    124 	for (;;) {
    125 		/* 25 seems fast enough not to cause any visual lag */
    126 		if (serv_poll(&servers, 25) < 0) {
    127 			perror("serv_poll()");
    128 			exit(EXIT_FAILURE);
    129 		}
    130 
    131 		pinginact = config_getl("misc.pingtime");
    132 		reconnectinterval = config_getl("reconnect.interval");
    133 		maxreconnectinterval = config_getl("reconnect.maxinterval");
    134 		for (sp = servers; sp; sp = sp->next) {
    135 			if (sp->rpollfd->revents) {
    136 				/* received an event */
    137 				sp->pingsent = 0;
    138 				sp->lastrecv = time(NULL);
    139 				sp->rpollfd->revents = 0;
    140 				serv_read(sp);
    141 			} else if (!sp->pingsent && sp->lastrecv && (time(NULL) - sp->lastrecv) >= pinginact) {
    142 				/* haven't heard from server in pinginact seconds, sending a ping */
    143 				serv_write(sp, Sched_now, "PING :ground control to Major Tom\r\n");
    144 				sp->pingsent = time(NULL);
    145 			} else if (sp->pingsent && (time(NULL) - sp->pingsent) >= pinginact) {
    146 				/* haven't gotten a response in pinginact seconds since
    147 				 * sending ping, this connexion is probably dead now */
    148 				serv_disconnect(sp, 1, NULL);
    149 				hist_format(sp->history, Activity_error, HIST_SHOW,
    150 						"SELF_CONNECTLOST %s %s %s :No ping reply in %d seconds",
    151 						sp->name, sp->host, sp->port, pinginact);
    152 			} else if (sp->status == ConnStatus_notconnected && sp->reconnect &&
    153 					((time(NULL) - sp->lastconnected) >= maxreconnectinterval ||
    154 					(time(NULL) - sp->lastconnected) >= (sp->connectfail * reconnectinterval))) {
    155 				/* time since last connected is sufficient to initiate reconnect */
    156 				serv_connect(sp);
    157 			}
    158 		}
    159 
    160 		if (oldselected.channel != selected.channel || oldselected.server != selected.server) {
    161 			if (windows[Win_nicklist].location)
    162 				windows[Win_nicklist].refresh = 1;
    163 			if (windows[Win_buflist].location)
    164 				windows[Win_buflist].refresh = 1;
    165 		}
    166 
    167 		if (oldselected.history != selected.history)
    168 			windows[Win_main].refresh = 1;
    169 
    170 		oldselected.channel = selected.channel;
    171 		oldselected.server = selected.server;
    172 		oldselected.history = selected.history;
    173 		oldselected.name = selected.name;
    174 
    175 		if (uineedredraw) {
    176 			uineedredraw = 0;
    177 			ui_redraw();
    178 			for (i=0; i < Win_last; i++)
    179 				windows[i].refresh = 0;
    180 			continue;
    181 		}
    182 
    183 		refreshed = inputrefreshed = 0;
    184 		for (i=0; i < Win_last; i++) {
    185 			if (windows[i].refresh && windows[i].location) {
    186 				if (windows[i].handler)
    187 					windows[i].handler();
    188 				wnoutrefresh(windows[i].window);
    189 				windows[i].refresh = 0;
    190 				refreshed = 1;
    191 				if (i == Win_input)
    192 					inputrefreshed = 1;
    193 			}
    194 		}
    195 		doupdate();
    196 
    197 		/* refresh Win_input after any other window to
    198 		 * force ncurses to place the cursor here. */
    199 		if (refreshed && !inputrefreshed)
    200 			wrefresh(windows[Win_input].window);
    201 
    202 		ui_read();
    203 	}
    204 
    205 	return 0;
    206 }