1 /* $OpenBSD: cmd-set-option.c,v 1.120 2018/10/18 08:38:01 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 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 MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <fnmatch.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include "tmux.h" 26 27 /* 28 * Set an option. 29 */ 30 31 static enum cmd_retval cmd_set_option_exec(struct cmd *, struct cmdq_item *); 32 33 static int cmd_set_option_set(struct cmd *, struct cmdq_item *, 34 struct options *, struct options_entry *, const char *); 35 static int cmd_set_option_flag(struct cmdq_item *, 36 const struct options_table_entry *, struct options *, 37 const char *); 38 static int cmd_set_option_choice(struct cmdq_item *, 39 const struct options_table_entry *, struct options *, 40 const char *); 41 42 const struct cmd_entry cmd_set_option_entry = { 43 .name = "set-option", 44 .alias = "set", 45 46 .args = { "aFgoqst:uw", 1, 2 }, 47 .usage = "[-aFgosquw] [-t target-window] option [value]", 48 49 .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, 50 51 .flags = CMD_AFTERHOOK, 52 .exec = cmd_set_option_exec 53 }; 54 55 const struct cmd_entry cmd_set_window_option_entry = { 56 .name = "set-window-option", 57 .alias = "setw", 58 59 .args = { "aFgoqt:u", 1, 2 }, 60 .usage = "[-aFgoqu] " CMD_TARGET_WINDOW_USAGE " option [value]", 61 62 .target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL }, 63 64 .flags = CMD_AFTERHOOK, 65 .exec = cmd_set_option_exec 66 }; 67 68 static enum cmd_retval 69 cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) 70 { 71 struct args *args = self->args; 72 int append = args_has(args, 'a'); 73 struct cmd_find_state *fs = &item->target; 74 struct client *c, *loop; 75 struct session *s = fs->s; 76 struct winlink *wl = fs->wl; 77 struct window *w; 78 enum options_table_scope scope; 79 struct options *oo; 80 struct options_entry *parent, *o; 81 char *name, *argument, *value = NULL, *cause; 82 const char *target; 83 int window, idx, already, error, ambiguous; 84 85 /* Expand argument. */ 86 c = cmd_find_client(item, NULL, 1); 87 argument = format_single(item, args->argv[0], c, s, wl, NULL); 88 89 /* Parse option name and index. */ 90 name = options_match(argument, &idx, &ambiguous); 91 if (name == NULL) { 92 if (args_has(args, 'q')) 93 goto out; 94 if (ambiguous) 95 cmdq_error(item, "ambiguous option: %s", argument); 96 else 97 cmdq_error(item, "invalid option: %s", argument); 98 goto fail; 99 } 100 if (args->argc < 2) 101 value = NULL; 102 else if (args_has(args, 'F')) 103 value = format_single(item, args->argv[1], c, s, wl, NULL); 104 else 105 value = xstrdup(args->argv[1]); 106 107 /* 108 * Figure out the scope: for user options it comes from the arguments, 109 * otherwise from the option name. 110 */ 111 if (*name == '@') { 112 window = (self->entry == &cmd_set_window_option_entry); 113 scope = options_scope_from_flags(args, window, fs, &oo, &cause); 114 } else { 115 if (options_get_only(global_options, name) != NULL) 116 scope = OPTIONS_TABLE_SERVER; 117 else if (options_get_only(global_s_options, name) != NULL) 118 scope = OPTIONS_TABLE_SESSION; 119 else if (options_get_only(global_w_options, name) != NULL) 120 scope = OPTIONS_TABLE_WINDOW; 121 else { 122 scope = OPTIONS_TABLE_NONE; 123 xasprintf(&cause, "unknown option: %s", argument); 124 } 125 } 126 if (scope == OPTIONS_TABLE_NONE) { 127 if (args_has(args, 'q')) 128 goto out; 129 cmdq_error(item, "%s", cause); 130 free(cause); 131 goto fail; 132 } 133 134 /* Which table should this option go into? */ 135 if (scope == OPTIONS_TABLE_SERVER) 136 oo = global_options; 137 else if (scope == OPTIONS_TABLE_SESSION) { 138 if (args_has(self->args, 'g')) 139 oo = global_s_options; 140 else if (s == NULL) { 141 target = args_get(args, 't'); 142 if (target != NULL) 143 cmdq_error(item, "no such session: %s", target); 144 else 145 cmdq_error(item, "no current session"); 146 goto fail; 147 } else 148 oo = s->options; 149 } else if (scope == OPTIONS_TABLE_WINDOW) { 150 if (args_has(self->args, 'g')) 151 oo = global_w_options; 152 else if (wl == NULL) { 153 target = args_get(args, 't'); 154 if (target != NULL) 155 cmdq_error(item, "no such window: %s", target); 156 else 157 cmdq_error(item, "no current window"); 158 goto fail; 159 } else 160 oo = wl->window->options; 161 } 162 o = options_get_only(oo, name); 163 parent = options_get(oo, name); 164 165 /* Check that array options and indexes match up. */ 166 if (idx != -1) { 167 if (*name == '@' || options_array_size(parent, NULL) == -1) { 168 cmdq_error(item, "not an array: %s", argument); 169 goto fail; 170 } 171 } 172 173 /* With -o, check this option is not already set. */ 174 if (!args_has(args, 'u') && args_has(args, 'o')) { 175 if (idx == -1) 176 already = (o != NULL); 177 else { 178 if (o == NULL) 179 already = 0; 180 else 181 already = (options_array_get(o, idx) != NULL); 182 } 183 if (already) { 184 if (args_has(args, 'q')) 185 goto out; 186 cmdq_error(item, "already set: %s", argument); 187 goto fail; 188 } 189 } 190 191 /* Change the option. */ 192 if (args_has(args, 'u')) { 193 if (o == NULL) 194 goto out; 195 if (idx == -1) { 196 if (*name == '@') 197 options_remove(o); 198 else if (oo == global_options || 199 oo == global_s_options || 200 oo == global_w_options) 201 options_default(oo, options_table_entry(o)); 202 else 203 options_remove(o); 204 } else 205 options_array_set(o, idx, NULL, 0); 206 } else if (*name == '@') { 207 if (value == NULL) { 208 cmdq_error(item, "empty value"); 209 goto fail; 210 } 211 options_set_string(oo, name, append, "%s", value); 212 } else if (idx == -1 && options_array_size(parent, NULL) == -1) { 213 error = cmd_set_option_set(self, item, oo, parent, value); 214 if (error != 0) 215 goto fail; 216 } else { 217 if (value == NULL) { 218 cmdq_error(item, "empty value"); 219 goto fail; 220 } 221 if (o == NULL) 222 o = options_empty(oo, options_table_entry(parent)); 223 if (idx == -1) { 224 if (!append) 225 options_array_clear(o); 226 options_array_assign(o, value); 227 } else if (options_array_set(o, idx, value, append) != 0) { 228 cmdq_error(item, "invalid index: %s", argument); 229 goto fail; 230 } 231 } 232 233 /* Update timers and so on for various options. */ 234 if (strcmp(name, "automatic-rename") == 0) { 235 RB_FOREACH(w, windows, &windows) { 236 if (w->active == NULL) 237 continue; 238 if (options_get_number(w->options, "automatic-rename")) 239 w->active->flags |= PANE_CHANGED; 240 } 241 } 242 if (strcmp(name, "key-table") == 0) { 243 TAILQ_FOREACH(loop, &clients, entry) 244 server_client_set_key_table(loop, NULL); 245 } 246 if (strcmp(name, "user-keys") == 0) { 247 TAILQ_FOREACH(loop, &clients, entry) { 248 if (loop->tty.flags & TTY_OPENED) 249 tty_keys_build(&loop->tty); 250 } 251 } 252 if (strcmp(name, "status") == 0 || 253 strcmp(name, "status-interval") == 0) 254 status_timer_start_all(); 255 if (strcmp(name, "monitor-silence") == 0) 256 alerts_reset_all(); 257 if (strcmp(name, "window-style") == 0 || 258 strcmp(name, "window-active-style") == 0) { 259 RB_FOREACH(w, windows, &windows) 260 w->flags |= WINDOW_STYLECHANGED; 261 } 262 if (strcmp(name, "pane-border-status") == 0) { 263 RB_FOREACH(w, windows, &windows) 264 layout_fix_panes(w); 265 } 266 RB_FOREACH(s, sessions, &sessions) 267 status_update_saved(s); 268 269 /* 270 * Update sizes and redraw. May not always be necessary but do it 271 * anyway. 272 */ 273 recalculate_sizes(); 274 TAILQ_FOREACH(loop, &clients, entry) { 275 if (loop->session != NULL) 276 server_redraw_client(loop); 277 } 278 279 out: 280 free(argument); 281 free(value); 282 free(name); 283 return (CMD_RETURN_NORMAL); 284 285 fail: 286 free(argument); 287 free(value); 288 free(name); 289 return (CMD_RETURN_ERROR); 290 } 291 292 static int 293 cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, 294 struct options_entry *parent, const char *value) 295 { 296 const struct options_table_entry *oe; 297 struct args *args = self->args; 298 int append = args_has(args, 'a'); 299 struct options_entry *o; 300 long long number; 301 const char *errstr, *new; 302 char *old; 303 key_code key; 304 305 oe = options_table_entry(parent); 306 if (value == NULL && 307 oe->type != OPTIONS_TABLE_FLAG && 308 oe->type != OPTIONS_TABLE_CHOICE) { 309 cmdq_error(item, "empty value"); 310 return (-1); 311 } 312 313 switch (oe->type) { 314 case OPTIONS_TABLE_STRING: 315 old = xstrdup(options_get_string(oo, oe->name)); 316 options_set_string(oo, oe->name, append, "%s", value); 317 new = options_get_string(oo, oe->name); 318 if (oe->pattern != NULL && fnmatch(oe->pattern, new, 0) != 0) { 319 options_set_string(oo, oe->name, 0, "%s", old); 320 free(old); 321 cmdq_error(item, "value is invalid: %s", value); 322 return (-1); 323 } 324 free(old); 325 return (0); 326 case OPTIONS_TABLE_NUMBER: 327 number = strtonum(value, oe->minimum, oe->maximum, &errstr); 328 if (errstr != NULL) { 329 cmdq_error(item, "value is %s: %s", errstr, value); 330 return (-1); 331 } 332 options_set_number(oo, oe->name, number); 333 return (0); 334 case OPTIONS_TABLE_KEY: 335 key = key_string_lookup_string(value); 336 if (key == KEYC_UNKNOWN) { 337 cmdq_error(item, "bad key: %s", value); 338 return (-1); 339 } 340 options_set_number(oo, oe->name, key); 341 return (0); 342 case OPTIONS_TABLE_COLOUR: 343 if ((number = colour_fromstring(value)) == -1) { 344 cmdq_error(item, "bad colour: %s", value); 345 return (-1); 346 } 347 o = options_set_number(oo, oe->name, number); 348 options_style_update_new(oo, o); 349 return (0); 350 case OPTIONS_TABLE_ATTRIBUTES: 351 if ((number = attributes_fromstring(value)) == -1) { 352 cmdq_error(item, "bad attributes: %s", value); 353 return (-1); 354 } 355 o = options_set_number(oo, oe->name, number); 356 options_style_update_new(oo, o); 357 return (0); 358 case OPTIONS_TABLE_FLAG: 359 return (cmd_set_option_flag(item, oe, oo, value)); 360 case OPTIONS_TABLE_CHOICE: 361 return (cmd_set_option_choice(item, oe, oo, value)); 362 case OPTIONS_TABLE_STYLE: 363 o = options_set_style(oo, oe->name, append, value); 364 if (o == NULL) { 365 cmdq_error(item, "bad style: %s", value); 366 return (-1); 367 } 368 options_style_update_old(oo, o); 369 return (0); 370 case OPTIONS_TABLE_ARRAY: 371 break; 372 } 373 return (-1); 374 } 375 376 static int 377 cmd_set_option_flag(struct cmdq_item *item, 378 const struct options_table_entry *oe, struct options *oo, 379 const char *value) 380 { 381 int flag; 382 383 if (value == NULL || *value == '\0') 384 flag = !options_get_number(oo, oe->name); 385 else if (strcmp(value, "1") == 0 || 386 strcasecmp(value, "on") == 0 || 387 strcasecmp(value, "yes") == 0) 388 flag = 1; 389 else if (strcmp(value, "0") == 0 || 390 strcasecmp(value, "off") == 0 || 391 strcasecmp(value, "no") == 0) 392 flag = 0; 393 else { 394 cmdq_error(item, "bad value: %s", value); 395 return (-1); 396 } 397 options_set_number(oo, oe->name, flag); 398 return (0); 399 } 400 401 static int 402 cmd_set_option_choice(struct cmdq_item *item, 403 const struct options_table_entry *oe, struct options *oo, 404 const char *value) 405 { 406 const char **cp; 407 int n, choice = -1; 408 409 if (value == NULL) { 410 choice = options_get_number(oo, oe->name); 411 if (choice < 2) 412 choice = !choice; 413 } else { 414 n = 0; 415 for (cp = oe->choices; *cp != NULL; cp++) { 416 if (strcmp(*cp, value) == 0) 417 choice = n; 418 n++; 419 } 420 if (choice == -1) { 421 cmdq_error(item, "unknown value: %s", value); 422 return (-1); 423 } 424 } 425 options_set_number(oo, oe->name, choice); 426 return (0); 427 } 428