kranewm

An ICCCM & EWMH compliant X11 reparenting, dynamic window manager, written in C++
git clone git://git.deurzen.net/kranewm
Log | Files | Refs | LICENSE

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 }