model.cc (114430B)
1 #include "../winsys/common.hh" 2 #include "../winsys/util.hh" 3 #include "defaults.hh" 4 #include "model.hh" 5 6 #include <algorithm> 7 #include <chrono> 8 #include <cmath> 9 #include <cstdlib> 10 #include <cstring> 11 #include <functional> 12 #include <set> 13 #include <sstream> 14 #include <thread> 15 #include <unordered_set> 16 #include <vector> 17 18 extern "C" { 19 #include <sys/wait.h> 20 #include <unistd.h> 21 } 22 23 #ifdef DEBUG 24 #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG 25 #endif 26 27 #include "spdlog/spdlog.h" 28 29 using namespace winsys; 30 31 static Model* g_instance; 32 33 Model::Model(Connection& conn) 34 : m_conn(conn), 35 m_running(true), 36 m_partitions({}, true), 37 m_contexts({}, true), 38 m_workspaces({}, true), 39 mp_partition(nullptr), 40 mp_context(nullptr), 41 mp_workspace(nullptr), 42 mp_prev_partition(nullptr), 43 mp_prev_context(nullptr), 44 mp_prev_workspace(nullptr), 45 mp_attachment(std::atomic(nullptr)), 46 m_move_buffer(Buffer::BufferKind::Move), 47 m_resize_buffer(Buffer::BufferKind::Resize), 48 m_stack({}), 49 m_order({}), 50 m_client_map({}), 51 m_pid_map({}), 52 m_fullscreen_map({}), 53 m_leader_map({}), 54 m_sticky_clients({}), 55 m_unmanaged_windows({}), 56 mp_focus(nullptr), 57 mp_jumped_from(nullptr), 58 m_key_bindings({ 59 #define CALL(args) [](Model& model) {model.args;} 60 { { Key::Q, { Main, Ctrl, Shift } }, 61 CALL(exit()) 62 }, 63 64 // client state modifiers 65 { { Key::C, { Main } }, 66 CALL(kill_focus()) 67 }, 68 { { Key::Space, { Main, Shift } }, 69 CALL(set_floating_focus(Toggle::Reverse)) 70 }, 71 { { Key::F, { Main } }, 72 CALL(set_fullscreen_focus(Toggle::Reverse)) 73 }, 74 { { Key::X, { Main } }, 75 CALL(set_sticky_focus(Toggle::Reverse)) 76 }, 77 { { Key::F, { Main, Sec, Ctrl } }, 78 CALL(set_contained_focus(Toggle::Reverse)) 79 }, 80 { { Key::I, { Main, Sec, Ctrl } }, 81 CALL(set_invincible_focus(Toggle::Reverse)) 82 }, 83 { { Key::P, { Main, Sec, Ctrl } }, 84 CALL(set_producing_focus(Toggle::Reverse)) 85 }, 86 { { Key::Y, { Main } }, 87 CALL(set_iconify_focus(Toggle::Reverse)) 88 }, 89 { { Key::U, { Main } }, 90 CALL(pop_deiconify()) 91 }, 92 { { Key::U, { Main, Sec } }, 93 CALL(deiconify_all()) 94 }, 95 96 // client arrangers 97 { { Key::Space, { Main, Ctrl } }, 98 CALL(center_focus()) 99 }, 100 { { Key::H, { Main, Ctrl } }, 101 [](Model& model) { 102 Client_ptr focus = model.mp_focus; 103 104 if (focus && model.is_free(focus)) 105 model.nudge_focus(Edge::Left, 15); 106 else 107 model.shuffle_main(Direction::Backward); 108 } 109 }, 110 { { Key::J, { Main, Ctrl } }, 111 [](Model& model) { 112 Client_ptr focus = model.mp_focus; 113 114 if (focus && model.is_free(focus)) 115 model.nudge_focus(Edge::Bottom, 15); 116 else 117 model.shuffle_stack(Direction::Forward); 118 } 119 }, 120 { { Key::K, { Main, Ctrl } }, 121 [](Model& model) { 122 Client_ptr focus = model.mp_focus; 123 124 if (focus && model.is_free(focus)) 125 model.nudge_focus(Edge::Top, 15); 126 else 127 model.shuffle_stack(Direction::Backward); 128 } 129 }, 130 { { Key::L, { Main, Ctrl } }, 131 [](Model& model) { 132 Client_ptr focus = model.mp_focus; 133 134 if (focus && model.is_free(focus)) 135 model.nudge_focus(Edge::Right, 15); 136 else 137 model.shuffle_main(Direction::Forward); 138 } 139 }, 140 { { Key::H, { Main, Ctrl, Shift } }, 141 CALL(stretch_focus(Edge::Left, 15)) 142 }, 143 { { Key::J, { Main, Ctrl, Shift } }, 144 CALL(stretch_focus(Edge::Bottom, 15)) 145 }, 146 { { Key::K, { Main, Ctrl, Shift } }, 147 CALL(stretch_focus(Edge::Top, 15)) 148 }, 149 { { Key::L, { Main, Ctrl, Shift } }, 150 CALL(stretch_focus(Edge::Right, 15)) 151 }, 152 { { Key::Y, { Main, Ctrl, Shift } }, 153 CALL(stretch_focus(Edge::Left, -15)) 154 }, 155 { { Key::U, { Main, Ctrl, Shift } }, 156 CALL(stretch_focus(Edge::Bottom, -15)) 157 }, 158 { { Key::I, { Main, Ctrl, Shift } }, 159 CALL(stretch_focus(Edge::Top, -15)) 160 }, 161 { { Key::O, { Main, Ctrl, Shift } }, 162 CALL(stretch_focus(Edge::Right, -15)) 163 }, 164 { { Key::Left, { Main, Ctrl } }, 165 CALL(snap_focus(Edge::Left)) 166 }, 167 { { Key::Down, { Main, Ctrl } }, 168 CALL(snap_focus(Edge::Bottom)) 169 }, 170 { { Key::Up, { Main, Ctrl } }, 171 CALL(snap_focus(Edge::Top)) 172 }, 173 { { Key::Right, { Main, Ctrl } }, 174 CALL(snap_focus(Edge::Right)) 175 }, 176 { { Key::J, { Main } }, 177 CALL(cycle_focus(Direction::Forward)) 178 }, 179 { { Key::K, { Main } }, 180 CALL(cycle_focus(Direction::Backward)) 181 }, 182 { { Key::J, { Main, Shift } }, 183 CALL(drag_focus(Direction::Forward)) 184 }, 185 { { Key::K, { Main, Shift } }, 186 CALL(drag_focus(Direction::Backward)) 187 }, 188 { { Key::R, { Main } }, 189 CALL(reverse_clients()) 190 }, 191 { { Key::SemiColon, { Main, Shift } }, 192 CALL(rotate_clients(Direction::Forward)) 193 }, 194 { { Key::Comma, { Main, Shift } }, 195 CALL(rotate_clients(Direction::Backward)) 196 }, 197 198 // client jump criteria 199 { { Key::B, { Main } }, 200 CALL(jump_client({ 201 SearchSelector::SelectionCriterium::ByClassEquals, 202 "qutebrowser" 203 })) 204 }, 205 { { Key::B, { Main, Shift } }, 206 CALL(jump_client({ 207 SearchSelector::SelectionCriterium::ByClassEquals, 208 "Firefox" 209 })) 210 }, 211 { { Key::B, { Main, Ctrl } }, 212 CALL(jump_client({ 213 SearchSelector::SelectionCriterium::ByClassContains, 214 "Chromium" 215 })) 216 }, 217 { { Key::Space, { Main, Sec } }, 218 CALL(jump_client({ 219 SearchSelector::SelectionCriterium::ByClassEquals, 220 "Spotify" 221 })) 222 }, 223 { { Key::E, { Main } }, 224 CALL(jump_client({ 225 SearchSelector::SelectionCriterium::ByNameContains, 226 "[vim]" 227 })) 228 }, 229 { { Key::Comma, { Main } }, 230 CALL(jump_client({ 231 model.active_workspace()->index(), 232 Workspace::ClientSelector::SelectionCriterium::AtFirst 233 })) 234 }, 235 { { Key::Period, { Main } }, 236 CALL(jump_client({ 237 model.active_workspace()->index(), 238 Workspace::ClientSelector::SelectionCriterium::AtMain 239 })) 240 }, 241 { { Key::Slash, { Main } }, 242 CALL(jump_client({ 243 model.active_workspace()->index(), 244 Workspace::ClientSelector::SelectionCriterium::AtLast 245 })) 246 }, 247 248 // workspace behavior modifiers 249 { { Key::M, { Main, Shift } }, 250 CALL(set_focus_follows_mouse(Toggle::Reverse, model.active_workspace())) 251 }, 252 253 // workspace layout modifiers 254 { { Key::F, { Main, Shift } }, 255 CALL(set_layout(LayoutHandler::LayoutKind::Float)) 256 }, 257 { { Key::L, { Main, Shift } }, 258 CALL(set_layout(LayoutHandler::LayoutKind::FramelessFloat)) 259 }, 260 { { Key::Z, { Main } }, 261 CALL(set_layout(LayoutHandler::LayoutKind::SingleFloat)) 262 }, 263 { { Key::Z, { Main, Shift } }, 264 CALL(set_layout(LayoutHandler::LayoutKind::FramelessSingleFloat)) 265 }, 266 { { Key::M, { Main } }, 267 CALL(set_layout(LayoutHandler::LayoutKind::Monocle)) 268 }, 269 { { Key::D, { Main, Ctrl } }, 270 CALL(set_layout(LayoutHandler::LayoutKind::MainDeck)) 271 }, 272 { { Key::D, { Main, Shift } }, 273 CALL(set_layout(LayoutHandler::LayoutKind::StackDeck)) 274 }, 275 { { Key::D, { Main, Ctrl, Shift } }, 276 CALL(set_layout(LayoutHandler::LayoutKind::DoubleDeck)) 277 }, 278 { { Key::G, { Main } }, 279 CALL(set_layout(LayoutHandler::LayoutKind::Center)) 280 }, 281 { { Key::T, { Main } }, 282 CALL(set_layout(LayoutHandler::LayoutKind::DoubleStack)) 283 }, 284 { { Key::T, { Main, Shift } }, 285 CALL(set_layout(LayoutHandler::LayoutKind::CompactDoubleStack)) 286 }, 287 { { Key::P, { Main, Ctrl, Shift } }, 288 CALL(set_layout(LayoutHandler::LayoutKind::Paper)) 289 }, 290 { { Key::P, { Main, Sec, Ctrl, Shift } }, 291 CALL(set_layout(LayoutHandler::LayoutKind::CompactPaper)) 292 }, 293 { { Key::Y, { Main, Shift } }, 294 CALL(set_layout(LayoutHandler::LayoutKind::HorizontalStack)) 295 }, 296 { { Key::Y, { Main, Ctrl } }, 297 CALL(set_layout(LayoutHandler::LayoutKind::CompactHorizontalStack)) 298 }, 299 { { Key::V, { Main, Shift } }, 300 CALL(set_layout(LayoutHandler::LayoutKind::VerticalStack)) 301 }, 302 { { Key::V, { Main, Ctrl } }, 303 CALL(set_layout(LayoutHandler::LayoutKind::CompactVerticalStack)) 304 }, 305 { { Key::F, { Main, Ctrl, Shift } }, 306 CALL(set_layout_retain_region(LayoutHandler::LayoutKind::Float)) 307 }, 308 { { Key::Space, { Main } }, 309 CALL(toggle_layout()) 310 }, 311 312 // workspace layout data modifiers 313 { { Key::Equal, { Main } }, 314 CALL(change_gap_size(2)) 315 }, 316 { { Key::Minus, { Main } }, 317 CALL(change_gap_size(-2)) 318 }, 319 { { Key::Equal, { Main, Shift } }, 320 CALL(reset_gap_size()) 321 }, 322 { { Key::I, { Main } }, 323 CALL(change_main_count(1)) 324 }, 325 { { Key::D, { Main } }, 326 CALL(change_main_count(-1)) 327 }, 328 { { Key::L, { Main } }, 329 CALL(change_main_factor(.05f)) 330 }, 331 { { Key::H, { Main } }, 332 CALL(change_main_factor(-.05f)) 333 }, 334 { { Key::PageUp, { Main, Shift } }, 335 CALL(change_margin(5)) 336 }, 337 { { Key::PageDown, { Main, Shift } }, 338 CALL(change_margin(-5)) 339 }, 340 { { Key::Left, { Main, Shift } }, 341 CALL(change_margin(Edge::Left, 5)) 342 }, 343 { { Key::Left, { Main, Ctrl, Shift } }, 344 CALL(change_margin(Edge::Left, -5)) 345 }, 346 { { Key::Up, { Main, Shift } }, 347 CALL(change_margin(Edge::Top, 5)) 348 }, 349 { { Key::Up, { Main, Ctrl, Shift } }, 350 CALL(change_margin(Edge::Top, -5)) 351 }, 352 { { Key::Right, { Main, Shift } }, 353 CALL(change_margin(Edge::Right, 5)) 354 }, 355 { { Key::Right, { Main, Ctrl, Shift } }, 356 CALL(change_margin(Edge::Right, -5)) 357 }, 358 { { Key::Down, { Main, Shift } }, 359 CALL(change_margin(Edge::Bottom, 5)) 360 }, 361 { { Key::Down, { Main, Ctrl, Shift } }, 362 CALL(change_margin(Edge::Bottom, -5)) 363 }, 364 { { Key::Comma, { Main, Ctrl, Shift } }, 365 CALL(cycle_layout_data(Direction::Backward)) 366 }, 367 { { Key::Period, { Main, Ctrl, Shift } }, 368 CALL(cycle_layout_data(Direction::Forward)) 369 }, 370 { { Key::Slash, { Main, Ctrl, Shift } }, 371 CALL(toggle_layout_data()) 372 }, 373 { { Key::Delete, { Main, Ctrl, Shift } }, 374 CALL(copy_data_from_prev_layout()) 375 }, 376 { { Key::Equal, { Main, Ctrl, Shift } }, 377 CALL(reset_margin()) 378 }, 379 { { Key::Equal, { Main, Sec, Ctrl, Shift } }, 380 CALL(reset_layout_data()) 381 }, 382 383 // workspace layout storage and retrieval 384 { { Key::F1, { Main, Shift } }, 385 CALL(save_layout(0)) 386 }, 387 { { Key::F2, { Main, Shift } }, 388 CALL(save_layout(1)) 389 }, 390 { { Key::F3, { Main, Shift } }, 391 CALL(save_layout(2)) 392 }, 393 { { Key::F4, { Main, Shift } }, 394 CALL(save_layout(3)) 395 }, 396 { { Key::F5, { Main, Shift } }, 397 CALL(save_layout(4)) 398 }, 399 { { Key::F6, { Main, Shift } }, 400 CALL(save_layout(5)) 401 }, 402 { { Key::F7, { Main, Shift } }, 403 CALL(save_layout(6)) 404 }, 405 { { Key::F8, { Main, Shift } }, 406 CALL(save_layout(7)) 407 }, 408 { { Key::F9, { Main, Shift } }, 409 CALL(save_layout(8)) 410 }, 411 { { Key::F10, { Main, Shift } }, 412 CALL(save_layout(9)) 413 }, 414 { { Key::F11, { Main, Shift } }, 415 CALL(save_layout(10)) 416 }, 417 { { Key::F12, { Main, Shift } }, 418 CALL(save_layout(11)) 419 }, 420 { { Key::F1, { Main } }, 421 CALL(load_layout(0)) 422 }, 423 { { Key::F2, { Main } }, 424 CALL(load_layout(1)) 425 }, 426 { { Key::F3, { Main } }, 427 CALL(load_layout(2)) 428 }, 429 { { Key::F4, { Main } }, 430 CALL(load_layout(3)) 431 }, 432 { { Key::F5, { Main } }, 433 CALL(load_layout(4)) 434 }, 435 { { Key::F6, { Main } }, 436 CALL(load_layout(5)) 437 }, 438 { { Key::F7, { Main } }, 439 CALL(load_layout(6)) 440 }, 441 { { Key::F8, { Main } }, 442 CALL(load_layout(7)) 443 }, 444 { { Key::F9, { Main } }, 445 CALL(load_layout(8)) 446 }, 447 { { Key::F10, { Main } }, 448 CALL(load_layout(9)) 449 }, 450 { { Key::F11, { Main } }, 451 CALL(load_layout(10)) 452 }, 453 { { Key::F12, { Main } }, 454 CALL(load_layout(11)) 455 }, 456 457 // context activators 458 { { Key::Escape, { Main, Ctrl } }, 459 CALL(toggle_context()) 460 }, 461 { { Key::RightBracket, { Main, Ctrl } }, 462 CALL(activate_next_context(Direction::Forward)) 463 }, 464 { { Key::LeftBracket, { Main, Ctrl } }, 465 CALL(activate_next_context(Direction::Backward)) 466 }, 467 { { Key::One, { Main, Ctrl } }, 468 CALL(activate_context(Util::Change<Index>{ 0 })) 469 }, 470 { { Key::Two, { Main, Ctrl } }, 471 CALL(activate_context(1)) 472 }, 473 { { Key::Three, { Main, Ctrl } }, 474 CALL(activate_context(2)) 475 }, 476 { { Key::Four, { Main, Ctrl } }, 477 CALL(activate_context(3)) 478 }, 479 { { Key::Five, { Main, Ctrl } }, 480 CALL(activate_context(4)) 481 }, 482 { { Key::Six, { Main, Ctrl } }, 483 CALL(activate_context(5)) 484 }, 485 { { Key::Seven, { Main, Ctrl } }, 486 CALL(activate_context(6)) 487 }, 488 { { Key::Eight, { Main, Ctrl } }, 489 CALL(activate_context(7)) 490 }, 491 { { Key::Nine, { Main, Ctrl } }, 492 CALL(activate_context(8)) 493 }, 494 { { Key::Zero, { Main, Ctrl } }, 495 CALL(activate_context(9)) 496 }, 497 498 // workspace activators 499 { { Key::Escape, { Main } }, 500 CALL(toggle_workspace_current_context()) 501 }, 502 { { Key::RightBracket, { Main } }, 503 CALL(activate_next_workspace_current_context(Direction::Forward)) 504 }, 505 { { Key::LeftBracket, { Main } }, 506 CALL(activate_next_workspace_current_context(Direction::Backward)) 507 }, 508 { { Key::One, { Main } }, 509 CALL(activate_workspace_current_context(Util::Change<Index>{ 0 })) 510 }, 511 { { Key::Two, { Main } }, 512 CALL(activate_workspace_current_context(1)) 513 }, 514 { { Key::Three, { Main } }, 515 CALL(activate_workspace_current_context(2)) 516 }, 517 { { Key::Four, { Main } }, 518 CALL(activate_workspace_current_context(3)) 519 }, 520 { { Key::Five, { Main } }, 521 CALL(activate_workspace_current_context(4)) 522 }, 523 { { Key::Six, { Main } }, 524 CALL(activate_workspace_current_context(5)) 525 }, 526 { { Key::Seven, { Main } }, 527 CALL(activate_workspace_current_context(6)) 528 }, 529 { { Key::Eight, { Main } }, 530 CALL(activate_workspace_current_context(7)) 531 }, 532 { { Key::Nine, { Main } }, 533 CALL(activate_workspace_current_context(8)) 534 }, 535 { { Key::Zero, { Main } }, 536 CALL(activate_workspace_current_context(9)) 537 }, 538 539 // workspace client movers 540 { { Key::BackSlash, { Main } }, 541 CALL(attach_next_client()) 542 }, 543 { { Key::RightBracket, { Main, Shift } }, 544 CALL(move_focus_to_next_workspace(Direction::Forward)) 545 }, 546 { { Key::LeftBracket, { Main, Shift } }, 547 CALL(move_focus_to_next_workspace(Direction::Backward)) 548 }, 549 { { Key::One, { Main, Shift } }, 550 CALL(move_focus_to_workspace(0)) 551 }, 552 { { Key::Two, { Main, Shift } }, 553 CALL(move_focus_to_workspace(1)) 554 }, 555 { { Key::Three, { Main, Shift } }, 556 CALL(move_focus_to_workspace(2)) 557 }, 558 { { Key::Four, { Main, Shift } }, 559 CALL(move_focus_to_workspace(3)) 560 }, 561 { { Key::Five, { Main, Shift } }, 562 CALL(move_focus_to_workspace(4)) 563 }, 564 { { Key::Six, { Main, Shift } }, 565 CALL(move_focus_to_workspace(5)) 566 }, 567 { { Key::Seven, { Main, Shift } }, 568 CALL(move_focus_to_workspace(6)) 569 }, 570 { { Key::Eight, { Main, Shift } }, 571 CALL(move_focus_to_workspace(7)) 572 }, 573 { { Key::Nine, { Main, Shift } }, 574 CALL(move_focus_to_workspace(8)) 575 }, 576 { { Key::Zero, { Main, Shift } }, 577 CALL(move_focus_to_workspace(9)) 578 }, 579 580 // screen region modifiers 581 { { Key::V, { Main } }, 582 CALL(activate_screen_struts(Toggle::Reverse)) 583 }, 584 585 // external commands 586 #define CALL_EXTERNAL(command) CALL(spawn_external(#command)) 587 { { Key::PlayPause, {} }, 588 CALL_EXTERNAL(playerctl play-pause) 589 }, 590 { { Key::P, { Main, Sec } }, 591 CALL_EXTERNAL(playerctl play-pause) 592 }, 593 { { Key::PreviousTrack, {} }, 594 CALL_EXTERNAL(playerctl previous) 595 }, 596 { { Key::K, { Main, Sec } }, 597 CALL_EXTERNAL(playerctl previous) 598 }, 599 { { Key::NextTrack, {} }, 600 CALL_EXTERNAL(playerctl next) 601 }, 602 { { Key::J, { Main, Sec } }, 603 CALL_EXTERNAL(playerctl next) 604 }, 605 { { Key::StopMedia, {} }, 606 CALL_EXTERNAL(playerctl stop) 607 }, 608 { { Key::BackSpace, { Main, Sec } }, 609 CALL_EXTERNAL(playerctl stop) 610 }, 611 { { Key::VolumeMute, {} }, 612 CALL_EXTERNAL(amixer -D pulse sset Master toggle) 613 }, 614 { { Key::VolumeDown, {} }, 615 CALL_EXTERNAL(amixer -D pulse sset Master 5%-) 616 }, 617 { { Key::VolumeUp, {} }, 618 CALL_EXTERNAL(amixer -D pulse sset Master 5%+) 619 }, 620 { { Key::VolumeMute, { Shift } }, 621 CALL_EXTERNAL(amixer -D pulse sset Capture toggle) 622 }, 623 { { Key::MicMute, {} }, 624 CALL_EXTERNAL(amixer -D pulse sset Capture toggle) 625 }, 626 { { Key::Space, { Ctrl } }, 627 CALL_EXTERNAL(dunstctl close) 628 }, 629 { { Key::Space, { Ctrl, Shift } }, 630 CALL_EXTERNAL(dunstctl close-all) 631 }, 632 { { Key::Comma, { Ctrl, Shift } }, 633 CALL_EXTERNAL(dunstctl history-pop) 634 }, 635 { { Key::Return, { Main } }, 636 CALL_EXTERNAL(alacritty) 637 }, 638 { { Key::Return, { Main, Shift } }, 639 CALL(spawn_external("alacritty --class " + WM_NAME + ":cf,Alacritty")) 640 }, 641 { { Key::SemiColon, { Main } }, 642 CALL_EXTERNAL(caja) 643 }, 644 { { Key::A, { Main } }, 645 CALL_EXTERNAL(skippy-xd) 646 }, 647 { { Key::P, { Main } }, 648 CALL_EXTERNAL(dmenu_run) 649 }, 650 { { Key::Q, { Main } }, 651 CALL_EXTERNAL(qutebrowser) 652 }, 653 { { Key::Q, { Main, Shift } }, 654 CALL_EXTERNAL(firefox) 655 }, 656 { { Key::Q, { Main, Ctrl } }, 657 CALL_EXTERNAL(chromium) 658 }, 659 { { Key::Apostrophe, { Main } }, 660 CALL_EXTERNAL(blueberry) 661 }, 662 { { Key::P, { Main, Shift } }, 663 CALL_EXTERNAL($HOME/bin/dmenupass) 664 }, 665 { { Key::P, { Main, Ctrl } }, 666 CALL_EXTERNAL($HOME/bin/dmenupass --copy) 667 }, 668 { { Key::O, { Main, Shift } }, 669 CALL_EXTERNAL($HOME/bin/dmenunotify) 670 }, 671 { { Key::G, { Main, Shift } }, 672 CALL_EXTERNAL($HOME/bin/grabcolor) 673 }, 674 { { Key::S, { Main, Ctrl } }, 675 CALL_EXTERNAL($HOME/bin/simplenote) 676 }, 677 { { Key::PrintScreen, { Main } }, 678 CALL_EXTERNAL($HOME/bin/screenshot -s) 679 }, 680 { { Key::PrintScreen, { Main, Shift } }, 681 CALL_EXTERNAL($HOME/bin/screenshot) 682 }, 683 #undef CALL_EXTERNAL 684 #undef CALL 685 }), 686 m_mouse_bindings({ 687 { { MouseInput::MouseInputTarget::Client, Button::Right, { Main, Ctrl } }, 688 [](Model& model, Client_ptr client) { 689 if (client) 690 model.set_floating_client(Toggle::Reverse, client); 691 692 return true; 693 } 694 }, 695 { { MouseInput::MouseInputTarget::Client, Button::Middle, { Main, Ctrl, Shift } }, 696 [](Model& model, Client_ptr client) { 697 if (client) 698 model.set_fullscreen_client(Toggle::Reverse, client); 699 700 return true; 701 } 702 }, 703 { { MouseInput::MouseInputTarget::Client, Button::Middle, { Main } }, 704 [](Model& model, Client_ptr client) { 705 if (client) 706 model.center_client(client); 707 708 return true; 709 } 710 }, 711 { { MouseInput::MouseInputTarget::Client, Button::ScrollDown, { Main, Ctrl, Shift } }, 712 [](Model& model, Client_ptr client) { 713 if (client) 714 model.inflate_client(-16, client); 715 716 return true; 717 } 718 }, 719 { { MouseInput::MouseInputTarget::Client, Button::ScrollUp, { Main, Ctrl, Shift } }, 720 [](Model& model, Client_ptr client) { 721 if (client) 722 model.inflate_client(16, client); 723 724 return true; 725 } 726 }, 727 { { MouseInput::MouseInputTarget::Client, Button::Left, { Main } }, 728 [](Model& model, Client_ptr client) { 729 if (client) 730 model.start_moving(client); 731 732 return true; 733 } 734 }, 735 { { MouseInput::MouseInputTarget::Client, Button::Right, { Main } }, 736 [](Model& model, Client_ptr client) { 737 if (client) 738 model.start_resizing(client); 739 740 return true; 741 } 742 }, 743 { { MouseInput::MouseInputTarget::Global, Button::ScrollDown, { Main } }, 744 [](Model& model, Client_ptr) { 745 model.cycle_focus(Direction::Forward); 746 return false; 747 } 748 }, 749 { { MouseInput::MouseInputTarget::Global, Button::ScrollUp, { Main } }, 750 [](Model& model, Client_ptr) { 751 model.cycle_focus(Direction::Backward); 752 return false; 753 } 754 }, 755 { { MouseInput::MouseInputTarget::Global, Button::ScrollDown, { Main, Shift } }, 756 [](Model& model, Client_ptr) { 757 model.activate_next_workspace(Direction::Forward); 758 return false; 759 } 760 }, 761 { { MouseInput::MouseInputTarget::Global, Button::ScrollUp, { Main, Shift } }, 762 [](Model& model, Client_ptr) { 763 model.activate_next_workspace(Direction::Backward); 764 return false; 765 } 766 }, 767 { { MouseInput::MouseInputTarget::Client, Button::Forward, { Main } }, 768 [](Model& model, Client_ptr client) { 769 if (client) 770 model.move_client_to_next_workspace(Direction::Forward, client); 771 772 return false; 773 } 774 }, 775 { { MouseInput::MouseInputTarget::Client, Button::Backward, { Main } }, 776 [](Model& model, Client_ptr client) { 777 if (client) 778 model.move_client_to_next_workspace(Direction::Backward, client); 779 780 return false; 781 } 782 }, 783 { { MouseInput::MouseInputTarget::Client, Button::Right, { Main, Ctrl, Shift } }, 784 [](Model& model, Client_ptr client) { 785 if (client) 786 model.kill_client(client); 787 788 return false; 789 } 790 }, 791 { { MouseInput::MouseInputTarget::Global, Button::Left, { Main, Sec, Ctrl } }, 792 [](Model& model, Client_ptr) { 793 model.spawn_external("alacritty --class " + WM_NAME + ":cf,Alacritty"); 794 return false; 795 } 796 }, 797 }), 798 m_config() 799 { 800 #ifdef DEBUG 801 spdlog::set_level(spdlog::level::debug); 802 #endif 803 804 g_instance = this; 805 806 static const std::vector<std::string> context_names{ 807 "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" 808 }; 809 810 static const std::vector<std::string> workspace_names{ 811 "main", "web", "term", {}, {}, {}, {}, {}, {}, {} 812 }; 813 814 for (std::size_t i = 0; i < context_names.size(); ++i) { 815 Context_ptr context = new Context(i, context_names[i]); 816 m_contexts.insert_at_back(context); 817 818 for (std::size_t j = 0; j < workspace_names.size(); ++j) { 819 Workspace_ptr workspace = new Workspace( 820 workspace_names.size() * i + j, 821 workspace_names[j], 822 context 823 ); 824 825 m_workspaces.insert_at_back(workspace); 826 context->register_workspace(workspace); 827 } 828 829 context->activate_workspace(Index{0}); 830 } 831 832 if constexpr (Config::ipc_enabled) 833 m_conn.init_wm_ipc(); 834 835 acquire_partitions(); 836 837 std::vector<std::string> desktop_names; 838 desktop_names.reserve(m_workspaces.size()); 839 std::transform( 840 m_workspaces.begin(), 841 m_workspaces.end(), 842 std::back_inserter(desktop_names), 843 [](Workspace_ptr workspace) -> std::string { 844 return workspace->identifier(); 845 } 846 ); 847 848 m_conn.init_for_wm(desktop_names); 849 850 m_contexts.activate_at_index(0); 851 m_workspaces.activate_at_index(0); 852 853 mp_context = *m_contexts.active_element(); 854 mp_workspace = *m_workspaces.active_element(); 855 856 m_conn.set_current_desktop(0); 857 858 std::vector<KeyInput> key_inputs; 859 std::vector<MouseInput> mouse_inputs; 860 861 key_inputs.reserve(m_key_bindings.size()); 862 mouse_inputs.reserve(m_mouse_bindings.size()); 863 864 std::transform( 865 m_key_bindings.begin(), 866 m_key_bindings.end(), 867 std::back_inserter(key_inputs), 868 [](auto kv) -> KeyInput { return kv.first; } 869 ); 870 871 std::transform( 872 m_mouse_bindings.begin(), 873 m_mouse_bindings.end(), 874 std::back_inserter(mouse_inputs), 875 [](auto kv) -> MouseInput { return kv.first; } 876 ); 877 878 m_conn.grab_bindings(key_inputs, mouse_inputs); 879 880 for (Window window : m_conn.top_level_windows()) 881 manage(window, !m_conn.must_manage_window(window), true); 882 883 if constexpr (!Config::debugging) { 884 spawn_external(m_config.directory + m_config.blocking_autostart); 885 spawn_external(m_config.directory + m_config.nonblocking_autostart); 886 spdlog::info("ran autostart scripts"); 887 } 888 889 spdlog::info("initialized " + WM_NAME); 890 } 891 892 Model::~Model() 893 { 894 for (std::size_t i = 0; i < m_partitions.size(); ++i) 895 delete m_partitions[i]; 896 897 for (std::size_t i = 0; i < m_contexts.size(); ++i) 898 delete m_contexts[i]; 899 900 for (std::size_t i = 0; i < m_workspaces.size(); ++i) 901 delete m_workspaces[i]; 902 903 std::unordered_set<Client_ptr> clients{}; 904 905 for (auto [_,client] : m_client_map) 906 clients.insert(client); 907 908 for (Client_ptr client : clients) 909 delete client; 910 911 m_partitions.clear(); 912 m_contexts.clear(); 913 m_workspaces.clear(); 914 m_client_map.clear(); 915 } 916 917 918 void 919 Model::run() 920 { 921 while (m_running) 922 if constexpr (Config::ipc_enabled) { 923 if (m_conn.check_progress()) { 924 // process IPC message 925 m_conn.process_messages( 926 [=,this](winsys::Message message) { 927 std::visit(m_message_visitor, message); 928 } 929 ); 930 931 // process windowing system event 932 m_conn.process_events( 933 [=,this](winsys::Event event) { 934 std::visit(m_event_visitor, event); 935 } 936 ); 937 } 938 } else 939 std::visit(m_event_visitor, m_conn.step()); 940 } 941 942 943 void 944 Model::init_signals() const 945 { 946 struct sigaction child_sa, exit_sa, ignore_sa; 947 948 std::memset(&child_sa, 0, sizeof(child_sa)); 949 child_sa.sa_handler = &Model::wait_children; 950 951 std::memset(&exit_sa, 0, sizeof(exit_sa)); 952 exit_sa.sa_handler = &Model::handle_signal; 953 954 std::memset(&ignore_sa, 0, sizeof(ignore_sa)); 955 ignore_sa.sa_handler = SIG_IGN; 956 957 sigaction(SIGCHLD, &child_sa, NULL); 958 sigaction(SIGINT, &exit_sa, NULL); 959 sigaction(SIGHUP, &exit_sa, NULL); 960 sigaction(SIGINT, &exit_sa, NULL); 961 sigaction(SIGTERM, &exit_sa, NULL); 962 sigaction(SIGPIPE, &ignore_sa, NULL); 963 } 964 965 // window management actions 966 967 void 968 Model::acquire_partitions() 969 { 970 std::size_t index = m_partitions.active_index(); 971 std::vector<Screen> connected_outputs = m_conn.connected_outputs(); 972 973 connected_outputs.erase( 974 std::unique( 975 connected_outputs.begin(), 976 connected_outputs.end(), 977 [](Screen const& screen1, Screen const& screen2) { 978 return screen1.full_region() == screen2.full_region(); 979 } 980 ), 981 connected_outputs.end() 982 ); 983 984 if (connected_outputs.size() > m_contexts.size()) { 985 // TODO: generate more contexts? 986 spdlog::info("more outputs than available contexts"); 987 return; 988 } 989 990 if (connected_outputs.empty()) { 991 spdlog::info("could not acquire any partitions"); 992 return; 993 } 994 995 std::vector<Context_ptr> partition_contexts( 996 std::max(connected_outputs.size(), m_partitions.size()), 997 nullptr 998 ); 999 1000 std::unordered_set<Index> used_contexts{}; 1001 1002 for (std::size_t i = 0; i < m_partitions.size(); ++i) { 1003 partition_contexts[m_partitions[i]->index()] = m_partitions[i]->context(); 1004 used_contexts.insert(m_partitions[i]->context()->index()); 1005 delete m_partitions[i]; 1006 } 1007 1008 m_partitions.clear(); 1009 1010 std::vector<Partition_ptr> contextless_partitions{}; 1011 1012 for (std::size_t i = 0; i < connected_outputs.size(); ++i) { 1013 m_partitions.insert_at_back(new Partition( 1014 connected_outputs[i], 1015 i 1016 )); 1017 1018 if (partition_contexts[i] != nullptr) 1019 m_partitions[i]->set_context(partition_contexts[i]); 1020 else 1021 contextless_partitions.push_back(m_partitions[i]); 1022 } 1023 1024 for (std::size_t i = 0, context_index = 0; i < contextless_partitions.size(); ++context_index) { 1025 if (Util::contains(used_contexts, context_index)) 1026 continue; 1027 1028 contextless_partitions[i++]->set_context(m_contexts[context_index]); 1029 } 1030 1031 if (index < m_partitions.size()) 1032 m_partitions.activate_at_index(index); 1033 else 1034 m_partitions.activate_at_index(0); 1035 1036 mp_partition = *m_partitions.active_element(); 1037 Screen& screen = active_screen(); 1038 1039 screen.compute_placeable_region(); 1040 1041 std::vector<Region> workspace_regions(m_workspaces.size(), screen.full_region()); 1042 1043 m_conn.set_desktop_geometry(workspace_regions); 1044 m_conn.set_desktop_viewport(workspace_regions); 1045 m_conn.set_workarea(workspace_regions); 1046 1047 for (auto& [_,client] : m_client_map) { 1048 Region screen_region = screen.full_region(); 1049 Region client_region = client->free_region; 1050 1051 if (client_region.pos.x >= screen_region.dim.w) 1052 client_region.pos.x = screen_region.dim.w - client_region.dim.w; 1053 1054 if (client_region.pos.y >= screen_region.dim.h) 1055 client_region.pos.y = screen_region.dim.h - client_region.dim.h; 1056 1057 client->set_free_region(client_region); 1058 } 1059 1060 spdlog::debug("acquired {} partitions", m_partitions.size()); 1061 } 1062 1063 void 1064 Model::resolve_active_partition(winsys::Pos pos) 1065 { 1066 if (m_partitions.size() == 1 || mp_partition->contains(pos)) 1067 return; 1068 1069 for (Partition_ptr partition : m_partitions) 1070 if (partition->contains(pos)) 1071 activate_partition(partition); 1072 } 1073 1074 Screen& 1075 Model::active_screen() 1076 { 1077 return mp_partition->screen(); 1078 } 1079 1080 Screen const& 1081 Model::active_screen() const 1082 { 1083 return mp_partition->screen(); 1084 } 1085 1086 Client_ptr 1087 Model::get_client(Window window) 1088 { 1089 return Util::retrieve(m_client_map, window).value_or(nullptr); 1090 } 1091 1092 Client_ptr 1093 Model::get_const_client(Window window) const 1094 { 1095 return Util::const_retrieve(m_client_map, window).value_or(nullptr); 1096 } 1097 1098 1099 Client_ptr 1100 Model::search_client(SearchSelector const& selector) 1101 { 1102 static constexpr struct LastFocusedComparer final { 1103 bool 1104 operator()(const Client_ptr lhs, const Client_ptr rhs) const 1105 { 1106 return lhs->last_focused < rhs->last_focused; 1107 } 1108 } last_focused_comparer{}; 1109 1110 static std::set<Client_ptr, LastFocusedComparer> clients{{}, last_focused_comparer}; 1111 clients.clear(); 1112 1113 switch (selector.criterium()) { 1114 case SearchSelector::SelectionCriterium::OnWorkspaceBySelector: 1115 { 1116 auto const& [index,selector_] = selector.workspace_selector(); 1117 1118 if (index <= m_workspaces.size()) { 1119 Workspace_ptr workspace = m_workspaces[index]; 1120 std::optional<Client_ptr> client = workspace->find_client(selector_); 1121 1122 if (client && (*client)->managed) 1123 clients.insert(*client); 1124 } 1125 1126 break; 1127 } 1128 default: 1129 { 1130 for (auto const&[_,client] : m_client_map) 1131 if (client->managed && client_matches_search(client, selector)) 1132 clients.insert(client); 1133 1134 break; 1135 } 1136 } 1137 1138 return clients.empty() 1139 ? nullptr 1140 : *clients.rbegin(); 1141 } 1142 1143 bool 1144 Model::client_matches_search(Client_ptr client, SearchSelector const& selector) const 1145 { 1146 switch (selector.criterium()) { 1147 case SearchSelector::SelectionCriterium::OnWorkspaceBySelector: 1148 { 1149 auto const& [index,selector_] = selector.workspace_selector(); 1150 1151 if (index <= m_workspaces.size()) { 1152 Workspace_ptr workspace = m_workspaces[index]; 1153 std::optional<Client_ptr> client_ = workspace->find_client(selector_); 1154 1155 return client_ && client_ == client; 1156 } 1157 1158 return false; 1159 } 1160 case SearchSelector::SelectionCriterium::ByNameEquals: 1161 { 1162 return client->name == selector.string_value(); 1163 } 1164 case SearchSelector::SelectionCriterium::ByClassEquals: 1165 { 1166 return client->class_ == selector.string_value(); 1167 } 1168 case SearchSelector::SelectionCriterium::ByInstanceEquals: 1169 { 1170 return client->instance == selector.string_value(); 1171 } 1172 case SearchSelector::SelectionCriterium::ByNameContains: 1173 { 1174 return client->name.find(selector.string_value()) != std::string::npos; 1175 } 1176 case SearchSelector::SelectionCriterium::ByClassContains: 1177 { 1178 return client->class_.find(selector.string_value()) != std::string::npos; 1179 } 1180 case SearchSelector::SelectionCriterium::ByInstanceContains: 1181 { 1182 return client->instance.find(selector.string_value()) != std::string::npos; 1183 } 1184 case SearchSelector::SelectionCriterium::ForCondition: 1185 { 1186 return selector.filter()(client); 1187 } 1188 default: return false; 1189 } 1190 1191 return false; 1192 } 1193 1194 1195 Partition_ptr 1196 Model::active_partition() const 1197 { 1198 return mp_partition; 1199 } 1200 1201 Partition_ptr 1202 Model::get_partition(Index index) const 1203 { 1204 if (index < m_partitions.size()) 1205 return m_partitions[index]; 1206 1207 return nullptr; 1208 } 1209 1210 1211 Context_ptr 1212 Model::active_context() const 1213 { 1214 return mp_context; 1215 } 1216 1217 Context_ptr 1218 Model::get_context(Index index) const 1219 { 1220 if (index < m_contexts.size()) 1221 return m_contexts[index]; 1222 1223 return nullptr; 1224 } 1225 1226 1227 Workspace_ptr 1228 Model::active_workspace() const 1229 { 1230 return mp_workspace; 1231 } 1232 1233 Workspace_ptr 1234 Model::get_workspace(Index index) const 1235 { 1236 if (index < m_workspaces.size()) 1237 return m_workspaces[index]; 1238 1239 return nullptr; 1240 } 1241 1242 1243 bool 1244 Model::is_free(Client_ptr client) const 1245 { 1246 return Client::is_free(client) 1247 || ((!client->fullscreen || client->contained) 1248 && (client->sticky 1249 ? mp_workspace 1250 : client->workspace 1251 )->layout_is_free()); 1252 } 1253 1254 void 1255 Model::place_client(Placement& placement) 1256 { 1257 Client_ptr client = placement.client; 1258 1259 if (!placement.region) { 1260 switch (placement.method) { 1261 case Placement::PlacementMethod::Free: 1262 { 1263 client->set_free_decoration(placement.decoration); 1264 break; 1265 } 1266 case Placement::PlacementMethod::Tile: 1267 { 1268 client->free_decoration = Decoration::FREE_DECORATION; 1269 client->set_tile_decoration(placement.decoration); 1270 break; 1271 } 1272 } 1273 1274 unmap_client(client); 1275 return; 1276 } 1277 1278 switch (placement.method) { 1279 case Placement::PlacementMethod::Free: 1280 { 1281 client->set_free_decoration(placement.decoration); 1282 client->set_free_region(*placement.region); 1283 break; 1284 } 1285 case Placement::PlacementMethod::Tile: 1286 { 1287 client->free_decoration = Decoration::FREE_DECORATION; 1288 client->set_tile_decoration(placement.decoration); 1289 client->set_tile_region(*placement.region); 1290 break; 1291 } 1292 } 1293 1294 map_client(client); 1295 m_conn.place_window(client->window, client->inner_region); 1296 m_conn.place_window(client->frame, client->active_region); 1297 1298 render_decoration(client); 1299 m_conn.update_window_offset(client->window, client->frame); 1300 } 1301 1302 void 1303 Model::map_client(Client_ptr client) 1304 { 1305 if (!client->mapped) { 1306 client->mapped = true; 1307 m_conn.map_window(client->window); 1308 m_conn.map_window(client->frame); 1309 render_decoration(client); 1310 } 1311 } 1312 1313 void 1314 Model::unmap_client(Client_ptr client) 1315 { 1316 if (client->mapped) { 1317 client->mapped = false; 1318 client->expect_unmap(); 1319 m_conn.unmap_window(client->frame); 1320 } 1321 } 1322 1323 void 1324 Model::focus_client(Client_ptr client) 1325 { 1326 if (!client->sticky) { 1327 activate_workspace(client->workspace); 1328 mp_workspace->activate_client(client); 1329 } 1330 1331 if (client->iconified) 1332 set_iconify_client(Toggle::Off, client); 1333 1334 unfocus_client(mp_focus); 1335 m_conn.ungrab_buttons(client->frame); 1336 1337 client->focus(); 1338 client->urgent = false; 1339 client->attaching = false; 1340 1341 mp_focus = client; 1342 1343 if (mp_workspace->layout_is_persistent() || mp_workspace->layout_is_single()) 1344 apply_layout(mp_workspace); 1345 1346 if (m_conn.get_focused_window() != client->window) 1347 m_conn.focus_window(client->window); 1348 1349 render_decoration(client); 1350 apply_stack(mp_workspace); 1351 } 1352 1353 void 1354 Model::unfocus_client(Client_ptr client) 1355 { 1356 if (!client) 1357 return; 1358 1359 client->unfocus(); 1360 m_conn.regrab_buttons(client->frame); 1361 render_decoration(client); 1362 } 1363 1364 void 1365 Model::sync_focus() 1366 { 1367 Client_ptr active = mp_workspace->active(); 1368 1369 if (active && active != mp_focus) 1370 focus_client(active); 1371 else if (mp_workspace->empty()) { 1372 m_conn.unfocus(); 1373 mp_focus = nullptr; 1374 } 1375 } 1376 1377 1378 void 1379 Model::attach_next_client() 1380 { 1381 static std::atomic<unsigned> count{ 0 }; 1382 static auto attachment_resetter = [&,this]() mutable { 1383 unsigned current = ++count; 1384 1385 std::this_thread::sleep_for(std::chrono::seconds(30)); 1386 1387 if (current == count) 1388 mp_attachment.exchange(nullptr); 1389 }; 1390 1391 mp_attachment.exchange(mp_workspace); 1392 std::thread(attachment_resetter).detach(); 1393 } 1394 1395 1396 void 1397 Model::toggle_partition() 1398 { 1399 if (mp_prev_partition) 1400 activate_partition(mp_prev_partition); 1401 } 1402 1403 void 1404 Model::activate_next_partition(winsys::Direction direction) 1405 { 1406 activate_partition(m_partitions.next_index(direction)); 1407 } 1408 1409 void 1410 Model::activate_partition(Util::Change<Index> index) 1411 { 1412 if (index >= m_partitions.size()) 1413 return; 1414 1415 activate_partition(get_partition(index)); 1416 } 1417 1418 1419 void 1420 Model::activate_partition(Partition_ptr next_partition) 1421 { 1422 if (next_partition == mp_partition) 1423 return; 1424 1425 stop_moving(); 1426 stop_resizing(); 1427 1428 m_partitions.activate_element(next_partition); 1429 mp_partition = next_partition; 1430 1431 activate_context(next_partition->context()); 1432 } 1433 1434 1435 void 1436 Model::toggle_context() 1437 { 1438 if (mp_prev_context) 1439 activate_context(mp_prev_context); 1440 } 1441 1442 void 1443 Model::activate_next_context(winsys::Direction direction) 1444 { 1445 activate_context(m_contexts.next_index(direction)); 1446 } 1447 1448 void 1449 Model::activate_context(Util::Change<Index> index) 1450 { 1451 if (index >= m_contexts.size()) 1452 return; 1453 1454 activate_context(get_context(index)); 1455 } 1456 1457 void 1458 Model::activate_context(Context_ptr next_context) 1459 { 1460 if (next_context == mp_context) 1461 return; 1462 1463 stop_moving(); 1464 stop_resizing(); 1465 1466 Context_ptr prev_context = mp_context; 1467 mp_prev_context = prev_context; 1468 1469 Partition_ptr next_partition = next_context->partition(); 1470 Partition_ptr prev_partition = prev_context->partition(); 1471 1472 m_contexts.activate_element(next_context); 1473 mp_context = next_context; 1474 1475 activate_workspace(next_context->workspace()); 1476 } 1477 1478 1479 void 1480 Model::toggle_workspace() 1481 { 1482 if (mp_prev_workspace) 1483 activate_workspace(mp_prev_workspace); 1484 } 1485 1486 void 1487 Model::toggle_workspace_current_context() 1488 { 1489 Workspace_ptr prev_workspace = mp_context->prev_workspace(); 1490 1491 if (prev_workspace) 1492 activate_workspace(prev_workspace); 1493 } 1494 1495 void 1496 Model::activate_next_workspace(Direction direction) 1497 { 1498 activate_workspace(m_workspaces.next_index(direction)); 1499 } 1500 1501 void 1502 Model::activate_next_workspace_current_context(Direction direction) 1503 { 1504 activate_workspace(*mp_context->workspaces().next_element(direction)); 1505 } 1506 1507 void 1508 Model::activate_workspace(Util::Change<Index> index) 1509 { 1510 if (index >= m_workspaces.size()) 1511 return; 1512 1513 activate_workspace(get_workspace(index)); 1514 } 1515 1516 void 1517 Model::activate_workspace_current_context(Util::Change<Index> index) 1518 { 1519 if (index >= mp_context->size()) 1520 return; 1521 1522 activate_workspace((*mp_context)[index]); 1523 } 1524 1525 void 1526 Model::activate_workspace(Workspace_ptr next_workspace) 1527 { 1528 if (next_workspace == mp_workspace) 1529 return; 1530 1531 stop_moving(); 1532 stop_resizing(); 1533 1534 Workspace_ptr prev_workspace = mp_workspace; 1535 mp_prev_workspace = prev_workspace; 1536 1537 Context_ptr next_context = next_workspace->context(); 1538 Context_ptr prev_context = prev_workspace->context(); 1539 1540 m_conn.set_current_desktop(next_workspace->index()); 1541 1542 for (Client_ptr client : *next_workspace) 1543 map_client(client); 1544 1545 if (next_context == prev_context) { 1546 for (Client_ptr client : *mp_workspace) 1547 if (!client->sticky) 1548 unmap_client(client); 1549 else 1550 m_conn.set_window_notify_enter(client->frame, 1551 next_workspace->focus_follows_mouse()); 1552 1553 1554 for (Client_ptr client : m_sticky_clients) 1555 client->workspace = next_workspace; 1556 } 1557 1558 next_context->activate_workspace(next_workspace); 1559 m_workspaces.activate_element(next_workspace); 1560 mp_workspace = next_workspace; 1561 1562 apply_layout(next_workspace); 1563 apply_stack(next_workspace); 1564 1565 sync_focus(); 1566 } 1567 1568 void 1569 Model::render_decoration(Client_ptr client) 1570 { 1571 Decoration& decoration = client->active_decoration; 1572 1573 std::optional<std::size_t> border_width = std::nullopt; 1574 std::optional<Color> border_color = std::nullopt; 1575 std::optional<Color> frame_color = std::nullopt; 1576 1577 switch (client->get_outside_state()) { 1578 case Client::OutsideState::Focused: 1579 { 1580 if (decoration.border) { 1581 border_width = decoration.border->width; 1582 border_color = decoration.border->colors.focused; 1583 } 1584 1585 if (decoration.frame) 1586 frame_color = decoration.frame->colors.focused; 1587 1588 break; 1589 } 1590 case Client::OutsideState::FocusedDisowned: 1591 { 1592 if (decoration.border) { 1593 border_width = decoration.border->width; 1594 border_color = decoration.border->colors.fdisowned; 1595 } 1596 1597 if (decoration.frame) 1598 frame_color = decoration.frame->colors.fdisowned; 1599 1600 break; 1601 } 1602 case Client::OutsideState::FocusedSticky: 1603 { 1604 if (decoration.border) { 1605 border_width = decoration.border->width; 1606 border_color = decoration.border->colors.fsticky; 1607 } 1608 1609 if (decoration.frame) 1610 frame_color = decoration.frame->colors.fsticky; 1611 1612 break; 1613 } 1614 case Client::OutsideState::Unfocused: 1615 { 1616 if (decoration.border) { 1617 border_width = decoration.border->width; 1618 border_color = decoration.border->colors.unfocused; 1619 } 1620 1621 if (decoration.frame) 1622 frame_color = decoration.frame->colors.unfocused; 1623 1624 break; 1625 } 1626 case Client::OutsideState::UnfocusedDisowned: 1627 { 1628 if (decoration.border) { 1629 border_width = decoration.border->width; 1630 border_color = decoration.border->colors.udisowned; 1631 } 1632 1633 if (decoration.frame) 1634 frame_color = decoration.frame->colors.udisowned; 1635 1636 break; 1637 } 1638 case Client::OutsideState::UnfocusedSticky: 1639 { 1640 if (decoration.border) { 1641 border_width = decoration.border->width; 1642 border_color = decoration.border->colors.usticky; 1643 } 1644 1645 if (decoration.frame) 1646 frame_color = decoration.frame->colors.usticky; 1647 1648 break; 1649 } 1650 case Client::OutsideState::Urgent: 1651 { 1652 if (decoration.border) { 1653 border_width = decoration.border->width; 1654 border_color = decoration.border->colors.urgent; 1655 } 1656 1657 if (decoration.frame) 1658 frame_color = decoration.frame->colors.urgent; 1659 1660 break; 1661 } 1662 } 1663 1664 if (border_width) { 1665 m_conn.set_window_border_width(client->frame, *border_width); 1666 m_conn.set_window_border_color(client->frame, *border_color); 1667 } 1668 1669 if (frame_color) 1670 m_conn.set_window_background_color(client->frame, *frame_color); 1671 } 1672 1673 1674 void 1675 Model::manage(const Window window, const bool ignore, const bool may_map) 1676 { 1677 static std::unordered_map<std::string, Rules> default_rules_memoized{}; 1678 1679 std::optional<Region> window_geometry = m_conn.get_window_geometry(window); 1680 1681 if (ignore || !window_geometry) { 1682 if (may_map && m_conn.window_is_mappable(window)) 1683 m_conn.map_window(window); 1684 1685 m_conn.init_unmanaged(window); 1686 m_unmanaged_windows.push_back(window); 1687 1688 return; 1689 } 1690 1691 std::optional<Pid> pid = m_conn.get_window_pid(window); 1692 std::optional<Pid> ppid = m_conn.get_ppid(pid); 1693 1694 while (ppid && m_pid_map.count(*ppid) == 0) 1695 ppid = m_conn.get_ppid(ppid); 1696 1697 Client_ptr producer = nullptr; 1698 1699 if (ppid) { 1700 std::optional<Client_ptr> ppid_client = Util::retrieve(m_pid_map, *ppid); 1701 1702 if (ppid_client) 1703 producer = *ppid_client; 1704 } 1705 1706 std::string name = m_conn.get_icccm_window_name(window); 1707 std::string class_ = m_conn.get_icccm_window_class(window); 1708 std::string instance = m_conn.get_icccm_window_instance(window); 1709 1710 std::string client_handle = name 1711 + ":" + class_ 1712 + ":" + instance; 1713 1714 std::unordered_set<WindowType> types = m_conn.get_window_types(window); 1715 std::unordered_set<WindowState> states = m_conn.get_window_states(window); 1716 1717 Region geometry = *window_geometry; 1718 1719 Window frame = m_conn.create_frame(geometry); 1720 1721 bool center = false; 1722 bool floating = m_conn.must_free_window(window); 1723 bool fullscreen = m_conn.window_is_fullscreen(window); 1724 bool sticky = m_conn.window_is_sticky(window); 1725 1726 Index partition = mp_partition->index(); 1727 Index context = mp_context->index(); 1728 Index workspace = mp_workspace->index(); 1729 1730 std::optional<Index> desktop = m_conn.get_window_desktop(window); 1731 1732 if (desktop) { 1733 context = *desktop / m_workspaces.size(); 1734 workspace = *desktop % m_workspaces.size(); 1735 } 1736 1737 std::optional<Hints> hints = m_conn.get_icccm_window_hints(window); 1738 std::optional<SizeHints> size_hints 1739 = m_conn.get_icccm_window_size_hints(window, std::nullopt); 1740 1741 if (size_hints) { 1742 size_hints->apply(geometry.dim); 1743 center = !size_hints->by_user; 1744 } else { 1745 geometry.apply_minimum_dim(Client::MIN_CLIENT_DIM); 1746 center = true; 1747 } 1748 1749 center &= Pos::is_at_origin(geometry.pos); 1750 1751 Extents extents = Decoration::FREE_DECORATION.extents(); 1752 geometry.apply_extents(extents); 1753 1754 std::optional<Window> parent = m_conn.get_icccm_window_transient_for(window); 1755 std::optional<Window> leader = m_conn.get_icccm_window_client_leader(window); 1756 1757 Client_ptr client = new Client( 1758 window, 1759 frame, 1760 name, 1761 class_, 1762 instance, 1763 get_partition(partition), 1764 get_context(context), 1765 get_workspace(workspace), 1766 pid, 1767 ppid 1768 ); 1769 1770 if (parent) { 1771 Client_ptr parent_client = get_client(*parent); 1772 1773 if (parent_client) { 1774 client->parent = parent_client; 1775 parent_client->children.push_back(client); 1776 floating = true; 1777 } 1778 1779 m_stack.add_above_other(frame, 1780 parent_client ? parent_client->frame : *parent); 1781 } 1782 1783 if (leader) { 1784 if (m_leader_map.count(*leader) > 0) 1785 m_leader_map[*leader].push_back(client); 1786 else 1787 m_leader_map[*leader] = { client }; 1788 1789 client->leader = leader; 1790 } 1791 1792 std::optional<Rules> default_rules 1793 = Util::retrieve(default_rules_memoized, client_handle); 1794 1795 if (!default_rules) 1796 for (auto& [selector,default_rules_] : m_config.default_rules) 1797 if (client_matches_search(client, *selector)) { 1798 default_rules_memoized[client_handle] = default_rules_; 1799 default_rules = default_rules_; 1800 } 1801 1802 Rules rules = default_rules 1803 ? Rules::merge_rules(*default_rules, Rules::parse_rules(client->instance)) 1804 : Rules::parse_rules(client->instance); 1805 1806 if (center || (rules.do_center && *rules.do_center)) { 1807 const Region screen_region = active_screen().placeable_region(); 1808 1809 geometry.pos.x = screen_region.pos.x 1810 + (screen_region.dim.w - geometry.dim.w) / 2; 1811 1812 geometry.pos.y = screen_region.pos.y 1813 + (screen_region.dim.h - geometry.dim.h) / 2; 1814 } 1815 1816 client->set_free_region(geometry); 1817 client->floating = floating; 1818 client->urgent = hints ? hints->urgent : false; 1819 client->size_hints = size_hints; 1820 1821 if (rules.do_float) 1822 client->floating = *rules.do_float; 1823 1824 if (rules.do_fullscreen) 1825 fullscreen = *rules.do_fullscreen; 1826 1827 if (rules.to_partition && *rules.to_partition < m_partitions.size()) 1828 client->partition = get_partition(*rules.to_partition); 1829 1830 if (rules.to_context && *rules.to_context < m_contexts.size()) 1831 client->context = get_context(*rules.to_context); 1832 1833 if (mp_attachment) { 1834 client->workspace = mp_attachment.exchange(nullptr); 1835 client->attaching = true; 1836 } else if (rules.to_workspace && *rules.to_workspace < m_workspaces.size()) 1837 client->workspace = get_workspace(*rules.to_workspace); 1838 1839 if (leader) { 1840 std::vector<Client_ptr>& members = m_leader_map.at(*leader); 1841 Workspace_ptr group_attachment = nullptr; 1842 1843 if (std::any_of( 1844 members.begin(), 1845 members.end(), 1846 [&group_attachment](Client_ptr member) mutable -> bool { 1847 if (member->attaching) { 1848 group_attachment = member->workspace; 1849 return true; 1850 } 1851 1852 return false; 1853 }) && group_attachment 1854 ) { 1855 client->workspace = group_attachment; 1856 } 1857 } 1858 1859 if (parent) { 1860 Client_ptr parent_client = get_client(*parent); 1861 1862 if (parent_client && parent_client->attaching) { 1863 client->workspace = parent_client->workspace; 1864 client->attaching = true; 1865 } 1866 } 1867 1868 if (pid) 1869 m_pid_map[*pid] = client; 1870 1871 m_conn.place_window(frame, client->free_region); 1872 m_conn.unmap_window(window); 1873 m_conn.unmap_window(frame); 1874 m_conn.reparent_window(window, frame, Pos { extents.left, extents.top }); 1875 1876 m_client_map[window] = client; 1877 m_client_map[frame] = client; 1878 1879 m_conn.insert_window_in_save_set(window); 1880 m_conn.init_window(window); 1881 m_conn.init_frame(frame, client->workspace->focus_follows_mouse()); 1882 m_conn.set_window_border_width(window, 0); 1883 m_conn.set_window_desktop(window, client->workspace->index()); 1884 m_conn.set_icccm_window_state(window, IcccmWindowState::Normal); 1885 1886 if (client->size_hints) 1887 client->size_hints->apply(client->free_region.dim); 1888 1889 client->free_region.apply_minimum_dim(Client::MIN_CLIENT_DIM); 1890 client->workspace->add_client(client); 1891 1892 if (rules.snap_edges) 1893 for(Edge edge : *rules.snap_edges) 1894 snap_client(edge, client); 1895 1896 if (client->workspace == mp_workspace) { 1897 apply_layout(mp_workspace); 1898 1899 if (!rules.do_focus) 1900 focus_client(client); 1901 } 1902 1903 if (Util::contains(states, WindowState::DemandsAttention)) 1904 handle_state_request({ 1905 window, 1906 WindowState::DemandsAttention, 1907 Toggle::On, 1908 false 1909 }); 1910 1911 if (sticky) 1912 set_sticky_client(Toggle::On, client); 1913 1914 if (fullscreen) 1915 set_fullscreen_client(Toggle::On, client); 1916 1917 if (producer && producer->producing) 1918 consume_client(producer, client); 1919 1920 if (rules.do_focus) { 1921 if (*rules.do_focus) { 1922 stop_moving(); 1923 stop_resizing(); 1924 focus_client(client); 1925 } else if (mp_focus) { 1926 Client_ptr prev_focus = mp_focus; 1927 focus_client(client); 1928 focus_client(prev_focus); 1929 } 1930 } 1931 } 1932 1933 void 1934 Model::unmanage(Client_ptr client) 1935 { 1936 if (client->consume_unmap_if_expecting()) 1937 return; 1938 1939 for (Client_ptr consumer : client->consumers) 1940 check_unconsume_client(consumer, false); 1941 1942 check_unconsume_client(client); 1943 1944 set_sticky_client(winsys::Toggle::Off, client); 1945 1946 Workspace_ptr workspace = client->workspace; 1947 1948 m_conn.unparent_window(client->window, client->active_region.pos); 1949 1950 m_conn.cleanup_window(client->window); 1951 m_conn.destroy_window(client->frame); 1952 1953 workspace->remove_client(client); 1954 workspace->remove_icon(client); 1955 workspace->remove_disowned(client); 1956 1957 if (client->pid) 1958 m_pid_map.erase(*client->pid); 1959 1960 if (client->producer) 1961 Util::erase_remove(client->producer->consumers, client); 1962 1963 if (client->parent) 1964 Util::erase_remove(client->parent->children, client); 1965 1966 m_client_map.erase(client->window); 1967 m_client_map.erase(client->frame); 1968 1969 m_fullscreen_map.erase(client); 1970 1971 if (client->leader) { 1972 std::vector<Client_ptr>& members = m_leader_map.at(*client->leader); 1973 Util::erase_remove(members, client); 1974 1975 if (members.empty()) 1976 m_leader_map.erase(*client->leader); 1977 } 1978 1979 Util::erase_remove(m_sticky_clients, client); 1980 Util::erase_remove(m_order, client->frame); 1981 1982 m_stack.remove_window(client->frame); 1983 1984 if (client == mp_jumped_from) 1985 mp_jumped_from = nullptr; 1986 1987 sync_focus(); 1988 apply_layout(workspace); 1989 } 1990 1991 void 1992 Model::start_moving(Client_ptr client) 1993 { 1994 if (m_move_buffer.is_occupied() || m_resize_buffer.is_occupied()) 1995 return; 1996 1997 m_move_buffer.set( 1998 client, 1999 Grip::Top | Grip::Left, 2000 m_conn.get_pointer_position(), 2001 client->free_region 2002 ); 2003 2004 m_conn.init_move(client->frame); 2005 } 2006 2007 void 2008 Model::start_resizing(Client_ptr client) 2009 { 2010 if (m_move_buffer.is_occupied() || m_resize_buffer.is_occupied()) 2011 return; 2012 2013 Region region = client->free_region; 2014 Pos pos = m_conn.get_pointer_position(); 2015 Pos center = Pos { 2016 region.pos.x + static_cast<int>(static_cast<float>(region.dim.w) / 2.f), 2017 region.pos.y + static_cast<int>(static_cast<double>(region.dim.h) / 2.f) 2018 }; 2019 2020 Grip grip = static_cast<Grip>(0); 2021 2022 if (pos.x >= center.x) 2023 grip |= Grip::Right; 2024 else 2025 grip |= Grip::Left; 2026 2027 if (pos.y >= center.y) 2028 grip |= Grip::Bottom; 2029 else 2030 grip |= Grip::Top; 2031 2032 m_resize_buffer.set( 2033 client, 2034 grip, 2035 pos, 2036 region 2037 ); 2038 2039 m_conn.init_resize(client->frame); 2040 } 2041 2042 void 2043 Model::stop_moving() 2044 { 2045 if (m_move_buffer.is_occupied()) { 2046 m_conn.release_pointer(); 2047 m_move_buffer.unset(); 2048 } 2049 } 2050 2051 void 2052 Model::stop_resizing() 2053 { 2054 if (m_resize_buffer.is_occupied()) { 2055 m_conn.release_pointer(); 2056 m_resize_buffer.unset(); 2057 } 2058 } 2059 2060 2061 void 2062 Model::perform_move(Pos& pos) 2063 { 2064 if (!m_move_buffer.is_occupied()) 2065 return; 2066 2067 Client_ptr client = m_move_buffer.client(); 2068 2069 if (!client || !is_free(client)) { 2070 stop_moving(); 2071 return; 2072 } 2073 2074 Region client_region = *m_move_buffer.client_region(); 2075 Pos grip_pos = *m_move_buffer.grip_pos(); 2076 2077 Region region = Region { 2078 Pos { 2079 client_region.pos.x + pos.x - grip_pos.x, 2080 client_region.pos.y + pos.y - grip_pos.y, 2081 }, 2082 client->free_region.dim, 2083 }; 2084 2085 client->set_free_region(region); 2086 2087 Placement placement = Placement { 2088 Placement::PlacementMethod::Free, 2089 client, 2090 client->free_decoration, 2091 region 2092 }; 2093 2094 place_client(placement); 2095 } 2096 2097 void 2098 Model::perform_resize(Pos& pos) 2099 { 2100 if (!m_resize_buffer.is_occupied()) 2101 return; 2102 2103 Client_ptr client = m_resize_buffer.client(); 2104 2105 if (!client || !is_free(client)) { 2106 stop_resizing(); 2107 return; 2108 } 2109 2110 Region client_region = *m_resize_buffer.client_region(); 2111 Pos grip_pos = *m_resize_buffer.grip_pos(); 2112 Grip grip = *m_resize_buffer.grip(); 2113 2114 Region region = client->free_region; 2115 2116 Decoration decoration = client->free_decoration; 2117 Extents extents = Extents { 0, 0, 0, 0 }; 2118 2119 if (decoration.border) { 2120 extents.left += decoration.border->width; 2121 extents.top += decoration.border->width; 2122 extents.right += decoration.border->width; 2123 extents.bottom += decoration.border->width; 2124 } 2125 2126 if (decoration.frame) { 2127 extents.left += decoration.frame->extents.left; 2128 extents.top += decoration.frame->extents.top; 2129 extents.right += decoration.frame->extents.right; 2130 extents.bottom += decoration.frame->extents.bottom; 2131 } 2132 2133 region.remove_extents(extents); 2134 2135 int dx = pos.x - grip_pos.x; 2136 int dy = pos.y - grip_pos.y; 2137 2138 int dest_w; 2139 int dest_h; 2140 2141 if ((grip & Grip::Left) != 0) 2142 dest_w = client_region.dim.w - dx; 2143 else 2144 dest_w = client_region.dim.w + dx; 2145 2146 if ((grip & Grip::Top) != 0) 2147 dest_h = client_region.dim.h - dy; 2148 else 2149 dest_h = client_region.dim.h + dy; 2150 2151 region.dim.w = std::max(0, dest_w); 2152 region.dim.h = std::max(0, dest_h); 2153 2154 if (client->size_hints) 2155 client->size_hints->apply(region.dim); 2156 2157 region.apply_extents(extents); 2158 2159 if ((grip & Grip::Top) != 0) 2160 region.pos.y 2161 = client_region.pos.y + (client_region.dim.h - region.dim.h); 2162 2163 if ((grip & Grip::Left) != 0) 2164 region.pos.x 2165 = client_region.pos.x + (client_region.dim.w - region.dim.w); 2166 2167 if (region == client->previous_region) 2168 return; 2169 2170 Placement placement = Placement { 2171 Placement::PlacementMethod::Free, 2172 client, 2173 client->free_decoration, 2174 region 2175 }; 2176 2177 place_client(placement); 2178 } 2179 2180 2181 void 2182 Model::set_focus_follows_mouse(Toggle toggle, Index index) 2183 { 2184 if (index < m_workspaces.size()) 2185 set_focus_follows_mouse(toggle, m_workspaces[index]); 2186 } 2187 2188 void 2189 Model::set_focus_follows_mouse(Toggle toggle, Workspace_ptr workspace) 2190 { 2191 bool focus_follows_mouse; 2192 2193 switch (toggle) { 2194 case Toggle::On: focus_follows_mouse = true; break; 2195 case Toggle::Off: focus_follows_mouse = false; break; 2196 case Toggle::Reverse: focus_follows_mouse = !workspace->focus_follows_mouse(); break; 2197 default: return; 2198 } 2199 2200 if (workspace->focus_follows_mouse() != focus_follows_mouse) { 2201 workspace->set_focus_follows_mouse(focus_follows_mouse); 2202 2203 for (Client_ptr client : *mp_workspace) 2204 if (!client->sticky) 2205 m_conn.set_window_notify_enter(client->frame, focus_follows_mouse); 2206 2207 for (Client_ptr client : m_sticky_clients) 2208 m_conn.set_window_notify_enter(client->frame, focus_follows_mouse); 2209 } 2210 } 2211 2212 2213 void 2214 Model::apply_layout(Index index) 2215 { 2216 if (index < m_workspaces.size()) 2217 apply_layout(m_workspaces[index]); 2218 } 2219 2220 void 2221 Model::apply_layout(Workspace_ptr workspace) 2222 { 2223 if (workspace != mp_workspace) 2224 return; 2225 2226 for (Placement placement : workspace->arrange(active_screen().placeable_region())) 2227 place_client(placement); 2228 } 2229 2230 2231 void 2232 Model::apply_stack(Index index) 2233 { 2234 if (index < m_workspaces.size()) 2235 apply_stack(m_workspaces[index]); 2236 } 2237 2238 void 2239 Model::apply_stack(Workspace_ptr workspace) 2240 { 2241 static std::vector<Window> stack; 2242 2243 static constexpr struct LastTouchedComparer final { 2244 bool 2245 operator()(const Client_ptr lhs, const Client_ptr rhs) const 2246 { 2247 return lhs->last_touched < rhs->last_touched; 2248 } 2249 } last_touched_comparer{}; 2250 2251 static std::set<Client_ptr, LastTouchedComparer> last_touched_clients{{}, last_touched_comparer}; 2252 2253 if (workspace != mp_workspace) 2254 return; 2255 2256 last_touched_clients.clear(); 2257 std::for_each( 2258 workspace->begin(), 2259 workspace->end(), 2260 [](Client_ptr client) { 2261 last_touched_clients.insert(client); 2262 } 2263 ); 2264 2265 std::vector<Client_ptr> clients{last_touched_clients.begin(), last_touched_clients.end()}; 2266 2267 auto fullscreen_iter = std::stable_partition( 2268 clients.begin(), 2269 clients.end(), 2270 [](Client_ptr client) -> bool { 2271 return client->fullscreen && !client->contained; 2272 } 2273 ); 2274 2275 auto free_iter = std::stable_partition( 2276 fullscreen_iter, 2277 clients.end(), 2278 [=,this](Client_ptr client) -> bool { 2279 return is_free(client); 2280 } 2281 ); 2282 2283 stack.reserve(3 * clients.size()); 2284 stack.clear(); 2285 2286 Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Desktop)); 2287 Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Below_)); 2288 Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Dock)); 2289 2290 std::transform( 2291 free_iter, 2292 clients.end(), 2293 std::back_inserter(stack), 2294 [](Client_ptr client) -> Window { 2295 return client->frame; 2296 } 2297 ); 2298 2299 std::transform( 2300 clients.begin(), 2301 fullscreen_iter, 2302 std::back_inserter(stack), 2303 [](Client_ptr client) -> Window { 2304 return client->frame; 2305 } 2306 ); 2307 2308 std::transform( 2309 fullscreen_iter, 2310 free_iter, 2311 std::back_inserter(stack), 2312 [](Client_ptr client) -> Window { 2313 return client->frame; 2314 } 2315 ); 2316 2317 Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Above_)); 2318 Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Notification)); 2319 2320 bool order_changed = false; 2321 std::optional<Window> prev_window 2322 = stack.empty() ? std::nullopt : std::optional(stack[0]); 2323 2324 for (std::size_t i = 1; i < stack.size(); ++i) { 2325 if (!order_changed) { 2326 if (i < m_order.size()) 2327 order_changed |= m_order[i] != stack[i]; 2328 else 2329 order_changed = true; 2330 } 2331 2332 if (order_changed) 2333 m_conn.stack_window_above(stack[i], prev_window); 2334 2335 prev_window = stack[i]; 2336 } 2337 2338 if (!order_changed) 2339 return; 2340 2341 m_order = stack; 2342 2343 static constexpr struct ManagedSinceComparer final { 2344 bool 2345 operator()(const Client_ptr lhs, const Client_ptr rhs) const 2346 { 2347 return lhs->managed_since < rhs->managed_since; 2348 } 2349 } managed_since_comparer{}; 2350 2351 static std::set<Client_ptr, ManagedSinceComparer> managed_since_clients{{}, managed_since_comparer}; 2352 managed_since_clients.clear(); 2353 2354 std::for_each( 2355 m_client_map.begin(), 2356 m_client_map.end(), 2357 [](auto kv) { 2358 managed_since_clients.insert(kv.second); 2359 } 2360 ); 2361 2362 static std::vector<Window> order_list; 2363 order_list.reserve(managed_since_clients.size()); 2364 order_list.clear(); 2365 2366 std::transform( 2367 managed_since_clients.begin(), 2368 managed_since_clients.end(), 2369 std::back_inserter(order_list), 2370 [](Client_ptr client) -> Window { 2371 return client->window; 2372 } 2373 ); 2374 2375 m_conn.update_client_list(order_list); 2376 2377 last_touched_clients.clear(); 2378 std::for_each( 2379 m_client_map.begin(), 2380 m_client_map.end(), 2381 [](auto kv) { 2382 last_touched_clients.insert(kv.second); 2383 } 2384 ); 2385 2386 order_list.reserve(last_touched_clients.size()); 2387 order_list.clear(); 2388 2389 std::transform( 2390 last_touched_clients.begin(), 2391 last_touched_clients.end(), 2392 std::back_inserter(order_list), 2393 [](Client_ptr client) -> Window { 2394 return client->window; 2395 } 2396 ); 2397 2398 m_conn.update_client_list_stacking(order_list); 2399 } 2400 2401 2402 void 2403 Model::cycle_focus(Direction direction) 2404 { 2405 if (mp_workspace->size() <= 1) 2406 return; 2407 2408 mp_workspace->cycle(direction); 2409 focus_client(mp_workspace->active()); 2410 sync_focus(); 2411 } 2412 2413 void 2414 Model::drag_focus(Direction direction) 2415 { 2416 if (mp_workspace->size() <= 1) 2417 return; 2418 2419 mp_workspace->drag(direction); 2420 focus_client(mp_workspace->active()); 2421 sync_focus(); 2422 2423 apply_layout(mp_workspace); 2424 } 2425 2426 2427 void 2428 Model::reverse_clients() 2429 { 2430 if (mp_workspace->size() <= 1) 2431 return; 2432 2433 mp_workspace->reverse(); 2434 focus_client(mp_workspace->active()); 2435 sync_focus(); 2436 2437 apply_layout(mp_workspace); 2438 apply_stack(mp_workspace); 2439 } 2440 2441 void 2442 Model::rotate_clients(Direction direction) 2443 { 2444 if (mp_workspace->size() <= 1) 2445 return; 2446 2447 mp_workspace->rotate(direction); 2448 focus_client(mp_workspace->active()); 2449 sync_focus(); 2450 2451 apply_layout(mp_workspace); 2452 apply_stack(mp_workspace); 2453 } 2454 2455 void 2456 Model::shuffle_main(winsys::Direction direction) 2457 { 2458 std::size_t main_count 2459 = std::min(mp_workspace->main_count(), mp_workspace->size()); 2460 2461 if (main_count <= 1) 2462 return; 2463 2464 Index focus_index = *mp_workspace->clients().index(); 2465 std::optional<Index> last_touched_index = std::nullopt; 2466 2467 if (focus_index >= main_count) { 2468 Client_ptr last_touched = *std::max_element( 2469 mp_workspace->begin(), 2470 mp_workspace->begin() + main_count, 2471 [](Client_ptr client1, Client_ptr client2) -> bool { 2472 return client1->last_touched < client2->last_touched; 2473 } 2474 ); 2475 2476 last_touched_index 2477 = mp_workspace->clients().index_of_element(last_touched); 2478 } 2479 2480 mp_workspace->shuffle_main(direction); 2481 focus_client(mp_workspace->active()); 2482 sync_focus(); 2483 2484 if (last_touched_index) 2485 (*mp_workspace)[*last_touched_index]->touch(); 2486 2487 apply_layout(mp_workspace); 2488 apply_stack(mp_workspace); 2489 } 2490 2491 void 2492 Model::shuffle_stack(winsys::Direction direction) 2493 { 2494 std::size_t main_count 2495 = std::min(mp_workspace->main_count(), mp_workspace->size()); 2496 2497 if ((mp_workspace->size() - main_count) <= 1) 2498 return; 2499 2500 Index focus_index = *mp_workspace->clients().index(); 2501 std::optional<Index> last_touched_index = std::nullopt; 2502 2503 if (focus_index < main_count) { 2504 Client_ptr last_touched = *std::max_element( 2505 mp_workspace->begin() + main_count, 2506 mp_workspace->end(), 2507 [](Client_ptr client1, Client_ptr client2) -> bool { 2508 return client1->last_touched < client2->last_touched; 2509 } 2510 ); 2511 2512 last_touched_index 2513 = mp_workspace->clients().index_of_element(last_touched); 2514 } 2515 2516 mp_workspace->shuffle_stack(direction); 2517 focus_client(mp_workspace->active()); 2518 sync_focus(); 2519 2520 if (last_touched_index) 2521 (*mp_workspace)[*last_touched_index]->touch(); 2522 2523 apply_layout(mp_workspace); 2524 apply_stack(mp_workspace); 2525 } 2526 2527 2528 void 2529 Model::move_focus_to_next_workspace(Direction direction) 2530 { 2531 if (mp_focus) 2532 move_client_to_next_workspace(direction, mp_focus); 2533 } 2534 2535 void 2536 Model::move_client_to_next_workspace(Direction direction, Client_ptr client) 2537 { 2538 Index index = mp_workspace->index(); 2539 2540 switch (direction) { 2541 case Direction::Forward: 2542 { 2543 index = (index + 1) % m_workspaces.size(); 2544 break; 2545 } 2546 case Direction::Backward: 2547 { 2548 index = (index == 0 ? m_workspaces.size() : index) - 1; 2549 break; 2550 } 2551 default: return; 2552 } 2553 2554 move_client_to_workspace(index, client); 2555 } 2556 2557 void 2558 Model::move_focus_to_workspace(Index index) 2559 { 2560 if (mp_focus) 2561 move_client_to_workspace(index, mp_focus); 2562 } 2563 2564 void 2565 Model::move_client_to_workspace(Index index, Client_ptr client) 2566 { 2567 if (index >= m_workspaces.size() 2568 || index == client->workspace->index() 2569 || client->sticky 2570 ) { 2571 return; 2572 } 2573 2574 m_conn.set_window_desktop(client->window, index); 2575 2576 Workspace_ptr from = client->workspace; 2577 Workspace_ptr to = get_workspace(index); 2578 2579 if (from->focus_follows_mouse() != to->focus_follows_mouse()) 2580 m_conn.set_window_notify_enter(client->frame, to->focus_follows_mouse()); 2581 2582 client->workspace = to; 2583 2584 if (from == mp_workspace) 2585 unmap_client(client); 2586 2587 to->add_client(client); 2588 apply_layout(to); 2589 apply_stack(to); 2590 2591 from->remove_client(client); 2592 apply_layout(from); 2593 apply_stack(from); 2594 2595 sync_focus(); 2596 } 2597 2598 2599 void 2600 Model::toggle_layout() 2601 { 2602 mp_workspace->toggle_layout(); 2603 apply_layout(mp_workspace); 2604 apply_stack(mp_workspace); 2605 } 2606 2607 void 2608 Model::set_layout(LayoutHandler::LayoutKind layout) 2609 { 2610 mp_workspace->set_layout(layout); 2611 apply_layout(mp_workspace); 2612 apply_stack(mp_workspace); 2613 } 2614 2615 void 2616 Model::set_layout_retain_region(LayoutHandler::LayoutKind layout) 2617 { 2618 Cycle<Client_ptr> const& clients = mp_workspace->clients(); 2619 std::vector<Region> regions; 2620 2621 bool was_tiled = !mp_workspace->layout_is_free(); 2622 2623 if (was_tiled) { 2624 regions.reserve(clients.size()); 2625 2626 std::transform( 2627 clients.begin(), 2628 clients.end(), 2629 std::back_inserter(regions), 2630 [=,this](Client_ptr client) -> Region { 2631 if (is_free(client)) 2632 return client->free_region; 2633 else 2634 return client->tile_region; 2635 } 2636 ); 2637 } 2638 2639 mp_workspace->set_layout(layout); 2640 2641 if (was_tiled && mp_workspace->layout_is_free()) 2642 for (std::size_t i = 0; i < clients.size(); ++i) 2643 clients[i]->set_free_region(regions[i]); 2644 2645 apply_layout(mp_workspace); 2646 apply_stack(mp_workspace); 2647 } 2648 2649 2650 void 2651 Model::toggle_layout_data() 2652 { 2653 mp_workspace->toggle_layout_data(); 2654 apply_layout(mp_workspace); 2655 } 2656 2657 void 2658 Model::cycle_layout_data(Direction direction) 2659 { 2660 mp_workspace->cycle_layout_data(direction); 2661 apply_layout(mp_workspace); 2662 } 2663 2664 void 2665 Model::copy_data_from_prev_layout() 2666 { 2667 mp_workspace->copy_data_from_prev_layout(); 2668 apply_layout(mp_workspace); 2669 } 2670 2671 2672 void 2673 Model::change_gap_size(Util::Change<int> change) 2674 { 2675 mp_workspace->change_gap_size(change); 2676 apply_layout(mp_workspace); 2677 } 2678 2679 void 2680 Model::change_main_count(Util::Change<int> change) 2681 { 2682 mp_workspace->change_main_count(change); 2683 apply_layout(mp_workspace); 2684 } 2685 2686 void 2687 Model::change_main_factor(Util::Change<float> change) 2688 { 2689 mp_workspace->change_main_factor(change); 2690 apply_layout(mp_workspace); 2691 } 2692 2693 void 2694 Model::change_margin(Util::Change<int> change) 2695 { 2696 mp_workspace->change_margin(change); 2697 apply_layout(mp_workspace); 2698 } 2699 2700 void 2701 Model::change_margin(Edge edge, Util::Change<int> change) 2702 { 2703 mp_workspace->change_margin(edge, change); 2704 apply_layout(mp_workspace); 2705 } 2706 2707 void 2708 Model::reset_gap_size() 2709 { 2710 mp_workspace->reset_gap_size(); 2711 apply_layout(mp_workspace); 2712 } 2713 2714 void 2715 Model::reset_margin() 2716 { 2717 mp_workspace->reset_margin(); 2718 apply_layout(mp_workspace); 2719 } 2720 2721 void 2722 Model::reset_layout_data() 2723 { 2724 mp_workspace->reset_layout_data(); 2725 apply_layout(mp_workspace); 2726 } 2727 2728 2729 void 2730 Model::save_layout(std::size_t number) const 2731 { 2732 mp_workspace->save_layout(number); 2733 } 2734 2735 void 2736 Model::load_layout(std::size_t number) 2737 { 2738 mp_workspace->load_layout(number); 2739 apply_layout(mp_workspace); 2740 apply_stack(mp_workspace); 2741 } 2742 2743 2744 void 2745 Model::kill_focus() 2746 { 2747 if (mp_focus) 2748 kill_client(mp_focus); 2749 } 2750 2751 void 2752 Model::kill_client(Client_ptr client) 2753 { 2754 if (!client->invincible) { 2755 m_conn.kill_window(client->window); 2756 m_conn.flush(); 2757 } 2758 } 2759 2760 2761 void 2762 Model::jump_client(SearchSelector const& selector) 2763 { 2764 Client_ptr client = search_client(selector); 2765 2766 if (client) { 2767 if (client == mp_focus) { 2768 if (mp_jumped_from && client != mp_jumped_from) 2769 client = mp_jumped_from; 2770 } 2771 2772 if (mp_focus) 2773 mp_jumped_from = mp_focus; 2774 2775 focus_client(client); 2776 } 2777 } 2778 2779 2780 void 2781 Model::set_floating_focus(Toggle toggle) 2782 { 2783 if (mp_focus) 2784 set_floating_client(toggle, mp_focus); 2785 } 2786 2787 void 2788 Model::set_floating_client(Toggle toggle, Client_ptr client) 2789 { 2790 switch (toggle) { 2791 case Toggle::On: client->floating = true; break; 2792 case Toggle::Off: client->floating = false; break; 2793 case Toggle::Reverse: client->floating = !client->floating; break; 2794 default: return; 2795 } 2796 2797 apply_layout(client->workspace); 2798 apply_stack(client->workspace); 2799 } 2800 2801 void 2802 Model::set_fullscreen_focus(Toggle toggle) 2803 { 2804 if (mp_focus) 2805 set_fullscreen_client(toggle, mp_focus); 2806 } 2807 2808 void 2809 Model::set_fullscreen_client(Toggle toggle, Client_ptr client) 2810 { 2811 switch (toggle) { 2812 case Toggle::On: 2813 { 2814 if (client->fullscreen) 2815 return; 2816 2817 client->fullscreen = true; 2818 2819 m_conn.set_window_state( 2820 client->window, 2821 WindowState::Fullscreen, 2822 true 2823 ); 2824 2825 Workspace_ptr workspace = client->workspace; 2826 2827 apply_layout(workspace); 2828 apply_stack(workspace); 2829 render_decoration(client); 2830 2831 m_fullscreen_map[client] = client->free_region; 2832 2833 return; 2834 } 2835 case Toggle::Off: 2836 { 2837 if (!client->fullscreen) 2838 return; 2839 2840 if (!client->contained) 2841 client->set_free_region(m_fullscreen_map.at(client)); 2842 2843 client->fullscreen = false; 2844 2845 m_conn.set_window_state( 2846 client->window, 2847 WindowState::Fullscreen, 2848 false 2849 ); 2850 2851 Workspace_ptr workspace = client->workspace; 2852 2853 apply_layout(workspace); 2854 apply_stack(workspace); 2855 render_decoration(client); 2856 2857 m_fullscreen_map.erase(client); 2858 2859 return; 2860 } 2861 case Toggle::Reverse: 2862 { 2863 set_fullscreen_client( 2864 client->fullscreen 2865 ? Toggle::Off 2866 : Toggle::On, 2867 client 2868 ); 2869 2870 return; 2871 } 2872 default: return; 2873 } 2874 } 2875 2876 void 2877 Model::set_sticky_focus(Toggle toggle) 2878 { 2879 if (mp_focus) 2880 set_sticky_client(toggle, mp_focus); 2881 } 2882 2883 void 2884 Model::set_sticky_client(Toggle toggle, Client_ptr client) 2885 { 2886 switch (toggle) { 2887 case Toggle::On: 2888 { 2889 if (client->sticky) 2890 return; 2891 2892 if (client->iconified) 2893 set_iconify_client(Toggle::Off, client); 2894 2895 std::for_each( 2896 m_workspaces.begin(), 2897 m_workspaces.end(), 2898 [client](Workspace_ptr workspace) { 2899 if (workspace != client->workspace) 2900 workspace->add_client(client); 2901 } 2902 ); 2903 2904 m_conn.set_window_desktop(client->window, 0xFFFFFFFF); 2905 m_conn.set_window_state( 2906 client->window, 2907 WindowState::Sticky, 2908 true 2909 ); 2910 2911 Workspace_ptr workspace = client->workspace; 2912 2913 client->stick(); 2914 2915 apply_layout(workspace); 2916 render_decoration(client); 2917 2918 return; 2919 } 2920 case Toggle::Off: 2921 { 2922 if (!client->sticky) 2923 return; 2924 2925 std::for_each( 2926 m_workspaces.begin(), 2927 m_workspaces.end(), 2928 [=,this](Workspace_ptr workspace) { 2929 if (workspace != mp_workspace) { 2930 workspace->remove_client(client); 2931 workspace->remove_icon(client); 2932 workspace->remove_disowned(client); 2933 } else 2934 client->workspace = workspace; 2935 } 2936 ); 2937 2938 m_conn.set_window_desktop(client->window, mp_workspace->index()); 2939 m_conn.set_window_state( 2940 client->window, 2941 WindowState::Sticky, 2942 false 2943 ); 2944 2945 client->unstick(); 2946 2947 apply_layout(mp_workspace); 2948 render_decoration(client); 2949 2950 return; 2951 } 2952 case Toggle::Reverse: 2953 { 2954 set_sticky_client( 2955 client->sticky 2956 ? Toggle::Off 2957 : Toggle::On, 2958 client 2959 ); 2960 2961 return; 2962 } 2963 default: return; 2964 } 2965 } 2966 2967 void 2968 Model::set_contained_focus(Toggle toggle) 2969 { 2970 if (mp_focus) 2971 set_contained_client(toggle, mp_focus); 2972 } 2973 2974 void 2975 Model::set_contained_client(Toggle toggle, Client_ptr client) 2976 { 2977 switch (toggle) { 2978 case Toggle::On: 2979 { 2980 client->contained = true; 2981 2982 Workspace_ptr workspace = client->workspace; 2983 2984 apply_layout(workspace); 2985 apply_stack(workspace); 2986 2987 return; 2988 } 2989 case Toggle::Off: 2990 { 2991 client->contained = false; 2992 2993 Workspace_ptr workspace = client->workspace; 2994 2995 apply_layout(workspace); 2996 apply_stack(workspace); 2997 2998 return; 2999 } 3000 case Toggle::Reverse: 3001 { 3002 set_contained_client( 3003 client->contained 3004 ? Toggle::Off 3005 : Toggle::On, 3006 client 3007 ); 3008 3009 return; 3010 } 3011 default: return; 3012 } 3013 } 3014 3015 void 3016 Model::set_invincible_focus(Toggle toggle) 3017 { 3018 if (mp_focus) 3019 set_invincible_client(toggle, mp_focus); 3020 } 3021 3022 void 3023 Model::set_invincible_client(Toggle toggle, Client_ptr client) 3024 { 3025 if (toggle == Toggle::Reverse) 3026 set_invincible_client( 3027 client->invincible 3028 ? Toggle::Off 3029 : Toggle::On, 3030 client 3031 ); 3032 else 3033 client->invincible 3034 = toggle == Toggle::On ? true : false; 3035 } 3036 3037 void 3038 Model::set_producing_focus(Toggle toggle) 3039 { 3040 if (mp_focus) 3041 set_producing_client(toggle, mp_focus); 3042 } 3043 3044 void 3045 Model::set_producing_client(Toggle toggle, Client_ptr client) 3046 { 3047 if (toggle == Toggle::Reverse) 3048 set_producing_client( 3049 client->producing 3050 ? Toggle::Off 3051 : Toggle::On, 3052 client 3053 ); 3054 else 3055 client->producing 3056 = toggle == Toggle::On ? true : false; 3057 } 3058 3059 void 3060 Model::set_iconifyable_focus(Toggle toggle) 3061 { 3062 if (mp_focus) 3063 set_iconifyable_client(toggle, mp_focus); 3064 } 3065 3066 void 3067 Model::set_iconifyable_client(Toggle toggle, Client_ptr client) 3068 { 3069 if (toggle == Toggle::Reverse) 3070 set_iconifyable_client( 3071 client->iconifyable 3072 ? Toggle::Off 3073 : Toggle::On, 3074 client 3075 ); 3076 else 3077 client->iconifyable 3078 = toggle == Toggle::On ? true : false; 3079 } 3080 3081 void 3082 Model::set_iconify_focus(Toggle toggle) 3083 { 3084 if (mp_focus) 3085 set_iconify_client(toggle, mp_focus); 3086 } 3087 3088 void 3089 Model::set_iconify_client(Toggle toggle, Client_ptr client) 3090 { 3091 switch (toggle) { 3092 case Toggle::On: 3093 { 3094 if (client->iconified || client->sticky) 3095 return; 3096 3097 Workspace_ptr workspace = client->workspace; 3098 workspace->client_to_icon(client); 3099 3100 m_conn.set_icccm_window_state( 3101 client->window, 3102 IcccmWindowState::Iconic 3103 ); 3104 3105 unmap_client(client); 3106 3107 apply_layout(workspace); 3108 sync_focus(); 3109 3110 client->iconified = true; 3111 3112 return; 3113 } 3114 case Toggle::Off: 3115 { 3116 if (!client->iconified) 3117 return; 3118 3119 Workspace_ptr workspace = client->workspace; 3120 workspace->icon_to_client(client); 3121 3122 m_conn.set_icccm_window_state( 3123 client->window, 3124 IcccmWindowState::Normal 3125 ); 3126 3127 client->iconified = false; 3128 3129 apply_layout(workspace); 3130 sync_focus(); 3131 3132 return; 3133 } 3134 case Toggle::Reverse: 3135 { 3136 set_iconify_client( 3137 client->iconified 3138 ? Toggle::Off 3139 : Toggle::On, 3140 client 3141 ); 3142 3143 return; 3144 } 3145 default: return; 3146 } 3147 } 3148 3149 3150 void 3151 Model::consume_client(Client_ptr producer, Client_ptr client) 3152 { 3153 static std::unordered_set<std::string> ignored_producers_memoized{}; 3154 static std::unordered_set<std::string> ignored_consumers_memoized{}; 3155 static std::unordered_set<std::string> allowed_producers_memoized{}; 3156 static std::unordered_set<std::string> allowed_consumers_memoized{}; 3157 3158 Workspace_ptr pworkspace = producer->workspace; 3159 Workspace_ptr cworkspace = client->workspace; 3160 3161 if (client->producer || !cworkspace->contains(client)) 3162 return; 3163 3164 std::string producer_handle = producer->name 3165 + ":" + producer->class_ 3166 + ":" + producer->instance; 3167 3168 std::string consumer_handle = client->name 3169 + ":" + client->class_ 3170 + ":" + client->instance; 3171 3172 if (!Util::contains(allowed_producers_memoized, producer_handle)) { 3173 if (Util::contains(ignored_producers_memoized, producer_handle)) 3174 return; 3175 3176 for (SearchSelector_ptr selector : m_config.ignored_producers) 3177 if (client_matches_search(producer, *selector)) { 3178 ignored_producers_memoized.insert(producer_handle); 3179 return; 3180 } 3181 allowed_producers_memoized.insert(producer_handle); 3182 } 3183 3184 if (!Util::contains(allowed_consumers_memoized, producer_handle)) { 3185 if (Util::contains(ignored_consumers_memoized, consumer_handle)) 3186 return; 3187 3188 for (SearchSelector_ptr selector : m_config.ignored_consumers) 3189 if (client_matches_search(client, *selector)) { 3190 ignored_consumers_memoized.insert(consumer_handle); 3191 return; 3192 } else 3193 allowed_consumers_memoized.insert(consumer_handle); 3194 } 3195 3196 if (m_move_buffer.client() == producer) 3197 stop_moving(); 3198 3199 if (m_resize_buffer.client() == producer) 3200 stop_resizing(); 3201 3202 unmap_client(producer); 3203 3204 if (producer->consumers.size() == 0) { 3205 producer->managed = false; 3206 3207 if (pworkspace == cworkspace) { 3208 cworkspace->remove_client(client); 3209 pworkspace->replace_client(producer, client); 3210 } else 3211 pworkspace->remove_client(producer); 3212 3213 apply_layout(pworkspace); 3214 apply_layout(cworkspace); 3215 } 3216 3217 client->producer = producer; 3218 producer->consumers.push_back(client); 3219 3220 sync_focus(); 3221 apply_stack(pworkspace); 3222 apply_stack(cworkspace); 3223 } 3224 3225 void 3226 Model::check_unconsume_client(Client_ptr client, bool must_replace_consumer) 3227 { 3228 Client_ptr producer = client->producer; 3229 3230 if (!producer || producer->managed) 3231 return; 3232 3233 Workspace_ptr cworkspace = client->workspace; 3234 3235 client->producer = nullptr; 3236 Util::erase_remove(producer->consumers, client); 3237 3238 if (producer->consumers.size() == 0) { 3239 producer->managed = true; 3240 3241 if (cworkspace->contains(client)) { 3242 if (must_replace_consumer) 3243 cworkspace->replace_client(client, producer); 3244 else 3245 cworkspace->remove_client(producer); 3246 3247 producer->workspace = cworkspace; 3248 } else { 3249 if (must_replace_consumer) 3250 mp_workspace->add_client(producer); 3251 3252 producer->workspace = mp_workspace; 3253 } 3254 3255 apply_layout(mp_workspace); 3256 apply_layout(cworkspace); 3257 } 3258 3259 sync_focus(); 3260 apply_stack(mp_workspace); 3261 apply_stack(cworkspace); 3262 } 3263 3264 3265 void 3266 Model::center_focus() 3267 { 3268 if (mp_focus) 3269 center_client(mp_focus); 3270 } 3271 3272 void 3273 Model::center_client(Client_ptr client) 3274 { 3275 if (!is_free(client)) 3276 return; 3277 3278 const Region screen_region = active_screen().placeable_region(); 3279 Region region = client->free_region; 3280 3281 region.pos.x = screen_region.pos.x 3282 + (screen_region.dim.w - region.dim.w) / 2; 3283 3284 region.pos.y = screen_region.pos.y 3285 + (screen_region.dim.h - region.dim.h) / 2; 3286 3287 Placement placement = Placement { 3288 Placement::PlacementMethod::Free, 3289 client, 3290 client->free_decoration, 3291 region 3292 }; 3293 3294 place_client(placement); 3295 } 3296 3297 void 3298 Model::nudge_focus(Edge edge, Util::Change<std::size_t> change) 3299 { 3300 if (mp_focus) 3301 nudge_client(edge, change, mp_focus); 3302 } 3303 3304 void 3305 Model::nudge_client(Edge edge, Util::Change<std::size_t> change, Client_ptr client) 3306 { 3307 if (!is_free(client)) 3308 return; 3309 3310 Region region = client->free_region; 3311 3312 switch (edge) { 3313 case Edge::Left: 3314 { 3315 region.pos.x -= change; 3316 break; 3317 } 3318 case Edge::Top: 3319 { 3320 region.pos.y -= change; 3321 break; 3322 } 3323 case Edge::Right: 3324 { 3325 region.pos.x += change; 3326 break; 3327 } 3328 case Edge::Bottom: 3329 { 3330 region.pos.y += change; 3331 break; 3332 } 3333 default: return; 3334 } 3335 3336 Placement placement = Placement { 3337 Placement::PlacementMethod::Free, 3338 client, 3339 client->free_decoration, 3340 region 3341 }; 3342 3343 place_client(placement); 3344 } 3345 3346 void 3347 Model::stretch_focus(Edge edge, Util::Change<int> change) 3348 { 3349 if (mp_focus) 3350 stretch_client(edge, change, mp_focus); 3351 } 3352 3353 void 3354 Model::stretch_client(Edge edge, Util::Change<int> change, Client_ptr client) 3355 { 3356 if (!is_free(client)) 3357 return; 3358 3359 Decoration decoration = client->free_decoration; 3360 Extents extents = Extents { 0, 0, 0, 0 }; 3361 3362 if (decoration.border) { 3363 extents.left += decoration.border->width; 3364 extents.top += decoration.border->width; 3365 extents.right += decoration.border->width; 3366 extents.bottom += decoration.border->width; 3367 } 3368 3369 if (decoration.frame) { 3370 extents.left += decoration.frame->extents.left; 3371 extents.top += decoration.frame->extents.top; 3372 extents.right += decoration.frame->extents.right; 3373 extents.bottom += decoration.frame->extents.bottom; 3374 } 3375 3376 Region region = client->free_region; 3377 region.remove_extents(extents); 3378 3379 switch (edge) { 3380 case Edge::Left: 3381 { 3382 if (!(change < 0 && -change >= region.dim.h)) { 3383 if (region.dim.w + change <= Client::MIN_CLIENT_DIM.w) { 3384 region.pos.x -= Client::MIN_CLIENT_DIM.w - region.dim.w; 3385 region.dim.w = Client::MIN_CLIENT_DIM.w; 3386 } else { 3387 region.pos.x -= change; 3388 region.dim.w += change; 3389 } 3390 } 3391 3392 break; 3393 } 3394 case Edge::Top: 3395 { 3396 if (!(change < 0 && -change >= region.dim.h)) { 3397 if (region.dim.h + change <= Client::MIN_CLIENT_DIM.h) { 3398 region.pos.y -= Client::MIN_CLIENT_DIM.h - region.dim.h; 3399 region.dim.h = Client::MIN_CLIENT_DIM.h; 3400 } else { 3401 region.pos.y -= change; 3402 region.dim.h += change; 3403 } 3404 } 3405 3406 break; 3407 } 3408 case Edge::Right: 3409 { 3410 if (!(change < 0 && -change >= region.dim.w)) { 3411 if (region.dim.w + change <= Client::MIN_CLIENT_DIM.w) 3412 region.dim.w = Client::MIN_CLIENT_DIM.w; 3413 else 3414 region.dim.w += change; 3415 } 3416 3417 break; 3418 } 3419 case Edge::Bottom: 3420 { 3421 if (!(change < 0 && -change >= region.dim.h)) { 3422 if (region.dim.h + change <= Client::MIN_CLIENT_DIM.h) 3423 region.dim.h = Client::MIN_CLIENT_DIM.h; 3424 else 3425 region.dim.h += change; 3426 } 3427 3428 break; 3429 } 3430 default: return; 3431 } 3432 3433 region.apply_extents(extents); 3434 3435 Placement placement = Placement { 3436 Placement::PlacementMethod::Free, 3437 client, 3438 client->free_decoration, 3439 region 3440 }; 3441 3442 place_client(placement); 3443 } 3444 3445 void 3446 Model::inflate_focus(Util::Change<int> change) 3447 { 3448 if (mp_focus) 3449 inflate_client(change, mp_focus); 3450 } 3451 3452 void 3453 Model::inflate_client(Util::Change<int> change, Client_ptr client) 3454 { 3455 if (!is_free(client)) 3456 return; 3457 3458 Decoration decoration = client->free_decoration; 3459 Extents extents = Extents { 0, 0, 0, 0 }; 3460 3461 if (decoration.border) { 3462 extents.left += decoration.border->width; 3463 extents.top += decoration.border->width; 3464 extents.right += decoration.border->width; 3465 extents.bottom += decoration.border->width; 3466 } 3467 3468 if (decoration.frame) { 3469 extents.left += decoration.frame->extents.left; 3470 extents.top += decoration.frame->extents.top; 3471 extents.right += decoration.frame->extents.right; 3472 extents.bottom += decoration.frame->extents.bottom; 3473 } 3474 3475 Region region = client->free_region; 3476 region.remove_extents(extents); 3477 3478 double ratio = static_cast<double>(region.dim.w) 3479 / static_cast<double>(region.dim.w + region.dim.h); 3480 3481 double width_inc = ratio * change; 3482 double height_inc = change - width_inc; 3483 3484 int dw = std::lround(width_inc); 3485 int dh = std::lround(height_inc); 3486 3487 if ((dw < 0 && -dw >= region.dim.w) 3488 || (dh < 0 && -dh >= region.dim.h) 3489 || (region.dim.w + dw <= Client::MIN_CLIENT_DIM.w) 3490 || (region.dim.h + dh <= Client::MIN_CLIENT_DIM.h)) 3491 { 3492 return; 3493 } 3494 3495 region.dim.w += dw; 3496 region.dim.h += dh; 3497 3498 region.apply_extents(extents); 3499 3500 int dx = region.dim.w - client->free_region.dim.w; 3501 int dy = region.dim.h - client->free_region.dim.h; 3502 3503 dx = std::lround(dx / static_cast<double>(2)); 3504 dy = std::lround(dy / static_cast<double>(2)); 3505 3506 region.pos.x -= dx; 3507 region.pos.y -= dy; 3508 3509 client->set_free_region(region); 3510 3511 Placement placement = Placement { 3512 Placement::PlacementMethod::Free, 3513 client, 3514 client->free_decoration, 3515 region 3516 }; 3517 3518 place_client(placement); 3519 } 3520 3521 void 3522 Model::snap_focus(Edge edge) 3523 { 3524 if (mp_focus) 3525 snap_client(edge, mp_focus); 3526 } 3527 3528 void 3529 Model::snap_client(Edge edge, Client_ptr client) 3530 { 3531 if (!is_free(client)) 3532 return; 3533 3534 const Region screen_region = active_screen().placeable_region(); 3535 Region region = client->free_region; 3536 3537 switch (edge) { 3538 case Edge::Left: 3539 { 3540 region.pos.x = screen_region.pos.x; 3541 3542 break; 3543 } 3544 case Edge::Top: 3545 { 3546 region.pos.y = screen_region.pos.y; 3547 3548 break; 3549 } 3550 case Edge::Right: 3551 { 3552 region.pos.x = std::max( 3553 0, 3554 (screen_region.dim.w + screen_region.pos.x) - region.dim.w 3555 ); 3556 3557 break; 3558 } 3559 case Edge::Bottom: 3560 { 3561 region.pos.y = std::max( 3562 0, 3563 (screen_region.dim.h + screen_region.pos.y) - region.dim.h 3564 ); 3565 3566 break; 3567 } 3568 } 3569 3570 Placement placement = Placement { 3571 Placement::PlacementMethod::Free, 3572 client, 3573 client->free_decoration, 3574 region 3575 }; 3576 3577 place_client(placement); 3578 } 3579 3580 3581 void 3582 Model::activate_screen_struts(winsys::Toggle toggle) 3583 { 3584 Screen& screen = active_screen(); 3585 3586 switch (toggle) { 3587 case Toggle::On: 3588 { 3589 if (screen.showing_struts()) 3590 return; 3591 3592 for (Window strut : screen.show_and_get_struts(true)) 3593 m_conn.map_window(strut); 3594 3595 apply_layout(mp_workspace); 3596 3597 return; 3598 } 3599 case Toggle::Off: 3600 { 3601 if (!screen.showing_struts()) 3602 return; 3603 3604 for (Window strut : screen.show_and_get_struts(false)) 3605 m_conn.unmap_window(strut); 3606 3607 apply_layout(mp_workspace); 3608 3609 return; 3610 } 3611 case Toggle::Reverse: 3612 { 3613 activate_screen_struts( 3614 screen.showing_struts() 3615 ? Toggle::Off 3616 : Toggle::On 3617 ); 3618 3619 return; 3620 } 3621 default: return; 3622 } 3623 } 3624 3625 3626 void 3627 Model::pop_deiconify() 3628 { 3629 std::optional<Client_ptr> icon = mp_workspace->pop_icon(); 3630 3631 if (icon) 3632 set_iconify_client(Toggle::Off, *icon); 3633 } 3634 3635 void 3636 Model::deiconify_all() 3637 { 3638 for (std::size_t i = 0; i < mp_workspace->size(); ++i) 3639 pop_deiconify(); 3640 } 3641 3642 3643 void 3644 Model::spawn_external(std::string&& command) const 3645 { 3646 spdlog::info("calling external command: " + command); 3647 m_conn.call_external_command(command); 3648 } 3649 3650 3651 void 3652 Model::exit() 3653 { 3654 for (auto& [window,client] : m_client_map) 3655 m_conn.unparent_window(client->window, client->free_region.pos); 3656 3657 m_conn.cleanup(); 3658 m_running = false; 3659 3660 spdlog::info("exiting " + WM_NAME); 3661 } 3662 3663 3664 // handlers 3665 3666 void 3667 Model::handle_mouse(MouseEvent event) 3668 { 3669 MouseInput input = event.capture.input; 3670 3671 switch (event.capture.kind) { 3672 case MouseCapture::MouseCaptureKind::Motion: 3673 { 3674 resolve_active_partition(event.capture.root_rpos); 3675 3676 perform_move(event.capture.root_rpos); 3677 perform_resize(event.capture.root_rpos); 3678 3679 return; 3680 } 3681 case MouseCapture::MouseCaptureKind::Release: 3682 { 3683 stop_moving(); 3684 stop_resizing(); 3685 3686 return; 3687 } 3688 default: break; 3689 } 3690 3691 Client_ptr client 3692 = event.capture.window 3693 ? get_client(*event.capture.window) 3694 : nullptr; 3695 3696 #define CALL_MUST_FOCUS(binding) \ 3697 ((*binding)(*this, client) && client && client != mp_focus) 3698 { // global binding 3699 input.target = MouseInput::MouseInputTarget::Global; 3700 auto binding = Util::retrieve(m_mouse_bindings, input); 3701 3702 if (binding) { 3703 if (CALL_MUST_FOCUS(binding)) 3704 focus_client(client); 3705 3706 return; 3707 } 3708 } 3709 3710 if (event.on_root) { // root binding 3711 input.target = MouseInput::MouseInputTarget::Root; 3712 auto binding = Util::retrieve(m_mouse_bindings, input); 3713 3714 if (binding) { 3715 if (CALL_MUST_FOCUS(binding)) 3716 focus_client(client); 3717 3718 return; 3719 } 3720 } 3721 3722 { // client binding 3723 input.target = MouseInput::MouseInputTarget::Client; 3724 auto binding = Util::retrieve(m_mouse_bindings, input); 3725 3726 if (binding) { 3727 if (CALL_MUST_FOCUS(binding)) 3728 focus_client(client); 3729 3730 return; 3731 } 3732 } 3733 #undef CALL_MUST_FOCUS 3734 3735 if (client && client != mp_focus) 3736 focus_client(client); 3737 } 3738 3739 void 3740 Model::handle_key(KeyEvent event) 3741 { 3742 auto binding = Util::retrieve(m_key_bindings, event.capture.input); 3743 3744 if (binding) 3745 (*binding)(*this); 3746 } 3747 3748 void 3749 Model::handle_map_request(MapRequestEvent event) 3750 { 3751 bool must_restack = false; 3752 bool may_map = true; 3753 3754 std::optional<std::vector<std::optional<Strut>>> struts 3755 = m_conn.get_window_strut(event.window); 3756 3757 if (struts) { 3758 Screen& screen = active_screen(); 3759 screen.add_struts(*struts); 3760 3761 if (screen.showing_struts()) { 3762 screen.compute_placeable_region(); 3763 3764 if (m_conn.window_is_mappable(event.window)) 3765 m_conn.map_window(event.window); 3766 3767 apply_layout(mp_workspace); 3768 must_restack = true; 3769 } else 3770 may_map = false; 3771 } 3772 3773 std::unordered_set<WindowType> types 3774 = m_conn.get_window_types(event.window); 3775 3776 std::unordered_set<WindowState> states 3777 = m_conn.get_window_states(event.window); 3778 3779 std::optional<Region> region 3780 = m_conn.get_window_geometry(event.window); 3781 3782 std::optional<StackHandler::StackLayer> layer = std::nullopt; 3783 3784 if (Util::contains(states, WindowState::Below_)) 3785 layer = StackHandler::StackLayer::Below_; 3786 else if (Util::contains(types, WindowType::Desktop)) 3787 layer = StackHandler::StackLayer::Desktop; 3788 else if (Util::contains(types, WindowType::Dock)) { 3789 Screen& screen = active_screen(); 3790 3791 if (region && !screen.contains_strut(event.window)) { 3792 const Region screen_region = screen.full_region(); 3793 std::optional<Edge> edge = std::nullopt; 3794 std::optional<Strut> strut = std::nullopt; 3795 3796 if (Pos::is_at_origin(region->pos)) { 3797 if (region->dim.w == screen_region.dim.w) { 3798 edge = Edge::Top; 3799 strut = Strut { event.window, region->dim.h }; 3800 } else if (region->dim.h == screen_region.dim.h) { 3801 edge = Edge::Left; 3802 strut = Strut { event.window, region->dim.w }; 3803 } else if (region->dim.w > screen_region.dim.h) { 3804 edge = Edge::Top; 3805 strut = Strut { event.window, region->dim.h }; 3806 } else if (region->dim.w < screen_region.dim.h) { 3807 edge = Edge::Left; 3808 strut = Strut { event.window, region->dim.w }; 3809 } 3810 } 3811 3812 if (!strut) { 3813 if (region->pos.y == region->dim.h) { 3814 edge = Edge::Bottom; 3815 strut = Strut { event.window, screen_region.dim.h - region->dim.h }; 3816 } else if (region->pos.x == region->dim.w) { 3817 edge = Edge::Right; 3818 strut = Strut { event.window, screen_region.dim.w - region->dim.w }; 3819 } 3820 } 3821 3822 if (strut) { 3823 screen.add_strut(*edge, *strut); 3824 3825 if (screen.showing_struts()) { 3826 screen.compute_placeable_region(); 3827 m_conn.map_window(strut->window); 3828 3829 apply_layout(mp_workspace); 3830 } else 3831 may_map = false; 3832 } 3833 } 3834 3835 layer = StackHandler::StackLayer::Dock; 3836 } else if (Util::contains(types, WindowType::Notification)) 3837 layer = StackHandler::StackLayer::Notification; 3838 else if (Util::contains(states, WindowState::Above_)) 3839 layer = StackHandler::StackLayer::Above_; 3840 3841 if (layer) { 3842 m_stack.add_window(event.window, *layer); 3843 must_restack = true; 3844 } 3845 3846 if (must_restack) 3847 apply_stack(mp_workspace); 3848 3849 if (!may_map) 3850 m_conn.unmap_window(event.window); 3851 3852 if (!(m_client_map.count(event.window) > 0)) 3853 manage(event.window, event.ignore, may_map); 3854 } 3855 3856 void 3857 Model::handle_map(MapEvent) 3858 {} 3859 3860 void 3861 Model::handle_enter(EnterEvent event) 3862 { 3863 Client_ptr client = get_client(event.window); 3864 3865 if (!client || client == mp_focus) 3866 return; 3867 3868 unfocus_client(mp_focus); 3869 focus_client(client); 3870 } 3871 3872 void 3873 Model::handle_leave(LeaveEvent event) 3874 { 3875 Client_ptr client = get_client(event.window); 3876 3877 if (!client) 3878 return; 3879 3880 unfocus_client(client); 3881 } 3882 3883 void 3884 Model::handle_destroy(DestroyEvent event) 3885 { 3886 Screen& screen = active_screen(); 3887 3888 if (screen.contains_strut(event.window)) { 3889 screen.remove_strut(event.window); 3890 screen.compute_placeable_region(); 3891 3892 apply_layout(mp_workspace); 3893 apply_stack(mp_workspace); 3894 } 3895 3896 m_stack.remove_window(event.window); 3897 3898 Util::erase_remove(m_unmanaged_windows, event.window); 3899 3900 Client_ptr client = get_client(event.window); 3901 3902 if (!client) 3903 return; 3904 3905 unmanage(client); 3906 } 3907 3908 void 3909 Model::handle_expose(ExposeEvent) 3910 {} 3911 3912 void 3913 Model::handle_unmap(UnmapEvent event) 3914 { 3915 if (Util::contains(m_unmanaged_windows, event.window)) 3916 return; 3917 3918 handle_destroy(DestroyEvent { event.window }); 3919 } 3920 3921 void 3922 Model::handle_state_request(StateRequestEvent event) 3923 { 3924 Client_ptr client = get_client(event.window); 3925 3926 if (!client) 3927 return; 3928 3929 switch (event.state) { 3930 case WindowState::Fullscreen: 3931 { 3932 set_fullscreen_client(event.action, client); 3933 return; 3934 } 3935 case WindowState::Sticky: 3936 { 3937 set_sticky_client(event.action, client); 3938 return; 3939 } 3940 case WindowState::DemandsAttention: 3941 { 3942 if (client->focused) 3943 return; 3944 3945 bool value; 3946 3947 switch (event.action) { 3948 case Toggle::On: value = true; break; 3949 case Toggle::Off: value = false; break; 3950 case Toggle::Reverse: value = !client->urgent; break; 3951 default: return; 3952 } 3953 3954 m_conn.set_icccm_window_hints(client->window, Hints { 3955 value, 3956 std::nullopt, 3957 std::nullopt, 3958 std::nullopt 3959 }); 3960 3961 client->urgent = value; 3962 render_decoration(client); 3963 3964 return; 3965 } 3966 default: break; 3967 } 3968 } 3969 3970 void 3971 Model::handle_focus_request(FocusRequestEvent event) 3972 { 3973 Client_ptr client = get_client(event.window); 3974 3975 if (!client || client->attaching || event.on_root) 3976 return; 3977 3978 focus_client(client); 3979 } 3980 3981 void 3982 Model::handle_close_request(CloseRequestEvent event) 3983 { 3984 if (!event.on_root) 3985 m_conn.kill_window(event.window); 3986 } 3987 3988 void 3989 Model::handle_workspace_request(WorkspaceRequestEvent event) 3990 { 3991 if (!event.on_root) 3992 activate_workspace(event.index); 3993 } 3994 3995 void 3996 Model::handle_placement_request(PlacementRequestEvent event) 3997 { 3998 if (!event.pos && !event.dim) 3999 return; 4000 4001 Client_ptr client = get_client(event.window); 4002 4003 if (!client) { 4004 std::optional<Region> geometry = m_conn.get_window_geometry(event.window); 4005 4006 if (geometry) { 4007 if (event.pos) 4008 geometry->pos = *event.pos; 4009 4010 if (event.dim) 4011 geometry->dim = *event.dim; 4012 4013 m_conn.place_window(event.window, *geometry); 4014 } 4015 4016 return; 4017 } 4018 4019 if (!is_free(client)) 4020 return; 4021 4022 Decoration decoration = client->free_decoration; 4023 Extents extents = Extents { 0, 0, 0, 0 }; 4024 4025 if (decoration.border) { 4026 extents.left += decoration.border->width; 4027 extents.top += decoration.border->width; 4028 extents.right += decoration.border->width; 4029 extents.bottom += decoration.border->width; 4030 } 4031 4032 if (decoration.frame) { 4033 extents.left += decoration.frame->extents.left; 4034 extents.top += decoration.frame->extents.top; 4035 extents.right += decoration.frame->extents.right; 4036 extents.bottom += decoration.frame->extents.bottom; 4037 } 4038 4039 Region region; 4040 4041 if (event.window == client->window) { 4042 Pos pos; 4043 Dim dim; 4044 4045 if (event.pos) 4046 pos = Pos { 4047 event.pos->x - extents.left, 4048 event.pos->y - extents.top 4049 }; 4050 else 4051 pos = client->free_region.pos; 4052 4053 if (event.dim) 4054 dim = Dim { 4055 event.dim->w + extents.left + extents.right, 4056 event.dim->h + extents.top + extents.bottom 4057 }; 4058 else 4059 dim = client->free_region.dim; 4060 4061 region = Region { 4062 pos, dim 4063 }; 4064 } else { 4065 region = Region { 4066 event.pos.value_or(client->free_region.pos), 4067 event.dim.value_or(client->free_region.dim) 4068 }; 4069 } 4070 4071 region.remove_extents(extents); 4072 4073 if (client->size_hints) 4074 client->size_hints->apply(region.dim); 4075 4076 region.apply_minimum_dim(Client::MIN_CLIENT_DIM); 4077 region.apply_extents(extents); 4078 4079 client->set_free_region(region); 4080 4081 Placement placement{ 4082 Placement::PlacementMethod::Free, 4083 client, 4084 client->free_decoration, 4085 region 4086 }; 4087 4088 place_client(placement); 4089 } 4090 4091 void 4092 Model::handle_grip_request(GripRequestEvent event) 4093 { 4094 Client_ptr client = get_client(event.window); 4095 4096 if (!client) 4097 return; 4098 4099 stop_moving(); 4100 stop_resizing(); 4101 4102 if (event.grip) { 4103 m_resize_buffer.set( 4104 client, 4105 *event.grip, 4106 m_conn.get_pointer_position(), 4107 client->free_region 4108 ); 4109 4110 m_conn.init_resize(client->frame); 4111 } else 4112 start_moving(client); 4113 } 4114 4115 void 4116 Model::handle_restack_request(RestackRequestEvent) 4117 {} 4118 4119 void 4120 Model::handle_configure(ConfigureEvent event) 4121 { 4122 if (event.on_root) { 4123 acquire_partitions(); 4124 apply_layout(m_workspaces.active_index()); 4125 } 4126 } 4127 4128 void 4129 Model::handle_property(PropertyEvent event) 4130 { 4131 switch (event.kind) { 4132 case PropertyKind::Name: 4133 { 4134 Client_ptr client = get_client(event.window); 4135 4136 if (!client) 4137 return; 4138 4139 client->name = m_conn.get_icccm_window_name(event.window); 4140 4141 return; 4142 } 4143 case PropertyKind::Class: 4144 { 4145 Client_ptr client = get_client(event.window); 4146 4147 if (!client) 4148 return; 4149 4150 client->class_ = m_conn.get_icccm_window_class(event.window); 4151 client->instance = m_conn.get_icccm_window_instance(event.window); 4152 4153 return; 4154 } 4155 case PropertyKind::Size: 4156 { 4157 Client_ptr client = get_client(event.window); 4158 4159 if (!client) 4160 return; 4161 4162 client->size_hints 4163 = m_conn.get_icccm_window_size_hints(event.window, Client::MIN_CLIENT_DIM); 4164 4165 std::optional<Region> geometry = m_conn.get_window_geometry(event.window); 4166 4167 if (!geometry) 4168 return; 4169 4170 Region region = *geometry; 4171 4172 if (client->size_hints) 4173 client->size_hints->apply(region.dim); 4174 4175 region.apply_minimum_dim(Client::MIN_CLIENT_DIM); 4176 4177 Decoration decoration = client->free_decoration; 4178 Extents extents = Extents { 0, 0, 0, 0 }; 4179 4180 if (decoration.border) { 4181 extents.left += decoration.border->width; 4182 extents.top += decoration.border->width; 4183 extents.right += decoration.border->width; 4184 extents.bottom += decoration.border->width; 4185 } 4186 4187 if (decoration.frame) { 4188 extents.left += decoration.frame->extents.left; 4189 extents.top += decoration.frame->extents.top; 4190 extents.right += decoration.frame->extents.right; 4191 extents.bottom += decoration.frame->extents.bottom; 4192 } 4193 4194 region.pos = client->free_region.pos; 4195 region.dim.w += extents.left + extents.right; 4196 region.dim.h += extents.top + extents.bottom; 4197 4198 client->set_free_region(region); 4199 4200 if (client->managed) 4201 apply_layout(client->workspace); 4202 4203 return; 4204 } 4205 case PropertyKind::Strut: 4206 { 4207 std::optional<std::vector<std::optional<Strut>>> struts 4208 = m_conn.get_window_strut(event.window); 4209 4210 if (struts) { 4211 Screen& screen = active_screen(); 4212 4213 screen.remove_strut(event.window); 4214 screen.add_struts(*struts); 4215 screen.compute_placeable_region(); 4216 4217 apply_layout(mp_workspace); 4218 apply_stack(mp_workspace); 4219 } 4220 4221 return; 4222 } 4223 default: break; 4224 } 4225 } 4226 4227 void 4228 Model::handle_frame_extents_request(FrameExtentsRequestEvent event) 4229 { 4230 Client_ptr client = get_client(event.window); 4231 4232 Extents extents = Extents { 0, 0, 0, 0 }; 4233 4234 if (client) { 4235 Decoration decoration = client->active_decoration; 4236 4237 if (decoration.border) { 4238 extents.left += decoration.border->width; 4239 extents.top += decoration.border->width; 4240 extents.right += decoration.border->width; 4241 extents.bottom += decoration.border->width; 4242 } 4243 4244 if (decoration.frame) { 4245 extents.left += decoration.frame->extents.left; 4246 extents.top += decoration.frame->extents.top; 4247 extents.right += decoration.frame->extents.right; 4248 extents.bottom += decoration.frame->extents.bottom; 4249 } 4250 } 4251 4252 m_conn.set_window_frame_extents(event.window, extents); 4253 } 4254 4255 void 4256 Model::handle_screen_change() 4257 {} 4258 4259 4260 void 4261 Model::process_command(winsys::CommandMessage message) 4262 { 4263 static const std::unordered_map<std::string_view, winsys::Direction> directions{ 4264 { "Forward", winsys::Direction::Forward }, 4265 { "forward", winsys::Direction::Forward }, 4266 { "fwd", winsys::Direction::Forward }, 4267 { "f", winsys::Direction::Forward }, 4268 { "Backward", winsys::Direction::Backward }, 4269 { "backward", winsys::Direction::Backward }, 4270 { "bwd", winsys::Direction::Backward }, 4271 { "b", winsys::Direction::Backward }, 4272 }; 4273 4274 static const std::unordered_map<std::string_view, std::function<void(void)>> commands{ 4275 { "toggle_partition", 4276 [&,this]() { 4277 toggle_partition(); 4278 } 4279 }, 4280 { "activate_next_partition", 4281 [&,this]() { 4282 if (!message.args.empty() && directions.count(message.args.front())) 4283 activate_next_partition(directions.at(message.args.front())); 4284 } 4285 }, 4286 { "activate_partition", 4287 [&,this]() { 4288 if (!message.args.empty()) { 4289 std::istringstream index_stream(message.args.front()); 4290 Index index = 0; 4291 4292 if (index_stream >> index) 4293 activate_partition(index); 4294 } 4295 } 4296 }, 4297 4298 { "toggle_context", 4299 [&,this]() { 4300 toggle_context(); 4301 } 4302 }, 4303 { "activate_next_context", 4304 [&,this]() { 4305 if (!message.args.empty() && directions.count(message.args.front())) 4306 activate_next_context(directions.at(message.args.front())); 4307 } 4308 }, 4309 { "activate_context", 4310 [&,this]() { 4311 if (!message.args.empty()) { 4312 std::istringstream index_stream(message.args.front()); 4313 Index index = 0; 4314 4315 if (index_stream >> index) 4316 activate_context(index); 4317 } 4318 } 4319 }, 4320 4321 { "toggle_workspace", 4322 [&,this]() { 4323 toggle_workspace(); 4324 } 4325 }, 4326 { "activate_next_workspace", 4327 [&,this]() { 4328 if (!message.args.empty() && directions.count(message.args.front())) 4329 activate_next_workspace(directions.at(message.args.front())); 4330 } 4331 }, 4332 { "activate_workspace", 4333 [&,this]() { 4334 if (!message.args.empty()) { 4335 std::istringstream index_stream(message.args.front()); 4336 Index index = 0; 4337 4338 if (index_stream >> index) 4339 activate_workspace(index); 4340 } 4341 } 4342 }, 4343 }; 4344 4345 if (!message.args.empty()) { 4346 std::string command = message.args.front(); 4347 message.args.pop_front(); 4348 4349 if (commands.count(command) > 0) 4350 commands.at(command)(); 4351 } 4352 } 4353 4354 void 4355 Model::process_config(winsys::ConfigMessage) 4356 { 4357 // TODO 4358 } 4359 4360 void 4361 Model::process_client(winsys::WindowMessage) 4362 { 4363 // TODO 4364 } 4365 4366 void 4367 Model::process_workspace(winsys::WorkspaceMessage) 4368 { 4369 // TODO 4370 } 4371 4372 void 4373 Model::process_query(winsys::QueryMessage) 4374 { 4375 // TODO 4376 } 4377 4378 4379 // static methods 4380 4381 void 4382 Model::wait_children(int) 4383 { 4384 struct sigaction child_sa; 4385 4386 std::memset(&child_sa, 0, sizeof(child_sa)); 4387 child_sa.sa_handler = &Model::wait_children; 4388 4389 sigaction(SIGCHLD, &child_sa, NULL); 4390 while (waitpid(-1, 0, WNOHANG) > 0); 4391 } 4392 4393 void 4394 Model::handle_signal(int sig) 4395 { 4396 if (sig == SIGHUP || sig == SIGINT || sig == SIGTERM) 4397 g_instance->exit(); 4398 }