1 /*
2  * Copyright (c) 2016-2020 Paul Mattes.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *     * Redistributions of source code must retain the above copyright
9  *       notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above copyright
11  *       notice, this list of conditions and the following disclaimer in the
12  *       documentation and/or other materials provided with the distribution.
13  *     * Neither the name of Paul Mattes, nor his contributors may be used to
14  *       endorse or promote products derived from this software without
15  *       specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
18  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20  * EVENT SHALL PAUL MATTES, JEFF SPARKES OR GTRC BE LIABLE FOR ANY DIRECT,
21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  *	model.c
31  *		Support for changing model and oversize at run-time.
32  */
33 
34 #include "globals.h"
35 #include "appres.h"
36 #include "resources.h"
37 
38 #include "ctlrc.h"
39 #include "popups.h"
40 #include "screen.h"
41 #include "telnet.h"
42 #include "toggles.h"
43 #include "utils.h"
44 
45 #include "model.h"
46 
47 static char *pending_model;
48 static char *pending_oversize;
49 
50 /*
51  * Canonical representation of the model, returning color and extended
52  * state.
53  */
54 static char *
canonical_model_x(const char * res,int * model,bool * is_color,bool * is_extended)55 canonical_model_x(const char *res, int *model, bool *is_color,
56 	bool *is_extended)
57 {
58     size_t sl;
59     char *digitp = NULL;
60     char *colorp = "9";
61     bool extended = false;
62 
63     if (res == NULL) {
64 	return NULL;
65     }
66     sl = strlen(res);
67     if ((sl != 1 && sl != 6 && sl != 8) ||
68 	(sl == 1 &&
69 	 (digitp = strchr("2345", res[0])) == NULL) ||
70 	(((sl == 6) || (sl == 8)) &&
71 	 (strncmp(res, "327", 3) ||
72 	  (colorp = strchr("89", res[3])) == NULL ||
73 	  res[4] != '-' ||
74 	  (digitp = strchr("2345", res[5])) == NULL)) ||
75 	((sl == 8) &&
76 	 (res[6] != '-' || strchr("Ee", res[7]) == NULL))) {
77 	return NULL;
78     }
79     if (sl == 1 || sl == 8) {
80 	extended = true;
81     }
82     *model = *digitp - '0';
83     *is_color = (*colorp == '9');
84     *is_extended = extended;
85     return xs_buffer("327%c-%c%s", *colorp, *digitp, extended? "-E": "");
86 }
87 
88 /*
89  * Canonical representation of the model.
90  */
91 static char *
canonical_model(const char * res)92 canonical_model(const char *res)
93 {
94     int model;
95     bool color, extended;
96 
97     return canonical_model_x(res, &model, &color, &extended);
98 }
99 
100 /*
101  * Toggle the model.
102  */
103 static bool
toggle_model(const char * name _is_unused,const char * value)104 toggle_model(const char *name _is_unused, const char *value)
105 {
106     if (!model_can_change()) {
107 	popup_an_error("Cannot change " ResModel);
108 	return false;
109     }
110 
111     Replace(pending_model, *value? NewString(value): NULL);
112     return true;
113 }
114 
115 /*
116  * Toggle oversize.
117  */
118 static bool
toggle_oversize(const char * name _is_unused,const char * value)119 toggle_oversize(const char *name _is_unused, const char *value)
120 {
121     if (!model_can_change()) {
122 	popup_an_error("Cannot change " ResOversize);
123 	return false;
124     }
125 
126     Replace(pending_oversize, NewString(value));
127     return true;
128 }
129 
130 /*
131  * Done function for changing the model and oversize.
132  */
133 static bool
toggle_model_done(bool success)134 toggle_model_done(bool success)
135 {
136     unsigned ovr = 0, ovc = 0;
137     int model_number = model_num;
138     bool is_color = mode.m3279;
139     bool is_extended = mode.extended;
140     struct {
141 	int model_num;
142 	int rows;
143 	int cols;
144 	int ov_cols;
145 	int ov_rows;
146 	bool m3279;
147 	bool alt;
148 	bool extended;
149     } old;
150     bool oversize_was_pending = (pending_oversize != NULL);
151     bool res = true;
152 
153     if (!success ||
154 	    (pending_model == NULL &&
155 	     pending_oversize == NULL)) {
156 	goto done;
157     }
158 
159     if (PCONNECTED) {
160 	popup_an_error("Cannot change %s or %s while connected",
161 		ResModel, ResOversize);
162 	goto fail;
163     }
164 
165     if (pending_model != NULL && !strcmp(pending_model, appres.model)) {
166 	Replace(pending_model, NULL);
167     }
168     if (pending_oversize != NULL &&
169 	    appres.oversize != NULL &&
170 	    !strcmp(pending_oversize, appres.oversize)) {
171 	Replace(pending_oversize, NULL);
172     }
173     if (pending_model == NULL && pending_oversize == NULL) {
174 	goto done;
175     }
176 
177     /* Reconcile simultaneous changes. */
178     if (pending_model != NULL) {
179 	char *canon = canonical_model_x(pending_model, &model_number,
180 		&is_color, &is_extended);
181 
182 	if (canon == NULL) {
183 	    popup_an_error("%s value must be 327{89}-{2345}[-E]",
184 		    ResModel);
185 	    goto fail;
186 	}
187 
188 	Replace(pending_model, canon);
189     }
190 
191     if (!is_extended) {
192 	/* Without extended, no oversize. */
193 	Replace(pending_oversize, NewString(""));
194     }
195 
196     if (pending_oversize != NULL) {
197 	if (*pending_oversize) {
198 	    char x, junk;
199 	    if (sscanf(pending_oversize, "%u%c%u%c", &ovc, &x, &ovr, &junk) != 3
200 		    || x != 'x') {
201 		popup_an_error("%s value must be <cols>x<rows>",
202 			ResOversize);
203 		goto fail;
204 	    }
205 	} else {
206 	    ovc = 0;
207 	    ovr = 0;
208 	}
209     } else {
210 	ovc = ov_cols;
211 	ovr = ov_rows;
212     }
213 
214     /* Save the current settings. */
215     old.model_num = model_num;
216     old.rows = ROWS;
217     old.cols = COLS;
218     old.ov_rows = ov_rows;
219     old.ov_cols = ov_cols;
220     old.m3279 = mode.m3279;
221     old.alt = screen_alt;
222     old.extended = mode.extended;
223 
224     /* Change settings. */
225     mode.m3279 = is_color;
226     mode.extended = is_extended;
227     set_rows_cols(model_number, ovc, ovr);
228 
229     if (model_num != model_number ||
230 	    ov_rows != (int)ovr ||
231 	    ov_cols != (int)ovc) {
232 	/* Failed. Restore the old settings. */
233 	mode.m3279 = old.m3279;
234 	set_rows_cols(old.model_num, old.ov_cols, old.ov_rows);
235 	ROWS = old.rows;
236 	COLS = old.cols;
237 	screen_alt = old.alt;
238 	mode.extended = old.extended;
239 	return false;
240     }
241 
242     ROWS = maxROWS;
243     COLS = maxCOLS;
244     ctlr_reinit(MODEL_CHANGE);
245 
246     /* Reset the screen state. */
247     screen_init();
248     ctlr_erase(true);
249 
250     /* Report the new terminal name. */
251     if (appres.termname == NULL) {
252 	st_changed(ST_TERMINAL_NAME, appres.termname != NULL);
253     }
254 
255     if (pending_model != NULL) {
256 	Replace(appres.model, pending_model);
257     }
258     pending_model = NULL;
259     if (pending_oversize != NULL) {
260 	if (*pending_oversize) {
261 	    Replace(appres.oversize, pending_oversize);
262 	    pending_oversize = NULL;
263 	} else {
264 	    bool force = !oversize_was_pending && appres.oversize != NULL;
265 
266 	    Replace(appres.oversize, NULL);
267 	    if (force) {
268 		/* Turning off extended killed oversize. */
269 		force_toggle_notify(ResOversize, IA_NONE);
270 	    }
271 	}
272     }
273 
274     goto done;
275 
276 fail:
277     res = false;
278 
279 done:
280     Replace(pending_model, NULL);
281     Replace(pending_oversize, NULL);
282     return res;
283 }
284 
285 /*
286  * Terminal name toggle.
287  */
288 static bool
toggle_terminal_name(const char * name _is_unused,const char * value)289 toggle_terminal_name(const char *name _is_unused, const char *value)
290 {
291     if (PCONNECTED) {
292 	popup_an_error("%s cannot change while connected",
293 		ResTermName);
294 	return false;
295     }
296 
297     appres.termname = clean_termname(*value? value: NULL);
298     net_set_default_termtype();
299     return true;
300 }
301 
302 /*
303  * Toggle the NOP interval.
304  */
305 static bool
toggle_nop_seconds(const char * name _is_unused,const char * value)306 toggle_nop_seconds(const char *name _is_unused, const char *value)
307 {
308     unsigned long l;
309     char *end;
310     int secs;
311 
312     if (!*value) {
313 	appres.nop_seconds = 0;
314 	net_nop_seconds();
315 	return true;
316     }
317 
318     l = strtoul(value, &end, 10);
319     secs = (int)l;
320     if (*end != '\0' || (unsigned long)secs != l || secs < 0) {
321 	popup_an_error("Invalid %s value", ResNopSeconds);
322 	return false;
323     }
324     appres.nop_seconds = secs;
325     net_nop_seconds();
326     return true;
327 }
328 
329 /**
330  * Module registration.
331  */
332 void
model_register(void)333 model_register(void)
334 {
335     /* Register the toggles. */
336     register_extended_toggle(ResModel, toggle_model, toggle_model_done,
337 	    canonical_model, (void **)&appres.model, XRM_STRING);
338     register_extended_toggle(ResOversize, toggle_oversize, toggle_model_done,
339 	    NULL, (void **)&appres.oversize, XRM_STRING);
340     register_extended_toggle(ResTermName, toggle_terminal_name, NULL, NULL,
341 	    (void **)&appres.termname, XRM_STRING);
342     register_extended_toggle(ResNopSeconds, toggle_nop_seconds, NULL,
343 	    NULL, (void **)&appres.nop_seconds, XRM_INT);
344 }
345