1 /* $OpenBSD: cmd-set-option.c,v 1.125 2019/04/26 11:38:51 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 const struct cmd_entry cmd_set_hook_entry = { 69 .name = "set-hook", 70 .alias = NULL, 71 72 .args = { "agRt:u", 1, 2 }, 73 .usage = "[-agRu] " CMD_TARGET_SESSION_USAGE " hook [command]", 74 75 .target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL }, 76 77 .flags = CMD_AFTERHOOK, 78 .exec = cmd_set_option_exec 79 }; 80 81 static enum cmd_retval 82 cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) 83 { 84 struct args *args = self->args; 85 int append = args_has(args, 'a'); 86 struct cmd_find_state *fs = &item->target; 87 struct client *c, *loop; 88 struct session *s = fs->s; 89 struct winlink *wl = fs->wl; 90 struct window *w; 91 enum options_table_scope scope; 92 struct options *oo; 93 struct options_entry *parent, *o; 94 char *name, *argument, *value = NULL, *cause; 95 const char *target; 96 int window, idx, already, error, ambiguous; 97 struct style *sy; 98 99 /* Expand argument. */ 100 c = cmd_find_client(item, NULL, 1); 101 argument = format_single(item, args->argv[0], c, s, wl, NULL); 102 103 if (self->entry == &cmd_set_hook_entry && args_has(args, 'R')) { 104 notify_hook(item, argument); 105 return (CMD_RETURN_NORMAL); 106 } 107 108 /* Parse option name and index. */ 109 name = options_match(argument, &idx, &ambiguous); 110 if (name == NULL) { 111 if (args_has(args, 'q')) 112 goto out; 113 if (ambiguous) 114 cmdq_error(item, "ambiguous option: %s", argument); 115 else 116 cmdq_error(item, "invalid option: %s", argument); 117 goto fail; 118 } 119 if (args->argc < 2) 120 value = NULL; 121 else if (args_has(args, 'F')) 122 value = format_single(item, args->argv[1], c, s, wl, NULL); 123 else 124 value = xstrdup(args->argv[1]); 125 126 /* 127 * Figure out the scope: for user options it comes from the arguments, 128 * otherwise from the option name. 129 */ 130 if (*name == '@') { 131 window = (self->entry == &cmd_set_window_option_entry); 132 scope = options_scope_from_flags(args, window, fs, &oo, &cause); 133 } else { 134 if (options_get_only(global_options, name) != NULL) 135 scope = OPTIONS_TABLE_SERVER; 136 else if (options_get_only(global_s_options, name) != NULL) 137 scope = OPTIONS_TABLE_SESSION; 138 else if (options_get_only(global_w_options, name) != NULL) 139 scope = OPTIONS_TABLE_WINDOW; 140 else { 141 scope = OPTIONS_TABLE_NONE; 142 xasprintf(&cause, "unknown option: %s", argument); 143 } 144 } 145 if (scope == OPTIONS_TABLE_NONE) { 146 if (args_has(args, 'q')) 147 goto out; 148 cmdq_error(item, "%s", cause); 149 free(cause); 150 goto fail; 151 } 152 153 /* Which table should this option go into? */ 154 if (scope == OPTIONS_TABLE_SERVER) 155 oo = global_options; 156 else if (scope == OPTIONS_TABLE_SESSION) { 157 if (args_has(self->args, 'g')) 158 oo = global_s_options; 159 else if (s == NULL) { 160 target = args_get(args, 't'); 161 if (target != NULL) 162 cmdq_error(item, "no such session: %s", target); 163 else 164 cmdq_error(item, "no current session"); 165 goto fail; 166 } else 167 oo = s->options; 168 } else if (scope == OPTIONS_TABLE_WINDOW) { 169 if (args_has(self->args, 'g')) 170 oo = global_w_options; 171 else if (wl == NULL) { 172 target = args_get(args, 't'); 173 if (target != NULL) 174 cmdq_error(item, "no such window: %s", target); 175 else 176 cmdq_error(item, "no current window"); 177 goto fail; 178 } else 179 oo = wl->window->options; 180 } 181 o = options_get_only(oo, name); 182 parent = options_get(oo, name); 183 184 /* Check that array options and indexes match up. */ 185 if (idx != -1 && (*name == '@' || !options_isarray(parent))) { 186 cmdq_error(item, "not an array: %s", argument); 187 goto fail; 188 } 189 190 /* With -o, check this option is not already set. */ 191 if (!args_has(args, 'u') && args_has(args, 'o')) { 192 if (idx == -1) 193 already = (o != NULL); 194 else { 195 if (o == NULL) 196 already = 0; 197 else 198 already = (options_array_get(o, idx) != NULL); 199 } 200 if (already) { 201 if (args_has(args, 'q')) 202 goto out; 203 cmdq_error(item, "already set: %s", argument); 204 goto fail; 205 } 206 } 207 208 /* Change the option. */ 209 if (args_has(args, 'u')) { 210 if (o == NULL) 211 goto out; 212 if (idx == -1) { 213 if (*name == '@') 214 options_remove(o); 215 else if (oo == global_options || 216 oo == global_s_options || 217 oo == global_w_options) 218 options_default(oo, options_table_entry(o)); 219 else 220 options_remove(o); 221 } else if (options_array_set(o, idx, NULL, 0, &cause) != 0) { 222 cmdq_error(item, "%s", cause); 223 free(cause); 224 goto fail; 225 } 226 } else if (*name == '@') { 227 if (value == NULL) { 228 cmdq_error(item, "empty value"); 229 goto fail; 230 } 231 options_set_string(oo, name, append, "%s", value); 232 } else if (idx == -1 && !options_isarray(parent)) { 233 error = cmd_set_option_set(self, item, oo, parent, value); 234 if (error != 0) 235 goto fail; 236 } else { 237 if (value == NULL) { 238 cmdq_error(item, "empty value"); 239 goto fail; 240 } 241 if (o == NULL) 242 o = options_empty(oo, options_table_entry(parent)); 243 if (idx == -1) { 244 if (!append) 245 options_array_clear(o); 246 if (options_array_assign(o, value, &cause) != 0) { 247 cmdq_error(item, "%s", cause); 248 free(cause); 249 goto fail; 250 } 251 } else if (options_array_set(o, idx, value, append, 252 &cause) != 0) { 253 cmdq_error(item, "%s", cause); 254 free(cause); 255 goto fail; 256 } 257 } 258 259 /* Update timers and so on for various options. */ 260 if (strcmp(name, "automatic-rename") == 0) { 261 RB_FOREACH(w, windows, &windows) { 262 if (w->active == NULL) 263 continue; 264 if (options_get_number(w->options, "automatic-rename")) 265 w->active->flags |= PANE_CHANGED; 266 } 267 } 268 if (strcmp(name, "key-table") == 0) { 269 TAILQ_FOREACH(loop, &clients, entry) 270 server_client_set_key_table(loop, NULL); 271 } 272 if (strcmp(name, "user-keys") == 0) { 273 TAILQ_FOREACH(loop, &clients, entry) { 274 if (loop->tty.flags & TTY_OPENED) 275 tty_keys_build(&loop->tty); 276 } 277 } 278 if (strcmp(name, "status-fg") == 0 || strcmp(name, "status-bg") == 0) { 279 sy = options_get_style(oo, "status-style"); 280 sy->gc.fg = options_get_number(oo, "status-fg"); 281 sy->gc.bg = options_get_number(oo, "status-bg"); 282 } 283 if (strcmp(name, "status-style") == 0) { 284 sy = options_get_style(oo, "status-style"); 285 options_set_number(oo, "status-fg", sy->gc.fg); 286 options_set_number(oo, "status-bg", sy->gc.bg); 287 } 288 if (strcmp(name, "status") == 0 || 289 strcmp(name, "status-interval") == 0) 290 status_timer_start_all(); 291 if (strcmp(name, "monitor-silence") == 0) 292 alerts_reset_all(); 293 if (strcmp(name, "window-style") == 0 || 294 strcmp(name, "window-active-style") == 0) { 295 RB_FOREACH(w, windows, &windows) 296 w->flags |= WINDOW_STYLECHANGED; 297 } 298 if (strcmp(name, "pane-border-status") == 0) { 299 RB_FOREACH(w, windows, &windows) 300 layout_fix_panes(w); 301 } 302 RB_FOREACH(s, sessions, &sessions) 303 status_update_cache(s); 304 305 /* 306 * Update sizes and redraw. May not always be necessary but do it 307 * anyway. 308 */ 309 recalculate_sizes(); 310 TAILQ_FOREACH(loop, &clients, entry) { 311 if (loop->session != NULL) 312 server_redraw_client(loop); 313 } 314 315 out: 316 free(argument); 317 free(value); 318 free(name); 319 return (CMD_RETURN_NORMAL); 320 321 fail: 322 free(argument); 323 free(value); 324 free(name); 325 return (CMD_RETURN_ERROR); 326 } 327 328 static int 329 cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, 330 struct options_entry *parent, const char *value) 331 { 332 const struct options_table_entry *oe; 333 struct args *args = self->args; 334 int append = args_has(args, 'a'); 335 struct options_entry *o; 336 long long number; 337 const char *errstr, *new; 338 char *old; 339 key_code key; 340 341 oe = options_table_entry(parent); 342 if (value == NULL && 343 oe->type != OPTIONS_TABLE_FLAG && 344 oe->type != OPTIONS_TABLE_CHOICE) { 345 cmdq_error(item, "empty value"); 346 return (-1); 347 } 348 349 switch (oe->type) { 350 case OPTIONS_TABLE_STRING: 351 old = xstrdup(options_get_string(oo, oe->name)); 352 options_set_string(oo, oe->name, append, "%s", value); 353 new = options_get_string(oo, oe->name); 354 if (oe->pattern != NULL && fnmatch(oe->pattern, new, 0) != 0) { 355 options_set_string(oo, oe->name, 0, "%s", old); 356 free(old); 357 cmdq_error(item, "value is invalid: %s", value); 358 return (-1); 359 } 360 free(old); 361 return (0); 362 case OPTIONS_TABLE_NUMBER: 363 number = strtonum(value, oe->minimum, oe->maximum, &errstr); 364 if (errstr != NULL) { 365 cmdq_error(item, "value is %s: %s", errstr, value); 366 return (-1); 367 } 368 options_set_number(oo, oe->name, number); 369 return (0); 370 case OPTIONS_TABLE_KEY: 371 key = key_string_lookup_string(value); 372 if (key == KEYC_UNKNOWN) { 373 cmdq_error(item, "bad key: %s", value); 374 return (-1); 375 } 376 options_set_number(oo, oe->name, key); 377 return (0); 378 case OPTIONS_TABLE_COLOUR: 379 if ((number = colour_fromstring(value)) == -1) { 380 cmdq_error(item, "bad colour: %s", value); 381 return (-1); 382 } 383 options_set_number(oo, oe->name, number); 384 return (0); 385 case OPTIONS_TABLE_FLAG: 386 return (cmd_set_option_flag(item, oe, oo, value)); 387 case OPTIONS_TABLE_CHOICE: 388 return (cmd_set_option_choice(item, oe, oo, value)); 389 case OPTIONS_TABLE_STYLE: 390 o = options_set_style(oo, oe->name, append, value); 391 if (o == NULL) { 392 cmdq_error(item, "bad style: %s", value); 393 return (-1); 394 } 395 return (0); 396 case OPTIONS_TABLE_COMMAND: 397 break; 398 } 399 return (-1); 400 } 401 402 static int 403 cmd_set_option_flag(struct cmdq_item *item, 404 const struct options_table_entry *oe, struct options *oo, 405 const char *value) 406 { 407 int flag; 408 409 if (value == NULL || *value == '\0') 410 flag = !options_get_number(oo, oe->name); 411 else if (strcmp(value, "1") == 0 || 412 strcasecmp(value, "on") == 0 || 413 strcasecmp(value, "yes") == 0) 414 flag = 1; 415 else if (strcmp(value, "0") == 0 || 416 strcasecmp(value, "off") == 0 || 417 strcasecmp(value, "no") == 0) 418 flag = 0; 419 else { 420 cmdq_error(item, "bad value: %s", value); 421 return (-1); 422 } 423 options_set_number(oo, oe->name, flag); 424 return (0); 425 } 426 427 static int 428 cmd_set_option_choice(struct cmdq_item *item, 429 const struct options_table_entry *oe, struct options *oo, 430 const char *value) 431 { 432 const char **cp; 433 int n, choice = -1; 434 435 if (value == NULL) { 436 choice = options_get_number(oo, oe->name); 437 if (choice < 2) 438 choice = !choice; 439 } else { 440 n = 0; 441 for (cp = oe->choices; *cp != NULL; cp++) { 442 if (strcmp(*cp, value) == 0) 443 choice = n; 444 n++; 445 } 446 if (choice == -1) { 447 cmdq_error(item, "unknown value: %s", value); 448 return (-1); 449 } 450 } 451 options_set_number(oo, oe->name, choice); 452 return (0); 453 } 454