1 /* HexChat
2  * Copyright (C) 1998-2010 Peter Zelezny.
3  * Copyright (C) 2009-2013 Berke Viktor.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 /* per-channel/dialog settings :: /CHANOPT */
21 
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 
30 #ifdef WIN32
31 #include <io.h>
32 #else
33 #include <unistd.h>
34 #endif
35 
36 #include "hexchat.h"
37 
38 #include "cfgfiles.h"
39 #include "server.h"
40 #include "text.h"
41 #include "util.h"
42 #include "hexchatc.h"
43 
44 
45 static GSList *chanopt_list = NULL;
46 static gboolean chanopt_open = FALSE;
47 static gboolean chanopt_changed = FALSE;
48 
49 
50 typedef struct
51 {
52 	char *name;
53 	char *alias;	/* old names from 2.8.4 */
54 	int offset;
55 } channel_options;
56 
57 #define S_F(xx) STRUCT_OFFSET_STR(struct session,xx)
58 
59 static const channel_options chanopt[] =
60 {
61 	{"alert_balloon", NULL, S_F(alert_balloon)},
62 	{"alert_beep", "BEEP", S_F(alert_beep)},
63 	{"alert_taskbar", NULL, S_F(alert_taskbar)},
64 	{"alert_tray", "TRAY", S_F(alert_tray)},
65 
66 	{"text_hidejoinpart", "CONFMODE", S_F(text_hidejoinpart)},
67 	{"text_logging", NULL, S_F(text_logging)},
68 	{"text_scrollback", NULL, S_F(text_scrollback)},
69 	{"text_strip", NULL, S_F(text_strip)},
70 };
71 
72 #undef S_F
73 
74 static char *
chanopt_value(guint8 val)75 chanopt_value (guint8 val)
76 {
77 	switch (val)
78 	{
79 	case SET_OFF:
80 		return _("OFF");
81 	case SET_ON:
82 		return _("ON");
83 	case SET_DEFAULT:
84 		return _("{unset}");
85 	default:
86 		g_assert_not_reached ();
87 		return NULL;
88 	}
89 }
90 
91 static guint8
str_to_chanopt(const char * str)92 str_to_chanopt (const char *str)
93 {
94 	if (!g_ascii_strcasecmp (str, "ON") || !strcmp (str, "1"))
95 		return SET_ON;
96 	else if (!g_ascii_strcasecmp (str, "OFF") || !strcmp (str, "0"))
97 		return SET_OFF;
98 	else
99 		return SET_DEFAULT;
100 }
101 
102 /* handle the /CHANOPT command */
103 
104 int
chanopt_command(session * sess,char * tbuf,char * word[],char * word_eol[])105 chanopt_command (session *sess, char *tbuf, char *word[], char *word_eol[])
106 {
107 	int dots, i = 0, j, p = 0;
108 	guint8 val;
109 	int offset = 2;
110 	char *find;
111 	gboolean quiet = FALSE;
112 	int newval = -1;
113 
114 	if (!strcmp (word[2], "-quiet"))
115 	{
116 		quiet = TRUE;
117 		offset++;
118 	}
119 
120 	find = word[offset++];
121 
122 	if (word[offset][0])
123 	{
124 		newval = str_to_chanopt (word[offset]);
125 	}
126 
127 	if (!quiet)
128 		PrintTextf (sess, "\002%s\002: %s \002%s\002: %s\n",
129 						_("Network"),
130 						sess->server->network ? server_get_network (sess->server, TRUE) : _("<none>"),
131 						_("Channel"),
132 						sess->session_name[0] ? sess->session_name : _("<none>"));
133 
134 	while (i < sizeof (chanopt) / sizeof (channel_options))
135 	{
136 		if (find[0] == 0 || match (find, chanopt[i].name) || (chanopt[i].alias && match (find, chanopt[i].alias)))
137 		{
138 			if (newval != -1)	/* set new value */
139 			{
140 				*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = newval;
141 				chanopt_changed = TRUE;
142 			}
143 
144 			if (!quiet)	/* print value */
145 			{
146 				strcpy (tbuf, chanopt[i].name);
147 				p = strlen (tbuf);
148 
149 				tbuf[p++] = 3;
150 				tbuf[p++] = '2';
151 
152 				dots = 20 - strlen (chanopt[i].name);
153 
154 				for (j = 0; j < dots; j++)
155 					tbuf[p++] = '.';
156 				tbuf[p++] = 0;
157 
158 				val = G_STRUCT_MEMBER (guint8, sess, chanopt[i].offset);
159 				PrintTextf (sess, "%s\0033:\017 %s", tbuf, chanopt_value (val));
160 			}
161 		}
162 		i++;
163 	}
164 
165 	return TRUE;
166 }
167 
168 /* is a per-channel setting set? Or is it UNSET and
169  * the global version is set? */
170 
171 gboolean
chanopt_is_set(unsigned int global,guint8 per_chan_setting)172 chanopt_is_set (unsigned int global, guint8 per_chan_setting)
173 {
174 	if (per_chan_setting == SET_ON || per_chan_setting == SET_OFF)
175 		return per_chan_setting;
176 	else
177 		return global;
178 }
179 
180 /* === below is LOADING/SAVING stuff only === */
181 
182 typedef struct
183 {
184 	/* Per-Channel Alerts */
185 	/* use a byte, because we need a pointer to each element */
186 	guint8 alert_balloon;
187 	guint8 alert_beep;
188 	guint8 alert_taskbar;
189 	guint8 alert_tray;
190 
191 	/* Per-Channel Settings */
192 	guint8 text_hidejoinpart;
193 	guint8 text_logging;
194 	guint8 text_scrollback;
195 	guint8 text_strip;
196 
197 	char *network;
198 	char *channel;
199 
200 } chanopt_in_memory;
201 
202 
203 static chanopt_in_memory *
chanopt_find(char * network,char * channel,gboolean add_new)204 chanopt_find (char *network, char *channel, gboolean add_new)
205 {
206 	GSList *list;
207 	chanopt_in_memory *co;
208 	int i;
209 
210 	for (list = chanopt_list; list; list = list->next)
211 	{
212 		co = list->data;
213 		if (!g_ascii_strcasecmp (co->channel, channel) &&
214 			 !g_ascii_strcasecmp (co->network, network))
215 			return co;
216 	}
217 
218 	if (!add_new)
219 		return NULL;
220 
221 	/* allocate a new one */
222 	co = g_new0 (chanopt_in_memory, 1);
223 	co->channel = g_strdup (channel);
224 	co->network = g_strdup (network);
225 
226 	/* set all values to SET_DEFAULT */
227 	i = 0;
228 	while (i < sizeof (chanopt) / sizeof (channel_options))
229 	{
230 		*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = SET_DEFAULT;
231 		i++;
232 	}
233 
234 	chanopt_list = g_slist_prepend (chanopt_list, co);
235 	chanopt_changed = TRUE;
236 
237 	return co;
238 }
239 
240 static void
chanopt_add_opt(chanopt_in_memory * co,char * var,int new_value)241 chanopt_add_opt (chanopt_in_memory *co, char *var, int new_value)
242 {
243 	int i;
244 
245 	i = 0;
246 	while (i < sizeof (chanopt) / sizeof (channel_options))
247 	{
248 		if (!strcmp (var, chanopt[i].name))
249 		{
250 			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = new_value;
251 
252 		}
253 		i++;
254 	}
255 }
256 
257 /* load chanopt.conf from disk into our chanopt_list GSList */
258 
259 static void
chanopt_load_all(void)260 chanopt_load_all (void)
261 {
262 	int fh;
263 	char buf[256];
264 	char *eq;
265 	char *network = NULL;
266 	chanopt_in_memory *current = NULL;
267 
268 	/* 1. load the old file into our GSList */
269 	fh = hexchat_open_file ("chanopt.conf", O_RDONLY, 0, 0);
270 	if (fh != -1)
271 	{
272 		while (waitline (fh, buf, sizeof buf, FALSE) != -1)
273 		{
274 			eq = strchr (buf, '=');
275 			if (!eq)
276 				continue;
277 			eq[0] = 0;
278 
279 			if (eq != buf && eq[-1] == ' ')
280 				eq[-1] = 0;
281 
282 			if (!strcmp (buf, "network"))
283 			{
284 				g_free (network);
285 				network = g_strdup (eq + 2);
286 			}
287 			else if (!strcmp (buf, "channel"))
288 			{
289 				current = chanopt_find (network, eq + 2, TRUE);
290 				chanopt_changed = FALSE;
291 			}
292 			else
293 			{
294 				if (current)
295 					chanopt_add_opt (current, buf, str_to_chanopt (eq + 2));
296 			}
297 
298 		}
299 		close (fh);
300 		g_free (network);
301 	}
302 }
303 
304 void
chanopt_load(session * sess)305 chanopt_load (session *sess)
306 {
307 	int i;
308 	guint8 val;
309 	chanopt_in_memory *co;
310 	char *network;
311 
312 	if (sess->session_name[0] == 0)
313 		return;
314 
315 	network = server_get_network (sess->server, FALSE);
316 	if (!network)
317 		return;
318 
319 	if (!chanopt_open)
320 	{
321 		chanopt_open = TRUE;
322 		chanopt_load_all ();
323 	}
324 
325 	co = chanopt_find (network, sess->session_name, FALSE);
326 	if (!co)
327 		return;
328 
329 	/* fill in all the sess->xxxxx fields */
330 	i = 0;
331 	while (i < sizeof (chanopt) / sizeof (channel_options))
332 	{
333 		val = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
334 		*(guint8 *)G_STRUCT_MEMBER_P(sess, chanopt[i].offset) = val;
335 		i++;
336 	}
337 }
338 
339 void
chanopt_save(session * sess)340 chanopt_save (session *sess)
341 {
342 	int i;
343 	guint8 vals;
344 	guint8 valm;
345 	chanopt_in_memory *co;
346 	char *network;
347 
348 	if (sess->session_name[0] == 0)
349 		return;
350 
351 	network = server_get_network (sess->server, FALSE);
352 	if (!network)
353 		return;
354 
355 	/* 2. reconcile sess with what we loaded from disk */
356 
357 	co = chanopt_find (network, sess->session_name, TRUE);
358 
359 	i = 0;
360 	while (i < sizeof (chanopt) / sizeof (channel_options))
361 	{
362 		vals = G_STRUCT_MEMBER(guint8, sess, chanopt[i].offset);
363 		valm = G_STRUCT_MEMBER(guint8, co, chanopt[i].offset);
364 
365 		if (vals != valm)
366 		{
367 			*(guint8 *)G_STRUCT_MEMBER_P(co, chanopt[i].offset) = vals;
368 			chanopt_changed = TRUE;
369 		}
370 
371 		i++;
372 	}
373 }
374 
375 static void
chanopt_save_one_channel(chanopt_in_memory * co,int fh)376 chanopt_save_one_channel (chanopt_in_memory *co, int fh)
377 {
378 	int i;
379 	char buf[256];
380 	guint8 val;
381 
382 	g_snprintf (buf, sizeof (buf), "%s = %s\n", "network", co->network);
383 	write (fh, buf, strlen (buf));
384 
385 	g_snprintf (buf, sizeof (buf), "%s = %s\n", "channel", co->channel);
386 	write (fh, buf, strlen (buf));
387 
388 	i = 0;
389 	while (i < sizeof (chanopt) / sizeof (channel_options))
390 	{
391 		val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
392 		if (val != SET_DEFAULT)
393 		{
394 			g_snprintf (buf, sizeof (buf), "%s = %d\n", chanopt[i].name, val);
395 			write (fh, buf, strlen (buf));
396 		}
397 		i++;
398 	}
399 }
400 
401 void
chanopt_save_all(gboolean flush)402 chanopt_save_all (gboolean flush)
403 {
404 	int i;
405 	int num_saved;
406 	int fh;
407 	GSList *list;
408 	chanopt_in_memory *co;
409 	guint8 val;
410 
411 	if (!chanopt_list || !chanopt_changed)
412 	{
413 		return;
414 	}
415 
416 	fh = hexchat_open_file ("chanopt.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
417 	if (fh == -1)
418 	{
419 		return;
420 	}
421 
422 	for (num_saved = 0, list = chanopt_list; list; list = list->next)
423 	{
424 		co = list->data;
425 
426 		i = 0;
427 		while (i < sizeof (chanopt) / sizeof (channel_options))
428 		{
429 			val = G_STRUCT_MEMBER (guint8, co, chanopt[i].offset);
430 			/* not using global/default setting, must save */
431 			if (val != SET_DEFAULT)
432 			{
433 				if (num_saved != 0)
434 					write (fh, "\n", 1);
435 
436 				chanopt_save_one_channel (co, fh);
437 				num_saved++;
438 				goto cont;
439 			}
440 			i++;
441 		}
442 
443 cont:
444 		if (flush)
445 		{
446 			g_free (co->network);
447 			g_free (co->channel);
448 			g_free (co);
449 		}
450 	}
451 
452 	close (fh);
453 
454 	if (flush)
455 	{
456 		g_slist_free (chanopt_list);
457 		chanopt_list = NULL;
458 	}
459 
460 	chanopt_open = FALSE;
461 	chanopt_changed = FALSE;
462 }
463