diff options
| author | Crucible <crucible@localhost> | 2026-10-22 19:17:45 -0500 |
|---|---|---|
| committer | Crucible <crucible@localhost> | 2026-10-22 19:17:45 -0500 |
| commit | b4a1c96b776780b6ed75aa034ed4f3f64be9199c (patch) | |
| tree | 0c94b8abcf0367ebd73a5ab930c790038d86dbf2 | |
| parent | 2eaf3b0e17c56ca25d67211bfd1e18a1a9926736 (diff) | |
| download | srcux-b4a1c96b776780b6ed75aa034ed4f3f64be9199c.tar.gz srcux-b4a1c96b776780b6ed75aa034ed4f3f64be9199c.tar.xz srcux-b4a1c96b776780b6ed75aa034ed4f3f64be9199c.zip | |
Publish srcux candidate 0000000823-20261022-191401
13 files changed, 1079 insertions, 0 deletions
diff --git a/gui-apps/ambient-background/ambient-background-1.0.0-r17.ebuild b/gui-apps/ambient-background/ambient-background-1.0.0-r18.ebuild index db547dbb907b..38d2e9f66be3 100644 --- a/gui-apps/ambient-background/ambient-background-1.0.0-r17.ebuild +++ b/gui-apps/ambient-background/ambient-background-1.0.0-r18.ebuild @@ -33,6 +33,8 @@ RDEPEND=" >=gui-wm/wayfire-0.11.0-r60:0= " +PATCHES=( "${FILESDIR}/${P}-idle-timer.patch" ) + src_unpack() { mkdir -p "${S}" || die cp -a "${FILESDIR}/${P}-r16/." "${S}/" || die diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-idle-timer.patch b/gui-apps/ambient-background/files/ambient-background-1.0.0-idle-timer.patch new file mode 100644 index 000000000000..2e695ec81e11 --- /dev/null +++ b/gui-apps/ambient-background/files/ambient-background-1.0.0-idle-timer.patch @@ -0,0 +1,243 @@ +diff --git a/core/ambient-background.cpp b/core/ambient-background.cpp +index 2fb658d..60a9213 100644 +--- a/core/ambient-background.cpp ++++ b/core/ambient-background.cpp +@@ -96,6 +96,9 @@ class ambient_plugin_t : public wf::plugin_interface_t { + std::map<wf::output_t*, output_state_t> outputs; + wf::scene::node_ptr shield; + wf::wl_timer<true> timer; ++ wf::wl_idle_call rearm; ++ unsigned timer_interval = 0; ++ bool in_tick = false; + bool manual = false, suspended = false, daemon_started = false, cursor_hidden = false; + unsigned reset_generation = 0; + double last_tick = now(), heartbeat = 0, restore_until = 0, last_launch = 0; +@@ -131,6 +134,7 @@ class ambient_plugin_t : public wf::plugin_interface_t { + auto it = std::find(children.begin(), children.end(), anchor); + children.insert(it, node); + root->set_children_list(children); ++ wf::scene::update(root, wf::scene::update_flag::CHILDREN_LIST); + } + bool locked() const { + for (auto& [o, s] : outputs) if (o->is_inhibited()) return true; +@@ -141,7 +145,7 @@ class ambient_plugin_t : public wf::plugin_interface_t { + if (!stitch->zones.empty()) return stitch->zones; + return {{o->to_string(), o->get_relative_geometry()}}; + } +- void update_zones(wf::output_t *o, output_state_t& state) { ++ bool update_zones(wf::output_t *o, output_state_t& state) { + auto old = state.zones; + state.zones.clear(); + for (auto z : targets(o)) { +@@ -151,13 +155,17 @@ class ambient_plugin_t : public wf::plugin_interface_t { + } + state.zones.push_back(zone); + } ++ if (old.size() != state.zones.size()) return true; ++ for (size_t i = 0; i < old.size(); i++) ++ if (old[i].name != state.zones[i].name || old[i].box != state.zones[i].box) return true; ++ return false; + } + void add_output(wf::output_t *o) { + if (outputs.count(o)) return; + auto& state = outputs[o]; + update_zones(o, state); +- state.hyperspace = std::make_unique<ambient_hyperspace_background>([o] { o->render->damage_whole(); }); +- state.nebula = std::make_unique<ambient_nebula_background>([o] { o->render->damage_whole(); }); ++ state.hyperspace = std::make_unique<ambient_hyperspace_background>([this,o] { o->render->damage_whole(); schedule(); }); ++ state.nebula = std::make_unique<ambient_nebula_background>([this,o] { o->render->damage_whole(); schedule(); }); + state.nebula->set_active(active() && !locked() && std::string(backend) == "nebula"); + state.hyperspace->set_active(active() && !locked() && std::string(backend) == "hyperspace"); + o->add_activator(toggle_binding, &toggle); +@@ -268,7 +276,9 @@ class ambient_plugin_t : public wf::plugin_interface_t { + if (auto view=previous_focus.lock()) if (view->is_mapped()) wf::get_core().seat->focus_view(view->self()); + previous_focus.reset(); + } ++ for (auto& [o,s]:outputs) o->render->damage_whole(); + update_shield(); ++ schedule(); + } + wf::activator_callback toggle = [this] (auto) { if (!active() || locked()) return false; set_manual(!manual); return true; }; + wf::activator_callback toggle_background = [this] (auto) { +@@ -285,6 +295,8 @@ class ambient_plugin_t : public wf::plugin_interface_t { + void paint(wf::output_t *o,const wf::render_target_t& target,const wf::regionf_t& damage) { + auto it=outputs.find(o); if (it==outputs.end() || !enabled || locked()) return; + auto& state=it->second; ++ // Unlocking has no signal; resume animation on the first repaint. ++ schedule(); + GL_CALL(glDisable(GL_DEPTH_TEST)); GL_CALL(glDisable(GL_CULL_FACE)); + wf::gles::for_each_scissor_rect(target,damage,[&] { + OpenGL::render_rectangle(o->get_relative_geometry(),{0,0,0,1},wf::gles::render_target_orthographic_projection(target)); +@@ -332,31 +344,80 @@ class ambient_plugin_t : public wf::plugin_interface_t { + } + GL_CALL(glDisable(GL_BLEND)); + } ++ bool stale(double t) const { return heartbeat ? t-heartbeat>3 : daemon_started && t-last_launch>3; } ++ float zone_target(const zone_t& z, double t, bool is_stale) const { ++ return manual ? 0 : (is_stale || t<restore_until || locked() ? 1 : z.target); ++ } ++ bool effect_animating(output_state_t& s) { ++ bool visible=active() && !locked(); ++ auto b=std::string(backend); ++ s.hyperspace->set_active(visible && b=="hyperspace"); ++ s.nebula->set_active(visible && b=="nebula"); ++ if (!visible) return false; ++ if (b=="hyperspace") return s.hyperspace->animated(); ++ if (b=="nebula") return s.nebula->animated(); ++ return true; // Digital rain frames come from its client buffers. ++ } ++ unsigned frame_interval() const { ++ if (std::string(backend)=="hyperspace" && !outputs.empty()) ++ return outputs.begin()->second.hyperspace->frame_interval(); ++ return 33; ++ } ++ // Frames only while an effect animates or a zone fades. Otherwise, while ++ // enabled, poll slowly for the daemon heartbeat, lock and zone changes. ++ unsigned wanted_interval() { ++ if (!enabled) return 0; ++ double t=now(); bool is_stale=stale(t); ++ bool busy=manual || t<restore_until; ++ for (auto& [o,s]:outputs) { ++ busy |= effect_animating(s); ++ for (auto& z:s.zones) busy |= z.opacity != zone_target(z,t,is_stale); ++ } ++ return busy ? frame_interval() : 1000; ++ } ++ void schedule() { ++ if (in_tick) return; // tick() re-evaluates when it returns ++ unsigned interval=wanted_interval(); ++ if (interval == timer_interval) return; ++ timer.disconnect(); timer_interval=interval; last_tick=now(); ++ if (interval) timer.set_timeout(interval,[this]{return tick();}); ++ } + bool tick() { ++ in_tick=true; + double t=now(), dt=std::clamp(t-last_tick,0.0,.1); last_tick=t; ++ // A slow poll tick starts any new fade with a single frame step. ++ if (timer_interval > frame_interval()) dt=std::min(dt,frame_interval()/1000.0); + if (locked() && manual) set_manual(false); +- bool stale=(heartbeat ? t-heartbeat>3 : daemon_started && t-last_launch>3); +- if (stale && manual) set_manual(false); ++ bool is_stale=stale(t); ++ if (is_stale && manual) set_manual(false); + for (auto& [o,s]:outputs) { +- update_zones(o,s); +- s.hyperspace->set_active(active() && !locked() && std::string(backend)=="hyperspace"); +- s.nebula->set_active(active() && !locked() && std::string(backend)=="nebula"); ++ // The locker owns animation while protected. Do not keep waking ++ // the desktop renderer behind a black or powered-off lock screen. ++ bool visible=active() && !locked(); ++ if (update_zones(o,s) && visible) o->render->damage_whole(); ++ bool animating=effect_animating(s); + for (auto& z:s.zones) { +- float target = manual ? 0 : (stale || t<restore_until || locked() ? 1 : z.target); ++ auto before=z; ++ float target=zone_target(z,t,is_stale); + double duration=manual ? double(manual_seconds) : double(wake_seconds); +- z.opacity += std::clamp(target-z.opacity,float(-dt/std::max(.05,duration)),float(dt/std::max(.05,duration))); ++ float step=float(dt/std::max(.05,duration)); ++ z.opacity=std::abs(target-z.opacity)<=step ? target : z.opacity+std::clamp(target-z.opacity,-step,step); + z.opacity=std::clamp(z.opacity,0.f,1.f); +- if (stale) { z.target=1;z.black=0;z.effect=1; } ++ if (is_stale) { z.target=1;z.black=0;z.effect=1; } ++ if (visible && !animating && (z.opacity!=before.opacity || z.black!=before.black || z.effect!=before.effect)) ++ o->render->damage(z.box); + } +- // The locker owns animation while protected. Do not keep waking +- // the desktop renderer behind a black or powered-off lock screen. +- if (active() && !locked()) o->render->damage_whole(); ++ if (animating) o->render->damage_whole(); + } + update_shield(); +- if (enabled && stale && !std::string(controller).empty() && t-last_launch>10) { ++ if (enabled && is_stale && !std::string(controller).empty() && t-last_launch>10) { + last_launch=t;wf::get_core().run(std::string(controller)); + } +- return true; ++ in_tick=false; ++ if (wanted_interval() == timer_interval) return true; ++ // The timer cannot be re-armed from its own callback. ++ timer_interval=0; rearm.run_once([this]{schedule();}); ++ return false; + } + wf::json_t status() { + wf::json_t j=wf::ipc::json_ok(); +@@ -431,6 +492,7 @@ class ambient_plugin_t : public wf::plugin_interface_t { + if (enabled && !daemon_started && !std::string(controller).empty()) { + daemon_started=true;last_launch=now();wf::get_core().run(std::string(controller)); + } ++ schedule(); + } + wf::signal::connection_t<wf::ambient_query_signal> query = [this] (auto ev) { + ev->enabled=enabled && outputs.count(ev->output); +@@ -441,7 +503,12 @@ class ambient_plugin_t : public wf::plugin_interface_t { + wf::signal::connection_t<wf::ambient_transition_signal> transition = [this] (auto ev) { + if (outputs.count(ev->output)) outputs.at(ev->output).hyperspace->set_transition(ev->moving); + }; +- wf::signal::connection_t<wf::output_added_signal> added = [this] (auto ev) { add_output(ev->output); }; ++ // Background and wallpaper options are read on demand without callbacks. ++ wf::signal::connection_t<wf::reload_config_signal> reloaded = [this] (auto) { ++ if (active()) for (auto& [o,s]:outputs) o->render->damage_whole(); ++ schedule(); ++ }; ++ wf::signal::connection_t<wf::output_added_signal> added = [this] (auto ev) { add_output(ev->output); schedule(); }; + wf::signal::connection_t<wf::output_pre_remove_signal> removed = [this] (auto ev) { + if (outputs.count(ev->output)) { + ev->output->rem_binding(&toggle);ev->output->rem_binding(&restore); +@@ -450,6 +517,7 @@ class ambient_plugin_t : public wf::plugin_interface_t { + if(s.node)wf::scene::remove_child(s.node); + if(s.fade_node)wf::scene::remove_child(s.fade_node); + outputs.erase(ev->output); ++ schedule(); + } + }; + public: +@@ -467,7 +535,7 @@ class ambient_plugin_t : public wf::plugin_interface_t { + for (auto o:wf::get_core().output_layout->get_outputs()) add_output(o); + wf::get_core().output_layout->connect(&added);wf::get_core().output_layout->connect(&removed); + wf::get_core().connect(&query);wf::get_core().connect(&render);wf::get_core().connect(&transition); +- wf::get_core().connect(&on_key); ++ wf::get_core().connect(&on_key);wf::get_core().connect(&reloaded); + enabled.set_callback([this]{suspended=false;changed();});backend.set_callback([this]{changed();}); + repo->register_method("ambient/status",[this](wf::json_t){return status();}); + repo->register_method("ambient/toggle",[this](wf::json_t){set_manual(!manual);return status();}); +@@ -490,19 +558,21 @@ class ambient_plugin_t : public wf::plugin_interface_t { + for (size_t i=0;i<data["outputs"].size();i++) { + wf::json_t item=data["outputs"][i]; + for(auto& [o,s]:outputs)for(auto& z:s.zones)if(z.name==item["name"].as_string()) { +- z.target=item["opacity"].as_double();z.black=item["black"].as_double();z.effect=item["effect"].as_double(); ++ float black=item["black"].as_double(), effect=item["effect"].as_double(); ++ if ((black!=z.black || effect!=z.effect) && active() && !locked()) o->render->damage(z.box); ++ z.target=item["opacity"].as_double();z.black=black;z.effect=effect; + } + } +- heartbeat=now();return wf::ipc::json_ok(); ++ heartbeat=now();schedule();return wf::ipc::json_ok(); + }); +- changed();timer.set_timeout(33,[this]{return tick();}); ++ changed(); + } + void fini() override { +- timer.disconnect();enabled.set_callback(nullptr);backend.set_callback(nullptr); +- set_manual(false); ++ enabled.set_callback(nullptr);backend.set_callback(nullptr); ++ set_manual(false);timer.disconnect();rearm.disconnect();timer_interval=0; + if(cursor_hidden) { wf::get_core().unhide_cursor();cursor_hidden=false; } + if(shield) {wf::scene::remove_child(shield);shield.reset();} +- query.disconnect();render.disconnect();transition.disconnect();on_key.disconnect();added.disconnect();removed.disconnect(); ++ query.disconnect();render.disconnect();transition.disconnect();on_key.disconnect();reloaded.disconnect();added.disconnect();removed.disconnect(); + for(auto& [o,s]:outputs) { + o->rem_binding(&toggle);o->rem_binding(&restore); + o->rem_binding(&toggle_background); +diff --git a/effects/nebula/nebula-background.hpp b/effects/nebula/nebula-background.hpp +index 51df5eb..c0eec86 100644 +--- a/effects/nebula/nebula-background.hpp ++++ b/effects/nebula/nebula-background.hpp +@@ -31,6 +31,7 @@ class __attribute__((visibility("hidden"))) ambient_nebula_background { + explicit ambient_nebula_background(std::function<void()> changed); + ~ambient_nebula_background(); + void set_active(bool enabled); ++ bool animated() const { return active && double(speed)>0; } + void invalidate() { dirty=true; } + bool has_scene() const { return configured_seed>=0; } + std::array<double,2> scene() const { return {double(scene_seed),phase}; } diff --git a/gui-libs/pixdecor/files/pixdecor-corner-resize.patch b/gui-libs/pixdecor/files/pixdecor-corner-resize.patch new file mode 100644 index 000000000000..48c5b1ce13ab --- /dev/null +++ b/gui-libs/pixdecor/files/pixdecor-corner-resize.patch @@ -0,0 +1,18 @@ +diff --git a/src/deco-subsurface.cpp b/src/deco-subsurface.cpp +index f626204..8eb57e9 100644 +--- a/src/deco-subsurface.cpp ++++ b/src/deco-subsurface.cpp +@@ -263,7 +263,12 @@ class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_in + const double x = local.x - shadow, y = local.y - shadow; + const double dx = std::max({radius - x, 0.0, x - (w - radius)}); + const double dy = std::max({radius - y, 0.0, y - (h - radius)}); +- if (dx > 0 && dy > 0 && dx * dx + dy * dy > radius * radius) ++ // Keep the corner resize handles (where the layout's edge ++ // handles overlap, see calculate_resize_edges()) hittable. ++ const double handle = theme.get_input_size(); ++ const bool corner_handle = std::min(x, w - x) < handle && ++ std::min(y, h - y) < handle; ++ if (!corner_handle && dx > 0 && dy > 0 && dx * dx + dy * dy > radius * radius) + { + return {}; + } diff --git a/gui-libs/pixdecor/pixdecor-0.1.0_p20261006-r1.ebuild b/gui-libs/pixdecor/pixdecor-0.1.0_p20261006-r2.ebuild index eb0d7eb34303..be5306c3d66b 100644 --- a/gui-libs/pixdecor/pixdecor-0.1.0_p20261006-r1.ebuild +++ b/gui-libs/pixdecor/pixdecor-0.1.0_p20261006-r2.ebuild @@ -20,6 +20,7 @@ PATCHES=( "${FILESDIR}/${PN}-shade-deco-lifetime.patch" "${FILESDIR}/${PN}-shade-output-lifetime.patch" "${FILESDIR}/${PN}-theme-ipc-validate.patch" + "${FILESDIR}/${PN}-corner-resize.patch" ) LICENSE="MIT" diff --git a/gui-libs/pixdecor/pixdecor-9999-r5.ebuild b/gui-libs/pixdecor/pixdecor-9999-r6.ebuild index f64605697e03..5ec0d321a8c7 100644 --- a/gui-libs/pixdecor/pixdecor-9999-r5.ebuild +++ b/gui-libs/pixdecor/pixdecor-9999-r6.ebuild @@ -20,6 +20,7 @@ PATCHES=( "${FILESDIR}/${PN}-shade-deco-lifetime.patch" "${FILESDIR}/${PN}-shade-output-lifetime.patch" "${FILESDIR}/${PN}-theme-ipc-validate.patch" + "${FILESDIR}/${PN}-corner-resize.patch" ) LICENSE="MIT" diff --git a/gui-libs/wayfire-plugins-custom/files/window-state-preview-budget-v1.patch b/gui-libs/wayfire-plugins-custom/files/window-state-preview-budget-v1.patch new file mode 100644 index 000000000000..f1c3f4024cdb --- /dev/null +++ b/gui-libs/wayfire-plugins-custom/files/window-state-preview-budget-v1.patch @@ -0,0 +1,35 @@ +diff --git a/plugins/window-state/src/spatial.hpp b/plugins/window-state/src/spatial.hpp +index eedaf43..639ef78 100644 +--- a/plugins/window-state/src/spatial.hpp ++++ b/plugins/window-state/src/spatial.hpp +@@ -2,6 +2,7 @@ + #include <wayfire/txn/transaction-manager.hpp> + #include <wayfire/seat.hpp> + #include <deque> ++#include <cmath> + #include "previews.hpp" + + namespace +@@ -338,6 +339,13 @@ class SpatialOwner + if (!tokens.is_array() || tokens.empty() || tokens.size()>(hover?1:12)) throw std::runtime_error("preview batch exceeds limit"); + if (Clock::now()-last_preview<std::chrono::milliseconds(180)) throw std::runtime_error("preview refresh is rate limited"); + last_preview=Clock::now();Json frames=Json::array(); ++ // Bound the whole reply, not only each frame: twelve 160x90 RGBA ++ // frames encode to ~920 KB, past the nonblocking socket's send ++ // buffer (~208 KiB default; Wayfire without ipc-backpressure drops ++ // the client on a short write) and near the 1 MiB IPC message cap. ++ // Batches shrink to two full frames' worth (~150 KB encoded). ++ const double fit=std::min(1.0,std::sqrt(2.0/tokens.size())); ++ const int batch_width=int(160*fit),batch_height=int(90*fit); + for (const auto& token : tokens) + { + auto row=find(rows,token);auto v=wf::toplevel_cast(wf::ipc::find_view_by_id(row["id"].get<int>())); +@@ -345,7 +353,7 @@ class SpatialOwner + throw std::runtime_error("previews unavailable while locked or desktop interaction is active"); + // Wayfire writes IPC responses synchronously to a nonblocking socket. + // A raw 480x300 RGBA frame can exceed its socket send buffer. +- auto frame=capture_preview(v,hover?240:160,hover?150:90); ++ auto frame=capture_preview(v,hover?240:batch_width,hover?150:batch_height); + frame["token"]=row["token"];frame["version"]=row["version"];frames.push_back(std::move(frame)); + } + return {{"instance",instance},{"topology-revision",topo["revision"]},{"frames",frames}}; diff --git a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r11.ebuild b/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r12.ebuild index d7796a0a0645..8d382ed8fd2b 100644 --- a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r11.ebuild +++ b/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r12.ebuild @@ -26,6 +26,7 @@ src_prepare() { eapply "${FILESDIR}/window-state-control-exclusion-v1.patch" eapply "${FILESDIR}/window-state-preview-ipc-budget-v1.patch" eapply "${FILESDIR}/window-state-settings-exclusion-v1.patch" + eapply "${FILESDIR}/window-state-preview-budget-v1.patch" } RDEPEND=" diff --git a/gui-libs/wayfire-plugins-extra/files/wayfire-plugins-extra-0.11.2-follow-focus-explicit.patch b/gui-libs/wayfire-plugins-extra/files/wayfire-plugins-extra-0.11.2-follow-focus-explicit.patch new file mode 100644 index 000000000000..22e4d994261e --- /dev/null +++ b/gui-libs/wayfire-plugins-extra/files/wayfire-plugins-extra-0.11.2-follow-focus-explicit.patch @@ -0,0 +1,130 @@ +diff --git a/src/follow-focus.cpp b/src/follow-focus.cpp +index ced6a92..b271452 100644 +--- a/src/follow-focus.cpp ++++ b/src/follow-focus.cpp +@@ -28,10 +28,12 @@ + #include <wayfire/output-layout.hpp> + #include <wayfire/per-output-plugin.hpp> + #include <wayfire/view-helpers.hpp> ++#include <wayfire/workspace-set.hpp> + #include <wayfire/view.hpp> + #include <wayfire/util.hpp> + #include <wayfire/seat.hpp> + #include <wayfire/signal-definitions.hpp> ++#include <wayfire/plugins/common/util.hpp> + + namespace follow_focus + { +@@ -44,8 +46,10 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + wayfire_view focus_view = nullptr; + wf::wl_timer<false> change_output_focus, change_view_focus; + wf::pointf_t last_output_coords, last_view_coords; +- wf::wl_idle_call workspace_focus_idle; ++ wf::wl_idle_call workspace_focus_idle, workspace_switch_idle; + bool workspace_focus_pending = false; ++ bool workspace_switch_started = false; ++ uint64_t workspace_switch_focus_ts = 0; + wf::option_wrapper_t<bool> focus_on_workspace_change{"follow-focus/focus_on_workspace_change"}; + + wf::option_wrapper_t<bool> should_change_view{"follow-focus/change_view"}; +@@ -172,7 +176,8 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + if (!should_change_view || !focus_on_workspace_change || + output != wf::get_core().seat->get_active_output() || + !output->can_activate_plugin(wf::CAPABILITY_MANAGE_COMPOSITOR) || +- !view || view != focus_view || view->get_output() != output) ++ !view || view != focus_view || view->get_output() != output || ++ workspace_focus_overridden()) + { + return false; + } +@@ -183,6 +188,29 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + }); + } + ++ // Remember the seat's focus timestamp when a workspace switch begins, so ++ // focus given explicitly as part of the switch (scale, switcher, ++ // activation requests, a window carried along) can be told apart from ++ // the core's refocus after the switch, which does not update timestamps. ++ // Requests are serviced synchronously, so the mark expires on idle. ++ void mark_workspace_switch_start() ++ { ++ if (!workspace_switch_started && !workspace_focus_pending) ++ { ++ workspace_switch_started = true; ++ workspace_switch_focus_ts = wf::get_core().seat->get_last_focus_timestamp(); ++ workspace_switch_idle.run_once([=] () { workspace_switch_started = false; }); ++ } ++ } ++ ++ bool workspace_focus_overridden() ++ { ++ auto active = wf::toplevel_cast(wf::get_core().seat->get_active_view()); ++ return active && (active->get_output() == output) && ++ (wf::get_focus_timestamp(active) > workspace_switch_focus_ts) && ++ output->wset()->view_visible_on(active, output->wset()->get_current_workspace()); ++ } ++ + // A workspace switch can put a window under a stationary pointer. Wait + // until the switcher's animation/grab is finished, then use the normal + // follow-focus delay and raise policy without requiring pointer movement. +@@ -195,8 +223,11 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + return; + } + ++ // A lock (output inhibited) or an explicit focus since the switch ++ // makes the pending pointer focus stale; drop it. + if (!should_change_view || !focus_on_workspace_change || +- output != wf::get_core().seat->get_active_output()) ++ output != wf::get_core().seat->get_active_output() || ++ output->is_inhibited() || workspace_focus_overridden()) + { + workspace_focus_pending = false; + return; +@@ -221,12 +252,30 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + { + if (ev->old_viewport != ev->new_viewport) + { ++ mark_workspace_switch_start(); ++ workspace_switch_started = false; + change_view_focus.disconnect(); + workspace_focus_pending = should_change_view && focus_on_workspace_change; + schedule_workspace_focus(); + } + }; + ++ wf::signal::connection_t<wf::workspace_change_request_signal> workspace_change_request = ++ [=] (wf::workspace_change_request_signal *ev) ++ { ++ if (ev->old_viewport != ev->new_viewport) ++ { ++ mark_workspace_switch_start(); ++ } ++ }; ++ ++ // Emitted for views carried along with a switch, before they are focused. ++ wf::signal::connection_t<wf::view_change_workspace_signal> view_change_workspace = ++ [=] (wf::view_change_workspace_signal *ev) ++ { ++ mark_workspace_switch_start(); ++ }; ++ + wf::signal::connection_t<wf::output_plugin_activated_changed_signal> plugin_changed = + [=] (wf::output_plugin_activated_changed_signal *ev) + { +@@ -256,6 +305,8 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + { + wf::get_core().connect(&pointer_motion); + wf::get_core().connect(&pointer_motion_absolute); ++ output->connect(&workspace_change_request); ++ output->connect(&view_change_workspace); + output->connect(&workspace_changed); + output->connect(&plugin_changed); + } +@@ -263,6 +314,7 @@ class wayfire_follow_focus : public wf::per_output_plugin_instance_t + void fini() override + { + workspace_focus_idle.disconnect(); ++ workspace_switch_idle.disconnect(); + change_output_focus.disconnect(); + change_view_focus.disconnect(); + } diff --git a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r5.ebuild b/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r6.ebuild index 02fe0199f8af..c25403fc7b21 100644 --- a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r5.ebuild +++ b/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r6.ebuild @@ -54,6 +54,7 @@ PATCHES=( "${FILESDIR}/wayfire-plugins-extra-0.11.0-force-fullscreen-teardown.patch" "${FILESDIR}/wayfire-plugins-extra-0.11.2-force-fullscreen-physical-scope.patch" "${FILESDIR}/wayfire-plugins-extra-0.11.2-join-views-multi-view.patch" + "${FILESDIR}/wayfire-plugins-extra-0.11.2-follow-focus-explicit.patch" ) src_install() { diff --git a/gui-wm/wayfire/files/wayfire-0.11.0-r71-cube-teardown-backgrounds.patch b/gui-wm/wayfire/files/wayfire-0.11.0-r71-cube-teardown-backgrounds.patch new file mode 100644 index 000000000000..6ae91ff09170 --- /dev/null +++ b/gui-wm/wayfire/files/wayfire-0.11.0-r71-cube-teardown-backgrounds.patch @@ -0,0 +1,149 @@ +diff --git a/plugins/cube/cube-background.hpp b/plugins/cube/cube-background.hpp +index c6e3f48..500be89 100644 +--- a/plugins/cube/cube-background.hpp ++++ b/plugins/cube/cube-background.hpp +@@ -9,6 +9,14 @@ class wf_cube_background_base + public: + virtual void render_frame(const wf::render_target_t& fb, + wf_cube_animation_attribs& attribs) = 0; ++ // Backgrounds without a camera-independent pose ignore follow_cube. ++ virtual void render_frame(const wf::render_target_t& fb, ++ wf_cube_animation_attribs& attribs, bool follow_cube) ++ { ++ (void)follow_cube; ++ render_frame(fb, attribs); ++ } ++ + virtual ~wf_cube_background_base() = default; + }; + +diff --git a/plugins/cube/cube.cpp b/plugins/cube/cube.cpp +index 3b4b857..ce9614b 100644 +--- a/plugins/cube/cube.cpp ++++ b/plugins/cube/cube.cpp +@@ -1316,7 +1316,12 @@ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer + return; + } + +- wf::scene::remove_child(render_node); ++ // A render pause may already have removed the node. ++ if (render_node) ++ { ++ wf::scene::remove_child(render_node); ++ } ++ + output->render->damage_whole(); + + render_node = nullptr; +@@ -1712,6 +1717,29 @@ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer + { + hyperspace_background->render(data.target, data.damage, + wf::dimensions(output->get_relative_geometry()), opacity); ++ } else if (opacity > 0 && background && (std::string(background_mode) == "skydome" || ++ std::string(background_mode) == "cubemap")) ++ { ++ // The textures are opaque, so fade them with a constant alpha. ++ GL_CALL(glDisable(GL_DEPTH_TEST)); ++ GL_CALL(glDisable(GL_CULL_FACE)); ++ if (opacity < 1.0f) ++ { ++ GL_CALL(glEnable(GL_BLEND)); ++ GL_CALL(glBlendColor(0, 0, 0, opacity)); ++ GL_CALL(glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA)); ++ } else ++ { ++ GL_CALL(glDisable(GL_BLEND)); ++ } ++ ++ // At rest the pose is identity and both modes agree; while ++ // moving, follow the cube's yaw and pitch as upstream does. ++ wf::gles::for_each_scissor_rect(data.target, data.damage, [&] ++ { ++ background->render_frame(data.target, animation, !resting_renderer); ++ }); ++ GL_CALL(glDisable(GL_BLEND)); + } else if (opacity > 0) + { + // None clears the owned pixels even when the image opacity +@@ -1981,12 +2009,28 @@ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer + { + shutting_down = true; + background_timer.disconnect(); +- clear_post_move_window_dim(); + if (output->is_plugin_active(grab_interface.name)) + { + deactivate(); + } ++ ++ // deactivate() keeps the carried window hidden and dimmed while the ++ // modifiers are held, but the listeners that release it die with this ++ // instance. Restore it and drop every node of ours from the scene. ++ clear_fixed_workspace_overlay(false); ++ clear_post_move_window_dim(); + remove_rest_renderer(); ++ if (render_node) ++ { ++ wf::scene::remove_child(render_node); ++ render_node = nullptr; ++ } ++ ++ if (fixed_top_node) ++ { ++ wf::scene::remove_child(fixed_top_node); ++ fixed_top_node = nullptr; ++ } + + wf::gles::run_in_context_if_gles([&] + { +diff --git a/plugins/cube/cubemap.hpp b/plugins/cube/cubemap.hpp +index e68c8e1..6360612 100644 +--- a/plugins/cube/cubemap.hpp ++++ b/plugins/cube/cubemap.hpp +@@ -9,12 +9,12 @@ class wf_cube_background_cubemap : public wf_cube_background_base + wf_cube_background_cubemap(); + virtual void render_frame(const wf::render_target_t& fb, + wf_cube_animation_attribs& attribs) override; ++ void render_frame(const wf::render_target_t& fb, ++ wf_cube_animation_attribs& attribs, bool follow_cube) override; + + ~wf_cube_background_cubemap(); + + private: +- void render_frame(const wf::render_target_t& fb, +- wf_cube_animation_attribs& attribs, bool follow_cube); + void reload_texture(); + void create_program(); + +diff --git a/plugins/cube/simple-background.hpp b/plugins/cube/simple-background.hpp +index 470aadf..7769269 100644 +--- a/plugins/cube/simple-background.hpp ++++ b/plugins/cube/simple-background.hpp +@@ -9,6 +9,7 @@ class wf_cube_simple_background : public wf_cube_background_base + + public: + wf_cube_simple_background(); ++ using wf_cube_background_base::render_frame; + virtual void render_frame(const wf::render_target_t& fb, + wf_cube_animation_attribs& attribs) override; + }; +diff --git a/plugins/cube/skydome.hpp b/plugins/cube/skydome.hpp +index a64bce9..9df12d0 100644 +--- a/plugins/cube/skydome.hpp ++++ b/plugins/cube/skydome.hpp +@@ -11,14 +11,14 @@ class wf_cube_background_skydome : public wf_cube_background_base + wf_cube_background_skydome(wf::output_t *output); + virtual void render_frame(const wf::render_target_t& fb, + wf_cube_animation_attribs& attribs) override; ++ void render_frame(const wf::render_target_t& fb, ++ wf_cube_animation_attribs& attribs, bool follow_cube) override; + + virtual ~wf_cube_background_skydome(); + + private: + wf::output_t *output; + +- void render_frame(const wf::render_target_t& fb, +- wf_cube_animation_attribs& attribs, bool follow_cube); + void load_program(); + void fill_vertices(); + void reload_texture(); diff --git a/gui-wm/wayfire/files/wayfire-0.11.0-r71-stitch-noop-reload.patch b/gui-wm/wayfire/files/wayfire-0.11.0-r71-stitch-noop-reload.patch new file mode 100644 index 000000000000..169861ff907a --- /dev/null +++ b/gui-wm/wayfire/files/wayfire-0.11.0-r71-stitch-noop-reload.patch @@ -0,0 +1,170 @@ +diff --git a/src/core/output-layout.cpp b/src/core/output-layout.cpp +index 1aeb6be..d481851 100644 +--- a/src/core/output-layout.cpp ++++ b/src/core/output-layout.cpp +@@ -1411,6 +1411,38 @@ class output_layout_t::impl + wf::option_wrapper_t<bool> stitch_enabled{"core/stitch_enabled"}; + wf::option_wrapper_t<std::string> stitch_outputs{"core/stitch_outputs"}; + ++ /* Inputs and result of the last stitched reconfiguration, so that a ++ * config reload which changes nothing relevant does not rebuild the ++ * group and re-scope every view. */ ++ struct applied_stitch_t ++ { ++ std::string members; ++ int max_buffer_size; ++ output_configuration_t requested; ++ output_configuration_t applied; ++ std::string scope_settings; ++ }; ++ std::optional<applied_stitch_t> applied_stitch; ++ ++ /** Options read by window scoping, which reload re-applies to views. */ ++ std::string window_scope_settings() ++ { ++ std::string key; ++ for (auto name : {"core/window_scope", "core/window_scope_rules", "grid/zone_gap"}) ++ { ++ auto opt = get_core().config->get_option(name); ++ key += (opt ? opt->get_value_str() : "") + '\n'; ++ } ++ ++ for (auto& [handle, lo] : outputs) ++ { ++ auto opt = lo->config_section ? lo->config_section->get_option_or("zone_gap") : nullptr; ++ key += std::string(handle->name) + '=' + (opt ? opt->get_value_str() : "") + '\n'; ++ } ++ ++ return key; ++ } ++ + void release_mirror_buffers() + { + // A source may be removed in the same transaction. Release its cursor +@@ -1805,6 +1837,11 @@ class output_layout_t::impl + } + + outputs.erase(to_remove); ++ if (applied_stitch) ++ { ++ applied_stitch->requested.erase(to_remove); ++ applied_stitch->applied.erase(to_remove); ++ } + + /* If no physical outputs, then at least the noop output */ + assert(get_outputs().size() || is_shutting_down()); +@@ -1992,7 +2029,31 @@ class output_layout_t::impl + { + configuration[output].source = OUTPUT_IMAGE_SOURCE_NONE; + } ++ } ++ ++ std::string members = stitch_outputs; ++ int max_buffer_size = wf::option_wrapper_t<int>("workarounds/max_buffer_size"); ++ if (stitch_output && !removing && stitch_enabled && applied_stitch && ++ (applied_stitch->members == members) && (applied_stitch->max_buffer_size == max_buffer_size) && ++ (applied_stitch->requested == configuration) && ++ (applied_stitch->applied == get_current_configuration())) ++ { ++ // Group, member slices and all other outputs are already as ++ // configured. Only re-scope views if window scope options changed. ++ auto scope_settings = window_scope_settings(); ++ if (applied_stitch->scope_settings != scope_settings) ++ { ++ applied_stitch->scope_settings = std::move(scope_settings); ++ rescope_views(scoped_views); ++ } + ++ return; ++ } ++ ++ applied_stitch.reset(); ++ auto requested = configuration; ++ for (auto& [output, layout_output] : this->outputs) ++ { + layout_output->stitch_source_box.reset(); + } + +@@ -2004,11 +2065,13 @@ class output_layout_t::impl + + // The public mirror state does not include source rectangles; reapply + // active groups even when only member order or slice geometry changed. ++ bool applied = false; + if (stitch_output || configuration != get_current_configuration()) + { + if (test_configuration(configuration)) + { + apply_configuration(configuration); ++ applied = true; + } + } + +@@ -2022,6 +2085,12 @@ class output_layout_t::impl + LOGI("Combined desktop: ", stitch_output->width, "x", stitch_output->height, + " on VIRTUAL-1"); + } ++ ++ if (applied && wo) ++ { ++ applied_stitch = applied_stitch_t{members, max_buffer_size, std::move(requested), ++ get_current_configuration(), window_scope_settings()}; ++ } + } else + { + for (auto& [handle, lo] : outputs) +@@ -2032,6 +2101,11 @@ class output_layout_t::impl + + destroy_stitch_output(); + } ++ rescope_views(scoped_views); ++ } ++ ++ void rescope_views(std::vector<std::pair<wayfire_toplevel_view, wf::point_t>>& scoped_views) ++ { + for (auto& [view, workspace] : scoped_views) + { + if (!view->is_mapped() || !view->get_output()) continue; +@@ -2339,6 +2413,16 @@ class output_layout_t::impl + wlr_configuration); + } + ++ void set_stitch_member_power(output_layout_output_t& lo, bool enabled) ++ { ++ lo.stitch_power_off = !enabled; ++ lo.apply_state(lo.current_state); ++ if (auto source = find_output(lo.current_state.mirror_from)) ++ { ++ source->render->damage_whole(); ++ } ++ } ++ + void set_power_mode(wlr_output_power_v1_set_mode_event *ev) + { + LOGD("output: ", ev->output->name, " power mode: ", ev->mode); +@@ -2358,7 +2442,10 @@ class output_layout_t::impl + } + if (stitch_output && outputs.at(target)->stitch_source_box) + { +- target = stitch_output; ++ // Power only this member's slice. wlroots reports the member's ++ // new mode to clients when the connector commits enabled/disabled. ++ set_stitch_member_power(*outputs.at(target), !wants_dpms); ++ return; + } + + config[target].source = (wants_dpms ? OUTPUT_IMAGE_SOURCE_DPMS : OUTPUT_IMAGE_SOURCE_SELF); +@@ -2386,13 +2473,7 @@ class output_layout_t::impl + + if (lo->stitch_source_box) + { +- lo->stitch_power_off = !enabled; +- lo->apply_state(lo->current_state); +- if (auto source = find_output(lo->current_state.mirror_from)) +- { +- source->render->damage_whole(); +- } +- ++ set_stitch_member_power(*lo, enabled); + return true; + } + diff --git a/gui-wm/wayfire/files/wayfire-0.11.0-r71-switch-tile-exclusivity.patch b/gui-wm/wayfire/files/wayfire-0.11.0-r71-switch-tile-exclusivity.patch new file mode 100644 index 000000000000..53489523c71b --- /dev/null +++ b/gui-wm/wayfire/files/wayfire-0.11.0-r71-switch-tile-exclusivity.patch @@ -0,0 +1,325 @@ +diff --git a/plugins/cube/cube.cpp b/plugins/cube/cube.cpp +index 3b4b857..813a459 100644 +--- a/plugins/cube/cube.cpp ++++ b/plugins/cube/cube.cpp +@@ -907,13 +907,13 @@ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer + output->render->schedule_redraw(); + } + +- void animate_workspace_delta(wf::point_t delta, ++ bool animate_workspace_delta(wf::point_t delta, + wf::point_t anchor_workspace, + wayfire_toplevel_view fixed_view) + { + if (!activate()) + { +- return; ++ return false; + } + + end_mouse_overview(); +@@ -957,14 +957,15 @@ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer + start_animation(); + update_view_matrix(); + output->render->schedule_redraw(); ++ return true; + } + + wf::signal::connection_t<cube_workspace_animation_signal> + on_cube_workspace_animation = [=] (cube_workspace_animation_signal *ev) + { +- animate_workspace_delta(ev->delta, ev->anchor_workspace, +- ev->fixed_view); +- ev->carried_out = true; ++ // Report failure so vswitch can fall back to its own animation. ++ ev->carried_out = animate_workspace_delta(ev->delta, ++ ev->anchor_workspace, ev->fixed_view); + }; + + wf::signal::connection_t<cube_render_pause_signal> +diff --git a/plugins/cube/meson.build b/plugins/cube/meson.build +index 48ea276..af57b9a 100644 +--- a/plugins/cube/meson.build ++++ b/plugins/cube/meson.build +@@ -2,5 +2,7 @@ animiate = shared_module('cube', + ['cube.cpp', 'cubemap.cpp', 'skydome.cpp', 'simple-background.cpp', 'warp-background.cpp', 'hyperspace-background.cpp'], + include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs], + dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], ++ link_args: ['-Wl,--version-script=' + meson.project_source_root() / 'plugins/common/plugin-exports.map'], ++ link_depends: '../../plugins/common/plugin-exports.map', + install: true, + install_dir: join_paths(get_option('libdir'), 'wayfire')) +diff --git a/plugins/grid/grid.cpp b/plugins/grid/grid.cpp +index ed24b42..46bf79e 100644 +--- a/plugins/grid/grid.cpp ++++ b/plugins/grid/grid.cpp +@@ -294,6 +294,8 @@ class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracke + public: + void init() override + { ++ wf::get_core().store_data(std::make_unique<wf::custom_data_t>(), ++ wf::grid::grid_loaded_marker); + init_output_tracking(); + // Older metadata leaves new ini options untyped, or absent. Promote + // them before loading activators, preserving any configured binding. +@@ -465,6 +467,7 @@ class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracke + void fini() override + { + fini_output_tracking(); ++ wf::get_core().erase_data(wf::grid::grid_loaded_marker); + } + + bool can_adjust_view(wayfire_toplevel_view view) +diff --git a/plugins/grid/meson.build b/plugins/grid/meson.build +index e107495..71c5746 100644 +--- a/plugins/grid/meson.build ++++ b/plugins/grid/meson.build +@@ -5,6 +5,8 @@ all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, json, plugin_pch_dep] + shared_module('grid', ['grid.cpp'], + include_directories: all_include_dirs, + dependencies: all_deps, ++ link_args: ['-Wl,--version-script=' + meson.project_source_root() / 'plugins/common/plugin-exports.map'], ++ link_depends: '../../plugins/common/plugin-exports.map', + install: true, + install_dir: conf_data.get('PLUGIN_PATH')) + +diff --git a/plugins/grid/wayfire/plugins/grid.hpp b/plugins/grid/wayfire/plugins/grid.hpp +index d981735..1da956e 100644 +--- a/plugins/grid/wayfire/plugins/grid.hpp ++++ b/plugins/grid/wayfire/plugins/grid.hpp +@@ -23,6 +23,13 @@ enum move_op_t + MOVE_OP_FINISH_PREVIEW = 3, + }; + ++/** ++ * Core custom data name present while the Grid plugin is loaded, so that ++ * move-snap leaves view_tile_request_signal to Grid regardless of load order. ++ * Checked by name only: custom data types do not compare across plugin builds. ++ */ ++static constexpr const char *grid_loaded_marker = "wf-grid-plugin-loaded"; ++ + /** + * name: request + * on: core +diff --git a/plugins/single_plugins/meson.build b/plugins/single_plugins/meson.build +index f2acc10..716b824 100644 +--- a/plugins/single_plugins/meson.build ++++ b/plugins/single_plugins/meson.build +@@ -18,11 +18,19 @@ extra_libs = { + 'vswipe': [workspace_wall], + } + ++# Export only the plugin entry points, so that live-swapped revisions do not ++# bind to each other's inline/template symbols. ++export_map = 'plugins/common/plugin-exports.map' ++export_map_plugins = ['move'] ++ + foreach plugin : plugins + plugin_module = shared_module(plugin, plugin + '.cpp', + include_directories: all_include_dirs, + dependencies: all_deps, + link_with: extra_libs.get(plugin, []), ++ link_args: plugin in export_map_plugins ? ++ ['-Wl,--version-script=' + meson.project_source_root() / export_map] : [], ++ link_depends: plugin in export_map_plugins ? '../../' + export_map : [], + install: true, + install_dir: conf_data.get('PLUGIN_PATH')) + +diff --git a/plugins/single_plugins/move-snap.hpp b/plugins/single_plugins/move-snap.hpp +index ddcb70f..2c2fce5 100644 +--- a/plugins/single_plugins/move-snap.hpp ++++ b/plugins/single_plugins/move-snap.hpp +@@ -9,6 +9,7 @@ + #include <wayfire/workspace-set.hpp> + #include <wayfire/toplevel.hpp> + #include <wayfire/plugins/common/move-drag-interface.hpp> ++#include <wayfire/plugins/grid.hpp> + #include <wayfire/plugins/wobbly/wobbly-signal.hpp> + #include <chrono> + #include "move-snap-map.hpp" +@@ -300,7 +301,8 @@ class move_snap_t : public wf::per_output_tracker_mixin_t<> + // Without Grid, ordinary tiling reflows still preserve half/quarter sizes. + wf::signal::connection_t<wf::view_tile_request_signal> on_tile = [this] (auto *ev) + { +- if (ev->carried_out || !eligible(ev->view) || !ev->edges) return; ++ if (ev->carried_out || wf::get_core().has_data(wf::grid::grid_loaded_marker)) return; ++ if (!eligible(ev->view) || !ev->edges) return; + int slot = wf::grid::get_slot_from_tiled_edges(ev->edges); + if (!slot) return; + auto area = wf::window_scope_workarea(ev->view); +diff --git a/plugins/tile/tile-plugin.cpp b/plugins/tile/tile-plugin.cpp +index 5d66c4d..ab03b2f 100644 +--- a/plugins/tile/tile-plugin.cpp ++++ b/plugins/tile/tile-plugin.cpp +@@ -183,10 +183,26 @@ class tile_output_plugin_t : public wf::pointer_interaction_t, public wf::custom + ev->carried_out = true; + stop_controller(true); + auto& data = tile_workspace_set_data_t::get(ev->view->get_wset()); +- data.reconcile_view(ev->view, ev->workspace); ++ reconcile_requested_view(data, ev->view, ev->workspace); + data.update_root_size(); + }; + ++ /** ++ * Most tile/fullscreen requests carry the output's current workspace, not ++ * the view's. Only an explicit window-scope reflow may move the node to ++ * another workspace; otherwise just reconcile its region on its own one. ++ */ ++ void reconcile_requested_view(tile_workspace_set_data_t& data, ++ wayfire_toplevel_view view, wf::point_t requested) ++ { ++ auto vp = view->has_data<wf::window_scope_reflow_t>() ? ++ std::optional<wf::point_t>{requested} : data.workspace_of(view); ++ if (vp) ++ { ++ data.reconcile_view(view, *vp); ++ } ++ } ++ + wf::signal::connection_t<view_fullscreen_request_signal> on_fullscreen_request = + [=] (view_fullscreen_request_signal *ev) + { +@@ -196,8 +212,9 @@ class tile_output_plugin_t : public wf::pointer_interaction_t, public wf::custom + } + + ev->carried_out = true; +- tile_workspace_set_data_t::get(ev->view->get_wset()).reconcile_view(ev->view, ev->workspace); +- tile_workspace_set_data_t::get(ev->view->get_wset()).set_view_fullscreen(ev->view, ev->state); ++ auto& data = tile_workspace_set_data_t::get(ev->view->get_wset()); ++ reconcile_requested_view(data, ev->view, ev->workspace); ++ data.set_view_fullscreen(ev->view, ev->state); + }; + + void change_view_workspace(wayfire_toplevel_view view, std::optional<wf::point_t> vp = {}) +diff --git a/plugins/tile/tile-wset.hpp b/plugins/tile/tile-wset.hpp +index a695bc3..6a6f17a 100644 +--- a/plugins/tile/tile-wset.hpp ++++ b/plugins/tile/tile-wset.hpp +@@ -117,8 +117,27 @@ class tile_workspace_set_data_t : public wf::custom_data_t + return output->workarea->get_workarea(); + } + ++ wf::point_t clamp_workspace(wf::point_t vp) ++ { ++ auto size = wset.lock()->get_workspace_grid_size(); ++ return {std::clamp(vp.x, 0, size.width - 1), std::clamp(vp.y, 0, size.height - 1)}; ++ } ++ ++ /** The workspace whose (virtual or physical) root holds the view's node. */ ++ std::optional<wf::point_t> workspace_of(wayfire_toplevel_view view) ++ { ++ auto node = tile::view_node_t::get_node(view); ++ if (!node) return {}; ++ auto root = tile::get_root(node); ++ std::optional<wf::point_t> result; ++ for_each_root([&] (auto& candidate, auto vp, const auto&) ++ { if (candidate.get() == root.get()) result = vp; }); ++ return result; ++ } ++ + std::unique_ptr<tile::tree_node_t>& root_at(wf::point_t vp, const std::string& monitor = "") + { ++ vp = clamp_workspace(vp); + if (monitor.empty()) return roots[vp.x][vp.y]; + auto& grid = physical_roots[monitor]; + auto size = wset.lock()->get_workspace_grid_size(); +@@ -166,6 +185,7 @@ class tile_workspace_set_data_t : public wf::custom_data_t + { + auto node = tile::view_node_t::get_node(view); + if (!node) return; ++ vp = clamp_workspace(vp); + auto& destination = root_at(vp, region_for(view)); + if (tile::get_root(node).get() == destination.get()) return; + autocommit_transaction_t tx; +diff --git a/plugins/vswitch/vswitch.cpp b/plugins/vswitch/vswitch.cpp +index 34b63be..3ad44e8 100644 +--- a/plugins/vswitch/vswitch.cpp ++++ b/plugins/vswitch/vswitch.cpp +@@ -387,6 +387,15 @@ class vswitch : public wf::per_output_plugin_instance_t + on_done(); + } + ++ /** The workspace has already changed: slide in from @old_ws. */ ++ void slide_from(wf::point_t old_ws) ++ { ++ auto cws = output->wset()->get_current_workspace(); ++ animation.dx.set(old_ws.x - cws.x, 0); ++ animation.dy.set(old_ws.y - cws.y, 0); ++ animation.start(); ++ } ++ + private: + std::function<void()> on_done; + }; +@@ -665,27 +674,36 @@ class vswitch : public wf::per_output_plugin_instance_t + + if (!is_active()) + { ++ // Respect plugin exclusivity (expo, scale, ...) like upstream's ++ // start_switch() did. ++ if (!output->can_activate_plugin(&grab_interface)) ++ { ++ return false; ++ } ++ + if (fixed_view) + { + clear_held_window_dim(); + wf::vswitch::move_view(fixed_view, target_ws); + output->wset()->set_workspace(target_ws); + wf::get_core().seat->focus_view(view); +- +- cube_workspace_animation_signal ev; +- ev.delta = animation_delta; +- ev.anchor_workspace = cws; +- ev.fixed_view = fixed_view; +- output->emit(&ev); +- return true; ++ } else ++ { ++ output->wset()->set_workspace(target_ws); + } + +- output->wset()->set_workspace(target_ws); + cube_workspace_animation_signal ev; + ev.delta = animation_delta; + ev.anchor_workspace = cws; +- ev.fixed_view = nullptr; ++ ev.fixed_view = fixed_view; + output->emit(&ev); ++ ++ // Without a cube to animate it, use vswitch's own slide. ++ if (!ev.carried_out && start_switch()) ++ { ++ algorithm->slide_from(cws); ++ } ++ + return true; + } + +@@ -695,8 +713,7 @@ class vswitch : public wf::per_output_plugin_instance_t + } + + algorithm->set_overlay_view(toplevel_cast(view)); +- algorithm->set_target_workspace( +- output->wset()->get_current_workspace() + delta); ++ algorithm->set_target_workspace(target_ws); + + return true; + } +@@ -781,15 +798,6 @@ class vswitch : public wf::per_output_plugin_instance_t + return; + } + +- if (!ev->fixed_views.empty()) +- { +- cube_workspace_animation_signal cube_ev; +- cube_ev.delta = ev->new_viewport - ev->old_viewport; +- cube_ev.anchor_workspace = ev->old_viewport; +- cube_ev.fixed_view = ev->fixed_views[0]; +- output->emit(&cube_ev); +- } +- + if (is_active()) + { + ev->carried_out = add_direction(ev->new_viewport - ev->old_viewport); diff --git a/gui-wm/wayfire/wayfire-0.11.0-r70.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r71.ebuild index 8efecefbb78f..ce38e25cb609 100644 --- a/gui-wm/wayfire/wayfire-0.11.0-r70.ebuild +++ b/gui-wm/wayfire/wayfire-0.11.0-r71.ebuild @@ -156,6 +156,9 @@ PATCHES=( "${FILESDIR}"/${P}-r67-grid-gap.patch "${FILESDIR}"/${P}-r68-ipc-control.patch "${FILESDIR}"/${P}-ipc-backpressure.patch + "${FILESDIR}"/${P}-r71-switch-tile-exclusivity.patch + "${FILESDIR}"/${P}-r71-cube-teardown-backgrounds.patch + "${FILESDIR}"/${P}-r71-stitch-noop-reload.patch ) # The local patches change the plugin ABI (scene layer indices, class layouts). |
