--- a/plugins/single_plugins/oswitch.cpp +++ b/plugins/single_plugins/oswitch.cpp @@ -8,10 +8,86 @@ #include #include #include +#include +#include +#include +#include +#include class wayfire_oswitch : public wf::plugin_interface_t { wf::wl_idle_call idle_switch_output; + + bool switch_stitched(int direction) + { + auto output = wf::get_core().seat->get_active_output(); + auto layout = output ? output->get_data() : nullptr; + if (!layout || layout->zones.size() < 2) + { + return false; + } + + // Resolve the target when the binding has finished dispatching. A + // physical member is a focus region, not a separate workspace/output. + idle_switch_output.run_once([direction] () + { + auto& core = wf::get_core(); + auto output = core.seat->get_active_output(); + auto layout = output ? output->get_data() : nullptr; + if (!layout || layout->zones.size() < 2) return; + auto pointer = output->get_cursor_position(); + auto contains = [] (wf::geometry_t g, double x, double y) + { + return x >= g.x && x < g.x + g.width && y >= g.y && y < g.y + g.height; + }; + const wf::output_stitch_zone_t *current = nullptr; + for (const auto& zone : layout->zones) + if (contains(zone.geometry, pointer.x, pointer.y)) current = &zone; + if (!current) return; + const wf::output_stitch_zone_t *next = nullptr; + double distance = std::numeric_limits::max(); + double origin = current->geometry.x + current->geometry.width / 2.0; + for (const auto& zone : layout->zones) + { + double delta = direction * (zone.geometry.x + zone.geometry.width / 2.0 - origin); + if (delta > 0 && delta < distance) + { + next = &zone; + distance = delta; + } + } + if (!next) return; + auto region = next->geometry; + wayfire_toplevel_view focus = nullptr; + uint64_t newest = 0; + for (auto view : output->wset()->get_views(wf::WSET_CURRENT_WORKSPACE | + wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED)) + { + if (!view->get_root_node()->is_enabled() || !view->get_keyboard_focus_surface()) continue; + auto box = view->get_geometry(); + if (!contains(region, box.x + box.width / 2.0, box.y + box.height / 2.0)) continue; + auto timestamp = wf::get_focus_timestamp(view); + if (!focus || timestamp > newest) + { + focus = view; + newest = timestamp; + } + } + double x = region.x + region.width / 2.0; + double y = region.y + region.height / 2.0; + if (focus) + { + auto box = focus->get_geometry(); + x = (std::max(region.x, box.x) + std::min(region.x + region.width, box.x + box.width)) / 2.0; + y = (std::max(region.y, box.y) + std::min(region.y + region.height, box.y + box.height)) / 2.0; + } + auto global = output->get_layout_geometry(); + core.warp_cursor({global.x + x, global.y + y}); + if (focus) core.default_wm->focus_request(focus); + else core.seat->focus_view(nullptr); + }); + return true; + } wf::output_t *get_left_output() { @@ -196,6 +272,7 @@ wf::activator_callback switch_left = [=] (auto) { + if (switch_stitched(-1)) return true; auto target_output = get_left_output(); switch_to_output(target_output); return true; @@ -203,6 +280,7 @@ wf::activator_callback switch_right = [=] (auto) { + if (switch_stitched(1)) return true; auto target_output = get_right_output(); switch_to_output(target_output); return true;