-rw-r--r-- src/action.c
1 #include <sys/vt.h> 2 #include <wlr/backend.h> 3 #include <wlr/backend/session.h> 4 #include <wlr/types/wlr_output_layout.h> 5 6 #include "action.h" 7 #include "config.h" 8 #include "cursor.h" 9 #include "desk.h" 10 #include "output.h" 11 #include "parse.h" 12 #include "scratchpad.h" 13 #include "shell.h" 14 #include "types.h" 15 16 17 static void terminate(void *data); 18 static void exec_command(void *data); 19 static void close_window(void *data); 20 static void move_window(void *data); 21 static void focus_in_direction(void *data); 22 static void next_desk(void *data); 23 static void prev_desk(void *data); 24 static void reset_zoom(void *data); 25 static void zoom(void *data); 26 static void set_mark(void *data); 27 static void go_to_mark(void *data); 28 static void halfimize(void *data); 29 static void maximize(void *data); 30 static void send_to_desk(void *data); 31 static void toggle_scratchpad(void *data); 32 static void to_region(void *data); 33 static void zoom_pinch(void *data); 34 static void zoom_pinch_begin(void *data); 35 static void zoom_scroll(void *data); 36 37 38 static struct { 39 const char *name; 40 const action action; 41 const action begin; 42 } pinch_map[] = { 43 { "zoom", &zoom_pinch, &zoom_pinch_begin }, 44 }; 45 46 47 static struct { 48 const char *name; 49 const action action; 50 } scroll_map[] = { 51 { "zoom", &zoom_scroll }, 52 { "pan_desk", &pan_desk }, 53 }; 54 55 56 static struct { 57 const char *name; 58 const action action; 59 bool (*data_handler)(void **data, char *args); 60 } action_map[] = { 61 { "terminate", &terminate, NULL }, 62 { "exec", &exec_command, &str_handler }, 63 { "close_window", &close_window, NULL }, 64 { "move_window", &move_window, NULL }, 65 { "focus", &focus_in_direction, &dir_handler }, 66 { "next_desk", &next_desk, NULL }, 67 { "prev_desk", &prev_desk, NULL }, 68 { "pan_desk", &pan_desk, &motion_handler }, 69 { "reset_zoom", &reset_zoom, NULL }, 70 { "zoom", &zoom, &str_handler }, 71 { "set_mark", &set_mark, NULL }, 72 { "go_to_mark", &go_to_mark, NULL }, 73 { "toggle_fullscreen", &toggle_fullscreen, NULL }, 74 { "halfimize", &halfimize, &dir_handler }, 75 { "maximize", &maximize, NULL }, 76 { "send_to_desk", &send_to_desk, &str_handler }, 77 { "scratchpad", &toggle_scratchpad, &scratchpad_handler }, 78 { "to_region", &to_region, &box_handler }, 79 }; 80 81 82 bool get_action( 83 char *name, action *found, char *args, void **data, char *response, int flag 84 ) { 85 // flag can be PINCH or SCROLL to access their actions, otherwise neither 86 *data = NULL; 87 size_t i; 88 89 switch (flag) { 90 case PINCH: 91 for (i = 0; i < sizeof(pinch_map) / sizeof(pinch_map[0]); i++) { 92 if (strcmp(pinch_map[i].name, name) == 0) { 93 *found = pinch_map[i].action; 94 action begin = pinch_map[i].begin; 95 *data = calloc(1, sizeof(action)); 96 *(action *)(*data) = begin; 97 return true; 98 } 99 } 100 break; 101 102 case SCROLL: 103 for (i = 0; i < sizeof(scroll_map) / sizeof(scroll_map[0]); i++) { 104 if (strcmp(scroll_map[i].name, name) == 0) { 105 *found = scroll_map[i].action; 106 return true; 107 } 108 } 109 break; 110 111 default: 112 for (i = 0; i < sizeof(action_map) / sizeof(action_map[0]); i++) { 113 if (strcmp(action_map[i].name, name) == 0) { 114 *found = action_map[i].action; 115 116 if (action_map[i].data_handler != NULL) { 117 if (!action_map[i].data_handler(data, args)) { 118 sprintf(response, "Command malformed/incomplete."); 119 return false; 120 } 121 } 122 123 return true; 124 } 125 } 126 } 127 128 return false; 129 } 130 131 132 bool do_action(char *message, char *response) { 133 void *data; 134 action action; 135 if (get_action(message, &action, strtok(NULL, "\n\r"), &data, response, 0)) { 136 (*action)(data); 137 return true; 138 } else { 139 return false; 140 } 141 } 142 143 144 static void terminate(void *data) { 145 wl_display_terminate(wimp.display); 146 } 147 148 149 static void exec_command(void *data) { 150 pid_t pid = fork(); 151 if (pid == 0) { 152 if (execl("/bin/sh", "/bin/sh", "-c", data, (void *)NULL) == -1) { 153 exit(EXIT_FAILURE); 154 } 155 } else if (pid < 0) { 156 wlr_log(WLR_ERROR, "Failed to fork for exec command."); 157 } else { 158 wlr_log(WLR_DEBUG, "Executing: %s", (char *)data); 159 } 160 } 161 162 163 void change_vt(void *data) { 164 unsigned vt = *(unsigned *)data; 165 struct wlr_session *session = wlr_backend_get_session(wimp.backend); 166 wlr_session_change_vt(session, vt); 167 } 168 169 170 static void close_window(void *data) { 171 struct wlr_surface *surface = wimp.seat->keyboard_state.focused_surface; 172 if (surface) { 173 struct wlr_xdg_surface *xdg_surface = surface->role_data; 174 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL && xdg_surface->toplevel) { 175 xdg_toplevel_send_close(xdg_surface->toplevel->resource); 176 } 177 } 178 } 179 180 181 static void move_window(void *data) { 182 struct view *view; 183 if (wimp.grabbed_view) { 184 view = wimp.grabbed_view; 185 } else { 186 double sx, sy; 187 struct wlr_surface *surface; 188 bool is_layer; 189 view = under_pointer(&surface, &sx, &sy, &is_layer); 190 if (!view || is_layer) { 191 return; 192 } 193 } 194 double zoom = wimp.current_desk->zoom; 195 struct wlr_box damage = { 196 .x = view->x * zoom, 197 .y = view->y * zoom, 198 .width = view->surface->geometry.width * zoom, 199 .height = view->surface->geometry.height * zoom, 200 }; 201 struct motion motion = *(struct motion*)data; 202 if (motion.dx > 0) { 203 damage.width += motion.dx; 204 } else { 205 damage.x -= motion.dx; 206 damage.width -= motion.dx; 207 } 208 if (motion.dy > 0) { 209 damage.height += motion.dy; 210 } else { 211 damage.y -= motion.dy; 212 damage.height -= motion.dy; 213 } 214 view->x += motion.dx; 215 view->y += motion.dy; 216 damage_box(&damage, true); 217 } 218 219 220 static void focus_in_direction(void *data) { 221 if (wl_list_empty(&wimp.current_desk->views)) { 222 return; 223 } 224 225 struct view *current = wl_container_of(wimp.current_desk->views.next, current, link); 226 enum direction dir = *(enum direction*)data; 227 228 struct view *next = NULL; 229 struct view *view; 230 double vx, vy, dx, dy, vdist, dist; 231 double x = current->x + current->surface->geometry.width / 2; 232 double y = current->y + current->surface->geometry.height / 2; 233 double c = y - x; // y = x + c / slope 234 double n = y + x; // y = n - x \ slope 235 bool above_c = dir & (DOWN | LEFT); 236 bool above_n = dir & (DOWN | RIGHT); 237 238 wl_list_for_each(view, &wimp.current_desk->views, link) { 239 vx = view->x + view->surface->geometry.width / 2; 240 vy = view->y + view->surface->geometry.height / 2; 241 dx = x - vx; 242 dy = y - vy; 243 vdist = dx * dx + dy * dy; 244 if ( 245 (above_c ^ (vy - vx < c)) && 246 (above_n ^ (vy + vx < n)) && 247 (!next || vdist < dist) && 248 view != current 249 ) { 250 next = view; 251 dist = vdist; 252 } 253 } 254 255 if (next) { 256 unfullscreen(); 257 pan_to_view(next); 258 focus_view(next, NULL); 259 } else { 260 pan_to_view(current); 261 focus_view(current, NULL); 262 } 263 } 264 265 266 static void next_desk(void *data) { 267 struct desk *desk; 268 if (wimp.current_desk->index + 1 == wimp.desk_count) { 269 desk = wl_container_of(wimp.desks.next, desk, link); 270 } else { 271 desk = wl_container_of(wimp.current_desk->link.next, desk, link); 272 } 273 set_desk(desk); 274 } 275 276 277 static void prev_desk(void *data) { 278 struct desk *desk; 279 if (wimp.current_desk->index == 0) { 280 desk = wl_container_of(wimp.desks.prev, desk, link); 281 } else { 282 desk = wl_container_of(wimp.current_desk->link.prev, desk, link); 283 } 284 set_desk(desk); 285 } 286 287 288 void pan_desk(void *data) { 289 struct desk *desk = wimp.current_desk; 290 struct motion motion = *(struct motion*)data; 291 double dx = motion.dx / desk->zoom; 292 double dy = motion.dy / desk->zoom; 293 if (motion.is_percentage) { 294 struct wlr_box *extents = wlr_output_layout_get_box(wimp.output_layout, NULL); 295 dx = extents->width * (dx / 100); 296 dy = extents->height * (dy / 100); 297 } 298 unfullscreen(); 299 struct view *view; 300 wl_list_for_each(view, &desk->views, link) { 301 view->x -= dx; 302 view->y -= dy; 303 } 304 desk->panned_x -= dx; 305 desk->panned_y -= dy; 306 damage_all_outputs(); 307 } 308 309 310 static void zoom(void *data) { 311 /* Passed value (data) is the percentage step (+ve or -ve) */ 312 struct desk *desk = wimp.current_desk; 313 double f = 1 + (*(double*)data / 100); 314 315 double next_zoom = desk->zoom * f; 316 if (next_zoom < wimp.zoom_min) { 317 f = wimp.zoom_min / desk->zoom; 318 } else if (next_zoom > wimp.zoom_max) { 319 f = wimp.zoom_max / desk->zoom; 320 } 321 unfullscreen(); 322 desk->zoom *= f; 323 double fx = wimp.cursor->x * (f - 1) / desk->zoom; 324 double fy = wimp.cursor->y * (f - 1) / desk->zoom; 325 struct view *view; 326 wl_list_for_each(view, &desk->views, link) { 327 view->x -= fx; 328 view->y -= fy; 329 } 330 desk->panned_x -= fx; 331 desk->panned_y -= fy; 332 damage_all_outputs(); 333 } 334 335 336 static void reset_zoom(void *data) { 337 double dz = 100 * 1 / wimp.current_desk->zoom - 100; 338 zoom(&dz); 339 } 340 341 342 static void zoom_scroll(void *data) { 343 struct motion motion = *(struct motion*)data; 344 double dz = - motion.dx - motion.dy; 345 zoom(&dz); 346 } 347 348 349 static double zoom_pinch_initial; 350 351 352 static void zoom_pinch(void *data) { 353 double scale = *(double*)data; 354 double dz = 100 * scale * zoom_pinch_initial / wimp.current_desk->zoom - 100; 355 zoom(&dz); 356 } 357 358 359 static void zoom_pinch_begin(void *data) { 360 zoom_pinch_initial = wimp.current_desk->zoom; 361 } 362 363 364 static void set_mark(void *data) { 365 struct mark *mark = calloc(1, sizeof(struct mark)); 366 struct desk *desk = wimp.current_desk; 367 mark->desk = desk; 368 mark->x = desk->panned_x; 369 mark->y = desk->panned_y; 370 mark->zoom = desk->zoom; 371 mark->key = 0; 372 wl_list_insert(&wimp.marks, &mark->link); 373 wimp.mark_waiting = true; 374 damage_mark_indicator(); 375 } 376 377 378 void actually_set_mark(const xkb_keysym_t sym) { 379 struct mark *mark; 380 struct mark *existing = NULL; 381 382 if (sym == XKB_KEY_Escape) { 383 mark = wl_container_of(wimp.marks.next, mark, link); 384 wl_list_remove(&mark->link); 385 free(mark); 386 return; 387 } 388 389 wl_list_for_each(mark, &wimp.marks, link) { 390 if (mark->key == sym) { 391 existing = mark; 392 break; 393 } 394 } 395 if (existing) { 396 wl_list_remove(&existing->link); 397 free(existing); 398 } 399 400 mark = wl_container_of(wimp.marks.next, mark, link); 401 mark->key = sym; 402 403 if (wimp.bind_marks) { 404 struct binding *kb, *tmp; 405 wl_list_for_each_safe(kb, tmp, &wimp.key_bindings, link) { 406 if (kb->key == sym && kb->mods == 0) { 407 return; 408 } 409 } 410 kb = calloc(1, sizeof(struct binding)); 411 kb->mods = 0; 412 kb->key = sym; 413 kb->data = calloc(1, sizeof(xkb_keysym_t)); 414 kb->action = &actually_go_to_mark; 415 *(xkb_keysym_t *)(kb->data) = sym; 416 wl_list_insert(&wimp.key_bindings, &kb->link); 417 } 418 } 419 420 421 static void go_to_mark(void *data) { 422 wimp.mark_waiting = true; 423 damage_mark_indicator(); 424 } 425 426 427 void actually_go_to_mark(void *data) { 428 const xkb_keysym_t sym = *(xkb_keysym_t *)data; 429 struct mark *mark; 430 431 if (sym == XKB_KEY_Escape) { 432 return; 433 } 434 435 bool found = false; 436 wl_list_for_each(mark, &wimp.marks, link) { 437 if (mark->key == sym) { 438 found = true; 439 break; 440 } 441 } 442 if (!found) { 443 return; 444 } 445 446 unfullscreen(); 447 struct motion motion = { 448 .dx = mark->desk->panned_x - mark->x, 449 .dy = mark->desk->panned_y - mark->y, 450 .is_percentage = false, 451 }; 452 mark->desk->zoom = mark->zoom; 453 set_desk(mark->desk); 454 pan_desk(&motion); 455 456 struct view *view; 457 struct wlr_box *extents = wlr_output_layout_get_box(wimp.output_layout, NULL); 458 wl_list_for_each(view, &wimp.current_desk->views, link) { 459 if ( 460 view->x + view->surface->geometry.width < 0 || extents->width < view->x || 461 view->y + view->surface->geometry.height < 0 || extents->height < view->y 462 ) { 463 continue; 464 } 465 focus_view(view, NULL); 466 return; 467 } 468 } 469 470 471 void toggle_fullscreen(void *data) { 472 struct wlr_surface *surface = wimp.seat->keyboard_state.focused_surface; 473 if (!surface || !wlr_surface_is_xdg_surface(surface)) { 474 return; 475 } 476 struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_from_wlr_surface(surface); 477 struct view *view = wl_container_of(wimp.current_desk->views.next, view, link); 478 fullscreen_xdg_surface(view, xdg_surface, NULL); 479 } 480 481 482 static void halfimize(void *data) { 483 struct wlr_surface *surface = wimp.seat->keyboard_state.focused_surface; 484 if (!surface || !wlr_surface_is_xdg_surface(surface) || wl_list_empty(&wimp.current_desk->views)) { 485 return; 486 } 487 struct view *view = wl_container_of(wimp.current_desk->views.next, view, link); 488 enum direction dir = *(enum direction*)data; 489 double vx = view->x + view->surface->geometry.width / 2; 490 double vy = view->y + view->surface->geometry.height / 2; 491 double x, y, width, height; 492 wlr_output_layout_closest_point(wimp.output_layout, NULL, vx, vy, &x, &y); 493 struct wlr_output *output = wlr_output_layout_output_at(wimp.output_layout, x, y); 494 unfullscreen(); 495 496 switch (dir) { 497 case RIGHT: 498 x = output->width / 2; 499 y = 0; 500 width = output->width / 2; 501 height = output->height; 502 break; 503 case LEFT: 504 x = 0; 505 y = 0; 506 width = output->width / 2; 507 height = output->height; 508 break; 509 case UP: 510 x = 0; 511 y = 0; 512 width = output->width; 513 height = output->height / 2; 514 break; 515 case DOWN: 516 x = 0; 517 y = output->height / 2; 518 width = output->width; 519 height = output->height / 2; 520 break; 521 case NO_DIRECTION: 522 return; 523 } 524 525 double zoom = wimp.current_desk->zoom; 526 int border_width = wimp.current_desk->border_width; 527 struct wlr_box new = { 528 .x = x / zoom + border_width, 529 .y = y / zoom + border_width, 530 .width = (width - border_width * 2) / zoom, 531 .height = (height - border_width * 2) / zoom, 532 }; 533 view_apply_geometry(view, &new); 534 } 535 536 537 static void maximize(void *data) { 538 struct wlr_surface *surface = wimp.seat->keyboard_state.focused_surface; 539 if (!surface || !wlr_surface_is_xdg_surface(surface) || wl_list_empty(&wimp.current_desk->views)) { 540 return; 541 } 542 543 struct view *view = wl_container_of(wimp.current_desk->views.next, view, link); 544 double vx = view->x + view->surface->geometry.width / 2; 545 double vy = view->y + view->surface->geometry.height / 2; 546 double x, y; 547 wlr_output_layout_closest_point(wimp.output_layout, NULL, vx, vy, &x, &y); 548 struct wlr_output *output = wlr_output_layout_output_at(wimp.output_layout, x, y); 549 unfullscreen(); 550 551 double zoom = wimp.current_desk->zoom; 552 int border_width = wimp.current_desk->border_width; 553 struct wlr_box new = { 554 .x = border_width, 555 .y = border_width, 556 .width = (output->width - border_width * 2) / zoom, 557 .height = (output->height - border_width * 2) / zoom, 558 }; 559 view_apply_geometry(view, &new); 560 } 561 562 563 static void send_to_desk(void *data) { 564 struct view *view = wl_container_of(wimp.current_desk->views.next, view, link); 565 int index = *(double*)data - 1; 566 unfullscreen(); 567 view_to_desk(view, index); 568 } 569 570 571 static void toggle_scratchpad(void *data) { 572 struct scratchpad *scratchpad = scratchpad_from_id(*(int *)data); 573 574 if (scratchpad->view) { 575 if (scratchpad->is_mapped) { 576 unmap_view(scratchpad->view); 577 } else { 578 map_view(scratchpad->view); 579 } 580 return; 581 } 582 583 wimp.scratchpad_waiting = true; 584 585 scratchpad->pid = fork(); 586 if (scratchpad->pid == 0) { 587 execl("/bin/sh", "/bin/sh", "-c", scratchpad->command, (void *)NULL); 588 exit(EXIT_FAILURE); 589 } else if (scratchpad->pid < 0) { 590 wlr_log(WLR_ERROR, "Failed to fork for scratchpad."); 591 wimp.scratchpad_waiting = false; 592 } else { 593 wlr_log(WLR_DEBUG, "Launching scratchpad: %s", scratchpad->command); 594 } 595 } 596 597 598 static void to_region(void *data) { 599 struct wlr_box *box = (struct wlr_box *)data; 600 struct wlr_box *extents = wlr_output_layout_get_box(wimp.output_layout, NULL); 601 602 struct motion motion = { 603 .dx = - (extents->width / 2) + (box->width / 2) + box->x, 604 .dy = - (extents->height / 2) + (box->height / 2) + box->y, 605 .is_percentage = false, 606 }; 607 pan_desk(&motion); 608 609 double f; 610 if (extents->width / extents->height > box->width / box->height) { 611 f = wimp.current_desk->zoom / ((double)box->width / (double)extents->width); 612 } else { 613 f = wimp.current_desk->zoom / ((double)box->height / (double)extents->height); 614 } 615 double dz = 100 * f / wimp.current_desk->zoom - 100; 616 double cx = wimp.cursor->x; 617 double cy = wimp.cursor->y; 618 wimp.cursor->x = extents->width / 2; 619 wimp.cursor->y = extents->height / 2; 620 zoom(&dz); 621 wimp.cursor->x = cx; 622 wimp.cursor->y = cy; 623 } 624