wayfire-0.10.0/0000755000175000017500000000000015106126711013120 5ustar dkondordkondorwayfire-0.10.0/wayfire.ini0000664000175000017500000002365015053502647015306 0ustar dkondordkondor# Default config for Wayfire # # Copy this to ~/.config/wayfire.ini and edit it to your liking. # # Take the tutorial to get started. # https://github.com/WayfireWM/wayfire/wiki/Tutorial # # Read the Configuration document for a complete reference. # https://github.com/WayfireWM/wayfire/wiki/Configuration # Input configuration ────────────────────────────────────────────────────────── # Example configuration: # # [input] # xkb_layout = us,fr # xkb_variant = dvorak,bepo # xkb_options = grp:win_space_toggle # # See Input options for a complete reference. # https://github.com/WayfireWM/wayfire/wiki/Configuration#input # Output configuration ───────────────────────────────────────────────────────── # Example configuration: # # [output:eDP-1] # mode = 1920x1080@60000 # position = 0,0 # transform = normal # scale = 1.000000 # # You can get the names of your outputs with wlr-randr. # https://github.com/emersion/wlr-randr # # See also kanshi for configuring your outputs automatically. # https://wayland.emersion.fr/kanshi/ # # See Output options for a complete reference. # https://github.com/WayfireWM/wayfire/wiki/Configuration#output # Core options ───────────────────────────────────────────────────────────────── [core] # List of plugins to be enabled. # See the Configuration document for a complete list. plugins = \ alpha \ animate \ autostart \ command \ cube \ decoration \ expo \ fast-switcher \ fisheye \ foreign-toplevel \ grid \ gtk-shell \ idle \ invert \ move \ oswitch \ place \ resize \ session-lock \ shortcuts-inhibit \ switcher \ vswitch \ wayfire-shell \ window-rules \ wm-actions \ wobbly \ wrot \ zoom # Note: [blur] is not enabled by default, because it can be resource-intensive. # Feel free to add it to the list if you want it. # You can find its documentation here: # https://github.com/WayfireWM/wayfire/wiki/Configuration#blur # Close focused window. close_top_view = KEY_Q | KEY_F4 # Workspaces arranged into a grid: 3 × 3. vwidth = 3 vheight = 3 # Prefer client-side decoration or server-side decoration preferred_decoration_mode = client # Mouse bindings ─────────────────────────────────────────────────────────────── # Drag windows by holding down Super and left mouse button. [move] activate = BTN_LEFT # Resize them with right mouse button + Super. [resize] activate = BTN_RIGHT # Zoom in the desktop by scrolling + Super. [zoom] modifier = # Change opacity by scrolling with Super + Alt. [alpha] modifier = # Rotate windows with the mouse. [wrot] activate = BTN_RIGHT # Fisheye effect. [fisheye] toggle = KEY_F # Startup commands ───────────────────────────────────────────────────────────── [autostart] #Gtk+3 applications slow startup or .desktop files not opening #https://github.com/WayfireWM/wayfire/wiki/Tips-&-Tricks#gtk3-applications-slow-startup-or-desktop-files-not-opening 0_env = dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY # Automatically start background and panel. # Set to false if you want to override the default clients. autostart_wf_shell = true # Set the wallpaper, start a panel and dock if you want one. # https://github.com/WayfireWM/wf-shell # # These are started by the autostart_wf_shell option above. # # background = wf-background # panel = wf-panel # # You may also use wf-dock, # which is included in wf-shell but is not enabled by default. # # dock = wf-dock # Output configuration # https://wayland.emersion.fr/kanshi/ outputs = kanshi # Notifications # https://wayland.emersion.fr/mako/ notifications = mako # Screen color temperature # https://sr.ht/~kennylevinsen/wlsunset/ gamma = wlsunset # Idle configuration # https://github.com/swaywm/swayidle # https://github.com/swaywm/swaylock idle = swayidle before-sleep swaylock # XDG desktop portal # Needed by some GTK applications portal = /usr/libexec/xdg-desktop-portal # Example configuration: # # [idle] # toggle = KEY_Z # screensaver_timeout = 300 # dpms_timeout = 600 # # Disables the compositor going idle with Super + z. # This will lock your screen after 300 seconds of inactivity, then turn off # your displays after another 300 seconds. # Applications ───────────────────────────────────────────────────────────────── [command] # Start a terminal # https://github.com/alacritty/alacritty binding_terminal = KEY_ENTER command_terminal = alacritty # Start your launcher # https://hg.sr.ht/~scoopta/wofi # Note: Add mode=run or mode=drun to ~/.config/wofi/config. # You can also specify the mode with --show option. binding_launcher = KEY_ENTER command_launcher = wofi # Screen locker # https://github.com/swaywm/swaylock binding_lock = KEY_ESC command_lock = swaylock # Logout # https://github.com/ArtsyMacaw/wlogout binding_logout = KEY_ESC command_logout = wlogout # Screenshots # https://wayland.emersion.fr/grim/ # https://wayland.emersion.fr/slurp/ binding_screenshot = KEY_PRINT command_screenshot = grim $(date '+%F_%T').webp binding_screenshot_interactive = KEY_PRINT command_screenshot_interactive = slurp | grim -g - $(date '+%F_%T').webp # Volume controls # https://alsa-project.org repeatable_binding_volume_up = KEY_VOLUMEUP command_volume_up = amixer set Master 5%+ repeatable_binding_volume_down = KEY_VOLUMEDOWN command_volume_down = amixer set Master 5%- binding_mute = KEY_MUTE command_mute = amixer set Master toggle # Screen brightness # https://haikarainen.github.io/light/ repeatable_binding_light_up = KEY_BRIGHTNESSUP command_light_up = light -A 5 repeatable_binding_light_down = KEY_BRIGHTNESSDOWN command_light_down = light -U 5 # Windows ────────────────────────────────────────────────────────────────────── # Actions related to window management functionalities. # # Example configuration: # # [wm-actions] # toggle_fullscreen = KEY_F # toggle_always_on_top = KEY_X # toggle_sticky = KEY_X # Position the windows in certain regions of the output. [grid] # # ⇱ ↑ ⇲ │ 7 8 9 # ← f → │ 4 5 6 # ⇱ ↓ ⇲ d │ 1 2 3 0 # ‾ ‾ slot_bl = KEY_KP1 slot_b = KEY_KP2 slot_br = KEY_KP3 slot_l = KEY_LEFT | KEY_KP4 slot_c = KEY_UP | KEY_KP5 slot_r = KEY_RIGHT | KEY_KP6 slot_tl = KEY_KP7 slot_t = KEY_KP8 slot_tr = KEY_KP9 # Restore default. restore = KEY_DOWN | KEY_KP0 # Change active window with an animation. [switcher] next_view = KEY_TAB prev_view = KEY_TAB # Simple active window switcher. [fast-switcher] activate = KEY_ESC # Workspaces ─────────────────────────────────────────────────────────────────── # Switch to workspace. [vswitch] binding_left = KEY_LEFT binding_down = KEY_DOWN binding_up = KEY_UP binding_right = KEY_RIGHT # Move the focused window with the same key-bindings, but add Shift. with_win_left = KEY_LEFT with_win_down = KEY_DOWN with_win_up = KEY_UP with_win_right = KEY_RIGHT # Show the current workspace row as a cube. [cube] activate = BTN_LEFT # Switch to the next or previous workspace. #rotate_left = KEY_H #rotate_right = KEY_L # Show an overview of all workspaces. [expo] toggle = KEY_E # Select a workspace. # Workspaces are arranged into a grid of 3 × 3. # The numbering is left to right, line by line. # # ⇱ k ⇲ # h ⏎ l # ⇱ j ⇲ # ‾ ‾ # See core.vwidth and core.vheight for configuring the grid. select_workspace_1 = KEY_1 select_workspace_2 = KEY_2 select_workspace_3 = KEY_3 select_workspace_4 = KEY_4 select_workspace_5 = KEY_5 select_workspace_6 = KEY_6 select_workspace_7 = KEY_7 select_workspace_8 = KEY_8 select_workspace_9 = KEY_9 # Outputs ────────────────────────────────────────────────────────────────────── # Change focused output. [oswitch] # Switch to the next output. next_output = KEY_O # Same with the window. next_output_with_win = KEY_O # Invert the colors of the whole output. [invert] toggle = KEY_I # Send toggle menu event. [wayfire-shell] toggle_menu = # Rules ──────────────────────────────────────────────────────────────────────── # Example configuration: # # [window-rules] # maximize_alacritty = on created if app_id is "Alacritty" then maximize # # You can get the properties of your applications with the following command: # $ WAYLAND_DEBUG=1 alacritty 2>&1 | kak # # See Window rules for a complete reference. # https://github.com/WayfireWM/wayfire/wiki/Configuration#window-rules wayfire-0.10.0/test/0000775000175000017500000000000015053502647014110 5ustar dkondordkondorwayfire-0.10.0/test/misc/0000775000175000017500000000000015053502647015043 5ustar dkondordkondorwayfire-0.10.0/test/misc/safe-list-test.cpp0000664000175000017500000000327215053502647020417 0ustar dkondordkondor#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include TEST_CASE("Safe-list basics") { wf::safe_list_t list; list.push_back(5); list.push_back(6); REQUIRE(list.size() == 2); list.for_each([x = 0] (int i) mutable { if (x == 0) { REQUIRE(i == 5); } if (x == 1) { REQUIRE(i == 6); } REQUIRE(x <= 1); x++; }); list.remove_if([] (int i) { return i == 5; }); REQUIRE(list.size() == 1); list.for_each([x = 0] (int i) mutable { if (x == 0) { REQUIRE(i == 6); } REQUIRE(x <= 0); x++; }); } TEST_CASE("safe-list remove self") { using cb = std::function; wf::safe_list_t list; cb self; list.push_back(&self); list.for_each_reverse([&] (cb *c) { REQUIRE(c == &self); list.remove_all(c); }); } TEST_CASE("safe-list remove next") { using cb = std::function; wf::safe_list_t list; cb self, next; list.push_back(&self); list.push_back(&next); int calls = 0; list.for_each([&] (cb *c) { calls++; REQUIRE(calls <= 1); REQUIRE(c == &self); list.remove_all(&next); REQUIRE(list.back() == &self); }); } TEST_CASE("safe-list push next") { using cb = std::function; wf::safe_list_t list; cb self, next; list.push_back(&self); int calls = 0; list.for_each([&] (cb *c) { calls++; REQUIRE(calls <= 1); list.push_back(&next); }); REQUIRE(list.size() == 2); } wayfire-0.10.0/test/misc/meson.build0000664000175000017500000000053615053502647017211 0ustar dkondordkondortracking_allocator = executable( 'tracking_allocator', 'tracking-allocator.cpp', dependencies: libwayfire, install: false) test('Tracking factory test', tracking_allocator) safe_list = executable( 'safe_list', 'safe-list-test.cpp', dependencies: [doctest, wfconfig], install: false) test('Safe list test', safe_list) wayfire-0.10.0/test/misc/tracking-allocator.cpp0000664000175000017500000000257615053502647021341 0ustar dkondordkondor#include "wayfire/nonstd/tracking-allocator.hpp" #include "wayfire/signal-provider.hpp" #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include class base_t : public wf::signal::provider_t { public: using wf::signal::provider_t::provider_t; static int destroyed; virtual ~base_t() { ++destroyed; } }; class derived_t : public base_t { public: derived_t(int useless) { (void)useless; } virtual ~derived_t() = default; }; int base_t::destroyed = 0; TEST_CASE("Misc factory works") { auto& allocator = wf::tracking_allocator_t::get(); REQUIRE(&allocator == &wf::tracking_allocator_t::get()); REQUIRE((void*)&allocator != (void*)&wf::tracking_allocator_t::get()); auto obj_a = allocator.allocate(); REQUIRE(allocator.get_all().size() == 1); int destruct_events = 0; wf::signal::connection_t> on_destroy; { auto obj_b = allocator.allocate(5); on_destroy = [&destruct_events, expected = obj_b.get()] (wf::destruct_signal *ev) { REQUIRE(ev->object == expected); ++destruct_events; }; obj_b->connect(&on_destroy); REQUIRE(allocator.get_all().size() == 2); } REQUIRE(destruct_events == 1); REQUIRE(allocator.get_all().size() == 1); } wayfire-0.10.0/test/txn/0000775000175000017500000000000015053502647014721 5ustar dkondordkondorwayfire-0.10.0/test/txn/transaction-manager-test.cpp0000664000175000017500000002103715053502647022342 0ustar dkondordkondor#include "wayfire/signal-provider.hpp" #include "wayfire/txn/transaction-manager.hpp" #include "wayfire/util.hpp" #include #include #include #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include "transaction-test-object.hpp" #include #include "../../src/core/txn/transaction-manager-impl.hpp" static wf::txn::transaction_uptr new_tx() { return std::make_unique(0, [] (auto, auto) {}); } TEST_CASE("Simple transaction is scheduled and executed") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj = std::make_shared(false); auto tx = new_tx(); tx->add_object(obj); mgr.schedule_transaction(std::move(tx)); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj->number_committed == 1); REQUIRE(obj->number_applied == 0); obj->emit_ready(); REQUIRE(obj->number_committed == 1); REQUIRE(obj->number_applied == 1); REQUIRE(mgr.committed.size() == 0); REQUIRE(mgr.pending.size() == 0); REQUIRE(mgr.done.size() == 1); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); REQUIRE(mgr.committed.size() == 0); REQUIRE(mgr.pending.size() == 0); REQUIRE(mgr.done.size() == 0); } TEST_CASE("Transactions for the same object wait on each other") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj = std::make_shared(false); auto tx1 = new_tx(); tx1->add_object(obj); mgr.schedule_transaction(std::move(tx1)); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 0); auto tx2 = new_tx(); tx2->add_object(obj); mgr.schedule_transaction(std::move(tx2)); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 1); // tx1 is now ready, tx2 is committed obj->emit_ready(); REQUIRE(mgr.done.size() == 1); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj->number_committed == 2); REQUIRE(obj->number_applied == 1); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); REQUIRE(mgr.done.size() == 0); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj->number_committed == 2); REQUIRE(obj->number_applied == 1); obj->emit_ready(); REQUIRE(mgr.committed.size() == 0); REQUIRE(mgr.done.size() == 1); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj->number_applied == 2); } TEST_CASE("Transactions are merged correctly") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj_a = std::make_shared(false); auto obj_b = std::make_shared(false); auto obj_c = std::make_shared(false); auto tx0 = new_tx(); auto tx1 = new_tx(); auto tx2 = new_tx(); auto tx3 = new_tx(); auto tx4 = new_tx(); // Block the other transactions from happening tx0->add_object(obj_a); tx0->add_object(obj_b); tx0->add_object(obj_c); mgr.schedule_transaction(std::move(tx0)); tx1->add_object(obj_a); tx1->add_object(obj_b); tx2->add_object(obj_a); tx3->add_object(obj_b); tx3->add_object(obj_c); tx4->add_object(obj_a); tx4->add_object(obj_b); // tx1 is scheduled and committed immediately mgr.schedule_transaction(std::move(tx1)); // tx2, tx3, and tx4 should be merged together mgr.schedule_transaction(std::move(tx2)); mgr.schedule_transaction(std::move(tx3)); mgr.schedule_transaction(std::move(tx4)); REQUIRE(mgr.pending.size() == 1); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.done.size() == 0); REQUIRE(mgr.pending.front()->get_objects().size() == 3); } TEST_CASE("Transactions which are immediately ready also work") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj = std::make_shared(true); auto tx1 = new_tx(); tx1->add_object(obj); mgr.schedule_transaction(std::move(tx1)); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); // dispatch_idle will execute all idle callbacks: the first callback commits, then the transaction is // immediately applied, and after that, it is cleaned up on a second idle run. REQUIRE(mgr.done.size() == 0); REQUIRE(mgr.committed.size() == 0); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj->number_applied == 1); } TEST_CASE("Non-conflicting transactions are scheduled together") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj_a = std::make_shared(false); auto tx1 = new_tx(); tx1->add_object(obj_a); auto obj_b = std::make_shared(false); auto tx2 = new_tx(); tx2->add_object(obj_b); mgr.schedule_transaction(std::move(tx1)); mgr.schedule_transaction(std::move(tx2)); wl_event_loop_dispatch_idle(wf::wl_idle_call::loop); REQUIRE(mgr.done.size() == 0); REQUIRE(mgr.committed.size() == 2); REQUIRE(mgr.pending.size() == 0); REQUIRE(obj_a->number_committed == 1); REQUIRE(obj_b->number_committed == 1); } TEST_CASE("Schedule from apply()") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj_a = std::make_shared(true); auto obj_b = std::make_shared(true); auto tx1 = new_tx(); tx1->add_object(obj_a); auto tx2 = new_tx(); tx2->add_object(obj_b); obj_a->apply_callback = [&] { REQUIRE(obj_a->number_applied == 1); REQUIRE(obj_a->number_committed == 1); mgr.schedule_transaction(std::move(tx2)); }; mgr.schedule_transaction(std::move(tx1)); REQUIRE(mgr.committed.size() == 0); REQUIRE(mgr.pending.size() == 0); REQUIRE(mgr.done.size() == 2); REQUIRE(obj_b->number_committed == 1); REQUIRE(obj_b->number_applied == 1); } TEST_CASE("Schedule from apply() with blocking") { setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj_a = std::make_shared(false); auto obj_b = std::make_shared(true); auto tx1 = new_tx(); tx1->add_object(obj_a); auto tx2 = new_tx(); tx2->add_object(obj_b); bool added = false; obj_b->apply_callback = [&] { if (added) { return; } // Avoid infinite recursion, otherwise the second object will continuously be committed. added = true; auto tx1_2 = new_tx(); tx1_2->add_object(obj_a); auto tx2_2 = new_tx(); tx2_2->add_object(obj_b); REQUIRE(obj_a->number_applied == 0); REQUIRE(obj_a->number_committed == 1); mgr.schedule_transaction(std::move(tx1_2)); mgr.schedule_transaction(std::move(tx2_2)); }; mgr.schedule_transaction(std::move(tx1)); mgr.schedule_transaction(std::move(tx2)); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 1); REQUIRE(mgr.done.size() == 2); REQUIRE(obj_b->number_committed == 2); REQUIRE(obj_b->number_applied == 2); REQUIRE(obj_a->number_committed == 1); REQUIRE(obj_a->number_applied == 0); } TEST_CASE("Concurrent committed") { // This testcase exists to check that the code can handle multiple committed transactions and that // committed transactions are properly moved to the done array. setup_wayfire_debugging_state(); wf::txn::transaction_manager_t::impl mgr; auto obj_a = std::make_shared(false); auto obj_b = std::make_shared(false); auto tx1 = new_tx(); tx1->add_object(obj_a); auto tx2 = new_tx(); tx2->add_object(obj_a); auto tx3 = new_tx(); tx3->add_object(obj_b); // Make sure tx3 is before tx2 mgr.schedule_transaction(std::move(tx1)); mgr.schedule_transaction(std::move(tx3)); mgr.schedule_transaction(std::move(tx2)); REQUIRE(mgr.committed.size() == 2); REQUIRE(mgr.pending.size() == 1); REQUIRE(mgr.done.size() == 0); obj_a->emit_ready(); REQUIRE(mgr.committed.size() == 2); REQUIRE(mgr.pending.size() == 0); REQUIRE(mgr.done.size() == 1); obj_a->emit_ready(); REQUIRE(mgr.committed.size() == 1); REQUIRE(mgr.pending.size() == 0); REQUIRE(mgr.done.size() == 2); } wayfire-0.10.0/test/txn/meson.build0000664000175000017500000000061315053502647017063 0ustar dkondordkondortxn_test = executable( 'transaction-test', 'transaction-test.cpp', dependencies: libwayfire, install: false) test('Test transaction basic functionality', txn_test) txn_manager_test = executable( 'transaction-manager-test', 'transaction-manager-test.cpp', dependencies: libwayfire, install: false) test('Test transaction manager functionality', txn_manager_test) wayfire-0.10.0/test/txn/transaction-test-object.hpp0000664000175000017500000000245615053502647022207 0ustar dkondordkondor#pragma once #include #include #include class txn_test_object_t : public wf::txn::transaction_object_t { public: int number_committed = 0; int number_applied = 0; std::function apply_callback; bool autoready; txn_test_object_t(bool autocommit) { this->autoready = autocommit; } void commit() override { number_committed++; if (autoready) { emit_ready(); } } void apply() override { number_applied++; if (apply_callback) { apply_callback(); } } void emit_ready() { wf::txn::object_ready_signal ev; ev.self = this; this->emit(&ev); } }; inline void setup_wayfire_debugging_state() { wf::log::initialize_logging(std::cout, wf::log::LOG_LEVEL_DEBUG, wf::log::LOG_COLOR_MODE_ON); wf::log::enabled_categories.set((size_t)wf::log::logging_category::TXN, 1); wf::log::enabled_categories.set((size_t)wf::log::logging_category::TXNI, 1); // Set wl_idle_call's loop to a fake loop so that the test doesn't crash when using signals which in turn // use safe_list_t which needs wl_idle_calls. wf::wl_idle_call::loop = wl_event_loop_create(); } wayfire-0.10.0/test/txn/transaction-test.cpp0000664000175000017500000000741015053502647020731 0ustar dkondordkondor#include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include #include #include #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include "transaction-test-object.hpp" #include static void run_transaction_test(bool timeout, bool autoready) { setup_wayfire_debugging_state(); wf::wl_timer::callback_t tx_timeout_callback; wf::txn::transaction_t::timer_setter_t timer_setter = [&] (uint64_t time, wf::wl_timer::callback_t cb) { REQUIRE(time == 1234); tx_timeout_callback = cb; }; bool applied = false; wf::signal::connection_t on_apply = [&] (wf::txn::transaction_applied_signal *ev) { REQUIRE(ev->timed_out == timeout); applied = true; }; wf::txn::transaction_t tx(1234, timer_setter); tx.connect(&on_apply); auto object1 = std::make_shared(autoready); auto object2 = std::make_shared(autoready); tx.add_object(object1); tx.add_object(object2); // Nothing should happen before commit REQUIRE(!tx_timeout_callback); REQUIRE(object1->number_applied == 0); REQUIRE(object1->number_committed == 0); REQUIRE(object2->number_applied == 0); REQUIRE(object2->number_committed == 0); REQUIRE(tx.get_objects() == std::vector{object1, object2}); tx.commit(); REQUIRE(tx_timeout_callback); if (autoready) { REQUIRE(object1->number_applied == 1); REQUIRE(object1->number_committed == 1); REQUIRE(object2->number_applied == 1); REQUIRE(object2->number_committed == 1); REQUIRE(applied == true); return; } REQUIRE(object1->number_applied == 0); REQUIRE(object1->number_committed == 1); REQUIRE(object2->number_applied == 0); REQUIRE(object2->number_committed == 1); object1->emit_ready(); REQUIRE(object1->number_applied == 0); REQUIRE(object1->number_committed == 1); REQUIRE(object2->number_applied == 0); REQUIRE(object2->number_committed == 1); REQUIRE(applied == false); if (!timeout) { object2->emit_ready(); } else { tx_timeout_callback(); } REQUIRE(object1->number_applied == 1); REQUIRE(object1->number_committed == 1); REQUIRE(object2->number_applied == 1); REQUIRE(object2->number_committed == 1); REQUIRE(applied == true); } TEST_CASE("Transaction can be successfully committed and applied") { run_transaction_test(false, false); } TEST_CASE("Transaction is applied even after a timeout") { run_transaction_test(true, false); } TEST_CASE("Transaction is applied immediately if all objects are ready") { run_transaction_test(false, true); } TEST_CASE("Edge cases") { setup_wayfire_debugging_state(); int applied = 0; wf::signal::connection_t on_apply = [&] (wf::txn::transaction_applied_signal *ev) { ++applied; }; wf::txn::transaction_t::timer_setter_t timer_setter = [&] (uint64_t time, wf::wl_timer::callback_t cb) { REQUIRE(time == 0); cb(); }; wf::txn::transaction_t tx(0, timer_setter); tx.connect(&on_apply); SUBCASE("Empty transaction") {} SUBCASE("Transaction with timeout=0 without autocommit") { auto object = std::make_shared(false); tx.add_object(object); } SUBCASE("Transaction with timeout=0 and autocommit") { auto object = std::make_shared(true); tx.add_object(object); } tx.commit(); REQUIRE(applied == 1); } wayfire-0.10.0/test/meson.build0000664000175000017500000000006015053502647016246 0ustar dkondordkondorsubdir('geometry') subdir('txn') subdir('misc') wayfire-0.10.0/test/geometry/0000775000175000017500000000000015053502647015743 5ustar dkondordkondorwayfire-0.10.0/test/geometry/geometry_test.cpp0000664000175000017500000000040515053502647021340 0ustar dkondordkondor#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include TEST_CASE("Point addition") { wf::point_t a = {1, 2}; wf::point_t b = {3, 4}; using namespace wf; REQUIRE_EQ(a + b, wf::point_t{4, 6}); } wayfire-0.10.0/test/geometry/meson.build0000664000175000017500000000024115053502647020102 0ustar dkondordkondorgeometry_test = executable( 'geometry_test', 'geometry_test.cpp', dependencies: libwayfire, install: false) test('Geometry test', geometry_test) wayfire-0.10.0/wf-touch/0000775000175000017500000000000014752715631014671 5ustar dkondordkondorwayfire-0.10.0/wf-touch/test/0000775000175000017500000000000014752715631015650 5ustar dkondordkondorwayfire-0.10.0/wf-touch/test/shared.hpp0000664000175000017500000000120514752715631017625 0ustar dkondordkondor#define _USE_MATH_DEFINES #include #include using namespace wf::touch; static finger_t finger_in_dir(double x, double y) { return finger_t { .origin = {0, 0}, .current = {x, y} }; } const uint32_t lu = MOVE_DIRECTION_LEFT | MOVE_DIRECTION_UP; const uint32_t ld = MOVE_DIRECTION_LEFT | MOVE_DIRECTION_DOWN; const uint32_t rd = MOVE_DIRECTION_RIGHT | MOVE_DIRECTION_DOWN; const uint32_t ru = MOVE_DIRECTION_RIGHT | MOVE_DIRECTION_UP; static finger_t finger_2p(double x, double y, double a, double b) { return finger_t { .origin = {x, y}, .current = {a, b} }; } wayfire-0.10.0/wf-touch/test/action_test.cpp0000664000175000017500000001257314752715631020700 0ustar dkondordkondor#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include "shared.hpp" TEST_CASE("touch_action_t") { touch_action_t touch_down{2, true}; touch_down.set_target({0, 0, 10, 10}); touch_down.set_duration(150); touch_down.set_move_tolerance(5); gesture_event_t event_down; event_down.type = EVENT_TYPE_TOUCH_DOWN; event_down.time = 75; // check normal operation, with tolerance gesture_state_t state; state.fingers[0] = finger_2p(0, 0, 0, 0); touch_down.reset(0); CHECK(touch_down.update_state(state, event_down) == ACTION_STATUS_RUNNING); gesture_event_t motion; motion.type = EVENT_TYPE_MOTION; motion.finger = 0; motion.time = 100; motion.pos = {1, 1}; state.fingers[0] = finger_2p(0, 0, 1, 1); CHECK(touch_down.update_state(state, motion) == ACTION_STATUS_RUNNING); state.fingers[1] = finger_in_dir(2, 2); event_down.finger = 2; event_down.pos = {2, 2}; event_down.time = 150; CHECK(touch_down.update_state(state, event_down) == ACTION_STATUS_COMPLETED); // check outside of bounds state.fingers[0] = finger_2p(15, 15, 20, 20); touch_down.reset(0); CHECK(touch_down.update_state(state, event_down) == ACTION_STATUS_CANCELLED); state.fingers[0] = finger_2p(0, 0, 0, 0); // check timeout touch_down.reset(0); CHECK(touch_down.update_state(state, gesture_event_t{.type = EVENT_TYPE_TIMEOUT}) == ACTION_STATUS_CANCELLED); touch_action_t touch_up{2, false}; gesture_event_t event_up; event_up.type = EVENT_TYPE_TOUCH_UP; event_up.time = 150; // start touch up action state.fingers[1] = finger_2p(2, 2, 3, 3); touch_up.reset(0); CHECK(touch_up.update_state(state, event_up) == ACTION_STATUS_RUNNING); // complete it state.fingers.erase(1); CHECK(touch_up.update_state(state, event_up) == ACTION_STATUS_COMPLETED); // check tolerance exceeded state.fingers[1] = finger_2p(2, 2, 2, 3); touch_up.set_move_tolerance(0); touch_up.reset(0); CHECK(touch_up.update_state(state, event_up) == ACTION_STATUS_CANCELLED); } TEST_CASE("wf::touch::hold_action_t") { hold_action_t hold{50}; hold.set_move_tolerance(1); gesture_state_t state; state.fingers[0] = finger_in_dir(1, 0); gesture_event_t ev; // check ok state hold.reset(0); ev.time = 49; ev.type = EVENT_TYPE_MOTION; CHECK(hold.update_state(state, ev) == ACTION_STATUS_RUNNING); CHECK(hold.update_state(state, gesture_event_t{.type = EVENT_TYPE_TIMEOUT}) == ACTION_STATUS_COMPLETED); // check finger breaks action hold.reset(0); ev.type = EVENT_TYPE_TOUCH_UP; ev.time = 49; CHECK(hold.update_state(state, ev) == ACTION_STATUS_CANCELLED); // check too much movement state.fingers[0] = finger_in_dir(2, 0); ev.time = 49; hold.reset(0); CHECK(hold.update_state(state, ev) == ACTION_STATUS_CANCELLED); } TEST_CASE("wf::touch::drag_action_t") { drag_action_t drag{MOVE_DIRECTION_LEFT, 50}; drag.set_move_tolerance(5); gesture_state_t state; state.fingers[0] = finger_in_dir(-50, 0); state.fingers[1] = finger_in_dir(-50, 3); gesture_event_t ev; ev.type = EVENT_TYPE_MOTION; ev.time = 0; // check ok drag.reset(0); CHECK(drag.update_state(state, ev) == ACTION_STATUS_COMPLETED); // check distance not enough drag.reset(0); state.fingers[0] = finger_in_dir(-49, 0); CHECK(drag.update_state(state, ev) == ACTION_STATUS_RUNNING); // check exceeds tolerance state.fingers[1] = finger_in_dir(0, 6); drag.reset(0); CHECK(drag.update_state(state, ev) == ACTION_STATUS_CANCELLED); // check touch cancels ev.type = EVENT_TYPE_TOUCH_UP; state.fingers[1] = finger_in_dir(-50, 3); drag.reset(0); CHECK(drag.update_state(state, ev) == ACTION_STATUS_CANCELLED); } TEST_CASE("wf::touch::pinch_action_t") { pinch_action_t in{0.5}, out{2}; gesture_state_t state; state.fingers[0] = finger_2p(1, 0, 2, 1); state.fingers[1] = finger_2p(-1, -2, -3, -4); gesture_event_t ev; ev.time = 0; ev.type = EVENT_TYPE_MOTION; // ok out.reset(0); CHECK(out.update_state(state, ev) == ACTION_STATUS_COMPLETED); std::swap(state.fingers[0].origin, state.fingers[0].current); std::swap(state.fingers[1].origin, state.fingers[1].current); in.reset(0); CHECK(in.update_state(state, ev) == ACTION_STATUS_COMPLETED); // too much movement in.set_move_tolerance(1); in.reset(0); state.fingers[0].current += point_t{2, 0}; state.fingers[1].current += point_t{2, 0}; CHECK(in.update_state(state, ev) == ACTION_STATUS_CANCELLED); // touch cancels in.reset(0); state.fingers[0].current -= point_t{2, 0}; state.fingers[1].current -= point_t{2, 0}; ev.type = EVENT_TYPE_TOUCH_DOWN; CHECK(in.update_state(state, ev) == ACTION_STATUS_CANCELLED); } TEST_CASE("wf::touch::rotate_action_t") { gesture_state_t state; state.fingers[0] = finger_2p(0, 1, 1, 0); state.fingers[1] = finger_2p(1, 0, 0, -1); state.fingers[2] = finger_2p(0, -1, -1, 0); state.fingers[3] = finger_2p(-1, 0, 0, 1); CHECK(state.get_rotation_angle() == doctest::Approx(-M_PI / 2.0)); rotate_action_t rotate{-M_PI / 3.0}; gesture_event_t ev; ev.type = EVENT_TYPE_MOTION; CHECK(rotate.update_state(state, ev) == ACTION_STATUS_COMPLETED); // TODO: incomplete tests } wayfire-0.10.0/wf-touch/test/meson.build0000664000175000017500000000073714752715631020021 0ustar dkondordkondorbasic_test = executable( 'basic_test', 'basic_test.cpp', dependencies: [wftouch, doctest], install: false) test('Basic test', basic_test) action_test = executable( 'action_test', 'action_test.cpp', dependencies: [wftouch, doctest], install: false) test('Action test', action_test) gesture_test = executable( 'gesture_test', 'gesture_test.cpp', dependencies: [wftouch, doctest], install: false) test('Gesture test', gesture_test) wayfire-0.10.0/wf-touch/test/gesture_test.cpp0000664000175000017500000001300314752715631021066 0ustar dkondordkondor#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include using namespace wf::touch; class fake_timer_t : public timer_interface_t { public: std::vector requests; std::function last_cb; void set_timeout(uint32_t req, std::function cb) override { requests.push_back(req); last_cb = cb; } void reset() override { requests.push_back(-1); } }; TEST_CASE("wf::touch::gesture_t") { int completed = 0; int cancelled = 0; gesture_callback_t callback1 = [&] () { ++completed; }; gesture_callback_t callback2 = [&] () { ++cancelled; }; auto _timer = std::make_unique(); auto timer_ptr = _timer.get(); #define REQUIRE_TIMERS(...) CHECK(timer_ptr->requests == std::vector{__VA_ARGS__}); SUBCASE("Hold gesture") { gesture_t hold = gesture_builder_t() .action(touch_action_t(2, true).set_duration(100)) .action(hold_action_t(200)) .on_completed(callback1) .on_cancelled(callback2) .build(); hold.set_timer(std::move(_timer)); hold.reset(0); hold.update_state( gesture_event_t{ .type = EVENT_TYPE_TOUCH_DOWN, .time = 0, .finger = 0, .pos = {0, 0}}); REQUIRE_TIMERS(100); SUBCASE("Timeout") { timer_ptr->last_cb(); CHECK(hold.get_status() == ACTION_STATUS_CANCELLED); } SUBCASE("OK") { hold.update_state( gesture_event_t{ .type = EVENT_TYPE_TOUCH_DOWN, .time = 10, .finger = 1, .pos = {0, 0}}); REQUIRE_TIMERS(100, -1, 200); timer_ptr->last_cb(); CHECK(hold.get_status() == ACTION_STATUS_COMPLETED); REQUIRE_TIMERS(100, -1, 200, -1); } } SUBCASE("double-tap gesture") { gesture_t double_tap = gesture_builder_t() .action(touch_action_t(1, true).set_duration(100)) .action(touch_action_t(1, false).set_duration(100)) .action(touch_action_t(1, true).set_duration(100)) .action(touch_action_t(1, false)) .on_completed(callback1) .on_cancelled(callback2) .build(); double_tap.set_timer(std::move(_timer)); double_tap.reset(0); double_tap.update_state({.type = EVENT_TYPE_TOUCH_DOWN, .time = 0, .finger = 0, .pos = {0, 0}}); double_tap.update_state({.type = EVENT_TYPE_TOUCH_UP, .time = 20, .finger = 0, .pos = {0, 0}}); CHECK(double_tap.get_status() == ACTION_STATUS_RUNNING); SUBCASE("Success") { double_tap.reset(80); double_tap.update_state({.type = EVENT_TYPE_TOUCH_DOWN, .time = 80, .finger = 0, .pos = {0, 0}}); double_tap.update_state({.type = EVENT_TYPE_TOUCH_UP, .time = 90, .finger = 0, .pos = {0, 0}}); CHECK(completed == 1); CHECK(cancelled == 0); } SUBCASE("Timeout") { REQUIRE_TIMERS(100, -1, 100, -1, 100); timer_ptr->last_cb(); CHECK(double_tap.get_status() == ACTION_STATUS_CANCELLED); CHECK(completed == 0); CHECK(cancelled == 1); REQUIRE_TIMERS(100, -1, 100, -1, 100, -1); double_tap.reset(150); CHECK(double_tap.get_status() == ACTION_STATUS_RUNNING); } } SUBCASE("swipe") { gesture_t swipe = gesture_builder_t() .action(touch_action_t(1, true)) .action(hold_action_t(5)) .action(drag_action_t(MOVE_DIRECTION_LEFT, 10)) .action(hold_action_t(5)) .action(drag_action_t(MOVE_DIRECTION_RIGHT, 10)) .on_completed(callback1) .on_cancelled(callback2) .build(); swipe.set_timer(std::move(_timer)); swipe.reset(0); gesture_event_t touch_down; touch_down.finger = 0; touch_down.pos = {0, 0}; touch_down.type = EVENT_TYPE_TOUCH_DOWN; touch_down.time = 0; swipe.update_state(touch_down); CHECK(swipe.get_progress() >= 0.2); SUBCASE("complete") { timer_ptr->last_cb(); gesture_event_t motion_left; motion_left.finger = 0; motion_left.pos = {-10, 0}; motion_left.time = 10; motion_left.type = EVENT_TYPE_MOTION; swipe.update_state(motion_left); timer_ptr->last_cb(); CHECK(cancelled == 0); CHECK(completed == 0); CHECK(swipe.get_progress() >= 0.6); gesture_event_t motion_right = motion_left; motion_right.pos = {0, 0}; motion_right.time = 20; swipe.update_state(motion_right); CHECK(cancelled == 0); CHECK(completed == 1); SUBCASE("restart") { swipe.reset(0); CHECK(swipe.get_progress() == 0.0); swipe.update_state(touch_down); timer_ptr->last_cb(); swipe.update_state(motion_left); timer_ptr->last_cb(); swipe.update_state(motion_right); CHECK(cancelled == 0); CHECK(completed == 2); } } SUBCASE("cancelled") { touch_down.finger = 1; swipe.update_state(touch_down); CHECK(cancelled == 1); CHECK(completed == 0); } } } wayfire-0.10.0/wf-touch/test/basic_test.cpp0000664000175000017500000001056714752715631020505 0ustar dkondordkondor#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include #include "shared.hpp" TEST_CASE("get_move_in_direction") { CHECK(finger_in_dir(1, 0).get_direction() == MOVE_DIRECTION_RIGHT); CHECK(finger_in_dir(-1, -1).get_direction() == lu); CHECK(finger_in_dir(1, 1).get_direction() == rd); CHECK(finger_in_dir(0, 0).get_direction() == 0); CHECK(finger_in_dir(-10, 1).get_direction() == MOVE_DIRECTION_LEFT); } TEST_CASE("get_drag_distance") { CHECK(finger_in_dir(0, 5).get_drag_distance(MOVE_DIRECTION_DOWN) == doctest::Approx(5)); CHECK(finger_in_dir(-1, -1).get_drag_distance(MOVE_DIRECTION_DOWN) == doctest::Approx(0)); } TEST_CASE("get_incorrect_drag_distance") { CHECK(finger_in_dir(-1, -1).get_incorrect_drag_distance(lu) == doctest::Approx(0)); CHECK(finger_in_dir(-1, -1).get_incorrect_drag_distance(ru) == doctest::Approx(std::sqrt(2))); CHECK(finger_in_dir(-1, -1).get_incorrect_drag_distance(ld) == doctest::Approx(std::sqrt(2))); CHECK(finger_in_dir(5, 5).get_incorrect_drag_distance(MOVE_DIRECTION_RIGHT) == doctest::Approx(5)); CHECK(finger_in_dir(4, 0).get_incorrect_drag_distance(MOVE_DIRECTION_LEFT) == doctest::Approx(4)); } TEST_CASE("get_pinch_scale") { gesture_state_t state; state.fingers[0] = finger_2p(1, 0, 2, 1); state.fingers[1] = finger_2p(-1, -2, -3, -4); CHECK(state.get_pinch_scale() > 2); std::swap(state.fingers[0].origin, state.fingers[0].current); std::swap(state.fingers[1].origin, state.fingers[1].current); CHECK(state.get_pinch_scale() < 0.5); state.fingers[0] = finger_2p(1, 1, 1, 1); state.fingers[1] = finger_2p(2, 2, 2, 2); CHECK(state.get_pinch_scale() == doctest::Approx(1)); } TEST_CASE("get_rotation_angle") { gesture_state_t state; state.fingers[0] = finger_2p(0, 1, 1, 0); state.fingers[1] = finger_2p(1, 0, 0, -1); state.fingers[2] = finger_2p(0, -1, -1, 0); state.fingers[3] = finger_2p(-1, 0, 0, 1); CHECK(state.get_rotation_angle() == doctest::Approx(-M_PI / 2.0)); // triangle (0, 0), (56, 15), (15, 56) is almost equilateral state.fingers.clear(); state.fingers[0] = finger_2p(0, 0, 56, 15); state.fingers[1] = finger_2p(56, 15, 15, 56); state.fingers[2] = finger_2p(15, 56, 0, 0); CHECK(state.get_rotation_angle() == doctest::Approx(2.0 * M_PI / 3.0).epsilon(0.05)); } TEST_CASE("finger_t") { CHECK(finger_in_dir(1, 1).delta() == point_t{1, 1}); } static void compare_point(const point_t& a, const point_t& b) { CHECK(a.x == doctest::Approx(b.x)); CHECK(a.y == doctest::Approx(b.y)); } static void compare_finger(const finger_t& a, const finger_t& b) { compare_point(a.origin, b.origin); compare_point(a.current, b.current); } TEST_CASE("gesture_state_t") { gesture_state_t state; state.fingers[0] = finger_in_dir(1, 2); state.fingers[1] = finger_in_dir(3, 4); state.fingers[2] = finger_in_dir(5, 6); compare_finger(state.get_center(), finger_in_dir(3, 4)); } TEST_CASE("gesture_state_t::update") { gesture_state_t state; gesture_event_t ev; ev.finger = 0; ev.pos = {4, 5}; ev.type = EVENT_TYPE_TOUCH_DOWN; state.update(ev); CHECK(state.fingers.size() == 1); compare_finger(state.fingers[0], finger_2p(4, 5, 4, 5)); ev.finger = 0; ev.pos = {6, 7}; ev.type = EVENT_TYPE_MOTION; state.update(ev); CHECK(state.fingers.size() == 1); compare_finger(state.fingers[0], finger_2p(4, 5, 6, 7)); ev.finger = 1; ev.pos = {7, -1}; ev.type = EVENT_TYPE_TOUCH_DOWN; state.update(ev); CHECK(state.fingers.size() == 2); compare_finger(state.fingers[0], finger_2p(4, 5, 6, 7)); compare_finger(state.fingers[1], finger_2p(7, -1, 7, -1)); ev.type = EVENT_TYPE_TOUCH_UP; ev.finger = 0; state.update(ev); CHECK(state.fingers.size() == 1); compare_finger(state.fingers[1], finger_2p(7, -1, 7, -1)); } TEST_CASE("gesture_state_t::reset_origin") { gesture_state_t state; state.fingers[0] = finger_in_dir(6, 7); state.reset_origin(); CHECK(state.fingers.size() == 1); compare_finger(state.fingers[0], finger_2p(6, 7, 6, 7)); } TEST_CASE("touch_target_t") { touch_target_t target{-1, 1, 2, 2}; CHECK(target.contains({0, 2})); CHECK(target.contains({-1, 1})); CHECK(!target.contains({1, 3})); CHECK(!target.contains({0, 5})); } wayfire-0.10.0/wf-touch/src/0000775000175000017500000000000014752715631015460 5ustar dkondordkondorwayfire-0.10.0/wf-touch/src/touch.cpp0000664000175000017500000001276214752715631017316 0ustar dkondordkondor#include using namespace wf::touch; point_t wf::touch::finger_t::delta() const { return this->current - this->origin; } finger_t wf::touch::gesture_state_t::get_center() const { finger_t center; center.origin = {0, 0}; center.current = {0, 0}; for (auto& f : this->fingers) { center.origin += f.second.origin; center.current += f.second.current; } center.origin /= this->fingers.size(); center.current /= this->fingers.size(); return center; } void wf::touch::gesture_state_t::update(const gesture_event_t& event) { switch (event.type) { case EVENT_TYPE_TOUCH_DOWN: fingers[event.finger].origin = event.pos; // fallthrough case EVENT_TYPE_MOTION: fingers[event.finger].current = event.pos; break; case EVENT_TYPE_TOUCH_UP: fingers.erase(event.finger); break; default: break; } } void wf::touch::gesture_state_t::reset_origin() { for (auto& f : fingers) { f.second.origin = f.second.current; } } wf::touch::gesture_action_t& wf::touch::gesture_action_t::set_duration(uint32_t duration) { this->duration = duration; return *this; } std::optional wf::touch::gesture_action_t::get_duration() const { return this->duration; } void wf::touch::gesture_action_t::reset(uint32_t time) { this->start_time = time; } bool wf::touch::touch_target_t::contains(const point_t& pt) const { return x <= pt.x && pt.x < x + width && y <= pt.y && pt.y < y + height; } class wf::touch::gesture_t::impl { public: gesture_callback_t completed; gesture_callback_t cancelled; std::vector> actions; size_t current_action = 0; action_status_t status = ACTION_STATUS_CANCELLED; gesture_state_t finger_state; std::unique_ptr timer; void start_gesture(uint32_t time) { status = ACTION_STATUS_RUNNING; finger_state.fingers.clear(); current_action = 0; actions[0]->reset(time); start_timer(); } void start_timer() { if (auto dur = actions[current_action]->get_duration()) { timer->set_timeout(*dur, [=] () { update_state(gesture_event_t{.type = EVENT_TYPE_TIMEOUT}); }); } } void update_state(const gesture_event_t& event) { if (status != ACTION_STATUS_RUNNING) { // nothing to do return; } auto& idx = current_action; auto old_finger_state = finger_state; finger_state.update(event); auto next_action = [&] () -> bool { timer->reset(); ++idx; if (idx < actions.size()) { actions[idx]->reset(event.time); finger_state.reset_origin(); start_timer(); return true; } return false; }; action_status_t pending_status = actions[idx]->update_state(finger_state, event); switch (pending_status) { case ACTION_STATUS_RUNNING: return; // nothing more to do case ACTION_STATUS_CANCELLED: this->status = ACTION_STATUS_CANCELLED; timer->reset(); cancelled(); return; case ACTION_STATUS_COMPLETED: bool has_next = next_action(); if (!has_next) { this->status = ACTION_STATUS_COMPLETED; completed(); return; } } } }; void wf::touch::gesture_t::set_timer(std::unique_ptr timer) { priv->timer = std::move(timer); } wf::touch::gesture_t::gesture_t(std::vector> actions, gesture_callback_t completed, gesture_callback_t cancelled) { this->priv = std::make_unique(); priv->actions = std::move(actions); priv->completed = completed; priv->cancelled = cancelled; } wf::touch::gesture_t::gesture_t(gesture_t&& other) { this->priv = std::move(other.priv); } wf::touch::gesture_t& wf::touch::gesture_t::operator=(gesture_t&& other) { this->priv = std::move(other.priv); return *this; } wf::touch::gesture_t::~gesture_t() = default; double wf::touch::gesture_t::get_progress() const { if (priv->status == ACTION_STATUS_CANCELLED) { return 0.0; } return 1.0 * priv->current_action / priv->actions.size(); } void wf::touch::gesture_t::update_state(const gesture_event_t& event) { assert(priv->timer); assert(!priv->actions.empty()); priv->update_state(event); } wf::touch::action_status_t wf::touch::gesture_t::get_status() const { return priv->status; } void wf::touch::gesture_t::reset(uint32_t time) { assert(priv->timer); assert(!priv->actions.empty()); if (priv->status == ACTION_STATUS_RUNNING) { return; } priv->start_gesture(time); } wf::touch::gesture_builder_t::gesture_builder_t() {} wf::touch::gesture_builder_t& wf::touch::gesture_builder_t::on_completed(gesture_callback_t callback) { this->_on_completed = callback; return *this; } wf::touch::gesture_builder_t& wf::touch::gesture_builder_t::on_cancelled(gesture_callback_t callback) { this->_on_cancelled = callback; return *this; } wf::touch::gesture_t wf::touch::gesture_builder_t::build() { return gesture_t(std::move(actions), _on_completed, _on_cancelled); } wayfire-0.10.0/wf-touch/src/actions.cpp0000664000175000017500000001356414752715631017635 0ustar dkondordkondor#include #include using namespace wf::touch; /* -------------------------- Touch action ---------------------------------- */ wf::touch::touch_action_t::touch_action_t(int cnt_fingers, bool touch_down) { this->cnt_fingers = cnt_fingers; this->type = touch_down ? EVENT_TYPE_TOUCH_DOWN : EVENT_TYPE_TOUCH_UP; this->target.x = -1e9; this->target.y = -1e9; this->target.width = 2e9; this->target.height = 2e9; } wf::touch::touch_action_t& wf::touch::touch_action_t::set_target(const touch_target_t& target) { this->target = target; return *this; } static double find_max_delta(const gesture_state_t& state) { double max_length = 0; for (auto& f : state.fingers) { max_length = std::max(max_length, glm::length(f.second.delta())); } return max_length; } bool wf::touch::touch_action_t::exceeds_tolerance(const gesture_state_t& state) { return find_max_delta(state) > this->move_tolerance; } void wf::touch::touch_action_t::reset(uint32_t time) { gesture_action_t::reset(time); this->cnt_touch_events = 0; } action_status_t wf::touch::touch_action_t::update_state( const gesture_state_t& state, const gesture_event_t& event) { if (exceeds_tolerance(state)) { return ACTION_STATUS_CANCELLED; } switch (event.type) { case EVENT_TYPE_MOTION: return ACTION_STATUS_RUNNING; case EVENT_TYPE_TIMEOUT: return ACTION_STATUS_CANCELLED; case EVENT_TYPE_TOUCH_UP: // fallthrough case EVENT_TYPE_TOUCH_DOWN: if (this->type != event.type) { // down when we want up or vice versa return ACTION_STATUS_CANCELLED; } for (auto& f : state.fingers) { point_t relevant_point = (this->type == EVENT_TYPE_TOUCH_UP ? f.second.current : f.second.origin); if (!this->target.contains(relevant_point)) { return ACTION_STATUS_CANCELLED; } } this->cnt_touch_events++; if (this->cnt_touch_events == this->cnt_fingers) { return ACTION_STATUS_COMPLETED; } else { return ACTION_STATUS_RUNNING; } } return ACTION_STATUS_RUNNING; } /*- -------------------------- Hold action ---------------------------------- */ wf::touch::hold_action_t::hold_action_t(int32_t threshold) { set_duration(threshold); } action_status_t wf::touch::hold_action_t::update_state(const gesture_state_t& state, const gesture_event_t& event) { switch (event.type) { case EVENT_TYPE_MOTION: if (exceeds_tolerance(state)) { return ACTION_STATUS_CANCELLED; } else { return ACTION_STATUS_RUNNING; } case EVENT_TYPE_TIMEOUT: return ACTION_STATUS_COMPLETED; default: return ACTION_STATUS_CANCELLED; } } bool wf::touch::hold_action_t::exceeds_tolerance(const gesture_state_t& state) { return find_max_delta(state) > this->move_tolerance; } /*- -------------------------- Drag action ---------------------------------- */ wf::touch::drag_action_t::drag_action_t(uint32_t direction, double threshold) { this->direction = direction; this->threshold = threshold; } action_status_t wf::touch::drag_action_t::update_state(const gesture_state_t& state, const gesture_event_t& event) { if (event.type != EVENT_TYPE_MOTION) { return ACTION_STATUS_CANCELLED; } if (exceeds_tolerance(state)) { return ACTION_STATUS_CANCELLED; } const double dragged = state.get_center().get_drag_distance(this->direction); if (dragged >= this->threshold) { return ACTION_STATUS_COMPLETED; } else { return ACTION_STATUS_RUNNING; } } bool wf::touch::drag_action_t::exceeds_tolerance(const gesture_state_t& state) { for (auto& f : state.fingers) { if (f.second.get_incorrect_drag_distance(this->direction) > move_tolerance) { return true; } } return false; } /*- -------------------------- Pinch action ---------------------------------- */ wf::touch::pinch_action_t::pinch_action_t(double threshold) { this->threshold = threshold; } action_status_t wf::touch::pinch_action_t::update_state(const gesture_state_t& state, const gesture_event_t& event) { if (event.type != EVENT_TYPE_MOTION) { return ACTION_STATUS_CANCELLED; } if (exceeds_tolerance(state)) { return ACTION_STATUS_CANCELLED; } const double current_scale = state.get_pinch_scale(); if (((this->threshold < 1.0) && (current_scale <= threshold)) || ((this->threshold > 1.0) && (current_scale >= threshold))) { return ACTION_STATUS_COMPLETED; } return ACTION_STATUS_RUNNING; } bool wf::touch::pinch_action_t::exceeds_tolerance(const gesture_state_t& state) { return glm::length(state.get_center().delta()) > this->move_tolerance; } /*- -------------------------- Rotate action ---------------------------------- */ wf::touch::rotate_action_t::rotate_action_t(double threshold) { this->threshold = threshold; } action_status_t wf::touch::rotate_action_t::update_state(const gesture_state_t& state, const gesture_event_t& event) { if (event.type != EVENT_TYPE_MOTION) { return ACTION_STATUS_CANCELLED; } if (exceeds_tolerance(state)) { return ACTION_STATUS_CANCELLED; } const double current_scale = state.get_rotation_angle(); if (((this->threshold < 0.0) && (current_scale <= threshold)) || ((this->threshold > 0.0) && (current_scale >= threshold))) { return ACTION_STATUS_COMPLETED; } return ACTION_STATUS_RUNNING; } bool wf::touch::rotate_action_t::exceeds_tolerance(const gesture_state_t& state) { return glm::length(state.get_center().delta()) > this->move_tolerance; } wayfire-0.10.0/wf-touch/src/math.cpp0000664000175000017500000000672214752715631017124 0ustar dkondordkondor#define GLM_ENABLE_EXPERIMENTAL // for glm::orientedAngle #include #include #include #include #include #define _ << " " << #define debug(x) #x << " = " << (x) static constexpr double DIRECTION_TAN_THRESHOLD = 1.0 / 3.0; using namespace wf::touch; uint32_t wf::touch::finger_t::get_direction() const { double to_left = this->get_drag_distance(MOVE_DIRECTION_LEFT); double to_right = this->get_drag_distance(MOVE_DIRECTION_RIGHT); double to_up = this->get_drag_distance(MOVE_DIRECTION_UP); double to_down = this->get_drag_distance(MOVE_DIRECTION_DOWN); double horizontal = std::max(to_left, to_right); double vertical = std::max(to_up, to_down); uint32_t result = 0; if (to_left > 0 && to_left / vertical >= DIRECTION_TAN_THRESHOLD) { result |= MOVE_DIRECTION_LEFT; } else if (to_right > 0 && to_right / vertical >= DIRECTION_TAN_THRESHOLD) { result |= MOVE_DIRECTION_RIGHT; } if (to_up > 0 && to_up / horizontal >= DIRECTION_TAN_THRESHOLD) { result |= MOVE_DIRECTION_UP; } else if (to_down > 0 && to_down / horizontal >= DIRECTION_TAN_THRESHOLD) { result |= MOVE_DIRECTION_DOWN; } return result; } /** Get normal vector in direction */ static point_t get_dir_nv(uint32_t direction) { assert((direction != 0) && ((direction & 0b1111) == direction)); point_t dir = {0, 0}; if (direction & MOVE_DIRECTION_LEFT) { dir.x = -1; } else if (direction & MOVE_DIRECTION_RIGHT) { dir.x = 1; } if (direction & MOVE_DIRECTION_UP) { dir.y = -1; } else if (direction & MOVE_DIRECTION_DOWN) { dir.y = 1; } return dir; } double wf::touch::finger_t::get_drag_distance(uint32_t direction) const { const auto normal = get_dir_nv(direction); const auto delta = this->delta(); /* grahm-schmidt */ const double amount_alongside_dir = glm::dot(delta, normal) / glm::dot(normal, normal); if (amount_alongside_dir >= 0) { return glm::length(amount_alongside_dir * normal); } return 0; } double wf::touch::finger_t::get_incorrect_drag_distance(uint32_t direction) const { const auto normal = get_dir_nv(direction); const auto delta = this->delta(); /* grahm-schmidt */ double amount_alongside_dir = glm::dot(delta, normal) / glm::dot(normal, normal); if (amount_alongside_dir < 0) { /* Drag in opposite direction */ return glm::length(delta); } const auto residual = delta - normal * amount_alongside_dir; return glm::length(residual); } double wf::touch::gesture_state_t::get_pinch_scale() const { auto center = get_center(); double old_dist = 0; double new_dist = 0; for (const auto& f : fingers) { old_dist += glm::length(f.second.origin - center.origin); new_dist += glm::length(f.second.current - center.current); } old_dist /= fingers.size(); new_dist /= fingers.size(); return new_dist / old_dist; } double wf::touch::gesture_state_t::get_rotation_angle() const { auto center = get_center(); double angle_sum = 0; for (const auto& f : fingers) { auto v1 = glm::normalize(f.second.origin - center.origin); auto v2 = glm::normalize(f.second.current - center.current); angle_sum += glm::orientedAngle(v1, v2); } angle_sum /= fingers.size(); return angle_sum; } wayfire-0.10.0/wf-touch/wayfire/0000775000175000017500000000000014752715631016337 5ustar dkondordkondorwayfire-0.10.0/wf-touch/wayfire/touch/0000775000175000017500000000000014752715631017461 5ustar dkondordkondorwayfire-0.10.0/wf-touch/wayfire/touch/touch.hpp0000664000175000017500000003034014752715631021314 0ustar dkondordkondor#pragma once /** * Touchscreen gesture library, designed for use in Wayfire (and elsewhere). * Goal is to process touch events and detect various configurable gestures. * * High-level design: * A gesture consists of one or more consecutive actions. * * An action is usually a simple part of the gesture which can be processed * separately, for ex. touch down with 3 fingers, swipe in a direction, etc. * * When processing events, the gesture starts with its first action. Once it is * completed, the processing continues with the next action, and so on, until * either all actions are completed or an action cancels the gesture. */ #include #include #include #include #include #include namespace wf { namespace touch { using point_t = glm::dvec2; /** * Movement direction. */ enum move_direction_t { MOVE_DIRECTION_LEFT = (1 << 0), MOVE_DIRECTION_RIGHT = (1 << 1), MOVE_DIRECTION_UP = (1 << 2), MOVE_DIRECTION_DOWN = (1 << 3), }; struct finger_t { point_t origin; point_t current; /** Get movement vector */ point_t delta() const; /** Find direction of movement, a bitmask of move_direction_t */ uint32_t get_direction() const; /** Find drag distance in the given direction */ double get_drag_distance(uint32_t direction) const; /** Find drag distance in opposite and perpendicular directions */ double get_incorrect_drag_distance(uint32_t direction) const; }; enum gesture_event_type_t { /** Finger touched down the screen */ EVENT_TYPE_TOUCH_DOWN, /** Finger was lifted off the screen */ EVENT_TYPE_TOUCH_UP, /** Finger moved across the screen */ EVENT_TYPE_MOTION, /** Timeout since action start */ EVENT_TYPE_TIMEOUT, }; /** * Represents a single update on the touch state. */ struct gesture_event_t { /** type of the event */ gesture_event_type_t type; /** timestamp of the event in milliseconds */ uint32_t time{}; /** finger id which the event is about */ int32_t finger{}; /** coordinates of the finger */ point_t pos{}; }; /** * Contains all fingers. */ struct gesture_state_t { public: // finger_id -> finger_t std::map fingers; /** Update fingers based on the event */ void update(const gesture_event_t& event); /** Reset finger origin to current positions */ void reset_origin(); /** Find the center points of the fingers. */ finger_t get_center() const; /** Get the pinch scale of current touch points. */ double get_pinch_scale() const; /** * Get the rotation angle in radians of current touch points. * NB: Works only for rotation < 180 degrees. */ double get_rotation_angle() const; }; /** * Represents the status of an action after it is updated */ enum action_status_t { /** Action is done after this event. */ ACTION_STATUS_COMPLETED, /** Action is still running after this event. */ ACTION_STATUS_RUNNING, /** The whole gesture should be cancelled. */ ACTION_STATUS_CANCELLED, }; /** * Represents a part of the gesture. */ class gesture_action_t { public: /** * Set the duration of the action in milliseconds. * * After the duration times out, the action will receive * * * This is the maximal time needed for this action to be happening to * consider it complete. * * @return this */ gesture_action_t& set_duration(uint32_t duration); /** @return The duration of the gesture action. */ std::optional get_duration() const; /** * Update the action's state according to the new state. * * NOTE: The actual implementation should update the @start_time field. * * @param state The gesture state since the last reset of the gesture. * @param event The event causing this update. * @return The new action status. */ virtual action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) = 0; /** * Reset the action. * Called whenever the action is started again. */ virtual void reset(uint32_t time); virtual ~gesture_action_t() {} protected: gesture_action_t() {} /** Time of the first event. */ int64_t start_time; private: std::optional duration; // maximal duration }; #define WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(x) \ x& set_move_tolerance(double tolerance) \ { \ this->move_tolerance = tolerance; \ return *this; \ } \ x& set_duration(uint32_t duration) \ { \ gesture_action_t::set_duration(duration); \ return *this; \ } /** * Represents a target area where the touch event takes place. */ struct touch_target_t { double x; double y; double width; double height; bool contains(const point_t& point) const; }; /** * Represents the action of touching down with several fingers. */ class touch_action_t : public gesture_action_t { public: /** * Create a new touch down or up action. * * @param cnt_fingers The number of fingers that need to be touched down * or released to consider the action completed. * @param touch_down Whether the action is touch down or touch up. */ touch_action_t(int cnt_fingers, bool touch_down); WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(touch_action_t); /** * Set the target area of this gesture. * * @return this */ touch_action_t& set_target(const touch_target_t& target); /** * Mark the action as completed iff state has the right amount of fingers * and if the event is a touch down. */ action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override; void reset(uint32_t time) override; protected: /** @return True if the fingers have moved too much. */ bool exceeds_tolerance(const gesture_state_t& state); private: int cnt_fingers; int cnt_touch_events; gesture_event_type_t type; uint32_t move_tolerance = 1e9; touch_target_t target; }; /** * Represents the action of holding the fingers still for a certain amount * of time. */ class hold_action_t : public gesture_action_t { public: /** * Create a new hold action. * * @param threshold The time is milliseconds needed to consider the gesture * complete. */ hold_action_t(int32_t threshold); WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(hold_action_t); /** * The action is already completed iff no fingers have been added or * released and the given amount of time has passed without much movement. */ action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override; protected: /** @return True if the fingers have moved too much. */ bool exceeds_tolerance(const gesture_state_t& state); private: uint32_t move_tolerance = 1e9; }; /** * Represents the action of dragging the fingers in a particular direction * over a particular distance. */ class drag_action_t : public gesture_action_t { public: /** * Create a new drag action. * * @param direction The direction of the drag action. * @param threshold The distance that needs to be covered. */ drag_action_t(uint32_t direction, double threshold); WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(drag_action_t); /** * The action is already completed iff no fingers have been added or * released and the given amount of time has passed without much movement. */ action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override; protected: /** * @return True if any finger has moved more than the threshold in an * incorrect direction. */ bool exceeds_tolerance(const gesture_state_t& state); private: double threshold; uint32_t direction; uint32_t move_tolerance = 1e9; }; /** * Represents a pinch action. */ class pinch_action_t : public gesture_action_t { public: /** * Create a new pinch action. * * @param threshold The threshold to be exceeded. * If threshold is less/more than 1, then the action is complete when * the actual pinch scale is respectively less/more than threshold. */ pinch_action_t(double threshold); WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(pinch_action_t); /** * The action is already completed iff no fingers have been added or * released and the pinch threshold has been reached without much movement. */ action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override; protected: /** * @return True if gesture center has moved more than tolerance. */ bool exceeds_tolerance(const gesture_state_t& state); private: double threshold; uint32_t move_tolerance = 1e9; }; /** * Represents a rotate action. */ class rotate_action_t : public gesture_action_t { public: /** * Create a new rotate action. * * @param threshold The threshold to be exceeded. * If threshold is less/more than 0, then the action is complete when * the actual rotation angle is respectively less/more than threshold. */ rotate_action_t(double threshold); WFTOUCH_BUILDER_REPEAT_MEMBERS_WITH_CAST(rotate_action_t); /** * The action is already completed iff no fingers have been added or * released and the rotation threshold has been reached without much movement. */ action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override; protected: /** * @return True if gesture center has moved more than tolerance. */ bool exceeds_tolerance(const gesture_state_t& state); private: double threshold; uint32_t move_tolerance = 1e9; }; using gesture_callback_t = std::function; class timer_interface_t { public: virtual void set_timeout(uint32_t msec, std::function handler) = 0; virtual void reset() = 0; virtual ~timer_interface_t() = default; }; /** * Represents a series of actions forming a gesture together. */ class gesture_t { public: /** * Create a new gesture consisting of the given actions. * * @param actions The actions the gesture consists of. * @param completed The callback to execute each time the gesture is * completed. * @param cancelled The callback to execute each time the gesture is * cancelled. */ gesture_t(std::vector> actions = {}, gesture_callback_t completed = [](){}, gesture_callback_t cancelled = [](){}); gesture_t(gesture_t&& other); gesture_t& operator=(gesture_t&& other); ~gesture_t(); /** @return What percentage of the actions are complete. */ double get_progress() const; /** * Update the gesture state. * * @param event The next event. */ void update_state(const gesture_event_t& event); /** * Get the current state of the gesture. */ action_status_t get_status() const; /** * Reset the gesture state. * * @param time The time of the event causing the start of gesture * recognition, this is typically the first touch event. */ void reset(uint32_t time); /** * Set the timer to use for the gesture. * This needs to be called before using the gesture. * * In wayfire, this is usually set by core. */ void set_timer(std::unique_ptr timer); private: class impl; std::unique_ptr priv; }; /** * A helper class to facilitate gesture construction. */ class gesture_builder_t { public: gesture_builder_t(); template gesture_builder_t& action(const ActionType& action) { actions.push_back(std::make_unique(action)); return *this; } gesture_builder_t& on_completed(gesture_callback_t callback); gesture_builder_t& on_cancelled(gesture_callback_t callback); gesture_t build(); private: gesture_callback_t _on_completed = [](){}; gesture_callback_t _on_cancelled = [](){}; std::vector> actions; }; } } wayfire-0.10.0/wf-touch/meson.build0000664000175000017500000000146614752715631017042 0ustar dkondordkondorproject('wf-touch', ['cpp'], version : '0.0', meson_version: '>=0.47.0', default_options : ['cpp_std=c++17']) glm = dependency('glm', required: false) if not glm.found() and not meson.get_compiler('cpp').check_header('glm/glm.hpp') error('GLM not found, and directly using the header \'glm/glm.hpp\' is not possible.') endif wf_touch_inc_dirs = include_directories('.') install_headers([ 'wayfire/touch/touch.hpp'], subdir: 'wayfire/touch') wftouch_lib = static_library('wftouch', ['src/touch.cpp', 'src/actions.cpp', 'src/math.cpp'], dependencies: glm, install: true) wftouch = declare_dependency(link_with: wftouch_lib, include_directories: wf_touch_inc_dirs, dependencies: glm) doctest = dependency('doctest', required: get_option('tests')) if doctest.found() subdir('test') endif wayfire-0.10.0/wf-touch/meson_options.txt0000664000175000017500000000012214752715631020321 0ustar dkondordkondoroption('tests', type: 'feature', value: 'auto', description: 'Enable unit tests') wayfire-0.10.0/wf-touch/README.md0000664000175000017500000000034614752715631016153 0ustar dkondordkondor# wf-touch Touchscreen gesture library # Acknowledgements The library's design has been heavily inspired by https://github.com/grahnen/libtouch, which has also been used as a reference for some implementation details at times. wayfire-0.10.0/wf-touch/LICENSE0000664000175000017500000000205014752715631015673 0ustar dkondordkondorMIT License Copyright (c) 2020 Wayfire Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wayfire-0.10.0/plugins/0000775000175000017500000000000015053502647014612 5ustar dkondordkondorwayfire-0.10.0/plugins/tile/0000775000175000017500000000000015053502647015547 5ustar dkondordkondorwayfire-0.10.0/plugins/tile/tile-ipc.hpp0000664000175000017500000002607215053502647017775 0ustar dkondordkondor#pragma once #include #include #include #include "tree.hpp" #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "tile-wset.hpp" namespace wf { namespace tile { struct json_builder_data_t { std::set touched_wsets; std::set touched_views; gap_size_t gaps; }; /** * Get a json description of the given tiling tree. */ inline wf::json_t tree_to_json(const std::unique_ptr& root, const wf::point_t& offset, double rel_size = 1.0) { wf::json_t js; js["percent"] = rel_size; js["geometry"] = wf::ipc::geometry_to_json(root->geometry - offset); if (auto view = root->as_view_node()) { js["view-id"] = view->view->get_id(); return js; } auto split = root->as_split_node(); wf::dassert(split != nullptr, "Expected to be split node"); wf::json_t children = wf::json_t::array(); if (split->get_split_direction() == SPLIT_HORIZONTAL) { for (auto& child : split->children) { children.append( tree_to_json(child, offset, 1.0 * child->geometry.height / split->geometry.height)); } js["horizontal-split"] = std::move(children); } else { for (auto& child : split->children) { children.append(tree_to_json( child, offset, 1.0 * child->geometry.width / split->geometry.width)); } js["vertical-split"] = std::move(children); } return js; } /** * Go over the json description and verify that it is a valid tiling tree. * @return An error message if the tree is invalid. */ inline std::optional verify_json_tree(const json_reference_t& json, json_builder_data_t& data, const wf::dimensions_t& available_geometry) { if (!json.is_object()) { return "JSON Tree structure is wrong!"; } if ((available_geometry.width <= data.gaps.left + data.gaps.right) || (available_geometry.height <= data.gaps.top + data.gaps.bottom)) { return "Geometry becomes too small for some nodes!"; } json["width"] = available_geometry.width; json["height"] = available_geometry.height; if (json.has_member("view-id")) { if (!json["view-id"].is_uint64()) { return "view-id should be unsigned integer!"; } auto view = toplevel_cast(wf::ipc::find_view_by_id(json["view-id"])); if (!view) { return "No view found with id " + std::to_string((uint32_t)json["view-id"]); } if (!view->toplevel()->pending().mapped) { return "Cannot tile pending-unmapped views!"; } if (data.touched_views.count(view)) { return "View tiled twice!"; } data.touched_views.insert(view); data.touched_wsets.insert(view->get_wset().get()); return {}; } const bool is_horiz_split = json.has_member("horizontal-split") && json["horizontal-split"].is_array(); const bool is_vert_split = json.has_member("vertical-split") && json["vertical-split"].is_array(); if (!is_horiz_split && !is_vert_split) { return "Node is neither a view, nor a split node!"; } int32_t split_axis = is_horiz_split ? available_geometry.height : available_geometry.width; double weight_sum = 0; const auto& children_list = is_horiz_split ? json["horizontal-split"] : json["vertical-split"]; for (size_t i = 0; i < children_list.size(); i++) { if (!children_list[i].has_member("weight")) { return "Expected 'weight' field for each child node!"; } if (!children_list[i]["weight"].is_double()) { return "Expected 'weight' field to be a number!"; } weight_sum += double(children_list[i]["weight"]); } int32_t size_sum = 0; for (size_t i = 0; i < children_list.size(); i++) { int32_t size = double(children_list[i]["weight"]) / weight_sum * split_axis; size_sum += size; if (i == children_list.size() - 1) { // This is needed because of rounding errors, we always round down, but in the end, we need to // make sure that the nodes cover the whole screen. size += split_axis - size_sum; } const wf::dimensions_t available_for_child = is_horiz_split ? wf::dimensions_t{available_geometry.width, size} : wf::dimensions_t{size, available_geometry.height}; auto error = verify_json_tree(children_list[i], data, available_for_child); if (error.has_value()) { return error; } } // All was OK return {}; } inline std::unique_ptr build_tree_from_json_rec(const json_reference_t& json, tile_workspace_set_data_t *wdata, wf::point_t vp) { std::unique_ptr root; if (json.has_member("view-id")) { auto view = toplevel_cast(wf::ipc::find_view_by_id(json["view-id"])); root = wdata->setup_view_tiling(view, vp); } else { const bool is_horiz_split = json.has_member("horizontal-split"); const auto& children_list = is_horiz_split ? json["horizontal-split"] : json["vertical-split"]; auto split_parent = std::make_unique( is_horiz_split ? tile::SPLIT_HORIZONTAL : tile::SPLIT_VERTICAL); for (size_t i = 0; i < children_list.size(); i++) { split_parent->children.push_back(build_tree_from_json_rec(children_list[i], wdata, vp)); split_parent->children.back()->parent = {split_parent.get()}; } root = std::move(split_parent); } root->geometry.x = 0; root->geometry.y = 0; root->geometry.width = json["width"]; root->geometry.height = json["height"]; return root; } /** * Build a tiling tree from a json description. * * Note that the tree description first has to be verified and pre-processed by verify_json_tree(). */ inline std::unique_ptr build_tree_from_json(const wf::json_t& json, tile_workspace_set_data_t *wdata, wf::point_t vp) { auto root = build_tree_from_json_rec(json, wdata, vp); if (root->as_view_node()) { // Handle cases with a single view. auto split_root = std::make_unique(tile_workspace_set_data_t::default_split); split_root->children.push_back(std::move(root)); return split_root; } return root; } inline wf::json_t handle_ipc_get_layout(const json_t& params) { auto wset_index = wf::ipc::json_get_uint64(params, "wset-index"); if (!params.has_member("workspace") or !params["workspace"].is_object()) { return wf::ipc::json_error("Missing 'workspace' field"); } auto x = wf::ipc::json_get_int64(params["workspace"], "x"); auto y = wf::ipc::json_get_int64(params["workspace"], "y"); auto ws = ipc::find_workspace_set_by_index(wset_index); if (ws) { auto grid_size = ws->get_workspace_grid_size(); if ((x >= grid_size.width) || (y >= grid_size.height)) { return wf::ipc::json_error("invalid workspace coordinates"); } auto response = wf::ipc::json_ok(); auto cur_ws = ws->get_current_workspace(); auto resolution = ws->get_last_output_geometry().value_or(tile::default_output_resolution); wf::point_t offset = {cur_ws.x * resolution.width, cur_ws.y * resolution.height}; response["layout"] = tree_to_json(tile_workspace_set_data_t::get(ws->shared_from_this()).roots[x][y], offset); return response; } return wf::ipc::json_error("wset-index not found"); } inline wf::json_t handle_ipc_set_layout(json_t params) { auto wset_index = wf::ipc::json_get_uint64(params, "wset-index"); if (!params.has_member("workspace") or !params["workspace"].is_object()) { return wf::ipc::json_error("Missing 'workspace' field"); } auto x = wf::ipc::json_get_int64(params["workspace"], "x"); auto y = wf::ipc::json_get_int64(params["workspace"], "y"); if (!params.has_member("layout") || !params["layout"].is_object()) { return wf::ipc::json_error("Missing 'layout' field"); } auto ws = ipc::find_workspace_set_by_index(wset_index); if (!ws) { return wf::ipc::json_error("wset-index not found"); } auto grid_size = ws->get_workspace_grid_size(); if ((x >= grid_size.width) || (y >= grid_size.height)) { return wf::ipc::json_error("invalid workspace coordinates"); } auto& tile_ws = tile_workspace_set_data_t::get(ws->shared_from_this()); tile::json_builder_data_t data; data.gaps = tile_ws.get_gaps(); auto workarea = tile_ws.roots[x][y]->geometry; if (auto err = tile::verify_json_tree(params["layout"], data, wf::dimensions(workarea))) { return wf::ipc::json_error(*err); } // Step 1: detach any views which are currently present in the layout, but should no longer be // in the layout std::vector> views_to_remove; tile::for_each_view(tile_ws.roots[x][y], [&] (wayfire_toplevel_view view) { if (!data.touched_views.count(view)) { views_to_remove.push_back(tile::view_node_t::get_node(view)); } }); tile_ws.detach_views(views_to_remove); { autocommit_transaction_t tx; data.touched_wsets.erase(nullptr); // Step 2: temporarily detach some of the nodes for (auto& touched_view : data.touched_views) { auto tile = wf::tile::view_node_t::get_node(touched_view); if (tile) { tile->parent->remove_child(tile, tx.tx); } if (touched_view->get_wset().get() != ws) { auto old_wset = touched_view->get_wset(); wf::emit_view_pre_moved_to_wset_pre(touched_view, touched_view->get_wset(), ws->shared_from_this()); if (old_wset) { old_wset->remove_view(touched_view); } ws->add_view(touched_view); wf::emit_view_moved_to_wset(touched_view, old_wset, ws->shared_from_this()); } } // Step 3: set up the new layout tile_ws.roots[x][y] = build_tree_from_json(params["layout"], &tile_ws, {static_cast(x), static_cast(y)}); tile::flatten_tree(tile_ws.roots[x][y]); tile_ws.roots[x][y]->set_gaps(tile_ws.get_gaps()); tile_ws.roots[x][y]->set_geometry(workarea, tx.tx); } data.touched_wsets.insert(ws); // Step 4: flatten roots, set gaps, trigger resize everywhere for (auto& touched_ws : data.touched_wsets) { auto& tws = tile_workspace_set_data_t::get(touched_ws->shared_from_this()); tws.flatten_roots(); // will also trigger resize everywhere tws.update_gaps(); } return wf::ipc::json_ok(); } } } wayfire-0.10.0/plugins/tile/tree.hpp0000664000175000017500000001605515053502647017226 0ustar dkondordkondor#ifndef WF_TILE_PLUGIN_TREE #define WF_TILE_PLUGIN_TREE #include "wayfire/signal-definitions.hpp" #include "wayfire/workspace-set.hpp" #include #include #include #include namespace wf { namespace tile { /** * A tree node represents a logical container of views in the tiled part of * a workspace. * * There are two types of nodes: * 1. View tree nodes, i.e leaves, they contain a single view * 2. Split tree nodes, they contain at least 1 child view. */ struct split_node_t; struct view_node_t; struct gap_size_t { /* Gap on the left side */ int32_t left = 0; /* Gap on the right side */ int32_t right = 0; /* Gap on the top side */ int32_t top = 0; /* Gap on the bottom side */ int32_t bottom = 0; /* Gap for internal splits */ int32_t internal = 0; }; struct tree_node_t { /** The node parent, or nullptr if this is the root node */ nonstd::observer_ptr parent; /** The children of the node */ std::vector> children; /** The geometry occupied by the node */ wf::geometry_t geometry; /** Set the geometry available for the node and its subnodes. */ virtual void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx); /** Set the gaps for the node and subnodes. */ virtual void set_gaps(const gap_size_t& gaps) = 0; gap_size_t get_gaps() const { return gaps; } virtual ~tree_node_t() {} /** Simply dynamic cast this to a split_node_t */ nonstd::observer_ptr as_split_node(); /** Simply dynamic cast this to a view_node_t */ nonstd::observer_ptr as_view_node(); protected: /* Gaps */ gap_size_t gaps; }; /** * A node which contains a split can be split either horizontally or vertically */ enum split_direction_t { SPLIT_HORIZONTAL = 0, SPLIT_VERTICAL = 1, }; /* * Represents a node in the tree which contains at 1 one child node */ struct split_node_t : public tree_node_t { /** * Add the given child to the list of children. * * The new child will get resized so that its area is at most 1/(N+1) of the * total node area, where N is the number of children before adding the new * child. * * @param index The index at which to insert the new child, or -1 for * adding to the end of the child list. */ void add_child(std::unique_ptr child, wf::txn::transaction_uptr& tx, int index = -1); /** * Remove a child from the node, and return its unique_ptr */ std::unique_ptr remove_child( nonstd::observer_ptr child, wf::txn::transaction_uptr& tx); /** * Set the total geometry available to the node. This will recursively * resize the children nodes, so that they fit inside the new geometry and * have a size proportional to their old size. */ void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) override; /** * Set the gaps for the subnodes. The internal gap will override * the corresponding edges for each child. */ void set_gaps(const gap_size_t& gaps) override; split_node_t(split_direction_t direction); split_direction_t get_split_direction() const; private: split_direction_t split_direction; /** * Resize the children so that they fit inside the given * available_geometry. */ void recalculate_children(wf::geometry_t available_geometry, wf::txn::transaction_uptr& tx); /** * Calculate the geometry of a child if it has child_size as one * dimension. Whether this is width/height depends on the node split type. * * @param child_pos The position from which the child starts, relative to * the node itself * * @return The geometry of the child, in global coordinates */ wf::geometry_t get_child_geometry(int32_t child_pos, int32_t child_size); /** Return the size of the node in the dimension in which the split happens */ int32_t calculate_splittable() const; /** Return the size of the geometry in the dimension in which the split * happens */ int32_t calculate_splittable(wf::geometry_t geometry) const; }; struct tile_adjust_transformer_signal {}; /** * Represents a leaf in the tree, contains a single view */ struct view_node_t : public tree_node_t { view_node_t(wayfire_toplevel_view view); ~view_node_t(); wayfire_toplevel_view view; /** * Set the geometry of the node and the contained view. * * Note that the resulting view geometry will not always be equal to the * geometry of the node. For example, a fullscreen view will always have * the geometry of the whole output. */ void set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) override; /** * Set the gaps for non-fullscreen mode. * The gap sizes will be subtracted from all edges of the view's geometry. */ void set_gaps(const gap_size_t& gaps) override; /* Return the tree node corresponding to the view, or nullptr if none */ static nonstd::observer_ptr get_node(wayfire_view view); private: struct scale_transformer_t; nonstd::observer_ptr transformer; wf::signal::connection_t on_geometry_changed; wf::signal::connection_t on_adjust_transformer; wf::option_wrapper_t animation_duration{"simple-tile/animation_duration"}; /** * Check whether the crossfade animation should be enabled for the view * currently. */ bool needs_crossfade(); wf::geometry_t calculate_target_geometry(); void update_transformer(); }; /** * Flatten the tree as much as possible, i.e remove nodes with only one * split-node child. * * The only exception is "the root", which will always be a split node. * * Note: this will potentially invalidate pointers to the tree and modify * the given parameter. * * @return True if the tree has any views in it. */ bool flatten_tree(std::unique_ptr& root); /** * Get the root of the tree which node is part of */ nonstd::observer_ptr get_root(nonstd::observer_ptr node); /** * Transform coordinates from the tiling trees coordinate system to wset-local coordinates. */ wf::geometry_t get_wset_local_coordinates(std::shared_ptr wset, wf::geometry_t g); wf::point_t get_wset_local_coordinates(std::shared_ptr wset, wf::point_t g); // Since wsets may not have been attached to any output yet, they may not have a native 'resolution'. // In this case, we use a default resolution of 1920x1080 in order to layout views. This resolution will be // automatically adjusted once the wset is added to an output. static constexpr wf::geometry_t default_output_resolution = {0, 0, 1920, 1080}; } } #endif /* end of include guard: WF_TILE_PLUGIN_TREE */ wayfire-0.10.0/plugins/tile/tile-dragging.hpp0000664000175000017500000003402515053502647021001 0ustar dkondordkondor#pragma once #include "tree-controller.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/move-drag-interface.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/plugins/common/preview-indication.hpp" #include "tree.hpp" #include "tile-wset.hpp" #include "wayfire/debug.hpp" namespace wf { namespace tile { class drag_manager_t { wf::shared_data::ref_ptr_t drag_helper; std::shared_ptr preview; bool currently_dropping = false; public: drag_manager_t() { drag_helper->connect(&on_drag_motion); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_done); } ~drag_manager_t() { hide_preview(); } wf::signal::connection_t on_drag_motion = [=] (wf::move_drag::drag_motion_signal *ev) { if (should_show_preview(drag_helper->view, drag_helper->current_output)) { update_preview(drag_helper->current_output, drag_helper->view); } }; wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if (should_show_preview(drag_helper->view, ev->focus_output)) { drag_helper->set_scale(2, 0.5); update_preview(ev->focus_output, drag_helper->view); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if (should_show_preview(ev->main_view, ev->focused_output)) { currently_dropping = true; if (!handle_drop(ev->main_view, ev->focused_output)) { if (ev->main_view->get_output() != ev->focused_output) { // Allow usual movement to the new output. currently_dropping = false; move_drag::adjust_view_on_output(ev); } } currently_dropping = false; } hide_preview(); }; bool is_dragging(wayfire_toplevel_view view) { return currently_dropping; } bool dragged_view_is_tiled(wayfire_toplevel_view view) { return view && view_node_t::get_node(view); } bool should_show_preview(wayfire_toplevel_view view, wf::output_t *output) { return dragged_view_is_tiled(view) && output && (output->can_activate_plugin(CAPABILITY_MANAGE_COMPOSITOR) || output->is_plugin_active("simple-tile")); } void hide_preview() { if (this->preview) { auto target = preview->get_output() ? preview->get_output()->get_cursor_position().round_down() : wf::point_t{0, 0}; this->preview->set_target_geometry(target, 0.0, true); this->preview.reset(); } } void update_preview(wf::output_t *output, wayfire_toplevel_view dragged_view) { auto input = get_global_input_coordinates(output); auto view = check_drop_destination(output, input, dragged_view); if (!view) { return hide_preview(); } auto split = calculate_insert_type(view, input); if (preview && (preview->get_output() != output)) { hide_preview(); } if (!this->preview) { auto start_coords = get_wset_local_coordinates(output->wset(), input); preview = std::make_shared(start_coords, output, "simple-tile"); } auto preview_geometry = calculate_split_preview(view, split); preview_geometry = get_wset_local_coordinates(output->wset(), preview_geometry); if (preview_geometry != preview->get_target_geometry()) { this->preview->set_target_geometry(preview_geometry, 1.0); } } static int find_idx(nonstd::observer_ptr child) { auto& children = child->parent->children; auto it = std::find_if(children.begin(), children.end(), [child] (const std::unique_ptr& n) { return n.get() == child.get(); }); wf::dassert(it != children.end(), "Child not found"); return it - children.begin(); } static int remove_child(nonstd::observer_ptr child) { int idx = find_idx(child); child->parent->children.erase(child->parent->children.begin() + idx); return idx; } void move_tiled_view(wayfire_toplevel_view view, wf::output_t *target) { wf::scene::remove_child(view->get_root_node()); view->get_wset()->remove_view(view); target->wset()->add_view(view); auto& wsdata = tile_workspace_set_data_t::get(target); auto vp = target->wset()->get_current_workspace(); wf::scene::readd_front(wsdata.tiled_sublayer[vp.x][vp.y], view->get_root_node()); } void handle_swap(wayfire_toplevel_view a, wayfire_toplevel_view b) { auto output_a = a->get_output(); auto output_b = b->get_output(); if (output_a != output_b) { wf::emit_view_pre_moved_to_wset_pre(a, output_a->wset(), output_b->wset()); wf::emit_view_pre_moved_to_wset_pre(b, output_b->wset(), output_a->wset()); move_tiled_view(a, output_b); move_tiled_view(b, output_a); } { autocommit_transaction_t tx; auto old_tile_a = view_node_t::get_node(a); auto old_tile_b = view_node_t::get_node(b); auto parent_a = old_tile_a->parent; auto parent_b = old_tile_b->parent; auto geometry_a = old_tile_a->geometry; auto geometry_b = old_tile_b->geometry; auto gaps_a = old_tile_a->get_gaps(); auto gaps_b = old_tile_b->get_gaps(); int idx_a = remove_child(old_tile_a); int idx_b = remove_child(old_tile_b); auto new_b = std::make_unique(b); new_b->set_gaps(gaps_a); new_b->set_geometry(geometry_a, tx.tx); auto new_a = std::make_unique(a); new_a->set_gaps(gaps_b); new_a->set_geometry(geometry_b, tx.tx); new_a->parent = parent_b; new_b->parent = parent_a; // Insert to the smaller position first, if they both have the same parent. // Otherwise the larger position might not be valid because we removed 2 elements. if ((parent_a != parent_b) || (idx_a < idx_b)) { parent_a->children.insert(parent_a->children.begin() + idx_a, std::move(new_b)); parent_b->children.insert(parent_b->children.begin() + idx_b, std::move(new_a)); } else { parent_b->children.insert(parent_b->children.begin() + idx_b, std::move(new_a)); parent_a->children.insert(parent_a->children.begin() + idx_a, std::move(new_b)); } } if (output_a != output_b) { wf::emit_view_moved_to_wset(a, output_a->wset(), output_b->wset()); wf::emit_view_moved_to_wset(b, output_b->wset(), output_a->wset()); } } void handle_move_retile(wayfire_toplevel_view source, nonstd::observer_ptr target, split_insertion_t split) { auto source_output = source->get_output(); auto target_output = target->view->get_output(); if (source_output != target_output) { wf::emit_view_pre_moved_to_wset_pre(source, source->get_wset(), target->view->get_wset()); move_tiled_view(source, target_output); } autocommit_transaction_t tx; auto split_type = (split == INSERT_LEFT || split == INSERT_RIGHT) ? SPLIT_VERTICAL : SPLIT_HORIZONTAL; auto source_node = view_node_t::get_node(source); if (target->parent->get_split_direction() == split_type) { /* We can simply add the dragged view as a sibling of the target view */ auto src = source_node->parent->remove_child(source_node, tx.tx); int idx = find_idx(target); if ((split == INSERT_RIGHT) || (split == INSERT_BELOW)) { ++idx; } target->parent->add_child(std::move(src), tx.tx, idx); } else { /* Case 2: we need a new split just for the dropped on and the dragged * views */ auto new_split = std::make_unique(split_type); /* The size will be autodetermined by the tree structure, but we set * some valid size here to avoid UB */ new_split->set_geometry(target->geometry, tx.tx); /* Find the position of the dropped view and its parent */ int idx = find_idx(target); auto dropped_parent = target->parent; /* Remove both views */ auto dropped_view = target->parent->remove_child(target, tx.tx); auto dragged_view = source_node->parent->remove_child(source_node, tx.tx); if ((split == INSERT_ABOVE) || (split == INSERT_LEFT)) { new_split->add_child(std::move(dragged_view), tx.tx); new_split->add_child(std::move(dropped_view), tx.tx); } else { new_split->add_child(std::move(dropped_view), tx.tx); new_split->add_child(std::move(dragged_view), tx.tx); } /* Put them in place */ dropped_parent->add_child(std::move(new_split), tx.tx, idx); } tile_workspace_set_data_t::get(source_output).refresh(tx.tx); tile_workspace_set_data_t::get(target_output).refresh(tx.tx); if (source_output != target_output) { wf::emit_view_moved_to_wset(source, source_output->wset(), target_output->wset()); } } bool handle_drop(wayfire_toplevel_view view, wf::output_t *output) { auto input = get_global_input_coordinates(output); auto dropped_at = check_drop_destination(output, input, view); if (!dropped_at) { return false; } auto split = calculate_insert_type(dropped_at, input); if (split == INSERT_NONE) { return false; } if (split == INSERT_SWAP) { handle_swap(view, dropped_at->view); } else { handle_move_retile(view, dropped_at, split); } return true; } /** * Calculate the position of the split that needs to be created if a view is * dropped at @input over @node * * @param sensitivity What percentage of the view is "active", i.e the threshold * for INSERT_NONE */ static split_insertion_t calculate_insert_type( nonstd::observer_ptr node, wf::point_t input, double sensitivity) { auto window = node->geometry; if (!(window & input)) { return INSERT_NONE; } /* * Calculate how much to the left, right, top and bottom of the window * our input is, then filter through the sensitivity. * * In the end, take the edge which is closest to input. */ std::vector> edges; double px = 1.0 * (input.x - window.x) / window.width; double py = 1.0 * (input.y - window.y) / window.height; edges.push_back({px, INSERT_LEFT}); edges.push_back({py, INSERT_ABOVE}); edges.push_back({1.0 - px, INSERT_RIGHT}); edges.push_back({1.0 - py, INSERT_BELOW}); /* Remove edges that are too far away */ auto it = std::remove_if(edges.begin(), edges.end(), [sensitivity] (auto pair) { return pair.first > sensitivity; }); edges.erase(it, edges.end()); if (edges.empty()) { return INSERT_SWAP; } /* Return the closest edge */ return std::min_element(edges.begin(), edges.end())->second; } /* By default, 1/3rd of the view can be dropped into */ static constexpr double SPLIT_PREVIEW_PERCENTAGE = 1.0 / 3.0; /** * Calculate the position of the split that needs to be created if a view is * dropped at @input over @node */ split_insertion_t calculate_insert_type( nonstd::observer_ptr node, wf::point_t input) { return calculate_insert_type(node, input, SPLIT_PREVIEW_PERCENTAGE); } /** * Calculate the bounds of the split preview */ wf::geometry_t calculate_split_preview(nonstd::observer_ptr over, split_insertion_t split_type) { auto preview = over->geometry; switch (split_type) { case INSERT_RIGHT: preview.x += preview.width * (1 - SPLIT_PREVIEW_PERCENTAGE); // fallthrough case INSERT_LEFT: preview.width = preview.width * SPLIT_PREVIEW_PERCENTAGE; break; case INSERT_BELOW: preview.y += preview.height * (1 - SPLIT_PREVIEW_PERCENTAGE); // fallthrough case INSERT_ABOVE: preview.height = preview.height * SPLIT_PREVIEW_PERCENTAGE; break; default: break; // nothing to do } return preview; } /** * Return the node under the input which is suitable for dropping on. */ nonstd::observer_ptr check_drop_destination(wf::output_t *output, wf::point_t global_coords, wayfire_toplevel_view dragged_view) { auto ws = output->wset()->get_current_workspace(); auto dropped_at = find_view_at(tile_workspace_set_data_t::get(output).roots[ws.x][ws.y], global_coords); if (!dropped_at || (dropped_at->view == dragged_view)) { return nullptr; } return dropped_at; } }; } } wayfire-0.10.0/plugins/tile/tile-wset.hpp0000664000175000017500000002342515053502647020203 0ustar dkondordkondor#pragma once #include "tree.hpp" #include "tree-controller.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/txn/transaction-manager.hpp" #include "wayfire/scene-operations.hpp" #include #include struct autocommit_transaction_t { public: wf::txn::transaction_uptr tx; autocommit_transaction_t() { tx = wf::txn::transaction_t::create(); } ~autocommit_transaction_t() { if (!tx->get_objects().empty()) { wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } } }; namespace wf { /** * When a view is moved from one output to the other, we want to keep its tiled * status. To achieve this, we do the following: * * 1. In view-pre-moved-to-output handler, we set view_auto_tile_t custom data. * 2. In detach handler, we just remove the view as usual. * 3. We now know we will receive attach as next event. * Check for view_auto_tile_t, and tile the view again. */ class view_auto_tile_t : public wf::custom_data_t {}; class tile_workspace_set_data_t : public wf::custom_data_t { public: std::vector>> roots; std::vector> tiled_sublayer; static constexpr wf::tile::split_direction_t default_split = wf::tile::SPLIT_VERTICAL; wf::option_wrapper_t inner_gaps{"simple-tile/inner_gap_size"}; wf::option_wrapper_t outer_horiz_gaps{"simple-tile/outer_horiz_gap_size"}; wf::option_wrapper_t outer_vert_gaps{"simple-tile/outer_vert_gap_size"}; tile_workspace_set_data_t(std::shared_ptr wset) { this->wset = wset; wset->connect(&on_wset_attached); wset->connect(&on_workspace_grid_changed); resize_roots(wset->get_workspace_grid_size()); if (wset->get_attached_output()) { wset->get_attached_output()->connect(&on_workarea_changed); } inner_gaps.set_callback(update_gaps); outer_horiz_gaps.set_callback(update_gaps); outer_vert_gaps.set_callback(update_gaps); } wf::signal::connection_t on_workarea_changed = [=] (auto) { update_root_size(); }; wf::signal::connection_t on_wset_attached = [=] (auto) { on_workarea_changed.disconnect(); if (wset.lock()->get_attached_output()) { wset.lock()->get_attached_output()->connect(&on_workarea_changed); update_root_size(); } }; wf::signal::connection_t on_workspace_grid_changed = [=] (auto) { wf::dassert(!wset.expired(), "wset should not expire, ever!"); resize_roots(wset.lock()->get_workspace_grid_size()); }; void resize_roots(wf::dimensions_t wsize) { for (size_t i = 0; i < tiled_sublayer.size(); i++) { for (size_t j = 0; j < tiled_sublayer[i].size(); j++) { if (wset.lock()->is_workspace_valid({(int)i, (int)j})) { destroy_sublayer(tiled_sublayer[i][j]); } } } roots.resize(wsize.width); tiled_sublayer.resize(wsize.width); for (int i = 0; i < wsize.width; i++) { roots[i].resize(wsize.height); tiled_sublayer[i].resize(wsize.height); for (int j = 0; j < wsize.height; j++) { roots[i][j] = std::make_unique(default_split); tiled_sublayer[i][j] = std::make_shared(false); wf::scene::add_front(wset.lock()->get_node(), tiled_sublayer[i][j]); } } update_root_size(); update_gaps(); } void update_root_size() { auto wo = wset.lock()->get_attached_output(); wf::geometry_t workarea = wo ? wo->workarea->get_workarea() : tile::default_output_resolution; wf::geometry_t output_geometry = wset.lock()->get_last_output_geometry().value_or(tile::default_output_resolution); auto wsize = wset.lock()->get_workspace_grid_size(); for (int i = 0; i < wsize.width; i++) { for (int j = 0; j < wsize.height; j++) { /* Set size */ auto vp_geometry = workarea; vp_geometry.x += i * output_geometry.width; vp_geometry.y += j * output_geometry.height; autocommit_transaction_t tx; roots[i][j]->set_geometry(vp_geometry, tx.tx); } } } void destroy_sublayer(wf::scene::floating_inner_ptr sublayer) { // Transfer views to the top auto root = wset.lock()->get_node(); auto children = root->get_children(); auto sublayer_children = sublayer->get_children(); sublayer->set_children_list({}); children.insert(children.end(), sublayer_children.begin(), sublayer_children.end()); root->set_children_list(children); wf::scene::update(root, wf::scene::update_flag::CHILDREN_LIST); wf::scene::remove_child(sublayer); } tile::gap_size_t get_gaps() const { return { .left = outer_horiz_gaps, .right = outer_horiz_gaps, .top = outer_vert_gaps, .bottom = outer_vert_gaps, .internal = inner_gaps, }; } void update_gaps_with_tx(wf::txn::transaction_uptr& tx) { for (auto& col : roots) { for (auto& root : col) { root->set_gaps(get_gaps()); root->set_geometry(root->geometry, tx); } } } void refresh(wf::txn::transaction_uptr& tx) { flatten_roots(); update_gaps_with_tx(tx); } std::function update_gaps = [=] () { autocommit_transaction_t tx; update_gaps_with_tx(tx.tx); }; void flatten_roots() { for (auto& col : roots) { for (auto& root : col) { tile::flatten_tree(root); } } } static tile_workspace_set_data_t& get(std::shared_ptr set) { if (!set->has_data()) { set->store_data(std::make_unique(set)); } return *set->get_data(); } static tile_workspace_set_data_t& get(wf::output_t *output) { return get(output->wset()); } static std::unique_ptr& get_current_root(wf::output_t *output) { auto set = output->wset(); auto vp = set->get_current_workspace(); auto& data = get(output); return data.roots[vp.x][vp.y]; } static scene::floating_inner_ptr get_current_sublayer(wf::output_t *output) { auto set = output->wset(); auto vp = set->get_current_workspace(); auto& data = get(output); return data.tiled_sublayer[vp.x][vp.y]; } std::weak_ptr wset; std::unique_ptr setup_view_tiling(wayfire_toplevel_view view, wf::point_t vp) { view->set_allowed_actions(VIEW_ALLOW_WS_CHANGE); auto node = view->get_root_node(); wf::scene::readd_front(tiled_sublayer[vp.x][vp.y], node); view_bring_to_front(view); return std::make_unique(view); } void attach_view(wayfire_toplevel_view view, std::optional _vp = {}) { auto vp = _vp.value_or(wset.lock()->get_current_workspace()); auto view_node = setup_view_tiling(view, vp); { autocommit_transaction_t tx; roots[vp.x][vp.y]->as_split_node()->add_child(std::move(view_node), tx.tx); } consider_exit_fullscreen(view); } /** Remove the given view from its tiling container */ void detach_views(std::vector> views, bool reinsert = true) { { autocommit_transaction_t tx; for (auto& v : views) { auto view = v->view; view->set_allowed_actions(VIEW_ALLOW_ALL); // After this, `v` is freed. v->parent->remove_child(v, tx.tx); if (view->pending_fullscreen() && view->is_mapped()) { wf::get_core().default_wm->fullscreen_request(view, nullptr, false); } if (reinsert && view->get_output()) { wf::scene::readd_front(view->get_output()->wset()->get_node(), view->get_root_node()); } } } /* View node is invalid now */ flatten_roots(); update_root_size(); } /** * Consider unfullscreening all fullscreen views because a new view has been focused or attached to the * tiling tree. */ void consider_exit_fullscreen(wayfire_toplevel_view view) { if (tile::view_node_t::get_node(view) && !view->pending_fullscreen()) { auto vp = this->wset.lock()->get_current_workspace(); for_each_view(roots[vp.x][vp.y], [&] (wayfire_toplevel_view view) { if (view->pending_fullscreen()) { set_view_fullscreen(view, false); } }); } } void set_view_fullscreen(wayfire_toplevel_view view, bool fullscreen) { /* Set fullscreen, and trigger resizing of the views (which will commit the view) */ view->toplevel()->pending().fullscreen = fullscreen; update_root_size(); } }; } wayfire-0.10.0/plugins/tile/tree.cpp0000664000175000017500000003607415053502647017224 0ustar dkondordkondor#include "tree.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { namespace tile { void tree_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr&) { this->geometry = geometry; } nonstd::observer_ptr tree_node_t::as_split_node() { return nonstd::make_observer(dynamic_cast(this)); } nonstd::observer_ptr tree_node_t::as_view_node() { return nonstd::make_observer(dynamic_cast(this)); } wf::point_t get_wset_local_coordinates(std::shared_ptr wset, wf::point_t p) { auto vp = wset->get_current_workspace(); auto size = wset->get_last_output_geometry().value_or(default_output_resolution); p.x -= vp.x * size.width; p.y -= vp.y * size.height; return p; } wf::geometry_t get_wset_local_coordinates(std::shared_ptr wset, wf::geometry_t g) { auto new_tl = get_wset_local_coordinates(wset, wf::point_t{g.x, g.y}); g.x = new_tl.x; g.y = new_tl.y; return g; } /* ---------------------- split_node_t implementation ----------------------- */ wf::geometry_t split_node_t::get_child_geometry( int32_t child_pos, int32_t child_size) { wf::geometry_t child_geometry = this->geometry; switch (get_split_direction()) { case SPLIT_HORIZONTAL: child_geometry.y += child_pos; child_geometry.height = child_size; break; case SPLIT_VERTICAL: child_geometry.x += child_pos; child_geometry.width = child_size; break; } return child_geometry; } int32_t split_node_t::calculate_splittable(wf::geometry_t available) const { switch (get_split_direction()) { case SPLIT_HORIZONTAL: return available.height; case SPLIT_VERTICAL: return available.width; } return -1; } int32_t split_node_t::calculate_splittable() const { return calculate_splittable(this->geometry); } void split_node_t::recalculate_children(wf::geometry_t available, wf::txn::transaction_uptr& tx) { if (this->children.empty()) { return; } double old_child_sum = 0.0; for (auto& child : this->children) { old_child_sum += calculate_splittable(child->geometry); } int32_t total_splittable = calculate_splittable(available); /* Sum of children sizes up to now */ double up_to_now = 0.0; auto progress = [=] (double current) { return (current / old_child_sum) * total_splittable; }; set_gaps(this->gaps); /* For each child, assign its percentage of the whole. */ for (auto& child : this->children) { /* Calculate child_start/end every time using the percentage from the * beginning. This way we avoid rounding errors causing empty spaces */ int32_t child_start = progress(up_to_now); up_to_now += calculate_splittable(child->geometry); int32_t child_end = progress(up_to_now); /* Set new size */ int32_t child_size = child_end - child_start; child->set_geometry(get_child_geometry(child_start, child_size), tx); } } void split_node_t::add_child(std::unique_ptr child, wf::txn::transaction_uptr& tx, int index) { /* * Strategy: * Calculate the size of the new child relative to the old children, so * that proportions are right. After that, rescale all nodes. */ int num_children = this->children.size(); /* Calculate where the new child should be, in current proportions */ int size_new_child; if (num_children > 0) { size_new_child = (calculate_splittable() + num_children - 1) / num_children; } else { size_new_child = calculate_splittable(); } if ((index == -1) || (index > num_children)) { index = num_children; } /* Add child to the list */ child->parent = {this}; // Set size of the child to make sure it gets properly recalculated later child->geometry = get_child_geometry(0, size_new_child); this->children.emplace(this->children.begin() + index, std::move(child)); set_gaps(this->gaps); /* Recalculate geometry */ recalculate_children(geometry, tx); } std::unique_ptr split_node_t::remove_child( nonstd::observer_ptr child, wf::txn::transaction_uptr& tx) { /* Remove child */ std::unique_ptr result; auto it = this->children.begin(); while (it != this->children.end()) { if (it->get() == child.get()) { result = std::move(*it); it = this->children.erase(it); } else { ++it; } } /* Remaining children have the full geometry */ recalculate_children(this->geometry, tx); result->parent = nullptr; return result; } void split_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) { tree_node_t::set_geometry(geometry, tx); recalculate_children(geometry, tx); } void split_node_t::set_gaps(const gap_size_t& gaps) { this->gaps = gaps; for (const auto& child : this->children) { gap_size_t child_gaps = gaps; /* See which edges are modified by this split */ int32_t *first_edge, *second_edge; switch (this->split_direction) { case SPLIT_HORIZONTAL: first_edge = &child_gaps.top; second_edge = &child_gaps.bottom; break; case SPLIT_VERTICAL: first_edge = &child_gaps.left; second_edge = &child_gaps.right; break; default: assert(false); } /* Override internal edges */ if (child != this->children.front()) { *first_edge = gaps.internal; } if (child != this->children.back()) { *second_edge = gaps.internal; } child->set_gaps(child_gaps); } } split_direction_t split_node_t::get_split_direction() const { return this->split_direction; } split_node_t::split_node_t(split_direction_t dir) { this->split_direction = dir; this->geometry = {0, 0, 0, 0}; } /* -------------------- view_node_t implementation -------------------------- */ struct view_node_custom_data_t : public custom_data_t { nonstd::observer_ptr ptr; view_node_custom_data_t(view_node_t *node) { ptr = nonstd::make_observer(node); } }; /** * A simple transformer to scale and translate the view in such a way that * its displayed wm geometry region is a specified box on the screen */ static const std::string scale_transformer_name = "simple-tile-scale-transformer"; struct view_node_t::scale_transformer_t : public wf::scene::view_2d_transformer_t { wf::geometry_t box; scale_transformer_t(wayfire_toplevel_view view, wf::geometry_t box) : wf::scene::view_2d_transformer_t(view) { set_box(box); } void set_box(wf::geometry_t box) { assert(box.width > 0 && box.height > 0); auto _view = view.lock(); if (!_view) { return; } _view->damage(); auto current = toplevel_cast(_view.get())->get_geometry(); if ((current.width <= 0) || (current.height <= 0)) { /* view possibly unmapped?? */ return; } double scale_horiz = 1.0 * box.width / current.width; double scale_vert = 1.0 * box.height / current.height; /* Position of top-left corner after scaling */ double scaled_x = current.x + (current.width / 2.0 * (1 - scale_horiz)); double scaled_y = current.y + (current.height / 2.0 * (1 - scale_vert)); this->scale_x = scale_horiz; this->scale_y = scale_vert; this->translation_x = box.x - scaled_x; this->translation_y = box.y - scaled_y; } }; /** * A class for animating the view, emits a signal when the animation is over. */ class tile_view_animation_t : public wf::grid::grid_animation_t { public: using wf::grid::grid_animation_t::grid_animation_t; ~tile_view_animation_t() { // The grid animation does this too, however, we want to remove the // transformer so that we can enforce the correct geometry from the // start. view->get_transformed_node()->rem_transformer(); tile_adjust_transformer_signal ev; view->emit(&ev); } tile_view_animation_t(const tile_view_animation_t &) = delete; tile_view_animation_t(tile_view_animation_t &&) = delete; tile_view_animation_t& operator =(const tile_view_animation_t&) = delete; tile_view_animation_t& operator =(tile_view_animation_t&&) = delete; }; view_node_t::view_node_t(wayfire_toplevel_view view) { this->view = view; wf::dassert(!view->has_data(), "View already has custom data!"); view->store_data(std::make_unique(this)); this->on_geometry_changed.set_callback([=] (auto) { update_transformer(); }); on_adjust_transformer.set_callback([=] (auto) { update_transformer(); }); view->connect(&on_geometry_changed); view->connect(&on_adjust_transformer); } view_node_t::~view_node_t() { view->get_transformed_node()->rem_transformer(scale_transformer_name); view->erase_data(); } void view_node_t::set_gaps(const gap_size_t& size) { if ((this->gaps.top != size.top) || (this->gaps.bottom != size.bottom) || (this->gaps.left != size.left) || (this->gaps.right != size.right)) { this->gaps = size; } } wf::geometry_t view_node_t::calculate_target_geometry() { /* Calculate view geometry in coordinates local to the active workspace, * because tree coordinates are kept in workspace-agnostic coordinates. */ auto wset = view->get_wset(); auto local_geometry = get_wset_local_coordinates(wset, geometry); local_geometry.x += gaps.left; local_geometry.y += gaps.top; local_geometry.width -= gaps.left + gaps.right; local_geometry.height -= gaps.top + gaps.bottom; auto size = wset->get_last_output_geometry().value_or(default_output_resolution); /* If view is maximized, we want to use the full available geometry */ if (view->pending_fullscreen()) { auto vp = wset->get_current_workspace(); int view_vp_x = std::floor(1.0 * geometry.x / size.width); int view_vp_y = std::floor(1.0 * geometry.y / size.height); local_geometry = { (view_vp_x - vp.x) * size.width, (view_vp_y - vp.y) * size.height, size.width, size.height, }; } if (view->sticky) { local_geometry.x = (local_geometry.x % size.width + size.width) % size.width; local_geometry.y = (local_geometry.y % size.height + size.height) % size.height; } return local_geometry; } bool view_node_t::needs_crossfade() { if (animation_duration.value().length_ms == 0) { return false; } if (view->has_data()) { return true; } if (!view->get_output()) { return false; } if (view->get_output()->is_plugin_active("simple-tile")) { // Disable animations while controllers are active return false; } return true; } static nonstd::observer_ptr ensure_animation( wayfire_toplevel_view view, wf::option_sptr_t duration) { if (!view->has_data()) { const auto type = wf::grid::grid_animation_t::CROSSFADE; view->store_data( std::make_unique(view, type, duration)); } return view->get_data(); } void view_node_t::set_geometry(wf::geometry_t geometry, wf::txn::transaction_uptr& tx) { tree_node_t::set_geometry(geometry, tx); if (!view->is_mapped()) { return; } wf::get_core().default_wm->update_last_windowed_geometry(view); view->toplevel()->pending().tiled_edges = TILED_EDGES_ALL; tx->add_object(view->toplevel()); auto target = calculate_target_geometry(); if (this->needs_crossfade() && (target != view->get_geometry())) { view->get_transformed_node()->rem_transformer(scale_transformer_name); ensure_animation(view, animation_duration) ->adjust_target_geometry(target, -1, tx); } else { view->toplevel()->pending().geometry = target; tx->add_object(view->toplevel()); } } void view_node_t::update_transformer() { auto target_geometry = calculate_target_geometry(); if ((target_geometry.width <= 0) || (target_geometry.height <= 0)) { return; } if (view->has_data()) { // Still animating return; } auto wm = view->get_geometry(); if (wm != target_geometry) { auto tr = ensure_named_transformer(view, wf::TRANSFORMER_2D, scale_transformer_name, view, target_geometry); tr->set_box(target_geometry); } else { view->get_transformed_node()->rem_transformer(scale_transformer_name); } } nonstd::observer_ptr view_node_t::get_node(wayfire_view view) { if (!view->has_data()) { return nullptr; } return view->get_data()->ptr; } /* ----------------- Generic tree operations implementation ----------------- */ bool flatten_tree(std::unique_ptr& root) { /* Cannot flatten a view node */ if (root->as_view_node()) { return true; } for (auto it = root->children.begin(); it != root->children.end();) { if (!flatten_tree(*it)) { it = root->children.erase(it); } else { ++it; } } if (root->children.empty()) { return false; } /* No flattening required on this level */ if (root->children.size() >= 2) { return true; } nonstd::observer_ptr child_ptr = {root->children.front()}; /* A single view child => cannot make it root */ if (child_ptr->as_view_node()) { if (!root->parent) { return true; } } /* Rewire the tree, skipping the current root */ child_ptr->parent = root->parent; root = std::move(root->children.front()); return true; } nonstd::observer_ptr get_root( nonstd::observer_ptr node) { if (!node->parent) { return {dynamic_cast(node.get())}; } return get_root(node->parent); } } } wayfire-0.10.0/plugins/tile/meson.build0000664000175000017500000000066015053502647017713 0ustar dkondordkondortile = shared_module('simple-tile', ['tile-plugin.cpp', 'tree.cpp', 'tree-controller.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, grid_inc, wobbly_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], link_with: [move_drag_interface], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.10.0/plugins/tile/tile-plugin.cpp0000664000175000017500000003755115053502647020517 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "tree-controller.hpp" #include "tree.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view.hpp" #include "tile-wset.hpp" #include "tile-ipc.hpp" #include "tile-dragging.hpp" static bool can_tile_view(wayfire_toplevel_view view) { if (view->parent) { return false; } if ((view->toplevel()->get_min_size() == view->toplevel()->get_max_size()) && (view->toplevel()->get_min_size().width > 0) && (view->toplevel()->get_min_size().height > 0)) { return false; } return true; } namespace wf { class tile_output_plugin_t : public wf::pointer_interaction_t, public wf::custom_data_t { private: wf::view_matcher_t tile_by_default{"simple-tile/tile_by_default"}; wf::option_wrapper_t keep_fullscreen_on_adjacent{"simple-tile/keep_fullscreen_on_adjacent"}; wf::option_wrapper_t button_move{"simple-tile/button_move"}; wf::option_wrapper_t button_resize{"simple-tile/button_resize"}; wf::option_wrapper_t key_toggle_tile{"simple-tile/key_toggle"}; wf::option_wrapper_t key_focus_left{"simple-tile/key_focus_left"}; wf::option_wrapper_t key_focus_right{"simple-tile/key_focus_right"}; wf::option_wrapper_t key_focus_above{"simple-tile/key_focus_above"}; wf::option_wrapper_t key_focus_below{"simple-tile/key_focus_below"}; wf::output_t *output; public: std::unique_ptr input_grab; static std::unique_ptr get_default_controller() { return std::make_unique(); } std::unique_ptr controller = get_default_controller(); /** Check whether we currently have a fullscreen tiled view */ bool has_fullscreen_view() { int count_fullscreen = 0; for_each_view(tile_workspace_set_data_t::get_current_root(output), [&] (wayfire_toplevel_view view) { count_fullscreen += view->pending_fullscreen(); }); return count_fullscreen > 0; } /** Check whether the current pointer focus is tiled view */ wayfire_toplevel_view get_tiled_focus() { auto focus = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (focus && tile::view_node_t::get_node(focus)) { return focus; } return nullptr; } template void start_controller() { auto tiled_focus = get_tiled_focus(); /* No action possible in this case */ if (has_fullscreen_view() || !tiled_focus) { return; } if (!output->activate_plugin(&grab_interface)) { return; } input_grab->grab_input(wf::scene::layer::OVERLAY); controller = std::make_unique(output->wset().get(), tiled_focus); } void stop_controller(bool force_stop) { if (!output->is_plugin_active(grab_interface.name)) { return; } // Deactivate plugin, so that others can react to the events output->deactivate_plugin(&grab_interface); input_grab->ungrab_input(); controller->input_released(force_stop); controller = get_default_controller(); } bool tile_window_by_default(wayfire_toplevel_view view) { return tile_by_default.matches(view) && can_tile_view(view); } void attach_view(wayfire_toplevel_view view, std::optional vp = {}) { if (!view->get_wset()) { return; } stop_controller(true); tile_workspace_set_data_t::get(view->get_wset()).attach_view(view, vp); } void detach_view(wayfire_toplevel_view view, bool reinsert = true) { stop_controller(true); // Note that stopping the controller might untile the view, or change its tiled node. if (auto node = tile::view_node_t::get_node(view)) { tile_workspace_set_data_t::get(view->get_wset()).detach_views({node}, reinsert); } } wf::signal::connection_t on_view_mapped = [=] (view_mapped_signal *ev) { if (auto toplevel = toplevel_cast(ev->view)) { if (tile_window_by_default(toplevel)) { attach_view(toplevel); } } }; wf::signal::connection_t on_tile_request = [=] (view_tile_request_signal *ev) { if (ev->carried_out || !tile::view_node_t::get_node(ev->view)) { return; } // we ignore those requests because we manage the tiled state manually ev->carried_out = true; }; wf::signal::connection_t on_fullscreen_request = [=] (view_fullscreen_request_signal *ev) { if (ev->carried_out || !tile::view_node_t::get_node(ev->view)) { return; } ev->carried_out = true; tile_workspace_set_data_t::get(ev->view->get_wset()).set_view_fullscreen(ev->view, ev->state); }; void change_view_workspace(wayfire_toplevel_view view, std::optional vp = {}) { auto existing_node = wf::tile::view_node_t::get_node(view); if (existing_node) { detach_view(view); attach_view(view, vp); } } wf::signal::connection_t on_view_change_workspace = [=] (view_change_workspace_signal *ev) { if (ev->old_workspace_valid) { change_view_workspace(ev->view, ev->to); } }; wf::signal::connection_t on_view_minimized = [=] (view_minimized_signal *ev) { auto existing_node = wf::tile::view_node_t::get_node(ev->view); if (ev->view->minimized && existing_node) { detach_view(ev->view); } if (!ev->view->minimized && tile_window_by_default(ev->view)) { attach_view(ev->view); } }; /** * Execute the given function on the focused view iff we can activate the * tiling plugin, there is a focused view and the focused view is a tiled * view * * @param need_tiled Whether the view needs to be tiled */ bool conditioned_view_execute(bool need_tiled, std::function func) { auto view = wf::get_core().seat->get_active_view(); if (!toplevel_cast(view) || (view->get_output() != output)) { return false; } if (need_tiled && !tile::view_node_t::get_node(view)) { return false; } if (output->can_activate_plugin(&grab_interface)) { func(toplevel_cast(view)); return true; } return false; } wf::key_callback on_toggle_tiled_state = [=] (auto) { return conditioned_view_execute(false, [=] (wayfire_toplevel_view view) { auto existing_node = tile::view_node_t::get_node(view); if (existing_node) { detach_view(view); wf::get_core().default_wm->tile_request(view, 0); } else { attach_view(view); } }); }; bool focus_adjacent(tile::split_insertion_t direction) { return conditioned_view_execute(true, [=] (wayfire_toplevel_view view) { auto adjacent = tile::find_first_view_in_direction( tile::view_node_t::get_node(view), direction); bool was_fullscreen = view->pending_fullscreen(); if (adjacent) { /* This will lower the fullscreen status of the view */ view_bring_to_front(adjacent->view); wf::get_core().seat->focus_view(adjacent->view); if (was_fullscreen && keep_fullscreen_on_adjacent) { wf::get_core().default_wm->fullscreen_request(adjacent->view, output, true); } } }); } wf::key_callback on_focus_adjacent = [=] (wf::keybinding_t binding) { if (binding == key_focus_left) { return focus_adjacent(tile::INSERT_LEFT); } if (binding == key_focus_right) { return focus_adjacent(tile::INSERT_RIGHT); } if (binding == key_focus_above) { return focus_adjacent(tile::INSERT_ABOVE); } if (binding == key_focus_below) { return focus_adjacent(tile::INSERT_BELOW); } return false; }; wf::button_callback on_move_view = [=] (auto) { start_controller(); return false; // pass button to the grab node }; wf::button_callback on_resize_view = [=] (auto) { start_controller(); return false; // pass button to the grab node }; void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WL_POINTER_BUTTON_STATE_RELEASED) { stop_controller(false); } } void handle_pointer_motion(wf::pointf_t, uint32_t) override { controller->input_motion(); } void setup_callbacks() { output->add_button(button_move, &on_move_view); output->add_button(button_resize, &on_resize_view); output->add_key(key_toggle_tile, &on_toggle_tiled_state); output->add_key(key_focus_left, &on_focus_adjacent); output->add_key(key_focus_right, &on_focus_adjacent); output->add_key(key_focus_above, &on_focus_adjacent); output->add_key(key_focus_below, &on_focus_adjacent); } wf::plugin_activation_data_t grab_interface = { .name = "simple-tile", .capabilities = CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] { stop_controller(true); }, }; public: tile_output_plugin_t(wf::output_t *wo) { this->output = wo; input_grab = std::make_unique("simple-tile", output, nullptr, this, nullptr); output->connect(&on_view_mapped); output->connect(&on_tile_request); output->connect(&on_fullscreen_request); output->connect(&on_view_change_workspace); output->connect(&on_view_minimized); setup_callbacks(); } ~tile_output_plugin_t() { output->rem_binding(&on_move_view); output->rem_binding(&on_resize_view); output->rem_binding(&on_toggle_tiled_state); output->rem_binding(&on_focus_adjacent); stop_controller(true); } }; class tile_plugin_t : public wf::plugin_interface_t, wf::per_output_tracker_mixin_t<> { shared_data::ref_ptr_t ipc_repo; shared_data::ref_ptr_t drag_helper; std::unique_ptr preview_manager; public: void init() override { init_output_tracking(); wf::get_core().connect(&on_view_pre_moved_to_wset); wf::get_core().connect(&on_view_moved_to_wset); wf::get_core().connect(&on_focus_changed); wf::get_core().connect(&on_view_unmapped); ipc_repo->register_method("simple-tile/get-layout", ipc_get_layout); ipc_repo->register_method("simple-tile/set-layout", ipc_set_layout); preview_manager = std::make_unique(); } void fini() override { preview_manager.reset(); fini_output_tracking(); for (auto wset : workspace_set_t::get_all()) { wset->erase_data(); } for (auto wo : wf::get_core().output_layout->get_outputs()) { wo->erase_data(); } ipc_repo->unregister_method("simple-tile/get-layout"); ipc_repo->unregister_method("simple-tile/set-layout"); } void stop_controller(std::shared_ptr wset) { if (auto wo = wset->get_attached_output()) { auto tile = wo->get_data(); if (tile) { tile->stop_controller(true); } } } wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { auto toplevel = toplevel_cast(ev->view); if (!toplevel) { return; } if (wf::tile::view_node_t::get_node(ev->view)) { wf::dassert(toplevel->get_wset() != nullptr); auto wo = toplevel->get_output(); if (wo && (toplevel->get_wset() == wo->wset())) { wo->get_data()->detach_view(toplevel); } else { tile_workspace_set_data_t::get(toplevel->get_wset()).detach_views( {wf::tile::view_node_t::get_node(ev->view)}); } } }; wf::signal::connection_t on_view_pre_moved_to_wset = [=] (view_pre_moved_to_wset_signal *ev) { auto node = wf::tile::view_node_t::get_node(ev->view); if (node && !preview_manager->is_dragging(ev->view)) { ev->view->store_data(std::make_unique()); if (ev->old_wset) { stop_controller(ev->old_wset); tile_workspace_set_data_t::get(ev->old_wset).detach_views({node}); } } }; wf::signal::connection_t on_focus_changed = [=] (keyboard_focus_changed_signal *ev) { if (auto toplevel = toplevel_cast(wf::node_to_view(ev->new_focus))) { if (toplevel->get_wset()) { tile_workspace_set_data_t::get(toplevel->get_wset()).consider_exit_fullscreen(toplevel); } } }; wf::signal::connection_t on_view_moved_to_wset = [=] (view_moved_to_wset_signal *ev) { if (ev->view->has_data() && ev->new_wset) { ev->view->erase_data(); stop_controller(ev->new_wset); tile_workspace_set_data_t::get(ev->new_wset).attach_view(ev->view); } }; void handle_new_output(wf::output_t *output) override { output->store_data(std::make_unique(output)); } void handle_output_removed(wf::output_t *output) override { output->erase_data(); } ipc::method_callback ipc_get_layout = [=] (const wf::json_t& params) { return tile::handle_ipc_get_layout(params); }; ipc::method_callback ipc_set_layout = [=] (wf::json_t params) -> wf::json_t { return tile::handle_ipc_set_layout(params); }; }; std::unique_ptr& tile::get_root(wf::workspace_set_t *set, wf::point_t workspace) { return tile_workspace_set_data_t::get(set->shared_from_this()).roots[workspace.x][workspace.y]; } } DECLARE_WAYFIRE_PLUGIN(wf::tile_plugin_t); wayfire-0.10.0/plugins/tile/tree-controller.hpp0000664000175000017500000001132315053502647021400 0ustar dkondordkondor#ifndef WF_TILE_PLUGIN_TREE_CONTROLLER_HPP #define WF_TILE_PLUGIN_TREE_CONTROLLER_HPP #include "tree.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include #include /* Contains functions which are related to manipulating the tiling tree */ namespace wf { class preview_indication_t; namespace tile { /** * Run callback for each view in the tree */ void for_each_view(nonstd::observer_ptr root, std::function callback); enum split_insertion_t { /** Insert is invalid */ INSERT_NONE = 0, /** Insert above the view */ INSERT_ABOVE = 1, /** Insert below the view */ INSERT_BELOW = 2, /** Insert to the left of the view */ INSERT_LEFT = 3, /** Insert to the right of the view */ INSERT_RIGHT = 4, /** Insert by swapping with the source view */ INSERT_SWAP = 5, }; /** * Find the first view in the indicated direction */ nonstd::observer_ptr find_first_view_in_direction( nonstd::observer_ptr from, split_insertion_t direction); /** * Represents the current mode in which the tile plugin is. * * Invariant: while a controller is active, the tree structure shouldn't change, * except for changes by the controller itself. * * If such an external event happens, then controller will be destroyed. */ class tile_controller_t { public: virtual ~tile_controller_t() = default; /** Called when the input is moved */ virtual void input_motion() {} /** * Called when the input is released or the controller should stop * Note that a controller may be deleted without receiving input_released(), * in which case it should simply stop operation. */ virtual void input_released(bool force_stop) {} }; std::unique_ptr& get_root(wf::workspace_set_t *set, wf::point_t workspace); /** * Represents the moving view action, i.e dragging a window to change its * position in the grid */ class move_view_controller_t : public tile_controller_t { public: /** * Start the drag-to-reorder action. */ move_view_controller_t(wf::workspace_set_t *wset, wayfire_toplevel_view view); ~move_view_controller_t(); void input_motion() override; void input_released(bool force_stop) override; protected: wf::shared_data::ref_ptr_t drag_helper; }; class resize_view_controller_t : public tile_controller_t { public: /** * Start the drag-to-resize action. */ resize_view_controller_t(wf::workspace_set_t *wset, wayfire_toplevel_view view); ~resize_view_controller_t(); void input_motion() override; protected: wf::output_t *output; /** Last input event location */ wf::point_t last_point; /** Edges of the grabbed view that we're resizing */ uint32_t resizing_edges; /** Calculate the resizing edges for the grabbing view. */ uint32_t calculate_resizing_edges(wf::point_t point); /** The view we are resizing */ nonstd::observer_ptr grabbed_view; /* * A resizing pair of nodes is a pair of nodes we need to resize * The first one is always to the left/above the second one. */ using resizing_pair_t = std::pair, nonstd::observer_ptr>; /** The horizontally-aligned pair we're resizing */ resizing_pair_t horizontal_pair; /** The vertically-aligned pair we're resizing */ resizing_pair_t vertical_pair; /* * Find a resizing pair in the given direction. * * The resizing pair depends on the currently grabbed view and the * resizing edges. */ resizing_pair_t find_resizing_pair(bool horizontal); /** * Adjust the given positions and sizes while resizing. * * @param x1 The start of the first geometry * @param len1 The dimension of the first geometry * @param x2 The start of the second geometry, should be x1 + len1 * @param len2 The length of the second geometry * * @param delta How much change to apply */ void adjust_geometry(int32_t& x1, int32_t& len1, int32_t& x2, int32_t& len2, int32_t delta); }; /** * Calculate which view node is at the given position * * Returns null if no view nodes are present. */ nonstd::observer_ptr find_view_at(nonstd::observer_ptr root, wf::point_t input); /** * Translate coordinates from output-local coordinates to the coordinate * system of the tiling trees, depending on the current workspace */ wf::point_t get_global_input_coordinates(wf::output_t *output); } } #endif /* end of include guard: WF_TILE_PLUGIN_TREE_CONTROLLER_HPP */ wayfire-0.10.0/plugins/tile/tree-controller.cpp0000664000175000017500000002170715053502647021402 0ustar dkondordkondor#include "tree-controller.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { namespace tile { void for_each_view(nonstd::observer_ptr root, std::function callback) { if (root->as_view_node()) { callback(root->as_view_node()->view); return; } for (auto& child : root->children) { for_each_view(child, callback); } } nonstd::observer_ptr find_view_at( nonstd::observer_ptr root, wf::point_t input) { if (root->as_view_node()) { return root->as_view_node(); } for (auto& child : root->children) { if (child->geometry & input) { return find_view_at({child}, input); } } /* Children probably empty? */ return nullptr; } nonstd::observer_ptr find_first_view_in_direction( nonstd::observer_ptr from, split_insertion_t direction) { auto window = from->geometry; /* Since nodes are arranged tightly into a grid, we can just find the * proper edge and find the view there */ wf::point_t point; switch (direction) { case INSERT_ABOVE: point = { window.x + window.width / 2, window.y - 1, }; break; case INSERT_BELOW: point = { window.x + window.width / 2, window.y + window.height, }; break; case INSERT_LEFT: point = { window.x - 1, window.y + window.height / 2, }; break; case INSERT_RIGHT: point = { window.x + window.width, window.y + window.height / 2, }; break; default: assert(false); } auto root = from; while (root->parent) { root = root->parent; } return find_view_at(root, point); } /* ------------------------ move_view_controller_t -------------------------- */ move_view_controller_t::move_view_controller_t(wf::workspace_set_t *set, wayfire_toplevel_view grabbed_view) { if (this->drag_helper->view) { // Race conditions are possible: spontaneous move requests from xwayland could trigger move without // a pressed button, and then if the user tries to use simple-tile, it leads to a crash. return; } this->drag_helper->set_pending_drag(wf::get_core().get_cursor_position()); move_drag::drag_options_t drag_options; drag_options.initial_scale = 1.0; drag_options.enable_snap_off = true; drag_options.snap_off_threshold = 20; drag_options.join_views = false; this->drag_helper->start_drag(grabbed_view, drag_options); } move_view_controller_t::~move_view_controller_t() {} void move_view_controller_t::input_motion() { drag_helper->handle_motion(wf::get_core().get_cursor_position().round_down()); } void move_view_controller_t::input_released(bool force_stop) { drag_helper->handle_input_released(); } wf::geometry_t eval(nonstd::observer_ptr node) { return node ? node->geometry : wf::geometry_t{0, 0, 0, 0}; } /* ----------------------- resize tile controller --------------------------- */ resize_view_controller_t::resize_view_controller_t(wf::workspace_set_t *wset, wayfire_toplevel_view) { this->last_point = get_global_input_coordinates(wset->get_attached_output()); this->grabbed_view = find_view_at(get_root(wset, wset->get_current_workspace()), last_point); this->output = wset->get_attached_output(); if (this->grabbed_view) { this->resizing_edges = calculate_resizing_edges(last_point); horizontal_pair = this->find_resizing_pair(true); vertical_pair = this->find_resizing_pair(false); } } resize_view_controller_t::~resize_view_controller_t() {} uint32_t resize_view_controller_t::calculate_resizing_edges(wf::point_t grab) { uint32_t result_edges = 0; auto window = this->grabbed_view->geometry; assert(window & grab); if (grab.x < window.x + window.width / 2) { result_edges |= WLR_EDGE_LEFT; } else { result_edges |= WLR_EDGE_RIGHT; } if (grab.y < window.y + window.height / 2) { result_edges |= WLR_EDGE_TOP; } else { result_edges |= WLR_EDGE_BOTTOM; } return result_edges; } resize_view_controller_t::resizing_pair_t resize_view_controller_t::find_resizing_pair(bool horiz) { split_insertion_t direction; /* Calculate the direction in which we are looking for the resizing pair */ if (horiz) { if (this->resizing_edges & WLR_EDGE_TOP) { direction = INSERT_ABOVE; } else { direction = INSERT_BELOW; } } else { if (this->resizing_edges & WLR_EDGE_LEFT) { direction = INSERT_LEFT; } else { direction = INSERT_RIGHT; } } /* Find a view in the resizing direction, then look for the least common * ancestor(LCA) of the grabbed view and the found view. * * Then the resizing pair is a pair of children of the LCA */ auto pair_view = find_first_view_in_direction(this->grabbed_view, direction); if (!pair_view) // no pair { return {nullptr, grabbed_view}; } /* Calculate all ancestors of the grabbed view */ std::set> grabbed_view_ancestors; nonstd::observer_ptr ancestor = grabbed_view; while (ancestor) { grabbed_view_ancestors.insert(ancestor); ancestor = ancestor->parent; } /* Find the LCA: this is the first ancestor of the pair_view which is also * an ancestor of the grabbed view */ nonstd::observer_ptr lca = pair_view; /* The child of lca we came from the second time */ nonstd::observer_ptr lca_successor = nullptr; while (lca && !grabbed_view_ancestors.count({lca})) { lca_successor = lca; lca = lca->parent; } /* In the "worst" case, the root of the tree is an LCA. * Also, an LCA is a split because it is an ancestor of two different * view nodes */ assert(lca && lca->children.size()); resizing_pair_t result_pair; for (auto& child : lca->children) { if (grabbed_view_ancestors.count({child})) { result_pair.first = {child}; break; } } result_pair.second = lca_successor; /* Make sure the first node in the resizing pair is always to the * left or above of the second one */ if ((direction == INSERT_LEFT) || (direction == INSERT_ABOVE)) { std::swap(result_pair.first, result_pair.second); } return result_pair; } void resize_view_controller_t::adjust_geometry(int32_t& x1, int32_t& len1, int32_t& x2, int32_t& len2, int32_t delta) { /* * On the line: * * x1 (x1+len1)=x2 x2+len2-1 * ._______________.___________________. */ constexpr int MIN_SIZE = 50; int maxPositive = std::max(0, len2 - MIN_SIZE); int maxNegative = std::max(0, len1 - MIN_SIZE); /* Make sure we don't shrink one dimension too much */ delta = clamp(delta, -maxNegative, maxPositive); /* Adjust sizes */ len1 += delta; x2 += delta; len2 -= delta; } void resize_view_controller_t::input_motion() { auto input = get_global_input_coordinates(output); if (!this->grabbed_view) { return; } auto tx = wf::txn::transaction_t::create(); if (horizontal_pair.first && horizontal_pair.second) { int dy = input.y - last_point.y; auto g1 = horizontal_pair.first->geometry; auto g2 = horizontal_pair.second->geometry; adjust_geometry(g1.y, g1.height, g2.y, g2.height, dy); horizontal_pair.first->set_geometry(g1, tx); horizontal_pair.second->set_geometry(g2, tx); } if (vertical_pair.first && vertical_pair.second) { int dx = input.x - last_point.x; auto g1 = vertical_pair.first->geometry; auto g2 = vertical_pair.second->geometry; adjust_geometry(g1.x, g1.width, g2.x, g2.width, dx); vertical_pair.first->set_geometry(g1, tx); vertical_pair.second->set_geometry(g2, tx); } wf::get_core().tx_manager->schedule_transaction(std::move(tx)); this->last_point = input; } wf::point_t get_global_input_coordinates(wf::output_t *output) { wf::pointf_t local = output->get_cursor_position(); auto vp = output->wset()->get_current_workspace(); auto size = output->get_screen_size(); local.x += size.width * vp.x; local.y += size.height * vp.y; return {(int)local.x, (int)local.y}; } } // namespace tile } wayfire-0.10.0/plugins/wobbly/0000775000175000017500000000000015053502647016110 5ustar dkondordkondorwayfire-0.10.0/plugins/wobbly/wobbly.h0000664000175000017500000000343715053502647017566 0ustar dkondordkondor/************************************************************************** * * Copyright 2014 Scott Moreau * All Rights Reserved. * **************************************************************************/ #include #include #define MINIMAL_FRICTION 0.1 #define MAXIMAL_FRICTION 10.0 #define MINIMAL_SPRING_K 0.1 #define MAXIMAL_SPRING_K 10.0 #define WOBBLY_MASS 15.0 double wobbly_settings_get_friction(); double wobbly_settings_get_spring_k(); struct wobbly_surface { void *ww; int x, y, width, height; int x_cells, y_cells; int grabbed, synced; int vertex_count; GLfloat *v, *uv; }; struct wobbly_rect { float tlx, tly; float brx, bry; }; int wobbly_init(struct wobbly_surface *surface); void wobbly_fini(struct wobbly_surface *surface); void wobbly_set_top_anchor(struct wobbly_surface *surface, int x, int y, int w, int h); void wobbly_grab_notify(struct wobbly_surface *surface, int x, int y); void wobbly_slight_wobble(struct wobbly_surface *surface); void wobbly_ungrab_notify(struct wobbly_surface *surface); void wobbly_scale(struct wobbly_surface *surface, double dx, double dy); void wobbly_resize(struct wobbly_surface *surface, int width, int height); void wobbly_move_notify(struct wobbly_surface *surface, int x, int y); void wobbly_prepare_paint(struct wobbly_surface *surface, int msSinceLastPaint); void wobbly_done_paint(struct wobbly_surface *surface); void wobbly_add_geometry(struct wobbly_surface *surface); struct wobbly_rect wobbly_boundingbox(struct wobbly_surface *surface); void wobbly_force_geometry(struct wobbly_surface *surface, int x, int y, int w, int h); void wobbly_unenforce_geometry(struct wobbly_surface *surface); void wobbly_translate(struct wobbly_surface *surface, int dx, int dy); wayfire-0.10.0/plugins/wobbly/wayfire/0000775000175000017500000000000015053502647017556 5ustar dkondordkondorwayfire-0.10.0/plugins/wobbly/wayfire/plugins/0000775000175000017500000000000015053502647021237 5ustar dkondordkondorwayfire-0.10.0/plugins/wobbly/wayfire/plugins/wobbly/0000775000175000017500000000000015053502647022535 5ustar dkondordkondorwayfire-0.10.0/plugins/wobbly/wayfire/plugins/wobbly/wobbly-signal.hpp0000664000175000017500000000706515053502647026027 0ustar dkondordkondor#pragma once #include #include #include enum wobbly_event { WOBBLY_EVENT_GRAB = (1 << 0), WOBBLY_EVENT_MOVE = (1 << 1), WOBBLY_EVENT_END = (1 << 2), WOBBLY_EVENT_ACTIVATE = (1 << 3), WOBBLY_EVENT_TRANSLATE = (1 << 4), WOBBLY_EVENT_FORCE_TILE = (1 << 5), WOBBLY_EVENT_UNTILE = (1 << 6), WOBBLY_EVENT_SCALE = (1 << 7), }; /** * on: core * when: This signal is used to control(start/stop/update) the wobbly state * for a view. Note that plugins usually would use the helper functions below, * instead of emitting this signal directly. */ struct wobbly_signal { wayfire_toplevel_view view; wobbly_event events; /** * For EVENT_GRAB and EVENT_MOVE: the coordinates of the grab * For EVENT_TRANSLATE: the amount of translation */ wf::point_t pos; /** * For EVENT_SCALE: the new size of the base surface. */ wf::geometry_t geometry; }; /** * Start wobblying when the view is being grabbed, for ex. when moving it */ inline void start_wobbly(wayfire_toplevel_view view, int grab_x, int grab_y) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_GRAB; sig.pos = {grab_x, grab_y}; wf::get_core().emit(&sig); } /** * Start wobblying when the view is being grabbed, for ex. when moving it. * The position is relative to the view, i.e [0.5, 0.5] is the midpoint. */ inline void start_wobbly_rel(wayfire_toplevel_view view, wf::pointf_t rel_grab) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_GRAB; auto bbox = view->get_bounding_box(); sig.pos.x = bbox.x + rel_grab.x * bbox.width; sig.pos.y = bbox.y + rel_grab.y * bbox.height; wf::get_core().emit(&sig); } /** * Release the wobbly grab */ inline void end_wobbly(wayfire_toplevel_view view) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_END; wf::get_core().emit(&sig); } /** * Indicate that the grab has moved (i.e cursor moved, touch moved, etc.) */ inline void move_wobbly(wayfire_toplevel_view view, int grab_x, int grab_y) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_MOVE; sig.pos = {grab_x, grab_y}; wf::get_core().emit(&sig); } /** * Temporarily activate wobbly on the view. * This is useful when animating some transition like fullscreening, tiling, etc. */ inline void activate_wobbly(wayfire_toplevel_view view) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_ACTIVATE; wf::get_core().emit(&sig); } /** * Translate the wobbly model (and its grab point, if any). */ inline void translate_wobbly(wayfire_toplevel_view view, wf::point_t delta) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_TRANSLATE; sig.pos = delta; wf::get_core().emit(&sig); } /** * Set the wobbly model forcibly (un)tiled. * This means that its four corners will be held in place, until the model is * untiled. */ inline void set_tiled_wobbly(wayfire_toplevel_view view, bool tiled) { wobbly_signal sig; sig.view = view; sig.events = tiled ? WOBBLY_EVENT_FORCE_TILE : WOBBLY_EVENT_UNTILE; wf::get_core().emit(&sig); } /** * Change the wobbly model geometry, without re-activating the springs. */ inline void modify_wobbly(wayfire_toplevel_view view, wf::geometry_t target) { wobbly_signal sig; sig.view = view; sig.events = WOBBLY_EVENT_SCALE; sig.geometry = target; wf::get_core().emit(&sig); } wayfire-0.10.0/plugins/wobbly/meson.build0000664000175000017500000000113215053502647020247 0ustar dkondordkondorwobbly_c_model = static_library('wobbly-c-model', ['wobbly.c'], install: false) wobbly = shared_module('wobbly', ['wobbly.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], link_with: wobbly_c_model, install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers(['wayfire/plugins/wobbly/wobbly-signal.hpp'], subdir: 'wayfire/plugins/wobbly') wayfire-0.10.0/plugins/wobbly/wobbly.c0000664000175000017500000005213515053502647017560 0ustar dkondordkondor/* * Copyright © 2005 Novell, Inc. * Copyright © 2014 Scott Moreau * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Novell, Inc. not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior permission. * Novell, Inc. makes no representations about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: David Reveman * Scott Moreau */ /* * Spring model implemented by Kristian Hogsberg. */ #include #include #include #include #include #include "wobbly.h" #define GRID_WIDTH 4 #define GRID_HEIGHT 4 #define MODEL_MAX_SPRINGS (GRID_WIDTH * GRID_HEIGHT * 2) typedef struct _xy_pair { float x, y; } Point, Vector; typedef struct _Edge { float next, prev; float start; float end; float attract; float velocity; } Edge; typedef struct _Object { Vector force; Point position; Vector velocity; float theta; int immobile; Edge vertEdge; Edge horzEdge; } Object; typedef struct _Spring { Object *a; Object *b; Vector offset; } Spring; typedef struct _Model { Object *objects; int numObjects; Spring springs[MODEL_MAX_SPRINGS]; int numSprings; Object *anchorObject; float steps; Point topLeft; Point bottomRight; } Model; typedef struct _WobblyWindow { Model *model; int wobbly; int grabbed; int velocity; int grab_dx; int grab_dy; unsigned int state; } WobblyWindow; #define WobblyInitial (1L << 0) #define WobblyForce (1L << 1) #define WobblyVelocity (1L << 2) static void objectInit(Object *object, float positionX, float positionY, float velocityX, float velocityY) { object->force.x = 0; object->force.y = 0; object->position.x = positionX; object->position.y = positionY; object->velocity.x = velocityX; object->velocity.y = velocityY; object->theta = 0; object->immobile = 0; object->vertEdge.next = 0.0f; object->horzEdge.next = 0.0f; } static void springInit(Spring *spring, Object *a, Object *b, float offsetX, float offsetY) { spring->a = a; spring->b = b; spring->offset.x = offsetX; spring->offset.y = offsetY; } static void modelCalcBounds(Model *model) { int i; model->topLeft.x = SHRT_MAX; model->topLeft.y = SHRT_MAX; model->bottomRight.x = SHRT_MIN; model->bottomRight.y = SHRT_MIN; for (i = 0; i < model->numObjects; i++) { if (model->objects[i].position.x < model->topLeft.x) model->topLeft.x = model->objects[i].position.x; else if (model->objects[i].position.x > model->bottomRight.x) model->bottomRight.x = model->objects[i].position.x; if (model->objects[i].position.y < model->topLeft.y) model->topLeft.y = model->objects[i].position.y; else if (model->objects[i].position.y > model->bottomRight.y) model->bottomRight.y = model->objects[i].position.y; } } static void modelAddSpring(Model *model, Object *a, Object *b, float offsetX, float offsetY) { Spring *spring; spring = &model->springs[model->numSprings]; model->numSprings++; springInit (spring, a, b, offsetX, offsetY); } static void modelSetMiddleAnchor(Model *model, int x, int y, int width, int height) { float gx, gy; gx = ((GRID_WIDTH - 1) / 2 * width) / (float) (GRID_WIDTH - 1); gy = ((GRID_HEIGHT - 1) / 2 * height) / (float) (GRID_HEIGHT - 1); if (model->anchorObject) model->anchorObject->immobile = 0; model->anchorObject = &model->objects[GRID_WIDTH * ((GRID_HEIGHT-1)/2) + (GRID_WIDTH-1)/ 2]; model->anchorObject->position.x = x + gx; model->anchorObject->position.y = y + gy; model->anchorObject->immobile = 1; } static void modelSetTopAnchor(Model *model, int x, int y, int width) { float gx; gx = ((GRID_WIDTH - 1) / 2 * width) / (float) (GRID_WIDTH - 1); if (model->anchorObject) model->anchorObject->immobile = 0; model->anchorObject = &model->objects[(GRID_WIDTH-1)/ 2]; model->anchorObject->position.x = x + gx; model->anchorObject->position.y = y; model->anchorObject->immobile = 1; } static void modelInitObjects(Model *model, int x, int y, int width, int height) { int gridX, gridY, i = 0; float gw, gh; gw = GRID_WIDTH - 1; gh = GRID_HEIGHT - 1; for (gridY = 0; gridY < GRID_HEIGHT; gridY++) { for (gridX = 0; gridX < GRID_WIDTH; gridX++) { objectInit (&model->objects[i], x + (gridX * width) / gw, y + (gridY * height) / gh, 0, 0); i++; } } if (!model->anchorObject) modelSetMiddleAnchor (model, x, y, width, height); } static void modelInitSprings(Model *model, int width, int height) { int gridX, gridY, i = 0; float hpad, vpad; model->numSprings = 0; hpad = ((float) width) / (GRID_WIDTH - 1); vpad = ((float) height) / (GRID_HEIGHT - 1); for (gridY = 0; gridY < GRID_HEIGHT; gridY++) { for (gridX = 0; gridX < GRID_WIDTH; gridX++) { if (gridX > 0) { modelAddSpring (model, &model->objects[i - 1], &model->objects[i], hpad, 0); } if (gridY > 0) { modelAddSpring (model, &model->objects[i - GRID_WIDTH], &model->objects[i], 0, vpad); } i++; } } } static Model * createModel(int x, int y, int width, int height) { Model *model; model = malloc(sizeof(Model)); if (!model) return 0; model->numObjects = GRID_WIDTH * GRID_HEIGHT; model->objects = malloc (sizeof (Object) * model->numObjects); if (!model->objects) { free (model); return 0; } model->anchorObject = 0; model->numSprings = 0; model->steps = 0; modelInitObjects (model, x, y, width, height); modelInitSprings (model, width, height); modelCalcBounds (model); return model; } static void objectApplyForce(Object *object, float fx, float fy) { object->force.x += fx; object->force.y += fy; } static void springExertForces(Spring *spring, float k) { Vector da, db; Vector a, b; a = spring->a->position; b = spring->b->position; da.x = 0.5f * (b.x - a.x - spring->offset.x); da.y = 0.5f * (b.y - a.y - spring->offset.y); db.x = 0.5f * (a.x - b.x + spring->offset.x); db.y = 0.5f * (a.y - b.y + spring->offset.y); objectApplyForce (spring->a, k * da.x, k * da.y); objectApplyForce (spring->b, k * db.x, k * db.y); } static float modelStepObject(Object *object, float friction, float *force) { object->theta += 0.05f; if (object->immobile) { object->velocity.x = 0.0f; object->velocity.y = 0.0f; object->force.x = 0.0f; object->force.y = 0.0f; *force = 0.0f; return 0.0f; } else { object->force.x -= friction * object->velocity.x; object->force.y -= friction * object->velocity.y; object->velocity.x += object->force.x / WOBBLY_MASS; object->velocity.y += object->force.y / WOBBLY_MASS; object->position.x += object->velocity.x; object->position.y += object->velocity.y; *force = fabs(object->force.x) + fabs(object->force.y); object->force.x = 0.0f; object->force.y = 0.0f; return fabs(object->velocity.x) + fabs(object->velocity.y); } } static int modelStep(Model *model, float friction, float k, float time) { int i, j, steps, wobbly = 0; float velocitySum = 0.0f; float force, forceSum = 0.0f; model->steps += time / 15.0f; steps = floor (model->steps); model->steps -= steps; if (!steps) return 1; for (j = 0; j < steps; j++) { for (i = 0; i < model->numSprings; i++) springExertForces (&model->springs[i], k); for (i = 0; i < model->numObjects; i++) { velocitySum += modelStepObject(&model->objects[i], friction, &force); forceSum += force; } } modelCalcBounds (model); if (velocitySum > 0.5f) wobbly |= WobblyVelocity; if (forceSum > 20.0f) wobbly |= WobblyForce; return wobbly; } static void bezierPatchEvaluate (Model *model, float u, float v, float *patchX, float *patchY) { float coeffsU[4], coeffsV[4]; float x, y; int i, j; coeffsU[0] = (1 - u) * (1 - u) * (1 - u); coeffsU[1] = 3 * u * (1 - u) * (1 - u); coeffsU[2] = 3 * u * u * (1 - u); coeffsU[3] = u * u * u; coeffsV[0] = (1 - v) * (1 - v) * (1 - v); coeffsV[1] = 3 * v * (1 - v) * (1 - v); coeffsV[2] = 3 * v * v * (1 - v); coeffsV[3] = v * v * v; x = y = 0.0f; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { x += coeffsU[i] * coeffsV[j] * model->objects[j * GRID_WIDTH + i].position.x; y += coeffsU[i] * coeffsV[j] * model->objects[j * GRID_HEIGHT + i].position.y; } } *patchX = x; *patchY = y; } static int wobblyEnsureModel(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (!ww->model) { ww->model = createModel(surface->x, surface->y, surface->width, surface->height); if (!ww->model) return 0; } return 1; } static float objectDistance(Object *object, float x, float y) { float dx, dy; dx = object->position.x - x; dy = object->position.y - y; return sqrt(dx * dx + dy * dy); } static Object *modelFindNearestObject(Model *model, float x, float y) { Object *object = &model->objects[0]; float distance, minDistance = 0.0; int i; for (i = 0; i < model->numObjects; i++) { distance = objectDistance(&model->objects[i], x, y); if (i == 0 || distance < minDistance) { minDistance = distance; object = &model->objects[i]; } } return object; } static void modelAdjustCorners(Model *model, int x, int y, int width, int height, int make_immobile) { Object *o; o = &model->objects[0]; o->position.x = x; o->position.y = y; o->immobile = make_immobile; o = &model->objects[GRID_WIDTH - 1]; o->position.x = x + width; o->position.y = y; o->immobile = make_immobile; o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)]; o->position.x = x; o->position.y = y + height; o->immobile = make_immobile; o = &model->objects[model->numObjects - 1]; o->position.x = x + width; o->position.y = y + height; o->immobile = make_immobile; if (!model->anchorObject) model->anchorObject = &model->objects[0]; } static int modelRemoveEdgeAnchors(Model *model) { int result = 0; Object *o; o = &model->objects[0]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[GRID_WIDTH - 1]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[GRID_WIDTH * (GRID_HEIGHT - 1)]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } o = &model->objects[model->numObjects - 1]; if (o != model->anchorObject) { result |= o->immobile; o->immobile = 0; } return result; } void wobbly_prepare_paint(struct wobbly_surface *surface, int msSinceLastPaint) { WobblyWindow *ww = surface->ww; float friction, springK; friction = wobbly_settings_get_friction(); springK = wobbly_settings_get_spring_k(); if (ww->wobbly) { if (ww->wobbly & (WobblyInitial | WobblyVelocity | WobblyForce)) { ww->wobbly = modelStep(ww->model, friction, springK, (ww->wobbly & WobblyVelocity) ? msSinceLastPaint : 16); if (ww->wobbly) { modelCalcBounds(ww->model); } else { surface->x = ww->model->topLeft.x; surface->y = ww->model->topLeft.y; surface->synced = 1; } } } } void wobbly_done_paint(struct wobbly_surface *surface) { WobblyWindow *ww = (WobblyWindow*)surface->ww; if (ww->wobbly) { surface->x = ww->model->topLeft.x; surface->y = ww->model->topLeft.y; } } void wobbly_add_geometry(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; float width, height; float deformedX, deformedY; int x, y, iw, ih; float cell_w, cell_h; GLfloat *v, *uv; if (ww->wobbly) { width = surface->width; height = surface->height; cell_w = width / surface->x_cells; cell_h = height / surface->y_cells; iw = surface->x_cells + 1; ih = surface->y_cells + 1; v = realloc(surface->v, sizeof(GLfloat) * 2 * iw * ih); uv = realloc(surface->uv, sizeof(GLfloat) * 2 * iw * ih); surface->v = v; surface->uv = uv; for (y = 0; y < ih; y++) { for (x = 0; x < iw; x++) { bezierPatchEvaluate(ww->model, (x * cell_w) / width, (y * cell_h) / height, &deformedX, &deformedY); *v++ = deformedX; *v++ = deformedY; *uv++ = (x * cell_w) / width; *uv++ = 1.0 - ((y * cell_h) / height); } } } } void wobbly_resize(struct wobbly_surface *surface, int width, int height) { WobblyWindow *ww = surface->ww; width = width > 1 ? width : 1; height = height > 1 ? height : 1; surface->synced = 0; ww->wobbly |= WobblyInitial; if (ww->model) modelInitSprings(ww->model, width, height); ww->grab_dx = (ww->grab_dx * width) / surface->width; ww->grab_dy = (ww->grab_dy * height) / surface->height; surface->width = width; surface->height = height; } void wobbly_move_notify(struct wobbly_surface *surface, int x, int y) { WobblyWindow *ww = surface->ww; if (ww->grabbed) { ww->model->anchorObject->position.x = x + ww->grab_dx; ww->model->anchorObject->position.y = y + ww->grab_dy; ww->wobbly |= WobblyInitial; surface->synced = 0; } } void wobbly_slight_wobble(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { Object *centerObj; Spring *s; int i; centerObj = modelFindNearestObject(ww->model, surface->x + surface->width / 2, surface->y + surface->height / 2); for (i = 0; i < ww->model->numSprings; i++) { s = &ww->model->springs[i]; if (s->a == centerObj) { s->b->velocity.x -= s->offset.x * 0.05f; s->b->velocity.y -= s->offset.y * 0.05f; } else if (s->b == centerObj) { s->a->velocity.x += s->offset.x * 0.05f; s->a->velocity.y += s->offset.y * 0.05f; } } ww->wobbly |= WobblyInitial; } } void wobbly_set_top_anchor(struct wobbly_surface *surface, int x, int y, int w, int h) { (void)h; WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { modelSetTopAnchor(ww->model, x, y, w); } } void wobbly_grab_notify(struct wobbly_surface *surface, int x, int y) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { Spring *s; int i; if (ww->model->anchorObject) ww->model->anchorObject->immobile = 0; ww->model->anchorObject = modelFindNearestObject(ww->model, x, y); ww->model->anchorObject->immobile = 1; ww->grab_dx = ww->model->anchorObject->position.x - x; ww->grab_dy = ww->model->anchorObject->position.y - y; ww->grabbed = 1; for (i = 0; i < ww->model->numSprings; i++) { s = &ww->model->springs[i]; if (s->a == ww->model->anchorObject) { s->b->velocity.x -= s->offset.x * 0.05f; s->b->velocity.y -= s->offset.y * 0.05f; } else if (s->b == ww->model->anchorObject) { s->a->velocity.x += s->offset.x * 0.05f; s->a->velocity.y += s->offset.y * 0.05f; } } ww->wobbly |= WobblyInitial; } } void wobbly_ungrab_notify(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (ww->grabbed) { if (ww->model) { if (ww->model->anchorObject) ww->model->anchorObject->immobile = 0; ww->model->anchorObject = NULL; ww->wobbly |= WobblyInitial; } surface->synced = 0; ww->grabbed = 0; } } int wobbly_init(struct wobbly_surface *surface) { WobblyWindow *ww; ww = malloc(sizeof (WobblyWindow)); if (!ww) return 0; ww->model = 0; ww->wobbly = 0; ww->grabbed = 0; ww->state = 0; surface->ww = ww; if(!wobblyEnsureModel(surface)) { free(ww); return 0; } return 1; } void wobbly_fini(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (ww->model) { free(ww->model->objects); free(ww->model); free(surface->v); } free (ww); } void wobbly_force_geometry(struct wobbly_surface *surface, int x, int y, int w, int h) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { if (!ww->grabbed && ww->model->anchorObject) { ww->model->anchorObject->immobile = 0; ww->model->anchorObject = NULL; } surface->x = x; surface->y = y; surface->width = w > 0 ? w : 1; surface->height = h > 0 ? h : 1; surface->synced = 0; modelInitSprings(ww->model, w, h); modelAdjustCorners(ww->model, x, y, w, h, 1); ww->wobbly |= WobblyInitial; } } void wobbly_unenforce_geometry(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { if (modelRemoveEdgeAnchors(ww->model)) { if (!ww->model->anchorObject || !ww->model->anchorObject->immobile) { modelSetMiddleAnchor(ww->model, surface->x, surface->y, surface->width, surface->height); } modelInitSprings(ww->model, surface->width, surface->height); } ww->wobbly |= WobblyInitial; } } void wobbly_translate(struct wobbly_surface *surface, int dx, int dy) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { for (int i = 0; i < ww->model->numObjects; i++) { ww->model->objects[i].position.x += dx; ww->model->objects[i].position.y += dy; } ww->model->topLeft.x += dx; ww->model->topLeft.y += dy; ww->model->bottomRight.x += dx; ww->model->bottomRight.y += dy; } } static void scale(float origin, float *x, double scale) { *x = (*x - origin) * scale + origin; } void wobbly_scale(struct wobbly_surface *surface, double dx, double dy) { WobblyWindow *ww = surface->ww; if (wobblyEnsureModel(surface)) { for (int i = 0; i < ww->model->numObjects; i++) { scale(surface->x, &ww->model->objects[i].position.x, dx); scale(surface->y, &ww->model->objects[i].position.y, dy); } scale(surface->x, &ww->model->topLeft.x, dx); scale(surface->y, &ww->model->topLeft.y, dy); scale(surface->x, &ww->model->bottomRight.x, dx); scale(surface->y, &ww->model->bottomRight.y, dy); } } struct wobbly_rect wobbly_boundingbox(struct wobbly_surface *surface) { WobblyWindow *ww = surface->ww; struct wobbly_rect result; memset(&result, 0, sizeof(result)); if (ww->model) { result.tlx = ww->model->topLeft.x; result.tly = ww->model->topLeft.y; result.brx = ww->model->bottomRight.x; result.bry = ww->model->bottomRight.y; } return result; } wayfire-0.10.0/plugins/wobbly/wobbly.cpp0000664000175000017500000007020115053502647020112 0ustar dkondordkondor#include "wayfire/debug.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include #include #include #include #include #include #include #include extern "C" { #include "wobbly.h" } #include "wayfire/plugins/wobbly/wobbly-signal.hpp" namespace wobbly_graphics { namespace { const char *vertex_source = R"( #version 100 attribute highp vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; } )"; const char *frag_source = R"( #version 100 @builtin_ext@ varying highp vec2 uvpos; @builtin@ void main() { gl_FragColor = get_pixel(uvpos); } )"; } /** * Enumerate the needed triangles for rendering the model */ void prepare_geometry(wobbly_surface *model, wf::geometry_t src_box, std::vector& vert, std::vector& uv) { float x = src_box.x, y = src_box.y, w = src_box.width, h = src_box.height; std::vector idx; int per_row = model->x_cells + 1; for (int j = 0; j < model->y_cells; j++) { for (int i = 0; i < model->x_cells; i++) { idx.push_back(i * per_row + j); idx.push_back((i + 1) * per_row + j + 1); idx.push_back(i * per_row + j + 1); idx.push_back(i * per_row + j); idx.push_back((i + 1) * per_row + j); idx.push_back((i + 1) * per_row + j + 1); } } if (!model->v || !model->uv) { for (auto id : idx) { float tile_w = w / model->x_cells; float tile_h = h / model->y_cells; int i = id / per_row; int j = id % per_row; vert.push_back(i * tile_w + x); vert.push_back(j * tile_h + y); uv.push_back(1.0f * i / model->x_cells); uv.push_back(1.0f - 1.0f * j / model->y_cells); } } else { for (auto i : idx) { vert.push_back(model->v[2 * i]); vert.push_back(model->v[2 * i + 1]); uv.push_back(model->uv[2 * i]); uv.push_back(model->uv[2 * i + 1]); } } } /* Requires bound opengl context */ void render_triangles(OpenGL::program_t *program, wf::gles_texture_t tex, glm::mat4 mat, float *pos, float *uv, int cnt) { program->use(tex.type); program->set_active_texture(tex); program->attrib_pointer("position", 2, 0, pos); program->attrib_pointer("uvPosition", 2, 0, uv); program->uniformMatrix4f("MVP", mat); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(GL_TRIANGLES, 0, 3 * cnt)); GL_CALL(glDisable(GL_BLEND)); program->deactivate(); } } namespace wobbly_settings { wf::option_wrapper_t friction{"wobbly/friction"}; wf::option_wrapper_t spring_k{"wobbly/spring_k"}; wf::option_wrapper_t resolution{"wobbly/grid_resolution"}; } extern "C" { double wobbly_settings_get_friction() { return wf::clamp((double)wobbly_settings::friction, MINIMAL_FRICTION, MAXIMAL_FRICTION); } double wobbly_settings_get_spring_k() { return wf::clamp((double)wobbly_settings::spring_k, MINIMAL_SPRING_K, MAXIMAL_SPRING_K); } } namespace wf { using wobbly_model_t = std::unique_ptr; static const std::string wobbly_transformer_name = "wobbly"; /** * Different states in which wobbly can be */ enum ewobbly_state_t { WOBBLY_STATE_FLOATING = 0, WOBBLY_STATE_FREE = 1, WOBBLY_STATE_GRABBED = 2, WOBBLY_STATE_TILED = 3, WOBBLY_STATE_TILED_GRABBED = 4, }; /** * Interface representing the wobbly state. */ class iwobbly_state_t { public: virtual ~iwobbly_state_t() = default; iwobbly_state_t(const iwobbly_state_t &) = delete; iwobbly_state_t(iwobbly_state_t &&) = delete; iwobbly_state_t& operator =(const iwobbly_state_t&) = delete; iwobbly_state_t& operator =(iwobbly_state_t&&) = delete; /** Called when the state has been updated. */ virtual void handle_state_update_done() {} /** Called when a grab starts */ virtual void handle_grab_start(wf::point_t grab, bool takeover) {} /** Called when the wobbly grab is moved. */ virtual void handle_grab_move(wf::point_t grab) {} /** Query the last grab point */ virtual wf::point_t get_grab_position() const { return {0, 0}; } /** * Called when the wobbly grab is ended. * @param release_grab Whether to remove the grabbed object in the model. */ virtual void handle_grab_end(bool release_grab) {} /** Called when the next frame is being prepared */ virtual void handle_frame() { this->bounding_box = wf::view_bounding_box_up_to(view, "wobbly"); } /** Called when the view wm geometry changes */ virtual void handle_wm_geometry(const wf::geometry_t& old_wm_geometry) {} /** Called when the workspace is changed. */ virtual void handle_workspace_change(wf::point_t old, wf::point_t cur) {} /** @return true if the wobbly animation is done. */ virtual bool is_wobbly_done() const { return model->synced; } /** @return the current state of wobbly */ virtual ewobbly_state_t get_wobbly_state() const = 0; /** * This isn't really meant to be used standalone, only subclasses should * be instantiated. */ iwobbly_state_t(const wobbly_model_t& m, wayfire_toplevel_view v) : view(v), model(m) { bounding_box = {model->x, model->y, model->width, model->height}; } /** * Translate the model by the given offset */ virtual void translate_model(int dx, int dy) { wobbly_translate(model.get(), dx, dy); wobbly_add_geometry(model.get()); bounding_box.x += dx; bounding_box.y += dy; model->x += dx; model->y += dy; } virtual void update_base_geometry(wf::geometry_t base) { wobbly_scale(model.get(), 1.0 * base.width / bounding_box.width, 1.0 * base.height / bounding_box.height); wobbly_translate(model.get(), base.x - bounding_box.x, base.y - bounding_box.y); wobbly_resize(model.get(), base.width, base.height); this->bounding_box = base; model->x = base.x; model->y = base.y; model->width = std::max(1, base.width); model->height = std::max(1, base.height); } protected: wayfire_toplevel_view view; const wobbly_model_t& model; wf::geometry_t bounding_box; }; /** * Determines the behavior of the wobbly model when the view is grabbed, * for ex. when moving or resizing. */ class wobbly_state_grabbed_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; virtual void handle_grab_start(wf::point_t grab, bool takeover) override { this->last_grab = {(int)grab.x, (int)grab.y}; if (!takeover) { wobbly_grab_notify(model.get(), last_grab.x, last_grab.y); } } virtual wf::point_t get_grab_position() const override { return this->last_grab; } /** @return the current state of wobbly */ virtual ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_GRABBED; } virtual void handle_grab_end(bool release_grab) override { if (release_grab) { wobbly_ungrab_notify(model.get()); } } virtual void translate_model(int dx, int dy) override { iwobbly_state_t::translate_model(dx, dy); this->last_grab.x += dx; this->last_grab.y += dy; } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); if (wf::dimensions(old_bbox) != wf::dimensions(bounding_box)) { /* Directly accept new size, but keep position, * because it is managed by the grab. */ wobbly_resize(model.get(), bounding_box.width, bounding_box.height); } } protected: wf::point_t last_grab; void handle_grab_move(wf::point_t grab) override { wobbly_move_notify(model.get(), grab.x, grab.y); this->last_grab = {(int)grab.x, (int)grab.y}; } bool is_wobbly_done() const override { return false; } }; static void wobbly_tiled_state_handle_frame(const wobbly_model_t& model, const wf::geometry_t& old_bbox, const wf::geometry_t& new_bbox) { if (new_bbox != old_bbox) { /* Bounding box (excluding the wobbly transformer) changed, this * means the view got resized/moved by something outside of wobbly. * Adjust the geometry. */ wobbly_force_geometry(model.get(), new_bbox.x, new_bbox.y, new_bbox.width, new_bbox.height); } } /** * Determines the behavior of the wobbly model when the view is tiled or * fullscreen, i.e the view keeps its geometry where it was put. */ class wobbly_state_tiled_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; void handle_state_update_done() override { wobbly_force_geometry(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); wobbly_tiled_state_handle_frame(model, old_bbox, bounding_box); } virtual ~wobbly_state_tiled_t() { wobbly_unenforce_geometry(model.get()); } wobbly_state_tiled_t(const wobbly_state_tiled_t &) = delete; wobbly_state_tiled_t(wobbly_state_tiled_t &&) = delete; wobbly_state_tiled_t& operator =(const wobbly_state_tiled_t&) = delete; wobbly_state_tiled_t& operator =(wobbly_state_tiled_t&&) = delete; ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_TILED; } }; /** * Determines the behavior of the wobbly model when the view is tiled or * fullscreen, i.e the view keeps its geometry where it was put, and it has * an active grab at the same time. * * This is basically a combination of tiled and grabbed. */ class wobbly_state_tiled_grabbed_t : public wobbly_state_grabbed_t { public: using wobbly_state_grabbed_t::wobbly_state_grabbed_t; void handle_state_update_done() override { wobbly_force_geometry(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); } void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); wobbly_tiled_state_handle_frame(model, old_bbox, bounding_box); } virtual ~wobbly_state_tiled_grabbed_t() { wobbly_unenforce_geometry(model.get()); } wobbly_state_tiled_grabbed_t(const wobbly_state_tiled_grabbed_t &) = delete; wobbly_state_tiled_grabbed_t(wobbly_state_tiled_grabbed_t &&) = delete; wobbly_state_tiled_grabbed_t& operator =(const wobbly_state_tiled_grabbed_t&) = delete; wobbly_state_tiled_grabbed_t& operator =( wobbly_state_tiled_grabbed_t&&) = delete; ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_TILED_GRABBED; } }; /** * Determines the behavior of the wobbly model when the view is wobblying freely * without being grabbed or tiled. In this state, the model dictates the * position of the view. */ class wobbly_state_floating_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; protected: bool is_wobbly_done() const override { if (!model->synced) { return false; } /* Synchronize view position with the model */ auto tr = view->get_transformed_node()->get_transformer("wobbly"); if (!tr) { return true; } auto new_bbox = tr->get_children_bounding_box(); auto wm = view->get_geometry(); int target_x = model->x + wm.x - new_bbox.x; int target_y = model->y + wm.y - new_bbox.y; if ((target_x != wm.x) || (target_y != wm.y)) { view->move(model->x + wm.x - new_bbox.x, model->y + wm.y - new_bbox.y); } return true; } void handle_frame() override { auto tr = view->get_transformed_node()->get_transformer("wobbly"); if (tr) { // Transformer might not exist anymore if we've destroyed the model but we still hold on to any // render instances. auto new_bbox = tr->get_children_bounding_box(); update_base_geometry(new_bbox); } } void handle_wm_geometry(const wf::geometry_t& old_wm) override { update_base_geometry(wf::view_bounding_box_up_to(view, "wobbly")); } void handle_workspace_change(wf::point_t old, wf::point_t cur) override { auto size = view->get_output()->get_screen_size(); auto delta = old - cur; translate_model(delta.x * size.width, delta.y * size.height); } ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_FLOATING; } }; /** * Determines the behavior of wobbly when the view is not grabbed or tiled, * but the model should keep the true origin of the view. */ class wobbly_state_free_t : public iwobbly_state_t { public: using iwobbly_state_t::iwobbly_state_t; protected: void handle_frame() override { auto old_bbox = bounding_box; iwobbly_state_t::handle_frame(); if (wf::dimensions(old_bbox) != wf::dimensions(bounding_box)) { wobbly_set_top_anchor(model.get(), bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height); wobbly_resize(model.get(), bounding_box.width, bounding_box.height); } } void handle_workspace_change(wf::point_t old, wf::point_t cur) override { auto size = view->get_output()->get_screen_size(); auto delta = old - cur; wobbly_translate(model.get(), delta.x * size.width, delta.y * size.height); } ewobbly_state_t get_wobbly_state() const override { return WOBBLY_STATE_FREE; } }; } class wobbly_transformer_node_t : public wf::scene::transformer_base_node_t { public: wobbly_transformer_node_t(wayfire_toplevel_view view, OpenGL::program_t *wobbly_prog) : transformer_base_node_t(false) { this->view = view; this->wobbly_program = wobbly_prog; init_model(); last_frame = wf::get_current_time(); view->get_output()->connect(&on_workspace_changed); view->connect(&on_view_unmap); view->connect(&on_view_tiled); view->connect(&on_view_fullscreen); view->connect(&view_output_changed); view->connect(&on_view_geometry_changed); /* Set to free state initially but then look for the correct state */ this->state = std::make_unique(model, view); update_wobbly_state(false, {0, 0}, false); } ~wobbly_transformer_node_t() { state = nullptr; wobbly_fini(model.get()); } std::string stringify() const override { return "wobbly"; } wf::geometry_t get_bounding_box() override { auto box = wobbly_boundingbox(model.get()); wlr_box result; result.x = box.tlx; result.y = box.tly; result.width = std::ceil(box.brx - box.tlx); result.height = std::ceil(box.bry - box.tly); return result; } void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) override; std::unique_ptr model; void destroy_self() { view->get_transformed_node()->rem_transformer("wobbly"); } OpenGL::program_t *wobbly_program; private: wayfire_toplevel_view view; wf::signal::connection_t on_view_unmap = [=] (wf::view_unmapped_signal*) { destroy_self(); }; wf::signal::connection_t on_view_tiled = [=] (wf::view_tiled_signal *ev) { update_wobbly_state(false, {0, 0}, false); }; wf::signal::connection_t on_view_fullscreen = [=] (wf::view_fullscreen_signal *ev) { update_wobbly_state(false, {0, 0}, false); }; wf::signal::connection_t on_view_geometry_changed = [=] (wf::view_geometry_changed_signal *ev) { state->handle_wm_geometry(ev->old_geometry); }; wf::signal::connection_t on_workspace_changed = [=] (wf::workspace_changed_signal *ev) { state->handle_workspace_change(ev->old_viewport, ev->new_viewport); }; wf::signal::connection_t view_output_changed = [=] (wf::view_set_output_signal *ev) { /* Wobbly is active only when there's already been an output */ wf::dassert(ev->output != nullptr, "wobbly cannot be active on nullptr output!"); if (!view->get_output()) { // Destructor won't be able to disconnect bc view output is invalid return destroy_self(); } /* Translate wobbly when its output changes */ auto old_geometry = ev->output->get_layout_geometry(); auto new_geometry = view->get_output()->get_layout_geometry(); state->translate_model(old_geometry.x - new_geometry.x, old_geometry.y - new_geometry.y); on_workspace_changed.disconnect(); view->get_output()->connect(&on_workspace_changed); }; std::unique_ptr state; uint32_t last_frame; bool force_tile = false; void init_model() { model = std::make_unique(); auto g = view->get_bounding_box(); model->x = g.x; model->y = g.y; model->width = std::max(1, g.width); model->height = std::max(1, g.height); model->grabbed = 0; model->synced = 1; model->x_cells = wobbly_settings::resolution; model->y_cells = wobbly_settings::resolution; model->v = NULL; model->uv = NULL; wobbly_init(model.get()); } public: void update_model() { view->damage(); /* It is possible that the wobbly state needs to adjust view geometry. * We do not want it to get feedback from itself */ on_view_geometry_changed.disconnect(); state->handle_frame(); view->connect(&on_view_geometry_changed); /* Update all the wobbly model */ auto now = wf::get_current_time(); if (now > last_frame) { view->get_transformed_node()->begin_transform_update(); wobbly_prepare_paint(model.get(), now - last_frame); /* Update wobbly geometry */ last_frame = now; wobbly_add_geometry(model.get()); wobbly_done_paint(model.get()); view->get_transformed_node()->end_transform_update(); } if (state->is_wobbly_done()) { destroy_self(); } } /** * Update the current wobbly state based on: * 1. View state (tiled & fullscreen) * 2. Current wobbly state (grabbed or not) * 3. Whether we are starting a grab, or ending a grab. * * @param start_grab Whether to start a new grab at @grab * @param grab The position of the starting grab. * @param end_grab Whether to end an existing grab. */ void update_wobbly_state(bool start_grab, wf::point_t grab, bool end_grab) { bool was_grabbed = (state->get_wobbly_state() == wf::WOBBLY_STATE_GRABBED || state->get_wobbly_state() == wf::WOBBLY_STATE_TILED_GRABBED); bool grabbed = (start_grab || was_grabbed) && !end_grab; bool tiled = false; if (grabbed) { // If the view is grabbed, the grabbing plugin says whether to tile // or not tiled = force_tile; } else { tiled = (force_tile || view->pending_tiled_edges()) || view->pending_fullscreen(); } uint32_t next_state_mask = 0; if (tiled && grabbed) { next_state_mask = wf::WOBBLY_STATE_TILED_GRABBED; } else if (tiled) { next_state_mask = wf::WOBBLY_STATE_TILED; } else if (grabbed) { next_state_mask = wf::WOBBLY_STATE_GRABBED; } else if (was_grabbed || (state->get_wobbly_state() == wf::WOBBLY_STATE_FLOATING)) { /* If previously grabbed, we can let the view float freely */ next_state_mask = wf::WOBBLY_STATE_FLOATING; } else { /* Otherwise, we need to keep the position */ next_state_mask = wf::WOBBLY_STATE_FREE; } if (next_state_mask == state->get_wobbly_state()) { return; } std::unique_ptr next_state; switch (next_state_mask) { case wf::WOBBLY_STATE_FREE: next_state = std::make_unique< wf::wobbly_state_free_t>(model, view); break; case wf::WOBBLY_STATE_FLOATING: next_state = std::make_unique< wf::wobbly_state_floating_t>(model, view); break; case wf::WOBBLY_STATE_TILED: next_state = std::make_unique< wf::wobbly_state_tiled_t>(model, view); break; case wf::WOBBLY_STATE_GRABBED: next_state = std::make_unique< wf::wobbly_state_grabbed_t>(model, view); break; case wf::WOBBLY_STATE_TILED_GRABBED: next_state = std::make_unique< wf::wobbly_state_tiled_grabbed_t>(model, view); break; default: /* Not reached except by a bug */ assert(false); } if (was_grabbed) { this->state->handle_grab_end(end_grab); } if (grabbed) { if (was_grabbed) { grab = this->state->get_grab_position(); } next_state->handle_grab_start(grab, was_grabbed); } /* New state has been set up */ this->state = std::move(next_state); this->state->handle_state_update_done(); } public: void start_grab(wf::point_t grab) { update_wobbly_state(true, grab, false); } void move(wf::point_t point) { state->handle_grab_move(point); } void translate(wf::point_t delta) { state->translate_model(delta.x, delta.y); } void end_grab() { update_wobbly_state(false, {0, 0}, true); } void wobble() { wobbly_slight_wobble(model.get()); model->synced = 0; } void update_base_geometry(wf::geometry_t g) { state->update_base_geometry(g); } void set_force_tile(bool force_tile) { this->force_tile = force_tile; update_wobbly_state(false, {0, 0}, false); } }; class wobbly_render_instance_t : public wf::scene::transformer_render_instance_t { wf::output_t *wo = nullptr; wf::effect_hook_t pre_hook; public: wobbly_render_instance_t(wobbly_transformer_node_t *self, wf::scene::damage_callback push_damage, wf::output_t *shown_on) : transformer_render_instance_t(self, push_damage, shown_on) { if (shown_on) { wo = shown_on; pre_hook = [=] () { self->update_model(); }; wo->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); } } ~wobbly_render_instance_t() { if (wo) { wo->render->rem_effect(&pre_hook); } } void transform_damage_region(wf::region_t& damage) override { damage |= self->get_bounding_box(); } void render(const wf::scene::render_instruction_t& data) override { std::vector vert, uv; auto subbox = self->get_children_bounding_box(); wobbly_graphics::prepare_geometry(self->model.get(), subbox, vert, uv); auto tex = wf::gles_texture_t{get_texture(data.target.scale)}; data.pass->custom_gles_subpass(data.target, [&] { for (auto box : data.damage) { wf::gles::render_target_logic_scissor(data.target, wlr_box_from_pixman_box(box)); wobbly_graphics::render_triangles(self->wobbly_program, tex, wf::gles::render_target_orthographic_projection(data.target), vert.data(), uv.data(), self->model->x_cells * self->model->y_cells * 2); } }); } }; void wobbly_transformer_node_t::gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { instances.push_back(std::make_unique( this, push_damage, shown_on)); } class wayfire_wobbly : public wf::plugin_interface_t { wf::signal::connection_t wobbly_changed = [=] (wobbly_signal *ev) { adjust_wobbly(ev); }; public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("wobbly: requires GLES2 support, but current renderer is ", render_type); return; } wf::get_core().connect(&wobbly_changed); wf::gles::run_in_context_if_gles([&] { program.compile(wobbly_graphics::vertex_source, wobbly_graphics::frag_source); }); } void adjust_wobbly(wobbly_signal *data) { auto tr_manager = data->view->get_transformed_node(); if ((data->events == WOBBLY_EVENT_ACTIVATE) && tr_manager->get_transformer("wobbly")) { return; } if ((data->events & (WOBBLY_EVENT_GRAB | WOBBLY_EVENT_ACTIVATE)) && !tr_manager->get_transformer("wobbly")) { tr_manager->add_transformer( std::make_shared(data->view, &program), wf::TRANSFORMER_HIGHLEVEL, "wobbly"); } auto wobbly = tr_manager->get_transformer("wobbly"); if (!wobbly) { return; } if (data->events & WOBBLY_EVENT_ACTIVATE) { wobbly->wobble(); } if (data->events & WOBBLY_EVENT_GRAB) { wobbly->start_grab(data->pos); } if (data->events & WOBBLY_EVENT_MOVE) { wobbly->move(data->pos); } if (data->events & WOBBLY_EVENT_TRANSLATE) { wobbly->translate(data->pos); } if (data->events & WOBBLY_EVENT_END) { wobbly->end_grab(); } if (data->events & WOBBLY_EVENT_FORCE_TILE) { wobbly->set_force_tile(true); } if (data->events & WOBBLY_EVENT_UNTILE) { wobbly->set_force_tile(false); } if (data->events & WOBBLY_EVENT_SCALE) { wobbly->update_base_geometry(data->geometry); } } void fini() override { for (auto& view : wf::get_core().get_all_views()) { auto wobbly = view->get_transformed_node()->get_transformer("wobbly"); if (wobbly) { wobbly->destroy_self(); } } wf::gles::run_in_context_if_gles([&] { program.free_resources(); }); } private: OpenGL::program_t program; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wobbly); wayfire-0.10.0/plugins/window-rules/0000775000175000017500000000000015053502647017251 5ustar dkondordkondorwayfire-0.10.0/plugins/window-rules/window-rules.cpp0000664000175000017500000001336315053502647022422 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lambda-rules-registration.hpp" #include "view-action-interface.hpp" #include "wayfire/signal-provider.hpp" class wayfire_window_rules_t : public wf::per_output_plugin_instance_t { public: void init() override; void fini() override; void apply(const std::string & signal, wayfire_view view); private: void setup_rules_from_config(); wf::lexer_t _lexer; // Created rule handler. wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { apply("created", ev->view); }; // Maximized rule handler. wf::signal::connection_t _tiled = [=] (wf::view_tiled_signal *ev) { apply("maximized", ev->view); apply("unmaximized", ev->view); }; // Minimized rule handler. wf::signal::connection_t _minimized = [=] (wf::view_minimized_signal *ev) { apply("minimized", ev->view); }; // Fullscreened rule handler. wf::signal::connection_t _fullscreened = [=] (wf::view_fullscreen_signal *ev) { apply("fullscreened", ev->view); }; // Auto-reload on changes to config file wf::signal::connection_t _reload_config = [=] (wf::reload_config_signal *ev) { setup_rules_from_config(); }; std::vector> _rules; wf::view_access_interface_t _access_interface; wf::view_action_interface_t _action_interface; nonstd::observer_ptr _lambda_registrations; }; void wayfire_window_rules_t::init() { // Get the lambda rules registrations. _lambda_registrations = wf::lambda_rules_registrations_t::get_instance(); _lambda_registrations->window_rule_instances++; setup_rules_from_config(); output->connect(&on_view_mapped); output->connect(&_tiled); output->connect(&_minimized); output->connect(&_fullscreened); wf::get_core().connect(&_reload_config); } void wayfire_window_rules_t::fini() { _lambda_registrations->window_rule_instances--; if (_lambda_registrations->window_rule_instances == 0) { wf::get_core().erase_data(); } } void wayfire_window_rules_t::apply(const std::string & signal, wayfire_view view) { if (view == nullptr) { return; } auto toplevel = toplevel_cast(view); if ((signal == "maximized") && (!toplevel || (toplevel->pending_tiled_edges() != wf::TILED_EDGES_ALL))) { return; } if ((signal == "unmaximized") && (!toplevel || (toplevel->pending_tiled_edges() == wf::TILED_EDGES_ALL))) { return; } for (const auto & rule : _rules) { _access_interface.set_view(view); _action_interface.set_view(view); auto error = rule->apply(signal, _access_interface, _action_interface); if (error) { LOGE("Window-rules: Error while executing rule on ", signal, " signal."); } } auto bounds = _lambda_registrations->rules(); auto begin = std::get<0>(bounds); auto end = std::get<1>(bounds); while (begin != end) { auto registration = std::get<1>(*begin); bool error = false; // Assume we will use the view access interface. _access_interface.set_view(view); wf::access_interface_t & access_iface = _access_interface; // If a custom access interface is set in the regoistration, use this one. if (registration->access_interface != nullptr) { access_iface = *registration->access_interface; } // Load if lambda wrapper. if (registration->if_lambda != nullptr) { registration->rule_instance->setIfLambda( [registration, signal, view] () -> bool { return registration->if_lambda(signal, view); }); } // Load else lambda wrapper. if (registration->else_lambda) { registration->rule_instance->setElseLambda( [registration, signal, view] () -> bool { return registration->else_lambda(signal, view); }); } // Run the lambda rule. error = registration->rule_instance->apply(signal, _access_interface); // Unload wrappers. registration->rule_instance->setIfLambda(nullptr); registration->rule_instance->setElseLambda(nullptr); if (error) { LOGE("Window-rules: Error while executing rule on signal: ", signal, ", rule text:", registration->rule); } ++begin; } } void wayfire_window_rules_t::setup_rules_from_config() { _rules.clear(); wf::option_wrapper_t> rule_list_option{"window-rules/rules"}; auto rule_list = rule_list_option.value(); for (const auto& [name, rule_str] : rule_list) { LOGD("Registering ", rule_str); _lexer.reset(rule_str); auto rule = wf::rule_parser_t().parse(_lexer); if (rule != nullptr) { _rules.push_back(rule); } } } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/window-rules/lambda-rules-registration.hpp0000664000175000017500000001547115053502647025052 0ustar dkondordkondor#ifndef LAMBDARULESREGISTRATION_HPP #define LAMBDARULESREGISTRATION_HPP #include #include #include #include #include "wayfire/core.hpp" #include "wayfire/object.hpp" #include "wayfire/nonstd/observer_ptr.h" #include "wayfire/parser/lambda_rule_parser.hpp" #include "wayfire/rule/lambda_rule.hpp" #include "wayfire/util/log.hpp" #include "wayfire/view.hpp" class wayfire_window_rules_t; namespace wf { struct lambda_rule_registration_t; using map_type = std::map>; using lambda_reg_t = std::function; /** * @brief The lambda_rule_registration_t struct represents registration information * for a single lambda rule. * * To make a registration, create one of these structures in a shared_ptr, fill in * the appropriate values and * register it on the lambda_rules_registrations_t singleton instance. * * At minimum, the rule string and if_lambda need to be set. * * The rule text defines the condition that will will be matched by window-rules. If * the condition described * in the rule text evaluates to true (using access_interface to determine the * current values of variables), * the if_lambda function will be executed. If the condition evaluates to false, the * else_lambda (if not * nullptr) will be executed. */ struct lambda_rule_registration_t { public: /** * @brief rule This is the rule text. * * @note The registering plugin is supposed to set this value before registering. */ std::string rule; /** * @brief if_lambda This is the lambda method to be executed if the specified * condition holds. * * @note The registering plugin is supposed to set this value before registering. */ wf::lambda_reg_t if_lambda; /** * @brief else_lambda This is the lambda method to be executed if the specified * condition does not hold. * * @note The registering plugin is supposed to set this value before registering. * @note In most cases this should be left blank. * * @attention: Be very careful with this lambda because it will be executed on * the signal for each view * that did NOT match the condition. */ wf::lambda_reg_t else_lambda; /** * @brief access_interface Access interface to be used when evaluating the rule. * * @note If this is left blank (nullptr), the standard view_access_interface_t * instance will be used. */ std::shared_ptr access_interface; private: /** * @brief rule_instance Pointer to the parsed rule object. * * @attention You should not set this. Leave it at nullptr, the registration * process will fill in this * variable. Window rules can then use this cached rule instance on * each signal occurrence. */ std::shared_ptr rule_instance; // Friendship for window rules to be able to execute the rules. friend class ::wayfire_window_rules_t; // Friendship for the rules registrations to be able to modify the rule set. friend class lambda_rules_registrations_t; }; /** * @brief The lambda_rules_registrations_t class is a helper class for easy * registration and unregistration of * lambda rules for the window rules plugin. * * This class is a singleton and can only be used via the getInstance() method. * * The instance is stored in wf::core. The getInstance() method will fetch from * wf:core, and lazy-init if the * instance is not yet present. */ class lambda_rules_registrations_t : public custom_data_t { public: /** * @brief getInstance Static accessor for the singleton. * * @return Observer pointer to the singleton instance, fetched from wf::core. */ static nonstd::observer_ptr get_instance() { auto instance = get_core().get_data(); if (instance == nullptr) { get_core().store_data(std::unique_ptr( new lambda_rules_registrations_t())); instance = get_core().get_data(); if (instance == nullptr) { LOGE("Window lambda rules: Lazy-init of lambda registrations failed."); } else { LOGD( "Window lambda rules: Lazy-init of lambda registrations succeeded."); } } return instance; } /** * @brief registerLambdaRule Registers a lambda rule with its associated key. * * This method will return error result if the key is not unique or the * registration struct is incomplete. * * @param[in] key Unique key for the registration. * @param[in] registration The registration structure. * * @return True in case of error, false if ok. */ bool register_lambda_rule(std::string key, std::shared_ptr registration) { if (_registrations.find(key) != _registrations.end()) { return true; // Error, key already exists. } if (registration->if_lambda == nullptr) { return true; // Error, no if lambda specified. } registration->rule_instance = lambda_rule_parser_t().parse( registration->rule, nullptr, nullptr); if (registration->rule_instance == nullptr) { return true; // Error, failed to parse rule. } _registrations.emplace(key, registration); return false; } /** * @brief unregisterLambdaRule Unregisters a lambda rule with its associated key. * * Has no effect if no rule is registered with this key. * * @param[in] key Unique key for the registration. */ void unregister_lambda_rule(std::string key) { _registrations.erase(key); } /** * @brief rules Gets the boundaries of the rules map as a tuple of cbegin() and * cend() const_iterators. * * @return Boundaries of the rules map. */ std::tuple rules() { return std::tuple(_registrations.cbegin(), _registrations.cend()); } private: /** * @brief lambda_rules_registrations_t Constructor, private to enforce singleton * design pattern. */ lambda_rules_registrations_t() = default; /** * @brief _registrations The map holding all the current registrations. */ map_type _registrations; // Necessary for window-rules to manage the lifetime of the object uint32_t window_rule_instances = 0; friend class ::wayfire_window_rules_t; }; } // End namespace wf. #endif // LAMBDARULESREGISTRATION_HPP wayfire-0.10.0/plugins/window-rules/meson.build0000664000175000017500000000073415053502647021417 0ustar dkondordkondorwindow_rules = shared_module('window-rules', ['window-rules.cpp', 'view-action-interface.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, grid_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wfutils, plugin_pch_dep], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.10.0/plugins/window-rules/view-action-interface.hpp0000664000175000017500000000360715053502647024153 0ustar dkondordkondor#ifndef VIEW_ACTION_INTERFACE_HPP #define VIEW_ACTION_INTERFACE_HPP #include "wayfire/action/action_interface.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include namespace wf { class view_action_interface_t : public action_interface_t { public: virtual ~view_action_interface_t() override; virtual bool execute(const std::string & name, const std::vector & args) override; void set_view(wayfire_view view); private: void _maximize(); void _unmaximize(); void _minimize(); void _unminimize(); void _make_sticky(); void _always_on_top(); std::tuple _expect_float(const std::vector & args, std::size_t position); std::tuple _expect_double(const std::vector & args, std::size_t position); std::tuple _expect_int(const std::vector & args, std::size_t position); std::tuple _validate_alpha(const std::vector & args); std::tuple _validate_geometry( const std::vector & args); std::tuple _validate_position( const std::vector & args); std::tuple _validate_size(const std::vector & args); std::tuple _validate_ws(const std::vector& args); void _set_alpha(float alpha); void _set_geometry(int x, int y, int w, int h); void _set_geometry_ppt(int x, int y, int w, int h); void _start_on_output(std::string output); void _move(int x, int y); void _resize(int w, int h); void _assign_ws(wf::point_t point); wf::geometry_t _get_workspace_grid_geometry(wf::output_t *output) const; wayfire_toplevel_view _view; wayfire_view _nontoplevel; }; } // End namespace wf. #endif // VIEW_ACTION_INTERFACE_HPP wayfire-0.10.0/plugins/window-rules/view-action-interface.cpp0000664000175000017500000003325115053502647024144 0ustar dkondordkondor#include "view-action-interface.hpp" #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view.hpp" #include "wayfire/plugins/grid.hpp" #include "wayfire/util/log.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/output-layout.hpp" #include "../wm-actions/wm-actions-signals.hpp" #include #include #include #include #include #include #include namespace wf { view_action_interface_t::~view_action_interface_t() {} bool view_action_interface_t::execute(const std::string & name, const std::vector & args) { const auto& execute_set_alpha = [&] { auto alpha = _validate_alpha(args); if (std::get<0>(alpha)) { _set_alpha(std::get<1>(alpha)); } }; if (!_view) { // We have a non-toplevel view. We can only adjust alpha for it ... if ((name != "set") || (args.size() != 2) || (wf::get_string(args[0]) != "alpha")) { LOGW("The only allowed action for non-toplevel views is", " set alpha , matched ", _nontoplevel); return true; } execute_set_alpha(); return false; } if (name == "set") { auto id = wf::get_string(args.at(0)); if (id == "sticky") { _make_sticky(); return false; } else if (id == "always_on_top") { _always_on_top(); return false; } if ((args.size() < 2) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Set execution requires at least 2 arguments, the first of which should be an identifier."); return true; } if (id == "alpha") { execute_set_alpha(); } else if (id == "geometry") { auto geometry = _validate_geometry(args); if (std::get<0>(geometry)) { _set_geometry(std::get<1>(geometry), std::get<2>(geometry), std::get<3>(geometry), std::get<4>(geometry)); } } else if (id == "geometry_ppt") { auto geometry = _validate_geometry(args); if (std::get<0>(geometry)) { _set_geometry_ppt(std::get<1>(geometry), std::get<2>(geometry), std::get<3>(geometry), std::get<4>(geometry)); } } else { LOGE("View action interface: Unsupported set operation to identifier ", id); return true; } return false; } else if (name == "maximize") { _maximize(); return false; } else if (name == "unmaximize") { _unmaximize(); return false; } else if (name == "minimize") { _minimize(); return false; } else if (name == "unminimize") { _unminimize(); return false; } else if (name == "snap") { if ((args.size() < 1) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Snap execution requires 1 string as argument."); return true; } auto output = _view->get_output(); if (output == nullptr) { LOGE("View action interface: Output associated with view was null."); return true; } auto location = wf::get_string(args.at(0)); grid::slot_t slot; if (location == "top") { slot = grid::SLOT_TOP; } else if (location == "top_right") { slot = grid::SLOT_TR; } else if (location == "right") { slot = grid::SLOT_RIGHT; } else if (location == "bottom_right") { slot = grid::SLOT_BR; } else if (location == "bottom") { slot = grid::SLOT_BOTTOM; } else if (location == "bottom_left") { slot = grid::SLOT_BL; } else if (location == "left") { slot = grid::SLOT_LEFT; } else if (location == "top_left") { slot = grid::SLOT_TL; } else if (location == "center") { slot = grid::SLOT_CENTER; } else { LOGE( "View action interface: Incorrect string literal for snap location: ", location, "."); return true; } LOGI("View action interface: Snap to ", location, "."); wf::get_core().default_wm->tile_request(_view, grid::get_tiled_edges_for_slot(slot)); return false; } else if (name == "start_on_output") { if ((args.size() < 1) || (wf::is_string(args.at(0)) == false)) { LOGE( "View action interface: Start on output execution requires 1 string as argument."); return true; } auto name = wf::get_string(args.at(0)); _start_on_output(name); return false; } else if (name == "move") { auto position = _validate_position(args); if (std::get<0>(position)) { _move(std::get<1>(position), std::get<2>(position)); return false; } LOGE("View action interface: invalid arguments for move"); return true; } else if (name == "resize") { auto size = _validate_size(args); if (std::get<0>(size)) { _resize(std::get<1>(size), std::get<2>(size)); return false; } LOGE("View action interface: invalid arguments for resize"); return true; } else if (name == "assign_workspace") { auto [ok, ws] = _validate_ws(args); if (ok) { _assign_ws(ws); return false; } return true; } LOGE("View action interface: Unsupported action execution requested. Name: ", name, "."); return true; } void view_action_interface_t::set_view(wayfire_view view) { _nontoplevel = view; _view = toplevel_cast(view); } void view_action_interface_t::_maximize() { wf::get_core().default_wm->tile_request(_view, wf::TILED_EDGES_ALL); } void view_action_interface_t::_unmaximize() { wf::get_core().default_wm->tile_request(_view, 0); } void view_action_interface_t::_minimize() { wf::get_core().default_wm->minimize_request(_view, true); } void view_action_interface_t::_unminimize() { wf::get_core().default_wm->minimize_request(_view, false); } void view_action_interface_t::_make_sticky() { _view->set_sticky(1); } void view_action_interface_t::_always_on_top() { wf::wm_actions_set_above_state_signal data; auto output = _view->get_output(); if (!output) { return; } data.view = _view; data.above = true; output->emit(&data); } std::tuple view_action_interface_t::_expect_float( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_float(args.at(position)))) { return {true, wf::get_float(args.at(position))}; } return {false, 0.0f}; } std::tuple view_action_interface_t::_expect_double( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_double(args.at(position)))) { return {true, wf::get_double(args.at(position))}; } return {false, 0.0}; } std::tuple view_action_interface_t::_expect_int( const std::vector & args, std::size_t position) { if ((args.size() > position) && (wf::is_int(args.at(position)))) { return {true, wf::get_int(args.at(position))}; } return {false, 0}; } std::tuple view_action_interface_t::_validate_alpha( const std::vector & args) { auto arg_float = _expect_float(args, 1); if (std::get<0>(arg_float)) { return arg_float; } else { auto arg_double = _expect_double(args, 1); if (std::get<0>(arg_double)) { return {true, static_cast(std::get<1>(arg_double))}; } } LOGE( "View action interface: Invalid arguments. Expected 'set alpha [float|double]."); return {false, 1.0f}; } std::tuple view_action_interface_t::_validate_geometry( const std::vector & args) { auto arg_x = _expect_int(args, 1); auto arg_y = _expect_int(args, 2); auto arg_w = _expect_int(args, 3); auto arg_h = _expect_int(args, 4); if (std::get<0>(arg_x) && std::get<0>(arg_y) && std::get<0>(arg_w) && std::get<0>(arg_h)) { return {true, std::get<1>(arg_x), std::get<1>(arg_y), std::get<1>(arg_w), std::get<1>(arg_h)}; } LOGE( "View action interface: Invalid arguments. Expected 'set geometry int int int int."); return {false, 0, 0, 0, 0}; } std::tuple view_action_interface_t::_validate_position( const std::vector & args) { auto arg_x = _expect_int(args, 0); auto arg_y = _expect_int(args, 1); if (std::get<0>(arg_x) && std::get<0>(arg_y)) { return {true, std::get<1>(arg_x), std::get<1>(arg_y)}; } LOGE("View action interface: Invalid arguments. Expected 'move int int."); return {false, 0, 0}; } std::tuple view_action_interface_t::_validate_size( const std::vector & args) { auto arg_w = _expect_int(args, 0); auto arg_h = _expect_int(args, 1); if (std::get<0>(arg_w) && std::get<0>(arg_h)) { return {true, std::get<1>(arg_w), std::get<1>(arg_h)}; } LOGE("View action interface: Invalid arguments. Expected 'resize int int."); return {false, 0, 0}; } void view_action_interface_t::_set_alpha(float alpha) { alpha = std::clamp(alpha, 0.1f, 1.0f); // Apply view transformer if needed and set alpha. auto tr = wf::ensure_named_transformer( _nontoplevel, wf::TRANSFORMER_2D, "alpha", _nontoplevel); if (fabs(tr->alpha - alpha) > FLT_EPSILON) { tr->alpha = alpha; _nontoplevel->damage(); LOGI("View action interface: Alpha set to ", alpha, "."); } } void view_action_interface_t::_set_geometry(int x, int y, int w, int h) { _resize(w, h); _move(x, y); } void view_action_interface_t::_set_geometry_ppt(int x, int y, int w, int h) { auto output = _view->get_output(); if (!output) { return; } auto og = output->get_relative_geometry(); x = std::clamp(x, 0, 100); y = std::clamp(y, 0, 100); w = std::clamp(w, 0, 100); h = std::clamp(h, 0, 100); x = og.width * x / 100; y = og.height * y / 100; w = og.width * w / 100; h = og.height * h / 100; _resize(w, h); _move(x, y); } void view_action_interface_t::_start_on_output(std::string name) { auto output = wf::get_core().output_layout->find_output(name); if (!output) { return; } if (_view->parent) { return; } if (_view->get_output() == output) { return; } move_view_to_output(_view, output, true); } std::tuple view_action_interface_t::_validate_ws( const std::vector& args) { if (!this->_view->get_output()) { return {false, {}}; } if (args.size() != 2) { LOGE("Invalid workspace identifier, expected "); } auto [ok1, x] = _expect_int(args, 0); auto [ok2, y] = _expect_int(args, 1); if (!ok1 || !ok2) { LOGE("Workspace coordinates should be integers!"); return {false, {}}; } auto wsize = _view->get_output()->wset()->get_workspace_grid_size(); if (((0 <= x) && (x < wsize.width)) && ((0 <= y) && (y < wsize.height))) { return {true, {x, y}}; } LOGE("Workspace coordinates out of bounds!"); return {false, {}}; } wf::geometry_t view_action_interface_t::_get_workspace_grid_geometry( wf::output_t *output) const { auto vsize = output->wset()->get_workspace_grid_size(); auto vp = output->wset()->get_current_workspace(); auto res = output->get_screen_size(); return wf::geometry_t{ -vp.x * res.width, -vp.y * res.height, vsize.width * res.width, vsize.height * res.height, }; } void view_action_interface_t::_move(int x, int y) { // Clamp x and y to sane values. Do not allow to move outside of workspace grid. auto output = _view->get_output(); if (output != nullptr) { auto grid = this->_get_workspace_grid_geometry(output); auto view_geometry = _view->get_pending_geometry(); view_geometry.x = x; view_geometry.y = y; view_geometry = wf::clamp(view_geometry, grid); _view->move(view_geometry.x, view_geometry.y); } } void view_action_interface_t::_resize(int w, int h) { // Clamp w and h to sane values. Do not allow to get bigger then output. Do not // allow to get smaller then 40x30. auto output = _view->get_output(); if (output != nullptr) { auto dimensions = output->get_screen_size(); w = std::clamp(w, 40, dimensions.width); h = std::clamp(h, 30, dimensions.height); _view->resize(w, h); } } void view_action_interface_t::_assign_ws(wf::point_t point) { auto output = _view->get_output(); auto delta = point - output->wset()->get_current_workspace(); auto size = output->get_screen_size(); auto wm = _view->get_pending_geometry(); _view->move(wm.x + delta.x * size.width, wm.y + delta.y * size.height); } } // End namespace wf. wayfire-0.10.0/plugins/protocols/0000775000175000017500000000000015053502647016636 5ustar dkondordkondorwayfire-0.10.0/plugins/protocols/gtk-shell.cpp0000664000175000017500000002676215053502647021251 0ustar dkondordkondor#include "gtk-shell-protocol.h" #include "wayfire/core.hpp" #include "wayfire/object.hpp" #include #include #include #include #include #include #include #include #include #include "gtk-shell.hpp" #define GTK_SHELL_VERSION 3 class wf_gtk_shell : public wf::custom_data_t { public: std::map surface_app_id; }; struct wf_gtk_surface { wl_resource *resource; wl_resource *wl_surface; wf::wl_listener_wrapper on_configure; wf::wl_listener_wrapper on_destroy; }; /** * In gnome-shell/mutter/meta windows/views keep track of the properties * specified as arguments here. * Currently only the app_id is implemented / required. */ static void handle_gtk_surface_set_dbus_properties(wl_client *client, wl_resource *resource, const char *application_id, const char *app_menu_path, const char *menubar_path, const char *window_object_path, const char *application_object_path, const char *unique_bus_name) { auto surface = static_cast(wl_resource_get_user_data(resource)); if (application_id) { wf::get_core().get_data_safe()->surface_app_id[surface->wl_surface] = application_id; } } /** * Modal dialogs may be handled differently than non-modal dialogs. * It is a hint that this should be attached to the parent surface. * In gnome this does not affect input-focus. * This function sets the modal hint. */ static void handle_gtk_surface_set_modal(wl_client *client, wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface); if (view) { view->store_data(std::make_unique(), "gtk-shell-modal"); } } /** * Modal dialogs may be handled differently than non-modal dialogs. * It is a hint that this should be attached to the parent surface. * In gnome this does not affect input-focus. * This function removes the modal hint. */ static void handle_gtk_surface_unset_modal(wl_client *client, wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_view view = wf::wl_surface_to_wayfire_view(surface->wl_surface); if (view) { view->erase_data("gtk-shell-modal"); } } /** * The surface requests focus, for example single instance applications like * gnome-control-center, gnome-clocks, dconf-editor are single instance and if * they are already running and launched again, this will request that they get * focused. * This function is superseded by handle_gtk_surface_request_focus a newer * equivalelent * used by gtk-applications now. This function is for compatibility reasons. */ static void handle_gtk_surface_present(wl_client *client, wl_resource *resource, uint32_t time) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_toplevel_view view = toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { wf::get_core().default_wm->focus_request(view, true); } } /** * The surface requests focus, for example single instance applications like * gnome-control-center, gnome-clocks, dconf-editor are single instance and if * they are already running and launched again, this will request that they get * focused. */ static void handle_gtk_surface_request_focus(struct wl_client *client, struct wl_resource *resource, const char *startup_id) { auto surface = static_cast(wl_resource_get_user_data(resource)); wayfire_toplevel_view view = toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { wf::get_core().default_wm->focus_request(view, true); } } /** * Helper function used by send_gtk_surface_configure * and send_gtk_surface_configure_edges */ static void append_to_array(wl_array *array, uint32_t value) { uint32_t *tmp; tmp = (uint32_t*)wl_array_add(array, sizeof(*tmp)); *tmp = value; } /** * Tells the client about the window state in more detail than xdg_surface. * This currently only includes which edges are tiled. */ static void send_gtk_surface_configure(wf_gtk_surface *surface, wayfire_toplevel_view view) { int version = wl_resource_get_version(surface->resource); wl_array states; wl_array_init(&states); if (view->pending_tiled_edges()) { append_to_array(&states, GTK_SURFACE1_STATE_TILED); } if ((version >= GTK_SURFACE1_STATE_TILED_TOP_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_TOP)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_TOP); } if ((version >= GTK_SURFACE1_STATE_TILED_RIGHT_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_RIGHT)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_RIGHT); } if ((version >= GTK_SURFACE1_STATE_TILED_BOTTOM_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_BOTTOM)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_BOTTOM); } if ((version >= GTK_SURFACE1_STATE_TILED_LEFT_SINCE_VERSION) && (view->pending_tiled_edges() & WLR_EDGE_LEFT)) { append_to_array(&states, GTK_SURFACE1_STATE_TILED_LEFT); } gtk_surface1_send_configure(surface->resource, &states); wl_array_release(&states); } /** * Tells gtk which edges should be resizable. */ static void send_gtk_surface_configure_edges(wf_gtk_surface *surface, wayfire_toplevel_view view) { wl_array edges; wl_array_init(&edges); if (!view->pending_tiled_edges()) { append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_TOP); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_RIGHT); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_BOTTOM); append_to_array(&edges, GTK_SURFACE1_EDGE_CONSTRAINT_RESIZABLE_LEFT); } gtk_surface1_send_configure_edges(surface->resource, &edges); wl_array_release(&edges); } /** * Augments xdg_surface's configure with additional gtk-specific information. */ static void handle_xdg_surface_on_configure(wf_gtk_surface *surface) { wayfire_toplevel_view view = wf::toplevel_cast(wf::wl_surface_to_wayfire_view(surface->wl_surface)); if (view) { send_gtk_surface_configure(surface, view); if (wl_resource_get_version(surface->resource) >= GTK_SURFACE1_CONFIGURE_EDGES_SINCE_VERSION) { send_gtk_surface_configure_edges(surface, view); } } } /** * Prevents a race condition where the xdg_surface is destroyed before * the gtk_surface's resource and the gtk_surface's destructor tries to * disconnect these signals which causes a use-after-free */ static void handle_xdg_surface_on_destroy(wf_gtk_surface *surface) { surface->on_configure.disconnect(); surface->on_destroy.disconnect(); } /** * Destroys the gtk_surface object. */ static void handle_gtk_surface_destroy(wl_resource *resource) { auto surface = static_cast(wl_resource_get_user_data(resource)); delete surface; } /** * Supported functions of the gtk_surface_interface implementation */ const struct gtk_surface1_interface gtk_surface1_impl = { .set_dbus_properties = handle_gtk_surface_set_dbus_properties, .set_modal = handle_gtk_surface_set_modal, .unset_modal = handle_gtk_surface_unset_modal, .present = handle_gtk_surface_present, .request_focus = handle_gtk_surface_request_focus, }; /** * Initializes a gtk_surface object and passes it to the client. */ static void handle_gtk_shell_get_gtk_surface(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface) { wf_gtk_surface *gtk_surface = new wf_gtk_surface; gtk_surface->resource = wl_resource_create(client, >k_surface1_interface, wl_resource_get_version(resource), id); gtk_surface->wl_surface = surface; wl_resource_set_implementation(gtk_surface->resource, >k_surface1_impl, gtk_surface, handle_gtk_surface_destroy); wlr_surface *wlr_surface = wlr_surface_from_resource(surface); if (wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface)) { gtk_surface->on_configure.set_callback([=] (void*) { handle_xdg_surface_on_configure(gtk_surface); }); gtk_surface->on_configure.connect(&xdg_surface->events.configure); gtk_surface->on_destroy.set_callback([=] (void*) { handle_xdg_surface_on_destroy(gtk_surface); }); gtk_surface->on_destroy.connect(&xdg_surface->events.destroy); } } /** * Supplements the request_focus() and present() * to prevent focus stealing if user interaction happened * between the time application was called and request_focus was received. * Not implemented. */ static void handle_gtk_shell_notify_launch(wl_client *client, wl_resource *resource, const char *startup_id) {} /** * A view could use this to receive notification when the surface is ready. * Gets the DESKTOP_STARTUP_ID from environment and unsets this env var afterwards * so any child processes don't inherit it. * Not implemented. */ static void handle_gtk_shell_set_startup_id(wl_client *client, wl_resource *resource, const char *startup_id) {} /** * A view could use this to invoke the system bell, be it aural, visual or none at * all. */ static void handle_gtk_shell_system_bell(wl_client *client, wl_resource *resource, wl_resource *surface) { wf::view_system_bell_signal data; if (surface) { auto gtk_surface = static_cast(wl_resource_get_user_data(surface)); data.view = wf::wl_surface_to_wayfire_view(gtk_surface->wl_surface); } wf::get_core().emit(&data); } /** * Supported functions of the gtk_shell_interface implementation */ static const struct gtk_shell1_interface gtk_shell1_impl = { .get_gtk_surface = handle_gtk_shell_get_gtk_surface, .set_startup_id = handle_gtk_shell_set_startup_id, .system_bell = handle_gtk_shell_system_bell, .notify_launch = handle_gtk_shell_notify_launch, }; /** * Destroy the gtk_shell object. * gtk_shell exists as long as the compositor runs. */ static void handle_gtk_shell1_destroy(wl_resource *resource) {} /** * Binds the gtk_shell to wayland. */ void bind_gtk_shell1(wl_client *client, void *data, uint32_t version, uint32_t id) { auto resource = wl_resource_create(client, >k_shell1_interface, GTK_SHELL_VERSION, id); wl_resource_set_implementation(resource, >k_shell1_impl, data, handle_gtk_shell1_destroy); } class wayfire_gtk_shell_impl : public wf::plugin_interface_t { public: void init() override { auto display = wf::get_core().display; wl_global_create(display, >k_shell1_interface, GTK_SHELL_VERSION, NULL, bind_gtk_shell1); wf::get_core().connect(&on_app_id_query); } bool is_unloadable() override { return false; } wf::signal::connection_t on_app_id_query = [=] (gtk_shell_app_id_query_signal *ev) { if (auto surface = ev->view->get_wlr_surface()) { auto shell = wf::get_core().get_data_safe(); ev->app_id = shell->surface_app_id[surface->resource]; } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_gtk_shell_impl); wayfire-0.10.0/plugins/protocols/gtk-shell.hpp0000664000175000017500000000045315053502647021243 0ustar dkondordkondor#pragma once #include /** * A signal to query the gtk_shell plugin about the gtk-shell-specific app_id of the given view. */ struct gtk_shell_app_id_query_signal { wayfire_view view; // Set by the gtk-shell plugin in response to the signal std::string app_id; }; wayfire-0.10.0/plugins/protocols/session-lock.cpp0000664000175000017500000004113515053502647021757 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/seat.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/output.hpp" #include "wayfire/output-layout.hpp" #include #include #include #include #include class lock_surface_keyboard_interaction : public wf::keyboard_interaction_t { public: lock_surface_keyboard_interaction(wlr_surface *surface) : surface(surface) {} void handle_keyboard_enter(wf::seat_t *seat) override { wlr_seat_keyboard_enter(seat->seat, surface, nullptr, 0, nullptr); } void handle_keyboard_leave(wf::seat_t *seat) override { wlr_seat_keyboard_clear_focus(seat->seat); } void handle_keyboard_key(wf::seat_t *seat, wlr_keyboard_key_event event) override { wlr_seat_keyboard_notify_key(seat->seat, event.time_msec, event.keycode, event.state); } private: wlr_surface *surface; }; // Mixin class for common functionality between lock_surface_node and lock_crashed_node. template class lock_base_node : public Node... { public: template lock_base_node(wf::output_t *output, T&&... v) : Node(std::forward(v)...)..., output(output) {} wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) { if (output != this->output) { return wf::keyboard_focus_node_t{}; } wf::keyboard_focus_node_t node = { .node = this, .importance = wf::focus_importance::HIGH, .allow_focus_below = false, }; return node; } protected: wf::output_t *output; }; // Scenegraph node for the surface displayed by the session lock client. class lock_surface_node : public lock_base_node { public: lock_surface_node(wlr_session_lock_surface_v1 *lock_surface, wf::output_t *output) : lock_base_node(output, lock_surface->surface, true /* autocommit */), lock_surface(lock_surface), interaction(std::make_unique(lock_surface->surface)) {} void configure(wf::dimensions_t size) { wlr_session_lock_surface_v1_configure(lock_surface, size.width, size.height); LOGC(LSHELL, "surface_configure on ", lock_surface->output->name, " ", size); } void display() { auto layer_node = output->node_for_layer(wf::scene::layer::LOCK); wf::scene::add_front(layer_node, shared_from_this()); wf::wlr_surface_controller_t::create_controller(lock_surface->surface, layer_node); wf::get_core().seat->set_active_node(shared_from_this()); wf::get_core().seat->refocus(); } void destroy() { wf::scene::damage_node(shared_from_this(), get_bounding_box()); wf::wlr_surface_controller_t::try_free_controller(this->lock_surface->surface); wf::scene::remove_child(shared_from_this()); const char *name = this->output->handle ? this->output->handle->name : "(deleted)"; this->interaction = std::make_unique(); LOGC(LSHELL, "lock_surface on ", name, " destroyed"); } wf::keyboard_interaction_t& keyboard_interaction() { return *interaction; } private: wlr_session_lock_surface_v1 *lock_surface; std::unique_ptr interaction; }; // Scenegraph node used to keep the screen locked if the session lock client crashes. class lock_crashed_node : public lock_base_node { public: lock_crashed_node(wf::output_t *output) : lock_base_node(output) { set_position({0, 0}); // TODO: it seems better to create the node and add it to the back of the scenegraph so // it will be displayed if the client crashes and its surface is destroyed. // Unfortunately this causes the surface to briefly appear before the lock screen. // So make the background completely transparent instead, and then add the text // when the client suraface is destroyed. wf::cairo_text_t::params params( 1280 /* font_size */, wf::color_t{0.1, 0.1, 0.1, 0} /* bg_color */, wf::color_t{0.9, 0.9, 0.9, 1} /* fg_color */); params.rounded_rect = true; set_text_params(params); set_size(output->get_screen_size()); } void display(std::string text) { wf::cairo_text_t::params params( 1280 /* font_size */, wf::color_t{0, 0, 0, 1} /* bg_color */, wf::color_t{0.9, 0.9, 0.9, 1} /* fg_color */); set_text_params(params); // TODO: make the text smaller and display a useful message instead of a big explosion. set_text(text); auto layer_node = output->node_for_layer(wf::scene::layer::LOCK); if (parent() == nullptr) { wf::scene::add_back(layer_node, shared_from_this()); } wf::get_core().seat->set_active_node(shared_from_this()); } void display_crashed() { display("💥"); } // Ensure pointer interaction is not passed to views behind this node. std::optional find_node_at(const wf::pointf_t& at) override { wf::scene::input_node_t result; result.node = this; result.local_coords = at; return result; } }; class wf_session_lock_plugin : public wf::plugin_interface_t { enum lock_state { LOCKING, LOCKED, UNLOCKED, DESTROYED, ZOMBIE, }; struct output_state { std::shared_ptr surface_node; wf::wl_listener_wrapper surface_destroy; std::shared_ptr crashed_node; output_state(wf::output_t *output) { crashed_node = std::make_shared(output); crashed_node->set_text(""); } ~output_state() { surface_destroy.disconnect(); surface_node.reset(); crashed_node.reset(); } }; class wayfire_session_lock { public: wayfire_session_lock(wf_session_lock_plugin *plugin, wlr_session_lock_v1 *lock) : plugin(plugin), lock(lock) { auto& ol = wf::get_core().output_layout; output_added.set_callback([this] (wf::output_added_signal *ev) { handle_output_added(ev->output); }); ol->connect(&output_added); output_removed.set_callback([this] (wf::output_removed_signal *ev) { handle_output_removed(ev->output); }); ol->connect(&output_removed); output_changed.set_callback([this] (wf::output_configuration_changed_signal *ev) { auto output_state = output_states[ev->output]; auto size = ev->output->get_screen_size(); if (output_state->surface_node) { output_state->surface_node->configure(size); } if (output_state->crashed_node) { output_state->crashed_node->set_size(size); } }); for (auto output : ol->get_outputs()) { handle_output_added(output); } new_surface.set_callback([this] (void *data) { wlr_session_lock_surface_v1 *lock_surface = (wlr_session_lock_surface_v1*)data; wlr_output *wo = lock_surface->output; auto output = wf::get_core().output_layout->find_output(lock_surface->output); if (!output || (output_states.find(output) == output_states.end())) { LOGE("lock_surface created on deleted output ", wo->name); return; } auto surface_node = std::make_shared(lock_surface, output); surface_node->configure(output->get_screen_size()); output_states[output]->surface_destroy.set_callback( [this, surface_node, output] (void*) { surface_node->destroy(); // Output might have been removed. if (output_states.find(output) != output_states.end()) { output_states[output]->surface_node.reset(); if (output_states[output]->crashed_node) { output_states[output]->crashed_node->display_crashed(); } } output_states[output]->surface_destroy.disconnect(); }); output_states[output]->surface_destroy.connect(&lock_surface->events.destroy); output_states[output]->surface_node = std::move(surface_node); if (state == LOCKED) { // Output is already inhibited. output_states[output]->surface_node->display(); } else if (have_all_surfaces()) { // All lock surfaces ready. Lock. lock_timer.disconnect(); lock_all(); } }); new_surface.connect(&lock->events.new_surface); unlock.set_callback([this] (void *data) { unlock_all(); }); unlock.connect(&lock->events.unlock); destroy.set_callback([this] (void *data) { disconnect_signals(); set_state(state == UNLOCKED ? DESTROYED : ZOMBIE); if (state == ZOMBIE) { // ensure that the crashed node is displayed in this case as well lock_all(); } LOGC(LSHELL, "session lock destroyed"); }); destroy.connect(&lock->events.destroy); lock_timer.set_timeout(1000, [this] (void) { lock_all(); }); set_state(LOCKING); } ~wayfire_session_lock() { disconnect_signals(); output_changed.disconnect(); output_added.disconnect(); output_removed.disconnect(); remove_crashed_nodes(); } private: void handle_output_added(wf::output_t *output) { output_states[output] = std::make_shared(output); if ((state == LOCKED) || (state == ZOMBIE)) { lock_output(output, output_states[output]); } output->connect(&output_changed); } void handle_output_removed(wf::output_t *output) { output->disconnect(&output_changed); output_states.erase(output); } bool have_all_surfaces() { for (const auto& [_, output_state] : output_states) { if (!output_state->surface_node) { return false; } } return true; } void lock_output(wf::output_t *output, std::shared_ptr output_state) { output->set_inhibited(true); if (state == ZOMBIE) { output_state->crashed_node->display_crashed(); } else if (output_state->surface_node) { output_state->surface_node->display(); } else { // if the surface node has not yet been displayed we show an empty surface output_state->crashed_node->display(""); } } void lock_all() { for (const auto& [output, output_state] : output_states) { lock_output(output, output_state); } if (state != ZOMBIE) { wlr_session_lock_v1_send_locked(lock); set_state(LOCKED); } LOGC(LSHELL, "lock"); } // TODO: rename to remove_all_surfaces and have it also remove/disconnect lock surfaces? void remove_crashed_nodes() { for (const auto& [output, output_state] : output_states) { if (output_state->crashed_node) { wf::scene::damage_node(output_state->crashed_node, output_state->crashed_node->get_bounding_box()); wf::scene::remove_child(output_state->crashed_node); output_state->crashed_node.reset(); } } } void unlock_all() { remove_crashed_nodes(); for (const auto& [output, output_state] : output_states) { output->set_inhibited(false); } set_state(UNLOCKED); LOGC(LSHELL, "unlock"); } void set_state(lock_state new_state) { state = new_state; plugin->notify_lock_state(state); } void disconnect_signals() { // Disconnect lock_session protocol signals and lock timer because the client has gone. // Leave output monitoring signals connected: if the client crashed and this lock is // in ZOMBIE state, it must keep monitoring output changes to add/remove/resize // lock_crashed_nodes. new_surface.disconnect(); unlock.disconnect(); destroy.disconnect(); lock_timer.disconnect(); } wf_session_lock_plugin *plugin; wlr_session_lock_v1 *lock; wf::wl_timer lock_timer; std::map> output_states; wf::wl_listener_wrapper new_surface; wf::wl_listener_wrapper unlock; wf::wl_listener_wrapper destroy; wf::signal::connection_t output_added; wf::signal::connection_t output_changed; wf::signal::connection_t output_removed; lock_state state = UNLOCKED; }; public: void init() override { auto display = wf::get_core().display; manager = wlr_session_lock_manager_v1_create(display); new_lock.set_callback([this] (void *data) { wlr_session_lock_v1 *wlr_lock = (wlr_session_lock_v1*)data; if (!cur_lock) { cur_lock.reset(new wayfire_session_lock(this, wlr_lock)); LOGC(LSHELL, "new_lock"); } else { LOGE("new_lock: already locked"); wlr_session_lock_v1_destroy(wlr_lock); } }); new_lock.connect(&manager->events.new_lock); destroy.set_callback([] (void *data) { LOGC(LSHELL, "session_lock_manager destroyed"); }); destroy.connect(&manager->events.destroy); } void notify_lock_state(lock_state state) { switch (state) { case UNLOCKED: case LOCKING: break; case LOCKED: // Screen locked. // If a previous lock is in zombie state, delete it, so it stops listening for output // changes, removes lock_crashed_nodes, etc. prev_lock.reset(); break; case DESTROYED: // Session lock client terminated after unlocking. cur_lock.reset(); wf::get_core().seat->refocus(); break; case ZOMBIE: // Session lock client crashed. // Keep session locked, but remember previous lock so that if a new client connects and // then unlocks, crashed nodes are removed, outputs are un-inhibited, etc. LOGC(LSHELL, "session_lock_manager destroyed"); prev_lock = std::move(cur_lock); break; } } void fini() override { // TODO: unlock everything? } bool is_unloadable() override { return false; } private: wlr_session_lock_manager_v1 *manager; wf::wl_listener_wrapper new_lock; wf::wl_listener_wrapper destroy; std::shared_ptr cur_lock, prev_lock; }; DECLARE_WAYFIRE_PLUGIN(wf_session_lock_plugin); wayfire-0.10.0/plugins/protocols/foreign-toplevel.cpp0000664000175000017500000002347015053502647022631 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/signal-definitions.hpp" #include #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include "gtk-shell.hpp" #include "config.h" class wayfire_foreign_toplevel; using foreign_toplevel_map_type = std::map>; class wayfire_foreign_toplevel { wayfire_toplevel_view view; wlr_foreign_toplevel_handle_v1 *handle; foreign_toplevel_map_type *view_to_toplevel; public: wayfire_foreign_toplevel(wayfire_toplevel_view view, wlr_foreign_toplevel_handle_v1 *handle, foreign_toplevel_map_type *view_to_toplevel) { this->view = view; this->handle = handle; this->view_to_toplevel = view_to_toplevel; init_request_handlers(); toplevel_handle_v1_close_request.connect(&handle->events.request_close); toplevel_handle_v1_maximize_request.connect(&handle->events.request_maximize); toplevel_handle_v1_minimize_request.connect(&handle->events.request_minimize); toplevel_handle_v1_activate_request.connect(&handle->events.request_activate); toplevel_handle_v1_fullscreen_request.connect(&handle->events.request_fullscreen); toplevel_handle_v1_set_rectangle_request.connect(&handle->events.set_rectangle); toplevel_send_title(); toplevel_send_app_id(); toplevel_send_state(); toplevel_update_output(view->get_output(), true); view->connect(&on_title_changed); view->connect(&on_app_id_changed); view->connect(&on_set_output); view->connect(&on_tiled); view->connect(&on_minimized); view->connect(&on_fullscreen); view->connect(&on_activated); view->connect(&on_parent_changed); } ~wayfire_foreign_toplevel() { toplevel_handle_v1_close_request.disconnect(); toplevel_handle_v1_maximize_request.disconnect(); toplevel_handle_v1_minimize_request.disconnect(); toplevel_handle_v1_activate_request.disconnect(); toplevel_handle_v1_fullscreen_request.disconnect(); toplevel_handle_v1_set_rectangle_request.disconnect(); wlr_foreign_toplevel_handle_v1_destroy(handle); } private: void toplevel_send_title() { wlr_foreign_toplevel_handle_v1_set_title(handle, view->get_title().c_str()); } void toplevel_send_app_id() { std::string app_id; auto default_app_id = view->get_app_id(); gtk_shell_app_id_query_signal ev; ev.view = view; wf::get_core().emit(&ev); std::string app_id_mode = wf::option_wrapper_t("workarounds/app_id_mode"); if ((app_id_mode == "gtk-shell") && (ev.app_id.length() > 0)) { app_id = ev.app_id; } else if (app_id_mode == "full") { #if WF_HAS_XWAYLAND auto wlr_surface = view->get_wlr_surface(); if (wlr_surface) { if (wlr_xwayland_surface *xw_surface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)) { ev.app_id = nonull(xw_surface->instance); } } #endif app_id = default_app_id + " " + ev.app_id + " wf-ipc-" + std::to_string(view->get_id()); } else { app_id = default_app_id; } wlr_foreign_toplevel_handle_v1_set_app_id(handle, app_id.c_str()); } void toplevel_send_state() { wlr_foreign_toplevel_handle_v1_set_maximized(handle, view->pending_tiled_edges() == wf::TILED_EDGES_ALL); wlr_foreign_toplevel_handle_v1_set_activated(handle, view->activated); wlr_foreign_toplevel_handle_v1_set_minimized(handle, view->minimized); wlr_foreign_toplevel_handle_v1_set_fullscreen(handle, view->pending_fullscreen()); /* update parent as well */ auto it = view_to_toplevel->find(view->parent); if (it == view_to_toplevel->end()) { wlr_foreign_toplevel_handle_v1_set_parent(handle, nullptr); } else { wlr_foreign_toplevel_handle_v1_set_parent(handle, it->second->handle); } } void toplevel_update_output(wf::output_t *output, bool enter) { if (output && enter) { wlr_foreign_toplevel_handle_v1_output_enter(handle, output->handle); } if (output && !enter) { wlr_foreign_toplevel_handle_v1_output_leave(handle, output->handle); } } wf::signal::connection_t on_title_changed = [=] (auto) { toplevel_send_title(); }; wf::signal::connection_t on_app_id_changed = [=] (auto) { toplevel_send_app_id(); }; wf::signal::connection_t on_set_output = [=] (wf::view_set_output_signal *ev) { toplevel_update_output(ev->output, false); toplevel_update_output(view->get_output(), true); }; wf::signal::connection_t on_minimized = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_fullscreen = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_tiled = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_activated = [=] (auto) { toplevel_send_state(); }; wf::signal::connection_t on_parent_changed = [=] (auto) { toplevel_send_state(); }; wf::wl_listener_wrapper toplevel_handle_v1_maximize_request; wf::wl_listener_wrapper toplevel_handle_v1_activate_request; wf::wl_listener_wrapper toplevel_handle_v1_minimize_request; wf::wl_listener_wrapper toplevel_handle_v1_set_rectangle_request; wf::wl_listener_wrapper toplevel_handle_v1_fullscreen_request; wf::wl_listener_wrapper toplevel_handle_v1_close_request; void init_request_handlers() { toplevel_handle_v1_maximize_request.set_callback([&] (void *data) { auto ev = static_cast(data); wf::get_core().default_wm->tile_request(view, ev->maximized ? wf::TILED_EDGES_ALL : 0); }); toplevel_handle_v1_minimize_request.set_callback([&] (void *data) { auto ev = static_cast(data); wf::get_core().default_wm->minimize_request(view, ev->minimized); }); toplevel_handle_v1_activate_request.set_callback([&] (auto) { wf::get_core().default_wm->focus_request(view); }); toplevel_handle_v1_close_request.set_callback([&] (auto) { view->close(); }); toplevel_handle_v1_set_rectangle_request.set_callback([&] (void *data) { auto ev = static_cast(data); auto surface = wf::wl_surface_to_wayfire_view(ev->surface->resource); if (!surface) { LOGE("Setting minimize hint to unknown surface. Wayfire currently" "supports only setting hints relative to views."); return; } handle_minimize_hint(view.get(), surface.get(), {ev->x, ev->y, ev->width, ev->height}); }); toplevel_handle_v1_fullscreen_request.set_callback([&] ( void *data) { auto ev = static_cast(data); auto wo = wf::get_core().output_layout->find_output(ev->output); wf::get_core().default_wm->fullscreen_request(view, wo, ev->fullscreen); }); } void handle_minimize_hint(wf::toplevel_view_interface_t *view, wf::view_interface_t *relative_to, wlr_box hint) { if (relative_to->get_output() != view->get_output()) { LOGE("Minimize hint set to surface on a different output, " "problems might arise"); /* TODO: translate coordinates in case minimize hint is on another output */ } wf::pointf_t relative = relative_to->get_surface_root_node()->to_global({0, 0}); hint.x += relative.x; hint.y += relative.y; view->set_minimize_hint(hint); } }; class wayfire_foreign_toplevel_protocol_impl : public wf::plugin_interface_t { public: void init() override { toplevel_manager = wlr_foreign_toplevel_manager_v1_create(wf::get_core().display); wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_view_unmapped); } void fini() override {} bool is_unloadable() override { return false; } private: wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { auto handle = wlr_foreign_toplevel_handle_v1_create(toplevel_manager); handle_for_view[toplevel] = std::make_unique(toplevel, handle, &handle_for_view); } }; wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { handle_for_view.erase(toplevel_cast(ev->view)); }; wlr_foreign_toplevel_manager_v1 *toplevel_manager; std::map> handle_for_view; }; DECLARE_WAYFIRE_PLUGIN(wayfire_foreign_toplevel_protocol_impl); wayfire-0.10.0/plugins/protocols/text-input-v1-v3.hpp0000664000175000017500000001563315053502647022352 0ustar dkondordkondor#pragma once #include "text-input-unstable-v1-protocol.h" #include "wayfire/nonstd/wlroots-full.hpp" #include "wayfire/debug.hpp" class wayfire_im_text_input_base_t { public: wayfire_im_text_input_base_t(wl_client *client, void *dbg_handle) : client(client), dbg_handle(dbg_handle) {} virtual ~wayfire_im_text_input_base_t() = default; void set_focus_surface(wlr_surface *surface) { wl_client *next_client = surface ? wl_resource_get_client(surface->resource) : nullptr; if (current_focus) { if (!next_client || (next_client != client) || (surface != current_focus)) { LOGC(IM, "Leave text input ti=", dbg_handle); disable_focus(); current_focus = nullptr; } } if ((next_client == client) && (surface != current_focus)) { LOGC(IM, "Enter text input ti=", dbg_handle, " surface=", surface); enable_focus(surface); current_focus = surface; } } virtual void activate(wlr_surface *surface) {} virtual void deactivate() {} virtual void send_commit_string(uint32_t serial, const char *text) {} virtual void send_preedit_string(uint32_t serial, const char *text, const char *commit) {} virtual void send_preedit_styling(uint32_t index, uint32_t length, uint32_t style) {} virtual void send_preedit_cursor(int32_t index) {} virtual void send_delete_surrounding_text(int32_t index, uint32_t length) {} virtual void send_cursor_position(int32_t index, int32_t anchor) {} virtual void send_modifiers_map(wl_array *map) {} virtual void send_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) {} virtual void send_language(uint32_t serial, const char *language) {} virtual void send_text_direction(uint32_t serial, uint32_t direction) {} virtual void enable_focus(wlr_surface *surface) = 0; virtual void disable_focus() = 0; wl_client *client = NULL; wlr_surface *current_focus = NULL; void *dbg_handle; }; class wayfire_im_v1_text_input_v1 : public wayfire_im_text_input_base_t { public: wayfire_im_v1_text_input_v1(wl_resource *text_input) : wayfire_im_text_input_base_t(wl_resource_get_client(text_input), text_input) { this->text_input_v1 = text_input; } void enable_focus(wlr_surface *surface) override { this->enabled = true; } void disable_focus() override { this->enabled = false; } void activate(wlr_surface *surface) override { zwp_text_input_v1_send_enter(text_input_v1, surface->resource); } void deactivate() override { zwp_text_input_v1_send_leave(text_input_v1); } void send_commit_string(uint32_t serial, const char *text) override { zwp_text_input_v1_send_commit_string(text_input_v1, serial, text); } void send_preedit_string(uint32_t serial, const char *text, const char *commit) override { zwp_text_input_v1_send_preedit_string(text_input_v1, serial, text, commit); } void send_preedit_styling(uint32_t index, uint32_t length, uint32_t style) override { zwp_text_input_v1_send_preedit_styling(text_input_v1, index, length, style); } void send_preedit_cursor(int32_t index) override { zwp_text_input_v1_send_preedit_cursor(text_input_v1, index); } void send_delete_surrounding_text(int32_t index, uint32_t length) override { zwp_text_input_v1_send_delete_surrounding_text(text_input_v1, index, length); } void send_cursor_position(int32_t index, int32_t anchor) override { zwp_text_input_v1_send_cursor_position(text_input_v1, index, anchor); } void send_modifiers_map(struct wl_array *map) override { zwp_text_input_v1_send_modifiers_map(text_input_v1, map); } void send_keysym(uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) override { zwp_text_input_v1_send_keysym(text_input_v1, serial, time, sym, state, modifiers); } void send_language(uint32_t serial, const char *language) override { zwp_text_input_v1_send_language(text_input_v1, serial, language); } void send_text_direction(uint32_t serial, uint32_t direction) override { zwp_text_input_v1_send_text_direction(text_input_v1, serial, direction); } wl_resource *text_input_v1; bool enabled = false; bool can_focus() const { return enabled; } }; class wayfire_im_v1_text_input_v3 : public wayfire_im_text_input_base_t { public: wayfire_im_v1_text_input_v3(wlr_text_input_v3 *text_input) : wayfire_im_text_input_base_t(wl_resource_get_client(text_input->resource), text_input) { this->text_input_v3 = text_input; on_enable.connect(&text_input->events.enable); on_disable.connect(&text_input->events.disable); on_destroy.connect(&text_input->events.destroy); on_commit.connect(&text_input->events.commit); } void enable_focus(wlr_surface *surface) override { wlr_text_input_v3_send_enter(text_input_v3, surface); } void disable_focus() override { wlr_text_input_v3_send_leave(text_input_v3); } bool is_enabled() const { return text_input_v3->current_enabled; } wlr_text_input_v3 *text_input_v3 = NULL; wf::wl_listener_wrapper on_enable; wf::wl_listener_wrapper on_disable; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_commit; int32_t cursor = 0; void send_commit_string(uint32_t serial, const char *text) override { wlr_text_input_v3_send_commit_string(text_input_v3, text); wlr_text_input_v3_send_done(text_input_v3); } void send_preedit_string(uint32_t serial, const char *text, const char *commit) override { int begin = std::min((int)strlen(text), cursor); int end = std::min((int)strlen(text), cursor); // Send null preedit_string if it's empty. // // This makes GTK to emit preedit-end signals so that vte paints its // cursor. The preedit_string from input-method-v1 can't be null. auto preedit_string = strlen(text) > 0 ? text : nullptr; wlr_text_input_v3_send_preedit_string(text_input_v3, preedit_string, begin, end); wlr_text_input_v3_send_done(text_input_v3); } void send_preedit_cursor(int32_t index) override { cursor = index; } void send_delete_surrounding_text(int32_t index, uint32_t length) override { if ((index > 0) || (index + (int32_t)length < 0)) { // Ignore overflows return; } wlr_text_input_v3_send_delete_surrounding_text(text_input_v3, -index, -index + length); wlr_text_input_v3_send_done(text_input_v3); } }; wayfire-0.10.0/plugins/protocols/meson.build0000664000175000017500000000122415053502647020777 0ustar dkondordkondorprotocol_plugins = [ 'foreign-toplevel', 'gtk-shell', 'wayfire-shell', 'xdg-activation', 'shortcuts-inhibit', 'input-method-v1', 'session-lock' ] all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs] all_deps = [wlroots, pixman, wfconfig, wf_protos, json, cairo, pango, pangocairo, plugin_pch_dep] foreach plugin : protocol_plugins shared_module(plugin, plugin + '.cpp', include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) endforeach install_headers(['input-method-v1.hpp'], subdir: 'wayfire/plugins/input-method-v1/') wayfire-0.10.0/plugins/protocols/shortcuts-inhibit.cpp0000664000175000017500000001331315053502647023025 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include "wayfire/seat.hpp" #include "wayfire/view.hpp" #include "wayfire/matcher.hpp" #include "wayfire/bindings-repository.hpp" #include #include #include #include class wayfire_shortcuts_inhibit : public wf::plugin_interface_t { public: void init() override { inhibit_manager = wlr_keyboard_shortcuts_inhibit_v1_create(wf::get_core().display); keyboard_inhibit_new.set_callback([&] (void *data) { auto wlr_inhibitor = (struct wlr_keyboard_shortcuts_inhibitor_v1*)data; if (inhibitors.count(wlr_inhibitor->surface)) { LOGE("Duplicate inhibitors for one surface not supported!"); return; } inhibitors[wlr_inhibitor->surface] = std::make_unique(); auto& inhibitor = inhibitors[wlr_inhibitor->surface]; inhibitor->inhibitor = wlr_inhibitor; inhibitor->on_destroy.set_callback([=] (auto) { deactivate_for_surface(wlr_inhibitor->surface); this->inhibitors.erase(wlr_inhibitor->surface); }); inhibitor->on_destroy.connect(&wlr_inhibitor->events.destroy); check_inhibit(wf::get_core().seat->get_active_node()); }); keyboard_inhibit_new.connect(&inhibit_manager->events.new_inhibitor); wf::get_core().connect(&on_kb_focus_change); wf::get_core().connect(&on_view_mapped); wf::get_core().connect(&on_key_press); } void fini() override {} void check_inhibit(wf::scene::node_ptr focus) { auto focus_view = focus ? wf::node_to_view(focus) : nullptr; wlr_surface *new_focus = focus_view ? focus_view->get_keyboard_focus_surface() : nullptr; if (!inhibitors.count(new_focus)) { new_focus = nullptr; } if (new_focus == last_focus) { return; } deactivate_for_surface(last_focus); if (ignore_views.matches(focus_view)) { return; } activate_for_surface(new_focus); } bool is_unloadable() override { return false; } private: wf::view_matcher_t ignore_views{"shortcuts-inhibit/ignore_views"}; wlr_keyboard_shortcuts_inhibit_manager_v1 *inhibit_manager; wf::wl_listener_wrapper keyboard_inhibit_new; wf::view_matcher_t inhibit_by_default{"shortcuts-inhibit/inhibit_by_default"}; struct inhibitor_t { bool active = false; wlr_keyboard_shortcuts_inhibitor_v1 *inhibitor; wf::wl_listener_wrapper on_destroy; }; std::map> inhibitors; wlr_surface *last_focus = nullptr; void activate_for_surface(wlr_surface *surface) { if (!surface) { return; } auto& inhibitor = inhibitors[surface]; if (!inhibitor->active) { LOGD("Activating inhibitor for surface ", surface); wf::get_core().bindings->set_enabled(false); if (inhibitor->inhibitor) { wlr_keyboard_shortcuts_inhibitor_v1_activate(inhibitor->inhibitor); } inhibitor->active = true; } last_focus = surface; } void deactivate_for_surface(wlr_surface *surface) { if (!surface) { return; } auto& inhibitor = inhibitors[surface]; if (inhibitor->active) { LOGD("Deactivating inhibitor for surface ", surface); wf::get_core().bindings->set_enabled(true); if (inhibitor->inhibitor) { wlr_keyboard_shortcuts_inhibitor_v1_deactivate(inhibitor->inhibitor); } inhibitor->active = false; } last_focus = nullptr; } wf::signal::connection_t on_kb_focus_change = [=] (wf::keyboard_focus_changed_signal *ev) { check_inhibit(ev->new_focus); }; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (inhibit_by_default.matches(ev->view) && ev->view->get_keyboard_focus_surface()) { auto surface = ev->view->get_keyboard_focus_surface(); inhibitors[surface] = std::make_unique(); auto& inhibitor = inhibitors[surface]; inhibitor->inhibitor = nullptr; inhibitor->on_destroy.set_callback([this, surface] (auto) { deactivate_for_surface(surface); this->inhibitors.erase(surface); }); inhibitor->on_destroy.connect(&surface->events.destroy); check_inhibit(wf::get_core().seat->get_active_node()); } }; wf::option_wrapper_t break_grab_key{"shortcuts-inhibit/break_grab"}; wf::signal::connection_t> on_key_press = [=] (wf::input_event_signal *ev) { auto break_key = break_grab_key.value(); if ((ev->event->state == WL_KEYBOARD_KEY_STATE_PRESSED) && (wf::get_core().seat->get_keyboard_modifiers() == break_key.get_modifiers()) && (ev->event->keycode == break_key.get_key())) { LOGD("Force-break active inhibitor"); deactivate_for_surface(last_focus); } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_shortcuts_inhibit); wayfire-0.10.0/plugins/protocols/wayfire-shell.cpp0000664000175000017500000003506115053502647022122 0ustar dkondordkondor/** * Implementation of the wayfire-shell-unstable-v2 protocol */ #include #include #include #include #include #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/render-manager.hpp" #include "wayfire-shell-unstable-v2-protocol.h" #include "wayfire/signal-definitions.hpp" #include "wayfire/plugins/ipc/ipc-activator.hpp" #include "wayfire/util.hpp" /* ----------------------------- wfs_hotspot -------------------------------- */ static void handle_hotspot_destroy(wl_resource *resource); /** * Represents a zwf_shell_hotspot_v2. * Lifetime is managed by the resource. */ class wfs_hotspot { private: wf::geometry_t hotspot_geometry; bool hotspot_triggered = false; wf::wl_idle_call idle_check_input; wf::wl_timer timer; uint32_t timeout_ms; wl_resource *hotspot_resource; wf::signal::connection_t> on_tablet_axis = [=] (wf::post_input_event_signal *ev) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_cursor_position(); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t> on_motion_event = [=] (auto) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_cursor_position(); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t> on_touch_motion = [=] (auto) { idle_check_input.run_once([=] () { auto gcf = wf::get_core().get_touch_position(0); wf::point_t gc{(int)gcf.x, (int)gcf.y}; process_input_motion(gc); }); }; wf::signal::connection_t on_output_removed; void process_input_motion(wf::point_t gc) { if (!(hotspot_geometry & gc)) { if (hotspot_triggered) { zwf_hotspot_v2_send_leave(hotspot_resource); } /* Cursor outside of the hotspot */ hotspot_triggered = false; timer.disconnect(); return; } if (hotspot_triggered) { /* Hotspot was already triggered, wait for the next time the cursor * enters the hotspot area to trigger again */ return; } if (!timer.is_connected()) { timer.set_timeout(timeout_ms, [=] () { hotspot_triggered = true; zwf_hotspot_v2_send_enter(hotspot_resource); }); } } wf::geometry_t calculate_hotspot_geometry(wf::output_t *output, uint32_t edge_mask, uint32_t distance) const { wf::geometry_t slot = output->get_layout_geometry(); if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) { slot.height = distance; } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) { slot.y += slot.height - distance; slot.height = distance; } if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) { slot.width = distance; } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) { slot.x += slot.width - distance; slot.width = distance; } return slot; } wfs_hotspot(const wfs_hotspot &) = delete; wfs_hotspot(wfs_hotspot &&) = delete; wfs_hotspot& operator =(const wfs_hotspot&) = delete; wfs_hotspot& operator =(wfs_hotspot&&) = delete; public: /** * Create a new hotspot. * It is guaranteedd that edge_mask contains at most 2 non-opposing edges. */ wfs_hotspot(wf::output_t *output, uint32_t edge_mask, uint32_t distance, uint32_t timeout, wl_client *client, uint32_t id) { this->timeout_ms = timeout; this->hotspot_geometry = calculate_hotspot_geometry(output, edge_mask, distance); hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 1, id); wl_resource_set_implementation(hotspot_resource, NULL, this, handle_hotspot_destroy); // setup output destroy listener on_output_removed.set_callback([this, output] (wf::output_removed_signal *ev) { if (ev->output == output) { /* Make hotspot inactive by setting the region to empty */ hotspot_geometry = {0, 0, 0, 0}; process_input_motion({0, 0}); } }); wf::get_core().connect(&on_motion_event); wf::get_core().connect(&on_touch_motion); wf::get_core().connect(&on_tablet_axis); wf::get_core().output_layout->connect(&on_output_removed); } ~wfs_hotspot() = default; }; static void handle_hotspot_destroy(wl_resource *resource) { auto *hotspot = (wfs_hotspot*)wl_resource_get_user_data(resource); delete hotspot; wl_resource_set_user_data(resource, nullptr); } /* ------------------------------ wfs_output -------------------------------- */ static void handle_output_destroy(wl_resource *resource); static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource); static void handle_zwf_output_inhibit_output_done(wl_client*, wl_resource *resource); static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id); static struct zwf_output_v2_interface zwf_output_impl = { .inhibit_output = handle_zwf_output_inhibit_output, .inhibit_output_done = handle_zwf_output_inhibit_output_done, .create_hotspot = handle_zwf_output_create_hotspot, }; /** * A signal emitted on the wayfire output where the menu should be toggled. */ struct wayfire_shell_toggle_menu_signal {}; /** * Represents a zwf_output_v2. * Lifetime is managed by the wl_resource */ class wfs_output { uint32_t num_inhibits = 0; wl_resource *shell_resource; wl_resource *resource; wf::output_t *output; void disconnect_from_output() { wf::get_core().output_layout->disconnect(&on_output_removed); on_fullscreen_layer_focused.disconnect(); } wf::signal::connection_t on_output_removed = [=] (wf::output_removed_signal *ev) { if (ev->output == this->output) { disconnect_from_output(); this->output = nullptr; } }; wf::signal::connection_t on_fullscreen_layer_focused = [=] (wf::fullscreen_layer_focused_signal *ev) { if (ev->has_promoted) { zwf_output_v2_send_enter_fullscreen(resource); } else { zwf_output_v2_send_leave_fullscreen(resource); } }; wf::signal::connection_t on_toggle_menu = [=] (auto) { if (wl_resource_get_version(shell_resource) < ZWF_OUTPUT_V2_TOGGLE_MENU_SINCE_VERSION) { return; } zwf_output_v2_send_toggle_menu(resource); }; public: wfs_output(wf::output_t *output, wl_resource *shell_resource, wl_client *client, int id) { this->output = output; this->shell_resource = shell_resource; resource = wl_resource_create(client, &zwf_output_v2_interface, std::min(wl_resource_get_version(shell_resource), 2), id); wl_resource_set_implementation(resource, &zwf_output_impl, this, handle_output_destroy); output->connect(&on_fullscreen_layer_focused); output->connect(&on_toggle_menu); wf::get_core().output_layout->connect(&on_output_removed); } ~wfs_output() { if (!this->output) { /* The wayfire output was destroyed. Gracefully do nothing */ return; } disconnect_from_output(); /* Remove any remaining inhibits, otherwise the compositor will never * be "unlocked" */ while (num_inhibits > 0) { this->output->render->add_inhibit(false); --num_inhibits; } } wfs_output(const wfs_output &) = delete; wfs_output(wfs_output &&) = delete; wfs_output& operator =(const wfs_output&) = delete; wfs_output& operator =(wfs_output&&) = delete; void inhibit_output() { ++this->num_inhibits; if (this->output) { this->output->render->add_inhibit(true); } } void inhibit_output_done() { if (this->num_inhibits == 0) { wl_resource_post_no_memory(resource); return; } --this->num_inhibits; if (this->output) { this->output->render->add_inhibit(false); } } void create_hotspot(uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id) { if (!this->output) { // It can happen that the client requests a hotspot immediately after an output is destroyed - // this is an inherent race condition because the compositor and client are not in sync. // // In this case, we create a dummy hotspot resource to avoid Wayland protocol errors. auto resource = wl_resource_create( wl_resource_get_client(this->resource), &zwf_hotspot_v2_interface, 1, id); wl_resource_set_implementation(resource, NULL, NULL, NULL); return; } // will be auto-deleted when the resource is destroyed by the client new wfs_hotspot(this->output, hotspot, threshold, timeout, wl_resource_get_client(this->resource), id); } }; static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->inhibit_output(); } static void handle_zwf_output_inhibit_output_done( wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->inhibit_output_done(); } static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id) { auto output = (wfs_output*)wl_resource_get_user_data(resource); output->create_hotspot(hotspot, threshold, timeout, id); } static void handle_output_destroy(wl_resource *resource) { auto *output = (wfs_output*)wl_resource_get_user_data(resource); delete output; wl_resource_set_user_data(resource, nullptr); } /* ------------------------------ wfs_surface ------------------------------- */ static void handle_surface_destroy(wl_resource *resource); static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource); static struct zwf_surface_v2_interface zwf_surface_impl = { .interactive_move = handle_zwf_surface_interactive_move, }; /** * Represents a zwf_surface_v2. * Lifetime is managed by the wl_resource */ class wfs_surface { wl_resource *resource; wayfire_view view; wf::signal::connection_t on_unmap = [=] (auto) { view = nullptr; }; public: wfs_surface(wayfire_view view, wl_client *client, int id) { this->view = view; resource = wl_resource_create(client, &zwf_surface_v2_interface, 1, id); wl_resource_set_implementation(resource, &zwf_surface_impl, this, handle_surface_destroy); view->connect(&on_unmap); } ~wfs_surface() = default; void interactive_move() { LOGE("Interactive move no longer supported!"); } }; static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); surface->interactive_move(); } static void handle_surface_destroy(wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); delete surface; wl_resource_set_user_data(resource, nullptr); } static void zwf_shell_manager_get_wf_output(wl_client *client, wl_resource *resource, wl_resource *output, uint32_t id) { auto wlr_out = (wlr_output*)wl_resource_get_user_data(output); auto wo = wf::get_core().output_layout->find_output(wlr_out); if (wo) { // will be deleted when the resource is destroyed new wfs_output(wo, resource, client, id); } } static void zwf_shell_manager_get_wf_surface(wl_client *client, wl_resource *resource, wl_resource *surface, uint32_t id) { auto view = wf::wl_surface_to_wayfire_view(surface); if (view) { /* Will be freed when the resource is destroyed */ new wfs_surface(view, client, id); } } const struct zwf_shell_manager_v2_interface zwf_shell_manager_v2_impl = { zwf_shell_manager_get_wf_output, zwf_shell_manager_get_wf_surface, }; void bind_zwf_shell_manager(wl_client *client, void *data, uint32_t version, uint32_t id) { auto resource = wl_resource_create(client, &zwf_shell_manager_v2_interface, version, id); wl_resource_set_implementation(resource, &zwf_shell_manager_v2_impl, NULL, NULL); } struct wayfire_shell { wl_global *shell_manager; }; wayfire_shell *wayfire_shell_create(wl_display *display) { wayfire_shell *ws = new wayfire_shell; ws->shell_manager = wl_global_create(display, &zwf_shell_manager_v2_interface, 2, NULL, bind_zwf_shell_manager); if (ws->shell_manager == NULL) { LOGE("Failed to create wayfire_shell interface"); delete ws; return NULL; } return ws; } class wayfire_shell_protocol_impl : public wf::plugin_interface_t { wf::ipc_activator_t toggle_menu{"wayfire-shell/toggle_menu"}; wf::ipc_activator_t::handler_t toggle_menu_cb = [=] (wf::output_t *toggle_menu_output, wayfire_view) { wayfire_shell_toggle_menu_signal toggle_menu; toggle_menu_output->emit(&toggle_menu); return true; }; public: void init() override { wf_shell = wayfire_shell_create(wf::get_core().display); toggle_menu.set_handler(toggle_menu_cb); } void fini() override { wl_global_destroy(wf_shell->shell_manager); delete wf_shell; } private: wayfire_shell *wf_shell; }; DECLARE_WAYFIRE_PLUGIN(wayfire_shell_protocol_impl); wayfire-0.10.0/plugins/protocols/input-method-v1.hpp0000664000175000017500000000046515053502647022315 0ustar dkondordkondor#pragma once #include namespace wf { /** * on: core * Emitted when a text input (v1 or v3) is activated. */ struct input_method_v1_activate_signal {}; /** * on: core * Emitted when a text input (v1 or v3) is deactivated. */ struct input_method_v1_deactivate_signal {}; } wayfire-0.10.0/plugins/protocols/xdg-activation.cpp0000664000175000017500000001052415053502647022265 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include #include "config.h" class wayfire_xdg_activation_protocol_impl : public wf::plugin_interface_t { public: wayfire_xdg_activation_protocol_impl() { set_callbacks(); } void init() override { xdg_activation = wlr_xdg_activation_v1_create(wf::get_core().display); if (timeout >= 0) { xdg_activation->token_timeout_msec = 1000 * timeout; } xdg_activation_request_activate.connect(&xdg_activation->events.request_activate); xdg_activation_new_token.connect(&xdg_activation->events.new_token); } void fini() override { xdg_activation_request_activate.disconnect(); xdg_activation_new_token.disconnect(); xdg_activation_token_destroy.disconnect(); last_token = nullptr; } bool is_unloadable() override { return false; } private: void set_callbacks() { xdg_activation_request_activate.set_callback([this] (void *data) { auto event = static_cast(data); if (!event->token->seat) { LOGI("Denying focus request, token was rejected at creation"); return; } if (only_last_token && (event->token != last_token)) { LOGI("Denying focus request, token is expired"); return; } last_token = nullptr; // avoid reusing the same token wayfire_view view = wf::wl_surface_to_wayfire_view(event->surface->resource); if (!view) { LOGE("Could not get view"); return; } auto toplevel = wf::toplevel_cast(view); if (!toplevel) { LOGE("Could not get toplevel view"); return; } LOGD("Activating view"); wf::get_core().default_wm->focus_request(toplevel); }); xdg_activation_new_token.set_callback([this] (void *data) { auto token = static_cast(data); if (!token->seat) { // note: for a valid seat, wlroots already checks that the serial is valid LOGI("Not registering activation token, seat was not supplied"); return; } if (check_surface && !token->surface) { // note: for a valid surface, wlroots already checks that this is the active surface LOGI("Not registering activation token, surface was not supplied"); token->seat = nullptr; // this will ensure that this token will be rejected later return; } // update our token and connect its destroy signal last_token = token; xdg_activation_token_destroy.disconnect(); xdg_activation_token_destroy.connect(&token->events.destroy); }); xdg_activation_token_destroy.set_callback([this] (void *data) { last_token = nullptr; xdg_activation_token_destroy.disconnect(); }); timeout.set_callback(timeout_changed); } wf::config::option_base_t::updated_callback_t timeout_changed = [this] () { if (xdg_activation && (timeout >= 0)) { xdg_activation->token_timeout_msec = 1000 * timeout; } }; struct wlr_xdg_activation_v1 *xdg_activation; wf::wl_listener_wrapper xdg_activation_request_activate; wf::wl_listener_wrapper xdg_activation_new_token; wf::wl_listener_wrapper xdg_activation_token_destroy; struct wlr_xdg_activation_token_v1 *last_token = nullptr; wf::option_wrapper_t check_surface{"xdg-activation/check_surface"}; wf::option_wrapper_t only_last_token{"xdg-activation/only_last_request"}; wf::option_wrapper_t timeout{"xdg-activation/timeout"}; }; DECLARE_WAYFIRE_PLUGIN(wayfire_xdg_activation_protocol_impl); wayfire-0.10.0/plugins/protocols/input-method-v1.cpp0000664000175000017500000010174615053502647022314 0ustar dkondordkondor#include #include #include #include #include #include #include "input-method-unstable-v1-protocol.h" #include "wayfire/option-wrapper.hpp" #include "wayfire/nonstd/wlroots-full.hpp" #include "wayfire/nonstd/wlroots.hpp" #include "wayfire/debug.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/seat.hpp" #include "text-input-v1-v3.hpp" #include "input-method-v1.hpp" class wayfire_input_method_v1_context { public: wayfire_input_method_v1_context(wayfire_im_text_input_base_t *text_input, wl_resource *current_im, const struct zwp_input_method_context_v1_interface *context_impl) { this->text_input = text_input; this->current_im = current_im; context = wl_resource_create(wl_resource_get_client(current_im), &zwp_input_method_context_v1_interface, 1, 0); wl_resource_set_implementation(context, context_impl, this, handle_ctx_destruct_final); zwp_input_method_v1_send_activate(current_im, context); } static void handle_ctx_destruct_final(wl_resource *resource) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->deactivate(true); } } void handle_text_input_v3_commit() { auto ti_v3 = dynamic_cast(text_input); wf::dassert(ti_v3, "handle_text_input_v3_commit called without text_input_v3"); auto text_input_v3 = ti_v3->text_input_v3; zwp_input_method_context_v1_send_content_type(context, text_input_v3->current.content_type.hint, text_input_v3->current.content_type.purpose); zwp_input_method_context_v1_send_surrounding_text(context, text_input_v3->current.surrounding.text ?: "", text_input_v3->current.surrounding.cursor, text_input_v3->current.surrounding.anchor); zwp_input_method_context_v1_send_commit_state(context, ctx_serial++); } void deactivate(bool im_killed = false) { wl_resource_set_user_data(context, NULL); auto& seat = wf::get_core().seat; if (im_killed) { // Remove keys which core still thinks are pressed down physically: they will be sent as release // events to the client at a later point. for (auto& hw_pressed : seat->get_pressed_keys()) { if (currently_pressed_keys_client.count(hw_pressed)) { currently_pressed_keys_client.erase( currently_pressed_keys_client.find(hw_pressed)); } } // For the other keys (where we potentially swallowed the release event, but the IM did not // respond yet with a release), release those keys. for (auto& key : currently_pressed_keys_client) { wlr_seat_keyboard_notify_key(seat->seat, wf::get_current_time(), key, WL_KEYBOARD_KEY_STATE_RELEASED); } currently_pressed_keys_client.clear(); if (active_grab_keyboard) { wl_resource_set_user_data(active_grab_keyboard, NULL); } this->text_input = NULL; return; } this->text_input = NULL; zwp_input_method_v1_send_deactivate(current_im, context); if (active_grab_keyboard) { for (auto& key : currently_pressed_keys_im) { wl_keyboard_send_key(active_grab_keyboard, vkbd_serial++, wf::get_current_time(), key, WL_KEYBOARD_KEY_STATE_RELEASED); } currently_pressed_keys_im.clear(); wl_resource_destroy(active_grab_keyboard); } } void handle_im_key(uint32_t time, uint32_t key, uint32_t state) { auto& seat = wf::get_core().seat; wlr_seat_keyboard_notify_key(seat->seat, time, key, state); update_pressed_keys(currently_pressed_keys_client, key, state); } void handle_im_modifiers(uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { wlr_keyboard_modifiers mods{ .depressed = mods_depressed, .latched = mods_latched, .locked = mods_locked, .group = group }; auto& seat = wf::get_core().seat; wlr_seat_keyboard_notify_modifiers(seat->seat, &mods); } void grab_keyboard(wl_client *client, uint32_t id) { this->active_grab_keyboard = wl_resource_create(client, &wl_keyboard_interface, 1, id); wl_resource_set_implementation(active_grab_keyboard, NULL, this, unbind_keyboard); wf::get_core().connect(&on_keyboard_key); wf::get_core().connect(&on_keyboard_modifiers); } static void unbind_keyboard(wl_resource *keyboard) { auto self = static_cast(wl_resource_get_user_data(keyboard)); if (!self) { return; } self->active_grab_keyboard = NULL; self->last_sent_keymap_keyboard = NULL; self->on_keyboard_key.disconnect(); self->on_keyboard_modifiers.disconnect(); self->currently_pressed_keys_im.clear(); } void update_pressed_keys(std::multiset& set, uint32_t key, uint32_t state) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { set.insert(key); } else if (set.count(key)) { set.erase(set.find(key)); } } wf::signal::connection_t> on_keyboard_key = [=] (wf::pre_client_input_event_signal *ev) { if (active_grab_keyboard && !ev->carried_out) { check_send_keymap(wlr_keyboard_from_input_device(ev->device)); ev->carried_out = true; wl_keyboard_send_key(active_grab_keyboard, vkbd_serial++, ev->event->time_msec, ev->event->keycode, ev->event->state); // Keep track of pressed keys so that we can release all of them at the end. // Otherwise the IM gets stuck thinking that some modifiers are pressed, etc. update_pressed_keys(currently_pressed_keys_im, ev->event->keycode, ev->event->state); } }; wf::signal::connection_t> on_keyboard_modifiers = [=] (wf::input_event_signal *ev) { if (active_grab_keyboard) { auto kbd = wlr_keyboard_from_input_device(ev->device); check_send_keymap(kbd); wl_keyboard_send_modifiers(active_grab_keyboard, vkbd_serial++, kbd->modifiers.depressed, kbd->modifiers.latched, kbd->modifiers.locked, kbd->modifiers.group); } }; void check_send_keymap(wlr_keyboard *current_kbd) { if (current_kbd == last_sent_keymap_keyboard) { return; } last_sent_keymap_keyboard = current_kbd; if (current_kbd->keymap != NULL) { wl_keyboard_send_keymap(active_grab_keyboard, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, current_kbd->keymap_fd, current_kbd->keymap_size); } else { int fd = open("/dev/null", O_RDONLY | O_CLOEXEC); wl_keyboard_send_keymap(active_grab_keyboard, WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP, fd, 0); close(fd); } // Send new modifiers wl_keyboard_send_modifiers(active_grab_keyboard, vkbd_serial++, current_kbd->modifiers.depressed, current_kbd->modifiers.latched, current_kbd->modifiers.locked, current_kbd->modifiers.group); } std::multiset currently_pressed_keys_im; std::multiset currently_pressed_keys_client; wlr_keyboard *last_sent_keymap_keyboard = NULL; wl_resource *active_grab_keyboard = NULL; uint32_t ctx_serial = 0; uint32_t vkbd_serial = 0; wl_resource *current_im = NULL; wl_resource *context = NULL; // NULL if inactive wayfire_im_text_input_base_t *text_input = NULL; }; void handle_im_context_destroy(wl_client *client, wl_resource *resource) { wl_resource_destroy(resource); } void handle_im_context_commit_string(wl_client *client, wl_resource *resource, uint32_t serial, const char *text) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_commit_string(serial, text); } } void handle_im_context_preedit_string(wl_client *client, wl_resource *resource, uint32_t serial, const char *text, const char *commit) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_preedit_string(serial, text, commit); } } void handle_im_context_preedit_styling(wl_client *client, wl_resource *resource, uint32_t index, uint32_t length, uint32_t style) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_preedit_styling(index, length, style); } } void handle_im_context_preedit_cursor(wl_client *client, wl_resource *resource, int32_t index) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_preedit_cursor(index); } } void handle_im_context_delete_surrounding_text(wl_client *client, wl_resource *resource, int32_t index, uint32_t length) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_delete_surrounding_text(index, length); } } void handle_im_context_cursor_position(wl_client *client, wl_resource *resource, int32_t index, int32_t anchor) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_cursor_position(index, anchor); } } void handle_im_context_modifiers_map(wl_client *client, wl_resource *resource, struct wl_array *map) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_modifiers_map(map); } } void handle_im_context_keysym(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_keysym(serial, time, sym, state, modifiers); } } void handle_im_context_grab_keyboard(wl_client *client, wl_resource *resource, uint32_t keyboard_id) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->grab_keyboard(client, keyboard_id); } else { // Create a dummy resource to avoid Wayland protocol errors. // But, we have already moved on from this context, so we won't send any events. auto resource = wl_resource_create(client, &wl_keyboard_interface, 1, keyboard_id); wl_resource_set_implementation(resource, NULL, NULL, NULL); } } void handle_im_context_key(wl_client*, wl_resource *resource, uint32_t, uint32_t time, uint32_t key, uint32_t state) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->handle_im_key(time, key, state); } } void handle_im_context_modifiers(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context) { context->handle_im_modifiers(serial, mods_depressed, mods_latched, mods_locked, group); } } void handle_im_context_language(wl_client *client, wl_resource *resource, uint32_t serial, const char *language) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_language(serial, language); } } void handle_im_context_text_direction(wl_client *client, wl_resource *resource, uint32_t serial, uint32_t direction) { auto context = static_cast(wl_resource_get_user_data(resource)); if (context && context->text_input) { context->text_input->send_text_direction(serial, direction); } } static const struct zwp_input_method_context_v1_interface context_implementation = { .destroy = handle_im_context_destroy, .commit_string = handle_im_context_commit_string, .preedit_string = handle_im_context_preedit_string, .preedit_styling = handle_im_context_preedit_styling, .preedit_cursor = handle_im_context_preedit_cursor, .delete_surrounding_text = handle_im_context_delete_surrounding_text, .cursor_position = handle_im_context_cursor_position, .modifiers_map = handle_im_context_modifiers_map, .keysym = handle_im_context_keysym, .grab_keyboard = handle_im_context_grab_keyboard, .key = handle_im_context_key, .modifiers = handle_im_context_modifiers, .language = handle_im_context_language, .text_direction = handle_im_context_text_direction }; void handle_input_panel_get_input_panel_surface(wl_client *client, wl_resource *resource, uint32_t id, struct wl_resource *surface); static const struct zwp_input_panel_v1_interface panel_implementation = { .get_input_panel_surface = handle_input_panel_get_input_panel_surface }; void handle_input_panel_surface_set_toplevel(wl_client *client, wl_resource *resource, struct wl_resource *output, uint32_t position); void handle_input_panel_surface_set_overlay_panel(wl_client *client, wl_resource *resource); static const struct zwp_input_panel_surface_v1_interface panel_surface_implementation = { .set_toplevel = handle_input_panel_surface_set_toplevel, .set_overlay_panel = handle_input_panel_surface_set_overlay_panel }; class wayfire_input_method_v1_panel_surface { public: wayfire_input_method_v1_panel_surface(wl_client *client, uint32_t id, wf::text_input_v3_im_relay_interface_t *relay, wlr_surface *surface) { LOGC(IM, "Input method panel surface created."); resource = wl_resource_create(client, &zwp_input_panel_surface_v1_interface, 1, id); wl_resource_set_implementation(resource, &panel_surface_implementation, this, handle_destroy); this->surface = surface; this->relay = relay; on_surface_commit.set_callback([=] (void*) { if (wlr_surface_has_buffer(surface) && !surface->mapped) { wlr_surface_map(surface); } else if (!wlr_surface_has_buffer(surface) && surface->mapped) { wlr_surface_unmap(surface); } }); on_surface_commit.connect(&surface->events.commit); // Initial commit, maybe already ok? on_surface_commit.emit(NULL); on_surface_destroy.set_callback([=] (void*) { if (surface->mapped) { wlr_surface_unmap(surface); } on_surface_destroy.disconnect(); on_surface_commit.disconnect(); }); on_surface_destroy.connect(&surface->events.destroy); } void set_overlay_panel() { LOGC(IM, "Input method panel surface set to overlay."); popup = wf::text_input_v3_popup::create(relay, surface); if (surface->mapped) { popup->map(); } } ~wayfire_input_method_v1_panel_surface() { if (popup && popup->is_mapped()) { popup->unmap(); } } private: wl_resource *resource; wlr_surface *surface; wf::text_input_v3_im_relay_interface_t *relay; std::shared_ptr popup = nullptr; wf::wl_listener_wrapper on_surface_commit; wf::wl_listener_wrapper on_surface_destroy; static void handle_destroy(wl_resource *destroy) { auto panel = (wayfire_input_method_v1_panel_surface*)wl_resource_get_user_data(destroy); delete panel; } }; void handle_input_panel_get_input_panel_surface(wl_client *client, wl_resource *resource, uint32_t id, struct wl_resource *surface) { new wayfire_input_method_v1_panel_surface(client, id, (wf::text_input_v3_im_relay_interface_t*)wl_resource_get_user_data(resource), (wlr_surface*)wl_resource_get_user_data(surface)); } void handle_input_panel_surface_set_toplevel(wl_client *client, wl_resource *resource, wl_resource *output, uint32_t position) { LOGE("The set toplevel request is not supported by the IM-v1 implementation!"); } void handle_input_panel_surface_set_overlay_panel(wl_client *client, wl_resource *resource) { auto panel = (wayfire_input_method_v1_panel_surface*)wl_resource_get_user_data(resource); if (panel) { panel->set_overlay_panel(); } } class wayfire_input_method_v1 : public wf::plugin_interface_t, public wf::text_input_v3_im_relay_interface_t { public: void init() override { if (enable_input_method_v2) { LOGE("Enabling both input-method-v2 and input-method-v1 is a bad idea!"); return; } input_method_manager = wl_global_create(wf::get_core().display, &zwp_input_method_v1_interface, 1, this, handle_bind_im_v1); input_panel_manager = wl_global_create(wf::get_core().display, &zwp_input_panel_v1_interface, 1, this, handle_bind_im_panel_v1); if (enable_text_input_v1) { text_input_v1_manager = wl_global_create(wf::get_core().display, &zwp_text_input_manager_v1_interface, 1, this, handle_bind_text_input_v1); } if (enable_text_input_v3) { wf::get_core().protocols.text_input = wlr_text_input_manager_v3_create(wf::get_core().display); on_text_input_v3_created.connect(&wf::get_core().protocols.text_input->events.text_input); on_text_input_v3_created.set_callback([&] (void *data) { handle_text_input_v3_created(static_cast(data)); }); } wf::get_core().connect(&on_keyboard_focus_changed); } void fini() override { if (input_method_manager) { reset_current_im_context(); wl_global_destroy(input_method_manager); if (current_im) { wl_resource_set_user_data(current_im, NULL); } } if (text_input_v1_manager) { wl_global_destroy(text_input_v1_manager); for (auto& [input, _] : im_text_inputs_v1) { wl_resource_set_user_data(input, NULL); } } } bool is_unloadable() override { return false; } wf::signal::connection_t on_keyboard_focus_changed = [=] (wf::keyboard_focus_changed_signal *ev) { auto view = wf::node_to_view(ev->new_focus); auto surf = view ? view->get_wlr_surface() : nullptr; if (last_focus_surface != surf) { reset_current_im_context(); last_focus_surface = surf; for_each_text_input([=] (wayfire_im_text_input_base_t *text_input) { text_input->set_focus_surface(last_focus_surface); }); } }; // Handlers for text-input-v1 private: wl_global *text_input_v1_manager = nullptr; static void handle_bind_text_input_v1(wl_client *client, void *data, uint32_t version, uint32_t id) { ((wayfire_input_method_v1*)data)->bind_text_input_v1_manager(client, id); } void bind_text_input_v1_manager(wl_client *client, uint32_t id) { wl_resource *resource = wl_resource_create(client, &zwp_text_input_manager_v1_interface, 1, id); static const struct zwp_text_input_manager_v1_interface text_input_manager_implementation = { handle_create_text_input_v1 }; wl_resource_set_implementation(resource, &text_input_manager_implementation, this, NULL); } static void handle_create_text_input_v1(wl_client *client, wl_resource *resource, uint32_t id) { auto self = (wayfire_input_method_v1*)wl_resource_get_user_data(resource); auto ti_resource = wl_resource_create(client, &zwp_text_input_v1_interface, 1, id); // Define the struct with function pointers static const struct zwp_text_input_v1_interface text_input_v1_impl = { .activate = handle_text_input_v1_activate, .deactivate = handle_text_input_v1_deactivate, .show_input_panel = handle_text_input_v1_show_input_panel, .hide_input_panel = handle_text_input_v1_hide_input_panel, .reset = handle_text_input_v1_reset, .set_surrounding_text = handle_text_input_v1_set_surrounding_text, .set_content_type = handle_text_input_v1_set_content_type, .set_cursor_rectangle = handle_text_input_v1_set_cursor_rectangle, .set_preferred_language = handle_text_input_v1_set_preferred_language, .commit_state = handle_text_input_v1_commit_state, .invoke_action = handle_text_input_v1_invoke_action }; wl_resource_set_implementation(ti_resource, &text_input_v1_impl, self, handle_text_input_v1_destroy); self->im_text_inputs_v1[ti_resource] = std::make_unique(ti_resource); } static void handle_text_input_v1_destroy(wl_resource *resource) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self) { self->im_handle_text_input_disable(self->im_text_inputs_v1[resource].get()); self->im_text_inputs_v1.erase(resource); } } static void handle_text_input_v1_activate(wl_client *client, wl_resource *resource, wl_resource *seat, wl_resource *surface) { auto self = static_cast(wl_resource_get_user_data(resource)); auto *ti = self->im_text_inputs_v1[resource].get(); if (!ti->can_focus() || (ti->current_focus->resource != surface)) { LOGC(IM, "text-input-v1: ignore activate request for wrong focus surface!"); // Only for focused surfaces! return; } if (self->current_im_context) { self->im_handle_text_input_disable(self->current_im_context->text_input); } self->im_handle_text_input_enable(ti); } static void handle_text_input_v1_deactivate(wl_client *client, wl_resource *resource, wl_resource *seat) { auto self = static_cast(wl_resource_get_user_data(resource)); auto *ti = self->im_text_inputs_v1[resource].get(); self->im_handle_text_input_disable(ti); } static void handle_text_input_v1_show_input_panel(wl_client *client, wl_resource *resource) {} static void handle_text_input_v1_hide_input_panel(wl_client *client, wl_resource *resource) {} static void handle_text_input_v1_reset(wl_client *client, wl_resource *resource) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_reset(self->current_im_context->context); } } static void handle_text_input_v1_set_surrounding_text(wl_client *client, wl_resource *resource, const char *text, uint32_t cursor, uint32_t anchor) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_surrounding_text(self->current_im_context->context, text, cursor, anchor); } } static void handle_text_input_v1_set_content_type(wl_client *client, wl_resource *resource, uint32_t hint, uint32_t purpose) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_content_type(self->current_im_context->context, hint, purpose); } } static void handle_text_input_v1_set_cursor_rectangle(wl_client *client, wl_resource *resource, int32_t x, int32_t y, int32_t width, int32_t height) { // ignore, we do not support cursor rectangle or input popups for text-input-v1 yet } static void handle_text_input_v1_set_preferred_language(wl_client *client, wl_resource *resource, const char *language) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_preferred_language(self->current_im_context->context, language); } } static void handle_text_input_v1_commit_state(wl_client *client, wl_resource *resource, uint32_t serial) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_commit_state(self->current_im_context->context, serial); } } static void handle_text_input_v1_invoke_action(wl_client *client, wl_resource *resource, uint32_t button, uint32_t index) { auto self = static_cast(wl_resource_get_user_data(resource)); if (self->current_im_context) { zwp_input_method_context_v1_send_invoke_action(self->current_im_context->context, button, index); } } // Handlers for text-input-v3 private: void handle_text_input_v3_created(wlr_text_input_v3 *input) { im_text_inputs_v3[input] = std::make_unique(input); im_text_inputs_v3[input]->on_enable.set_callback([=] (void *data) { im_handle_text_input_enable(im_text_inputs_v3[input].get()); }); im_text_inputs_v3[input]->on_disable.set_callback([=] (void *data) { im_handle_text_input_disable(im_text_inputs_v3[input].get()); }); im_text_inputs_v3[input]->on_destroy.set_callback([=] (void *data) { handle_text_input_v3_destroyed(input); }); im_text_inputs_v3[input]->on_commit.set_callback([=] (void *data) { handle_text_input_v3_commit(input); }); im_text_inputs_v3[input]->set_focus_surface(last_focus_surface); } void handle_text_input_v3_destroyed(wlr_text_input_v3 *input) { im_handle_text_input_disable(im_text_inputs_v3[input].get()); im_text_inputs_v3.erase(input); } void handle_text_input_v3_commit(wlr_text_input_v3 *input) { if (current_im_context && (current_im_context->text_input == im_text_inputs_v3[input].get())) { current_im_context->handle_text_input_v3_commit(); wf::text_input_commit_signal data; data.cursor_rect = input->current.cursor_rectangle; emit(&data); } } wlr_text_input_v3 *find_focused_text_input_v3() override { if (!current_im_context) { return nullptr; } auto as_v3 = dynamic_cast(current_im_context->text_input); return as_v3 ? as_v3->text_input_v3 : nullptr; } // Implementation of input-method-v1 private: void bind_input_method_manager(wl_client *client, uint32_t id) { wl_resource *resource = wl_resource_create(client, &zwp_input_method_v1_interface, 1, id); if (current_im) { LOGE("Trying to bind to input-method-v1 while another input method is active is not supported!"); wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "Input method already bound"); return; } LOGC(IM, "Input method bound"); wl_resource_set_implementation(resource, NULL, this, handle_destroy_im); current_im = resource; for (auto& [_, im] : im_text_inputs_v3) { if (im->is_enabled()) { im_handle_text_input_enable(im.get()); } } } static void handle_bind_im_v1(wl_client *client, void *data, uint32_t version, uint32_t id) { ((wayfire_input_method_v1*)data)->bind_input_method_manager(client, id); } static void handle_destroy_im(wl_resource *resource) { LOGC(IM, "Input method unbound"); auto data = wl_resource_get_user_data(resource); if (data) { ((wayfire_input_method_v1*)data)->reset_current_im_context(true); ((wayfire_input_method_v1*)data)->current_im = nullptr; } } void im_handle_text_input_enable(wayfire_im_text_input_base_t *text_input) { wf::input_method_v1_activate_signal data; wf::get_core().emit(&data); if (!current_im) { LOGC(IM, "No IM currently connected: ignoring enable request."); return; } if (!last_focus_surface || (text_input->current_focus != last_focus_surface)) { LOGC(IM, "Ignoring enable request for text input ", text_input->dbg_handle, ": stale request"); return; } if (current_im_context) { LOGC(IM, "Text input activated while old context is still around?"); return; } LOGC(IM, "Enabling IM context for ", text_input->dbg_handle); current_im_context = std::make_unique( text_input, current_im, &context_implementation); } void im_handle_text_input_disable(wayfire_im_text_input_base_t *input) { wf::input_method_v1_deactivate_signal data; wf::get_core().emit(&data); if (!current_im_context || (current_im_context->text_input != input)) { return; } reset_current_im_context(); } void reset_current_im_context(bool im_killed = false) { if (!current_im_context) { return; } LOGC(IM, "Disabling IM context for ", current_im_context->text_input->dbg_handle); current_im_context->deactivate(im_killed); current_im_context.reset(); } // input-method-panel impl private: void bind_input_method_panel(wl_client *client, uint32_t id) { LOGC(IM, "Input method panel interface bound"); wl_resource *resource = wl_resource_create(client, &zwp_input_panel_v1_interface, 1, id); wl_resource_set_implementation(resource, &panel_implementation, dynamic_cast(this), handle_destroy_im_panel); } static void handle_bind_im_panel_v1(wl_client *client, void *data, uint32_t version, uint32_t id) { ((wayfire_input_method_v1*)data)->bind_input_method_panel(client, id); } static void handle_destroy_im_panel(wl_resource *resource) { LOGC(IM, "Input method panel interface unbound"); } private: wf::option_wrapper_t enable_input_method_v2{"workarounds/enable_input_method_v2"}; wf::option_wrapper_t enable_text_input_v1{"input-method-v1/enable_text_input_v1"}; wf::option_wrapper_t enable_text_input_v3{"input-method-v1/enable_text_input_v3"}; wl_global *input_method_manager = NULL; wl_global *input_panel_manager = NULL; wl_resource *current_im = NULL; wf::wl_listener_wrapper on_text_input_v3_created; wlr_surface *last_focus_surface = NULL; std::unique_ptr current_im_context = NULL; std::map> im_text_inputs_v1; std::map> im_text_inputs_v3; void for_each_text_input(std::function func) { for (auto& [_, im] : im_text_inputs_v1) { func(im.get()); } for (auto& [_, im] : im_text_inputs_v3) { func(im.get()); } } }; DECLARE_WAYFIRE_PLUGIN(wayfire_input_method_v1); wayfire-0.10.0/plugins/wm-actions/0000775000175000017500000000000015053502647016673 5ustar dkondordkondorwayfire-0.10.0/plugins/wm-actions/wm-actions-signals.hpp0000664000175000017500000000125615053502647023127 0ustar dkondordkondor#pragma once #include namespace wf { /** * on: output * when: Emitted whenever some entity requests that the view's above state * is supposed to change. * arguments: above: whether or not to set above state */ struct wm_actions_set_above_state_signal { wayfire_view view; /** The requested above state. If this is true, the view will be * added to the always-above layer. If it is false, the view will * be placed in the 'normal' workspace layer. */ bool above; }; /** * on: output * when: Emitted whenever a views above layer has been changed. */ struct wm_actions_above_changed_signal { wayfire_view view; }; } wayfire-0.10.0/plugins/wm-actions/meson.build0000664000175000017500000000077215053502647021043 0ustar dkondordkondorwm_actions = shared_module('wm-actions', ['wm-actions.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers( [ 'wm-actions-signals.hpp' ], subdir: 'wayfire/plugins' ) wayfire-0.10.0/plugins/wm-actions/wm-actions.cpp0000664000175000017500000003752315053502647021472 0ustar dkondordkondor#include #include #include #include #include #include "plugins/common/wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-activator.hpp" #include "wayfire/core.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/window-manager.hpp" #include "wayfire/seat.hpp" #include "wayfire/nonstd/reverse.hpp" #include "wm-actions-signals.hpp" #include "wayfire/nonstd/reverse.hpp" class always_on_top_root_node_t : public wf::scene::output_node_t { public: using output_node_t::output_node_t; std::string stringify() const override { return "always-on-top for output " + get_output()->to_string() + " " + stringify_flags(); } }; class wayfire_wm_actions_output_t : public wf::per_output_plugin_instance_t { wf::scene::floating_inner_ptr always_above; bool showdesktop_active = false; wf::option_wrapper_t minimize{ "wm-actions/minimize"}; wf::option_wrapper_t toggle_maximize{ "wm-actions/toggle_maximize"}; wf::option_wrapper_t toggle_above{ "wm-actions/toggle_always_on_top"}; wf::option_wrapper_t toggle_fullscreen{ "wm-actions/toggle_fullscreen"}; wf::option_wrapper_t toggle_sticky{ "wm-actions/toggle_sticky"}; wf::option_wrapper_t send_to_back{ "wm-actions/send_to_back"}; wf::plugin_activation_data_t grab_interface = { .name = "wm-actions", .capabilities = 0, }; public: bool set_keep_above_state(wayfire_view view, bool above) { if (!view || !output->can_activate_plugin(&grab_interface)) { return false; } if (above) { wf::scene::readd_front(always_above, view->get_root_node()); view->store_data(std::make_unique(), "wm-actions-above"); } else { wf::scene::readd_front(output->wset()->get_node(), view->get_root_node()); if (view->has_data("wm-actions-above")) { view->erase_data("wm-actions-above"); } } wf::wm_actions_above_changed_signal data; data.view = view; output->emit(&data); return true; } /** * Find the selected toplevel view, or nullptr if the selected view is not * toplevel. */ wayfire_toplevel_view choose_view(wf::activator_source_t source) { wayfire_view view; if (source == wf::activator_source_t::BUTTONBINDING) { view = wf::get_core().get_cursor_focus_view(); } else { view = wf::get_core().seat->get_active_view(); } return wf::toplevel_cast(view); } /** * Calling a specific view / specific keep_above action via signal */ wf::signal::connection_t on_set_above_state_signal = [=] (wf::wm_actions_set_above_state_signal *signal) { if (!set_keep_above_state(signal->view, signal->above)) { LOG(wf::log::LOG_LEVEL_DEBUG, "view above action failed via signal."); } }; /** * Ensures views marked as above are still above if their output changes. */ wf::signal::connection_t on_view_output_changed = [=] (wf::view_moved_to_wset_signal *signal) { if (!signal->new_wset || (signal->new_wset->get_attached_output() != output)) { return; } auto view = signal->view; if (!view) { return; } if (view->has_data("wm-actions-above")) { wf::scene::readd_front(always_above, view->get_root_node()); } }; /** * Ensures views marked as above are still above if they are minimized and * unminimized. */ wf::signal::connection_t on_view_minimized = [=] (wf::view_minimized_signal *ev) { if (ev->view->get_output() != output) { return; } if (ev->view->has_data("wm-actions-above") && !ev->view->minimized) { wf::scene::readd_front(always_above, ev->view->get_root_node()); } }; void check_disable_showdesktop(wayfire_view view) { if ((view->role != wf::VIEW_ROLE_TOPLEVEL) || !view->is_mapped()) { return; } disable_showdesktop(); } /** * Disables show desktop if the workspace is changed or any view is attached, * mapped or unminimized. */ wf::signal::connection_t view_set_output = [=] (wf::view_set_output_signal *ev) { check_disable_showdesktop(ev->view); }; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { check_disable_showdesktop(ev->view); }; wf::signal::connection_t workspace_changed = [=] (wf::workspace_changed_signal *ev) { disable_showdesktop(); }; wf::signal::connection_t view_minimized = [=] (wf::view_minimized_signal *ev) { if ((ev->view->role != wf::VIEW_ROLE_TOPLEVEL) || !ev->view->is_mapped()) { return; } if (!ev->view->minimized) { disable_showdesktop(); } }; /** * Execute for_view on the selected view, if available. */ bool execute_for_selected_view(wf::activator_source_t source, std::function for_view) { auto view = choose_view(source); if (!view || !output->can_activate_plugin(&grab_interface)) { return false; } return for_view(view); } /** * The default activator bindings. */ wf::activator_callback on_toggle_above = [=] (auto ev) -> bool { auto view = choose_view(ev.source); if (view) { return set_keep_above_state(view, !view->has_data("wm-actions-above")); } else { return false; } }; wf::activator_callback on_minimize = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->minimize_request(view, !view->minimized); return true; }); }; wf::activator_callback on_toggle_maximize = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->tile_request(view, view->pending_tiled_edges() == wf::TILED_EDGES_ALL ? 0 : wf::TILED_EDGES_ALL); return true; }); }; wf::activator_callback on_toggle_fullscreen = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { wf::get_core().default_wm->fullscreen_request(view, view->get_output(), !view->pending_fullscreen()); return true; }); }; wf::activator_callback on_toggle_sticky = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [] (wayfire_toplevel_view view) { view->set_sticky(view->sticky ^ 1); return true; }); }; bool on_toggle_showdesktop() { showdesktop_active = !showdesktop_active; if (showdesktop_active) { for (auto& view : output->wset()->get_views()) { if (!view->minimized) { wf::get_core().default_wm->minimize_request(view, true); view->store_data(std::make_unique(), "wm-actions-showdesktop"); } } output->connect(&view_set_output); output->connect(&workspace_changed); output->connect(&view_minimized); output->connect(&on_view_mapped); return true; } disable_showdesktop(); return true; } void do_send_to_back(wayfire_view view) { auto view_root = view->get_root_node(); if (auto parent = dynamic_cast(view_root->parent())) { auto parent_children = parent->get_children(); parent_children.erase( std::remove(parent_children.begin(), parent_children.end(), view_root), parent_children.end()); parent_children.push_back(view_root); parent->set_children_list(parent_children); wf::scene::update(parent->shared_from_this(), wf::scene::update_flag::CHILDREN_LIST); } } wf::activator_callback on_send_to_back = [=] (auto ev) -> bool { return execute_for_selected_view(ev.source, [this] (wayfire_view view) { auto views = view->get_output()->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED | wf::WSET_SORT_STACKING); wayfire_view bottom_view = views[views.size() - 1]; if (view != bottom_view) { do_send_to_back(view); // Change focus to the last focused view on this workspace // Update the list after restacking. views = view->get_output()->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED | wf::WSET_SORT_STACKING); wf::get_core().seat->focus_view(views[0]); } return true; }); }; void disable_showdesktop() { view_set_output.disconnect(); workspace_changed.disconnect(); view_minimized.disconnect(); auto views = output->wset()->get_views(wf::WSET_SORT_STACKING); for (auto& view : wf::reverse(views)) { if (view->has_data("wm-actions-showdesktop")) { view->erase_data("wm-actions-showdesktop"); wf::get_core().default_wm->minimize_request(view, false); } } showdesktop_active = false; } public: void init() override { always_above = std::make_shared(output); wf::scene::add_front(wf::get_core().scene()->layers[(int)wf::scene::layer::WORKSPACE], always_above); output->add_activator(minimize, &on_minimize); output->add_activator(toggle_maximize, &on_toggle_maximize); output->add_activator(toggle_above, &on_toggle_above); output->add_activator(toggle_fullscreen, &on_toggle_fullscreen); output->add_activator(toggle_sticky, &on_toggle_sticky); output->add_activator(send_to_back, &on_send_to_back); output->connect(&on_set_above_state_signal); output->connect(&on_view_minimized); wf::get_core().connect(&on_view_output_changed); } void fini() override { for (auto view : output->wset()->get_views()) { if (view->has_data("wm-actions-above")) { set_keep_above_state(view, false); } } wf::scene::remove_child(always_above); output->rem_binding(&on_minimize); output->rem_binding(&on_toggle_maximize); output->rem_binding(&on_toggle_above); output->rem_binding(&on_toggle_fullscreen); output->rem_binding(&on_toggle_sticky); output->rem_binding(&on_send_to_back); } }; class wayfire_wm_actions_t : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::shared_data::ref_ptr_t ipc_repo; wf::ipc_activator_t toggle_showdesktop{"wm-actions/toggle_showdesktop"}; public: void init() override { init_output_tracking(); ipc_repo->register_method("wm-actions/set-minimized", ipc_minimize); ipc_repo->register_method("wm-actions/set-always-on-top", ipc_set_always_on_top); ipc_repo->register_method("wm-actions/set-fullscreen", ipc_set_fullscreen); ipc_repo->register_method("wm-actions/set-sticky", ipc_set_sticky); ipc_repo->register_method("wm-actions/send-to-back", ipc_send_to_back); toggle_showdesktop.set_handler(on_toggle_showdesktop); } void fini() override { fini_output_tracking(); ipc_repo->unregister_method("wm-actions/set-minimized"); ipc_repo->unregister_method("wm-actions/set-always-on-top"); ipc_repo->unregister_method("wm-actions/set-fullscreen"); ipc_repo->unregister_method("wm-actions/set-sticky"); ipc_repo->unregister_method("wm-actions/send-to-back"); } wf::json_t execute_for_view(const wf::json_t& params, std::function view_op) { uint64_t view_id = wf::ipc::json_get_uint64(params, "view_id"); bool state = wf::ipc::json_get_bool(params, "state"); wayfire_toplevel_view view = toplevel_cast(wf::ipc::find_view_by_id(view_id)); if (!view) { return wf::ipc::json_error("toplevel view id not found!"); } view_op(view, state); return wf::ipc::json_ok(); } wf::ipc::method_callback ipc_minimize = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->minimize_request(view, state); }); }; wf::ipc::method_callback ipc_maximize = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->tile_request(view, state ? wf::TILED_EDGES_ALL : 0); }); }; wf::ipc::method_callback ipc_set_always_on_top = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { if (!view->get_output()) { view->store_data(std::make_unique(), "wm-actions-above"); return; } output_instance[view->get_output()]->set_keep_above_state(view, state); }); }; wf::ipc::method_callback ipc_set_fullscreen = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { wf::get_core().default_wm->fullscreen_request(view, nullptr, state); }); }; wf::ipc::method_callback ipc_set_sticky = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { view->set_sticky(state); }); }; wf::ipc::method_callback ipc_send_to_back = [=] (const wf::json_t& js) { return execute_for_view(js, [=] (wayfire_toplevel_view view, bool state) { if (!view->get_output()) { return; } output_instance[view->get_output()]->do_send_to_back(view); }); }; wf::ipc_activator_t::handler_t on_toggle_showdesktop = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->on_toggle_showdesktop(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wm_actions_t); wayfire-0.10.0/plugins/ipc-rules/0000775000175000017500000000000015053502647016515 5ustar dkondordkondorwayfire-0.10.0/plugins/ipc-rules/ipc-utility-methods.hpp0000664000175000017500000002700115053502647023143 0ustar dkondordkondor#pragma once #include "config.h" #include "ipc-rules-common.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/debug.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include #include #include extern "C" { #include #include } namespace wf { class ipc_rules_utility_methods_t { private: wlr_backend *headless_backend = NULL; std::set our_outputs; public: void init_utility_methods(ipc::method_repository_t *method_repository) { method_repository->register_method("wayfire/configuration", get_wayfire_configuration_info); method_repository->register_method("wayfire/create-headless-output", create_headless_output); method_repository->register_method("wayfire/destroy-headless-output", destroy_headless_output); method_repository->register_method("wayfire/get-config-option", get_config_option); method_repository->register_method("wayfire/set-config-options", set_config_options); method_repository->register_method("wayfire/get-keyboard-state", get_kb_state); method_repository->register_method("wayfire/set-keyboard-state", set_kb_state); } void fini_utility_methods(ipc::method_repository_t *method_repository) { method_repository->unregister_method("wayfire/configuration"); method_repository->unregister_method("wayfire/create-headless-output"); method_repository->unregister_method("wayfire/destroy-headless-output"); method_repository->unregister_method("wayfire/get-config-option"); method_repository->unregister_method("wayfire/set-config-option"); method_repository->unregister_method("wayfire/get-keyboard-state"); method_repository->unregister_method("wayfire/set-keyboard-state"); } wf::ipc::method_callback get_wayfire_configuration_info = [=] (wf::json_t) { wf::json_t response; response["api-version"] = WAYFIRE_API_ABI_VERSION; response["plugin-path"] = PLUGIN_PATH; response["plugin-xml-dir"] = PLUGIN_XML_DIR; response["xwayland-support"] = WF_HAS_XWAYLAND; response["build-commit"] = wf::version::git_commit; response["build-branch"] = wf::version::git_branch; return response; }; wf::ipc::method_callback create_headless_output = [=] (const wf::json_t& data) { auto width = wf::ipc::json_get_uint64(data, "width"); auto height = wf::ipc::json_get_uint64(data, "height"); if (!headless_backend) { auto& core = wf::get_core(); headless_backend = wlr_headless_backend_create(core.ev_loop); wlr_multi_backend_add(core.backend, headless_backend); wlr_backend_start(headless_backend); } auto handle = wlr_headless_add_output(headless_backend, width, height); auto wo = wf::get_core().output_layout->find_output(handle); our_outputs.insert(wo->get_id()); auto response = wf::ipc::json_ok(); response["output"] = output_to_json(wo); return response; }; wf::ipc::method_callback destroy_headless_output = [=] (const wf::json_t& data) { auto output = wf::ipc::json_get_optional_string(data, "output"); auto output_id = wf::ipc::json_get_optional_uint64(data, "output-id"); if (!output.has_value() && !output_id.has_value()) { return wf::ipc::json_error("Missing `output` or `output-id`!"); } wf::output_t *wo = NULL; if (output.has_value()) { wo = wf::get_core().output_layout->find_output(output.value()); } else if (output_id.has_value()) { wo = wf::ipc::find_output_by_id(output_id.value()); } if (!wo) { return wf::ipc::json_error("Output not found!"); } if (!our_outputs.count(wo->get_id())) { return wf::ipc::json_error("Output is not a headless output created from an IPC command!"); } our_outputs.erase(wo->get_id()); wlr_output_destroy(wo->handle); return wf::ipc::json_ok(); }; wf::ipc::method_callback get_config_option = [=] (const wf::json_t& data) { auto option_name = wf::ipc::json_get_string(data, "option"); auto option = wf::get_core().config->get_option(option_name); if (!option) { return wf::ipc::json_error("Option not found!"); } auto response = wf::ipc::json_ok(); std::shared_ptr compound_option = std::dynamic_pointer_cast(option); if (compound_option) { wf::config::compound_option_t::stored_type_t values = compound_option->get_value_untyped(); auto values_json = wf::json_t::array(); for (size_t i = 0; i < values.size(); i++) { auto values_json_ith = wf::json_t::array(); for (size_t j = 0; j < values[i].size(); j++) { values_json_ith.append(values[i][j]); } values_json.append(values_json_ith); } response["value"] = values_json; return response; } // Normal option - can be converted into a string response["value"] = option->get_value_str(); response["default"] = option->get_default_value_str(); return response; }; std::string json_to_string(const wf::json_t& data) { if (data.is_string()) { return data; } std::string buffer; data.map_serialized([&] (const char *src, size_t size) { buffer = std::string{src, size}; }); return buffer; } std::optional add_compound_entry(const wf::json_t& entry, const std::string& entry_name, const wf::config::compound_option_t::entries_t& tuple_entries, std::vector>& values) { values.emplace_back(); values.back().push_back(entry_name); if (!entry.is_object() && (tuple_entries.size() == 1)) { auto str_value = json_to_string(entry); if (!tuple_entries[0]->is_parsable(str_value)) { return "Failed to parse entry " + str_value; } values.back().push_back(str_value); } else if (entry.is_array()) { // A simple tuple => copy one to one if (entry.size() != tuple_entries.size()) { return "Number of entries does not match option type!"; } for (size_t i = 0; i < entry.size(); i++) { auto str_value = json_to_string(entry[i]); if (!tuple_entries[i]->is_parsable(str_value)) { return "Failed to parse entry " + str_value; } values.back().push_back(str_value); } } else if (entry.is_object()) { for (size_t i = 0; i < tuple_entries.size(); i++) { if (entry.has_member(tuple_entries[i]->get_name())) { auto str_value = json_to_string(entry[tuple_entries[i]->get_name()]); if (!tuple_entries[i]->is_parsable(str_value)) { return "Failed to parse entry " + str_value; } values.back().push_back(str_value); } else if (tuple_entries[i]->get_default_value().has_value()) { values.back().push_back(tuple_entries[i]->get_default_value().value()); } else { return "Missing entry without default value " + tuple_entries[i]->get_name(); } } } else { return "Compound entry must be an array or object"; } return {}; } std::optional parse_compound_json(const wf::json_t& data, std::shared_ptr option) { std::vector> values; const auto& tuple_entries = option->get_entries(); int counter = 0; if (data.is_array()) { for (size_t i = 0; i < data.size(); i++) { std::string entry_name = "autogenerated" + std::to_string(counter++); if (auto err = add_compound_entry(data[i], entry_name, tuple_entries, values)) { return err; } } } else if (data.is_object()) { for (auto& key : data.get_member_names()) { if (auto err = add_compound_entry(data[key], key, tuple_entries, values)) { return err; } } } else { return "Compound value must be an array or object!"; } option->set_value_untyped(values); return {}; } wf::ipc::method_callback set_config_options = [=] (const wf::json_t& data) -> json_t { if (!data.is_object()) { return wf::ipc::json_error("Options must be an object!"); } for (auto& option : data.get_member_names()) { auto opt = wf::get_core().config->get_option(option); if (!opt) { return wf::ipc::json_error(option + ": Option not found!"); } if (auto compound = std::dynamic_pointer_cast(opt)) { auto error = parse_compound_json(data[option], compound); if (error.has_value()) { return wf::ipc::json_error(option + ": " + error.value()); } } else { if (!opt->set_value_str(json_to_string(data[option]))) { return wf::ipc::json_error(option + ": Invalid value for option " + std::string(json_to_string(data[option])) + "!"); } } opt->set_locked(true); } reload_config_signal event; wf::get_core().emit(&event); return wf::ipc::json_ok(); }; wf::ipc::method_callback get_kb_state = [=] (const wf::json_t& data) -> json_t { auto seat = wf::get_core().get_current_seat(); auto keyboard = wlr_seat_get_keyboard(seat); return get_keyboard_state(keyboard); }; wf::ipc::method_callback set_kb_state = [=] (const wf::json_t& data) -> json_t { auto seat = wf::get_core().get_current_seat(); auto keyboard = wlr_seat_get_keyboard(seat); uint32_t index = wf::ipc::json_get_uint64(data, "layout-index"); if (!keyboard) { return wf::ipc::json_error("no keyboard currently in use!"); } if (index >= xkb_keymap_num_layouts(keyboard->keymap)) { return wf::ipc::json_error("invalid layout index!"); } wlr_keyboard_notify_modifiers(keyboard, keyboard->modifiers.depressed, keyboard->modifiers.latched, keyboard->modifiers.locked, index); return wf::ipc::json_ok(); }; }; } wayfire-0.10.0/plugins/ipc-rules/ipc-rules-common.hpp0000664000175000017500000001734015053502647022424 0ustar dkondordkondor#pragma once #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include #include #include #include "config.h" #include "wayfire/plugins/common/util.hpp" #include #include #include static inline wf::json_t output_to_json(wf::output_t *o) { if (!o) { return wf::json_t::null(); } wf::json_t response; response["id"] = o->get_id(); response["name"] = o->to_string(); response["geometry"] = wf::ipc::geometry_to_json(o->get_layout_geometry()); response["workarea"] = wf::ipc::geometry_to_json(o->workarea->get_workarea()); response["wset-index"] = o->wset()->get_index(); response["workspace"]["x"] = o->wset()->get_current_workspace().x; response["workspace"]["y"] = o->wset()->get_current_workspace().y; response["workspace"]["grid_width"] = o->wset()->get_workspace_grid_size().width; response["workspace"]["grid_height"] = o->wset()->get_workspace_grid_size().height; return response; } static inline pid_t get_view_pid(wayfire_view view) { pid_t pid = -1; if (!view) { return pid; } #if WF_HAS_XWAYLAND wlr_surface *wlr_surface = view->get_wlr_surface(); if (wlr_surface && wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)) { pid = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface)->pid; } else #endif if (view && view->get_client()) { wl_client_get_credentials(view->get_client(), &pid, 0, 0); } return pid; // NOLINT } static inline wf::geometry_t get_view_base_geometry(wayfire_view view) { auto sroot = view->get_surface_root_node(); for (auto& ch : sroot->get_children()) { if (auto wlr_surf = dynamic_cast(ch.get())) { auto bbox = wlr_surf->get_bounding_box(); wf::pointf_t origin = sroot->to_global({0, 0}); bbox.x = origin.x; bbox.y = origin.y; return bbox; } } return sroot->get_bounding_box(); } static inline std::string role_to_string(enum wf::view_role_t role) { switch (role) { case wf::VIEW_ROLE_TOPLEVEL: return "toplevel"; case wf::VIEW_ROLE_UNMANAGED: return "unmanaged"; case wf::VIEW_ROLE_DESKTOP_ENVIRONMENT: return "desktop-environment"; default: return "unknown"; } } static inline std::string layer_to_string(std::optional layer) { if (!layer.has_value()) { return "none"; } switch (layer.value()) { case wf::scene::layer::BACKGROUND: return "background"; case wf::scene::layer::BOTTOM: return "bottom"; case wf::scene::layer::WORKSPACE: return "workspace"; case wf::scene::layer::TOP: return "top"; case wf::scene::layer::UNMANAGED: return "unmanaged"; case wf::scene::layer::OVERLAY: return "overlay"; case wf::scene::layer::LOCK: return "lock"; case wf::scene::layer::DWIDGET: return "dew"; default: break; } wf::dassert(false, "invalid layer!"); assert(false); // prevent compiler warning } static inline std::string get_view_type(wayfire_view view) { if (view->role == wf::VIEW_ROLE_TOPLEVEL) { return "toplevel"; } if (view->role == wf::VIEW_ROLE_UNMANAGED) { #if WF_HAS_XWAYLAND auto surf = view->get_wlr_surface(); if (surf && wlr_xwayland_surface_try_from_wlr_surface(surf)) { return "x-or"; } #endif return "unmanaged"; } auto layer = wf::get_view_layer(view); if ((layer == wf::scene::layer::BACKGROUND) || (layer == wf::scene::layer::BOTTOM)) { return "background"; } else if (layer == wf::scene::layer::TOP) { return "panel"; } else if (layer == wf::scene::layer::OVERLAY) { return "overlay"; } return "unknown"; } static inline wf::json_t view_to_json(wayfire_view view) { if (!view) { return wf::json_t::null(); } auto output = view->get_output(); wf::json_t description; description["id"] = view->get_id(); description["pid"] = get_view_pid(view); description["title"] = view->get_title(); description["app-id"] = view->get_app_id(); description["base-geometry"] = wf::ipc::geometry_to_json(get_view_base_geometry(view)); auto toplevel = wf::toplevel_cast(view); description["parent"] = toplevel && toplevel->parent ? (int)toplevel->parent->get_id() : -1; description["geometry"] = wf::ipc::geometry_to_json(toplevel ? toplevel->get_pending_geometry() : view->get_bounding_box()); description["bbox"] = wf::ipc::geometry_to_json(view->get_bounding_box()); description["output-id"] = view->get_output() ? view->get_output()->get_id() : -1; description["output-name"] = output ? output->to_string() : "null"; description["last-focus-timestamp"] = wf::get_focus_timestamp(view); description["role"] = role_to_string(view->role); description["mapped"] = view->is_mapped(); description["layer"] = layer_to_string(get_view_layer(view)); description["tiled-edges"] = toplevel ? toplevel->pending_tiled_edges() : 0; description["fullscreen"] = toplevel ? toplevel->pending_fullscreen() : false; description["minimized"] = toplevel ? toplevel->minimized : false; description["activated"] = toplevel ? toplevel->activated : false; description["sticky"] = toplevel ? toplevel->sticky : false; description["wset-index"] = toplevel && toplevel->get_wset() ? static_cast(toplevel->get_wset()->get_index()) : -1; description["min-size"] = wf::ipc::dimensions_to_json( toplevel ? toplevel->toplevel()->get_min_size() : wf::dimensions_t{0, 0}); description["max-size"] = wf::ipc::dimensions_to_json( toplevel ? toplevel->toplevel()->get_max_size() : wf::dimensions_t{0, 0}); description["focusable"] = view->is_focusable(); description["type"] = get_view_type(view); return description; } static inline wf::json_t wset_to_json(wf::workspace_set_t *wset) { if (!wset) { return wf::json_t::null(); } wf::json_t response; response["index"] = wset->get_index(); response["name"] = wset->to_string(); auto output = wset->get_attached_output(); response["output-id"] = output ? (int)output->get_id() : -1; response["output-name"] = output ? output->to_string() : ""; response["workspace"]["x"] = wset->get_current_workspace().x; response["workspace"]["y"] = wset->get_current_workspace().y; response["workspace"]["grid_width"] = wset->get_workspace_grid_size().width; response["workspace"]["grid_height"] = wset->get_workspace_grid_size().height; return response; } static inline wf::json_t get_keyboard_state(wlr_keyboard *keyboard) { const auto& get_layout_name = [&] (xkb_layout_index_t layout) { auto layout_name = xkb_keymap_layout_get_name(keyboard->keymap, layout); return layout_name ? layout_name : "unknown"; }; wf::json_t state; state["possible-layouts"] = wf::json_t::array(); if (keyboard) { auto layout = xkb_state_serialize_layout(keyboard->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); state["layout"] = get_layout_name(layout); state["layout-index"] = layout; auto n_layouts = xkb_keymap_num_layouts(keyboard->keymap); for (size_t i = 0; i < n_layouts; i++) { state["possible-layouts"].append(get_layout_name(i)); } } else { state["layout"] = "unknown"; } return state; } wayfire-0.10.0/plugins/ipc-rules/ipc-rules.cpp0000664000175000017500000002132315053502647021125 0ustar dkondordkondor#include #include #include #include #include #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/core.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/window-manager.hpp" #include #include #include "ipc-rules-common.hpp" #include "ipc-input-methods.hpp" #include "ipc-utility-methods.hpp" #include "ipc-events.hpp" class ipc_rules_t : public wf::plugin_interface_t, public wf::ipc_rules_input_methods_t, public wf::ipc_rules_utility_methods_t, public wf::ipc_rules_events_methods_t { public: void init() override { method_repository->register_method("window-rules/list-views", list_views); method_repository->register_method("window-rules/list-outputs", list_outputs); method_repository->register_method("window-rules/list-wsets", list_wsets); method_repository->register_method("window-rules/view-info", get_view_info); method_repository->register_method("window-rules/output-info", get_output_info); method_repository->register_method("window-rules/wset-info", get_wset_info); method_repository->register_method("window-rules/get_cursor_position", get_cursor_position); method_repository->register_method("window-rules/configure-view", configure_view); method_repository->register_method("window-rules/focus-view", focus_view); method_repository->register_method("window-rules/get-focused-view", get_focused_view); method_repository->register_method("window-rules/get-focused-output", get_focused_output); method_repository->register_method("window-rules/close-view", close_view); init_input_methods(method_repository.get()); init_utility_methods(method_repository.get()); init_events(method_repository.get()); } void fini() override { method_repository->unregister_method("window-rules/list-views"); method_repository->unregister_method("window-rules/list-outputs"); method_repository->unregister_method("window-rules/list-wsets"); method_repository->unregister_method("window-rules/view-info"); method_repository->unregister_method("window-rules/output-info"); method_repository->unregister_method("window-rules/wset-info"); method_repository->unregister_method("window-rules/configure-view"); method_repository->unregister_method("window-rules/focus-view"); method_repository->unregister_method("window-rules/get-focused-view"); method_repository->unregister_method("window-rules/get-focused-output"); method_repository->unregister_method("window-rules/get-cursor-position"); method_repository->unregister_method("window-rules/close-view"); fini_input_methods(method_repository.get()); fini_utility_methods(method_repository.get()); fini_events(method_repository.get()); } wf::ipc::method_callback list_views = [=] (wf::json_t) { wf::json_t response = wf::json_t::array(); for (auto& view : wf::get_core().get_all_views()) { wf::json_t v = view_to_json(view); response.append(v); } return response; }; wf::ipc::method_callback get_view_info = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); if (auto view = wf::ipc::find_view_by_id(id)) { auto response = wf::ipc::json_ok(); response["info"] = view_to_json(view); return response; } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback get_focused_view = [=] (wf::json_t data) { if (auto view = wf::get_core().seat->get_active_view()) { auto response = wf::ipc::json_ok(); response["info"] = view_to_json(view); return response; } else { auto response = wf::ipc::json_ok(); response["info"] = wf::json_t::null(); return response; } }; wf::ipc::method_callback get_focused_output = [=] (wf::json_t data) { auto active_output = wf::get_core().seat->get_active_output(); auto response = wf::ipc::json_ok(); if (active_output) { response["info"] = output_to_json(active_output); } else { response["info"] = wf::json_t::null(); } return response; }; wf::ipc::method_callback focus_view = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); if (auto view = wf::ipc::find_view_by_id(id)) { auto response = wf::ipc::json_ok(); auto toplevel = wf::toplevel_cast(view); if (!toplevel) { return wf::ipc::json_error("view is not toplevel"); } wf::get_core().default_wm->focus_request(toplevel); return response; } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback close_view = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); if (auto view = wf::ipc::find_view_by_id(id)) { auto response = wf::ipc::json_ok(); view->close(); return response; } return wf::ipc::json_error("no such view"); }; wf::ipc::method_callback list_outputs = [=] (wf::json_t) { wf::json_t response = wf::json_t::array(); for (auto& output : wf::get_core().output_layout->get_outputs()) { response.append(output_to_json(output)); } return response; }; wf::ipc::method_callback get_output_info = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); auto wo = wf::ipc::find_output_by_id(id); if (!wo) { return wf::ipc::json_error("output not found"); } auto response = output_to_json(wo); return response; }; wf::ipc::method_callback configure_view = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); auto output_id = wf::ipc::json_get_optional_uint64(data, "output_id"); if (data.has_member("geometry") && !data["geometry"].is_object()) { return wf::ipc::json_error("invalid geometry"); } auto sticky = wf::ipc::json_get_optional_bool(data, "sticky"); auto view = wf::ipc::find_view_by_id(id); if (!view) { return wf::ipc::json_error("view not found"); } auto toplevel = wf::toplevel_cast(view); if (!toplevel) { return wf::ipc::json_error("view is not toplevel"); } if (output_id.has_value()) { auto wo = wf::ipc::find_output_by_id(output_id.value()); if (!wo) { return wf::ipc::json_error("output not found"); } wf::move_view_to_output(toplevel, wo, !data.has_member("geometry")); } if (data.has_member("geometry")) { auto geometry = wf::ipc::geometry_from_json(data["geometry"]); if (!geometry) { return wf::ipc::json_error("invalid geometry"); } toplevel->set_geometry(*geometry); } if (sticky.has_value()) { toplevel->set_sticky(sticky.value()); } return wf::ipc::json_ok(); }; wf::ipc::method_callback list_wsets = [=] (wf::json_t) { wf::json_t response = wf::json_t::array(); for (auto& workspace_set : wf::workspace_set_t::get_all()) { response.append(wset_to_json(workspace_set.get())); } return response; }; wf::ipc::method_callback get_wset_info = [=] (wf::json_t data) { auto id = wf::ipc::json_get_uint64(data, "id"); auto ws = wf::ipc::find_workspace_set_by_index(id); if (!ws) { return wf::ipc::json_error("workspace set not found"); } auto response = wset_to_json(ws); return response; }; wf::ipc::method_callback get_cursor_position = [=] (wf::json_t data) -> wf::json_t { wf::json_t response = wf::ipc::json_ok(); auto cursor = wf::get_core().get_cursor_position(); response["pos"]["x"] = cursor.x; response["pos"]["y"] = cursor.y; return response; }; private: wf::shared_data::ref_ptr_t method_repository; }; DECLARE_WAYFIRE_PLUGIN(ipc_rules_t); wayfire-0.10.0/plugins/ipc-rules/meson.build0000664000175000017500000000066515053502647020666 0ustar dkondordkondorall_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs] all_deps = [wlroots, pixman, wfconfig, wftouch, json, plugin_pch_dep] shared_module('ipc-rules', ['ipc-rules.cpp'], include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_headers(['ipc-rules-common.hpp'], subdir: 'wayfire/plugins/ipc') wayfire-0.10.0/plugins/ipc-rules/ipc-events.hpp0000664000175000017500000003223115053502647021304 0ustar dkondordkondor#pragma once #include "ipc-rules-common.hpp" #include #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/seat.hpp" #include #include namespace wf { class ipc_rules_events_methods_t : public wf::per_output_tracker_mixin_t<> { public: void init_events(ipc::method_repository_t *method_repository) { method_repository->register_method("window-rules/events/watch", on_client_watch); method_repository->connect(&on_client_disconnected); init_output_tracking(); } void fini_events(ipc::method_repository_t *method_repository) { method_repository->unregister_method("window-rules/events/watch"); fini_output_tracking(); } void handle_new_output(wf::output_t *output) override { for (auto& [_, event] : signal_map) { if (event.connected_count) { event.register_output(output); } } wf::json_t data; data["event"] = "output-added"; data["output"] = output_to_json(output); send_event_to_subscribes(data, data["event"]); } void handle_output_removed(wf::output_t *output) override { wf::json_t data; data["event"] = "output-removed"; data["output"] = output_to_json(output); send_event_to_subscribes(data, data["event"]); } // Template FOO for efficient management of signals: ensure that only actually listened-for signals // are connected. struct signal_registration_handler { std::function register_core = [] () {}; std::function register_output = [] (wf::output_t*) {}; std::function unregister = [] () {}; int connected_count = 0; void increase_count() { connected_count++; if (connected_count > 1) { return; } register_core(); for (auto& wo : wf::get_core().output_layout->get_outputs()) { register_output(wo); } } void decrease_count() { connected_count--; if (connected_count > 0) { return; } unregister(); } }; template static signal_registration_handler get_generic_core_registration_cb( wf::signal::connection_t *conn) { return { .register_core = [=] () { wf::get_core().connect(conn); }, .unregister = [=] () { conn->disconnect(); } }; } template signal_registration_handler get_generic_output_registration_cb(wf::signal::connection_t *conn) { return { .register_output = [=] (wf::output_t *wo) { wo->connect(conn); }, .unregister = [=] () { conn->disconnect(); } }; } std::map signal_map = { {"view-mapped", get_generic_core_registration_cb(&on_view_mapped)}, {"view-unmapped", get_generic_core_registration_cb(&on_view_unmapped)}, {"view-set-output", get_generic_core_registration_cb(&on_view_set_output)}, {"view-geometry-changed", get_generic_core_registration_cb(&on_view_geometry_changed)}, {"view-wset-changed", get_generic_core_registration_cb(&on_view_moved_to_wset)}, {"view-focused", get_generic_core_registration_cb(&on_kbfocus_changed)}, {"view-title-changed", get_generic_core_registration_cb(&on_title_changed)}, {"view-app-id-changed", get_generic_core_registration_cb(&on_app_id_changed)}, {"plugin-activation-state-changed", get_generic_core_registration_cb(&on_plugin_activation_changed)}, {"output-gain-focus", get_generic_core_registration_cb(&on_output_gain_focus)}, {"keyboard-modifier-state-changed", get_generic_core_registration_cb(&on_keyboard_modifiers)}, {"view-tiled", get_generic_output_registration_cb(&_tiled)}, {"view-minimized", get_generic_output_registration_cb(&_minimized)}, {"view-fullscreen", get_generic_output_registration_cb(&_fullscreened)}, {"view-sticky", get_generic_output_registration_cb(&_stickied)}, {"view-workspace-changed", get_generic_output_registration_cb(&_view_workspace)}, {"output-wset-changed", get_generic_output_registration_cb(&on_wset_changed)}, {"wset-workspace-changed", get_generic_output_registration_cb(&on_wset_workspace_changed)}, }; // Track a list of clients which have requested watch std::map> clients; wf::ipc::method_callback_full on_client_watch = [=] (wf::json_t data, wf::ipc::client_interface_t *client) { static constexpr const char *EVENTS = "events"; if (data.has_member(EVENTS) && !data[EVENTS].is_array()) { return wf::ipc::json_error("Event list is not an array!"); } std::set subscribed_to; if (data.has_member(EVENTS)) { for (size_t i = 0; i < data[EVENTS].size(); i++) { const auto& sub = data[EVENTS][i]; if (!sub.is_string()) { return wf::ipc::json_error("Event list contains non-string entries!"); } if (signal_map.count(sub)) { subscribed_to.insert(sub); } else { return wf::ipc::json_error("Event not found: \"" + sub.as_string() + "\""); } } } else { for (auto& [ev_name, _] : signal_map) { subscribed_to.insert(ev_name); } } for (auto& ev_name : subscribed_to) { signal_map[ev_name].increase_count(); } clients[client] = subscribed_to; return wf::ipc::json_ok(); }; wf::signal::connection_t on_client_disconnected = [=] (wf::ipc::client_disconnected_signal *ev) { for (auto& ev_name : clients[ev->client]) { signal_map[ev_name].decrease_count(); } clients.erase(ev->client); }; void send_view_to_subscribes(wayfire_view view, std::string event_name) { wf::json_t event; event["event"] = event_name; event["view"] = view_to_json(view); send_event_to_subscribes(event, event_name); } void send_event_to_subscribes(const wf::json_t& data, const std::string& event_name) { for (auto& [client, events] : clients) { if (events.empty() || events.count(event_name)) { client->send_json(data); } } } wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { send_view_to_subscribes(ev->view, "view-mapped"); }; wf::signal::connection_t on_view_unmapped = [=] (wf::view_unmapped_signal *ev) { send_view_to_subscribes(ev->view, "view-unmapped"); }; wf::signal::connection_t on_view_set_output = [=] (wf::view_set_output_signal *ev) { wf::json_t data; data["event"] = "view-set-output"; data["output"] = output_to_json(ev->output); data["view"] = view_to_json(ev->view); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_view_geometry_changed = [=] (wf::view_geometry_changed_signal *ev) { wf::json_t data; data["event"] = "view-geometry-changed"; data["old-geometry"] = wf::ipc::geometry_to_json(ev->old_geometry); data["view"] = view_to_json(ev->view); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_view_moved_to_wset = [=] (wf::view_moved_to_wset_signal *ev) { wf::json_t data; data["event"] = "view-wset-changed"; data["old-wset"] = wset_to_json(ev->old_wset.get()); data["new-wset"] = wset_to_json(ev->new_wset.get()); data["view"] = view_to_json(ev->view); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_kbfocus_changed = [=] (wf::keyboard_focus_changed_signal *ev) { send_view_to_subscribes(wf::node_to_view(ev->new_focus), "view-focused"); }; // Tiled rule handler. wf::signal::connection_t _tiled = [=] (wf::view_tiled_signal *ev) { wf::json_t data; data["event"] = "view-tiled"; data["old-edges"] = ev->old_edges; data["new-edges"] = ev->new_edges; data["view"] = view_to_json(ev->view); send_event_to_subscribes(data, data["event"]); }; // Minimized rule handler. wf::signal::connection_t _minimized = [=] (wf::view_minimized_signal *ev) { send_view_to_subscribes(ev->view, "view-minimized"); }; // Fullscreened rule handler. wf::signal::connection_t _fullscreened = [=] (wf::view_fullscreen_signal *ev) { send_view_to_subscribes(ev->view, "view-fullscreen"); }; // Stickied rule handler. wf::signal::connection_t _stickied = [=] (wf::view_set_sticky_signal *ev) { send_view_to_subscribes(ev->view, "view-sticky"); }; wf::signal::connection_t _view_workspace = [=] (wf::view_change_workspace_signal *ev) { wf::json_t data; data["event"] = "view-workspace-changed"; data["from"] = wf::ipc::point_to_json(ev->from); data["to"] = wf::ipc::point_to_json(ev->to); data["view"] = view_to_json(ev->view); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_title_changed = [=] (wf::view_title_changed_signal *ev) { send_view_to_subscribes(ev->view, "view-title-changed"); }; wf::signal::connection_t on_app_id_changed = [=] (wf::view_app_id_changed_signal *ev) { send_view_to_subscribes(ev->view, "view-app-id-changed"); }; wf::signal::connection_t on_plugin_activation_changed = [=] (wf::output_plugin_activated_changed_signal *ev) { wf::json_t data; data["event"] = "plugin-activation-state-changed"; data["plugin"] = ev->plugin_name; data["state"] = ev->activated; data["output"] = ev->output ? (int)ev->output->get_id() : -1; data["output-data"] = output_to_json(ev->output); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_output_gain_focus = [=] (wf::output_gain_focus_signal *ev) { wf::json_t data; data["event"] = "output-gain-focus"; data["output"] = output_to_json(ev->output); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t> on_keyboard_modifiers = [=] (wf::input_event_signal *ev) { auto seat = wf::get_core().get_current_seat(); auto keyboard = wlr_seat_get_keyboard(seat); if (ev->device != &keyboard->base) { return; } wf::json_t data; data["event"] = "keyboard-modifier-state-changed"; data["state"] = get_keyboard_state(keyboard); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_wset_changed = [=] (wf::workspace_set_changed_signal *ev) { wf::json_t data; data["event"] = "output-wset-changed"; data["new-wset"] = ev->new_wset ? (int)ev->new_wset->get_id() : -1; data["output"] = ev->output ? (int)ev->output->get_id() : -1; data["new-wset-data"] = wset_to_json(ev->new_wset.get()); data["output-data"] = output_to_json(ev->output); send_event_to_subscribes(data, data["event"]); }; wf::signal::connection_t on_wset_workspace_changed = [=] (wf::workspace_changed_signal *ev) { wf::json_t data; data["event"] = "wset-workspace-changed"; data["previous-workspace"] = wf::ipc::point_to_json(ev->old_viewport); data["new-workspace"] = wf::ipc::point_to_json(ev->new_viewport); data["output"] = ev->output ? (int)ev->output->get_id() : -1; data["wset"] = (ev->output && ev->output->wset()) ? (int)ev->output->wset()->get_id() : -1; data["output-data"] = output_to_json(ev->output); data["wset-data"] = ev->output ? wset_to_json(ev->output->wset().get()) : json_t::null(); send_event_to_subscribes(data, data["event"]); }; }; } wayfire-0.10.0/plugins/ipc-rules/ipc-input-methods.hpp0000664000175000017500000000572015053502647022603 0ustar dkondordkondor#pragma once #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include #include namespace wf { class ipc_rules_input_methods_t { public: void init_input_methods(ipc::method_repository_t *method_repository) { method_repository->register_method("input/list-devices", list_input_devices); method_repository->register_method("input/configure-device", configure_input_device); } void fini_input_methods(ipc::method_repository_t *method_repository) { method_repository->unregister_method("input/list-devices"); method_repository->unregister_method("input/configure-device"); } static std::string wlr_input_device_type_to_string(wlr_input_device_type type) { switch (type) { case WLR_INPUT_DEVICE_KEYBOARD: return "keyboard"; case WLR_INPUT_DEVICE_POINTER: return "pointer"; case WLR_INPUT_DEVICE_TOUCH: return "touch"; case WLR_INPUT_DEVICE_TABLET: return "tablet_tool"; case WLR_INPUT_DEVICE_TABLET_PAD: return "tablet_pad"; case WLR_INPUT_DEVICE_SWITCH: return "switch"; default: return "unknown"; } } wf::ipc::method_callback list_input_devices = [&] (const wf::json_t&) { wf::json_t response = json_t::array(); for (auto& device : wf::get_core().get_input_devices()) { wf::json_t d; d["id"] = (intptr_t)device->get_wlr_handle(); d["name"] = nonull(device->get_wlr_handle()->name); d["vendor"] = "unknown"; d["product"] = "unknown"; if (wlr_input_device_is_libinput(device->get_wlr_handle())) { if (auto libinput_handle = wlr_libinput_get_device_handle(device->get_wlr_handle())) { d["vendor"] = libinput_device_get_id_vendor(libinput_handle); d["product"] = libinput_device_get_id_product(libinput_handle); } } d["type"] = wlr_input_device_type_to_string(device->get_wlr_handle()->type); d["enabled"] = device->is_enabled(); response.append(d); } return response; }; wf::ipc::method_callback configure_input_device = [&] (const wf::json_t& data) { auto id = wf::ipc::json_get_int64(data, "id"); auto enabled = wf::ipc::json_get_bool(data, "enabled"); for (auto& device : wf::get_core().get_input_devices()) { if ((intptr_t)device->get_wlr_handle() == id) { device->set_enabled(enabled); return wf::ipc::json_ok(); } } return wf::ipc::json_error("Unknown input device!"); }; }; } wayfire-0.10.0/plugins/ipc/0000775000175000017500000000000015053502647015365 5ustar dkondordkondorwayfire-0.10.0/plugins/ipc/ipc.cpp0000664000175000017500000002150615053502647016650 0ustar dkondordkondor#include "ipc.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include #include #include #include #include #include #include #include #include /** * Handle WL_EVENT_READABLE on the socket. * Indicates a new connection. */ int wl_loop_handle_ipc_fd_connection(int, uint32_t, void *data) { (*((std::function*)data))(); return 0; } wf::ipc::server_t::server_t() { accept_new_client = [=] () { do_accept_new_client(); }; } void wf::ipc::server_t::init(std::string socket_path) { this->fd = setup_socket(socket_path.c_str()); if (fd == -1) { LOGE("Failed to create debug IPC socket!"); return; } listen(fd, 3); source = wl_event_loop_add_fd(wl_display_get_event_loop(wf::get_core().display), fd, WL_EVENT_READABLE, wl_loop_handle_ipc_fd_connection, &accept_new_client); } wf::ipc::server_t::~server_t() { if (fd != -1) { close(fd); unlink(saddr.sun_path); wl_event_source_remove(source); } } int wf::ipc::server_t::setup_socket(const char *address) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { return -1; } if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { return -1; } if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { return -1; } // Ensure no old instance left after a crash or similar unlink(address); saddr.sun_family = AF_UNIX; strncpy(saddr.sun_path, address, sizeof(saddr.sun_path) - 1); int r = bind(fd, (sockaddr*)&saddr, sizeof(saddr)); if (r != 0) { LOGE("Failed to bind debug IPC socket at address ", address, " !"); // TODO: shutdown socket? return -1; } return fd; } void wf::ipc::server_t::do_accept_new_client() { // Heavily inspired by Sway int cfd = accept(this->fd, NULL, NULL); if (cfd == -1) { LOGW("Error accepting client connection"); return; } int flags; if (((flags = fcntl(cfd, F_GETFD)) == -1) || (fcntl(cfd, F_SETFD, flags | FD_CLOEXEC) == -1)) { LOGE("Failed setting CLOEXEC"); close(cfd); return; } if (((flags = fcntl(cfd, F_GETFL)) == -1) || (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) == -1)) { LOGE("Failed setting NONBLOCK"); close(cfd); return; } this->clients.push_back(std::make_unique(this, cfd)); } void wf::ipc::server_t::client_disappeared(client_t *client) { LOGD("Removing IPC client ", client); client_disconnected_signal ev; ev.client = client; method_repository->emit(&ev); auto it = std::remove_if(clients.begin(), clients.end(), [&] (const auto& cl) { return cl.get() == client; }); clients.erase(it, clients.end()); } void wf::ipc::server_t::handle_incoming_message( client_t *client, wf::json_t message) { client->send_json(method_repository->call_method(message["method"], message["data"], client)); } /* --------------------------- Per-client code ------------------------------*/ int wl_loop_handle_ipc_client_fd_event(int, uint32_t mask, void *data) { (*((std::function*)data))(mask); return 0; } static constexpr int MAX_MESSAGE_LEN = (1 << 20); static constexpr int HEADER_LEN = 4; wf::ipc::client_t::client_t(server_t *ipc, int fd) { LOGD("New IPC client, fd ", fd); this->fd = fd; this->ipc = ipc; auto ev_loop = wf::get_core().ev_loop; source = wl_event_loop_add_fd(ev_loop, fd, WL_EVENT_READABLE, wl_loop_handle_ipc_client_fd_event, &this->handle_fd_activity); // +1 for null byte at the end buffer.resize(MAX_MESSAGE_LEN + 1); this->handle_fd_activity = [=] (uint32_t event_mask) { handle_fd_incoming(event_mask); }; } // -1 error, 0 success, 1 try again later int wf::ipc::client_t::read_up_to(int n, int *available) { int need = n - current_buffer_valid; int want = std::min(need, *available); while (want > 0) { int r = read(fd, buffer.data() + current_buffer_valid, want); if (r <= 0) { LOGI("Read: EOF or error (%d) %s\n", r, strerror(errno)); return -1; } want -= r; *available -= r; current_buffer_valid += r; } if (current_buffer_valid < n) { // didn't read all n bytes return 1; } return 0; } void wf::ipc::client_t::handle_fd_incoming(uint32_t event_mask) { if (event_mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) { ipc->client_disappeared(this); // this no longer exists return; } int available = 0; if (ioctl(this->fd, FIONREAD, &available) != 0) { LOGE("Failed to inspect message buffer!"); ipc->client_disappeared(this); return; } while (available > 0) { if (current_buffer_valid < HEADER_LEN) { if (read_up_to(HEADER_LEN, &available) < 0) { ipc->client_disappeared(this); return; } continue; } const uint32_t len = *((uint32_t*)buffer.data()); if (len > MAX_MESSAGE_LEN - HEADER_LEN) { LOGE("Client tried to pass too long a message!"); ipc->client_disappeared(this); return; } const int next_target = HEADER_LEN + len; int r = read_up_to(next_target, &available); if (r < 0) { ipc->client_disappeared(this); return; } if (r > 0) { // Try again continue; } // Finally, received the message, make sure we have a terminating NULL byte buffer[current_buffer_valid] = '\0'; char *str = buffer.data() + HEADER_LEN; json_t message; auto err = json_t::parse_string(std::string_view{str, len}, message); if (err.has_value()) { json_t error; error["error"] = std::string("Client's message could not be parsed, error: ") + *err; LOGE((std::string)error["error"], ": ", str); this->send_json(error); ipc->client_disappeared(this); return; } if (!message.has_member("method") || !message["method"].is_string()) { LOGI("Start"); json_t error; error["error"] = "Client's message does not contain a method to be called!"; LOGI("MID"); LOGE(error["error"].as_string()); LOGI("END"); this->send_json(error); ipc->client_disappeared(this); return; } ipc->handle_incoming_message(this, std::move(message)); // Reset for next message current_buffer_valid = 0; } } wf::ipc::client_t::~client_t() { wl_event_source_remove(source); shutdown(fd, SHUT_RDWR); close(this->fd); } static bool write_exact(int fd, const char *buf, ssize_t n) { while (n > 0) { ssize_t w = write(fd, buf, n); if (w <= 0) { return false; } n -= w; buf += w; } return true; } bool wf::ipc::client_t::send_json(wf::json_t json) { bool status = false; json.map_serialized([&] (const char *buffer, size_t size) { if (size > MAX_MESSAGE_LEN) { LOGE("Error sending json to client: message too long!"); shutdown(fd, SHUT_RDWR); return; } uint32_t len = size; if (!write_exact(fd, (char*)&len, 4) || !write_exact(fd, buffer, len)) { LOGE("Error sending json to client!"); shutdown(fd, SHUT_RDWR); return; } status = true; }); return status; } namespace wf { class ipc_plugin_t : public wf::plugin_interface_t { private: shared_data::ref_ptr_t server; public: void init() override { char *pre_socket = getenv("_WAYFIRE_SOCKET"); const auto& dname = wf::get_core().wayland_display; pid_t pid = getpid(); const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); std::string socket; if (pre_socket) { socket = pre_socket; } else if (runtime_dir) { socket = std::string(runtime_dir) + "/wayfire-" + dname + "-" + ".socket"; } else { socket = "/tmp/wayfire-" + dname + "-" + std::to_string(pid) + ".socket"; } setenv("WAYFIRE_SOCKET", socket.c_str(), 1); server->init(socket); } bool is_unloadable() override { return false; } int get_order_hint() const override { return INT_MIN; } }; } // namespace wf DECLARE_WAYFIRE_PLUGIN(wf::ipc_plugin_t); wayfire-0.10.0/plugins/ipc/wayfire/0000775000175000017500000000000015053502647017033 5ustar dkondordkondorwayfire-0.10.0/plugins/ipc/wayfire/plugins/0000775000175000017500000000000015053502647020514 5ustar dkondordkondorwayfire-0.10.0/plugins/ipc/wayfire/plugins/ipc/0000775000175000017500000000000015053502647021267 5ustar dkondordkondorwayfire-0.10.0/plugins/ipc/wayfire/plugins/ipc/ipc-method-repository.hpp0000664000175000017500000000773315053502647026260 0ustar dkondordkondor#pragma once #include #include #include "wayfire/signal-provider.hpp" #include #include namespace wf { namespace ipc { class ipc_method_exception_t : public std::exception { public: ipc_method_exception_t(std::string msg) : msg(msg) {} std::string msg; const char *what() const noexcept override { return msg.c_str(); } }; /** * A client_interface_t represents a client which has connected to the IPC socket. * It can be used by plugins to send back data to a specific client. */ class client_interface_t { public: virtual bool send_json(json_t json) = 0; virtual ~client_interface_t() = default; }; /** * A signal emitted on the ipc method repository when a client disconnects. */ struct client_disconnected_signal { client_interface_t *client; }; /** * An IPC method has a name and a callback. The callback is a simple function which takes a json object which * contains the method's parameters and returns the result of the operation. */ using method_callback = std::function; /** * Same as @method_callback, but also supports getting information about the connected ipc client. */ using method_callback_full = std::function; /** * The IPC method repository keeps track of all registered IPC methods. It can be used even without the IPC * plugin itself, as it facilitates inter-plugin calls similarly to signals. * * The method_repository_t is a singleton and is accessed by creating a shared_data::ref_ptr_t to it. */ class method_repository_t : public wf::signal::provider_t { public: /** * Register a new method to the method repository. If the method already exists, the old handler will be * overwritten. */ void register_method(std::string method, method_callback_full handler) { this->methods[method] = handler; } /** * Register a new method to the method repository. If the method already exists, the old handler will be * overwritten. */ void register_method(std::string method, method_callback handler) { this->methods[method] = [handler] (const wf::json_t& data, client_interface_t*) { return handler(data); }; } /** * Remove the last registered handler for the given method. */ void unregister_method(std::string method) { this->methods.erase(method); } /** * Call an IPC method with the given name and given parameters. * If the method was not registered, a JSON object containing an error will be returned. */ wf::json_t call_method(std::string method, json_t data, client_interface_t *client = nullptr) { if (this->methods.count(method)) { try { return this->methods[method](std::move(data), client); } catch (const ipc_method_exception_t& e) { json_t response; response["error"] = "Error during execution of the handler for method \"" + method + "\": " + e.what(); return response; } } json_t response; response["error"] = "No such method found!"; response["method"] = method; return response; } method_repository_t() { register_method("list-methods", [this] (auto) { wf::json_t response; response["methods"] = wf::json_t::array(); for (auto& [method, _] : methods) { response["methods"].append(method); } return response; }); } private: std::map methods; }; // A few helper definitions for IPC method implementations. inline wf::json_t json_ok() { wf::json_t r; r["result"] = "ok"; return r; } inline wf::json_t json_error(std::string msg) { wf::json_t r; r["error"] = msg; return r; } } } wayfire-0.10.0/plugins/ipc/wayfire/plugins/ipc/ipc-activator.hpp0000664000175000017500000000700015053502647024542 0ustar dkondordkondor#pragma once #include #include #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/bindings.hpp" #include "wayfire/core.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/output.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/seat.hpp" namespace wf { /** * The IPC activator class is a helper class which combines an IPC method with a normal activator binding. */ class ipc_activator_t { public: ipc_activator_t() {} ipc_activator_t(std::string name) { load_from_xml_option(name); } void load_from_xml_option(std::string name) { activator.load_option(name); wf::get_core().bindings->add_activator(activator, &activator_cb); repo->register_method(name, ipc_cb); this->name = name; } ~ipc_activator_t() { wf::get_core().bindings->rem_binding(&activator_cb); repo->unregister_method(name); } /** * The handler is given over an optional output and a view to execute the action for. * Note that the output is always set (if not explicitly given, then it is set to the currently focused * output), however the view might be nullptr if not indicated in the IPC call or in the case of * activators, no suitable view could be found for the cursor/keyboard focus. */ using handler_t = std::function; void set_handler(handler_t hnd) { this->hnd = hnd; } private: wf::option_wrapper_t activator; shared_data::ref_ptr_t repo; std::string name; handler_t hnd; activator_callback activator_cb = [=] (const wf::activator_data_t& data) -> bool { if (hnd) { return hnd(choose_output(), choose_view(data.source)); } return false; }; ipc::method_callback ipc_cb = [=] (const wf::json_t& data) { auto output_id = ipc::json_get_optional_int64(data, "output_id"); if (!output_id.has_value()) { output_id = ipc::json_get_optional_int64(data, "output-id"); } auto view_id = ipc::json_get_optional_int64(data, "view_id"); if (!view_id.has_value()) { view_id = ipc::json_get_optional_int64(data, "view-id"); } wf::output_t *wo = wf::get_core().seat->get_active_output(); if (output_id.has_value()) { wo = ipc::find_output_by_id(output_id.value()); if (!wo) { return ipc::json_error("output id not found!"); } } wayfire_view view; if (view_id.has_value()) { view = ipc::find_view_by_id(view_id.value()); if (!view) { return ipc::json_error("view id not found!"); } } if (hnd) { hnd(wo, view); } return ipc::json_ok(); }; wf::output_t *choose_output() { return wf::get_core().seat->get_active_output(); } wayfire_view choose_view(wf::activator_source_t source) { wayfire_view view; if (source == wf::activator_source_t::BUTTONBINDING) { view = wf::get_core().get_cursor_focus_view(); } else { view = wf::get_core().seat->get_active_view(); } return view; } }; } wayfire-0.10.0/plugins/ipc/wayfire/plugins/ipc/ipc-helpers.hpp0000664000175000017500000000713615053502647024222 0ustar dkondordkondor#pragma once #include #include "wayfire/geometry.hpp" #include #include #include #include #include #include #include "wayfire/plugins/ipc/ipc-method-repository.hpp" namespace wf { namespace ipc { #define WFJSON_GETTER_FUNCTION(type, ctype) \ inline ctype json_get_ ## type(const wf::json_t& data, std::string field) \ { \ if (!data.has_member(field)) \ { \ throw ipc_method_exception_t("Missing \"" + field + "\""); \ } \ else if (!data[field].is_ ## type()) \ { \ throw ipc_method_exception_t( \ "Field \"" + field + "\" does not have the correct type, expected " #type); \ } \ \ return (ctype)data[field]; \ } \ \ inline std::optional json_get_optional_ ## type(const wf::json_t& data, \ std::string field) \ { \ if (!data.has_member(field)) \ { \ return {}; \ } \ else if (!data[field].is_ ## type()) \ { \ throw ipc_method_exception_t( \ "Field \"" + field + "\" does not have the correct type, expected " #type); \ } \ \ return (ctype)data[field]; \ } WFJSON_GETTER_FUNCTION(int64, int64_t); WFJSON_GETTER_FUNCTION(uint64, uint64_t); WFJSON_GETTER_FUNCTION(double, double); WFJSON_GETTER_FUNCTION(string, std::string); WFJSON_GETTER_FUNCTION(bool, bool); #undef WFJSON_GETTER_FUNCTION inline wayfire_view find_view_by_id(uint32_t id) { for (auto view : wf::get_core().get_all_views()) { if (view->get_id() == id) { return view; } } return nullptr; } inline wf::output_t *find_output_by_id(int32_t id) { for (auto wo : wf::get_core().output_layout->get_outputs()) { if ((int)wo->get_id() == id) { return wo; } } return nullptr; } inline wf::workspace_set_t *find_workspace_set_by_index(int32_t index) { for (auto wset : wf::workspace_set_t::get_all()) { if ((int)wset->get_index() == index) { return wset.get(); } } return nullptr; } inline wf::json_t geometry_to_json(wf::geometry_t g) { wf::json_t j; j["x"] = g.x; j["y"] = g.y; j["width"] = g.width; j["height"] = g.height; return j; } #define CHECK(field, type) (j.has_member(field) && j[field].is_ ## type()) inline std::optional geometry_from_json(const wf::json_t& j) { if (!CHECK("x", int) || !CHECK("y", int) || !CHECK("width", int) || !CHECK("height", int)) { return {}; } return wf::geometry_t{ .x = j["x"], .y = j["y"], .width = j["width"], .height = j["height"], }; } inline wf::json_t point_to_json(wf::point_t p) { wf::json_t j; j["x"] = p.x; j["y"] = p.y; return j; } inline std::optional point_from_json(const wf::json_t& j) { if (!CHECK("x", int) || !CHECK("y", int)) { return {}; } return wf::point_t{ .x = j["x"], .y = j["y"], }; } inline wf::json_t dimensions_to_json(wf::dimensions_t d) { wf::json_t j; j["width"] = d.width; j["height"] = d.height; return j; } inline std::optional dimensions_from_json(const wf::json_t& j) { if (!CHECK("width", int) || !CHECK("height", int)) { return {}; } return wf::dimensions_t{ .width = j["width"], .height = j["height"], }; } #undef CHECK } } wayfire-0.10.0/plugins/ipc/meson.build0000664000175000017500000000151015053502647017524 0ustar dkondordkondorevdev = dependency('libevdev') ipc_include_dirs = include_directories('.', 'wayfire/plugins/ipc') ipc = shared_module('ipc', ['ipc.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wftouch, json, evdev, plugin_pch_dep], install: true, install_dir: conf_data.get('PLUGIN_PATH')) stipc = shared_module('stipc', ['stipc.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wfconfig, wftouch, json, evdev, plugin_pch_dep], install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_headers(['wayfire/plugins/ipc/ipc-method-repository.hpp', 'wayfire/plugins/ipc/ipc-helpers.hpp', 'wayfire/plugins/ipc/ipc-activator.hpp'], subdir: 'wayfire/plugins/ipc') wayfire-0.10.0/plugins/ipc/ipc.hpp0000664000175000017500000000316615053502647016657 0ustar dkondordkondor#pragma once #include #include #include #include #include "wayfire/plugins/ipc/ipc-method-repository.hpp" namespace wf { namespace ipc { /** * Represents a single connected client to the IPC socket. */ class server_t; class client_t : public client_interface_t { public: client_t(server_t *server, int client_fd); ~client_t(); bool send_json(wf::json_t json) override; private: int fd; wl_event_source *source; server_t *ipc; int current_buffer_valid = 0; std::vector buffer; int read_up_to(int n, int *available); /** Handle incoming data on the socket */ std::function handle_fd_activity; void handle_fd_incoming(uint32_t); }; /** * The IPC server is a singleton object accessed via shared_data::ref_ptr_t. * It represents the IPC socket used for communication with clients. */ class server_t { public: server_t(); void init(std::string socket_path); ~server_t(); private: friend class client_t; wf::shared_data::ref_ptr_t method_repository; void handle_incoming_message(client_t *client, wf::json_t message); void client_disappeared(client_t *client); int fd = -1; /** * Setup a socket at the given address, and set it as CLOEXEC and non-blocking. */ int setup_socket(const char *address); sockaddr_un saddr; wl_event_source *source; std::vector> clients; std::function accept_new_client; void do_accept_new_client(); }; } } wayfire-0.10.0/plugins/ipc/stipc.cpp0000664000175000017500000005034215053502647017217 0ustar dkondordkondor#include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/util.hpp" #include #include #include #include #include #include "src/view/view-impl.hpp" #include #include #define WAYFIRE_PLUGIN #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include } #include #include static void locate_wayland_backend(wlr_backend *backend, void *data) { if (wlr_backend_is_wl(backend)) { wlr_backend **result = (wlr_backend**)data; *result = backend; } } namespace wf { static const struct wlr_pointer_impl pointer_impl = { .name = "stipc-pointer", }; static void led_update(wlr_keyboard *keyboard, uint32_t leds) {} static const struct wlr_keyboard_impl keyboard_impl = { .name = "stipc-keyboard", .led_update = led_update, }; static const struct wlr_touch_impl touch_impl = { .name = "stipc-touch-device", }; static const struct wlr_tablet_impl tablet_impl = { .name = "stipc-tablet", }; static const struct wlr_tablet_pad_impl tablet_pad_impl = { .name = "stipc-tablet-pad", }; static void init_wlr_tool(wlr_tablet_tool *tablet_tool) { std::memset(tablet_tool, 0, sizeof(*tablet_tool)); tablet_tool->type = WLR_TABLET_TOOL_TYPE_PEN; tablet_tool->pressure = true; wl_signal_init(&tablet_tool->events.destroy); } class headless_input_backend_t { public: wlr_backend *backend; wlr_pointer pointer; wlr_keyboard keyboard; wlr_touch touch; wlr_tablet tablet; wlr_tablet_tool tablet_tool; wlr_tablet_pad tablet_pad; headless_input_backend_t() { auto& core = wf::get_core(); backend = wlr_headless_backend_create(core.ev_loop); wlr_multi_backend_add(core.backend, backend); wlr_pointer_init(&pointer, &pointer_impl, "stipc_pointer"); wlr_keyboard_init(&keyboard, &keyboard_impl, "stipc_keyboard"); wlr_touch_init(&touch, &touch_impl, "stipc_touch"); wlr_tablet_init(&tablet, &tablet_impl, "stipc_tablet_tool"); wlr_tablet_pad_init(&tablet_pad, &tablet_pad_impl, "stipc_tablet_pad"); init_wlr_tool(&tablet_tool); wl_signal_emit_mutable(&backend->events.new_input, &pointer.base); wl_signal_emit_mutable(&backend->events.new_input, &keyboard.base); wl_signal_emit_mutable(&backend->events.new_input, &touch.base); wl_signal_emit_mutable(&backend->events.new_input, &tablet.base); wl_signal_emit_mutable(&backend->events.new_input, &tablet_pad.base); if (core.get_current_state() >= compositor_state_t::RUNNING) { wlr_backend_start(backend); } wl_signal_emit_mutable(&tablet_pad.events.attach_tablet, &tablet_tool); } ~headless_input_backend_t() { auto& core = wf::get_core(); wlr_pointer_finish(&pointer); wlr_keyboard_finish(&keyboard); wlr_touch_finish(&touch); wlr_tablet_finish(&tablet); wlr_tablet_pad_finish(&tablet_pad); wlr_multi_backend_remove(core.backend, backend); wlr_backend_destroy(backend); } void do_key(uint32_t key, wl_keyboard_key_state state) { wlr_keyboard_key_event ev; ev.keycode = key; ev.state = state; ev.update_state = true; ev.time_msec = get_current_time(); wlr_keyboard_notify_key(&keyboard, &ev); } void do_button(uint32_t button, wl_pointer_button_state state) { wlr_pointer_button_event ev; ev.pointer = &pointer; ev.button = button; ev.state = state; ev.time_msec = get_current_time(); wl_signal_emit(&pointer.events.button, &ev); wl_signal_emit(&pointer.events.frame, NULL); } void do_motion(double x, double y) { auto cursor = wf::get_core().get_cursor_position(); wlr_pointer_motion_event ev; ev.pointer = &pointer; ev.time_msec = get_current_time(); ev.delta_x = ev.unaccel_dx = x - cursor.x; ev.delta_y = ev.unaccel_dy = y - cursor.y; wl_signal_emit(&pointer.events.motion, &ev); wl_signal_emit(&pointer.events.frame, NULL); } void convert_xy_to_relative(double *x, double *y) { auto layout = wf::get_core().output_layout->get_handle(); wlr_box box; wlr_output_layout_get_box(layout, NULL, &box); *x = 1.0 * (*x - box.x) / box.width; *y = 1.0 * (*y - box.y) / box.height; } void do_touch(int finger, double x, double y) { convert_xy_to_relative(&x, &y); if (!wf::get_core().get_touch_state().fingers.count(finger)) { wlr_touch_down_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; ev.touch_id = finger; wl_signal_emit(&touch.events.down, &ev); } else { wlr_touch_motion_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; ev.touch_id = finger; wl_signal_emit(&touch.events.motion, &ev); } wl_signal_emit(&touch.events.frame, NULL); } void do_touch_release(int finger) { wlr_touch_up_event ev; ev.touch = &touch; ev.time_msec = get_current_time(); ev.touch_id = finger; wl_signal_emit(&touch.events.up, &ev); wl_signal_emit(&touch.events.frame, NULL); } void do_tablet_proximity(bool prox_in, double x, double y) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_proximity_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.state = prox_in ? WLR_TABLET_TOOL_PROXIMITY_IN : WLR_TABLET_TOOL_PROXIMITY_OUT; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; wl_signal_emit(&tablet.events.proximity, &ev); } void do_tablet_tip(bool tip_down, double x, double y) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_tip_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.state = tip_down ? WLR_TABLET_TOOL_TIP_DOWN : WLR_TABLET_TOOL_TIP_UP; ev.time_msec = get_current_time(); ev.x = x; ev.y = y; wl_signal_emit(&tablet.events.tip, &ev); } void do_tablet_button(uint32_t button, bool down) { wlr_tablet_tool_button_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.button = button; ev.state = down ? WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED; ev.time_msec = get_current_time(); wl_signal_emit(&tablet.events.button, &ev); } void do_tablet_axis(double x, double y, double pressure) { convert_xy_to_relative(&x, &y); wlr_tablet_tool_axis_event ev; ev.tablet = &tablet; ev.tool = &tablet_tool; ev.time_msec = get_current_time(); ev.pressure = pressure; ev.x = x; ev.y = y; ev.updated_axes = WLR_TABLET_TOOL_AXIS_X | WLR_TABLET_TOOL_AXIS_Y | WLR_TABLET_TOOL_AXIS_PRESSURE; wl_signal_emit(&tablet.events.axis, &ev); } void do_tablet_pad_button(uint32_t button, bool state) { wlr_tablet_pad_button_event ev; ev.group = 0; ev.button = button; ev.state = state ? WLR_BUTTON_PRESSED : WLR_BUTTON_RELEASED; ev.mode = 0; ev.time_msec = get_current_time(); wl_signal_emit(&tablet_pad.events.button, &ev); } headless_input_backend_t(const headless_input_backend_t&) = delete; headless_input_backend_t(headless_input_backend_t&&) = delete; headless_input_backend_t& operator =(const headless_input_backend_t&) = delete; headless_input_backend_t& operator =(headless_input_backend_t&&) = delete; }; class stipc_plugin_t : public wf::plugin_interface_t { wf::shared_data::ref_ptr_t method_repository; public: void init() override { input = std::make_unique(); method_repository->register_method("stipc/create_wayland_output", create_wayland_output); method_repository->register_method("stipc/destroy_wayland_output", destroy_wayland_output); method_repository->register_method("stipc/feed_key", feed_key); method_repository->register_method("stipc/feed_button", feed_button); method_repository->register_method("stipc/move_cursor", move_cursor); method_repository->register_method("stipc/run", run); method_repository->register_method("stipc/ping", ping); method_repository->register_method("stipc/get_display", get_display); method_repository->register_method("stipc/layout_views", layout_views); method_repository->register_method("stipc/touch", do_touch); method_repository->register_method("stipc/touch_release", do_touch_release); method_repository->register_method("stipc/tablet/tool_proximity", do_tool_proximity); method_repository->register_method("stipc/tablet/tool_button", do_tool_button); method_repository->register_method("stipc/tablet/tool_axis", do_tool_axis); method_repository->register_method("stipc/tablet/tool_tip", do_tool_tip); method_repository->register_method("stipc/tablet/pad_button", do_pad_button); method_repository->register_method("stipc/delay_next_tx", delay_next_tx); method_repository->register_method("stipc/get_xwayland_pid", get_xwayland_pid); method_repository->register_method("stipc/get_xwayland_display", get_xwayland_display); } bool is_unloadable() override { return false; } ipc::method_callback layout_views = [] (wf::json_t data) -> wf::json_t { auto views = wf::get_core().get_all_views(); if (!data.has_member("views") || !data["views"].is_array()) { return wf::ipc::json_error("Views not specified"); } for (size_t i = 0; i < data["views"].size(); i++) { const auto& v = data["views"][i]; auto id = wf::ipc::json_get_uint64(v, "id"); int x = wf::ipc::json_get_int64(v, "x"); int y = wf::ipc::json_get_int64(v, "y"); int width = wf::ipc::json_get_int64(v, "width"); int height = wf::ipc::json_get_int64(v, "height"); auto output = wf::ipc::json_get_optional_string(v, "output"); auto it = std::find_if(views.begin(), views.end(), [&] (auto& view) { return view->get_id() == uint64_t(v["id"]); }); if (it == views.end()) { return wf::ipc::json_error("Could not find view with id " + std::to_string(id)); } auto toplevel = toplevel_cast(*it); if (!toplevel) { return wf::ipc::json_error("View is not toplevel view id " + std::to_string(id)); } if (output.has_value()) { auto wo = wf::get_core().output_layout->find_output(output.value()); if (!wo) { return wf::ipc::json_error("Unknown output " + (std::string)output.value()); } move_view_to_output(toplevel, wo, false); } wf::geometry_t g{x, y, width, height}; toplevel->set_geometry(g); } return wf::ipc::json_ok(); }; ipc::method_callback create_wayland_output = [] (wf::json_t) { auto backend = wf::get_core().backend; wlr_backend *wayland_backend = NULL; wlr_multi_for_each_backend(backend, locate_wayland_backend, &wayland_backend); if (!wayland_backend) { return wf::ipc::json_error("Wayfire is not running in nested wayland mode!"); } wlr_wl_output_create(wayland_backend); return wf::ipc::json_ok(); }; ipc::method_callback destroy_wayland_output = [] (wf::json_t data) -> json_t { auto output_str = wf::ipc::json_get_string(data, "output"); wlr_output *output = NULL; for (auto& wo : wf::get_core().output_layout->get_current_configuration()) { if (output_str == wo.first->name) { output = wo.first; } } if (!output) { return wf::ipc::json_error("Could not find output: \"" + output_str + "\""); } if (!wlr_output_is_wl(output)) { return wf::ipc::json_error("Output is not a wayland output!"); } wlr_output_destroy(output); return wf::ipc::json_ok(); }; struct key_t { bool modifier; int code; }; std::variant parse_key(wf::json_t data) { auto combo = wf::ipc::json_get_string(data, "combo"); if (combo.size() < 4) { return std::string("Missing or wrong json type for `combo`!"); } // Check super modifier bool modifier = false; if (combo.substr(0, 2) == "S-") { modifier = true; combo = combo.substr(2); } int key = libevdev_event_code_from_name(EV_KEY, combo.c_str()); if (key == -1) { return std::string("Failed to parse combo \"" + combo + "\""); } return key_t{modifier, key}; } ipc::method_callback feed_key = [=] (wf::json_t data) { auto key = wf::ipc::json_get_string(data, "key"); auto state = wf::ipc::json_get_bool(data, "state"); int keycode = libevdev_event_code_from_name(EV_KEY, key.c_str()); if (keycode == -1) { return wf::ipc::json_error("Failed to parse evdev key \"" + key + "\""); } if (state) { input->do_key(keycode, WL_KEYBOARD_KEY_STATE_PRESSED); } else { input->do_key(keycode, WL_KEYBOARD_KEY_STATE_RELEASED); } return wf::ipc::json_ok(); }; ipc::method_callback feed_button = [=] (wf::json_t data) { auto result = parse_key(data); auto button = std::get_if(&result); if (!button) { return wf::ipc::json_error(std::get(result)); } auto mode = wf::ipc::json_get_string(data, "mode"); if ((mode == "press") || (mode == "full")) { if (button->modifier) { input->do_key(KEY_LEFTMETA, WL_KEYBOARD_KEY_STATE_PRESSED); } input->do_button(button->code, WL_POINTER_BUTTON_STATE_PRESSED); } if ((mode == "release") || (mode == "full")) { input->do_button(button->code, WL_POINTER_BUTTON_STATE_RELEASED); if (button->modifier) { input->do_key(KEY_LEFTMETA, WL_KEYBOARD_KEY_STATE_RELEASED); } } return wf::ipc::json_ok(); }; ipc::method_callback move_cursor = [=] (wf::json_t data) { auto x = wf::ipc::json_get_double(data, "x"); auto y = wf::ipc::json_get_double(data, "y"); input->do_motion(x, y); return wf::ipc::json_ok(); }; ipc::method_callback do_touch = [=] (wf::json_t data) { auto finger = wf::ipc::json_get_int64(data, "finger"); auto x = wf::ipc::json_get_double(data, "x"); auto y = wf::ipc::json_get_double(data, "y"); input->do_touch(finger, x, y); return wf::ipc::json_ok(); }; ipc::method_callback do_touch_release = [=] (wf::json_t data) { auto finger = wf::ipc::json_get_int64(data, "finger"); input->do_touch_release(finger); return wf::ipc::json_ok(); }; ipc::method_callback run = [=] (wf::json_t data) { auto cmd = wf::ipc::json_get_string(data, "cmd"); auto response = wf::ipc::json_ok(); pid_t pid = wf::get_core().run(cmd); if (!pid) { return wf::ipc::json_error("failed to run command"); } response["pid"] = pid; return response; }; ipc::method_callback ping = [=] (wf::json_t data) { return wf::ipc::json_ok(); }; ipc::method_callback get_display = [=] (wf::json_t data) { wf::json_t dpy; dpy["wayland"] = wf::get_core().wayland_display; dpy["xwayland"] = wf::get_core().get_xwayland_display(); return dpy; }; ipc::method_callback do_tool_proximity = [=] (wf::json_t data) { auto proximity_in = wf::ipc::json_get_bool(data, "proximity_in"); auto x = wf::ipc::json_get_double(data, "x"); auto y = wf::ipc::json_get_double(data, "y"); input->do_tablet_proximity(proximity_in, x, y); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_button = [=] (wf::json_t data) { auto button = wf::ipc::json_get_int64(data, "button"); auto state = wf::ipc::json_get_bool(data, "state"); input->do_tablet_button(button, state); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_axis = [=] (wf::json_t data) { auto x = wf::ipc::json_get_double(data, "x"); auto y = wf::ipc::json_get_double(data, "y"); auto pressure = wf::ipc::json_get_double(data, "pressure"); input->do_tablet_axis(x, y, pressure); return wf::ipc::json_ok(); }; ipc::method_callback do_tool_tip = [=] (wf::json_t data) { auto x = wf::ipc::json_get_double(data, "x"); auto y = wf::ipc::json_get_double(data, "y"); auto state = wf::ipc::json_get_bool(data, "state"); input->do_tablet_tip(state, x, y); return wf::ipc::json_ok(); }; ipc::method_callback do_pad_button = [=] (wf::json_t data) { auto button = wf::ipc::json_get_int64(data, "button"); auto state = wf::ipc::json_get_bool(data, "state"); input->do_tablet_pad_button(button, state); return wf::ipc::json_ok(); }; class never_ready_object : public wf::txn::transaction_object_t { public: void commit() override {} void apply() override {} std::string stringify() const override { return "force-timeout"; } }; int delay_counter = 0; wf::signal::connection_t on_new_tx = [=] (wf::txn::new_transaction_signal *ev) { ev->tx->add_object(std::make_shared()); delay_counter--; if (delay_counter <= 0) { on_new_tx.disconnect(); } }; ipc::method_callback delay_next_tx = [=] (wf::json_t) { if (!on_new_tx.is_connected()) { wf::get_core().tx_manager->connect(&on_new_tx); } ++delay_counter; return wf::ipc::json_ok(); }; ipc::method_callback get_xwayland_pid = [=] (wf::json_t) { auto response = wf::ipc::json_ok(); response["pid"] = wf::xwayland_get_pid(); return response; }; ipc::method_callback get_xwayland_display = [=] (wf::json_t) { auto response = wf::ipc::json_ok(); response["display"] = wf::xwayland_get_display(); return response; }; std::unique_ptr input; }; } DECLARE_WAYFIRE_PLUGIN(wf::stipc_plugin_t); wayfire-0.10.0/plugins/grid/0000775000175000017500000000000015053502647015537 5ustar dkondordkondorwayfire-0.10.0/plugins/grid/wayfire/0000775000175000017500000000000015053502647017205 5ustar dkondordkondorwayfire-0.10.0/plugins/grid/wayfire/plugins/0000775000175000017500000000000015053502647020666 5ustar dkondordkondorwayfire-0.10.0/plugins/grid/wayfire/plugins/grid.hpp0000664000175000017500000000365715053502647022337 0ustar dkondordkondor#pragma once #include #include #include namespace wf { namespace grid { /** * name: request * on: core * when: Emitted before move renders a grid indicator and sets the slot. * carried_out: true if a plugin can handle move request to grid. */ struct grid_request_signal { /* True if a plugin handled this signal */ bool carried_out = false; }; /** * The slot where a view can be placed with grid. * BL = bottom-left, TR = top-right, etc. */ enum slot_t { SLOT_NONE = 0, SLOT_BL = 1, SLOT_BOTTOM = 2, SLOT_BR = 3, SLOT_LEFT = 4, SLOT_CENTER = 5, SLOT_RIGHT = 6, SLOT_TL = 7, SLOT_TOP = 8, SLOT_TR = 9, }; /* * 7 8 9 * 4 5 6 * 1 2 3 */ inline uint32_t get_tiled_edges_for_slot(uint32_t slot) { if (slot == 0) { return 0; } uint32_t edges = wf::TILED_EDGES_ALL; if (slot % 3 == 0) { edges &= ~WLR_EDGE_LEFT; } if (slot % 3 == 1) { edges &= ~WLR_EDGE_RIGHT; } if (slot <= 3) { edges &= ~WLR_EDGE_TOP; } if (slot >= 7) { edges &= ~WLR_EDGE_BOTTOM; } return edges; } inline uint32_t get_slot_from_tiled_edges(uint32_t edges) { for (int slot = 0; slot <= 9; slot++) { if (get_tiled_edges_for_slot(slot) == edges) { return slot; } } return 0; } /* * 7 8 9 * 4 5 6 * 1 2 3 * */ inline wf::geometry_t get_slot_dimensions(wf::output_t *output, int n) { auto area = output->workarea->get_workarea(); int w2 = area.width / 2; int h2 = area.height / 2; if (n % 3 == 1) { area.width = w2; } if (n % 3 == 0) { area.width = w2, area.x += w2; } if (n >= 7) { area.height = h2; } else if (n <= 3) { area.height = h2, area.y += h2; } return area; } } } wayfire-0.10.0/plugins/grid/wayfire/plugins/crossfade.hpp0000664000175000017500000002344115053502647023354 0ustar dkondordkondor#pragma once #include #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace wf { namespace grid { /** * A transformer used for a simple crossfade + scale animation. * * It fades out the scaled contents from original_buffer, and fades in the * current contents of the view, based on the alpha value in the transformer. */ class crossfade_node_t : public scene::view_2d_transformer_t { public: wayfire_toplevel_view view; // The contents of the view before the change. wf::auxilliary_buffer_t original_buffer; public: wf::geometry_t displayed_geometry; double overlay_alpha; crossfade_node_t(wayfire_toplevel_view view) : view_2d_transformer_t(view) { displayed_geometry = view->get_geometry(); this->view = view; auto root_node = view->get_surface_root_node(); const wf::geometry_t bbox = root_node->get_bounding_box(); const wf::geometry_t g = view->get_geometry(); const float scale = view->get_output()->handle->scale; original_buffer.allocate(wf::dimensions(g), scale); wf::render_target_t target{original_buffer}; target.geometry = view->get_geometry(); target.scale = view->get_output()->handle->scale; std::vector instances; root_node->gen_render_instances(instances, [] (auto) {}, view->get_output()); render_pass_params_t params; params.background_color = {0, 0, 0, 0}; params.damage = bbox; params.target = target; params.instances = &instances; params.flags = RPASS_CLEAR_BACKGROUND; wf::render_pass_t::run(params); } std::string stringify() const override { return "crossfade"; } float get_scale_x() const override { auto current_geometry = view->get_geometry(); return 1.0 * displayed_geometry.width / current_geometry.width; } float get_scale_y() const override { auto current_geometry = view->get_geometry(); return 1.0 * displayed_geometry.height / current_geometry.height; } float get_translation_x() const override { auto current_geometry = view->get_geometry(); return (displayed_geometry.x + displayed_geometry.width / 2.0) - (current_geometry.x + current_geometry.width / 2.0); } float get_translation_y() const override { auto current_geometry = view->get_geometry(); return (displayed_geometry.y + displayed_geometry.height / 2.0) - (current_geometry.y + current_geometry.height / 2.0); } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override; }; class crossfade_render_instance_t : public scene::render_instance_t { std::shared_ptr self; wf::signal::connection_t on_damage; public: crossfade_render_instance_t(crossfade_node_t *self, scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); scene::damage_callback push_damage_child = [=] (const wf::region_t&) { // XXX: we could attempt to calculate a meaningful damage, but // we update on each frame anyway so .. push_damage(self->get_bounding_box()); }; on_damage = [=] (auto) { push_damage(self->get_bounding_box()); }; self->connect(&on_damage); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } void render(const wf::scene::render_instruction_t& data) override { double ra; const double N = 2; if (self->overlay_alpha < 0.5) { ra = std::pow(self->overlay_alpha * 2, 1.0 / N) / 2.0; } else { ra = std::pow((self->overlay_alpha - 0.5) * 2, N) / 2.0 + 0.5; } wf::texture_t tex = wf::texture_t{self->original_buffer.get_texture()}; data.pass->add_texture(tex, data.target, self->displayed_geometry, data.damage, 1.0 - ra); } }; inline void crossfade_node_t::gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) { // Step 2: render overlay (instances are sorted front-to-back) instances.push_back( std::make_unique(this, push_damage)); // Step 1: render the scaled view scene::view_2d_transformer_t::gen_render_instances( instances, push_damage, shown_on); } /** * A class used for crossfade/wobbly animation of a change in a view's geometry. */ class grid_animation_t : public wf::custom_data_t { public: enum type_t { CROSSFADE, WOBBLY, NONE, }; /** * Create an animation object for the given view. * * @param type Indicates which animation method to use. * @param duration Indicates the duration of the animation (only for crossfade) */ grid_animation_t(wayfire_toplevel_view view, type_t type, wf::option_sptr_t duration) { this->view = view; this->output = view->get_output(); this->type = type; this->animation = wf::geometry_animation_t{duration}; output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); output->connect(&on_disappear); } /** * Set the view geometry and start animating towards that target using the * animation type. * * @param geometry The target geometry. * @param target_edges The tiled edges the view should have at the end of the * animation. If target_edges are -1, then the tiled edges of the view will * not be changed. */ void adjust_target_geometry(wf::geometry_t geometry, int32_t target_edges, wf::txn::transaction_uptr& tx) { // Apply the desired attributes to the view const auto& set_state = [&] () { if (target_edges >= 0) { wf::get_core().default_wm->update_last_windowed_geometry(view); view->toplevel()->pending().fullscreen = false; view->toplevel()->pending().tiled_edges = target_edges; } view->toplevel()->pending().geometry = geometry; tx->add_object(view->toplevel()); }; if (type != CROSSFADE) { /* Order is important here: first we set the view geometry, and * after that we set the snap request. Otherwise the wobbly plugin * will think the view actually moved */ set_state(); if (type == WOBBLY) { activate_wobbly(view); } return destroy(); } // Crossfade animation original = view->get_geometry(); animation.set_start(original); animation.set_end(geometry); animation.start(); // Add crossfade transformer ensure_view_transformer( view, wf::TRANSFORMER_2D, view); // Start the transition set_state(); } void adjust_target_geometry(wf::geometry_t geometry, int32_t target_edges) { auto tx = wf::txn::transaction_t::create(); adjust_target_geometry(geometry, target_edges, tx); wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } ~grid_animation_t() { view->get_transformed_node()->rem_transformer(); output->render->rem_effect(&pre_hook); } grid_animation_t(const grid_animation_t &) = delete; grid_animation_t(grid_animation_t &&) = delete; grid_animation_t& operator =(const grid_animation_t&) = delete; grid_animation_t& operator =(grid_animation_t&&) = delete; protected: wf::effect_hook_t pre_hook = [=] () { if (!animation.running()) { return destroy(); } if (view->get_geometry() != original) { original = view->get_geometry(); animation.set_end(original); } auto tr = view->get_transformed_node()->get_transformer(); view->get_transformed_node()->begin_transform_update(); tr->displayed_geometry = animation; tr->overlay_alpha = animation.progress(); view->get_transformed_node()->end_transform_update(); }; void destroy() { view->erase_data(); } wf::geometry_t original; wayfire_toplevel_view view; wf::output_t *output; wf::signal::connection_t on_disappear = [=] (view_disappeared_signal *ev) { if (ev->view == view) { destroy(); } }; wf::geometry_animation_t animation; type_t type; }; } } wayfire-0.10.0/plugins/grid/meson.build0000664000175000017500000000074415053502647017706 0ustar dkondordkondorgrid_inc = include_directories('.') all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, wobbly_inc, grid_inc, ipc_include_dirs] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, json, plugin_pch_dep] shared_module('grid', ['grid.cpp'], include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_subdir('wayfire', install_dir: get_option('includedir')) wayfire-0.10.0/plugins/grid/grid.cpp0000664000175000017500000002105415053502647017172 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include "wayfire/plugin.hpp" #include "wayfire/signal-definitions.hpp" #include #include "wayfire/plugins/grid.hpp" #include "wayfire/plugins/crossfade.hpp" #include #include #include #include "wayfire/plugins/ipc/ipc-activator.hpp" #include "wayfire/signal-provider.hpp" const std::string grid_view_id = "grid-view"; class wf_grid_slot_data : public wf::custom_data_t { public: int slot; }; nonstd::observer_ptr ensure_grid_view(wayfire_toplevel_view view) { if (!view->has_data()) { wf::option_wrapper_t animation_type{"grid/type"}; wf::option_wrapper_t duration{"grid/duration"}; wf::grid::grid_animation_t::type_t type = wf::grid::grid_animation_t::NONE; if (animation_type.value() == "crossfade") { type = wf::grid::grid_animation_t::CROSSFADE; } else if (animation_type.value() == "wobbly") { type = wf::grid::grid_animation_t::WOBBLY; } view->store_data( std::make_unique(view, type, duration)); } return view->get_data(); } class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t<> { std::vector slots = {"unused", "bl", "b", "br", "l", "c", "r", "tl", "t", "tr"}; wf::ipc_activator_t bindings[10]; wf::ipc_activator_t restore{"grid/restore"}; wf::plugin_activation_data_t grab_interface{ .name = "grid", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP, }; wf::ipc_activator_t::handler_t handle_restore = [=] (wf::output_t *wo, wayfire_view view) { if (!wo->can_activate_plugin(&grab_interface)) { return false; } auto toplevel = toplevel_cast(view); if (!view) { return false; } wf::get_core().default_wm->tile_request(toplevel, 0); return true; }; public: void init() override { init_output_tracking(); restore.set_handler(handle_restore); for (int i = 1; i < 10; i++) { bindings[i].load_from_xml_option("grid/slot_" + slots[i]); bindings[i].set_handler([=] (wf::output_t *wo, wayfire_view view) { if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_DESKTOP)) { return false; } if (auto toplevel = toplevel_cast(view)) { handle_slot(toplevel, i); return true; } return false; }); } wf::get_core().connect(&grid_request_signal_cb); } wf::signal::connection_t grid_request_signal_cb = [=] (wf::grid::grid_request_signal *ev) { ev->carried_out = true; }; void handle_new_output(wf::output_t *output) override { output->connect(&on_workarea_changed); output->connect(&on_maximize_signal); output->connect(&on_fullscreen_signal); output->connect(&on_tiled); } void handle_output_removed(wf::output_t *output) override { // no-op } void fini() override { fini_output_tracking(); } bool can_adjust_view(wayfire_toplevel_view view) { const uint32_t req_actions = wf::VIEW_ALLOW_MOVE | wf::VIEW_ALLOW_RESIZE; const bool is_floating = (view->get_allowed_actions() & req_actions) == req_actions; return is_floating && (view->get_output() != nullptr) && view->toplevel()->pending().mapped; } void handle_slot(wayfire_toplevel_view view, int slot, wf::point_t delta = {0, 0}) { if (!can_adjust_view(view)) { return; } view->get_data_safe()->slot = slot; auto slot_geometry = wf::grid::get_slot_dimensions(view->get_output(), slot) + delta; ensure_grid_view(view)->adjust_target_geometry( slot_geometry, wf::grid::get_tiled_edges_for_slot(slot)); } wf::signal::connection_t on_workarea_changed = [=] (wf::workarea_changed_signal *ev) { for (auto& view : ev->output->wset()->get_views(wf::WSET_MAPPED_ONLY)) { auto data = view->get_data_safe(); /* Detect if the view was maximized outside of the grid plugin */ auto wm = view->get_pending_geometry(); if (view->pending_tiled_edges() && (wm.width == ev->old_workarea.width) && (wm.height == ev->old_workarea.height)) { data->slot = wf::grid::SLOT_CENTER; } if (!data->slot) { continue; } /* Workarea changed, and we have a view which is tiled into some slot. * We need to make sure it remains in its slot. So we calculate the * viewport of the view, and tile it there */ auto output_geometry = ev->output->get_relative_geometry(); int vx = std::floor(1.0 * wm.x / output_geometry.width); int vy = std::floor(1.0 * wm.y / output_geometry.height); handle_slot(view, data->slot, {vx *output_geometry.width, vy * output_geometry.height}); } }; wf::geometry_t adjust_for_workspace(std::shared_ptr wset, wf::geometry_t geometry, wf::point_t workspace) { auto delta_ws = workspace - wset->get_current_workspace(); auto scr_size = wset->get_last_output_geometry().value(); geometry.x += delta_ws.x * scr_size.width; geometry.y += delta_ws.y * scr_size.height; return geometry; } wf::signal::connection_t on_maximize_signal = [=] (wf::view_tile_request_signal *data) { if (data->carried_out || (data->desired_size.width <= 0) || !data->view->get_output() || !data->view->get_wset() || !can_adjust_view(data->view)) { return; } data->carried_out = true; uint32_t slot = wf::grid::get_slot_from_tiled_edges(data->edges); if (slot > 0) { data->desired_size = wf::grid::get_slot_dimensions(data->view->get_output(), slot); } data->view->get_data_safe()->slot = slot; ensure_grid_view(data->view)->adjust_target_geometry( adjust_for_workspace(data->view->get_wset(), data->desired_size, data->workspace), wf::grid::get_tiled_edges_for_slot(slot)); }; wf::signal::connection_t on_fullscreen_signal = [=] (wf::view_fullscreen_request_signal *data) { static const std::string fs_data_name = "grid-saved-fs"; if (data->carried_out || (data->desired_size.width <= 0) || !data->view->get_output() || !data->view->get_wset() || !can_adjust_view(data->view)) { return; } int32_t edges = -1; auto geom = data->desired_size; if (!data->state && data->view->has_data()) { uint32_t slot = data->view->get_data_safe()->slot; if (slot > 0) { geom = wf::grid::get_slot_dimensions(data->view->get_output(), slot); edges = wf::grid::get_tiled_edges_for_slot(slot); } } data->carried_out = true; ensure_grid_view(data->view)->adjust_target_geometry( adjust_for_workspace(data->view->get_wset(), geom, data->workspace), edges); }; wf::signal::connection_t on_tiled = [=] (wf::view_tiled_signal *ev) { if (!ev->view->has_data()) { return; } auto data = ev->view->get_data_safe(); if (ev->new_edges != wf::grid::get_tiled_edges_for_slot(data->slot)) { ev->view->erase_data(); } }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_grid); wayfire-0.10.0/plugins/meson.build0000664000175000017500000000445615053502647016765 0ustar dkondordkondorif get_option('custom_pch') # Add a workaround for https://github.com/mesonbuild/meson/issues/4350 # We create a PCH ourselves and manage it for all plugins. pch_file = meson.current_source_dir() / 'pch/plugin_pch.hpp' custom_pch_flags = ['@INPUT@', '-c', '-o', '@OUTPUT@', '-MD', '-MF', '@DEPFILE@', '-std=' + get_option('cpp_std'), '-pthread', '-fPIC'] + get_option('cpp_args') # Meson enables this unconditionally on everything not-macos and non-msvc custom_pch_flags += ['-D_FILE_OFFSET_BITS=64'] # Gather flags from the compiler ... if get_option('b_ndebug') == 'false' or (get_option('b_ndebug') == 'if-release' and get_option('buildtype') != 'release') custom_pch_flags += ['-D_GLIBCXX_ASSERTIONS=1'] else custom_pch_flags += ['-DNDEBUG'] endif custom_pch_flags += ['-O' + get_option('optimization')] if get_option('debug') custom_pch_flags += ['-g'] endif if has_asan custom_pch_flags += ['-fsanitize=address'] endif pch = custom_target('plugin_pch', input: pch_file, output: 'plugin_pch.hpp.gch', depfile: 'plugin_pch.hpp.d', command: cpp.cmd_array() + custom_pch_flags) fs = import('fs') if cpp.get_id() == 'clang' # In clang, everything is simple, we just tell the compiler which PCH file to use plugin_pch_arg = ['-include-pch', pch.full_path(), '-pthread'] elif cpp.get_id() == 'gcc' # GCC requires that the .gch and the .hpp file are in the same dir. fs.copyfile(pch_file) # copy to build dir where .gch is found plugin_pch_arg = ['-I' + fs.parent(pch.full_path()), '-include', fs.name(pch_file), '-pthread', '-fpch-preprocess'] else error('Unsupported compiler for custom pch: ' + cpp.get_id()) endif plugin_pch_dep = declare_dependency(sources: pch, compile_args: plugin_pch_arg) else plugin_pch_args = [] plugin_pch_dep = declare_dependency() endif wobbly_inc = include_directories('wobbly/') subdir('common') plugins = [ 'ipc', 'protocols', 'vswitch', 'wobbly', 'grid', 'decor', 'animate', 'cube', 'window-rules', 'blur', 'tile', 'wm-actions', 'scale', 'single_plugins', 'ipc-rules', ] devenv = environment() foreach plugin : plugins devenv.append('WAYFIRE_PLUGIN_PATH', meson.current_build_dir() + '/' + plugin) subdir(plugin) endforeach meson.add_devenv(devenv) wayfire-0.10.0/plugins/pch/0000775000175000017500000000000015053502647015364 5ustar dkondordkondorwayfire-0.10.0/plugins/pch/plugin_pch.hpp0000664000175000017500000000035615053502647020231 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include wayfire-0.10.0/plugins/animate/0000775000175000017500000000000015053502647016230 5ustar dkondordkondorwayfire-0.10.0/plugins/animate/spin.hpp0000664000175000017500000000605315053502647017716 0ustar dkondordkondor/* * The MIT License (MIT) * * Copyright (c) 2024 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "animate.hpp" #include #include #include #include #include wf::option_wrapper_t spin_rotations{"animate/spin_rotations"}; namespace wf { namespace spin { static const std::string spin_transformer_name = "spin-transformer"; using namespace wf::animation; class spin_animation_t : public duration_t { public: using duration_t::duration_t; }; class spin_animation : public animate::animation_base_t { wayfire_view view; animate::animation_type type; wf::spin::spin_animation_t progression; public: void init(wayfire_view view, wf::animation_description_t dur, animate::animation_type type) override { this->view = view; this->type = type; this->progression = wf::spin::spin_animation_t(wf::create_option(dur)); if (type & WF_ANIMATE_HIDING_ANIMATION) { this->progression.reverse(); } this->progression.start(); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, spin_transformer_name); } bool step() override { auto transform = view->get_transformed_node() ->get_transformer(spin_transformer_name); auto progress = this->progression.progress(); transform->alpha = progress; transform->angle = progress * M_PI * 2.0 * int(spin_rotations); transform->scale_x = 0.01 + progress * 0.99; transform->scale_y = 0.01 + progress * 0.99; return progression.running(); } void reverse() override { this->progression.reverse(); } ~spin_animation() { view->get_transformed_node()->rem_transformer(spin_transformer_name); } }; } } wayfire-0.10.0/plugins/animate/basic_animations.hpp0000664000175000017500000001147615053502647022255 0ustar dkondordkondor#include "animate.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include class fade_animation : public wf::animate::animation_base_t { wayfire_view view; float start = 0, end = 1; wf::animation::simple_animation_t progression; std::string name; public: void init(wayfire_view view, wf::animation_description_t dur, wf::animate::animation_type type) override { this->view = view; this->progression = wf::animation::simple_animation_t(wf::create_option(dur)); this->progression.animate(start, end); if (type & WF_ANIMATE_HIDING_ANIMATION) { this->progression.flip(); } name = "animation-fade-" + std::to_string(type); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, name); } bool step() override { auto transform = view->get_transformed_node() ->get_transformer(name); transform->alpha = this->progression; return progression.running(); } void reverse() override { this->progression.reverse(); } ~fade_animation() { view->get_transformed_node()->rem_transformer(name); } }; using namespace wf::animation; class zoom_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t alpha{*this}; timed_transition_t zoom{*this}; timed_transition_t offset_x{*this}; timed_transition_t offset_y{*this}; }; class zoom_animation : public fade_animation::animation_base_t { wayfire_view view; zoom_animation_t progression; std::string name; public: void init(wayfire_view view, wf::animation_description_t dur, wf::animate::animation_type type) override { this->view = view; this->progression = zoom_animation_t(wf::create_option(dur)); this->progression.alpha = wf::animation::timed_transition_t( this->progression, 0, 1); this->progression.zoom = wf::animation::timed_transition_t( this->progression, 1. / 3, 1); this->progression.offset_x = wf::animation::timed_transition_t( this->progression, 0, 0); this->progression.offset_y = wf::animation::timed_transition_t( this->progression, 0, 0); this->progression.start(); if (type & WF_ANIMATE_MINIMIZE_STATE_ANIMATION) { auto toplevel = wf::toplevel_cast(view); wf::dassert(toplevel != nullptr, "We cannot minimize non-toplevel views!"); auto hint = toplevel->get_minimize_hint(); if ((hint.width > 0) && (hint.height > 0)) { int hint_cx = hint.x + hint.width / 2; int hint_cy = hint.y + hint.height / 2; auto bbox = toplevel->get_geometry(); int view_cx = bbox.x + bbox.width / 2; int view_cy = bbox.y + bbox.height / 2; progression.offset_x.set(1.0 * hint_cx - view_cx, 0); progression.offset_y.set(1.0 * hint_cy - view_cy, 0); if ((bbox.width > 0) && (bbox.height > 0)) { double scale_x = 1.0 * hint.width / bbox.width; double scale_y = 1.0 * hint.height / bbox.height; progression.zoom.set(std::min(scale_x, scale_y), 1); } } } if (type & WF_ANIMATE_HIDING_ANIMATION) { progression.alpha.flip(); progression.zoom.flip(); progression.offset_x.flip(); progression.offset_y.flip(); } name = "animation-zoom-" + std::to_string(type); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, name); } bool step() override { auto our_transform = view->get_transformed_node() ->get_transformer(name); float c = this->progression.zoom; our_transform->alpha = std::min(1.0f, (float)this->progression.alpha); our_transform->scale_x = c; our_transform->scale_y = c; our_transform->translation_x = this->progression.offset_x; our_transform->translation_y = this->progression.offset_y; return this->progression.running(); } void reverse() override { this->progression.reverse(); } ~zoom_animation() { view->get_transformed_node()->rem_transformer(name); } }; wayfire-0.10.0/plugins/animate/system_fade.hpp0000664000175000017500000000274515053502647021254 0ustar dkondordkondor#ifndef SYSTEM_FADE_HPP #define SYSTEM_FADE_HPP #include #include #include #include #include /* animates wake from suspend/startup by fading in the whole output */ class wf_system_fade { wf::animation::simple_animation_t progression; wf::output_t *output; wf::effect_hook_t damage_hook, render_hook; public: wf_system_fade(wf::output_t *out, wf::animation_description_t dur) : progression(wf::create_option(dur)), output(out) { damage_hook = [=] () { output->render->damage_whole(); }; render_hook = [=] () { render(); }; output->render->add_effect(&damage_hook, wf::OUTPUT_EFFECT_PRE); output->render->add_effect(&render_hook, wf::OUTPUT_EFFECT_OVERLAY); output->render->set_redraw_always(); this->progression.animate(1, 0); } void render() { wf::color_t color{0, 0, 0, this->progression}; auto fb = output->render->get_target_framebuffer(); output->render->get_current_pass()->add_rect(color, fb, fb.geometry, fb.geometry); if (!progression.running()) { finish(); } } void finish() { output->render->rem_effect(&damage_hook); output->render->rem_effect(&render_hook); output->render->set_redraw_always(false); delete this; } }; #endif wayfire-0.10.0/plugins/animate/animate.hpp0000664000175000017500000000370615053502647020365 0ustar dkondordkondor#ifndef ANIMATE_H_ #define ANIMATE_H_ #include #include #include #define WF_ANIMATE_HIDING_ANIMATION (1 << 0) #define WF_ANIMATE_SHOWING_ANIMATION (1 << 1) #define WF_ANIMATE_MAP_STATE_ANIMATION (1 << 2) #define WF_ANIMATE_MINIMIZE_STATE_ANIMATION (1 << 3) namespace wf { namespace animate { enum animation_type { ANIMATION_TYPE_MAP = WF_ANIMATE_SHOWING_ANIMATION | WF_ANIMATE_MAP_STATE_ANIMATION, ANIMATION_TYPE_UNMAP = WF_ANIMATE_HIDING_ANIMATION | WF_ANIMATE_MAP_STATE_ANIMATION, ANIMATION_TYPE_MINIMIZE = WF_ANIMATE_HIDING_ANIMATION | WF_ANIMATE_MINIMIZE_STATE_ANIMATION, ANIMATION_TYPE_RESTORE = WF_ANIMATE_SHOWING_ANIMATION | WF_ANIMATE_MINIMIZE_STATE_ANIMATION, }; class animation_base_t { public: virtual void init(wayfire_view view, wf::animation_description_t duration, animation_type type) {} /** * @return True if the animation should continue for at least one more frame. */ virtual bool step() { return false; } /** * Reverse the animation direction (hiding -> showing, showing -> hiding) */ virtual void reverse() {} virtual ~animation_base_t() = default; }; struct effect_description_t { std::function()> generator; std::function()> default_duration; }; /** * The effects registry holds a list of all available animation effects. * Plugins can access the effects registry via ref_ptr_t helper in wayfire/plugins/common/shared-core-data.hpp * They may add/remove their own effects. */ class animate_effects_registry_t { public: void register_effect(std::string name, effect_description_t effect) { effects[name] = effect; } void unregister_effect(std::string name) { effects.erase(name); } std::map effects; }; } } #endif wayfire-0.10.0/plugins/animate/zap.hpp0000664000175000017500000000630615053502647017540 0ustar dkondordkondor/* * The MIT License (MIT) * * Copyright (c) 2024 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "animate.hpp" #include #include #include #include #include namespace wf { namespace zap { static const std::string zap_transformer_name = "zap-transformer"; using namespace wf::animation; class zap_animation_t : public duration_t { public: using duration_t::duration_t; }; class zap_animation : public animate::animation_base_t { wayfire_view view; animate::animation_type type; wf::zap::zap_animation_t progression; public: void init(wayfire_view view, wf::animation_description_t dur, animate::animation_type type) override { this->view = view; this->type = type; this->progression = wf::zap::zap_animation_t(wf::create_option(dur)); if (type & WF_ANIMATE_HIDING_ANIMATION) { this->progression.reverse(); } this->progression.start(); auto tr = std::make_shared(view); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL, zap_transformer_name); } bool step() override { auto transform = view->get_transformed_node() ->get_transformer(zap_transformer_name); auto progress = this->progression.progress(); auto progress_pt_one = std::clamp(progress, 0.0, 1.0 / 3.0) * 3.0; auto progress_pt_two = (std::clamp(progress, 1.0 / 3.0, (1.0 / 3.0) * 2.0) - 1.0 / 3.0) * 3.0; auto progress_pt_three = (std::clamp(progress, (1.0 / 3.0) * 2.0, 1.0) - (1.0 / 3.0) * 2.0) * 3.0; transform->alpha = progress_pt_one; transform->scale_x = 0.01 + progress_pt_two * 0.99; transform->scale_y = 0.01 + progress_pt_three * 0.99; return progression.running(); } void reverse() override { this->progression.reverse(); } ~zap_animation() { view->get_transformed_node()->rem_transformer(zap_transformer_name); } }; } } wayfire-0.10.0/plugins/animate/unmapped-view-node.hpp0000664000175000017500000000340115053502647022443 0ustar dkondordkondor#pragma once #include "wayfire/geometry.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include namespace wf { class unmapped_view_snapshot_node : public wf::scene::node_t { wf::auxilliary_buffer_t snapshot; wf::dimensions_t snapshot_logical_size; std::weak_ptr _view; public: unmapped_view_snapshot_node(wayfire_view view) : node_t(false) { view->take_snapshot(snapshot); snapshot_logical_size = wf::dimensions(view->get_surface_root_node()->get_bounding_box()); _view = view->weak_from_this(); } wf::geometry_t get_bounding_box() override { if (auto view = _view.lock()) { auto current_bbox = view->get_surface_root_node()->get_bounding_box(); return wf::construct_box(wf::origin(current_bbox), snapshot_logical_size); } return {0, 0, 0, 0}; } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { instances.push_back(std::make_unique(this, push_damage, shown_on)); } std::string stringify() const override { return "unmapped-view-snapshot-node " + this->stringify_flags(); } private: class rinstance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::scene::render_instruction_t& data) { wf::texture_t texture = wf::texture_t{self->snapshot.get_texture()}; data.pass->add_texture(texture, data.target, self->get_bounding_box(), data.damage); } }; }; } wayfire-0.10.0/plugins/animate/animate.cpp0000664000175000017500000003321115053502647020352 0ustar dkondordkondor#include #include #include #include #include #include #include "animate.hpp" #include "plugins/common/wayfire/plugins/common/shared-core-data.hpp" #include "system_fade.hpp" #include "basic_animations.hpp" #include "squeezimize.hpp" #include "zap.hpp" #include "spin.hpp" #include "fire/fire.hpp" #include "unmapped-view-node.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view.hpp" #include /* Represents an animation running for a specific view * animation_t is which animation to use (i.e fire, zoom, etc). */ class animation_hook : public wf::custom_data_t { public: std::shared_ptr view; wf::animate::animation_type type; std::string name; wf::output_t *current_output = nullptr; std::unique_ptr animation; std::shared_ptr unmapped_contents; void damage_whole_view() { view->damage(); if (unmapped_contents) { wf::scene::damage_node(unmapped_contents, unmapped_contents->get_bounding_box()); } } /* Update animation right before each frame */ wf::effect_hook_t update_animation_hook = [=] () { damage_whole_view(); bool result = animation->step(); damage_whole_view(); if (!result) { stop_hook(false); } }; /** * Switch the output the view is being animated on, and update the lastly * animated output in the global list. */ void set_output(wf::output_t *new_output) { if (current_output) { current_output->render->rem_effect(&update_animation_hook); } if (new_output) { new_output->render->add_effect(&update_animation_hook, wf::OUTPUT_EFFECT_PRE); } current_output = new_output; } wf::signal::connection_t on_set_output = [=] (auto) { set_output(view->get_output()); }; animation_hook(wayfire_view view, std::unique_ptr _animation, wf::animation_description_t duration, wf::animate::animation_type type, std::string name) { this->type = type; this->view = view->shared_from_this(); this->name = name; this->animation = std::move(_animation); this->animation->init(view, duration, type); set_output(view->get_output()); /* Animation is driven by the output render cycle the view is on. * Thus, we need to keep in sync with the current output. */ view->connect(&on_set_output); wf::scene::set_node_enabled(view->get_root_node(), true); if (type == wf::animate::ANIMATION_TYPE_UNMAP) { set_unmapped_contents(); } } void stop_hook(bool detached) { view->erase_data(name); } // When showing the final unmap animation, we show a ``fake'' node instead of the actual view contents, // because the underlying (sub)surfaces might be destroyed. // // The unmapped contents have to be visible iff the view is in an unmap animation. void set_unmapped_contents() { if (!unmapped_contents) { unmapped_contents = std::make_shared(view); auto parent = dynamic_cast( view->get_surface_root_node()->parent()); if (parent) { wf::scene::add_front( std::dynamic_pointer_cast(parent->shared_from_this()), unmapped_contents); } } } void unset_unmapped_contents() { if (unmapped_contents) { wf::scene::remove_child(unmapped_contents); unmapped_contents.reset(); } } void set_animation_type(wf::animate::animation_type new_type) { if (new_type == wf::animate::ANIMATION_TYPE_UNMAP) { set_unmapped_contents(); } else { unset_unmapped_contents(); } const bool direction_change = (type & WF_ANIMATE_HIDING_ANIMATION) != (new_type & WF_ANIMATE_HIDING_ANIMATION); this->type = new_type; if (animation && direction_change) { animation->reverse(); } } ~animation_hook() { /** * Order here is very important. * After doing unref() the view will be potentially destroyed. * Hence, we want to deinitialize everything else before that. */ set_output(nullptr); on_set_output.disconnect(); this->animation.reset(); unset_unmapped_contents(); wf::scene::set_node_enabled(view->get_root_node(), false); } animation_hook(const animation_hook &) = delete; animation_hook(animation_hook &&) = delete; animation_hook& operator =(const animation_hook&) = delete; animation_hook& operator =(animation_hook&&) = delete; }; class wayfire_animation : public wf::plugin_interface_t, private wf::per_output_tracker_mixin_t<> { wf::option_wrapper_t open_animation{"animate/open_animation"}; wf::option_wrapper_t close_animation{"animate/close_animation"}; wf::option_wrapper_t minimize_animation{"animate/minimize_animation"}; wf::option_wrapper_t default_duration{"animate/duration"}; wf::option_wrapper_t fade_duration{"animate/fade_duration"}; wf::option_wrapper_t zoom_duration{"animate/zoom_duration"}; wf::option_wrapper_t fire_duration{"animate/fire_duration"}; wf::option_wrapper_t squeezimize_duration{"animate/squeezimize_duration"}; wf::option_wrapper_t zap_duration{"animate/zap_duration"}; wf::option_wrapper_t spin_duration{"animate/spin_duration"}; wf::option_wrapper_t startup_duration{"animate/startup_duration"}; wf::view_matcher_t animation_enabled_for{"animate/enabled_for"}; wf::view_matcher_t fade_enabled_for{"animate/fade_enabled_for"}; wf::view_matcher_t zoom_enabled_for{"animate/zoom_enabled_for"}; wf::view_matcher_t fire_enabled_for{"animate/fire_enabled_for"}; wf::shared_data::ref_ptr_t effects_registry; template void register_effect(std::string name, wf::option_sptr_t option) { effects_registry->register_effect(name, wf::animate::effect_description_t{ .generator = [] { return std::make_unique(); }, .default_duration = [option] { return option->get_value(); }, }); } public: void init() override { init_output_tracking(); register_effect("fade", default_duration); register_effect("zoom", default_duration); register_effect("zap", zap_duration); if (wf::get_core().is_gles2()) { register_effect("fire", default_duration); register_effect("spin", spin_duration); register_effect("squeezimize", squeezimize_duration); } else { LOGW("Running with Vulkan/Pixman renderers, disabling fire, spin and squeezimize animations!"); } } void handle_new_output(wf::output_t *output) override { output->connect(&on_view_mapped); output->connect(&on_view_pre_unmap); output->connect(&on_render_start); output->connect(&on_minimize_request); } void handle_output_removed(wf::output_t *output) override { cleanup_views_on_output(output); } void fini() override { cleanup_views_on_output(nullptr); effects_registry->unregister_effect("fade"); effects_registry->unregister_effect("zoom"); effects_registry->unregister_effect("fire"); effects_registry->unregister_effect("zap"); effects_registry->unregister_effect("spin"); effects_registry->unregister_effect("squeezimize"); } void cleanup_views_on_output(wf::output_t *output) { std::vector> all_views; for (auto& view : wf::get_core().get_all_views()) { all_views.push_back(view->shared_from_this()); } for (auto& view : all_views) { auto wo = view->get_output(); if ((wo != output) && output) { continue; } for (auto& anim : effects_registry->effects) { auto map_name = get_map_animation_cdata_name(anim.first); if (view->has_data(map_name)) { view->get_data(map_name)->stop_hook(false); } auto minimize_name = get_minimize_animation_cdata_name(anim.first); if (view->has_data(minimize_name)) { view->get_data(minimize_name)->stop_hook(false); } } } } struct view_animation_t { std::string animation_name; wf::animation_description_t duration; }; view_animation_t get_animation_for_view( wf::option_wrapper_t& anim_type, wayfire_view view) { /* Determine the animation for the given view. * Note that the matcher plugin might not have been loaded, so * we need to have a fallback algorithm */ if (fade_enabled_for.matches(view)) { return {"fade", fade_duration}; } if (zoom_enabled_for.matches(view)) { return {"zoom", zoom_duration}; } if (fire_enabled_for.matches(view)) { return {"fire", fire_duration}; } if (animation_enabled_for.matches(view)) { if (!effects_registry->effects.count(anim_type)) { if ((std::string)anim_type != "none") { LOGE("Unknown animation type: \"", (std::string)anim_type, "\""); } return {"none", wf::animation_description_t{0, {}, ""}}; } return { .animation_name = anim_type, .duration = effects_registry->effects[anim_type].default_duration().value_or(default_duration) }; } return {"none", wf::animation_description_t{0, {}, ""}}; } std::string get_map_animation_cdata_name(std::string animation_name) { return "animation-hook-" + animation_name; } std::string get_minimize_animation_cdata_name(std::string animation_name) { return "animation-hook-" + animation_name + "-minimize"; } void set_animation(wayfire_view view, std::string animation_name, wf::animate::animation_type type, wf::animation_description_t duration) { if ((animation_name == "none") || !effects_registry->effects.count(animation_name)) { return; } auto cdata_name = (type & WF_ANIMATE_MINIMIZE_STATE_ANIMATION) ? get_minimize_animation_cdata_name(animation_name) : get_map_animation_cdata_name(animation_name); auto& effect = effects_registry->effects[animation_name]; if (view->has_data(cdata_name)) { view->get_data(cdata_name)->set_animation_type(type); return; } auto hook = std::make_unique(view, effect.generator(), duration, type, cdata_name); view->store_data(std::move(hook), cdata_name); } /* TODO: enhance - add more animations */ wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { auto animation = get_animation_for_view(open_animation, ev->view); set_animation(ev->view, animation.animation_name, wf::animate::ANIMATION_TYPE_MAP, animation.duration); }; wf::signal::connection_t on_view_pre_unmap = [=] (wf::view_pre_unmap_signal *ev) { auto animation = get_animation_for_view(close_animation, ev->view); set_animation(ev->view, animation.animation_name, wf::animate::ANIMATION_TYPE_UNMAP, animation.duration); }; wf::signal::connection_t on_minimize_request = [=] (wf::view_minimize_request_signal *ev) { auto animation = get_animation_for_view(minimize_animation, ev->view); set_animation(ev->view, minimize_animation, ev->state ? wf::animate::ANIMATION_TYPE_MINIMIZE : wf::animate::ANIMATION_TYPE_RESTORE, animation.duration); // ev->carried_out should remain false, so that core also does the automatic minimize/restore and // refocus }; wf::signal::connection_t on_render_start = [=] (wf::output_start_rendering_signal *ev) { new wf_system_fade(ev->output, startup_duration); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_animation); wayfire-0.10.0/plugins/animate/meson.build0000664000175000017500000000134215053502647020372 0ustar dkondordkondordependencies = [wlroots, pixman, wfconfig] animate_pch_deps = [plugin_pch_dep] if get_option('enable_openmp') dependencies += [dependency('openmp')] # PCH does not have openmp enabled animate_pch_deps = [] endif animiate = shared_module('animate', ['animate.cpp', 'fire/particle.cpp', 'fire/fire.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: dependencies + animate_pch_deps, install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers(['animate.hpp'], subdir: 'wayfire/plugins/animate') wayfire-0.10.0/plugins/animate/fire/0000775000175000017500000000000015053502647017155 5ustar dkondordkondorwayfire-0.10.0/plugins/animate/fire/fire.cpp0000664000175000017500000002026615053502647020614 0ustar dkondordkondor#include "fire.hpp" #include "particle.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-transform.hpp" #include #include #include #include static wf::option_wrapper_t fire_particles{"animate/fire_particles"}; static wf::option_wrapper_t fire_particle_size{"animate/fire_particle_size"}; static wf::option_wrapper_t random_fire_color{"animate/random_fire_color"}; static wf::option_wrapper_t fire_color{"animate/fire_color"}; // generate a random float between s and e static float random(float s, float e) { double r = 1.0 * (std::rand() % RAND_MAX) / (RAND_MAX - 1); return (s * r + (1 - r) * e); } static int particle_count_for_width(int width) { int particles = fire_particles; return particles * std::min(width / 400.0, 3.5); } class fire_node_t : public wf::scene::floating_inner_node_t { public: std::unique_ptr ps; fire_node_t() : floating_inner_node_t(false) { ps = std::make_unique(1); ps->set_initer( [=] (Particle& p) { init_particle_with_node(p, get_children_bounding_box(), progress_line); }); } static void init_particle_with_node(Particle& p, wf::geometry_t bounding_box, double progress) { p.life = 1; p.fade = random(0.1, 0.6); wf::color_t color_setting = fire_color; float r; float g; float b; if (!random_fire_color) { // The calculation here makes the variation lower at darker values float randomize_amount_r = (color_setting.r * 0.857) / 2; float randomize_amount_g = (color_setting.g * 0.857) / 2; float randomize_amount_b = (color_setting.b * 0.857) / 2; r = random(color_setting.r - randomize_amount_r, std::min(color_setting.r + randomize_amount_r, 1.0)); g = random(color_setting.g - randomize_amount_g, std::min(color_setting.g + randomize_amount_g, 1.0)); b = random(color_setting.b - randomize_amount_b, std::min(color_setting.b + randomize_amount_b, 1.0)); } else { r = random(0, 1); g = random(0, 1); b = random(0, 1); r = 2 * pow(r, 16); g = 2 * pow(g, 16); b = 2 * pow(b, 16); } p.color = {r, g, b, 1}; const double cur_pos = bounding_box.height * progress; p.pos = {random(0, bounding_box.width), random(cur_pos - 10, cur_pos + 10)}; p.start_pos = p.pos; p.speed = {random(-10, 10), random(-25, 5)}; p.g = {-1, -3}; double size = fire_particle_size; p.base_radius = p.radius = random(size * 0.8, size * 1.2); } std::string stringify() const override { return "fire"; } void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override; wf::geometry_t get_bounding_box() override { static constexpr int left_border = 200; static constexpr int right_border = 200; static constexpr int top_border = 200; static constexpr int bottom_border = 200; auto view = get_children_bounding_box(); view.x -= left_border; view.y -= top_border; view.width += left_border + right_border; view.height += top_border + bottom_border; return view; } float progress_line; void set_progress_line(float line) { progress_line = line; } }; class fire_render_instance_t : public wf::scene::render_instance_t { std::shared_ptr self; public: fire_render_instance_t(fire_node_t *self, wf::scene::damage_callback push_damage, wf::output_t *output) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); auto child_damage = [=] (const wf::region_t& damage) { push_damage(damage | self->get_bounding_box()); }; for (auto& ch : self->get_children()) { if (ch->is_enabled()) { ch->gen_render_instances(children, child_damage, output); } } } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (children.empty()) { return; } // Step 2: we render ourselves auto bbox = self->get_bounding_box(); instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & bbox, }); // Step 1: render the view below normally, however, make sure it doesn't // render above the progress line bbox = self->get_children_bounding_box(); bbox.height *= self->progress_line; auto child_damage = damage & bbox; for (auto& ch : children) { ch->schedule_instructions(instructions, target, child_damage); } } void render(const wf::scene::render_instruction_t& data) override { auto bbox = self->get_children_bounding_box(); auto translate = glm::translate(glm::mat4(1.0), {bbox.x, bbox.y, 0}); data.pass->custom_gles_subpass(data.target, [&] { for (auto box : data.damage) { wf::gles::render_target_logic_scissor(data.target, wlr_box_from_pixman_box(box)); self->ps->render(wf::gles::render_target_orthographic_projection(data.target) * translate); } }); } void presentation_feedback(wf::output_t *output) override { for (auto& ch : children) { ch->presentation_feedback(output); } } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (auto& ch : this->children) { ch->compute_visibility(output, visible); } } private: std::vector children; }; void fire_node_t::gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique( this, push_damage, output)); } static float fire_duration_mod_for_height(int height) { return std::min(height / 400.0, 3.0); } void FireAnimation::init(wayfire_view view, wf::animation_description_t dur, wf::animate::animation_type type) { this->view = view; auto bbox = view->get_transformed_node()->get_bounding_box(); dur.length_ms *= fire_duration_mod_for_height(bbox.height); this->progression = wf::animation::simple_animation_t( wf::create_option(dur)); this->progression.animate(0, 1); if (type & WF_ANIMATE_HIDING_ANIMATION) { this->progression.flip(); } name = "animation-fire-" + std::to_string(type); auto tr = std::make_shared(); view->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL + 1, name); } bool FireAnimation::step() { auto transformer = view->get_transformed_node() ->get_transformer(name); transformer->set_progress_line(this->progression); if (this->progression.running()) { transformer->ps->spawn(transformer->ps->size() / 10); } transformer->ps->update(); transformer->ps->resize(particle_count_for_width( transformer->get_children_bounding_box().width)); return this->progression.running() || transformer->ps->statistic(); } void FireAnimation::reverse() { this->progression.reverse(); } FireAnimation::~FireAnimation() { view->get_transformed_node()->rem_transformer(name); } wayfire-0.10.0/plugins/animate/fire/particle.hpp0000664000175000017500000000447115053502647021477 0ustar dkondordkondor#ifndef ANIMATION_FIRE_PARTICLE_HPP #define ANIMATION_FIRE_PARTICLE_HPP #include #include #include #include struct Particle { float life = -1; float fade; float radius, base_radius; glm::vec2 pos{0.0, 0.0}, speed{0.0, 0.0}, g{0.0, 0.0}; glm::vec2 start_pos; glm::vec4 color{1.0, 1.0, 1.0, 1.0}; /* update the given particle * time is the percentage of the frame which has elapsed * must be thread-safe */ void update(float time); }; /* a function to initialize a particle */ using ParticleIniter = std::function; class ParticleSystem { public: /* the user of this class has to set up a proper GL context * before creating the ParticleSystem */ ParticleSystem(int num_part); ~ParticleSystem(); void set_initer(ParticleIniter init); ParticleSystem(const ParticleSystem &) = delete; ParticleSystem(ParticleSystem &&) = delete; ParticleSystem& operator =(const ParticleSystem&) = delete; ParticleSystem& operator =(ParticleSystem&&) = delete; /* spawn at most num new particles. * returns the number of actually spawned particles */ int spawn(int num); /* change the maximal number of particles * Warning: This might kill a lot of particles */ void resize(int num); // return the maximal number of particles int size(); /* update all particles */ void update(); // number of particles alive int statistic(); /* render particles, each will be multiplied by matrix * The user of this class has to set up the same GL context that was * used during the creation of the particle system */ void render(glm::mat4 matrix); private: ParticleSystem() = delete; ParticleIniter pinit_func = [] (auto) {}; uint32_t last_update_msec; std::atomic particles_alive; std::vector ps; static constexpr int color_per_particle = 4; std::vector color, dark_color; static constexpr int radius_per_particle = 1; std::vector radius; static constexpr int center_per_particle = 2; std::vector center; OpenGL::program_t program; void update_worker(float time, int i); void create_program(); }; #endif /* end of include guard: ANIMATION_FIRE_PARTICLE_HPP */ wayfire-0.10.0/plugins/animate/fire/shaders.hpp0000664000175000017500000000201515053502647021315 0ustar dkondordkondor#ifndef PARTICLE_ANIMATION_SHADER #define PARTICLE_ANIMATION_SHADER static const char *particle_vert_source = R"( #version 100 attribute highp float radius; attribute highp vec2 position; attribute highp vec2 center; attribute highp vec4 color; uniform mat4 matrix; varying highp vec2 uv; varying highp vec4 out_color; varying highp float R; void main() { uv = position * radius; gl_Position = matrix * vec4(center.x + uv.x * 0.75, center.y + uv.y, 0.0, 1.0); R = radius; out_color = color; } )"; static const char *particle_frag_source = R"( #version 100 varying highp vec2 uv; varying highp vec4 out_color; varying highp float R; uniform highp float smoothing; void main() { highp float len = length(uv); if (len >= R) { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); } else { highp float factor = 1.0 - len / R; factor = pow(factor, smoothing); gl_FragColor = factor * out_color; } } )"; #endif /* end of include guard: PARTICLE_ANIMATION_SHADER */ wayfire-0.10.0/plugins/animate/fire/particle.cpp0000664000175000017500000001016415053502647021466 0ustar dkondordkondor#include "particle.hpp" #include "shaders.hpp" #include void Particle::update(float time) { if (life <= 0) // ignore { return; } const float slowdown = 0.8; pos += speed * 0.2f * slowdown; speed += g * 0.3f * slowdown; if (life != 0) { color.a /= life; } life -= fade * 0.3 * slowdown; radius = base_radius * std::pow(life, 0.5); color.a *= life; if (start_pos.x < pos.x) { g.x = -1; } else { g.x = 1; } if (life <= 0) { /* move outside */ pos = {-10000, -10000}; } } ParticleSystem::ParticleSystem(int particles) { resize(particles); last_update_msec = wf::get_current_time(); create_program(); particles_alive.store(0); } void ParticleSystem::set_initer(ParticleIniter init) { this->pinit_func = init; } ParticleSystem::~ParticleSystem() { wf::gles::run_in_context([&] { program.free_resources(); }); } int ParticleSystem::spawn(int num) { std::atomic spawned(0); # pragma omp parallel for for (size_t i = 0; i < ps.size(); i++) { if ((ps[i].life <= 0) && (spawned < num)) { pinit_func(ps[i]); ++spawned; ++particles_alive; } } return spawned; } void ParticleSystem::resize(int num) { if (num == (int)ps.size()) { return; } # pragma omp parallel for for (size_t i = num; i < ps.size(); i++) { if (ps[i].life >= 0) { --particles_alive; } } ps.resize(num); color.resize(color_per_particle * num); dark_color.resize(color_per_particle * num); radius.resize(radius_per_particle * num); center.resize(center_per_particle * num); } int ParticleSystem::size() { return ps.size(); } void ParticleSystem::update_worker(float time, int i) { if (ps[i].life <= 0) { return; } ps[i].update(time); if (ps[i].life <= 0) { --particles_alive; } for (int j = 0; j < 4; j++) // maybe use memcpy? { color[4 * i + j] = ps[i].color[j]; dark_color[4 * i + j] = ps[i].color[j] * 0.5; } center[2 * i] = ps[i].pos[0]; center[2 * i + 1] = ps[i].pos[1]; radius[i] = ps[i].radius; } void ParticleSystem::update() { // FIXME: don't hardcode 60FPS float time = (wf::get_current_time() - last_update_msec) / 16.0; last_update_msec = wf::get_current_time(); # pragma omp parallel for for (size_t i = 0; i < ps.size(); i++) { update_worker(time, i); } } int ParticleSystem::statistic() { return particles_alive; } void ParticleSystem::create_program() { wf::gles::run_in_context([&] { program.set_simple(OpenGL::compile_program(particle_vert_source, particle_frag_source)); }); } void ParticleSystem::render(glm::mat4 matrix) { program.use(wf::TEXTURE_TYPE_RGBA); static float vertex_data[] = { -1, -1, 1, -1, 1, 1, -1, 1 }; program.attrib_pointer("position", 2, 0, vertex_data); program.attrib_divisor("position", 0); program.attrib_pointer("radius", 1, 0, radius.data()); program.attrib_divisor("radius", 1); program.attrib_pointer("center", 2, 0, center.data()); program.attrib_divisor("center", 1); // matrix program.uniformMatrix4f("matrix", matrix); /* Darken the background */ program.attrib_pointer("color", 4, 0, dark_color.data()); program.attrib_divisor("color", 1); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA)); program.uniform1f("smoothing", 0.7); // TODO: optimize shaders for this case GL_CALL(glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, ps.size())); // particle color program.attrib_pointer("color", 4, 0, color.data()); GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); program.uniform1f("smoothing", 0.5); GL_CALL(glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, ps.size())); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program.deactivate(); } wayfire-0.10.0/plugins/animate/fire/fire.hpp0000664000175000017500000000134415053502647020615 0ustar dkondordkondor#ifndef FIRE_ANIMATION_HPP #define FIRE_ANIMATION_HPP #include #include #include "../animate.hpp" class FireTransformer; class ParticleSystem; class FireAnimation : public wf::animate::animation_base_t { std::string name; // the name of the transformer in the view's table wayfire_view view; wf::animation::simple_animation_t progression; public: ~FireAnimation(); void init(wayfire_view view, wf::animation_description_t, wf::animate::animation_type type) override; bool step() override; /* return true if continue, false otherwise */ void reverse() override; /* reverse the animation */ }; #endif /* end of include guard: FIRE_ANIMATION_HPP */ wayfire-0.10.0/plugins/animate/squeezimize.hpp0000664000175000017500000003771015053502647021323 0ustar dkondordkondor/* * The MIT License (MIT) * * Copyright (c) 2024 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "animate.hpp" static const char *squeeze_vert_source = R"( #version 100 attribute highp vec2 position; attribute highp vec2 uv_in; uniform mat4 matrix; varying highp vec2 uv; void main() { uv = uv_in; gl_Position = matrix * vec4(position, 0.0, 1.0); } )"; static const char *squeeze_frag_source = R"( #version 100 @builtin_ext@ @builtin@ precision highp float; varying highp vec2 uv; uniform highp float progress; uniform highp vec4 src_box; uniform highp vec4 target_box; uniform int upward; void main() { float y, sigmoid; vec2 uv_squeeze; float inv_w = 1.0 / (src_box.z - src_box.x); float inv_h = 1.0 / (src_box.w - src_box.y); float progress_pt_one = pow(clamp(progress, 0.0, 0.25) * 4.0, 2.0); float progress_pt_two = pow(progress, 2.0); uv_squeeze.x = inv_w * (uv.x - src_box.x); uv_squeeze.y = inv_h * (uv.y - 1.0 + src_box.w); if (upward == 1) { y = uv.y; uv_squeeze.y += -progress_pt_two * (inv_h - target_box.w); sigmoid = 1.0 / (1.0 + pow(2.718, -(y * (1.0 / (src_box.w - target_box.w)) * 15.0 - 10.0))); } else { y = 1.0 - uv.y; uv_squeeze.y -= -progress_pt_two * (inv_h - target_box.y + target_box.w); sigmoid = 1.0 / (1.0 + pow(2.718, -(y * (1.0 / (target_box.w - src_box.y)) * 15.0 - 10.0))); } uv_squeeze.x += sigmoid * progress_pt_one * (src_box.x - target_box.x) * inv_w; uv_squeeze.x *= (sigmoid * ((src_box.z - src_box.x) - (target_box.z - target_box.x)) / (target_box.z - target_box.x) * progress_pt_one) + 1.0; if (uv_squeeze.x < 0.0 || uv_squeeze.y < 0.0 || uv_squeeze.x > 1.0 || uv_squeeze.y > 1.0) { discard; } gl_FragColor = get_pixel(uv_squeeze); } )"; static const char *squeeze_frag_source_horiz = R"( #version 100 @builtin_ext@ @builtin@ precision mediump float; varying highp vec2 uv; uniform mediump float progress; uniform mediump vec4 src_box; uniform mediump vec4 target_box; uniform int upward; void main() { float y, sigmoid; vec2 uv_squeeze; float inv_w = 1.0 / (src_box.z - src_box.x); float inv_h = 1.0 / (src_box.w - src_box.y); float progress_pt_one = pow(clamp(progress, 0.0, 0.25) * 4.0, 2.0); float progress_pt_two = pow(progress, 2.0); uv_squeeze.x = inv_w * (uv.x - src_box.x); uv_squeeze.y = inv_h * (1.0 - uv.y - src_box.y); if (upward == 1) { y = 1.0 - uv.x; uv_squeeze.x += progress_pt_two * (inv_w - target_box.z); sigmoid = 1.0 / (1.0 + pow(2.718, -(y * (1.0 / (src_box.z - target_box.z)) * 15.0 - 10.0))); } else { y = uv.x; uv_squeeze.x -= progress_pt_two * (inv_w - target_box.x + target_box.z); sigmoid = 1.0 / (1.0 + pow(2.718, -(y * (1.0 / (target_box.z - src_box.x)) * 15.0 - 10.0))); } uv_squeeze.y += sigmoid * progress_pt_one * (src_box.y - target_box.y) * inv_h; uv_squeeze.y *= (sigmoid * ((src_box.w - src_box.y) - (target_box.w - target_box.y)) / (target_box.w - target_box.y) * progress_pt_one) + 1.0; uv_squeeze.y = 1.0 - uv_squeeze.y; if (uv_squeeze.x < 0.0 || uv_squeeze.y < 0.0 || uv_squeeze.x > 1.0 || uv_squeeze.y > 1.0) { discard; } gl_FragColor = get_pixel(uv_squeeze); } )"; namespace wf { namespace squeezimize { static std::string squeezimize_transformer_name = "animation-squeezimize"; using namespace wf::scene; using namespace wf::animation; class squeezimize_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t squeeze{*this}; }; class squeezimize_transformer : public wf::scene::view_2d_transformer_t { public: OpenGL::program_t program; wf::geometry_t minimize_target; wf::geometry_t animation_geometry; squeezimize_animation_t progression; bool upward = false; class simple_node_render_instance_t : public wf::scene::transformer_render_instance_t { wf::signal::connection_t on_node_damaged = [=] (node_damage_signal *ev) { push_to_parent(ev->region); }; damage_callback push_to_parent; public: simple_node_render_instance_t(squeezimize_transformer *self, damage_callback push_damage, wf::output_t *output) : wf::scene::transformer_render_instance_t(self, push_damage, output) { this->push_to_parent = push_damage; self->connect(&on_node_damaged); } ~simple_node_render_instance_t() {} void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } void transform_damage_region(wf::region_t& damage) override { damage |= wf::region_t{self->animation_geometry}; } void render(const render_instruction_t& data) override { auto src_box = self->get_children_bounding_box(); auto progress = self->progression.progress(); static const float vertex_data_uv[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, }; self->animation_geometry.x = std::min(src_box.x, self->minimize_target.x); self->animation_geometry.y = std::min(src_box.y, self->minimize_target.y); self->animation_geometry.width = std::max(std::max(std::max(src_box.width, self->minimize_target.width), (self->minimize_target.x + self->minimize_target.width) - src_box.x), (src_box.x + src_box.width) - self->minimize_target.x); self->animation_geometry.height = std::max(std::max(std::max(src_box.height, self->minimize_target.height), (self->minimize_target.y + self->minimize_target.height) - src_box.y), (src_box.y + src_box.height) - self->minimize_target.y); const float vertex_data_pos[] = { 1.0f * self->animation_geometry.x, 1.0f * self->animation_geometry.y + self->animation_geometry.height, 1.0f * self->animation_geometry.x + self->animation_geometry.width, 1.0f * self->animation_geometry.y + self->animation_geometry.height, 1.0f * self->animation_geometry.x + self->animation_geometry.width, 1.0f * self->animation_geometry.y, 1.0f * self->animation_geometry.x, 1.0f * self->animation_geometry.y, }; const glm::vec4 src_box_pos{ float(src_box.x - self->animation_geometry.x) / self->animation_geometry.width, float(src_box.y - self->animation_geometry.y) / self->animation_geometry.height, float((src_box.x - self->animation_geometry.x) + src_box.width) / self->animation_geometry.width, float((src_box.y - self->animation_geometry.y) + src_box.height) / self->animation_geometry.height }; const glm::vec4 target_box_pos{ float(self->minimize_target.x - self->animation_geometry.x) / self->animation_geometry.width, float(self->minimize_target.y - self->animation_geometry.y) / self->animation_geometry.height, float((self->minimize_target.x - self->animation_geometry.x) + self->minimize_target.width) / self->animation_geometry.width, float((self->minimize_target.y - self->animation_geometry.y) + self->minimize_target.height) / self->animation_geometry.height }; auto src_tex = wf::gles_texture_t{this->get_texture(1.0)}; data.pass->custom_gles_subpass(data.target, [&] { self->program.use(wf::TEXTURE_TYPE_RGBA); self->program.uniformMatrix4f("matrix", wf::gles::render_target_orthographic_projection(data.target)); self->program.attrib_pointer("position", 2, 0, vertex_data_pos); self->program.attrib_pointer("uv_in", 2, 0, vertex_data_uv); self->program.uniform1i("upward", self->upward); self->program.uniform1f("progress", progress); self->program.uniform4f("src_box", src_box_pos); self->program.uniform4f("target_box", target_box_pos); self->program.set_active_texture(src_tex); for (auto box : data.damage) { gles::render_target_logic_scissor(data.target, wlr_box_from_pixman_box(box)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } }); } }; squeezimize_transformer(wayfire_view view, wf::animation_description_t duration, wf::geometry_t minimize_target, wf::geometry_t bbox) : wf::scene::view_2d_transformer_t(view) { this->progression = squeezimize_animation_t{wf::create_option<>(duration)}; this->minimize_target = minimize_target; /* If there is no minimize target set, minimize to the bottom center of the output */ if ((this->minimize_target.width <= 0) || (this->minimize_target.height <= 0)) { if (auto output = view->get_output()) { auto og = output->get_relative_geometry(); this->minimize_target.x = og.width / 2 - 50; this->minimize_target.y = og.height; this->minimize_target.width = 100; this->minimize_target.height = 50; } } animation_geometry.x = std::min(bbox.x, this->minimize_target.x); animation_geometry.y = std::min(bbox.y, this->minimize_target.y); animation_geometry.width = std::max(std::max(std::max(bbox.width, this->minimize_target.width), (this->minimize_target.x + this->minimize_target.width) - bbox.x), (bbox.x + bbox.width) - this->minimize_target.x); animation_geometry.height = std::max(std::max(std::max(bbox.height, this->minimize_target.height), (this->minimize_target.y + this->minimize_target.height) - bbox.y), (bbox.y + bbox.height) - this->minimize_target.y); bool horiz; // auto src_box = view->get_bounding_box(); auto output = view->get_output(); auto geom = output->get_relative_geometry(); // check which edge the target is closest to double x = minimize_target.x + minimize_target.width / 2.0; double y = minimize_target.y + minimize_target.height / 2.0; double y2 = y * geom.width / geom.height; if (x < y2) { // bottom left part of the screen if (x < geom.width - y2) { // left edge horiz = true; this->upward = true; } else { // bottom edge horiz = false; this->upward = false; } } else { // top right part of the screen if (x > geom.width - y2) { // right edge horiz = true; this->upward = false; } else { // top edge horiz = false; this->upward = true; } } wf::gles::run_in_context_if_gles([&] { program.compile(squeeze_vert_source, horiz ? squeeze_frag_source_horiz : squeeze_frag_source); }); } wf::geometry_t get_bounding_box() override { return this->animation_geometry; } void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override { instances.push_back(std::make_unique( this, push_damage, shown_on)); } void init_animation(bool squeeze) { if (!squeeze) { this->progression.reverse(); } this->progression.start(); } virtual ~squeezimize_transformer() { wf::gles::run_in_context_if_gles([&] { program.free_resources(); }); } }; class squeezimize_animation : public animate::animation_base_t { wayfire_view view; public: void init(wayfire_view view, wf::animation_description_t dur, animate::animation_type type) override { this->view = view; pop_transformer(view); auto bbox = view->get_transformed_node()->get_children_bounding_box(); auto toplevel = wf::toplevel_cast(view); wf::dassert(toplevel != nullptr, "We cannot minimize non-toplevel views!"); auto hint = toplevel->get_minimize_hint(); auto tmgr = view->get_transformed_node(); auto node = std::make_shared(view, dur, hint, bbox); tmgr->add_transformer(node, wf::TRANSFORMER_HIGHLEVEL + 1, squeezimize_transformer_name); node->init_animation(type & WF_ANIMATE_HIDING_ANIMATION); } ~squeezimize_animation() { pop_transformer(this->view); } void pop_transformer(wayfire_view view) { view->get_transformed_node()->rem_transformer(squeezimize_transformer_name); } bool step() override { auto tmgr = view->get_transformed_node(); if (auto tr = tmgr->get_transformer(squeezimize_transformer_name)) { auto running = tr->progression.running(); if (!running) { return false; } return running; } return false; } void reverse() override { if (auto tr = view->get_transformed_node()->get_transformer( squeezimize_transformer_name)) { tr->progression.reverse(); } } }; } } wayfire-0.10.0/plugins/animate/shaders/0000775000175000017500000000000015053502647017661 5ustar dkondordkondorwayfire-0.10.0/plugins/animate/shaders/frag.glsl0000664000175000017500000000064015053502647021463 0ustar dkondordkondor#version 310 es in highp vec4 out_color; in highp vec2 pos; out highp vec4 fragColor; layout(location = 4) uniform highp float radii; void main() { highp float dist_center = sqrt(pos.x * pos.x + pos.y * pos.y); highp float factor = (radii - dist_center) / radii; if (factor < 0.0) factor = 0.0; highp float factor2 = factor * factor; fragColor = vec4(out_color.xyz, out_color.w * factor2); } wayfire-0.10.0/plugins/animate/shaders/vertex.glsl0000664000175000017500000000057315053502647022066 0ustar dkondordkondor#version 310 es layout(location = 0) in highp vec2 position; layout(location = 1) in highp vec2 center; layout(location = 2) in highp vec4 color; layout(location = 3) uniform highp vec2 global_offset; out highp vec4 out_color; out highp vec2 pos; void main() { gl_Position = vec4 (position + center + global_offset, 0.0, 1.0); out_color = color; pos = position; } wayfire-0.10.0/plugins/cube/0000775000175000017500000000000015053502647015530 5ustar dkondordkondorwayfire-0.10.0/plugins/cube/simple-background.cpp0000664000175000017500000000044315053502647021643 0ustar dkondordkondor#include #include "simple-background.hpp" wf_cube_simple_background::wf_cube_simple_background() {} void wf_cube_simple_background::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs&) { OpenGL::clear(background_color, GL_COLOR_BUFFER_BIT); } wayfire-0.10.0/plugins/cube/simple-background.hpp0000664000175000017500000000073715053502647021656 0ustar dkondordkondor#ifndef WF_CUBE_SIMPLE_BACKGROUND_HPP #define WF_CUBE_SIMPLE_BACKGROUND_HPP #include "cube-background.hpp" class wf_cube_simple_background : public wf_cube_background_base { wf::option_wrapper_t background_color{"cube/background"}; public: wf_cube_simple_background(); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; }; #endif /* end of include guard: WF_CUBE_SIMPLE_BACKGROUND_HPP */ wayfire-0.10.0/plugins/cube/cube.cpp0000664000175000017500000006514715053502647017167 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include "wayfire/plugins/ipc/ipc-activator.hpp" #include #include #include "cube.hpp" #include "simple-background.hpp" #include "skydome.hpp" #include "cubemap.hpp" #include "cube-control-signal.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #define Z_OFFSET_NEAR 0.89567f #define Z_OFFSET_FAR 2.00000f #define ZOOM_MAX 10.0f #define ZOOM_MIN 0.1f #ifdef USE_GLES32 #include #endif #include "shaders.tpp" #include "shaders-3-2.tpp" class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t { class cube_render_node_t : public wf::scene::node_t { class cube_render_instance_t : public wf::scene::render_instance_t { std::shared_ptr self; wf::scene::damage_callback push_damage; std::vector> ws_instances; std::vector ws_damage; std::vector framebuffers; wf::signal::connection_t on_cube_damage = [=] (wf::scene::node_damage_signal *ev) { push_damage(ev->region); }; public: cube_render_instance_t(cube_render_node_t *self, wf::scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; self->connect(&on_cube_damage); ws_damage.resize(self->workspaces.size()); framebuffers.resize(self->workspaces.size()); ws_instances.resize(self->workspaces.size()); for (int i = 0; i < (int)self->workspaces.size(); i++) { auto push_damage_child = [=] (const wf::region_t& damage) { ws_damage[i] |= damage; push_damage(self->get_bounding_box()); }; self->workspaces[i]->gen_render_instances(ws_instances[i], push_damage_child, self->cube->output); ws_damage[i] |= self->workspaces[i]->get_bounding_box(); } } ~cube_render_instance_t() {} void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target.translated(-wf::origin(self->get_bounding_box())), .damage = damage & self->get_bounding_box(), }); auto bbox = self->get_bounding_box(); damage ^= bbox; for (int i = 0; i < (int)ws_instances.size(); i++) { const float scale = self->cube->output->handle->scale; auto bbox = self->workspaces[i]->get_bounding_box(); framebuffers[i].allocate(wf::dimensions(bbox), scale); wf::render_target_t target{framebuffers[i]}; target.geometry = self->workspaces[i]->get_bounding_box(); target.scale = self->cube->output->handle->scale; wf::render_pass_params_t params; params.instances = &ws_instances[i]; params.damage = ws_damage[i]; params.reference_output = self->cube->output; params.target = target; params.flags = wf::RPASS_CLEAR_BACKGROUND | wf::RPASS_EMIT_SIGNALS; wf::render_pass_t::run(params); ws_damage[i].clear(); } } void render(const wf::scene::render_instruction_t& data) override { self->cube->render(data, framebuffers); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (int i = 0; i < (int)self->workspaces.size(); i++) { wf::region_t ws_region = self->workspaces[i]->get_bounding_box(); for (auto& ch : this->ws_instances[i]) { ch->compute_visibility(output, ws_region); } } } }; public: cube_render_node_t(wayfire_cube *cube) : node_t(false) { this->cube = cube; auto w = cube->output->wset()->get_workspace_grid_size().width; auto y = cube->output->wset()->get_current_workspace().y; for (int i = 0; i < w; i++) { auto node = std::make_shared(cube->output, wf::point_t{i, y}); workspaces.push_back(node); } } virtual void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { if (shown_on != this->cube->output) { return; } instances.push_back(std::make_unique( this, push_damage)); } wf::geometry_t get_bounding_box() { return cube->output->get_layout_geometry(); } private: std::vector> workspaces; wayfire_cube *cube; }; std::unique_ptr input_grab; std::shared_ptr render_node; wf::option_wrapper_t XVelocity{"cube/speed_spin_horiz"}, YVelocity{"cube/speed_spin_vert"}, ZVelocity{"cube/speed_zoom"}; wf::option_wrapper_t zoom_opt{"cube/zoom"}; /* the Z camera distance so that (-1, 1) is mapped to the whole screen * for the given FOV */ float identity_z_offset; OpenGL::program_t program; wf_cube_animation_attribs animation; wf::option_wrapper_t use_light{"cube/light"}; wf::option_wrapper_t use_deform{"cube/deform"}; std::string last_background_mode; std::unique_ptr background; wf::option_wrapper_t background_mode{"cube/background_mode"}; void reload_background() { if (last_background_mode == (std::string)background_mode) { return; } last_background_mode = background_mode; if (last_background_mode == "simple") { background = std::make_unique(); } else if (last_background_mode == "skydome") { background = std::make_unique(output); } else if (last_background_mode == "cubemap") { background = std::make_unique(); } else { LOGE("cube: Unrecognized background mode %s. Using default \"simple\"", last_background_mode.c_str()); background = std::make_unique(); } } bool tessellation_support; int get_num_faces() { return output->wset()->get_workspace_grid_size().width; } wf::plugin_activation_data_t grab_interface{ .name = "cube", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { deactivate(); }, }; public: void init() override { input_grab = std::make_unique("cube", output, nullptr, this, nullptr); input_grab->set_wants_raw_input(true); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.offset_z.set(0, 0); animation.cube_animation.rotation.set(0, 0); animation.cube_animation.zoom.set(1, 1); animation.cube_animation.ease_deformation.set(0, 0); animation.cube_animation.start(); reload_background(); output->connect(&on_cube_control); wf::gles::run_in_context([&] { load_program(); }); } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WL_POINTER_BUTTON_STATE_RELEASED) { input_ungrabbed(); } } void handle_pointer_axis(const wlr_pointer_axis_event& event) override { if (event.orientation == WL_POINTER_AXIS_VERTICAL_SCROLL) { pointer_scrolled(event.delta); } } void load_program() { #ifdef USE_GLES32 std::string ext_string(reinterpret_cast(glGetString(GL_EXTENSIONS))); tessellation_support = ext_string.find(std::string("GL_EXT_tessellation_shader")) != std::string::npos; #else tessellation_support = false; #endif if (!tessellation_support) { program.set_simple(OpenGL::compile_program(cube_vertex_2_0, cube_fragment_2_0)); } else { #ifdef USE_GLES32 auto id = GL_CALL(glCreateProgram()); GLuint vss, fss, tcs, tes, gss; vss = OpenGL::compile_shader(cube_vertex_3_2, GL_VERTEX_SHADER); fss = OpenGL::compile_shader(cube_fragment_3_2, GL_FRAGMENT_SHADER); tcs = OpenGL::compile_shader(cube_tcs_3_2, GL_TESS_CONTROL_SHADER); tes = OpenGL::compile_shader(cube_tes_3_2, GL_TESS_EVALUATION_SHADER); gss = OpenGL::compile_shader(cube_geometry_3_2, GL_GEOMETRY_SHADER); GL_CALL(glAttachShader(id, vss)); GL_CALL(glAttachShader(id, tcs)); GL_CALL(glAttachShader(id, tes)); GL_CALL(glAttachShader(id, gss)); GL_CALL(glAttachShader(id, fss)); GL_CALL(glLinkProgram(id)); GL_CALL(glUseProgram(id)); GL_CALL(glDeleteShader(vss)); GL_CALL(glDeleteShader(fss)); GL_CALL(glDeleteShader(tcs)); GL_CALL(glDeleteShader(tes)); GL_CALL(glDeleteShader(gss)); program.set_simple(id); #endif } animation.projection = glm::perspective(45.0f, 1.f, 0.1f, 100.f); } wf::signal::connection_t on_cube_control = [=] (cube_control_signal *d) { rotate_and_zoom_cube(d->angle, d->zoom, d->ease, d->last_frame); d->carried_out = true; }; void rotate_and_zoom_cube(double angle, double zoom, double ease, bool last_frame) { if (last_frame) { deactivate(); return; } if (!activate()) { return; } float offset_z = identity_z_offset + Z_OFFSET_NEAR; animation.cube_animation.rotation.set(angle, angle); animation.cube_animation.zoom.set(zoom, zoom); animation.cube_animation.ease_deformation.set(ease, ease); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.offset_z.set(offset_z, offset_z); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); } /* Tries to initialize renderer, activate plugin, etc. */ bool activate() { if (output->is_plugin_active(grab_interface.name)) { return true; } if (!output->activate_plugin(&grab_interface)) { return false; } wf::get_core().connect(&on_motion_event); render_node = std::make_shared(this); wf::scene::add_front(wf::get_core().scene(), render_node); output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); output->render->set_require_depth_buffer(true); wf::get_core().hide_cursor(); input_grab->grab_input(wf::scene::layer::OVERLAY); auto wsize = output->wset()->get_workspace_grid_size(); animation.side_angle = 2 * M_PI / float(wsize.width); identity_z_offset = 0.5 / std::tan(animation.side_angle / 2); if (wsize.width == 1) { // tan(M_PI) is 0, so identity_z_offset is invalid identity_z_offset = 0.0f; } reload_background(); animation.cube_animation.offset_z.set(identity_z_offset + Z_OFFSET_NEAR, identity_z_offset + Z_OFFSET_NEAR); return true; } int calculate_viewport_dx_from_rotation() { float dx = -animation.cube_animation.rotation / animation.side_angle; return std::floor(dx + 0.5); } /* Disable custom rendering and deactivate plugin */ void deactivate() { if (!output->is_plugin_active(grab_interface.name)) { return; } wf::scene::remove_child(render_node); output->render->damage_whole(); render_node = nullptr; output->render->rem_effect(&pre_hook); output->render->set_require_depth_buffer(false); input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); wf::get_core().unhide_cursor(); on_motion_event.disconnect(); /* Figure out how much we have rotated and switch workspace */ int size = get_num_faces(); int dvx = calculate_viewport_dx_from_rotation(); auto cws = output->wset()->get_current_workspace(); int nvx = (cws.x + (dvx % size) + size) % size; output->wset()->set_workspace({nvx, cws.y}); /* We are finished with rotation, make sure the next time cube is used * it is properly reset */ animation.cube_animation.rotation.set(0, 0); } /* Sets attributes target to such values that the cube effect isn't visible, * i.e towards the starting(or ending) position * * It doesn't change rotation because that is different in different cases - * for example when moved by the keyboard or with a button grab */ void reset_attribs() { animation.cube_animation.zoom.restart_with_end(1.0); animation.cube_animation.offset_z.restart_with_end( identity_z_offset + Z_OFFSET_NEAR); animation.cube_animation.offset_y.restart_with_end(0); animation.cube_animation.ease_deformation.restart_with_end(0); } /* Start moving to a workspace to the left/right using the keyboard */ bool move_vp(int dir) { if (!activate()) { return false; } /* After the rotation is done, we want to exit cube and focus the target * workspace */ animation.in_exit = true; /* Set up rotation target to the next workspace in the given direction, * and reset other attribs */ reset_attribs(); animation.cube_animation.rotation.restart_with_end( animation.cube_animation.rotation.end - dir * animation.side_angle); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); return true; } /* Initiate with an button grab. */ bool input_grabbed() { if (!activate()) { return false; } /* Rotations, offset_y and zoom stay as they are now, as they have been * grabbed. * offset_z changes to the default one. * * We also need to make sure the cube gets deformed */ animation.in_exit = false; float current_rotation = animation.cube_animation.rotation; float current_offset_y = animation.cube_animation.offset_y; float current_zoom = animation.cube_animation.zoom; animation.cube_animation.rotation.set(current_rotation, current_rotation); animation.cube_animation.offset_y.set(current_offset_y, current_offset_y); animation.cube_animation.offset_z.restart_with_end( zoom_opt + identity_z_offset + Z_OFFSET_NEAR); animation.cube_animation.zoom.set(current_zoom, current_zoom); animation.cube_animation.ease_deformation.restart_with_end(1); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); // Let the button go to the input grab return false; } /* Mouse grab was released */ void input_ungrabbed() { animation.in_exit = true; /* Rotate cube so that selected workspace aligns with the output */ float current_rotation = animation.cube_animation.rotation; int dvx = calculate_viewport_dx_from_rotation(); animation.cube_animation.rotation.set(current_rotation, -dvx * animation.side_angle); /* And reset other attributes, again to align the workspace with the output * */ reset_attribs(); animation.cube_animation.start(); update_view_matrix(); output->render->schedule_redraw(); } /* Update the view matrix used in the next frame */ void update_view_matrix() { auto zoom_translate = glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -animation.cube_animation.offset_z)); auto rotation = glm::rotate(glm::mat4(1.0), (float)animation.cube_animation.offset_y, glm::vec3(1., 0., 0.)); auto view = glm::lookAt(glm::vec3(0., 0., 0.), glm::vec3(0., 0., -animation.cube_animation.offset_z), glm::vec3(0., 1., 0.)); animation.view = zoom_translate * rotation * view; } glm::mat4 output_transform(const wf::render_target_t& target) { auto scale = glm::scale(glm::mat4(1.0), {1, -1, 1}); return wf::gles::render_target_gl_to_framebuffer(target) * scale; } glm::mat4 calculate_vp_matrix(const wf::render_target_t& dest) { float zoom_factor = animation.cube_animation.zoom; auto scale_matrix = glm::scale(glm::mat4(1.0), glm::vec3(1. / zoom_factor, 1. / zoom_factor, 1. / zoom_factor)); return output_transform(dest) * animation.projection * animation.view * scale_matrix; } /* Calculate the base model matrix for the i-th side of the cube */ glm::mat4 calculate_model_matrix(int i) { const float angle = i * animation.side_angle + animation.cube_animation.rotation; auto rotation = glm::rotate(glm::mat4(1.0), angle, glm::vec3(0, 1, 0)); double additional_z = 0.0; // Special case: 2 faces // In this case, we need to make sure that the two faces are just // slightly moved away from each other, to avoid artifacts which can // happen if both sides are touching. if (get_num_faces() == 2) { additional_z = 1e-3; } auto translation = glm::translate(glm::mat4(1.0), glm::vec3(0, 0, identity_z_offset + additional_z)); return rotation * translation; } /* Render the sides of the cube, using the given culling mode - cw or ccw */ void render_cube(GLuint front_face, std::vector& buffers) { GL_CALL(glFrontFace(front_face)); static const GLuint indexData[] = {0, 1, 2, 0, 2, 3}; auto cws = output->wset()->get_current_workspace(); for (int i = 0; i < get_num_faces(); i++) { int index = (cws.x + i) % get_num_faces(); GL_CALL(glBindTexture(GL_TEXTURE_2D, wf::gles_texture_t::from_aux(buffers[index]).tex_id)); auto model = calculate_model_matrix(i); program.uniformMatrix4f("model", model); if (tessellation_support) { #ifdef USE_GLES32 GL_CALL(glDrawElements(GL_PATCHES, 6, GL_UNSIGNED_INT, &indexData)); #endif } else { GL_CALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, &indexData)); } } } void render(const wf::scene::render_instruction_t& data, std::vector& buffers) { data.pass->custom_gles_subpass([&] { if (program.get_program_id(wf::TEXTURE_TYPE_RGBA) == 0) { load_program(); } GL_CALL(glClear(GL_DEPTH_BUFFER_BIT)); background->render_frame(data.target, animation); auto vp = calculate_vp_matrix(data.target); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glEnable(GL_DEPTH_TEST)); GL_CALL(glDepthFunc(GL_LESS)); static GLfloat vertexData[] = { -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5 }; static GLfloat coordData[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; program.attrib_pointer("position", 2, 0, vertexData); program.attrib_pointer("uvPosition", 2, 0, coordData); program.uniformMatrix4f("VP", vp); if (tessellation_support) { program.uniform1i("deform", use_deform); program.uniform1i("light", use_light); program.uniform1f("ease", animation.cube_animation.ease_deformation); } /* We render the cube in two stages, based on winding. * By using two stages, we ensure that we first render the cube sides * that are on the back, and then we render those at the front, so we * don't have to use depth testing and we also can support alpha cube. */ GL_CALL(glEnable(GL_CULL_FACE)); render_cube(GL_CCW, buffers); render_cube(GL_CW, buffers); GL_CALL(glDisable(GL_CULL_FACE)); GL_CALL(glDisable(GL_DEPTH_TEST)); program.deactivate(); }); } wf::effect_hook_t pre_hook = [=] () { update_view_matrix(); wf::scene::damage_node(render_node, render_node->get_bounding_box()); if (animation.cube_animation.running()) { output->render->schedule_redraw(); } else if (animation.in_exit) { deactivate(); } }; wf::signal::connection_t> on_motion_event = [=] (wf::input_event_signal *ev) { pointer_moved(ev->event); ev->event->delta_x = 0; ev->event->delta_y = 0; ev->event->unaccel_dx = 0; ev->event->unaccel_dy = 0; }; void pointer_moved(wlr_pointer_motion_event *ev) { if (animation.in_exit) { return; } double xdiff = ev->delta_x; double ydiff = ev->delta_y * -1.0; animation.cube_animation.zoom.restart_with_end( animation.cube_animation.zoom.end); double current_off_y = animation.cube_animation.offset_y; double off_y = current_off_y + ydiff * YVelocity; off_y = wf::clamp(off_y, -1.5, 1.5); animation.cube_animation.offset_y.set(current_off_y, off_y); animation.cube_animation.offset_z.restart_with_end( animation.cube_animation.offset_z.end); float current_rotation = animation.cube_animation.rotation; animation.cube_animation.rotation.restart_with_end( current_rotation + xdiff * XVelocity); animation.cube_animation.ease_deformation.restart_with_end( animation.cube_animation.ease_deformation.end); animation.cube_animation.start(); output->render->schedule_redraw(); } void pointer_scrolled(double amount) { if (animation.in_exit) { return; } animation.cube_animation.offset_y.restart_with_end( animation.cube_animation.offset_y.end); animation.cube_animation.offset_z.restart_with_end( animation.cube_animation.offset_z.end); animation.cube_animation.rotation.restart_with_end( animation.cube_animation.rotation.end); animation.cube_animation.ease_deformation.restart_with_end( animation.cube_animation.ease_deformation.end); float target_zoom = animation.cube_animation.zoom; float start_zoom = target_zoom; target_zoom += std::min(std::pow(target_zoom, 1.5f), ZOOM_MAX) * amount * ZVelocity; target_zoom = std::min(std::max(target_zoom, ZOOM_MIN), ZOOM_MAX); animation.cube_animation.zoom.set(start_zoom, target_zoom); animation.cube_animation.start(); output->render->schedule_redraw(); } void fini() override { if (output->is_plugin_active(grab_interface.name)) { deactivate(); } wf::gles::run_in_context_if_gles([&] { program.free_resources(); }); } }; class wayfire_cube_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t rotate_left{"cube/rotate_left"}; wf::ipc_activator_t rotate_right{"cube/rotate_right"}; wf::ipc_activator_t activate{"cube/activate"}; public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("cube: requires GLES2 support, but current renderer is ", render_type); return; } this->init_output_tracking(); rotate_left.set_handler(rotate_left_cb); rotate_right.set_handler(rotate_right_cb); activate.set_handler(activate_cb); } void fini() override { this->fini_output_tracking(); } wf::ipc_activator_t::handler_t rotate_left_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->move_vp(-1); }; wf::ipc_activator_t::handler_t rotate_right_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->move_vp(+1); }; wf::ipc_activator_t::handler_t activate_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->input_grabbed(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_cube_global); wayfire-0.10.0/plugins/cube/cube-background.hpp0000664000175000017500000000057215053502647021300 0ustar dkondordkondor#ifndef WF_CUBE_BACKGROUND_HPP #define WF_CUBE_BACKGROUND_HPP #include #include "cube.hpp" class wf_cube_background_base { public: virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) = 0; virtual ~wf_cube_background_base() = default; }; #endif /* end of include guard: WF_CUBE_BACKGROUND_HPP */ wayfire-0.10.0/plugins/cube/cubemap-shaders.tpp0000664000175000017500000000066315053502647021325 0ustar dkondordkondorstatic const char* cubemap_vertex = R"(#version 100 attribute highp vec3 position; varying highp vec3 direction; uniform mat4 cubeMapMatrix; void main() { gl_Position = cubeMapMatrix * vec4(position, 1.0); direction = position; })"; static const char* cubemap_fragment = R"(#version 100 varying highp vec3 direction; uniform samplerCube smp; void main() { gl_FragColor = vec4(textureCube(smp, direction).xyz, 1); })"; wayfire-0.10.0/plugins/cube/skydome.cpp0000664000175000017500000001131015053502647017703 0ustar dkondordkondor#include "skydome.hpp" #include #include #include #include #include #include "shaders.tpp" #define SKYDOME_GRID_WIDTH 128 #define SKYDOME_GRID_HEIGHT 128 wf_cube_background_skydome::wf_cube_background_skydome(wf::output_t *output) { this->output = output; load_program(); reload_texture(); } wf_cube_background_skydome::~wf_cube_background_skydome() { wf::gles::run_in_context([&] { program.free_resources(); if (tex != (GLuint) - 1) { GL_CALL(glDeleteTextures(1, &tex)); } }); } void wf_cube_background_skydome::load_program() { wf::gles::run_in_context([&] { program.set_simple(OpenGL::compile_program(cube_vertex_2_0, cube_fragment_2_0)); }); } void wf_cube_background_skydome::reload_texture() { if (!last_background_image.compare(background_image)) { return; } last_background_image = background_image; if (tex == (uint32_t)-1) { GL_CALL(glGenTextures(1, &tex)); } GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); if (image_io::load_from_file(last_background_image, GL_TEXTURE_2D)) { GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); } else { LOGE("Failed to load skydome image from \"%s\".", last_background_image.c_str()); GL_CALL(glDeleteTextures(1, &tex)); tex = -1; } GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); } void wf_cube_background_skydome::fill_vertices() { if (mirror_opt == last_mirror) { return; } last_mirror = mirror_opt; float scale = 75.0; int gw = SKYDOME_GRID_WIDTH + 1; int gh = SKYDOME_GRID_HEIGHT; vertices.clear(); indices.clear(); coords.clear(); for (int i = 1; i < gh; i++) { for (int j = 0; j < gw; j++) { float theta = ((2 * M_PI) / (gw - 1)) * j; float phi = (M_PI / gh) * i; vertices.push_back(cos(theta) * sin(phi) * scale); vertices.push_back(cos(phi) * scale); vertices.push_back(sin(theta) * sin(phi) * scale); if (last_mirror == 0) { coords.push_back((float)j / (gw - 1)); coords.push_back((float)(i - 1) / (gh - 2)); } else { float u = ((float)j / (gw - 1)) * 2.0; coords.push_back(u - ((u > 1.0) ? (2.0 * (u - 1.0)) : 0)); coords.push_back((float)(i - 1) / (gh - 2)); } } } for (int i = 1; i < gh - 1; i++) { for (int j = 0; j < gw - 1; j++) { indices.push_back((i - 1) * gw + j); indices.push_back((i - 1) * gw + j + gw); indices.push_back((i - 1) * gw + j + 1); indices.push_back((i - 1) * gw + j + 1); indices.push_back((i - 1) * gw + j + gw); indices.push_back((i - 1) * gw + j + gw + 1); } } } void wf_cube_background_skydome::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) { fill_vertices(); reload_texture(); if (tex == (uint32_t)-1) { GL_CALL(glClearColor(TEX_ERROR_FLAG_COLOR)); GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); return; } wf::gles::bind_render_buffer(fb); program.use(wf::TEXTURE_TYPE_RGBA); auto rotation = glm::rotate(glm::mat4(1.0), (float)(attribs.cube_animation.offset_y * 0.5), glm::vec3(1., 0., 0.)); auto view = glm::lookAt(glm::vec3(0., 0., 0.), glm::vec3(0., 0., -attribs.cube_animation.offset_z), glm::vec3(0., 1., 0.)); auto vp = wf::gles::output_transform(fb) * attribs.projection * view * rotation; program.uniformMatrix4f("VP", vp); program.attrib_pointer("position", 3, 0, vertices.data()); program.attrib_pointer("uvPosition", 2, 0, coords.data()); auto cws = output->wset()->get_current_workspace(); auto model = glm::rotate(glm::mat4(1.0), float(attribs.cube_animation.rotation) - cws.x * attribs.side_angle, glm::vec3(0, 1, 0)); program.uniformMatrix4f("model", model); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, tex)); GL_CALL(glDrawElements(GL_TRIANGLES, 6 * SKYDOME_GRID_WIDTH * (SKYDOME_GRID_HEIGHT - 2), GL_UNSIGNED_INT, indices.data())); program.deactivate(); } wayfire-0.10.0/plugins/cube/meson.build0000664000175000017500000000071515053502647017675 0ustar dkondordkondoranimiate = shared_module('cube', ['cube.cpp', 'cubemap.cpp', 'skydome.cpp', 'simple-background.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.10.0/plugins/cube/cubemap.cpp0000664000175000017500000001100515053502647017645 0ustar dkondordkondor#include "cubemap.hpp" #include #include #include #include #include "cubemap-shaders.tpp" wf_cube_background_cubemap::wf_cube_background_cubemap() { create_program(); reload_texture(); } wf_cube_background_cubemap::~wf_cube_background_cubemap() { wf::gles::run_in_context([&] { program.free_resources(); GL_CALL(glDeleteTextures(1, &tex)); GL_CALL(glDeleteBuffers(1, &vbo_cube_vertices)); GL_CALL(glDeleteBuffers(1, &ibo_cube_indices)); }); } void wf_cube_background_cubemap::create_program() { wf::gles::run_in_context([&] { program.set_simple( OpenGL::compile_program(cubemap_vertex, cubemap_fragment)); }); } void wf_cube_background_cubemap::reload_texture() { if (!last_background_image.compare(background_image)) { return; } last_background_image = background_image; wf::gles::run_in_context([&] { if (tex == (uint32_t)-1) { GL_CALL(glGenTextures(1, &tex)); GL_CALL(glGenBuffers(1, &vbo_cube_vertices)); GL_CALL(glGenBuffers(1, &ibo_cube_indices)); } GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, tex)); if (!image_io::load_from_file(last_background_image, GL_TEXTURE_CUBE_MAP)) { LOGE("Failed to load cubemap background image from \"%s\".", last_background_image.c_str()); GL_CALL(glDeleteTextures(1, &tex)); GL_CALL(glDeleteBuffers(1, &vbo_cube_vertices)); GL_CALL(glDeleteBuffers(1, &ibo_cube_indices)); tex = -1; } if (tex != (uint32_t)-1) { GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)); } GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0)); }); } void wf_cube_background_cubemap::render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) { reload_texture(); if (tex == (uint32_t)-1) { GL_CALL(glClearColor(TEX_ERROR_FLAG_COLOR)); GL_CALL(glClear(GL_COLOR_BUFFER_BIT)); return; } program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glDepthMask(GL_FALSE)); GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, tex)); GLfloat cube_vertices[] = { -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, }; GLushort cube_indices[] = { 3, 7, 6, // right 3, 6, 2, // right 4, 0, 1, // left 4, 1, 5, // left 4, 7, 3, // top 4, 3, 0, // top 1, 2, 6, // bottom 1, 6, 5, // bottom 0, 3, 2, // front 0, 2, 1, // front 7, 4, 5, // back 7, 5, 6, // back }; glBindBuffer(GL_ARRAY_BUFFER, vbo_cube_vertices); glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_cube_indices); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW); GLint vertex = glGetAttribLocation(program.get_program_id( wf::TEXTURE_TYPE_RGBA), "position"); glEnableVertexAttribArray(vertex); glVertexAttribPointer(vertex, 3, GL_FLOAT, GL_FALSE, 0, 0); auto model = glm::rotate(glm::mat4(1.0), float(attribs.cube_animation.rotation), glm::vec3(0, 1, 0)); glm::vec3 look_at{0., (double)-attribs.cube_animation.offset_y, (double)attribs.cube_animation.offset_z}; auto view = glm::lookAt(glm::vec3(0., 0., 0.), look_at, glm::vec3(0., 1., 0.)); auto vp = wf::gles::output_transform(fb) * attribs.projection * view; model = vp * model; program.uniformMatrix4f("cubeMapMatrix", model); glDrawElements(GL_TRIANGLES, 12 * 3, GL_UNSIGNED_SHORT, 0); program.deactivate(); GL_CALL(glDepthMask(GL_TRUE)); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } wayfire-0.10.0/plugins/cube/cube.hpp0000664000175000017500000000156715053502647017170 0ustar dkondordkondor#ifndef WF_CUBE_HPP #define WF_CUBE_HPP #include #include #include #include #include #define TEX_ERROR_FLAG_COLOR 0, 1, 0, 1 using namespace wf::animation; class cube_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t offset_y{*this}; timed_transition_t offset_z{*this}; timed_transition_t rotation{*this}; timed_transition_t zoom{*this}; timed_transition_t ease_deformation{*this}; }; struct wf_cube_animation_attribs { wf::option_wrapper_t animation_duration{"cube/initial_animation"}; cube_animation_t cube_animation{animation_duration}; glm::mat4 projection, view; float side_angle; bool in_exit; }; #endif /* end of include guard: WF_CUBE_HPP */ wayfire-0.10.0/plugins/cube/cube-control-signal.hpp0000664000175000017500000000117115053502647022110 0ustar dkondordkondor#ifndef CUBE_CONTROL_SIGNAL #define CUBE_CONTROL_SIGNAL #include /* A private signal, currently shared by idle & cube * * It is used to rotate the cube from the idle plugin as a screensaver. */ /* Rotate cube to given angle and zoom level */ struct cube_control_signal { double angle; // cube rotation in radians double zoom; // 1.0 means 100%; increase value to zoom double ease; // for cube deformation; range 0.0-1.0 bool last_frame; // ends cube animation if true bool carried_out; // false if cube is disabled }; #endif /* end of include guard: CUBE_CONTROL_SIGNAL */ wayfire-0.10.0/plugins/cube/skydome.hpp0000664000175000017500000000174015053502647017716 0ustar dkondordkondor#ifndef WF_CUBE_BACKGROUND_SKYDOME #define WF_CUBE_BACKGROUND_SKYDOME #include "cube-background.hpp" #include "wayfire/output.hpp" #include class wf_cube_background_skydome : public wf_cube_background_base { public: wf_cube_background_skydome(wf::output_t *output); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; virtual ~wf_cube_background_skydome(); private: wf::output_t *output; void load_program(); void fill_vertices(); void reload_texture(); OpenGL::program_t program; GLuint tex = -1; std::vector vertices; std::vector coords; std::vector indices; std::string last_background_image; int last_mirror = -1; wf::option_wrapper_t background_image{"cube/skydome_texture"}; wf::option_wrapper_t mirror_opt{"cube/skydome_mirror"}; }; #endif /* end of include guard: WF_CUBE_BACKGROUND_SKYDOME */ wayfire-0.10.0/plugins/cube/shaders.tpp0000664000175000017500000000073015053502647017706 0ustar dkondordkondor#pragma once static const char* cube_vertex_2_0 = R"(#version 100 attribute highp vec3 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 VP; uniform mat4 model; void main() { gl_Position = VP * model * vec4(position, 1.0); uvpos = uvPosition; })"; static const char* cube_fragment_2_0 = R"(#version 100 varying highp vec2 uvpos; uniform sampler2D smp; void main() { gl_FragColor = vec4(texture2D(smp, uvpos).xyz, 1); })"; wayfire-0.10.0/plugins/cube/cubemap.hpp0000664000175000017500000000130315053502647017652 0ustar dkondordkondor#ifndef WF_CUBE_CUBEMAP_HPP #define WF_CUBE_CUBEMAP_HPP #include "cube-background.hpp" class wf_cube_background_cubemap : public wf_cube_background_base { public: wf_cube_background_cubemap(); virtual void render_frame(const wf::render_target_t& fb, wf_cube_animation_attribs& attribs) override; ~wf_cube_background_cubemap(); private: void reload_texture(); void create_program(); OpenGL::program_t program; GLuint tex = -1; GLuint vbo_cube_vertices; GLuint ibo_cube_indices; std::string last_background_image; wf::option_wrapper_t background_image{"cube/cubemap_image"}; }; #endif /* end of include guard: WF_CUBE_CUBEMAP_HPP */ wayfire-0.10.0/plugins/cube/shaders-3-2.tpp0000664000175000017500000000674715053502647020223 0ustar dkondordkondorstatic const char *cube_vertex_3_2 = R"(#version 320 es in vec3 position; in vec2 uvPosition; out vec2 uvpos; out vec3 vPos; void main() { vPos = position; uvpos = uvPosition; })"; static const char *cube_tcs_3_2 = R"(#version 320 es layout(vertices = 3) out; in vec2 uvpos[]; in vec3 vPos[]; out vec3 tcPosition[]; out vec2 uv[]; #define ID gl_InvocationID uniform int deform; uniform int light; void main() { tcPosition[ID] = vPos[ID]; uv[ID] = uvpos[ID]; if(ID == 0){ /* deformation requires tessellation and lighting even higher degree to make lighting smoother */ float tessLevel = 1.0f; if(deform > 0) tessLevel = 30.0f; if(light > 0) tessLevel = 50.0f; gl_TessLevelInner[0] = tessLevel; gl_TessLevelOuter[0] = tessLevel; gl_TessLevelOuter[1] = tessLevel; gl_TessLevelOuter[2] = tessLevel; } })"; static const char *cube_tes_3_2 = R"(#version 320 es layout(triangles) in; in vec3 tcPosition[]; in vec2 uv[]; out vec2 tesuv; out vec3 tePosition; uniform mat4 model; uniform mat4 VP; uniform int deform; uniform float ease; vec2 interpolate2D(vec2 v0, vec2 v1, vec2 v2) { return vec2(gl_TessCoord.x) * v0 + vec2(gl_TessCoord.y) * v1 + vec2(gl_TessCoord.z) * v2; } vec3 interpolate3D(vec3 v0, vec3 v1, vec3 v2) { return vec3(gl_TessCoord.x) * v0 + vec3(gl_TessCoord.y) * v1 + vec3(gl_TessCoord.z) * v2; } vec3 tp; void main() { tesuv = interpolate2D(uv[0], uv[1], uv[2]); tp = interpolate3D(tcPosition[0], tcPosition[1], tcPosition[2]); tp = (model * vec4(tp, 1.0)).xyz; if(deform > 0) { float r = 0.5; float d = distance(tp.xz, vec2(0, 0)); float scale = 1.0; if(deform == 1) scale = r / d; else scale = d / r; scale = pow(scale, ease); tp = vec3(tp[0] * scale, tp[1], tp[2] * scale); } tePosition = tp; gl_Position = VP * vec4 (tp, 1.0); })"; static const char* cube_geometry_3_2 = R"(#version 320 es layout(triangles) in; layout(triangle_strip, max_vertices = 3) out; in vec2 tesuv[3]; in vec3 tePosition[3]; uniform int light; out vec2 guv; out vec3 colorFactor; #define AL 0.3 // ambient lighting #define DL (1.0-AL) // diffuse lighting void main() { const vec3 lightSource = vec3(0, 0, 2); const vec3 lightNormal = normalize(lightSource); if(light == 1) { vec3 A = tePosition[2] - tePosition[0]; vec3 B = tePosition[1] - tePosition[0]; vec3 N = normalize(cross(A, B)); vec3 center = (tePosition[0] + tePosition[1] + tePosition[2]) / 3.0; float d = distance(center, lightSource); float ambient_coeff = pow(clamp(2.0 / d, 0.0, 1.0), 10.0); float value = clamp(pow(abs(dot(N, lightNormal)), 1.5), 0.0, 1.0); float df = AL * ambient_coeff + DL * value; colorFactor = vec3(df, df, df); } else colorFactor = vec3(1.0, 1.0, 1.0); gl_Position = gl_in[0].gl_Position; guv = tesuv[0]; EmitVertex(); gl_Position = gl_in[1].gl_Position; guv = tesuv[1]; EmitVertex(); gl_Position = gl_in[2].gl_Position; guv = tesuv[2]; EmitVertex(); })"; static const char *cube_fragment_3_2 = R"(#version 320 es in highp vec2 guv; in highp vec3 colorFactor; layout(location = 0) out highp vec4 outColor; uniform sampler2D smp; void main() { outColor = vec4(texture(smp, guv).xyz * colorFactor, 1.0); })"; wayfire-0.10.0/plugins/blur/0000775000175000017500000000000015053502647015556 5ustar dkondordkondorwayfire-0.10.0/plugins/blur/blur.hpp0000664000175000017500000001752415053502647017244 0ustar dkondordkondor#pragma once #include "wayfire/geometry.hpp" #include #include #include #include /* The MIT License (MIT) * * Copyright (c) 2018 Ilia Bozhinov * Copyright (c) 2018 Scott Moreau * * The design of blur takes extra consideration due to the fact that * the results of blurred pixels rely on surrounding pixel values. * This means that when damage happens for only part of the scene (1), * blurring this area can result to artifacts because of sampling * beyond the edges of the area. To work around this issue, wayfire * issues two signals - workspace-stream-pre and workspace-stream-post. * workspace-stream-pre gives plugins an opportunity to pad the rects * of the damage region (2) and save a snap-shot of the padded area from * the buffer containing the last frame. This will be used to redraw * the area that will contain artifacts after rendering. This is ok * because this area is outside of the original damage area, so the * pixels won't be changing in this region of the scene. pre_render is * called with the padded damage region as an argument (2). The padded * damage extents (3) are used for blitting from the framebuffer, which * contains the scene rendered up until the view for which pre_render * is called. The padded damage extents rect is blurred with artifacts * in pre_render, after which it is then alpha blended with the window * and rendered to the framebuffer. Finally, workspace-stream-post * allows a chance to redraw the padded area with the saved pixels, * before swapping buffers. As long as the padding is enough to cover * the maximum sample offset that the shader uses, there should be a * seamless experience. * * 1) * ................................................................. * | | * | | * | .................................. | * | | |.. | * | | | | | * | | Damage region | | | * | | | | | * | | | | | * | | | | | * | | | | | * | ```|```````````````````````````````| | * | ````````````````````````````````` | * | | * | | * ````````````````````````````````````````````````````````````````` * * 2) * ................................................................. * | | * | ...................................... | * | | .................................. |..<-- Padding | * | | | |.. | | * | | | | | | | * | | | Padded | | | | * | | | Damage region | | | | * | | | | | | | * | | | | | | | * | | | | | | | * | | | | | | | * | | ```|```````````````````````````````| | | * | ```| ````````````````````````````````` |<-- Padding | * | ````````````````````````````````````` | * ````````````````````````````````````````````````````````````````` * * 3) * ................................................................. * | | * | x1| |x2 | * | y1__ ...................................... . | * | | |.. | * | | | | * | | ^ | | * | | | | * | | <- Padded extents -> | | * | | | | * | | v | | * | | | | * | y2__ ```| | | * | ` ````````````````````````````````````` | * | | * ````````````````````````````````````````````````````````````````` */ class wf_blur_base { protected: /* used to store temporary results in blur algorithms, cleaned up in base * destructor */ wf::auxilliary_buffer_t fb[2]; wf::geometry_t prepared_geometry; /* the program created by the given algorithm, cleaned up in base destructor */ OpenGL::program_t program[2]; /* the program used by wf_blur_base to combine the blurred, unblurred and * view texture */ OpenGL::program_t blend_program; /* used to get individual algorithm options from config * should be set by the constructor */ std::string algorithm_name; wf::option_wrapper_t saturation_opt; wf::option_wrapper_t offset_opt; wf::option_wrapper_t degrade_opt, iterations_opt; wf::config::option_base_t::updated_callback_t options_changed; /* renders the in texture to the out framebuffer. * assumes a properly bound and initialized GL program */ void render_iteration(wf::region_t blur_region, wf::auxilliary_buffer_t& in, wf::auxilliary_buffer_t& out, int width, int height); /* copy the source pixels from region, storing into result * returns the result geometry, in framebuffer coords */ wlr_box copy_region(wf::auxilliary_buffer_t& result, const wf::render_target_t& source, const wf::region_t& region); /* blur fb[0] * width and height are the scaled dimensions of the buffer * returns the index of the fb where the result is stored (0 or 1) */ virtual int blur_fb0(const wf::region_t& blur_region, int width, int height) = 0; public: wf_blur_base(std::string name); virtual ~wf_blur_base(); virtual int calculate_blur_radius(); /** * Calculate the blurred background region. * * @param target_fb A render target containing the background to be blurred. * @param damage The region to be blurred. */ void prepare_blur(const wf::render_target_t& target_fb, const wf::region_t& damage); /** * Render a view with a blended background as prepared from @prepare_blur. * * @param src_tex The texture of the view to render. * @param src_box The geometry of the view in framebuffer logical coordinates. * @param damage The region to repaint, in logical coordinates. * @param background_source_fb The framebuffer used to prepare the background blur. * @param target_fb The target to draw to. */ void render(wf::gles_texture_t src_tex, wlr_box src_box, const wf::region_t& damage, const wf::render_target_t& background_source_fb, const wf::render_target_t& target_fb); }; std::unique_ptr create_box_blur(); std::unique_ptr create_bokeh_blur(); std::unique_ptr create_kawase_blur(); std::unique_ptr create_gaussian_blur(); std::unique_ptr create_blur_from_name(std::string algorithm_name); wayfire-0.10.0/plugins/blur/bokeh.cpp0000664000175000017500000000543415053502647017360 0ustar dkondordkondor#include "blur.hpp" static const char *bokeh_vertex_shader = R"( #version 100 attribute highp vec2 position; varying highp vec2 uv; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uv = (position.xy + vec2(1.0, 1.0)) / 2.0; } )"; static const char *bokeh_fragment_shader = R"( #version 100 precision highp float; uniform float offset; uniform int iterations; uniform vec2 halfpixel; uniform int mode; uniform sampler2D bg_texture; varying highp vec2 uv; #define GOLDEN_ANGLE 2.39996 mat2 rot = mat2(cos(GOLDEN_ANGLE), sin(GOLDEN_ANGLE), -sin(GOLDEN_ANGLE), cos(GOLDEN_ANGLE)); void main() { float radius = offset; vec4 acc = vec4(0), div = acc; float r = 1.0; vec2 vangle = vec2(radius / sqrt(float(iterations)), radius / sqrt(float(iterations))); for (int j = 0; j < iterations; j++) { r += 1.0 / r; vangle = rot * vangle; vec4 col = texture2D(bg_texture, uv + (r - 1.0) * vangle * halfpixel * 2.0); vec4 bokeh = pow(col, vec4(4.0)); acc += col * bokeh; div += bokeh; } if (iterations == 0) gl_FragColor = texture2D(bg_texture, uv); else gl_FragColor = acc / div; } )"; class wf_bokeh_blur : public wf_blur_base { public: wf_bokeh_blur() : wf_blur_base("bokeh") { wf::gles::run_in_context_if_gles([&] { program[0].set_simple(OpenGL::compile_program(bokeh_vertex_shader, bokeh_fragment_shader)); }); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int iterations = iterations_opt; float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; wf::gles::run_in_context_if_gles([&] { /* Upload data to shader */ program[0].use(wf::TEXTURE_TYPE_RGBA); program[0].uniform2f("halfpixel", 0.5f / width, 0.5f / height); program[0].uniform1f("offset", offset); program[0].uniform1i("iterations", iterations); program[0].attrib_pointer("position", 2, 0, vertexData); GL_CALL(glDisable(GL_BLEND)); render_iteration(blur_region, fb[0], fb[1], width, height); /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[0].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); }); return 1; } int calculate_blur_radius() override { return 5 * wf_blur_base::offset_opt*wf_blur_base::degrade_opt; } }; std::unique_ptr create_bokeh_blur() { return std::make_unique(); } wayfire-0.10.0/plugins/blur/meson.build0000664000175000017500000000121615053502647017720 0ustar dkondordkondorblur_base = shared_library('wayfire-blur-base', ['blur-base.cpp', 'box.cpp', 'gaussian.cpp', 'kawase.cpp', 'bokeh.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], override_options: ['b_lundef=false'], install: true) install_headers(['blur.hpp'], subdir: 'wayfire/plugins/blur') blur = shared_module('blur', ['blur.cpp'], link_with: blur_base, include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.10.0/plugins/blur/blur.cpp0000664000175000017500000003153015053502647017230 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include #include #include "blur.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include using blur_algorithm_provider = std::function()>; static int calculate_damage_padding(const wf::render_target_t& target, int blur_radius) { float scale = target.scale; if (target.subbuffer) { const float subbox_scale_x = 1.0 * target.subbuffer->width / target.geometry.width; const float subbox_scale_y = 1.0 * target.subbuffer->height / target.geometry.height; scale *= std::max(subbox_scale_x, subbox_scale_y); } return std::ceil(blur_radius / scale); } namespace wf { namespace scene { class blur_node_t : public transformer_base_node_t { public: blur_algorithm_provider provider; blur_node_t(blur_algorithm_provider provider) : transformer_base_node_t(false) { this->provider = provider; } std::string stringify() const override { return "blur"; } void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; struct saved_pixels_t { wf::auxilliary_buffer_t pixels; wf::region_t region; bool taken = false; }; std::list saved_pixels; saved_pixels_t *acquire_saved_pixel_buffer() { auto it = std::find_if(saved_pixels.begin(), saved_pixels.end(), [] (const saved_pixels_t& buffer) { return !buffer.taken; }); if (it != saved_pixels.end()) { it->taken = true; return &(*it); } saved_pixels.emplace_back(); saved_pixels.back().taken = true; return &saved_pixels.back(); } void release_saved_pixel_buffer(saved_pixels_t *buffer) { buffer->taken = false; } }; class blur_render_instance_t : public transformer_render_instance_t { blur_node_t::saved_pixels_t *saved_pixels = nullptr; public: using transformer_render_instance_t::transformer_render_instance_t; bool is_fully_opaque(wf::region_t damage) { if (self->get_children().size() == 1) { if (auto opaque = dynamic_cast(self->get_children().front().get())) { return (damage ^ opaque->get_opaque_region()).empty(); } } return false; } wf::region_t calculate_translucent_damage(const wf::render_target_t& target, wf::region_t damage) { if (self->get_children().size() == 1) { if (auto opaque = dynamic_cast(self->get_children().front().get())) { const int padding = calculate_damage_padding(target, self->provider()->calculate_blur_radius()); auto opaque_region = opaque->get_opaque_region(); opaque_region.expand_edges(-padding); wf::region_t translucent_region = damage ^ opaque_region; return translucent_region; } } return damage; } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { const int padding = calculate_damage_padding(target, self->provider()->calculate_blur_radius()); auto bbox = self->get_bounding_box(); // In order to render a part of the blurred background, we need to sample // from area which is larger than the damaged area. However, the edges // of the expanded area suffer from the same problem (e.g. the blurred // background has artifacts). The solution to this is to expand the // damage and keep a copy of the pixels where we redraw, but wouldn't // have redrawn if not for blur. After that, we copy those old areas // back to the destination framebuffer, giving the illusion that they // were never damaged. auto padded_region = damage & bbox; if (is_fully_opaque(padded_region & target.geometry)) { // If there are no regions to blur, we can directly render them. for (auto& ch : this->children) { ch->schedule_instructions(instructions, target, damage); } return; } padded_region.expand_edges(padding); padded_region &= bbox; // Don't forget to keep expanded damage within the bounds of the render // target, otherwise we may be sampling from outside of it (undefined // contents). padded_region &= target.geometry; // Actual region which will be repainted by this render instance. wf::region_t we_repaint = padded_region; this->saved_pixels = self->acquire_saved_pixel_buffer(); saved_pixels->region = target.framebuffer_region_from_geometry_region(padded_region) ^ target.framebuffer_region_from_geometry_region(damage); // Nodes below should re-render the padded areas so that we can sample from them damage |= padded_region; saved_pixels->pixels.allocate(target.get_size()); wf::gles::run_in_context_if_gles([&] { GLuint target_fb = wf::gles::ensure_render_buffer_fb_id(target); wf::gles::bind_render_buffer(saved_pixels->pixels.get_renderbuffer()); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, target_fb)); /* Copy pixels in padded_region from target_fb to saved_pixels. */ for (const auto& box : saved_pixels->region) { GL_CALL(glBlitFramebuffer( box.x1, box.y1, box.x2, box.y2, box.x1, box.y1, box.x2, box.y2, GL_COLOR_BUFFER_BIT, GL_LINEAR)); } }); instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = we_repaint, }); } void render(const wf::scene::render_instruction_t& data) override { auto bounding_box = self->get_bounding_box(); data.pass->custom_gles_subpass([&] { auto tex = wf::gles_texture_t{get_texture(data.target.scale)}; if (!data.damage.empty()) { auto translucent_damage = calculate_translucent_damage(data.target, data.damage); self->provider()->prepare_blur(data.target, translucent_damage); self->provider()->render(tex, bounding_box, data.damage, data.target, data.target); } GL_CALL(glDisable(GL_SCISSOR_TEST)); GLuint saved_fb = wf::gles::ensure_render_buffer_fb_id(saved_pixels->pixels.get_renderbuffer()); wf::gles::bind_render_buffer(data.target); // Setup framebuffer I/O. target_fb contains the frame // rendered with expanded damage and artifacts on the edges. // saved_pixels has the the padded region of pixels to overwrite the // artifacts that blurring has left behind. GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, saved_fb)); /* Copy pixels back from saved_pixels to target_fb. */ for (const auto& box : saved_pixels->region) { GL_CALL(glBlitFramebuffer( box.x1, box.y1, box.x2, box.y2, box.x1, box.y1, box.x2, box.y2, GL_COLOR_BUFFER_BIT, GL_LINEAR)); } /* Reset stuff */ saved_pixels->region.clear(); self->release_saved_pixel_buffer(saved_pixels); saved_pixels = NULL; }); } direct_scanout try_scanout(wf::output_t *output) override { // Enable direct scanout if it is possible return scene::try_scanout_from_list(children, output); } }; void blur_node_t::gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) { auto uptr = std::make_unique(this, push_damage, shown_on); if (uptr->has_instances()) { instances.push_back(std::move(uptr)); } } } } class wayfire_blur : public wf::plugin_interface_t { // Before doing a render pass, expand the damage by the blur radius. // This is needed, because when blurring, the pixels that changed // affect a larger area than the really damaged region, e.g. the region // that comes from client damage. wf::signal::connection_t on_render_pass_begin = [=] (wf::render_pass_begin_signal *ev) { if (!provider) { return; } const int padding = calculate_damage_padding(ev->pass.get_target(), provider()->calculate_blur_radius()); ev->damage.expand_edges(padding); ev->damage &= ev->pass.get_target().geometry; }; public: blur_algorithm_provider provider; wf::button_callback button_toggle; wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (blur_by_default.matches(ev->view)) { add_transformer(ev->view); } }; wf::view_matcher_t blur_by_default{"blur/blur_by_default"}; wf::option_wrapper_t method_opt{"blur/method"}; wf::option_wrapper_t toggle_button{"blur/toggle"}; wf::config::option_base_t::updated_callback_t blur_method_changed; std::unique_ptr blur_algorithm; void add_transformer(wayfire_view view) { auto tmanager = view->get_transformed_node(); if (tmanager->get_transformer()) { return; } auto provider = [=] () { return blur_algorithm.get(); }; auto node = std::make_shared(provider); tmanager->add_transformer(node, wf::TRANSFORMER_BLUR); } void pop_transformer(wayfire_view view) { auto tmanager = view->get_transformed_node(); tmanager->rem_transformer(); } void remove_transformers() { for (auto& view : wf::get_core().get_all_views()) { pop_transformer(view); } } public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("blur: requires GLES2 support, but current renderer is ", render_type); return; } wf::get_core().connect(&on_render_pass_begin); blur_method_changed = [=] () { blur_algorithm = create_blur_from_name(method_opt); wf::scene::damage_node(wf::get_core().scene(), wf::get_core().scene()->get_bounding_box()); }; /* Create initial blur algorithm */ blur_method_changed(); method_opt.set_callback(blur_method_changed); /* Toggles the blur state of the view the user clicked on */ button_toggle = [=] (auto) { auto view = wf::get_core().get_cursor_focus_view(); if (!view) { return false; } if (view->get_transformed_node()->get_transformer()) { pop_transformer(view); } else { add_transformer(view); } return true; }; wf::get_core().bindings->add_button(toggle_button, &button_toggle); provider = [=] () { return this->blur_algorithm.get(); }; wf::get_core().connect(&on_view_mapped); for (auto& view : wf::get_core().get_all_views()) { if (blur_by_default.matches(view)) { add_transformer(view); } } } void fini() override { remove_transformers(); wf::get_core().bindings->rem_binding(&button_toggle); /* Call blur algorithm destructor */ blur_algorithm = nullptr; } }; DECLARE_WAYFIRE_PLUGIN(wayfire_blur); wayfire-0.10.0/plugins/blur/kawase.cpp0000664000175000017500000001057415053502647017544 0ustar dkondordkondor#include "blur.hpp" static const char *kawase_vertex_shader = R"( #version 100 attribute highp vec2 position; varying highp vec2 uv; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uv = (position.xy + vec2(1.0, 1.0)) / 2.0; })"; static const char *kawase_fragment_shader_down = R"( #version 100 precision highp float; uniform float offset; uniform vec2 halfpixel; uniform sampler2D bg_texture; varying highp vec2 uv; void main() { vec4 sum = texture2D(bg_texture, uv) * 4.0; sum += texture2D(bg_texture, uv - halfpixel.xy * offset); sum += texture2D(bg_texture, uv + halfpixel.xy * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset); sum += texture2D(bg_texture, uv - vec2(halfpixel.x, -halfpixel.y) * offset); gl_FragColor = sum / 8.0; })"; static const char *kawase_fragment_shader_up = R"( #version 100 precision highp float; uniform float offset; uniform vec2 halfpixel; uniform sampler2D bg_texture; varying highp vec2 uv; void main() { vec4 sum = texture2D(bg_texture, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(bg_texture, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(0.0, halfpixel.y * 2.0) * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); sum += texture2D(bg_texture, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; sum += texture2D(bg_texture, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); sum += texture2D(bg_texture, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; gl_FragColor = sum / 12.0; })"; class wf_kawase_blur : public wf_blur_base { public: wf_kawase_blur() : wf_blur_base("kawase") { wf::gles::run_in_context_if_gles([&] { program[0].set_simple(OpenGL::compile_program(kawase_vertex_shader, kawase_fragment_shader_down)); program[1].set_simple(OpenGL::compile_program(kawase_vertex_shader, kawase_fragment_shader_up)); }); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int iterations = iterations_opt; float offset = offset_opt; int sampleWidth, sampleHeight; /* Upload data to shader */ static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; program[0].use(wf::TEXTURE_TYPE_RGBA); /* Downsample */ program[0].attrib_pointer("position", 2, 0, vertexData); /* Disable blending, because we may have transparent background, which * we want to render on uncleared framebuffer */ GL_CALL(glDisable(GL_BLEND)); program[0].uniform1f("offset", offset); for (int i = 0; i < iterations; i++) { sampleWidth = width / (1 << i); sampleHeight = height / (1 << i); auto region = blur_region * (1.0 / (1 << i)); program[0].uniform2f("halfpixel", 0.5f / sampleWidth, 0.5f / sampleHeight); render_iteration(region, fb[i % 2], fb[1 - i % 2], sampleWidth, sampleHeight); } program[0].deactivate(); /* Upsample */ program[1].use(wf::TEXTURE_TYPE_RGBA); program[1].attrib_pointer("position", 2, 0, vertexData); program[1].uniform1f("offset", offset); for (int i = iterations - 1; i >= 0; i--) { sampleWidth = width / (1 << i); sampleHeight = height / (1 << i); auto region = blur_region * (1.0 / (1 << i)); program[1].uniform2f("halfpixel", 0.5f / sampleWidth, 0.5f / sampleHeight); render_iteration(region, fb[1 - i % 2], fb[i % 2], sampleWidth, sampleHeight); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[1].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); return 0; } int calculate_blur_radius() override { return pow(2, iterations_opt + 1) * offset_opt * degrade_opt; } }; std::unique_ptr create_kawase_blur() { return std::make_unique(); } wayfire-0.10.0/plugins/blur/gaussian.cpp0000664000175000017500000001021315053502647020071 0ustar dkondordkondor#include "blur.hpp" static const char *gaussian_vertex_shader = R"( #version 100 attribute highp vec2 position; uniform vec2 size; uniform float offset; varying highp vec2 blurcoord[5]; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); vec2 texcoord = (position.xy + vec2(1.0, 1.0)) / 2.0; blurcoord[0] = texcoord; blurcoord[1] = texcoord + vec2(1.5 * offset) / size; blurcoord[2] = texcoord - vec2(1.5 * offset) / size; blurcoord[3] = texcoord + vec2(3.5 * offset) / size; blurcoord[4] = texcoord - vec2(3.5 * offset) / size; } )"; static const char *gaussian_fragment_shader_horz = R"( #version 100 precision highp float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); bp += texture2D(bg_texture, vec2(blurcoord[0].x, uv.y)) * 0.204164; bp += texture2D(bg_texture, vec2(blurcoord[1].x, uv.y)) * 0.304005; bp += texture2D(bg_texture, vec2(blurcoord[2].x, uv.y)) * 0.304005; bp += texture2D(bg_texture, vec2(blurcoord[3].x, uv.y)) * 0.093913; bp += texture2D(bg_texture, vec2(blurcoord[4].x, uv.y)) * 0.093913; gl_FragColor = bp; })"; static const char *gaussian_fragment_shader_vert = R"( #version 100 precision highp float; uniform sampler2D bg_texture; uniform int mode; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); bp += texture2D(bg_texture, vec2(uv.x, blurcoord[0].y)) * 0.204164; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[1].y)) * 0.304005; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[2].y)) * 0.304005; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[3].y)) * 0.093913; bp += texture2D(bg_texture, vec2(uv.x, blurcoord[4].y)) * 0.093913; gl_FragColor = bp; })"; class wf_gaussian_blur : public wf_blur_base { public: wf_gaussian_blur() : wf_blur_base("gaussian") { wf::gles::run_in_context_if_gles([&] { program[0].set_simple(OpenGL::compile_program( gaussian_vertex_shader, gaussian_fragment_shader_horz)); program[1].set_simple(OpenGL::compile_program( gaussian_vertex_shader, gaussian_fragment_shader_vert)); }); } void upload_data(int i, int width, int height) { float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; program[i].use(wf::TEXTURE_TYPE_RGBA); program[i].uniform2f("size", width, height); program[i].uniform1f("offset", offset); program[i].attrib_pointer("position", 2, 0, vertexData); } void blur(const wf::region_t& blur_region, int i, int width, int height) { program[i].use(wf::TEXTURE_TYPE_RGBA); render_iteration(blur_region, fb[i], fb[!i], width, height); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int i, iterations = iterations_opt; wf::gles::run_in_context_if_gles([&] { GL_CALL(glDisable(GL_BLEND)); /* Enable our shader and pass some data to it. The shader * does gaussian blur on the background texture in two passes, * one horizontal and one vertical */ upload_data(0, width, height); upload_data(1, width, height); for (i = 0; i < iterations; i++) { /* Blur horizontally */ blur(blur_region, 0, width, height); /* Blur vertically */ blur(blur_region, 1, width, height); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program[1].deactivate(); }); return 0; } int calculate_blur_radius() override { return 4 * wf_blur_base::calculate_blur_radius(); } }; std::unique_ptr create_gaussian_blur() { return std::make_unique(); } wayfire-0.10.0/plugins/blur/blur-base.cpp0000664000175000017500000002467515053502647020154 0ustar dkondordkondor#include "blur.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include #include #include #include static const char *blur_blend_vertex_shader = R"( #version 100 attribute highp vec2 position; attribute highp vec2 uv_in; varying highp vec2 uvpos[2]; uniform mat4 mvp; uniform mat4 background_uv_matrix; void main() { gl_Position = mvp * vec4(position, 0.0, 1.0); uvpos[0] = uv_in; uvpos[1] = vec4(background_uv_matrix * vec4(uv_in - 0.5, 0.0, 1.0)).xy + 0.5; })"; static const char *blur_blend_fragment_shader = R"( #version 100 @builtin_ext@ precision highp float; @builtin@ uniform float sat; uniform sampler2D bg_texture; varying highp vec2 uvpos[2]; vec3 saturation(vec3 rgb, float adjustment) { // Algorithm from Chapter 16 of OpenGL Shading Language const vec3 w = vec3(0.2125, 0.7154, 0.0721); vec3 intensity = vec3(dot(rgb, w)); return mix(intensity, rgb, adjustment); } void main() { vec4 bp = texture2D(bg_texture, uvpos[1]); bp = vec4(saturation(bp.rgb, sat), bp.a); vec4 wp = get_pixel(uvpos[0]); vec4 c = clamp(4.0 * wp.a, 0.0, 1.0) * bp; gl_FragColor = wp + (1.0 - wp.a) * c; })"; wf_blur_base::wf_blur_base(std::string name) { this->algorithm_name = name; this->saturation_opt.load_option("blur/saturation"); this->offset_opt.load_option("blur/" + algorithm_name + "_offset"); this->degrade_opt.load_option("blur/" + algorithm_name + "_degrade"); this->iterations_opt.load_option("blur/" + algorithm_name + "_iterations"); this->options_changed = [=] () { wf::scene::damage_node(wf::get_core().scene(), wf::get_core().scene()->get_bounding_box()); }; this->saturation_opt.set_callback(options_changed); this->offset_opt.set_callback(options_changed); this->degrade_opt.set_callback(options_changed); this->iterations_opt.set_callback(options_changed); wf::gles::run_in_context_if_gles([&] { blend_program.compile(blur_blend_vertex_shader, blur_blend_fragment_shader); }); } wf_blur_base::~wf_blur_base() { wf::gles::run_in_context_if_gles([&] { program[0].free_resources(); program[1].free_resources(); blend_program.free_resources(); }); } int wf_blur_base::calculate_blur_radius() { return offset_opt * degrade_opt * std::max(1, (int)iterations_opt); } void wf_blur_base::render_iteration(wf::region_t blur_region, wf::auxilliary_buffer_t& in, wf::auxilliary_buffer_t& out, int width, int height) { /* Special case for small regions where we can't really blur, because we * simply have too few pixels */ width = std::max(width, 1); height = std::max(height, 1); out.allocate({width, height}); GLuint tex_id = wf::gles_texture_t::from_aux(in).tex_id; wf::gles::bind_render_buffer(out.get_renderbuffer()); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, tex_id)); for (auto& b : blur_region) { wf::gles::scissor_render_buffer(out.get_renderbuffer(), wlr_box_from_pixman_box(b)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } } /** @return Smallest integer >= x which is divisible by mod */ static int round_up(int x, int mod) { return mod * int((x + mod - 1) / mod); } /** * Calculate the smallest box which contains @box and whose x, y, width, height * are divisible by @degrade, and clamp that box to @bounds. */ static wf::geometry_t sanitize(wf::geometry_t box, int degrade, wf::geometry_t bounds) { wf::geometry_t out_box; out_box.x = degrade * int(box.x / degrade); out_box.y = degrade * int(box.y / degrade); out_box.width = round_up(box.width, degrade); out_box.height = round_up(box.height, degrade); if (out_box.x + out_box.width < box.x + box.width) { out_box.width += degrade; } if (out_box.y + out_box.height < box.y + box.height) { out_box.height += degrade; } return wf::clamp(out_box, bounds); } wlr_box wf_blur_base::copy_region(wf::auxilliary_buffer_t& result, const wf::render_target_t& source, const wf::region_t& region) { auto subbox = source.framebuffer_box_from_geometry_box( wlr_box_from_pixman_box(region.get_extents())); auto source_box = source.framebuffer_box_from_geometry_box(source.geometry); // Make sure that the box is aligned properly for degrading, otherwise, // we get a flickering subbox = sanitize(subbox, degrade_opt, source_box); int degraded_width = subbox.width / degrade_opt; int degraded_height = subbox.height / degrade_opt; result.allocate({degraded_width, degraded_height}); GLuint src_fb = wf::gles::ensure_render_buffer_fb_id(source); GLuint dst_fb = wf::gles::ensure_render_buffer_fb_id(result.get_renderbuffer()); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, src_fb)); GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_fb)); GL_CALL(glBlitFramebuffer( subbox.x, subbox.y, subbox.x + subbox.width, subbox.y + subbox.height, 0, 0, degraded_width, degraded_height, GL_COLOR_BUFFER_BIT, GL_NEAREST)); return subbox; } void wf_blur_base::prepare_blur(const wf::render_target_t& target_fb, const wf::region_t& damage) { if (damage.empty()) { return; } int degrade = degrade_opt; auto damage_box = copy_region(fb[0], target_fb, damage); /* As an optimization, we create a region that blur can use * to perform minimal rendering required to blur. We start * by translating the input damage region */ wf::region_t blur_damage; for (auto b : damage) { blur_damage |= target_fb.framebuffer_box_from_geometry_box( wlr_box_from_pixman_box(b)); } /* Scale and translate the region */ blur_damage += -wf::point_t{damage_box.x, damage_box.y}; blur_damage *= 1.0 / degrade; int r = blur_fb0(blur_damage, fb[0].get_size().width, fb[0].get_size().height); /* Make sure the result is always fb[0], because that's what is used in render() * */ if (r != 0) { std::swap(fb[0], fb[1]); } prepared_geometry = damage_box; } static wf::pointf_t get_center(wf::geometry_t g) { return {g.x + g.width / 2.0, g.y + g.height / 2.0}; } void wf_blur_base::render(wf::gles_texture_t src_tex, wlr_box src_box, const wf::region_t& damage, const wf::render_target_t& background_source_fb, const wf::render_target_t& target_fb) { wf::gles_texture_t blurred_background = wf::gles_texture_t::from_aux(fb[0]); wf::gles::ensure_render_buffer_fb_id(target_fb); blend_program.use(src_tex.type); /* Use shader and enable vertex and texcoord data */ static const float vertex_data_uv[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, }; const float vertex_data_pos[] = { 1.0f * src_box.x, 1.0f * src_box.y + src_box.height, 1.0f * src_box.x + src_box.width, 1.0f * src_box.y + src_box.height, 1.0f * src_box.x + src_box.width, 1.0f * src_box.y, 1.0f * src_box.x, 1.0f * src_box.y, }; blend_program.attrib_pointer("position", 2, 0, vertex_data_pos); blend_program.attrib_pointer("uv_in", 2, 0, vertex_data_uv); // The blurred background is contained in a framebuffer with dimensions equal to the projected damage. // We need to calculate a mapping between the uv coordinates of the view (which may be bigger than the // damage) and the uv coordinates used for sampling the blurred background. // How it works: // 1. translate UV coordinates to (-0.5, -0.5) ~ (0.5, 0.5) range // 2. apply inverse framebuffer transform (needed because on rotated outputs, the framebuffer box includes // rotation). // 3. Scale to match the view size // 4. Translate to match the view auto view_box = background_source_fb.framebuffer_box_from_geometry_box(src_box); // Projected view auto blurred_box = prepared_geometry; // prepared_geometry is the projected damage bounding box glm::mat4 fb_fix = wf::gles::output_transform(target_fb); const auto scale_x = 1.0 * view_box.width / blurred_box.width; const auto scale_y = 1.0 * view_box.height / blurred_box.height; glm::mat4 scale = glm::scale(glm::mat4(1.0), glm::vec3{scale_x, scale_y, 1.0}); const wf::pointf_t center_view = get_center(view_box); const wf::pointf_t center_prepared = get_center(blurred_box); const auto translate_x = 1.0 * (center_view.x - center_prepared.x) / view_box.width; const auto translate_y = 1.0 * (center_view.y - center_prepared.y) / view_box.height; glm::mat4 fix_center = glm::translate(glm::mat4(1.0), glm::vec3{translate_x, translate_y, 0.0}); glm::mat4 composite = scale * fix_center * fb_fix; blend_program.uniformMatrix4f("background_uv_matrix", composite); /* Blend blurred background with window texture src_tex */ blend_program.uniformMatrix4f("mvp", wf::gles::render_target_orthographic_projection(target_fb)); /* XXX: core should give us the number of texture units used */ blend_program.uniform1i("bg_texture", 1); blend_program.uniform1f("sat", saturation_opt); blend_program.set_active_texture(src_tex); GL_CALL(glActiveTexture(GL_TEXTURE0 + 1)); GL_CALL(glBindTexture(GL_TEXTURE_2D, blurred_background.tex_id)); /* Render it to target_fb */ wf::gles::bind_render_buffer(target_fb); for (const auto& box : damage) { wf::gles::render_target_logic_scissor(target_fb, wlr_box_from_pixman_box(box)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } /* * Disable stuff * GL_CALL(glActiveTexture(GL_TEXTURE0 + 1)); */ GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); blend_program.deactivate(); } std::unique_ptr create_blur_from_name(std::string algorithm_name) { if (algorithm_name == "box") { return create_box_blur(); } if (algorithm_name == "bokeh") { return create_bokeh_blur(); } if (algorithm_name == "kawase") { return create_kawase_blur(); } if (algorithm_name == "gaussian") { return create_gaussian_blur(); } LOGE("Unrecognized blur algorithm %s. Using default kawase blur.", algorithm_name.c_str()); return create_kawase_blur(); } wayfire-0.10.0/plugins/blur/box.cpp0000664000175000017500000000673615053502647017066 0ustar dkondordkondor#include "blur.hpp" static const char *box_vertex_shader = R"( #version 100 attribute highp vec2 position; uniform vec2 size; uniform float offset; varying highp vec2 blurcoord[5]; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); vec2 texcoord = (position.xy + vec2(1.0, 1.0)) / 2.0; blurcoord[0] = texcoord; blurcoord[1] = texcoord + vec2(1.5 * offset) / size; blurcoord[2] = texcoord - vec2(1.5 * offset) / size; blurcoord[3] = texcoord + vec2(3.5 * offset) / size; blurcoord[4] = texcoord - vec2(3.5 * offset) / size; } )"; static const char *box_fragment_shader_horz = R"( #version 100 precision highp float; uniform sampler2D bg_texture; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); for(int i = 0; i < 5; i++) { vec2 uv = vec2(blurcoord[i].x, uv.y); bp += texture2D(bg_texture, uv); } gl_FragColor = bp / 5.0; } )"; static const char *box_fragment_shader_vert = R"( #version 100 precision highp float; uniform sampler2D bg_texture; varying highp vec2 blurcoord[5]; void main() { vec2 uv = blurcoord[0]; vec4 bp = vec4(0.0); for(int i = 0; i < 5; i++) { vec2 uv = vec2(uv.x, blurcoord[i].y); bp += texture2D(bg_texture, uv); } gl_FragColor = bp / 5.0; } )"; class wf_box_blur : public wf_blur_base { public: void get_id_locations(int i) {} wf_box_blur() : wf_blur_base("box") { wf::gles::run_in_context_if_gles([&] { program[0].set_simple(OpenGL::compile_program(box_vertex_shader, box_fragment_shader_horz)); program[1].set_simple(OpenGL::compile_program(box_vertex_shader, box_fragment_shader_vert)); }); } void upload_data(int i, int width, int height) { float offset = offset_opt; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; program[i].use(wf::TEXTURE_TYPE_RGBA); program[i].uniform2f("size", width, height); program[i].uniform1f("offset", offset); program[i].attrib_pointer("position", 2, 0, vertexData); } void blur(const wf::region_t& blur_region, int i, int width, int height) { program[i].use(wf::TEXTURE_TYPE_RGBA); render_iteration(blur_region, fb[i], fb[1 - i], width, height); } int blur_fb0(const wf::region_t& blur_region, int width, int height) override { int i, iterations = iterations_opt; GL_CALL(glDisable(GL_BLEND)); /* Enable our shader and pass some data to it. The shader * does box blur on the background texture in two passes, * one horizontal and one vertical */ upload_data(0, width, height); upload_data(1, width, height); for (i = 0; i < iterations; i++) { /* Blur horizontally */ blur(blur_region, 0, width, height); /* Blur vertically */ blur(blur_region, 1, width, height); } /* Reset gl state */ GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); program[0].deactivate(); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); return 0; } int calculate_blur_radius() override { return 4 * wf_blur_base::calculate_blur_radius(); } }; std::unique_ptr create_box_blur() { return std::make_unique(); } wayfire-0.10.0/plugins/vswitch/0000775000175000017500000000000015053502647016301 5ustar dkondordkondorwayfire-0.10.0/plugins/vswitch/wayfire/0000775000175000017500000000000015053502647017747 5ustar dkondordkondorwayfire-0.10.0/plugins/vswitch/wayfire/plugins/0000775000175000017500000000000015053502647021430 5ustar dkondordkondorwayfire-0.10.0/plugins/vswitch/wayfire/plugins/vswitch.hpp0000664000175000017500000002247015053502647023635 0ustar dkondordkondor#pragma once #include "wayfire/scene-input.hpp" #include "wayfire/seat.hpp" #include "wayfire/core.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/util.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { namespace vswitch { /** * A simple class to register the vswitch bindings and get a custom callback called. */ class control_bindings_t { public: /** * Create a vswitch binding instance for the given output. * * The bindings will not be automatically connected. */ control_bindings_t(wf::output_t *output) { this->output = output; workspace_bindings.set_callback(on_cfg_reload); workspace_bindings_win.set_callback(on_cfg_reload); bindings_win.set_callback(on_cfg_reload); } virtual ~control_bindings_t() { tear_down(); } control_bindings_t(const control_bindings_t &) = delete; control_bindings_t(control_bindings_t &&) = delete; control_bindings_t& operator =(const control_bindings_t&) = delete; control_bindings_t& operator =(control_bindings_t&&) = delete; /** * A binding callback for vswitch. * * @param delta The difference between current and target workspace. * @param view The view to be moved together with the switch, or nullptr. * @param window_only Move only the view to the given workspace. It is * guaranteed that @view will not be nullptr if this is true. */ using binding_callback_t = std::function; /** * Connect bindings on the output. * * @param callback The callback to execute on each binding */ void setup(binding_callback_t callback) { tear_down(); this->user_cb = callback; // Setup a new binding on the output. // // @param name The binding name // @param dx, dy The delta from the current workspace // @param only Whether to grab the current view // @param only Whether to move the view without changing workspaces /* *INDENT-OFF* */ // Uncrustify has problems with this macro #define SETUP(name, dx, dy, view, only) \ wf::option_wrapper_t binding_##name { \ "vswitch/"#name}; \ activator_cbs.push_back(std::make_unique()); \ *activator_cbs.back() = [=] (const wf::activator_data_t&) \ {\ return handle_dir({dx, dy}, view, only, callback); \ };\ output->add_activator(binding_##name, activator_cbs.back().get()); // Setup bindings for the 4 directions (left/right/up/down) #define SETUP4(prefix, view, only) \ SETUP(prefix ## _left, -1, 0, view, only) \ SETUP(prefix ## _right, 1, 0, view, only) \ SETUP(prefix ## _up, 0, -1, view, only) \ SETUP(prefix ## _down, 0, 1, view, only) SETUP4(binding, nullptr, false); SETUP4(with_win, get_target_view(), false); SETUP4(send_win, get_target_view(), true); #undef SETUP4 #undef SETUP /* *INDENT-ON* */ // Setup the bindings for switching back to the last workspace for the output /* *INDENT-OFF* */ // Uncrustify has problems with this macro #define SETUP_LAST(name, view, only) \ wf::option_wrapper_t binding_##name { \ "vswitch/"#name}; \ activator_cbs.push_back(std::make_unique()); \ *activator_cbs.back() = [=] (const wf::activator_data_t&) \ {\ return handle_dir(-get_last_dir(), view, only, callback); \ };\ output->add_activator(binding_##name, activator_cbs.back().get()); SETUP_LAST(binding_last, nullptr, false); SETUP_LAST(with_win_last, get_target_view(), false); SETUP_LAST(send_win_last, get_target_view(), true); #undef SETUP_LAST /* *INDENT-ON* */ // Setup a binding for going directly to a workspace identified by a name const auto& setup_direct_binding = [=] (wf::activatorbinding_t binding, std::string workspace_name, bool grab_view, bool only_view) { auto ws_nr = wf::option_type::from_string(workspace_name); if (!ws_nr) { LOGE("Invalid vswitch binding, no such workspace ", workspace_name); return; } int nr = ws_nr.value(); --nr; activator_cbs.push_back(std::make_unique()); *activator_cbs.back() = [=] (const wf::activator_data_t&) { auto [wsize, hsize] = output->wset()->get_workspace_grid_size(); // Calculate target x,y each time. // Workspace grid size might change at runtime, so we cannot // compute them at initialization time wf::point_t target{ nr % wsize, nr / wsize, }; wf::point_t current = output->wset()->get_current_workspace(); auto view = (grab_view ? get_target_view() : nullptr); return handle_dir(target - current, view, only_view, callback); }; output->add_activator(wf::create_option(binding), activator_cbs.back().get()); }; for (auto& [name, act] : workspace_bindings.value()) { setup_direct_binding(act, name, false, false); } for (auto& [name, act] : workspace_bindings_win.value()) { setup_direct_binding(act, name, true, false); } for (auto& [name, act] : bindings_win.value()) { setup_direct_binding(act, name, true, true); } } /** * Disconnect the bindings. */ void tear_down() { for (auto& cb : activator_cbs) { output->rem_binding(cb.get()); } activator_cbs.clear(); } protected: binding_callback_t user_cb; std::vector> activator_cbs; wf::point_t last_dir = {0, 0}; wf::wl_idle_call idle_reload; wf::config::option_base_t::updated_callback_t on_cfg_reload = [=] () { // Aggregate multiple updates together idle_reload.run_once([=] () { // Reload only if the plugin has already setup bindings once, // otherwise, we do not have any callbacks to register. if (user_cb) { setup(user_cb); } }); }; wf::option_wrapper_t> workspace_bindings{"vswitch/workspace_bindings"}; wf::option_wrapper_t> workspace_bindings_win{"vswitch/workspace_bindings_win"}; wf::option_wrapper_t> bindings_win{"vswitch/bindings_win"}; wf::option_wrapper_t wraparound{"vswitch/wraparound"}; wf::output_t *output; /** Find the view to switch workspace with */ virtual wayfire_toplevel_view get_target_view() { auto view = find_topmost_parent(toplevel_cast(wf::get_core().seat->get_active_view())); if (!view || (view->role != wf::VIEW_ROLE_TOPLEVEL)) { return nullptr; } return view; } virtual wf::point_t get_last_dir() { return this->last_dir; } /** * Handle binding in the given direction. The next workspace will be * determined by the current workspace, target direction and wraparound * mode. */ virtual bool handle_dir(wf::point_t dir, wayfire_toplevel_view view, bool window_only, binding_callback_t callback) { if (!view && window_only) { // Maybe there is no view, in any case, no need to do anything return false; } auto ws = output->wset()->get_current_workspace(); auto target_ws = ws + dir; if (!output->wset()->is_workspace_valid(target_ws)) { if (wraparound) { auto grid_size = output->wset()->get_workspace_grid_size(); target_ws.x = (target_ws.x + grid_size.width) % grid_size.width; target_ws.y = (target_ws.y + grid_size.height) % grid_size.height; } else { target_ws = ws; } } // Remember the direction we are moving now so that we can potentially // move back. Only remember when we are actually changing the workspace // and not just move a view around. if (!window_only) { if (target_ws != ws) { this->last_dir = target_ws - ws; } } return callback(target_ws - ws, view, window_only); } }; } } wayfire-0.10.0/plugins/vswitch/meson.build0000664000175000017500000000072515053502647020447 0ustar dkondordkondorvswitch_inc = include_directories('.') vswitch = shared_module('vswitch', ['vswitch.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], link_with: [workspace_wall], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) install_headers(['wayfire/plugins/vswitch.hpp'], subdir: 'wayfire/plugins') wayfire-0.10.0/plugins/vswitch/vswitch.cpp0000664000175000017500000004660415053502647020506 0ustar dkondordkondor#include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/core.hpp" #include #include #include #include #include #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/unstable/translation-node.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/render-manager.hpp" namespace wf { namespace vswitch { using namespace animation; class workspace_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t dx{*this}; timed_transition_t dy{*this}; }; /** * A small helper function to move a view and its children to workspace @to_ws. * @relative flag tells us if @to_ws is relative to current workspace or not. */ static void move_view(wayfire_toplevel_view view, wf::point_t to_ws, bool relative = false) { // Get the wset. auto wset = view->get_wset(); // Coordinates for moving the view. // We need from, because @to_ws can be relative wf::point_t from, to; from = wset->get_view_main_workspace(view); to = (relative ? from : wf::point_t{0, 0}) + to_ws; // Move all the views of this view's tree, including unmapped ones. for (auto& v : view->enumerate_views(false)) { wset->move_to_workspace(v, to); } if (auto output = view->get_output()) { wf::view_change_workspace_signal signal; signal.view = view; signal.from = from; signal.to = to; output->emit(&signal); } wf::get_core().seat->refocus(); } /** * A simple scenegraph node which draws a view at a fixed position and as an overlay over the workspace wall. */ class vswitch_overlay_node_t : public wf::scene::node_t { std::weak_ptr _view; public: vswitch_overlay_node_t(wayfire_toplevel_view view) : node_t(true) { _view = view->weak_from_this(); } // Since we do not grab focus via a grab node, route focus to the view being rendered as an overlay wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) { if (auto view = _view.lock()) { return view->get_transformed_node()->keyboard_refocus(output); } return wf::keyboard_focus_node_t{}; } virtual void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output = nullptr) { if (auto view = _view.lock()) { view->get_transformed_node()->gen_render_instances(instances, push_damage, output); } } virtual wf::geometry_t get_bounding_box() { if (auto view = _view.lock()) { return view->get_transformed_node()->get_bounding_box(); } return {0, 0, 0, 0}; } }; /** * Represents the action of switching workspaces with the vswitch algorithm. * * The workspace is actually switched at the end of the animation */ class workspace_switch_t { public: /** * Initialize the workspace switch process. * * @param output The output the workspace switch happens on. */ workspace_switch_t(output_t *output) { this->output = output; wall = std::make_unique(output); animation = workspace_animation_t{ wf::option_wrapper_t{"vswitch/duration"} }; } /** * Initialize switching animation. * At this point, the calling plugin needs to have the custom renderer * ability set. */ virtual void start_switch() { /* Setup wall */ wall->set_gap_size(gap); wall->set_viewport(wall->get_workspace_rectangle( output->wset()->get_current_workspace())); wall->set_background_color(background_color); wall->start_output_renderer(); if (overlay_view_node) { wf::scene::readd_front(wf::get_core().scene(), overlay_view_node); } output->render->add_effect(&post_render, OUTPUT_EFFECT_POST); running = true; /* Setup animation */ animation.dx.set(0, 0); animation.dy.set(0, 0); animation.start(); } /** * Start workspace switch animation towards the given workspace, * and set that workspace as current. * * @param workspace The new target workspace. */ virtual void set_target_workspace(point_t workspace) { point_t cws = output->wset()->get_current_workspace(); animation.dx.set(animation.dx + cws.x - workspace.x, 0); animation.dy.set(animation.dy + cws.y - workspace.y, 0); animation.start(); std::vector fixed_views; if (overlay_view) { fixed_views.push_back(overlay_view); } output->wset()->set_workspace(workspace, fixed_views); } /** * Set the overlay view. It will be hidden from the normal workspace layers * and shown on top of the workspace wall. The overlay view's position is * not animated together with the workspace transition, but its alpha is. * * Note: if the view disappears, the caller is responsible for resetting the * overlay view. * * @param view The desired overlay view, or NULL if the overlay view needs * to be unset. */ virtual void set_overlay_view(wayfire_toplevel_view view) { if (this->overlay_view == view) { /* Nothing to do */ return; } /* Reset old view */ if (this->overlay_view) { wf::scene::set_node_enabled(overlay_view->get_transformed_node(), true); overlay_view->get_transformed_node()->rem_transformer( vswitch_view_transformer_name); wf::scene::remove_child(overlay_view_node); overlay_view_node.reset(); } /* Set new view */ this->overlay_view = view; if (view) { view->get_transformed_node()->add_transformer( std::make_shared(view), wf::TRANSFORMER_2D, vswitch_view_transformer_name); wf::scene::set_node_enabled(view->get_transformed_node(), false); // Render as an overlay, but make sure it is translated to the local output auto vswitch_overlay = std::make_shared(view); overlay_view_node = std::make_shared(); overlay_view_node->set_children_list({vswitch_overlay}); overlay_view_node->set_offset(origin(output->get_layout_geometry())); wf::scene::add_front(wf::get_core().scene(), overlay_view_node); } } /** @return the current overlay view, might be NULL. */ virtual wayfire_view get_overlay_view() { return this->overlay_view; } /** * Called automatically when the workspace switch animation is done. * By default, this stops the animation. * * @param normal_exit Whether the operation has ended because of animation * running out, in which case the workspace and the overlay view are * adjusted, and otherwise not. */ virtual void stop_switch(bool normal_exit) { if (normal_exit) { auto old_ws = output->wset()->get_current_workspace(); adjust_overlay_view_switch_done(old_ws); } wall->stop_output_renderer(true); output->render->rem_effect(&post_render); running = false; } virtual bool is_running() const { return running; } virtual ~workspace_switch_t() {} protected: option_wrapper_t gap{"vswitch/gap"}; option_wrapper_t background_color{"vswitch/background"}; workspace_animation_t animation; output_t *output; std::unique_ptr wall; const std::string vswitch_view_transformer_name = "vswitch-transformer"; wayfire_toplevel_view overlay_view; std::shared_ptr overlay_view_node; bool running = false; void update_overlay_fb() { if (!overlay_view) { return; } double progress = animation.progress(); auto tmanager = overlay_view->get_transformed_node(); auto tr = tmanager->get_transformer( vswitch_view_transformer_name); static constexpr double smoothing_in = 0.4; static constexpr double smoothing_out = 0.2; static constexpr double smoothing_amount = 0.5; tmanager->begin_transform_update(); if (progress <= smoothing_in) { tr->alpha = 1.0 - (smoothing_amount / smoothing_in) * progress; } else if (progress >= 1.0 - smoothing_out) { tr->alpha = 1.0 - (smoothing_amount / smoothing_out) * (1.0 - progress); } else { tr->alpha = smoothing_amount; } tmanager->end_transform_update(); } wf::effect_hook_t post_render = [=] () { auto start = wall->get_workspace_rectangle( output->wset()->get_current_workspace()); auto size = output->get_screen_size(); geometry_t viewport = { (int)std::round(animation.dx * (size.width + gap) + start.x), (int)std::round(animation.dy * (size.height + gap) + start.y), start.width, start.height, }; wall->set_viewport(viewport); update_overlay_fb(); output->render->damage_whole(); output->render->schedule_redraw(); if (!animation.running()) { stop_switch(true); } }; /** * Emit the view-change-workspace signal from the old workspace to the current * workspace and unset the view. */ virtual void adjust_overlay_view_switch_done(wf::point_t old_workspace) { if (!overlay_view) { return; } wf::view_change_workspace_signal data; data.view = overlay_view; data.from = old_workspace; data.to = output->wset()->get_current_workspace(); output->emit(&data); set_overlay_view(nullptr); wf::get_core().seat->refocus(); } }; } } class vswitch : public wf::per_output_plugin_instance_t { private: /** * Adapter around the general algorithm, so that our own stop function is * called. */ class vswitch_basic_plugin : public wf::vswitch::workspace_switch_t { public: vswitch_basic_plugin(wf::output_t *output, std::function on_done) : workspace_switch_t(output) { this->on_done = on_done; } void stop_switch(bool normal_exit) override { workspace_switch_t::stop_switch(normal_exit); on_done(); } private: std::function on_done; }; std::unique_ptr algorithm; std::unique_ptr bindings; // Capabilities which are always required for vswitch, for now wall needs // a custom renderer. static constexpr uint32_t base_caps = wf::CAPABILITY_CUSTOM_RENDERER; wf::plugin_activation_data_t grab_interface = { .name = "vswitch", .cancel = [=] () { algorithm->stop_switch(false); }, }; public: void init() { output->connect(&on_set_workspace_request); output->connect(&on_grabbed_view_disappear); algorithm = std::make_unique(output, [=] () { output->deactivate_plugin(&grab_interface); }); bindings = std::make_unique(output); bindings->setup([this] (wf::point_t delta, wayfire_toplevel_view view, bool only_view) { // Do not switch workspace with sticky view, they are on all // workspaces anyway if (view && view->sticky) { view = nullptr; } if (this->set_capabilities(wf::CAPABILITY_MANAGE_DESKTOP)) { if (delta == wf::point_t{0, 0}) { // Consume input event return true; } if (only_view && view) { if (!view->get_wset()) { return false; } wf::vswitch::move_view(view, delta, true); return true; } return add_direction(delta, view); } else { return false; } }); } inline bool is_active() { return output->is_plugin_active(grab_interface.name); } inline bool can_activate() { return is_active() || output->can_activate_plugin(&grab_interface); } /** * Check if we can switch the plugin capabilities. * This makes sense only if the plugin is already active, otherwise, * the operation can succeed. * * @param caps The additional capabilities required, aside from the * base caps. */ bool set_capabilities(uint32_t caps) { uint32_t total_caps = caps | base_caps; if (!is_active()) { this->grab_interface.capabilities = total_caps; return true; } // already have everything needed if ((grab_interface.capabilities & total_caps) == total_caps) { // note: do not downgrade, in case total_caps are a subset of // current_caps return true; } // check for only the additional caps if (output->can_activate_plugin(caps)) { grab_interface.capabilities = total_caps; return true; } else { return false; } } bool add_direction(wf::point_t delta, wayfire_view view = nullptr) { if (!is_active() && !start_switch()) { return false; } if (view && ((view->role != wf::VIEW_ROLE_TOPLEVEL) || !view->is_mapped())) { view = nullptr; } algorithm->set_overlay_view(toplevel_cast(view)); algorithm->set_target_workspace( output->wset()->get_current_workspace() + delta); return true; } wf::signal::connection_t on_grabbed_view_disappear = [=] (wf::view_disappeared_signal *ev) { if (ev->view == algorithm->get_overlay_view()) { algorithm->set_overlay_view(nullptr); } }; wf::signal::connection_t on_set_workspace_request = [=] (wf::workspace_change_request_signal *ev) { if (ev->old_viewport == ev->new_viewport) { // nothing to do ev->carried_out = true; return; } if (is_active()) { ev->carried_out = add_direction(ev->new_viewport - ev->old_viewport); } else { if (this->set_capabilities(0)) { if (ev->fixed_views.size() > 2) { LOGE("NOT IMPLEMENTED: ", "changing workspace with more than 1 fixed view"); } ev->carried_out = add_direction(ev->new_viewport - ev->old_viewport, ev->fixed_views.empty() ? nullptr : ev->fixed_views[0]); } } }; bool start_switch() { if (!output->activate_plugin(&grab_interface)) { return false; } algorithm->start_switch(); return true; } void fini() { if (is_active()) { algorithm->stop_switch(false); } bindings->tear_down(); } }; class wf_vswitch_global_plugin_t : public wf::per_output_plugin_t { wf::shared_data::ref_ptr_t ipc_repo; public: void init() override { per_output_plugin_t::init(); ipc_repo->register_method("vswitch/set-workspace", request_workspace); ipc_repo->register_method("vswitch/send-view", send_view); } void fini() override { per_output_plugin_t::fini(); ipc_repo->unregister_method("vswitch/set-workspace"); ipc_repo->unregister_method("vswitch/send-view"); } wf::ipc::method_callback request_workspace = [=] (const wf::json_t& data) { uint64_t x = wf::ipc::json_get_uint64(data, "x"); uint64_t y = wf::ipc::json_get_uint64(data, "y"); uint64_t output_id = wf::ipc::json_get_uint64(data, "output-id"); std::optional view_id = wf::ipc::json_get_optional_uint64(data, "view-id"); auto wo = wf::ipc::find_output_by_id(output_id); if (!wo) { return wf::ipc::json_error("Invalid output!"); } auto grid_size = wo->wset()->get_workspace_grid_size(); if ((int(data["x"]) >= grid_size.width) || (int(data["y"]) >= grid_size.height)) { return wf::ipc::json_error("Workspace coordinates are too big!"); } wayfire_toplevel_view switch_with_view; if (view_id.has_value()) { auto view = toplevel_cast(wf::ipc::find_view_by_id(view_id.value())); if (!view) { return wf::ipc::json_error("Invalid view or view not toplevel!"); } if (!view->is_mapped()) { return wf::ipc::json_error("Cannot grab unmapped view!"); } if (view->get_output() != wo) { return wf::ipc::json_error("Cannot grab view on a different output!"); } switch_with_view = view; } if (output_instance[wo]->set_capabilities(wf::CAPABILITY_MANAGE_COMPOSITOR)) { wf::point_t new_viewport = {int(x), int(y)}; wf::point_t cur_viewport = wo->wset()->get_current_workspace(); wf::point_t delta = new_viewport - cur_viewport; output_instance[wo]->add_direction(delta, switch_with_view); } return wf::ipc::json_ok(); }; wf::ipc::method_callback send_view = [=] (const wf::json_t& data) { uint64_t x = wf::ipc::json_get_uint64(data, "x"); uint64_t y = wf::ipc::json_get_uint64(data, "y"); uint64_t view_id = wf::ipc::json_get_uint64(data, "view-id"); auto view = wf::toplevel_cast(wf::ipc::find_view_by_id(view_id)); if (!view) { return wf::ipc::json_error("Invalid view or view not toplevel!"); } if (!view->is_mapped()) { return wf::ipc::json_error("Cannot grab unmapped view!"); } if (!view->get_wset()) { return wf::ipc::json_error("The given view does not belong to any wset!"); } auto grid_size = view->get_wset()->get_workspace_grid_size(); if ((int(data["x"]) >= grid_size.width) || (int(data["y"]) >= grid_size.height)) { return wf::ipc::json_error("Workspace coordinates are too big!"); } wf::vswitch::move_view(view, {(int)x, (int)y}); return wf::ipc::json_ok(); }; }; DECLARE_WAYFIRE_PLUGIN(wf_vswitch_global_plugin_t); wayfire-0.10.0/plugins/scale/0000775000175000017500000000000015053502647015701 5ustar dkondordkondorwayfire-0.10.0/plugins/scale/scale-title-overlay.cpp0000664000175000017500000003321715053502647022300 0ustar dkondordkondor#include "scale.hpp" #include "scale-title-overlay.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output.hpp" #include "wayfire/plugins/scale-signal.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view-transform.hpp" #include #include #include #include #include #include /** * Class storing an overlay with a view's title, only stored for parent views. */ struct view_title_texture_t : public wf::custom_data_t { wayfire_toplevel_view view; wf::cairo_text_t overlay; wf::cairo_text_t::params par; bool overflow = false; wayfire_toplevel_view dialog; /* the texture should be rendered on top of this dialog */ /** * Render the overlay text in our texture, cropping it to the size by * the given box. */ void update_overlay_texture(wf::dimensions_t dim) { par.max_size = dim; update_overlay_texture(); } void update_overlay_texture() { auto res = overlay.render_text(view->get_title(), par); overflow = res.width > overlay.get_size().width; } wf::signal::connection_t view_changed_title = [=] (wf::view_title_changed_signal *ev) { update_overlay_texture(); }; view_title_texture_t(wayfire_toplevel_view v, int font_size, const wf::color_t& bg_color, const wf::color_t& text_color, float output_scale) : view(v) { par.font_size = font_size; par.bg_color = bg_color; par.text_color = text_color; par.exact_size = true; par.output_scale = output_scale; view->connect(&view_changed_title); } }; namespace wf { namespace scene { class title_overlay_node_t : public node_t { public: enum class position { TOP, CENTER, BOTTOM, }; /* save the transformed view, since we need it in the destructor */ wayfire_toplevel_view view; /* the position on the screen we currently render to */ wf::geometry_t geometry{0, 0, 0, 0}; scale_show_title_t& parent; unsigned int text_height; /* set in the constructor, should not change */ position pos = position::CENTER; /* Whether we are currently rendering the overlay by this transformer. * Set in the pre-render hook and used in the render function. */ bool overlay_shown = false; wf::wl_idle_call idle_update_title; private: /** * Gets the overlay texture stored with the given view. */ view_title_texture_t& get_overlay_texture(wayfire_toplevel_view view) { auto data = view->get_data(); if (!data) { auto new_data = new view_title_texture_t(view, parent.title_font_size, parent.bg_color, parent.text_color, parent.output->handle->scale); view->store_data(std::unique_ptr( new_data)); return *new_data; } return *data.get(); } wf::geometry_t get_scaled_bbox(wayfire_toplevel_view v) { auto tr = v->get_transformed_node()-> get_transformer("scale"); if (tr) { auto wm_geometry = v->get_geometry(); return get_bbox_for_node(tr, wm_geometry); } return v->get_bounding_box(); } wf::dimensions_t find_maximal_title_size() { wf::dimensions_t max_size = {0, 0}; auto parent = find_topmost_parent(view); for (auto v : parent->enumerate_views()) { if (!v->get_transformed_node()->is_enabled()) { continue; } auto bbox = get_scaled_bbox(v); max_size.width = std::max(max_size.width, bbox.width); max_size.height = std::max(max_size.height, bbox.height); } return max_size; } /** * Check if this view should display an overlay. */ bool should_have_overlay() { if (this->parent.show_view_title_overlay == scale_show_title_t::title_overlay_t::NEVER) { return false; } auto parent = find_topmost_parent(view); if ((this->parent.show_view_title_overlay == scale_show_title_t::title_overlay_t::MOUSE) && (this->parent.last_title_overlay != parent)) { return false; } while (!parent->children.empty()) { parent = parent->children[0]; } return view == parent; } void update_title() { if (!should_have_overlay()) { if (overlay_shown) { this->do_push_damage(get_bounding_box()); } overlay_shown = false; return; } auto old_bbox = get_bounding_box(); overlay_shown = true; auto box = find_maximal_title_size(); auto output_scale = parent.output->handle->scale; /** * regenerate the overlay texture in the following cases: * 1. Output's scale changed * 2. The overlay does not fit anymore * 3. The overlay previously did not fit, but there is more space now * TODO: check if this wastes too high CPU power when views are being * animated and maybe redraw less frequently */ auto& tex = get_overlay_texture(find_topmost_parent(view)); if ((tex.overlay.get_texture().texture == nullptr) || (output_scale != tex.par.output_scale) || (tex.overlay.get_size().width > box.width * output_scale) || (tex.overflow && (tex.overlay.get_size().width < std::floor(box.width * output_scale)))) { tex.par.output_scale = output_scale; tex.update_overlay_texture({box.width, box.height}); } geometry.width = tex.overlay.get_size().width / output_scale; geometry.height = tex.overlay.get_size().height / output_scale; auto bbox = get_scaled_bbox(view); geometry.x = bbox.x + bbox.width / 2 - geometry.width / 2; switch (pos) { case position::TOP: geometry.y = bbox.y; break; case position::CENTER: geometry.y = bbox.y + bbox.height / 2 - geometry.height / 2; break; case position::BOTTOM: geometry.y = bbox.y + bbox.height - geometry.height / 2; break; } this->do_push_damage(old_bbox); this->do_push_damage(get_bounding_box()); } public: title_overlay_node_t( wayfire_toplevel_view view_, position pos_, scale_show_title_t& parent_) : node_t(false), view(view_), parent(parent_), pos(pos_) { auto parent = find_topmost_parent(view); auto& title = get_overlay_texture(parent); if (title.overlay.get_texture().texture != nullptr) { text_height = (unsigned int)std::ceil( title.overlay.get_size().height / title.par.output_scale); } else { text_height = wf::cairo_text_t::measure_height(title.par.font_size, true); } idle_update_title.set_callback([=] () { update_title(); }); idle_update_title.run_once(); } ~title_overlay_node_t() { view->erase_data(); } void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) override; void do_push_damage(wf::region_t updated_region) { node_damage_signal ev; ev.region = updated_region; this->emit(&ev); } std::string stringify() const override { return "scale-title-overlay"; } wf::geometry_t get_bounding_box() override { return geometry; } }; class title_overlay_render_instance_t : public render_instance_t { wf::signal::connection_t on_node_damaged = [=] (node_damage_signal *ev) { push_to_parent(ev->region); }; std::shared_ptr self; damage_callback push_to_parent; public: title_overlay_render_instance_t(title_overlay_node_t *self, damage_callback push_dmg) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_to_parent = push_dmg; self->connect(&on_node_damaged); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (!self->overlay_shown || !self->view->has_data()) { return; } // We want to render ourselves only, the node does not have children instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } void render(const wf::scene::render_instruction_t& data) override { auto& title = *self->view->get_data(); auto tr = self->view->get_transformed_node() ->get_transformer("scale"); if (!title.overlay.get_texture().texture) { /* this should not happen */ return; } data.pass->add_texture(title.overlay.get_texture(), data.target, self->geometry, data.damage, tr->alpha); self->idle_update_title.run_once(); } }; void title_overlay_node_t::gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique( this, push_damage)); } } } scale_show_title_t::scale_show_title_t() : view_filter{[this] (auto) { update_title_overlay_opt(); }}, scale_end{[this] (auto) { show_view_title_overlay = title_overlay_t::NEVER; last_title_overlay = nullptr; post_absolute_motion.disconnect(); post_motion.disconnect(); } }, add_title_overlay{[this] (scale_transformer_added_signal *signal) { const std::string& opt = show_view_title_overlay_opt; if (opt == "never") { /* TODO: support changing this option while scale is running! */ return; } using namespace wf::scene; const std::string& pos_opt = title_position; title_overlay_node_t::position pos = title_overlay_node_t::position::CENTER; if (pos_opt == "top") { pos = title_overlay_node_t::position::TOP; } else if (pos_opt == "bottom") { pos = title_overlay_node_t::position::BOTTOM; } auto tr = signal->view->get_transformed_node()->get_transformer("scale"); auto parent = std::dynamic_pointer_cast( tr->parent()->shared_from_this()); auto node = std::make_shared(signal->view, pos, *this); wf::scene::add_front(parent, node); wf::scene::damage_node(parent, parent->get_bounding_box()); } }, rem_title_overlay{[] (scale_transformer_removed_signal *signal) { using namespace wf::scene; node_t *tr = signal->view->get_transformed_node()->get_transformer("scale").get(); while (tr) { for (auto& ch : tr->get_children()) { if (dynamic_cast(ch.get())) { remove_child(ch); break; } } tr = tr->parent(); } } }, post_motion{[=] (auto) { update_title_overlay_mouse(); } }, post_absolute_motion{[=] (auto) { update_title_overlay_mouse(); } } {} void scale_show_title_t::init(wf::output_t *output) { this->output = output; output->connect(&view_filter); output->connect(&add_title_overlay); output->connect(&rem_title_overlay); output->connect(&scale_end); } void scale_show_title_t::fini() { post_motion.disconnect(); post_absolute_motion.disconnect(); } void scale_show_title_t::update_title_overlay_opt() { const std::string& tmp = show_view_title_overlay_opt; if (tmp == "all") { show_view_title_overlay = title_overlay_t::ALL; } else if (tmp == "mouse") { show_view_title_overlay = title_overlay_t::MOUSE; } else { show_view_title_overlay = title_overlay_t::NEVER; } if (show_view_title_overlay == title_overlay_t::MOUSE) { update_title_overlay_mouse(); post_absolute_motion.disconnect(); post_motion.disconnect(); wf::get_core().connect(&post_absolute_motion); wf::get_core().connect(&post_motion); } } void scale_show_title_t::update_title_overlay_mouse() { wayfire_toplevel_view v = scale_find_view_at(wf::get_core().get_cursor_position(), output); if (v) { v = wf::find_topmost_parent(v); if (v->role != wf::VIEW_ROLE_TOPLEVEL) { v = nullptr; } } if (v != last_title_overlay) { if (last_title_overlay) { last_title_overlay->damage(); } last_title_overlay = v; if (v) { v->damage(); } } } wayfire-0.10.0/plugins/scale/wayfire/0000775000175000017500000000000015053502647017347 5ustar dkondordkondorwayfire-0.10.0/plugins/scale/wayfire/plugins/0000775000175000017500000000000015053502647021030 5ustar dkondordkondorwayfire-0.10.0/plugins/scale/wayfire/plugins/scale-signal.hpp0000664000175000017500000000574715053502647024120 0ustar dkondordkondor/* definition of filters for scale plugin and activator */ #ifndef SCALE_SIGNALS_H #define SCALE_SIGNALS_H #include #include #include #include /** * name: scale-filter * on: output * when: This signal is sent from the scale plugin whenever it is updating the * list of views to display, with the list of views to be displayed in * views_shown. Plugins can move views to views_hidden to request them not to * be displayed by scale. * * Note: it is an error to remove a view from views_shown without adding it to * views_hidden; this will result in views rendered in wrong locations. * * If multiple plugins are connected to this signal, they are called in the * order defined by the logic in signal_provider_t; plugins should not depend * on being called in a predictable order. Specifically, plugins should not * expect views_hidden to be empty (and should not call clear() on it). It is OK * for a plugin to move a view from views_hidden to views_shown, but this will * likely not have predictable results. */ struct scale_filter_signal { std::vector& views_shown; std::vector& views_hidden; scale_filter_signal(std::vector& shown, std::vector& hidden) : views_shown(shown), views_hidden(hidden) {} }; /* Convenience function for processing a list of views if the plugin wants to * filter based on a simple predicate. The predicate should return true for * views to be hidden. */ template void scale_filter_views(scale_filter_signal *signal, pred&& p) { auto it = std::remove_if(signal->views_shown.begin(), signal->views_shown.end(), [signal, &p] (wayfire_toplevel_view v) { bool r = p(v); if (r) { signal->views_hidden.push_back(v); } return r; }); signal->views_shown.erase(it, signal->views_shown.end()); } /** * name: scale-end * on: output * when: When scale ended / is deactivated. A plugin performing filtering can * connect to this signal to reset itself if filtering is not supposed to * happen at the next activation of scale. * argument: unused */ struct scale_end_signal {}; /** * name: scale-update * on: output * when: A plugin can emit this signal to request scale to be updated. This is * intended for plugins that filter the scaled views to request an update when * the filter is changed. It is a no-op if scale is not currently running. * argument: unused */ struct scale_update_signal {}; /** * name: scale-transformer-added * on: output * when: This signal is emitted when scale adds a transformer to a view, so * plugins extending its functionality can add their overlays to it. * argument: pointer to the newly added transformer */ struct scale_transformer_added_signal { wayfire_toplevel_view view; }; struct scale_transformer_removed_signal { wayfire_toplevel_view view; }; #endif wayfire-0.10.0/plugins/scale/meson.build0000664000175000017500000000143715053502647020050 0ustar dkondordkondorall_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, wobbly_inc, ipc_include_dirs, include_directories('.')] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json, plugin_pch_dep] shared_module('scale', ['scale.cpp', 'scale-title-overlay.cpp'], include_directories: all_include_dirs, dependencies: all_deps, link_with: [move_drag_interface], install: true, install_dir: conf_data.get('PLUGIN_PATH')) shared_module('scale-title-filter', 'scale-title-filter.cpp', include_directories: all_include_dirs, dependencies: all_deps, install: true, install_dir: conf_data.get('PLUGIN_PATH')) install_headers(['wayfire/plugins/scale-signal.hpp'], subdir: 'wayfire/plugins') wayfire-0.10.0/plugins/scale/scale-title-filter.cpp0000664000175000017500000002701315053502647022101 0ustar dkondordkondor#include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class scale_title_filter; /** * Class storing the filter text, shared among all outputs */ struct scale_title_filter_text { std::string title_filter; /* since title filter is utf-8, here we store the length of each * character when adding them so backspace will work properly */ std::vector char_len; /* Individual plugins running on each output -- this is used to update them * when the shared filter text changes. */ std::vector output_instances; void add_instance(scale_title_filter *ptr) { output_instances.push_back(ptr); } void rem_instance(scale_title_filter *ptr) { auto it = std::remove(output_instances.begin(), output_instances.end(), ptr); output_instances.erase(it, output_instances.end()); } /** * Add any character corresponding to the given keycode to the filter. * * Updates the overlays and filter on all outputs if necessary. */ void add_key(struct xkb_state *xkb_state, xkb_keycode_t keycode); /** * Remove the last character from the overlay. * * Updates the overlays and filter on all outputs if necessary. */ void rem_char(); /** * Check if scale has ended on all outputs and clears the filter in this case. */ void check_scale_end(); /** * Clear the current filter text. Does not update output-specific instances. */ void clear() { title_filter.clear(); char_len.clear(); } }; class scale_title_filter : public wf::per_output_plugin_instance_t { wf::option_wrapper_t case_sensitive{"scale-title-filter/case_sensitive"}; wf::option_wrapper_t share_filter{"scale-title-filter/share_filter"}; scale_title_filter_text local_filter; wf::shared_data::ref_ptr_t global_filter; inline void fix_case(std::string& string) { if (case_sensitive) { return; } auto transform = [] (unsigned char c) -> unsigned char { if (std::isspace(c)) { return ' '; } return (c <= 127) ? (unsigned char)std::tolower(c) : c; }; std::transform(string.begin(), string.end(), string.begin(), transform); } bool should_show_view(wayfire_view view) { auto filter = get_active_filter().title_filter; if (filter.empty()) { return true; } auto title = view->get_title(); auto app_id = view->get_app_id(); fix_case(title); fix_case(app_id); fix_case(filter); return (title.find(filter) != std::string::npos) || (app_id.find(filter) != std::string::npos); } scale_title_filter_text& get_active_filter() { return share_filter ? *global_filter.get() : local_filter; } public: bool scale_running = false; scale_title_filter() { local_filter.add_instance(this); } void init() override { global_filter->add_instance(this); share_filter.set_callback(shared_option_changed); output->connect(&view_filter); output->connect(&scale_end); } void fini() override { do_end_scale(); global_filter->rem_instance(this); } wf::signal::connection_t view_filter = [=] (scale_filter_signal *ev) { if (!scale_running) { wf::get_core().connect(&scale_key); scale_running = true; update_overlay(); } scale_filter_views(ev, [this] (wayfire_toplevel_view v) { return !should_show_view(v); }); }; std::map> keys; wf::key_repeat_t::callback_t handle_key_repeat = [=] (uint32_t raw_keycode) { auto seat = wf::get_core().get_current_seat(); auto keyboard = wlr_seat_get_keyboard(seat); if (!keyboard) { return false; /* should not happen */ } auto xkb_state = keyboard->xkb_state; xkb_keycode_t keycode = raw_keycode + 8; xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, keycode); auto& filter = get_active_filter(); if (keysym == XKB_KEY_BackSpace) { filter.rem_char(); } else { filter.add_key(xkb_state, keycode); } return true; }; wf::wl_idle_call idle_update_filter; void update_filter() { // Delay updating the filter in case the last key causes scale to exit. idle_update_filter.run_once([&] { if (scale_running) { scale_update_signal ev; output->emit(&ev); update_overlay(); } }); } wf::signal::connection_t> scale_key = [this] (wf::input_event_signal *ev) { if (ev->event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { keys.erase(ev->event->keycode); return; } if ((ev->event->keycode == KEY_ESC) || (ev->event->keycode == KEY_ENTER)) { return; } if (output != wf::get_core().seat->get_active_output()) { return; } keys[ev->event->keycode] = std::make_unique(ev->event->keycode, handle_key_repeat); handle_key_repeat(ev->event->keycode); }; wf::signal::connection_t scale_end = [=] (scale_end_signal *ev) { do_end_scale(); }; void do_end_scale() { scale_key.disconnect(); keys.clear(); clear_overlay(); scale_running = false; get_active_filter().check_scale_end(); } wf::config::option_base_t::updated_callback_t shared_option_changed = [this] () { if (scale_running) { /* clear the filter that is not used anymore */ auto& filter = get_active_filter(); filter.clear(); scale_update_signal ev; output->emit(&ev); update_overlay(); } }; protected: /* * Text overlay with the current filter */ wf::cairo_text_t filter_overlay; wf::dimensions_t overlay_size; float output_scale = 1.0f; /* render function */ wf::effect_hook_t render_hook = [=] () { render(); }; /* flag to indicate if render_hook is active */ bool render_active = false; wf::option_wrapper_t bg_color{"scale-title-filter/bg_color"}; wf::option_wrapper_t text_color{"scale-title-filter/text_color"}; wf::option_wrapper_t show_overlay{"scale-title-filter/overlay"}; wf::option_wrapper_t font_size{"scale-title-filter/font_size"}; static wf::dimensions_t min(const wf::dimensions_t& x, const wf::dimensions_t& y) { return {std::min(x.width, y.width), std::min(x.height, y.height)}; } static wf::dimensions_t max(const wf::dimensions_t& x, const wf::dimensions_t& y) { return {std::max(x.width, y.width), std::max(x.height, y.height)}; } void update_overlay() { const auto& filter = get_active_filter().title_filter; if (!show_overlay || filter.empty()) { /* remove any overlay */ clear_overlay(); return; } auto dim = output->get_screen_size(); auto new_size = filter_overlay.render_text(filter, wf::cairo_text_t::params(font_size, bg_color, text_color, output_scale, dim)); if (!render_active) { output->render->add_effect(&render_hook, wf::OUTPUT_EFFECT_OVERLAY); render_active = true; } auto surface_size = min(new_size, {filter_overlay.get_size().width, filter_overlay.get_size().height}); auto damage = max(surface_size, overlay_size); output->render->damage({ dim.width / 2 - (int)(damage.width / output_scale / 2), dim.height / 2 - (int)(damage.height / output_scale / 2), (int)(damage.width / output_scale), (int)(damage.height / output_scale) }); overlay_size = surface_size; } /* render the current content of the overlay texture */ void render() { auto out_fb = output->render->get_target_framebuffer(); auto dim = output->get_screen_size(); if (output_scale != out_fb.scale) { output_scale = out_fb.scale; update_overlay(); } auto tex = filter_overlay.get_texture(); if (!tex.texture) { return; } wf::geometry_t geometry{ dim.width / 2 - (int)(overlay_size.width / output_scale / 2), dim.height / 2 - (int)(overlay_size.height / output_scale / 2), (int)(overlay_size.width / output_scale), (int)(overlay_size.height / output_scale) }; tex.source_box = wlr_fbox{ filter_overlay.get_size().width / 2.0 - overlay_size.width / 2.0, filter_overlay.get_size().height / 2.0 - overlay_size.height / 2.0, overlay_size.width * 1.0, overlay_size.height * 1.0, }; auto damage = output->render->get_scheduled_damage() & geometry; output->render->get_current_pass()->add_texture(tex, out_fb, geometry, damage); } /* clear everything rendered by this plugin and deactivate rendering */ void clear_overlay() { if (render_active) { output->render->rem_effect(&render_hook); output->render->damage_whole(); render_active = false; } } }; void scale_title_filter_text::add_key(struct xkb_state *xkb_state, xkb_keycode_t keycode) { /* taken from libxkbcommon guide */ int size = xkb_state_key_get_utf8(xkb_state, keycode, nullptr, 0); if (size <= 0) { return; } std::string tmp(size, 0); xkb_state_key_get_utf8(xkb_state, keycode, tmp.data(), size + 1); char_len.push_back(size); title_filter += tmp; for (auto p : output_instances) { p->update_filter(); } } void scale_title_filter_text::rem_char() { if (!title_filter.empty()) { int len = char_len.back(); char_len.pop_back(); title_filter.resize(title_filter.length() - len); } else { return; } for (auto p : output_instances) { p->update_filter(); } } void scale_title_filter_text::check_scale_end() { bool scale_running = false; for (auto p : output_instances) { if (p->scale_running) { scale_running = true; break; } } if (!scale_running) { clear(); } } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/scale/scale.cpp0000664000175000017500000013505015053502647017500 0ustar dkondordkondor/** * Original code by: Scott Moreau, Daniel Kondor */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wayfire/plugins/ipc/ipc-activator.hpp" #include "scale.hpp" #include "scale-title-overlay.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view.hpp" static constexpr const char *SCALE_TRANSFORMER = "scale"; using namespace wf::animation; class scale_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t scale_x{*this}; timed_transition_t scale_y{*this}; timed_transition_t translation_x{*this}; timed_transition_t translation_y{*this}; }; struct wf_scale_animation_attribs { wf::option_wrapper_t duration{"scale/duration"}; scale_animation_t scale_animation{duration}; }; struct view_scale_data { int row, col; std::shared_ptr transformer; wf::animation::simple_animation_t fade_animation; wf_scale_animation_attribs animation; enum class view_visibility_t { VISIBLE, /* view is shown in position determined by layout_slots() */ HIDING, /* view is in the process of hiding (due to filters) */ HIDDEN, /* view is hidden by a filter (with set_visible(false)) */ }; view_visibility_t visibility = view_visibility_t::VISIBLE; bool was_minimized = false; /* flag to indicate if this view was originally minimized */ }; /** * Scale has the following hard coded bindings are as follows: * KEY_ENTER: * - Ends scale, switching to the workspace of the focused view * KEY_ESC: * - Ends scale, switching to the workspace where scale was started, * and focuses the initially active view * KEY_UP: * KEY_DOWN: * KEY_LEFT: * KEY_RIGHT: * - When scale is active, change focus of the views * * BTN_LEFT: * - Ends scale, switching to the workspace of the surface clicked * BTN_MIDDLE: * - If middle_click_close is true, closes the view clicked */ class wayfire_scale : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { /* helper class for optionally showing title overlays */ scale_show_title_t show_title; std::vector current_row_sizes; wf::point_t initial_workspace; bool hook_set; /* View that was active before scale began. */ std::weak_ptr initial_focus_view; /* View that has active focus. */ wayfire_toplevel_view current_focus_view; // View over which the last input press happened wayfire_toplevel_view last_selected_view; std::map scale_data; wf::option_wrapper_t spacing{"scale/spacing"}; wf::option_wrapper_t outer_margin{"scale/outer_margin"}; wf::option_wrapper_t middle_click_close{"scale/middle_click_close"}; wf::option_wrapper_t inactive_alpha{"scale/inactive_alpha"}; wf::option_wrapper_t minimized_alpha{"scale/minimized_alpha"}; wf::option_wrapper_t allow_scale_zoom{"scale/allow_zoom"}; wf::option_wrapper_t include_minimized{"scale/include_minimized"}; wf::option_wrapper_t close_on_new_view{"scale/close_on_new_view"}; /* maximum scale -- 1.0 means we will not "zoom in" on a view */ const double max_scale_factor = 1.0; /* maximum scale for child views (relative to their parents) * zero means unconstrained, 1.0 means child cannot be scaled * "larger" than the parent */ const double max_scale_child = 1.0; /* true if the currently running scale should include views from * all workspaces */ bool all_workspaces; std::unique_ptr workspace_bindings; wf::shared_data::ref_ptr_t drag_helper; std::unique_ptr grab; wf::plugin_activation_data_t grab_interface{ .name = SCALE_TRANSFORMER, .capabilities = wf::CAPABILITY_MANAGE_DESKTOP | wf::CAPABILITY_GRAB_INPUT, .cancel = [=] () { finalize(); }, }; public: bool active = false; void init() override { hook_set = false; grab = std::make_unique(SCALE_TRANSFORMER, output, this, this, this); allow_scale_zoom.set_callback(allow_scale_zoom_option_changed); setup_workspace_switching(); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_done); drag_helper->connect(&on_drag_snap_off); show_title.init(output); output->connect(&update_cb); } void setup_workspace_switching() { workspace_bindings = std::make_unique(output); workspace_bindings->setup([&] (wf::point_t delta, wayfire_toplevel_view view, bool only_view) { if (!output->is_plugin_active(grab_interface.name)) { return false; } if (delta == wf::point_t{0, 0}) { // Consume input event return true; } if (only_view) { // For now, scale does not let you move views between workspaces return false; } auto ws = output->wset()->get_current_workspace() + delta; // vswitch picks the top view, we want the focused one std::vector fixed_views; if (view && current_focus_view && !all_workspaces) { fixed_views.push_back(current_focus_view); } output->wset()->request_workspace(ws, fixed_views); return true; }); } /* Add a transformer that will be used to scale the view */ bool add_transformer(wayfire_toplevel_view view) { if (view->get_transformed_node()->get_transformer(SCALE_TRANSFORMER)) { return false; } auto tr = std::make_shared(view); scale_data[view].transformer = tr; view->get_transformed_node()->add_transformer(tr, wf::TRANSFORMER_2D + 1, SCALE_TRANSFORMER); /* Handle potentially minimized views by making them visible, * however, they start out as fully transparent. */ if (view->minimized) { tr->alpha = 0.0; wf::scene::set_node_enabled(view->get_root_node(), true); scale_data[view].was_minimized = true; } /* Transformers are added only once when scale is activated so * this is a good place to connect the geometry-changed handler */ view->connect(&view_geometry_changed); view->connect(&view_unmapped); set_tiled_wobbly(view, true); /* signal that a transformer was added to this view */ scale_transformer_added_signal data; data.view = view; output->emit(&data); return true; } /* Remove the scale transformer from the view */ void pop_transformer(wayfire_toplevel_view view) { /* signal that a transformer was added to this view */ scale_transformer_removed_signal data; data.view = view; output->emit(&data); view->get_transformed_node()->rem_transformer(SCALE_TRANSFORMER); view->disconnect(&view_unmapped); set_tiled_wobbly(view, false); } /* Remove scale transformers from all views */ void remove_transformers() { for (auto& e : scale_data) { for (auto& toplevel : e.first->enumerate_views(false)) { pop_transformer(toplevel); } if (e.second.was_minimized) { wf::scene::set_node_enabled(e.first->get_root_node(), false); } if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled(e.first->get_transformed_node(), true); } e.second.visibility = view_scale_data::view_visibility_t::VISIBLE; } } /* Check whether views exist on other workspaces */ bool all_same_as_current_workspace_views() { return get_all_workspace_views().size() == get_current_workspace_views().size(); } /* Activate scale, switch activator modes and deactivate */ bool handle_toggle(bool want_all_workspaces) { if (active && (all_same_as_current_workspace_views() || (want_all_workspaces == this->all_workspaces))) { deactivate(); return true; } this->all_workspaces = want_all_workspaces; if (active) { switch_scale_modes(); return true; } else { return activate(); } } wf::signal::connection_t update_cb = [=] (scale_update_signal *ev) { if (active) { layout_slots(get_views()); output->render->schedule_redraw(); } }; void handle_pointer_button( const wlr_pointer_button_event& event) override { process_input(event.button, event.state, wf::get_core().get_cursor_position()); } void handle_touch_down(uint32_t, int finger_id, wf::pointf_t pos) override { if (finger_id == 0) { process_input(BTN_LEFT, WLR_BUTTON_PRESSED, pos); } } void handle_touch_up(uint32_t, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id == 0) { process_input(BTN_LEFT, WLR_BUTTON_RELEASED, lift_off_position); } } void handle_touch_motion(uint32_t time, int finger_id, wf::pointf_t position) override { if (finger_id == 0) { handle_pointer_motion(position, time); } } /* Fade all views' alpha to inactive alpha except the * view argument */ void fade_out_all_except(wayfire_toplevel_view view) { for (auto& e : scale_data) { auto v = e.first; if (wf::find_topmost_parent(v) == wf::find_topmost_parent(view)) { continue; } if (e.second.visibility != view_scale_data::view_visibility_t::VISIBLE) { continue; } fade_out(v); } } /* Fade in view alpha */ void fade_in(wayfire_toplevel_view view) { if (!view || !scale_data.count(view)) { return; } set_hook(); auto alpha = scale_data[view].transformer->alpha; scale_data[view].fade_animation.animate(alpha, 1); if (view->children.size()) { fade_in(view->children.front()); } } /* Fade out view alpha */ void fade_out(wayfire_toplevel_view view) { if (!view) { return; } set_hook(); for (auto v : view->enumerate_views(false)) { // Could happen if we have a never-mapped child view if (!scale_data.count(v)) { continue; } auto alpha = scale_data[v].transformer->alpha; double target_alpha = (v->minimized) ? minimized_alpha : inactive_alpha; scale_data[v].fade_animation.animate(alpha, target_alpha); } } /* Switch to the workspace for the untransformed view geometry */ void select_view(wayfire_toplevel_view view) { if (!view) { return; } auto ws = get_view_main_workspace(view); output->wset()->request_workspace(ws); } /* Updates current and initial view focus variables accordingly */ void check_focus_view(wayfire_toplevel_view view) { if (view == current_focus_view) { current_focus_view = nullptr; } if (view == last_selected_view) { last_selected_view = nullptr; } } /* Remove transformer from view and remove view from the scale_data map */ void remove_view(wayfire_toplevel_view view) { if (!view || !scale_data.count(view)) { return; } if (scale_data.at(view).was_minimized) { wf::scene::set_node_enabled(view->get_root_node(), false); } for (auto v : view->enumerate_views(false)) { check_focus_view(v); pop_transformer(v); scale_data.erase(v); } } /* Process button event */ void process_input(uint32_t button, uint32_t state, wf::pointf_t input_position) { if (!active) { return; } if (state == WLR_BUTTON_PRESSED) { auto view = scale_find_view_at(input_position, output); if (view && should_scale_view(view)) { // Mark the view as the target of the next input release operation last_selected_view = view; } else { last_selected_view = nullptr; } drag_helper->set_pending_drag(input_position); return; } drag_helper->handle_input_released(); auto view = scale_find_view_at(input_position, output); if (!view || (last_selected_view != view)) { last_selected_view = nullptr; // Operation was cancelled, for ex. dragged outside of the view return; } // Reset last_selected_view, because it is no longer held last_selected_view = nullptr; switch (button) { case BTN_LEFT: // Focus the view under the mouse current_focus_view = view; fade_out_all_except(view); fade_in(wf::find_topmost_parent(view)); // End scale initial_focus_view.reset(); deactivate(); break; case BTN_MIDDLE: // Check kill the view if (middle_click_close) { view->close(); } break; default: break; } } void handle_pointer_motion(wf::pointf_t to_f, uint32_t time) override { wf::point_t to{(int)std::round(to_f.x), (int)std::round(to_f.y)}; if (!drag_helper->view && last_selected_view && drag_helper->should_start_pending_drag(to)) { wf::move_drag::drag_options_t opts; opts.join_views = true; opts.enable_snap_off = true; opts.snap_off_threshold = 200; // We want to receive raw inputs (e.g. no fake pointer releases) in case the view is moved to // another output. grab->set_wants_raw_input(true); drag_helper->start_drag(last_selected_view, opts); drag_helper->handle_motion(to); } else if (drag_helper->view) { drag_helper->handle_motion(to); if (last_selected_view) { const double threshold = 20.0; if (drag_helper->distance_to_grab_origin(to) > threshold) { last_selected_view = nullptr; } } } } /* Get the workspace for the center point of the untransformed view geometry */ wf::point_t get_view_main_workspace(wayfire_toplevel_view view) { view = wf::find_topmost_parent(view); auto ws = output->wset()->get_current_workspace(); auto og = output->get_layout_geometry(); auto vg = view->get_geometry(); auto center = wf::point_t{vg.x + vg.width / 2, vg.y + vg.height / 2}; return wf::point_t{ ws.x + (int)std::floor((double)center.x / og.width), ws.y + (int)std::floor((double)center.y / og.height)}; } /* Given row and column, return a view at this position in the scale grid, * or the first scaled view if none is found */ wayfire_toplevel_view find_view_in_grid(int row, int col) { for (auto& view : scale_data) { if ((view.first->parent == nullptr) && (view.second.visibility == view_scale_data::view_visibility_t::VISIBLE) && ((view.second.row == row) && (view.second.col == col))) { return view.first; } } return get_views().front(); } /* Process key event */ void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event ev) override { auto view = toplevel_cast(wf::get_active_view_for_output(output)); if (!view) { view = current_focus_view; if (view) { fade_out_all_except(view); fade_in(view); wf::get_core().default_wm->focus_raise_view(view); return; } } else if (!scale_data.count(view)) { return; } int cur_row = view ? scale_data[view].row : 0; int cur_col = view ? scale_data[view].col : 0; int next_row = cur_row; int next_col = cur_col; if ((ev.state != WLR_KEY_PRESSED) || wf::get_core().seat->get_keyboard_modifiers()) { return; } switch (ev.keycode) { case KEY_UP: next_row--; break; case KEY_DOWN: next_row++; break; case KEY_LEFT: next_col--; break; case KEY_RIGHT: next_col++; break; case KEY_ENTER: deactivate(); select_view(current_focus_view); wf::get_core().default_wm->focus_raise_view(view); return; case KEY_ESC: deactivate(); output->wset()->request_workspace(initial_workspace); if (auto focus = initial_focus_view.lock()) { wf::get_core().default_wm->focus_raise_view(focus); } initial_focus_view.reset(); return; default: return; } if (!view) { return; } if (!current_row_sizes.empty()) { next_row = (next_row + current_row_sizes.size()) % current_row_sizes.size(); if (cur_row != next_row) { /* when moving to and from the last row, the number of columns * may be different, so this bit figures out which view we * should switch focus to */ float p = 1.0 * cur_col / current_row_sizes[cur_row]; next_col = p * current_row_sizes[next_row]; } else { next_col = (next_col + current_row_sizes[cur_row]) % current_row_sizes[cur_row]; } } else { next_row = cur_row; next_col = cur_col; } view = find_view_in_grid(next_row, next_col); if (view && (current_focus_view != view)) { fade_out_all_except(view); fade_in(view); current_focus_view = view; // Update activated state wf::get_core().seat->focus_view(view); } } /* Assign the transformer values to the view transformers */ void transform_views() { for (auto& e : scale_data) { auto view = e.first; auto& view_data = e.second; if (!view || !view_data.transformer) { continue; } if (view_data.fade_animation.running() || view_data.animation.scale_animation.running()) { view->get_transformed_node()->begin_transform_update(); view_data.transformer->scale_x = view_data.animation.scale_animation.scale_x; view_data.transformer->scale_y = view_data.animation.scale_animation.scale_y; view_data.transformer->translation_x = view_data.animation.scale_animation.translation_x; view_data.transformer->translation_y = view_data.animation.scale_animation.translation_y; view_data.transformer->alpha = view_data.fade_animation; if ((view_data.visibility == view_scale_data::view_visibility_t::HIDING) && !view_data.fade_animation.running()) { view_data.visibility = view_scale_data::view_visibility_t::HIDDEN; wf::scene::set_node_enabled(view->get_transformed_node(), false); } view->get_transformed_node()->end_transform_update(); } } } /* Returns a list of views for all workspaces */ std::vector get_all_workspace_views() { return output->wset()->get_views( (include_minimized ? 0 : wf::WSET_EXCLUDE_MINIMIZED) | wf::WSET_MAPPED_ONLY); } /* Returns a list of views for the current workspace */ std::vector get_current_workspace_views() { std::vector views; for (auto& view : get_all_workspace_views()) { auto vg = view->get_geometry(); auto og = output->get_relative_geometry(); wf::region_t wr{og}; wf::point_t center{vg.x + vg.width / 2, vg.y + vg.height / 2}; if (wr.contains_point(center)) { views.push_back(view); } } return views; } /* Returns a list of views to be scaled */ std::vector get_views() { std::vector views; if (all_workspaces) { views = get_all_workspace_views(); } else { views = get_current_workspace_views(); } return views; } /** * @return true if the view is to be scaled. */ bool should_scale_view(wayfire_toplevel_view view) { auto views = get_views(); return std::find( views.begin(), views.end(), wf::find_topmost_parent(view)) != views.end(); } /* Convenience assignment function */ void setup_view_transform(view_scale_data& view_data, double scale_x, double scale_y, double translation_x, double translation_y, double target_alpha) { view_data.animation.scale_animation.scale_x.set( view_data.transformer->scale_x, scale_x); view_data.animation.scale_animation.scale_y.set( view_data.transformer->scale_y, scale_y); view_data.animation.scale_animation.translation_x.set( view_data.transformer->translation_x, translation_x); view_data.animation.scale_animation.translation_y.set( view_data.transformer->translation_y, translation_y); view_data.animation.scale_animation.start(); view_data.fade_animation = wf::animation::simple_animation_t( wf::option_wrapper_t{"scale/duration"}); view_data.fade_animation.animate(view_data.transformer->alpha, target_alpha); } static bool view_compare_x(const wayfire_toplevel_view& a, const wayfire_toplevel_view& b) { auto vg_a = a->get_geometry(); std::vector a_coords = {vg_a.x, vg_a.width, vg_a.y, vg_a.height}; auto vg_b = b->get_geometry(); std::vector b_coords = {vg_b.x, vg_b.width, vg_b.y, vg_b.height}; return a_coords < b_coords; } static bool view_compare_y(const wayfire_toplevel_view& a, const wayfire_toplevel_view& b) { auto vg_a = a->get_geometry(); std::vector a_coords = {vg_a.y, vg_a.height, vg_a.x, vg_a.width}; auto vg_b = b->get_geometry(); std::vector b_coords = {vg_b.y, vg_b.height, vg_b.x, vg_b.width}; return a_coords < b_coords; } std::vector> view_sort( std::vector& views) { std::vector> view_grid; // First ensure a consistent sorting of all views using a persistent // identifier before sorting by geometry. // This is so that if two views have exactly the same geometry, // they will always appear in the same order in the output list. std::sort(views.begin(), views.end(), [] (auto a, auto b) { return a.get() < b.get(); }); std::stable_sort(views.begin(), views.end(), view_compare_y); int rows = sqrt(views.size() + 1); int views_per_row = (int)std::ceil((double)views.size() / rows); size_t n = views.size(); for (size_t i = 0; i < n; i += views_per_row) { size_t j = std::min(i + views_per_row, n); view_grid.emplace_back(views.begin() + i, views.begin() + j); std::stable_sort(view_grid.back().begin(), view_grid.back().end(), view_compare_x); } return view_grid; } /* Filter the views to be arranged by layout_slots() */ void filter_views(std::vector& views) { std::vector filtered_views; scale_filter_signal signal(views, filtered_views); output->emit(&signal); /* update hidden views -- ensure that they and their children have a * transformer and are in scale_data */ for (auto view : filtered_views) { for (auto v : view->enumerate_views(true)) { add_transformer(v); auto& view_data = scale_data[v]; if (view_data.visibility == view_scale_data::view_visibility_t::VISIBLE) { view_data.visibility = view_scale_data::view_visibility_t::HIDING; setup_view_transform(view_data, 1, 1, 0, 0, 0); } if (v == current_focus_view) { current_focus_view = nullptr; } } } if (!current_focus_view) { std::sort(views.begin(), views.end(), [=] (wayfire_toplevel_view a, wayfire_toplevel_view b) { if (a->minimized != b->minimized) { /* avoid focusing minimized views if possible, so * sort them after non-minimized ones */ return b->minimized; } return wf::get_focus_timestamp(a) > wf::get_focus_timestamp(b); }); current_focus_view = views.empty() ? nullptr : views.front(); wf::get_core().default_wm->focus_raise_view(current_focus_view); } } /* Compute target scale layout geometry for all the view transformers * and start animating. Initial code borrowed from the compiz scale * plugin algorithm */ void layout_slots(std::vector views) { wf::dassert(active || hook_set, "Scale is not active"); if (!views.size()) { if (!all_workspaces && active) { deactivate(); } return; } filter_views(views); auto workarea = output->workarea->get_workarea(); workarea.x += outer_margin; workarea.y += outer_margin; workarea.width -= outer_margin * 2; workarea.height -= outer_margin * 2; auto sorted_rows = view_sort(views); size_t cnt_rows = sorted_rows.size(); const double scaled_height = std::max((double) (workarea.height - (cnt_rows + 1) * spacing) / cnt_rows, 1.0); current_row_sizes.clear(); for (size_t i = 0; i < cnt_rows; i++) { size_t cnt_cols = sorted_rows[i].size(); current_row_sizes.push_back(cnt_cols); const double scaled_width = std::max((double) (workarea.width - (cnt_cols + 1) * spacing) / cnt_cols, 1.0); for (size_t j = 0; j < cnt_cols; j++) { double x = workarea.x + spacing + (spacing + scaled_width) * j; double y = workarea.y + spacing + (spacing + scaled_height) * i; auto view = sorted_rows[i][j]; // Calculate current transformation of the view, in order to // ensure that new views in the view tree start directly at the // correct position double main_view_dx = 0; double main_view_dy = 0; double main_view_scale = 1.0; if (scale_data.count(view)) { main_view_dx = scale_data[view].transformer->translation_x; main_view_dy = scale_data[view].transformer->translation_y; main_view_scale = scale_data[view].transformer->scale_x; } // Calculate target alpha for this view and its children double target_alpha = 1.0; if (view != current_focus_view) { target_alpha = (view->minimized) ? minimized_alpha : inactive_alpha; } // Helper function to calculate the desired scale for a view const auto& calculate_scale = [=] (wf::dimensions_t vg) { double w = std::max(1.0, scaled_width); double h = std::max(1.0, scaled_height); const double scale = std::min(w / vg.width, h / vg.height); if (!allow_scale_zoom) { return std::min(scale, max_scale_factor); } return scale; }; add_transformer(view); auto geom = view->get_geometry(); double view_scale = calculate_scale({geom.width, geom.height}); for (auto& child : view->enumerate_views(true)) { // Ensure a transformer for the view, and make sure that // new views in the view tree start off with the correct // attributes set. auto new_child = add_transformer(child); auto& child_data = scale_data[child]; if (new_child) { child_data.transformer->translation_x = main_view_dx; child_data.transformer->translation_y = main_view_dy; child_data.transformer->scale_x = main_view_scale; child_data.transformer->scale_y = main_view_scale; } if (child_data.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled( child->get_transformed_node(), true); } child_data.visibility = view_scale_data::view_visibility_t::VISIBLE; child_data.row = i; child_data.col = j; if (!active) { // On exit, we just animate towards normal state setup_view_transform(child_data, 1, 1, 0, 0, 1); continue; } auto vg = child->get_geometry(); wf::pointf_t center = {vg.x + vg.width / 2.0, vg.y + vg.height / 2.0}; // Take padding into account double scale = calculate_scale({vg.width, vg.height}); // Ensure child is not scaled more than parent if (!allow_scale_zoom && (child != view) && (max_scale_child > 0.0)) { scale = std::min(max_scale_child * view_scale, scale); } // Target geometry is centered around the center slot const double dx = x - center.x + scaled_width / 2.0; const double dy = y - center.y + scaled_height / 2.0; setup_view_transform(child_data, scale, scale, dx, dy, target_alpha); } } } set_hook(); transform_views(); } /* Called when adding or removing a group of views to be scaled, * in this case between views on all workspaces and views on the * current workspace */ void switch_scale_modes() { if (!output->is_plugin_active(grab_interface.name)) { return; } if (all_workspaces) { layout_slots(get_views()); return; } bool rearrange = false; for (auto& e : scale_data) { if (!should_scale_view(e.first)) { setup_view_transform(e.second, 1, 1, 0, 0, 1); rearrange = true; } } if (rearrange) { layout_slots(get_views()); } } /* Toggle between restricting maximum scale to 100% or allowing it * to become the greater. This is particularly noticeable when * scaling a single view or a view with child views. */ wf::config::option_base_t::updated_callback_t allow_scale_zoom_option_changed = [=] () { if (!output->is_plugin_active(grab_interface.name)) { return; } layout_slots(get_views()); }; void handle_new_view(wayfire_toplevel_view view, bool close_scale) { if (!should_scale_view(view)) { return; } if (close_scale) { deactivate(); return; } layout_slots(get_views()); } wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { handle_new_view(toplevel, close_on_new_view); } }; void handle_view_unmapped(wayfire_toplevel_view view) { remove_view(view); if (scale_data.empty()) { finalize(); } else if (!view->parent) { layout_slots(get_views()); } } /* Workspace changed */ wf::signal::connection_t workspace_changed = [=] (wf::workspace_changed_signal *ev) { if (current_focus_view) { wf::get_core().default_wm->focus_raise_view(current_focus_view); } layout_slots(get_views()); }; wf::signal::connection_t workarea_changed = [=] (wf::workarea_changed_signal *ev) { layout_slots(get_views()); }; /* View geometry changed. Also called when workspace changes */ wf::signal::connection_t view_geometry_changed = [=] (wf::view_geometry_changed_signal *ev) { auto views = get_views(); if (!views.size()) { deactivate(); return; } layout_slots(std::move(views)); }; /* View minimized */ wf::signal::connection_t view_minimized = [=] (wf::view_minimized_signal *ev) { // Handle view restoration, view minimization is handled by disappeared already. if (!ev->view->minimized) { layout_slots(get_views()); } else if (include_minimized && scale_data.count(ev->view)) { if (!scale_data.at(ev->view).was_minimized) { scale_data.at(ev->view).was_minimized = true; wf::scene::set_node_enabled(ev->view->get_root_node(), true); } fade_out(ev->view); } }; /* View unmapped */ wf::signal::connection_t view_unmapped = [=] (wf::view_unmapped_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { check_focus_view(toplevel); handle_view_unmapped(toplevel); } }; /* Our own refocus that uses untransformed coordinates */ void refocus() { if (current_focus_view) { wf::get_core().default_wm->focus_raise_view(current_focus_view); select_view(current_focus_view); return; } wayfire_toplevel_view next_focus = nullptr; auto views = get_current_workspace_views(); for (auto v : views) { if (v->is_mapped() && v->get_keyboard_focus_surface()) { next_focus = v; break; } } wf::get_core().default_wm->focus_raise_view(next_focus); } /* Returns true if any scale animation is running */ bool animation_running() { for (auto& e : scale_data) { if (e.second.fade_animation.running() || e.second.animation.scale_animation.running()) { return true; } } return false; } /* Assign transform values to the actual transformer */ wf::effect_hook_t pre_hook = [=] () { transform_views(); }; /* Keep rendering until all animation has finished */ wf::effect_hook_t post_hook = [=] () { bool running = animation_running(); if (running) { output->render->schedule_redraw(); } if (active || running) { return; } finalize(); }; bool can_handle_drag() { return output->is_plugin_active(this->grab_interface.name); } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { grab->set_wants_raw_input(true); drag_helper->set_scale(1.0); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag() && !drag_helper->is_view_held_in_place()) { if (ev->main_view->get_output() == ev->focused_output) { // View left on the same output, don't do anything for (auto& v : ev->all_views) { set_tiled_wobbly(v.view, true); } layout_slots(get_views()); return; } wf::move_drag::adjust_view_on_output(ev); } grab->set_wants_raw_input(false); }; wf::signal::connection_t on_drag_snap_off = [=] (auto) { last_selected_view = nullptr; }; /* Activate and start scale animation */ bool activate() { if (active) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } auto views = get_views(); if (views.empty()) { output->deactivate_plugin(&grab_interface); return false; } initial_workspace = output->wset()->get_current_workspace(); auto active_view = wf::get_active_view_for_output(output); if (active_view) { initial_focus_view = active_view->weak_from_this(); current_focus_view = toplevel_cast(active_view); if (std::find(views.begin(), views.end(), current_focus_view) == views.end()) { current_focus_view = nullptr; } } else { initial_focus_view.reset(); current_focus_view = nullptr; } // Make sure no leftover events from the activation binding // trigger an action in scale last_selected_view = nullptr; grab->grab_input(wf::scene::layer::WORKSPACE); if (current_focus_view != wf::get_core().seat->get_active_view()) { wf::get_core().default_wm->focus_raise_view(current_focus_view); } active = true; layout_slots(get_views()); output->connect(&on_view_mapped); output->connect(&workspace_changed); output->connect(&workarea_changed); output->connect(&view_minimized); fade_out_all_except(current_focus_view); fade_in(current_focus_view); return true; } /* Deactivate and start unscale animation */ void deactivate() { active = false; set_hook(); on_view_mapped.disconnect(); view_minimized.disconnect(); workspace_changed.disconnect(); workarea_changed.disconnect(); view_geometry_changed.disconnect(); grab->ungrab_input(); output->deactivate_plugin(&grab_interface); for (auto& e : scale_data) { if (e.first->minimized && (e.first != current_focus_view)) { e.second.visibility = view_scale_data::view_visibility_t::HIDING; setup_view_transform(e.second, 1, 1, 0, 0, 0); } else { fade_in(e.first); setup_view_transform(e.second, 1, 1, 0, 0, 1); if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN) { wf::scene::set_node_enabled(e.first->get_transformed_node(), true); } e.second.visibility = view_scale_data::view_visibility_t::VISIBLE; } } refocus(); scale_end_signal signal; output->emit(&signal); } /* Completely end scale, including animation */ void finalize() { if (active) { /* only emit the signal if deactivate() was not called before */ scale_end_signal signal; output->emit(&signal); if (drag_helper->view) { drag_helper->handle_input_released(); } } active = false; unset_hook(); remove_transformers(); scale_data.clear(); grab->ungrab_input(); on_view_mapped.disconnect(); view_minimized.disconnect(); workspace_changed.disconnect(); workarea_changed.disconnect(); view_geometry_changed.disconnect(); output->deactivate_plugin(&grab_interface); wf::scene::update(wf::get_core().scene(), wf::scene::update_flag::INPUT_STATE); } /* Utility hook setter */ void set_hook() { if (hook_set) { return; } output->render->add_effect(&post_hook, wf::OUTPUT_EFFECT_POST); output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); output->render->schedule_redraw(); hook_set = true; } /* Utility hook unsetter */ void unset_hook() { if (!hook_set) { return; } output->render->rem_effect(&post_hook); output->render->rem_effect(&pre_hook); hook_set = false; } void fini() override { finalize(); show_title.fini(); } }; class wayfire_scale_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t toggle_ws{"scale/toggle"}; wf::ipc_activator_t toggle_all{"scale/toggle_all"}; public: void init() override { this->init_output_tracking(); toggle_ws.set_handler(toggle_cb); toggle_all.set_handler(toggle_all_cb); } void fini() override { this->fini_output_tracking(); } void handle_new_output(wf::output_t *output) override { per_output_tracker_mixin_t::handle_new_output(output); output->connect(&on_view_set_output); } void handle_output_removed(wf::output_t *output) override { per_output_tracker_mixin_t::handle_output_removed(output); output->disconnect(&on_view_set_output); } wf::signal::connection_t on_view_set_output = [=] (wf::view_set_output_signal *ev) { if (auto toplevel = wf::toplevel_cast(ev->view)) { auto old_output = ev->output; if (old_output && output_instance.count(old_output)) { this->output_instance[old_output]->handle_view_unmapped(toplevel); } auto new_output = ev->view->get_output(); if (new_output && output_instance.count(new_output) && output_instance[new_output]->active) { this->output_instance[ev->view->get_output()]->handle_new_view(toplevel, false); } } }; wf::ipc_activator_t::handler_t toggle_cb = [=] (wf::output_t *output, wayfire_view) { if (this->output_instance[output]->handle_toggle(false)) { output->render->schedule_redraw(); return true; } return false; }; wf::ipc_activator_t::handler_t toggle_all_cb = [=] (wf::output_t *output, wayfire_view) { if (this->output_instance[output]->handle_toggle(true)) { output->render->schedule_redraw(); return true; } return false; }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_scale_global); wayfire-0.10.0/plugins/scale/scale-title-overlay.hpp0000664000175000017500000000337715053502647022311 0ustar dkondordkondor#pragma once #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include namespace wf { namespace scene { class title_overlay_node_t; } } class scale_show_title_t { protected: /* Overlays for showing the title of each view */ wf::option_wrapper_t bg_color{"scale/bg_color"}; wf::option_wrapper_t text_color{"scale/text_color"}; wf::option_wrapper_t show_view_title_overlay_opt{ "scale/title_overlay"}; wf::option_wrapper_t title_font_size{"scale/title_font_size"}; wf::option_wrapper_t title_position{"scale/title_position"}; wf::output_t *output; public: scale_show_title_t(); void init(wf::output_t *output); void fini(); protected: /* signals */ wf::signal::connection_t view_filter; wf::signal::connection_t scale_end; wf::signal::connection_t add_title_overlay; wf::signal::connection_t rem_title_overlay; wf::signal::connection_t> post_motion; wf::signal::connection_t> post_absolute_motion; enum class title_overlay_t { NEVER, MOUSE, ALL, }; friend class wf::scene::title_overlay_node_t; title_overlay_t show_view_title_overlay; /* only used if title overlay is set to follow the mouse */ wayfire_view last_title_overlay = nullptr; void update_title_overlay_opt(); void update_title_overlay_mouse(); }; wayfire-0.10.0/plugins/scale/scale.hpp0000664000175000017500000000075515053502647017510 0ustar dkondordkondor#pragma once #include "wayfire/debug.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/plugins/common/util.hpp" #include #include #include #include inline wayfire_toplevel_view scale_find_view_at(wf::pointf_t at, wf::output_t *output) { auto offset = wf::origin(output->get_layout_geometry()); at.x -= offset.x; at.y -= offset.y; return wf::find_output_view_at(output, at); } wayfire-0.10.0/plugins/common/0000775000175000017500000000000015053502647016102 5ustar dkondordkondorwayfire-0.10.0/plugins/common/move-drag-interface.cpp0000664000175000017500000005064415053502647022436 0ustar dkondordkondor#include "wayfire/plugins/common/move-drag-interface.hpp" #include "wayfire/debug.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/seat.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-helpers.hpp" namespace wf { namespace move_drag { static wf::geometry_t find_geometry_around(wf::dimensions_t size, wf::point_t grab, wf::pointf_t relative) { return wf::geometry_t{ grab.x - (int)std::floor(relative.x * size.width), grab.y - (int)std::floor(relative.y * size.height), size.width, size.height, }; } /** * A transformer used while dragging. * * It is primarily used to scale the view is a plugin needs it, and also to keep it * centered around the `grab_position`. */ class scale_around_grab_t : public wf::scene::transformer_base_node_t { public: /** * Factor for scaling down the view. * A factor 2.0 means that the view will have half of its width and height. */ wf::animation::simple_animation_t scale_factor{wf::create_option(300)}; wf::animation::simple_animation_t alpha_factor{wf::create_option(300)}; /** * A place relative to the view, where it is grabbed. * * Coordinates are [0, 1]. A grab at (0.5, 0.5) means that the view is grabbed * at its center. */ wf::pointf_t relative_grab; /** * The position where the grab appears on the outputs, in output-layout * coordinates. */ wf::point_t grab_position; scale_around_grab_t() : transformer_base_node_t(false) {} std::string stringify() const override { return "move-drag"; } wf::pointf_t scale_around_grab(wf::pointf_t point, double factor) { auto bbox = get_children_bounding_box(); auto gx = bbox.x + bbox.width * relative_grab.x; auto gy = bbox.y + bbox.height * relative_grab.y; return { (point.x - gx) * factor + gx, (point.y - gy) * factor + gy, }; } wf::pointf_t to_local(const wf::pointf_t& point) override { return scale_around_grab(point, scale_factor); } wf::pointf_t to_global(const wf::pointf_t& point) override { return scale_around_grab(point, 1.0 / scale_factor); } wf::geometry_t get_bounding_box() override { auto bbox = get_children_bounding_box(); int w = std::floor(bbox.width / scale_factor); int h = std::floor(bbox.height / scale_factor); return find_geometry_around({w, h}, grab_position, relative_grab); } class render_instance_t : public scene::transformer_render_instance_t { public: using transformer_render_instance_t::transformer_render_instance_t; void transform_damage_region(wf::region_t& region) override { region |= self->get_bounding_box(); } void render(const wf::scene::render_instruction_t& data) override { auto bbox = self->get_bounding_box(); auto tex = this->get_texture(data.target.scale); data.pass->add_texture(tex, data.target, bbox, data.damage, self->alpha_factor); } }; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { instances.push_back(std::make_unique(this, push_damage, shown_on)); } }; static const std::string move_drag_transformer = "move-drag-transformer"; /** * Represents a view which is being dragged. * Multiple views exist only if join_views is set to true. */ struct dragged_view_t { // The view being dragged wayfire_toplevel_view view; // Its transformer std::shared_ptr transformer; // The last bounding box used for damage. // This is needed in case the view resizes or something like that, in which // case we don't have access to the previous bbox. wf::geometry_t last_bbox; }; // A node to render the dragged views in global coordinates. // The assumption is that all nodes have a view transformer which transforms them to global (not output-local) // coordinates and thus we just need to schedule them for rendering. class dragged_view_node_t : public wf::scene::node_t { public: std::vector views; dragged_view_node_t(std::vector views) : node_t(false) { this->views = views; } std::string stringify() const override { return "move-drag-view " + stringify_flags(); } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output = nullptr) override { instances.push_back(std::make_unique( std::dynamic_pointer_cast(shared_from_this()), push_damage, output)); } wf::geometry_t get_bounding_box() override { wf::region_t bounding; for (auto& view : views) { // Note: bbox will be in output layout coordinates now, since this is // how the transformer works auto bbox = view.view->get_transformed_node()->get_bounding_box(); bounding |= bbox; } return wlr_box_from_pixman_box(bounding.get_extents()); } class dragged_view_render_instance_t : public wf::scene::render_instance_t { wf::geometry_t last_bbox = {0, 0, 0, 0}; wf::scene::damage_callback push_damage; wf::output_t *shown_on = nullptr; std::weak_ptr self; std::unique_ptr children_manager; wf::signal::connection_t on_node_damage = [=] (scene::node_damage_signal *data) { push_damage(data->region); }; public: dragged_view_render_instance_t(std::shared_ptr self, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { this->self = self; this->push_damage = push_damage; std::vector all_rendered; for (auto& view : self->views) { all_rendered.push_back(view.view->get_transformed_node()); } auto push_damage_child = [=] (wf::region_t child_damage) { push_damage(last_bbox); last_bbox = this->self.lock()->get_bounding_box(); push_damage(last_bbox); }; this->shown_on = shown_on; this->children_manager = std::make_unique(all_rendered, push_damage_child, shown_on); const int BIG_NUMBER = 1e5; wf::region_t big_region = wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER}; children_manager->set_visibility_region(big_region); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { for (auto& inst : children_manager->get_instances()) { inst->schedule_instructions(instructions, target, damage); } } void presentation_feedback(wf::output_t *output) override { for (auto& instance : children_manager->get_instances()) { instance->presentation_feedback(output); } } }; }; struct core_drag_t::impl { // All views being dragged, more than one in case of join_views. std::vector all_views; // Current parameters drag_options_t params; // View is held in place, waiting for snap-off bool view_held_in_place = false; std::shared_ptr render_node; wf::effect_hook_t on_pre_frame = [=] () { for (auto& v : this->all_views) { if (v.transformer->scale_factor.running()) { v.view->damage(); } } }; wf::signal::connection_t on_view_unmap; wf::signal::connection_t on_output_removed; }; core_drag_t::core_drag_t() { this->priv = std::make_unique(); priv->on_view_unmap = [=] (auto *ev) { handle_input_released(); }; priv->on_output_removed = [=] (wf::output_removed_signal *ev) { if (current_output == ev->output) { update_current_output(nullptr); } }; wf::get_core().output_layout->connect(&priv->on_output_removed); } core_drag_t::~core_drag_t() = default; void core_drag_t::rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative) { auto dim = wf::dimensions(wf::view_bounding_box_up_to(view, "wobbly")); modify_wobbly(view, find_geometry_around(dim, grab, relative)); } bool core_drag_t::should_start_pending_drag(wf::point_t current_position) { if (!tentative_grab_position.has_value()) { return false; } return distance_to_grab_origin(current_position) > 5; } void core_drag_t::start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, const drag_options_t& options) { wf::dassert(tentative_grab_position.has_value(), "First, the drag operation should be set as pending!"); wf::dassert(grab_view->is_mapped(), "Dragged view should be mapped!"); wf::dassert(!this->view, "Drag operation already in progress!"); auto bbox = wf::view_bounding_box_up_to(grab_view, "wobbly"); wf::point_t rel_grab_pos = { int(bbox.x + relative.x * bbox.width), int(bbox.y + relative.y * bbox.height), }; if (options.join_views) { grab_view = wf::find_topmost_parent(grab_view); } this->view = grab_view; priv->params = options; wf::get_core().default_wm->set_view_grabbed(view, true); auto target_views = get_target_views(grab_view, options.join_views); for (auto& v : target_views) { dragged_view_t dragged; dragged.view = v; // Setup view transform auto tr = std::make_shared(); dragged.transformer = {tr}; tr->relative_grab = find_relative_grab( wf::view_bounding_box_up_to(v, "wobbly"), rel_grab_pos); tr->grab_position = *tentative_grab_position; tr->scale_factor.animate(options.initial_scale, options.initial_scale); tr->alpha_factor.animate(1, 1); v->get_transformed_node()->add_transformer( tr, wf::TRANSFORMER_HIGHLEVEL - 1); // Hide the view, we will render it as an overlay wf::scene::set_node_enabled(v->get_transformed_node(), false); v->damage(); // Make sure that wobbly has the correct geometry from the start! rebuild_wobbly(v, *tentative_grab_position, dragged.transformer->relative_grab); // TODO: make this configurable! start_wobbly_rel(v, dragged.transformer->relative_grab); priv->all_views.push_back(dragged); v->connect(&priv->on_view_unmap); } // Setup overlay hooks priv->render_node = std::make_shared(priv->all_views); wf::scene::add_front(wf::get_core().scene(), priv->render_node); wf::get_core().set_cursor("grabbing"); // Set up snap-off if (priv->params.enable_snap_off) { for (auto& v : priv->all_views) { set_tiled_wobbly(v.view, true); } priv->view_held_in_place = true; } } void core_drag_t::start_drag(wayfire_toplevel_view view, const drag_options_t& options) { wf::dassert(tentative_grab_position.has_value(), "First, the drag operation should be set as pending!"); if (options.join_views) { view = wf::find_topmost_parent(view); } auto bbox = view->get_transformed_node()->get_bounding_box() + wf::origin(view->get_output()->get_layout_geometry()); start_drag(view, find_relative_grab(bbox, *tentative_grab_position), options); } void core_drag_t::handle_motion(wf::point_t to) { if (priv->view_held_in_place) { if (distance_to_grab_origin(to) >= (double)priv->params.snap_off_threshold) { priv->view_held_in_place = false; for (auto& v : priv->all_views) { set_tiled_wobbly(v.view, false); } update_current_output(to); snap_off_signal data; data.focus_output = current_output; emit(&data); } } // Update wobbly independently of the grab position. // This is because while held in place, wobbly is anchored to its edges // so we can still move the grabbed point without moving the view. for (auto& v : priv->all_views) { move_wobbly(v.view, to.x, to.y); if (!priv->view_held_in_place) { v.view->get_transformed_node()->begin_transform_update(); v.transformer->grab_position = to; v.view->get_transformed_node()->end_transform_update(); } } update_current_output(to); drag_motion_signal data; data.current_position = to; emit(&data); } double core_drag_t::distance_to_grab_origin(wf::point_t to) const { return abs(to - *tentative_grab_position); } void core_drag_t::handle_input_released() { if (!view || priv->all_views.empty()) { this->tentative_grab_position = {}; // Input already released => don't do anything return; } // Store data for the drag done signal drag_done_signal data; data.grab_position = priv->all_views.front().transformer->grab_position; for (auto& v : priv->all_views) { data.all_views.push_back( {v.view, v.transformer->relative_grab}); } data.main_view = this->view; data.focused_output = current_output; data.join_views = priv->params.join_views; // Remove overlay hooks and damage outputs BEFORE popping the transformer wf::scene::remove_child(priv->render_node); priv->render_node->views.clear(); priv->render_node = nullptr; for (auto& v : priv->all_views) { auto grab_position = v.transformer->grab_position; auto rel_pos = v.transformer->relative_grab; // Restore view to where it was before wf::scene::set_node_enabled(v.view->get_transformed_node(), true); v.view->get_transformed_node()->rem_transformer(); // Reset wobbly and leave it in output-LOCAL coordinates end_wobbly(v.view); // Important! If the view scale was not 1.0, the wobbly model needs to be // updated with the new size. Since this is an artificial resize, we need // to make sure that the resize happens smoothly. rebuild_wobbly(v.view, grab_position, rel_pos); // Put wobbly back in output-local space, the plugins will take it from // here. translate_wobbly(v.view, -wf::origin(v.view->get_output()->get_layout_geometry())); } // Reset our state wf::get_core().default_wm->set_view_grabbed(view, false); view = nullptr; priv->all_views.clear(); if (current_output) { current_output->render->rem_effect(&priv->on_pre_frame); current_output = nullptr; } wf::get_core().set_cursor("default"); // Lastly, let the plugins handle what happens on drag end. emit(&data); priv->view_held_in_place = false; priv->on_view_unmap.disconnect(); this->tentative_grab_position = {}; } void core_drag_t::set_scale(double new_scale, double alpha) { for (auto& view : priv->all_views) { view.transformer->scale_factor.animate(new_scale); view.transformer->alpha_factor.animate(alpha); } } bool core_drag_t::is_view_held_in_place() { return priv->view_held_in_place; } void core_drag_t::update_current_output(wf::point_t grab) { wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y}; auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin); update_current_output(output); } void core_drag_t::update_current_output(wf::output_t *output) { if (output != current_output) { if (current_output) { current_output->render->rem_effect(&priv->on_pre_frame); } drag_focus_output_signal data; data.previous_focus_output = current_output; current_output = output; data.focus_output = output; if (output) { wf::get_core().seat->focus_output(output); output->render->add_effect(&priv->on_pre_frame, OUTPUT_EFFECT_PRE); } emit(&data); } } /** * Move the view to the target output and put it at the coordinates of the grab. * Also take into account view's fullscreen and tiled state. * * Unmapped views are ignored. */ void adjust_view_on_output(drag_done_signal *ev) { // Any one of the views that are being dragged. // They are all part of the same view tree. auto parent = wf::find_topmost_parent(ev->main_view); if (!parent->is_mapped()) { return; } const bool change_output = parent->get_output() != ev->focused_output; auto old_wset = parent->get_wset(); if (change_output) { start_move_view_to_wset(parent, ev->focused_output->wset()); } // Calculate the position we're leaving the view on auto output_delta = -wf::origin(ev->focused_output->get_layout_geometry()); auto grab = ev->grab_position + output_delta; auto output_geometry = ev->focused_output->get_relative_geometry(); auto current_ws = ev->focused_output->wset()->get_current_workspace(); wf::point_t target_ws{ (int)std::floor(1.0 * grab.x / output_geometry.width), (int)std::floor(1.0 * grab.y / output_geometry.height), }; target_ws = target_ws + current_ws; auto gsize = ev->focused_output->wset()->get_workspace_grid_size(); target_ws.x = wf::clamp(target_ws.x, 0, gsize.width - 1); target_ws.y = wf::clamp(target_ws.y, 0, gsize.height - 1); // view to focus at the end of drag auto focus_view = ev->main_view; for (auto& v : ev->all_views) { if (!v.view->is_mapped()) { // Maybe some dialog got unmapped continue; } auto bbox = wf::view_bounding_box_up_to(v.view, "wobbly"); auto wm = v.view->get_geometry(); wf::point_t wm_offset = wf::origin(wm) + -wf::origin(bbox); bbox = wf::move_drag::find_geometry_around( wf::dimensions(bbox), grab, v.relative_grab); wf::point_t target = wf::origin(bbox) + wm_offset; v.view->move(target.x, target.y); if (v.view->pending_fullscreen()) { wf::get_core().default_wm->fullscreen_request(v.view, ev->focused_output, true, target_ws); } else if (v.view->pending_tiled_edges()) { wf::get_core().default_wm->tile_request(v.view, v.view->pending_tiled_edges(), target_ws); } // check focus timestamp and select the last focused view to (re)focus if (get_focus_timestamp(v.view) > get_focus_timestamp(focus_view)) { focus_view = v.view; } } // Ensure that every view is visible on parent's main workspace for (auto& v : parent->enumerate_views()) { ev->focused_output->wset()->move_to_workspace(v, target_ws); } if (change_output) { emit_view_moved_to_wset(parent, old_wset, ev->focused_output->wset()); } wf::get_core().default_wm->focus_raise_view(focus_view); } /** * Adjust the view's state after snap-off. */ void adjust_view_on_snap_off(wayfire_toplevel_view view) { if (view->pending_tiled_edges() && !view->pending_fullscreen()) { wf::get_core().default_wm->tile_request(view, 0); } } } } wayfire-0.10.0/plugins/common/wayfire/0000775000175000017500000000000015053502647017550 5ustar dkondordkondorwayfire-0.10.0/plugins/common/wayfire/plugins/0000775000175000017500000000000015053502647021231 5ustar dkondordkondorwayfire-0.10.0/plugins/common/wayfire/plugins/common/0000775000175000017500000000000015053502647022521 5ustar dkondordkondorwayfire-0.10.0/plugins/common/wayfire/plugins/common/simple-text-node.hpp0000664000175000017500000000333415053502647026433 0ustar dkondordkondor#include "wayfire/output.hpp" #include "wayfire/scene.hpp" #include #include class simple_text_node_t : public wf::scene::node_t { class render_instance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::scene::render_instruction_t& data) { auto g = self->get_bounding_box(); data.pass->add_texture(self->cr_text.get_texture(), data.target, g, data.damage); } }; wf::cairo_text_t cr_text; public: simple_text_node_t() : node_t(false) {} void gen_render_instances(std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output) override { instances.push_back(std::make_unique(this, push_damage, output)); } wf::geometry_t get_bounding_box() override { return wf::construct_box(position, size.value_or(cr_text.get_size())); } void set_position(wf::point_t position) { this->position = position; } void set_size(wf::dimensions_t size) { this->size = size; } void set_text_params(wf::cairo_text_t::params params) { this->params = params; } void set_text(std::string text) { wf::scene::damage_node(this->shared_from_this(), get_bounding_box()); cr_text.render_text(text, params); wf::scene::damage_node(this->shared_from_this(), get_bounding_box()); } private: wf::cairo_text_t::params params; std::optional size; wf::point_t position; }; wayfire-0.10.0/plugins/common/wayfire/plugins/common/cairo-util.hpp0000664000175000017500000002522415053502647025307 0ustar dkondordkondor#pragma once #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include #include #include #include #include #include #include // TODO: do we need some kind of dependency here? #include namespace wf { /** * Very basic wrapper around wlr_texture. * It destroys the texture automatically. */ struct owned_texture_t { owned_texture_t(const owned_texture_t& other) = delete; owned_texture_t& operator =(const owned_texture_t& other) = delete; owned_texture_t(owned_texture_t&& other) : owned_texture_t() { std::swap(tex, other.tex); std::swap(size, other.size); } owned_texture_t& operator =(owned_texture_t&& other) { if (&other == this) { return *this; } if (tex) { wlr_texture_destroy(tex); } tex = other.tex; other.tex = NULL; size = other.size; other.size = {0, 0}; return *this; } ~owned_texture_t() { if (tex) { wlr_texture_destroy(tex); } } wf::texture_t get_texture() const { return wf::texture_t{tex}; } wf::dimensions_t get_size() const { return size; } // Empty texture. owned_texture_t() {} // Assumes ownership of the texture! owned_texture_t(wlr_texture *new_tex) { tex = new_tex; } owned_texture_t(cairo_surface_t *surface) { int width = cairo_image_surface_get_width(surface); int height = cairo_image_surface_get_height(surface); int stride = cairo_image_surface_get_stride(surface); cairo_format_t fmt = cairo_image_surface_get_format(surface); uint32_t drm_fmt = 0; if ((width <= 0) || (height <= 0)) { // empty texture return; } switch (fmt) { case CAIRO_FORMAT_ARGB32: drm_fmt = DRM_FORMAT_ARGB8888; break; default: wf::dassert(false, "Unsupported cairo format: " + std::to_string(fmt) + "!"); } this->tex = wlr_texture_from_pixels(wf::get_core().renderer, drm_fmt, stride, width, height, cairo_image_surface_get_data(surface)); this->size = {width, height}; } private: wlr_texture *tex = NULL; wf::dimensions_t size = {0, 0}; }; /** * Simple wrapper around rendering text with Cairo. This object can be * kept around to avoid reallocation of the cairo surface and OpenGL * texture on repeated renders. */ struct cairo_text_t { /* parameters used for rendering */ struct params { /* font size */ int font_size = 12; /* color for background rectangle (only used if bg_rect == true) */ wf::color_t bg_color; /* text color */ wf::color_t text_color; /* scale everything by this amount */ float output_scale = 1.f; /* crop result to this size (if nonzero); * note that this is multiplied by output_scale */ wf::dimensions_t max_size{0, 0}; /* draw a rectangle in the background with bg_color */ bool bg_rect = true; /* round the corners of the background rectangle */ bool rounded_rect = true; /* if true, the resulting surface will be cropped to the * minimum size necessary to fit the text; otherwise, the * resulting surface might be bigger than necessary and the * text is centered in it */ bool exact_size = false; params() {} params(int font_size_, const wf::color_t& bg_color_, const wf::color_t& text_color_, float output_scale_ = 1.f, const wf::dimensions_t& max_size_ = {0, 0}, bool bg_rect_ = true, bool exact_size_ = false) : font_size(font_size_), bg_color(bg_color_), text_color(text_color_), output_scale(output_scale_), max_size(max_size_), bg_rect(bg_rect_), exact_size(exact_size_) {} }; /** * Render the given text in the texture tex. * * @param text text to render * @param par parameters for rendering * * @return The size needed to render in scaled coordinates. If this is larger * than the size of tex, it means the result was cropped (due to the constraint * given in par.max_size). If it is smaller, than the result is centered along * that dimension. */ wf::dimensions_t render_text(const std::string& text, const params& par) { if (!cr) { /* create with default size */ cairo_create_surface(); } PangoFontDescription *font_desc; PangoLayout *layout; PangoRectangle extents; /* TODO: font properties could be made parameters! */ font_desc = pango_font_description_from_string("sans-serif bold"); pango_font_description_set_absolute_size(font_desc, par.font_size * par.output_scale * PANGO_SCALE); layout = pango_cairo_create_layout(cr); pango_layout_set_font_description(layout, font_desc); pango_layout_set_text(layout, text.c_str(), text.size()); pango_layout_get_extents(layout, NULL, &extents); double xpad = par.bg_rect ? 10.0 * par.output_scale : 0.0; double ypad = par.bg_rect ? 0.2 * ((float)extents.height / PANGO_SCALE) : 0.0; int w = (int)((float)extents.width / PANGO_SCALE + 2 * xpad); int h = (int)((float)extents.height / PANGO_SCALE + 2 * ypad); wf::dimensions_t ret = {w, h}; if (par.max_size.width && (w > par.max_size.width * par.output_scale)) { w = (int)std::floor(par.max_size.width * par.output_scale); } if (par.max_size.height && (h > par.max_size.height * par.output_scale)) { h = (int)std::floor(par.max_size.height * par.output_scale); } if ((w != surface_size.width) || (h != surface_size.height)) { if (par.exact_size || (w > surface_size.width) || (h > surface_size.height)) { cairo_create_surface({w, h}); } } cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); int x = (surface_size.width - w) / 2; int y = (surface_size.height - h) / 2; if (par.bg_rect) { int min_r = (int)(20 * par.output_scale); int r = par.rounded_rect ? (h > min_r ? min_r : (h - 2) / 2) : 0; cairo_move_to(cr, x + r, y); cairo_line_to(cr, x + w - r, y); if (par.rounded_rect) { cairo_curve_to(cr, x + w, y, x + w, y, x + w, y + r); } cairo_line_to(cr, x + w, y + h - r); if (par.rounded_rect) { cairo_curve_to(cr, x + w, y + h, x + w, y + h, x + w - r, y + h); } cairo_line_to(cr, x + r, y + h); if (par.rounded_rect) { cairo_curve_to(cr, x, y + h, x, y + h, x, y + h - r); } cairo_line_to(cr, x, y + r); if (par.rounded_rect) { cairo_curve_to(cr, x, y, x, y, x + r, y); } cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, par.bg_color.r, par.bg_color.g, par.bg_color.b, par.bg_color.a); cairo_fill(cr); } x += xpad; y += ypad; cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_move_to(cr, x - (float)extents.x / PANGO_SCALE, y); cairo_set_source_rgba(cr, par.text_color.r, par.text_color.g, par.text_color.b, par.text_color.a); pango_cairo_show_layout(cr, layout); pango_font_description_free(font_desc); g_object_unref(layout); cairo_surface_flush(surface); this->tex = owned_texture_t{surface}; return ret; } cairo_text_t() = default; ~cairo_text_t() { cairo_free(); } cairo_text_t(const cairo_text_t &) = delete; cairo_text_t& operator =(const cairo_text_t&) = delete; cairo_text_t(cairo_text_t && o) noexcept : cr(o.cr), surface(o.surface), surface_size(o.surface_size), tex(std::move(o.tex)) { o.cr = nullptr; o.surface = nullptr; } cairo_text_t& operator =(cairo_text_t&& o) noexcept { if (&o == this) { return *this; } cairo_free(); tex = std::move(o.tex); cr = o.cr; surface = o.surface; surface_size = o.surface_size; o.cr = nullptr; o.surface = nullptr; return *this; } /** * Calculate the height of text rendered with a given font size. * * @param font_size Desired font size. * @param bg_rect Whether a background rectangle should be taken into account. * * @returns Required height of the surface. */ static unsigned int measure_height(int font_size, bool bg_rect = true) { cairo_text_t dummy; dummy.cairo_create_surface({1, 1}); cairo_font_extents_t font_extents; /* TODO: font properties could be made parameters! */ cairo_select_font_face(dummy.cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(dummy.cr, font_size); cairo_font_extents(dummy.cr, &font_extents); double ypad = bg_rect ? 0.2 * (font_extents.ascent + font_extents.descent) : 0.0; unsigned int h = (unsigned int)std::ceil(font_extents.ascent + font_extents.descent + 2 * ypad); return h; } wf::dimensions_t get_size() const { return surface_size; } wf::texture_t get_texture() const { return this->tex.get_texture(); } protected: /* cairo context and surface for the text */ cairo_t *cr = nullptr; cairo_surface_t *surface = nullptr; /* current width and height of the above surface */ wf::dimensions_t surface_size = {0, 0}; void cairo_free() { if (cr) { cairo_destroy(cr); } if (surface) { cairo_surface_destroy(surface); } cr = nullptr; surface = nullptr; } void cairo_create_surface(wf::dimensions_t size = {400, 100}) { cairo_free(); this->surface_size = size; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, surface_size.width, surface_size.height); cr = cairo_create(surface); } owned_texture_t tex; }; } wayfire-0.10.0/plugins/common/wayfire/plugins/common/move-drag-interface.hpp0000664000175000017500000001444715053502647027063 0ustar dkondordkondor#pragma once #include "wayfire/toplevel-view.hpp" #include "wayfire/geometry.hpp" #include #include #include namespace wf { /** * A collection of classes and interfaces which can be used by plugins which * support dragging views to move them. * * A plugin using these APIs would get support for: * * - Moving views on the same output, following the pointer or touch position. * - Holding views in place until a certain threshold is reached * - Wobbly windows (if enabled) * - Move the view freely between different outputs with different plugins active * on them, as long as all of these plugins support this interface. * - Show smooth transitions of the moving view when moving between different * outputs. * * A plugin using these APIs is expected to: * - Grab input on its respective output and forward any events to the core_drag_t * singleton. * - Have activated itself with CAPABILITY_MANAGE_COMPOSITOR * - Connect to and handle the signals described below. */ namespace move_drag { /** * name: focus-output * on: core_drag_t * when: Emitted output whenever the output where the drag happens changes, * including when the drag begins. */ struct drag_focus_output_signal { /** The output which was focused up to now, might be null. */ wf::output_t *previous_focus_output; /** The output which was focused now. */ wf::output_t *focus_output; }; /** * Emitted on core_drag_t when input motion is triggered. */ struct drag_motion_signal { wf::point_t current_position; }; /** * name: snap-off * on: core_drag_t * when: Emitted if snap-off is enabled and the view was moved more than the * threshold. */ struct snap_off_signal { /** The output which is focused now. */ wf::output_t *focus_output; }; /** * name: done * on: core_drag_t * when: Emitted after the drag operation has ended, and if the view is unmapped * while being dragged. */ struct drag_done_signal { /** The output where the view was dropped. */ wf::output_t *focused_output; /** Whether join-views was enabled for this drag. */ bool join_views; struct view_t { /** Dragged view. */ wayfire_toplevel_view view; /** * The position relative to the view where the grab was. * See scale_around_grab_t::relative_grab */ wf::pointf_t relative_grab; }; /** All views which were dragged. */ std::vector all_views; /** The main view which was dragged. */ wayfire_toplevel_view main_view; /** * The position of the input when the view was dropped. * In output-layout coordinates. */ wf::point_t grab_position; }; /** * Find the position of grab relative to the view. * Example: returns [0.5, 0.5] if the grab is the midpoint of the view. */ inline static wf::pointf_t find_relative_grab( wf::geometry_t view, wf::point_t grab) { return wf::pointf_t{ 1.0 * (grab.x - view.x) / view.width, 1.0 * (grab.y - view.y) / view.height, }; } inline std::vector get_target_views(wayfire_toplevel_view grabbed, bool join_views) { std::vector r = {grabbed}; if (join_views) { r = grabbed->enumerate_views(); } return r; } struct drag_options_t { /** * Whether to enable snap off, that is, hold the view in place until * a certain threshold is reached. */ bool enable_snap_off = false; /** * If snap-off is enabled, the amount of pixels to wait for motion until * snap-off is triggered. */ int snap_off_threshold = 0; /** * Join views together, i.e move main window and dialogues together. */ bool join_views = false; double initial_scale = 1.0; }; /** * An object for storing global move drag data (i.e shared between all outputs). * * Intended for use via wf::shared_data::ref_ptr_t. */ class core_drag_t : public signal::provider_t { /** * Rebuild the wobbly model after a change in the scaling, so that the wobbly * model does not try to animate the scaling change itself. */ void rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative); public: std::optional tentative_grab_position; core_drag_t(); ~core_drag_t(); /** * A button has been pressed which might start a drag action. */ template void set_pending_drag(const Point& current_position) { this->tentative_grab_position = {(int)current_position.x, (int)current_position.y}; } /** * Check whether a motion event makes a sufficient drag so that the drag operation may start at all. * * Note that in some cases this functionality is not used at all, if the action for example was triggered * by a binding. */ bool should_start_pending_drag(wf::point_t current_position); /** * Start the actual dragging operation. Note: this should be called **after** set_pending_drag(). * * @param grab_view The view which is being dragged. * @param grab_position The position of the input, in output-layout coordinates. * @param relative The position of the grab_position relative to view. */ void start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, const drag_options_t& options); void start_drag(wayfire_toplevel_view view, const drag_options_t& options); void handle_motion(wf::point_t to); double distance_to_grab_origin(wf::point_t to) const; void handle_input_released(); void set_scale(double new_scale, double alpha = 1.0); bool is_view_held_in_place(); // View currently being moved. wayfire_toplevel_view view; // Output where the action is happening. wf::output_t *current_output = NULL; private: struct impl; std::unique_ptr priv; void update_current_output(wf::point_t grab); void update_current_output(wf::output_t *output); }; /** * Move the view to the target output and put it at the coordinates of the grab. * Also take into account view's fullscreen and tiled state. * * Unmapped views are ignored. */ void adjust_view_on_output(drag_done_signal *ev); /** * Adjust the view's state after snap-off. */ void adjust_view_on_snap_off(wayfire_toplevel_view view); } } wayfire-0.10.0/plugins/common/wayfire/plugins/common/util.hpp0000664000175000017500000000667215053502647024222 0ustar dkondordkondor// A collection of small utility functions that plugins use. // FIXME: consider splitting into multiple files as util functions accumulate. #pragma once #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include "wayfire/output.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view-transform.hpp" #include namespace wf { inline uint64_t get_focus_timestamp(wayfire_view view) { const auto& node = view->get_surface_root_node(); return node->keyboard_interaction().last_focus_timestamp; } template inline std::shared_ptr ensure_view_transformer( wayfire_view view, int z_order, TransformerArgs... args) { auto trmanager = view->get_transformed_node(); auto transformer = trmanager->get_transformer(); if (!transformer) { transformer = std::make_shared(args...); trmanager->add_transformer(transformer, z_order); } return transformer; } template inline std::shared_ptr ensure_named_transformer( wayfire_view view, int z_order, std::string name, TransformerArgs... args) { auto trmanager = view->get_transformed_node(); auto transformer = trmanager->get_transformer(name); if (!transformer) { transformer = std::make_shared(args...); trmanager->add_transformer(transformer, z_order, name); } return transformer; } template inline wf::geometry_t view_bounding_box_up_to(wayfire_view view, std::string name = typeid(Transformer).name()) { auto transformer = view->get_transformed_node()->get_transformer(name); if (transformer) { return transformer->get_children_bounding_box(); } else { return view->get_transformed_node()->get_bounding_box(); } } /** * Find the topmost view on the given coordinates on the given output, bypassing any overlays / input grabs. */ inline wayfire_toplevel_view find_output_view_at(wf::output_t *output, const wf::pointf_t& coords) { for (auto& output_node : wf::collect_output_nodes(wf::get_core().scene(), output)) { auto as_output = std::dynamic_pointer_cast(output_node); if (!as_output || (as_output->get_output() != output) || !as_output->is_enabled()) { continue; } // We start the search directly from the output node's children. This is because the output nodes // usually reject all queries outside of their current visible geometry, but we want to be able to // query views from all workspaces, not just the current (and the only visible) one. for (auto& ch : output_node->get_children()) { if (!ch->is_enabled()) { continue; } auto isec = ch->find_node_at(coords); auto node = isec ? isec->node.get() : nullptr; if (auto view = wf::toplevel_cast(wf::node_to_view(node))) { if (wf::find_topmost_parent(view)->get_wset() == output->wset()) { return view; } } if (node) { return nullptr; } } } return nullptr; } } wayfire-0.10.0/plugins/common/wayfire/plugins/common/geometry-animation.hpp0000664000175000017500000000301615053502647027042 0ustar dkondordkondor#pragma once #include #include #include namespace wf { using namespace wf::animation; class geometry_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t x{*this}; timed_transition_t y{*this}; timed_transition_t width{*this}; timed_transition_t height{*this}; void set_start(wf::geometry_t geometry) { copy_fields(geometry, &timed_transition_t::start); } void set_end(wf::geometry_t geometry) { copy_fields(geometry, &timed_transition_t::end); } operator wf::geometry_t() const { return {(int)x, (int)y, (int)width, (int)height}; } protected: void copy_fields(wf::geometry_t geometry, double timed_transition_t::*member) { this->x.*member = geometry.x; this->y.*member = geometry.y; this->width.*member = geometry.width; this->height.*member = geometry.height; } }; /** Interpolate the geometry between a and b with alpha (in [0..1]), i.e a * * (1-alpha) + b * alpha */ static inline wf::geometry_t interpolate(wf::geometry_t a, wf::geometry_t b, double alpha) { const auto& interp = [=] (int32_t wf::geometry_t::*member) -> int32_t { return std::round((1 - alpha) * a.*member + alpha * b.*member); }; return { interp(&wf::geometry_t::x), interp(&wf::geometry_t::y), interp(&wf::geometry_t::width), interp(&wf::geometry_t::height) }; } } wayfire-0.10.0/plugins/common/wayfire/plugins/common/key-repeat.hpp0000664000175000017500000000166515053502647025310 0ustar dkondordkondor#pragma once #include #include namespace wf { struct key_repeat_t { wf::option_wrapper_t delay{"input/kb_repeat_delay"}; wf::option_wrapper_t rate{"input/kb_repeat_rate"}; wf::wl_timer timer_delay; wf::wl_timer timer_rate; using callback_t = std::function; key_repeat_t() {} key_repeat_t(uint32_t key, callback_t handler) { set_callback(key, handler); } void set_callback(uint32_t key, callback_t handler) { disconnect(); timer_delay.set_timeout(delay, [=] () { timer_rate.set_timeout(1000 / rate, [=] () { // handle can determine if key should be repeated return handler(key); }); }); } void disconnect() { timer_delay.disconnect(); timer_rate.disconnect(); } }; } wayfire-0.10.0/plugins/common/wayfire/plugins/common/workspace-wall.hpp0000664000175000017500000000714215053502647026171 0ustar dkondordkondor#pragma once #include "wayfire/workspace-set.hpp" // IWYU pragma: keep #include #include #include #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/output.hpp" namespace wf { /** * When the workspace wall is rendered via a render hook, the frame event * is emitted on each frame. * * The target framebuffer is passed as signal data. */ struct wall_frame_event_t { const wf::render_target_t& target; wall_frame_event_t(const wf::render_target_t& t) : target(t) {} }; /** * A helper class to render workspaces arranged in a grid. */ class workspace_wall_t : public wf::signal::provider_t { public: /** * Create a new workspace wall on the given output. */ workspace_wall_t(wf::output_t *_output); ~workspace_wall_t(); /** * Set the color of the background outside of workspaces. * * @param color The new background color. */ void set_background_color(const wf::color_t& color); /** * Set the size of the gap between adjacent workspaces, both horizontally * and vertically. * * @param size The new gap size, in pixels. */ void set_gap_size(int size); /** * Set which part of the workspace wall to render. * * If the output has effective resolution WxH and the gap size is G, then a * workspace with coordinates (i, j) has geometry * {i * (W + G), j * (H + G), W, H}. * * All other regions are painted with the background color. * * @param viewport_geometry The part of the workspace wall to render. */ void set_viewport(const wf::geometry_t& viewport_geometry); wf::geometry_t get_viewport() const; /** * Render the selected viewport on the framebuffer. * * @param fb The framebuffer to render on. * @param geometry The rectangle in fb to draw to, in the same coordinate * system as the framebuffer's geometry. */ void render_wall(const wf::render_target_t& fb, const wf::region_t& damage); /** * Register a render hook and paint the whole output as a desktop wall * with the set parameters. */ void start_output_renderer(); /** * Stop repainting the whole output. * * @param reset_viewport If true, the viewport will be reset to {0, 0, 0, 0} * and thus all workspace streams will be stopped. */ void stop_output_renderer(bool reset_viewport); /** * Calculate the geometry of a particular workspace, as described in * set_viewport(). * * @param ws The workspace whose geometry is to be computed. */ wf::geometry_t get_workspace_rectangle(const wf::point_t& ws) const; /** * Calculate the whole workspace wall region, including gaps around it. */ wf::geometry_t get_wall_rectangle() const; /** * Get/set the dimming factor for a given workspace. */ void set_ws_dim(const wf::point_t& ws, float value); protected: wf::output_t *output; wf::color_t background_color = {0, 0, 0, 0}; int gap_size = 0; wf::geometry_t viewport = {0, 0, 0, 0}; std::map, float> render_colors; float get_color_for_workspace(wf::point_t ws); /** * Get a list of workspaces visible in the viewport. */ std::vector get_visible_workspaces(wf::geometry_t viewport) const; protected: class workspace_wall_node_t; std::shared_ptr render_node; }; } wayfire-0.10.0/plugins/common/wayfire/plugins/common/preview-indication.hpp0000664000175000017500000001151315053502647027033 0ustar dkondordkondor#pragma once #include #include #include #include #include "geometry-animation.hpp" #include "wayfire/view.hpp" #include #include namespace wf { using namespace wf::animation; class preview_indication_animation_t : public geometry_animation_t { public: using geometry_animation_t::geometry_animation_t; timed_transition_t alpha{*this}; }; /** * A view which can be used to show previews for different actions on the * screen, for ex. when snapping a view */ class preview_indication_t : public std::enable_shared_from_this { wf::effect_hook_t pre_paint; wf::output_t *output; preview_indication_animation_t animation; bool should_close = false; /* Default colors */ const wf::option_wrapper_t base_color; const wf::option_wrapper_t base_border; const wf::option_wrapper_t base_border_w; std::shared_ptr _self_reference; public: std::shared_ptr view; /** * Create a new indication preview on the indicated output. * * @param start_geometry The geometry the preview should have, relative to * the output */ preview_indication_t(wf::geometry_t start_geometry, wf::output_t *output, const std::string& prefix) : animation(wf::create_option(200)), base_color(prefix + "/preview_base_color"), base_border(prefix + "/preview_base_border"), base_border_w(prefix + "/preview_border_width") { animation.set_start(start_geometry); animation.set_end(start_geometry); animation.alpha.set(0, 1); this->output = output; pre_paint = [=] () { update_animation(); }; output->render->add_effect(&pre_paint, wf::OUTPUT_EFFECT_PRE); view = color_rect_view_t::create(VIEW_ROLE_DESKTOP_ENVIRONMENT, output, wf::scene::layer::TOP); view->set_color(base_color); view->set_border_color(base_border); view->set_border(base_border_w); } /** A convenience wrapper around the full version */ preview_indication_t(wf::point_t start, wf::output_t *output, const std::string & prefix) : preview_indication_t(wf::geometry_t{start.x, start.y, 1, 1}, output, prefix) {} /** * Animate the preview to the given target geometry and alpha. * * @param close Whether the view should be closed when the target is * reached. */ void set_target_geometry(wf::geometry_t target, float alpha, bool close = false) { animation.x.restart_with_end(target.x); animation.y.restart_with_end(target.y); animation.width.restart_with_end(target.width); animation.height.restart_with_end(target.height); animation.alpha.restart_with_end(alpha); animation.start(); this->should_close = close; if (should_close) { // Take a reference until we finally close the view _self_reference = shared_from_this(); } } /** * A wrapper around set_target_geometry(wf::geometry_t, double, bool) */ void set_target_geometry(wf::point_t point, double alpha, bool should_close = false) { return set_target_geometry({point.x, point.y, 1, 1}, alpha, should_close); } wf::geometry_t get_target_geometry() const { return wf::geometry_t{ .x = int(animation.x.end), .y = int(animation.y.end), .width = int(animation.width.end), .height = int(animation.height.end), }; } wf::output_t *get_output() const { return output; } virtual ~preview_indication_t() { if (this->output) { this->output->render->rem_effect(&pre_paint); } } protected: /** Update the current state */ void update_animation() { wf::geometry_t current = animation; if (current != view->get_geometry()) { view->set_geometry(current); } double alpha = animation.alpha; auto cur_color = view->get_color(); auto cur_border_color = view->get_border_color(); if (base_color.value().a * alpha != cur_color.a) { cur_color.a = alpha * base_color.value().a; cur_border_color.a = alpha * base_border.value().a; view->set_color(cur_color); view->set_border_color(cur_border_color); } /* The end of unmap animation, just exit */ if (!animation.running() && should_close) { view->close(); view->damage(); _self_reference.reset(); } } }; } wayfire-0.10.0/plugins/common/wayfire/plugins/common/input-grab.hpp0000664000175000017500000001226115053502647025304 0ustar dkondordkondor#pragma once #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include #include #include #include namespace wf { namespace scene { /** * A scene node which can be used to implement input grab on a particular output. */ class grab_node_t : public node_t { std::string name; wf::output_t *output; keyboard_interaction_t *keyboard = nullptr; pointer_interaction_t *pointer = nullptr; touch_interaction_t *touch = nullptr; node_flags_bitmask_t m_flags = 0; public: grab_node_t(std::string name, wf::output_t *output, keyboard_interaction_t *keyboard = NULL, pointer_interaction_t *pointer = NULL, touch_interaction_t *touch = NULL) : node_t(false), name(name), output(output), keyboard(keyboard), pointer(pointer), touch(touch) {} node_flags_bitmask_t flags() const override { return node_t::flags() | m_flags; } void set_additional_flags(node_flags_bitmask_t add_flags) { this->m_flags = add_flags; } std::optional find_node_at(const wf::pointf_t& at) override { if (output->get_layout_geometry() & at) { input_node_t result; result.node = this; result.local_coords = to_local(at); return result; } return {}; } wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override { if (output != this->output) { return wf::keyboard_focus_node_t{}; } return wf::keyboard_focus_node_t{ .node = this, .importance = focus_importance::REGULAR, .allow_focus_below = false, }; } /** * Get a textual representation of the node, used for debugging purposes. * For example, see wf::dump_scene(). * The representation should therefore not contain any newline characters. */ std::string stringify() const override { return name + "-input-grab " + std::string(output ? output->to_string() : "null"); } keyboard_interaction_t& keyboard_interaction() override { return keyboard ? *keyboard : node_t::keyboard_interaction(); } pointer_interaction_t& pointer_interaction() override { return pointer ? *pointer : node_t::pointer_interaction(); } touch_interaction_t& touch_interaction() override { return touch ? *touch : node_t::touch_interaction(); } }; } /** * A helper class for managing input grabs on an output. */ class input_grab_t { wf::output_t *output; std::shared_ptr grab_node; public: input_grab_t(std::string name, wf::output_t *output, keyboard_interaction_t *keyboard = NULL, pointer_interaction_t *pointer = NULL, touch_interaction_t *touch = NULL) { this->output = output; grab_node = std::make_shared(name, output, keyboard, pointer, touch); } /** * Set/unset the RAW_INPUT flag on the grab node. */ void set_wants_raw_input(bool wants_raw) { grab_node->set_additional_flags(wants_raw ? (uint64_t)wf::scene::node_flags::RAW_INPUT : 0); } bool is_grabbed() const { return grab_node->parent() != nullptr; } /** * Grab input from all layers from background to @layer_below. */ void grab_input(wf::scene::layer layer_below) { wf::dassert(grab_node->parent() == nullptr, "Trying to grab twice!"); auto& root = wf::get_core().scene(); auto children = root->get_children(); auto idx = std::find(children.begin(), children.end(), root->layers[(int)layer_below]); wf::dassert(idx != children.end(), "Could not find node for a layer: " + std::to_string((int)layer_below)); children.insert(idx, grab_node); root->set_children_list(children); if (output == wf::get_core().seat->get_active_output()) { wf::get_core().transfer_grab(grab_node); } scene::update(root, scene::update_flag::CHILDREN_LIST | scene::update_flag::REFOCUS); // Set cursor to default. wf::get_core().set_cursor("default"); } void regrab_input() { const auto& check_focus = [&] (wf::scene::node_ptr focused) { return (focused == nullptr) || (focused == grab_node); }; if ((wf::get_core().seat->get_active_node() == grab_node) && check_focus(wf::get_core().get_cursor_focus()) && check_focus(wf::get_core().get_touch_focus())) { return; } if (output == wf::get_core().seat->get_active_output()) { wf::get_core().transfer_grab(grab_node); } scene::update(wf::get_core().scene(), scene::update_flag::REFOCUS); } /** * Ungrab the input. */ void ungrab_input() { if (grab_node->parent()) { wf::scene::remove_child(grab_node, scene::update_flag::REFOCUS); } } }; } wayfire-0.10.0/plugins/common/wayfire/plugins/common/shared-core-data.hpp0000664000175000017500000000363615053502647026345 0ustar dkondordkondor#pragma once #include #include namespace wf { /** * The purpose of shared is to allow multiple plugins or plugin instances to * have shared global custom data. * * While this is already possible if the shared data is stored as custom data on * `wf::get_core()`, the classes here provide convenient wrappers for managing * the lifetime of the shared data by utilizing RAII. */ namespace shared_data { namespace detail { /** Implementation detail: the actual data stored in core. */ template class shared_data_t : public wf::custom_data_t { public: T data; int32_t use_count = 0; }; } /** * A pointer to shared data which holds a reference to it (similar to * std::shared_ptr). Once the last reference is destroyed, data will be freed * from core. */ template class ref_ptr_t { public: ref_ptr_t() { update_use_count(+1); this->data = &wf::get_core().get_data_safe>()->data; } ref_ptr_t(const ref_ptr_t& other) { this->data = other.data; update_use_count(+1); } ref_ptr_t& operator =(const ref_ptr_t& other) { this->data = other.data; update_use_count(+1); } ref_ptr_t(ref_ptr_t&& other) = default; ref_ptr_t& operator =(ref_ptr_t&& other) = default; ~ref_ptr_t() { update_use_count(-1); } T *get() { return data; } T*operator ->() { return data; } private: // Update the use count, and delete data if necessary. void update_use_count(int32_t delta) { auto instance = wf::get_core().get_data_safe>(); instance->use_count += delta; if (instance->use_count <= 0) { wf::get_core().erase_data>(); } } // Pointer to the global data T *data; }; } } wayfire-0.10.0/plugins/common/meson.build0000664000175000017500000000126615053502647020251 0ustar dkondordkondorplugins_common_inc = include_directories('.') install_subdir('wayfire', install_dir: get_option('includedir')) workspace_wall = static_library('wayfire-workspace-wall', ['workspace-wall.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc], dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], override_options: ['b_lundef=false'], install: true) move_drag_interface = static_library('wayfire-move-drag-interface', ['move-drag-interface.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, wobbly_inc], dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], override_options: ['b_lundef=false'], install: true) wayfire-0.10.0/plugins/common/workspace-wall.cpp0000664000175000017500000003673415053502647021556 0ustar dkondordkondor#include "wayfire/plugins/common/workspace-wall.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/workspace-stream.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/region.hpp" #include "wayfire/core.hpp" #include namespace wf { template using per_workspace_map_t = std::map>; class workspace_wall_t::workspace_wall_node_t : public scene::node_t { class wwall_render_instance_t : public scene::render_instance_t { std::shared_ptr self; per_workspace_map_t> instances; scene::damage_callback push_damage; wf::signal::connection_t on_wall_damage = [=] (scene::node_damage_signal *ev) { push_damage(ev->region); }; wf::geometry_t get_workspace_rect(wf::point_t ws) { auto output_size = self->wall->output->get_screen_size(); return { .x = ws.x * (output_size.width + self->wall->gap_size), .y = ws.y * (output_size.height + self->wall->gap_size), .width = output_size.width, .height = output_size.height, }; } public: wwall_render_instance_t(workspace_wall_node_t *self, scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; self->connect(&on_wall_damage); for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { auto push_damage_child = [=] (const wf::region_t& damage) { // Store the damage because we'll have to update the buffers self->aux_buffer_damage[i][j] |= damage; wf::region_t our_damage; for (auto& rect : damage) { wf::geometry_t box = wlr_box_from_pixman_box(rect); box = box + wf::origin(get_workspace_rect({i, j})); auto A = self->wall->viewport; auto B = self->get_bounding_box(); our_damage |= scale_box(A, B, box); } // Also damage the 'screen' after transforming damage push_damage(our_damage); }; self->workspaces[i][j]->gen_render_instances(instances[i][j], push_damage_child, self->wall->output); } } } static int damage_sum_area(const wf::region_t& damage) { int sum = 0; for (const auto& rect : damage) { sum += (rect.y2 - rect.y1) * (rect.x2 - rect.x1); } return sum; } bool consider_rescale_workspace_buffer(int i, int j, const wf::region_t& visible_damage) { // In general, when rendering the auxilliary buffers for each workspace, we can render the // workspace thumbnails in a lower resolution, because at the end they are shown scaled. // This helps with performance and uses less GPU power. // // However, the situation is tricky because during the Expo animation the optimal render // scale constantly changes. Thus, in some cases it is actually far from optimal to rescale // on every frame - it is often better to just keep the buffers from the old scale. // // Nonetheless, we need to make sure to rescale when this makes sense, and to avoid visual // artifacts. auto bbox = self->workspaces[i][j]->get_bounding_box(); float render_scale = std::max( 1.0 * bbox.width / self->wall->viewport.width, 1.0 * bbox.height / self->wall->viewport.height); render_scale = std::min(render_scale, 1.0f); const float current_scale = self->aux_buffer_current_scale[i][j]; // Avoid keeping a low resolution if we are going up in the scale (for example, expo exit // animation) and we're close to the 1.0 scale. Otherwise, we risk popping artifacts as we // suddenly switch from low to high resolution. const bool rescale_magnification = (render_scale > 0.5) && (render_scale > current_scale * 1.1); // In general, it is worth changing the buffer scale if we have a lot of damage to the old // buffer, so that for ex. a full re-scale is actually cheaper than repaiting the old buffer. // This could easily happen for example if we have a video player during Expo start animation. const int repaint_cost_current_scale = damage_sum_area(visible_damage) * (current_scale * current_scale); const int repaint_rescale_cost = (bbox.width * bbox.height) * (render_scale * render_scale); if ((repaint_cost_current_scale > repaint_rescale_cost) || rescale_magnification) { self->aux_buffer_current_scale[i][j] = render_scale; const auto full_size = self->aux_buffers[i][j].get_size(); const int scaled_width = std::clamp(std::ceil(render_scale * full_size.width), 1.0f, 1.0f * full_size.width); const int scaled_height = std::clamp(std::ceil(render_scale * full_size.height), 1.0f, 1.0f * full_size.height); self->aux_buffer_current_subbox[i][j] = wf::geometry_t{0, 0, scaled_width, scaled_height}; self->aux_buffer_damage[i][j] |= self->workspaces[i][j]->get_bounding_box(); return true; } return false; } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { // Update workspaces in a render pass for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { const auto ws_bbox = self->wall->get_workspace_rectangle({i, j}); const auto visible_box = geometry_intersection(self->wall->viewport, ws_bbox) - wf::origin(ws_bbox); wf::region_t visible_damage = self->aux_buffer_damage[i][j] & visible_box; if (consider_rescale_workspace_buffer(i, j, visible_damage)) { visible_damage |= visible_box; } if (!visible_damage.empty()) { wf::render_target_t aux{self->aux_buffers[i][j]}; aux.subbuffer = self->aux_buffer_current_subbox[i][j]; aux.geometry = self->workspaces[i][j]->get_bounding_box(); aux.scale = self->wall->output->handle->scale; render_pass_params_t params; params.instances = &instances[i][j]; params.damage = visible_damage; params.reference_output = self->wall->output; params.target = aux; params.flags = RPASS_EMIT_SIGNALS; wf::render_pass_t::run(params); self->aux_buffer_damage[i][j] ^= visible_damage; } } } // Render the wall instructions.push_back(scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); damage ^= self->get_bounding_box(); } void render(const wf::scene::render_instruction_t& data) override { data.pass->clear(data.damage, self->wall->background_color); auto damage = data.target.framebuffer_region_from_geometry_region(data.damage); for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { auto box = wf::geometry_to_fbox(get_workspace_rect({i, j})); auto A = wf::geometry_to_fbox(self->wall->viewport); auto B = wf::geometry_to_fbox(self->get_bounding_box()); auto render_geometry = wf::scale_fbox(A, B, box); auto& buffer = self->aux_buffers[i][j]; float dim = self->wall->get_color_for_workspace({i, j}); const auto& subbox = self->aux_buffer_current_subbox[i][j]; auto tex = wf::texture_t{buffer.get_texture()}; tex.filter_mode = WLR_SCALE_FILTER_BILINEAR; if (subbox.has_value()) { tex.source_box = { 1.0 * subbox->x, 1.0 * subbox->y, 1.0 * subbox->width, 1.0 * subbox->height}; } data.pass->add_texture(tex, data.target, render_geometry, data.damage); data.pass->add_rect({0, 0, 0, 1.0 - dim}, data.target, render_geometry, data.damage); } } self->wall->render_wall(data.target, data.damage); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { for (int i = 0; i < (int)self->workspaces.size(); i++) { for (int j = 0; j < (int)self->workspaces[i].size(); j++) { wf::region_t ws_region = self->workspaces[i][j]->get_bounding_box(); for (auto& ch : this->instances[i][j]) { ch->compute_visibility(output, ws_region); } } } } }; public: std::map, float> render_colors; workspace_wall_node_t(workspace_wall_t *wall) : node_t(false) { this->wall = wall; auto [w, h] = wall->output->wset()->get_workspace_grid_size(); workspaces.resize(w); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { auto node = std::make_shared( wall->output, wf::point_t{i, j}); workspaces[i].push_back(node); auto bbox = workspaces[i][j]->get_bounding_box(); aux_buffers[i][j].allocate(wf::dimensions(bbox), wall->output->handle->scale, wf::buffer_allocation_hints_t{ .needs_alpha = false, }); aux_buffer_damage[i][j] |= bbox; aux_buffer_current_scale[i][j] = 1.0; aux_buffer_current_subbox[i][j] = std::nullopt; } } } virtual void gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *shown_on) override { if (shown_on != this->wall->output) { return; } instances.push_back(std::make_unique( this, push_damage)); } std::string stringify() const override { return "workspace-wall " + stringify_flags(); } wf::geometry_t get_bounding_box() override { return wall->output->get_layout_geometry(); } private: workspace_wall_t *wall; std::vector>> workspaces; // Buffers keeping the contents of almost-static workspaces per_workspace_map_t aux_buffers; // Damage accumulated for those buffers per_workspace_map_t aux_buffer_damage; // Current rendering scale for the workspace per_workspace_map_t aux_buffer_current_scale; // Current subbox for the workspace per_workspace_map_t> aux_buffer_current_subbox; }; workspace_wall_t::workspace_wall_t(wf::output_t *_output) : output(_output) { this->viewport = get_wall_rectangle(); } workspace_wall_t::~workspace_wall_t() { stop_output_renderer(false); } void workspace_wall_t::set_background_color(const wf::color_t& color) { this->background_color = color; } void workspace_wall_t::set_gap_size(int size) { this->gap_size = size; } void workspace_wall_t::set_viewport(const wf::geometry_t& viewport_geometry) { this->viewport = viewport_geometry; if (render_node) { scene::damage_node( this->render_node, this->render_node->get_bounding_box()); } } wf::geometry_t workspace_wall_t::get_viewport() const { return viewport; } void workspace_wall_t::render_wall( const wf::render_target_t& fb, const wf::region_t& damage) { wall_frame_event_t data{fb}; this->emit(&data); } void workspace_wall_t::start_output_renderer() { wf::dassert(render_node == nullptr, "Starting workspace-wall twice?"); render_node = std::make_shared(this); scene::add_front(wf::get_core().scene(), render_node); } void workspace_wall_t::stop_output_renderer(bool reset_viewport) { if (!render_node) { return; } scene::remove_child(render_node); render_node = nullptr; if (reset_viewport) { set_viewport({0, 0, 0, 0}); } } wf::geometry_t workspace_wall_t::get_workspace_rectangle( const wf::point_t& ws) const { auto size = this->output->get_screen_size(); return {ws.x * (size.width + gap_size), ws.y * (size.height + gap_size), size.width, size.height}; } wf::geometry_t workspace_wall_t::get_wall_rectangle() const { auto size = this->output->get_screen_size(); auto workspace_size = this->output->wset()->get_workspace_grid_size(); return {-gap_size, -gap_size, workspace_size.width * (size.width + gap_size) + gap_size, workspace_size.height * (size.height + gap_size) + gap_size}; } void workspace_wall_t::set_ws_dim(const wf::point_t& ws, float value) { render_colors[{ws.x, ws.y}] = value; if (render_node) { scene::damage_node(render_node, render_node->get_bounding_box()); } } float workspace_wall_t::get_color_for_workspace(wf::point_t ws) { auto it = render_colors.find({ws.x, ws.y}); if (it == render_colors.end()) { return 1.0; } return it->second; } std::vector workspace_wall_t::get_visible_workspaces( wf::geometry_t viewport) const { std::vector visible; auto wsize = output->wset()->get_workspace_grid_size(); for (int i = 0; i < wsize.width; i++) { for (int j = 0; j < wsize.height; j++) { if (viewport & get_workspace_rectangle({i, j})) { visible.push_back({i, j}); } } } return visible; } } // namespace wf wayfire-0.10.0/plugins/single_plugins/0000775000175000017500000000000015053502647017634 5ustar dkondordkondorwayfire-0.10.0/plugins/single_plugins/fisheye.cpp0000664000175000017500000001467015053502647022004 0ustar dkondordkondor/* * The MIT License (MIT) * * Copyright (c) 2018 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include static const char *vertex_shader = R"( #version 100 attribute highp vec2 position; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); } )"; static const char *fragment_shader = R"( #version 100 precision highp float; uniform vec2 u_resolution; uniform vec2 u_mouse; uniform float u_radius; uniform float u_zoom; uniform sampler2D u_texture; const float PI = 3.1415926535; void main() { float radius = u_radius; float zoom = u_zoom; float pw = 1.0 / u_resolution.x; float ph = 1.0 / u_resolution.y; vec4 p0 = vec4(u_mouse.x, u_resolution.y - u_mouse.y, 1.0 / radius, 0.0); vec4 p1 = vec4(pw, ph, PI / radius, (zoom - 1.0) * zoom); vec4 p2 = vec4(0, 0, -PI / 2.0, 0.0); vec4 t0, t1, t2, t3; vec3 tc = vec3(1.0, 0.0, 0.0); vec2 uv = vec2(gl_FragCoord.x, gl_FragCoord.y); t1 = p0.xyww - vec4(uv, 0.0, 0.0); t2.x = t2.y = t2.z = t2.w = 1.0 / sqrt(dot(t1.xyz, t1.xyz)); t0 = t2 - p0; t3.x = t3.y = t3.z = t3.w = 1.0 / t2.x; t3 = t3 * p1.z + p2.z; t3.x = t3.y = t3.z = t3.w = cos(t3.x); t3 = t3 * p1.w; t1 = t2 * t1; t1 = t1 * t3 + vec4(uv, 0.0, 0.0); if (t0.z < 0.0) { t1.x = uv.x; t1.y = uv.y; } t1 = t1 * p1 + p2; tc = texture2D(u_texture, t1.xy).rgb; gl_FragColor = vec4(tc, 1.0); } )"; class wayfire_fisheye : public wf::per_output_plugin_instance_t { wf::animation::simple_animation_t progression{wf::create_option(300)}; float target_zoom; bool active, hook_set; wf::option_wrapper_t radius{"fisheye/radius"}; wf::option_wrapper_t zoom{"fisheye/zoom"}; OpenGL::program_t program; wf::plugin_activation_data_t grab_interface = { .name = "fisheye", .capabilities = 0, }; public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("fisheye: requires GLES2 support, but current renderer is ", render_type); return; } wf::gles::run_in_context_if_gles([&] { program.set_simple(OpenGL::compile_program(vertex_shader, fragment_shader)); }); hook_set = active = false; output->add_activator(wf::option_wrapper_t{"fisheye/toggle"}, &toggle_cb); target_zoom = zoom; zoom.set_callback([=] () { if (active) { this->progression.animate(zoom); } }); } wf::activator_callback toggle_cb = [=] (auto) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (active) { active = false; progression.animate(0); } else { active = true; progression.animate(zoom); if (!hook_set) { hook_set = true; output->render->add_post(&render_hook); output->render->set_redraw_always(); } } return true; }; wf::post_hook_t render_hook = [=] (wf::auxilliary_buffer_t& source, const wf::render_buffer_t& dest) { auto oc = output->get_cursor_position(); wlr_box box = {(int)oc.x, (int)oc.y, 1, 1}; box = output->render->get_target_framebuffer(). framebuffer_box_from_geometry_box(box); oc.x = box.x; oc.y = box.y; static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; wf::gles::run_in_context_if_gles([&] { wf::gles::bind_render_buffer(dest); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glBindTexture(GL_TEXTURE_2D, wf::gles_texture_t::from_aux(source).tex_id)); GL_CALL(glActiveTexture(GL_TEXTURE0)); program.uniform2f("u_mouse", oc.x, oc.y); program.uniform2f("u_resolution", dest.get_size().width, dest.get_size().height); program.uniform1f("u_radius", radius); program.uniform1f("u_zoom", progression); program.attrib_pointer("position", 2, 0, vertexData); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program.deactivate(); }); if (!active && !progression.running()) { finalize(); } }; void finalize() { output->render->rem_post(&render_hook); output->render->set_redraw_always(false); hook_set = false; } void fini() override { if (hook_set) { finalize(); } wf::gles::run_in_context_if_gles([&] { program.free_resources(); }); output->rem_binding(&toggle_cb); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/place.cpp0000664000175000017500000000723015053502647021426 0ustar dkondordkondor#include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include #include #include #include #include #include class wayfire_place_window : public wf::per_output_plugin_instance_t { wf::signal::connection_t on_view_mapped = [=] (wf::view_mapped_signal *ev) { auto toplevel = wf::toplevel_cast(ev->view); if (!toplevel || toplevel->parent || toplevel->pending_fullscreen() || toplevel->pending_tiled_edges() || ev->is_positioned) { return; } ev->is_positioned = true; auto workarea = output->workarea->get_workarea(); std::string mode = placement_mode; if (mode == "cascade") { cascade(toplevel, workarea); } else if (mode == "maximize") { maximize(toplevel, workarea); } else if (mode == "random") { random(toplevel, workarea); } else { center(toplevel, workarea); } }; wf::signal::connection_t workarea_changed_cb = [=] (auto) { auto workarea = output->workarea->get_workarea(); if ((cascade_x < workarea.x) || (cascade_x > workarea.x + workarea.width)) { cascade_x = workarea.x; } if ((cascade_y < workarea.y) || (cascade_y > workarea.y + workarea.height)) { cascade_y = workarea.y; } }; wf::option_wrapper_t placement_mode{"place/mode"}; int cascade_x, cascade_y; public: void init() override { auto workarea = output->workarea->get_workarea(); cascade_x = workarea.x; cascade_y = workarea.y; output->connect(&workarea_changed_cb); output->connect(&on_view_mapped); } void cascade(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); if ((cascade_x + window.width > workarea.x + workarea.width) || (cascade_y + window.height > workarea.y + workarea.height)) { cascade_x = workarea.x; cascade_y = workarea.y; } view->move(cascade_x, cascade_y); cascade_x += workarea.width * .03; cascade_y += workarea.height * .03; } void random(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); wf::geometry_t area; int pos_x, pos_y; area.x = workarea.x; area.y = workarea.y; area.width = workarea.width - window.width; area.height = workarea.height - window.height; if ((area.width <= 0) || (area.height <= 0)) { center(view, workarea); return; } pos_x = rand() % area.width + area.x; pos_y = rand() % area.height + area.y; view->move(pos_x, pos_y); } void center(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::geometry_t window = view->get_pending_geometry(); window.x = workarea.x + (workarea.width / 2) - (window.width / 2); window.y = workarea.y + (workarea.height / 2) - (window.height / 2); view->move(window.x, window.y); } void maximize(wayfire_toplevel_view & view, wf::geometry_t workarea) { wf::get_core().default_wm->tile_request(view, wf::TILED_EDGES_ALL); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/autostart.cpp0000664000175000017500000000324115053502647022366 0ustar dkondordkondor#include #include #include #include #include #include class wayfire_autostart : public wf::plugin_interface_t { wf::option_wrapper_t autostart_wf_shell{"autostart/autostart_wf_shell"}; wf::option_wrapper_t> autostart_entries{"autostart/autostart"}; public: void init() override { /* Run only once, at startup */ auto section = wf::get_core().config->get_section("autostart"); bool panel_manually_started = false; bool background_manually_started = false; for (const auto& [name, command] : autostart_entries.value()) { // Because we accept any option names, we should ignore regular // options if (name == "autostart_wf_shell") { continue; } wf::get_core().run(command); if (command.find("wf-panel") != std::string::npos) { panel_manually_started = true; } if (command.find("wf-background") != std::string::npos) { background_manually_started = true; } } if (autostart_wf_shell && !panel_manually_started) { wf::get_core().run("wf-panel"); } if (autostart_wf_shell && !background_manually_started) { wf::get_core().run("wf-background"); } } bool is_unloadable() override { return false; } }; DECLARE_WAYFIRE_PLUGIN(wayfire_autostart); wayfire-0.10.0/plugins/single_plugins/zoom.cpp0000664000175000017500000000724315053502647021332 0ustar dkondordkondor#include #include #include #include #include class wayfire_zoom_screen : public wf::per_output_plugin_instance_t { enum class interpolation_method_t { LINEAR = 0, NEAREST = 1, }; wf::option_wrapper_t modifier{"zoom/modifier"}; wf::option_wrapper_t speed{"zoom/speed"}; wf::option_wrapper_t smoothing_duration{"zoom/smoothing_duration"}; wf::option_wrapper_t interpolation_method{"zoom/interpolation_method"}; wf::animation::simple_animation_t progression{smoothing_duration}; bool hook_set = false; wf::plugin_activation_data_t grab_interface = { .name = "zoom", .capabilities = 0, }; public: void init() override { progression.set(1, 1); output->add_axis(modifier, &axis); } void update_zoom_target(float delta) { float target = progression.end; target -= target * delta * speed; target = wf::clamp(target, 1.0f, 50.0f); if (target != progression.end) { progression.animate(target); if (!hook_set) { hook_set = true; output->render->add_post(&render_hook); output->render->set_redraw_always(); } } } wf::axis_callback axis = [=] (wlr_pointer_axis_event *ev) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (ev->orientation != WL_POINTER_AXIS_VERTICAL_SCROLL) { return false; } update_zoom_target(ev->delta); return true; }; wf::post_hook_t render_hook = [=] (wf::auxilliary_buffer_t& source, const wf::render_buffer_t& destination) { auto w = destination.get_size().width; auto h = destination.get_size().height; auto oc = output->get_cursor_position(); double x, y; wlr_box b = output->get_relative_geometry(); wlr_box_closest_point(&b, oc.x, oc.y, &x, &y); /* get rotation & scale */ wlr_box box = {int(x), int(y), 1, 1}; box = output->render->get_target_framebuffer().framebuffer_box_from_geometry_box(box); x = box.x; y = box.y; // Store progression once to avoid its value changing in subsequent calls, could be very tricky due to // timing. And if we use slightly different progressions, we can get an invalid rect. const float factor = (float)progression; const float scale = (factor - 1) / factor; const float x1 = x * scale; const float y1 = y * scale; const float tw = std::clamp(w / factor, 0.0f, w - x1); const float th = std::clamp(h / factor, 0.0f, h - y1); auto filter_mode = (interpolation_method == (int)interpolation_method_t::NEAREST) ? WLR_SCALE_FILTER_NEAREST : WLR_SCALE_FILTER_BILINEAR; destination.blit(source, {x1, y1, tw, th}, {0, 0, w, h}, filter_mode); if (!progression.running() && (progression - 1 <= 0.01)) { unset_hook(); } }; void unset_hook() { output->render->set_redraw_always(false); output->render->rem_post(&render_hook); hook_set = false; } void fini() override { if (hook_set) { output->render->rem_post(&render_hook); } output->rem_binding(&axis); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/preserve-output.cpp0000664000175000017500000001015115053502647023527 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/plugin.hpp" #include #include #include #include #include #include #include namespace wf { namespace preserve_output { static std::string make_output_identifier(wf::output_t *output) { std::string identifier = ""; identifier += nonull(output->handle->make); identifier += "|"; identifier += nonull(output->handle->model); identifier += "|"; identifier += nonull(output->handle->serial); return identifier; } struct per_output_state_t { std::shared_ptr workspace_set; std::chrono::time_point destroy_timestamp; bool was_focused = false; }; class preserve_output_t : public wf::plugin_interface_t { wf::option_wrapper_t last_output_focus_timeout{"preserve-output/last_output_focus_timeout"}; std::map saved_outputs; bool focused_output_expired(const per_output_state_t& state) const { using namespace std::chrono; const auto now = steady_clock::now(); const auto elapsed_since_focus = duration_cast(now - state.destroy_timestamp).count(); return elapsed_since_focus > last_output_focus_timeout; } void save_output(wf::output_t *output) { auto ident = make_output_identifier(output); auto& data = saved_outputs[ident]; data.was_focused = (output == wf::get_core().seat->get_active_output()); data.destroy_timestamp = std::chrono::steady_clock::now(); data.workspace_set = output->wset(); LOGD("Saving workspace set ", data.workspace_set->get_index(), " from output ", output->to_string(), " with identifier ", ident); // Set a dummy workspace set with no views at all. output->set_workspace_set(wf::workspace_set_t::create()); // Detach workspace set from its old output data.workspace_set->attach_to_output(nullptr); } void try_restore_output(wf::output_t *output) { std::string ident = make_output_identifier(output); if (!saved_outputs.count(ident)) { LOGD("No saved identifier for ", output->to_string()); return; } auto& data = saved_outputs[ident]; auto new_output = data.workspace_set->get_attached_output(); if (new_output && (new_output->wset() == data.workspace_set)) { // The wset was moved to a different output => We should leave it where it is LOGD("Saved workspace for ", output->to_string(), " has been remapped to another output."); return; } LOGD("Restoring workspace set ", data.workspace_set->get_index(), " to output ", output->to_string()); output->set_workspace_set(data.workspace_set); if (data.was_focused && !focused_output_expired(data)) { wf::get_core().seat->focus_output(output); } saved_outputs.erase(ident); } wf::signal::connection_t output_pre_remove = [=] (output_pre_remove_signal *ev) { if (wlr_output_is_headless(ev->output->handle)) { // For example, NOOP-1 return; } if (wf::get_core().get_current_state() == compositor_state_t::RUNNING) { LOGD("Received pre-remove event: ", ev->output->to_string()); save_output(ev->output); } }; wf::signal::connection_t on_new_output = [=] (output_added_signal *ev) { if (wlr_output_is_headless(ev->output->handle)) { // For example, NOOP-1 return; } try_restore_output(ev->output); }; public: void init() override { wf::get_core().output_layout->connect(&on_new_output); wf::get_core().output_layout->connect(&output_pre_remove); } }; } } DECLARE_WAYFIRE_PLUGIN(wf::preserve_output::preserve_output_t); wayfire-0.10.0/plugins/single_plugins/resize.cpp0000664000175000017500000003355615053502647021655 0ustar dkondordkondor#include "wayfire/geometry.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/txn/transaction-manager.hpp" #include #include #include #include #include #include #include #include #include #include #include #include class wayfire_resize : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { wf::signal::connection_t on_resize_request = [=] (wf::view_resize_request_signal *request) { if (!request->view) { return; } auto touch = wf::get_core().get_touch_position(0); if (!std::isnan(touch.x) && !std::isnan(touch.y)) { is_using_touch = true; } else { is_using_touch = false; } was_client_request = true; preserve_aspect = false; initiate(request->view, request->edges); }; wf::signal::connection_t on_view_disappeared = [=] (wf::view_disappeared_signal *ev) { if (ev->view == view) { view = nullptr; input_pressed(WLR_BUTTON_RELEASED); } }; wf::button_callback activate_binding; wf::button_callback activate_binding_preserve_aspect; wayfire_toplevel_view view; bool was_client_request, is_using_touch; bool preserve_aspect = false; wf::point_t grab_start; wf::geometry_t grabbed_geometry; uint32_t edges; wf::option_wrapper_t user_min_width{"resize/min_width"}; wf::option_wrapper_t user_min_height{"resize/min_height"}; wf::option_wrapper_t button{"resize/activate"}; wf::option_wrapper_t button_preserve_aspect{ "resize/activate_preserve_aspect"}; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "resize", .capabilities = wf::CAPABILITY_GRAB_INPUT | wf::CAPABILITY_MANAGE_DESKTOP, }; public: void init() override { input_grab = std::make_unique("resize", output, nullptr, this, this); activate_binding = [=] (auto) { return activate(false); }; activate_binding_preserve_aspect = [=] (auto) { return activate(true); }; output->add_button(button, &activate_binding); output->add_button(button_preserve_aspect, &activate_binding_preserve_aspect); grab_interface.cancel = [=] () { input_pressed(WLR_BUTTON_RELEASED); }; output->connect(&on_resize_request); output->connect(&on_view_disappeared); } bool activate(bool should_preserve_aspect) { auto view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (view) { is_using_touch = false; was_client_request = false; preserve_aspect = should_preserve_aspect; initiate(view); } return false; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if ((event.state == WL_POINTER_BUTTON_STATE_RELEASED) && was_client_request && (event.button == BTN_LEFT)) { return input_pressed(event.state); } if ((event.button != wf::buttonbinding_t(button).get_button()) && (event.button != wf::buttonbinding_t(button_preserve_aspect).get_button())) { return; } input_pressed(event.state); } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { input_motion(); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id == 0) { input_pressed(WLR_BUTTON_RELEASED); } } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id == 0) { input_motion(); } } /* Returns the currently used input coordinates in global compositor space */ wf::point_t get_global_input_coords() { wf::pointf_t input; if (is_using_touch) { input = wf::get_core().get_touch_position(0); } else { input = wf::get_core().get_cursor_position(); } return {(int)input.x, (int)input.y}; } /* Returns the currently used input coordinates in output-local space */ wf::point_t get_input_coords() { auto og = output->get_layout_geometry(); return get_global_input_coords() - wf::point_t{og.x, og.y}; } /* Calculate resize edges, grab starts at (sx, sy), view's geometry is vg */ uint32_t calculate_edges(wf::geometry_t vg, int sx, int sy) { int view_x = sx - vg.x; int view_y = sy - vg.y; uint32_t edges = 0; if (view_x < vg.width / 2) { edges |= WLR_EDGE_LEFT; } else { edges |= WLR_EDGE_RIGHT; } if (view_y < vg.height / 2) { edges |= WLR_EDGE_TOP; } else { edges |= WLR_EDGE_BOTTOM; } return edges; } bool initiate(wayfire_toplevel_view view, uint32_t forced_edges = 0) { if (!view || (view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) || !view->is_mapped() || view->pending_fullscreen()) { return false; } this->edges = forced_edges ?: calculate_edges(view->get_bounding_box(), get_input_coords().x, get_input_coords().y); if ((edges == 0) || !(view->get_allowed_actions() & wf::VIEW_ALLOW_RESIZE)) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } input_grab->set_wants_raw_input(true); input_grab->grab_input(wf::scene::layer::OVERLAY); grab_start = get_input_coords(); grabbed_geometry = view->get_geometry(); if (view->pending_tiled_edges()) { view->toplevel()->pending().tiled_edges = 0; } this->view = view; auto og = view->get_bounding_box(); int anchor_x = og.x; int anchor_y = og.y; if (edges & WLR_EDGE_LEFT) { anchor_x += og.width; } if (edges & WLR_EDGE_TOP) { anchor_y += og.height; } start_wobbly(view, anchor_x, anchor_y); wf::get_core().set_cursor(wlr_xcursor_get_resize_name((wlr_edges)edges)); return true; } void input_pressed(uint32_t state) { if (state != WLR_BUTTON_RELEASED) { return; } input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); if (view) { end_wobbly(view); wf::view_change_workspace_signal workspace_may_changed; workspace_may_changed.view = this->view; workspace_may_changed.to = output->wset()->get_current_workspace(); workspace_may_changed.old_workspace_valid = false; output->emit(&workspace_may_changed); } } // Convert resize edges to gravity uint32_t calculate_gravity() { uint32_t gravity = 0; if (edges & WLR_EDGE_LEFT) { gravity |= WLR_EDGE_RIGHT; } if (edges & WLR_EDGE_RIGHT) { gravity |= WLR_EDGE_LEFT; } if (edges & WLR_EDGE_TOP) { gravity |= WLR_EDGE_BOTTOM; } if (edges & WLR_EDGE_BOTTOM) { gravity |= WLR_EDGE_TOP; } return gravity; } wf::dimensions_t calculate_min_size() { // Min size, if not set to something larger, is 1x1 + decoration size wf::dimensions_t min_size = view->toplevel()->get_min_size(); min_size.width = std::max(1, min_size.width); min_size.height = std::max(1, min_size.height); min_size = wf::expand_dimensions_by_margins(min_size, view->toplevel()->pending().margins); min_size.width = std::max(min_size.width, static_cast(user_min_width)); min_size.height = std::max(min_size.height, static_cast(user_min_height)); return min_size; } wf::dimensions_t calculate_max_size(wf::dimensions_t min) { // Max size is whatever is set by the client, if not set, then it is MAX_INT wf::dimensions_t max_size = view->toplevel()->get_max_size(); int64_t width, height; if (max_size.width > 0) { width = static_cast(max_size.width) + view->toplevel()->pending().margins.left + view->toplevel()->pending().margins.right; } else { width = std::numeric_limits::max(); } if (max_size.height > 0) { height = static_cast(max_size.height) + view->toplevel()->pending().margins.top + view->toplevel()->pending().margins.bottom; } else { height = std::numeric_limits::max(); } // Sanitize values in case desired.width/height gets negative for example. max_size.width = static_cast(std::clamp(width, static_cast(min.width), static_cast(std::numeric_limits::max()))); max_size.height = static_cast(std::clamp(height, static_cast(min.height), static_cast(std::numeric_limits::max()))); return max_size; } bool does_shrink(int dx, int dy) { if (std::abs(dx) > std::abs(dy)) { return dx < 0; } else { return dy < 0; } } void input_motion() { auto input = get_input_coords(); int dx = input.x - grab_start.x; int dy = input.y - grab_start.y; wf::geometry_t desired = grabbed_geometry; double ratio = 1.0; if (preserve_aspect) { ratio = (double)desired.width / desired.height; } if (edges & WLR_EDGE_LEFT) { desired.x += dx; desired.width -= dx; } else if (edges & WLR_EDGE_RIGHT) { desired.width += dx; } if (edges & WLR_EDGE_TOP) { desired.y += dy; desired.height -= dy; } else if (edges & WLR_EDGE_BOTTOM) { desired.height += dy; } auto min_size = calculate_min_size(); auto max_size = calculate_max_size(min_size); auto desired_unconstrained = desired; if (preserve_aspect) { float new_ratio = 1.0 * desired.width / desired.height; if ((new_ratio < ratio) ^ does_shrink(desired.width - grabbed_geometry.width, desired.height - grabbed_geometry.height)) { // If we do not shrink: the window is taller than it should be, so expand width first. // If we do shrink: the window is wider than it should be, so shrink width first. desired.width = std::clamp(int(desired.height * ratio), min_size.width, max_size.width); desired.height = std::clamp(int(desired.width / ratio), min_size.height, max_size.height); // Clamp once more to ensure we fit the min/max sizes. desired.width = std::clamp(int(desired.height * ratio), min_size.width, max_size.width); } else { // The window is wider than it should be, so expand height first. desired.height = std::clamp(int(desired.width / ratio), min_size.height, max_size.height); desired.width = std::clamp(int(desired.height * ratio), min_size.width, max_size.width); // Clamp once more to ensure we fit the min/max sizes. desired.height = std::clamp(int(desired.width / ratio), min_size.height, max_size.height); } } else { desired.width = std::clamp(desired.width, min_size.width, max_size.width); desired.height = std::clamp(desired.height, min_size.height, max_size.height); } // If we had to change the size due to ratio/min/max constraints, make sure to keep the gravity // correct. if (edges & WLR_EDGE_LEFT) { desired.x += desired_unconstrained.width - desired.width; } if (edges & WLR_EDGE_TOP) { desired.y += desired_unconstrained.height - desired.height; } if (wf::dimensions(view->toplevel()->pending().geometry) != wf::dimensions(desired)) { view->toplevel()->pending().gravity = calculate_gravity(); view->toplevel()->pending().geometry = desired; wf::get_core().tx_manager->schedule_object(view->toplevel()); } } void fini() override { if (input_grab->is_grabbed()) { input_pressed(WLR_BUTTON_RELEASED); } output->rem_binding(&activate_binding); output->rem_binding(&activate_binding_preserve_aspect); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/extra-gestures.cpp0000664000175000017500000000721415053502647023326 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include namespace wf { using namespace touch; class extra_gestures_plugin_t : public per_output_plugin_instance_t { gesture_t touch_and_hold_move; gesture_t tap_to_close; wf::option_wrapper_t move_fingers{"extra-gestures/move_fingers"}; wf::option_wrapper_t move_delay{"extra-gestures/move_delay"}; wf::option_wrapper_t close_fingers{"extra-gestures/close_fingers"}; wf::plugin_activation_data_t grab_interface = { .capabilities = CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { build_touch_and_hold_move(); move_fingers.set_callback([=] () { build_touch_and_hold_move(); }); move_delay.set_callback([=] () { build_touch_and_hold_move(); }); build_tap_to_close(); close_fingers.set_callback([=] () { build_tap_to_close(); }); } /** * Run an action on the view under the touch points, if the touch points * are on the current output and the view is toplevel. */ void execute_view_action(std::function action) { auto& core = wf::get_core(); auto state = core.get_touch_state(); auto center_touch_point = state.get_center().current; wf::pointf_t center = {center_touch_point.x, center_touch_point.y}; if (core.output_layout->get_output_at(center.x, center.y) != this->output) { return; } /** Make sure we don't interfere with already activated plugins */ if (!output->can_activate_plugin(&this->grab_interface)) { return; } auto view = core.get_view_at({center.x, center.y}); if (view && (view->role == VIEW_ROLE_TOPLEVEL)) { action(view); } } void build_touch_and_hold_move() { wf::get_core().rem_touch_gesture(&touch_and_hold_move); touch_and_hold_move = wf::touch::gesture_builder_t() .action(touch_action_t(move_fingers, true) .set_move_tolerance(50) .set_duration(100)) .action(hold_action_t(move_delay) .set_move_tolerance(100)) .on_completed([=] () { execute_view_action([] (wayfire_view view) { if (auto toplevel = wf::toplevel_cast(view)) { wf::get_core().default_wm->move_request(toplevel); } }); }).build(); wf::get_core().add_touch_gesture(&touch_and_hold_move); } void build_tap_to_close() { wf::get_core().rem_touch_gesture(&tap_to_close); tap_to_close = wf::touch::gesture_builder_t() .action(touch_action_t(close_fingers, true) .set_move_tolerance(50) .set_duration(150)) .action(touch_action_t(close_fingers, false) .set_move_tolerance(50) .set_duration(150)) .on_completed([=] () { execute_view_action([] (wayfire_view view) { view->close(); }); }) .build(); wf::get_core().add_touch_gesture(&tap_to_close); } void fini() override { wf::get_core().rem_touch_gesture(&touch_and_hold_move); wf::get_core().rem_touch_gesture(&tap_to_close); } }; } DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/oswitch.cpp0000664000175000017500000002024515053502647022023 0ustar dkondordkondor#include "wayfire/plugin.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/util.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include class wayfire_oswitch : public wf::plugin_interface_t { wf::wl_idle_call idle_switch_output; wf::output_t *get_left_output() { return get_output_in_direction(-1, 0); } wf::output_t *get_right_output() { return get_output_in_direction(1, 0); } wf::output_t *get_above_output() { return get_output_in_direction(0, -1); } wf::output_t *get_below_output() { return get_output_in_direction(0, 1); } wf::output_t *get_output_relative(int step) { /* get the target output n steps after current output * if current output's index is i, and if there're n monitors * then return the (i + step) mod n th monitor */ auto current_output = wf::get_core().seat->get_active_output(); auto os = wf::get_core().output_layout->get_outputs(); auto it = std::find(os.begin(), os.end(), current_output); if (it == os.end()) { LOGI("Current output not found in output list"); return current_output; } int size = os.size(); int current_index = it - os.begin(); int target_index = ((current_index + step) % size + size) % size; return os[target_index]; } wf::output_t *get_output_in_direction(int dir_x, int dir_y) { auto current_output = wf::get_core().seat->get_active_output(); if (!current_output) { return nullptr; } auto current_geo = current_output->get_layout_geometry(); wf::point_t current_center = { current_geo.x + current_geo.width / 2, current_geo.y + current_geo.height / 2 }; wf::output_t *best_output = nullptr; double best_score = -INFINITY; const int MIN_OVERLAP = 20; for (auto& output : wf::get_core().output_layout->get_outputs()) { if (output == current_output) { continue; } auto geo = output->get_layout_geometry(); wf::point_t center = { geo.x + geo.width / 2, geo.y + geo.height / 2 }; double dx = center.x - current_center.x; double dy = center.y - current_center.y; if (((dir_x != 0) && (dx * dir_x <= 0)) || ((dir_y != 0) && (dy * dir_y <= 0))) { continue; } double ortho_overlap = 1.0; if (dir_x != 0) { int current_top = current_geo.y; int current_bottom = current_geo.y + current_geo.height; int other_top = geo.y; int other_bottom = geo.y + geo.height; int overlap = std::min(current_bottom, other_bottom) - std::max(current_top, other_top); ortho_overlap = (double)overlap / current_geo.height; } else if (dir_y != 0) { int current_left = current_geo.x; int current_right = current_geo.x + current_geo.width; int other_left = geo.x; int other_right = geo.x + geo.width; int overlap = std::min(current_right, other_right) - std::max(current_left, other_left); ortho_overlap = (double)overlap / current_geo.width; } if (ortho_overlap * 100 < MIN_OVERLAP) { continue; } double distance = sqrt(dx * dx + dy * dy); double score = ortho_overlap / distance; if (score > best_score) { best_output = output; best_score = score; } } return best_output ? best_output : current_output; } void switch_to_output(wf::output_t *target_output) { if (!target_output) { LOGI("No output found in requested direction. Cannot switch."); return; } /* when we switch the output, the oswitch keybinding * may be activated for the next output, which we don't want, * so we postpone the switch */ idle_switch_output.run_once([=] () { wf::get_core().seat->focus_output(target_output); target_output->ensure_pointer(true); }); } void switch_to_output_with_window(wf::output_t *target_output) { auto current_output = wf::get_core().seat->get_active_output(); auto view = wf::find_topmost_parent(wf::toplevel_cast(wf::get_active_view_for_output(current_output))); if (view) { move_view_to_output(view, target_output, true); } switch_to_output(target_output); } wf::activator_callback next_output = [=] (auto) { auto target_output = get_output_relative(1); switch_to_output(target_output); return true; }; wf::activator_callback next_output_with_window = [=] (auto) { auto target_output = get_output_relative(1); switch_to_output_with_window(target_output); return true; }; wf::activator_callback prev_output = [=] (auto) { auto target_output = get_output_relative(-1); switch_to_output(target_output); return true; }; wf::activator_callback prev_output_with_window = [=] (auto) { auto target_output = get_output_relative(-1); switch_to_output_with_window(target_output); return true; }; wf::activator_callback switch_left = [=] (auto) { auto target_output = get_left_output(); switch_to_output(target_output); return true; }; wf::activator_callback switch_right = [=] (auto) { auto target_output = get_right_output(); switch_to_output(target_output); return true; }; wf::activator_callback switch_up = [=] (auto) { auto target_output = get_above_output(); switch_to_output(target_output); return true; }; wf::activator_callback switch_down = [=] (auto) { auto target_output = get_below_output(); switch_to_output(target_output); return true; }; public: void init() { auto& bindings = wf::get_core().bindings; bindings->add_activator(wf::option_wrapper_t{"oswitch/next_output"}, &next_output); bindings->add_activator(wf::option_wrapper_t{"oswitch/next_output_with_win"}, &next_output_with_window); bindings->add_activator(wf::option_wrapper_t{"oswitch/prev_output"}, &prev_output); bindings->add_activator(wf::option_wrapper_t{"oswitch/prev_output_with_win"}, &prev_output_with_window); bindings->add_activator(wf::option_wrapper_t{"oswitch/left_output"}, &switch_left); bindings->add_activator(wf::option_wrapper_t{"oswitch/right_output"}, &switch_right); bindings->add_activator(wf::option_wrapper_t{"oswitch/above_output"}, &switch_up); bindings->add_activator(wf::option_wrapper_t{"oswitch/below_output"}, &switch_down); } void fini() { auto& bindings = wf::get_core().bindings; bindings->rem_binding(&next_output); bindings->rem_binding(&next_output_with_window); bindings->rem_binding(&prev_output); bindings->rem_binding(&prev_output_with_window); bindings->rem_binding(&switch_left); bindings->rem_binding(&switch_right); bindings->rem_binding(&switch_up); bindings->rem_binding(&switch_down); idle_switch_output.disconnect(); } }; DECLARE_WAYFIRE_PLUGIN(wayfire_oswitch); wayfire-0.10.0/plugins/single_plugins/meson.build0000664000175000017500000000167515053502647022007 0ustar dkondordkondorplugins = [ 'move', 'resize', 'command', 'autostart', 'vswipe', 'wrot', 'expo', 'switcher', 'fast-switcher', 'oswitch', 'place', 'invert', 'fisheye', 'zoom', 'alpha', 'idle', 'extra-gestures', 'preserve-output', 'wsets', 'xkb-bindings', ] all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, wobbly_inc, grid_inc, ipc_include_dirs] if wlroots_features['vulkan_renderer'] plugins += 'vk-color-management' endif all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json, plugin_pch_dep] extra_libs = { 'move': [move_drag_interface], 'expo': [workspace_wall, move_drag_interface], 'vswipe': [workspace_wall], } foreach plugin : plugins shared_module(plugin, plugin + '.cpp', include_directories: all_include_dirs, dependencies: all_deps, link_with: extra_libs.get(plugin, []), install: true, install_dir: conf_data.get('PLUGIN_PATH')) endforeach wayfire-0.10.0/plugins/single_plugins/move.cpp0000664000175000017500000004527615053502647021324 0ustar dkondordkondor#include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class wayfire_move : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { wf::button_callback activate_binding; wf::option_wrapper_t enable_snap{"move/enable_snap"}; wf::option_wrapper_t join_views{"move/join_views"}; wf::option_wrapper_t snap_threshold{"move/snap_threshold"}; wf::option_wrapper_t quarter_snap_threshold{"move/quarter_snap_threshold"}; wf::option_wrapper_t workspace_switch_after{"move/workspace_switch_after"}; wf::option_wrapper_t activate_button{"move/activate"}; wf::option_wrapper_t move_enable_snap_off{"move/enable_snap_off"}; wf::option_wrapper_t move_snap_off_threshold{"move/snap_off_threshold"}; struct { std::shared_ptr preview; wf::grid::slot_t slot_id = wf::grid::SLOT_NONE; } slot; wf::wl_timer workspace_switch_timer; wf::shared_data::ref_ptr_t drag_helper; bool can_handle_drag(wayfire_toplevel_view view) { bool yes = output->can_activate_plugin(&grab_interface, wf::PLUGIN_ACTIVATE_ALLOW_MULTIPLE); yes &= view && (view->get_allowed_actions() & wf::VIEW_ALLOW_MOVE); return yes; } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag(drag_helper->view)) { drag_helper->set_scale(1.0); if (!output->is_plugin_active(grab_interface.name)) { grab_input(drag_helper->view); } else { input_grab->regrab_input(); update_slot(calc_slot(get_input_coords())); } } else { update_slot(wf::grid::SLOT_NONE); } }; wf::signal::connection_t on_drag_snap_off = [=] (wf::move_drag::snap_off_signal *ev) { if ((ev->focus_output == output) && can_handle_drag(drag_helper->view)) { wf::move_drag::adjust_view_on_snap_off(drag_helper->view); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag(ev->main_view) && !drag_helper->is_view_held_in_place()) { // Mark the last windowed geometry (which is the geometry before the view was grabbed: grabs work // not by moving the view, but by translating it with a transformer. Therefore, the view geometry // is still the geometry before the drag. wf::get_core().default_wm->update_last_windowed_geometry(ev->main_view); // Artificially continue the grab for a little bit more, so that the last windowed geometry does // not get overwritten. wf::get_core().default_wm->set_view_grabbed(ev->main_view, true); wf::move_drag::adjust_view_on_output(ev); if (enable_snap && (slot.slot_id != wf::grid::SLOT_NONE)) { wf::get_core().default_wm->tile_request(ev->main_view, wf::grid::get_tiled_edges_for_slot(slot.slot_id)); /* Update slot, will hide the preview as well */ update_slot(wf::grid::SLOT_NONE); } wf::get_core().default_wm->set_view_grabbed(ev->main_view, false); wf::view_change_workspace_signal data; data.view = ev->main_view; data.to = output->wset()->get_current_workspace(); data.old_workspace_valid = false; output->emit(&data); } deactivate(); }; // We listen for raw pointer button events independently of the active/inactive state of move. // We need this to determine the grab point for client-initiated move (i.e. when the user clicks and drags // the titlebar). Usually, there is a bit of delay in the signal, for example, GTK tells the compositor to // start interactive move after the pointer has moved ~5 pixels (but it can be much worse for programmed // tests). So, here we store the mouse position for every button press, and use that in client-initiated // move. // // We do the same for touch events. wf::point_t last_input_press_position = {0, 0}; wf::signal::connection_t> on_raw_pointer_button = [=] (wf::input_event_signal *ev) { if (ev->event->state == WL_POINTER_BUTTON_STATE_PRESSED) { last_input_press_position = get_global_input_coords(); } }; wf::signal::connection_t> on_raw_touch_down = [=] (wf::post_input_event_signal *ev) { if (ev->event->touch_id == 0) { last_input_press_position = get_global_input_coords(); } }; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "move", .capabilities = wf::CAPABILITY_GRAB_INPUT | wf::CAPABILITY_MANAGE_DESKTOP, }; public: void init() override { wf::get_core().connect(&on_raw_pointer_button); wf::get_core().connect(&on_raw_touch_down); input_grab = std::make_unique("move", output, nullptr, this, this); input_grab->set_wants_raw_input(true); activate_binding = [=] (auto) { auto view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (view && (view->role != wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) && !drag_helper->view) { initiate(view, get_global_input_coords()); } // Even if we initiated, we want the button press to go to the grab node return false; }; output->add_button(activate_button, &activate_binding); using namespace std::placeholders; grab_interface.cancel = [=] () { input_pressed(WLR_BUTTON_RELEASED); }; output->connect(&move_request); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_snap_off); drag_helper->connect(&on_drag_done); } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state != WL_POINTER_BUTTON_STATE_RELEASED) { return; } drag_helper->handle_input_released(); return; } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { handle_input_motion(); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (wf::get_core().get_touch_state().fingers.empty()) { input_pressed(WLR_BUTTON_RELEASED); } } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { handle_input_motion(); } wf::signal::connection_t move_request = [=] (wf::view_move_request_signal *ev) { if (!drag_helper->view) { initiate(ev->view, last_input_press_position); } }; /** * Calculate plugin activation flags for the view. * * Activation flags ignore input inhibitors if the view is in the desktop * widget layer (i.e OSKs) */ uint32_t get_act_flags(wayfire_toplevel_view view) { auto view_layer = wf::get_view_layer(view).value_or(wf::scene::layer::WORKSPACE); /* Allow moving an on-screen keyboard while screen is locked */ bool ignore_inhibit = view_layer == wf::scene::layer::DWIDGET; uint32_t act_flags = 0; if (ignore_inhibit) { act_flags |= wf::PLUGIN_ACTIVATION_IGNORE_INHIBIT; } return act_flags; } /** * Calculate the view which is the actual target of this move operation. * * Usually, this is the view itself or its topmost parent if the join_views * option is set. */ wayfire_toplevel_view get_target_view(wayfire_toplevel_view view) { if (join_views) { return wf::find_topmost_parent(view); } else { return view; } } bool can_move_view(wayfire_toplevel_view view) { if (!view || !view->is_mapped() || !(view->get_allowed_actions() & wf::VIEW_ALLOW_MOVE)) { return false; } view = get_target_view(view); return output->can_activate_plugin(&grab_interface, get_act_flags(view)); } bool grab_input(wayfire_toplevel_view view) { view = view ?: drag_helper->view; if (!view) { return false; } if (!output->activate_plugin(&grab_interface, get_act_flags(view))) { return false; } this->input_grab->grab_input(wf::scene::layer::OVERLAY); update_slot(wf::grid::SLOT_NONE); return true; } bool initiate(wayfire_toplevel_view view, wf::point_t grab_position) { // First, make sure that the view is on the output the input is. auto target_output = wf::get_core().output_layout->get_output_at(grab_position.x, grab_position.y); if (target_output && (view->get_output() != target_output)) { auto parent = wf::find_topmost_parent(view); auto offset = wf::origin(parent->get_output()->get_layout_geometry()) + -wf::origin(target_output->get_layout_geometry()); move_view_to_output(parent, target_output, false); for (auto& v : parent->enumerate_views(false)) { v->move(v->get_geometry().x + offset.x, v->get_geometry().y + offset.y); } // On the new output wf::get_core().default_wm->move_request(view); return false; } wayfire_toplevel_view grabbed_view = view; view = get_target_view(view); if (!can_move_view(view)) { return false; } if (!grab_input(view)) { return false; } wf::move_drag::drag_options_t opts; opts.enable_snap_off = move_enable_snap_off && (view->pending_fullscreen() || view->pending_tiled_edges()); opts.snap_off_threshold = move_snap_off_threshold; opts.join_views = join_views; if (join_views) { // ensure that the originally grabbed view will be focused wf::get_core().seat->focus_view(grabbed_view); } drag_helper->set_pending_drag(grab_position); drag_helper->start_drag(view, opts); drag_helper->handle_motion(get_global_input_coords()); update_slot(wf::grid::SLOT_NONE); return true; } void deactivate() { input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); } void input_pressed(uint32_t state) { if (state != WLR_BUTTON_RELEASED) { return; } drag_helper->handle_input_released(); } /* Calculate the slot to which the view would be snapped if the input * is released at output-local coordinates (x, y) */ wf::grid::slot_t calc_slot(wf::point_t point) { if (!is_snap_enabled()) { return wf::grid::SLOT_NONE; } auto g = output->workarea->get_workarea(); if (!(output->get_relative_geometry() & point)) { return wf::grid::SLOT_NONE; } int threshold = snap_threshold; bool is_left = point.x - g.x <= threshold; bool is_right = g.x + g.width - point.x <= threshold; bool is_top = point.y - g.y < threshold; bool is_bottom = g.x + g.height - point.y < threshold; bool is_far_left = point.x - g.x <= quarter_snap_threshold; bool is_far_right = g.x + g.width - point.x <= quarter_snap_threshold; bool is_far_top = point.y - g.y < quarter_snap_threshold; bool is_far_bottom = g.x + g.height - point.y < quarter_snap_threshold; wf::grid::slot_t slot = wf::grid::SLOT_NONE; if ((is_left && is_far_top) || (is_far_left && is_top)) { slot = wf::grid::SLOT_TL; } else if ((is_right && is_far_top) || (is_far_right && is_top)) { slot = wf::grid::SLOT_TR; } else if ((is_right && is_far_bottom) || (is_far_right && is_bottom)) { slot = wf::grid::SLOT_BR; } else if ((is_left && is_far_bottom) || (is_far_left && is_bottom)) { slot = wf::grid::SLOT_BL; } else if (is_right) { slot = wf::grid::SLOT_RIGHT; } else if (is_left) { slot = wf::grid::SLOT_LEFT; } else if (is_top) { // Maximize when dragging to the top slot = wf::grid::SLOT_CENTER; } else if (is_bottom) { slot = wf::grid::SLOT_BOTTOM; } return slot; } void update_workspace_switch_timeout(wf::grid::slot_t slot_id) { if ((workspace_switch_after == -1) || (slot_id == wf::grid::SLOT_NONE)) { workspace_switch_timer.disconnect(); return; } int dx = 0, dy = 0; if (slot_id >= 7) { dy = -1; } if (slot_id <= 3) { dy = 1; } if (slot_id % 3 == 1) { dx = -1; } if (slot_id % 3 == 0) { dx = 1; } if ((dx == 0) && (dy == 0)) { workspace_switch_timer.disconnect(); return; } wf::point_t cws = output->wset()->get_current_workspace(); wf::point_t tws = {cws.x + dx, cws.y + dy}; wf::dimensions_t ws_dim = output->wset()->get_workspace_grid_size(); wf::geometry_t possible = { 0, 0, ws_dim.width, ws_dim.height }; /* Outside of workspace grid */ if (!(possible & tws)) { workspace_switch_timer.disconnect(); return; } workspace_switch_timer.set_timeout(workspace_switch_after, [this, tws] () { output->wset()->request_workspace(tws); }); } void update_slot(wf::grid::slot_t new_slot_id) { /* No changes in the slot, just return */ if (slot.slot_id == new_slot_id) { return; } /* Destroy previous preview */ if (slot.preview) { auto input = get_input_coords(); slot.preview->set_target_geometry({input.x, input.y, 1, 1}, 0, true); slot.preview = nullptr; } wf::grid::grid_request_signal grid_signal; wf::get_core().emit(&grid_signal); if (grid_signal.carried_out || (new_slot_id == wf::grid::slot_t::SLOT_CENTER)) { slot.slot_id = new_slot_id; } else { slot.slot_id = new_slot_id = wf::grid::slot_t::SLOT_NONE; } /* Show a preview overlay */ if (new_slot_id) { wf::geometry_t slot_geometry = wf::grid::get_slot_dimensions(output, new_slot_id); /* Unknown slot geometry, can't show a preview */ if ((slot_geometry.width <= 0) || (slot_geometry.height <= 0)) { return; } auto input = get_input_coords(); slot.preview = std::make_shared( wf::geometry_t{input.x, input.y, 1, 1}, output, "move"); slot.preview->set_target_geometry(slot_geometry, 1); } update_workspace_switch_timeout(new_slot_id); } /* Returns the currently used input coordinates in global compositor space */ wf::point_t get_global_input_coords() { wf::pointf_t input; if (wf::get_core().get_touch_state().fingers.empty()) { input = wf::get_core().get_cursor_position(); } else { auto center = wf::get_core().get_touch_state().get_center().current; input = {center.x, center.y}; } return {(int)input.x, (int)input.y}; } /* Returns the currently used input coordinates in output-local space */ wf::point_t get_input_coords() { auto og = output->get_layout_geometry(); auto coords = get_global_input_coords() - wf::point_t{og.x, og.y}; return coords; } bool is_snap_enabled() { if (!enable_snap || !drag_helper->view || drag_helper->is_view_held_in_place()) { return false; } // Make sure that fullscreen views are not tiled. // We allow movement of fullscreen views but they should always // retain their fullscreen state (but they can be moved to other // workspaces). Unsetting the fullscreen state can break some // Xwayland games. if (drag_helper->view->pending_fullscreen()) { return false; } if (drag_helper->view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT) { return false; } return true; } void handle_input_motion() { drag_helper->handle_motion(get_global_input_coords()); update_slot(calc_slot(get_input_coords())); } void fini() override { if (input_grab->is_grabbed()) { input_pressed(WLR_BUTTON_RELEASED); } output->rem_binding(&activate_binding); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/switcher.cpp0000664000175000017500000006655715053502647022213 0ustar dkondordkondor#include "wayfire/object.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include constexpr const char *switcher_transformer = "switcher-3d"; constexpr const char *switcher_transformer_background = "switcher-3d"; constexpr float background_dim_factor = 0.6; using namespace wf::animation; class SwitcherPaintAttribs { public: SwitcherPaintAttribs(const duration_t& duration) : scale_x(duration, 1, 1), scale_y(duration, 1, 1), off_x(duration, 0, 0), off_y(duration, 0, 0), off_z(duration, 0, 0), rotation(duration, 0, 0), alpha(duration, 1, 1) {} timed_transition_t scale_x, scale_y; timed_transition_t off_x, off_y, off_z; timed_transition_t rotation, alpha; }; enum SwitcherViewPosition { SWITCHER_POSITION_LEFT = 0, SWITCHER_POSITION_CENTER = 1, SWITCHER_POSITION_RIGHT = 2, }; static constexpr bool view_expired(int view_position) { return view_position < SWITCHER_POSITION_LEFT || view_position > SWITCHER_POSITION_RIGHT; } struct SwitcherView { wayfire_toplevel_view view; SwitcherPaintAttribs attribs; int position; SwitcherView(duration_t& duration) : attribs(duration) {} /* Make animation start values the current progress of duration */ void refresh_start() { for_each([] (timed_transition_t& t) { t.restart_same_end(); }); } void to_end() { for_each([] (timed_transition_t& t) { t.set(t.end, t.end); }); } private: void for_each(std::function call) { call(attribs.off_x); call(attribs.off_y); call(attribs.off_z); call(attribs.scale_x); call(attribs.scale_y); call(attribs.alpha); call(attribs.rotation); } }; class WayfireSwitcher : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t { wf::option_wrapper_t view_thumbnail_scale{ "switcher/view_thumbnail_scale"}; wf::option_wrapper_t speed{"switcher/speed"}; wf::option_wrapper_t view_thumbnail_rotation{ "switcher/view_thumbnail_rotation"}; duration_t duration{speed}; duration_t background_dim_duration{speed}; timed_transition_t background_dim{background_dim_duration}; std::unique_ptr input_grab; /* If a view comes before another in this list, it is on top of it */ std::vector views; // the modifiers which were used to activate switcher uint32_t activating_modifiers = 0; bool active = false; class switcher_render_node_t : public wf::scene::node_t { class switcher_render_instance_t : public wf::scene::render_instance_t { std::shared_ptr self; wf::scene::damage_callback push_damage; wf::signal::connection_t on_switcher_damage = [=] (wf::scene::node_damage_signal *ev) { push_damage(ev->region); }; public: switcher_render_instance_t(switcher_render_node_t *self, wf::scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; self->connect(&on_switcher_damage); } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); // Don't render anything below auto bbox = self->get_bounding_box(); damage ^= bbox; } void render(const wf::scene::render_instruction_t& data) override { self->switcher->render(data); } }; public: switcher_render_node_t(WayfireSwitcher *switcher) : node_t(false) { this->switcher = switcher; } virtual void gen_render_instances( std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *shown_on) { if (shown_on != this->switcher->output) { return; } instances.push_back(std::make_unique(this, push_damage)); } wf::geometry_t get_bounding_box() { return switcher->output->get_layout_geometry(); } private: WayfireSwitcher *switcher; }; std::shared_ptr render_node; wf::plugin_activation_data_t grab_interface = { .name = "switcher", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("switcher: requires GLES2 support, but current renderer is ", render_type); return; } output->add_key( wf::option_wrapper_t{"switcher/next_view"}, &next_view_binding); output->add_key( wf::option_wrapper_t{"switcher/prev_view"}, &prev_view_binding); output->connect(&view_disappeared); input_grab = std::make_unique("switcher", output, this, nullptr, nullptr); grab_interface.cancel = [=] () {deinit_switcher();}; } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { auto mod = wf::get_core().seat->modifier_from_keycode(event.keycode); if ((event.state == WLR_KEY_RELEASED) && (mod & activating_modifiers)) { handle_done(); } } wf::key_callback next_view_binding = [=] (auto) { handle_switch_request(-1); return false; }; wf::key_callback prev_view_binding = [=] (auto) { handle_switch_request(1); return false; }; wf::effect_hook_t pre_hook = [=] () { dim_background(background_dim); wf::scene::damage_node(render_node, render_node->get_bounding_box()); if (!duration.running()) { cleanup_expired(); if (!active) { deinit_switcher(); } } }; wf::signal::connection_t view_disappeared = [=] (wf::view_disappeared_signal *ev) { if (auto toplevel = toplevel_cast(ev->view)) { handle_view_removed(toplevel); } }; void handle_view_removed(wayfire_toplevel_view view) { // not running at all, don't care if (!output->is_plugin_active(grab_interface.name)) { return; } bool need_action = false; for (auto& sv : views) { need_action |= (sv.view == view); } // don't do anything if we're not using this view if (!need_action) { return; } if (active) { arrange(); } else { cleanup_views([=] (SwitcherView& sv) { return sv.view == view; }); } } bool handle_switch_request(int dir) { if (get_workspace_views().empty()) { return false; } /* If we haven't grabbed, then we haven't setup anything */ if (!output->is_plugin_active(grab_interface.name)) { if (!init_switcher()) { return false; } } /* Maybe we're still animating the exit animation from a previous * switcher activation? */ if (!active) { active = true; input_grab->grab_input(wf::scene::layer::OVERLAY); focus_next(dir); arrange(); activating_modifiers = wf::get_core().seat->get_keyboard_modifiers(); } else { next_view(dir); } return true; } /* When switcher is done and starts animating towards end */ void handle_done() { cleanup_expired(); dearrange(); input_grab->ungrab_input(); } /* Sets up basic hooks needed while switcher works and/or displays animations. * Also lower any fullscreen views that are active */ bool init_switcher() { if (!output->activate_plugin(&grab_interface)) { return false; } output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE); render_node = std::make_shared(this); wf::scene::add_front(wf::get_core().scene(), render_node); return true; } /* The reverse of init_switcher */ void deinit_switcher() { output->deactivate_plugin(&grab_interface); output->render->rem_effect(&pre_hook); wf::scene::remove_child(render_node); render_node = nullptr; for (auto& view : output->wset()->get_views()) { if (view->has_data("switcher-minimized-showed")) { view->erase_data("switcher-minimized-showed"); wf::scene::set_node_enabled(view->get_root_node(), false); } view->get_transformed_node()->rem_transformer(switcher_transformer); view->get_transformed_node()->rem_transformer( switcher_transformer_background); } views.clear(); wf::scene::update(wf::get_core().scene(), wf::scene::update_flag::INPUT_STATE); } /* offset from the left or from the right */ float get_center_offset() { return output->get_relative_geometry().width / 3.0; } /* get the scale for non-focused views */ float get_back_scale() { return 0.66; } /* offset in Z-direction for non-focused views */ float get_z_offset() { return -1.0; } /* Move view animation target to the left * @param dir -1 for left, 1 for right */ void move(SwitcherView& sv, int dir) { sv.attribs.off_x.restart_with_end( sv.attribs.off_x.end + get_center_offset() * dir); sv.attribs.off_y.restart_same_end(); float z_sign = 0; if (sv.position == SWITCHER_POSITION_CENTER) { // Move from center to either left or right, so backwards z_sign = 1; } else if (view_expired(sv.position + dir)) { // Expires, don't move z_sign = 0; } else { // Not from center, doesn't expire -> comes to the center z_sign = -1; } sv.attribs.off_z.restart_with_end( sv.attribs.off_z.end + get_z_offset() * z_sign); /* scale views that aren't in the center */ sv.attribs.scale_x.restart_with_end( sv.attribs.scale_x.end * std::pow(get_back_scale(), z_sign)); sv.attribs.scale_y.restart_with_end( sv.attribs.scale_y.end * std::pow(get_back_scale(), z_sign)); float radians_rotation = -((float)view_thumbnail_rotation * (M_PI / 180.0)); sv.attribs.rotation.restart_with_end( sv.attribs.rotation.end + radians_rotation * dir); sv.position += dir; sv.attribs.alpha.restart_with_end( view_expired(sv.position) ? 0.3 : 1.0); } /* Calculate how much a view should be scaled to fit into the slots */ float calculate_scaling_factor(const wf::geometry_t& bbox) const { /* Each view should not be more than this percentage of the * width/height of the output */ constexpr float screen_percentage = 0.45; auto og = output->get_relative_geometry(); float max_width = og.width * screen_percentage; float max_height = og.height * screen_percentage; float needed_exact = std::min(max_width / bbox.width, max_height / bbox.height); // don't scale down if the view is already small enough return std::min(needed_exact, 1.0f) * view_thumbnail_scale; } /* Calculate alpha for the view when switcher is inactive. */ float get_view_normal_alpha(wayfire_toplevel_view view) { /* Usually views are visible, but if they were minimized, * and we aren't restoring the view, it has target alpha 0.0 */ if (view->minimized && (views.empty() || (view != views[0].view))) { return 0.0; } return 1.0; } /* Move untransformed view to the center */ void arrange_center_view(SwitcherView& sv) { auto og = output->get_relative_geometry(); auto bbox = wf::view_bounding_box_up_to(sv.view, switcher_transformer); float dx = (og.width / 2.0 - bbox.width / 2.0) - bbox.x; float dy = bbox.y - (og.height / 2.0 - bbox.height / 2.0); sv.attribs.off_x.set(0, dx); sv.attribs.off_y.set(0, dy); float scale = calculate_scaling_factor(bbox); sv.attribs.scale_x.set(1, scale); sv.attribs.scale_y.set(1, scale); sv.attribs.alpha.set(get_view_normal_alpha(sv.view), 1.0); } /* Position the view, starting from untransformed position */ void arrange_view(SwitcherView& sv, int position) { arrange_center_view(sv); if (position == SWITCHER_POSITION_CENTER) { /* view already centered */ } else { move(sv, position - SWITCHER_POSITION_CENTER); } } // returns a list of mapped views std::vector get_workspace_views() const { return output->wset()->get_views(wf::WSET_MAPPED_ONLY | wf::WSET_CURRENT_WORKSPACE); } /* Change the current focus to the next or the previous view */ void focus_next(int dir) { auto ws_views = get_workspace_views(); /* Change the focused view and rearrange views so that focused is on top */ int size = ws_views.size(); // calculate focus index & focus it int focused_view_index = (size + dir) % size; auto focused_view = ws_views[focused_view_index]; wf::view_bring_to_front(focused_view); } /* Create the initial arrangement on the screen * Also changes the focus to the next or the last view, depending on dir */ void arrange() { // clear views in case that deinit() hasn't been run views.clear(); duration.start(); background_dim.set(1, background_dim_factor); background_dim_duration.start(); auto ws_views = get_workspace_views(); for (auto v : ws_views) { views.push_back(create_switcher_view(v)); } std::sort(views.begin(), views.end(), [] (SwitcherView& a, SwitcherView& b) { return wf::get_focus_timestamp(a.view) > wf::get_focus_timestamp(b.view); }); if (ws_views.empty()) { return; } /* Add a copy of the unfocused view if we have just 2 */ if (ws_views.size() == 2) { views.push_back(create_switcher_view(ws_views.back())); } arrange_view(views[0], SWITCHER_POSITION_CENTER); /* If we have just 1 view, don't do anything else */ if (ws_views.size() > 1) { arrange_view(views.back(), SWITCHER_POSITION_LEFT); } for (int i = 1; i < (int)views.size() - 1; i++) { arrange_view(views[i], SWITCHER_POSITION_RIGHT); } // We want the next view to be focused right off the bat // But we want it to be animated. handle_switch_request(-1); } void dearrange() { /* When we have just 2 views on the workspace, we have 2 copies * of the unfocused view. When dearranging those copies, they overlap. * If the view is translucent, this means that the view gets darker than * it really is. * To circumvent this, we just fade out one of the copies */ wayfire_toplevel_view fading_view = nullptr; if (count_different_active_views() == 2) { fading_view = get_unfocused_view(); } for (auto& sv : views) { sv.attribs.off_x.restart_with_end(0); sv.attribs.off_y.restart_with_end(0); sv.attribs.off_z.restart_with_end(0); sv.attribs.scale_x.restart_with_end(1.0); sv.attribs.scale_y.restart_with_end(1.0); sv.attribs.rotation.restart_with_end(0); sv.attribs.alpha.restart_with_end(get_view_normal_alpha(sv.view)); if (sv.view == fading_view) { sv.attribs.alpha.end = 0.0; // make sure we don't fade out the other unfocused view instance as // well fading_view = nullptr; } } background_dim.restart_with_end(1); background_dim_duration.start(); duration.start(); active = false; /* Potentially restore view[0] if it was maximized */ if (views.size()) { wf::get_core().default_wm->focus_raise_view(views[0].view); } } std::vector get_background_views() const { return wf::collect_views_from_output(output, {wf::scene::layer::BACKGROUND, wf::scene::layer::BOTTOM}); } std::vector get_overlay_views() const { return wf::collect_views_from_output(output, {wf::scene::layer::TOP, wf::scene::layer::OVERLAY, wf::scene::layer::DWIDGET}); } void dim_background(float dim) { for (auto view : get_background_views()) { if (dim == 1.0) { view->get_transformed_node()->rem_transformer( switcher_transformer_background); } else { auto tr = wf::ensure_named_transformer( view, wf::TRANSFORMER_3D, switcher_transformer_background, view); tr->color[0] = tr->color[1] = tr->color[2] = dim; } } } SwitcherView create_switcher_view(wayfire_toplevel_view view) { /* we add a view transform if there isn't any. * * Note that a view might be visible on more than 1 place, so damage * tracking doesn't work reliably. To circumvent this, we simply damage * the whole output */ if (!view->get_transformed_node()->get_transformer(switcher_transformer)) { if (view->minimized) { wf::scene::set_node_enabled(view->get_root_node(), true); view->store_data(std::make_unique(), "switcher-minimized-showed"); } view->get_transformed_node()->add_transformer( std::make_shared(view), wf::TRANSFORMER_3D, switcher_transformer); } SwitcherView sw{duration}; sw.view = view; sw.position = SWITCHER_POSITION_CENTER; return sw; } void render_view_scene(wayfire_view view, const wf::render_target_t& buffer) { std::vector instances; view->get_transformed_node()->gen_render_instances(instances, [] (auto) {}); wf::render_pass_params_t params; params.instances = &instances; params.damage = view->get_transformed_node()->get_bounding_box(); params.reference_output = this->output; params.target = buffer; wf::render_pass_t::run(params); } void render_view(const SwitcherView& sv, const wf::render_target_t& buffer) { auto transform = sv.view->get_transformed_node() ->get_transformer(switcher_transformer); assert(transform); transform->translation = glm::translate(glm::mat4(1.0), {(double)sv.attribs.off_x, (double)sv.attribs.off_y, (double)sv.attribs.off_z}); transform->scaling = glm::scale(glm::mat4(1.0), {(double)sv.attribs.scale_x, (double)sv.attribs.scale_y, 1.0}); transform->rotation = glm::rotate(glm::mat4(1.0), (float)sv.attribs.rotation, {0.0, 1.0, 0.0}); transform->color[3] = sv.attribs.alpha; render_view_scene(sv.view, buffer); } void render(const wf::scene::render_instruction_t& data) { data.pass->clear(data.target.geometry, {0, 0, 0, 1}); auto local_target = data.target.translated(-wf::origin(render_node->get_bounding_box())); for (auto view : get_background_views()) { render_view_scene(view, local_target); } /* Render in the reverse order because we don't use depth testing */ for (auto& view : wf::reverse(views)) { render_view(view, local_target); } for (auto view : get_overlay_views()) { render_view_scene(view, local_target); } } /* delete all views matching the given criteria, skipping the first "start" views * */ void cleanup_views(std::function criteria) { auto it = views.begin(); while (it != views.end()) { if (criteria(*it)) { it = views.erase(it); } else { ++it; } } } /* Removes all expired views from the list */ void cleanup_expired() { cleanup_views([=] (SwitcherView& sv) { return view_expired(sv.position); }); } /* sort views according to their Z-order */ void rebuild_view_list() { std::stable_sort(views.begin(), views.end(), [] (const SwitcherView& a, const SwitcherView& b) { enum category { FOCUSED = 0, UNFOCUSED = 1, EXPIRED = 2, }; auto view_category = [] (const SwitcherView& sv) { if (sv.position == SWITCHER_POSITION_CENTER) { return FOCUSED; } if (view_expired(sv.position)) { return EXPIRED; } return UNFOCUSED; }; category aCat = view_category(a), bCat = view_category(b); if (aCat == bCat) { return a.position < b.position; } else { return aCat < bCat; } }); } void next_view(int dir) { cleanup_expired(); if (count_different_active_views() <= 1) { return; } /* Count of views in the left/right slots */ int count_right = 0; int count_left = 0; /* Move the topmost view from the center and the left/right group, * depending on the direction*/ int to_move = (1 << SWITCHER_POSITION_CENTER) | (1 << (1 - dir)); for (auto& sv : views) { if (!view_expired(sv.position) && ((1 << sv.position) & to_move)) { to_move ^= (1 << sv.position); // only the topmost one move(sv, dir); } else if (!view_expired(sv.position)) { /* Make sure animations start from where we are now */ sv.refresh_start(); } count_left += (sv.position == SWITCHER_POSITION_LEFT); count_right += (sv.position == SWITCHER_POSITION_RIGHT); } /* Create a new view on the missing slot, but if both are missing, * show just the centered view */ if (bool(count_left) ^ bool(count_right)) { const int empty_slot = 1 - dir; fill_empty_slot(empty_slot); } rebuild_view_list(); wf::view_bring_to_front(views.front().view); duration.start(); } int count_different_active_views() { std::set active_views; for (auto& sv : views) { active_views.insert(sv.view); } return active_views.size(); } /* Move the last view in the given slot so that it becomes invalid */ wayfire_toplevel_view invalidate_last_in_slot(int slot) { for (int i = views.size() - 1; i >= 0; i--) { if (views[i].position == slot) { move(views[i], slot - 1); return views[i].view; } } return nullptr; } /* Returns the non-focused view in the case where there is only 1 view */ wayfire_toplevel_view get_unfocused_view() { for (auto& sv : views) { if (!view_expired(sv.position) && (sv.position != SWITCHER_POSITION_CENTER)) { return sv.view; } } return nullptr; } void fill_empty_slot(const int empty_slot) { const int full_slot = 2 - empty_slot; /* We have an empty slot. We invalidate the bottom-most view in the * opposite slot, and create a new view with the same content to * fill in the empty slot */ auto view_to_create = invalidate_last_in_slot(full_slot); /* special case: we have just 2 views * in this case, the "new" view should not be the same as the * invalidated view(because this view is focused now), but the * one which isn't focused */ if (count_different_active_views() == 2) { view_to_create = get_unfocused_view(); } assert(view_to_create); auto sv = create_switcher_view(view_to_create); arrange_view(sv, empty_slot); /* directly show it on the target position */ sv.to_end(); sv.attribs.alpha.set(0, 1); views.push_back(std::move(sv)); } void fini() override { if (output->is_plugin_active(grab_interface.name)) { input_grab->ungrab_input(); deinit_switcher(); } output->rem_binding(&next_view_binding); output->rem_binding(&prev_view_binding); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/vswipe.cpp0000664000175000017500000002776315053502647021674 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vswipe-processing.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/signal-provider.hpp" using namespace wf::animation; class vswipe_smoothing_t : public duration_t { public: using duration_t::duration_t; timed_transition_t dx{*this}; timed_transition_t dy{*this}; }; static inline wf::geometry_t interpolate(wf::geometry_t a, wf::geometry_t b, double xalpha, double yalpha) { const auto& interp = [=] (int32_t wf::geometry_t::*member, double alpha) -> int32_t { return std::round((1 - alpha) * a.*member + alpha * b.*member); }; return { interp(&wf::geometry_t::x, xalpha), interp(&wf::geometry_t::y, yalpha), interp(&wf::geometry_t::width, xalpha), interp(&wf::geometry_t::height, yalpha) }; } class vswipe : public wf::per_output_plugin_instance_t { private: enum swipe_direction_t { HORIZONTAL = 1, VERTICAL = 2, DIAGONAL = HORIZONTAL | VERTICAL, UNKNOWN = 0, }; struct { bool swiping = false; bool animating = false; swipe_direction_t direction; wf::pointf_t initial_deltas; wf::pointf_t delta_sum; wf::pointf_t delta_prev; wf::pointf_t delta_last; int vx = 0; int vy = 0; int vw = 0; int vh = 0; } state; std::unique_ptr wall; wf::option_wrapper_t enable_horizontal{"vswipe/enable_horizontal"}; wf::option_wrapper_t enable_vertical{"vswipe/enable_vertical"}; wf::option_wrapper_t enable_free_movement{"vswipe/enable_free_movement"}; wf::option_wrapper_t smooth_transition{"vswipe/enable_smooth_transition"}; wf::option_wrapper_t background_color{"vswipe/background"}; wf::option_wrapper_t animation_duration{"vswipe/duration"}; vswipe_smoothing_t smooth_delta{animation_duration}; wf::option_wrapper_t fingers{"vswipe/fingers"}; wf::option_wrapper_t gap{"vswipe/gap"}; wf::option_wrapper_t threshold{"vswipe/threshold"}; wf::option_wrapper_t delta_threshold{"vswipe/delta_threshold"}; wf::option_wrapper_t speed_factor{"vswipe/speed_factor"}; wf::option_wrapper_t speed_cap{"vswipe/speed_cap"}; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "vswipe", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { finalize_and_exit(); }, }; public: void init() override { input_grab = std::make_unique("vswipe", output); wf::get_core().connect(&on_swipe_begin); wf::get_core().connect(&on_swipe_update); wf::get_core().connect(&on_swipe_end); wall = std::make_unique(output); wall->connect(&this->on_frame); } wf::effect_hook_t post_frame = [=] () { if (!smooth_delta.running() && !state.swiping) { finalize_and_exit(); return; } output->render->schedule_redraw(); output->render->damage_whole(); }; wf::signal::connection_t on_frame = [=] (wf::wall_frame_event_t *ev) { wf::point_t current_workspace = {state.vx, state.vy}; int dx = 0, dy = 0; if (state.direction & HORIZONTAL) { dx = 1; } if (state.direction & VERTICAL) { dy = 1; } wf::point_t next_ws = {current_workspace.x + dx, current_workspace.y + dy}; auto g1 = wall->get_workspace_rectangle(current_workspace); auto g2 = wall->get_workspace_rectangle(next_ws); wall->set_viewport(interpolate(g1, g2, -smooth_delta.dx, -smooth_delta.dy)); }; template using event = wf::input_event_signal; wf::signal::connection_t> on_swipe_begin = [=] (event *ev) { if (!enable_horizontal && !enable_vertical) { return; } if (output->is_plugin_active(grab_interface.name)) { return; } if (static_cast(ev->event->fingers) != fingers) { return; } // Plugins are per output, swipes are global, so we need to handle // the swipe only when the cursor is on *our* (plugin instance's) output if (!(output->get_relative_geometry() & output->get_cursor_position())) { return; } state.swiping = true; state.direction = UNKNOWN; state.initial_deltas = {0.0, 0.0}; smooth_delta.dx.set(0, 0); smooth_delta.dy.set(0, 0); state.delta_last = {0, 0}; state.delta_prev = {0, 0}; state.delta_sum = {0, 0}; // We switch the actual workspace before the finishing animation, // so the rendering of the animation cannot dynamically query current // workspace again, so it's stored here auto grid = output->wset()->get_workspace_grid_size(); auto ws = output->wset()->get_current_workspace(); state.vw = grid.width; state.vh = grid.height; state.vx = ws.x; state.vy = ws.y; }; void start_swipe(swipe_direction_t direction) { assert(direction != UNKNOWN); state.direction = direction; if (!output->activate_plugin(&grab_interface)) { return; } input_grab->grab_input(wf::scene::layer::OVERLAY); wf::get_core().seat->focus_output(output); auto ws = output->wset()->get_current_workspace(); wall->set_background_color(background_color); wall->set_gap_size(gap); wall->set_viewport(wall->get_workspace_rectangle(ws)); wall->start_output_renderer(); output->render->add_effect(&post_frame, wf::OUTPUT_EFFECT_POST); } // XXX: how to determine this?? static constexpr double initial_direction_threshold = 0.05; static constexpr double secondary_direction_threshold = 0.3; static constexpr double diagonal_threshold = 1.73; // tan(30deg) bool is_diagonal(wf::pointf_t deltas) { /* Diagonal movement is possible if the slope is not too steep * and we have moved enough */ double slope = deltas.x / deltas.y; bool diagonal = wf::clamp(slope, 1.0 / diagonal_threshold, diagonal_threshold) == slope; diagonal &= (deltas.x * deltas.x + deltas.y * deltas.y) >= initial_direction_threshold * initial_direction_threshold; return diagonal; } swipe_direction_t calculate_direction(wf::pointf_t deltas) { auto grid = output->wset()->get_workspace_grid_size(); bool horizontal = deltas.x > initial_direction_threshold; bool vertical = deltas.y > initial_direction_threshold; horizontal &= deltas.x > deltas.y; vertical &= deltas.y > deltas.x; if (is_diagonal(deltas) && enable_free_movement) { return DIAGONAL; } else if (horizontal && (grid.width > 1) && enable_horizontal) { return HORIZONTAL; } else if (vertical && (grid.height > 1) && enable_vertical) { return VERTICAL; } return UNKNOWN; } wf::signal::connection_t> on_swipe_update = [&] (event *ev) { if (!state.swiping) { return; } state.delta_sum.x += ev->event->dx / speed_factor; state.delta_sum.y += ev->event->dy / speed_factor; if (state.direction == UNKNOWN) { state.initial_deltas.x += std::abs(ev->event->dx / speed_factor); state.initial_deltas.y += std::abs(ev->event->dy / speed_factor); state.direction = calculate_direction(state.initial_deltas); if (state.direction == UNKNOWN) { return; } start_swipe(state.direction); } else if ((state.direction != DIAGONAL) && enable_free_movement) { /* Consider promoting to diagonal movement */ double other = (state.direction == HORIZONTAL ? state.delta_sum.y : state.delta_sum.x); if (std::abs(other) > secondary_direction_threshold) { state.direction = DIAGONAL; } } const double cap = speed_cap; state.delta_prev = state.delta_last; double current_delta_processed; const auto& process_delta = [&] (double delta, wf::timed_transition_t& total_delta, int ws, int ws_max) { current_delta_processed = vswipe_process_delta(delta / speed_factor, total_delta, ws, ws_max, cap, enable_free_movement); double new_delta_end = total_delta.end + current_delta_processed; double new_delta_start = smooth_transition ? total_delta : new_delta_end; total_delta.set(new_delta_start, new_delta_end); }; if (state.direction & HORIZONTAL) { process_delta(ev->event->dx, smooth_delta.dx, state.vx, state.vw); } if (state.direction & VERTICAL) { process_delta(ev->event->dy, smooth_delta.dy, state.vy, state.vh); } state.delta_last = {ev->event->dx, ev->event->dy}; smooth_delta.start(); }; wf::signal::connection_t> on_swipe_end = [=] (auto) { if (!state.swiping || !output->is_plugin_active(grab_interface.name)) { state.swiping = false; return; } state.swiping = false; const double move_threshold = wf::clamp((double)threshold, 0.0, 1.0); const double fast_threshold = wf::clamp((double)delta_threshold, 0.0, 1000.0); wf::point_t target_delta = {0, 0}; wf::point_t target_workspace = {state.vx, state.vy}; if (state.direction & HORIZONTAL) { target_delta.x = vswipe_finish_target(smooth_delta.dx.end, state.vx, state.vw, state.delta_prev.x + state.delta_last.x, move_threshold, fast_threshold, enable_free_movement); target_workspace.x -= target_delta.x; } if (state.direction & VERTICAL) { target_delta.y = vswipe_finish_target(smooth_delta.dy.end, state.vy, state.vh, state.delta_prev.y + state.delta_last.y, move_threshold, fast_threshold, enable_free_movement); target_workspace.y -= target_delta.y; } smooth_delta.dx.restart_with_end(target_delta.x); smooth_delta.dy.restart_with_end(target_delta.y); smooth_delta.start(); output->wset()->set_workspace(target_workspace); state.animating = true; }; void finalize_and_exit() { state.swiping = false; input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); wall->stop_output_renderer(true); output->render->rem_effect(&post_frame); state.animating = false; } void fini() override { if (state.swiping) { finalize_and_exit(); } } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/wsets.cpp0000664000175000017500000002340015053502647021504 0ustar dkondordkondor#include #include "wayfire/bindings.hpp" #include "wayfire/object.hpp" #include "wayfire/seat.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/common/cairo-util.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/plugins/common/simple-text-node.hpp" #include #include #include #include #include #include #include #include #include #include #include #include class wayfire_wsets_plugin_t : public wf::plugin_interface_t { public: void init() override { method_repository->register_method("wsets/set-output-wset", set_output_wset); method_repository->register_method("wsets/send-view-to-wset", send_view_to_wset); setup_bindings(); wf::get_core().output_layout->connect(&on_new_output); for (auto& wo : wf::get_core().output_layout->get_outputs()) { available_sets[wo->wset()->get_index()] = wo->wset(); } } void fini() override { method_repository->unregister_method("wsets/set-output-wset"); method_repository->unregister_method("wsets/send-view-to-wset"); for (auto& binding : select_callback) { wf::get_core().bindings->rem_binding(&binding); } for (auto& binding : send_callback) { wf::get_core().bindings->rem_binding(&binding); } } private: wf::shared_data::ref_ptr_t method_repository; wf::option_wrapper_t> workspace_bindings{"wsets/wsets_bindings"}; wf::option_wrapper_t> send_to_bindings{"wsets/send_window_bindings"}; wf::option_wrapper_t label_duration{"wsets/label_duration"}; std::list select_callback; std::list send_callback; std::map> available_sets; wf::ipc::method_callback set_output_wset = [=] (wf::json_t data) { auto output_id = wf::ipc::json_get_int64(data, "output-id"); auto wset_index = wf::ipc::json_get_int64(data, "wset-index"); wf::output_t *o = wf::ipc::find_output_by_id(output_id); if (!o) { return wf::ipc::json_error("output not found"); } select_workspace(wset_index, o); return wf::ipc::json_ok(); }; wf::ipc::method_callback send_view_to_wset = [=] (wf::json_t data) { auto view_id = wf::ipc::json_get_int64(data, "view-id"); auto wset_index = wf::ipc::json_get_int64(data, "wset-index"); wayfire_toplevel_view view = toplevel_cast(wf::ipc::find_view_by_id(view_id)); if (!view) { return wf::ipc::json_error("view not found"); } send_window_to(wset_index, view); return wf::ipc::json_ok(); }; void setup_bindings() { for (const auto& [workspace, binding] : workspace_bindings.value()) { int index = wf::option_type::from_string(workspace.c_str()).value_or(-1); if (index < 0) { LOGE("[WSETS] Invalid workspace set ", index, " in configuration!"); continue; } select_callback.push_back([=] (auto) { select_workspace(index); return true; }); wf::get_core().bindings->add_activator(wf::create_option(binding), &select_callback.back()); } for (const auto& [workspace, binding] : send_to_bindings.value()) { int index = wf::option_type::from_string(workspace.c_str()).value_or(-1); if (index < 0) { LOGE("[WSETS] Invalid workspace set ", index, " in configuration!"); continue; } send_callback.push_back([=] (auto) { auto wo = wf::get_core().seat->get_active_output(); auto view = toplevel_cast(wf::get_active_view_for_output(wo)); if (!view) { return false; } send_window_to(index, view); return true; }); wf::get_core().bindings->add_activator(wf::create_option(binding), &send_callback.back()); } } struct output_overlay_data_t : public wf::custom_data_t { std::shared_ptr node; wf::wl_timer timer; ~output_overlay_data_t() { wf::scene::damage_node(node, node->get_bounding_box()); wf::scene::remove_child(node); timer.disconnect(); } }; void cleanup_wsets() { auto it = available_sets.begin(); while (it != available_sets.end()) { auto wset = it->second; if (wset->get_views().empty() && (!wset->get_attached_output() || (wset->get_attached_output()->wset() != wset))) { it = available_sets.erase(it); } else { ++it; } } } void show_workspace_set_overlay(wf::output_t *wo) { auto overlay = wo->get_data_safe(); if (!overlay->node) { overlay->node = std::make_shared(); } overlay->node->set_text("Workspace set " + std::to_string(wo->wset()->get_index())); overlay->node->set_position({10, 10}); overlay->node->set_text_params(wf::cairo_text_t::params(32 /* font_size */, wf::color_t{0.1, 0.1, 0.1, 0.9} /* bg_color */, wf::color_t{0.9, 0.9, 0.9, 1} /* fg_color */)); wf::scene::readd_front(wo->node_for_layer(wf::scene::layer::DWIDGET), overlay->node); wf::scene::damage_node(overlay->node, overlay->node->get_bounding_box()); overlay->timer.set_timeout(label_duration.value().length_ms, [wo] () { wo->erase_data(); }); } /** * Find the workspace set with the given index, or create a new one if it does not exist already. * In addition, take a reference to it. */ void locate_or_create_wset(uint64_t index) { if (available_sets.count(index)) { return; } auto all_wsets = wf::workspace_set_t::get_all(); auto it = std::find_if(all_wsets.begin(), all_wsets.end(), [&] (auto wset) { return wset->get_index() == index; }); if (it == all_wsets.end()) { available_sets[index] = wf::workspace_set_t::create(index); } else { available_sets[index] = (*it)->shared_from_this(); } } bool select_workspace(int index, wf::output_t *wo = wf::get_core().seat->get_active_output()) { if (!wo) { return false; } if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_COMPOSITOR)) { return false; } locate_or_create_wset(index); if (wo->wset() != available_sets[index]) { LOGC(WSET, "Output ", wo->to_string(), " selecting workspace set id=", index); if (auto old_output = available_sets[index]->get_attached_output()) { if (old_output->wset() == available_sets[index]) { // Create new empty wset for the output old_output->set_workspace_set(wf::workspace_set_t::create()); available_sets[old_output->wset()->get_index()] = old_output->wset(); show_workspace_set_overlay(old_output); } } wo->set_workspace_set(available_sets[index]); } // We want to show the overlay even if we remain on the same workspace set show_workspace_set_overlay(wo); cleanup_wsets(); return true; } bool send_window_to(int index, wayfire_toplevel_view view) { auto wo = wf::get_core().seat->get_active_output(); if (!wo) { return false; } if (!wo->can_activate_plugin(wf::CAPABILITY_MANAGE_COMPOSITOR)) { return false; } locate_or_create_wset(index); auto target_wset = available_sets[index]; const auto& old_wset = view->get_wset(); if (old_wset) { old_wset->remove_view(view); } wf::scene::remove_child(view->get_root_node()); wf::emit_view_pre_moved_to_wset_pre(view, old_wset, target_wset); if (view->get_output() != target_wset->get_attached_output()) { view->set_output(target_wset->get_attached_output()); } wf::scene::readd_front(target_wset->get_node(), view->get_root_node()); target_wset->add_view(view); wf::emit_view_moved_to_wset(view, old_wset, target_wset); wf::get_core().seat->refocus(); return true; } wf::signal::connection_t on_new_output = [=] (wf::output_added_signal *ev) { available_sets[ev->output->wset()->get_index()] = ev->output->wset(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_wsets_plugin_t); wayfire-0.10.0/plugins/single_plugins/vk-color-management.cpp0000664000175000017500000000544615053502647024217 0ustar dkondordkondor#include "wayfire/nonstd/wlroots-full.hpp" #include "wayfire/opengl.hpp" #include #include #include #include #include #include class wayfire_passthrough_screen : public wf::per_output_plugin_instance_t { wlr_renderer *vk_renderer = NULL; public: void init() override { if (wf::get_core().is_vulkan()) { LOGE("The vk-color-management plugin is not necessary with the vulkan backend!"); return; } output->render->add_post(&render_hook); vk_renderer = wlr_vk_renderer_create_with_drm_fd(wlr_renderer_get_drm_fd(wf::get_core().renderer)); } wf::post_hook_t render_hook = [=] (wf::auxilliary_buffer_t& source, const wf::render_buffer_t& destination) { GL_CALL(glFinish()); wlr_dmabuf_attributes dmabuf{}; if (!wlr_buffer_get_dmabuf(source.get_buffer(), &dmabuf)) { LOGE("Failed to get dmabuf!"); return; } auto vk_tex = wlr_texture_from_dmabuf(vk_renderer, &dmabuf); if (!vk_tex) { LOGE("Failed to create vk texture!"); return; } // Get the size of the destination buffer auto w = destination.get_size().width; auto h = destination.get_size().height; // Begin a buffer pass with the destination buffer wlr_buffer_pass_options pass_opts{}; // Reset options pass_opts.color_transform = output->render->get_color_transform(); auto pass = wlr_renderer_begin_buffer_pass(vk_renderer, destination.get_buffer(), &pass_opts); // Set up options to render the source texture to the destination wlr_render_texture_options tex{}; tex.texture = vk_tex; // Use the source texture tex.blend_mode = WLR_RENDER_BLEND_MODE_NONE; // Copy the entire source buffer to the entire destination buffer tex.src_box = {0.0, 0.0, (double)source.get_size().width, (double)source.get_size().height}; tex.dst_box = {0, 0, w, h}; tex.filter_mode = WLR_SCALE_FILTER_BILINEAR; // Use bilinear filtering for a smooth copy tex.transform = WL_OUTPUT_TRANSFORM_NORMAL; tex.alpha = NULL; tex.clip = NULL; wlr_render_pass_add_texture(pass, &tex); wlr_render_pass_submit(pass); wlr_texture_destroy(vk_tex); }; void fini() override { if (vk_renderer) { wlr_renderer_destroy(vk_renderer); output->render->rem_post(&render_hook); } } }; // Declare the plugin DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/expo.cpp0000664000175000017500000006042415053502647021321 0ustar dkondordkondor#include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/plugins/ipc/ipc-activator.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include "wayfire/plugins/wobbly/wobbly-signal.hpp" #include #include #include #include #include /* TODO: this file should be included in some header maybe(plugin.hpp) */ #include class wayfire_expo : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { private: wf::point_t convert_workspace_index_to_coords(int index) { index--; // compensate for indexing from 0 auto wsize = output->wset()->get_workspace_grid_size(); int x = index % wsize.width; int y = index / wsize.width; return wf::point_t{x, y}; } wf::option_wrapper_t background_color{"expo/background"}; wf::option_wrapper_t zoom_duration{"expo/duration"}; wf::option_wrapper_t delimiter_offset{"expo/offset"}; wf::option_wrapper_t keyboard_interaction{"expo/keyboard_interaction"}; wf::option_wrapper_t inactive_brightness{"expo/inactive_brightness"}; wf::option_wrapper_t transition_length{"expo/transition_length"}; wf::geometry_animation_t zoom_animation{zoom_duration}; wf::option_wrapper_t move_enable_snap_off{"move/enable_snap_off"}; wf::option_wrapper_t move_snap_off_threshold{"move/snap_off_threshold"}; wf::option_wrapper_t move_join_views{"move/join_views"}; wf::shared_data::ref_ptr_t drag_helper; wf::option_wrapper_t> workspace_bindings{"expo/workspace_bindings"}; std::vector keyboard_select_cbs; std::vector> keyboard_select_options; struct { bool active = false; bool button_pressed = false; bool zoom_in = false; bool accepting_input = false; } state; wf::point_t target_ws, initial_ws; std::unique_ptr wall; wf::key_repeat_t key_repeat; uint32_t key_pressed = 0; /* fade animations for each workspace */ std::vector> ws_fade; std::unique_ptr input_grab; public: void setup_workspace_bindings_from_config() { for (const auto& [workspace, binding] : workspace_bindings.value()) { int workspace_index = atoi(workspace.c_str()); auto wsize = output->wset()->get_workspace_grid_size(); if ((workspace_index > (wsize.width * wsize.height)) || (workspace_index < 1)) { continue; } wf::point_t target = convert_workspace_index_to_coords(workspace_index); keyboard_select_options.push_back(wf::create_option(binding)); keyboard_select_cbs.push_back([=] (auto) { if (!state.active) { return false; } else { if (!zoom_animation.running() || state.zoom_in) { if (target_ws != target) { shade_workspace(target_ws, true); target_ws = target; shade_workspace(target_ws, false); } deactivate(); } } return true; }); } } wf::plugin_activation_data_t grab_interface = { .name = "expo", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, .cancel = [=] () { finalize_and_exit(); }, }; void init() override { input_grab = std::make_unique("expo", output, this, this, this); setup_workspace_bindings_from_config(); wall = std::make_unique(this->output); drag_helper->connect(&on_drag_output_focus); drag_helper->connect(&on_drag_snap_off); drag_helper->connect(&on_drag_done); } bool handle_toggle() { if (!state.active) { return activate(); } else if (!zoom_animation.running() || state.zoom_in) { deactivate(); } return true; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.button != BTN_LEFT) { return; } auto gc = output->get_cursor_position(); handle_input_press(gc.x, gc.y, event.state); } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { handle_input_move({(int)pointer_position.x, (int)pointer_position.y}); } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { if (event.state == WLR_KEY_PRESSED) { if (should_handle_key()) { handle_key_pressed(event.keycode); } } else { if (event.keycode == key_pressed) { key_repeat.disconnect(); key_pressed = 0; } } } void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id > 0) { return; } auto og = output->get_layout_geometry(); handle_input_press(position.x - og.x, position.y - og.y, WLR_BUTTON_PRESSED); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { if (finger_id > 0) { return; } handle_input_press(0, 0, WLR_BUTTON_RELEASED); } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { if (finger_id > 0) // we handle just the first finger { return; } handle_input_move({(int)position.x, (int)position.y}); } bool can_handle_drag() { return output->is_plugin_active(grab_interface.name); } wf::signal::connection_t on_drag_output_focus = [=] (wf::move_drag::drag_focus_output_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { state.button_pressed = true; auto [vw, vh] = output->wset()->get_workspace_grid_size(); drag_helper->set_scale(std::max(vw, vh)); input_grab->set_wants_raw_input(true); } }; wf::signal::connection_t on_drag_snap_off = [=] (wf::move_drag::snap_off_signal *ev) { if ((ev->focus_output == output) && can_handle_drag()) { wf::move_drag::adjust_view_on_snap_off(drag_helper->view); } }; wf::signal::connection_t on_drag_done = [=] (wf::move_drag::drag_done_signal *ev) { if ((ev->focused_output == output) && can_handle_drag() && !drag_helper->is_view_held_in_place()) { bool same_output = ev->main_view->get_output() == output; auto offset = wf::origin(output->get_layout_geometry()); auto local = input_coordinates_to_output_local_coordinates( ev->grab_position + -offset); for (auto& v : wf::move_drag::get_target_views(ev->main_view, ev->join_views)) { translate_wobbly(v, local - (ev->grab_position - offset)); } ev->grab_position = local + offset; wf::move_drag::adjust_view_on_output(ev); if (same_output && (move_started_ws != offscreen_point)) { wf::view_change_workspace_signal data; data.view = ev->main_view; data.from = move_started_ws; data.to = target_ws; output->emit(&data); } move_started_ws = offscreen_point; } input_grab->set_wants_raw_input(false); this->state.button_pressed = false; }; bool activate() { if (!output->activate_plugin(&grab_interface)) { return false; } input_grab->grab_input(wf::scene::layer::OVERLAY); state.active = true; state.button_pressed = false; state.accepting_input = true; start_zoom(true); wall->start_output_renderer(); output->render->add_effect(&pre_frame, wf::OUTPUT_EFFECT_PRE); output->render->schedule_redraw(); auto cws = output->wset()->get_current_workspace(); initial_ws = target_ws = cws; for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { output->add_activator(keyboard_select_options[i], &keyboard_select_cbs[i]); } resize_ws_fade(); on_workspace_grid_changed.disconnect(); output->wset()->connect(&on_workspace_grid_changed); highlight_active_workspace(); return true; } void start_zoom(bool zoom_in) { wall->set_background_color(background_color); wall->set_gap_size(this->delimiter_offset); if (zoom_in) { zoom_animation.set_start(wall->get_workspace_rectangle( output->wset()->get_current_workspace())); /* Make sure workspaces are centered */ auto wsize = output->wset()->get_workspace_grid_size(); auto size = output->get_screen_size(); const int maxdim = std::max(wsize.width, wsize.height); const int gap = this->delimiter_offset; const int fullw = (gap + size.width) * maxdim + gap; const int fullh = (gap + size.height) * maxdim + gap; auto rectangle = wall->get_wall_rectangle(); rectangle.x -= (fullw - rectangle.width) / 2; rectangle.y -= (fullh - rectangle.height) / 2; rectangle.width = fullw; rectangle.height = fullh; zoom_animation.set_end(rectangle); } else { zoom_animation.set_start(zoom_animation); zoom_animation.set_end( wall->get_workspace_rectangle(target_ws)); } state.zoom_in = zoom_in; zoom_animation.start(); wall->set_viewport(zoom_animation); } void deactivate() { state.accepting_input = false; start_zoom(false); output->wset()->set_workspace(target_ws); for (size_t i = 0; i < keyboard_select_cbs.size(); i++) { output->rem_binding(&keyboard_select_cbs[i]); } } wf::geometry_t get_grid_geometry() { auto wsize = output->wset()->get_workspace_grid_size(); auto full_g = output->get_layout_geometry(); wf::geometry_t grid; grid.x = grid.y = 0; grid.width = full_g.width * wsize.width; grid.height = full_g.height * wsize.height; return grid; } /** * Handle an input press event. * * @param x, y The position of the event in output-local coordinates. */ void handle_input_press(int32_t x, int32_t y, uint32_t state) { const bool zooming = zoom_animation.running(); if (!this->state.active) { return; } if (zooming && !this->state.zoom_in) { if (state == WLR_BUTTON_PRESSED) { // Zoom out of expo, change target workspace only if (update_target_workspace(x, y)) { output->wset()->set_workspace(target_ws); // Calculate animation so that it appears as if we smoothly changed destination wf::geometry_t cur_viewport = zoom_animation; wf::geometry_t target_viewport = wall->get_workspace_rectangle(target_ws); float cur_a = std::clamp(zoom_animation.progress(), 0.01, 0.99); wf::geometry_t start_viewport = wf::interpolate(cur_viewport, target_viewport, 1.0 - 1.0 / (1.0 - cur_a)); zoom_animation.set_start(start_viewport); zoom_animation.set_end(target_viewport); } } return; } if ((state == WLR_BUTTON_RELEASED) && !this->drag_helper->view) { this->state.button_pressed = false; deactivate(); } else if (state == WLR_BUTTON_RELEASED) { this->state.button_pressed = false; this->drag_helper->handle_input_released(); } else { this->state.button_pressed = true; drag_helper->set_pending_drag(wf::get_core().get_cursor_position()); update_target_workspace(x, y); } } void start_moving(wayfire_toplevel_view view, wf::point_t local_grab) { if (!(view->get_allowed_actions() & (wf::VIEW_ALLOW_WS_CHANGE | wf::VIEW_ALLOW_MOVE))) { return; } auto ws_coords = input_coordinates_to_output_local_coordinates(local_grab); auto bbox = wf::view_bounding_box_up_to(view, "wobbly"); view->damage(); // Make sure that the view is in output-local coordinates! translate_wobbly(view, local_grab - ws_coords); auto [vw, vh] = output->wset()->get_workspace_grid_size(); wf::move_drag::drag_options_t opts; opts.initial_scale = std::max(vw, vh); opts.enable_snap_off = move_enable_snap_off && (view->pending_fullscreen() || view->pending_tiled_edges()); opts.snap_off_threshold = move_snap_off_threshold; opts.join_views = move_join_views; drag_helper->start_drag(view, wf::move_drag::find_relative_grab(bbox, ws_coords), opts); move_started_ws = target_ws; input_grab->set_wants_raw_input(true); } const wf::point_t offscreen_point = {-10, -10}; void handle_input_move(wf::point_t to) { if (!state.button_pressed) { return; } auto local = to - wf::origin(output->get_layout_geometry()); if (drag_helper->view) { drag_helper->handle_motion(to); update_target_workspace(local.x, local.y); return; } if (!drag_helper->should_start_pending_drag(to) || zoom_animation.running()) { /* Ignore small movements */ return; } auto local_grab = *drag_helper->tentative_grab_position - wf::origin(output->get_layout_geometry()); if (auto view = find_view_at_coordinates(local_grab.x, local_grab.y)) { start_moving(view, local_grab); drag_helper->handle_motion(to); } /* As input coordinates are always positive, this will ensure that any * subsequent motion events while grabbed are allowed */ update_target_workspace(local.x, local.y); } /** * Helper to determine if keyboard presses should be handled */ bool should_handle_key() { return state.accepting_input && keyboard_interaction && !state.button_pressed; } void handle_key_pressed(uint32_t key) { wf::point_t old_target = target_ws; switch (key) { case KEY_ENTER: deactivate(); return; case KEY_ESC: target_ws = initial_ws; shade_workspace(old_target, true); shade_workspace(target_ws, false); deactivate(); return; case KEY_UP: case KEY_K: target_ws.y -= 1; break; case KEY_DOWN: case KEY_J: target_ws.y += 1; break; case KEY_RIGHT: case KEY_L: target_ws.x += 1; break; case KEY_LEFT: case KEY_H: target_ws.x -= 1; break; default: return; } /* this part is only reached if one of the arrow keys is pressed */ if (key != key_pressed) { // update key repeat callbacks // (note: this will disconnect any previous callback) key_repeat.set_callback(key, [this] (uint32_t key) { if (!should_handle_key()) { // disconnect if key events should no longer be handled key_pressed = 0; return false; } handle_key_pressed(key); return true; // repeat }); key_pressed = key; } // ensure that the new target is valid (use wrap-around) auto dim = output->wset()->get_workspace_grid_size(); target_ws.x = (target_ws.x + dim.width) % dim.width; target_ws.y = (target_ws.y + dim.height) % dim.height; shade_workspace(old_target, true); shade_workspace(target_ws, false); } /** * shade all but the selected workspace instantly (without animation) */ void highlight_active_workspace() { auto dim = output->wset()->get_workspace_grid_size(); for (int x = 0; x < dim.width; x++) { for (int y = 0; y < dim.height; y++) { if ((x == target_ws.x) && (y == target_ws.y)) { wall->set_ws_dim({x, y}, 1.0); } else { wall->set_ws_dim({x, y}, inactive_brightness); } } } } /** * start an animation for shading the given workspace */ void shade_workspace(const wf::point_t& ws, bool shaded) { double target = shaded ? inactive_brightness : 1.0; auto& anim = ws_fade.at(ws.x).at(ws.y); if (anim.running()) { anim.animate(target); } else { anim.animate(shaded ? 1.0 : inactive_brightness, target); } output->render->schedule_redraw(); } wf::point_t move_started_ws = offscreen_point; /** * Find the coordinate of the given point from output-local coordinates * to coordinates relative to the first workspace (i.e (0,0)) */ void input_coordinates_to_global_coordinates(int & sx, int & sy) { auto og = output->get_layout_geometry(); auto wsize = output->wset()->get_workspace_grid_size(); float max = std::max(wsize.width, wsize.height); float grid_start_x = og.width * (max - wsize.width) / float(max) / 2; float grid_start_y = og.height * (max - wsize.height) / float(max) / 2; sx -= grid_start_x; sy -= grid_start_y; sx *= max; sy *= max; } /** * Find the coordinate of the given point from output-local coordinates * to output-workspace-local coordinates */ wf::point_t input_coordinates_to_output_local_coordinates(wf::point_t ip) { input_coordinates_to_global_coordinates(ip.x, ip.y); auto cws = output->wset()->get_current_workspace(); auto og = output->get_relative_geometry(); /* Translate coordinates into output-local coordinate system, * relative to the current workspace */ return { ip.x - cws.x * og.width, ip.y - cws.y * og.height, }; } wayfire_toplevel_view find_view_at_coordinates(int gx, int gy) { auto local = input_coordinates_to_output_local_coordinates({gx, gy}); wf::pointf_t localf = {1.0 * local.x, 1.0 * local.y}; auto view = wf::find_output_view_at(output, localf); if (view && view->is_mapped()) { return view; } else { return nullptr; } } std::optional find_workspace_at(int x, int y) { wlr_box box = {x, y, 1, 1}; auto og = output->get_relative_geometry(); auto in_grid = wf::origin(wf::scale_box(og, wall->get_viewport(), box)); auto grid = output->wset()->get_workspace_grid_size(); for (int i = 0; i < grid.width; i++) { for (int j = 0; j < grid.height; j++) { if (wall->get_workspace_rectangle({i, j}) & in_grid) { return wf::point_t{i, j}; } } } return std::nullopt; } bool update_target_workspace(int x, int y) { auto tmp = find_workspace_at(x, y); if (tmp.has_value() && (tmp != target_ws)) { shade_workspace(target_ws, true); target_ws = tmp.value(); shade_workspace(target_ws, false); return true; } return false; } wf::effect_hook_t pre_frame = [=] () { if (zoom_animation.running()) { wall->set_viewport(zoom_animation); } else if (!state.zoom_in) { finalize_and_exit(); return; } auto size = this->output->wset()->get_workspace_grid_size(); for (int x = 0; x < size.width; x++) { for (int y = 0; y < size.height; y++) { auto& anim = ws_fade.at(x).at(y); if (anim.running()) { wall->set_ws_dim({x, y}, anim); } } } }; void resize_ws_fade() { auto size = this->output->wset()->get_workspace_grid_size(); ws_fade.resize(size.width); for (auto& v : ws_fade) { size_t h = size.height; if (v.size() > h) { v.resize(h); } else { while (v.size() < h) { v.emplace_back(transition_length); } } } } wf::signal::connection_t on_workspace_grid_changed = [=] (auto) { resize_ws_fade(); // check that the target and initial workspaces are still in the grid auto size = this->output->wset()->get_workspace_grid_size(); initial_ws.x = std::min(initial_ws.x, size.width - 1); initial_ws.y = std::min(initial_ws.y, size.height - 1); if ((target_ws.x >= size.width) || (target_ws.y >= size.height)) { target_ws.x = std::min(target_ws.x, size.width - 1); target_ws.y = std::min(target_ws.y, size.height - 1); highlight_active_workspace(); } }; void finalize_and_exit() { state.active = false; if (drag_helper->view) { drag_helper->handle_input_released(); } output->deactivate_plugin(&grab_interface); input_grab->ungrab_input(); wall->stop_output_renderer(true); output->render->rem_effect(&pre_frame); key_repeat.disconnect(); key_pressed = 0; on_workspace_grid_changed.disconnect(); } void fini() override { if (state.active) { finalize_and_exit(); } } }; class wayfire_expo_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { wf::ipc_activator_t toggle_binding{"expo/toggle"}; public: void init() override { this->init_output_tracking(); toggle_binding.set_handler(toggle_cb); } void fini() override { this->fini_output_tracking(); } wf::ipc_activator_t::handler_t toggle_cb = [=] (wf::output_t *output, wayfire_view) { return this->output_instance[output]->handle_toggle(); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_expo_global); wayfire-0.10.0/plugins/single_plugins/alpha.cpp0000664000175000017500000001450615053502647021433 0ustar dkondordkondor/* * The MIT License (MIT) * * Copyright (c) 2018 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" class wayfire_alpha : public wf::plugin_interface_t { wf::option_wrapper_t modifier{"alpha/modifier"}; wf::option_wrapper_t min_value{"alpha/min_value"}; wf::plugin_activation_data_t grab_interface{ .name = "alpha", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP, }; wf::shared_data::ref_ptr_t ipc_repo; public: void init() override { min_value.set_callback(min_value_changed); wf::get_core().bindings->add_axis(modifier, &axis_cb); ipc_repo->register_method("wf/alpha/set-view-alpha", ipc_set_view_alpha); ipc_repo->register_method("wf/alpha/get-view-alpha", ipc_get_view_alpha); } wf::ipc::method_callback ipc_set_view_alpha = [=] (wf::json_t data) -> wf::json_t { auto view_id = wf::ipc::json_get_uint64(data, "view-id"); auto alpha = wf::ipc::json_get_double(data, "alpha"); auto view = wf::ipc::find_view_by_id(view_id); if (view && view->is_mapped()) { auto tr = ensure_transformer(view); adjust_alpha(view, tr, alpha); } else { return wf::ipc::json_error("Failed to find view with given id. Maybe it was closed?"); } return wf::ipc::json_ok(); }; wf::ipc::method_callback ipc_get_view_alpha = [=] (wf::json_t data) { auto view_id = wf::ipc::json_get_uint64(data, "view-id"); auto view = wf::ipc::find_view_by_id(view_id); if (!view) { return wf::ipc::json_error("Failed to find view with given id. Maybe it was closed?"); } auto tmgr = view->get_transformed_node(); auto transformer = tmgr->get_transformer("alpha"); auto response = wf::ipc::json_ok(); if (transformer) { response["alpha"] = transformer->alpha; } else { response["alpha"] = 1.0; } return response; }; std::shared_ptr ensure_transformer(wayfire_view view) { auto tmgr = view->get_transformed_node(); if (!tmgr->get_transformer("alpha")) { auto node = std::make_shared(view); tmgr->add_transformer(node, wf::TRANSFORMER_2D, "alpha"); } return tmgr->get_transformer("alpha"); } void adjust_alpha(wayfire_view view, std::shared_ptr tr, float alpha) { tr->alpha = alpha; if (alpha == 1.0) { return view->get_transformed_node()->rem_transformer("alpha"); } else { view->damage(); } } void update_alpha(wayfire_view view, float delta) { auto transformer = ensure_transformer(view); float desired_value = std::clamp(transformer->alpha - delta * 0.003, (double)min_value, 1.0); adjust_alpha(view, transformer, desired_value); } wf::axis_callback axis_cb = [=] (wlr_pointer_axis_event *ev) { auto gc = wf::get_core().get_cursor_position(); auto current_output = wf::get_core().output_layout->get_output_coords_at(gc, gc); if (!current_output || !current_output->can_activate_plugin(&grab_interface)) { return false; } auto view = wf::get_core().get_cursor_focus_view(); if (!view) { return false; } auto layer = wf::get_view_layer(view).value_or(wf::scene::layer::BACKGROUND); if (layer == wf::scene::layer::BACKGROUND) { return false; } if (ev->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL) { update_alpha(view, ev->delta); return true; } return false; }; wf::config::option_base_t::updated_callback_t min_value_changed = [=] () { for (auto& view : wf::get_core().get_all_views()) { auto tmgr = view->get_transformed_node(); auto transformer = tmgr->get_transformer("alpha"); if (transformer && (transformer->alpha < min_value)) { transformer->alpha = min_value; view->damage(); } } }; void fini() override { for (auto& view : wf::get_core().get_all_views()) { view->get_transformed_node()->rem_transformer("alpha"); } wf::get_core().bindings->rem_binding(&axis_cb); ipc_repo->unregister_method("wf/alpha/set-view-alpha"); ipc_repo->unregister_method("wf/alpha/get-view-alpha"); } }; DECLARE_WAYFIRE_PLUGIN(wayfire_alpha); wayfire-0.10.0/plugins/single_plugins/wrot.cpp0000664000175000017500000002157215053502647021342 0ustar dkondordkondor#include #include "wayfire/debug.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/view.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include #include #include #include #include #include static const char *transformer_3d = "wrot-3d"; static const char *transformer_2d = "wrot-2d"; static double cross(double x1, double y1, double x2, double y2) // cross product { return x1 * y2 - x2 * y1; } static double vlen(double x1, double y1) // length of vector centered at the origin { return std::sqrt(x1 * x1 + y1 * y1); } enum class mode { NONE, ROT_2D, ROT_3D, }; class wf_wrot : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t { wf::button_callback call; wf::option_wrapper_t reset_radius{"wrot/reset_radius"}; wf::option_wrapper_t sensitivity{"wrot/sensitivity"}; wf::option_wrapper_t invert{"wrot/invert"}; wf::pointf_t last_position; wayfire_toplevel_view current_view = nullptr; std::unique_ptr input_grab; mode current_mode = mode::NONE; void reset_all() { for (auto v : wf::get_core().get_all_views()) { v->get_transformed_node()->rem_transformer(transformer_2d); v->get_transformed_node()->rem_transformer(transformer_3d); } } wf::button_callback call_3d = [this] (auto) { if (current_mode != mode::NONE) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } current_view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (!current_view || (current_view->role != wf::VIEW_ROLE_TOPLEVEL)) { output->deactivate_plugin(&grab_interface); return false; } wf::get_core().default_wm->focus_raise_view(current_view); current_view->connect(¤t_view_unmapped); input_grab->grab_input(wf::scene::layer::OVERLAY); last_position = output->get_cursor_position(); current_mode = mode::ROT_3D; return false; // pass btn press to grab node }; wf::key_callback reset = [this] (auto) { reset_all(); return true; }; wf::key_callback reset_one = [this] (auto) { auto view = wf::get_active_view_for_output(output); if (view) { view->get_transformed_node()->rem_transformer(transformer_2d); view->get_transformed_node()->rem_transformer(transformer_3d); } return true; }; wf::signal::connection_t current_view_unmapped = [this] (auto) { if (input_grab->is_grabbed()) { current_view = nullptr; input_released(); } }; void motion_2d(int x, int y) { auto tr = wf::ensure_named_transformer( current_view, wf::TRANSFORMER_2D, transformer_2d, current_view); current_view->get_transformed_node()->begin_transform_update(); auto g = current_view->get_geometry(); double cx = g.x + g.width / 2.0; double cy = g.y + g.height / 2.0; double x1 = last_position.x - cx, y1 = last_position.y - cy; double x2 = x - cx, y2 = y - cy; if (vlen(x2, y2) <= reset_radius) { current_view->get_transformed_node()->end_transform_update(); current_view->get_transformed_node()->rem_transformer(transformer_2d); return; } /* cross(a, b) = |a| * |b| * sin(a, b) */ tr->angle -= std::asin(cross(x1, y1, x2, y2) / vlen(x1, y1) / vlen(x2, y2)); current_view->get_transformed_node()->end_transform_update(); last_position = {1.0 * x, 1.0 * y}; } void motion_3d(int x, int y) { if ((x == last_position.x) && (y == last_position.y)) { return; } auto tr = wf::ensure_named_transformer( current_view, wf::TRANSFORMER_3D, transformer_3d, current_view); current_view->get_transformed_node()->begin_transform_update(); float dx = x - last_position.x; float dy = y - last_position.y; float ascale = glm::radians(sensitivity / 60.0f); float dir = invert ? -1.f : 1.f; tr->rotation = glm::rotate(tr->rotation, vlen(dx, dy) * ascale, {dir *dy, dir * dx, 0}); current_view->get_transformed_node()->end_transform_update(); last_position = {(double)x, (double)y}; } wf::plugin_activation_data_t grab_interface = { .name = "wrot", .capabilities = wf::CAPABILITY_GRAB_INPUT, }; public: void init() override { input_grab = std::make_unique("wrot", output, nullptr, this, nullptr); call = [=] (auto) { if (current_mode != mode::NONE) { return false; } if (!output->activate_plugin(&grab_interface)) { return false; } current_view = toplevel_cast(wf::get_core().get_cursor_focus_view()); if (!current_view || (current_view->role != wf::VIEW_ROLE_TOPLEVEL)) { output->deactivate_plugin(&grab_interface); return false; } wf::get_core().default_wm->focus_raise_view(current_view); current_view->connect(¤t_view_unmapped); input_grab->grab_input(wf::scene::layer::OVERLAY); last_position = output->get_cursor_position(); current_mode = mode::ROT_2D; return false; // pass btn press to the grab node }; output->add_button(wf::option_wrapper_t("wrot/activate"), &call); output->add_button(wf::option_wrapper_t("wrot/activate-3d"), &call_3d); output->add_key(wf::option_wrapper_t{"wrot/reset"}, &reset); output->add_key(wf::option_wrapper_t{"wrot/reset-one"}, &reset_one); grab_interface.cancel = [=] () { if (input_grab->is_grabbed()) { input_released(); } }; } void handle_pointer_button(const wlr_pointer_button_event& event) override { if (event.state == WL_POINTER_BUTTON_STATE_RELEASED) { input_released(); } } void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) override { if (current_view && current_view->get_output()) { auto og = current_view->get_output()->get_layout_geometry(); pointer_position.x -= og.x; pointer_position.y -= og.y; } if (current_mode == mode::ROT_2D) { motion_2d(pointer_position.x, pointer_position.y); } else if (current_mode == mode::ROT_3D) { motion_3d(pointer_position.x, pointer_position.y); } } void input_released() { input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); current_view_unmapped.disconnect(); if ((current_mode == mode::ROT_3D) && current_view) { auto tr = current_view->get_transformed_node() ->get_transformer(transformer_3d); if (tr) { /* check if the view was rotated to perpendicular to the screen * and move it a bit more, so it will not get "stuck" that way */ glm::vec4 n{0, 0, 1.f, 0}; glm::vec4 x = tr->rotation * n; auto dot = glm::dot(n, x); if (std::abs(dot) < 0.05) { /* rotate 2.5 degrees around an axis perpendicular to x */ current_view->get_transformed_node()->begin_transform_update(); tr->rotation = glm::rotate(tr->rotation, glm::radians( dot < 0 ? -2.5f : 2.5f), {x.y, -x.x, 0}); current_view->get_transformed_node()->end_transform_update(); } } } current_mode = mode::NONE; } void fini() override { if (input_grab->is_grabbed()) { input_released(); } reset_all(); output->rem_binding(&call); output->rem_binding(&call_3d); output->rem_binding(&reset); output->rem_binding(&reset_one); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/fast-switcher.cpp0000664000175000017500000001477615053502647023142 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/plugins/common/input-grab.hpp" #include "wayfire/plugins/common/util.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/view-helpers.hpp" #include #include #include #include #include #include #include #include #include /* * This plugin provides abilities to switch between views. * It works similarly to the alt-esc binding in Windows or GNOME */ class wayfire_fast_switcher : public wf::per_output_plugin_instance_t, public wf::keyboard_interaction_t { wf::option_wrapper_t activate_key{"fast-switcher/activate"}; wf::option_wrapper_t activate_key_backward{ "fast-switcher/activate_backward"}; wf::option_wrapper_t inactive_alpha{"fast-switcher/inactive_alpha"}; std::vector views; // all views on current viewport size_t current_view_index = 0; // the modifiers which were used to activate switcher uint32_t activating_modifiers = 0; bool active = false; std::unique_ptr input_grab; wf::plugin_activation_data_t grab_interface = { .name = "fast-switcher", .capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR, }; public: void init() override { output->add_key(activate_key, &fast_switch); output->add_key(activate_key_backward, &fast_switch_backward); input_grab = std::make_unique("fast-switch", output, this, nullptr, nullptr); grab_interface.cancel = [=] () { switch_terminate(); }; } void handle_keyboard_key(wf::seat_t*, wlr_keyboard_key_event event) override { auto mod = wf::get_core().seat->modifier_from_keycode(event.keycode); if ((event.state == WLR_KEY_RELEASED) && (mod & activating_modifiers)) { switch_terminate(); } } void view_chosen(int i, bool reorder_only) { /* No view available */ if (!((0 <= i) && (i < (int)views.size()))) { return; } current_view_index = i; set_view_highlighted(views[i], true); for (int i = (int)views.size() - 1; i >= 0; i--) { wf::view_bring_to_front(views[i]); } if (reorder_only) { wf::view_bring_to_front(views[i]); } else { wf::get_core().default_wm->focus_raise_view(views[i]); } } wf::signal::connection_t cleanup_view = [=] (wf::view_disappeared_signal *ev) { size_t i = 0; for (; i < views.size() && views[i] != ev->view; i++) {} if (i == views.size()) { return; } views.erase(views.begin() + i); if (views.empty()) { switch_terminate(); return; } if (i <= current_view_index) { int new_index = (current_view_index + views.size() - 1) % views.size(); view_chosen(new_index, true); } }; const std::string transformer_name = "fast-switcher"; void set_view_alpha(wayfire_view view, float alpha) { auto tr = wf::ensure_named_transformer( view, wf::TRANSFORMER_2D, transformer_name, view); view->get_transformed_node()->begin_transform_update(); tr->alpha = alpha; view->get_transformed_node()->end_transform_update(); } void set_view_highlighted(wayfire_toplevel_view view, bool selected) { // set alpha and decoration to indicate selected view view->set_activated(selected); // changes decoration focus state double alpha = selected ? 1.0 : inactive_alpha; set_view_alpha(view, alpha); } void update_views() { views = output->wset()->get_views( wf::WSET_CURRENT_WORKSPACE | wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED); std::sort(views.begin(), views.end(), [] (wayfire_toplevel_view& a, wayfire_toplevel_view& b) { return wf::get_focus_timestamp(a) > wf::get_focus_timestamp(b); }); } bool do_switch(bool forward) { if (active) { switch_next(forward); return true; } if (!output->activate_plugin(&grab_interface)) { return false; } update_views(); if (views.size() < 1) { output->deactivate_plugin(&grab_interface); return false; } current_view_index = 0; active = true; /* Set all to semi-transparent */ for (auto view : views) { set_view_highlighted(view, false); } input_grab->grab_input(wf::scene::layer::OVERLAY); activating_modifiers = wf::get_core().seat->get_keyboard_modifiers(); switch_next(forward); output->connect(&cleanup_view); return true; } wf::key_callback fast_switch = [=] (auto) { return do_switch(true); }; wf::key_callback fast_switch_backward = [=] (auto) { return do_switch(false); }; void switch_terminate() { // May modify alpha view_chosen(current_view_index, false); // Ungrab after selecting the correct view, so that it gets the focus directly input_grab->ungrab_input(); output->deactivate_plugin(&grab_interface); // Remove transformers after modifying alpha for (auto view : views) { view->get_transformed_node()->rem_transformer(transformer_name); } active = false; cleanup_view.disconnect(); } void switch_next(bool forward) { set_view_highlighted(views[current_view_index], false); // deselect last view int index = current_view_index; if (forward) { index = (index + 1) % views.size(); } else { index = index ? index - 1 : views.size() - 1; } view_chosen(index, true); } void fini() override { if (active) { switch_terminate(); } output->rem_binding(&fast_switch); output->rem_binding(&fast_switch_backward); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/xkb-bindings.cpp0000664000175000017500000000777615053502647022740 0ustar dkondordkondor#include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include #include #include "wayfire/core.hpp" #include "wayfire/seat.hpp" #include namespace wf { static std::string to_lower(std::string str) { std::transform(str.begin(), str.end(), str.begin(), tolower); return str; } static const std::map mod_name_to_mod = { {to_lower(XKB_MOD_NAME_SHIFT), WLR_MODIFIER_SHIFT}, {to_lower(XKB_MOD_NAME_CAPS), WLR_MODIFIER_CAPS}, {to_lower(XKB_MOD_NAME_CTRL), WLR_MODIFIER_CTRL}, {"ctrl", WLR_MODIFIER_CTRL}, {to_lower(XKB_MOD_NAME_ALT), WLR_MODIFIER_ALT}, {"alt", WLR_MODIFIER_ALT}, {to_lower(XKB_MOD_NAME_NUM), WLR_MODIFIER_MOD2}, {"mod3", WLR_MODIFIER_MOD3}, {to_lower(XKB_MOD_NAME_LOGO), WLR_MODIFIER_LOGO}, {"mod5", WLR_MODIFIER_MOD5}, }; static uint32_t parse_modifier(std::string mod_name) { auto it = mod_name_to_mod.find(to_lower(mod_name)); if (it != mod_name_to_mod.end()) { return it->second; } return 0; } std::vector tokenize_at(std::string str, char token) { std::vector tokens; std::istringstream iss(str); std::string token_str; while (std::getline(iss, token_str, token)) { tokens.push_back(token_str); } return tokens; } struct xkb_binding_t { uint32_t mods; std::string keysym; bool operator ==(const xkb_binding_t& other) const { return (mods == other.mods) && (keysym == other.keysym); } }; static std::optional parse_keybinding_xkb(std::string binding) { binding.erase(std::remove(binding.begin(), binding.end(), ' '), binding.end()); auto tokens = tokenize_at(binding, '+'); if (tokens.empty()) { return std::nullopt; } uint32_t mods = 0; for (size_t i = 0; i < tokens.size() - 1; i++) { auto mod = parse_modifier(tokens[i]); if (!mod) { return std::nullopt; } mods |= mod; } return xkb_binding_t{mods, tokens.back()}; } class xkb_bindings_t : public wf::plugin_interface_t { public: void init() override { wf::get_core().connect(&on_parse_extension); wf::get_core().connect(&on_keyboard_key); wf::get_core().bindings->reparse_extensions(); } void fini() override { on_parse_extension.disconnect(); wf::get_core().bindings->reparse_extensions(); } wf::signal::connection_t on_parse_extension = [=] (parse_activator_extension_signal *ev) { if (auto binding = parse_keybinding_xkb(ev->extension_binding)) { ev->tag = binding.value(); } }; wf::signal::connection_t> on_keyboard_key = [=] (input_event_signal *ev) { if (!ev->device || (ev->mode == wf::input_event_processing_mode_t::IGNORE) || (ev->event->state != WL_KEYBOARD_KEY_STATE_PRESSED)) { return; } auto keyboard = wlr_keyboard_from_input_device(ev->device); xkb_keysym_t sym = xkb_state_key_get_one_sym(keyboard->xkb_state, ev->event->keycode + 8); if (sym == XKB_KEY_NoSymbol) { return; } static constexpr size_t max_keysym_len = 128; char buffer[max_keysym_len]; size_t len = xkb_keysym_get_name(sym, buffer, max_keysym_len); std::string keysym = std::string(buffer, len); uint32_t mods = wf::get_core().seat->get_keyboard_modifiers(); wf::activator_data_t data = { .source = wf::activator_source_t::KEYBINDING, .activation_data = ev->event->keycode }; if (wf::get_core().bindings->handle_extension(xkb_binding_t{mods, keysym}, data)) { ev->mode = wf::input_event_processing_mode_t::NO_CLIENT; } }; }; } DECLARE_WAYFIRE_PLUGIN(wf::xkb_bindings_t); wayfire-0.10.0/plugins/single_plugins/command.cpp0000664000175000017500000003526715053502647021773 0ustar dkondordkondor#include "wayfire/plugins/ipc/ipc-helpers.hpp" #include "wayfire/plugins/ipc/ipc-method-repository.hpp" #include "wayfire/bindings.hpp" #include "wayfire/plugin.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Initial repeat delay passed */ static int repeat_delay_timeout_handler(void *callback) { (*reinterpret_cast*>(callback))(); return 1; // disconnect } /* Between each repeat */ static int repeat_once_handler(void *callback) { (*reinterpret_cast*>(callback))(); return 1; // continue timer } /* Provides a way to bind specific commands to activator bindings. * * It supports 4 modes: * * 1. Regular bindings * 2. Repeatable bindings - for example, if the user binds a keybinding, then * after a specific delay the command begins to be executed repeatedly, until * the user released the key. In the config file, repeatable bindings have the * prefix repeatable_ * 3. Always bindings - bindings that can be executed even if a plugin is already * active, or if the screen is locked. They have a prefix always_ * 4. Release bindings - Execute a command when a binding is released. Useful for * Push-To-Talk. They have a prefix release_ * */ class wayfire_command : public wf::plugin_interface_t { std::vector bindings; struct ipc_binding_t { wf::activator_callback callback; wf::ipc::client_interface_t *client; }; static inline uint64_t binding_to_id(const ipc_binding_t& binding) { return (std::uintptr_t)(&binding.callback); } std::list ipc_bindings; using command_callback = std::function; struct { uint32_t pressed_button = 0; uint32_t pressed_key = 0; command_callback callback; } repeat; wl_event_source *repeat_source = NULL; wl_event_source *repeat_delay_source = NULL; enum binding_mode { BINDING_NORMAL, BINDING_REPEAT, BINDING_RELEASE, }; bool on_binding(command_callback callback, binding_mode mode, bool exec_always, const wf::activator_data_t& data) { /* We already have a repeatable command, do not accept further bindings */ if (repeat.pressed_key || repeat.pressed_button) { return false; } auto focused_output = wf::get_core().seat->get_active_output(); if (!exec_always && !focused_output->can_activate_plugin(&grab_interface)) { return false; } if (mode == BINDING_RELEASE) { repeat.callback = callback; if ((data.source == wf::activator_source_t::KEYBINDING) || (data.source == wf::activator_source_t::MODIFIERBINDING)) { repeat.pressed_key = data.activation_data; wf::get_core().connect(&on_key_event_release); } else { repeat.pressed_button = data.activation_data; wf::get_core().connect(&on_button_event_release); } return true; } else { callback(); } /* No repeat necessary in any of those cases */ if ((mode != BINDING_REPEAT) || (data.source == wf::activator_source_t::GESTURE) || (data.activation_data == 0)) { return true; } repeat.callback = callback; if (data.source == wf::activator_source_t::KEYBINDING) { repeat.pressed_key = data.activation_data; } else { repeat.pressed_button = data.activation_data; } repeat_delay_source = wl_event_loop_add_timer(wf::get_core().ev_loop, repeat_delay_timeout_handler, &on_repeat_delay_timeout); wl_event_source_timer_update(repeat_delay_source, wf::option_wrapper_t("input/kb_repeat_delay")); wf::get_core().connect(&on_button_event); wf::get_core().connect(&on_key_event); return true; } std::function on_repeat_delay_timeout = [=] () { repeat_delay_source = NULL; repeat_source = wl_event_loop_add_timer(wf::get_core().ev_loop, repeat_once_handler, &on_repeat_once); on_repeat_once(); }; std::function on_repeat_once = [=] () { uint32_t repeat_rate = wf::option_wrapper_t("input/kb_repeat_rate"); if ((repeat_rate <= 0) || (repeat_rate > 1000)) { return reset_repeat(); } wl_event_source_timer_update(repeat_source, 1000 / repeat_rate); repeat.callback(); }; void reset_repeat() { if (repeat_delay_source) { wl_event_source_remove(repeat_delay_source); repeat_delay_source = NULL; } if (repeat_source) { wl_event_source_remove(repeat_source); repeat_source = NULL; } repeat.pressed_key = repeat.pressed_button = 0; on_button_event.disconnect(); on_key_event.disconnect(); } wf::signal::connection_t> on_button_event = [=] (wf::input_event_signal *ev) { if ((ev->event->button == repeat.pressed_button) && (ev->event->state == WL_POINTER_BUTTON_STATE_RELEASED)) { reset_repeat(); } }; wf::signal::connection_t> on_key_event = [=] (wf::input_event_signal *ev) { if ((ev->event->keycode == repeat.pressed_key) && (ev->event->state == WL_KEYBOARD_KEY_STATE_RELEASED)) { reset_repeat(); } }; wf::signal::connection_t> on_key_event_release = [=] (wf::input_event_signal *ev) { if ((ev->event->keycode == repeat.pressed_key) && (ev->event->state == WL_KEYBOARD_KEY_STATE_RELEASED)) { repeat.callback(); repeat.pressed_key = repeat.pressed_button = 0; on_key_event_release.disconnect(); } }; wf::signal::connection_t> on_button_event_release = [=] (wf::input_event_signal *ev) { if ((ev->event->button == repeat.pressed_button) && (ev->event->state == WL_POINTER_BUTTON_STATE_RELEASED)) { repeat.callback(); repeat.pressed_key = repeat.pressed_button = 0; on_button_event_release.disconnect(); } }; wf::shared_data::ref_ptr_t method_repository; public: wf::option_wrapper_t> regular_bindings{"command/bindings"}; wf::option_wrapper_t> repeat_bindings{ "command/repeatable_bindings" }; wf::option_wrapper_t> always_bindings{ "command/always_bindings" }; wf::option_wrapper_t> release_bindings{ "command/release_bindings" }; std::function setup_bindings_from_config = [=] () { clear_bindings(); using namespace std::placeholders; auto regular = regular_bindings.value(); auto repeatable = repeat_bindings.value(); auto always = always_bindings.value(); auto release = release_bindings.value(); bindings.resize( regular.size() + repeatable.size() + always.size() + release.size()); size_t i = 0; const auto& push_bindings = [&] (wf::config::compound_list_t& list, binding_mode mode, bool always_exec = false) { for (const auto& [_, _cmd, activator] : list) { std::string cmd = _cmd; command_callback cb = [cmd] () -> bool { return wf::get_core().run(cmd); }; bindings[i] = std::bind(std::mem_fn(&wayfire_command::on_binding), this, cb, mode, always_exec, _1); wf::get_core().bindings->add_activator(wf::create_option(activator), &bindings[i]); ++i; } }; push_bindings(regular, BINDING_NORMAL); push_bindings(repeatable, BINDING_REPEAT); push_bindings(always, BINDING_NORMAL, true); push_bindings(release, BINDING_RELEASE); }; void clear_bindings() { for (auto& binding : bindings) { wf::get_core().bindings->rem_binding(&binding); } bindings.clear(); } wf::signal::connection_t on_reload_config = [=] (auto) { setup_bindings_from_config(); }; wf::plugin_activation_data_t grab_interface = { .name = "command", .capabilities = wf::CAPABILITY_GRAB_INPUT, }; void init() { using namespace std::placeholders; setup_bindings_from_config(); wf::get_core().connect(&on_reload_config); method_repository->connect(&on_client_disconnect); method_repository->register_method("command/register-binding", on_register_binding); method_repository->register_method("command/unregister-binding", on_unregister_binding); method_repository->register_method("command/clear-bindings", on_clear_ipc_bindings); } void fini() { method_repository->unregister_method("command/register-binding"); method_repository->unregister_method("command/unregister-binding"); method_repository->unregister_method("command/clear-bindings"); clear_bindings(); } wf::ipc::method_callback_full on_register_binding = [&] (const wf::json_t& js, wf::ipc::client_interface_t *client) { auto binding_str = wf::ipc::json_get_string(js, "binding"); auto mode_str = wf::ipc::json_get_optional_string(js, "mode"); auto exec_always = wf::ipc::json_get_optional_bool(js, "exec-always").value_or(false); auto call_method = wf::ipc::json_get_optional_string(js, "call-method"); auto command = wf::ipc::json_get_optional_string(js, "command"); if (call_method.has_value() && !js.has_member("call-data")) { return wf::ipc::json_error("call-method requires call-data!"); } auto binding = wf::option_type::from_string(binding_str); if (!binding) { return wf::ipc::json_error("Invalid binding!"); } binding_mode mode = BINDING_NORMAL; if (mode_str.has_value()) { if (mode_str == "release") { mode = BINDING_RELEASE; } else if (mode_str == "repeat") { mode = BINDING_REPEAT; } else { return wf::ipc::json_error("Invalid mode!"); } } ipc_bindings.push_back({}); uint64_t id = binding_to_id(ipc_bindings.back()); wf::activator_callback act_callback; bool temporary_binding = false; if (call_method.has_value()) { act_callback = [=] (const wf::activator_data_t& data) { return on_binding([js, this] () -> bool { method_repository->call_method(js["call-method"], js["call-data"]); return true; }, mode, exec_always, data); }; } else if (command.has_value()) { act_callback = [=] (const wf::activator_data_t& data) { return on_binding([js] () -> bool { return wf::get_core().run(js["command"]); }, mode, exec_always, data); }; } else { temporary_binding = true; act_callback = [=] (const wf::activator_data_t& data) { return on_binding([client, id] () -> bool { wf::json_t event; event["event"] = "command-binding"; event["binding-id"] = id; return client->send_json(event); }, mode, exec_always, data); }; } ipc_bindings.back().callback = act_callback; ipc_bindings.back().client = temporary_binding ? client : NULL; wf::get_core().bindings->add_activator(wf::create_option(*binding), &ipc_bindings.back().callback); wf::json_t response = wf::ipc::json_ok(); response["binding-id"] = id; return response; }; wf::ipc::method_callback on_unregister_binding = [&] (const wf::json_t& js) { auto binding_id = wf::ipc::json_get_uint64(js, "binding-id"); ipc_bindings.remove_if([&] (const ipc_binding_t& binding) { if (binding_to_id(binding) == binding_id) { wf::get_core().bindings->rem_binding((void*)&binding.callback); return true; } return false; }); return wf::ipc::json_ok(); }; void clear_ipc_bindings(std::function filter) { ipc_bindings.remove_if([&] (const ipc_binding_t& binding) { if (filter(binding)) { wf::get_core().bindings->rem_binding((void*)&binding.callback); return true; } return false; }); } wf::ipc::method_callback on_clear_ipc_bindings = [&] (const wf::json_t& js) { clear_ipc_bindings([&] (const ipc_binding_t& binding) { return binding.client == nullptr; }); return wf::ipc::json_ok(); }; wf::signal::connection_t on_client_disconnect = [=] (wf::ipc::client_disconnected_signal *ev) { clear_ipc_bindings([&] (const ipc_binding_t& binding) { return binding.client == ev->client; }); }; }; DECLARE_WAYFIRE_PLUGIN(wayfire_command); wayfire-0.10.0/plugins/single_plugins/vswipe-processing.hpp0000664000175000017500000000451415053502647024040 0ustar dkondordkondor#include #include static inline double vswipe_process_delta(const double delta, const double accumulated_dx, const int vx, const int vw, const double speed_cap = 0.5, const bool free_movement = false) { // The slowdown below must be applied differently for going out of bounds. double sdx_offset = free_movement ? std::copysign(0, accumulated_dx) : accumulated_dx; if (vx - accumulated_dx < 0.0) { sdx_offset = (accumulated_dx - std::floor(accumulated_dx)) + 1.0; } if (vx - accumulated_dx > vw - 1.0) { sdx_offset = (accumulated_dx - std::ceil(accumulated_dx)) - 1.0; } // To achieve a "rubberband" resistance effect when going too far, ease-in // of the whole swiped distance is used as a slowdown factor for the current // delta. const double ease = 1.0 - std::pow(std::abs(sdx_offset) - 0.025, 4.0); // If we're moving further in the limit direction, slow down all the way // to extremely slow, but reversing the direction should be easier. const double slowdown = wf::clamp(ease, std::signbit(delta) == std::signbit(sdx_offset) ? 0.005 : 0.2, 1.0); return wf::clamp(delta, -speed_cap, speed_cap) * slowdown; } static inline int vswipe_finish_target(const double accumulated_dx, const int vx, const int vw, const double last_deltas = 0, const double move_threshold = 0.35, const double fast_threshold = 24, const bool free_movement = false) { int target_dx = 0; if (accumulated_dx > 0) { target_dx = std::floor(accumulated_dx); if ((accumulated_dx - target_dx > move_threshold) || ((!free_movement || !target_dx) && (last_deltas > fast_threshold))) { ++target_dx; } if (vx - target_dx < 0) { target_dx = vx; } } else if (accumulated_dx < 0) { target_dx = std::ceil(accumulated_dx); if ((accumulated_dx - target_dx < -move_threshold) || ((!free_movement || !target_dx) && (last_deltas < -fast_threshold))) { --target_dx; } if (vx - target_dx > vw - 1) { target_dx = vx - vw + 1; } } if (!free_movement) { target_dx = wf::clamp(target_dx, -1, 1); } return target_dx; } wayfire-0.10.0/plugins/single_plugins/idle.cpp0000664000175000017500000002634615053502647021270 0ustar dkondordkondor#include "wayfire/per-output-plugin.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/output.hpp" #include "wayfire/core.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/util.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/seat.hpp" #include "wayfire/toplevel-view.hpp" #include "../cube/cube-control-signal.hpp" #include #include #include #include #include #define CUBE_ZOOM_BASE 1.0 enum cube_screensaver_state { CUBE_SCREENSAVER_DISABLED, CUBE_SCREENSAVER_RUNNING, CUBE_SCREENSAVER_STOPPING, }; using namespace wf::animation; class screensaver_animation_t : public duration_t { public: using duration_t::duration_t; timed_transition_t rot{*this}; timed_transition_t zoom{*this}; timed_transition_t ease{*this}; }; class wayfire_idle { wf::option_wrapper_t dpms_timeout{"idle/dpms_timeout"}; wf::option_wrapper_t disable_initially{"idle/disable_initially"}; bool is_idle = false; public: wf::signal::connection_t on_seat_activity; std::optional hotkey_inhibitor; wf::wl_timer timeout_dpms; wayfire_idle() { dpms_timeout.set_callback([=] () { create_dpms_timeout(); }); on_seat_activity = [=] (void*) { create_dpms_timeout(); }; if (disable_initially) { return; } create_dpms_timeout(); wf::get_core().connect(&on_seat_activity); } void create_dpms_timeout() { if (dpms_timeout <= 0) { timeout_dpms.disconnect(); return; } if (!timeout_dpms.is_connected() && is_idle) { is_idle = false; set_state(wf::OUTPUT_IMAGE_SOURCE_DPMS, wf::OUTPUT_IMAGE_SOURCE_SELF); return; } timeout_dpms.disconnect(); timeout_dpms.set_timeout(1000 * dpms_timeout, [=] () { is_idle = true; set_state(wf::OUTPUT_IMAGE_SOURCE_SELF, wf::OUTPUT_IMAGE_SOURCE_DPMS); }); } ~wayfire_idle() { timeout_dpms.disconnect(); wf::get_core().disconnect(&on_seat_activity); } /* Change all outputs with state from to state to */ void set_state(wf::output_image_source_t from, wf::output_image_source_t to) { auto config = wf::get_core().output_layout->get_current_configuration(); for (auto& entry : config) { if (entry.second.source == from) { entry.second.source = to; } } wf::get_core().output_layout->apply_configuration(config); } }; class wayfire_idle_plugin : public wf::per_output_plugin_instance_t { double rotation = 0.0; wf::option_wrapper_t zoom_speed{"idle/cube_zoom_speed"}; screensaver_animation_t screensaver_animation{zoom_speed}; wf::option_wrapper_t screensaver_timeout{"idle/screensaver_timeout"}; wf::option_wrapper_t cube_rotate_speed{"idle/cube_rotate_speed"}; wf::option_wrapper_t cube_max_zoom{"idle/cube_max_zoom"}; wf::option_wrapper_t disable_on_fullscreen{"idle/disable_on_fullscreen"}; wf::option_wrapper_t disable_initially{"idle/disable_initially"}; std::optional fullscreen_inhibitor; bool has_fullscreen = false; cube_screensaver_state state = CUBE_SCREENSAVER_DISABLED; bool hook_set = false; bool output_inhibited = false; uint32_t last_time; wf::wl_timer timeout_screensaver; wf::signal::connection_t on_seat_activity; wf::shared_data::ref_ptr_t global_idle; wf::activator_callback toggle = [=] (auto) { if (global_idle->hotkey_inhibitor.has_value()) { global_idle->hotkey_inhibitor.reset(); } else { global_idle->hotkey_inhibitor.emplace(); } return true; }; wf::signal::connection_t fullscreen_state_changed = [=] (wf::fullscreen_layer_focused_signal *ev) { this->has_fullscreen = ev->has_promoted; update_fullscreen(); }; wf::signal::connection_t inhibit_changed = [=] (wf::idle_inhibit_changed_signal *ev) { if (!ev) { return; } if (ev->inhibit) { wf::get_core().disconnect(&global_idle->on_seat_activity); wf::get_core().disconnect(&on_seat_activity); global_idle->timeout_dpms.disconnect(); timeout_screensaver.disconnect(); } else { wf::get_core().connect(&global_idle->on_seat_activity); wf::get_core().connect(&on_seat_activity); global_idle->create_dpms_timeout(); create_screensaver_timeout(); } }; wf::config::option_base_t::updated_callback_t disable_on_fullscreen_changed = [=] () { update_fullscreen(); }; void update_fullscreen() { bool want = disable_on_fullscreen && has_fullscreen; if (want && !fullscreen_inhibitor.has_value()) { fullscreen_inhibitor.emplace(); } if (!want && fullscreen_inhibitor.has_value()) { fullscreen_inhibitor.reset(); } } wf::plugin_activation_data_t grab_interface = { .name = "idle", .capabilities = 0, }; public: void init() override { output->add_activator(wf::option_wrapper_t{"idle/toggle"}, &toggle); output->connect(&fullscreen_state_changed); disable_on_fullscreen.set_callback(disable_on_fullscreen_changed); if (auto toplevel = toplevel_cast(wf::get_active_view_for_output(output))) { /* Currently, the fullscreen count would always be 0 or 1, * since fullscreen-layer-focused is only emitted on changes between 0 * and 1 **/ has_fullscreen = toplevel->pending_fullscreen(); } wf::get_core().connect(&inhibit_changed); on_seat_activity = [=] (void*) { create_screensaver_timeout(); }; if (disable_initially) { return; } update_fullscreen(); screensaver_timeout.set_callback([=] () { create_screensaver_timeout(); }); create_screensaver_timeout(); wf::get_core().connect(&on_seat_activity); } void create_screensaver_timeout() { if (screensaver_timeout <= 0) { timeout_screensaver.disconnect(); return; } if (!timeout_screensaver.is_connected() && output_inhibited) { uninhibit_output(); return; } if (!timeout_screensaver.is_connected() && (state == CUBE_SCREENSAVER_RUNNING)) { stop_screensaver(); return; } timeout_screensaver.disconnect(); timeout_screensaver.set_timeout(1000 * screensaver_timeout, [=] () { start_screensaver(); }); } void inhibit_output() { if (output_inhibited) { return; } if (hook_set) { output->render->rem_effect(&screensaver_frame); hook_set = false; } output->render->add_inhibit(true); output->render->damage_whole(); state = CUBE_SCREENSAVER_DISABLED; output_inhibited = true; } void uninhibit_output() { if (!output_inhibited) { return; } output->render->add_inhibit(false); output->render->damage_whole(); output_inhibited = false; } void screensaver_terminate() { cube_control_signal data; data.angle = 0.0; data.zoom = CUBE_ZOOM_BASE; data.ease = 0.0; data.last_frame = true; data.carried_out = false; output->emit(&data); if (hook_set) { output->render->rem_effect(&screensaver_frame); hook_set = false; } if (state == CUBE_SCREENSAVER_DISABLED) { uninhibit_output(); } state = CUBE_SCREENSAVER_DISABLED; } wf::effect_hook_t screensaver_frame = [=] () { cube_control_signal data; uint32_t current = wf::get_current_time(); uint32_t elapsed = current - last_time; last_time = current; if ((state == CUBE_SCREENSAVER_STOPPING) && !screensaver_animation.running()) { screensaver_terminate(); return; } if (state == CUBE_SCREENSAVER_STOPPING) { rotation = screensaver_animation.rot; } else { rotation += (cube_rotate_speed / 5000.0) * elapsed; } if (rotation > M_PI * 2) { rotation -= M_PI * 2; } data.angle = rotation; data.zoom = screensaver_animation.zoom; data.ease = screensaver_animation.ease; data.last_frame = false; data.carried_out = false; output->emit(&data); if (!data.carried_out) { screensaver_terminate(); return; } if (state == CUBE_SCREENSAVER_STOPPING) { wf::get_core().seat->notify_activity(); } }; void start_screensaver() { cube_control_signal data; data.angle = 0.0; data.zoom = CUBE_ZOOM_BASE; data.ease = 0.0; data.last_frame = false; data.carried_out = false; output->emit(&data); if (data.carried_out) { if (!hook_set) { output->render->add_effect( &screensaver_frame, wf::OUTPUT_EFFECT_PRE); hook_set = true; } } else if (state == CUBE_SCREENSAVER_DISABLED) { inhibit_output(); return; } state = CUBE_SCREENSAVER_RUNNING; rotation = 0.0; screensaver_animation.zoom.set(CUBE_ZOOM_BASE, cube_max_zoom); screensaver_animation.ease.set(0.0, 1.0); screensaver_animation.start(); last_time = wf::get_current_time(); } void stop_screensaver() { if (state == CUBE_SCREENSAVER_DISABLED) { uninhibit_output(); return; } state = CUBE_SCREENSAVER_STOPPING; double end = rotation > M_PI ? M_PI * 2 : 0.0; screensaver_animation.rot.set(rotation, end); screensaver_animation.zoom.restart_with_end(CUBE_ZOOM_BASE); screensaver_animation.ease.restart_with_end(0.0); screensaver_animation.start(); } void fini() override { wf::get_core().disconnect(&on_seat_activity); wf::get_core().disconnect(&inhibit_changed); timeout_screensaver.disconnect(); output->rem_binding(&toggle); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/single_plugins/invert.cpp0000664000175000017500000000755315053502647021661 0ustar dkondordkondor#include #include #include #include static const char *vertex_shader = R"( #version 100 attribute highp vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; } )"; static const char *fragment_shader = R"( #version 100 varying highp vec2 uvpos; uniform sampler2D smp; uniform bool preserve_hue; void main() { highp vec4 tex = texture2D(smp, uvpos); if (preserve_hue) { highp float hue = tex.a - min(tex.r, min(tex.g, tex.b)) - max(tex.r, max(tex.g, tex.b)); gl_FragColor = hue + tex; } else { gl_FragColor = vec4(1.0 - tex.r, 1.0 - tex.g, 1.0 - tex.b, 1.0); } } )"; class wayfire_invert_screen : public wf::per_output_plugin_instance_t { wf::post_hook_t hook; wf::activator_callback toggle_cb; wf::option_wrapper_t preserve_hue{"invert/preserve_hue"}; bool active = false; OpenGL::program_t program; wf::plugin_activation_data_t grab_interface = { .name = "invert", .capabilities = 0, }; public: void init() override { if (!wf::get_core().is_gles2()) { const char *render_type = wf::get_core().is_vulkan() ? "vulkan" : (wf::get_core().is_pixman() ? "pixman" : "unknown"); LOGE("invert: requires GLES2 support, but current renderer is ", render_type); return; } wf::option_wrapper_t toggle_key{"invert/toggle"}; hook = [=] (wf::auxilliary_buffer_t& source, const wf::render_buffer_t& destination) { render(source, destination); }; toggle_cb = [=] (auto) { if (!output->can_activate_plugin(&grab_interface)) { return false; } if (active) { output->render->rem_post(&hook); } else { output->render->add_post(&hook); } active = !active; return true; }; wf::gles::run_in_context([&] { program.set_simple(OpenGL::compile_program(vertex_shader, fragment_shader)); }); output->add_activator(toggle_key, &toggle_cb); } void render(wf::auxilliary_buffer_t& source, const wf::render_buffer_t& destination) { static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; static const float coordData[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; wf::gles::run_in_context([&] { wf::gles::bind_render_buffer(destination); program.use(wf::TEXTURE_TYPE_RGBA); GL_CALL(glBindTexture(GL_TEXTURE_2D, wf::gles_texture_t::from_aux(source).tex_id)); GL_CALL(glActiveTexture(GL_TEXTURE0)); program.attrib_pointer("position", 2, 0, vertexData); program.attrib_pointer("uvPosition", 2, 0, coordData); program.uniform1i("preserve_hue", preserve_hue); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program.deactivate(); }); } void fini() override { if (active) { output->render->rem_post(&hook); } wf::gles::run_in_context_if_gles([&] { program.free_resources(); }); output->rem_binding(&toggle_cb); } }; DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t); wayfire-0.10.0/plugins/decor/0000775000175000017500000000000015053502647015706 5ustar dkondordkondorwayfire-0.10.0/plugins/decor/deco-subsurface.cpp0000664000175000017500000002703115053502647021467 0ustar dkondordkondor#include "wayfire/geometry.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel.hpp" #include #define GLM_FORCE_RADIANS #include #include #include #include #include #include #include #include #include #include "deco-subsurface.hpp" #include "deco-layout.hpp" #include "deco-theme.hpp" #include #include #include class simple_decoration_node_t : public wf::scene::node_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { std::weak_ptr _view; wf::signal::connection_t title_set = [=] (wf::view_title_changed_signal *ev) { if (auto view = _view.lock()) { view->damage(); } }; void update_title(int width, int height, double scale) { if (auto view = _view.lock()) { wf::dimensions_t target_size = { static_cast(width * scale), static_cast(height * scale) }; if ((title_texture.tex.get_size() != target_size) || (title_texture.current_text != view->get_title())) { auto surface = theme.render_text(view->get_title(), target_size.width, target_size.height); title_texture.tex = wf::owned_texture_t{surface}; cairo_surface_destroy(surface); title_texture.current_text = view->get_title(); } } } struct { wf::owned_texture_t tex; std::string current_text = ""; } title_texture; public: wf::decor::decoration_theme_t theme; wf::decor::decoration_layout_t layout; wf::region_t cached_region; wf::dimensions_t size; int current_thickness; int current_titlebar; simple_decoration_node_t(wayfire_toplevel_view view) : node_t(false), theme{}, layout{theme, [=] (wlr_box box) { wf::scene::damage_node(shared_from_this(), box + get_offset()); }} { this->_view = view->weak_from_this(); view->connect(&title_set); if (view->parent) { theme.set_buttons(wf::decor::button_type_t(wf::decor::BUTTON_TOGGLE_MAXIMIZE | wf::decor::BUTTON_CLOSE)); } else { theme.set_buttons(wf::decor::button_type_t(wf::decor::BUTTON_MINIMIZE | wf::decor::BUTTON_TOGGLE_MAXIMIZE | wf::decor::BUTTON_CLOSE)); } // make sure to hide frame if the view is fullscreen update_decoration_size(); } wf::point_t get_offset() { return {-current_thickness, -current_titlebar}; } void render(const wf::scene::render_instruction_t& data) { auto origin = get_offset(); /* Clear background */ wlr_box geometry{origin.x, origin.y, size.width, size.height}; bool activated = false; if (auto view = _view.lock()) { activated = view->activated; } theme.render_background(data, geometry, activated); /* Draw title & buttons */ auto renderables = layout.get_renderable_areas(); for (auto item : renderables) { if (item->get_type() == wf::decor::DECORATION_AREA_TITLE) { wf::geometry_t title_geometry = item->get_geometry() + origin; update_title(title_geometry.width, title_geometry.height, data.target.scale); if (title_texture.tex.get_texture().texture != NULL) { data.pass->add_texture(title_texture.tex.get_texture(), data.target, title_geometry, data.damage); } } else // button { item->as_button().render(data, item->get_geometry() + origin); } } } std::optional find_node_at(const wf::pointf_t& at) override { if (auto view = _view.lock()) { wf::pointf_t local = at - wf::pointf_t{get_offset()}; if (cached_region.contains_pointf(local) && view->is_mapped()) { return wf::scene::input_node_t{ .node = this, .local_coords = local, }; } } return {}; } pointer_interaction_t& pointer_interaction() override { return *this; } touch_interaction_t& touch_interaction() override { return *this; } class decoration_render_instance_t : public wf::scene::render_instance_t { std::shared_ptr self; wf::scene::damage_callback push_damage; wf::signal::connection_t on_surface_damage = [=] (wf::scene::node_damage_signal *data) { push_damage(data->region); }; public: decoration_render_instance_t(simple_decoration_node_t *self, wf::scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; self->connect(&on_surface_damage); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { auto our_region = self->cached_region + self->get_offset(); wf::region_t our_damage = damage & our_region; if (!our_damage.empty()) { instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = std::move(our_damage), }); } } void render(const wf::scene::render_instruction_t& data) override { self->render(data); } }; void gen_render_instances(std::vector& instances, wf::scene::damage_callback push_damage, wf::output_t *output = nullptr) override { instances.push_back(std::make_unique(this, push_damage)); } wf::geometry_t get_bounding_box() override { return wf::construct_box(get_offset(), size); } /* wf::compositor_surface_t implementation */ void handle_pointer_enter(wf::pointf_t point) override { point -= wf::pointf_t{get_offset()}; layout.handle_motion(point.x, point.y); } void handle_pointer_leave() override { layout.handle_focus_lost(); } void handle_pointer_motion(wf::pointf_t to, uint32_t) override { to -= wf::pointf_t{get_offset()}; handle_action(layout.handle_motion(to.x, to.y)); } void handle_pointer_button(const wlr_pointer_button_event& ev) override { if (ev.button != BTN_LEFT) { return; } handle_action(layout.handle_press_event(ev.state == WL_POINTER_BUTTON_STATE_PRESSED)); } void handle_action(wf::decor::decoration_layout_t::action_response_t action) { if (auto view = _view.lock()) { switch (action.action) { case wf::decor::DECORATION_ACTION_MOVE: return wf::get_core().default_wm->move_request(view); case wf::decor::DECORATION_ACTION_RESIZE: return wf::get_core().default_wm->resize_request(view, action.edges); case wf::decor::DECORATION_ACTION_CLOSE: return view->close(); case wf::decor::DECORATION_ACTION_TOGGLE_MAXIMIZE: if (view->pending_tiled_edges()) { return wf::get_core().default_wm->tile_request(view, 0); } else { return wf::get_core().default_wm->tile_request(view, wf::TILED_EDGES_ALL); } break; case wf::decor::DECORATION_ACTION_MINIMIZE: return wf::get_core().default_wm->minimize_request(view, true); break; default: break; } } } void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) override { handle_touch_motion(time_ms, finger_id, position); handle_action(layout.handle_press_event()); } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) override { handle_action(layout.handle_press_event(false)); layout.handle_focus_lost(); } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) override { position -= wf::pointf_t{get_offset()}; layout.handle_motion(position.x, position.y); } void resize(wf::dimensions_t dims) { if (auto view = _view.lock()) { view->damage(); size = dims; layout.resize(size.width, size.height); if (!view->toplevel()->current().fullscreen) { this->cached_region = layout.calculate_region(); } view->damage(); } } void update_decoration_size() { bool fullscreen = _view.lock()->toplevel()->current().fullscreen; if (fullscreen) { current_thickness = 0; current_titlebar = 0; this->cached_region.clear(); } else { current_thickness = theme.get_border_size(); current_titlebar = theme.get_title_height() + theme.get_border_size(); this->cached_region = layout.calculate_region(); } } }; wf::simple_decorator_t::simple_decorator_t(wayfire_toplevel_view view) { this->view = view; deco = std::make_shared(view); deco->resize(wf::dimensions(view->get_pending_geometry())); wf::scene::add_back(view->get_surface_root_node(), deco); view->connect(&on_view_activated); view->connect(&on_view_geometry_changed); view->connect(&on_view_fullscreen); on_view_activated = [this] (auto) { wf::scene::damage_node(deco, deco->get_bounding_box()); }; on_view_geometry_changed = [this] (auto) { deco->resize(wf::dimensions(this->view->get_geometry())); }; on_view_fullscreen = [this] (auto) { deco->update_decoration_size(); if (!this->view->toplevel()->current().fullscreen) { deco->resize(wf::dimensions(this->view->get_geometry())); } }; } wf::simple_decorator_t::~simple_decorator_t() { wf::scene::remove_child(deco); } wf::decoration_margins_t wf::simple_decorator_t::get_margins(const wf::toplevel_state_t& state) { if (state.fullscreen) { return {0, 0, 0, 0}; } const int thickness = deco->theme.get_border_size(); const int titlebar = deco->theme.get_title_height() + deco->theme.get_border_size(); return wf::decoration_margins_t{ .left = thickness, .right = thickness, .bottom = thickness, .top = titlebar, }; } wayfire-0.10.0/plugins/decor/deco-theme.hpp0000664000175000017500000000462015053502647020433 0ustar dkondordkondor#pragma once #include #include #include "deco-button.hpp" namespace wf { namespace decor { /** * A class which manages the outlook of decorations. * It is responsible for determining the background colors, sizes, etc. */ class decoration_theme_t { public: /** Create a new theme with the default parameters */ decoration_theme_t(); /** @return The available height for displaying the title */ int get_title_height() const; /** @return The available border for resizing */ int get_border_size() const; /** Set the flags for buttons */ void set_buttons(button_type_t flags); button_type_t button_flags; /** * Fill the given rectangle with the background color(s). * * @param data The render data (pass, target, damage) * @param rectangle The rectangle to redraw. * @param active Whether to use active or inactive colors */ void render_background(const wf::scene::render_instruction_t& data, wf::geometry_t rectangle, bool active) const; /** * Render the given text on a cairo_surface_t with the given size. * The caller is responsible for freeing the memory afterwards. */ cairo_surface_t *render_text(std::string text, int width, int height) const; struct button_state_t { /** Button width */ double width; /** Button height */ double height; /** Button outline size */ double border; /** Progress of button hover, in range [-1, 1]. * Negative numbers are usually used for pressed state. */ double hover_progress; }; /** * Get the icon for the given button. * The caller is responsible for freeing the memory afterwards. * * @param button The button type. * @param state The button state. */ cairo_surface_t *get_button_surface(button_type_t button, const button_state_t& state) const; private: wf::option_wrapper_t font{"decoration/font"}; wf::option_wrapper_t font_color{"decoration/font_color"}; wf::option_wrapper_t title_height{"decoration/title_height"}; wf::option_wrapper_t border_size{"decoration/border_size"}; wf::option_wrapper_t active_color{"decoration/active_color"}; wf::option_wrapper_t inactive_color{"decoration/inactive_color"}; }; } } wayfire-0.10.0/plugins/decor/decoration.cpp0000664000175000017500000001365115053502647020547 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include "deco-subsurface.hpp" #include "wayfire/core.hpp" #include "wayfire/plugin.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/toplevel.hpp" class wayfire_decoration : public wf::plugin_interface_t { wf::view_matcher_t ignore_views{"decoration/ignore_views"}; wf::view_matcher_t forced_views{"decoration/forced_views"}; wf::signal::connection_t on_new_tx = [=] (wf::txn::new_transaction_signal *ev) { // For each transaction, we need to consider what happens with participating views for (const auto& obj : ev->tx->get_objects()) { if (auto toplevel = std::dynamic_pointer_cast(obj)) { // First check whether the toplevel already has decoration // In that case, we should just set the correct margins if (auto deco = toplevel->get_data()) { toplevel->pending().margins = deco->get_margins(toplevel->pending()); continue; } // Second case: the view is already mapped, or the transaction does not map it. // The view is not being decorated, so nothing to do here. if (toplevel->current().mapped || !toplevel->pending().mapped) { continue; } // Third case: the transaction will map the toplevel. auto view = wf::find_view_for_toplevel(toplevel); wf::dassert(view != nullptr, "Mapping a toplevel means there must be a corresponding view!"); if (should_decorate_view(view)) { adjust_new_decorations(view); } } } }; wf::signal::connection_t on_decoration_state_changed = [=] (wf::view_decoration_state_updated_signal *ev) { update_view_decoration(ev->view); }; // allows criteria containing maximized or floating check wf::signal::connection_t on_view_tiled = [=] (wf::view_tiled_signal *ev) { update_view_decoration(ev->view); }; public: void init() override { wf::get_core().connect(&on_decoration_state_changed); wf::get_core().tx_manager->connect(&on_new_tx); wf::get_core().connect(&on_view_tiled); for (auto& view : wf::get_core().get_all_views()) { update_view_decoration(view); } } void fini() override { for (auto view : wf::get_core().get_all_views()) { if (auto toplevel = wf::toplevel_cast(view)) { remove_decoration(toplevel); wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); } } } /** * Uses view_matcher_t to match whether the given view needs to be * ignored for decoration * * @param view The view to match * @return Whether the given view should be decorated? */ bool ignore_decoration_of_view(wayfire_view view) { return ignore_views.matches(view); } /** * Uses view_matcher_t to match whether to force decorations onto the * given view * * @param view The view to match * @return Whether the given view should be decorated? */ bool force_decoration_of_view(wayfire_view view) { return forced_views.matches(view); } bool should_decorate_view(wayfire_toplevel_view view) { return !ignore_decoration_of_view(view) && (force_decoration_of_view(view) || view->should_be_decorated()); } void adjust_new_decorations(wayfire_toplevel_view view) { auto toplevel = view->toplevel(); toplevel->store_data(std::make_unique(view)); auto deco = toplevel->get_data(); auto& pending = toplevel->pending(); pending.margins = deco->get_margins(pending); if (!pending.fullscreen && !pending.tiled_edges) { pending.geometry = wf::expand_geometry_by_margins(pending.geometry, pending.margins); if (view->get_output()) { pending.geometry = wf::clamp(pending.geometry, view->get_output()->workarea->get_workarea()); } } } void remove_decoration(wayfire_toplevel_view view) { view->toplevel()->erase_data(); auto& pending = view->toplevel()->pending(); if (!pending.fullscreen && !pending.tiled_edges) { pending.geometry = wf::shrink_geometry_by_margins(pending.geometry, pending.margins); } pending.margins = {0, 0, 0, 0}; } bool is_toplevel_decorated(const std::shared_ptr& toplevel) { return toplevel->has_data(); } void update_view_decoration(wayfire_view view) { if (auto toplevel = wf::toplevel_cast(view)) { const bool wants_decoration = should_decorate_view(toplevel); if (wants_decoration != is_toplevel_decorated(toplevel->toplevel())) { if (wants_decoration) { adjust_new_decorations(toplevel); } else { remove_decoration(toplevel); } wf::get_core().tx_manager->schedule_object(toplevel->toplevel()); } } } }; DECLARE_WAYFIRE_PLUGIN(wayfire_decoration); wayfire-0.10.0/plugins/decor/deco-subsurface.hpp0000664000175000017500000000165015053502647021473 0ustar dkondordkondor#ifndef DECO_SUBSURFACE_HPP #define DECO_SUBSURFACE_HPP #include "wayfire/object.hpp" #include "wayfire/toplevel.hpp" #include #include class simple_decoration_node_t; namespace wf { /** * A decorator object attached as custom data to a toplevel object. */ class simple_decorator_t : public wf::custom_data_t { wayfire_toplevel_view view; std::shared_ptr deco; wf::signal::connection_t on_view_activated; wf::signal::connection_t on_view_geometry_changed; wf::signal::connection_t on_view_fullscreen; public: simple_decorator_t(wayfire_toplevel_view view); ~simple_decorator_t(); wf::decoration_margins_t get_margins(const wf::toplevel_state_t& state); }; } #endif /* end of include guard: DECO_SUBSURFACE_HPP */ wayfire-0.10.0/plugins/decor/deco-layout.hpp0000664000175000017500000001265715053502647020657 0ustar dkondordkondor#pragma once #include #include #include "deco-button.hpp" namespace wf { namespace decor { static constexpr uint32_t DECORATION_AREA_RENDERABLE_BIT = (1 << 16); static constexpr uint32_t DECORATION_AREA_RESIZE_BIT = (1 << 17); static constexpr uint32_t DECORATION_AREA_MOVE_BIT = (1 << 18); /** Different types of areas around the decoration */ enum decoration_area_type_t { DECORATION_AREA_MOVE = DECORATION_AREA_MOVE_BIT, DECORATION_AREA_TITLE = DECORATION_AREA_MOVE_BIT | DECORATION_AREA_RENDERABLE_BIT, DECORATION_AREA_BUTTON = DECORATION_AREA_RENDERABLE_BIT, DECORATION_AREA_RESIZE_LEFT = WLR_EDGE_LEFT | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_RIGHT = WLR_EDGE_RIGHT | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_TOP = WLR_EDGE_TOP | DECORATION_AREA_RESIZE_BIT, DECORATION_AREA_RESIZE_BOTTOM = WLR_EDGE_BOTTOM | DECORATION_AREA_RESIZE_BIT, }; /** * Represents an area of the decoration which reacts to input events. */ struct decoration_area_t { public: /** * Initialize a new decoration area with the given type and geometry */ decoration_area_t(decoration_area_type_t type, wf::geometry_t g); /** * Initialize a new decoration area holding a button. * * @param g The geometry of the button. * @param damage_callback Callback to execute when button needs repaint. * @param theme The theme to use for the button. */ decoration_area_t(wf::geometry_t g, std::function damage_callback, const decoration_theme_t& theme); /** @return The geometry of the decoration area, relative to the layout */ wf::geometry_t get_geometry() const; /** @return The area's button, if the area is a button. Otherwise UB */ button_t& as_button(); /** @return The type of the decoration area */ decoration_area_type_t get_type() const; private: decoration_area_type_t type; wf::geometry_t geometry{}; /* For buttons only */ std::unique_ptr button; }; /** * Action which needs to be taken in response to an input event */ enum decoration_layout_action_t { DECORATION_ACTION_NONE = 0, /* Drag actions */ DECORATION_ACTION_MOVE = 1, DECORATION_ACTION_RESIZE = 2, /* Button actions */ DECORATION_ACTION_CLOSE = 3, DECORATION_ACTION_TOGGLE_MAXIMIZE = 4, DECORATION_ACTION_MINIMIZE = 5, }; class decoration_theme_t; /** * Manages the layout of the decorations, i.e positioning of the title, * buttons, etc. * * Also dispatches the input events to the appropriate place. */ class decoration_layout_t { public: /** * Create a new decoration layout for the given theme. * When the theme changes, the decoration layout needs to be created again. * * @param damage_callback The function to be called when a part of the * layout needs a repaint. */ decoration_layout_t(const decoration_theme_t& theme, std::function damage_callback); /** Regenerate layout using the new size */ void resize(int width, int height); /** * @return The decoration areas which need to be rendered, in top to bottom * order. */ std::vector> get_renderable_areas(); /** @return The combined region of all layout areas */ wf::region_t calculate_region() const; struct action_response_t { decoration_layout_action_t action; /* For resizing action, determine the edges for resize request */ uint32_t edges; }; /** Handle motion event to (x, y) relative to the decoration */ action_response_t handle_motion(int x, int y); /** * Handle press or release event. * @param pressed Whether the event is a press(true) or release(false) * event. * @return The action which needs to be carried out in response to this * event. */ action_response_t handle_press_event(bool pressed = true); /** * Handle focus lost event. */ void handle_focus_lost(); private: const int titlebar_size; const int border_size; const int button_width; const int button_height; const int button_padding; const decoration_theme_t& theme; std::function damage_callback; std::vector> layout_areas; bool is_grabbed = false; /* Position where the grab has started */ wf::point_t grab_origin; /* Last position of the input */ std::optional current_input; /* double-click timer */ wf::wl_timer timer; bool double_click_at_release = false; /** Create buttons in the layout, and return their total geometry */ wf::geometry_t create_buttons(int width, int height); /** Calculate resize edges based on @current_input */ uint32_t calculate_resize_edges() const; /** Update the cursor based on @current_input */ void update_cursor() const; /** * Find the layout area at the given coordinates, if any * @return The layout area or null on failure */ nonstd::observer_ptr find_area_at(std::optional point); /** Unset hover state of hovered button at @position, if any */ void unset_hover(std::optional position); wf::option_wrapper_t button_order{"decoration/button_order"}; }; } } wayfire-0.10.0/plugins/decor/meson.build0000664000175000017500000000063515053502647020054 0ustar dkondordkondordecoration = shared_module('decoration', ['decoration.cpp', 'deco-subsurface.cpp', 'deco-button.cpp', 'deco-layout.cpp', 'deco-theme.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc], dependencies: [wlroots, pixman, wf_protos, wfconfig, cairo, pango, pangocairo, plugin_pch_dep], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) wayfire-0.10.0/plugins/decor/deco-theme.cpp0000664000175000017500000001363015053502647020427 0ustar dkondordkondor#include "deco-theme.hpp" #include #include #include namespace wf { namespace decor { /** Create a new theme with the default parameters */ decoration_theme_t::decoration_theme_t() {} /** @return The available height for displaying the title */ int decoration_theme_t::get_title_height() const { return title_height; } /** @return The available border for resizing */ int decoration_theme_t::get_border_size() const { return border_size; } /** @return The available border for resizing */ void decoration_theme_t::set_buttons(button_type_t flags) { button_flags = flags; } /** * Fill the given rectangle with the background color(s). * * @param fb The target framebuffer, must have been bound already * @param rectangle The rectangle to redraw. * @param scissor The GL scissor rectangle to use. * @param active Whether to use active or inactive colors */ void decoration_theme_t::render_background(const wf::scene::render_instruction_t& data, wf::geometry_t rectangle, bool active) const { wf::color_t color = active ? active_color : inactive_color; data.pass->add_rect(color, data.target, rectangle, data.damage); } /** * Render the given text on a cairo_surface_t with the given size. * The caller is responsible for freeing the memory afterwards. */ cairo_surface_t*decoration_theme_t::render_text(std::string text, int width, int height) const { const auto format = CAIRO_FORMAT_ARGB32; auto surface = cairo_image_surface_create(format, width, height); if (height == 0) { return surface; } wf::color_t color = font_color; auto cr = cairo_create(surface); const float font_scale = 0.8; const float font_size = height * font_scale; PangoFontDescription *font_desc; PangoLayout *layout; // render text font_desc = pango_font_description_from_string(((std::string)font).c_str()); pango_font_description_set_absolute_size(font_desc, font_size * PANGO_SCALE); layout = pango_cairo_create_layout(cr); pango_layout_set_font_description(layout, font_desc); pango_layout_set_text(layout, text.c_str(), text.size()); cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); pango_cairo_show_layout(cr, layout); pango_font_description_free(font_desc); g_object_unref(layout); cairo_destroy(cr); return surface; } cairo_surface_t*decoration_theme_t::get_button_surface(button_type_t button, const button_state_t& state) const { cairo_surface_t *button_surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, state.width, state.height); auto cr = cairo_create(button_surface); cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST); /* Clear the button background */ cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_rectangle(cr, 0, 0, state.width, state.height); cairo_fill(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); /** A gray that looks good on light and dark themes */ color_t base = {0.60, 0.60, 0.63, 0.36}; /** * We just need the alpha component. * r == g == b == 0.0 will be directly set */ double line = 0.27; double hover = 0.27; /** Coloured base on hover/press. Don't compare float to 0 */ if (fabs(state.hover_progress) > 1e-3) { switch (button) { case BUTTON_CLOSE: base = {242.0 / 255.0, 80.0 / 255.0, 86.0 / 255.0, 0.63}; break; case BUTTON_TOGGLE_MAXIMIZE: base = {57.0 / 255.0, 234.0 / 255.0, 73.0 / 255.0, 0.63}; break; case BUTTON_MINIMIZE: base = {250.0 / 255.0, 198.0 / 255.0, 54.0 / 255.0, 0.63}; break; default: assert(false); } line *= 2.0; } /** Draw the base */ cairo_set_source_rgba(cr, base.r + 0.0 * state.hover_progress, base.g + 0.0 * state.hover_progress, base.b + 0.0 * state.hover_progress, base.a + hover * state.hover_progress); cairo_arc(cr, state.width / 2, state.height / 2, state.width / 2, 0, 2 * M_PI); cairo_fill(cr); /** Draw the border */ cairo_set_line_width(cr, state.border); cairo_set_source_rgba(cr, 0.00, 0.00, 0.00, line); // This renders great on my screen (110 dpi 1376x768 lcd screen) // How this would appear on a Hi-DPI screen is questionable double r = state.width / 2 - 0.5 * state.border; cairo_arc(cr, state.width / 2, state.height / 2, r, 0, 2 * M_PI); cairo_stroke(cr); /** Draw the icon */ cairo_set_source_rgba(cr, 0.00, 0.00, 0.00, line / 2); cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); switch (button) { case BUTTON_CLOSE: cairo_set_line_width(cr, 1.5 * state.border); cairo_move_to(cr, 1.0 * state.width / 4.0, 1.0 * state.height / 4.0); cairo_line_to(cr, 3.0 * state.width / 4.0, 3.0 * state.height / 4.0); // '\' part of x cairo_move_to(cr, 3.0 * state.width / 4.0, 1.0 * state.height / 4.0); cairo_line_to(cr, 1.0 * state.width / 4.0, 3.0 * state.height / 4.0); // '/' part of x cairo_stroke(cr); break; case BUTTON_TOGGLE_MAXIMIZE: cairo_set_line_width(cr, 1.5 * state.border); cairo_rectangle( cr, // Context state.width / 4.0, state.height / 4.0, // (x, y) state.width / 2.0, state.height / 2.0 // w x h ); cairo_stroke(cr); break; case BUTTON_MINIMIZE: cairo_set_line_width(cr, 1.75 * state.border); cairo_move_to(cr, 1.0 * state.width / 4.0, state.height / 2.0); cairo_line_to(cr, 3.0 * state.width / 4.0, state.height / 2.0); cairo_stroke(cr); break; default: assert(false); } cairo_fill(cr); cairo_destroy(cr); return button_surface; } } } wayfire-0.10.0/plugins/decor/deco-layout.cpp0000664000175000017500000002454215053502647020646 0ustar dkondordkondor#include "deco-layout.hpp" #include "deco-theme.hpp" #include #include #include #include #include #define BUTTON_HEIGHT_PC 0.7 namespace wf { namespace decor { /** * Represents an area of the decoration which reacts to input events. */ decoration_area_t::decoration_area_t(decoration_area_type_t type, wf::geometry_t g) { this->type = type; this->geometry = g; assert(type != DECORATION_AREA_BUTTON); } /** * Initialize a new decoration area holding a button */ decoration_area_t::decoration_area_t(wf::geometry_t g, std::function damage_callback, const decoration_theme_t& theme) { this->type = DECORATION_AREA_BUTTON; this->geometry = g; this->button = std::make_unique(theme, std::bind(damage_callback, g)); } wf::geometry_t decoration_area_t::get_geometry() const { return geometry; } button_t& decoration_area_t::as_button() { assert(button); return *button; } decoration_area_type_t decoration_area_t::get_type() const { return type; } decoration_layout_t::decoration_layout_t(const decoration_theme_t& th, std::function callback) : titlebar_size(th.get_title_height()), border_size(th.get_border_size()), /** * This is necessary. Otherwise, we will draw an * overly huge button. 70% of the titlebar height * is a decent size. (Equals 21 px by default) */ button_width(titlebar_size * BUTTON_HEIGHT_PC), button_height(titlebar_size * BUTTON_HEIGHT_PC), button_padding((titlebar_size - button_height) / 2), theme(th), damage_callback(callback) {} wf::geometry_t decoration_layout_t::create_buttons(int width, int) { std::stringstream stream((std::string)button_order); std::vector buttons; std::string button_name; while (stream >> button_name) { if ((button_name == "minimize") && (theme.button_flags & BUTTON_MINIMIZE)) { buttons.push_back(BUTTON_MINIMIZE); } if ((button_name == "maximize") && (theme.button_flags & BUTTON_TOGGLE_MAXIMIZE)) { buttons.push_back(BUTTON_TOGGLE_MAXIMIZE); } if ((button_name == "close") && (theme.button_flags & BUTTON_CLOSE)) { buttons.push_back(BUTTON_CLOSE); } } int per_button = 2 * button_padding + button_width; wf::geometry_t button_geometry = { width - border_size + button_padding, /* 1 more padding initially */ button_padding + border_size, button_width, button_height, }; for (auto type : wf::reverse(buttons)) { button_geometry.x -= per_button; this->layout_areas.push_back(std::make_unique( button_geometry, damage_callback, theme)); this->layout_areas.back()->as_button().set_button_type(type); } int total_width = -button_padding + buttons.size() * per_button; return { button_geometry.x, border_size, total_width, titlebar_size }; } /** Regenerate layout using the new size */ void decoration_layout_t::resize(int width, int height) { this->layout_areas.clear(); if (this->titlebar_size > 0) { auto button_geometry_expanded = create_buttons(width, height); /* Padding around the button, allows move */ this->layout_areas.push_back(std::make_unique( DECORATION_AREA_MOVE, button_geometry_expanded)); /* Titlebar dragging area (for move) */ wf::geometry_t title_geometry = { border_size, border_size, /* Up to the button, but subtract the padding to the left of the * title and the padding between title and button */ button_geometry_expanded.x - border_size, titlebar_size, }; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_TITLE, title_geometry)); } /* Resizing edges - left */ wf::geometry_t border_geometry = {0, 0, border_size, height}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_LEFT, border_geometry)); /* Resizing edges - right */ border_geometry = {width - border_size, 0, border_size, height}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_RIGHT, border_geometry)); /* Resizing edges - top */ border_geometry = {0, 0, width, border_size}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_TOP, border_geometry)); /* Resizing edges - bottom */ border_geometry = {0, height - border_size, width, border_size}; this->layout_areas.push_back(std::make_unique( DECORATION_AREA_RESIZE_BOTTOM, border_geometry)); } /** * @return The decoration areas which need to be rendered, in top to bottom * order. */ std::vector> decoration_layout_t::get_renderable_areas() { std::vector> renderable; for (auto& area : layout_areas) { if (area->get_type() & DECORATION_AREA_RENDERABLE_BIT) { renderable.push_back({area}); } } return renderable; } wf::region_t decoration_layout_t::calculate_region() const { wf::region_t r{}; for (auto& area : layout_areas) { auto g = area->get_geometry(); if ((g.width > 0) && (g.height > 0)) { r |= g; } } return r; } void decoration_layout_t::unset_hover(std::optional position) { auto area = find_area_at(position); if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_hover(false); } } /** Handle motion event to (x, y) relative to the decoration */ decoration_layout_t::action_response_t decoration_layout_t::handle_motion( int x, int y) { auto previous_area = find_area_at(current_input); auto current_area = find_area_at(wf::point_t{x, y}); if (previous_area == current_area) { if (is_grabbed && current_area && (current_area->get_type() & DECORATION_AREA_MOVE_BIT)) { is_grabbed = false; return {DECORATION_ACTION_MOVE, 0}; } } else { unset_hover(current_input); if (current_area && (current_area->get_type() == DECORATION_AREA_BUTTON)) { current_area->as_button().set_hover(true); } } this->current_input = {x, y}; update_cursor(); return {DECORATION_ACTION_NONE, 0}; } /** * Handle press or release event. * @param pressed Whether the event is a press(true) or release(false) * event. * @return The action which needs to be carried out in response to this * event. * */ decoration_layout_t::action_response_t decoration_layout_t::handle_press_event( bool pressed) { if (pressed) { auto area = find_area_at(current_input); if (area && (area->get_type() & DECORATION_AREA_MOVE_BIT)) { if (timer.is_connected()) { double_click_at_release = true; } else { timer.set_timeout(300, [] () { return false; }); } } if (area && (area->get_type() & DECORATION_AREA_RESIZE_BIT)) { return {DECORATION_ACTION_RESIZE, calculate_resize_edges()}; } if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_pressed(true); } is_grabbed = true; grab_origin = current_input.value_or(wf::point_t{0, 0}); } if (!pressed && double_click_at_release) { double_click_at_release = false; return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; } else if (!pressed && is_grabbed) { is_grabbed = false; auto begin_area = find_area_at(grab_origin); auto end_area = find_area_at(current_input); if (begin_area && (begin_area->get_type() == DECORATION_AREA_BUTTON)) { begin_area->as_button().set_pressed(false); if (end_area && (begin_area == end_area)) { switch (begin_area->as_button().get_button_type()) { case BUTTON_CLOSE: return {DECORATION_ACTION_CLOSE, 0}; case BUTTON_TOGGLE_MAXIMIZE: return {DECORATION_ACTION_TOGGLE_MAXIMIZE, 0}; case BUTTON_MINIMIZE: return {DECORATION_ACTION_MINIMIZE, 0}; default: break; } } } } return {DECORATION_ACTION_NONE, 0}; } /** * Find the layout area at the given coordinates, if any * @return The layout area or null on failure */ nonstd::observer_ptr decoration_layout_t::find_area_at(std::optional point) { if (!point) { return nullptr; } for (auto& area : this->layout_areas) { if (area->get_geometry() & *point) { return {area}; } } return nullptr; } /** Calculate resize edges based on @current_input */ uint32_t decoration_layout_t::calculate_resize_edges() const { if (!this->current_input.has_value()) { return 0; } uint32_t edges = 0; for (auto& area : layout_areas) { if (area->get_geometry() & *this->current_input) { if (area->get_type() & DECORATION_AREA_RESIZE_BIT) { edges |= (area->get_type() & ~DECORATION_AREA_RESIZE_BIT); } } } return edges; } /** Update the cursor based on @current_input */ void decoration_layout_t::update_cursor() const { uint32_t edges = calculate_resize_edges(); auto cursor_name = edges > 0 ? wlr_xcursor_get_resize_name((wlr_edges)edges) : "default"; wf::get_core().set_cursor(cursor_name); } void decoration_layout_t::handle_focus_lost() { if (is_grabbed) { this->is_grabbed = false; auto area = find_area_at(grab_origin); if (area && (area->get_type() == DECORATION_AREA_BUTTON)) { area->as_button().set_pressed(false); } } this->unset_hover(current_input); } } } wayfire-0.10.0/plugins/decor/deco-button.hpp0000664000175000017500000000520315053502647020642 0ustar dkondordkondor#pragma once #include #include #include #include #include #include #include #include #include namespace wf { namespace decor { class decoration_theme_t; enum button_type_t { BUTTON_CLOSE = 1 << 0, BUTTON_TOGGLE_MAXIMIZE = 1 << 1, BUTTON_MINIMIZE = 1 << 2, }; class button_t { public: /** * Create a new button with the given theme. * @param theme The theme to use. * @param damage_callback A callback to execute when the button needs a * repaint. Damage won't be reported while render() is being called. */ button_t(const decoration_theme_t& theme, std::function damage_callback); ~button_t(); button_t(const button_t &) = delete; button_t(button_t &&) = delete; button_t& operator =(const button_t&) = delete; button_t& operator =(button_t&&) = delete; /** * Set the type of the button. This will affect the displayed icon and * potentially other appearance like colors. */ void set_button_type(button_type_t type); /** @return The type of the button */ button_type_t get_button_type() const; /** * Set the button hover state. * Affects appearance. */ void set_hover(bool is_hovered); /** * Set whether the button is pressed or not. * Affects appearance. */ void set_pressed(bool is_pressed); /** * Render the button on the given framebuffer at the given coordinates. * Precondition: set_button_type() has been called, otherwise result is no-op * * @param data The render data * @param geometry The geometry of the button, in logical coordinates */ void render(const scene::render_instruction_t& data, wf::geometry_t geometry); private: const decoration_theme_t& theme; /* Whether the button needs repaint */ button_type_t type; wf::owned_texture_t button_texture; /* Whether the button is currently being hovered */ bool is_hovered = false; /* Whether the button is currently being held */ bool is_pressed = false; /* The shade of button background to use. */ wf::animation::simple_animation_t hover{wf::create_option(100)}; std::function damage_callback; wf::wl_idle_call idle_damage; /** Damage button the next time the main loop goes idle */ void add_idle_damage(); /** * Redraw the button surface and store it as a texture */ void update_texture(); }; } } wayfire-0.10.0/plugins/decor/deco-button.cpp0000664000175000017500000000431015053502647020633 0ustar dkondordkondor#include "deco-button.hpp" #include "deco-theme.hpp" #include #include #define HOVERED 1.0 #define NORMAL 0.0 #define PRESSED -0.7 namespace wf { namespace decor { button_t::button_t(const decoration_theme_t& t, std::function damage) : theme(t), damage_callback(damage) {} void button_t::set_button_type(button_type_t type) { this->type = type; this->hover.animate(0, 0); update_texture(); add_idle_damage(); } button_type_t button_t::get_button_type() const { return this->type; } void button_t::set_hover(bool is_hovered) { this->is_hovered = is_hovered; if (!this->is_pressed) { if (is_hovered) { this->hover.animate(HOVERED); } else { this->hover.animate(NORMAL); } } add_idle_damage(); } /** * Set whether the button is pressed or not. * Affects appearance. */ void button_t::set_pressed(bool is_pressed) { this->is_pressed = is_pressed; if (is_pressed) { this->hover.animate(PRESSED); } else { this->hover.animate(is_hovered ? HOVERED : NORMAL); } add_idle_damage(); } void button_t::render(const scene::render_instruction_t& data, wf::geometry_t geometry) { data.pass->add_texture(button_texture.get_texture(), data.target, geometry, data.damage); if (this->hover.running()) { add_idle_damage(); } } void button_t::update_texture() { /** * We render at 100% resolution * When uploading the texture, this gets scaled * to 70% of the titlebar height. Thus we will have * a very crisp image */ decoration_theme_t::button_state_t state = { .width = 1.0 * theme.get_title_height(), .height = 1.0 * theme.get_title_height(), .border = 1.0, .hover_progress = hover, }; auto surface = theme.get_button_surface(type, state); this->button_texture = owned_texture_t{surface}; cairo_surface_destroy(surface); } void button_t::add_idle_damage() { this->idle_damage.run_once([=] () { this->damage_callback(); update_texture(); }); } button_t::~button_t() {} } // namespace decor } wayfire-0.10.0/man/0000775000175000017500000000000015053502647013704 5ustar dkondordkondorwayfire-0.10.0/man/meson.build0000664000175000017500000000027415053502647016051 0ustar dkondordkondorconfigure_file(input: 'wayfire.1.in', output: 'wayfire.1', configuration: conf_data) install_man(join_paths(meson.project_build_root(), 'man', 'wayfire.1')) wayfire-0.10.0/man/wayfire.1.in0000664000175000017500000000530515053502647016044 0ustar dkondordkondor.Dd $Mdocdate: May 21 2023 $ .Dt WAYFIRE 1 .Os .Sh NAME .Nm wayfire .Nd modular and extensible wayland compositor .Sh SYNOPSIS .Nm wayfire .Op Fl c , -config Ar config_file .Op Fl B , -config-backend Ar config_backend .Op Fl d , -debug .Op Fl D , -damage-debug .Op Fl h , -help .Op Fl R , -damage-renderer .Op Fl v , -version .Sh DESCRIPTION .Nm is a wayland compositor focusing on modularity and extensibility by providing a small core compositor implementation with all major functionality being provided by plugins. The default plugins provide 3D effects similar to compiz, such as 3D cube, wobbly windows, blur, fish eye, etc. .Pp The optional flags are described as follows: .Pp .Bl -tag -width Ds -compact .It Fl c , -config Ar config_file .Pp Start .Nm with an alternative configuration file. The default configuration file is searched first in the .Ev ${WAYFIRE_CONFIG_FILE} environment variable, or paths .Pa ${XDG_CONFIG_HOME}/wayfire/wayfire.ini , .Pa ${HOME}/.config/wayfire/wayfire.ini , .Pa ${XDG_CONFIG_HOME}/wayfire.ini , .Pa ${HOME}/.config/wayfire.ini . .Pp .It Fl B , -config-backend Ar config_backend .Pp Specify config backend to use. .Pp .It Fl d , -debug .Pp Enable debug logging. .Pp .It Fl D , -damage-debug .Pp Enable additional debug for damaged regions. .Pp .It Fl h , -help .Pp Print a short help message. .Pp .It Fl R , -damage-renderer .Pp Rerender damaged regions. .Pp .It Fl v , -version .Pp Print the version. .El .Sh ENVIRONMENT VARIABLES .Nm respects the following environment variables: .Pp .Bl -tag -width Ds -compact .It Ev WAYFIRE_CONFIG_FILE The config file to use. .Pp .It Ev WAYFIRE_PLUGIN_XML_PATH .Pp A string of paths, separated by : , in which to look for plugin configuration files. By default .Nm looks for configuration files in .Pa @PLUGIN_XML_DIR@ and .Pa ${XDG_DATA_HOME}/wayfire/metadata . .Pp .It Ev WAYFIRE_PLUGIN_PATH .Pp A string of paths, separated by : , in which to look for plugins. By default .Nm looks for plugins in .Pa @PLUGIN_PATH@ and .Pa ${XDG_DATA_HOME}/wayfire/plugins . .Pp .It Ev _WAYFIRE_SOCKET .Pp Socket override to use to communicate with a specific .Nm instance using the IPC. Useful incase of multiple running instances of .Nm . .El .Pp In addition, .Nm also sets the following environment variables: .Pp .Bl -tag -width Ds -compact .It Ev WAYFIRE_SOCKET .Pp Socket to use when communicating with .Nm . .Pp .It Ev WAYLAND_DISPLAY .Pp Wayland display currently in effect. .Pp .It Ev CONFIG_FILE_ENV .Pp Which config file is being used. .Pp .It Ev _JAVA_AWT_WM_NONREPARENTING .Pp Needed for java based windows and it is set to 1. .Pp .It Ev DISPLAY .It Ev XCURSOR_SIZE .It Ev XCURSOR_THEME .Pp Variables for use with legacy .Xr xwayland 1 windows. .El .Sh SEE ALSO .Xr xwayland 1 wayfire-0.10.0/.gitignore0000664000175000017500000000175715053502647015133 0ustar dkondordkondor # Created by https://www.toptal.com/developers/gitignore/api/c++,meson,ninja,linux # Edit at https://www.toptal.com/developers/gitignore?templates=c++,meson,ninja,linux ### C++ ### # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran module files *.mod *.smod # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### Meson ### # subproject directories /subprojects/* !/subprojects/*.wrap ### Ninja ### .ninja_deps .ninja_log build/** .cache/** # End of https://www.toptal.com/developers/gitignore/api/c++,meson,ninja,linux .aider* wayfire-0.10.0/src/0000775000175000017500000000000015053502647013720 5ustar dkondordkondorwayfire-0.10.0/src/render.cpp0000664000175000017500000004427315053502647015715 0ustar dkondordkondor#include #include "core/core-impl.hpp" #include "wayfire/dassert.hpp" #include "wayfire/nonstd/reverse.hpp" #include "wayfire/opengl.hpp" #include #include wf::render_buffer_t::render_buffer_t(wlr_buffer *buffer, wf::dimensions_t size) { this->buffer = buffer; this->size = size; } wf::auxilliary_buffer_t::auxilliary_buffer_t(auxilliary_buffer_t&& other) { *this = std::move(other); } wf::auxilliary_buffer_t& wf::auxilliary_buffer_t::operator =(auxilliary_buffer_t&& other) { if (&other == this) { return *this; } this->texture = other.texture; this->buffer = other.buffer; other.buffer.buffer = NULL; other.texture = NULL; other.buffer.size = {0, 0}; return *this; } wf::auxilliary_buffer_t::~auxilliary_buffer_t() { free(); } static const wlr_drm_format *choose_format_from_set(const wlr_drm_format_set *set, wf::buffer_allocation_hints_t hints) { static std::vector alpha_formats = { DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888, DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, }; static std::vector no_alpha_formats = { DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888, DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888, }; const auto& possible_formats = hints.needs_alpha ? alpha_formats : no_alpha_formats; for (auto drm_format : possible_formats) { if (auto layout = wlr_drm_format_set_get(set, drm_format)) { return layout; } } return nullptr; } /** * Rounds a wlr_fbox to a wlr_box such that the integer box fully contains the float box. */ static wlr_box round_fbox_to_containing_box(wlr_fbox fbox) { return wlr_box{ .x = (int)std::floor(fbox.x), .y = (int)std::floor(fbox.y), .width = (int)std::ceil(fbox.x + fbox.width) - (int)std::floor(fbox.x), .height = (int)std::ceil(fbox.y + fbox.height) - (int)std::floor(fbox.y), }; } static const wlr_drm_format *choose_format(wlr_renderer *renderer, wf::buffer_allocation_hints_t hints) { auto supported_render_formats = wlr_renderer_get_texture_formats(wf::get_core().renderer, renderer->render_buffer_caps); // FIXME: in the wlroots vulkan renderer, we need to have SRGB writing support for optimal performance. // The issue is that not all modifiers support SRGB. Until the wlroots issue // (https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3986) is fixed, we need to somehow filter out // formats that don't support SRGB. Simplest way is to patch wlroots as indicated in the issue. if (renderer->WLR_PRIVATE.impl->get_render_formats) { static bool initialized = false; static wlr_drm_format_set performant_formats{}; if (!initialized) { auto render_fmts = renderer->WLR_PRIVATE.impl->get_render_formats(renderer); wlr_drm_format_set_intersect(&performant_formats, supported_render_formats, render_fmts); } if (auto format = choose_format_from_set(&performant_formats, hints)) { return format; } } return choose_format_from_set(supported_render_formats, hints); } static wf::dimensions_t sanitize_buffer_size(wf::dimensions_t size, float max_allowed_size) { if ((size.width > max_allowed_size) || (size.height > max_allowed_size)) { LOGW("Attempting to allocate a buffer which is too large ", size, "!"); float scale = std::min(max_allowed_size / size.width, max_allowed_size / size.height); size.width = std::ceil(size.width * scale); size.height = std::ceil(size.height * scale); } return size; } wf::buffer_reallocation_result_t wf::auxilliary_buffer_t::allocate(wf::dimensions_t size, float scale, buffer_allocation_hints_t hints) { // From 16k x 16k upwards, we very often hit various limits so there is no point in allocating larger // buffers. Plus, we never really need buffers that big in practice, so these usually indicate bugs in // the code. static wf::option_wrapper_t max_buffer_size{"workarounds/max_buffer_size"}; const int FALLBACK_MAX_BUFFER_SIZE = 4096; size.width = std::max(1.0f, std::ceil(size.width * scale)); size.height = std::max(1.0f, std::ceil(size.height * scale)); size = sanitize_buffer_size(size, max_buffer_size); if (buffer.get_size() == size) { return buffer_reallocation_result_t::SAME; } free(); auto renderer = wf::get_core().renderer; auto format = choose_format(renderer, hints); if (!format) { LOGE("Failed to find supported render format!"); return buffer_reallocation_result_t::FAILED; } buffer.buffer = wlr_allocator_create_buffer(wf::get_core_impl().allocator, size.width, size.height, format); if (!buffer.buffer) { // On some systems, we may not be able to allocate very big buffers, so try to allocate a smaller // size instead. size = sanitize_buffer_size(size, FALLBACK_MAX_BUFFER_SIZE); buffer.buffer = wlr_allocator_create_buffer(wf::get_core_impl().allocator, size.width, size.height, format); } if (!buffer.buffer) { LOGE("Failed to allocate auxilliary buffer! Size ", size, " format ", format->format); return buffer_reallocation_result_t::FAILED; } buffer.size = size; return buffer_reallocation_result_t::REALLOCATED; } void wf::auxilliary_buffer_t::free() { if (texture) { wlr_texture_destroy(texture); } texture = NULL; if (buffer.get_buffer()) { wlr_buffer_drop(buffer.get_buffer()); } buffer.buffer = NULL; buffer.size = {0, 0}; } wlr_buffer*wf::auxilliary_buffer_t::get_buffer() const { return buffer.get_buffer(); } wf::dimensions_t wf::auxilliary_buffer_t::get_size() const { return buffer.get_size(); } wlr_texture*wf::auxilliary_buffer_t::get_texture() { wf::dassert(buffer.get_buffer(), "No buffer allocated yet!"); if (!texture) { texture = wlr_texture_from_buffer(wf::get_core().renderer, buffer.get_buffer()); } return texture; } wf::render_buffer_t wf::auxilliary_buffer_t::get_renderbuffer() const { return buffer; } void wf::render_buffer_t::do_blit(wlr_texture *src_wlr_tex, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode) const { auto renderer = wf::get_core().renderer; auto target_buffer = this->get_buffer(); if (!target_buffer) { LOGE("Cannot copy to unallocated render buffer!"); return; } wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, target_buffer, NULL); if (!pass) { LOGE("Failed to start wlr render pass for render buffer copy!"); return; } wlr_render_texture_options opts{}; opts.texture = src_wlr_tex; opts.alpha = NULL; opts.blend_mode = WLR_RENDER_BLEND_MODE_NONE; opts.filter_mode = filter_mode; opts.transform = WL_OUTPUT_TRANSFORM_NORMAL; opts.clip = NULL; opts.src_box = src_box; opts.dst_box = dst_box; wlr_render_pass_add_texture(pass, &opts); if (!wlr_render_pass_submit(pass)) { LOGE("Blit to render buffer failed!"); } } void wf::render_buffer_t::blit(wf::auxilliary_buffer_t& source, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode) const { if (wlr_texture *src_wlr_tex = source.get_texture()) { do_blit(src_wlr_tex, src_box, dst_box, filter_mode); } else { LOGE("Failed to get source texture for auxilliary_buffer_t copy!"); } } void wf::render_buffer_t::blit(const wf::render_buffer_t& source, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode) const { if (wlr_texture *src_wlr_tex = wlr_texture_from_buffer(wf::get_core().renderer, source.get_buffer())) { do_blit(src_wlr_tex, src_box, dst_box, filter_mode); wlr_texture_destroy(src_wlr_tex); } else { LOGE("Failed to create texture from source render_buffer_t for copy!"); } } wf::render_target_t::render_target_t(const render_buffer_t& buffer) : render_buffer_t(buffer) {} wf::render_target_t::render_target_t(const auxilliary_buffer_t& buffer) : render_buffer_t( buffer.get_buffer(), buffer.get_size()) {} wf::render_target_t wf::render_target_t::translated(wf::point_t offset) const { render_target_t copy = *this; copy.geometry = copy.geometry + offset; return copy; } wlr_fbox wf::render_target_t::framebuffer_box_from_geometry_box(wlr_fbox box) const { /* Step 1: Make relative to the framebuffer */ box.x -= this->geometry.x; box.y -= this->geometry.y; /* Step 2: Apply scale to box */ box = box * scale; /* Step 3: rotate */ wf::dimensions_t size = get_size(); if (wl_transform & 1) { std::swap(size.width, size.height); } wlr_fbox result; wl_output_transform transform = wlr_output_transform_invert((wl_output_transform)wl_transform); wlr_fbox_transform(&result, &box, transform, size.width, size.height); if (subbuffer) { result = scale_fbox({0.0, 0.0, (double)get_size().width, (double)get_size().height}, geometry_to_fbox(subbuffer.value()), result); } return result; } wlr_box wf::render_target_t::framebuffer_box_from_geometry_box(wlr_box box) const { wlr_fbox fbox = geometry_to_fbox(box); wlr_fbox scaled_fbox = framebuffer_box_from_geometry_box(fbox); return round_fbox_to_containing_box(scaled_fbox); } wf::region_t wf::render_target_t::framebuffer_region_from_geometry_region(const wf::region_t& region) const { wf::region_t result; for (const auto& rect : region) { result |= framebuffer_box_from_geometry_box(wlr_box_from_pixman_box(rect)); } return result; } wlr_fbox wf::render_target_t::geometry_fbox_from_framebuffer_box(wlr_fbox fb_box) const { if (subbuffer) { fb_box = scale_fbox(geometry_to_fbox(subbuffer.value()), {0.0, 0.0, (double)get_size().width, (double)get_size().height}, fb_box); } wf::dimensions_t current_fb_dimensions = get_size(); wlr_fbox result; wlr_fbox_transform(&result, &fb_box, (wl_output_transform)wl_transform, current_fb_dimensions.width, current_fb_dimensions.height); if (scale != 0.0f) { result = result * (1.0 / scale); } else { LOGE("Render target scale is zero, cannot invert framebuffer box!"); return {0, 0, 0, 0}; // Return an empty/invalid box } result.x += this->geometry.x; result.y += this->geometry.y; return result; } wlr_box wf::render_target_t::geometry_box_from_framebuffer_box(wlr_box fb_box) const { return round_fbox_to_containing_box(geometry_fbox_from_framebuffer_box(geometry_to_fbox(fb_box))); } wf::region_t wf::render_target_t::geometry_region_from_framebuffer_region(const wf::region_t& region) const { wf::region_t result; for (const auto& rect : region) { result |= geometry_box_from_framebuffer_box(wlr_box_from_pixman_box(rect)); } return result; } wf::render_pass_t::render_pass_t(const render_pass_params_t& p) { this->params = p; this->params.renderer = p.renderer ?: wf::get_core().renderer; wf::dassert(p.target.get_buffer(), "Cannot run a render pass without a valid target!"); } wf::region_t wf::render_pass_t::run(const wf::render_pass_params_t& params) { wf::render_pass_t pass{params}; auto damage = pass.run_partial(); pass.submit(); return damage; } wf::region_t wf::render_pass_t::run_partial() { auto accumulated_damage = params.damage; if (params.flags & RPASS_EMIT_SIGNALS) { // Emit render_pass_begin render_pass_begin_signal ev{*this, accumulated_damage}; wf::get_core().emit(&ev); } wf::region_t swap_damage = accumulated_damage; // Gather instructions std::vector instructions; if (params.instances) { for (auto& inst : *params.instances) { inst->schedule_instructions(instructions, params.target, accumulated_damage); } } this->pass = wlr_renderer_begin_buffer_pass( params.renderer ?: wf::get_core().renderer, params.target.get_buffer(), params.pass_opts); if (!pass) { LOGE("Error: failed to start wlr render pass!"); return accumulated_damage; } // Clear visible background areas if (params.flags & RPASS_CLEAR_BACKGROUND) { clear(accumulated_damage, params.background_color); } // Render instances for (auto& instr : wf::reverse(instructions)) { instr.pass = this; instr.instance->render(instr); if (params.reference_output) { instr.instance->presentation_feedback(params.reference_output); } } if (params.flags & RPASS_EMIT_SIGNALS) { render_pass_end_signal end_ev{*this}; wf::get_core().emit(&end_ev); } return swap_damage; } wf::render_target_t wf::render_pass_t::get_target() const { return params.target; } wlr_renderer*wf::render_pass_t::get_wlr_renderer() const { return params.renderer; } wlr_render_pass*wf::render_pass_t::get_wlr_pass() { return pass; } void wf::render_pass_t::clear(const wf::region_t& region, const wf::color_t& color) { auto box = wf::construct_box({0, 0}, params.target.get_size()); auto damage = params.target.framebuffer_region_from_geometry_region(region); wlr_render_rect_options opts; opts.blend_mode = WLR_RENDER_BLEND_MODE_NONE; opts.box = box; opts.clip = damage.to_pixman(); opts.color = { .r = static_cast(color.r), .g = static_cast(color.g), .b = static_cast(color.b), .a = static_cast(color.a), }; wlr_render_pass_add_rect(pass, &opts); } void wf::render_pass_t::add_texture(const wf::texture_t& texture, const wf::render_target_t& adjusted_target, const wlr_fbox& geometry, const wf::region_t& damage, float alpha) { if (wlr_renderer_is_gles2(this->get_wlr_renderer())) { // This is a hack to make sure that plugins can do whatever they want and we render on the correct // target. For example, managing auxilliary textures can mess up with the state of the pipeline on // GLES but not on Vulkan, so to make it easier to write plugins, we just bind the render target again // here to ensure that the state is correct. wf::gles::bind_render_buffer(adjusted_target); } wf::region_t fb_damage = adjusted_target.framebuffer_region_from_geometry_region(damage); wlr_render_texture_options opts{}; opts.texture = texture.texture; opts.alpha = α opts.blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED; // use GL_NEAREST for integer scale. // GL_NEAREST makes scaled text blocky instead of blurry, which looks better // but only for integer scale. const auto preferred_filter = ((adjusted_target.scale - floor(adjusted_target.scale)) < 0.001) ? WLR_SCALE_FILTER_NEAREST : WLR_SCALE_FILTER_BILINEAR; opts.filter_mode = texture.filter_mode.value_or(preferred_filter); opts.transform = wlr_output_transform_compose(wlr_output_transform_invert(texture.transform), adjusted_target.wl_transform); opts.clip = fb_damage.to_pixman(); opts.src_box = texture.source_box.value_or(wlr_fbox{0, 0, 0, 0}); opts.dst_box = fbox_to_geometry(adjusted_target.framebuffer_box_from_geometry_box(geometry)); wlr_render_pass_add_texture(get_wlr_pass(), &opts); } void wf::render_pass_t::add_rect(const wf::color_t& color, const wf::render_target_t& adjusted_target, const wlr_fbox& geometry, const wf::region_t& damage) { if (wlr_renderer_is_gles2(this->get_wlr_renderer())) { wf::gles::bind_render_buffer(adjusted_target); } wf::region_t fb_damage = adjusted_target.framebuffer_region_from_geometry_region(damage); wlr_render_rect_options opts; opts.color = { .r = static_cast(color.r), .g = static_cast(color.g), .b = static_cast(color.b), .a = static_cast(color.a), }; opts.blend_mode = WLR_RENDER_BLEND_MODE_PREMULTIPLIED; opts.clip = fb_damage.to_pixman(); opts.box = fbox_to_geometry(adjusted_target.framebuffer_box_from_geometry_box(geometry)); wf::dassert(opts.box.width >= 0); wf::dassert(opts.box.height >= 0); wlr_render_pass_add_rect(pass, &opts); } void wf::render_pass_t::add_texture(const wf::texture_t& texture, const wf::render_target_t& adjusted_target, const wf::geometry_t& geometry, const wf::region_t& damage, float alpha) { add_texture(texture, adjusted_target, geometry_to_fbox(geometry), damage, alpha); } void wf::render_pass_t::add_rect(const wf::color_t& color, const wf::render_target_t& adjusted_target, const wf::geometry_t& geometry, const wf::region_t& damage) { add_rect(color, adjusted_target, geometry_to_fbox(geometry), damage); } bool wf::render_pass_t::submit() { bool status = wlr_render_pass_submit(pass); this->pass = NULL; return status; } wf::render_pass_t::~render_pass_t() { if (this->pass) { LOGW("Dropping unsubmitted render pass!"); } } wf::render_pass_t::render_pass_t(render_pass_t&& other) { *this = std::move(other); } wf::render_pass_t& wf::render_pass_t::operator =(render_pass_t&& other) { if (this == &other) { return *this; } this->pass = other.pass; other.pass = NULL; this->params = other.params; return *this; } bool wf::render_pass_t::prepare_gles_subpass() { return prepare_gles_subpass(params.target); } bool wf::render_pass_t::prepare_gles_subpass(const wf::render_target_t& target) { bool is_gles = wf::gles::run_in_context_if_gles([&] { GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); wf::gles::bind_render_buffer(target); }); return is_gles; } void wf::render_pass_t::finish_gles_subpass() { // Bind the framebuffer again so that the wlr pass can continue as usual. wf::gles::bind_render_buffer(params.target); GL_CALL(glDisable(GL_SCISSOR_TEST)); } wayfire-0.10.0/src/api/0000775000175000017500000000000015053502647014471 5ustar dkondordkondorwayfire-0.10.0/src/api/wayfire/0000775000175000017500000000000015053502647016137 5ustar dkondordkondorwayfire-0.10.0/src/api/wayfire/compositor-view.hpp0000664000175000017500000000443015053502647022017 0ustar dkondordkondor#ifndef COMPOSITOR_VIEW_HPP #define COMPOSITOR_VIEW_HPP #include "wayfire/geometry.hpp" #include "wayfire/view.hpp" #include namespace wf { /** * color_rect_view_t represents another common type of compositor view - a * view which is simply a colored rectangle with a border. */ class color_rect_view_t : public wf::view_interface_t { protected: wf::color_t _color; wf::color_t _border_color; int border; wf::geometry_t geometry; bool _is_mapped; class color_rect_node_t; /** * Create a colored rect view. The map signal is not fired by default. * The creator of the colored view should also add it to the desired layer. */ color_rect_view_t(); friend class wf::tracking_allocator_t; public: /** * Create and initialize a new color rect view. * The view will be automatically mapped, and if specified, put on the given output and layer. */ static std::shared_ptr create(view_role_t role, wf::output_t *start_output = nullptr, std::optional layer = {}); /** * Emit the unmap signal and then drop the internal reference. */ virtual void close() override; /** Set the view color. Color's alpha is not premultiplied */ virtual void set_color(wf::color_t color); /** Set the view border color. Color's alpha is not premultiplied */ virtual void set_border_color(wf::color_t border); /** Set the border width. */ virtual void set_border(int width); /** Get the view color. Color's alpha is not premultiplied */ wf::color_t get_color() { return _color; } /** Get the view border color. Color's alpha is not premultiplied */ wf::color_t get_border_color() { return _border_color; } /** Get the border width. */ int get_border() { return border; } /** Set the view geometry. */ virtual void set_geometry(wf::geometry_t geometry); virtual wf::geometry_t get_geometry(); /* required for view_interface_t */ virtual bool is_mapped() const override; virtual wlr_surface *get_keyboard_focus_surface() override; virtual bool is_focusable() const override; }; } #endif /* end of include guard: COMPOSITOR_VIEW_HPP */ wayfire-0.10.0/src/api/wayfire/geometry.hpp0000664000175000017500000001066415053502647020512 0ustar dkondordkondor#ifndef WF_GEOMETRY_HPP #define WF_GEOMETRY_HPP #include #include extern "C" { #include } namespace wf { struct point_t { int x, y; }; struct pointf_t { double x, y; pointf_t() : x(0), y(0) {} pointf_t(double _x, double _y) : x(_x), y(_y) {} explicit pointf_t(const point_t& pt) : x(pt.x), y(pt.y) {} pointf_t operator +(const pointf_t& other) const { return pointf_t{x + other.x, y + other.y}; } pointf_t operator -(const pointf_t& other) const { return pointf_t{x - other.x, y - other.y}; } pointf_t& operator +=(const pointf_t& other) { x += other.x; y += other.y; return *this; } pointf_t& operator -=(const pointf_t& other) { x -= other.x; y -= other.y; return *this; } pointf_t operator -() const { return pointf_t{-x, -y}; } point_t round_down() const { return point_t{static_cast(x), static_cast(y)}; } }; struct dimensions_t { int32_t width; int32_t height; }; using geometry_t = wlr_box; point_t origin(const geometry_t& geometry); dimensions_t dimensions(const geometry_t& geometry); geometry_t construct_box( const wf::point_t& origin, const wf::dimensions_t& dimensions); /* Returns the intersection of the two boxes, if the boxes don't intersect, * the resulting geometry has undefined (x,y) and width == height == 0 */ geometry_t geometry_intersection(const geometry_t& r1, const geometry_t& r2); std::ostream& operator <<(std::ostream& stream, const wf::point_t& point); std::ostream& operator <<(std::ostream& stream, const wf::pointf_t& pointf); std::ostream& operator <<(std::ostream& stream, const wf::dimensions_t& dims); bool operator ==(const wf::dimensions_t& a, const wf::dimensions_t& b); bool operator !=(const wf::dimensions_t& a, const wf::dimensions_t& b); bool operator ==(const wf::point_t& a, const wf::point_t& b); bool operator !=(const wf::point_t& a, const wf::point_t& b); wf::point_t operator +(const wf::point_t& a, const wf::point_t& b); wf::point_t operator -(const wf::point_t& a, const wf::point_t& b); wf::point_t operator -(const wf::point_t& a); /** Return the closest valume to @value which is in [@min, @max] */ template T clamp(T value, T min, T max) { return std::min(std::max(value, min), max); } /** * Return the closest geometry to window which is completely inside the output. * The returned geometry might be smaller, but never bigger than window. */ geometry_t clamp(geometry_t window, geometry_t output); // Transform a subbox from coordinate space A to coordinate space B. // The returned subbox will occupy the same relative part of @B as // @box occupies in @A. wf::geometry_t scale_box(wf::geometry_t A, wf::geometry_t B, wf::geometry_t box); // Transform a subbox from coordinate space A to coordinate space B. // The returned subbox will occupy the same relative part of @B as // @box occupies in @A. wlr_fbox scale_fbox(wlr_fbox A, wlr_fbox B, wlr_fbox box); // Helper function to convert wf::geometry_t to wlr_fbox wlr_fbox geometry_to_fbox(const geometry_t& geometry); // Helper function to convert wlr_fbox to wf::geometry_t geometry_t fbox_to_geometry(const wlr_fbox& fbox); } bool operator ==(const wf::geometry_t& a, const wf::geometry_t& b); bool operator !=(const wf::geometry_t& a, const wf::geometry_t& b); wf::point_t operator +(const wf::point_t& a, const wf::geometry_t& b); wf::geometry_t operator +(const wf::geometry_t & a, const wf::point_t& b); wf::geometry_t operator -(const wf::geometry_t & a, const wf::point_t& b); /** Scale the box */ wf::geometry_t operator *(const wf::geometry_t& box, double scale); wlr_fbox operator *(const wlr_fbox& box, double scale); /* @return The length of the given vector */ double abs(const wf::point_t & p); /* Returns true if point is inside rect */ bool operator &(const wf::geometry_t& rect, const wf::point_t& point); /* Returns true if point is inside rect */ bool operator &(const wf::geometry_t& rect, const wf::pointf_t& point); /* Returns true if the two geometries have a common point */ bool operator &(const wf::geometry_t& r1, const wf::geometry_t& r2); /* Make geometry and point printable */ std::ostream& operator <<(std::ostream& stream, const wf::geometry_t& geometry); std::ostream& operator <<(std::ostream& stream, const wlr_fbox& geometry); #endif /* end of include guard: WF_GEOMETRY_HPP */ wayfire-0.10.0/src/api/wayfire/per-output-plugin.hpp0000664000175000017500000000466415053502647022302 0ustar dkondordkondor#pragma once #include "wayfire/signal-provider.hpp" #include #include #include namespace wf { /** * A base class for plugins which want to have an instance per output. */ class per_output_plugin_instance_t { public: // Should be set before initializing the plugin instance. Usually done by per_output_tracker_mixin_t. wf::output_t *output = nullptr; virtual void init() {} virtual void fini() {} virtual ~per_output_plugin_instance_t() = default; }; /** * A mixin class which can be used to setup per-output-instance tracking. */ template class per_output_tracker_mixin_t { public: virtual ~per_output_tracker_mixin_t() = default; void init_output_tracking() { auto& ol = wf::get_core().output_layout; ol->connect(&on_output_added); ol->connect(&on_output_removed); for (auto wo : ol->get_outputs()) { handle_new_output(wo); } } void fini_output_tracking() { on_output_added.disconnect(); on_output_removed.disconnect(); for (auto& [output, inst] : output_instance) { inst->fini(); } output_instance.clear(); } protected: std::map> output_instance; wf::signal::connection_t on_output_added = [=] (output_added_signal *ev) { handle_new_output(ev->output); }; wf::signal::connection_t on_output_removed = [=] (output_pre_remove_signal *ev) { handle_output_removed(ev->output); }; virtual void handle_new_output(wf::output_t *output) { auto inst = std::make_unique(); inst->output = output; auto ptr = inst.get(); output_instance[output] = std::move(inst); ptr->init(); } virtual void handle_output_removed(wf::output_t *output) { output_instance[output]->fini(); output_instance.erase(output); } }; template class per_output_plugin_t : public wf::plugin_interface_t, public per_output_tracker_mixin_t { public: void init() override { this->init_output_tracking(); } void fini() override { this->fini_output_tracking(); } }; } wayfire-0.10.0/src/api/wayfire/window-manager.hpp0000664000175000017500000000634315053502647021575 0ustar dkondordkondor#pragma once #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" namespace wf { /** * An interface which describes basic window management operations on toplevels. */ class window_manager_t { public: virtual ~window_manager_t() = default; /** * Update the remembered last windowed geometry. * * When a view is being tiled or fullscreened, we usually want to remember its size and position so that * it can be restored to that geometry after unfullscreening/untiling. window-manager implementations keep * track of this when a plugin calls update_last_windowed_geometry(). */ virtual void update_last_windowed_geometry(wayfire_toplevel_view view); virtual void update_last_windowed_geometry(wayfire_toplevel_view view, wf::geometry_t windowed_geometry); /** * Get the stored last_windowed_geometry, if it was stored at all. */ virtual std::optional get_last_windowed_geometry(wayfire_toplevel_view view); /** * Mark the view as (un)grabbed. * While a view is grabbed, its last windowed geometry will not be updated. */ virtual void set_view_grabbed(wayfire_toplevel_view view, bool grabbed); /** * Request that an interactive move starts for the given view. */ virtual void move_request(wayfire_toplevel_view view); /** * Request that an interactive resize starts for the given view. */ virtual void resize_request(wayfire_toplevel_view view, uint32_t edges = 0); /** * Try to focus the view and its output. * This will first emit a focus_request signal for the view, and if it is not handled by any plugin, the * default focus actions will be taken (i.e @focus_raise_view(allow_switch_ws=true) will be called). */ virtual void focus_request(wayfire_view view, bool self_request = false); /** * Focus the view and take any actions necessary to make it visible: * - Unminimize minized views * - Switch to the view's workspace, if @allow_switch_ws is set. * - Raise the view to the top of the stack. */ virtual void focus_raise_view(wayfire_view view, bool allow_switch_ws = false); /** Request that the view is (un)minimized */ virtual void minimize_request(wayfire_toplevel_view view, bool minimized); /** * Request that the view is (un)tiled on the given workspace of its primary output. * * Note: by default, any tiled edges means that the view gets the full workarea. * * @param ws If no workspace is provided, the view will be tiled on the current workspace. Otherwise, * the view will be tiled on the provided workspace. */ virtual void tile_request(wayfire_toplevel_view view, uint32_t tiled_edges, std::optional ws = {}); /** * Request that the view is (un)fullscreened on the given workspace of its primary output. * * @param ws If no workspace is provided, the view will be fullscreened or restored to the current * workspace of its primary output. Otherwise, the operation will be done for the given workspace. */ virtual void fullscreen_request(wayfire_toplevel_view view, wf::output_t *output, bool state, std::optional ws = {}); }; } wayfire-0.10.0/src/api/wayfire/signal-provider.hpp0000664000175000017500000001163615053502647021764 0ustar dkondordkondor#pragma once #include #include #include #include namespace wf { namespace signal { class provider_t; /** * A base class for all connection_t, needed to store list of connections in a * type-safe way. */ class connection_base_t { public: connection_base_t(const connection_base_t&) = delete; connection_base_t(connection_base_t&&) = delete; connection_base_t& operator =(const connection_base_t&) = delete; connection_base_t& operator =(connection_base_t&&) = delete; /** * Automatically disconnects from every connected provider. */ virtual ~connection_base_t() { disconnect(); } bool is_connected() const { return !connected_to.empty(); } /** Disconnect from all connected signal providers */ void disconnect(); protected: connection_base_t() {} // Allow provider to deregister itself friend class provider_t; std::vector connected_to; }; /** * A connection to a signal on an object. * Uses RAII to automatically disconnect the signal when it goes out of scope. */ template class connection_t final : public connection_base_t { public: using callback = std::function; /** Initialize an empty signal connection */ connection_t() {} /** Automatically disconnects from all providers */ virtual ~connection_t() {} template using convertible_to_callback_t = std::enable_if_t, void>; /** Initialize a signal connection with the given callback */ template> connection_t(const T& callback) : connection_t() { set_callback(callback); } /** Initialize a signal connection with the given callback */ template> connection_t& operator =(const T& callback) { set_callback(callback); return *this; } template connection_t(std::function& callback) : connection_t() { set_callback(callback); } /** Set the signal callback or override the existing signal callback. */ void set_callback(callback cb) { this->current_callback = cb; } /** Call the stored callback with the given data. */ void emit(SignalType *data) { if (current_callback) { current_callback(data); } } private: // Non-copyable and non-movable, as that would require updating/duplicating // the signal handler. But this is usually not what users of this API want. // Also provider_t holds pointers to this object. connection_t(const connection_t&) = delete; connection_t(connection_t&&) = delete; connection_t& operator =(const connection_t&) = delete; connection_t& operator =(connection_t&&) = delete; callback current_callback; }; class provider_t { public: /** * Signals are designed to be useful for C++ plugins, however, they are * generally quite difficult to bind in other languages. * To avoid this problem, signal::provider_t also provides C-friendlier * callback support. * * The order of arguments is: (this_pointer, signal_name, data_pointer) */ using c_api_callback = std::function; /** Register a connection to be called when the given signal is emitted. */ template void connect(connection_t *callback) { connect_base(index(), callback); } /** Unregister a connection. */ void disconnect(connection_base_t *callback); /** Emit the given signal. */ template void emit(SignalType *data) { this->for_each_connection(index(), [&] (connection_base_t *tc) { auto real_type = dynamic_cast*>(tc); assert(real_type); real_type->emit(data); }); } provider_t(); ~provider_t(); // Non-movable, non-copyable: connection_t keeps reference to this object. // Unclear what happens if this object is duplicated, and plugins usually // don't want this either. provider_t(const provider_t& other) = delete; provider_t& operator =(const provider_t& other) = delete; provider_t(provider_t&& other) = delete; provider_t& operator =(provider_t&& other) = delete; private: template static inline std::type_index index() { return std::type_index(typeid(SignalType)); } void connect_base(std::type_index type, connection_base_t *callback); void for_each_connection(std::type_index type, std::function func); void disconnect_other_side(connection_base_t *callback); struct impl; std::unique_ptr priv; }; } } wayfire-0.10.0/src/api/wayfire/txn/0000775000175000017500000000000015053502647016750 5ustar dkondordkondorwayfire-0.10.0/src/api/wayfire/txn/transaction-manager.hpp0000664000175000017500000000457415053502647023430 0ustar dkondordkondor#pragma once #include "wayfire/signal-provider.hpp" #include "wayfire/txn/transaction-object.hpp" #include namespace wf { namespace txn { /* * The transaction manager keeps track of all committed and pending transactions and ensures that there is at * most one committed transaction for a given object. * * In order to do ensure correct ordering of transactions, it keeps a list of pending transactions. The first * transaction is committed as soon as there are no committed transactions with the same objects. In addition, * any new transactions which are not immediately committed but work with the same objects are coalesced * together. For example, if there are two transactions, one for objects A, for objects B,C and a third * transaction for objects A,B comes in, then all these three are merged together. This merging is done to * avoid sending excessive configure events to clients - for example during an interactive resize. */ class transaction_manager_t : public signal::provider_t { public: transaction_manager_t(); ~transaction_manager_t(); /** * Add a new transaction to the list of scheduled transactions. The transaction might be merged with * other transactions which came before or after it, according to the coalescing schema described above. * * Note that a transaction will never be started immediately. Instead, it will be started on the next idle * event of the event loop. */ void schedule_transaction(transaction_uptr tx); /** * A convenience function to create a transaction for a single object and schedule it via * schedule_transaction(). */ void schedule_object(transaction_object_sptr object); /** * Check whether there is a pending transaction for the given object. */ bool is_object_pending(transaction_object_sptr object) const; /** * Check whether there is a committed transaction for the given object. */ bool is_object_committed(transaction_object_sptr object) const; struct impl; std::unique_ptr priv; }; /** * The new-transaction signal is emitted before a new transaction is added to the transaction manager (e.g. * at the beginning of schedule_transaction()). The transaction may be merged into another transaction before * it is actually executed. */ struct new_transaction_signal { transaction_t *tx; }; } } wayfire-0.10.0/src/api/wayfire/txn/transaction-object.hpp0000664000175000017500000000413315053502647023253 0ustar dkondordkondor#pragma once #include #include #include namespace wf { namespace txn { /** * A transaction object participates in the transactions system. * * Transaction objects usually have double-buffered state, which may not be applicable immediately, that is, * when a state change is requested, it takes some time until the changes can be applied. Sometimes, multiple * such objects are updated together in a single transaction, in which case the changes are to be seen as * atomic across all participating objects. * * The typical example of transaction objects are toplevels, where changing for example the size of the * toplevel requires cooperation from the client, and therefore cannot be done instantaneously. * * When speaking about transaction objects, they usually have three different types of state: current, * committed and pending. Current state is what the object currently is configured as, committed state is a * state which will soon be current (e.g. changes are underway), and pending are changes which have been * planned for the future, but execution has not started yet. */ class transaction_object_t : public signal::provider_t { public: /** * Get a string representing the transaction object. Used for debugging purposes. */ virtual std::string stringify() const; /** * Make the pending state committed. * This function is called when a transaction is committed. */ virtual void commit() = 0; /** * Make the committed state current. * This function is called when all transaction objects in a transaction are ready to apply the committed * state. */ virtual void apply() = 0; virtual ~transaction_object_t() = default; }; using transaction_object_sptr = std::shared_ptr; /** * A signal emitted on a transaction_object_t to indicate that it is ready to be applied. */ struct object_ready_signal { transaction_object_t *self; }; /** * Emit the object-ready signal on the given object. */ void emit_object_ready(wf::txn::transaction_object_t *obj); } } wayfire-0.10.0/src/api/wayfire/txn/transaction.hpp0000664000175000017500000000450515053502647022012 0ustar dkondordkondor#pragma once #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include namespace wf { namespace txn { /** * A transaction contains one or more transaction objects whose state should be applied atomically, that is, * changes to the objects should be applied only after all the objects are ready to apply the changes. */ class transaction_t : public signal::provider_t { public: using timer_setter_t = std::function::callback_t)>; /** * Create a new transaction. * * @param timeout The timeout for the transaction in milliseconds after it is committed. * -1 means that core should pick a default timeout. */ static std::unique_ptr create(int64_t timeout = -1); /** * Create a new empty transaction. * * @param timer A function used to set timeout callbacks at runtime. * @param timeout The maximal duration, in milliseconds, to wait for transaction objects to become ready. * When the timeout is reached, all committed state is applied. */ transaction_t(uint64_t timeout, timer_setter_t timer_setter); /** * Add a new object to the transaction. If the object was already part of it, this is no-op. */ void add_object(transaction_object_sptr object); /** * Get a list of all the objects currently part of the transaction. */ const std::vector& get_objects() const; /** * Commit the transaction, that is, commit the pending state of all participating objects. * As soon as all objects are ready or the transaction times out, the state will be applied. */ void commit(); virtual ~transaction_t() = default; private: std::vector objects; int count_ready_objects = 0; uint64_t timeout; timer_setter_t timer_setter; void apply(bool did_timeout); wf::signal::connection_t on_object_ready; }; using transaction_uptr = std::unique_ptr; /** * A signal emitted on a transaction as soon as it has been applied. */ struct transaction_applied_signal { transaction_t *self; // Set to true if the transaction timed out and the desired object state may not have been achieved. bool timed_out; }; } } wayfire-0.10.0/src/api/wayfire/workspace-stream.hpp0000664000175000017500000000214215053502647022136 0ustar dkondordkondor#pragma once #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include #include #include namespace wf { /** * A workspace stream is a special node which displays a workspace of an output. */ class workspace_stream_node_t : public scene::node_t { public: workspace_stream_node_t(wf::output_t *output, wf::point_t workspace); // The color of the background of the workspace stream. // If not set, the default background color (specified in the config file) // of Wayfire is used. std::optional background; wf::output_t*const output; const wf::point_t ws; // node_t implementation public: std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) override; // The bounding box of a workspace stream is // (0, 0, output_width, output_height). wf::geometry_t get_bounding_box() override; class workspace_stream_instance_t; private: }; } wayfire-0.10.0/src/api/wayfire/view-access-interface.hpp0000664000175000017500000000314015053502647023015 0ustar dkondordkondor#pragma once #include "wayfire/condition/access_interface.hpp" #include "wayfire/view.hpp" #include #include namespace wf { /** * @brief The view_access_interface_t class is a view specific implementation of * access_interface_t. * * Refer to the docs of access_interface_t for more information. * * The following properties are supported: * * format: * property -> type (comment) * * "app_id" -> std::string * "title" -> std::string * "role" -> std::string * "fullscreen" -> bool * "activated" -> bool * "minimized" -> bool * "tiled-left" -> bool * "tiled-right" -> bool * "tiled-top" -> bool * "tiled-bottom" -> bool * "maximized" -> bool * "floating" -> bool * "type" -> std::string (This will return a type string like the matcher plugin did) */ class view_access_interface_t : public access_interface_t { public: /** * @brief view_access_interface_t Default constructor. */ view_access_interface_t(); /** * @brief view_access_interface_t Constructor that immediately assigns a view. * * @param[in] view The view to assign. */ view_access_interface_t(wayfire_view view); // Inherits docs. virtual ~view_access_interface_t() override; // Inherits docs. virtual variant_t get(const std::string & identifier, bool & error) override; /** * @brief set_view Setter for the view to interrogate. * * @param[in] view The view to assign. */ void set_view(wayfire_view view); private: /** * @brief _view The view to interrogate. */ wayfire_view _view; }; } // End namespace wf. wayfire-0.10.0/src/api/wayfire/util.hpp0000664000175000017500000001022615053502647017626 0ustar dkondordkondor#ifndef WF_UTIL_HPP #define WF_UTIL_HPP #include #include #include namespace wf { /** Convert timespect to milliseconds. */ int64_t timespec_to_msec(const timespec& ts); /** Returns current time in msec, using CLOCK_MONOTONIC as a base */ int64_t get_current_time(); /** * A wrapper around wl_listener compatible with C++11 std::functions */ struct wl_listener_wrapper { using callback_t = std::function; wl_listener_wrapper(); ~wl_listener_wrapper(); wl_listener_wrapper(const wl_listener_wrapper &) = delete; wl_listener_wrapper(wl_listener_wrapper &&) = delete; wl_listener_wrapper& operator =(const wl_listener_wrapper&) = delete; wl_listener_wrapper& operator =(wl_listener_wrapper&&) = delete; /** Set the callback to be used when the signal is fired. Can be called * multiple times to update it */ void set_callback(callback_t call); /** Connect this callback to a signal. Calling this on an already * connected listener will have no effect. * @return true if connection was successful */ bool connect(wl_signal *signal); /** Disconnect from the wl_signal. No-op if not connected */ void disconnect(); /** @return true if connected to a wl_signal */ bool is_connected() const; /** Call the stored callback. No-op if no callback was specified */ void emit(void *data); struct wrapper { wl_listener listener; wl_listener_wrapper *self; }; private: callback_t call; wrapper _wrap; }; /** * A wrapper for adding idle callbacks to the event loop */ class wl_idle_call { public: using callback_t = std::function; /* Initialize an empty idle call. */ wl_idle_call(); /** Will disconnect if connected */ ~wl_idle_call(); // Non-movable since wayland holds pointers to this object. wl_idle_call(const wl_idle_call &) = delete; wl_idle_call(wl_idle_call &&) = delete; wl_idle_call& operator =(const wl_idle_call&) = delete; wl_idle_call& operator =(wl_idle_call&&) = delete; /** Set the callback. This will disconnect the wl_idle_call if it is * connected */ void set_callback(callback_t call); /** Run the passed callback the next time the loop goes idle. No effect * if already waiting for idleness, or if the callback hasn't been set. */ void run_once(); /* Same as calling set_callback + run_once */ void run_once(callback_t call); /** Stop waiting for idle, no-op if not connected */ void disconnect(); /** @return true if the event source is active */ bool is_connected() const; /** execute the callback now. do not use manually! */ void execute(); static wl_event_loop *loop; private: callback_t call; wl_event_source *source = NULL; }; /** * A wrapper for wl_event_loop_add_timer / wl_event_loop_timer_update * * @param repeatable If repeatable is true, then the callback's return value indicates whether to repeat the * timer or not. In those cases, it is not possible to destroy the timer from within the callback. */ template class wl_timer { public: // If @repeatable is true, then the return value indicates whether the timer should repeat after the same // amount of time. using callback_t = std::function()>; wl_timer() = default; /** Disconnects the timer if connected */ ~wl_timer(); // Non-movable since wayland holds pointers to this object. wl_timer(const wl_timer &) = delete; wl_timer(wl_timer &&) = delete; wl_timer& operator =(const wl_timer&) = delete; wl_timer& operator =(wl_timer&&) = delete; /** Execute call after a timeout of timeout_ms */ void set_timeout(uint32_t timeout_ms, callback_t call); /** If a timeout has been registered, but not fired yet, remove the * timeout. Otherwise no-op */ void disconnect(); /** @return true if the event source is active */ bool is_connected(); private: wl_event_source *source = NULL; uint32_t timeout = -1; std::function execute; }; } #endif /* end of include guard: WF_UTIL_HPP */ wayfire-0.10.0/src/api/wayfire/output.hpp0000664000175000017500000001574115053502647020220 0ustar dkondordkondor#ifndef OUTPUT_HPP #define OUTPUT_HPP #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/bindings.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include #include #include namespace wf { class view_interface_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { class render_manager; class workspace_set_t; class output_workarea_manager_t; struct plugin_activation_data_t; /** * Flags which can be passed to wf::output_t::activate_plugin() and * wf::output_t::can_activate_plugin(). */ enum plugin_activation_flags_t { /** * Activate the plugin even if input is inhibited, for ex. even when a * lockscreen is active. */ PLUGIN_ACTIVATION_IGNORE_INHIBIT = (1 << 0), /* * Allow the same plugin to be activated multiple times. * The plugin will also have to be deactivated as many times as it has been * activated. */ PLUGIN_ACTIVATE_ALLOW_MULTIPLE = (1 << 1), }; class output_t : public wf::object_base_t, public wf::signal::provider_t { public: /** * The wlr_output that this output represents */ wlr_output *handle; /** * The render manager of this output */ std::unique_ptr render; /** * The manager of the workspace area for this output. */ std::unique_ptr workarea; /** * Get the current workspace set of the output. */ virtual std::shared_ptr wset() = 0; /** * Set the current workspace set. * * The old workspace set will become invisible (that is, necessary scenegraph nodes will be disabled), but * it will remain attached to the output. */ virtual void set_workspace_set(std::shared_ptr wset) = 0; /** * Get a textual representation of the output */ std::string to_string() const; /** * Get the logical resolution of the output, i.e if an output has mode * 3860x2160, scale 2 and transform 90, then get_screen_size will report * that it has logical resolution of 1080x1920 */ virtual wf::dimensions_t get_screen_size() const = 0; /** * Same as get_screen_size() but returns a wf::geometry_t with x,y = 0 */ wf::geometry_t get_relative_geometry() const; /** * Returns the output geometry as the output layout sees it. This is * typically the same as get_relative_geometry() but with meaningful x and y */ wf::geometry_t get_layout_geometry() const; /** * Moves the pointer so that it is inside the output * * @param center If set to true, the pointer will be centered on the * output, regardless of whether it was inside before. */ void ensure_pointer(bool center = false) const; /** * Gets the cursor position relative to the output */ wf::pointf_t get_cursor_position() const; virtual std::shared_ptr node_for_layer( wf::scene::layer layer) const = 0; /** * Checks if a plugin can activate. This may not succeed if a plugin * with the same abilities is already active or if input is inhibited. * * @param flags A bitwise OR of plugin_activation_flags_t. * * @return true if the plugin is able to be activated, false otherwise. */ virtual bool can_activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) = 0; /** * Same as can_activate_plugin(plugin_grab_interface_uptr), but checks for * any plugin with the given capabilities. * * @param caps The capabilities to check. * @param flags A bitwise OR of plugin_activation_flags_t. */ virtual bool can_activate_plugin(uint32_t caps, uint32_t flags = 0) = 0; /** * Activates a plugin. Note that this may not succeed, if a plugin with the * same abilities is already active. However the same plugin might be * activated twice. * * @param flags A bitwise OR of plugin_activation_flags_t. * * @return true if the plugin was successfully activated, false otherwise. */ virtual bool activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) = 0; /** * Deactivates a plugin once, i.e if the plugin was activated more than * once, only one activation is removed. * * @return true if the plugin remains activated, false otherwise. */ virtual bool deactivate_plugin(wf::plugin_activation_data_t *owner) = 0; /** * Send cancel to all active plugins, * see plugin_grab_interface_t::callbacks.cancel */ virtual void cancel_active_plugins() = 0; /** * @return true if a grab interface with the given name is activated, false * otherwise. */ virtual bool is_plugin_active(std::string owner_name) const = 0; /** * Sets (or unsets) the output as inhibited, so that no plugins can be activated * except those that ignore inhibitions. */ void set_inhibited(bool inhibited); /** * Returns whether the output is inhibited.. */ bool is_inhibited() const; /** * Switch the workspace so that view becomes visible. * @return true if workspace switch really occurred */ bool ensure_visible(wayfire_view view); /** * the add_* functions are used by plugins to register bindings. They pass * a wf::option_t, which means that core will always use the latest binding * which is in the option. * * Adding a binding happens on a per-output basis. If a plugin registers * bindings on each output, it will receive for ex. a keybinding only on * the currently focused one. * * @return The wf::binding_t which can be used to unregister the binding. */ virtual void add_key(option_sptr_t key, wf::key_callback*) = 0; virtual void add_axis(option_sptr_t axis, wf::axis_callback*) = 0; virtual void add_button(option_sptr_t button, wf::button_callback*) = 0; virtual void add_activator(option_sptr_t activator, wf::activator_callback*) = 0; /** * Remove all bindings which have the given callback, regardless of the type. */ virtual void rem_binding(void *callback) = 0; virtual ~output_t(); protected: /* outputs are instantiated internally by core */ output_t(); bool inhibited = false; }; /** * Find the active view on the given output. It is the same as wf::get_core().seat->get_active_view() if the * output is currently focused, otherwise NULL. */ wayfire_view get_active_view_for_output(wf::output_t *output); /** * Collect all nodes which belong to an output from the scenegraph. */ std::vector> collect_output_nodes( wf::scene::node_ptr root, wf::output_t *output); } #endif /* end of include guard: OUTPUT_HPP */ wayfire-0.10.0/src/api/wayfire/scene-render.hpp0000664000175000017500000001756615053502647021241 0ustar dkondordkondor#pragma once #include #include #include #include #include #include #include #include namespace wf { class output_t; namespace scene { class node_t; using node_ptr = std::shared_ptr; class render_instance_t; using render_instance_uptr = std::unique_ptr; /** * Describes the result of trying to do direct scanout of a render instance on * an output. */ enum class direct_scanout { /** * The node cannot be directly scanned out on the output, but does not occlude * any node below it which may be scanned out directly. */ SKIP, /** * The node cannot be directly scanned out on the output, but covers a part * of the output, thus makes direct scanout impossible. */ OCCLUSION, /** * The node was successfully scanned out. */ SUCCESS, }; /** * A single rendering call in a render pass. */ struct render_instruction_t { render_pass_t *pass = NULL; // auto-filled by the render pass scheduling instructions render_instance_t *instance = NULL; render_target_t target; wf::region_t damage; std::any data = {}; }; /** * When (parts) of the scenegraph have to be rendered, they have to be * 'instantiated' first. The instantiation of a (sub)tree of the scenegraph * is a tree of render instances, called a render tree. The purpose of the * render trees is to enable damage tracking (each render instance has its own * damage), while allowing arbitrary transformations in the scenegraph (e.g. a * render instance does not need to export information about how it transforms * its children). Due to this design, render trees have to be regenerated every * time the relevant portion of the scenegraph changes. * * Actually painting a render tree (called render pass) is a process involving * three steps: * * 1. Calculate the damage accumulated from the render tree. * 2. A front-to-back iteration through the render tree, so that every node * calculates the parts of the destination buffer it should actually repaint. * 3. A final back-to-front iteration where the actual rendering happens. */ class render_instance_t { public: virtual ~render_instance_t() = default; /** * Handle the front-to-back iteration (2.) from a render pass. * Each instance should add the render instructions (calls to * render_instance_t::render()) for itself and its children. * * @param instructions A list of render instructions to be executed. * Instructions are evaluated in the reverse order they are pushed * (e.g. from instructions.rbegin() to instructions.rend()). * @param target The target framebuffer to render the node and its children. * Note that some nodes may cause their children to be rendered to * auxiliary buffers. * @param damage The damaged region of the node, in node-local coordinates. * Nodes may subtract from the damage, to prevent rendering below opaque * regions, or expand it for certain special effects like blur. */ virtual void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) = 0; /** * Render the node with the given parameters. * Typically, this would be called by a render pass after calling schedule_instructions(). * * The node should not paint outside of the specified region. * All coordinates are to be given in the node's parent coordinate system. * * @param data The data required to repaint the node, including the current render pass, the render * target, damaged region, etc. */ virtual void render(const render_instruction_t& data) {} /** * Notify the render instance that it has been presented on an output. * Note that a render instance may get multiple presentation_feedback calls * for the same rendered frame. */ virtual void presentation_feedback(wf::output_t *output) {} /** * Attempt direct scanout on the given output. * * Direct scanout is an optimization where a buffer from a node is directly * attached as the front buffer of an output. This is possible in a single * case, namely when the topmost node with visible contents on an output * covers it perfectly. * * @return The result of the attempt, see @direct_scanout. */ virtual direct_scanout try_scanout(wf::output_t *output) { // By default, we report an occlusion, e.g. scanout is not possible, // neither for this node, nor for nodes below. return direct_scanout::OCCLUSION; } /** * Compute the render instance's visible region on the given output. * * The visible region can be used for things like determining when to send frame done events to * wlr_surfaces and to ignore damage to invisible parts of a render instance. */ virtual void compute_visibility(wf::output_t *output, wf::region_t& visible) {} }; using damage_callback = std::function; /** * A signal emitted when a part of the node is damaged. * on: the node itself. */ struct node_damage_signal { wf::region_t region; }; /** * A helper function to emit the damage signal on a node. */ template inline void damage_node(NodePtr node, wf::region_t damage) { node_damage_signal data; data.region = damage; node->emit(&data); } /** * A helper function for direct scanout implementations. * It tries to forward the direct scanout request to the first render instance * in the given list, and returns the first non-SKIP result, or SKIP, if no * instance interacts with direct scanout. */ direct_scanout try_scanout_from_list( const std::vector& instances, wf::output_t *scanout); /** * A helper function for compute_visibility implementations. It applies an offset to the damage and reverts it * afterwards. It also calls compute_visibility for the children instances. */ void compute_visibility_from_list(const std::vector& instances, wf::output_t *output, wf::region_t& region, const wf::point_t& offset); /** * A helper class for easier implementation of render instances. * It automatically schedules instruction for the current node and tracks damage from the main node. */ template class simple_render_instance_t : public render_instance_t { public: simple_render_instance_t(Node *self, damage_callback push_damage, wf::output_t *output) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; this->output = output; self->connect(&on_self_damage); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = damage & self->get_bounding_box(), }); } protected: std::shared_ptr self; wf::signal::connection_t on_self_damage = [=] (scene::node_damage_signal *ev) { push_damage(ev->region); }; damage_callback push_damage; wf::output_t *output; }; /** * Emitted on: node * The signal is used by some nodes to avoid unnecessary scenegraph recomputations. * For example it is used by nodes whose render instances keep a list of children, so that when the children * are updated, these nodes update only their internal list of children and not the entire scenegraph. */ struct node_regen_instances_signal {}; uint32_t optimize_nested_render_instances(wf::scene::node_ptr node, uint32_t flags); } } wayfire-0.10.0/src/api/wayfire/view.hpp0000664000175000017500000001673715053502647017640 0ustar dkondordkondor#ifndef VIEW_HPP #define VIEW_HPP #include #include #include #include "wayfire/nonstd/tracking-allocator.hpp" #include "wayfire/object.hpp" #include "wayfire/render.hpp" #include #include #include namespace wf { class view_interface_t; class workspace_set_t; struct render_target_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { class output_t; namespace scene { class view_node_t; class transform_manager_node_t; } /* abstraction for desktop-apis, no real need for plugins * This is a base class to all "drawables" - desktop views, subsurfaces, popups */ enum view_role_t { /** Regular views which can be moved around. */ VIEW_ROLE_TOPLEVEL, /** Views which position is fixed externally, for ex. Xwayland OR views */ VIEW_ROLE_UNMANAGED, /** * Views which are part of the desktop environment, for example panels, * background views, etc. */ VIEW_ROLE_DESKTOP_ENVIRONMENT, }; /** * The view_interface_t represents a window shown to the user. It includes panels, backgrounds, notifications, * and toplevels (which derive from the subclass toplevel_view_interface_t). * * Views should be allocated via the helper allocator tracking_allocator_t: * ``` * auto& alloc = tracking_allocator_t::get(); * alloc.allocate(arguments) * ``` * * This ensures that all plugins can query a list of all available views at any given time. */ class view_interface_t : public wf::signal::provider_t, public wf::object_base_t, public std::enable_shared_from_this { public: /** * Get the root of the view tree. This is the node which contains the view * and all of its child views. * * Usually, the tree root node has at least the transformed_node as its child, * and the tree root nodes of child views. */ const scene::floating_inner_ptr& get_root_node() const; /** * Get the root of the view itself, including its main surface, subsurfaces * and transformers, but not dialogs. */ const std::shared_ptr& get_transformed_node() const; /** * Get the node which contains the main view (+subsurfaces) only. */ const scene::floating_inner_ptr& get_surface_root_node() const; /** The current view role. */ view_role_t role = VIEW_ROLE_TOPLEVEL; /** Set the view role */ virtual void set_role(view_role_t new_role); /** Get a textual identifier for this view. */ std::string to_string() const; /** Wrap the view into a nonstd::observer_ptr<> */ wayfire_view self(); /** * Set the view's output. * * If the new output is different from the previous, the view will be * removed from the layer it was on the old output. */ virtual void set_output(wf::output_t *new_output); /** * Get the view's main output. */ virtual wf::output_t *get_output(); /** Request that the view closes. */ virtual void close(); /** * Ping the view's client. * If the ping request times out, `ping-timeout` event will be emitted. */ virtual void ping(); /** * @return The bounding box of the view, which includes all (sub)surfaces, * menus, etc. after applying the view transformations. */ virtual wlr_box get_bounding_box(); /** * @return the wlr_surface which should receive focus when focusing this * view. Views which aren't backed by a wlr_surface should implement the * compositor_view interface. * * In case no focus surface is available, or the view should not be focused, * nullptr should be returned. */ virtual wlr_surface *get_keyboard_focus_surface() = 0; /** * Check whether the surface is focusable. Note the actual ability to give * keyboard focus while the surface is mapped is determined by the keyboard * focus surface or the compositor_view implementation. * * This is meant for plugins like matcher, which need to check whether the * view is focusable at any point of the view life-cycle. */ virtual bool is_focusable() const; /** Damage the whole view and add the damage to its output */ virtual void damage(); /** @return the app-id of the view */ virtual std::string get_app_id() { return ""; } /** @return the title of the view */ virtual std::string get_title() { return ""; } /** @return true if the view has active transformers */ bool has_transformer(); /** * A snapshot of the view is a copy of the view's contents into a framebuffer. */ virtual void take_snapshot(wf::auxilliary_buffer_t& buffer); /** * @return the wl_client associated with this surface, or null if the * surface doesn't have a backing wlr_surface. */ wl_client *get_client(); /** * @return the wlr_surface associated with this surface, or null if no * the surface doesn't have a backing wlr_surface. */ virtual wlr_surface *get_wlr_surface(); virtual bool is_mapped() const { return false; } virtual ~view_interface_t(); class view_priv_impl; std::unique_ptr priv; template static std::shared_ptr create(Args... args) { static_assert(std::is_base_of_v, "view_interface_t::create can be used only when T is a view type!"); auto view = tracking_allocator_t::get().allocate(args...); view->base_initialization(); return view; } protected: view_interface_t(); void set_surface_root_node(scene::floating_inner_ptr surface_root_node); /** * Initialize the base implementation of a view, for example the node hierarchy. */ void base_initialization(); /** * Emit the view map signal. It indicates that a view has been mapped, i.e. * plugins can now "work" with it. Note that not all views will emit the map * event. */ virtual void emit_view_map(); /** * Emit the view unmap signal. It indicates that the view is in the process of * being destroyed. Most plugins should stop any actions they have on the view. */ virtual void emit_view_unmap(); /** * Emit the view pre-unmap signal. It is emitted right before the view * destruction start. At this moment a plugin can still take a snapshot of the * view. Note that not all views emit the pre-unmap signal, however the unmap * signal is mandatory for all views. */ virtual void emit_view_pre_unmap(); }; wayfire_view wl_surface_to_wayfire_view(wl_resource *surface); /** * Find a view this node belongs to. * May return NULL if @node is NULL or it is not a child of a view node. */ wayfire_view node_to_view(wf::scene::node_ptr node); wayfire_view node_to_view(wf::scene::node_t *node); /** * A base class for nodes which are to be identified as views. * Used by @node_to_view in order to figure out whether a given node is a view or not. */ class view_node_tag_t { public: view_node_tag_t(wayfire_view view) { this->view = view; } virtual ~view_node_tag_t() = default; wayfire_view get_view() const { return view; } private: wayfire_view view; }; } #endif wayfire-0.10.0/src/api/wayfire/object.hpp0000664000175000017500000000742215053502647020123 0ustar dkondordkondor#ifndef OBJECT_HPP #define OBJECT_HPP #include #include #include #include #include namespace wf { /** * Subclasses of custom_data_t can be stored inside an object_base_t */ class custom_data_t { public: custom_data_t() = default; virtual ~custom_data_t() = default; custom_data_t(custom_data_t&& other) = default; custom_data_t(const custom_data_t& other) = default; custom_data_t& operator =(custom_data_t&& other) = default; custom_data_t& operator =(const custom_data_t& other) = default; }; /** * A base class for "objects". Objects provide signals and ways for plugins to * store custom data about the object. */ class object_base_t { public: /** Get a human-readable description of the object */ std::string to_string() const; /** Get the ID of the object. Each object has a unique ID */ uint32_t get_id() const; /** * Retrieve custom data stored with the given name. If no such data exists, * then it is created with the default constructor. * * REQUIRES a default constructor * If your type doesn't have one, use store_data + get_data */ template nonstd::observer_ptr get_data_safe( std::string name = typeid(T).name()) { auto data = get_data(name); if (data) { return data; } else { store_data(std::make_unique(), name); return get_data(name); } } /* Retrieve custom data stored with the given name. If no such * data exists, NULL is returned */ template nonstd::observer_ptr get_data( std::string name = typeid(T).name()) { return nonstd::make_observer(dynamic_cast(_fetch_data(name))); } /* Assigns the given data to the given name */ template void store_data(std::unique_ptr stored_data, std::string name = typeid(T).name()) { _store_data(std::move(stored_data), name); } /* Returns true if there is saved data under the given name */ template bool has_data() { return has_data(typeid(T).name()); } /** @return true if there is saved data with the given name */ bool has_data(std::string name); /** Remove the saved data under the given name */ void erase_data(std::string name); /** Remove the saved data for the type T */ template void erase_data() { erase_data(typeid(T).name()); } /* Erase the saved data from the store and return the pointer */ template std::unique_ptr release_data( std::string name = typeid(T).name()) { if (!has_data(name)) { return {nullptr}; } auto stored = _fetch_erase(name); return std::unique_ptr(dynamic_cast(stored)); } virtual ~object_base_t(); object_base_t(const object_base_t &) = delete; object_base_t(object_base_t &&) = delete; object_base_t& operator =(const object_base_t&) = delete; object_base_t& operator =(object_base_t&&) = delete; protected: object_base_t(); /** Clear all stored data. */ void _clear_data(); private: /** Just get the data under the given name, or nullptr, if it does not exist */ custom_data_t *_fetch_data(std::string name); /** Get the data under the given name, and release the pointer, deleting * the entry in the map */ custom_data_t *_fetch_erase(std::string name); /** Store the given data under the given name */ void _store_data(std::unique_ptr data, std::string name); class obase_impl; std::unique_ptr obase_priv; }; } #endif /* end of include guard: OBJECT_HPP */ wayfire-0.10.0/src/api/wayfire/option-wrapper.hpp0000664000175000017500000000307015053502647021636 0ustar dkondordkondor#pragma once #include #include #include #include namespace wf { namespace detail { // Forward declaration to avoid adding unnecessary includes. [[noreturn]] void option_wrapper_debug_message(const std::string& option_name, const std::runtime_error& err); [[noreturn]] void option_wrapper_debug_message(const std::string& option_name, const std::logic_error& err); std::shared_ptr load_raw_option(const std::string& name); } /** * A simple wrapper around a config option. */ template class option_wrapper_t : public base_option_wrapper_t { public: /** * Initialize the option wrapper and directly load the given option. */ option_wrapper_t(const std::string& option_name) : wf::base_option_wrapper_t() { this->load_option(option_name); } void load_option(const std::string& option_name) { try { base_option_wrapper_t::load_option(option_name); } catch (const std::runtime_error& err) { detail::option_wrapper_debug_message(option_name, err); } catch (const std::logic_error& err) { detail::option_wrapper_debug_message(option_name, err); } } option_wrapper_t() : wf::base_option_wrapper_t() {} protected: std::shared_ptr load_raw_option(const std::string& name) override { return detail::load_raw_option(name); } }; } wayfire-0.10.0/src/api/wayfire/dassert.hpp0000664000175000017500000000144015053502647020314 0ustar dkondordkondor#pragma once #include #include namespace wf { /** * Print the current stacktrace at runtime. * * @param fast_mode If fast_mode is true, the stacktrace will be generated * using the fastest possible method. However, this means that not all * information will be printed (for ex., line numbers may be missing). */ void print_trace(bool fast_mode); /** * Assert that the condition is true. * Optionally print a message. * Print backtrace when the assertion fails and exit. */ inline void dassert(bool condition, std::string message = "") { if (!condition) { LOGE(message); print_trace(false); std::_Exit(-1); } } } #define DASSERT(condition) \ wf::dassert(condition, "Assertion failed at " __FILE__ ":" __LINE__) wayfire-0.10.0/src/api/wayfire/signal-definitions.hpp0000664000175000017500000004341115053502647022441 0ustar dkondordkondor#ifndef SIGNAL_DEFINITIONS_HPP #define SIGNAL_DEFINITIONS_HPP #include "wayfire/view.hpp" #include "wayfire/output.hpp" /** * Documentation of signals emitted from core components. * Each signal documentation follows the following scheme: * * name: The base name of the signal * on: Which components the plugin is emitted on. Prefixes are specified * in (), i.e test(view-) means that the signal is emitted on component * test with prefix 'view-'. * when: Description of when the signal is emitted. * argument: What the signal data represents when there is no dedicated * signal data struct. */ namespace wf { /* ----------------------------------------------------------------------------/ * Core signals * -------------------------------------------------------------------------- */ /** * on: core * when: Emitted when the wlroots backend has been started. */ struct core_backend_started_signal {}; /** * on: core * when: Emitted when the Wayfire initialization has been completed and the main loop is about to start. */ struct core_startup_finished_signal {}; /** * on: core * when: Right before the shutdown sequence starts. */ struct core_shutdown_signal {}; class input_device_t; /** * on: core * when: Whenever a new input device is added. */ struct input_device_added_signal { nonstd::observer_ptr device; }; /** * on: core * when: Whenever an input device is removed. */ struct input_device_removed_signal { nonstd::observer_ptr device; }; /** * on: core * when: When the corresponding switch device state changes. */ struct switch_signal { /** The switch device */ nonstd::observer_ptr device; /** On or off */ bool state; }; /** * Describes the various ways in which core should handle an input event. */ enum class input_event_processing_mode_t { /** * Core should process this event for input grabs, bindings and eventually * forward it to a client surface. */ FULL, /** * Core should process this event for input grabs and bindings, but not send * the event to the client. */ NO_CLIENT, /** * Core should not process this event at all. */ IGNORE, }; /** * Emitted for the following events: * pointer_motion, pointer_motion_absolute, pointer_button, pointer_axis, * pointer_swipe_begin, pointer_swipe_update, pointer_swipe_end, pointer_pinch_begin, pointer_pinch_update, * pointer_pinch_end, pointer_hold_begin, pointer_hold_end, * keyboard_key, keyboard_modifiers (mwlr_keyboard_modifiers), * touch_down, touch_up, touch_motion, * tablet_proximity, tablet_axis, tablet_button, tablet_tip * * on: core * when: The input event signals are sent from core whenever a new input from an * input device arrives. The events are sent before any processing is done, * and they are independent of plugin input grabs and other wayfire input * mechanisms. * * The event data can be modified by plugins, and then the modified event * will be used instead. However plugins which modify the event must ensure * that subsequent events are adjusted accordingly as well. * * example: The pointer_motion event is emitted with data of type * input_event_signal */ template struct input_event_signal { /* The event as it has arrived from wlroots */ wlr_event_t *event; /** * Describes how core should handle this event. * * This is currently supported for only a subset of signals, namely: * * pointer_button, keyboard_key, touch_down */ input_event_processing_mode_t mode = input_event_processing_mode_t::FULL; /** * The wlr device which triggered the event. May be NULL for events which are merged together for ex. via * wlr-cursor. */ wlr_input_device *device; }; /** * Same as @input_event_signal, but emitted after bindings have been handled and before the event is sent * to the client (if at all). * * Note: currently only keyboard_key events support this signal. * TODO: add the event for the rest of the event types. */ template struct pre_client_input_event_signal { wlr_event_t *event; wlr_input_device *device; /** Last opportunity for plugins to influence the processing of this event. */ bool carried_out = false; /** The node which will receive the event. May be NULL. */ wf::scene::node_ptr focus_node; }; /** * Same as @input_event_signal, but emitted after the event has been handled. */ template struct post_input_event_signal { wlr_event_t *event; wlr_input_device *device; }; /** * Emitted on core when the pointer focus changes. */ struct pointer_focus_changed_signal { wf::scene::node_ptr new_focus; }; /** * Emitted on core when the touch focus changes. */ struct touch_focus_changed_signal { wf::scene::node_ptr new_focus; int32_t finger_id; }; /** * on: core * when: When the config file is reloaded */ struct reload_config_signal {}; /** * on: core * when: idle inhibit changed. */ struct idle_inhibit_changed_signal { bool inhibit; }; /** * on: output, core(output-) * when: Immediately after the output becomes focused. */ struct output_gain_focus_signal { wf::output_t *output; }; /** * on: output, core * when: When an output activates or deactivates. Note that not all plugin actions are reflected with this * signal. A plugin activates on an output usually if it (temporarily) changes the way Wayfire works like * Expo, Scale, Vswitch. One-shot actions like command or wsets do not send this signal. */ struct output_plugin_activated_changed_signal { // The output on which the plugin was activated. May be NULL if the plugin works globally. wf::output_t *output; // The name of the plugin. std::string plugin_name; // Whether the plugin was activated (true) or deactivated (false). bool activated; }; /* ----------------------------------------------------------------------------/ * Output rendering signals (see also wayfire/workspace-stream.hpp) * -------------------------------------------------------------------------- */ /** * on: output * when: Whenever the output is ready to start rendering. This can happen * either on output creation or whenever all inhibits in wayfire-shell have * been removed. */ struct output_start_rendering_signal { wf::output_t *output; }; /* ----------------------------------------------------------------------------/ * Output workspace signals * -------------------------------------------------------------------------- */ /** * on: output * when: Whenever the current workspace set of the output changes. */ struct workspace_set_changed_signal { std::shared_ptr new_wset; wf::output_t *output; }; /** * on: workspace set, output * when: Whenever the current workspace on the output has changed. */ struct workspace_changed_signal { /** Previously focused workspace */ wf::point_t old_viewport; /** Workspace that is to be focused or became focused */ wf::point_t new_viewport; /** The output this is happening on */ wf::output_t *output; }; /** * on: output * when: Whenever a workspace change is requested by core or by a plugin. * This can be used by plugins who wish to handle workspace changing * themselves, for ex. if animating the transition. */ struct workspace_change_request_signal { /** Previously focused workspace */ wf::point_t old_viewport; /** Workspace that is to be focused or became focused */ wf::point_t new_viewport; /** The output this is happening on */ wf::output_t *output; /** Whether the request has already been handled. */ bool carried_out; /** * A list of views whose geometry should remain stationary. * The caller is responsible for ensuring that this doesn't move the views * outside of the visible area. * * Note that the views might still be moved if a previous workspace change * request is being serviced. */ std::vector fixed_views; }; /** * on: workspace set * when: Whenever the workspace grid size changes. */ struct workspace_grid_changed_signal { /** The grid size before the change. */ wf::dimensions_t old_grid_size; /** The grid size after the change. */ wf::dimensions_t new_grid_size; }; /** * on: output * when: Whenever the available workarea changes. */ struct workarea_changed_signal { wf::output_t *output; wf::geometry_t old_workarea; wf::geometry_t new_workarea; }; /** * on: output * when: Whenever a fullscreen view is promoted on top of the other layers. */ struct fullscreen_layer_focused_signal { bool has_promoted; }; /* ----------------------------------------------------------------------------/ * View signals * -------------------------------------------------------------------------- */ /** * on: view, output, core * when: After the view becomes mapped. This signal must also be emitted from all compositor views. */ struct view_mapped_signal { wayfire_view view; /* Indicates whether the position already has its initial position */ bool is_positioned = false; }; /** * on: view, output, core * when: Immediately before unmapping a mapped view. The signal may not be * emitted from all views, but it is necessary for unmap animations to work. */ struct view_pre_unmap_signal { wayfire_view view; }; /** * name: unmapped * on: view, output, core * when: After a previously mapped view becomes unmapped. This must be emitted * for all views. */ struct view_unmapped_signal { wayfire_view view; }; /** * on: view, new output, core * when: Immediately after the view's output changes. Note that child views may still be on the old output. */ struct view_set_output_signal { wayfire_view view; // The previous output of the view. wf::output_t *output; }; /* ----------------------------------------------------------------------------/ * View state signals * -------------------------------------------------------------------------- */ /** * on: view * when: After the view's parent changes. */ struct view_parent_changed_signal { wayfire_toplevel_view view; }; /** * on: view, output(view-) * when: After the view's minimized state changes. */ struct view_minimized_signal { wayfire_toplevel_view view; }; /** * on: output * when: Emitted whenever some entity requests that the view's minimized state * changes. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::minimize_request() */ struct view_minimize_request_signal { wayfire_toplevel_view view; /** true is minimized, false is restored */ bool state; /** * Whether some plugin will service the minimization request, in which * case other plugins and core should ignore the request. */ bool carried_out = false; }; /** * on: view * when: After the view's activated state changes. */ struct view_activated_state_signal { wayfire_toplevel_view view; }; /** * on: view, output(view-) * when: After the view's tiled edges change. */ struct view_tiled_signal { wayfire_toplevel_view view; /** Previously tiled edges */ uint32_t old_edges; /** Currently tiled edges */ uint32_t new_edges; }; /** * on: output * when: Emitted whenever some entity requests that the view's tiled edges * change. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::tile_request() */ struct view_tile_request_signal { wayfire_toplevel_view view; /** The desired edges */ uint32_t edges; /** * The geometry the view should have. This is for example the last geometry * a view had before being tiled. The given geometry is only a hint by core * and plugins may override it. It may also be undefined (0,0 0x0). */ wf::geometry_t desired_size; /** * The target workspace of the operation. */ wf::point_t workspace; /** * Whether some plugin will service the tile request, in which case other * plugins and core should ignore the request. */ bool carried_out = false; }; /** * on: view, output(view-) * when: After the view's fullscreen state changes. */ struct view_fullscreen_signal { wayfire_toplevel_view view; bool state; }; /** * on: output * when: Emitted whenever some entity requests that the view's fullscreen state * change. If no plugin is available to service the request, it is carried * out by core. See view_interface_t::fullscreen_request() */ struct view_fullscreen_request_signal { wayfire_toplevel_view view; /** The desired fullscreen state */ bool state; /** * Whether some plugin will service the fullscreen request, in which case * other plugins and core should ignore the request. */ bool carried_out = false; /** * The geometry the view should have. This is for example the last geometry * a view had before being fullscreened. The given geometry is only a hint * by core and plugins may override it. It may also be undefined (0,0 0x0). */ wf::geometry_t desired_size; /** * The target workspace of the operation. */ wf::point_t workspace; }; /** * on: view, core * when: Emitted whenever some entity (typically a panel) wants to focus the view. */ struct view_focus_request_signal { wayfire_view view; /** Set to true if core and other plugins should not handle this request. */ bool carried_out = false; /** Set to true if the request comes from the view client itself */ bool self_request; }; /** * name: set-sticky * on: view, output(view-) * when: Whenever the view's sticky state changes. */ struct view_set_sticky_signal { wayfire_toplevel_view view; }; /** * on: view, core * when: After the view's title has changed. */ struct view_title_changed_signal { wayfire_view view; }; /** * on: view, core * when: After the view's app-id has changed. */ struct view_app_id_changed_signal { wayfire_view view; }; /** * on: output, core * when: To show a menu with window related actions. */ struct view_show_window_menu_signal { wayfire_view view; /** The position as requested by the client, in surface coordinates */ wf::point_t relative_position; }; /** * on: view, output(view-), core(view-) * when: Whenever the view's wm geometry changes. */ struct view_geometry_changed_signal { wayfire_toplevel_view view; /** The old wm geometry */ wf::geometry_t old_geometry; }; /** * on: output * when: Whenever the view's workspace changes. (Every plugin changing the * view's workspace should emit this signal). */ struct view_change_workspace_signal { wayfire_toplevel_view view; wf::point_t from, to; /** * Indicates whether the old workspace is known. * If false, then the `from` field should be ignored. */ bool old_workspace_valid = true; }; /** * on: view, core * when: Whenever the value of view::should_be_decorated() changes. */ struct view_decoration_state_updated_signal { wayfire_toplevel_view view; }; /** * on: view * when: Whenever the client fails to respond to a ping request within * the expected time(10 seconds). */ struct view_ping_timeout_signal { wayfire_view view; }; /* ----------------------------------------------------------------------------/ * View <-> output signals * -------------------------------------------------------------------------- */ /** * on: core * when: Immediately before the view is moved to a different workspace set. * view-moved-to-set is emitted afterwards. */ struct view_pre_moved_to_wset_signal { /* The view being moved */ wayfire_toplevel_view view; /* The old wset the view was on, may be NULL. */ std::shared_ptr old_wset; /* The new wset the view is being moved to, may be NULL. */ std::shared_ptr new_wset; }; /** * on: core * when: After the view has been moved to a new wset. */ struct view_moved_to_wset_signal { /* The view being moved */ wayfire_toplevel_view view; /* The old wset the view was on, may be NULL. */ std::shared_ptr old_wset; /* The new wset the view is being moved to, may be NULL. */ std::shared_ptr new_wset; }; /** * on: output * when: This signal is a combination of the unmapped, minimized and set-output signals. In the latter case, * the signal is emitted on the view's previous output. The meaning of this signal is that the view is no * longer available for focus, interaction with the user, etc. on the output where it used to be. */ struct view_disappeared_signal { wayfire_view view; }; /** * on: output * when: Whenever an interactive move is requested on the view. See also * view_interface_t::move_request() */ struct view_move_request_signal { wayfire_toplevel_view view; }; /** * on: output * when: Whenever an interactive resize is requested on the view. See also * view_interface_t::resize_request() */ struct view_resize_request_signal { wayfire_toplevel_view view; /** The requested resize edges */ uint32_t edges; }; /** * on: view and core(view-) * when: the client indicates the views hints have changed (example urgency hint). */ struct view_hints_changed_signal { wayfire_view view; bool demands_attention = false; }; /** * on: core * when: Whenever a client wants to invoke the system bell if such is available. * Note the system bell may or may not be tied to a particular view, so the * signal may be emitted with a nullptr view. */ struct view_system_bell_signal { wayfire_view view; }; /** * on: input method relay * when: A text input commits */ struct text_input_commit_signal { wlr_box cursor_rect; }; } #endif wayfire-0.10.0/src/api/wayfire/scene.hpp0000664000175000017500000004425415053502647017756 0ustar dkondordkondor#pragma once #include #include #include #include #include #include #include #include namespace wf { class output_t; class output_layout_t; /** * Contains definitions of the common elements used in Wayfire's scenegraph. * * The scenegraph is a complete representation of the current rendering and input * state of Wayfire. The basic nodes forms a tree where every node is responsible * for managing its children's state. * * The rough structure of the scenegraph is as follows: * * Level 1: the root node, which is a simple container of other nodes. * Level 2: a list of layer nodes, which represent different types of content, * ordered in increasing stacking order (i.e. first layer is the bottommost). * Level 3: in each layer, there is a special output node for each currently * enabled output. By default, this node's bounding box is limited to the * extents of the output, so that no nodes overlap multiple outputs. * Level 4 and beyond: These levels typically contain views and group of views, * or special effects (particle systems and the like). * * Each level may contain additional nodes added by plugins (or by core in the * case of DnD views). The scenegraph generally allows full flexibility here, * but the aforementioned nodes are always available and used by most plugins * to ensure the most compatibility. * * The most common operations that a plugin needs to execute on the scenegraph * are reordering elements (and thus changing the stack order) and potentially * moving them between layers and outputs. In addition, the scenegraph can be * used in some more advanced cases: * * - The scenegraph may be used to implement what used to be custom renderers * prior to Wayfire 0.8.0, i.e. override the default output of a single * workspace covering the whole output. The preferred way to do that is to * disable the output nodes in each layer and add a custom node in one of the * layers which does the custom rendering and covers the whole output. * * - A similar 'trick' can be used for grabbing all input on a particular output * and is the preferred way to do what input grabs used to do prior to Wayfire * 0.8.0. To emulate a grab, create an input-only scene node and place it above * every other node. Thus it will always be selected for input on the output it * is visible on. * * - Always-on-top views are simply nodes which are placed above the workspace * set of each output. * * Regarding coordinate systems: each node possesses a coordinate system. Some * nodes (for example, nodes which simply group other nodes together) share the * coordinate system of their parent node. Other nodes (for example transformers) * are responsible for converting between the coordinate system of their children * and the coordinate system of their parent. */ namespace scene { class node_t; using node_ptr = std::shared_ptr; using node_weak_ptr = std::weak_ptr; class render_instance_t; using render_instance_uptr = std::unique_ptr; using damage_callback = std::function; /** * Describes the current state of a node. */ enum class node_flags : int { /** * If set, the node should be ignored by visitors and any plugins iterating * over the scenegraph. Such nodes (and their children) do not wish to receive * pointer, keyboard, etc. events and do not wish to be displayed. * * Note that plugins might still force those nodes to receive input and be * rendered by calling the corresponding methods directly. */ DISABLED = (1 << 0), /** * If set, the node indicates that it wishes to receive raw input events, that is, it may receive * unmatched pointer press/release events, unmatched touch up/down events, etc. */ RAW_INPUT = (1 << 1), }; using node_flags_bitmask_t = uint64_t; /** * A list of bitmask flags which indicate what parts of the node state have * changed. The information is useful when updating the scenegraph's state * with wf::scene::update(). */ namespace update_flag { enum update_flag { /** * The list of the node's children changed. */ CHILDREN_LIST = (1 << 0), /** * The node's enabled or disabled state changed. */ ENABLED = (1 << 1), /** * The node's input state changed, that is, the result of find_node_at() * may have changed. Typically, this is triggered when a surface is mapped, * unmapped or moved. */ INPUT_STATE = (1 << 2), /** * The node's geometry changed. Changes include not just the bounding box * of the view, but also things like opaque regions. */ GEOMETRY = (1 << 3), /** * A keyboard refocus might be necessary (for example, node removed, keyboard input state changed, etc.). */ REFOCUS = (1 << 4), /** * The update concerns a disabled node (which was disabled before and after the operation, so many * updates (for example regenerating render instances) are not necessary). */ MASKED = (1 << 5), }; } /** * Used as a result of an intersection of the scenegraph with the user input. */ struct input_node_t { nonstd::observer_ptr node; // The coordinates of the user input in surface-local coordinates. wf::pointf_t local_coords; }; /** * The base class for all nodes in the scenegraph. */ class node_t : public std::enable_shared_from_this, public wf::signal::provider_t { public: /** * Create a new no-op node. * Plugins very rarely need this, instead, subclasses of node_t should be * instantiated. */ node_t(bool is_structure); virtual ~node_t(); /** * Find the input node at the given position. * By default, the node will try to pass input to its children. * * @param at The point at which the query is made. It is always in the node's * coordinate system (e.g. resulting from the parent's to_local() function). */ virtual std::optional find_node_at(const wf::pointf_t& at); /** * Figure out which node should receive keyboard focus on the given output. * * Typically, the focus is set directly via core::set_active_node(). However, * in some cases we need to re-elect a node to focus (for example if the * focused node is destroyed). In these cases, the keyboard_refocus() method * on a node is called. It should return the desired focus node. * * By default, a node tries to focus one of its focusable children with the * highest focus_importance. In case of a tie, the node with the highest * last_focus_timestamp is selected. */ virtual wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output); /** * Convert a point from the coordinate system the node resides in, to the * coordinate system of its children. * * By default, the node's children share the coordinate system of their parent, * that is, `to_local(x) == x`. */ virtual wf::pointf_t to_local(const wf::pointf_t& point); /** * Convert a point from the coordinate system of the node's children to * the coordinate system the node resides in. Typically, this is the inverse * operation of to_local, e.g. `to_global(to_local(x)) == x`. * * By default, the node's children share the coordinate system of their parent, * that is, `to_global(x) == x`. */ virtual wf::pointf_t to_global(const wf::pointf_t& point); /** * Get a textual representation of the node, used for debugging purposes. * For example, see wf::dump_scene(). * The representation should therefore not contain any newline characters. */ virtual std::string stringify() const; /** * Get the current flags of the node. */ virtual node_flags_bitmask_t flags() const { return enabled_counter > 0 ? 0 : (int)node_flags::DISABLED; } /** * Get the keyboard interaction interface of this node. * By default, a no-op. */ virtual keyboard_interaction_t& keyboard_interaction() { static keyboard_interaction_t noop; return noop; } virtual pointer_interaction_t& pointer_interaction() { static pointer_interaction_t noop; return noop; } virtual touch_interaction_t& touch_interaction() { static touch_interaction_t noop; return noop; } /** * Generate render instances for this node and its children. * See the @render_instance_t interface for more details. * * The default implementation just generates render instances from its * children. * * @param instances A vector of render instances to add to. The instances * are sorted from the foremost (or topmost) to the last (bottom-most). * @param push_damage A callback used to report damage on the new render * instance. * @param output An optional parameter describing which output the render * instances will be shown on. It can be used to avoid generating instances * on outputs where the node should not be shown. However, this should be * conservatively approximated - it is fine to generate more render * instances than necessary, but not less. */ virtual void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output = nullptr); /** * Get a bounding box of the node in the node's parent coordinate system. * * The bounding box is a rectangular region in which the node and its * children are fully contained. * * The default implementation ignores the node itself and simply returns * the same result as @get_children_bounding_box. Nodes may override this * function if they want to apply a transform to their children, or if the * nodes themselves have visible elements that should be included in the * bounding box. */ virtual wf::geometry_t get_bounding_box(); /** * Get the bounding box of the node's children, in the coordinate system of * the node. * * In contrast to @get_bounding_box, this does not include the node itself, * and does not apply any transformations which may be implemented by the * node. It is simply the bounding box of the bounding boxes of the children * as reported by their get_bounding_box() method. */ wf::geometry_t get_children_bounding_box(); /** * Structure nodes are special nodes which core usually creates when Wayfire * is started (e.g. layer and output nodes). These nodes should not be * reordered or removed from the scenegraph. */ bool is_structure_node() const { return _is_structure; } /** * Get the parent of the current node in the scene graph. */ node_t *parent() const { return _parent; } /** * A helper function to get the status of the DISABLED flag. */ inline bool is_enabled() const { return !(flags() & (int)node_flags::DISABLED); } /** * A helper function to get the status of the RAW_INPUT flag. */ inline bool wants_raw_input() const { return (flags() & (int)node_flags::RAW_INPUT); } /** * Increase or decrease the enabled counter. A non-positive counter causes * the DISABLED flag to be set. * * By default, a node is created with an enabled counter equal to 1. */ void set_enabled(bool is_enabled); /** * Obtain an immutable list of the node's children. * Use set_children_list() of floating_inner_node_t to modify the children, * if the node supports that. */ const std::vector& get_children() const { return children; } /** * When a scenegraph change happens, core or the plugin which modifies the scenegraph is supposed to call * the @scene::update() function defined below, so that the scene graph can be updated properly, render * instances regenerated, etc. * * However, in many cases a full update is not necessary. For example, when subsurfaces are being * reordered, locally regenerating the render instances within the view render instances is enough. * For such cases, nodes can override this function and change the information which is propagated for * the update to their parent nodes. In the above example of subsurface reordering, the subsurface root * will update all of its render instances manually and not propagate CHILDREN_LIST updates to its parent. */ virtual uint32_t optimize_update(uint32_t update_flags); public: node_t(const node_t&) = delete; node_t(node_t&&) = delete; node_t& operator =(const node_t&) = delete; node_t& operator =(node_t&&) = delete; protected: bool _is_structure; int enabled_counter = 1; node_t *_parent = nullptr; friend class surface_root_node_t; friend class floating_inner_node_t; // A helper functions for stringify() implementations, serializes the flags() // to a string, e.g. node with KEYBOARD and USER_INPUT -> '(ku)' std::string stringify_flags() const; /** * A list of children nodes sorted from top to bottom. * * Note on special `structure` nodes: These nodes are typically present in * the normal list of children, but also accessible via a specialized pointer * in their parent's class. */ std::vector> children; void set_children_unchecked(std::vector new_list); }; /** * Inner nodes where plugins can add their own nodes and whose children can be * reordered freely. However, special care needs to be taken to avoid reordering * the special `structure` nodes. */ class floating_inner_node_t : public node_t { public: using node_t::node_t; ~floating_inner_node_t(); /** * Exchange the list of children of this node. * A typical usage (for example, bringing a node to the top): * 1. list = get_children() * 2. list.erase(target_node) * 3. list.insert(list.begin(), target_node) * 4. set_children_list(list) * * The set_children_list function also performs checks on the structure * nodes present in the inner node. If they were changed, the change is * rejected and false is returned. In all other cases, the list of * children is updated, and each child's parent is set to this node. */ bool set_children_list(std::vector new_list); }; using floating_inner_ptr = std::shared_ptr; /** * A Level 3 node which represents each output in each layer. * * Each output's children reside in a coordinate system offsetted by the output's * position in the output layout, e.g. each output has a position 0,0 in its * coordinate system. */ class output_node_t : public floating_inner_node_t { public: output_node_t(wf::output_t *output, bool auto_limits = true); ~output_node_t(); std::string stringify() const override; wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; /** * The output's render instance simply adjusts damage, rendering, etc. to * account for the output's position in the output layout. */ void gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; std::optional find_node_at(const wf::pointf_t& at) override; /** * Get the output this node is responsible for. */ wf::output_t *get_output() const; /** * The limit region of an output. * It defines the region of the output layout that this output occupies. * The output will not render anything outside of its limit region, and will * not find any intersections via find_node_at. */ std::optional limit_region; private: struct priv_t; std::unique_ptr priv; }; /** * A list of all layers in the root node. */ enum class layer : size_t { BACKGROUND = 0, BOTTOM = 1, WORKSPACE = 2, TOP = 3, UNMANAGED = 4, OVERLAY = 5, LOCK = 6, // For compatibility with workspace-manager, to be removed DWIDGET = 7, /** Not a real layer, but a placeholder for the number of layers. */ ALL_LAYERS, }; /** * A signal that the root node has been updated. * * on: scenegraph's root * when: Emitted when an update sequence finishes at the scenegraph's root. */ struct root_node_update_signal { uint32_t flags; }; /** * A signal that the node has been updated. Emitted on every node, including the root (so this signal is a * superset of root_node_update_signal). * * on: scenegraph nodes * when: Emitted when an update sequence reaches the given node. */ struct node_update_signal { scene::node_t *node; uint32_t flags; }; /** * The root (Level 1) node of the whole scenegraph. */ class root_node_t final : public floating_inner_node_t { public: root_node_t(); virtual ~root_node_t(); std::string stringify() const override; /** * An ordered list of all layers' nodes. */ std::shared_ptr layers[(size_t)layer::ALL_LAYERS]; struct priv_t; std::unique_ptr priv; }; /** * Increase or decrease the node's enabled counter (node_t::set_enabled()) and * also trigger a scenegraph update if necessary. */ void set_node_enabled(wf::scene::node_ptr node, bool enabled); /** * Trigger an update of the scenegraph's state. * * When any state of the node changes, this function should be called with a * bitmask list of flags that indicates which parts of the node's state changed. * * After updating the concrete node's state, the change is propagated to parent * nodes all the way up to the scenegraph's root. * * @param changed_node The node whose state changed. * @param flags A bit mask consisting of flags defined in the @update_flag enum. */ void update(node_ptr changed_node, uint32_t flags); } } // namespace wf wayfire-0.10.0/src/api/wayfire/img.hpp0000664000175000017500000000134415053502647017426 0ustar dkondordkondor#ifndef IMG_HPP_ #define IMG_HPP_ #include #include namespace image_io { /* Load the image from the given file, binding it to the given GL texture target * Bind the texture before you call this function * Guaranteed: doesn't change any GL state except pixel packing */ bool load_from_file(std::string name, GLuint target); /* Function that saves the given pixels(in rgba format) to a (currently) png file */ void write_to_file(std::string name, uint8_t *pixels, int w, int h, std::string type, bool invert = false); void write_to_file(std::string name, const wf::render_buffer_t& buffer); /* Initializes all backends, called at startup */ void init(); } #endif /* end of include guard: IMG_HPP_ */ wayfire-0.10.0/src/api/wayfire/unstable/0000775000175000017500000000000015053502647017754 5ustar dkondordkondorwayfire-0.10.0/src/api/wayfire/unstable/xwl-toplevel-base.hpp0000664000175000017500000000311215053502647024034 0ustar dkondordkondor#pragma once // WF_USE_CONFIG_H is set only when building Wayfire itself, external plugins // need to use #ifdef WF_USE_CONFIG_H #include #else #include #endif #include #include #include namespace wf { #if WF_HAS_XWAYLAND /** * A base class for views which base on a wlr_xwayland surface. * Contains the implementation of view_interface_t functions used in them. */ class xwayland_view_base_t : public virtual wf::view_interface_t { public: xwayland_view_base_t(wlr_xwayland_surface *xww); virtual ~xwayland_view_base_t(); virtual void do_map(wlr_surface *surface, bool autocommit, bool emit_map = true); virtual void do_unmap(); virtual void destroy(); void ping() override; void close() override; bool is_mapped() const override; std::string get_app_id() override; std::string get_title() override; wlr_surface *get_keyboard_focus_surface() override; bool is_focusable() const override; protected: std::string title, app_id; wlr_xwayland_surface *xw; bool kb_focus_enabled = true; /** Used by view implementations when the app id changes */ void handle_app_id_changed(std::string new_app_id); /** Used by view implementations when the title changes */ void handle_title_changed(std::string new_title); wf::wl_listener_wrapper on_destroy, on_set_title, on_set_app_id, on_ping_timeout; std::shared_ptr main_surface; }; #endif } wayfire-0.10.0/src/api/wayfire/unstable/wlr-surface-controller.hpp0000664000175000017500000000144415053502647025103 0ustar dkondordkondor#pragma once #include "wayfire/scene.hpp" #include namespace wf { struct node_recheck_constraints_signal {}; /** * A class for managing a wlr_surface. * It is responsible for adding subsurfaces to it. */ class wlr_surface_controller_t { public: static void create_controller(wlr_surface *surface, scene::floating_inner_ptr root_node); static void try_free_controller(wlr_surface *surface); private: wlr_surface_controller_t(wlr_surface *surface, scene::floating_inner_ptr root_node); ~wlr_surface_controller_t(); void update_subsurface_order_and_position(); scene::floating_inner_ptr root; wlr_surface *surface; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_new_subsurface; wf::wl_listener_wrapper on_commit; }; } wayfire-0.10.0/src/api/wayfire/unstable/xdg-toplevel-base.hpp0000664000175000017500000000264115053502647024012 0ustar dkondordkondor#pragma once #include #include #include namespace wf { /** * A base class for xdg_toplevel-based views which implements the view_interface_t (but not toplevel_view_t, * see @xdg_toplevel_view_t for the full implementation). */ class xdg_toplevel_view_base_t : public virtual wf::view_interface_t { public: xdg_toplevel_view_base_t(wlr_xdg_toplevel *toplevel, bool autocommit); virtual ~xdg_toplevel_view_base_t(); void close() override; void ping() override; wlr_surface *get_keyboard_focus_surface() override; bool is_focusable() const override; std::string get_app_id() override; std::string get_title() override; bool is_mapped() const override; /** Set the view state to mapped. */ virtual void map(); /** Set the view state to unmapped. */ virtual void unmap(); protected: wlr_xdg_toplevel *xdg_toplevel; std::string app_id; std::string title; virtual void destroy(); std::shared_ptr main_surface; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_new_popup; wf::wl_listener_wrapper on_set_title; wf::wl_listener_wrapper on_set_app_id; wf::wl_listener_wrapper on_ping_timeout; void handle_title_changed(std::string new_title); void handle_app_id_changed(std::string new_app_id); }; } wayfire-0.10.0/src/api/wayfire/unstable/wlr-view-keyboard-interaction.hpp0000664000175000017500000000344415053502647026361 0ustar dkondordkondor#pragma once #include #include #include #include #include #include namespace wf { /** * An implementation of keyboard_interaction_t for wlr_surface-based views. */ class wlr_view_keyboard_interaction_t : public wf::keyboard_interaction_t { std::weak_ptr view; bool force_enter; public: wlr_view_keyboard_interaction_t(wayfire_view _view, bool force_enter = false) { this->view = _view->weak_from_this(); this->force_enter = force_enter; } void handle_keyboard_enter(wf::seat_t *seat) override { if (auto ptr = view.lock()) { if (auto surface = ptr->get_keyboard_focus_surface()) { auto pressed_keys = seat->get_pressed_keys(); auto kbd = wlr_seat_get_keyboard(seat->seat); if (force_enter) { wlr_seat_keyboard_enter(seat->seat, surface, pressed_keys.data(), pressed_keys.size(), kbd ? &kbd->modifiers : NULL); } else { wlr_seat_keyboard_notify_enter(seat->seat, surface, pressed_keys.data(), pressed_keys.size(), kbd ? &kbd->modifiers : NULL); } } } } void handle_keyboard_leave(wf::seat_t *seat) override { if (auto ptr = view.lock()) { wlr_seat_keyboard_notify_clear_focus(seat->seat); } } void handle_keyboard_key(wf::seat_t *seat, wlr_keyboard_key_event event) override { wlr_seat_keyboard_notify_key(seat->seat, event.time_msec, event.keycode, event.state); } }; } wayfire-0.10.0/src/api/wayfire/unstable/translation-node.hpp0000664000175000017500000000412215053502647023745 0ustar dkondordkondor#pragma once #include #include namespace wf { namespace scene { /** * A node which simply applies an offset to its children. */ class translation_node_t : public wf::scene::floating_inner_node_t { public: translation_node_t(bool is_structure = false); /** * Set the offset the node applies to its children. * Note that damage is not automatically applied. */ void set_offset(wf::point_t offset); /** * Get the current offset (set via @set_offset). Default offset is {0, 0}. */ wf::point_t get_offset() const; public: // Implementation of node_t interface wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; uint32_t optimize_update(uint32_t flags) override; protected: wf::point_t offset = {0, 0}; }; class translation_node_instance_t : public render_instance_t { protected: std::vector children; damage_callback push_damage; std::shared_ptr self; wf::signal::connection_t on_node_damage; wf::signal::connection_t on_regen_instances; wf::output_t *shown_on; void regen_instances(); public: translation_node_instance_t(translation_node_t *self, damage_callback push_damage, wf::output_t *shown_on); // Implementation of render_instance_t void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override; void presentation_feedback(wf::output_t *output) override; wf::scene::direct_scanout try_scanout(wf::output_t *output) override; void compute_visibility(wf::output_t *output, wf::region_t& visible) override; }; } } wayfire-0.10.0/src/api/wayfire/unstable/wlr-surface-node.hpp0000664000175000017500000000645515053502647023654 0ustar dkondordkondor#pragma once #include "wayfire/geometry.hpp" #include "wayfire/util.hpp" #include "wayfire/view-transform.hpp" #include #include #include namespace wf { namespace scene { struct surface_state_t { // The surface state struct keeps a lock on the wlr_buffer, so that it is always valid as long as the // state is current. wlr_buffer *current_buffer = nullptr; wlr_texture *texture; // The texture of the wlr_client_buffer wf::region_t accumulated_damage; wf::dimensions_t size = {0, 0}; std::optional src_viewport; wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; // Sequence number of the last commit read from a wlr_surface state std::optional seq{}; // Read the current surface state, get a lock on the current surface buffer (releasing any old locks), // and accumulate damage. void merge_state(wlr_surface *surface); surface_state_t() = default; // Releases the lock on the current_buffer, if one is held. ~surface_state_t(); surface_state_t(const surface_state_t& other) = delete; surface_state_t& operator =(const surface_state_t& other) = delete; surface_state_t(surface_state_t&& other); surface_state_t& operator =(surface_state_t&& other); }; /** * An implementation of node_t for wlr_surfaces. * * The node typically does not have children and displays a single surface. It is assumed that the surface is * positioned at (0, 0), which means this node usually should be put with a parent node which manages the * position in the scenegraph. */ class wlr_surface_node_t : public node_t, public zero_copy_texturable_node_t { public: /** * @param autocommit Whether the surface should automatically apply new surface state on surface commit, * or it should wait until it is manually applied. */ wlr_surface_node_t(wlr_surface *surface, bool autocommit); std::optional find_node_at(const wf::pointf_t& at) override; std::string stringify() const override; pointer_interaction_t& pointer_interaction() override; touch_interaction_t& touch_interaction() override; void gen_render_instances(std::vector& instances, damage_callback damage, wf::output_t *output) override; wf::geometry_t get_bounding_box() override; std::optional to_texture() const override; wlr_surface *get_surface() const; void apply_state(surface_state_t&& state); void apply_current_surface_state(); void send_frame_done(bool delay_until_vblank); private: std::unique_ptr ptr_interaction; std::unique_ptr tch_interaction; wlr_surface *surface; std::map visibility; std::map pending_visibility_delta; wf::signal::connection_t on_output_remove; class wlr_surface_render_instance_t; void handle_enter(wf::output_t *output); void handle_leave(wf::output_t *output); void update_pending_outputs(); wf::wl_idle_call idle_update_outputs; wf::wl_listener_wrapper on_surface_destroyed; wf::wl_listener_wrapper on_surface_commit; const bool autocommit; surface_state_t current_state; }; } } wayfire-0.10.0/src/api/wayfire/unstable/wlr-subsurface-controller.hpp0000664000175000017500000000251215053502647025612 0ustar dkondordkondor#pragma once #include "wayfire/util.hpp" #include #include #include #include namespace wf { /** * A subsurface root node. It applies a translation to its children equal to the offset of the subsurface. */ class wlr_subsurface_root_node_t : public wf::scene::translation_node_t { public: wlr_subsurface_root_node_t(wlr_subsurface *subsurface); std::string stringify() const override; bool update_offset(bool damage = true); private: wlr_subsurface *subsurface; wf::wl_listener_wrapper on_subsurface_destroy; wf::wl_listener_wrapper on_subsurface_commit; }; /** * A class which manages a wlr_subsurface. Its lifetime is tied to the wlr_subsurface object. * * This class is responsible for managing the subsurface's state and enabling/disabling it when the subsurface * is mapped and unmapped. In addition, it should clean up the scenegraph when the subsurface is destroyed. */ class wlr_subsurface_controller_t { public: wlr_subsurface_controller_t(wlr_subsurface *sub); std::shared_ptr get_subsurface_root(); private: wlr_subsurface *sub; wl_listener_wrapper on_map, on_unmap, on_destroy; std::shared_ptr subsurface_root_node; }; } wayfire-0.10.0/src/api/wayfire/unstable/wlr-text-input-v3-popup.hpp0000664000175000017500000000370115053502647025100 0ustar dkondordkondor#pragma once #include #include #include #include #include namespace wf { class text_input_v3_im_relay_interface_t : public wf::signal::provider_t { public: virtual wlr_text_input_v3 *find_focused_text_input_v3() = 0; virtual ~text_input_v3_im_relay_interface_t() = default; }; /** * A view implementation which presents an input-method popup-like surface relative to a text-input-v3 cursor. */ class text_input_v3_popup : public wf::view_interface_t { public: text_input_v3_im_relay_interface_t *relay = nullptr; wlr_surface *surface = nullptr; text_input_v3_popup(text_input_v3_im_relay_interface_t *relay, wlr_surface *surface); static std::shared_ptr create(text_input_v3_im_relay_interface_t*, wlr_surface*); bool is_mapped() const override; std::string get_app_id() override; std::string get_title() override; wf::geometry_t get_geometry(); void map(); void unmap(); void update_geometry(); void update_cursor_rect(wlr_box*); ~text_input_v3_popup(); private: wf::geometry_t geometry{0, 0, 0, 0}; wlr_box old_cursor_rect{0, 0, 0, 0}; std::shared_ptr main_surface; std::shared_ptr surface_root_node; virtual wlr_surface *get_keyboard_focus_surface() override { return nullptr; } wf::signal::connection_t on_text_input_commit = [=] (auto s) { update_cursor_rect(&s->cursor_rect); }; wf::wl_listener_wrapper on_map; wf::wl_listener_wrapper on_unmap; wf::wl_listener_wrapper on_commit; wf::wl_listener_wrapper on_surface_destroy; public: // This is only a convenience wrapper for the users of this class. wf::wl_listener_wrapper on_destroy; }; } wayfire-0.10.0/src/api/wayfire/unstable/wlr-view-events.hpp0000664000175000017500000000433215053502647023545 0ustar dkondordkondor#pragma once // WF_USE_CONFIG_H is set only when building Wayfire itself, external plugins // need to use #ifdef WF_USE_CONFIG_H #include #else #include #endif #include #include namespace wf { /** * A signal emitted whenever a new xdg_surface object was created on the wlroots side. * By using this signal, plugins may indicate to core that they want to override the view implementation for * the given surface. */ struct new_xdg_surface_signal { wlr_xdg_surface *surface; /** * If a plugin sets this to false, then that plugin is responsible for allocating a view and the * corresponding nodes for the xdg_surface. Core will not handle the xdg_surface any further. */ bool use_default_implementation = true; }; #if WF_HAS_XWAYLAND /** * A signal emitted whenever a new wlr_xwayland_surface object was created on the wlroots side. * By using this signal, plugins may indicate to core that they want to override the view implementation for * the given surface. */ struct new_xwayland_surface_signal { wlr_xwayland_surface *surface; /** * If a plugin sets this to false, then that plugin is responsible for allocating a view and the * corresponding nodes for the xwayland_surface. Core will not handle the xwayland_surface any further. */ bool use_default_implementation = true; }; #endif /** * A signal emitted on core when a view with the default wayfire implementation is about to be mapped. * Plugins can take a look at the view and decide to overwrite its implementation. */ struct view_pre_map_signal { /** * The view which will be mapped after this signal, if plugins do not override it. */ wf::view_interface_t *view; /** * The wlr-surface of the view. */ wlr_surface *surface; /** * Plugins can set this to override the view implementation. If they do so, the view will not be mapped, * and instead the default controller and view implementation for the view will be destroyed after the * signal. Plugins are then free to provide a view implementation themselves. */ bool override_implementation = false; }; } wayfire-0.10.0/src/api/wayfire/core.hpp0000664000175000017500000002423215053502647017603 0ustar dkondordkondor#ifndef CORE_HPP #define CORE_HPP #include "wayfire/object.hpp" #include #include #include #include #include #include #include #include #include namespace wf { class view_interface_t; class toplevel_view_interface_t; class window_manager_t; class workspace_set_t; class config_backend_t; namespace scene { class root_node_t; } namespace txn { class transaction_manager_t; } namespace touch { class gesture_t; struct gesture_state_t; } namespace config { class config_manager_t; } } using wayfire_view = nonstd::observer_ptr; using wayfire_toplevel_view = nonstd::observer_ptr; namespace wf { class output_t; class output_layout_t; class input_device_t; class bindings_repository_t; class seat_t; /** Describes the state of the compositor */ enum class compositor_state_t { /** Not started */ UNKNOWN, /** * The compositor core has finished initializing. * Now the wlroots backends are being started, which results in * adding of new input and output devices. */ START_BACKEND, /** * Both core and wlroots have finished initialization. Now plugins are being started. */ START_PLUGINS, /** * The compositor has loaded the initial devices and plugins and is * running the main loop. */ RUNNING, /** * The compositor has stopped the main loop and is shutting down. */ SHUTDOWN, }; class compositor_core_t : public wf::object_base_t, public signal::provider_t { public: /** * The current configuration used by Wayfire */ std::unique_ptr config; /** * Command line arguments. */ int argc; char **argv; /** * The wayland display and its event loop */ wl_display *display; wl_event_loop *ev_loop; /** * The current wlr backend in use. The only case where another backend is * used is when there are no outputs added, in which case a noop backend is * used instead of this one */ wlr_backend *backend; wlr_session *session; wlr_renderer *renderer; wlr_allocator *allocator; std::unique_ptr config_backend; std::unique_ptr output_layout; std::unique_ptr bindings; std::unique_ptr seat; std::unique_ptr tx_manager; std::unique_ptr default_wm; /** * Various protocols supported by wlroots */ struct { wlr_data_device_manager *data_device; wlr_data_control_manager_v1 *data_control; wlr_gamma_control_manager_v1 *gamma_v1; wlr_screencopy_manager_v1 *screencopy; wlr_export_dmabuf_manager_v1 *export_dmabuf; wlr_server_decoration_manager *decorator_manager; wlr_xdg_decoration_manager_v1 *xdg_decorator; wlr_xdg_output_manager_v1 *output_manager; wlr_virtual_keyboard_manager_v1 *vkbd_manager; wlr_virtual_pointer_manager_v1 *vptr_manager; wlr_input_inhibit_manager *input_inhibit; wlr_idle_notifier_v1 *idle_notifier; wlr_idle_inhibit_manager_v1 *idle_inhibit; wlr_pointer_gestures_v1 *pointer_gestures; wlr_relative_pointer_manager_v1 *relative_pointer; wlr_pointer_constraints_v1 *pointer_constraints; wlr_tablet_manager_v2 *tablet_v2; wlr_input_method_manager_v2 *input_method = NULL; wlr_text_input_manager_v3 *text_input = NULL; wlr_presentation *presentation; wlr_primary_selection_v1_device_manager *primary_selection_v1; wlr_viewporter *viewporter; wlr_drm_lease_v1_manager *drm_v1; wlr_xdg_foreign_registry *foreign_registry; wlr_xdg_foreign_v1 *foreign_v1; wlr_xdg_foreign_v2 *foreign_v2; } protocols; std::string to_string() const { return "wayfire-core"; } /** * @return the current seat. For now, Wayfire supports only a single seat, * which means get_current_seat() will always return the same (and only) seat. */ virtual wlr_seat *get_current_seat() = 0; /** Set the cursor to the given name from the cursor theme, if available */ virtual void set_cursor(std::string name) = 0; /** * Decrements the hide ref counter and unhides the cursor if it becomes 0. * */ virtual void unhide_cursor() = 0; /** * Hides the cursor and increments the hide ref counter. * */ virtual void hide_cursor() = 0; /** * Move the cursor to a specific position. * @param position the new position for the cursor, in global coordinates. */ virtual void warp_cursor(wf::pointf_t position) = 0; /** * Break any grabs on pointer, touch and tablet input. * Then, transfer input focus to the given node in a grab mode. * Note that when transferring a grab, synthetic button release/etc. events are sent to the old pointer * and touch focus nodes (except if they are not RAW_INPUT nodes). * * The grab node, if it is not RAW_INPUT, will also not receive button release events for buttons pressed * before it grabbed the input, if it does not have the RAW_INPUT flag. * * @param node The node which should receive the grabbed input. */ virtual void transfer_grab(wf::scene::node_ptr node) = 0; /** no such coordinate will ever realistically be used for input */ static constexpr double invalid_coordinate = std::numeric_limits::quiet_NaN(); /** * @return The current cursor position in global coordinates or * {invalid_coordinate, invalid_coordinate} if no cursor. */ virtual wf::pointf_t get_cursor_position() = 0; /** * @deprecated, use get_touch_state() instead * * @return The current position of the given touch point, or * {invalid_coordinate,invalid_coordinate} if it is not found. */ virtual wf::pointf_t get_touch_position(int id) = 0; /** * @return The current state of all touch points. */ virtual const wf::touch::gesture_state_t& get_touch_state() = 0; /** * @return The surface which has the cursor focus, or null if none. */ virtual wf::scene::node_ptr get_cursor_focus() = 0; /** * @return The surface which has touch focus, or null if none. */ virtual wf::scene::node_ptr get_touch_focus(int finger_id = 0) = 0; /** @return The view whose surface is cursor focus */ wayfire_view get_cursor_focus_view(); /** @return The view whose surface is touch focus */ wayfire_view get_touch_focus_view(); /** * @return The view whose surface is under the given global coordinates, * or null if none */ wayfire_view get_view_at(wf::pointf_t point); /** * @return A list of all currently attached input devices. */ virtual std::vector> get_input_devices() = 0; /** * @return the wlr_cursor used for the input devices */ virtual wlr_cursor *get_wlr_cursor() = 0; /** * Register a new touchscreen gesture. */ virtual void add_touch_gesture( nonstd::observer_ptr gesture) = 0; /** * Unregister a touchscreen gesture. */ virtual void rem_touch_gesture( nonstd::observer_ptr gesture) = 0; /** * @deprecated. Use tracking_allocator_t::get_all() * * @return A list of all views core manages, regardless of their output, * properties, etc. */ std::vector get_all_views(); /** The wayland socket name of Wayfire */ std::string wayland_display; /** * Return the xwayland display name. * * @return The xwayland display name, or empty string if xwayland is not * available. */ virtual std::string get_xwayland_display() = 0; /** * Execute the given command in a POSIX shell. (/bin/sh) * * This also sets some environment variables for the new process, including * correct WAYLAND_DISPLAY and DISPLAY. * * @return The PID of the started client, or -1 on failure. */ virtual pid_t run(std::string command) = 0; /** * @return The current state of the compositor. */ virtual compositor_state_t get_current_state() = 0; /** * Shut down the whole compositor. * * Stops event loops, destroys outputs, views, etc. */ virtual void shutdown() = 0; /** * Get the root node of Wayfire's scenegraph. */ virtual const std::shared_ptr& scene() = 0; /** * Checks whether the current renderer is a GLES2 renderer. */ bool is_gles2() const; /** * Checks whether the current renderer is a Vulkan renderer. */ bool is_vulkan() const; /** * Checks whether the current renderer is a Pixman renderer. */ bool is_pixman() const; /** * Returns a reference to the only core instance. */ static compositor_core_t& get(); protected: compositor_core_t(); virtual ~compositor_core_t(); }; /** * Change the view's output to new_output. If the reconfigure flag is * set, it will adjust the view geometry for the new output and clamp * it to the output geometry so it is at an expected size and position. */ void move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, bool reconfigure); /** * Start a move of a view to a new workspace set (and thus potentially to a new output). * Note that the view will be removed from its current workspace set and added to the new one. * However, the view's geometry will not be adjusted, and neither will the view_moved_to_wset_signal be * emitted (in contrast to move_view_to_output, which emits view_moved_to_wset_signal). */ void start_move_view_to_wset(wayfire_toplevel_view v, std::shared_ptr new_wset); /** * Simply a convenience function to call wf::compositor_core_t::get() */ compositor_core_t& get_core(); } #endif // CORE_HPP wayfire-0.10.0/src/api/wayfire/workspace-set.hpp0000664000175000017500000002164215053502647021444 0ustar dkondordkondor#ifndef WORKSPACE_MANAGER_HPP #define WORKSPACE_MANAGER_HPP #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include "wayfire/output.hpp" #include "wayfire/signal-provider.hpp" #include #include #include #include #include namespace wf { class workspace_set_t; /** * on: workspace set * when: Whenever the workspace set is attached to a new (including nullptr) output. */ struct workspace_set_attached_signal { wf::workspace_set_t *set; wf::output_t *old_output; }; /** * A set of flags that can be ORed and used as flags for the workspace set's get_view() function. */ enum wset_view_flags { // Include mapped views only. WSET_MAPPED_ONLY = (1 << 0), // Exclude minimized views, they are included by default. WSET_EXCLUDE_MINIMIZED = (1 << 1), // Views on the current workspace only, a shorthand for requesting the current workspace and supplying it // as the second filter of get_views(). WSET_CURRENT_WORKSPACE = (1 << 2), // Sort the resulting array in the same order as the scenegraph nodes of the corresponding views. // Views not attached to the scenegraph (wf::get_core().scene()) are not included in the answer. // This operation may be slow, so it should not be used on hot paths. WSET_SORT_STACKING = (1 << 3), }; /** * Workspace manager is responsible for managing the layers, the workspaces and * the views in them. There is one workspace manager per output. * * In the default workspace_manager implementation, there is one set of layers * per output. Each layer is infinite and covers all workspaces. * * Each output also has a set of workspaces, arranged in a 2D grid. A view may * overlap multiple workspaces. */ class workspace_set_t : public wf::signal::provider_t, public wf::object_base_t, public std::enable_shared_from_this { public: /** * Create a new empty workspace set. By default, the workspace set uses the core/{vwidth,vheight} options * to determine the workspace grid dimensions and is not attached to any outputs. * * When first created, the workspace set is invisible. It may become visible when it is set as the current * workspace set on an output. * * @param index The index of the new workspace set. It will be used if available, otherwise, a the lowest * available index will be selected (starting from 1). */ static std::shared_ptr create(int64_t index = -1); ~workspace_set_t(); /** * Generate a list of all workspace sets currently allocated. */ static std::vector> get_all(); /** * Get the index of the workspace set. The index is assigned on creation and always the lowest unused * index is assigned to the new set. */ uint64_t get_index() const; /** * Attach the workspace set to the given output. * Note that this does not automatically make the workspace set visible on the output, it also needs to be * set as the current workspace set on it. * * @param output The new output to attach to, or nullptr. */ void attach_to_output(wf::output_t *output); /** * Get the currently attached output, or null. */ wf::output_t *get_attached_output(); /** * Get the output geometry of the last attached output. */ std::optional get_last_output_geometry(); /** * Get the scenegraph node belonging to the workspace set. * Each workspace set has one scenegraph node which is put in the workspace layer and contains most of * the views from the workspace set. It is nonetheless possible to add views which are placed elsewhere * in the scenegraph (for example, on a different layer). */ scene::floating_inner_ptr get_node() const; /** * Add the given view to the workspace set. * Until the view is removed, it will be counted as part of the workspace set. * This means that it will be moved when the workspace changes, and it will be part of the view list * returned by @get_views(). * * The workspace set is also responsible for associating the view with an output, in case the workspace * set is moved to a different output. * * Note that adding a view to the workspace set does not automatically add the view to the scenegraph. * The stacking order, layer information, etc. is all determined by the scenegraph and managed separately * from the workspace set, which serves an organizational purpose. * * Note 2: Special care should be taken when adding views that are not part of the default scenegraph * node of the workspace set (i.e. @get_node()). Plugins adding these views have to ensure that the views * are disabled if the workspace set is not active on any output. */ void add_view(wayfire_toplevel_view view); /** * Remove the view from the workspace set. * Note that the view will remain associated with the last output the workspace set was on. */ void remove_view(wayfire_toplevel_view view); /** * Get a list of all views currently in the workspace set. * * Note that the list is not sorted by default (use WSET_SORT_STACKING if sorting is needed), and may * contain views from different scenegraph layers. * * @param flags A bit mask of wset_view_flags. See the individual enum values for detailed description. * @param workspace An optional workspace to filter views for (i.e. only views from that workspace will be * included in the return value. WSET_CURRENT_WORKSPACE takes higher precedence than this value if * specified. */ std::vector get_views(uint32_t flags = 0, std::optional workspace = {}); /** * Get the main workspace for a view. * The main workspace is the one which contains the view's center. * * If the center is on an invalid workspace, the closest workspace will be returned. */ wf::point_t get_view_main_workspace(wayfire_toplevel_view view); /** * Check if the given view is visible on the given workspace */ bool view_visible_on(wayfire_toplevel_view view, wf::point_t ws); /** * Ensure that the view's wm_geometry is visible on the workspace ws. This * involves moving the view as appropriate. */ void move_to_workspace(wayfire_toplevel_view view, wf::point_t ws); /** * Directly change the active workspace. * * @param ws The new active workspace. * @param fixed_views Views which do not change their workspace relative * to the current workspace (together with their child views). Note that it * may result in views getting offscreen if they are not visible on the * current workspace. */ void set_workspace(wf::point_t ws, const std::vector& fixed_views = {}); /** * Switch to the given workspace. * If possible, use a plugin which provides animation. * * @param ws The new active workspace. * @param fixed_views Views which do not change their workspace relative * to the current workspace (together with their child views). See also * workspace-change-request-signal. */ void request_workspace(wf::point_t ws, const std::vector& fixed_views = {}); /** * @return The given workspace */ wf::point_t get_current_workspace(); /** * @return The number of workspace columns and rows */ wf::dimensions_t get_workspace_grid_size(); /** * Set the workspace grid size for this output. * * Once a plugin calls this, the number of workspaces will no longer be * updated according to the config file. */ void set_workspace_grid_size(wf::dimensions_t grid_size); /** * @return Whether the given workspace is valid */ bool is_workspace_valid(wf::point_t ws); private: struct impl; std::unique_ptr pimpl; friend class output_impl_t; friend class wf::tracking_allocator_t; workspace_set_t(int64_t index = -1); /** * Change the visibility of the workspace set. On each output, only one workspace set will be visible * (the current workspace set). When a workspace set is invisible, views in it will be disabled in the * scenegraph. */ void set_visible(bool visible); }; // A helper function to emit view-pre-moved-to-wset void emit_view_pre_moved_to_wset_pre(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset); // A helper function to emit view-moved-to-wset void emit_view_moved_to_wset(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset); } #endif /* end of include guard: WORKSPACE_MANAGER_HPP */ wayfire-0.10.0/src/api/wayfire/idle.hpp0000664000175000017500000000114015053502647017561 0ustar dkondordkondor#pragma once namespace wf { /** * Dummy non-copyable type that increments the global inhibitor count when created, * and decrements when destroyed. These changes influence wlroots idle enablement. */ class idle_inhibitor_t { public: idle_inhibitor_t(); ~idle_inhibitor_t(); idle_inhibitor_t(const idle_inhibitor_t &) = delete; idle_inhibitor_t(idle_inhibitor_t &&) = delete; idle_inhibitor_t& operator =(const idle_inhibitor_t&) = delete; idle_inhibitor_t& operator =(idle_inhibitor_t&&) = delete; private: static unsigned int inhibitors; void notify_update(); }; } wayfire-0.10.0/src/api/wayfire/config-backend.hpp0000664000175000017500000000443215053502647021505 0ustar dkondordkondor#pragma once #include #include #include namespace wf { /** * A base class for configuration backend plugins. * * A configuration backend plugin is loaded immediately after creating a * wayland display and initializing the logging infrastructure. Because of * this, the configuration backend plugins are not allowed to use any * Wayfire APIs, but can use wf-config. * * The job of a configuration backend plugin is to populate and update the * configuration options used in the rest of the code. */ class config_backend_t { public: /** * Initialize the config backend and do the initial loading of config options. * The config backend must follow the same option types as described in the XML * files. * * @param display The wayland display used by Wayfire. * @param config A reference to the config manager which needs to be * populated. * @param cmd_config_file The configuration file specified on the command line. */ virtual void init(wl_display *display, config::config_manager_t& config, const std::string& cmd_config_file) = 0; /** * Find the output section for a given output. * * The returned section must be a valid output object as * described in output.xml */ virtual std::shared_ptr get_output_section( wlr_output *output); /** * Find the output section for a given input device. * * The returned section must be a valid config object as described * in <@prefix>.xml (typically input.xml or input-device.xml). */ virtual std::shared_ptr get_input_device_section( std::string const & prefix, wlr_input_device *device); virtual ~config_backend_t() = default; protected: /** A helper to read the XML directories that Wayfire looks at */ virtual std::vector get_xml_dirs() const; }; } /** * A macro to declare the necessary functions, given the backend class name. */ #define DECLARE_WAYFIRE_CONFIG_BACKEND(PluginClass) \ extern "C" \ { \ wf::config_backend_t*newInstance() { return new PluginClass; } \ uint32_t getWayfireVersion() { return WAYFIRE_API_ABI_VERSION; } \ } wayfire-0.10.0/src/api/wayfire/nonstd/0000775000175000017500000000000015053502647017444 5ustar dkondordkondorwayfire-0.10.0/src/api/wayfire/nonstd/wlroots.hpp0000664000175000017500000000420015053502647021662 0ustar dkondordkondor#pragma once #ifndef WLR_USE_UNSTABLE #define WLR_USE_UNSTABLE 1 #endif /** * This file is used to put all wlroots headers needed in the Wayfire API * in an extern "C" block because wlroots headers are not always compatible * with C++. */ extern "C" { struct wlr_backend; struct wlr_renderer; struct wlr_seat; struct wlr_cursor; struct wlr_data_device_manager; struct wlr_data_control_manager_v1; struct wlr_gamma_control_manager_v1; struct wlr_xdg_output_manager_v1; struct wlr_export_dmabuf_manager_v1; struct wlr_server_decoration_manager; struct wlr_input_inhibit_manager; struct wlr_idle_inhibit_manager_v1; struct wlr_xdg_decoration_manager_v1; struct wlr_virtual_keyboard_manager_v1; struct wlr_virtual_pointer_manager_v1; struct wlr_idle_notifier_v1; struct wlr_screencopy_manager_v1; struct wlr_foreign_toplevel_manager_v1; struct wlr_pointer_gestures_v1; struct wlr_relative_pointer_manager_v1; struct wlr_pointer_constraints_v1; struct wlr_tablet_manager_v2; struct wlr_input_method_manager_v2; struct wlr_text_input_manager_v3; struct wlr_presentation; struct wlr_primary_selection_v1_device_manager; struct wlr_drm_lease_v1_manager; struct wlr_session_lock_manager_v1; struct wlr_xdg_foreign_v1; struct wlr_xdg_foreign_v2; struct wlr_xdg_foreign_registry; struct wlr_pointer_axis_event; struct wlr_pointer_motion_event; struct wlr_output_layout; struct wlr_surface; struct wlr_texture; struct wlr_viewporter; #include #include #include #include #define static #include #undef static #include #include #include #include #include static constexpr uint32_t WLR_KEY_PRESSED = WL_KEYBOARD_KEY_STATE_PRESSED; static constexpr uint32_t WLR_KEY_RELEASED = WL_KEYBOARD_KEY_STATE_RELEASED; struct mwlr_keyboard_modifiers_event { uint32_t time_msec; }; } wayfire-0.10.0/src/api/wayfire/nonstd/json.hpp0000664000175000017500000001064715053502647021136 0ustar dkondordkondor#pragma once #include #include #include #include #include extern "C" { struct yyjson_mut_val; struct yyjson_mut_doc; }; namespace wf { /** * A temporary non-owning reference to a JSON value. */ class json_t; class json_reference_t { public: // ------------------------------------------- Array support --------------------------------------------- bool is_array() const; json_reference_t operator [](const size_t& idx) const; void append(const json_reference_t& elem); void append(int value); void append(unsigned int value); void append(int64_t value); void append(uint64_t value); void append(double value); void append(bool value); void append(const std::string_view& value); void append(const char *value); size_t size() const; // ------------------------------------------- Object support -------------------------------------------- bool has_member(const std::string_view& key) const; bool is_object() const; bool is_null() const; json_reference_t operator [](const char *key) const; json_reference_t operator [](const std::string_view& key) const; std::vector get_member_names() const; json_reference_t& operator =(const json_reference_t& v); json_reference_t& operator =(const json_reference_t&& other); // ------------------------------------- Basic data types support ---------------------------------------- json_reference_t& operator =(const int& v); json_reference_t& operator =(const unsigned int& v); json_reference_t& operator =(const int64_t& v); json_reference_t& operator =(const uint64_t& v); json_reference_t& operator =(const bool& v); json_reference_t& operator =(const double& v); json_reference_t& operator =(const std::string_view& v); json_reference_t& operator =(const char *v); bool is_int() const; operator int() const; int as_int() const; bool is_int64() const; operator int64_t() const; int64_t as_int64() const; bool is_uint() const; operator unsigned int() const; unsigned int as_uint() const; bool is_uint64() const; operator uint64_t() const; uint64_t as_uint64() const; bool is_bool() const; operator bool() const; bool as_bool() const; bool is_double() const; operator double() const; double as_double() const; bool is_string() const; operator std::string() const; std::string as_string() const; yyjson_mut_val *get_raw_value() const { return v; } yyjson_mut_doc *get_raw_doc() const { return doc; } private: friend class json_t; json_reference_t() {} json_reference_t(yyjson_mut_doc *doc, yyjson_mut_val *val) : doc(doc), v(val) {} yyjson_mut_doc *doc = NULL; yyjson_mut_val *v = NULL; // Non-copyable, non-moveable. json_reference_t(const json_reference_t& other) = delete; json_reference_t(const json_reference_t&& other) = delete; }; class json_t final : public json_reference_t { public: json_t(); json_t(const json_reference_t& ref); json_t(const json_t& other); json_t(yyjson_mut_doc *doc); ~json_t(); json_t& operator =(const json_t& other); json_t(json_t&& other); json_t& operator =(json_t&& other); json_t(int v); json_t(unsigned int v); json_t(int64_t v); json_t(uint64_t v); json_t(double v); json_t(const std::string_view& v); json_t(const char *v); json_t(bool v); static json_t array(); static json_t null(); /** * Parse the given source as a JSON document. * * @result Where to store the parsed document on success. * @return On failure, an error message describing the problem with the JSON source will be returned. * Otherwise, the function will return std::nullopt. */ static std::optional parse_string(const std::string_view& source, json_t& result); /** * Serialize the JSON document and handle it using the provided callback. * * Note that the source string will be freed afterwards, so if the caller needs the data for longer, * they need to make a copy of it. */ void map_serialized(std::function callback) const; /** * Get a JSON string representation of the document. */ std::string serialize() const; private: void init(); }; } wayfire-0.10.0/src/api/wayfire/nonstd/wlroots-full.hpp0000664000175000017500000001125215053502647022627 0ustar dkondordkondor#pragma once /** * This file is used to put all wlroots headers needed in the Wayfire * (not only API) in an extern "C" block because wlroots headers are not * always compatible with C++. * * Note that some wlroots headers require generated protocol header files. * There are disabled unless the protocol header file is present. */ #include // WF_USE_CONFIG_H is set only when building Wayfire itself, external plugins // need to use #ifdef WF_USE_CONFIG_H #include #else #include #endif extern "C" { // Version #include #include // Rendering #define static #include #include #include #include #include #include #if WLR_HAS_GLES2_RENDERER #include #endif #if WLR_HAS_VULKAN_RENDERER #include #endif #if __has_include() #include #endif #include #undef static #include #include #include #include #include #include #include #include #include // Shells #if __has_include() #include #include #endif #include #include #include #include // layer-shell needs the protocol file, so we cannot expose it here #if __has_include() #define namespace namespace_t #include #undef namespace #endif #if WF_HAS_XWAYLAND // We need to rename class to class_t for the xwayland definitions. // However, it indirectly includes pthread.h which uses 'class' in the // C++ meaning, so we should include pthread before overriding 'class'. #if __has_include() #include #endif #define class class_t #define static #include #undef static #undef class #endif #include #include #include // Backends #include #define static #include #include #include #if WLR_HAS_X11_BACKEND #include #endif #include #undef static #include #include #include #include // Output management #include #include #if __has_include() #include #endif #include #include #include // Input #include #if __has_include() #include #endif #include #if __has_include() #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define delete delete_ #include #undef delete #include #include #include #include #include #include // Activation plugin #include } wayfire-0.10.0/src/api/wayfire/nonstd/reverse.hpp0000664000175000017500000000110115053502647021621 0ustar dkondordkondor#ifndef WF_REVERSE_HPP #define WF_REVERSE_HPP #include /* from https://stackoverflow.com/questions/8542591/c11-reverse-range-based-for-loop * */ namespace wf { template struct reversion_wrapper { T& iterable; }; template auto begin(reversion_wrapper w) { return std::rbegin(w.iterable); } template auto end(reversion_wrapper w) { return std::rend(w.iterable); } template reversion_wrapper reverse(T&& iterable) { return {iterable}; } } #endif /* end of include guard: WF_REVERSE_HPP */ wayfire-0.10.0/src/api/wayfire/nonstd/observer_ptr.h0000664000175000017500000001570015053502647022334 0ustar dkondordkondor// Copyright 2015, 2016 by Martin Moene // // nonstd::observer_ptr<> is a C++98 onward implementation for std::observer_ptr as of C++17. // // This code is licensed under the MIT License (MIT). // #pragma once #ifndef NONSTD_OBSERVER_PTR_H_INCLUDED #define NONSTD_OBSERVER_PTR_H_INCLUDED #include #include #include #define observer_ptr_VERSION "0.2.0" // Configuration: #ifndef nop_FEATURE_ALLOW_IMPLICIT_CONVERSION # define nop_FEATURE_ALLOW_IMPLICIT_CONVERSION 0 #endif #ifndef nop_CONFIG_CONFIRMS_COMPILATION_ERRORS # define nop_CONFIG_CONFIRMS_COMPILATION_ERRORS 0 #endif // Compiler detection: #define nop_CPP11_OR_GREATER ( __cplusplus >= 201103L ) #define nop_CPP14_OR_GREATER ( __cplusplus >= 201402L ) // half-open range [lo..hi): #define nop_BETWEEN( v, lo, hi ) ( lo <= v && v < hi ) #if defined(_MSC_VER) # define nop_COMPILER_MSVC_VERSION (_MSC_VER / 100 - 5 - (_MSC_VER < 1900)) #else # define nop_COMPILER_MSVC_VERSION 0 # define nop_COMPILER_NON_MSVC 1 #endif // Presence of C++ language features: #if nop_CPP11_OR_GREATER # define nop_HAVE_CONSTEXPR_11 1 #endif #if nop_CPP14_OR_GREATER # define nop_HAVE_CONSTEXPR_14 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 14 # define nop_HAVE_EXPLICIT_CONVERSION 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 14 # define nop_HAVE_NOEXCEPT 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 10 # define nop_HAVE_NULLPTR 1 #endif #if defined( __GNUC__ ) # define nop_HAVE_TYPEOF 1 #endif // Presence of C++ library features: // For the rest, consider VC12, VC14 as C++11 for observer_ptr<>: #if nop_COMPILER_MSVC_VERSION >= 12 # undef nop_CPP11_OR_GREATER # define nop_CPP11_OR_GREATER 1 #endif #if nop_CPP11_OR_GREATER || nop_COMPILER_MSVC_VERSION >= 11 # define nop_HAVE_STD_DECAY 1 # define nop_HAVE_STD_DECLVAL 1 #endif // C++ feature usage: #if nop_HAVE_CONSTEXPR_11 # define nop_constexpr constexpr #else # define nop_constexpr /*nothing*/ #endif #if nop_HAVE_CONSTEXPR_14 # define nop_constexpr14 constexpr #else # define nop_constexpr14 /*nothing*/ #endif #if nop_HAVE_EXPLICIT_CONVERSION # define nop_explicit explicit #else # define nop_explicit /*nothing*/ #endif #if nop_HAVE_NOEXCEPT # define nop_noexcept noexcept #else # define nop_noexcept /*nothing*/ #endif #if nop_HAVE_NULLPTR # define nop_NULLPTR nullptr #else # define nop_NULLPTR NULL #endif // common_type: #if nop_HAVE_STD_DECAY && nop_HAVE_STD_DECLVAL # include // std::decay # include // std::declval # define nop_HAVE_OWN_COMMON_TYPE 1 # define nop_HAVE_OWN_COMMON_TYPE_STD 1 #elif nop_HAVE_TYPEOF # define nop_HAVE_OWN_COMMON_TYPE 1 # define nop_HAVE_OWN_COMMON_TYPE_TYPEOF 1 #endif namespace nonstd { template< class W > class observer_ptr { public: typedef W element_type; typedef W * pointer; typedef W & reference; nop_constexpr14 observer_ptr() nop_noexcept : ptr( nop_NULLPTR ) {} #if nop_HAVE_NULLPTR nop_constexpr14 observer_ptr( std::nullptr_t ) nop_noexcept : ptr( nullptr ) {} #endif nop_constexpr14 observer_ptr( pointer p ) nop_noexcept : ptr(p) {} template< class W2 > nop_constexpr14 observer_ptr(observer_ptr other ) nop_noexcept : ptr( other.get() ) {} template< class W2 > nop_constexpr14 observer_ptr(const std::unique_ptr& other) : ptr(other.get()) {} template< class W2 > nop_constexpr14 observer_ptr(const std::shared_ptr& other) : ptr(other.get()) {} nop_constexpr14 pointer get() const nop_noexcept { return ptr; } nop_constexpr14 reference operator*() const { return assert( ptr != nop_NULLPTR ), *ptr; } nop_constexpr14 pointer operator->() const nop_noexcept { return ptr; } #if nop_HAVE_EXPLICIT_CONVERSION nop_constexpr14 explicit operator bool() const nop_noexcept { return ptr != nop_NULLPTR; } nop_constexpr14 explicit operator pointer() const nop_noexcept { return ptr; } #elif nop_FEATURE_ALLOW_IMPLICIT_CONVERSION nop_constexpr14 operator pointer() const nop_noexcept { return ptr; } #else typedef void (observer_ptr::*safe_bool)() const; void this_type_does_not_support_comparisons() const {} nop_constexpr14 operator safe_bool() const nop_noexcept { return ptr != nop_NULLPTR ? &observer_ptr::this_type_does_not_support_comparisons : 0; } #endif nop_constexpr14 pointer release() nop_noexcept { pointer p( ptr ); reset(); return p; } nop_constexpr14 void reset( pointer p = nop_NULLPTR ) nop_noexcept { ptr = p; } nop_constexpr14 void swap( observer_ptr & other ) nop_noexcept { using std::swap; swap(ptr, other.ptr); } private: pointer ptr; }; // specialized algorithms: template< class W > void swap( observer_ptr & p1, observer_ptr & p2 ) nop_noexcept { p1.swap( p2 ); } template< class W > observer_ptr make_observer( W * p ) nop_noexcept { return observer_ptr( p ); } template< class W1, class W2 > bool operator==( observer_ptr p1, observer_ptr p2 ) { return p1.get() == p2.get(); } template< class W1, class W2 > bool operator!=( observer_ptr p1, observer_ptr p2 ) { return !( p1 == p2 ); } #if nop_HAVE_NULLPTR template< class W > bool operator==( observer_ptr p, std::nullptr_t ) nop_noexcept { return !p; } template< class W > bool operator==( std::nullptr_t, observer_ptr p ) nop_noexcept { return !p; } template< class W > bool operator!=( observer_ptr p, std::nullptr_t ) nop_noexcept { return (bool)p; } template< class W > bool operator!=( std::nullptr_t, observer_ptr p ) nop_noexcept { return (bool)p; } #endif namespace detail { template< class T, class U > #if nop_HAVE_OWN_COMMON_TYPE_STD struct common_type { typedef typename std::decay< decltype(true ? std::declval() : std::declval()) >::type type; }; #elif nop_HAVE_OWN_COMMON_TYPE_TYPEOF struct common_type { typedef __typeof__( true ? T() : U() ) type; }; #else // fall back struct common_type { typedef T type; }; #endif } // namespace detail template< class W1, class W2 > bool operator<( observer_ptr p1, observer_ptr p2 ) { // return std::less()( p1.get(), p2.get() ); // where W3 is the composite pointer type (C++14 Section 5) of W1* and W2*. return std::less< typename detail::common_type::type >()( p1.get(), p2.get() ); } template< class W1, class W2 > bool operator>( observer_ptr p1, observer_ptr p2 ) { return p2 < p1; } template< class W1, class W2 > bool operator<=( observer_ptr p1, observer_ptr p2 ) { return !( p2 < p1 ); } template< class W1, class W2 > bool operator>=( observer_ptr p1, observer_ptr p2 ) { return !( p1 < p2 ); } } // namespace nonstd // #undef ... #endif // NONSTD_OBSERVER_PTR_H_INCLUDED // end of file wayfire-0.10.0/src/api/wayfire/nonstd/tracking-allocator.hpp0000664000175000017500000000422015053502647023733 0ustar dkondordkondor#pragma once #include #include #include #include #include #include namespace wf { /** * The destruct signal is emitted directly before an object is freed. * Emitted on objects managed with the tracking allocator which support signals. */ template struct destruct_signal { T *object; }; /** * The tracking allocator is a factory singleton for allocating objects of a certain type. * The objects are allocated via shared pointers, and the tracking allocator keeps a list of all allocated * objects, accessible by plugins. */ template class tracking_allocator_t { public: /** * Get the single global instance of the tracking allocator. */ static tracking_allocator_t& get() { static tracking_allocator_t allocator; return allocator; } template std::shared_ptr allocate(Args... args) { static_assert(std::is_base_of_v); auto ptr = std::shared_ptr( new ConcreteObjectType(std::forward(args)...), std::bind(&tracking_allocator_t::deallocate_object, this, std::placeholders::_1)); allocated_objects.push_back(ptr.get()); return ptr; } const std::vector>& get_all() { return allocated_objects; } private: std::vector> allocated_objects; void deallocate_object(ObjectType *obj) { if constexpr (std::is_base_of_v) { destruct_signal event; event.object = obj; obj->emit(&event); } auto it = std::find(allocated_objects.begin(), allocated_objects.end(), nonstd::observer_ptr{obj}); wf::dassert(it != allocated_objects.end(), "Object is not allocated?"); allocated_objects.erase(it); delete obj; } }; } wayfire-0.10.0/src/api/wayfire/bindings-repository.hpp0000664000175000017500000000754615053502647022676 0ustar dkondordkondor#pragma once #include #include #include #include #include namespace wf { /** * Emitted on core. * This signal gives plugins the ability to add their own activator binding types. * * wf-config parses the default key, button, touch and hotspot bindings. * Every binding which does not fit into one of these categories is classified as an extension binding and * stored as-is in a list of extension_binding_t's. Then, when Wayfire starts and on config reload, this * signal is emitted. Plugins can try to parse the extension bindings and to store a tag in the extension * binding. Later on, when they wish to trigger the binding, they can pass this tag to the bindings * repository. */ struct parse_activator_extension_signal { std::string extension_binding; std::any tag; }; /** * bindings_repository_t is responsible for managing a list of all bindings in * Wayfire, and for calling these bindings on the corresponding events. */ class bindings_repository_t { public: bindings_repository_t(); ~bindings_repository_t(); /** * Plugins can use the add_* functions to register their own bindings. * * @param key/axis/button/activator The corresponding values are used to know when to trigger this * binding. The values that the shared pointer points to may be modified, in which case core will use * the latest value stored there. * * @param cb The plugin callback to be executed when the binding is triggered. Can be used to unregister * the binding. */ void add_key(option_sptr_t key, wf::key_callback *cb); void add_axis(option_sptr_t axis, wf::axis_callback *cb); void add_button(option_sptr_t button, wf::button_callback *cb); void add_activator(option_sptr_t activator, wf::activator_callback *cb); /** * Handle a keybinding pressed by the user. * * @param pressed The keybinding which was triggered. * @param mod_binding_key The modifier which triggered the binding, if any. * * @return true if any of the matching registered bindings consume the event. */ bool handle_key(const wf::keybinding_t& pressed, uint32_t mod_binding_key); /** Handle an axis event. */ bool handle_axis(uint32_t modifiers, wlr_pointer_axis_event *ev); /** * Handle a buttonbinding pressed by the user. * @return true if any of the matching registered bindings consume the event. */ bool handle_button(const wf::buttonbinding_t& pressed); /** Handle a gesture from the user. */ void handle_gesture(const wf::touchgesture_t& gesture); /** * Trigger all extension bindings which match the given tag. * * @pass tag The tag to match. * @pass data The data to pass to the extension bindings. */ template bool handle_extension(const ExtensionTag& tag, const wf::activator_data_t& data) { return handle_extension_generic([tag] (const std::any& stored_tag) { const ExtensionTag *stored = std::any_cast(&stored_tag); return stored && (*stored == tag); }, data); } /** * Handle an extension call. The callback should return true if the binding matches. */ bool handle_extension_generic(std::function callback, const wf::activator_data_t& data); /** Erase binding of any type by callback */ void rem_binding(void *callback); /** * Enable or disable the repository. The state is reference-counted and starts at 1 (enabled). */ void set_enabled(bool enabled); /** * Re-parse all extension bindings. */ void reparse_extensions(); struct impl; std::unique_ptr priv; }; } wayfire-0.10.0/src/api/wayfire/scene-operations.hpp0000664000175000017500000000623215053502647022131 0ustar dkondordkondor#pragma once #include "wayfire/scene-input.hpp" #include "wayfire/util.hpp" #include #include // This header contains implementations of simple scenegraph related functionality // used in many places throughout the codebase. namespace wf { namespace scene { /** * Remove a child node from a parent node and update the parent. */ inline void remove_child(node_ptr child, uint32_t add_flags = 0) { if (!child->parent()) { return; } auto parent = dynamic_cast(child->parent()); wf::dassert(parent, "Removing a child from a non-floating container!"); auto children = parent->get_children(); children.erase(std::remove(children.begin(), children.end(), child), children.end()); parent->set_children_list(children); update(parent->shared_from_this(), update_flag::CHILDREN_LIST | add_flags); } inline void add_front(floating_inner_ptr parent, node_ptr child) { auto children = parent->get_children(); children.insert(children.begin(), child); parent->set_children_list(children); update(parent, update_flag::CHILDREN_LIST); } inline void readd_front(floating_inner_ptr parent, node_ptr child) { remove_child(child); add_front(parent, child); } inline void add_back(floating_inner_ptr parent, node_ptr child) { auto children = parent->get_children(); children.push_back(child); parent->set_children_list(children); update(parent, update_flag::CHILDREN_LIST); } inline void readd_back(floating_inner_ptr parent, node_ptr child) { remove_child(child); add_back(parent, child); } inline bool raise_to_front(node_ptr child) { auto dyn_parent = dynamic_cast(child->parent()); wf::dassert(dyn_parent, "Raise to front in a non-floating container!"); auto children = dyn_parent->get_children(); if (children.front() == child) { return false; } children.erase(std::remove(children.begin(), children.end(), child), children.end()); children.insert(children.begin(), child); dyn_parent->set_children_list(children); update(dyn_parent->shared_from_this(), update_flag::CHILDREN_LIST); return true; } /** * A render instance manager can be used as a helper when rendering parts of the scenegraph to a target * continuously (output, auxiliary buffer, etc.). * * It instantiates the render instances for the node(s) that are being rendered and updates them when * necessary, and keeps track of the visibility of nodes. */ struct render_instance_manager_t { public: render_instance_manager_t(std::vector nodes, damage_callback on_damage, wf::output_t *reference_output); void set_visibility_region(wf::region_t region); std::vector& get_instances(); private: std::vector nodes; std::vector instances; damage_callback on_damage; wf::output_t *reference_output; std::optional visibility_region; wf::signal::connection_t on_update; wf::wl_idle_call idle_visibility; void regen_instances(); void update_visibility(); }; } } wayfire-0.10.0/src/api/wayfire/toplevel-view.hpp0000664000175000017500000001522115053502647021453 0ustar dkondordkondor#pragma once #include "wayfire/nonstd/observer_ptr.h" #include #include namespace wf { class toplevel_t; class toplevel_view_interface_t; class window_manager_t; } using wayfire_toplevel_view = nonstd::observer_ptr; namespace wf { /** * A list of standard actions which may be allowed on a view. */ enum view_allowed_actions_t { // None of the actions below are allowed. VIEW_ALLOW_NONE = 0, // It is allowed to move the view anywhere on the screen. VIEW_ALLOW_MOVE = (1 << 0), // It is allowed to resize the view arbitrarily. VIEW_ALLOW_RESIZE = (1 << 1), // It is allowed to move the view to another workspace. VIEW_ALLOW_WS_CHANGE = (1 << 2), // All of the actions above are allowed. VIEW_ALLOW_ALL = VIEW_ALLOW_MOVE | VIEW_ALLOW_RESIZE | VIEW_ALLOW_WS_CHANGE, }; /** * A bitmask consisting of all tiled edges. * This corresponds to a maximized state. */ constexpr uint32_t TILED_EDGES_ALL = WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT; /** * Toplevel views are a subtype of views which have an associated toplevel object. As such, they may be moved, * resized, etc. freely by plugins and have many additional operations when compared to other view types. */ class toplevel_view_interface_t : public virtual wf::view_interface_t { public: /** * Get the toplevel object associated with the view. */ const std::shared_ptr& toplevel() const; /** * The toplevel parent of the view, for ex. the main view of a file chooser * dialogue. */ wayfire_toplevel_view parent = nullptr; /** * A list of the children views (typically dialogs). */ std::vector children; /** * Set the toplevel parent of the view, and adjust the children's list of * the parent. */ void set_toplevel_parent(wayfire_toplevel_view parent); /** * Generate a list of all views in the view's tree. * This includes the view itself, its @children and so on. * * @param mapped_only Whether to include only mapped views. * * @return A list of all views in the view's tree. This includes the view * itself, its @children and so on. */ std::vector enumerate_views(bool mapped_only = true); /** * A wrapper function for updating the toplevel's position. * Equivalent to setting the pending coordinates of the toplevel and committing it in a new transaction. */ void move(int x, int y); /** * A wrapper function for updating the toplevel's dimensions. * Equivalent to setting the pending dimensions of the toplevel and committing it in a new transaction. */ void resize(int w, int h); /** * A wrapper function for updating the toplevel's geometry. * Equivalent to setting the pending geometry of the toplevel and committing it in a new transaction. */ void set_geometry(wf::geometry_t g); /** * Request that the view resizes to its native size. */ virtual void request_native_size(); /** Whether the view is in activated state, usually you want to use either * set_activated() or focus_request() */ bool activated = false; /** Whether the view is in minimized state, usually you want to use either * set_minimized() or minimize_request() */ bool minimized = false; /** Whether the view is sticky. If a view is sticky it will not be affected * by changes of the current workspace. */ bool sticky = false; /** Set the minimized state of the view. */ virtual void set_minimized(bool minimized); /** Set the view's activated state. */ virtual void set_activated(bool active); /** Set the view's sticky state. */ virtual void set_sticky(bool sticky); inline uint32_t pending_tiled_edges() const { return toplevel()->pending().tiled_edges; } inline bool pending_fullscreen() const { return toplevel()->pending().fullscreen; } inline wf::geometry_t get_geometry() const { return toplevel()->current().geometry; } inline wf::geometry_t get_pending_geometry() const { return toplevel()->pending().geometry; } /** * Get the allowed actions for this view. By default, all actions are allowed, but plugins may disable * individual actions. * * The allowed actions are a bitmask of @view_allowed_actions_t. */ uint32_t get_allowed_actions() const; /** * Set the allowed actions for the view. * * @param actions The allowed actions, a bitmask of @view_allowed_actions_t. */ void set_allowed_actions(uint32_t actions) const; /** * Get the minimize target for this view, i.e when displaying a minimize * animation, where the animation's target should be. Defaults to {0,0,0,0}. * * @return the minimize target */ virtual wlr_box get_minimize_hint(); /** * Sets the minimize target for this view, i.e when displaying a minimize * animation, where the animation's target should be. * @param hint The new minimize target rectangle, in output-local coordinates. */ virtual void set_minimize_hint(wlr_box hint); /** @return true if the view needs decorations */ virtual bool should_be_decorated(); /** * Set the view's output. * * If the new output is different from the previous, the view will be * removed from the layer it was on the old output. */ virtual void set_output(wf::output_t *new_output) override; /** * Get the workspace set the view is attached to, if any. */ std::shared_ptr get_wset(); virtual ~toplevel_view_interface_t(); std::shared_ptr shared_from_this() { return std::dynamic_pointer_cast(view_interface_t::shared_from_this()); } std::weak_ptr weak_from_this() { return shared_from_this(); } protected: /** * Set the toplevel implementation used by this view. * This function is useful for view implementations only. */ void set_toplevel(std::shared_ptr toplevel); }; inline wayfire_toplevel_view toplevel_cast(wayfire_view view) { return dynamic_cast(view.get()); } // Find the view which has the given toplevel, if such a view exists. // The view might not exist if it was destroyed, but a plugin holds on to a stale toplevel pointer. wayfire_toplevel_view find_view_for_toplevel(std::shared_ptr toplevel); } wayfire-0.10.0/src/api/wayfire/toplevel.hpp0000664000175000017500000001267415053502647020514 0ustar dkondordkondor#pragma once #include #include #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include #include "wayfire/nonstd/wlroots.hpp" // IWYU pragma: keep namespace wf { /** * Describes the size of the decoration frame around a toplevel. */ struct decoration_margins_t { int left; int right; int bottom; int top; }; struct toplevel_state_t { /** * Mapped toplevel objects are ready to be presented to the user and can interact with input. * Unmapped toplevels usually are not displayed and do not interact with any plugins until they are mapped * at a later point in time. */ bool mapped = false; /** * The geometry of the toplevel, as seen by the 'window manager'. This includes for example decorations, * but excludes shadows or subsurfaces sticking out of the main surface. */ wf::geometry_t geometry = {100, 100, 0, 0}; /** * A bitmask of WLR_EDGE_* values. Indicates the edge or corner of the toplevel which should stay immobile * if the client resizes in a way not indicated by the compositor. * * The default gravity is the top-left corner, which stays immobile if the client for example resizes * itself or does not obey a resize request sent by the compositor. */ uint32_t gravity = WLR_EDGE_LEFT | WLR_EDGE_TOP; /** * The tiled edges of the toplevel. * Tiled edges are edges of the toplevel that are aligned to other objects (output edge, other toplevels, * etc.). Clients usually draw no shadows, rounded corners and similar decorations on tiled edges. * * Usually, when all edges are tiled, the toplevel is considered maximized. */ uint32_t tiled_edges = 0; /** * The fullscreen state of the view. Fullscreen clients are typically shown above panels and take up the * full size of their primary output. */ bool fullscreen = false; /** * The size of the server-side decorations around the view. * * Note that the margin values should be updated by decoration plugins before the toplevel state is * committed, for example during the new_transaction_signal. As a result, the pending margins are not * always meaningful for plugins, and they should avoid reading these values as they likely will not be * finalized before the view is actually committed. */ decoration_margins_t margins = {0, 0, 0, 0}; }; /** * Toplevels are a kind of views which can be moved, resized and whose state can change (fullscreen, tiled, * etc). Most of the toplevel's attributes are double-buffered and are changed via transactions. */ class toplevel_t : public wf::txn::transaction_object_t, public wf::object_base_t { public: /** * The current state of the toplevel, as was last committed by the client. The main surface's buffers * contents correspond to the current state. */ const toplevel_state_t& current() { return _current; } /** * The committed state of the toplevel, that is, the state which the compositor has requested from the * client. This state may be different than the current state in case the client has not committed in * response to the compositor's request. */ const toplevel_state_t& committed() { return _committed; } /** * The pending state of a toplevel. It may be changed by plugins. The pending state, however, will not be * applied until the toplevel is committed as a part of a transaction. */ toplevel_state_t& pending() { return _pending; } /** * The minimum desirable size of the toplevel if set by the client. * If the client has not indicated a minimum size in either dimension, that dimension will be set to 0. */ virtual wf::dimensions_t get_min_size() { return {0, 0}; } /** * The maximum desirable size of the toplevel if set by the client. * If the client has not indicated a maximum size in either dimension, that dimension will be set to 0. */ virtual wf::dimensions_t get_max_size() { return {0, 0}; } protected: toplevel_state_t _current; toplevel_state_t _pending; toplevel_state_t _committed; std::optional last_windowed_geometry; }; // Helper functions when working with toplevel state inline wf::dimensions_t expand_dimensions_by_margins(wf::dimensions_t dim, const decoration_margins_t& margins) { dim.width += margins.left + margins.right; dim.height += margins.top + margins.bottom; return dim; } inline wf::dimensions_t shrink_dimensions_by_margins(wf::dimensions_t dim, const decoration_margins_t& margins) { dim.width -= margins.left + margins.right; dim.height -= margins.top + margins.bottom; return dim; } inline wf::geometry_t expand_geometry_by_margins(wf::geometry_t geometry, const decoration_margins_t& margins) { geometry.x -= margins.left; geometry.y -= margins.top; geometry.width += margins.left + margins.right; geometry.height += margins.top + margins.bottom; return geometry; } inline wf::geometry_t shrink_geometry_by_margins(wf::geometry_t geometry, const decoration_margins_t& margins) { geometry.x += margins.left; geometry.y += margins.top; geometry.width -= margins.left + margins.right; geometry.height -= margins.top + margins.bottom; return geometry; } } wayfire-0.10.0/src/api/wayfire/matcher.hpp0000664000175000017500000000314015053502647020271 0ustar dkondordkondor#pragma once #include #include namespace wf { /** * view_matcher_t provides a way to match certain views based on conditions. * The conditions are represented as string options. * * For information about the syntax or the possible conditions, see * wf::view_condition_interface_t and wf::condition_parser_t. */ class view_matcher_t { public: /** * Create a new matcher from the given option. * * Whenever the option value changes, the condition will be updated. * * @param option The option where the condition is encoded. */ view_matcher_t(std::shared_ptr> option); /** * Create a new matcher from the given option name. * The option will be loaded from core. * * @throws a std::runtime_error if the option is not found, or if the option * is not a string. */ view_matcher_t(const std::string& option_name); view_matcher_t(const view_matcher_t &) = delete; view_matcher_t(view_matcher_t &&) = default; view_matcher_t& operator =(const view_matcher_t&) = delete; view_matcher_t& operator =(view_matcher_t&&) = default; /** * Set the condition option after initialization. */ void set_from_option(std::shared_ptr> option); /** * @return True if the view matches the condition specified, false otherwise. */ bool matches(wayfire_view view); /** Destructor */ ~view_matcher_t(); private: view_matcher_t(); class impl; std::unique_ptr priv; }; } wayfire-0.10.0/src/api/wayfire/input-device.hpp0000664000175000017500000000143715053502647021251 0ustar dkondordkondor#ifndef WF_INPUT_DEVICE_HPP #define WF_INPUT_DEVICE_HPP #include namespace wf { class input_device_t { public: /** * General comment * @return The represented wlr_input_device */ wlr_input_device *get_wlr_handle(); /** * @param enabled Whether the compositor should handle input events from * the device * @return true if the device state was successfully changed */ bool set_enabled(bool enabled = true); /** * @return true if the compositor should receive events from the device */ bool is_enabled(); virtual ~input_device_t() = default; protected: wlr_input_device *handle; input_device_t(wlr_input_device *handle); }; } #endif /* end of include guard: WF_INPUT_DEVICE_HPP */ wayfire-0.10.0/src/api/wayfire/view-helpers.hpp0000664000175000017500000000356315053502647021271 0ustar dkondordkondor#pragma once #include "toplevel.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include #include // This file contains helper functions which are helpful when working with views. Most of the operations are // simply wrappers around more low-level functionality provided by views, scenegraph, etc. namespace wf { /** * Find the scenegraph layer that the view is currently in. */ std::optional get_view_layer(wayfire_view view); /** * Reorder the nodes on the path from the view to the scenegraph root so that the view is as high in the * stacking order as possible. * * Also damages the affected nodes. */ void view_bring_to_front(wayfire_view view); /** * Iterate over all scenegraph nodes in the given scenegraph subtree and collect all enabled view nodes. * The nodes returned are in front-to-back order. */ std::vector collect_views_from_scenegraph(wf::scene::node_ptr root); /** * Collect all views from the scenegraph nodes of the output for the given layers. */ std::vector collect_views_from_output( wf::output_t *output, std::initializer_list layers); /** * Find the topmost parent in the chain of views. */ wayfire_view find_topmost_parent(wayfire_view v); wayfire_toplevel_view find_topmost_parent(wayfire_toplevel_view v); /** * A few simple functions which help in view implementations. */ namespace view_implementation { void emit_toplevel_state_change_signals(wayfire_toplevel_view view, const wf::toplevel_state_t& old_state); void emit_view_map_signal(wayfire_view view, bool has_position); void emit_ping_timeout_signal(wayfire_view view); void emit_geometry_changed_signal(wayfire_toplevel_view view, wf::geometry_t old_geometry); void emit_title_changed_signal(wayfire_view view); void emit_app_id_changed_signal(wayfire_view view); } } wayfire-0.10.0/src/api/wayfire/render-manager.hpp0000664000175000017500000001506415053502647021545 0ustar dkondordkondor#pragma once #include #include #include #include namespace wf { /* Effect hooks provide the plugins with a way to execute custom code * at certain parts of the repaint cycle */ using effect_hook_t = std::function; enum output_effect_type_t { /* Pre hooks are called before starting to repaint the output */ OUTPUT_EFFECT_PRE = 0, /** * Damage hooks are called before attaching the renderer to the output. * They are useful if the output damage needs to be modified, whereas * plugins that simply need to update their animation should use PRE hooks. */ OUTPUT_EFFECT_DAMAGE = 1, /* Overlay hooks are called right after repainting the output, but * before post hooks and before swapping buffers */ OUTPUT_EFFECT_OVERLAY = 2, /** * Pass done hooks are called after overlay hooks are all finished. * At this point, the main render pass for the output is over. */ OUTPUT_EFFECT_PASS_DONE = 3, /* Post hooks are called after the buffers have been swapped */ OUTPUT_EFFECT_POST = 4, /* Invalid type for a hook, used internally */ OUTPUT_EFFECT_TOTAL = 5, }; /** Post hooks are called just before swapping buffers. In contrast to * render hooks, post hooks operate on the whole output image, i.e they * are suitable for different postprocessing effects. * * When using post hooks, the output first gets rendered to a framebuffer, * which can then pass through multiple post hooks. The last hook then will * draw to the output's framebuffer. * * @param source Indicates the source buffer of the hook, which contains * the output image up to this moment. * * @param destination Indicates where the processed image should be stored. */ using post_hook_t = std::function; /** * The frame-done signal is emitted on an output when the frame has been completed (regardless of whether new * content was painted or not). */ struct frame_done_signal {}; /** Render manager * * Each output has a render manager, which is responsible for all rendering * operations that happen on it, and also for damage tracking. */ class render_manager { public: /** Create a render manager for the given output. Plugins do not need * to manually create render managers, as one is created for each output * automatically */ render_manager(output_t *o); ~render_manager(); /** * Rendering an output is done on demand, that is, when the output is * damaged. Some plugins however need to redraw the output as often as * possible, for ex. when displaying some kind of animation. * * auto_redraw() provides the plugins to temporarily request redrawing * of the output regardless of damage. * * @param always - Whether to always redraw, regardless of damage. Call * set_redraw_always(false) once for each set_redraw_always(true). */ void set_redraw_always(bool always = true); /** * Schedule a frame for the output. Note that if there is no damage for * the next frame, nothing will be redrawn */ void schedule_redraw(); /** * Inhibit rendering to the output. An inhibited output will show a * fully black image. Used mainly for compositor fade in/out on startup. */ void add_inhibit(bool add); /** * Add a new effect hook. * @param hook The hook callback * @param type The type of the effect hook */ void add_effect(effect_hook_t *hook, output_effect_type_t type); /** * Remove an added effect hook. No-op if the hook wasn't really added. * @param hook The hook callback to be removed */ void rem_effect(effect_hook_t *hook); /** * Add a new post hook. * * @param hook The hook callback */ void add_post(post_hook_t *hook); /** * Remove a post hook. No-op if hook isn't active. * * @param hook The hook to be removed. */ void rem_post(post_hook_t *hook); /** * @return The damaged region on the current output for the current * frame that is used when swapping buffers. This function should * only be called from overlay or postprocessing effect callbacks. * Otherwise it will return an empty region. */ wf::region_t get_swap_damage(); /** * @return The current render pass, NULL if no rendering operations are currently active on the output. */ wf::render_pass_t *get_current_pass(); /** * @return The damaged region on the current output for the current * frame. Note that a larger region might actually be repainted due to * double buffering. */ wf::region_t get_scheduled_damage(); /** * @return The current wlr_color_transform from the icc_profile option, or NULL if none is set. */ wlr_color_transform *get_color_transform(); /** * Damage all workspaces of the output. Should not be used inside render * hooks, view transformers, etc. */ void damage_whole(); /** * Same as damage_whole() but the output will actually be damaged on the * next time the event loop goes idle. This is safe to use inside render * hooks, transformers, etc. */ void damage_whole_idle(); /** * Same as damage_whole(), but damages only a part of the output. * * @param box The output box to be damaged, in output-local coordinates. * @param repaint Whether to automatically schedule an output repaint. */ void damage(const wlr_box& box, bool repaint = true); /** * Same as damage_whole(), but damages only a part of the output. * * @param region The output region to be damaged, in output-local coordinates. * @param repaint Whether to automatically schedule an output repaint. */ void damage(const wf::region_t& region, bool repaint = true); /** * @return A box in output-local coordinates containing the given * workspace of the output (returned value depends on current workspace). */ wlr_box get_ws_box(wf::point_t ws) const; /** * @return The framebuffer on which all rendering operations except post * effects happen. */ wf::render_target_t get_target_framebuffer() const; /** * Inform Wayfire whether a depth buffer is required for rendering on the default framebuffer for each * output. */ void set_require_depth_buffer(bool require); public: class impl; std::unique_ptr pimpl; }; } wayfire-0.10.0/src/api/wayfire/view-transform.hpp0000664000175000017500000003407115053502647021640 0ustar dkondordkondor#ifndef VIEW_TRANSFORM_HPP #define VIEW_TRANSFORM_HPP #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include #include namespace wf { namespace scene { class zero_copy_texturable_node_t { public: virtual ~zero_copy_texturable_node_t() = default; /** * Get a texture from the node without copying. * Note that this operation might fail for non-trivial transformers. */ virtual std::optional to_texture() const { return {}; } }; class opaque_region_node_t { public: virtual ~opaque_region_node_t() = default; /** * Get the opaque region of the node in its parent's coordinate system (same as get_bounding_box()). */ virtual wf::region_t get_opaque_region() const { return {}; } }; /** * A base class for all transformer nodes. * It facilitates the reuse of auxilliary buffers between render instances. */ class transformer_base_node_t : public scene::floating_inner_node_t { public: using floating_inner_node_t::floating_inner_node_t; uint32_t optimize_update(uint32_t flags) override; // A temporary buffer to render children to. wf::auxilliary_buffer_t inner_content; // Damage from the children, which is the region of @inner_content that // should be repainted on the next frame to have a valid copy of the // children's current content. wf::region_t cached_damage; wf::texture_t get_updated_contents(const wf::geometry_t& bbox, float scale, std::vector& children); void release_buffers(); ~transformer_base_node_t(); }; /** * A helper class for implementing transformer nodes. * Transformer nodes usually operate on views and implement special effects, like * for example rotating a view, blurring the background, etc. * * To allow arbitrary combinations of transformers, the different transformers are * arranged so that they build a chain where each transformer is the child of the * previous transformer, and the child of the last transformer is the view's * surface root node. For the actual composition of effects, every transformer * first renders its children (with the transformation which comes from the next * transformers in the chain) to a temporary buffer and then renders the temporary * buffer with the node's own transform applied. * * @param NodeType the concrete type of the node this instance belongs to, must be * a subclass of transformer_base_node_t. */ template class transformer_render_instance_t : public render_instance_t { protected: std::optional zero_copy_texture() { if (self->get_children().size() == 1) { auto child = self->get_children().front().get(); if (auto zcopy = dynamic_cast(child)) { return zcopy->to_texture(); } } return {}; } // A pointer to the transformer node this render instance belongs to. std::shared_ptr self; // A list of render instances of the next transformer or the view itself. std::vector children; /** * Get a texture which contains the contents of the children nodes. * If the node has a single child which supports zero-copy texture generation * via @to_texture, that method is preferred to avoid unnecessary copies. * * Otherwise, the children are rendered to an auxiliary buffer (@inner_content), * whose texture is returned. * * @param scale The scale to use when generating the texture. The scale * indicates how much bigger the temporary buffer should be than its logical * size. */ wf::texture_t get_texture(float scale) { // Optimization: if we have a single child (usually the surface root node) // and we can directly convert it to texture, we don't need a full render // pass. if (auto tex = zero_copy_texture()) { self->release_buffers(); return *tex; } return self->get_updated_contents(self->get_children_bounding_box(), scale, children); } void presentation_feedback(wf::output_t *output) override { for (auto& ch : children) { ch->presentation_feedback(output); } } virtual void transform_damage_region(wf::region_t& damage) {} wf::output_t *_shown_on; damage_callback _push_damage; wf::signal::connection_t on_regen_instances = [=] (auto) { regen_instances(); }; public: transformer_render_instance_t(NodeType *self, damage_callback push_damage, wf::output_t *shown_on) { static_assert(std::is_base_of_v, "transformer_render_instance_t should be instantiated with a " "subclass of node_t!"); this->self = std::dynamic_pointer_cast(self->shared_from_this()); self->cached_damage |= self->get_children_bounding_box(); this->_push_damage = push_damage; this->_shown_on = shown_on; regen_instances(); self->connect(&on_regen_instances); } void regen_instances() { auto push_damage_child = [=] (wf::region_t region) { self->cached_damage |= region; transform_damage_region(region); _push_damage(region); }; children.clear(); for (auto& ch : self->get_children()) { ch->gen_render_instances(children, push_damage_child, _shown_on); } } ~transformer_render_instance_t() {} void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (!damage.empty()) { auto our_damage = damage & self->get_bounding_box(); instructions.push_back(wf::scene::render_instruction_t{ .instance = this, .target = target, .damage = std::move(our_damage), }); } } void render(const wf::scene::render_instruction_t& data) override { wf::dassert(false, "Rendering not implemented for view transformer?"); } direct_scanout try_scanout(wf::output_t *output) override { // By default, disable direct scanout return direct_scanout::OCCLUSION; } bool has_instances() { return !children.empty(); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { if (!(visible & self->get_bounding_box()).empty()) { // By default, we are not sure how the visibility region is affected, so we take a simple 0-or-1 // approach: if anything of the bounding box is visible, we assume the whole view is visible, and // we do not subtract anything from the visibility region of the nodes below. wf::region_t copy = self->get_children_bounding_box(); for (auto& ch : this->children) { ch->compute_visibility(output, copy); } } } }; /** * A floating inner node which contains a chain of view transformers and a view * surface root node at the bottom of the chain. Its interface can be used to * add and sort view transformers. */ class transform_manager_node_t : public wf::scene::floating_inner_node_t { public: transform_manager_node_t() : floating_inner_node_t(false) {} /** * Marks a section of the code which updates one or more transformers added to this transform manager. * Doing so will ensure that the proper damage is propagated upwards in the scenegraph. */ void begin_transform_update(); /** * Marks the end of a section of the code which updates one or more transformers added to this transform * manager. Doing so will ensure that the proper damage is propagated upwards in the scenegraph. */ void end_transform_update(); /** * Add a new transformer in the transformer chain. * * @param transformer The transformer to be added. * @param z_order The order of this transformer relative to other transformers. * Smaller values indicate that the transformer should be applied before * others, see @transformer_z_order_t. * @param name A string which can be used as an ID for easily getting a * transformer node or removing it. */ template void add_transformer(std::shared_ptr transformer, int z_order, std::string name = typeid(ConcreteTransformer).name()) { _add_transformer(transformer, z_order, name); } template void rem_transformer(std::shared_ptr transformer) { _rem_transformer(transformer); } template void rem_transformer(std::string name = typeid(ConcreteTransformer).name()) { _rem_transformer(get_transformer(name)); } /** * Find a transformer with the given name and type. */ template std::shared_ptr get_transformer( std::string name = typeid(ConcreteTransformer).name()) { for (auto& tr : transformers) { if (tr.name == name) { return std::dynamic_pointer_cast(tr.node); } } return nullptr; } std::string stringify() const override { return "view-transform-root"; } private: struct added_transformer_t { wf::scene::floating_inner_ptr node; int z_order; std::string name; }; std::vector transformers; void _add_transformer(wf::scene::floating_inner_ptr transformer, int z_order, std::string name); void _rem_transformer(wf::scene::floating_inner_ptr transformer); }; /** * A simple transformer which supports 2D transformations on a view. */ class view_2d_transformer_t : public transformer_base_node_t { public: float scale_x = 1.0f; float scale_y = 1.0f; float translation_x = 0.0f; float translation_y = 0.0f; // An angle in radians indicating how much the view should be rotated // around its center counter-clockwise. float angle = 0.0f; // A multiplier for the view's opacity. // Note that if the view was not opaque to begin with, setting alpha=1.0 // does not make it opaque. float alpha = 1.0f; // Often a plugin needs to apply a 2d transform where each of the attributes is computed based // on other values. In order to make that simpler, the following functions can be overridden by custom // transformers in order to allow dynamic changes in the scale, translation and rotation. See the // crossfade animation as an example of this. // // Plugins applying simple 2D transformations can keep using the view_2d class directly and simply set // the attributes. virtual float get_scale_x() const { return scale_x; } virtual float get_scale_y() const { return scale_y; } virtual float get_translation_x() const { return translation_x; } virtual float get_translation_y() const { return translation_y; } virtual float get_angle() const { return angle; } virtual float get_alpha() const { return alpha; } view_2d_transformer_t(wayfire_view view); wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; wf::geometry_t get_bounding_box() override; void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; std::weak_ptr view; }; /** * A simple transformer which supports 3D transformations on a view. */ class view_3d_transformer_t : public transformer_base_node_t { protected: std::weak_ptr view; public: glm::mat4 view_proj{1.0}, translation{1.0}, rotation{1.0}, scaling{1.0}; glm::vec4 color{1, 1, 1, 1}; glm::mat4 calculate_total_transform(); public: view_3d_transformer_t(wayfire_view view); wf::pointf_t to_local(const wf::pointf_t& point) override; wf::pointf_t to_global(const wf::pointf_t& point) override; std::string stringify() const override; wf::geometry_t get_bounding_box() override; void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) override; static const float fov; // PI / 8 static glm::mat4 default_view_matrix(); static glm::mat4 default_proj_matrix(); }; } /** * When adding multiple transformers to a view, the relative order of these * transform nodes to each other matters. The transformer_z_order_t enum contains * a few common values used by transformers from core. Note that plugins may use * any integer as a Z order for a transformer. */ enum transformer_z_order_t { // Simple 2D transforms applied to the base surface. Used for things like // scaling, simple 2D rotation. TRANSFORMER_2D = 1, TRANSFORMER_3D = 2, // Highlevel transformation which is usually at the top of the stack. // Used for things like wobbly and fire animation. TRANSFORMER_HIGHLEVEL = 500, // The highest level of view transforms, used by blur. TRANSFORMER_BLUR = 1000, }; // Calculate a bounding box after applying the node transformation to @box, // assuming an affine transformation applied by the node. wf::geometry_t get_bbox_for_node(scene::node_ptr node, wf::geometry_t box); } #endif /* end of include guard: VIEW_TRANSFORM_HPP */ wayfire-0.10.0/src/api/wayfire/workarea.hpp0000664000175000017500000000447015053502647020470 0ustar dkondordkondor#pragma once #include #include #include namespace wf { /** * Each output has a workarea manager which keeps track of the available workarea on that output. The * available area is typically the full output area minus any space reserved for panels, bars, etc. */ class output_workarea_manager_t { public: /** * Special clients like panels can reserve place from an edge of the output. * It is used when calculating the dimensions of maximized/tiled windows and * others. The remaining space (which isn't reserved for panels) is called * the workarea. */ enum anchored_edge { ANCHORED_EDGE_TOP = 0, ANCHORED_EDGE_BOTTOM = 1, ANCHORED_EDGE_LEFT = 2, ANCHORED_EDGE_RIGHT = 3, }; struct anchored_area { // The edge from which to reserve area. anchored_edge edge; // Amount of space to reserve. int reserved_size; // The reflowed callback is optional and when present, is called every time the anchored areas are // reflowed (e.g. anchored areas are recalculated). The passed geometry is the available workarea // before the view's own request was considered. That means, for the first anchored area in the // workarea manager, the geometry will be the full output's geometry. For each subsequent anchored // area, the size of the previous anchored areas is excluded from the passed available workarea. std::function reflowed; }; /** * Add a reserved area. The actual recalculation must be manually * triggered by calling reflow_reserved_areas() */ void add_reserved_area(anchored_area *area); /** * Remove a reserved area. The actual recalculation must be manually * triggered by calling reflow_reserved_areas() */ void remove_reserved_area(anchored_area *area); /** * Recalculate reserved area for each anchored area */ void reflow_reserved_areas(); /** * @return The free space of the output after reserving the space for panels */ wf::geometry_t get_workarea(); output_workarea_manager_t(wf::output_t *output); ~output_workarea_manager_t(); private: struct impl; std::unique_ptr priv; }; } wayfire-0.10.0/src/api/wayfire/debug.hpp0000664000175000017500000000462015053502647017740 0ustar dkondordkondor#ifndef DEBUG_HPP #define DEBUG_HPP // WF_USE_CONFIG_H is set only when building Wayfire itself, external plugins // need to use #ifdef WF_USE_CONFIG_H #include #else #include #endif #define nonull(x) ((x) ? (x) : ("nil")) #include #include #include #include #include namespace wf { /** * Dump a scenegraph to the log. */ void dump_scene(scene::node_ptr root = wf::get_core().scene()); /** * Information about the version that Wayfire was built with. * Made available at runtime. */ namespace version { extern std::string git_commit; extern std::string git_branch; } namespace log { /** * A list of available logging categories. * Logging categories need to be manually enabled. */ enum class logging_category : size_t { // Transactions - general TXN = 0, // Transactions - individual objects TXNI = 1, // Views - events VIEWS = 2, // Wlroots messages WLR = 3, // Direct scanout SCANOUT = 4, // Pointer events POINTER = 5, // Workspace set events WSET = 6, // Keyboard-related events KBD = 7, // Xwayland-related events XWL = 8, // Layer-shell-related events LSHELL = 9, // Input-Method-related events IM = 10, // Rendering-related events RENDER = 11, // Input-device-related events INPUT_DEVICES = 12, // Output-device-related events OUTPUT = 13, TOTAL, }; extern std::bitset<(size_t)logging_category::TOTAL> enabled_categories; } } #define LOGC(CAT, ...) \ if (wf::log::enabled_categories[(size_t)wf::log::logging_category::CAT]) \ { \ LOGD("[", #CAT, "] ", __VA_ARGS__); \ } /* ------------------- Miscallaneous helpers for debugging ------------------ */ #include #include #include std::ostream& operator <<(std::ostream& out, const glm::mat4& mat); wf::pointf_t operator *(const glm::mat4& m, const wf::pointf_t& p); wf::pointf_t operator *(const glm::mat4& m, const wf::point_t& p); namespace wf { class view_interface_t; } using wayfire_view = nonstd::observer_ptr; namespace wf { std::ostream& operator <<(std::ostream& out, wayfire_view view); } #endif wayfire-0.10.0/src/api/wayfire/output-layout.hpp0000664000175000017500000001435015053502647021526 0ustar dkondordkondor#ifndef OUTPUT_LAYOUT_HPP #define OUTPUT_LAYOUT_HPP #include #include #include #include #include #include "geometry.hpp" #include "wayfire/signal-provider.hpp" #define RENDER_BIT_DEPTH_DEFAULT 8 namespace wf { class output_t; /* ----------------------------------------------------------------------------/ * Output signals * -------------------------------------------------------------------------- */ /** Base class for all output signals. */ /** * on: output-layout * when: Each time a new output is added. */ struct output_added_signal { wf::output_t *output; }; /** * on: output, output-layout(output-) * when: Emitted just before starting the destruction procedure for an output. */ struct output_pre_remove_signal { wf::output_t *output; }; /** * on: output-layout * when: Each time a new output is added. */ struct output_removed_signal { wf::output_t *output; }; enum output_config_field_t { /** Output source changed */ OUTPUT_SOURCE_CHANGE = (1 << 0), /** Output mode changed */ OUTPUT_MODE_CHANGE = (1 << 1), /** Output scale changed */ OUTPUT_SCALE_CHANGE = (1 << 2), /** Output transform changed */ OUTPUT_TRANSFORM_CHANGE = (1 << 3), /** Output position changed */ OUTPUT_POSITION_CHANGE = (1 << 4), }; struct output_state_t; /** * on: output-layout * when: Each time the configuration of the output layout changes. */ struct output_layout_configuration_changed_signal {}; /** * on: output * when: Each time the output's source, mode, scale, transform and/or position changes. */ struct output_configuration_changed_signal { wf::output_t *output; output_configuration_changed_signal(const wf::output_state_t& st) : state(st) {} /** * Which output attributes actually changed. * A bitwise OR of output_config_field_t. */ uint32_t changed_fields; /** * The new state of the output. */ const wf::output_state_t& state; }; /** Represents the source of pixels for this output */ enum output_image_source_t { OUTPUT_IMAGE_SOURCE_INVALID = 0x0, /** Output renders itself */ OUTPUT_IMAGE_SOURCE_SELF = 0x1, /** Output is turned off */ OUTPUT_IMAGE_SOURCE_NONE = 0x2, /** Output is in DPMS state */ OUTPUT_IMAGE_SOURCE_DPMS = 0x3, /** Output is in mirroring state */ OUTPUT_IMAGE_SOURCE_MIRROR = 0x4, }; /** Represents the current state of an output as the output layout sees it */ struct output_state_t { /* The current source of the output. * * If source is none, then the values below don't have a meaning. * If source is mirror, then only mirror_from and mode have a meaning */ output_image_source_t source = OUTPUT_IMAGE_SOURCE_INVALID; /** Position for the output */ wf::output_config::position_t position; /** Only width, height and refresh fields are used. */ wlr_output_mode mode; /** Whether a custom mode was requested for the output. */ bool uses_custom_mode = false; /* The transform of the output */ wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; /* The scale of the output */ double scale = 1.0; /* Whether or not adaptive sync is enabled */ bool vrr = false; /* Output format bit depth */ int depth = RENDER_BIT_DEPTH_DEFAULT; /* Output to take the image from. Valid only if source is mirror */ std::string mirror_from; bool operator ==(const output_state_t& other) const; }; /** An output configuration is simply a list of each output with its state */ using output_configuration_t = std::map; /* output_layout_t is responsible for managing outputs and their attributes - * mode, scale, position, transform. */ class output_layout_t : public wf::signal::provider_t { public: output_layout_t(wlr_backend *backend); ~output_layout_t(); /** * @return the underlying wlr_output_layout */ wlr_output_layout *get_handle(); /** * @return the output at the given coordinates, or null if no such output */ wf::output_t *get_output_at(int x, int y); /** * Get the output closest to the given origin and the closest coordinates * to origin which lie inside the output. * * @param origin The start coordinates * @param closest The closest point to origin inside the returned output * @return the output at the given coordinates */ wf::output_t *get_output_coords_at(wf::pointf_t origin, wf::pointf_t& closest); /** * @return the number of the active outputs in the output layout */ size_t get_num_outputs(); /** * @return a list of the active outputs in the output layout */ std::vector get_outputs(); /** * @return the "next" output in the layout. It is guaranteed that starting * with any output in the layout, and successively calling this function * will iterate over all outputs */ wf::output_t *get_next_output(wf::output_t *output); /** * @return the output_t associated with the wlr_output, or null if the * output isn't found */ wf::output_t *find_output(wlr_output *output); wf::output_t *find_output(std::string name); /** * @return the current output configuration. This contains ALL outputs, * not just the ones in the actual layout (so disabled ones are included * as well) */ output_configuration_t get_current_configuration(); /** * Apply the given configuration. It must contain exactly the outputs * returned by get_current_configuration() - no more and no less. * * Failure to apply the configuration on any output will reset all * outputs to their previous state. * * @param configuration The output configuration to be applied * @param test_only If true, this will only simulate applying * the configuration, without actually changing anything * * @return true on successful application, false otherwise */ bool apply_configuration(const output_configuration_t& configuration, bool test_only = false); class impl; std::unique_ptr pimpl; }; } #endif /* end of include guard: OUTPUT_LAYOUT_HPP */ wayfire-0.10.0/src/api/wayfire/bindings.hpp0000664000175000017500000000524315053502647020451 0ustar dkondordkondor#ifndef WF_BINDINGS_HPP #define WF_BINDINGS_HPP #include #include #include #include namespace wf { struct touchgesture_t; /** * A callback for key bindings. * Receives as a parameter the key combination which activated it. * * The returned value indicates whether the key event has been consumed, in which * case it will not be sent to clients (but may still be received by other plugins). */ using key_callback = std::function; /** * A callback for button bindings. * Receives as a parameter the button combination which activated it. * * The returned value indicates whether the button event has been consumed, in * which case it will not be sent to clients (but may still be received by other * plugins). */ using button_callback = std::function; /** * A callback for axis bindings. * Receives as a parameter an axis event from wlroots. * * The returned value indicates whether the event has been consumed, in which * case it will not be sent to clients (but may still be received by other * plugins). */ using axis_callback = std::function; /** * Describes the possible event sources that can activate an activator binding. */ enum class activator_source_t { /** Binding activated by a keybinding. */ KEYBINDING, /** Binding activated by a modifier keybinding. */ MODIFIERBINDING, /** Binding activated by a button binding. */ BUTTONBINDING, /** Binding activated by a touchscreen gesture. */ GESTURE, /** Binding activated by a hotspot. */ HOTSPOT, /** Binding was activated by another plugin. */ PLUGIN, /** Binding was activated by another plugin with custom data */ PLUGIN_WITH_DATA, }; /** * Data sent to activator bindings when they are activated. * Includes information from the activating event source. * * Note: some plugins might support extended activator data, i.e they might * accept a subclass of activator_data_t when source is PLUGIN_WITH_DATA. */ struct activator_data_t { /** The activating source type */ activator_source_t source; /** * Additional data from the event source which activates the activator. * * - The key which was pressed for KEYBINDING * - The modifier which was released for MODIFIERBINDING * - The button pressed for BUTTONBINDING * - The hotspot edges for HOTSPOT * - undefined otherwise */ uint32_t activation_data; }; using activator_callback = std::function; } #endif /* end of include guard: WF_BINDINGS_HPP */ wayfire-0.10.0/src/api/wayfire/scene-input.hpp0000664000175000017500000001453715053502647021114 0ustar dkondordkondor#pragma once #include #include #include #include #include namespace wf { class seat_t; namespace scene { class node_t; using node_ptr = std::shared_ptr; } /** * When refocusing on a particular output, there may be multiple nodes * which can receive keyboard focus. While usually the most recently focused * node is chosen, there are cases where this is not the desired behavior, for ex. * nodes which have keyboard grabs. In order to accommodate for these cases, * the focus_importance enum provides a way for nodes to indicate in what cases * they should receive keyboard focus. */ enum class focus_importance { // No focus at all. NONE = 0, // Node may accept focus, but further nodes should override it if sensible. LOW = 1, // Regularly focused node (typically regular views). REGULAR = 2, // Highest priority. Nodes which request focus like this usually do not // get their requests overridden. HIGH = 3, }; struct keyboard_focus_node_t { scene::node_t *node = nullptr; focus_importance importance = focus_importance::NONE; // Whether nodes below this node are allowed to get focus, no matter their // focus importance. bool allow_focus_below = true; /** * True iff: * 1. The other node has a higher focus importance * 2. Or, the other node has the same importance but a newer * last_focus_timestamp. */ bool operator <(const keyboard_focus_node_t& other) const; }; /** * An interface for scene nodes which interact with the keyboard. * * Note that by default, nodes do not receive keyboard input. Nodes which wish * to do so need to have node_flags::ACTIVE_KEYBOARD set. */ class keyboard_interaction_t { public: /** * Handle a keyboard enter event. * This means that the node is now focused. */ virtual void handle_keyboard_enter(wf::seat_t *seat) {} /** * Handle a keyboard leave event. * The node is no longer focused. */ virtual void handle_keyboard_leave(wf::seat_t *seat) {} /** * Handle a keyboard key event. * * These are received only after the node has received keyboard focus and * before it loses it. * * @return What should happen with the further processing of the event. */ virtual void handle_keyboard_key(wf::seat_t *seat, wlr_keyboard_key_event event) {} keyboard_interaction_t() = default; virtual ~keyboard_interaction_t() {} /** * The last time(nanoseconds since epoch) when the node was focused. * Updated automatically by core. */ uint64_t last_focus_timestamp = 0; }; /** * An interface for scene nodes which interact with pointer input. * * As opposed to keyboard input, all nodes are eligible for receiving pointer * and input. As a result, every node may receive motion, button, etc. events. * Nodes which do not wish to process events may simply not accept input at * any point (as the default accepts_input implementation does). */ class pointer_interaction_t { protected: pointer_interaction_t(const pointer_interaction_t&) = delete; pointer_interaction_t(pointer_interaction_t&&) = delete; pointer_interaction_t& operator =(const pointer_interaction_t&) = delete; pointer_interaction_t& operator =(pointer_interaction_t&&) = delete; public: pointer_interaction_t() = default; virtual ~pointer_interaction_t() = default; /** * The pointer entered the node and thus the node gains pointer focus. */ virtual void handle_pointer_enter(wf::pointf_t position) {} /** * Notify a node that it no longer has pointer focus. * This event is always sent after a corresponding pointer_enter event. */ virtual void handle_pointer_leave() {} /** * Handle a button press or release event. * * When a node consumes a button event, core starts an *implicit grab* for it. This has the effect that * all subsequent input events are forwarded to that node, until all buttons are released. Thus, a node is * guaranteed to always receive matching press and release events, except when it explicitly opts out via * the RAW_INPUT node flag. * * @param pointer_position The position where the pointer is currently at. * @param button The wlr event describing the event. */ virtual void handle_pointer_button( const wlr_pointer_button_event& event) {} /** * The user moved the pointer. * * @param pointer_position The new position of the pointer. * @param time_ms The time reported by the device when the event happened. */ virtual void handle_pointer_motion(wf::pointf_t pointer_position, uint32_t time_ms) {} /** * The user scrolled. * * @param pointer_position The position where the pointer is currently at. * @param event A structure describing the event. */ virtual void handle_pointer_axis(const wlr_pointer_axis_event& event) {} }; /** * An interface for scene nodes which interact with touch input. */ class touch_interaction_t { public: touch_interaction_t() = default; virtual ~touch_interaction_t() = default; /** * The user pressed down with a finger on the node. * * @param finger_id The id of the finger pressed down (first is 0, then 1, * 2, ..). Note that it is possible that the finger 0 is pressed down on * another node, then the current node may start receiving touch down * events beginning with finger 1, 2, ... * * @param position The coordinates of the finger. */ virtual void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t position) {} /** * The user lifted their finger off the node. * * @param finger_id The id of the finger being lifted. It is guaranteed that * the finger will have been pressed on the node before. * @param lift_off_position The last position the finger had before the * lift off. */ virtual void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t lift_off_position) {} /** * The user moved their finger without lifting it off. */ virtual void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t position) {} }; } wayfire-0.10.0/src/api/wayfire/region.hpp0000664000175000017500000000447315053502647020143 0ustar dkondordkondor#pragma once #include #include "wayfire/geometry.hpp" /* ---------------------- pixman utility functions -------------------------- */ namespace wf { struct region_t { region_t(); /* Makes a copy of the given region */ region_t(const pixman_region32_t *damage); region_t(const wlr_box& box); ~region_t(); region_t(const region_t& other); region_t(region_t&& other); region_t& operator =(const region_t& other); region_t& operator =(region_t&& other); bool empty() const; void clear(); void expand_edges(int amount); pixman_box32_t get_extents() const; bool contains_point(const point_t& point) const; bool contains_pointf(const pointf_t& point) const; /* Translate the region */ region_t operator +(const point_t& vector) const; region_t& operator +=(const point_t& vector); region_t operator -(const point_t& vector) const; region_t& operator -=(const point_t& vector); region_t operator *(float scale) const; region_t& operator *=(float scale); /* Region intersection */ region_t operator &(const wlr_box& box) const; region_t operator &(const region_t& other) const; region_t& operator &=(const wlr_box& box); region_t& operator &=(const region_t& other); /* Region union */ region_t operator |(const wlr_box& other) const; region_t operator |(const region_t& other) const; region_t& operator |=(const wlr_box& other); region_t& operator |=(const region_t& other); /* Subtract the box/region from the current region */ region_t operator ^(const wlr_box& box) const; region_t operator ^(const region_t& other) const; region_t& operator ^=(const wlr_box& box); region_t& operator ^=(const region_t& other); pixman_region32_t *to_pixman(); const pixman_region32_t *to_pixman() const; const pixman_box32_t *begin() const; const pixman_box32_t *end() const; private: pixman_region32_t _region; /* Returns a const-casted pixman_region32_t*, useful in const operators * where we use this->_region as only source for calculations, but pixman * won't let us pass a const pixman_region32_t* */ pixman_region32_t *unconst() const; }; } wlr_box wlr_box_from_pixman_box(const pixman_box32_t& box); pixman_box32_t pixman_box_from_wlr_box(const wlr_box& box); wayfire-0.10.0/src/api/wayfire/seat.hpp0000664000175000017500000000736515053502647017617 0ustar dkondordkondor#pragma once #include #include #include #include namespace wf { enum class keyboard_focus_reason { USER, GRAB, REFOCUS, UNKNOWN, }; /** * on: core * when: Keyboard focus is changed (may change to nullptr). */ struct keyboard_focus_changed_signal { wf::scene::node_ptr new_focus; keyboard_focus_reason reason = keyboard_focus_reason::UNKNOWN; }; /** * on: core * when: Seat activity has happened after being idle. */ struct seat_activity_signal {}; /** * A seat represents a group of input devices (mouse, keyboard, etc.) which logically belong together. * Each seat has its own keyboard, touch, pointer and tablet focus. * Currently, Wayfire supports only a single seat. */ class seat_t { public: // A pointer to the wlroots seat wlr_seat *const seat; wlr_seat*const seat; /** * Get the xkb_state of the currently active keyboard. * Note: may be null if there is no keyboard connected to the seat. */ xkb_state *get_xkb_state(); /** * Get a list of all currently pressed keys. */ std::vector get_pressed_keys(); /** * Get a bitmask of the pressed modifiers on the current keyboard. * The returned value is a bitmask of WLR_MODIFIER_*. */ uint32_t get_keyboard_modifiers(); /** * Figure out whether the given keycode is a modifier on the current keyboard's keymap. * If yes, return the modifier as a WLR_MODIFIER_* bitmask, otherwise, return 0. */ uint32_t modifier_from_keycode(uint32_t keycode); /** * Try to focus the given scenegraph node. This may not work if another node requests a higher focus * importance. * * Note that the focus_view function should be used for view nodes, as focusing views typically involves * more operations. Calling this function does not change the active view, even if the newly focused node * is a view node! * * The new_focus' last focus timestamp will be updated. */ void set_active_node(wf::scene::node_ptr node, wf::keyboard_focus_reason reason = keyboard_focus_reason::UNKNOWN); /** * Get the node which has current keyboard focus. */ wf::scene::node_ptr get_active_node(); /** * Try to focus the given view. This may not work if another view or a node requests a higher focus * importance. */ void focus_view(wayfire_view v); /** * Get the view which is currently marked as active. In general, this is the last view for which * @focus_view() was called, or for ex. when refocusing after a view disappears, the next view which * received focus. * * Usually, the active view has keyboard focus as well. In some cases (for ex. grabs), however, another * node might have the actual keyboard focus. */ wayfire_view get_active_view() const; /** * Get the last focus timestamp which was given out by a set_active_node request. */ uint64_t get_last_focus_timestamp() const; /** * Trigger a refocus operation. * See scene::node_t::keyboard_refocus() for details. */ void refocus(); /** * Focus the given output. The currently focused output is used to determine * which plugins receive various events (including bindings) */ void focus_output(wf::output_t *o); /** * Get the currently focused "active" output */ wf::output_t *get_active_output(); /** * Notify clients and plugins of input activity on the seat */ void notify_activity(); /** * Create and initialize a new seat. */ seat_t(wl_display *display, std::string name); ~seat_t(); struct impl; std::unique_ptr priv; }; } wayfire-0.10.0/src/api/wayfire/render.hpp0000664000175000017500000003715715053502647020144 0ustar dkondordkondor#pragma once #include #include #include #include #include #include #include namespace wf { class output_t; /** * A simple, non-owning wrapper for a wlr_texture + source box. */ struct texture_t { wlr_texture *texture = NULL; std::optional source_box = {}; wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; std::optional filter_mode = {}; }; struct auxilliary_buffer_t; /** * A simple wrapper for buffers which are used as render targets. * Note that a renderbuffer does not assume any ownership of the buffer. */ struct render_buffer_t { render_buffer_t() = default; render_buffer_t(wlr_buffer *buffer, wf::dimensions_t size); /** * Get the backing buffer. */ wlr_buffer *get_buffer() const { return buffer; } wf::dimensions_t get_size() const { return size; } /** * Copy a part of another buffer onto this buffer. * Note that this operation may involve the creation and deletion of textures, which can reset the * GL state. * * @param source The source buffer containing the pixel data to be copied from. * @param src_box The subrectangle of the source buffer to be copied from. * @param dst_box The subrectangle of the destination buffer to be copied to. * @param filter_mode The filter mode to use. */ void blit(wf::auxilliary_buffer_t& source, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode = WLR_SCALE_FILTER_BILINEAR) const; /** * Copy a part of another buffer onto this buffer. * Note that this operation may involve the creation and deletion of textures, which can reset the * GL state. * * @param source The source buffer containing the pixel data to be copied from. * @param src_box The subrectangle of the source buffer to be copied from. * @param dst_box The subrectangle of the destination buffer to be copied to. * @param filter_mode The filter mode to use. */ void blit(const wf::render_buffer_t& source, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode = WLR_SCALE_FILTER_BILINEAR) const; private: friend struct auxilliary_buffer_t; // The wlr_buffer backing the framebuffer. wlr_buffer *buffer = NULL; wf::dimensions_t size = {0, 0}; // Helper for copy operations void do_blit(wlr_texture *src_wlr_tex, wlr_fbox src_box, wf::geometry_t dst_box, wlr_scale_filter_mode filter_mode) const; }; /** * Hints for choosing a suitable underlying memory layout when allocating a buffer. */ struct buffer_allocation_hints_t { bool needs_alpha = true; }; /** * Result of an allocate() call for an auxilliary buffer. */ enum class buffer_reallocation_result_t { /** Buffer does not need reallocation (i.e buffer already had a good size) */ SAME, /** Buffer was successfully reallocated to the new size. */ REALLOCATED, /** Buffer reallocation failed. */ FAILED, }; /** * A class managing a buffer used for rendering purposes. * Typically, such buffers are used to composite several textures together, which are then composited onto * a final buffer. */ struct auxilliary_buffer_t { public: auxilliary_buffer_t() = default; auxilliary_buffer_t(const auxilliary_buffer_t& other) = delete; auxilliary_buffer_t(auxilliary_buffer_t&& other); auxilliary_buffer_t& operator =(const auxilliary_buffer_t& other) = delete; auxilliary_buffer_t& operator =(auxilliary_buffer_t&& other); ~auxilliary_buffer_t(); /** * Resize the framebuffer. * Note that this may change the underlying wlr_buffer/wlr_texture. * * @param width The desired width * @param height The desired height * @param scale The desired scale, so that the final size will be * ceil(width * scale) x ceil(height * scale). * * @return The result of the reallocation operation. */ buffer_reallocation_result_t allocate(wf::dimensions_t size, float scale = 1.0, buffer_allocation_hints_t hints = {}); /** * Free the wlr_buffer/wlr_texture backing this framebuffer. */ void free(); /** * Get the currently allocated wlr_buffer. * Note that the wlr_buffer may be NULL if no buffer has been allocated yet. */ wlr_buffer *get_buffer() const; /** * Get the currently allocated size. */ wf::dimensions_t get_size() const; /** * Get the current buffer and size as a renderbuffer. */ render_buffer_t get_renderbuffer() const; /** * Get the backing texture. * If no texture has been created for the buffer yet, a new texture will be created. */ wlr_texture *get_texture(); private: render_buffer_t buffer; // The wlr_texture creating from this framebuffer. wlr_texture *texture = NULL; }; /** * A render target contains a render buffer and information on how to map * coordinates from the logical coordinate space (output-local coordinates, etc.) * to buffer coordinates. * * A render target may or not cover the full framebuffer. */ struct render_target_t : public render_buffer_t { render_target_t() = default; explicit render_target_t(const render_buffer_t& buffer); explicit render_target_t(const auxilliary_buffer_t& buffer); // Describes the logical coordinates of the render area, in whatever // coordinate system the render target needs. wf::geometry_t geometry = {0, 0, 0, 0}; wl_output_transform wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; // The scale of a framebuffer is a hint at how bigger the actual framebuffer // is compared to the logical geometry. It is useful for plugins utilizing // auxiliary buffers in logical coordinates, so that they know they should // render with higher resolution and still get a crisp image on the screen. float scale = 1.0; // If set, the subbuffer indicates a subrectangle of the framebuffer which // is used instead of the full buffer. In that case, the logical @geometry // is mapped only to that subrectangle and not to the full framebuffer. // Note: (0,0) is top-left for subbuffer. std::optional subbuffer; /** * Get a render target which is the same as this, but whose geometry is * translated by @offset. */ render_target_t translated(wf::point_t offset) const; /** * Get the geometry of the given box after projecting it onto the framebuffer. * In the values returned, (0,0) is top-left. * * The resulting geometry is affected by the framebuffer geometry, scale and * transform. */ wlr_box framebuffer_box_from_geometry_box(wlr_box box) const; /** * Get the geometry of the given fbox after projecting it onto the framebuffer. * In the values returned, (0,0) is top-left. * * The resulting geometry is affected by the framebuffer geometry, scale and * transform. */ wlr_fbox framebuffer_box_from_geometry_box(wlr_fbox box) const; /** * Get the geometry of the given region after projecting it onto the framebuffer. This is the same as * iterating over the rects in the region and transforming them with framebuffer_box_from_geometry_box. */ wf::region_t framebuffer_region_from_geometry_region(const wf::region_t& region) const; /** * Get the geometry of the given framebuffer box after projecting it back to the logical coordinate space. * * The resulting geometry is affected by the framebuffer geometry, scale and * transform. */ wlr_box geometry_box_from_framebuffer_box(wlr_box fb_box) const; /** * Get the geometry of the given framebuffer fbox after projecting it back to the logical coordinate * space. * * The resulting geometry is affected by the framebuffer geometry, scale and * transform. */ wlr_fbox geometry_fbox_from_framebuffer_box(wlr_fbox fb_box) const; /** * Get the geometry of the given framebuffer region after projecting it back to the logical coordinate * space. * This is the same as iterating over the rects in the region and transforming them with * geometry_box_from_framebuffer_box. */ wf::region_t geometry_region_from_framebuffer_region(const wf::region_t& region) const; }; namespace scene { class render_instance_t; using render_instance_uptr = std::unique_ptr; } enum render_pass_flags { /** * Do not emit render-pass-{begin, end} signals. */ RPASS_EMIT_SIGNALS = (1 << 0), /** * Do not clear the background areas. */ RPASS_CLEAR_BACKGROUND = (1 << 1), }; /** * A struct containing the information necessary to execute a render pass. */ struct render_pass_params_t { /** The instances which are to be rendered in this render pass. */ std::vector *instances = NULL; /** The rendering target. */ render_target_t target; /** The total damage accumulated from the instances since the last repaint. */ region_t damage; /** * The background color visible below all instances, if * RPASS_CLEAR_BACKGROUND is specified. */ color_t background_color; /** * The output the instances were rendered, used for sending presentation * feedback. */ output_t *reference_output = nullptr; /** * The wlroots renderer to use for this pass. * In case that it is not set, wf::get_core().renderer will be used. */ wlr_renderer *renderer = nullptr; /** * Additional options for the wlroots buffer pass. */ wlr_buffer_pass_options *pass_opts = nullptr; /** * Flags for this render pass, see @render_pass_flags. */ uint32_t flags = 0; }; /** * A render pass is used to generate and execute a set of drawing commands to the same render target. */ class render_pass_t { render_pass_params_t params; wlr_render_pass *pass = NULL; public: render_pass_t(const render_pass_params_t& params); // Cannot copy a render pass: we would need to duplicate all the render commands, which does not make // sense. render_pass_t(const render_pass_t& other) = delete; render_pass_t& operator =(const render_pass_t& other) = delete; render_pass_t(render_pass_t&& other); render_pass_t& operator =(render_pass_t&& other); ~render_pass_t(); /** * Run a new render pass from start to finish. * This includes generate instructions for the render pass and executing them. * * The render pass goes as described below: * * 1. Optionally, emit render-pass-begin. * 2. Render instructions are generated from the given instances. During this phase, the instances may * start and execute sub-passes. * 3. The wlroots render pass begins. * 4. Optionally, clear visible background areas with @background_color. * 5. Render instructions are executed back-to-forth. * 6. Optionally, emit render-pass-end. * 7. The wlroots render pass is submitted. * * By specifying @flags, steps 1, 4, and 6 can be enabled and disabled. * * @return The full damage which was rendered on the render target. It may be more (or * less) than @params.damage because plugins are allowed to modify the * damage in render-pass-begin. */ static wf::region_t run(const wf::render_pass_params_t& params); /** * Same as @run, but does not submit the wlroots render pass (i.e step 7 is omitted). */ wf::region_t run_partial(); /** * The current wlroots render pass. * Note that one Wayfire pass may result in multiple wlroots render passes, if the render commands are * interspersed with custom rendering code in plugins, so this pointer may change over the duration of * the Wayfire render pass. */ wlr_render_pass *get_wlr_pass(); /** * Clear the given region (relative to the render target's geometry) with the given color. */ void clear(const wf::region_t& region, const wf::color_t& color); /** * Add a texture rendering operation to the pass. */ void add_texture(const wf::texture_t& texture, const wf::render_target_t& adjusted_target, const wf::geometry_t& geometry, const wf::region_t& damage, float alpha = 1.0); /** * Add a texture rendering operation to the pass using wlr_fbox for geometry. */ void add_texture(const wf::texture_t& texture, const wf::render_target_t& adjusted_target, const wlr_fbox& geometry, const wf::region_t& damage, float alpha = 1.0); /** * Add a colored rectangle to the pass. */ void add_rect(const wf::color_t& color, const wf::render_target_t& adjusted_target, const wf::geometry_t& geometry, const wf::region_t& damage); /** * Add a colored rectangle to the pass using wlr_fbox for geometry. */ void add_rect(const wf::color_t& color, const wf::render_target_t& adjusted_target, const wlr_fbox& geometry, const wf::region_t& damage); /** * Get the wlr_renderer used in this pass. */ wlr_renderer *get_wlr_renderer() const; /** * Get the render target. */ wf::render_target_t get_target() const; /** * Submit the wlroots render pass. * Should only be used after run_partial(). */ bool submit(); /** * A helper function for plugins which support custom OpenGL ES rendering. * * The callback is executed when running with the wlroots GLES renderer and is simply skipped otherwise. * It is guaranteed that if it is executed, then the pass' target buffer will be bound as the draw * framebuffer and its full size set as the viewport. In addition, the blending mode (1, 1-src_alpha) * will be enabled. * * Plugins need to reset any GL state that they change after this callback except the bound draw * framebuffer, the viewport and the currently bound program. * * The subpass functionality is intended to be used for custom rendering and could be used to support * both Vulkan and GLES rendering with minimum effort by running one GLES and one Vulkan subpass. * Depending on the active renderer, one of them will be skipped. */ template bool custom_gles_subpass(F&& fn) { if (prepare_gles_subpass()) { fn(); finish_gles_subpass(); return true; } return false; } template bool custom_gles_subpass(const wf::render_target_t& target, F&& fn) { if (prepare_gles_subpass()) { fn(); finish_gles_subpass(); return true; } return false; } private: bool prepare_gles_subpass(); bool prepare_gles_subpass(const wf::render_target_t& target); void finish_gles_subpass(); }; /** * Signal that a render pass starts. * emitted on: core. */ struct render_pass_begin_signal { render_pass_begin_signal(wf::render_pass_t& pass, wf::region_t& damage) : damage(damage), pass(pass) {} /** * The initial damage for this render pass. * Plugins may expand it further. */ wf::region_t& damage; /** * The render pass that is starting. */ wf::render_pass_t& pass; }; /** * Signal that is emitted once a render pass ends. * emitted on: core. */ struct render_pass_end_signal { render_pass_end_signal(wf::render_pass_t& pass) : pass(pass) {} wf::render_pass_t& pass; }; } wayfire-0.10.0/src/api/wayfire/opengl.hpp0000664000175000017500000002740615053502647020145 0ustar dkondordkondor#ifndef WF_OPENGL_HPP #define WF_OPENGL_HPP #include "wayfire/render.hpp" #include #include #include #include #include #define GLM_FORCE_RADIANS #include #include void gl_call(const char*, uint32_t, const char*); #ifndef __STRING # define __STRING(x) #x #endif /* * recommended to use this to make OpenGL calls, since it offers easier debugging * This macro is taken from WLC source code */ #define GL_CALL(x) x;gl_call(__PRETTY_FUNCTION__, __LINE__, __STRING(x)) struct gl_geometry { float x1, y1, x2, y2; }; namespace wf { // Extra functions for plugins dealing with render targets with OpenGL ES rendering. namespace gles { GLuint ensure_render_buffer_fb_id(const render_buffer_t& buffer); void bind_render_buffer(const render_buffer_t& buffer); /* Set the GL scissor to the given box, after inverting it to match GL coordinate * space */ void scissor_render_buffer(const render_buffer_t& buffer, wlr_box box); /* Returns a matrix which contains an orthographic projection from "geometry" * coordinates to the framebuffer coordinates. */ glm::mat4 render_target_orthographic_projection(const render_target_t& target); /* Returns a matrix which contains an orthographic projection from OpenGL [-1, 1] * coordinates coordinates to the framebuffer coordinates (includes rotation, * subbuffer, etc). */ glm::mat4 render_target_gl_to_framebuffer(const render_target_t& target); glm::mat4 output_transform(const render_target_t& target); /** * Set the scissor region to the given box. * * In contrast to framebuffer_t::scissor(), this method takes its argument * as a box with "logical" coordinates, not raw framebuffer coordinates. * * @param box The scissor box, in the same coordinate system as the * framebuffer's geometry. */ void render_target_logic_scissor(const render_target_t& target, wlr_box box); /** * Ensure that the default EGL/GLES context is current. */ bool ensure_context(bool fail_on_error = false); /** * Run code in the default EGL/GLES context, if we are running with GLES rendering. */ template bool run_in_context_if_gles(F&& code, bool fail_on_error = false) { if (ensure_context(fail_on_error)) { code(); return true; } return false; } /** * Run code in the default EGL/GLES context, print an error and exit otherwise. */ template bool run_in_context(F&& code) { return run_in_context_if_gles(code, true); } } /** Represents the different types(formats) of textures in Wayfire. */ enum texture_type_t { /* Regular OpenGL texture with 4 channels */ TEXTURE_TYPE_RGBA = 0, /* Regular OpenGL texture with 4 channels, but alpha channel should be * discarded. */ TEXTURE_TYPE_RGBX = 1, /** An EGLImage, it has been shared via dmabuf */ TEXTURE_TYPE_EXTERNAL = 2, /* Invalid */ TEXTURE_TYPE_ALL = 3, }; struct gles_texture_t { /* Texture type */ texture_type_t type = TEXTURE_TYPE_RGBA; /* Texture target */ GLenum target = GL_TEXTURE_2D; /* Actual texture ID */ GLuint tex_id; /** Invert Y? */ bool invert_y = false; /** Has viewport? */ bool has_viewport = false; /** * Part of the texture which is used for rendering. * Valid only if has_viewport is true. */ gl_geometry viewport_box; /* tex_id will be initialized later */ gles_texture_t(); /** Initialize a non-inverted RGBA texture with the given texture id */ gles_texture_t(GLuint tex); /** Initialize a texture with the attributes of the wlr texture */ explicit gles_texture_t(wlr_texture*, std::optional viewport = {}); explicit gles_texture_t(wf::texture_t tex); static gles_texture_t from_aux(auxilliary_buffer_t& buffer, std::optional viewport = {}); }; } namespace OpenGL { /* Clear the currently bound framebuffer with the given color */ void clear(wf::color_t color, uint32_t mask = GL_COLOR_BUFFER_BIT); enum rendering_flags_t { /* Invert the texture's X axis when sampling */ TEXTURE_TRANSFORM_INVERT_X = (1 << 0), /* Invert the texture's Y axis when sampling */ TEXTURE_TRANSFORM_INVERT_Y = (1 << 1), /* Use a subrectangle of the texture to render */ TEXTURE_USE_TEX_GEOMETRY = (1 << 2), /* * Enable an optimized, "cached" mode. * * The user first calls a render_texture variant with this bit set. * The default GL program will be called, uniforms uploaded, etc. * After that, draw_cached() may be used for each damaged rectangle. * In the end, clear_cache() is called. * * This allows re-use of uniform values for different damage rectangles. */ RENDER_FLAG_CACHED = (1 << 3), }; /** * Render a textured quad using the built-in shaders. * * @param texture The texture to render. * @param g The initial coordinates of the quad. * @param texg A rectangle containing the subtexture of @texture to render. * To enable rendering a subtexture, use * TEXTURE_USE_TEX_GEOMETRY. Texture coordinates are in the * usual coordinate system [0,1]x[0,1]. x1/y1 describe the * lower-left corner, and x2/y2 - the upper-right corner. * @param transform The matrix transformation to apply to the quad. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. */ void render_transformed_texture(wf::gles_texture_t texture, const gl_geometry& g, const gl_geometry& texg, glm::mat4 transform = glm::mat4(1.0), glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render a textured quad using the built-in shaders. * * @param texture The texture to render. * @param geometry The initial coordinates of the quad. * @param transform The matrix transformation to apply to the quad. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. In this variant, * TEX_GEOMETRY flag is ignored. */ void render_transformed_texture(wf::gles_texture_t texture, const wf::geometry_t& geometry, glm::mat4 transform = glm::mat4(1.0), glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render a textured quad on the given framebuffer. * * @param texture The texture to render. * @param fb The framebuffer to render onto. * It should have been already bound. * @param geometry The geometry of the quad to render, in the same coordinate * system as the framebuffer geometry. * @param color A color multiplier for each channel of the texture. * @param bits A bitwise OR of texture_rendering_flags_t. In this variant, * TEX_GEOMETRY flag is ignored. */ void render_texture(wf::gles_texture_t texture, const wf::render_target_t& framebuffer, const wf::geometry_t& geometry, glm::vec4 color = glm::vec4(1.f), uint32_t bits = 0); /** * Render the textured rectangle again. * * See RENDER_FLAG_CACHED for detailed explanation. */ void draw_cached(); /** * Clear the cached state. * * See RENDER_FLAG_CACHED for detailed explanation. */ void clear_cached(); /* Compiles the given shader source */ GLuint compile_shader(std::string source, GLuint type); /** * Create an OpenGL program from the given shader sources. * * @param vertex_source The source code of the vertex shader. * @param frag_source The source code of the fragment shader. */ GLuint compile_program(std::string vertex_source, std::string frag_source); /** * Render a colored rectangle using OpenGL. * * @param box The rectangle geometry. * @param color The color of the rectangle. * @param matrix The matrix to transform the rectangle with. */ void render_rectangle(wf::geometry_t box, wf::color_t color, glm::mat4 matrix); /** * An OpenGL program for rendering texture_t. * It contains multiple programs for the different texture types. * * All of the program_t's functions should only be used inside a rendering * block (see render_pass_t::custom_gles_subpass or wf::gles::(maybe_)run_in_context) */ class program_t { public: program_t(); /* Does nothing */ ~program_t(); program_t(const program_t &) = delete; program_t(program_t &&) = default; program_t& operator =(const program_t&) = delete; program_t& operator =(program_t&&) = default; /** * Compile the program consisting of @vertex_source and @fragment_source. * * Fragment source should contain two special symbols`@builtin@` and * `@builtin_ext@`.They will be replaced by the definitions needed for each * texture type, and will also provide a function `get_pixel(vec2)` to get * the texture pixel at the given position. `@builtin_ext@` has to be put * directly after the OpenGL version declaration, but there are no * restrictions about where to place `@builtin@`. * * The following identifiers should not be defined in the user source: * _wayfire_texture, _wayfire_uv_scale, _wayfire_y_base, get_pixel */ void compile(const std::string& vertex_source, const std::string& fragment_source); /** * Create a simple program * It will support only the given type. */ void set_simple(GLuint program_id, wf::texture_type_t type = wf::TEXTURE_TYPE_RGBA); /** Deletes the underlying OpenGL programs */ void free_resources(); /** * Call glUseProgram with the appropriate program for the given texture type. * Raises a runtime exception if the type is not supported by the * view_program_t . */ void use(wf::texture_type_t type); /** @return The program ID for the given texture type, or 0 on failure */ int get_program_id(wf::texture_type_t type); /** Set the given uniform for the currently used program. */ void uniform1i(const std::string& name, int value); /** Set the given uniform for the currently used program. */ void uniform1f(const std::string& name, float value); /** Set the given uniform for the currently used program. */ void uniform2f(const std::string& name, float x, float y); /** Set the given uniform for the currently used program. */ void uniform3f(const std::string& name, float x, float y, float z); /** Set the given uniform for the currently used program. */ void uniform4f(const std::string& name, const glm::vec4& value); /** Set the given uniform for the currently used program. */ void uniformMatrix4f(const std::string& name, const glm::mat4& value); /* * Set the attribute pointer and active the attribute. * * @param attrib The name of the attrib array. * @param size, stride, ptr, type The same as the corresponding arguments of * glVertexAttribPointer() */ void attrib_pointer(const std::string& attrib, int size, int stride, const void *ptr, GLenum type = GL_FLOAT); /* * Set the attrib divisor. Analogous to glVertexAttribDivisor(). * * @param attrib The name of the attribute. * @param divisor The divisor value. */ void attrib_divisor(const std::string& attrib, int divisor); /** * Set the active texture, and modify the builtin Y-inversion uniforms. * Will not work with custom programs. */ void set_active_texture(const wf::gles_texture_t& texture); /** * Deactivate the vertex attributes activated by attrib_pointer and * attrib_divisor, and reset the active OpenGL program. */ void deactivate(); private: class impl; std::unique_ptr priv; }; } /* utils */ glm::mat4 get_output_matrix_from_transform(wl_output_transform transform); #endif // WF_OPENGL_HPP wayfire-0.10.0/src/api/wayfire/plugin.hpp0000664000175000017500000001043315053502647020147 0ustar dkondordkondor#ifndef PLUGIN_H #define PLUGIN_H #include #include #include class wayfire_config; namespace wf { /** * Plugins can set their capabilities to indicate what kind of plugin they are. * At any point, only one plugin with a given capability can be active on its * output (although multiple plugins with the same capability can be loaded). */ enum plugin_capabilities_t { /** The plugin grabs input. * Required in order to use plugin_grab_interface_t::grab() */ CAPABILITY_GRAB_INPUT = 1 << 0, /** The plugin uses custom renderer */ CAPABILITY_CUSTOM_RENDERER = 1 << 1, /** The plugin manages the whole desktop, for ex. switches workspaces. */ CAPABILITY_MANAGE_DESKTOP = 1 << 2, /* Compound capabilities */ /** The plugin manages the whole compositor state */ CAPABILITY_MANAGE_COMPOSITOR = CAPABILITY_GRAB_INPUT | CAPABILITY_MANAGE_DESKTOP | CAPABILITY_CUSTOM_RENDERER, }; /** * Plugins use the plugin activation data to indicate that they are active on a particular output. * The information is used to avoid conflicts between plugins with the same capabilities. */ struct plugin_activation_data_t { // The name of the plugin. Used mostly for debugging purposes. std::string name = ""; // The plugin capabilities. A bitmask of the values specified above uint32_t capabilities = 0; /** * Each plugin might be deactivated forcefully, for example when the desktop is locked. Plugins should * honor this signal and exit their grabs/renderers immediately. Note: this is sent only to active * plugins. */ std::function cancel = [] () {}; }; class plugin_interface_t { public: /** * The init method is the entry of the plugin. In the init() method, the * plugin should register all bindings it provides, connect to signals, etc. */ virtual void init() = 0; /** * The fini method is called when a plugin is unloaded. It should clean up * all global state it has set (for ex. signal callbacks, bindings, ...), * because the plugin will be freed after this. */ virtual void fini(); /** * A plugin can request that it is never unloaded, even if it is removed * from the config's plugin list. * * Note that unloading a plugin is sometimes unavoidable, for ex. when the * output the plugin is running on is destroyed. So non-unloadable plugins * should still provide proper fini() methods. */ virtual bool is_unloadable() { return true; } /** * When Wayfire starts, plugins are first sorted according to their order_hint before being initialized. * * The initialization order can be important for plugins which provide basic services like IPC and should * therefore be loaded and initialized first. * * The lower the order_hint, the earlier the plugin will be loaded. * Plugins with equal order hints will be loaded according to the order in the `core/plugins` option. */ virtual int get_order_hint() const { return 0; } virtual ~plugin_interface_t() = default; }; } /** * Each plugin must provide a function which instantiates the plugin's class * and returns the instance. * * This function must have the name newInstance() and should be declared with * extern "C" so that the loader can find it. */ using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** * The version is defined as macro as well, to allow conditional compilation. */ #define WAYFIRE_API_ABI_VERSION_MACRO 2025'08'22 /** * The version of Wayfire's API/ABI */ constexpr uint32_t WAYFIRE_API_ABI_VERSION = WAYFIRE_API_ABI_VERSION_MACRO; /** * Each plugin must also provide a function which returns the Wayfire API/ABI * that it was compiled with. * * This function must have the name getWayfireVersion() and should be declared * with extern "C" so that the loader can find it. */ using wayfire_plugin_version_func = uint32_t (*)(); /** * A macro to declare the necessary functions, given the plugin class name */ #define DECLARE_WAYFIRE_PLUGIN(PluginClass) \ extern "C" \ { \ wf::plugin_interface_t*newInstance() { return new PluginClass; } \ uint32_t getWayfireVersion() { return WAYFIRE_API_ABI_VERSION; } \ } #endif wayfire-0.10.0/src/util.cpp0000664000175000017500000000534115053502647015404 0ustar dkondordkondor#include #include #include #include #include #include "wl-listener-wrapper.tpp" /* Misc helper functions */ int64_t wf::timespec_to_msec(const timespec& ts) { return ts.tv_sec * 1000ll + ts.tv_nsec / 1000000ll; } int64_t wf::get_current_time() { timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return wf::timespec_to_msec(ts); } static void handle_idle_listener(void *data) { auto call = (wf::wl_idle_call*)(data); call->execute(); } static int handle_timeout(void *data) { (*((std::function*)data))(); return 0; } namespace wf { wl_idle_call::wl_idle_call() = default; wl_idle_call::~wl_idle_call() { disconnect(); } void wl_idle_call::set_callback(callback_t call) { disconnect(); this->call = call; } wl_event_loop*wl_idle_call::loop = NULL; void wl_idle_call::run_once() { if (!call || source) { return; } auto use_loop = loop ?: get_core().ev_loop; source = wl_event_loop_add_idle(use_loop, handle_idle_listener, this); } void wl_idle_call::run_once(callback_t cb) { set_callback(cb); run_once(); } void wl_idle_call::disconnect() { if (!source) { return; } wl_event_source_remove(source); source = nullptr; } bool wl_idle_call::is_connected() const { return source; } void wl_idle_call::execute() { source = nullptr; if (call) { call(); } } template wl_timer::~wl_timer() { if (source) { wl_event_source_remove(source); } } template void wl_timer::set_timeout(uint32_t timeout_ms, callback_t call) { if (timeout_ms == 0) { disconnect(); call(); return; } this->execute = [=] () { if constexpr (Repeat) { if (call()) { wl_event_source_timer_update(source, this->timeout); } else { disconnect(); } } else { // Disconnect first, ensuring that if `this` is destroyed, we don't use it anymore. disconnect(); call(); } }; this->timeout = timeout_ms; if (!source) { source = wl_event_loop_add_timer(get_core().ev_loop, handle_timeout, &execute); } wl_event_source_timer_update(source, timeout_ms); } template void wl_timer::disconnect() { if (source) { wl_event_source_remove(source); } source = NULL; } template bool wl_timer::is_connected() { return source != NULL; } template class wl_timer; template class wl_timer; } // namespace wf wayfire-0.10.0/src/wl-listener-wrapper.tpp0000664000175000017500000000214415053502647020371 0ustar dkondordkondor/** Implementation for wf::wl_listener_wrapper */ #include namespace wf { static void handle_wrapped_listener(wl_listener *listener, void *data) { wf::wl_listener_wrapper::wrapper *wrap = wl_container_of(listener, wrap, listener); wrap->self->emit(data); } wl_listener_wrapper::wl_listener_wrapper() { _wrap.self = this; _wrap.listener.notify = handle_wrapped_listener; wl_list_init(&_wrap.listener.link); } wl_listener_wrapper::~wl_listener_wrapper() { disconnect(); } void wl_listener_wrapper::set_callback(callback_t _call) { this->call = _call; } bool wl_listener_wrapper::connect(wl_signal *signal) { if (is_connected()) { return false; } wl_signal_add(signal, &_wrap.listener); return true; } void wl_listener_wrapper::disconnect() { wl_list_remove(&_wrap.listener.link); wl_list_init(&_wrap.listener.link); } bool wl_listener_wrapper::is_connected() const { return !wl_list_empty(&_wrap.listener.link); } void wl_listener_wrapper::emit(void *data) { if (this->call) { this->call(data); } } } wayfire-0.10.0/src/view/0000775000175000017500000000000015053502647014672 5ustar dkondordkondorwayfire-0.10.0/src/view/view-impl.hpp0000664000175000017500000000532715053502647017323 0ustar dkondordkondor#ifndef VIEW_IMPL_HPP #define VIEW_IMPL_HPP #include #include #include #include #include "wayfire/core.hpp" #include "wayfire/nonstd/tracking-allocator.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/output.hpp" #include "wayfire/scene.hpp" #include "wayfire/view-transform.hpp" #include #include #include struct wlr_seat; namespace wf { /** * Private data used by the default view_interface_t implementation * TODO: split this into multiple classes, one for all views, one for wlr-backed ones. */ class view_interface_t::view_priv_impl { public: bool is_mapped = false; wlr_surface *wsurface = nullptr; size_t last_view_cnt = 0; uint32_t allowed_actions = VIEW_ALLOW_ALL; uint32_t edges = 0; wlr_box minimize_hint = {0, 0, 0, 0}; scene::floating_inner_ptr root_node; std::shared_ptr transformed_node; scene::node_ptr current_content; scene::node_ptr dummy_node; scene::floating_inner_ptr surface_root_node; wf::output_t *output; void set_mapped(wlr_surface *surface); void set_enabled(bool enabled); void set_mapped_surface_contents(std::shared_ptr content); void unset_mapped_surface_contents(); std::weak_ptr current_wset; std::shared_ptr toplevel; wf::signal::connection_t> pre_free; }; /** * Adjust the position of the view according to the new size of its buffer and the geometry. */ void adjust_geometry_for_gravity(wf::toplevel_state_t& desired_state, wf::dimensions_t actual_size); void adjust_view_output_on_map(wf::toplevel_view_interface_t *self); void adjust_view_pending_geometry_on_start_map(wf::toplevel_view_interface_t *self, wf::geometry_t map_geometry_client, bool map_fs, bool map_maximized); /** Emit the map signal for the given view */ void init_xdg_shell(); void init_xwayland(bool lazy); void init_layer_shell(); void fini_xdg_shell(); void fini_xwayland(); void fini_layer_shell(); std::string xwayland_get_display(); void xwayland_update_default_cursor(); /* Ensure that the given surface is on top of the Xwayland stack order. */ void xwayland_bring_to_front(wlr_surface *surface); int xwayland_get_pid(); void init_desktop_apis(); void fini_desktop_apis(); void init_xdg_decoration_handlers(); void fini_xdg_decoration_handlers(); pointf_t place_popup_at(wlr_surface *parent, wlr_surface *popup, wf::pointf_t relative); } #endif /* end of include guard: VIEW_IMPL_HPP */ wayfire-0.10.0/src/view/wlr-surface-touch-interaction.cpp0000664000175000017500000000437615053502647023277 0ustar dkondordkondor#include #include "../core/core-impl.hpp" #include "../core/seat/seat-impl.hpp" #include "view-impl.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/geometry.hpp" namespace wf { class wlr_surface_touch_interaction_t final : public wf::touch_interaction_t { wlr_surface *surface; public: wlr_surface_touch_interaction_t(wlr_surface *surface) { this->surface = surface; } void handle_touch_down(uint32_t time_ms, int finger_id, wf::pointf_t local) override { auto& seat = wf::get_core_impl().seat; seat->priv->last_press_release_serial = wlr_seat_touch_notify_down(seat->seat, surface, time_ms, finger_id, local.x, local.y); if (!seat->priv->drag_active) { wf::xwayland_bring_to_front(surface); } } void handle_touch_up(uint32_t time_ms, int finger_id, wf::pointf_t) override { auto& seat = wf::get_core_impl().seat; wlr_seat_touch_notify_up(seat->seat, time_ms, finger_id); // FIXME: wlroots does not give us the serial for touch up yet, so we just reset it to something // invalid. seat->priv->last_press_release_serial = 0; } void handle_touch_motion(uint32_t time_ms, int finger_id, wf::pointf_t local) override { auto& seat = wf::get_core_impl().seat; if (seat->priv->drag_active) { auto gc = wf::get_core().get_touch_position(finger_id); auto node = wf::get_core().scene()->find_node_at(gc); auto snode = node ? dynamic_cast(node->node.get()) : nullptr; if (snode && snode->get_surface()) { wlr_seat_touch_point_focus(seat->seat, snode->get_surface(), time_ms, finger_id, node->local_coords.x, node->local_coords.y); wlr_seat_touch_notify_motion(seat->seat, time_ms, finger_id, node->local_coords.x, node->local_coords.y); } else { wlr_seat_touch_point_clear_focus(seat->seat, time_ms, finger_id); } return; } wlr_seat_touch_notify_motion(seat->seat, time_ms, finger_id, local.x, local.y); } }; } wayfire-0.10.0/src/view/compositor-view.cpp0000664000175000017500000001202115053502647020540 0ustar dkondordkondor#include "wayfire/geometry.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include #include #include static void render_colored_rect(const wf::scene::render_instruction_t& data, int x, int y, int w, int h, const wf::color_t& color) { wf::color_t premultiply{color.r * color.a, color.g * color.a, color.b * color.a, color.a}; w = std::max(w, 0); h = std::max(h, 0); data.pass->add_rect(premultiply, data.target, wf::geometry_t{x, y, w, h}, data.damage); } class wf::color_rect_view_t::color_rect_node_t : public wf::scene::floating_inner_node_t { class color_rect_render_instance_t : public wf::scene::simple_render_instance_t { public: using simple_render_instance_t::simple_render_instance_t; void render(const wf::scene::render_instruction_t& data) override { auto view = self->_view.lock(); if (!view) { return; } auto geometry = self->get_bounding_box(); auto border = view->border; auto _border_color = view->_border_color; auto _color = view->_color; /* Draw the border, making sure border parts don't overlap, otherwise * we will get wrong corners if border has alpha != 1.0 */ // top render_colored_rect(data, geometry.x, geometry.y, geometry.width, border, _border_color); // bottom render_colored_rect(data, geometry.x, geometry.y + geometry.height - border, geometry.width, border, _border_color); // left render_colored_rect(data, geometry.x, geometry.y + border, border, geometry.height - 2 * border, _border_color); // right render_colored_rect(data, geometry.x + geometry.width - border, geometry.y + border, border, geometry.height - 2 * border, _border_color); /* Draw the inside of the rect */ render_colored_rect(data, geometry.x + border, geometry.y + border, geometry.width - 2 * border, geometry.height - 2 * border, _color); } }; std::weak_ptr _view; public: color_rect_node_t(std::weak_ptr view) : scene::floating_inner_node_t(false) { _view = view; } void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) override { instances.push_back(std::make_unique(this, push_damage, output)); } wf::geometry_t get_bounding_box() override { if (auto view = _view.lock()) { return view->get_geometry(); } else { return {0, 0, 0, 0}; } } }; /* Implementation of color_rect_view_t */ wf::color_rect_view_t::color_rect_view_t() : wf::view_interface_t() { this->geometry = {0, 0, 1, 1}; this->_color = {0, 0, 0, 1}; this->border = 0; } std::shared_ptr wf::color_rect_view_t::create(view_role_t role, wf::output_t *start_output, std::optional layer) { auto self = view_interface_t::create(); self->set_surface_root_node(std::make_shared(self)); self->set_role(role); self->_is_mapped = true; self->get_root_node()->set_enabled(true); if (start_output) { self->set_output(start_output); if (layer) { auto parent = (layer == wf::scene::layer::WORKSPACE) ? start_output->wset()->get_node() : start_output->node_for_layer(*layer); wf::scene::readd_front(parent, self->get_root_node()); } } wf::view_implementation::emit_view_map_signal(self, true); return self; } void wf::color_rect_view_t::close() { this->_is_mapped = false; emit_view_unmap(); } void wf::color_rect_view_t::set_color(wf::color_t color) { this->_color = color; damage(); } void wf::color_rect_view_t::set_border_color(wf::color_t border) { this->_border_color = border; damage(); } void wf::color_rect_view_t::set_border(int width) { this->border = width; damage(); } bool wf::color_rect_view_t::is_mapped() const { return _is_mapped; } void wf::color_rect_view_t::set_geometry(wf::geometry_t g) { damage(); this->geometry = g; damage(); } wf::geometry_t wf::color_rect_view_t::get_geometry() { return this->geometry; } wlr_surface*wf::color_rect_view_t::get_keyboard_focus_surface() { return nullptr; } bool wf::color_rect_view_t::is_focusable() const { return false; } wayfire-0.10.0/src/view/xwayland.cpp0000664000175000017500000002157515053502647017237 0ustar dkondordkondor#include #include #include "wayfire/core.hpp" #include "../core/core-impl.hpp" #include "../core/seat/cursor.hpp" #include #include #include #include "wayfire/unstable/wlr-view-events.hpp" #include "wayfire/util.hpp" #include "xwayland/xwayland-helpers.hpp" #include "xwayland/xwayland-view-base.hpp" #include "xwayland/xwayland-unmanaged-view.hpp" #include "xwayland/xwayland-toplevel-view.hpp" #if WF_HAS_XWAYLAND xcb_atom_t wf::xw::_NET_WM_WINDOW_TYPE_NORMAL; xcb_atom_t wf::xw::_NET_WM_WINDOW_TYPE_DIALOG; xcb_atom_t wf::xw::_NET_WM_WINDOW_TYPE_SPLASH; xcb_atom_t wf::xw::_NET_WM_WINDOW_TYPE_UTILITY; xcb_atom_t wf::xw::_NET_WM_WINDOW_TYPE_DND; namespace wf { /** * A class which manages a xwayland surface for the duration of the wlr_xwayland_surface lifetime. */ class xwayland_view_controller_t { std::shared_ptr view; wlr_xwayland_surface *xw; wf::wl_listener_wrapper on_destroy; wf::wl_listener_wrapper on_or_changed; wf::wl_listener_wrapper on_set_window_type; wf::wl_listener_wrapper on_set_parent; wf::wl_listener_wrapper on_map; wf::wl_listener_wrapper on_unmap; wf::wl_listener_wrapper on_associate; wf::wl_listener_wrapper on_dissociate; public: xwayland_view_controller_t(wlr_xwayland_surface *xsurf) { this->xw = xsurf; on_destroy.set_callback([=] (auto) { delete this; }); on_destroy.connect(&xw->events.destroy); create_view(determine_type()); on_or_changed.set_callback([&] (void*) { recreate_view(); }); on_set_window_type.set_callback([&] (void*) { recreate_view(); }); on_set_parent.set_callback([&] (void*) { /* Menus, etc. with TRANSIENT_FOR but not dialogs */ recreate_view(); }); on_or_changed.connect(&xw->events.set_override_redirect); on_set_window_type.connect(&xw->events.set_window_type); on_set_parent.connect(&xw->events.set_parent); on_map.set_callback([&] (void*) { LOGE("new xwayland surface ", xw->title, " class: ", xw->class_t, " instance: ", xw->instance); wf::view_pre_map_signal pre_map; pre_map.view = view.get(); pre_map.surface = xw->surface; wf::get_core().emit(&pre_map); if (pre_map.override_implementation) { delete this; } else { view->handle_map_request(xw->surface); } }); on_unmap.set_callback([&] (void*) { view->handle_unmap_request(); }); on_associate.set_callback([&] (void*) { on_map.connect(&xw->surface->events.map); on_unmap.connect(&xw->surface->events.unmap); }); on_dissociate.set_callback([&] (void*) { view->priv->wsurface = nullptr; on_map.disconnect(); on_unmap.disconnect(); }); on_associate.connect(&xw->events.associate); on_dissociate.connect(&xw->events.dissociate); } ~xwayland_view_controller_t() {} bool is_dialog() { if (xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_DIALOG) || (xw->parent && (xw->window_type_len == 0))) { return true; } else { return false; } } /** * Determine whether the view should be treated as override-redirect or not. */ bool is_unmanaged() { if (xw->override_redirect) { return true; } /** Example: Android Studio dialogs */ if (xw->parent && !this->is_dialog() && !wf::xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_NORMAL) && !wf::xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_UTILITY)) { return true; } return false; } /** * Determine whether the view should be treated as a drag icon. */ bool is_dnd() { return wf::xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_DND); } wf::xw::view_type determine_type() { wf::xw::view_type target_type = wf::xw::view_type::NORMAL; if (this->is_dnd()) { target_type = wf::xw::view_type::DND; } else if (this->is_unmanaged()) { target_type = wf::xw::view_type::UNMANAGED; } return target_type; } void create_view(wf::xw::view_type target_type) { switch (target_type) { case wf::xw::view_type::DND: this->view = wayfire_unmanaged_xwayland_view::create(xw); break; case wf::xw::view_type::UNMANAGED: this->view = wayfire_unmanaged_xwayland_view::create(xw); break; case wf::xw::view_type::NORMAL: this->view = wayfire_xwayland_view::create(xw); break; } if (xw->surface && xw->surface->mapped) { view->handle_map_request(xw->surface); } } /** * Destroy the view, and create a new one with the correct type - * unmanaged(override-redirect), DnD or normal. * * No-op if the view already has the correct type. */ void recreate_view() { const auto target_type = determine_type(); if (target_type == view->get_current_impl_type()) { // Nothing changed return; } // destroy the view (unmap + destroy) if (view->is_mapped()) { view->handle_unmap_request(); } view->destroy(); view = nullptr; // Create the new view. create_view(target_type); } }; } static wlr_xwayland *xwayland_handle = nullptr; static wf::wl_listener_wrapper on_xwayland_surface_created; static wf::wl_listener_wrapper on_xwayland_ready; #endif void wf::init_xwayland(bool lazy) { #if WF_HAS_XWAYLAND on_xwayland_surface_created.set_callback([] (void *data) { wf::new_xwayland_surface_signal ev; ev.surface = (wlr_xwayland_surface*)data; wf::get_core().emit(&ev); if (ev.use_default_implementation) { // Will be auto-freed on surface.destroy new wf::xwayland_view_controller_t{ev.surface}; } }); on_xwayland_ready.set_callback([&] (void *data) { if (!wf::xw::load_basic_atoms(xwayland_handle->display_name)) { LOGE("Failed to load Xwayland atoms."); } else { LOGD("Successfully loaded Xwayland atoms."); } wlr_xwayland_set_seat(xwayland_handle, wf::get_core().get_current_seat()); xwayland_update_default_cursor(); static wf::option_wrapper_t xwayland_startup_script{"core/xwayland_startup_script"}; auto script = xwayland_startup_script.value(); if (!script.empty()) { LOGD("Executing XWayland startup script: ", script); wf::get_core().run(script); } }); xwayland_handle = wlr_xwayland_create(wf::get_core().display, wf::get_core_impl().compositor, lazy); if (xwayland_handle) { on_xwayland_surface_created.connect(&xwayland_handle->events.new_surface); on_xwayland_ready.connect(&xwayland_handle->events.ready); } #endif } void wf::fini_xwayland() { #if WF_HAS_XWAYLAND if (xwayland_handle) { on_xwayland_surface_created.disconnect(); on_xwayland_ready.disconnect(); wlr_xwayland_destroy(xwayland_handle); } #endif } void wf::xwayland_update_default_cursor() { #if WF_HAS_XWAYLAND if (!xwayland_handle) { return; } auto xc = wf::get_core_impl().seat->priv->cursor->xcursor; auto cursor = wlr_xcursor_manager_get_xcursor(xc, "left_ptr", 1); if (cursor && (cursor->image_count > 0)) { auto image = cursor->images[0]; wlr_xwayland_set_cursor(xwayland_handle, image->buffer, image->width * 4, image->width, image->height, image->hotspot_x, image->hotspot_y); } #endif } void wf::xwayland_bring_to_front(wlr_surface *surface) { #if WF_HAS_XWAYLAND if (wlr_xwayland_surface *xwayland_surface = wlr_xwayland_surface_try_from_wlr_surface(surface)) { if (!xwayland_surface->override_redirect) { wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_ABOVE); } } #endif } std::string wf::xwayland_get_display() { #if WF_HAS_XWAYLAND return xwayland_handle ? nonull(xwayland_handle->display_name) : ""; #else return ""; #endif } int wf::xwayland_get_pid() { #if WF_HAS_XWAYLAND return xwayland_handle ? xwayland_handle->server->pid : -1; #else return -1; #endif } wayfire-0.10.0/src/view/xdg-shell.hpp0000664000175000017500000000524115053502647017274 0ustar dkondordkondor#ifndef XDG_SHELL_HPP #define XDG_SHELL_HPP #include #include #include #include #include "wayfire/geometry.hpp" #include "wayfire/seat.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view.hpp" class wayfire_xdg_popup_node; /** * A class for xdg-shell popups */ class wayfire_xdg_popup : public wf::view_interface_t { protected: wf::wl_listener_wrapper on_new_popup, on_map, on_unmap, on_ping_timeout, on_reposition; wf::signal::connection_t parent_geometry_changed; wf::signal::connection_t parent_title_changed; wf::signal::connection_t parent_app_id_changed; wf::signal::connection_t on_keyboard_focus_changed; void unconstrain(); void update_position(); wayfire_xdg_popup(wlr_xdg_popup *popup); friend class wf::tracking_allocator_t; public: ~wayfire_xdg_popup(); static std::shared_ptr create(wlr_xdg_popup *popup); std::weak_ptr popup_parent; wlr_xdg_popup *popup; void map(); void unmap(); void commit(); void destroy(); void close() override; void ping() override final; /* Just pass to the default wlr surface implementation */ bool is_mapped() const override; std::string get_app_id() override final; std::string get_title() override final; bool is_focusable() const override final; void move(int x, int y); wf::geometry_t get_geometry(); virtual wlr_surface *get_keyboard_focus_surface() override; private: /** * The bounding box of the view the last time it was rendered. * * This is used to damage the view when it is resized, because when a * transformer changes because the view is resized, we can't reliably * calculate the old view region to damage. */ wf::geometry_t last_bounding_box{0, 0, 0, 0}; /** The output geometry of the view */ wf::geometry_t geometry{100, 100, 0, 0}; wf::wl_listener_wrapper on_surface_commit; std::shared_ptr main_surface; std::shared_ptr surface_root_node; std::string title, app_id; void handle_app_id_changed(std::string new_app_id); void handle_title_changed(std::string new_title); void update_size(); bool should_close_on_focus_change(wf::keyboard_focus_changed_signal *ev); }; void create_xdg_popup(wlr_xdg_popup *popup); #endif /* end of include guard: XDG_SHELL_HPP */ wayfire-0.10.0/src/view/toplevel-node.cpp0000664000175000017500000001147415053502647020162 0ustar dkondordkondor#include "toplevel-node.hpp" #include "wayfire/view.hpp" #include #include wf::toplevel_view_node_t::toplevel_view_node_t(wayfire_toplevel_view view) : view_node_tag_t(view) { this->kb_interaction = std::make_unique(view); this->_view = view->weak_from_this(); } /** * Minimal percentage of the view which needs to be visible on a workspace * for it to count to be on that workspace. */ static constexpr double MIN_VISIBILITY_PC = 0.1; wf::keyboard_focus_node_t wf::toplevel_view_node_t::keyboard_refocus(wf::output_t *output) { auto view = _view.lock(); if (!view) { return wf::keyboard_focus_node_t{}; } if (!view->is_mapped() || !view->get_keyboard_focus_surface() || view->minimized || !view->get_output()) { return wf::keyboard_focus_node_t{}; } static wf::option_wrapper_t remove_output_limits{"workarounds/remove_output_limits"}; bool foreign_output = !remove_output_limits && (output != view->get_output()); if (foreign_output) { return wf::keyboard_focus_node_t{}; } // When refocusing, we consider each view visible on the output. // However, we want to filter out views which are 'barely visible', that is, // views where only a small area is visible, because the user typically does // not want to focus these views (they might be visible by mistake, or have // just a single pixel visible, etc). // // These views request a LOW focus_importance. // // NB: we refocus based on the pending geometry, because the new geometry might not have been applied // immediately after switching workspaces. auto output_box = output->get_layout_geometry(); auto view_box = view->get_pending_geometry() + wf::origin(view->get_output()->get_layout_geometry()); auto intersection = wf::geometry_intersection(output_box, view_box); double area = 1.0 * intersection.width * intersection.height; area /= 1.0 * view_box.width * view_box.height; if (area >= MIN_VISIBILITY_PC) { return wf::keyboard_focus_node_t{this, focus_importance::REGULAR}; } else if (area > 0) { return wf::keyboard_focus_node_t{this, focus_importance::LOW}; } else { return wf::keyboard_focus_node_t{}; } } wf::keyboard_interaction_t& wf::toplevel_view_node_t::keyboard_interaction() { return *kb_interaction; } std::string wf::toplevel_view_node_t::stringify() const { if (auto view = _view.lock()) { std::ostringstream out; out << view->self(); return out.str() + " " + stringify_flags(); } else { return "inert toplevel " + stringify_flags(); } } class toplevel_view_render_instance_t : public wf::scene::translation_node_instance_t { public: using translation_node_instance_t::translation_node_instance_t; wf::scene::direct_scanout try_scanout(wf::output_t *output) override { wf::toplevel_view_node_t *tnode = dynamic_cast(self.get()); auto view = tnode->get_view(); if (!view) { return wf::scene::direct_scanout::SKIP; } auto og = output->get_relative_geometry(); if (!(view->get_bounding_box() & og)) { return wf::scene::direct_scanout::SKIP; } auto result = try_scanout_from_list(children, output); if (result == wf::scene::direct_scanout::SUCCESS) { LOGC(SCANOUT, "Scanned out ", view, " on output ", output->to_string()); return wf::scene::direct_scanout::SUCCESS; } else { LOGC(SCANOUT, "Failed to scan out ", view, " on output ", output->to_string()); return wf::scene::direct_scanout::OCCLUSION; } return result; } }; void wf::toplevel_view_node_t::gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique(this, push_damage, output)); } std::optional wf::toplevel_view_node_t::to_texture() const { auto view = _view.lock(); if (!view || !view->is_mapped() || (get_children().size() != 1)) { return {}; } if (auto texturable = dynamic_cast(get_children().front().get())) { return texturable->to_texture(); } return {}; } wf::region_t wf::toplevel_view_node_t::get_opaque_region() const { auto view = _view.lock(); if (view && view->is_mapped() && view->get_wlr_surface()) { auto surf = view->get_wlr_surface(); wf::region_t region{&surf->opaque_region}; region += get_offset(); return region; } return {}; } wayfire-0.10.0/src/view/view-impl.cpp0000664000175000017500000003017115053502647017311 0ustar dkondordkondor#include "wayfire/core.hpp" #include "view-impl.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/seat.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/view.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/window-manager.hpp" #include "wayfire/workarea.hpp" #include "wayfire/workspace-set.hpp" #include #include #include #include void wf::view_implementation::emit_view_map_signal(wayfire_view view, bool has_position) { wf::view_mapped_signal data; data.view = view; data.is_positioned = has_position; view->emit(&data); if (view->get_output()) { view->get_output()->emit(&data); } wf::get_core().emit(&data); } void wf::view_implementation::emit_ping_timeout_signal(wayfire_view view) { wf::view_ping_timeout_signal data; data.view = view; view->emit(&data); } void wf::view_implementation::emit_geometry_changed_signal(wayfire_toplevel_view view, wf::geometry_t old_geometry) { wf::view_geometry_changed_signal data; data.view = view; data.old_geometry = old_geometry; view->emit(&data); wf::get_core().emit(&data); if (view->get_output()) { view->get_output()->emit(&data); } } void wf::view_interface_t::emit_view_map() { view_implementation::emit_view_map_signal(self(), false); } void wf::view_interface_t::emit_view_unmap() { view_unmapped_signal data; data.view = self(); if (get_output()) { get_output()->emit(&data); view_disappeared_signal disappeared_data; disappeared_data.view = self(); get_output()->emit(&disappeared_data); } this->emit(&data); wf::get_core().emit(&data); wf::scene::update(data.view->get_surface_root_node(), scene::update_flag::REFOCUS); } void wf::view_interface_t::emit_view_pre_unmap() { view_pre_unmap_signal data; data.view = self(); if (get_output()) { get_output()->emit(&data); } emit(&data); wf::get_core().emit(&data); } void wf::view_implementation::emit_title_changed_signal(wayfire_view view) { view_title_changed_signal data; data.view = view; view->emit(&data); wf::get_core().emit(&data); } void wf::view_implementation::emit_app_id_changed_signal(wayfire_view view) { view_app_id_changed_signal data; data.view = view; view->emit(&data); wf::get_core().emit(&data); } void wf::view_implementation::emit_toplevel_state_change_signals(wayfire_toplevel_view view, const wf::toplevel_state_t& old_state) { if (view->toplevel()->current().geometry != old_state.geometry) { emit_geometry_changed_signal(view, old_state.geometry); } if (view->toplevel()->current().tiled_edges != old_state.tiled_edges) { wf::view_tiled_signal data; data.view = view; data.old_edges = old_state.tiled_edges; data.new_edges = view->toplevel()->current().tiled_edges; view->emit(&data); wf::get_core().emit(&data); if (view->get_output()) { view->get_output()->emit(&data); } } if (view->toplevel()->current().fullscreen != old_state.fullscreen) { view_fullscreen_signal data; data.view = view; data.state = view->toplevel()->current().fullscreen; view->emit(&data); wf::get_core().emit(&data); if (view->get_output()) { view->get_output()->emit(&data); } } } void wf::init_desktop_apis() { init_xdg_shell(); init_layer_shell(); wf::option_wrapper_t xwayland_enabled("core/xwayland"); if ((xwayland_enabled.value() == "true") || (xwayland_enabled.value() == "lazy")) { init_xwayland(xwayland_enabled.value() == "lazy"); } } wayfire_view wf::wl_surface_to_wayfire_view(wl_resource *resource) { if (!resource) { return nullptr; } auto surface = (wlr_surface*)wl_resource_get_user_data(resource); if (!surface) { return nullptr; } void *handle = NULL; if (wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface)) { handle = xdg_surface->data; } if (wlr_layer_surface_v1 *layer_shell_surface = wlr_layer_surface_v1_try_from_wlr_surface(surface)) { handle = layer_shell_surface->data; } #if WF_HAS_XWAYLAND if (wlr_xwayland_surface *xwayland_surface = wlr_xwayland_surface_try_from_wlr_surface(surface)) { handle = xwayland_surface->data; } #endif wf::view_interface_t *view = static_cast(handle); return view ? view->self() : nullptr; } static inline void replace_node_or_add_front(wf::scene::floating_inner_ptr surface_root_node, wf::scene::node_ptr node_in_list, wf::scene::node_ptr new_node) { auto children = surface_root_node->get_children(); auto pos = std::find(children.begin(), children.end(), node_in_list); if (pos == children.end()) { pos = children.begin(); } else { pos = children.erase(pos); } children.insert(pos, new_node); surface_root_node->set_children_list(children); wf::scene::update(surface_root_node, wf::scene::update_flag::CHILDREN_LIST); } void wf::view_interface_t::view_priv_impl::set_mapped_surface_contents( std::shared_ptr content) { if (current_content == content) { return; } // Locate the proper place to add the surface contents. // This is not trivial because we may have added content node before (if we currently are remapping). // In those cases, we replace the content with the dummy node. replace_node_or_add_front(surface_root_node, dummy_node, content); current_content = content; if (content->get_surface()) { wlr_surface_controller_t::create_controller(content->get_surface(), surface_root_node); } } void wf::view_interface_t::view_priv_impl::unset_mapped_surface_contents() { replace_node_or_add_front(surface_root_node, current_content, dummy_node); if (auto wcont = dynamic_cast(current_content.get())) { if (wcont->get_surface()) { wlr_surface_controller_t::try_free_controller(wcont->get_surface()); } } current_content = nullptr; } void wf::view_interface_t::view_priv_impl::set_mapped(wlr_surface *surface) { wsurface = surface; is_mapped = !!surface; } void wf::view_interface_t::view_priv_impl::set_enabled(bool enabled) { if (enabled) { scene::set_node_enabled(root_node, true); } else { scene::set_node_enabled(root_node, false); } } // ---------------------------------------------- view helpers ----------------------------------------------- std::optional wf::get_view_layer(wayfire_view view) { wf::scene::node_t *node = view->get_root_node().get(); auto root = wf::get_core().scene().get(); while (node->parent()) { if (node->parent() == root) { for (int i = 0; i < (int)wf::scene::layer::ALL_LAYERS; i++) { if (node == root->layers[i].get()) { return (wf::scene::layer)i; } } } node = node->parent(); } return {}; } void wf::view_bring_to_front(wayfire_view view) { wf::scene::node_t *node = view->get_root_node().get(); wf::scene::node_t *damage_from = nullptr; bool actual_update = false; while (node->parent()) { if (!node->is_structure_node() && dynamic_cast(node->parent())) { damage_from = node->parent(); actual_update |= wf::scene::raise_to_front(node->shared_from_this()); } node = node->parent(); } if (damage_from && actual_update) { wf::scene::damage_node(damage_from->shared_from_this(), damage_from->get_bounding_box()); } } static void gather_views(wf::scene::node_ptr root, std::vector& views) { if (!root->is_enabled()) { return; } if (auto view = wf::node_to_view(root)) { views.push_back(view); return; } for (auto& ch : root->get_children()) { gather_views(ch, views); } } std::vector wf::collect_views_from_scenegraph(wf::scene::node_ptr root) { std::vector views; gather_views(root, views); return views; } std::vector wf::collect_views_from_output(wf::output_t *output, std::initializer_list layers) { std::vector views; for (auto layer : layers) { gather_views(output->node_for_layer(layer), views); } return views; } void wf::adjust_geometry_for_gravity(wf::toplevel_state_t& desired_state, wf::dimensions_t actual_size) { if (desired_state.gravity & WLR_EDGE_RIGHT) { desired_state.geometry.x += desired_state.geometry.width - actual_size.width; } if (desired_state.gravity & WLR_EDGE_BOTTOM) { desired_state.geometry.y += desired_state.geometry.height - actual_size.height; } desired_state.geometry.width = actual_size.width; desired_state.geometry.height = actual_size.height; } wayfire_view wf::find_topmost_parent(wayfire_view v) { if (auto toplevel = toplevel_cast(v)) { return find_topmost_parent(toplevel); } return v; } wayfire_toplevel_view wf::find_topmost_parent(wayfire_toplevel_view v) { while (v && v->parent) { v = v->parent; } return v; } // Offset relative to the parent surface wf::pointf_t wf::place_popup_at(wlr_surface *parent, wlr_surface *popup, wf::pointf_t relative) { auto popup_parent = wf::wl_surface_to_wayfire_view(parent->resource).get(); wf::pointf_t popup_offset = relative; // Get the {0, 0} of the parent view in output coordinates popup_offset += popup_parent->get_surface_root_node()->to_global({0, 0}); // Apply transformers to the popup position auto node = popup_parent->get_surface_root_node()->parent(); while (node != popup_parent->get_transformed_node().get()) { popup_offset = node->to_global(popup_offset); node = node->parent(); } return popup_offset; } void wf::adjust_view_output_on_map(wf::toplevel_view_interface_t *self) { wf::output_t *chosen_output = nullptr; if (self->parent) { chosen_output = self->parent->get_output(); } else if (self->get_output()) { chosen_output = self->get_output(); } else { chosen_output = wf::get_core().seat->get_active_output(); } if (self->get_output() != chosen_output) { self->set_output(chosen_output); } if (!self->parent && chosen_output) { wf::scene::readd_front(chosen_output->wset()->get_node(), self->get_root_node()); chosen_output->wset()->add_view({self}); } } void wf::fini_desktop_apis() { fini_xdg_shell(); fini_layer_shell(); // xwayland destroyed separately by core } void wf::adjust_view_pending_geometry_on_start_map(wf::toplevel_view_interface_t *self, wf::geometry_t map_geometry_client, bool map_fs, bool map_maximized) { if (map_fs) { self->toplevel()->pending().fullscreen = true; wf::get_core().default_wm->fullscreen_request(self, self->get_output(), true); } else if (map_maximized) { self->toplevel()->pending().tiled_edges = wf::TILED_EDGES_ALL; if (self->get_output()) { self->toplevel()->pending().geometry = self->get_output()->workarea->get_workarea(); } } else { auto map_geometry = wf::expand_geometry_by_margins(map_geometry_client, self->toplevel()->pending().margins); if (self->get_output()) { map_geometry = wf::clamp(map_geometry, self->get_output()->workarea->get_workarea()); } self->toplevel()->pending().geometry = map_geometry; } } wayfire-0.10.0/src/view/xwayland/0000775000175000017500000000000015053502647016521 5ustar dkondordkondorwayfire-0.10.0/src/view/xwayland/xwayland-view-base.hpp0000664000175000017500000000300315053502647022735 0ustar dkondordkondor#pragma once #include "config.h" #include #include #include #include #include "wayfire/util.hpp" #include "xwayland-helpers.hpp" #include #include #include #if WF_HAS_XWAYLAND class wayfire_xwayland_view_internal_base : public wf::xwayland_view_base_t { protected: wf::wl_listener_wrapper on_configure; /** The geometry requested by the client */ bool self_positioned = false; public: wayfire_xwayland_view_internal_base(wlr_xwayland_surface *xww) : xwayland_view_base_t(xww) {} /** * Get the current implementation type. */ virtual wf::xw::view_type get_current_impl_type() const = 0; virtual ~wayfire_xwayland_view_internal_base() = default; void _initialize() { on_configure.set_callback([&] (void *data) { handle_client_configure((wlr_xwayland_surface_configure_event*)data); }); on_configure.connect(&xw->events.request_configure); } virtual void handle_client_configure(wlr_xwayland_surface_configure_event *ev) {} virtual void handle_dissociate() {} void destroy() override { wf::xwayland_view_base_t::destroy(); on_configure.disconnect(); } virtual void handle_map_request(wlr_surface *surface) = 0; virtual void handle_unmap_request() = 0; }; #endif wayfire-0.10.0/src/view/xwayland/xwayland-toplevel-view.hpp0000664000175000017500000004625715053502647023677 0ustar dkondordkondor#pragma once #include "config.h" #include "view/view-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/seat.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/toplevel.hpp" #include "wayfire/util.hpp" #include "xwayland-view-base.hpp" #include #include #include "xwayland-toplevel.hpp" #include #include #include "../toplevel-node.hpp" #if WF_HAS_XWAYLAND class wayfire_xwayland_view : public wf::toplevel_view_interface_t, public wayfire_xwayland_view_internal_base { wf::wl_listener_wrapper on_request_move, on_request_resize, on_request_maximize, on_request_minimize, on_request_activate, on_request_fullscreen, on_set_hints, on_set_parent; wf::wl_listener_wrapper on_surface_commit; wf::wl_listener_wrapper on_set_decorations; std::shared_ptr toplevel; wf::signal::connection_t output_geometry_changed = [=] (wf::output_configuration_changed_signal *ev) { toplevel->set_output_offset(wf::origin(ev->output->get_layout_geometry())); }; void handle_client_configure(wlr_xwayland_surface_configure_event *ev) override { wf::point_t output_origin = {0, 0}; if (get_output()) { output_origin = wf::origin(get_output()->get_layout_geometry()); } LOGC(XWL, "Client configure ", self(), " at ", ev->x, ",", ev->y, " ", ev->width, "x", ev->height); if (!is_mapped()) { /* If the view is not mapped yet, let it be configured as it * wishes. We will position it properly in ::map() */ wlr_xwayland_surface_configure(xw, ev->x, ev->y, ev->width, ev->height); if ((ev->mask & XCB_CONFIG_WINDOW_X) && (ev->mask & XCB_CONFIG_WINDOW_Y)) { this->self_positioned = true; toplevel->pending().geometry.x = ev->x - output_origin.x; toplevel->pending().geometry.y = ev->y - output_origin.y; } return; } /* Use old x/y values */ ev->x = get_pending_geometry().x + output_origin.x; ev->y = get_pending_geometry().y + output_origin.y; configure_request(wlr_box{ev->x, ev->y, ev->width, ev->height}); } void update_decorated() { uint32_t csd_flags = WLR_XWAYLAND_SURFACE_DECORATIONS_NO_TITLE | WLR_XWAYLAND_SURFACE_DECORATIONS_NO_BORDER; this->set_decoration_mode(xw->decorations & csd_flags); } /* Translates geometry from X client configure requests to wayfire * coordinate system. The X coordinate system treats all outputs * as one big desktop, whereas wayfire treats the current workspace * of an output as 0,0 and everything else relative to that. This * means that we must take care when placing xwayland clients that * request a configure after initial mapping, while not on the * current workspace. * * @param output The view's output * @param ws_offset The view's workspace minus the current workspace * @param geometry The configure geometry as requested by the client * * @return Geometry with a position that is within the view's workarea. * The workarea is the workspace where the view was initially mapped. * Newly mapped views are placed on the current workspace. */ wf::geometry_t translate_geometry_to_output(wf::output_t *output, wf::point_t ws_offset, wf::geometry_t g) { auto outputs = wf::get_core().output_layout->get_outputs(); auto og = output->get_layout_geometry(); auto from = wf::get_core().output_layout->get_output_at( g.x + g.width / 2 + og.x, g.y + g.height / 2 + og.y); if (!from) { return g; } auto lg = from->get_layout_geometry(); g.x += (og.x - lg.x) + ws_offset.x * og.width; g.y += (og.y - lg.y) + ws_offset.y * og.height; if (!this->is_mapped()) { g.x *= (float)og.width / lg.width; g.y *= (float)og.height / lg.height; } return g; } void configure_request(wf::geometry_t configure_geometry) { /* Wayfire positions views relative to their output, but Xwayland * windows have a global positioning. So, we need to make sure that we * always transform between output-local coordinates and global * coordinates. Additionally, when clients send a configure request * after they have already been mapped, we keep the view on the * workspace where its center point was from last configure, in * case the current workspace is not where the view lives */ wf::output_t *o = get_output(); if (o) { auto view_workarea = (pending_fullscreen() ? o->get_relative_geometry() : o->workarea->get_workarea()); auto og = o->get_layout_geometry(); configure_geometry.x -= og.x; configure_geometry.y -= og.y; wayfire_toplevel_view view = wf::find_topmost_parent(wayfire_toplevel_view{this}); auto vg = view->get_pending_geometry(); // View workspace relative to current workspace wf::point_t view_ws = {0, 0}; if (view->is_mapped()) { view_ws = { (int)std::floor((vg.x + vg.width / 2.0) / og.width), (int)std::floor((vg.y + vg.height / 2.0) / og.height), }; view_workarea.x += og.width * view_ws.x; view_workarea.y += og.height * view_ws.y; } configure_geometry = translate_geometry_to_output( o, view_ws, configure_geometry); configure_geometry = wf::clamp(configure_geometry, view_workarea); } configure_geometry = wf::expand_geometry_by_margins(configure_geometry, toplevel->pending().margins); set_geometry(configure_geometry); } wf::signal::connection_t on_toplevel_applied = [&] (wf::xw::xwayland_toplevel_applied_state_signal *ev) { this->handle_toplevel_state_changed(ev->old_state); }; std::shared_ptr surface_root_node; // Reference count to this std::shared_ptr _self_ref; public: wayfire_xwayland_view(wlr_xwayland_surface *xww) : wayfire_xwayland_view_internal_base(xww) { this->toplevel = std::make_shared(xw); toplevel->connect(&on_toplevel_applied); this->priv->toplevel = toplevel; on_request_move.set_callback([&] (void*) { wf::get_core().default_wm->move_request({this}); }); on_request_resize.set_callback([&] (auto data) { auto ev = static_cast(data); wf::get_core().default_wm->resize_request({this}, ev->edges); }); on_request_activate.set_callback([&] (void*) { if (!this->activated && this->is_mapped()) { wf::get_core().default_wm->focus_request({this}, true); } }); on_request_maximize.set_callback([&] (void*) { wf::get_core().default_wm->tile_request({this}, (xw->maximized_horz && xw->maximized_vert) ? wf::TILED_EDGES_ALL : 0); }); on_request_fullscreen.set_callback([&] (void*) { wf::get_core().default_wm->fullscreen_request({this}, get_output(), xw->fullscreen); }); on_request_minimize.set_callback([&] (void *data) { auto ev = (wlr_xwayland_minimize_event*)data; wf::get_core().default_wm->minimize_request({this}, ev->minimize); }); on_set_parent.set_callback([&] (void*) { auto parent = xw->parent ? (wf::view_interface_t*)(xw->parent->data) : nullptr; if (parent && !parent->is_mapped()) { parent = nullptr; } if (wf::xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_NORMAL)) { parent = nullptr; } set_toplevel_parent(toplevel_cast(parent)); }); on_set_hints.set_callback([&] (void*) { wf::view_hints_changed_signal data; data.view = this; if (xw->hints && (xw->hints->flags & XCB_ICCCM_WM_HINT_X_URGENCY)) { data.demands_attention = true; } wf::get_core().emit(&data); this->emit(&data); }); on_set_decorations.set_callback([&] (void*) { update_decorated(); }); on_set_parent.connect(&xw->events.set_parent); on_set_hints.connect(&xw->events.set_hints); on_set_decorations.connect(&xw->events.set_decorations); on_request_move.connect(&xw->events.request_move); on_request_resize.connect(&xw->events.request_resize); on_request_activate.connect(&xw->events.request_activate); on_request_maximize.connect(&xw->events.request_maximize); on_request_minimize.connect(&xw->events.request_minimize); on_request_fullscreen.connect(&xw->events.request_fullscreen); } static std::shared_ptr create(wlr_xwayland_surface *surface) { auto self = wf::view_interface_t::create(surface); self->surface_root_node = std::make_shared(self); self->set_surface_root_node(self->surface_root_node); // Set the output early, so that we can emit the signals on the output self->set_output(wf::get_core().seat->get_active_output()); self->_initialize(); self->update_decorated(); // set initial parent self->on_set_parent.emit(nullptr); return self; } void set_activated(bool active) override { if (xw) { wlr_xwayland_surface_activate(xw, active); } wf::toplevel_view_interface_t::set_activated(active); } void handle_dissociate() override { on_surface_commit.disconnect(); } virtual void destroy() override { on_set_parent.disconnect(); on_set_hints.disconnect(); on_set_decorations.disconnect(); on_request_move.disconnect(); on_request_resize.disconnect(); on_request_activate.disconnect(); on_request_maximize.disconnect(); on_request_minimize.disconnect(); on_request_fullscreen.disconnect(); on_surface_commit.disconnect(); wayfire_xwayland_view_internal_base::destroy(); } void emit_view_map() override { /* Some X clients position themselves on map, and others let the window * manager determine this. We try to heuristically guess which of the * two cases we're dealing with by checking whether we have received * a valid ConfigureRequest before mapping */ bool client_self_positioned = self_positioned; wf::view_implementation::emit_view_map_signal(self(), client_self_positioned); } void store_xw_geometry_unmapped() { if ((xw->width > 0) && (xw->height > 0) && get_output()) { /* Save geometry which the window has put itself in */ wf::geometry_t save_geometry = { xw->x, xw->y, xw->width, xw->height }; /* Make sure geometry is properly visible on the view output */ save_geometry = save_geometry - wf::origin(get_output()->get_layout_geometry()); save_geometry = wf::clamp(save_geometry, get_output()->workarea->get_workarea()); wf::get_core().default_wm->update_last_windowed_geometry({this}, save_geometry); } } void handle_map_request(wlr_surface*) override { LOGC(VIEWS, "Start mapping ", self()); this->main_surface = std::make_shared(xw->surface, false); priv->set_mapped_surface_contents(main_surface); toplevel->set_main_surface(main_surface); toplevel->pending().mapped = true; bool map_maximized = xw->maximized_horz || xw->maximized_vert; bool map_fs = xw->fullscreen; if (map_maximized || map_fs) { store_xw_geometry_unmapped(); } wf::geometry_t desired_geometry = { xw->x, xw->y, xw->surface->current.width, xw->surface->current.height }; if (get_output()) { desired_geometry = desired_geometry + -wf::origin(get_output()->get_layout_geometry()); } wf::adjust_view_pending_geometry_on_start_map(this, desired_geometry, map_fs, map_maximized); wf::get_core().tx_manager->schedule_object(toplevel); } void handle_unmap_request() override { LOGC(VIEWS, "Start unmapping ", self()); emit_view_pre_unmap(); // Store a reference to this until the view is actually unmapped with a transaction. _self_ref = shared_from_this(); toplevel->set_main_surface(nullptr); toplevel->pending().mapped = false; wf::get_core().tx_manager->schedule_object(toplevel); on_surface_commit.disconnect(); } void map(wlr_surface *surface) { LOGC(VIEWS, "Do map ", self()); wf::adjust_view_output_on_map(this); do_map(surface, false); on_surface_commit.connect(&surface->events.commit); bool wants_focus = false; switch (wlr_xwayland_surface_icccm_input_model(xw)) { /* * Abbreviated from ICCCM section 4.1.7 (Input Focus): * * Passive Input - The client expects keyboard input but never * explicitly sets the input focus. * Locally Active Input - The client expects keyboard input and * explicitly sets the input focus, but it only does so when one * of its windows already has the focus. * * Passive and Locally Active clients set the input field of * WM_HINTS to True, which indicates that they require window * manager assistance in acquiring the input focus. */ case WLR_ICCCM_INPUT_MODEL_PASSIVE: case WLR_ICCCM_INPUT_MODEL_LOCAL: wants_focus = true; break; /* * Globally Active Input - The client expects keyboard input and * explicitly sets the input focus, even when it is in windows * the client does not own. ... It wants to prevent the window * manager from setting the input focus to any of its windows * [because it may or may not want focus]. * * Globally Active client windows may receive a WM_TAKE_FOCUS * message from the window manager. If they want the focus, they * should respond with a SetInputFocus request. */ case WLR_ICCCM_INPUT_MODEL_GLOBAL: /* * Assume that NORMAL and DIALOG windows are likely to * want focus. These window types should show up in the * Alt-Tab switcher and be automatically focused when * they become topmost. * * When we offer focus, we don't really focus the dialog. * Instead, by offering focus like that, we let the client * switch focus between its surfaces without involving the * compositor. */ if (wlr_xwayland_surface_has_window_type(xw, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_NORMAL) || wlr_xwayland_surface_has_window_type(xw, WLR_XWAYLAND_NET_WM_WINDOW_TYPE_DIALOG)) { wlr_xwayland_surface_offer_focus(xw); } break; /* * No Input - The client never expects keyboard input. * * No Input and Globally Active clients set the input field to * False, which requests that the window manager not set the * input focus to their top-level window. */ case WLR_ICCCM_INPUT_MODEL_NONE: break; } if (wants_focus) { wf::get_core().default_wm->focus_request(self()); } /* Might trigger repositioning */ set_toplevel_parent(this->parent); } void unmap() { LOGC(VIEWS, "Do unmap ", self()); do_unmap(); } void commit() { if (!xw->has_alpha) { pixman_region32_union_rect( &priv->wsurface->opaque_region, &priv->wsurface->opaque_region, 0, 0, priv->wsurface->current.width, priv->wsurface->current.height); } } virtual void request_native_size() override { toplevel->request_native_size(); } void set_minimized(bool minimized) override { wf::toplevel_view_interface_t::set_minimized(minimized); if (xw) { wlr_xwayland_surface_set_minimized(xw, minimized); } } void set_output(wf::output_t *wo) override { output_geometry_changed.disconnect(); wf::toplevel_view_interface_t::set_output(wo); if (wo) { wo->connect(&output_geometry_changed); toplevel->set_output_offset(wf::origin(wo->get_layout_geometry())); } else { toplevel->set_output_offset({0, 0}); } } void handle_toplevel_state_changed(wf::toplevel_state_t old_state) { surface_root_node->set_offset(wf::origin(toplevel->calculate_base_geometry())); if (xw && xw->surface && !old_state.mapped && toplevel->current().mapped) { map(xw->surface); } if (is_mapped() && !toplevel->current().mapped) { unmap(); } wf::view_implementation::emit_toplevel_state_change_signals({this}, old_state); wf::scene::update(this->get_surface_root_node(), wf::scene::update_flag::GEOMETRY); if (!wf::get_core().tx_manager->is_object_pending(toplevel)) { // Drop self-reference => object might be deleted afterwards _self_ref.reset(); } } wf::xw::view_type get_current_impl_type() const override { return wf::xw::view_type::NORMAL; } bool has_client_decoration = true; void set_decoration_mode(bool use_csd) { bool was_decorated = should_be_decorated(); this->has_client_decoration = use_csd; if (was_decorated != should_be_decorated()) { wf::view_decoration_state_updated_signal data; data.view = {this}; this->emit(&data); wf::get_core().emit(&data); } } bool should_be_decorated() override { return role == wf::VIEW_ROLE_TOPLEVEL && !has_client_decoration && (xw && !wf::xw::has_type(xw, wf::xw::_NET_WM_WINDOW_TYPE_SPLASH)); } }; #endif wayfire-0.10.0/src/view/xwayland/xwayland-toplevel-view.cpp0000664000175000017500000000000015053502647023642 0ustar dkondordkondorwayfire-0.10.0/src/view/xwayland/xwayland-toplevel.hpp0000664000175000017500000000300715053502647022711 0ustar dkondordkondor#pragma once #include "config.h" #include "wayfire/geometry.hpp" #include "wayfire/util.hpp" #include #include #include #include #if WF_HAS_XWAYLAND namespace wf { namespace xw { /** * A signal emitted on the xwayland_toplevel after the committed state is applied. */ struct xwayland_toplevel_applied_state_signal { toplevel_state_t old_state; }; class xwayland_toplevel_t : public wf::toplevel_t, public std::enable_shared_from_this { public: xwayland_toplevel_t(wlr_xwayland_surface *xw); void commit() override; void apply() override; wf::dimensions_t get_min_size() override; wf::dimensions_t get_max_size() override; void set_main_surface(std::shared_ptr main_surface); void set_output_offset(wf::point_t output_offset); wf::geometry_t calculate_base_geometry(); void request_native_size(); private: std::shared_ptr main_surface; scene::surface_state_t pending_state; void apply_pending_state(); wf::dimensions_t get_current_xw_size(); wf::wl_listener_wrapper on_surface_commit; wf::wl_listener_wrapper on_xw_destroy; wf::wl_idle_call idle_ready; wlr_xwayland_surface *xw; wf::point_t output_offset = {0, 0}; void handle_surface_commit(); void reconfigure_xwayland_surface(); void emit_ready(); bool pending_ready = false; }; } } #endif wayfire-0.10.0/src/view/xwayland/xwayland-view-base.cpp0000664000175000017500000000627515053502647022746 0ustar dkondordkondor#include "wayfire/unstable/xwl-toplevel-base.hpp" #include "wayfire/view-helpers.hpp" #include "../view-impl.hpp" #if WF_HAS_XWAYLAND wf::xwayland_view_base_t::xwayland_view_base_t(wlr_xwayland_surface *xww) { this->xw = xww; on_destroy.set_callback([=] (void*) { destroy(); }); on_set_title.set_callback([=] (void*) { handle_title_changed(nonull(xw->title)); }); on_set_app_id.set_callback([=] (void*) { handle_app_id_changed(nonull(xw->class_t)); }); on_ping_timeout.set_callback([=] (void*) { wf::view_implementation::emit_ping_timeout_signal(dynamic_cast(this)); }); this->title = nonull(xw->title); this->app_id = nonull(xw->class_t); on_destroy.connect(&xw->events.destroy); on_set_title.connect(&xw->events.set_title); on_set_app_id.connect(&xw->events.set_class); on_ping_timeout.connect(&xw->events.ping_timeout); xw->data = dynamic_cast(this); } wf::xwayland_view_base_t::~xwayland_view_base_t() { if (xw && (xw->data == dynamic_cast(this))) { xw->data = nullptr; } } void wf::xwayland_view_base_t::do_map(wlr_surface *surface, bool autocommit, bool emit_map) { if (!this->main_surface) { this->main_surface = std::make_shared(xw->surface, autocommit); priv->set_mapped_surface_contents(main_surface); } priv->set_mapped(xw->surface); priv->set_enabled(true); damage(); if (emit_map) { emit_view_map(); } } void wf::xwayland_view_base_t::do_unmap() { damage(); main_surface = nullptr; priv->unset_mapped_surface_contents(); priv->set_mapped(nullptr); emit_view_unmap(); priv->set_enabled(false); wf::scene::update(get_surface_root_node(), wf::scene::update_flag::INPUT_STATE); } void wf::xwayland_view_base_t::destroy() { this->xw = nullptr; on_destroy.disconnect(); on_set_title.disconnect(); on_set_app_id.disconnect(); on_ping_timeout.disconnect(); } void wf::xwayland_view_base_t::handle_app_id_changed(std::string new_app_id) { this->app_id = new_app_id; wf::view_implementation::emit_app_id_changed_signal( dynamic_cast(this)); } void wf::xwayland_view_base_t::handle_title_changed(std::string new_title) { this->title = new_title; wf::view_implementation::emit_title_changed_signal( dynamic_cast(this)); } std::string wf::xwayland_view_base_t::get_app_id() { return this->app_id; } std::string wf::xwayland_view_base_t::get_title() { return this->title; } void wf::xwayland_view_base_t::ping() { if (xw) { wlr_xwayland_surface_ping(xw); } } void wf::xwayland_view_base_t::close() { if (xw) { wlr_xwayland_surface_close(xw); } } bool wf::xwayland_view_base_t::is_mapped() const { return priv->is_mapped; } wlr_surface*wf::xwayland_view_base_t::get_keyboard_focus_surface() { if (is_mapped() && kb_focus_enabled) { return priv->wsurface; } return NULL; } bool wf::xwayland_view_base_t::is_focusable() const { return kb_focus_enabled; } #endif wayfire-0.10.0/src/view/xwayland/xwayland-helpers.cpp0000664000175000017500000000300215053502647022507 0ustar dkondordkondor#include "xwayland-helpers.hpp" #if WF_HAS_XWAYLAND std::optional wf::xw::load_atom(xcb_connection_t *connection, const std::string& name) { std::optional result; auto cookie = xcb_intern_atom(connection, 0, name.length(), name.c_str()); xcb_generic_error_t *error = NULL; xcb_intern_atom_reply_t *reply; reply = xcb_intern_atom_reply(connection, cookie, &error); if (!error && reply) { result = reply->atom; } free(reply); free(error); return result; } bool wf::xw::load_basic_atoms(const char *server_name) { auto connection = xcb_connect(server_name, NULL); if (!connection || xcb_connection_has_error(connection)) { return false; } _NET_WM_WINDOW_TYPE_NORMAL = load_atom(connection, "_NET_WM_WINDOW_TYPE_NORMAL").value_or(-1); _NET_WM_WINDOW_TYPE_DIALOG = load_atom(connection, "_NET_WM_WINDOW_TYPE_DIALOG").value_or(-1); _NET_WM_WINDOW_TYPE_SPLASH = load_atom(connection, "_NET_WM_WINDOW_TYPE_SPLASH").value_or(-1); _NET_WM_WINDOW_TYPE_UTILITY = load_atom(connection, "_NET_WM_WINDOW_TYPE_UTILITY").value_or(-1); _NET_WM_WINDOW_TYPE_DND = load_atom(connection, "_NET_WM_WINDOW_TYPE_DND").value_or(-1); xcb_disconnect(connection); return true; } bool wf::xw::has_type(wlr_xwayland_surface *xw, xcb_atom_t type) { for (size_t i = 0; i < xw->window_type_len; i++) { if (xw->window_type[i] == type) { return true; } } return false; } #endif wayfire-0.10.0/src/view/xwayland/xwayland-helpers.hpp0000664000175000017500000000127315053502647022524 0ustar dkondordkondor#pragma once #include "config.h" #include #include #if WF_HAS_XWAYLAND #include #include namespace wf { namespace xw { enum class view_type { NORMAL, UNMANAGED, DND, }; extern xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL; extern xcb_atom_t _NET_WM_WINDOW_TYPE_DIALOG; extern xcb_atom_t _NET_WM_WINDOW_TYPE_SPLASH; extern xcb_atom_t _NET_WM_WINDOW_TYPE_UTILITY; extern xcb_atom_t _NET_WM_WINDOW_TYPE_DND; std::optional load_atom(xcb_connection_t *connection, const std::string& name); bool load_basic_atoms(const char *server_name); bool has_type(wlr_xwayland_surface *xw, xcb_atom_t type); } } #endif wayfire-0.10.0/src/view/xwayland/xwayland-unmanaged-view.hpp0000664000175000017500000001716415053502647023777 0ustar dkondordkondor#pragma once #include "config.h" #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/unstable/translation-node.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include "xwayland-view-base.hpp" #include #include #include #include "../core/core-impl.hpp" #include "../core/seat/seat-impl.hpp" #include "wayfire/unstable/wlr-view-keyboard-interaction.hpp" #if WF_HAS_XWAYLAND namespace wf { class xwayland_unmanaged_view_node_t : public wf::scene::translation_node_t, public view_node_tag_t { public: xwayland_unmanaged_view_node_t(wayfire_view view) : view_node_tag_t(view) { _view = view->weak_from_this(); this->kb_interaction = std::make_unique(view); } wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override { auto view = _view.lock(); if (!view || !view->get_keyboard_focus_surface() || !view->is_mapped()) { return wf::keyboard_focus_node_t{}; } if (output != view->get_output()) { return wf::keyboard_focus_node_t{}; } const uint64_t last_ts = wf::get_core().seat->get_last_focus_timestamp(); const uint64_t our_ts = keyboard_interaction().last_focus_timestamp; auto cur_focus = wf::get_core_impl().seat->priv->keyboard_focus.get(); bool has_focus = (cur_focus == this) || (our_ts == last_ts); if (has_focus) { return wf::keyboard_focus_node_t{this, focus_importance::REGULAR}; } return wf::keyboard_focus_node_t{}; } keyboard_interaction_t& keyboard_interaction() override { return *kb_interaction; } std::string stringify() const override { if (auto view = _view.lock()) { std::ostringstream out; out << view->self(); return "unmanaged " + out.str() + " " + stringify_flags(); } else { return "inert unmanaged " + stringify_flags(); } } protected: std::weak_ptr _view; std::unique_ptr kb_interaction; }; } class wayfire_unmanaged_xwayland_view : public wayfire_xwayland_view_internal_base { protected: wf::wl_listener_wrapper on_set_geometry; /** * The bounding box of the view the last time it was rendered. * * This is used to damage the view when it is resized, because when a * transformer changes because the view is resized, we can't reliably * calculate the old view region to damage. */ wf::geometry_t last_bounding_box{0, 0, 0, 0}; void handle_client_configure(wlr_xwayland_surface_configure_event *ev) override { // We accept the client requests without any modification when it comes to unmanaged views. wlr_xwayland_surface_configure(xw, ev->x, ev->y, ev->width, ev->height); update_geometry_from_xsurface(); } void update_geometry_from_xsurface() { wf::region_t damage_region = last_bounding_box; // last bounding box damage_region |= get_bounding_box(); // in case resize happened since last move wf::scene::damage_node(get_root_node(), damage_region); wf::point_t new_position = {xw->x, xw->y}; // Move to the correct output, if the xsurface has changed geometry wf::pointf_t midpoint = {xw->x + xw->width / 2.0, xw->y + xw->height / 2.0}; wf::output_t *wo = wf::get_core().output_layout->get_output_coords_at(midpoint, midpoint); if (wo) { new_position = new_position - wf::origin(wo->get_layout_geometry()); } surface_root_node->set_offset(new_position); if (wo != get_output()) { LOGC(XWL, "Transferring xwayland unmanaged surface ", self(), " to output ", wo ? wo->to_string() : "null"); set_output(wo); if (wo && (get_current_impl_type() != wf::xw::view_type::DND)) { wf::scene::readd_front(wo->node_for_layer(wf::scene::layer::UNMANAGED), get_root_node()); } } LOGC(XWL, "Xwayland unmanaged surface ", self(), " new position ", new_position); last_bounding_box = get_bounding_box(); wf::scene::damage_node(get_root_node(), last_bounding_box); wf::scene::update(surface_root_node, wf::scene::update_flag::GEOMETRY); } std::shared_ptr surface_root_node; public: wayfire_unmanaged_xwayland_view(wlr_xwayland_surface *xww) : wayfire_xwayland_view_internal_base(xww) { LOGE("new unmanaged xwayland surface ", xw->title, " class: ", xw->class_t, " instance: ", xw->instance); role = wf::VIEW_ROLE_UNMANAGED; on_set_geometry.set_callback([&] (void*) { update_geometry_from_xsurface(); }); on_set_geometry.connect(&xw->events.set_geometry); _initialize(); } template static std::shared_ptr create(wlr_xwayland_surface *xw) { auto self = wf::view_interface_t::create(xw); self->surface_root_node = std::make_shared(self); self->set_surface_root_node(self->surface_root_node); self->_initialize(); return self; } void handle_map_request(wlr_surface *surface) override { LOGC(XWL, "Mapping unmanaged xwayland surface ", self()); update_geometry_from_xsurface(); do_map(surface, true, false); /* We update the keyboard focus before emitting the map event, so that * plugins can detect that this view can have keyboard focus. * * Note: only actual override-redirect views should get their focus disabled */ kb_focus_enabled = (!xw->override_redirect || wlr_xwayland_surface_override_redirect_wants_focus(xw)); wf::scene::readd_front(get_output()->node_for_layer(wf::scene::layer::UNMANAGED), get_root_node()); const bool wants_focus = (wlr_xwayland_surface_icccm_input_model(xw) != WLR_ICCCM_INPUT_MODEL_NONE); if (kb_focus_enabled && wants_focus) { wf::get_core().default_wm->focus_request(self()); } emit_view_map(); } void handle_unmap_request() override { LOGC(XWL, "Unmapping unmanaged xwayland surface ", self()); emit_view_pre_unmap(); do_unmap(); } void destroy() override { on_set_geometry.disconnect(); wayfire_xwayland_view_internal_base::destroy(); } wf::xw::view_type get_current_impl_type() const override { return wf::xw::view_type::UNMANAGED; } }; class wayfire_dnd_xwayland_view : public wayfire_unmanaged_xwayland_view { public: using wayfire_unmanaged_xwayland_view::wayfire_unmanaged_xwayland_view; wf::xw::view_type get_current_impl_type() const override { return wf::xw::view_type::DND; } ~wayfire_dnd_xwayland_view() { LOGD("Destroying a Xwayland drag icon"); } void handle_map_request(wlr_surface *surface) override { LOGC(XWL, "Mapping a Xwayland drag icon"); wayfire_unmanaged_xwayland_view::handle_map_request(surface); wf::scene::readd_front(wf::get_core().scene(), this->get_root_node()); } void handle_unmap_request() override { LOGC(XWL, "Mapping a Xwayland drag icon"); wayfire_unmanaged_xwayland_view::handle_unmap_request(); wf::scene::remove_child(this->get_root_node()); } }; #endif wayfire-0.10.0/src/view/xwayland/xwayland-toplevel.cpp0000664000175000017500000002306615053502647022713 0ustar dkondordkondor#include "xwayland-toplevel.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include #include "../view-impl.hpp" #include "wayfire/toplevel.hpp" #if WF_HAS_XWAYLAND wf::xw::xwayland_toplevel_t::xwayland_toplevel_t(wlr_xwayland_surface *xw) { this->xw = xw; on_surface_commit.set_callback([&] (void*) { handle_surface_commit(); }); on_xw_destroy.set_callback([&] (void*) { this->xw = NULL; on_xw_destroy.disconnect(); on_surface_commit.disconnect(); // Emit the ready signal on the next idle, to give all substructures time to properly deinitialize. idle_ready.run_once([&] () { emit_ready(); }); }); on_xw_destroy.connect(&xw->events.destroy); } void wf::xw::xwayland_toplevel_t::set_main_surface( std::shared_ptr main_surface) { this->main_surface = main_surface; on_surface_commit.disconnect(); if (main_surface) { if (main_surface->get_surface()) { on_surface_commit.connect(&main_surface->get_surface()->events.commit); } else { LOGW("Setting xwayland toplevel's main surface to a surface without wlr_surface!"); return; } auto size = expand_dimensions_by_margins(get_current_xw_size(), _current.margins); _pending.geometry.width = size.width; _current.geometry.width = size.width; _committed.geometry.width = size.width; _pending.geometry.height = size.height; _current.geometry.height = size.height; _committed.geometry.height = size.height; } } void wf::xw::xwayland_toplevel_t::set_output_offset(wf::point_t output_offset) { this->output_offset = output_offset; if (pending().mapped) { // We want to reconfigure xwayland surfaces with output changes only if they are mapped. // Otherwise, there is no need to generate x11 events, not to mention that perhaps we do not know the // position of the view yet (e.g. if it had never been mapped so far). reconfigure_xwayland_surface(); } } void wf::xw::xwayland_toplevel_t::request_native_size() { if (!xw || !xw->size_hints) { return; } if ((xw->size_hints->base_width > 0) && (xw->size_hints->base_height > 0)) { this->pending().geometry.width = xw->size_hints->base_width; this->pending().geometry.height = xw->size_hints->base_height; wf::get_core().tx_manager->schedule_object(this->shared_from_this()); } } void wf::xw::xwayland_toplevel_t::commit() { this->pending_ready = true; _committed = _pending; LOGC(TXNI, this, ": committing xwayland state mapped=", _pending.mapped, " geometry=", _pending.geometry, " tiled=", _pending.tiled_edges, " fs=", _pending.fullscreen, " margins=", _pending.margins.left, ",", _pending.margins.right, ",", _pending.margins.top, ",", _pending.margins.bottom); if (!this->xw) { // No longer mapped => we can do whatever emit_ready(); return; } wf::dimensions_t current_size = shrink_dimensions_by_margins(wf::dimensions(_current.geometry), _current.margins); if (_pending.mapped && !_current.mapped) { // We are trying to map the toplevel => check whether we should wait until it sets the proper // geometry, or whether we are 'only' mapping without resizing. current_size = get_current_xw_size(); } const wf::dimensions_t desired_size = wf::shrink_dimensions_by_margins( wf::dimensions(_pending.geometry), _pending.margins); bool wait_for_client = false; if (desired_size != current_size) { wait_for_client = true; reconfigure_xwayland_surface(); } if (_pending.tiled_edges != _current.tiled_edges) { wait_for_client = true; wlr_xwayland_surface_set_maximized(xw, !!_pending.tiled_edges, !!_pending.tiled_edges); } if (_pending.fullscreen != _current.fullscreen) { wait_for_client = true; wlr_xwayland_surface_set_fullscreen(xw, _pending.fullscreen); } if (wait_for_client && main_surface) { // Send frame done to let the client know it can resize main_surface->send_frame_done(true); } else { emit_ready(); } } void wf::xw::xwayland_toplevel_t::reconfigure_xwayland_surface() { if (!xw) { return; } const wf::geometry_t configure = shrink_geometry_by_margins(_pending.geometry, _pending.margins) + output_offset; if ((configure.width <= 0) || (configure.height <= 0)) { /* such a configure request would freeze xwayland. * This is most probably a bug somewhere in the compositor. */ LOGE("Configuring a xwayland surface with width/height <0"); return; } LOGC(XWL, "Configuring xwayland surface ", nonull(xw->title), " ", nonull(xw->class_t), " ", configure); wlr_xwayland_surface_configure(xw, configure.x, configure.y, configure.width, configure.height); } void wf::xw::xwayland_toplevel_t::apply() { xwayland_toplevel_applied_state_signal event_applied; event_applied.old_state = current(); // Damage the main surface before applying the new state. This ensures that the old position of the view // is damaged. if (main_surface && main_surface->parent()) { wf::scene::damage_node(main_surface->parent(), main_surface->parent()->get_bounding_box()); } if (!xw) { // If toplevel does no longer exist, we can't change the size anymore. _committed.geometry.width = _current.geometry.width; _committed.geometry.height = _current.geometry.height; if (_current.mapped == false) { // Avoid mapping if the view was already destroyed. _committed.mapped = false; } } if (main_surface && main_surface->get_surface()) { wf::adjust_geometry_for_gravity(_committed, expand_dimensions_by_margins(this->get_current_xw_size(), _committed.margins)); } this->_current = committed(); const bool is_pending = wf::get_core().tx_manager->is_object_pending(shared_from_this()); if (!is_pending) { // Adjust for potential moves due to gravity _pending = committed(); reconfigure_xwayland_surface(); } apply_pending_state(); emit(&event_applied); // Damage the new position. if (main_surface && main_surface->parent()) { wf::scene::damage_node(main_surface->parent(), main_surface->parent()->get_bounding_box()); } } void wf::xw::xwayland_toplevel_t::handle_surface_commit() { pending_state.merge_state(main_surface->get_surface()); const bool is_committed = wf::get_core().tx_manager->is_object_committed(shared_from_this()); if (is_committed) { const wf::dimensions_t desired_size = shrink_dimensions_by_margins(wf::dimensions(_committed.geometry), _committed.margins); if (get_current_xw_size() != desired_size) { // Desired state not reached => wait for the desired state to be reached. In the meantime, send a // frame done so that the client can redraw faster. main_surface->send_frame_done(true); return; } adjust_geometry_for_gravity(_committed, this->get_current_xw_size()); emit_ready(); return; } const bool is_pending = wf::get_core().tx_manager->is_object_pending(shared_from_this()); if (is_pending) { return; } auto toplevel_size = expand_dimensions_by_margins(get_current_xw_size(), current().margins); if (toplevel_size == wf::dimensions(current().geometry)) { // Size did not change, there are no transactions going on - apply the new texture directly apply_pending_state(); return; } adjust_geometry_for_gravity(_pending, toplevel_size); LOGC(VIEWS, "Client-initiated resize to geometry ", pending().geometry); auto tx = wf::txn::transaction_t::create(); tx->add_object(shared_from_this()); wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } wf::geometry_t wf::xw::xwayland_toplevel_t::calculate_base_geometry() { auto geometry = current().geometry; return shrink_geometry_by_margins(geometry, _current.margins); } void wf::xw::xwayland_toplevel_t::apply_pending_state() { if (xw && xw->surface) { pending_state.merge_state(xw->surface); } if (main_surface) { main_surface->apply_state(std::move(pending_state)); } } void wf::xw::xwayland_toplevel_t::emit_ready() { if (pending_ready) { pending_ready = false; emit_object_ready(this); } } wf::dimensions_t wf::xw::xwayland_toplevel_t::get_current_xw_size() { if (!main_surface || !main_surface->get_surface()) { return {0, 0}; } auto surf = main_surface->get_surface(); wf::dimensions_t size = wf::dimensions_t{surf->current.width, surf->current.height}; return size; } wf::dimensions_t wf::xw::xwayland_toplevel_t::get_min_size() { if (xw && xw->size_hints) { return wf::dimensions_t{ std::max(0, xw->size_hints->min_width), std::max(0, xw->size_hints->min_height) }; } return {0, 0}; } wf::dimensions_t wf::xw::xwayland_toplevel_t::get_max_size() { if (xw && xw->size_hints) { return wf::dimensions_t{ std::max(0, xw->size_hints->max_width), std::max(0, xw->size_hints->max_height) }; } return {0, 0}; } #endif wayfire-0.10.0/src/view/view-3d.cpp0000664000175000017500000003771715053502647016673 0ustar dkondordkondor#include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view-transform.hpp" #include "wayfire/opengl.hpp" #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include #include #include #include #include #include #include static const double PI = std::acos(-1); namespace wf { wf::geometry_t get_bbox_for_node(scene::node_t *node, wf::geometry_t box) { const auto p1 = node->to_global(wf::pointf_t(box.x, box.y)); const auto p2 = node->to_global(wf::pointf_t(box.x + box.width, box.y)); const auto p3 = node->to_global(wf::pointf_t(box.x, box.y + box.height)); const auto p4 = node->to_global( wf::pointf_t(box.x + box.width, box.y + box.height)); const int x1 = std::floor(std::min({p1.x, p2.x, p3.x, p4.x})); const int x2 = std::ceil(std::max({p1.x, p2.x, p3.x, p4.x})); const int y1 = std::floor(std::min({p1.y, p2.y, p3.y, p4.y})); const int y2 = std::ceil(std::max({p1.y, p2.y, p3.y, p4.y})); return wlr_box{x1, y1, x2 - x1, y2 - y1}; } wf::geometry_t get_bbox_for_node(scene::node_ptr node, wf::geometry_t box) { return get_bbox_for_node(node.get(), box); } namespace scene { void transform_manager_node_t::_add_transformer( wf::scene::floating_inner_ptr transformer, int z_order, std::string name) { wf::scene::damage_node(shared_from_this(), get_bounding_box()); size_t pos = 0; while (pos < transformers.size() && transformers[pos].z_order < z_order) { ++pos; } auto _parent = (pos == transformers.size() ? this->shared_from_this() : transformers[pos].node); auto parent = std::dynamic_pointer_cast(_parent); transformers.insert(transformers.begin() + pos, added_transformer_t{ .node = transformer, .z_order = z_order, .name = name, }); auto children = parent->get_children(); parent->set_children_list({transformer}); transformer->set_children_list(children); wf::scene::update(transformer, update_flag::CHILDREN_LIST); wf::scene::damage_node(shared_from_this(), get_bounding_box()); } void transform_manager_node_t::_rem_transformer( wf::scene::floating_inner_ptr node) { if (!node) { return; } wf::scene::damage_node(shared_from_this(), get_bounding_box()); auto children = node->get_children(); auto parent = dynamic_cast(node->parent()); wf::dassert(parent != nullptr, "transformer is missing a parent?"); node->set_children_list({}); parent->set_children_list(children); const auto& find_node = [&] (auto transformer) { return transformer.node == node; }; auto it = std::remove_if(transformers.begin(), transformers.end(), find_node); this->transformers.erase(it, transformers.end()); wf::scene::update(parent->shared_from_this(), update_flag::CHILDREN_LIST); wf::scene::damage_node(shared_from_this(), get_bounding_box()); } view_2d_transformer_t::view_2d_transformer_t(wayfire_view view) : transformer_base_node_t(false) { this->view = view->weak_from_this(); } static wf::pointf_t get_center(wf::geometry_t view) { return { view.x + view.width / 2.0, view.y + view.height / 2.0, }; } static wf::pointf_t get_center(std::weak_ptr _view) { if (auto view = _view.lock()) { if (auto toplevel = toplevel_cast(view)) { return get_center(toplevel->get_geometry()); } else { return get_center(view->get_surface_root_node()->get_bounding_box()); } } else { return {0, 0}; } } static void rotate_xy(double& x, double& y, double angle) { const auto cs = std::cos(angle); const auto sn = std::sin(angle); std::tie(x, y) = std::make_tuple(cs * x - sn * y, sn * x + cs * y); } wf::pointf_t view_2d_transformer_t::to_local(const wf::pointf_t& point) { auto midpoint = get_center(view); auto result = point - midpoint; result.x -= get_translation_x(); result.y -= get_translation_y(); rotate_xy(result.x, result.y, get_angle()); result.x /= get_scale_x(); result.y /= get_scale_y(); result += midpoint; return result; } wf::pointf_t view_2d_transformer_t::to_global(const wf::pointf_t& point) { auto midpoint = get_center(view); auto result = point - midpoint; result.x *= get_scale_x(); result.y *= get_scale_y(); rotate_xy(result.x, result.y, -get_angle()); result.x += get_translation_x(); result.y += get_translation_y(); return result + midpoint; } std::string view_2d_transformer_t::stringify() const { if (auto _view = view.lock()) { return "view-2d for " + _view->to_string(); } else { return "view-2d for dead view"; } } wf::geometry_t view_2d_transformer_t::get_bounding_box() { return get_bbox_for_node(this, get_children_bounding_box()); } static void transform_linear_damage(node_t *self, wf::region_t& damage) { auto copy = damage; damage.clear(); for (auto& box : copy) { damage |= get_bbox_for_node(self, wlr_box_from_pixman_box(box)); } } class view_2d_render_instance_t : public transformer_render_instance_t { public: using transformer_render_instance_t::transformer_render_instance_t; void transform_damage_region(wf::region_t& damage) override { transform_linear_damage(self.get(), damage); } void render(const wf::scene::render_instruction_t& data) override { if (std::abs(self->get_angle()) < 1e-3) { // No rotation, we can use render-agnostic functions. auto tex = this->get_texture(data.target.scale); tex.filter_mode = WLR_SCALE_FILTER_BILINEAR; auto bbox = self->get_bounding_box(); data.pass->add_texture(tex, data.target, bbox, data.damage, self->get_alpha()); return; } // Untransformed bounding box auto bbox = self->get_children_bounding_box(); auto midpoint = get_center(self->view); auto center_at = glm::translate(glm::mat4(1.0), {-midpoint.x, -midpoint.y, 0.0}); auto scale = glm::scale(glm::mat4(1.0), glm::vec3{self->get_scale_x(), self->get_scale_y(), 1.0}); auto rotate = glm::rotate(glm::mat4(1.0), -self->get_angle(), glm::vec3{0.0, 0.0, 1.0}); auto translate = glm::translate(glm::mat4(1.0), glm::vec3{self->get_translation_x() + midpoint.x, self->get_translation_y() + midpoint.y, 0.0}); auto ortho = wf::gles::render_target_orthographic_projection(data.target); auto full_matrix = ortho * translate * rotate * scale * center_at; data.pass->custom_gles_subpass([&] { auto tex = wf::gles_texture_t{this->get_texture(data.target.scale)}; wf::gles::bind_render_buffer(data.target); for (auto& box : data.damage) { wf::gles::render_target_logic_scissor(data.target, wlr_box_from_pixman_box(box)); // OpenGL::clear({1, 0, 0, 1}); OpenGL::render_transformed_texture(tex, bbox, full_matrix, glm::vec4{1.0, 1.0, 1.0, self->get_alpha()}); } }); } }; void view_2d_transformer_t::gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) { auto uptr = std::make_unique(this, push_damage, shown_on); if (uptr->has_instances()) { instances.push_back(std::move(uptr)); } } /* -------------------------------- 3d view --------------------------------- */ const float view_3d_transformer_t::fov = PI / 4; glm::mat4 view_3d_transformer_t::default_view_matrix() { return glm::lookAt( glm::vec3(0., 0., 1.0 / std::tan(fov / 2)), glm::vec3(0., 0., 0.), glm::vec3(0., 1., 0.)); } glm::mat4 view_3d_transformer_t::default_proj_matrix() { return glm::perspective(fov, 1.0f, .1f, 100.f); } view_3d_transformer_t::view_3d_transformer_t(wayfire_view view) : scene::transformer_base_node_t(false) { this->view = view->weak_from_this(); view_proj = default_proj_matrix() * default_view_matrix(); } static wf::pointf_t get_center_relative_coords(wf::geometry_t view, wf::pointf_t point) { return { (point.x - view.x) - view.width / 2.0, view.height / 2.0 - (point.y - view.y) }; } static wf::pointf_t get_absolute_coords_from_relative(wf::geometry_t view, wf::pointf_t point) { return { point.x + view.x + view.width / 2.0, (view.height / 2.0 - point.y) + view.y }; } /* TODO: cache total_transform, because it is often unnecessarily recomputed */ glm::mat4 view_3d_transformer_t::calculate_total_transform() { auto bbox = get_children_bounding_box(); float scale = std::max(bbox.width, bbox.height); scale = std::max(scale, 1.0f); glm::mat4 depth_scale = glm::scale(glm::mat4(1.0), {1, 1, 2.0 / scale}); return translation * view_proj * depth_scale * rotation * scaling; } wf::pointf_t view_3d_transformer_t::to_local(const wf::pointf_t& point) { auto wm_geom = get_children_bounding_box(); auto p = get_center_relative_coords(wm_geom, point); auto tr = calculate_total_transform(); /* Since we know that our original z coordinates were zero, we can write a * system of linear equations for the original (x,y) coordinates by writing * out the (x,y,w) components of the transformed coordinate. * * This results in the following matrix equation: * A x = b, where A and b are defined below and x is the vector * of untransformed coordinates that we want to compute. */ glm::dmat2 A{p.x * tr[0][3] - tr[0][0], p.y * tr[0][3] - tr[0][1], p.x * tr[1][3] - tr[1][0], p.y * tr[1][3] - tr[1][1]}; if (std::abs(glm::determinant(A)) < 1e-6) { /* This will happen if the transformed view is in rotated in a plane * perpendicular to the screen (i.e. it is displayed as a thin line). * We might want to add special casing for this so that the view can * still be "selected" in this case. */ return {wf::compositor_core_t::invalid_coordinate, wf::compositor_core_t::invalid_coordinate}; } glm::dvec2 b{tr[3][0] - p.x * tr[3][3], tr[3][1] - p.y * tr[3][3]}; /* TODO: use a better solution formula instead of explicitly calculating the * inverse to have better numerical stability. For a 2x2 matrix, the * difference will be small though. */ glm::dvec2 res = glm::inverse(A) * b; return get_absolute_coords_from_relative(wm_geom, {res.x, res.y}); } wf::pointf_t view_3d_transformer_t::to_global(const wf::pointf_t& point) { auto wm_geom = get_children_bounding_box(); auto p = get_center_relative_coords(wm_geom, point); glm::vec4 v(1.0f * p.x, 1.0f * p.y, 0, 1); v = calculate_total_transform() * v; if (std::abs(v.w) < 1e-6) { /* This should never happen as long as we use well-behaving matrices. * However if we set transform to the zero matrix we might get * this case where v.w is zero. In this case we assume the view is * just a single point at 0,0 */ v.x = v.y = 0; } else { v.x /= v.w; v.y /= v.w; } return get_absolute_coords_from_relative(wm_geom, {v.x, v.y}); } std::string view_3d_transformer_t::stringify() const { if (auto _view = view.lock()) { return "view-3d for " + _view->to_string(); } else { return "view-3d for dead view"; } } wf::geometry_t view_3d_transformer_t::get_bounding_box() { return get_bbox_for_node(this, get_children_bounding_box()); } struct transformable_quad { gl_geometry geometry; float off_x, off_y; }; static transformable_quad center_geometry(wf::geometry_t output_geometry, wf::geometry_t geometry, wf::pointf_t target_center) { transformable_quad quad; geometry.x -= output_geometry.x; geometry.y -= output_geometry.y; target_center.x -= output_geometry.x; target_center.y -= output_geometry.y; quad.geometry.x1 = -(target_center.x - geometry.x); quad.geometry.y1 = (target_center.y - geometry.y); quad.geometry.x2 = quad.geometry.x1 + geometry.width; quad.geometry.y2 = quad.geometry.y1 - geometry.height; quad.off_x = (geometry.x - output_geometry.width / 2.0) - quad.geometry.x1; quad.off_y = (output_geometry.height / 2.0 - geometry.y) - quad.geometry.y1; return quad; } class view_3d_render_instance_t : public transformer_render_instance_t { public: using transformer_render_instance_t::transformer_render_instance_t; void transform_damage_region(wf::region_t& damage) override { transform_linear_damage(self.get(), damage); } void render(const wf::scene::render_instruction_t& data) override { auto bbox = self->get_children_bounding_box(); auto quad = center_geometry(data.target.geometry, bbox, scene::get_center(bbox)); auto transform = self->calculate_total_transform(); auto translate = glm::translate(glm::mat4(1.0), {quad.off_x, quad.off_y, 0}); auto scale = glm::scale(glm::mat4(1.0), { 2.0 / data.target.geometry.width, 2.0 / data.target.geometry.height, 1.0 }); transform = wf::gles::render_target_gl_to_framebuffer(data.target) * scale * translate * transform; data.pass->custom_gles_subpass([&] { auto tex = wf::gles_texture_t{get_texture(data.target.scale)}; wf::gles::bind_render_buffer(data.target); for (auto& box : data.damage) { wf::gles::render_target_logic_scissor(data.target, wlr_box_from_pixman_box(box)); OpenGL::render_transformed_texture(tex, quad.geometry, {}, transform, self->color); } }); } }; void view_3d_transformer_t::gen_render_instances( std::vector& instances, damage_callback push_damage, wf::output_t *shown_on) { auto uptr = std::make_unique(this, push_damage, shown_on); if (uptr->has_instances()) { instances.push_back(std::move(uptr)); } } void transform_manager_node_t::begin_transform_update() { wf::scene::damage_node(this, get_bounding_box()); } void transform_manager_node_t::end_transform_update() { wf::scene::damage_node(this, get_bounding_box()); wf::scene::update(shared_from_this(), wf::scene::update_flag::GEOMETRY); } uint32_t transformer_base_node_t::optimize_update(uint32_t flags) { return optimize_nested_render_instances(shared_from_this(), flags); } wf::texture_t transformer_base_node_t::get_updated_contents(const wf::geometry_t& bbox, float scale, std::vector& children) { if (inner_content.allocate(wf::dimensions(bbox), scale) != buffer_reallocation_result_t::SAME) { cached_damage |= bbox; } wf::render_target_t target{inner_content}; target.scale = scale; target.geometry = bbox; render_pass_params_t params; params.instances = &children; params.target = target; params.damage = cached_damage; params.background_color = {0.0f, 0.0f, 0.0f, 0.0f}; params.flags = RPASS_CLEAR_BACKGROUND; wf::render_pass_t::run(params); cached_damage.clear(); return wf::texture_t{inner_content.get_texture(), {}}; } void transformer_base_node_t::release_buffers() { inner_content.free(); } transformer_base_node_t::~transformer_base_node_t() { release_buffers(); } } // namespace scene } wayfire-0.10.0/src/view/toplevel-node.hpp0000664000175000017500000000207215053502647020161 0ustar dkondordkondor#pragma once #include "wayfire/toplevel-view.hpp" #include #include #include #include namespace wf { /** * A surface root node for toplevel views. */ class toplevel_view_node_t : public wf::scene::translation_node_t, public scene::zero_copy_texturable_node_t, public scene::opaque_region_node_t, public view_node_tag_t { public: toplevel_view_node_t(wayfire_toplevel_view view); wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override; keyboard_interaction_t& keyboard_interaction() override; std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) override; std::optional to_texture() const override; wf::region_t get_opaque_region() const override; protected: std::weak_ptr _view; std::unique_ptr kb_interaction; }; } wayfire-0.10.0/src/view/wlr-surface-pointer-interaction.hpp0000664000175000017500000002050315053502647023630 0ustar dkondordkondor#include #include #include "../core/core-impl.hpp" #include "core/seat/seat-impl.hpp" #include "view-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" namespace wf { class wlr_surface_pointer_interaction_t final : public wf::pointer_interaction_t { wlr_surface *surface; wlr_pointer_constraint_v1 *last_constraint = NULL; wf::wl_listener_wrapper constraint_destroyed; scene::node_t *self; // From position relative to current focus to global scene coordinates wf::pointf_t get_absolute_position_from_relative(wf::pointf_t relative) { auto node = self; while (node) { relative = node->to_global(relative); node = node->parent(); } return relative; } inline static double distance_between_points(const wf::pointf_t& a, const wf::pointf_t& b) { return std::hypot(a.x - b.x, a.y - b.y); } inline static wf::pointf_t region_closest_point(const wf::region_t& region, const wf::pointf_t& ref) { if (region.empty() || region.contains_pointf(ref)) { return ref; } auto extents = region.get_extents(); wf::pointf_t result = {1.0 * extents.x1, 1.0 * extents.y1}; for (const auto& box : region) { auto wlr_box = wlr_box_from_pixman_box(box); double x, y; wlr_box_closest_point(&wlr_box, ref.x, ref.y, &x, &y); wf::pointf_t closest = {x, y}; if (distance_between_points(ref, result) > distance_between_points(ref, closest)) { result = closest; } } return result; } wf::pointf_t constrain_point(wf::pointf_t point) { point = get_node_local_coords(self, point); auto closest = region_closest_point({&this->last_constraint->region}, point); closest = get_absolute_position_from_relative(closest); return closest; } // A handler for pointer motion events before they are passed to the scenegraph. // Necessary for the implementation of pointer-constraints and relative-pointer. wf::signal::connection_t> on_pointer_motion = [=] (wf::input_event_signal *evv) { auto ev = evv->event; auto& seat = wf::get_core_impl().seat; // First, we send relative pointer motion as in the raw event, so that // clients get the correct delta independently of the pointer constraint. wlr_relative_pointer_manager_v1_send_relative_motion( wf::get_core().protocols.relative_pointer, seat->seat, (uint64_t)ev->time_msec * 1000, ev->delta_x, ev->delta_y, ev->unaccel_dx, ev->unaccel_dy); double dx = ev->delta_x; double dy = ev->delta_y; if (last_constraint) { wf::pointf_t gc = wf::get_core().get_cursor_position(); wf::pointf_t target = gc; switch (last_constraint->type) { case WLR_POINTER_CONSTRAINT_V1_CONFINED: target = constrain_point({gc.x + dx, gc.y + dy}); break; case WLR_POINTER_CONSTRAINT_V1_LOCKED: break; } ev->delta_x = target.x - gc.x; ev->delta_y = target.y - gc.y; } }; void _check_activate_constraint() { auto& seat = wf::get_core_impl().seat; auto constraint = wlr_pointer_constraints_v1_constraint_for_surface( wf::get_core().protocols.pointer_constraints, surface, seat->seat); if (constraint == last_constraint) { return; } _reset_constraint(); if (!constraint) { return; } constraint_destroyed.set_callback([=] (void*) { last_constraint = NULL; constraint_destroyed.disconnect(); }); constraint_destroyed.connect(&constraint->events.destroy); wlr_pointer_constraint_v1_send_activated(constraint); last_constraint = constraint; } wf::signal::connection_t on_recheck_constraints = [=] (node_recheck_constraints_signal *ev) { _check_activate_constraint(); }; void _reset_constraint() { if (!this->last_constraint) { return; } constraint_destroyed.disconnect(); wlr_pointer_constraint_v1_send_deactivated(last_constraint); last_constraint = NULL; } public: wlr_surface_pointer_interaction_t(wlr_surface *surface, wf::scene::node_t *self) { this->surface = surface; this->self = self; self->connect(&on_recheck_constraints); } void handle_pointer_button(const wlr_pointer_button_event& event) final { auto& seat = wf::get_core_impl().seat; bool drag_was_active = seat->priv->drag_active; seat->priv->last_press_release_serial = wlr_seat_pointer_notify_button(seat->seat, event.time_msec, event.button, event.state); if (drag_was_active != seat->priv->drag_active) { // Drag and drop ended. We should refocus the current surface, if we // still have focus, because we have set the wlroots focus in a // different place during DnD. auto& core = wf::get_core(); auto node = core.scene()->find_node_at(core.get_cursor_position()); if (node && (node->node.get() == self)) { wlr_seat_pointer_notify_enter(seat->seat, surface, node->local_coords.x, node->local_coords.y); } } } void handle_pointer_enter(wf::pointf_t local) final { auto seat = wf::get_core_impl().get_current_seat(); wlr_seat_pointer_notify_enter(seat, surface, local.x, local.y); _check_activate_constraint(); wf::xwayland_bring_to_front(surface); wf::get_core().connect(&on_pointer_motion); } void handle_pointer_motion(wf::pointf_t local, uint32_t time_ms) final { auto& seat = wf::get_core_impl().seat; if (seat->priv->drag_active) { // Special mode: when drag-and-drop is active, we get an implicit // grab on the originating node. So, the original node receives all // possible events. It then needs to make sure that the correct node // receives the event. handle_motion_dnd(time_ms); return; } wlr_seat_pointer_notify_motion(seat->seat, time_ms, local.x, local.y); } void handle_pointer_axis(const wlr_pointer_axis_event& ev) final { auto seat = wf::get_core_impl().get_current_seat(); wlr_seat_pointer_notify_axis(seat, ev.time_msec, ev.orientation, ev.delta, ev.delta_discrete, ev.source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); } void handle_pointer_leave() final { auto seat = wf::get_core_impl().get_current_seat(); if (seat->pointer_state.focused_surface == surface) { // We defocus only if our surface is still focused on the seat. wlr_seat_pointer_notify_clear_focus(seat); } _reset_constraint(); on_pointer_motion.disconnect(); } // ---------------------------- DnD implementation ---------------------- */ void handle_motion_dnd(uint32_t time_ms) { _reset_constraint(); auto seat = wf::get_core().get_current_seat(); auto gc = wf::get_core().get_cursor_position(); auto node = wf::get_core().scene()->find_node_at(gc); auto snode = node ? dynamic_cast(node->node.get()) : nullptr; if (snode && snode->get_surface()) { wlr_seat_pointer_notify_enter(seat, snode->get_surface(), node->local_coords.x, node->local_coords.y); wlr_seat_pointer_notify_motion(seat, time_ms, node->local_coords.x, node->local_coords.y); } } }; } wayfire-0.10.0/src/view/xdg-shell.cpp0000664000175000017500000003256315053502647017276 0ustar dkondordkondor#include #include #include #include "view/view-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/seat.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/unstable/wlr-view-keyboard-interaction.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view.hpp" #include "xdg-shell.hpp" #include "wayfire/output-layout.hpp" #include #include #include #include #include #include "xdg-shell/xdg-toplevel-view.hpp" #include class wayfire_xdg_popup_node : public wf::scene::translation_node_t { public: wayfire_xdg_popup_node(std::shared_ptr popup) : id(popup->get_id()) { this->_popup = popup; this->kb_interaction = std::make_unique(popup, true); } std::string stringify() const override { return "xdg-popup view id=" + std::to_string(id) + " " + stringify_flags(); } wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override { auto popup = _popup.lock(); if (!popup) { return {}; } if (!popup->is_mapped() || !popup->popup->seat || !popup->get_keyboard_focus_surface() || (output != popup->get_output())) { return {}; } return wf::keyboard_focus_node_t{ .node = this, .importance = wf::focus_importance::REGULAR, .allow_focus_below = false, }; } wf::keyboard_interaction_t& keyboard_interaction() override { return *kb_interaction; } private: uint64_t id = 0; std::weak_ptr _popup; std::unique_ptr kb_interaction; }; bool wayfire_xdg_popup::should_close_on_focus_change(wf::keyboard_focus_changed_signal *ev) { if (!is_mapped()) { return false; } auto view = wf::node_to_view(ev->new_focus); const bool focus_client_changes = view && (view->get_client() != this->get_client()); const bool has_grab = this->popup->seat != nullptr; if (has_grab) { return !view || focus_client_changes; } else { if ((ev->reason == wf::keyboard_focus_reason::UNKNOWN) || (ev->reason == wf::keyboard_focus_reason::REFOCUS)) { return false; } if ((ev->new_focus && !view) || focus_client_changes) { return true; } } return false; } wayfire_xdg_popup::wayfire_xdg_popup(wlr_xdg_popup *popup) : wf::view_interface_t() { auto parent_ptr = wf::wl_surface_to_wayfire_view(popup->parent->resource); wf::dassert(parent_ptr.get(), "Popup has no existing parent?"); this->popup_parent = parent_ptr->weak_from_this(); this->popup = popup; this->role = wf::VIEW_ROLE_UNMANAGED; if (!dynamic_cast(parent_ptr.get())) { // 'toplevel' popups are responsible for closing their popup tree when the parent loses focus. // Note: we shouldn't close nested popups manually, since the parent popups will destroy them as well. this->on_keyboard_focus_changed = [=] (wf::keyboard_focus_changed_signal *ev) { if (should_close_on_focus_change(ev)) { this->close(); } }; } on_surface_commit.set_callback([&] (void*) { commit(); }); // We'll receive the initial commit soon on_surface_commit.connect(&popup->base->surface->events.commit); LOGC(VIEWS, "New xdg popup"); this->main_surface = std::make_shared(popup->base->surface, false); on_map.set_callback([&] (void*) { map(); }); on_unmap.set_callback([&] (void*) { unmap(); }); on_new_popup.set_callback([&] (void *data) { create_xdg_popup((wlr_xdg_popup*)data); }); on_ping_timeout.set_callback([&] (void*) { wf::view_implementation::emit_ping_timeout_signal(self()); }); on_reposition.set_callback([&] (void*) { unconstrain(); }); on_map.connect(&popup->base->surface->events.map); on_unmap.connect(&popup->base->surface->events.unmap); on_new_popup.connect(&popup->base->events.new_popup); on_ping_timeout.connect(&popup->base->events.ping_timeout); on_reposition.connect(&popup->events.reposition); popup->base->data = this; parent_geometry_changed.set_callback([=] (auto) { this->update_position(); }); parent_app_id_changed.set_callback([=] (auto) { this->handle_app_id_changed(popup_parent.lock()->get_app_id()); }); parent_title_changed.set_callback([=] (auto) { this->handle_title_changed(popup_parent.lock()->get_title()); }); parent_ptr->connect(&this->parent_geometry_changed); parent_ptr->connect(&this->parent_app_id_changed); parent_ptr->connect(&this->parent_title_changed); } wayfire_xdg_popup::~wayfire_xdg_popup() = default; std::shared_ptr wayfire_xdg_popup::create(wlr_xdg_popup *popup) { auto self = wf::view_interface_t::create(popup); self->surface_root_node = std::make_shared(self); self->set_surface_root_node(self->surface_root_node); self->set_output(self->popup_parent.lock()->get_output()); return self; } void wayfire_xdg_popup::map() { LOGC(VIEWS, "Trying to map xdg-popup ", self()); if (!get_output()) { close(); return; } // Update main surface before everything else. // Note that map() happens before commit(), so the next commit will just set the same surface state // again. main_surface->apply_current_surface_state(); update_position(); auto parent = popup_parent.lock(); wf::scene::layer parent_layer = wf::get_view_layer(parent) .value_or(wf::scene::layer::WORKSPACE); auto target_layer = wf::scene::layer::UNMANAGED; if ((int)parent_layer > (int)wf::scene::layer::WORKSPACE) { target_layer = parent_layer; } wf::scene::floating_inner_ptr target_parent_node = get_output()->node_for_layer(target_layer); if (std::dynamic_pointer_cast(parent)) { target_parent_node = parent->get_root_node(); } wf::scene::readd_front(target_parent_node, get_root_node()); if (!popup->base->initial_commit) { on_surface_commit.connect(&popup->base->surface->events.commit); } priv->set_mapped_surface_contents(main_surface); priv->set_mapped(main_surface->get_surface()); priv->set_enabled(true); update_size(); damage(); emit_view_map(); wf::get_core().connect(&on_keyboard_focus_changed); if (popup->seat) { wf::get_core().seat->focus_view(self()); } } void wayfire_xdg_popup::unmap() { if (!is_mapped()) { LOGC(VIEWS, "Denying unmap of unmapped xdg-popup ", self()); return; } auto _self_ref = shared_from_this(); LOGC(VIEWS, "Unmapping xdg-popup ", self()); on_keyboard_focus_changed.disconnect(); damage(); emit_view_pre_unmap(); priv->unset_mapped_surface_contents(); priv->set_mapped(nullptr); on_surface_commit.disconnect(); emit_view_unmap(); priv->set_enabled(false); } void wayfire_xdg_popup::commit() { // Update main surface. main_surface->apply_current_surface_state(); if (popup->base->initial_commit) { unconstrain(); } update_size(); update_position(); } void wayfire_xdg_popup::update_position() { auto parent = popup_parent.lock(); if (!parent || !popup) { return; } // Offset relative to the parent surface wf::pointf_t local_offset = { popup->current.geometry.x * 1.0 - popup->base->current.geometry.x, popup->current.geometry.y * 1.0 - popup->base->current.geometry.y, }; if (wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(popup->parent)) { local_offset.x += xdg_surface->geometry.x; local_offset.y += xdg_surface->geometry.y; } wf::pointf_t popup_offset = wf::place_popup_at(popup->parent, popup->base->surface, local_offset); this->move(popup_offset.x, popup_offset.y); } void wayfire_xdg_popup::unconstrain() { wf::view_interface_t *toplevel_parent = this; while (true) { auto as_popup = dynamic_cast(toplevel_parent); if (as_popup) { toplevel_parent = as_popup->popup_parent.lock().get(); } else { break; } } if (!get_output() || !toplevel_parent) { return; } auto box = get_output()->get_relative_geometry(); auto parent_offset = toplevel_parent->get_surface_root_node()->to_global({0, 0}); box.x -= parent_offset.x; box.y -= parent_offset.y; wlr_xdg_popup_unconstrain_from_box(popup, &box); } void wayfire_xdg_popup::destroy() { on_map.disconnect(); on_unmap.disconnect(); on_new_popup.disconnect(); on_ping_timeout.disconnect(); on_reposition.disconnect(); popup->base->data = nullptr; popup = nullptr; } void wayfire_xdg_popup::close() { LOGC(VIEWS, "Closing xdg-popup ", self(), " ", is_mapped()); if (is_mapped()) { wlr_xdg_popup_destroy(popup); } } void wayfire_xdg_popup::ping() { if (popup) { wlr_xdg_surface_ping(popup->base); } } void wayfire_xdg_popup::update_size() { if (!is_mapped()) { return; } wf::dimensions_t current_size{popup->base->surface->current.width, popup->base->surface->current.height}; if (current_size == wf::dimensions(geometry)) { return; } /* Damage current size */ wf::scene::damage_node(get_root_node(), last_bounding_box); geometry.width = current_size.width; geometry.height = current_size.height; /* Damage new size */ last_bounding_box = get_bounding_box(); wf::scene::damage_node(get_root_node(), last_bounding_box); wf::scene::update(this->get_surface_root_node(), wf::scene::update_flag::GEOMETRY); } bool wayfire_xdg_popup::is_mapped() const { return priv->is_mapped; } void wayfire_xdg_popup::handle_app_id_changed(std::string new_app_id) { this->app_id = new_app_id; wf::view_implementation::emit_app_id_changed_signal(self()); } void wayfire_xdg_popup::handle_title_changed(std::string new_title) { this->title = new_title; wf::view_implementation::emit_title_changed_signal(self()); } std::string wayfire_xdg_popup::get_app_id() { return this->app_id; } std::string wayfire_xdg_popup::get_title() { return this->title; } void wayfire_xdg_popup::move(int x, int y) { wf::scene::damage_node(get_root_node(), last_bounding_box); surface_root_node->set_offset({x, y}); geometry.x = x; geometry.y = y; damage(); last_bounding_box = get_bounding_box(); wf::scene::update(this->get_surface_root_node(), wf::scene::update_flag::GEOMETRY); } wf::geometry_t wayfire_xdg_popup::get_geometry() { return geometry; } wlr_surface*wayfire_xdg_popup::get_keyboard_focus_surface() { static wf::option_wrapper_t focus_main_view{"workarounds/focus_main_surface_instead_of_popup"}; if (focus_main_view) { if (auto parent = this->popup_parent.lock()) { return parent->get_keyboard_focus_surface(); } } return priv->wsurface; } /** * A class which manages the xdg_popup_view for the duration of the wlr_xdg_popup object lifetime. */ class xdg_popup_controller_t { std::shared_ptr view; wf::wl_listener_wrapper on_destroy; public: xdg_popup_controller_t(wlr_xdg_popup *popup) { on_destroy.set_callback([=] (auto) { view->destroy(); delete this; }); on_destroy.connect(&popup->events.destroy); view = wayfire_xdg_popup::create(popup); } ~xdg_popup_controller_t() {} }; void create_xdg_popup(wlr_xdg_popup *popup) { if (!wf::wl_surface_to_wayfire_view(popup->parent->resource)) { LOGE("attempting to create a popup with unknown parent"); return; } // Will be freed by the destroy handler new xdg_popup_controller_t{popup}; } static wlr_xdg_shell *xdg_handle = nullptr; static wf::wl_listener_wrapper on_xdg_created; void wf::init_xdg_shell() { xdg_handle = wlr_xdg_shell_create(wf::get_core().display, XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION); if (xdg_handle) { on_xdg_created.set_callback([&] (void *data) { auto surf = static_cast(data); wf::new_xdg_surface_signal new_xdg_surf; new_xdg_surf.surface = surf->base; wf::get_core().emit( &new_xdg_surf); if (new_xdg_surf.use_default_implementation && (surf->base->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL)) { default_handle_new_xdg_toplevel(surf); } }); on_xdg_created.connect(&xdg_handle->events.new_toplevel); } } void wf::fini_xdg_shell() { on_xdg_created.disconnect(); } bool wayfire_xdg_popup::is_focusable() const { return false; } wayfire-0.10.0/src/view/wlr-subsurface-controller.cpp0000664000175000017500000000517515053502647022533 0ustar dkondordkondor#include "wayfire/geometry.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/unstable/wlr-subsurface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include #include wf::wlr_subsurface_controller_t::wlr_subsurface_controller_t(wlr_subsurface *sub) { if (sub->data) { delete (wlr_subsurface_controller_t*)sub->data; } this->sub = sub; sub->data = this; auto surface_node = std::make_shared(sub->surface, true); if (!sub->surface->mapped) { surface_node->set_enabled(false); } this->subsurface_root_node = std::make_shared(sub); subsurface_root_node->set_children_list({surface_node}); on_map.set_callback([=] (void*) { wf::scene::set_node_enabled(surface_node, true); }); on_unmap.set_callback([=] (void*) { wf::scene::set_node_enabled(surface_node, false); }); on_destroy.set_callback([=] (void*) { wf::scene::remove_child(subsurface_root_node); sub->data = NULL; delete this; }); on_map.connect(&sub->surface->events.map); on_unmap.connect(&sub->surface->events.unmap); on_destroy.connect(&sub->events.destroy); } std::shared_ptr wf::wlr_subsurface_controller_t::get_subsurface_root() { return this->subsurface_root_node; } wf::wlr_subsurface_root_node_t::wlr_subsurface_root_node_t(wlr_subsurface *subsurface) { this->subsurface = subsurface; this->on_subsurface_commit.set_callback([=] (void*) { update_offset(); }); this->on_subsurface_destroy.set_callback([=] (void*) { this->subsurface = NULL; on_subsurface_destroy.disconnect(); on_subsurface_commit.disconnect(); }); on_subsurface_destroy.connect(&subsurface->events.destroy); on_subsurface_commit.connect(&subsurface->surface->events.commit); // Set initial offset but don't damage yet this->offset = {subsurface->current.x, subsurface->current.y}; } std::string wf::wlr_subsurface_root_node_t::stringify() const { return "subsurface root node"; } bool wf::wlr_subsurface_root_node_t::update_offset(bool apply_damage) { wf::point_t offset = {subsurface->current.x, subsurface->current.y}; const bool changed = offset != get_offset(); if (changed && apply_damage) { scene::damage_node(this, get_bounding_box()); set_offset(offset); scene::damage_node(this, get_bounding_box()); } else if (changed) { set_offset(offset); } return changed; } wayfire-0.10.0/src/view/xdg-shell/0000775000175000017500000000000015053502647016561 5ustar dkondordkondorwayfire-0.10.0/src/view/xdg-shell/xdg-toplevel-view.hpp0000664000175000017500000000361615053502647022662 0ustar dkondordkondor#pragma once #include "view/toplevel-node.hpp" #include "wayfire/geometry.hpp" #include #include #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include #include #include "wayfire/toplevel.hpp" #include "wayfire/util.hpp" #include "xdg-toplevel.hpp" namespace wf { /** * An implementation of view_interface_t for xdg-shell toplevels. */ class xdg_toplevel_view_t : public xdg_toplevel_view_base_t, public wf::toplevel_view_interface_t { public: static std::shared_ptr create(wlr_xdg_toplevel *toplevel); void request_native_size() override; void set_activated(bool active) override; bool should_be_decorated() override; void set_decoration_mode(bool use_csd); bool is_mapped() const override; // start the map transaction void start_map_tx(); // start the unmap transaction void start_unmap_tx(); private: friend class wf::tracking_allocator_t; xdg_toplevel_view_t(wlr_xdg_toplevel *tlvl); bool has_client_decoration = true; wf::wl_listener_wrapper on_request_move, on_request_resize, on_request_minimize, on_request_maximize, on_request_fullscreen, on_set_parent, on_show_window_menu; std::shared_ptr surface_root_node; // A reference to 'this' used while unmapping, to ensure that the view lives until unmap happens. std::shared_ptr _self_ref; std::shared_ptr wtoplevel; wf::signal::connection_t on_toplevel_applied; void map() override; void destroy() override; void handle_toplevel_state_changed(toplevel_state_t old_state); }; void default_handle_new_xdg_toplevel(wlr_xdg_toplevel *toplevel); } wayfire-0.10.0/src/view/xdg-shell/xdg-toplevel-view.cpp0000664000175000017500000004364715053502647022665 0ustar dkondordkondor#include "xdg-toplevel-view.hpp" #include #include "view/toplevel-node.hpp" #include "wayfire/core.hpp" #include #include #include #include "../view-impl.hpp" #include "../xdg-shell.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/scene.hpp" #include "wayfire/seat.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include "../core/seat/seat-impl.hpp" // --------------------------------------- xdg-toplevel-base impl -------------------------------------------- wf::xdg_toplevel_view_base_t::xdg_toplevel_view_base_t(wlr_xdg_toplevel *toplevel, bool autocommit) { this->xdg_toplevel = toplevel; LOGI("new xdg_shell_stable surface: ", xdg_toplevel->title, " app-id: ", xdg_toplevel->app_id); this->main_surface = std::make_shared(toplevel->base->surface, autocommit); on_destroy.set_callback([&] (void*) { destroy(); }); on_new_popup.set_callback([&] (void *data) { create_xdg_popup((decltype(xdg_toplevel->base->popup))data); }); on_set_title.set_callback([&] (void*) { handle_title_changed(nonull(xdg_toplevel->title)); }); on_set_app_id.set_callback([&] (void*) { handle_app_id_changed(nonull(xdg_toplevel->app_id)); }); on_ping_timeout.set_callback([&] (void*) { wf::view_implementation::emit_ping_timeout_signal(self()); }); on_destroy.connect(&xdg_toplevel->events.destroy); on_new_popup.connect(&xdg_toplevel->base->events.new_popup); on_ping_timeout.connect(&xdg_toplevel->base->events.ping_timeout); on_set_title.connect(&xdg_toplevel->events.set_title); on_set_app_id.connect(&xdg_toplevel->events.set_app_id); xdg_toplevel->base->data = dynamic_cast(this); } void wf::xdg_toplevel_view_base_t::map() { LOGC(VIEWS, "Do map ", self()); priv->set_mapped(main_surface->get_surface()); priv->set_mapped_surface_contents(main_surface); priv->set_enabled(true); damage(); emit_view_map(); } void wf::xdg_toplevel_view_base_t::unmap() { LOGC(VIEWS, "Do unmap ", self()); damage(); priv->unset_mapped_surface_contents(); priv->set_mapped(nullptr); emit_view_unmap(); priv->set_enabled(false); wf::scene::update(get_surface_root_node(), wf::scene::update_flag::INPUT_STATE); } wf::xdg_toplevel_view_base_t::~xdg_toplevel_view_base_t() { if (xdg_toplevel && (xdg_toplevel->base->data == dynamic_cast(this))) { xdg_toplevel->base->data = nullptr; } } void wf::xdg_toplevel_view_base_t::destroy() { on_destroy.disconnect(); on_new_popup.disconnect(); on_set_title.disconnect(); on_set_app_id.disconnect(); on_ping_timeout.disconnect(); priv->wsurface = nullptr; xdg_toplevel = nullptr; } void wf::xdg_toplevel_view_base_t::handle_title_changed(std::string new_title) { this->title = new_title; wf::view_implementation::emit_title_changed_signal(self()); } void wf::xdg_toplevel_view_base_t::handle_app_id_changed(std::string new_app_id) { this->app_id = new_app_id; wf::view_implementation::emit_app_id_changed_signal(self()); } void wf::xdg_toplevel_view_base_t::close() { if (xdg_toplevel) { wlr_xdg_toplevel_send_close(xdg_toplevel); view_interface_t::close(); } } void wf::xdg_toplevel_view_base_t::ping() { if (xdg_toplevel) { wlr_xdg_surface_ping(xdg_toplevel->base); } } wlr_surface*wf::xdg_toplevel_view_base_t::get_keyboard_focus_surface() { if (xdg_toplevel && is_mapped()) { return xdg_toplevel->base->surface; } return nullptr; } bool wf::xdg_toplevel_view_base_t::is_focusable() const { return true; } std::string wf::xdg_toplevel_view_base_t::get_app_id() { return app_id; } std::string wf::xdg_toplevel_view_base_t::get_title() { return title; } bool wf::xdg_toplevel_view_base_t::is_mapped() const { return priv->is_mapped; } // ------------------------------------------ xdg-toplevel impl ---------------------------------------------- /** * When we get a request for setting CSD, the view might not have been * created. So, we store all requests in core, and the views pick this * information when they are created */ std::unordered_map uses_csd; wf::xdg_toplevel_view_t::xdg_toplevel_view_t(wlr_xdg_toplevel *tlvl) : xdg_toplevel_view_base_t(tlvl, false) { this->wtoplevel = std::make_shared(tlvl, this->main_surface); this->wtoplevel->connect(&this->on_toplevel_applied); this->priv->toplevel = this->wtoplevel; this->on_toplevel_applied = [&] (xdg_toplevel_applied_state_signal *ev) { this->handle_toplevel_state_changed(ev->old_state); }; on_show_window_menu.set_callback([&] (void *data) { wlr_xdg_toplevel_show_window_menu_event *event = (wlr_xdg_toplevel_show_window_menu_event*)data; auto view = self(); auto output = view->get_output(); if (!output) { return; } wf::view_show_window_menu_signal d; d.view = view; d.relative_position.x = event->x; d.relative_position.y = event->y; output->emit(&d); wf::get_core().emit(&d); }); on_set_parent.set_callback([&] (void*) { auto parent = xdg_toplevel->parent ? (wf::view_interface_t*)(xdg_toplevel->parent->base->data) : nullptr; set_toplevel_parent(toplevel_cast(parent)); }); on_request_move.set_callback([&] (void *data) { auto ev = static_cast(data); if (ev->serial == wf::get_core().seat->priv->last_press_release_serial) { wf::get_core().default_wm->move_request({this}); return; } }); on_request_resize.set_callback([&] (auto data) { auto ev = static_cast(data); if (ev->serial == wf::get_core().seat->priv->last_press_release_serial) { wf::get_core().default_wm->resize_request({this}, ev->edges); } }); on_request_minimize.set_callback([&] (void*) { wf::get_core().default_wm->minimize_request({this}, true); }); on_request_maximize.set_callback([&] (void *data) { wf::get_core().default_wm->tile_request({this}, xdg_toplevel->requested.maximized ? wf::TILED_EDGES_ALL : 0); }); on_request_fullscreen.set_callback([&] (void *data) { wlr_xdg_toplevel_requested *req = &xdg_toplevel->requested; auto wo = wf::get_core().output_layout->find_output(req->fullscreen_output); wf::get_core().default_wm->fullscreen_request({this}, wo, req->fullscreen); }); on_set_parent.connect(&xdg_toplevel->events.set_parent); on_request_move.connect(&xdg_toplevel->events.request_move); on_request_resize.connect(&xdg_toplevel->events.request_resize); on_request_maximize.connect(&xdg_toplevel->events.request_maximize); on_request_minimize.connect(&xdg_toplevel->events.request_minimize); on_show_window_menu.connect(&xdg_toplevel->events.request_show_window_menu); on_request_fullscreen.connect(&xdg_toplevel->events.request_fullscreen); if (xdg_toplevel && uses_csd.count(xdg_toplevel->base->surface)) { this->has_client_decoration = uses_csd[xdg_toplevel->base->surface]; } } std::shared_ptr wf::xdg_toplevel_view_t::create(wlr_xdg_toplevel *toplevel) { auto self = view_interface_t::create(toplevel); self->surface_root_node = std::make_shared(self); self->set_surface_root_node(self->surface_root_node); // Set the output early, so that we can emit the signals on the output self->set_output(wf::get_core().seat->get_active_output()); self->handle_title_changed(nonull(toplevel->title)); self->handle_app_id_changed(nonull(toplevel->app_id)); // set initial parent self->on_set_parent.emit(nullptr); if (toplevel->requested.fullscreen) { wf::get_core().default_wm->fullscreen_request(self, self->get_output(), true); } if (toplevel->requested.maximized) { wf::get_core().default_wm->tile_request(self, TILED_EDGES_ALL); } return self; } void wf::xdg_toplevel_view_t::request_native_size() { this->wtoplevel->request_native_size(); } void wf::xdg_toplevel_view_t::set_activated(bool active) { toplevel_view_interface_t::set_activated(active); if (xdg_toplevel && xdg_toplevel->base->surface->mapped) { wlr_xdg_toplevel_set_activated(xdg_toplevel, active); } else if (xdg_toplevel) { xdg_toplevel->pending.activated = active; } } bool wf::xdg_toplevel_view_t::should_be_decorated() { return !has_client_decoration; } bool wf::xdg_toplevel_view_t::is_mapped() const { return wtoplevel->current().mapped && priv->is_mapped; } void wf::xdg_toplevel_view_t::map() { adjust_view_output_on_map(this); xdg_toplevel_view_base_t::map(); wf::get_core().default_wm->focus_request(self()); /* Might trigger repositioning */ set_toplevel_parent(this->parent); } void wf::xdg_toplevel_view_t::handle_toplevel_state_changed(wf::toplevel_state_t old_state) { surface_root_node->set_offset(wf::origin(wtoplevel->calculate_base_geometry())); if (!old_state.mapped && wtoplevel->current().mapped) { map(); } if (old_state.mapped && !wtoplevel->current().mapped) { unmap(); } wf::view_implementation::emit_toplevel_state_change_signals({this}, old_state); scene::update(this->get_surface_root_node(), scene::update_flag::GEOMETRY); if (!wf::get_core().tx_manager->is_object_pending(wtoplevel)) { // Drop self-ref => `this` might get deleted _self_ref.reset(); } } void wf::xdg_toplevel_view_t::destroy() { wf::xdg_toplevel_view_base_t::destroy(); on_set_parent.disconnect(); on_request_move.disconnect(); on_request_resize.disconnect(); on_request_maximize.disconnect(); on_request_minimize.disconnect(); on_show_window_menu.disconnect(); on_request_fullscreen.disconnect(); } void wf::xdg_toplevel_view_t::set_decoration_mode(bool use_csd) { bool was_decorated = should_be_decorated(); this->has_client_decoration = use_csd; if (was_decorated != should_be_decorated()) { wf::view_decoration_state_updated_signal data; data.view = {this}; this->emit(&data); wf::get_core().emit(&data); } } /* decorations impl */ struct wf_server_decoration_t { wlr_server_decoration *decor; wf::wl_listener_wrapper on_mode_set, on_destroy; std::function mode_set = [&] (void*) { bool use_csd = decor->mode == WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; uses_csd[decor->surface] = use_csd; auto view = dynamic_cast( wf::wl_surface_to_wayfire_view(decor->surface->resource).get()); if (view) { view->set_decoration_mode(use_csd); } }; wf_server_decoration_t(wlr_server_decoration *_decor) : decor(_decor) { on_mode_set.set_callback(mode_set); on_destroy.set_callback([&] (void*) { uses_csd.erase(decor->surface); delete this; }); on_mode_set.connect(&decor->events.mode); on_destroy.connect(&decor->events.destroy); /* Read initial decoration settings */ mode_set(NULL); } }; struct wf_xdg_decoration_t { wlr_xdg_toplevel_decoration_v1 *decor; wf::wl_listener_wrapper on_mode_request, on_commit, on_destroy; wf::option_wrapper_t deco_mode{"core/preferred_decoration_mode"}; wf::option_wrapper_t force_preferred{"workarounds/force_preferred_decoration_mode"}; std::function mode_request = [&] (void*) { wlr_xdg_toplevel_decoration_v1_mode default_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; if ((std::string)deco_mode == "server") { default_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; } auto mode = decor->requested_mode; if ((mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE) || force_preferred) { mode = default_mode; } if (decor->toplevel->base->initialized) { wlr_xdg_toplevel_decoration_v1_set_mode(decor, mode); } }; std::function commit = [&] (void*) { wlr_xdg_surface *xdg_surface = decor->toplevel->base; bool use_csd = (decor->current.mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); uses_csd[xdg_surface->surface] = use_csd; auto wf_surface = dynamic_cast( wf::wl_surface_to_wayfire_view(xdg_surface->surface->resource).get()); if (wf_surface) { wf_surface->set_decoration_mode(use_csd); } if (decor->toplevel->base->initial_commit) { mode_request(NULL); } }; wf_xdg_decoration_t(wlr_xdg_toplevel_decoration_v1 *_decor) : decor(_decor) { on_mode_request.set_callback(mode_request); on_commit.set_callback(commit); on_destroy.set_callback([&] (void*) { uses_csd.erase(decor->toplevel->base->surface); delete this; }); on_mode_request.connect(&decor->events.request_mode); on_commit.connect(&decor->toplevel->base->surface->events.commit); on_destroy.connect(&decor->events.destroy); } }; static wf::wl_listener_wrapper on_org_kde_decoration_created; static void init_legacy_decoration() { static wf::option_wrapper_t deco_mode{"core/preferred_decoration_mode"}; uint32_t default_mode = WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; if ((std::string)deco_mode == "server") { default_mode = WLR_SERVER_DECORATION_MANAGER_MODE_SERVER; } wlr_server_decoration_manager_set_default_mode(wf::get_core().protocols.decorator_manager, default_mode); on_org_kde_decoration_created.set_callback([&] (void *data) { /* will be freed by the destroy request */ new wf_server_decoration_t((wlr_server_decoration*)(data)); }); on_org_kde_decoration_created.connect(&wf::get_core().protocols.decorator_manager->events.new_decoration); deco_mode.set_callback([&] () { uint32_t default_mode = WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; if ((std::string)deco_mode == "server") { default_mode = WLR_SERVER_DECORATION_MANAGER_MODE_SERVER; } wlr_server_decoration_manager_set_default_mode(wf::get_core().protocols.decorator_manager, default_mode); }); } static wf::wl_listener_wrapper on_xdg_decoration_created; static void init_xdg_decoration() { on_xdg_decoration_created.set_callback([&] (void *data) { /* will be freed by the destroy request */ new wf_xdg_decoration_t((wlr_xdg_toplevel_decoration_v1*)(data)); }); on_xdg_decoration_created.connect(&wf::get_core().protocols.xdg_decorator->events.new_toplevel_decoration); } void wf::init_xdg_decoration_handlers() { init_legacy_decoration(); init_xdg_decoration(); } void wf::fini_xdg_decoration_handlers() { on_org_kde_decoration_created.disconnect(); on_xdg_decoration_created.disconnect(); } void wf::xdg_toplevel_view_t::start_map_tx() { LOGC(VIEWS, "Start mapping ", self()); wlr_box box = xdg_toplevel->base->geometry; auto margins = wtoplevel->pending().margins; box.x = wtoplevel->pending().geometry.x + margins.left; box.y = wtoplevel->pending().geometry.y + margins.top; wtoplevel->pending().mapped = true; priv->set_mapped_surface_contents(main_surface); adjust_view_pending_geometry_on_start_map(this, box, pending_fullscreen(), pending_tiled_edges()); wf::get_core().tx_manager->schedule_object(wtoplevel); } void wf::xdg_toplevel_view_t::start_unmap_tx() { LOGC(VIEWS, "Start unmapping ", self()); emit_view_pre_unmap(); // Take reference until the view has been unmapped _self_ref = shared_from_this(); wtoplevel->pending().mapped = false; wf::get_core().tx_manager->schedule_object(wtoplevel); } /** * A class which manages the xdg_toplevel_view for the duration of the wlr_xdg_toplevel object lifetime. */ class xdg_toplevel_controller_t { std::shared_ptr view; wf::wl_listener_wrapper on_map; wf::wl_listener_wrapper on_unmap; wf::wl_listener_wrapper on_destroy; public: xdg_toplevel_controller_t(wlr_xdg_toplevel *toplevel) { on_destroy.set_callback([=] (auto) { delete this; }); on_destroy.connect(&toplevel->events.destroy); view = wf::xdg_toplevel_view_t::create(toplevel); on_map.set_callback([=] (void*) { wf::view_pre_map_signal pre_map; pre_map.view = view.get(); pre_map.surface = toplevel->base->surface; wf::get_core().emit(&pre_map); if (pre_map.override_implementation) { delete this; } else { view->start_map_tx(); } }); on_unmap.set_callback([&] (void*) { view->start_unmap_tx(); }); on_map.connect(&toplevel->base->surface->events.map); on_unmap.connect(&toplevel->base->surface->events.unmap); } ~xdg_toplevel_controller_t() {} }; void wf::default_handle_new_xdg_toplevel(wlr_xdg_toplevel *toplevel) { // Will be deleted by the destroy handler new xdg_toplevel_controller_t(toplevel); } wayfire-0.10.0/src/view/xdg-shell/xdg-toplevel.cpp0000664000175000017500000002201115053502647021673 0ustar dkondordkondor#include "xdg-toplevel.hpp" #include "wayfire/core.hpp" #include #include #include #include "wayfire/geometry.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/toplevel.hpp" #include "wayfire/txn/transaction-object.hpp" #include "../view-impl.hpp" #include "xdg-shell-protocol.h" wf::xdg_toplevel_t::xdg_toplevel_t(wlr_xdg_toplevel *toplevel, std::shared_ptr main_surface) { this->toplevel = toplevel; this->main_surface = main_surface; on_surface_commit.set_callback([&] (void*) { handle_surface_commit(); }); on_surface_commit.connect(&toplevel->base->surface->events.commit); on_toplevel_destroy.set_callback([&] (void*) { this->toplevel = NULL; on_toplevel_destroy.disconnect(); on_surface_commit.disconnect(); emit_ready(); }); on_toplevel_destroy.connect(&toplevel->base->events.destroy); } void wf::xdg_toplevel_t::request_native_size() { if (toplevel && toplevel->base->initialized) { // This will trigger a client-driven transaction wlr_xdg_toplevel_set_size(toplevel, 0, 0); } } void wf::xdg_toplevel_t::commit() { this->pending_ready = true; _committed = _pending; LOGC(TXNI, this, ": committing toplevel state mapped=", _pending.mapped, " geometry=", _pending.geometry, " tiled=", _pending.tiled_edges, " fs=", _pending.fullscreen, " margins=", _pending.margins.left, ",", _pending.margins.right, ",", _pending.margins.top, ",", _pending.margins.bottom); if (!this->toplevel || (_current.mapped && !_pending.mapped) || !toplevel->base->initialized) { // No longer mapped => we can do whatever emit_ready(); return; } auto configure_serial = configure_surface_with_state(_pending, _current); if (configure_serial.has_value()) { // Send frame done to let the client know it update its state as fast as possible. this->target_configure = *configure_serial; main_surface->send_frame_done(true); } else { emit_ready(); } } std::optional wf::xdg_toplevel_t::configure_surface_with_state( const wf::toplevel_state_t& desired_state, const wf::toplevel_state_t& base_state) { wf::dimensions_t current_size = shrink_dimensions_by_margins(wf::dimensions(base_state.geometry), base_state.margins); if (desired_state.mapped && !base_state.mapped) { // We are trying to map the toplevel => check whether we should wait until it sets the proper // geometry, or whether we are 'only' mapping without resizing. current_size = get_current_wlr_toplevel_size(); } const wf::dimensions_t desired_size = wf::shrink_dimensions_by_margins(wf::dimensions(desired_state.geometry), desired_state.margins); std::optional configure_serial; if ((current_size != desired_size) && (desired_state.geometry.width > 0) && (desired_state.geometry.height > 0)) { const int configure_width = std::max(1, desired_size.width); const int configure_height = std::max(1, desired_size.height); configure_serial = wlr_xdg_toplevel_set_size(this->toplevel, configure_width, configure_height); } if (base_state.tiled_edges != desired_state.tiled_edges) { wlr_xdg_toplevel_set_tiled(this->toplevel, desired_state.tiled_edges); auto version = wl_resource_get_version(toplevel->resource); if (version >= XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { configure_serial = wlr_xdg_toplevel_set_maximized(this->toplevel, (desired_state.tiled_edges == TILED_EDGES_ALL)); } else { configure_serial = wlr_xdg_toplevel_set_maximized(this->toplevel, !!desired_state.tiled_edges); } } if (base_state.fullscreen != desired_state.fullscreen) { configure_serial = wlr_xdg_toplevel_set_fullscreen(toplevel, desired_state.fullscreen); } return configure_serial; } void wf::xdg_toplevel_t::apply() { xdg_toplevel_applied_state_signal event_applied; event_applied.old_state = current(); // Damage the main surface before applying the new state. This ensures that the old position of the view // is damaged. if (main_surface->parent()) { wf::scene::damage_node(main_surface->parent(), main_surface->parent()->get_bounding_box()); } if (!toplevel) { // If toplevel does no longer exist, we can't change the size anymore. _committed.geometry.width = _current.geometry.width; _committed.geometry.height = _current.geometry.height; if (_current.mapped == false) { // Avoid mapping if the view was already destroyed. _committed.mapped = false; } } this->_current = committed(); const bool is_pending = wf::get_core().tx_manager->is_object_pending(shared_from_this()); if (!is_pending) { // Adjust for potential moves due to gravity _pending = committed(); } apply_pending_state(); emit(&event_applied); // Damage the new position. if (main_surface->parent()) { wf::scene::damage_node(main_surface->parent(), main_surface->parent()->get_bounding_box()); } } void wf::xdg_toplevel_t::handle_surface_commit() { pending_state.merge_state(toplevel->base->surface); if (toplevel->base->initial_commit) { wf::toplevel_state_t empty_state{}; configure_surface_with_state(_committed, empty_state); wlr_xdg_surface_schedule_configure(toplevel->base); return; } const bool is_committed = wf::get_core().tx_manager->is_object_committed(shared_from_this()); if (is_committed) { // TODO: handle overflow? if (this->toplevel->base->current.configure_serial < this->target_configure) { // Desired state not reached => wait for the desired state to be reached. In the meantime, send a // frame done so that the client can redraw faster. main_surface->send_frame_done(true); return; } const wf::dimensions_t real_size = expand_dimensions_by_margins(get_current_wlr_toplevel_size(), _committed.margins); wf::adjust_geometry_for_gravity(_committed, real_size); emit_ready(); return; } const bool is_pending = wf::get_core().tx_manager->is_object_pending(shared_from_this()); if (is_pending) { return; } auto toplevel_size = expand_dimensions_by_margins(get_current_wlr_toplevel_size(), _current.margins); if ((toplevel_size == wf::dimensions(current().geometry)) || !current().mapped) { if (toplevel) { if (this->wm_offset != wf::origin(toplevel->base->geometry)) { // Trigger reppositioning in the view implementation this->wm_offset = wf::origin(toplevel->base->geometry); xdg_toplevel_applied_state_signal event_applied; event_applied.old_state = current(); this->emit(&event_applied); } } // Size did not change, there are no transactions going on - apply the new texture directly apply_pending_state(); return; } adjust_geometry_for_gravity(_pending, toplevel_size); LOGC(VIEWS, "Client-initiated resize to geometry ", pending().geometry); auto tx = wf::txn::transaction_t::create(); tx->add_object(shared_from_this()); wf::get_core().tx_manager->schedule_transaction(std::move(tx)); } wf::geometry_t wf::xdg_toplevel_t::calculate_base_geometry() { auto geometry = current().geometry; geometry.x = geometry.x - wm_offset.x + _current.margins.left; geometry.y = geometry.y - wm_offset.y + _current.margins.top; geometry.width = main_surface->get_bounding_box().width; geometry.height = main_surface->get_bounding_box().height; return geometry; } void wf::xdg_toplevel_t::apply_pending_state() { if (toplevel) { pending_state.merge_state(toplevel->base->surface); } main_surface->apply_state(std::move(pending_state)); if (toplevel) { this->wm_offset = wf::origin(toplevel->base->geometry); } } void wf::xdg_toplevel_t::emit_ready() { if (pending_ready) { pending_ready = false; emit_object_ready(this); } } wf::dimensions_t wf::xdg_toplevel_t::get_current_wlr_toplevel_size() { // Size did change => Start a new transaction to change the size. return wf::dimensions(toplevel->base->geometry); } wf::dimensions_t wf::xdg_toplevel_t::get_min_size() { if (toplevel) { return wf::dimensions_t{toplevel->current.min_width, toplevel->current.min_height}; } return {0, 0}; } wf::dimensions_t wf::xdg_toplevel_t::get_max_size() { if (toplevel) { return wf::dimensions_t{toplevel->current.max_width, toplevel->current.max_height}; } return {0, 0}; } wayfire-0.10.0/src/view/xdg-shell/xdg-toplevel.hpp0000664000175000017500000000272515053502647021712 0ustar dkondordkondor#pragma once #include "wayfire/geometry.hpp" #include "wayfire/util.hpp" #include #include #include #include namespace wf { /** * A signal emitted on the xdg_toplevel after the committed state is applied. */ struct xdg_toplevel_applied_state_signal { toplevel_state_t old_state; }; class xdg_toplevel_t : public toplevel_t, public std::enable_shared_from_this { public: xdg_toplevel_t(wlr_xdg_toplevel *toplevel, std::shared_ptr surface); void commit() override; void apply() override; wf::geometry_t calculate_base_geometry(); void request_native_size(); wf::dimensions_t get_min_size() override; wf::dimensions_t get_max_size() override; private: std::shared_ptr main_surface; scene::surface_state_t pending_state; std::optional configure_surface_with_state(const wf::toplevel_state_t& desired_state, const wf::toplevel_state_t& base_state); void apply_pending_state(); wf::dimensions_t get_current_wlr_toplevel_size(); wf::wl_listener_wrapper on_surface_commit; wf::wl_listener_wrapper on_toplevel_destroy; wlr_xdg_toplevel *toplevel; wf::point_t wm_offset = {0, 0}; void handle_surface_commit(); uint32_t target_configure = 0; void emit_ready(); bool pending_ready = false; }; } wayfire-0.10.0/src/view/wlr-surface-controller.cpp0000664000175000017500000001313115053502647022010 0ustar dkondordkondor#include #include #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/unstable/wlr-subsurface-controller.hpp" #include static void update_subsurface_position(wlr_surface *surface, int, int, void*) { if (wlr_subsurface *sub = wlr_subsurface_try_from_wlr_surface(surface)) { if (sub->data) { auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); sub_root->update_offset(); } } } wf::wlr_surface_controller_t::wlr_surface_controller_t(wlr_surface *surface, scene::floating_inner_ptr root_node) { try_free_controller(surface); surface->data = this; this->surface = surface; this->root = root_node; on_destroy.set_callback([=] (void*) { delete this; }); on_new_subsurface.set_callback([=] (void *data) { auto sub = static_cast(data); // Allocate memory, it will be auto-freed when the wlr objects are destroyed auto sub_controller = new wlr_subsurface_controller_t(sub); create_controller(sub->surface, sub_controller->get_subsurface_root()); wlr_subsurface *s; wl_list_for_each(s, &surface->current.subsurfaces_below, current.link) { if (sub == s) { wf::scene::add_back(this->root, sub_controller->get_subsurface_root()); return; } } wf::scene::add_front(this->root, sub_controller->get_subsurface_root()); }); on_destroy.connect(&surface->events.destroy); on_new_subsurface.connect(&surface->events.new_subsurface); /* Handle subsurfaces which were created before the controller */ wlr_subsurface *sub; wl_list_for_each(sub, &surface->current.subsurfaces_below, current.link) { on_new_subsurface.emit(sub); } wl_list_for_each(sub, &surface->current.subsurfaces_above, current.link) { on_new_subsurface.emit(sub); } on_commit.set_callback([=] (void*) { if (!wlr_subsurface_try_from_wlr_surface(surface)) { wlr_surface_for_each_surface(surface, update_subsurface_position, nullptr); } update_subsurface_order_and_position(); }); on_commit.connect(&surface->events.commit); } wf::wlr_surface_controller_t::~wlr_surface_controller_t() { surface->data = nullptr; } void wf::wlr_surface_controller_t::create_controller( wlr_surface *surface, scene::floating_inner_ptr root_node) { new wlr_surface_controller_t(surface, root_node); } void wf::wlr_surface_controller_t::try_free_controller(wlr_surface *surface) { if (surface->data) { delete (wlr_surface_controller_t*)surface->data; } } void wf::wlr_surface_controller_t::update_subsurface_order_and_position() { auto old_bbox = root->get_bounding_box(); // Calculate whether we need to reorder the surfaces. auto all_subsurfaces = this->root->get_children(); // Go until we find the main node, it should be a wlr_surface node. auto it = std::find_if(all_subsurfaces.begin(), all_subsurfaces.end(), [=] (const scene::node_ptr& node) { if (auto wlr_node = dynamic_cast(node.get())) { return wlr_node->get_surface() == surface; } return false; }); if (it == all_subsurfaces.end()) { // Maybe unmapped? Can't do anything at the moment anyway. return; } // Now, put all subsurfaces above in the right order, then main view, then subsurfaces below. // For nodes that we have not copied, we put them at the start/end depending on their original // position. std::vector new_subsurface_order; // Update subsurface order bool subsurface_repositioned = false; wlr_subsurface *sub; wl_list_for_each_reverse(sub, &surface->current.subsurfaces_above, current.link) { auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); new_subsurface_order.push_back(sub_root); subsurface_repositioned |= sub_root->update_offset(false); } new_subsurface_order.push_back(*it); wl_list_for_each_reverse(sub, &surface->current.subsurfaces_below, current.link) { auto sub_root = ((wf::wlr_subsurface_controller_t*)sub->data)->get_subsurface_root(); new_subsurface_order.push_back(sub_root); subsurface_repositioned |= sub_root->update_offset(false); } // Place compositor subsurfaces correctly: either on top or below. for (auto iter = all_subsurfaces.begin(); iter != all_subsurfaces.end(); ++iter) { if (!dynamic_cast(iter->get()) && !dynamic_cast(iter->get())) { if (iter < it) { new_subsurface_order.insert(new_subsurface_order.begin(), *iter); } else { new_subsurface_order.push_back(*iter); } } } const bool order_changed = new_subsurface_order != all_subsurfaces; if (order_changed || subsurface_repositioned) { wf::scene::damage_node(root, old_bbox); if (order_changed) { root->set_children_list(new_subsurface_order); wf::scene::update(root, wf::scene::update_flag::CHILDREN_LIST); } wf::scene::damage_node(root, root->get_bounding_box()); } } wayfire-0.10.0/src/view/wlr-surface-node.cpp0000664000175000017500000003322015053502647020553 0ustar dkondordkondor#include "wayfire/unstable/wlr-surface-node.hpp" #include "pixman.h" #include "wayfire/geometry.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wlr-surface-pointer-interaction.hpp" #include "wlr-surface-touch-interaction.cpp" #include "wayfire/output-layout.hpp" #include #include #include #include #include #include wf::scene::surface_state_t::surface_state_t(surface_state_t&& other) { if (&other != this) { *this = std::move(other); } } wf::scene::surface_state_t& wf::scene::surface_state_t::operator =(surface_state_t&& other) { if (current_buffer) { wlr_buffer_unlock(current_buffer); } current_buffer = other.current_buffer; texture = other.texture; accumulated_damage = other.accumulated_damage; seq = other.seq; size = other.size; src_viewport = other.src_viewport; transform = other.transform; other.current_buffer = NULL; other.texture = NULL; other.accumulated_damage.clear(); other.src_viewport.reset(); other.seq.reset(); return *this; } void wf::scene::surface_state_t::merge_state(wlr_surface *surface) { // NB: lock the new buffer first, in case it is the same as the old one if (surface->buffer) { wlr_buffer_lock(&surface->buffer->base); } if (current_buffer) { wlr_buffer_unlock(current_buffer); } if (surface->buffer) { this->current_buffer = &surface->buffer->base; this->texture = surface->buffer->texture; this->size = {surface->current.width, surface->current.height}; this->transform = {surface->current.transform}; } else { this->current_buffer = NULL; this->texture = NULL; this->size = {0, 0}; } if (surface->current.viewport.has_src) { wlr_fbox fbox; wlr_surface_get_buffer_source_box(surface, &fbox); this->src_viewport = fbox; } else { this->src_viewport.reset(); } this->seq = surface->current.seq; wf::region_t current_damage; wlr_surface_get_effective_damage(surface, current_damage.to_pixman()); this->accumulated_damage |= current_damage; } wf::scene::surface_state_t::~surface_state_t() { if (current_buffer) { wlr_buffer_unlock(current_buffer); } } wf::scene::wlr_surface_node_t::wlr_surface_node_t(wlr_surface *surface, bool autocommit) : node_t(false), autocommit(autocommit) { this->surface = surface; this->ptr_interaction = std::make_unique(surface, this); this->tch_interaction = std::make_unique(surface); this->on_surface_destroyed.set_callback([=] (void*) { this->surface = NULL; this->ptr_interaction = std::make_unique(); this->tch_interaction = std::make_unique(); on_surface_commit.disconnect(); on_surface_destroyed.disconnect(); }); this->on_surface_commit.set_callback([=] (void*) { if (this->autocommit) { apply_current_surface_state(); } for (auto& [wo, _] : visibility) { wo->render->schedule_redraw(); } }); on_surface_destroyed.connect(&surface->events.destroy); on_surface_commit.connect(&surface->events.commit); send_frame_done(false); current_state.merge_state(surface); on_output_remove.set_callback([&] (wf::output_removed_signal *ev) { visibility.erase(ev->output); pending_visibility_delta.erase(ev->output); }); wf::get_core().output_layout->connect(&on_output_remove); } void wf::scene::wlr_surface_node_t::apply_state(surface_state_t&& state) { const bool size_changed = current_state.size != state.size; if (size_changed) { state.accumulated_damage |= wf::construct_box({0, 0}, current_state.size); state.accumulated_damage |= wf::construct_box({0, 0}, state.size); } this->current_state = std::move(state); wf::scene::damage_node(this, current_state.accumulated_damage); if (size_changed) { scene::update(this->shared_from_this(), scene::update_flag::GEOMETRY); } } void wf::scene::wlr_surface_node_t::apply_current_surface_state() { if (this->current_state.seq == surface->current.seq) { // Already up to date. return; } surface_state_t state; state.merge_state(surface); this->apply_state(std::move(state)); } std::optional wf::scene::wlr_surface_node_t::find_node_at(const wf::pointf_t& at) { if (!surface) { return {}; } if (wlr_surface_point_accepts_input(surface, at.x, at.y)) { wf::scene::input_node_t result; result.node = this; result.local_coords = at; return result; } return {}; } std::string wf::scene::wlr_surface_node_t::stringify() const { std::ostringstream name; name << "wlr-surface-node "; if (surface) { name << "surface"; } else { name << "inert"; } name << " " << stringify_flags(); return name.str(); } wf::pointer_interaction_t& wf::scene::wlr_surface_node_t::pointer_interaction() { return *this->ptr_interaction; } wf::touch_interaction_t& wf::scene::wlr_surface_node_t::touch_interaction() { return *this->tch_interaction; } void wf::scene::wlr_surface_node_t::send_frame_done(bool delay_until_vblank) { if (!surface) { return; } if (!delay_until_vblank || visibility.empty()) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_surface_send_frame_done(surface, &now); } else { for (auto& [wo, _] : visibility) { wlr_output_schedule_frame(wo->handle); } } } class wf::scene::wlr_surface_node_t::wlr_surface_render_instance_t : public render_instance_t { std::shared_ptr self; wf::signal::connection_t on_frame_done = [=] (wf::frame_done_signal *ev) { self->send_frame_done(false); }; wf::output_t *visible_on; damage_callback push_damage; wf::region_t last_visibility; wf::signal::connection_t on_surface_damage = [=] (node_damage_signal *data) { if (self->surface) { // Make sure to expand damage, because stretching the surface may cause additional damage. const float scale = self->surface->current.scale; const float output_scale = visible_on ? visible_on->handle->scale : 1.0; if (scale != output_scale) { data->region.expand_edges(std::ceil(std::abs(scale - output_scale))); } } static wf::option_wrapper_t use_opaque_optimizations{ "workarounds/enable_opaque_region_damage_optimizations" }; if (use_opaque_optimizations) { push_damage(data->region & last_visibility); } else { push_damage(data->region); } }; public: wlr_surface_render_instance_t(std::shared_ptr self, damage_callback push_damage, wf::output_t *visible_on) { if (visible_on) { self->handle_enter(visible_on); } this->self = self; this->push_damage = push_damage; this->visible_on = visible_on; self->connect(&on_surface_damage); } ~wlr_surface_render_instance_t() { if (visible_on) { self->handle_leave(visible_on); } } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { wf::region_t our_damage = damage & self->get_bounding_box(); if (!our_damage.empty()) { instructions.push_back(render_instruction_t{ .instance = this, .target = target, .damage = std::move(our_damage), }); if (self->surface) { pixman_region32_subtract(damage.to_pixman(), damage.to_pixman(), &self->surface->opaque_region); } } } void render(const wf::scene::render_instruction_t& data) override { if (!self->current_state.current_buffer) { return; } data.pass->add_texture(*self->to_texture(), data.target, self->get_bounding_box(), data.damage); } void presentation_feedback(wf::output_t *output) override { if (self->surface) { wlr_presentation_surface_scanned_out_on_output(self->surface, output->handle); } } direct_scanout try_scanout(wf::output_t *output) override { if (!self->surface) { return direct_scanout::SKIP; } if (self->get_bounding_box() != output->get_relative_geometry()) { return direct_scanout::OCCLUSION; } // Must have a wlr surface with the correct scale and transform auto wlr_surf = self->surface; if ((wlr_surf->current.scale != output->handle->scale) || (wlr_surf->current.transform != output->handle->transform)) { return direct_scanout::OCCLUSION; } // Finally, the opaque region must be the full surface. wf::region_t non_opaque = output->get_relative_geometry(); non_opaque ^= wf::region_t{&wlr_surf->opaque_region}; if (!non_opaque.empty()) { return direct_scanout::OCCLUSION; } wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_buffer(&state, &wlr_surf->buffer->base); wlr_presentation_surface_scanned_out_on_output(wlr_surf, output->handle); if (wlr_output_commit_state(output->handle, &state)) { wlr_output_state_finish(&state); return direct_scanout::SUCCESS; } else { wlr_output_state_finish(&state); return direct_scanout::OCCLUSION; } } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { auto our_box = self->get_bounding_box(); on_frame_done.disconnect(); last_visibility = visible & our_box; static wf::option_wrapper_t use_opaque_optimizations{ "workarounds/enable_opaque_region_damage_optimizations" }; if (!last_visibility.empty()) { // We are visible on the given output => send wl_surface.frame on output frame, so that clients // can draw the next frame. output->connect(&on_frame_done); if (use_opaque_optimizations && self->surface) { pixman_region32_subtract(visible.to_pixman(), visible.to_pixman(), &self->surface->opaque_region); } } } }; void wf::scene::wlr_surface_node_t::gen_render_instances( std::vector& instances, damage_callback damage, wf::output_t *output) { instances.push_back(std::make_unique( std::dynamic_pointer_cast(this->shared_from_this()), damage, output)); } wf::geometry_t wf::scene::wlr_surface_node_t::get_bounding_box() { return wf::construct_box({0, 0}, current_state.size); } wlr_surface*wf::scene::wlr_surface_node_t::get_surface() const { return this->surface; } std::optional wf::scene::wlr_surface_node_t::to_texture() const { if (this->current_state.current_buffer) { return wf::texture_t{current_state.texture, current_state.src_viewport, current_state.transform}; } return {}; } // Idea of handling output enter/leave events: when the event comes, we store the number of enters/leaves // for outputs and update them on the next idle. The idea is to cache together multiple events, which may // be triggered especially when visibility recomputation happens. void wf::scene::wlr_surface_node_t::handle_enter(wf::output_t *output) { pending_visibility_delta[output]++; idle_update_outputs.run_once([&] () { update_pending_outputs(); }); } void wf::scene::wlr_surface_node_t::handle_leave(wf::output_t *output) { pending_visibility_delta[output]--; idle_update_outputs.run_once([&] () { update_pending_outputs(); }); } void wf::scene::wlr_surface_node_t::update_pending_outputs() { for (auto& [wo, delta] : pending_visibility_delta) { if (delta > 0) { visibility[wo] += delta; if (surface) { wlr_surface_send_enter(surface, wo->handle); } } else if (delta < 0) { if (!visibility.count(wo)) { // output was destroyed, ignore. continue; } visibility[wo] += delta; if ((visibility[wo] <= 0) && surface) { wlr_surface_send_leave(surface, wo->handle); } if (visibility[wo] <= 0) { visibility.erase(wo); } } } if (surface && (visibility.size() > 0)) { float max_scale = 1; for (auto x : visibility) { max_scale = std::max(max_scale, x.first->handle->scale); } wlr_fractional_scale_v1_notify_scale(surface, max_scale); wlr_surface_set_preferred_buffer_scale(surface, max_scale); } pending_visibility_delta.clear(); } wayfire-0.10.0/src/view/view.cpp0000664000175000017500000001414715053502647016357 0ustar dkondordkondor#include #include #include #include "view-impl.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include "wayfire/view-transform.hpp" #include #include "wayfire/signal-definitions.hpp" #include void wf::view_interface_t::set_role(view_role_t new_role) { role = new_role; } std::string wf::view_interface_t::to_string() const { return "view-" + wf::object_base_t::to_string(); } wayfire_view wf::view_interface_t::self() { return wayfire_view(this); } /** Set the view's output. */ void wf::view_interface_t::set_output(wf::output_t *new_output) { view_set_output_signal data; data.view = self(); data.output = get_output(); // the old output this->priv->output = new_output; this->emit(&data); if (new_output) { new_output->emit(&data); } wf::get_core().emit(&data); if (data.output && (data.output != new_output)) { view_disappeared_signal data_disappeared; data_disappeared.view = self(); data.output->emit(&data_disappeared); wf::scene::update(get_root_node(), scene::update_flag::REFOCUS); } } wf::output_t*wf::view_interface_t::get_output() { return priv->output; } void wf::view_interface_t::ping() { // Do nothing, specialized in the various shells } void wf::view_interface_t::close() { /* no-op */ } wlr_box wf::view_interface_t::get_bounding_box() { return get_transformed_node()->get_bounding_box(); } bool wf::view_interface_t::is_focusable() const { return true; } void wf::view_interface_t::damage() { wf::scene::damage_node(get_surface_root_node(), get_surface_root_node()->get_bounding_box()); } bool wf::view_interface_t::has_transformer() { auto ch = get_transformed_node()->get_children(); return !ch.empty() && ch.front() != get_surface_root_node(); } void wf::view_interface_t::take_snapshot(wf::auxilliary_buffer_t& buffer) { auto root_node = get_surface_root_node(); const wf::geometry_t bbox = root_node->get_bounding_box(); float scale = get_output()->handle->scale; buffer.allocate(wf::dimensions(bbox), scale); wf::render_target_t target{buffer}; target.geometry = bbox; target.scale = scale; std::vector instances; root_node->gen_render_instances(instances, [] (auto) {}, get_output()); render_pass_params_t params; params.background_color = {0, 0, 0, 0}; params.damage = bbox; params.target = target; params.instances = &instances; params.flags = RPASS_CLEAR_BACKGROUND; render_pass_t::run(params); } wf::view_interface_t::view_interface_t() { this->priv = std::make_unique(); } class sentinel_node_t : public wf::scene::node_t { public: sentinel_node_t() : node_t(false) {} std::string stringify() const { return "sentinel node (unmapped contents)"; } }; void wf::view_interface_t::set_surface_root_node(scene::floating_inner_ptr surface_root_node) { priv->dummy_node = std::make_shared(); this->priv->surface_root_node = surface_root_node; // Plugins may want to add subsurfaces even before the view is mapped for the first time. // This is why we add a dummy node to track the position of the main surface in the order of surfaces // even before mapping the view for the first time. wf::scene::add_front(surface_root_node, priv->dummy_node); // Set up view content to scene. priv->transformed_node->set_children_list({surface_root_node}); } class view_root_node_t : public wf::scene::floating_inner_node_t, public wf::view_node_tag_t { public: view_root_node_t(wf::view_interface_t *_view) : floating_inner_node_t(false), view_node_tag_t(_view), view(_view->weak_from_this()) {} std::string stringify() const override { if (auto ptr = view.lock()) { std::ostringstream out; out << ptr->self(); return "view-root-node of " + out.str() + " " + stringify_flags(); } else { return "inert view-root-node " + stringify_flags(); } } private: std::weak_ptr view; }; void wf::view_interface_t::base_initialization() { priv->root_node = std::make_shared(this); priv->transformed_node = std::make_shared(); priv->root_node->set_children_list({priv->transformed_node}); priv->root_node->set_enabled(false); priv->pre_free = [=] (auto) { this->_clear_data(); if (auto self = dynamic_cast(this)) { auto children = self->children; for (auto ch : children) { ch->set_toplevel_parent(nullptr); } } }; this->connect(&priv->pre_free); } wf::view_interface_t::~view_interface_t() { wf::scene::remove_child(get_root_node()); } const wf::scene::floating_inner_ptr& wf::view_interface_t::get_root_node() const { return priv->root_node; } const std::shared_ptr& wf::view_interface_t::get_transformed_node() const { return priv->transformed_node; } const wf::scene::floating_inner_ptr& wf::view_interface_t::get_surface_root_node() const { return priv->surface_root_node; } wayfire_view wf::node_to_view(wf::scene::node_t *node) { while (node) { if (auto vnode = dynamic_cast(node)) { return vnode->get_view(); } node = node->parent(); } return nullptr; } wayfire_view wf::node_to_view(wf::scene::node_ptr node) { return node_to_view(node.get()); } wl_client*wf::view_interface_t::get_client() { if (get_wlr_surface()) { return wl_resource_get_client(get_wlr_surface()->resource); } return nullptr; } wlr_surface*wf::view_interface_t::get_wlr_surface() { return priv->wsurface; } wayfire-0.10.0/src/view/toplevel-view.cpp0000664000175000017500000002277315053502647020213 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include "view-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" static void reposition_relative_to_parent(wayfire_toplevel_view view) { if (!view->parent) { return; } auto parent_geometry = view->parent->get_pending_geometry(); auto wm_geometry = view->get_pending_geometry(); // Guess which workspace the parent is on wf::point_t center = { parent_geometry.x + parent_geometry.width / 2, parent_geometry.y + parent_geometry.height / 2, }; if (view->parent->is_mapped()) { auto parent_g = view->parent->get_pending_geometry(); wm_geometry.x = parent_g.x + (parent_g.width - wm_geometry.width) / 2; wm_geometry.y = parent_g.y + (parent_g.height - wm_geometry.height) / 2; } if (view->get_output()) { auto scr_size = view->get_output()->get_screen_size(); wf::point_t parent_ws = { (int)std::floor(1.0 * center.x / scr_size.width), (int)std::floor(1.0 * center.y / scr_size.height), }; auto workarea = view->get_output()->render->get_ws_box( view->get_output()->wset()->get_current_workspace() + parent_ws); if (!view->parent->is_mapped()) { /* if we have a parent which still isn't mapped, we cannot determine * the view's position, so we center it on the screen */ wm_geometry.x = workarea.width / 2 - wm_geometry.width / 2; wm_geometry.y = workarea.height / 2 - wm_geometry.height / 2; } wm_geometry = wf::clamp(wm_geometry, workarea); } /* make sure view is visible afterwards */ view->move(wm_geometry.x, wm_geometry.y); if ((wm_geometry.width != view->get_pending_geometry().width) || (wm_geometry.height != view->get_pending_geometry().height)) { view->resize(wm_geometry.width, wm_geometry.height); } } static void unset_toplevel_parent(wayfire_toplevel_view view) { if (view->parent) { auto& container = view->parent->children; auto it = std::remove(container.begin(), container.end(), view); container.erase(it, container.end()); wf::scene::remove_child(view->get_root_node()); } } static wayfire_toplevel_view find_toplevel_parent(wayfire_toplevel_view view) { while (view->parent) { view = view->parent; } return view; } /** * Check whether the toplevel parent needs refocus. * This may be needed because when focusing a view, its topmost child is given * keyboard focus. When the parent-child relations change, it may happen that * the parent needs to be focused again, this time with a different keyboard * focus surface. */ static void check_refocus_parent(wayfire_toplevel_view view) { view = find_toplevel_parent(view); if (wf::get_core().seat->get_active_view() == view) { wf::get_core().seat->focus_view(view); } } void wf::toplevel_view_interface_t::set_toplevel_parent(wayfire_toplevel_view new_parent) { auto old_parent = parent; if (parent != new_parent) { LOGC(VIEWS, "Setting toplevel parent of ", self(), " to ", new_parent); /* Erase from the old parent */ unset_toplevel_parent({this}); /* Add in the list of the new parent */ if (new_parent) { new_parent->children.insert(new_parent->children.begin(), wayfire_toplevel_view{this}); } parent = new_parent; view_parent_changed_signal ev; ev.view = {this}; this->emit(&ev); } if (parent) { /* Make sure the view is available only as a child */ if (this->get_wset()) { wf::view_pre_moved_to_wset_signal pre_move_to_wset; pre_move_to_wset.view = {this}; pre_move_to_wset.old_wset = get_wset(); pre_move_to_wset.new_wset = nullptr; wf::get_core().emit(&pre_move_to_wset); get_wset()->remove_view({this}); wf::view_moved_to_wset_signal move_to_wset; move_to_wset.view = {this}; move_to_wset.old_wset = pre_move_to_wset.old_wset; move_to_wset.new_wset = nullptr; wf::get_core().emit(&move_to_wset); } this->set_output(parent->get_output()); /* if the view isn't mapped, then it will be positioned properly in map() */ if (is_mapped()) { reposition_relative_to_parent({this}); } wf::scene::readd_front(parent->get_root_node(), this->get_root_node()); check_refocus_parent(parent); } else if (old_parent) { /* At this point, we are a regular view. */ if (this->get_output()) { wf::scene::readd_front(get_output()->wset()->get_node(), get_root_node()); get_output()->wset()->add_view({this}); check_refocus_parent(old_parent); } } } std::vector wf::toplevel_view_interface_t::enumerate_views(bool mapped_only) { if (!this->is_mapped() && mapped_only) { return {}; } std::vector result; result.reserve(priv->last_view_cnt); for (auto& v : this->children) { auto cdr = v->enumerate_views(mapped_only); result.insert(result.end(), cdr.begin(), cdr.end()); } result.push_back({this}); priv->last_view_cnt = result.size(); return result; } void wf::toplevel_view_interface_t::set_output(wf::output_t *new_output) { wf::dassert(!this->parent || this->parent->get_output() == new_output, "Cannot set different output for a view with a parent!"); wf::view_interface_t::set_output(new_output); for (auto& view : this->children) { view->set_output(new_output); } } void wf::toplevel_view_interface_t::move(int x, int y) { toplevel()->pending().geometry.x = x; toplevel()->pending().geometry.y = y; wf::get_core().tx_manager->schedule_object(toplevel()); } void wf::toplevel_view_interface_t::resize(int w, int h) { toplevel()->pending().geometry.width = w; toplevel()->pending().geometry.height = h; wf::get_core().tx_manager->schedule_object(toplevel()); } void wf::toplevel_view_interface_t::set_geometry(wf::geometry_t geometry) { toplevel()->pending().geometry = geometry; wf::get_core().tx_manager->schedule_object(toplevel()); } void wf::toplevel_view_interface_t::request_native_size() { /* no-op */ } void wf::toplevel_view_interface_t::set_minimized(bool minim) { if (minim == minimized) { return; } if (this->parent && minim) { LOGE("Ignoring a request to minimize a view with a parent, minimize the parent instead!"); return; } this->minimized = minim; wf::scene::set_node_enabled(get_root_node(), !minimized); view_minimized_signal data; data.view = {this}; this->emit(&data); if (get_output()) { get_output()->emit(&data); if (minim) { view_disappeared_signal data; data.view = self(); get_output()->emit(&data); wf::scene::update(get_root_node(), scene::update_flag::REFOCUS); } } } void wf::toplevel_view_interface_t::set_sticky(bool sticky) { if (this->sticky == sticky) { return; } damage(); this->sticky = sticky; damage(); wf::view_set_sticky_signal data; data.view = {this}; this->emit(&data); if (this->get_output()) { this->get_output()->emit(&data); } } void wf::toplevel_view_interface_t::set_activated(bool active) { activated = active; view_activated_state_signal ev; ev.view = {this}; this->emit(&ev); } wlr_box wf::toplevel_view_interface_t::get_minimize_hint() { return this->priv->minimize_hint; } void wf::toplevel_view_interface_t::set_minimize_hint(wlr_box hint) { this->priv->minimize_hint = hint; } bool wf::toplevel_view_interface_t::should_be_decorated() { return false; } wf::toplevel_view_interface_t::~toplevel_view_interface_t() { /* Note: at this point, it is invalid to call most functions */ unset_toplevel_parent({this}); } void wf::toplevel_view_interface_t::set_allowed_actions(uint32_t actions) const { priv->allowed_actions = actions; } uint32_t wf::toplevel_view_interface_t::get_allowed_actions() const { return priv->allowed_actions; } std::shared_ptr wf::toplevel_view_interface_t::get_wset() { return priv->current_wset.lock(); } const std::shared_ptr& wf::toplevel_view_interface_t::toplevel() const { return priv->toplevel; } void wf::toplevel_view_interface_t::set_toplevel( std::shared_ptr toplevel) { priv->toplevel = toplevel; } wayfire_toplevel_view wf::find_view_for_toplevel( std::shared_ptr toplevel) { // FIXME: this could be a lot more efficient if we simply store a custom data on the toplevel. for (auto& view : wf::get_core().get_all_views()) { if (auto tview = toplevel_cast(view)) { if (tview->toplevel() == toplevel) { return tview; } } } return nullptr; } wayfire-0.10.0/src/view/translation-node.cpp0000664000175000017500000000743315053502647020666 0ustar dkondordkondor#include #include #include #include wf::scene::translation_node_t::translation_node_t(bool is_structure) : wf::scene::floating_inner_node_t(is_structure) {} wf::pointf_t wf::scene::translation_node_t::to_local(const wf::pointf_t& point) { return point - wf::pointf_t{get_offset()}; } wf::pointf_t wf::scene::translation_node_t::to_global(const wf::pointf_t& point) { return point + wf::pointf_t{get_offset()}; } std::string wf::scene::translation_node_t::stringify() const { auto x = std::to_string(get_offset().x); auto y = std::to_string(get_offset().y); return "translation by " + x + "," + y + " " + stringify_flags(); } void wf::scene::translation_node_t::gen_render_instances( std::vector& instances, scene::damage_callback damage, wf::output_t *output) { instances.push_back(std::make_unique(this, damage, output)); } wf::geometry_t wf::scene::translation_node_t::get_bounding_box() { return get_children_bounding_box() + get_offset(); } wf::point_t wf::scene::translation_node_t::get_offset() const { return offset; } void wf::scene::translation_node_t::set_offset(wf::point_t offset) { this->offset = offset; } uint32_t wf::scene::translation_node_t::optimize_update(uint32_t flags) { return optimize_nested_render_instances(shared_from_this(), flags); } // ----------------------------------------- Render instance ------------------------------------------------- wf::scene::translation_node_instance_t::translation_node_instance_t( translation_node_t *self, damage_callback push_damage, wf::output_t *shown_on) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; this->shown_on = shown_on; on_node_damage = [=] (wf::scene::node_damage_signal *data) { push_damage(data->region); }; self->connect(&on_node_damage); on_regen_instances = [=] (auto) { regen_instances(); }; self->connect(&on_regen_instances); regen_instances(); } void wf::scene::translation_node_instance_t::regen_instances() { children.clear(); auto push_damage_child = [=] (wf::region_t child_damage) { child_damage += self->get_offset(); push_damage(child_damage); }; for (auto& ch : self->get_children()) { if (ch->is_enabled()) { ch->gen_render_instances(children, push_damage_child, shown_on); } } } void wf::scene::translation_node_instance_t::schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) { wf::region_t our_damage = damage & self->get_bounding_box(); if (!our_damage.empty()) { wf::point_t offset = self->get_offset(); damage += -offset; auto our_target = target.translated(-offset); for (auto& ch : this->children) { ch->schedule_instructions(instructions, our_target, damage); } damage += offset; } } void wf::scene::translation_node_instance_t::presentation_feedback(wf::output_t *output) { for (auto& ch : this->children) { ch->presentation_feedback(output); } } wf::scene::direct_scanout wf::scene::translation_node_instance_t::try_scanout(wf::output_t *output) { if (self->get_offset() != wf::point_t{0, 0}) { return wf::scene::direct_scanout::OCCLUSION; } return try_scanout_from_list(this->children, output); } void wf::scene::translation_node_instance_t::compute_visibility(wf::output_t *output, wf::region_t& visible) { compute_visibility_from_list(children, output, visible, self->get_offset()); } wayfire-0.10.0/src/view/layer-shell/0000775000175000017500000000000015053502647017113 5ustar dkondordkondorwayfire-0.10.0/src/view/layer-shell/layer-shell-node.cpp0000664000175000017500000000733415053502647022772 0ustar dkondordkondor#include "layer-shell-node.hpp" #include "wayfire/unstable/wlr-view-keyboard-interaction.hpp" #include "wayfire/scene-input.hpp" #include "../core/core-impl.hpp" #include "../core/seat/seat-impl.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/unstable/translation-node.hpp" #include wf::layer_shell_node_t::layer_shell_node_t(wayfire_view view) : view_node_tag_t(view) { this->kb_interaction = std::make_unique(view); this->_view = view->weak_from_this(); } std::string wf::layer_shell_node_t::stringify() const { auto view = _view.lock(); if (view) { std::ostringstream out; out << view->self(); return out.str() + " " + stringify_flags(); } else { return "inert layer-shell"; } } wf::keyboard_interaction_t& wf::layer_shell_node_t::keyboard_interaction() { return *kb_interaction; } wf::keyboard_focus_node_t wf::layer_shell_node_t::keyboard_refocus(wf::output_t *output) { auto view = _view.lock(); if (!view || !view->get_keyboard_focus_surface()) { return wf::keyboard_focus_node_t{}; } // Layer-shell views are treated differently. // Usually, they should not be focused at all. The only case we want to // focus them is when they were already focused, and should continue to // have focus, or when they have an active grab. if (auto surf = view->get_wlr_surface()) { if (wlr_layer_surface_v1 *layer_surface = wlr_layer_surface_v1_try_from_wlr_surface(surf)) { if (layer_surface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { // Active grab return wf::keyboard_focus_node_t{ .node = this, .importance = focus_importance::HIGH, .allow_focus_below = false }; } } } if (output != view->get_output()) { return wf::keyboard_focus_node_t{}; } const uint64_t last_ts = wf::get_core().seat->get_last_focus_timestamp(); const uint64_t our_ts = keyboard_interaction().last_focus_timestamp; auto cur_focus = wf::get_core_impl().seat->priv->keyboard_focus.get(); bool has_focus = (cur_focus == this) || (our_ts == last_ts); if (has_focus) { return wf::keyboard_focus_node_t{this, focus_importance::REGULAR}; } return wf::keyboard_focus_node_t{}; } wf::region_t wf::layer_shell_node_t::get_opaque_region() const { auto view = _view.lock(); if (view && view->is_mapped() && view->get_wlr_surface()) { auto surf = view->get_wlr_surface(); wf::region_t region{&surf->opaque_region}; region += this->get_offset(); return region; } return {}; } std::optional wf::layer_shell_node_t::to_texture() const { auto view = _view.lock(); if (!view || !view->is_mapped() || (get_children().size() != 1)) { return {}; } if (auto texturable = dynamic_cast(get_children().front().get())) { return texturable->to_texture(); } return {}; } void wf::layer_shell_node_t::gen_render_instances(std::vector & instances, scene::damage_callback push_damage, wf::output_t *shown_on) { auto view = _view.lock(); if (!view) { return; } // Special case: layer-shell views live only inside their outputs and should not be shown on other outputs if (shown_on && (view->get_output() != shown_on)) { return; } instances.push_back(std::make_unique(this, push_damage, shown_on)); } wayfire-0.10.0/src/view/layer-shell/layer-shell.cpp0000664000175000017500000005111715053502647022045 0ustar dkondordkondor#include #include #include #include #include #include #include #include "view/layer-shell/layer-shell-node.hpp" #include "wayfire/geometry.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" #include "../xdg-shell.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include #include #include "wayfire/output.hpp" #include "wayfire/output-layout.hpp" #include "../view-impl.hpp" #include "wlr-layer-shell-unstable-v1-protocol.h" #include static const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; static const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; class wayfire_layer_shell_view : public wf::view_interface_t { wf::wl_listener_wrapper on_map; wf::wl_listener_wrapper on_unmap; wf::wl_listener_wrapper on_new_popup; wf::wl_listener_wrapper on_commit_unmapped; wf::wl_listener_wrapper on_surface_commit; std::shared_ptr main_surface; std::shared_ptr surface_root_node; /** * The bounding box of the view the last time it was rendered. * * This is used to damage the view when it is resized, because when a * transformer changes because the view is resized, we can't reliably * calculate the old view region to damage. */ wf::geometry_t last_bounding_box{0, 0, 0, 0}; /** The output geometry of the view */ wf::geometry_t geometry{100, 100, 0, 0}; std::string app_id; friend class wf::tracking_allocator_t; wayfire_layer_shell_view(wlr_layer_surface_v1 *lsurf); bool keyboard_focus_enabled = true; public: wlr_layer_surface_v1 *lsurface; wlr_layer_surface_v1_state prev_state; static std::shared_ptr create(wlr_layer_surface_v1 *lsurface); std::unique_ptr anchored_area; void remove_anchored(bool reflow); virtual ~wayfire_layer_shell_view() = default; void map(); void unmap(); void commit(); void close() override; /* Handle the destruction of the underlying wlroots object */ void handle_destroy(); void configure(wf::geometry_t geometry); void set_output(wf::output_t *output) override; /** Calculate the target layer for this layer surface */ wf::scene::layer get_layer(); /* Just pass to the default wlr surface implementation */ bool is_mapped() const override { return priv->is_mapped; } std::string get_app_id() override final { return app_id; } std::string get_title() override final { return "layer-shell"; } /* Functions which are further specialized for the different shells */ void move(int x, int y) { wf::region_t damage = last_bounding_box; surface_root_node->set_offset({x, y}); this->geometry.x = x; this->geometry.y = y; last_bounding_box = get_bounding_box(); damage |= last_bounding_box; wf::scene::damage_node(get_root_node(), last_bounding_box); wf::scene::update(get_root_node(), wf::scene::update_flag::GEOMETRY); } wlr_surface *get_keyboard_focus_surface() override { if (is_mapped() && keyboard_focus_enabled) { return priv->wsurface; } return NULL; } }; wf::output_workarea_manager_t::anchored_edge anchor_to_edge(uint32_t edges) { if (edges == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { return wf::output_workarea_manager_t::ANCHORED_EDGE_TOP; } if (edges == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) { return wf::output_workarea_manager_t::ANCHORED_EDGE_BOTTOM; } if (edges == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) { return wf::output_workarea_manager_t::ANCHORED_EDGE_LEFT; } if (edges == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { return wf::output_workarea_manager_t::ANCHORED_EDGE_RIGHT; } abort(); } struct wf_layer_shell_manager { private: wf::signal::connection_t on_output_layout_changed = [=] (wf::output_layout_configuration_changed_signal *ev) { auto outputs = wf::get_core().output_layout->get_outputs(); for (auto wo : outputs) { arrange_layers(wo); } }; wf_layer_shell_manager() { wf::get_core().output_layout->connect(&on_output_layout_changed); } public: static wf_layer_shell_manager& get_instance() { /* Delay instantiation until first call, at which point core should * have been already initialized */ static wf_layer_shell_manager instance; return instance; } using layer_t = std::vector; static constexpr int COUNT_LAYERS = 4; layer_t layers[COUNT_LAYERS]; void handle_map(wayfire_layer_shell_view *view) { layers[view->lsurface->current.layer].push_back(view); arrange_layers(view->get_output()); } void remove_view_from_layer(wayfire_layer_shell_view *view, uint32_t layer) { auto& cont = layers[layer]; auto it = std::find(cont.begin(), cont.end(), view); if (it != cont.end()) { cont.erase(it); } } void handle_move_layer(wayfire_layer_shell_view *view) { for (int i = 0; i < COUNT_LAYERS; i++) { remove_view_from_layer(view, i); } handle_map(view); } void handle_unmap(wayfire_layer_shell_view *view) { view->remove_anchored(false); remove_view_from_layer(view, view->lsurface->current.layer); arrange_layers(view->get_output()); } layer_t filter_views(wf::output_t *output, int layer) { layer_t result; for (auto view : layers[layer]) { if (view->get_output() == output) { result.push_back(view); } } return result; } void set_exclusive_zone(wayfire_layer_shell_view *v) { int edges = v->lsurface->current.anchor; /* Special case we support */ if (__builtin_popcount(edges) == 3) { if ((edges & both_horiz) == both_horiz) { edges ^= both_horiz; } if ((edges & both_vert) == both_vert) { edges ^= both_vert; } } if ((edges == 0) || (__builtin_popcount(edges) > 1)) { LOGE( "Unsupported: layer-shell exclusive zone for surfaces anchored to 0, 2 or 4 edges"); return; } if (!v->anchored_area) { v->anchored_area = std::make_unique(); v->anchored_area->reflowed = [this, v] (wf::geometry_t avail_workarea) { pin_view(v, avail_workarea); }; /* Notice that the reflowed areas won't be changed until we call * reflow_reserved_areas(). However, by that time the information * in anchored_area will have been populated */ v->get_output()->workarea->add_reserved_area(v->anchored_area.get()); } v->anchored_area->edge = anchor_to_edge(edges); v->anchored_area->reserved_size = v->lsurface->current.exclusive_zone; LOGC(LSHELL, "Set exclusive zone for ", v->self(), " edges=", edges, " excl=", v->anchored_area->reserved_size); } void pin_view(wayfire_layer_shell_view *v, wf::geometry_t usable_workarea) { auto state = &v->lsurface->current; auto bounds = v->lsurface->current.exclusive_zone < 0 ? v->get_output()->get_relative_geometry() : usable_workarea; wf::geometry_t box; box.x = box.y = 0; box.width = state->desired_width; box.height = state->desired_height; LOGC(LSHELL, "Pin view ", v->self(), " desired=", wf::dimensions(box), " workarea=", bounds, " anchor=", state->anchor); if ((state->anchor & both_horiz) && (box.width == 0)) { box.x = bounds.x; box.width = bounds.width; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { box.x = bounds.x; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { box.x = bounds.x + (bounds.width - box.width); } else { box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); } if ((state->anchor & both_vert) && (box.height == 0)) { box.y = bounds.y; box.height = bounds.height; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { box.y = bounds.y; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { box.y = bounds.y + (bounds.height - box.height); } else { box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); } v->configure(box); } void arrange_exclusive_zone(wf::output_t *output, int layer) { auto views = filter_views(output, layer); /* First we need to put all views that have exclusive zone set. * The rest are then placed into the free area */ for (auto v : views) { if (v->lsurface->pending.exclusive_zone > 0) { set_exclusive_zone(v); } else { LOGC(LSHELL, "Unset anchored area for ", v->self()); /* Make sure the view doesn't have a reserved area anymore */ v->remove_anchored(false); } } } void arrange_floating(wf::output_t *output, int layer) { auto views = filter_views(output, layer); auto usable_workarea = output->workarea->get_workarea(); for (auto v : views) { /* The protocol dictates that the values -1 and 0 for exclusive zone * mean that it doesn't have one */ if (v->lsurface->pending.exclusive_zone < 1) { pin_view(v, usable_workarea); } } } void arrange_unmapped_view(wayfire_layer_shell_view *view) { if (view->lsurface->pending.exclusive_zone < 1) { return pin_view(view, view->get_output()->workarea->get_workarea()); } set_exclusive_zone(view); view->get_output()->workarea->reflow_reserved_areas(); } void arrange_layers(wf::output_t *output) { const auto layers = { ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, ZWLR_LAYER_SHELL_V1_LAYER_TOP, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND }; for (auto& layer : layers) { arrange_exclusive_zone(output, layer); } output->workarea->reflow_reserved_areas(); for (auto& layer : layers) { arrange_floating(output, layer); } } }; wayfire_layer_shell_view::wayfire_layer_shell_view(wlr_layer_surface_v1 *lsurf) : wf::view_interface_t(), lsurface(lsurf) { on_surface_commit.set_callback([&] (void*) { commit(); }); this->main_surface = std::make_shared(lsurf->surface, true); LOGD("Create a layer surface: namespace ", lsurf->namespace_t, " layer ", lsurf->current.layer); role = wf::VIEW_ROLE_DESKTOP_ENVIRONMENT; std::memset(&this->prev_state, 0, sizeof(prev_state)); lsurface->data = dynamic_cast(this); on_map.set_callback([&] (void*) { map(); }); on_unmap.set_callback([&] (void*) { unmap(); }); on_new_popup.set_callback([&] (void *data) { create_xdg_popup((wlr_xdg_popup*)data); }); on_commit_unmapped.set_callback([&] (void*) { if (!this->get_output()) { // This case can happen in the following scenario: // 1. Create output X // 2. Client opens a layer-shell surface Y on X // 3. X is destroyed, Y's output is now NULL // 4. Y commits return; } wf_layer_shell_manager::get_instance().arrange_unmapped_view(this); }); on_map.connect(&lsurface->surface->events.map); on_unmap.connect(&lsurface->surface->events.unmap); on_new_popup.connect(&lsurface->events.new_popup); on_commit_unmapped.connect(&lsurface->surface->events.commit); } std::shared_ptr wayfire_layer_shell_view::create(wlr_layer_surface_v1 *lsurface) { auto self = wf::view_interface_t::create(lsurface); self->surface_root_node = std::make_shared(self); self->set_surface_root_node(self->surface_root_node); /* If we already have an output set, then assign it before core assigns us * an output */ if (lsurface->output) { auto wo = wf::get_core().output_layout->find_output(lsurface->output); self->set_output(wo); } else { self->set_output(wf::get_core().seat->get_active_output()); } lsurface->output = self->get_output()->handle; return self; } void wayfire_layer_shell_view::handle_destroy() { this->lsurface = nullptr; on_map.disconnect(); on_unmap.disconnect(); on_new_popup.disconnect(); on_commit_unmapped.disconnect(); remove_anchored(true); } wf::scene::layer wayfire_layer_shell_view::get_layer() { static const std::vector desktop_widget_ids = { "keyboard", "de-widget", }; auto it = std::find(desktop_widget_ids.begin(), desktop_widget_ids.end(), nonull(lsurface->namespace_t)); switch (lsurface->current.layer) { case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: if (it != desktop_widget_ids.end()) { return wf::scene::layer::DWIDGET; } return wf::scene::layer::OVERLAY; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: return wf::scene::layer::TOP; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: return wf::scene::layer::BOTTOM; case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: return wf::scene::layer::BACKGROUND; default: throw std::domain_error("Invalid layer for layer surface!"); } } void wayfire_layer_shell_view::map() { { this->app_id = nonull(lsurface->namespace_t); wf::view_implementation::emit_app_id_changed_signal(self()); } // Disconnect, from now on regular commits will work on_commit_unmapped.disconnect(); priv->set_mapped_surface_contents(main_surface); priv->set_mapped(main_surface->get_surface()); priv->set_enabled(true); on_surface_commit.connect(&lsurface->surface->events.commit); /* Read initial data */ auto& state = lsurface->current; keyboard_focus_enabled = state.keyboard_interactive; wf::scene::add_front(get_output()->node_for_layer(get_layer()), get_root_node()); wf_layer_shell_manager::get_instance().handle_map(this); if ((state.keyboard_interactive >= 1) && (state.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP)) { wf::get_core().seat->focus_view(self()); } prev_state = state; emit_view_map(); } void wayfire_layer_shell_view::unmap() { damage(); emit_view_pre_unmap(); priv->unset_mapped_surface_contents(); priv->set_mapped(nullptr); on_surface_commit.disconnect(); emit_view_unmap(); priv->set_enabled(false); wf_layer_shell_manager::get_instance().handle_unmap(this); } void wayfire_layer_shell_view::commit() { wf::dimensions_t new_size{lsurface->surface->current.width, lsurface->surface->current.height}; if (new_size != wf::dimensions(geometry)) { this->geometry.width = new_size.width; this->geometry.height = new_size.height; wf::scene::damage_node(get_root_node(), last_bounding_box); } this->last_bounding_box = get_bounding_box(); auto state = &lsurface->current; /* Update the keyboard focus enabled state. If a refocusing is needed, i.e * the view state changed, then this will happen when arranging layers */ keyboard_focus_enabled = state->keyboard_interactive; if (state->committed) { /* Update layer manually */ if (prev_state.layer != state->layer) { wf::scene::readd_front(get_output()->node_for_layer(get_layer()), get_root_node()); /* Will also trigger reflowing */ wf_layer_shell_manager::get_instance().handle_move_layer(this); } else { /* Reflow reserved areas and positions */ wf_layer_shell_manager::get_instance().arrange_layers(get_output()); } if (prev_state.keyboard_interactive != state->keyboard_interactive) { if ((state->keyboard_interactive == 1) && (state->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP)) { wf::get_core().seat->focus_view(self()); } else if (state->keyboard_interactive == 0) { wf::get_core().seat->refocus(); } } prev_state = *state; } } void wayfire_layer_shell_view::set_output(wf::output_t *output) { if (this->get_output() != output) { // Happens in two cases: // View's output is being destroyed, no point in reflowing // View is about to be mapped, no anchored area at all. this->remove_anchored(false); } wf::view_interface_t::set_output(output); } void wayfire_layer_shell_view::close() { if (lsurface) { wlr_layer_surface_v1_destroy(lsurface); } } void wayfire_layer_shell_view::configure(wf::geometry_t box) { auto state = &lsurface->current; if ((state->anchor & both_horiz) == both_horiz) { box.x += state->margin.left; box.width -= state->margin.left + state->margin.right; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { box.x += state->margin.left; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { box.x -= state->margin.right; } if ((state->anchor & both_vert) == both_vert) { box.y += state->margin.top; box.height -= state->margin.top + state->margin.bottom; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { box.y += state->margin.top; } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { box.y -= state->margin.bottom; } if ((box.width < 0) || (box.height < 0)) { LOGE("layer-surface has calculated width and height < 0"); close(); } // TODO: transactions here could make sense, since we want to change x,y,w,h together, but have to wait // for the client to resize. move(box.x, box.y); wlr_layer_surface_v1_configure(lsurface, box.width, box.height); } void wayfire_layer_shell_view::remove_anchored(bool reflow) { if (anchored_area) { get_output()->workarea->remove_reserved_area(anchored_area.get()); anchored_area = nullptr; if (reflow) { get_output()->workarea->reflow_reserved_areas(); } } } /** * A class which manages the layer_shell_view for the duration of the wlr_layer_surface object lifetime. */ class layer_shell_view_controller_t { std::shared_ptr view; wf::wl_listener_wrapper on_destroy; public: layer_shell_view_controller_t(wlr_layer_surface_v1 *lsurface) { on_destroy.set_callback([=] (auto) { delete this; }); on_destroy.connect(&lsurface->events.destroy); view = wayfire_layer_shell_view::create(lsurface); } ~layer_shell_view_controller_t() { view->handle_destroy(); } }; static wlr_layer_shell_v1 *layer_shell_handle; static wf::wl_listener_wrapper on_layer_shell_surface_created; void wf::init_layer_shell() { layer_shell_handle = wlr_layer_shell_v1_create(wf::get_core().display, 4); if (layer_shell_handle) { on_layer_shell_surface_created.set_callback([] (void *data) { auto lsurf = static_cast(data); new layer_shell_view_controller_t{lsurf}; }); on_layer_shell_surface_created.connect(&layer_shell_handle->events.new_surface); } } void wf::fini_layer_shell() { on_layer_shell_surface_created.disconnect(); } wayfire-0.10.0/src/view/layer-shell/layer-shell-node.hpp0000664000175000017500000000200315053502647022763 0ustar dkondordkondor#pragma once #include #include #include #include namespace wf { /** * A surface root node for toplevel views. */ class layer_shell_node_t : public wf::scene::translation_node_t, public scene::zero_copy_texturable_node_t, public scene::opaque_region_node_t, public view_node_tag_t { public: layer_shell_node_t(wayfire_view view); wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) override; keyboard_interaction_t& keyboard_interaction() override; std::string stringify() const override; void gen_render_instances(std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) override; std::optional to_texture() const override; wf::region_t get_opaque_region() const override; protected: std::weak_ptr _view; std::unique_ptr kb_interaction; }; } wayfire-0.10.0/src/main.hpp0000664000175000017500000000040615053502647015355 0ustar dkondordkondor#pragma once #include extern struct wf_runtime_config { bool no_damage_track = false; bool legacy_wl_drm = false; bool damage_debug = false; } runtime_config; namespace wf { wf::log::color_mode_t detect_color_mode(); } wayfire-0.10.0/src/region.cpp0000664000175000017500000001654015053502647015715 0ustar dkondordkondor#include #include /* Pixman helpers */ wlr_box wlr_box_from_pixman_box(const pixman_box32_t& box) { return { box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1 }; } pixman_box32_t pixman_box_from_wlr_box(const wlr_box& box) { return { box.x, box.y, box.x + box.width, box.y + box.height }; } wf::region_t::region_t() { pixman_region32_init(&_region); } wf::region_t::region_t(const pixman_region32_t *region) : wf::region_t() { pixman_region32_copy(this->to_pixman(), region); } wf::region_t::region_t(const wlr_box& box) { pixman_region32_init_rect(&_region, box.x, box.y, box.width, box.height); } wf::region_t::~region_t() { pixman_region32_fini(&_region); } wf::region_t::region_t(const wf::region_t& other) : wf::region_t() { pixman_region32_copy(this->to_pixman(), other.unconst()); } wf::region_t::region_t(wf::region_t&& other) : wf::region_t() { std::swap(this->_region, other._region); } wf::region_t& wf::region_t::operator =(const wf::region_t& other) { if (&other == this) { return *this; } pixman_region32_copy(&_region, other.unconst()); return *this; } wf::region_t& wf::region_t::operator =(wf::region_t&& other) { if (&other == this) { return *this; } std::swap(_region, other._region); return *this; } bool wf::region_t::empty() const { return !pixman_region32_not_empty(this->unconst()); } void wf::region_t::clear() { pixman_region32_clear(&_region); } void wf::region_t::expand_edges(int amount) { /* FIXME: make sure we don't throw pixman errors when amount is bigger * than a rectangle size */ pixman_region32_t *region = this->to_pixman(); if (amount == 0) { return; } int nrects; const pixman_box32_t *src_rects = pixman_region32_rectangles(region, &nrects); pixman_box32_t *dst_rects = (pixman_box32_t*)malloc(nrects * sizeof(pixman_box32_t)); if (dst_rects == NULL) { return; } for (int i = 0; i < nrects; ++i) { dst_rects[i].x1 = src_rects[i].x1 - amount; dst_rects[i].x2 = src_rects[i].x2 + amount; dst_rects[i].y1 = src_rects[i].y1 - amount; dst_rects[i].y2 = src_rects[i].y2 + amount; /* If x1 > x2 or y1 > y2, this is an invalid rect. * Set the rect members to 0 so it is skipped. */ if ((dst_rects[i].x1 > dst_rects[i].x2) || (dst_rects[i].y1 > dst_rects[i].y2)) { dst_rects[i].x1 = dst_rects[i].x2 = dst_rects[i].y1 = dst_rects[i].y2 = 0; } } pixman_region32_fini(region); pixman_region32_init_rects(region, dst_rects, nrects); free(dst_rects); } pixman_box32_t wf::region_t::get_extents() const { return *pixman_region32_extents(this->unconst()); } bool wf::region_t::contains_point(const wf::point_t& point) const { return pixman_region32_contains_point(this->unconst(), point.x, point.y, NULL); } bool wf::region_t::contains_pointf(const wf::pointf_t& point) const { for (auto& box : *this) { if ((box.x1 <= point.x) && (point.x < box.x2)) { if ((box.y1 <= point.y) && (point.y < box.y2)) { return true; } } } return false; } /* Translate the region */ wf::region_t wf::region_t::operator +(const wf::point_t& vector) const { wf::region_t result{*this}; pixman_region32_translate(&result._region, vector.x, vector.y); return result; } wf::region_t& wf::region_t::operator +=(const wf::point_t& vector) { pixman_region32_translate(&_region, vector.x, vector.y); return *this; } wf::region_t wf::region_t::operator -(const wf::point_t& vector) const { wf::region_t result{*this}; pixman_region32_translate(&result._region, -vector.x, -vector.y); return result; } wf::region_t& wf::region_t::operator -=(const wf::point_t& vector) { pixman_region32_translate(&_region, -vector.x, -vector.y); return *this; } wf::region_t wf::region_t::operator *(float scale) const { wf::region_t result; wlr_region_scale(result.to_pixman(), this->unconst(), scale); return result; } wf::region_t& wf::region_t::operator *=(float scale) { wlr_region_scale(this->to_pixman(), this->to_pixman(), scale); return *this; } /* Region intersection */ wf::region_t wf::region_t::operator &(const wlr_box& box) const { wf::region_t result; pixman_region32_intersect_rect(result.to_pixman(), this->unconst(), box.x, box.y, box.width, box.height); return result; } wf::region_t wf::region_t::operator &(const wf::region_t& other) const { wf::region_t result; pixman_region32_intersect(result.to_pixman(), this->unconst(), other.unconst()); return result; } wf::region_t& wf::region_t::operator &=(const wlr_box& box) { pixman_region32_intersect_rect(this->to_pixman(), this->to_pixman(), box.x, box.y, box.width, box.height); return *this; } wf::region_t& wf::region_t::operator &=(const wf::region_t& other) { pixman_region32_intersect(this->to_pixman(), this->to_pixman(), other.unconst()); return *this; } /* Region union */ wf::region_t wf::region_t::operator |(const wlr_box& other) const { wf::region_t result; pixman_region32_union_rect(result.to_pixman(), this->unconst(), other.x, other.y, other.width, other.height); return result; } wf::region_t wf::region_t::operator |(const wf::region_t& other) const { wf::region_t result; pixman_region32_union(result.to_pixman(), this->unconst(), other.unconst()); return result; } wf::region_t& wf::region_t::operator |=(const wlr_box& other) { pixman_region32_union_rect(this->to_pixman(), this->to_pixman(), other.x, other.y, other.width, other.height); return *this; } wf::region_t& wf::region_t::operator |=(const wf::region_t& other) { pixman_region32_union(this->to_pixman(), this->to_pixman(), other.unconst()); return *this; } /* Subtract the box/region from the current region */ wf::region_t wf::region_t::operator ^(const wlr_box& box) const { wf::region_t result; wf::region_t sub{box}; pixman_region32_subtract(result.to_pixman(), this->unconst(), sub.to_pixman()); return result; } wf::region_t wf::region_t::operator ^(const wf::region_t& other) const { wf::region_t result; pixman_region32_subtract(result.to_pixman(), this->unconst(), other.unconst()); return result; } wf::region_t& wf::region_t::operator ^=(const wlr_box& box) { wf::region_t sub{box}; pixman_region32_subtract(this->to_pixman(), this->to_pixman(), sub.to_pixman()); return *this; } wf::region_t& wf::region_t::operator ^=(const wf::region_t& other) { pixman_region32_subtract(this->to_pixman(), this->to_pixman(), other.unconst()); return *this; } pixman_region32_t*wf::region_t::to_pixman() { return &_region; } const pixman_region32_t*wf::region_t::to_pixman() const { return &_region; } pixman_region32_t*wf::region_t::unconst() const { return const_cast(&_region); } const pixman_box32_t*wf::region_t::begin() const { int n; return pixman_region32_rectangles(unconst(), &n); } const pixman_box32_t*wf::region_t::end() const { int n; auto data = pixman_region32_rectangles(unconst(), &n); return data + n; } wayfire-0.10.0/src/main.cpp0000664000175000017500000003360415053502647015356 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include "main.hpp" #include #include "core/opengl-priv.hpp" #include "wayfire/config-backend.hpp" #include "core/plugin-loader.hpp" #include "core/core-impl.hpp" #include static std::string get_version_string() { return std::string(WAYFIRE_VERSION) + "-" + wf::version::git_commit + " (" + __DATE__ + ") branch " + wf::version::git_branch + " wlroots-" + WLR_VERSION_STR; } static void print_version_and_exit() { std::cout << get_version_string() << std::endl; exit(0); } static void print_help() { std::cout << "Wayfire: " << get_version_string() << std::endl; std::cout << "Usage: wayfire [OPTION]...\n" << std::endl; std::cout << " -c, --config specify config file to use " << "(overrides WAYFIRE_CONFIG_FILE from the environment)" << std::endl; std::cout << " -B, --config-backend specify config backend to use" << std::endl; std::cout << " -h, --help print this help" << std::endl; std::cout << " -d, --debug enable debug logging" << std::endl; std::cout << " -D, --damage-debug enable additional debug for damaged regions" << std::endl; std::cout << " -R, --damage-rerender rerender damaged regions" << std::endl; std::cout << " -l, --legacy-wl-drm use legacy drm for wayland clients" << std::endl; std::cout << " -v, --version print version and exit" << std::endl; exit(0); } static bool drop_permissions(void) { if ((getuid() != geteuid()) || (getgid() != getegid())) { // Set the gid and uid in the correct order. if ((setgid(getgid()) != 0) || (setuid(getuid()) != 0)) { LOGE("Unable to drop root, refusing to start"); return false; } } if ((setgid(0) != -1) || (setuid(0) != -1)) { LOGE("Unable to drop root (we shouldn't be able to " "restore it after setuid), refusing to start"); return false; } return true; } static void wlr_log_handler(wlr_log_importance level, const char *fmt, va_list args) { const int bufsize = 4 * 1024; char buffer[bufsize]; vsnprintf(buffer, bufsize, fmt, args); wf::log::log_level_t wlevel; switch (level) { case WLR_ERROR: wlevel = wf::log::LOG_LEVEL_ERROR; break; case WLR_INFO: wlevel = wf::log::LOG_LEVEL_INFO; break; case WLR_DEBUG: wlevel = wf::log::LOG_LEVEL_DEBUG; break; default: return; } bool enabled = wf::log::enabled_categories.test((int)wf::log::logging_category::WLR); if ((wlevel == wf::log::LOG_LEVEL_DEBUG) && !enabled) { return; } wf::log::log_plain(wlevel, buffer); } static std::optional exit_because_signal; static void signal_handler(int signal) { std::string error; switch (signal) { case SIGSEGV: error = "Segmentation fault"; break; case SIGFPE: error = "Floating-point exception"; break; case SIGABRT: error = "Fatal error(SIGABRT)"; break; case SIGINT: exit_because_signal = SIGINT; wf::get_core().shutdown(); return; case SIGTERM: exit_because_signal = SIGTERM; wf::get_core().shutdown(); return; default: error = "Unknown"; } LOGE("Fatal error: ", error); wf::print_trace(false); std::_Exit(-1); } static std::optional choose_socket(wl_display *display) { for (int i = 1; i <= 32; i++) { auto name = "wayland-" + std::to_string(i); if (wl_display_add_socket(display, name.c_str()) >= 0) { return name; } } return {}; } static wf::config_backend_t *load_backend(const std::string& backend) { std::string config_plugin(backend); if (backend.size()) { std::vector plugin_prefixes = wf::get_plugin_paths(); config_plugin = wf::get_plugin_path_for_name(plugin_prefixes, backend).value_or(""); } auto [_, init_ptr] = wf::get_new_instance_handle(config_plugin, false); if (!init_ptr) { return nullptr; } using backend_init_t = wf::config_backend_t * (*)(); auto init = wf::union_cast(init_ptr); return init(); } static std::string_view get_category_name(wf::log::logging_category category) { switch (category) { case wf::log::logging_category::TXN: return "txn"; case wf::log::logging_category::TXNI: return "txni"; case wf::log::logging_category::VIEWS: return "views"; case wf::log::logging_category::WLR: return "wlroots"; case wf::log::logging_category::SCANOUT: return "scanout"; case wf::log::logging_category::POINTER: return "pointer"; case wf::log::logging_category::WSET: return "wset"; case wf::log::logging_category::KBD: return "kbd"; case wf::log::logging_category::XWL: return "xwayland"; case wf::log::logging_category::LSHELL: return "layer-shell"; case wf::log::logging_category::IM: return "im"; case wf::log::logging_category::RENDER: return "render"; case wf::log::logging_category::INPUT_DEVICES: return "input-devices"; case wf::log::logging_category::OUTPUT: return "output"; default: wf::dassert(false); return "unknown"; } } void parse_extended_debugging(const std::vector& categories) { for (const auto& cat : categories) { size_t idx = 0; const size_t total = (size_t)wf::log::logging_category::TOTAL; for (; idx < total; idx++) { if (get_category_name((wf::log::logging_category)idx) == cat) { LOGD("Enabling debugging category \"", cat, "\""); wf::log::enabled_categories.set(idx, 1); break; } } if (idx == total) { LOGW("Unknown debugging category \"", cat, "\""); } } } // Override assert() handler to be more useful and print a trace. // extern "C" { // void __assert_fail(const char* expr, const char *filename, unsigned int line, const char *assert_func) // { // LOGE("Assertion failed: ", expr, " at ", filename, ":", line, " in ", assert_func); // wf::print_trace(false); // std::_Exit(-1); // } // } // Override exception handling to be more useful and print a trace. // extern "C" { // void __cxa_throw(void *ex, void *info, void (*dest)(void*)) // { // wf::print_trace(false); // std::_Exit(-1); // } // } // int main(int argc, char *argv[]) { wf::log::log_level_t log_level = wf::log::LOG_LEVEL_INFO; struct option opts[] = { { "config", required_argument, NULL, 'c' }, { "config-backend", required_argument, NULL, 'B' }, {"debug", optional_argument, NULL, 'd'}, {"damage-debug", no_argument, NULL, 'D'}, {"damage-rerender", no_argument, NULL, 'R'}, {"legacy-wl-drm", no_argument, NULL, 'l'}, {"with-great-power-comes-great-responsibility", no_argument, NULL, 'r'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"exit-on-gles-error", no_argument, NULL, '$'}, {0, 0, NULL, 0} }; std::string config_file; std::string config_backend = WF_DEFAULT_CONFIG_BACKEND; std::vector extended_debug_categories; bool allow_root = false; if (char *default_config_backend = getenv("WAYFIRE_DEFAULT_CONFIG_BACKEND")) { config_backend = default_config_backend; } if (!getenv("XDG_CURRENT_DESKTOP")) { setenv("XDG_CURRENT_DESKTOP", "wayfire", 1); } int c, i; while ((c = getopt_long(argc, argv, "c:B:d::DhRlrv", opts, &i)) != -1) { switch (c) { case 'c': config_file = optarg; break; case 'B': config_backend = optarg; break; case 'D': runtime_config.damage_debug = true; break; case 'R': runtime_config.no_damage_track = true; break; case 'l': runtime_config.legacy_wl_drm = true; break; case 'r': allow_root = true; break; case 'h': print_help(); break; case '$': OpenGL::exit_on_gles_error = true; break; case 'd': log_level = wf::log::LOG_LEVEL_DEBUG; // Make sure to parse things like `-d txn`, which getopt does not. // According to documentation, for optional arguments, optarg will // be NULL so we need to manually check. if (!optarg && (NULL != argv[optind]) && ('-' != argv[optind][0])) { optarg = argv[optind]; ++optind; } if (optarg) { extended_debug_categories.push_back(optarg); } break; case 'v': print_version_and_exit(); break; default: std::cerr << "Unrecognized command line argument " << optarg << "\n" << std::endl; } } /* Don't crash on SIGPIPE, e.g., when doing IPC to a client whose fd has been closed. */ signal(SIGPIPE, SIG_IGN); wf::log::initialize_logging(std::cout, log_level, wf::detect_color_mode()); parse_extended_debugging(extended_debug_categories); wlr_log_init(WLR_DEBUG, wlr_log_handler); #ifdef PRINT_TRACE /* In case of crash, print the stacktrace for debugging. * However, if ASAN is enabled, we'll get better stacktrace from there. */ signal(SIGSEGV, signal_handler); signal(SIGFPE, signal_handler); signal(SIGABRT, signal_handler); #endif signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); std::set_terminate([] () { std::cout << "Unhandled exception" << std::endl; wf::print_trace(false); std::abort(); }); LOGI("Starting wayfire: ", get_version_string()); /* First create display and initialize safe-list's event loop, so that * wf objects (which depend on safe-list) can work */ auto display = wl_display_create(); auto& core = wf::compositor_core_impl_t::allocate_core(); core.argc = argc; core.argv = argv; /** TODO: move this to core_impl constructor */ core.display = display; core.ev_loop = wl_display_get_event_loop(core.display); core.backend = wlr_backend_autocreate(core.ev_loop, &core.session); int drm_fd = -1; char *drm_device = getenv("WLR_RENDER_DRM_DEVICE"); if (drm_device) { drm_fd = open(drm_device, O_RDWR | O_CLOEXEC); } else { drm_fd = wlr_backend_get_drm_fd(core.backend); } if (drm_fd < 0) { #if WLR_HAS_UDMABUF_ALLOCATOR == 1 LOGW("Failed to open DRM render device, consider specifying WLR_RENDER_DRM_DEVICE." "Trying SW rendering instead."); #else LOGE("Failed to open DRM render device, consider specifying WLR_RENDER_DRM_DEVICE." "If you want to use software rendering, ensure that wlroots has been compiled with udmabuf " "allocator support (available in wlroots >= 0.19.0) and recompile Wayfire."); wl_display_destroy_clients(core.display); wl_display_destroy(core.display); return EXIT_FAILURE; #endif } // core.renderer = wlr_vk_renderer_create_with_drm_fd(drm_fd); core.renderer = wlr_renderer_autocreate(core.backend); // core.renderer = wlr_pixman_renderer_create(); // core.renderer = wlr_gles2_renderer_create_with_drm_fd(drm_fd); if (!core.renderer) { LOGE("Failed to create renderer"); wl_display_destroy_clients(core.display); wl_display_destroy(core.display); return EXIT_FAILURE; } core.allocator = wlr_allocator_autocreate(core.backend, core.renderer); assert(core.allocator); if (core.is_gles2()) { core.egl = wlr_gles2_renderer_get_egl(core.renderer); assert(core.egl); } if (!allow_root && !drop_permissions()) { wl_display_destroy_clients(core.display); wl_display_destroy(core.display); return EXIT_FAILURE; } auto backend = load_backend(config_backend); if (!backend) { LOGE("Failed to load configuration backend!"); wl_display_destroy_clients(core.display); wl_display_destroy(core.display); return EXIT_FAILURE; } LOGD("Using configuration backend: ", config_backend); core.config_backend = std::unique_ptr(backend); core.config_backend->init(display, *core.config, config_file); core.init(); auto socket = choose_socket(core.display); if (!socket) { LOGE("Failed to create wayland socket, exiting."); return -1; } core.wayland_display = socket.value(); LOGI("Using socket name ", core.wayland_display); if (!wlr_backend_start(core.backend)) { LOGE("Failed to initialize backend, exiting"); wlr_backend_destroy(core.backend); wl_display_destroy(core.display); return -1; } setenv("WAYLAND_DISPLAY", core.wayland_display.c_str(), 1); core.post_init(); wl_display_run(core.display); if (exit_because_signal == SIGINT) { LOGI("Got SIGINT, shutting down"); } else if (exit_because_signal == SIGTERM) { LOGI("Got SIGTERM, shutting down"); } wf::compositor_core_impl_t::deallocate_core(); LOGI("Shutdown successful!"); return EXIT_SUCCESS; } wayfire-0.10.0/src/debug.cpp0000664000175000017500000002756215053502647015526 0ustar dkondordkondor#include "main.hpp" #include #include #include "core/core-impl.hpp" #include "core/plugin-loader.hpp" #include #include #include #include #include #include #include #include #if __has_include() #include #include #endif #include #include #include #include #include #define MAX_FRAMES 256 #define MAX_FUNCTION_NAME 1024 #if __has_include() struct demangling_result { std::string executable; std::string function_name; std::string address; }; /** * Parse a mangled symbol and offset in the backtrace_symbols format: * * executable(function+offset) [global offset] */ demangling_result demangle_function(std::string symbol) { demangling_result result; size_t offset_begin = symbol.find_first_of("["); size_t offset_end = symbol.find_first_of("]"); if ((offset_begin != std::string::npos) && (offset_end != std::string::npos) && (offset_begin < offset_end)) { offset_begin++; result.address = symbol.substr(offset_begin, offset_end - offset_begin); } size_t function_begin = symbol.find_first_of("("); if (function_begin != std::string::npos) { result.executable = symbol.substr(0, function_begin); } size_t function_end = symbol.find_first_of("+"); if ((function_begin == std::string::npos) || (function_end == std::string::npos) || (function_begin >= function_end)) { return result; } ++function_begin; std::string function_name = symbol.substr(function_begin, function_end - function_begin); int status; char *demangled = abi::__cxa_demangle(function_name.data(), NULL, NULL, &status); if (status != 0) { free(demangled); return result; } result.function_name = demangled; free(demangled); return result; } /** * Execute the given command and read the first line of its output. * * Returns an empty string in case of an error. */ std::string read_output(std::string command) { // Open a pipe stream to read the output of the process `command`. FILE *file = popen(command.c_str(), "r"); if (!file) { // popen failed. return {}; } // Read the first line, up till and including the first newline if any, into a buffer. char buffer[MAX_FUNCTION_NAME + 1]; // +1 for the terminating zero. char *line_as_c_str = fgets(buffer, sizeof(buffer), file); // We're done with the pipe stream now. pclose(file); if (!line_as_c_str) { // fgets returned an error. return {}; } // If fgets returns non-NULL then the string is guaranteed to be zero terminated. size_t len = std::strlen(line_as_c_str); // Strip the trailing newline, if any. if ((len > 0) && (line_as_c_str[len - 1] == '\n')) { --len; line_as_c_str[len] = 0; } return {buffer, len}; } /** * Try to find the correct path to the given executable. * * If the path is relative(beginning with . or ..), or absolute, we already * have the corrent path. Otherwise, try to find it with 'which' */ std::string locate_executable(std::string executable) { if (!executable.length()) { return ""; } /* If absolute/relative address, we can't do anything */ if ((executable[0] == '/') || (executable[0] == '.')) { return executable; } /* If just an executable, try to find it in PATH */ auto path = read_output("which " + executable); return path; } /** * Find the first position where ".." is, and then strip everything before that. */ std::string strip_until_dots(std::string line) { size_t pos = line.find(".."); if (pos != std::string::npos) { return line.substr(pos); } return line; } /** * A hexadecimal offset to void* pointer conversion */ void *hex_to_ptr(std::string ptr) { size_t idx; return reinterpret_cast(std::stoul(ptr, &idx, 16)); } /** * Try to run addr2line for the given executable and address. */ std::string try_addr2line(std::string executable, std::string address, std::string flags = "") { std::string command = "addr2line " + flags + " -e " + executable + " " + address; auto location = read_output(command); return strip_until_dots(location); } /** * Check whether the addr2line returned a valid position. */ bool valid_addr2line_return(std::string output) { return !output.empty() && output[0] != '?'; } struct addr2line_result { std::string function_name; std::string function_source; }; /** * Try to locate the source file for the given address. * If addr2line is not available on the system, the operation simply returns * an string "unknown location(address)". */ addr2line_result locate_source_file(const demangling_result& dr) { auto executable = locate_executable(dr.executable); if (executable.empty() || dr.address.empty()) { return {"", ""}; } // First, try to check a symbol in the executable auto in_executable = try_addr2line(executable, dr.address); if (valid_addr2line_return(in_executable)) { auto function_name = try_addr2line(executable, dr.address, "-Cf"); return {function_name, in_executable}; } // Second, try to check a symbol in a library // We need the base address inside the library. Dl_info info; dladdr(hex_to_ptr(dr.address), &info); void *position_inside_lib = reinterpret_cast( (char*)hex_to_ptr(dr.address) - (char*)info.dli_fbase); std::ostringstream out; out << std::hex << position_inside_lib; std::string real_address = out.str(); return { try_addr2line(executable, real_address, "-Cf"), try_addr2line(executable, real_address), }; } #if HAS_ASAN extern "C" { void __sanitizer_print_stack_trace(void); } #endif void wf::print_trace(bool fast_mode) { if (!fast_mode) { #if HAS_ASAN // We run with asan: use __sanitizer_print_stack_trace to cause ASAN to print a nice stacktrace, // which is better than what we can come up with. __sanitizer_print_stack_trace(); return; #endif } void *addrlist[MAX_FRAMES]; int addrlen = backtrace(addrlist, MAX_FRAMES); if (addrlen == 0) { LOGE("Failed to determine backtrace, recompile with ASAN!"); return; } char **symbollist = backtrace_symbols(addrlist, addrlen); for (int i = 1; i < addrlen; i++) { auto result = demangle_function(symbollist[i]); std::ostringstream line; line << '#' << std::left << std::setw(2) << i << " "; if (HAS_ADDR2LINE && !fast_mode && result.address.size() && result.executable.size()) { auto source = locate_source_file(result); line << source.function_name << " " << source.function_source; } else if (result.function_name.size()) { line << result.function_name << " at " << result.address; } else { line << symbollist[i]; } auto contents = line.str(); if (contents.size() && (contents.back() == '\n')) { contents.pop_back(); } wf::log::log_plain(wf::log::LOG_LEVEL_ERROR, contents); } free(symbollist); } #else // has void wf::print_trace(bool) { LOGE("Compiled without execinfo.h, cannot provide a backtrace!", " Try using address sanitizer."); } #endif /* ------------------- Impl of debugging functions ---------------------------*/ #include std::ostream& operator <<(std::ostream& out, const glm::mat4& mat) { out << std::endl; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { out << std::setw(10) << std::fixed << std::setprecision(5) << mat[j][i] << std::setw(0) << ","; } out << std::endl; } return out; } wf::pointf_t operator *(const glm::mat4& m, const wf::pointf_t& p) { glm::vec4 v = {p.x, p.y, 0.0, 1.0}; v = m * v; return wf::pointf_t{v.x, v.y}; } wf::pointf_t operator *(const glm::mat4& m, const wf::point_t& p) { return m * wf::pointf_t{1.0 * p.x, 1.0 * p.y}; } std::ostream& wf::operator <<(std::ostream& out, wayfire_view view) { if (view) { out << "view id=" << view->get_id() << " title=\"" << view->get_title() << "\"" << " app_id=\"" << view->get_app_id() << "\""; } else { out << "(null view)"; } return out; } std::bitset<(size_t)wf::log::logging_category::TOTAL> wf::log::enabled_categories; wf::log::color_mode_t wf::detect_color_mode() { return isatty(STDOUT_FILENO) ? wf::log::LOG_COLOR_MODE_ON : wf::log::LOG_COLOR_MODE_OFF; } #define CLEAR_COLOR "\033[0m" #define GREY_COLOR "\033[30;1m" #define GREEN_COLOR "\033[32;1m" #define YELLOW_COLOR "\033[33;1m" #define MAGENTA_COLOR "\033[35;1m" template static void color_debug_log(const char *color, Args... args) { if (wf::detect_color_mode() == wf::log::LOG_COLOR_MODE_OFF) { LOGD(args...); } else { LOGD(color, args..., CLEAR_COLOR); } } static std::string fmt_pointer(void *ptr) { std::ostringstream ss; ss << ptr; return ss.str(); } static void _dump_scene(wf::scene::node_ptr root, int depth = 0) { using namespace wf::scene; // root // |-child // | |-nested // | | |-nested2 // std::ostringstream node_line; for (int i = 0; i < depth; i++) { node_line << "|"; if (i < depth - 1) { node_line << " "; } else { node_line << "-"; } } node_line << root->stringify(); node_line << " [" + fmt_pointer(root.get()) + "]"; node_line << " geometry=" << root->get_bounding_box(); const int greyed_flags = (int)node_flags::DISABLED; if (root->flags() & greyed_flags) { color_debug_log(GREY_COLOR, node_line.str()); } else { color_debug_log(CLEAR_COLOR, node_line.str()); } for (auto& ch : root->get_children()) { _dump_scene(ch, depth + 1); } } void wf::dump_scene(scene::node_ptr root) { _dump_scene(root); } void wf::detail::option_wrapper_debug_message(const std::string & option_name, const std::runtime_error & err) { LOGE("Wayfire encountered error loading option \"", option_name, "\": ", err.what(), ". ", "Usual reasons for this include missing or outdated plugin XML files, ", "a bug in the plugin itself, or mismatch between the versions of Wayfire and wf-config. ", "Make sure that you have the correct versions of all relevant packages and make sure that there ", "are no conflicting installations of Wayfire using the same prefix. ", "If this plugin was recently installed or updated, you need to restart Wayfire before using it."); auto& core = wf::get_core_impl(); if (core.plugin_mgr->is_loading_plugin()) { // re-throw the exception, will be caught in plugin_manager_t::reload_dynamic_plugins() throw err; } wf::print_trace(false); std::_Exit(0); } void wf::detail::option_wrapper_debug_message(const std::string & option_name, const std::logic_error & err) { LOGE("Wayfire encountered error loading option \"", option_name, "\": ", err.what(), ". ", "This usually indicates a bug in the plugin. ", "If this plugin was recently installed or updated, you need to restart Wayfire before using it."); auto& core = wf::get_core_impl(); if (core.plugin_mgr->is_loading_plugin()) { // re-throw the exception, will be caught in plugin_manager_t::reload_dynamic_plugins() throw err; } wf::print_trace(false); std::_Exit(0); } wayfire-0.10.0/src/meson.build0000664000175000017500000001421015053502647016060 0ustar dkondordkondorwayfire_sources = ['geometry.cpp', 'region.cpp', 'debug.cpp', 'util.cpp', 'json.cpp', 'render.cpp', 'core/window-manager.cpp', 'core/output-layout.cpp', 'core/plugin-loader.cpp', 'core/matcher.cpp', 'core/object.cpp', 'core/opengl.cpp', 'core/plugin.cpp', 'core/scene.cpp', 'core/core.cpp', 'core/idle.cpp', 'core/img.cpp', 'core/wm.cpp', 'core/view-access-interface.cpp', 'core/txn/transaction.cpp', 'core/txn/transaction-manager.cpp', 'core/seat/pointing-device.cpp', 'core/seat/input-manager.cpp', 'core/seat/input-method-relay.cpp', 'core/seat/input-method-popup.cpp', 'core/seat/bindings-repository.cpp', 'core/seat/hotspot-manager.cpp', 'core/seat/drag-icon.cpp', 'core/seat/keyboard.cpp', 'core/seat/pointer.cpp', 'core/seat/cursor.cpp', 'core/seat/switch.cpp', 'core/seat/tablet.cpp', 'core/seat/touch.cpp', 'core/seat/seat.cpp', 'view/wlr-surface-controller.cpp', 'view/wlr-subsurface-controller.cpp', 'view/view.cpp', 'view/toplevel-view.cpp', 'view/view-impl.cpp', 'view/toplevel-node.cpp', 'view/xdg-shell.cpp', 'view/xdg-shell/xdg-toplevel.cpp', 'view/xdg-shell/xdg-toplevel-view.cpp', 'view/xwayland.cpp', 'view/xwayland/xwayland-toplevel-view.cpp', 'view/xwayland/xwayland-view-base.cpp', 'view/xwayland/xwayland-toplevel.cpp', 'view/xwayland/xwayland-helpers.cpp', 'view/layer-shell/layer-shell.cpp', 'view/layer-shell/layer-shell-node.cpp', 'view/view-3d.cpp', 'view/compositor-view.cpp', 'view/wlr-surface-node.cpp', 'view/translation-node.cpp', 'output/output.cpp', 'output/workarea.cpp', 'output/render-manager.cpp', 'output/workspace-stream.cpp', 'output/workspace-impl.cpp'] wayfire_dependencies = [wayland_server, wlroots, xkbcommon, libinput, pixman, drm, egl, glesv2, glm, wf_protos, libdl, wfconfig, libinotify, backtrace, wfutils, xcb, wftouch, json, udev] if conf_data.get('BUILD_WITH_IMAGEIO') wayfire_dependencies += [jpeg, png] endif debug_arguments = [] addr2line = find_program('addr2line', required: false) if addr2line.found() debug_arguments += ['-DHAS_ADDR2LINE=1'] else debug_arguments += ['-DHAS_ADDR2LINE=0'] endif if has_asan print_trace = false debug_arguments += ['-DHAS_ASAN=1'] message('Address sanitizer enabled, disabling internal backtrace') message('Overriding print_trace build option') else debug_arguments += ['-DHAS_ASAN=0'] endif if print_trace debug_arguments += ['-DPRINT_TRACE'] endif # Generate information about Wayfire version git = find_program('git', native: true, required: false) git_commit_info = vcs_tag( input: 'version/git-commit.cpp.in', output: 'git-commit.cpp', replace_string: '@GIT_COMMIT@', command: git.found() ? [git, 'rev-parse', '--short', 'HEAD'] : ['echo', 'unknown'], fallback: 'unknown') git_branch_info = vcs_tag( input: 'version/git-branch.cpp.in', output: 'git-branch.cpp', replace_string: '@GIT_BRANCH@', command: git.found() ? [git, 'rev-parse', '--abbrev-ref', 'HEAD'] : ['echo', 'unknown'], fallback: 'unknown') # First build a static library of all sources, so that it can be reused # in tests libwayfire_sta = static_library('libwayfire', wayfire_sources, dependencies: wayfire_dependencies, include_directories: [wayfire_conf_inc, wayfire_api_inc], cpp_args: debug_arguments, install: false, cpp_pch: 'pch/pch.h') libwayfire = declare_dependency(link_whole: libwayfire_sta, include_directories: [wayfire_conf_inc, wayfire_api_inc], dependencies: wayfire_dependencies) tests_include_dirs = include_directories('.') # Generate main executable executable('wayfire', ['main.cpp', git_commit_info, git_branch_info], dependencies: libwayfire, install: true, cpp_args: debug_arguments) shared_module('default-config-backend', 'default-config-backend.cpp', dependencies: wayfire_dependencies, include_directories: [wayfire_conf_inc, wayfire_api_inc], cpp_args: debug_arguments, install_dir: conf_data.get('PLUGIN_PATH'), install: true) install_subdir('api/wayfire', install_dir: get_option('includedir')) public_api_requirements = [ cairo, pango, pangocairo, wayland_server, pixman, # These might be subprojects so we need to pass them as strings wlroots_dep_name, 'wf-config', ] if wlroots_features['gles2_renderer'] public_api_requirements += glesv2 endif if wlroots_features['vulkan_renderer'] public_api_requirements += vulkan endif pkgconfig = import('pkgconfig') pkgconfig.generate( version: meson.project_version(), filebase: meson.project_name(), name: meson.project_name(), description: 'A Wayland Compositor', requires: public_api_requirements, variables: ['metadatadir=${prefix}/' + metadata_dir_suffix, 'sysconfdir=' + sysconfdir, 'plugindir=${libdir}/wayfire', 'icondir=${prefix}/share/wayfire/icons', 'pkgdatadir='+pkgdatadir] ) devenv = environment() devenv.set('WAYFIRE_DEFAULT_CONFIG_BACKEND', meson.current_build_dir() + '/libdefault-config-backend.so') meson.add_devenv(devenv) wayfire-0.10.0/src/pch/0000775000175000017500000000000015053502647014472 5ustar dkondordkondorwayfire-0.10.0/src/pch/pch.h0000664000175000017500000000123315053502647015414 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "src/core/core-impl.hpp" #include "src/view/view-impl.hpp" wayfire-0.10.0/src/json.cpp0000664000175000017500000002713315053502647015403 0ustar dkondordkondor#include #include "wayfire/dassert.hpp" #include #include namespace wf { bool json_reference_t::is_array() const { return yyjson_mut_is_arr(v); } json_reference_t json_reference_t::operator [](const size_t& idx) const { wf::dassert(is_array()); wf::dassert(size() > idx); return json_reference_t{doc, yyjson_mut_arr_get(v, idx)}; } void json_reference_t::append(const json_reference_t& elem) { wf::dassert(is_array()); yyjson_mut_arr_append(v, yyjson_mut_val_mut_copy(doc, elem.v)); } void json_reference_t::append(int value) { wf::dassert(is_array()); yyjson_mut_arr_add_int(doc, v, value); } void json_reference_t::append(unsigned int value) { wf::dassert(is_array()); yyjson_mut_arr_add_uint(doc, v, value); } void json_reference_t::append(int64_t value) { wf::dassert(is_array()); yyjson_mut_arr_add_sint(doc, v, value); } void json_reference_t::append(uint64_t value) { wf::dassert(is_array()); yyjson_mut_arr_add_uint(doc, v, value); } void json_reference_t::append(double value) { wf::dassert(is_array()); yyjson_mut_arr_add_real(doc, v, value); } void json_reference_t::append(bool value) { wf::dassert(is_array()); yyjson_mut_arr_add_bool(doc, v, value); } void json_reference_t::append(const std::string_view& value) { wf::dassert(is_array()); yyjson_mut_arr_add_strncpy(doc, v, value.data(), value.size()); } void json_reference_t::append(const char *value) { wf::dassert(is_array()); yyjson_mut_arr_add_strcpy(doc, v, value); } size_t json_reference_t::size() const { wf::dassert(is_array()); return yyjson_mut_arr_size(v); } bool json_reference_t::has_member(const std::string_view& key) const { return is_object() && (yyjson_mut_obj_getn(v, key.data(), key.size()) != NULL); } bool json_reference_t::is_object() const { return yyjson_mut_is_obj(v); } bool json_reference_t::is_null() const { return yyjson_mut_is_null(v); } json_reference_t json_reference_t::operator [](const char *key) const { return this->operator [](std::string_view{key}); } json_reference_t json_reference_t::operator [](const std::string_view& key) const { wf::dassert(is_object() || is_null(), "Trying to access into JSON value that is not an object!"); if (is_null()) { yyjson_mut_set_obj(v); } auto ptr = yyjson_mut_obj_getn(v, key.data(), key.size()); if (ptr != NULL) { return json_reference_t{doc, ptr}; } auto key_yy = yyjson_mut_strncpy(doc, key.data(), key.size()); auto value = yyjson_mut_null(doc); yyjson_mut_obj_add(v, key_yy, value); return json_reference_t{doc, value}; } std::vector json_reference_t::get_member_names() const { std::vector members; yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(v); while (yyjson_mut_obj_iter_has_next(&iter)) { auto key = yyjson_mut_obj_iter_next(&iter); members.push_back(yyjson_mut_get_str(key)); } return members; } static void copy_yyjson(yyjson_mut_doc *dst_doc, yyjson_mut_val *dst, yyjson_mut_val *src) { if (yyjson_mut_is_null(src)) { yyjson_mut_set_null(dst); return; } if (yyjson_mut_is_bool(src)) { yyjson_mut_set_bool(dst, yyjson_mut_get_bool(src)); return; } if (yyjson_mut_is_sint(src)) { yyjson_mut_set_sint(dst, yyjson_mut_get_sint(src)); return; } if (yyjson_mut_is_uint(src)) { yyjson_mut_set_uint(dst, yyjson_mut_get_uint(src)); return; } if (yyjson_mut_is_real(src)) { yyjson_mut_set_real(dst, yyjson_mut_get_real(src)); return; } if (yyjson_mut_is_str(src)) { // copy ownership of string to dst_doc auto val = yyjson_mut_val_mut_copy(dst_doc, src); yyjson_mut_set_str(dst, yyjson_mut_get_str(val)); return; } if (yyjson_mut_is_arr(src)) { yyjson_mut_set_arr(dst); yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(src); while (yyjson_mut_arr_iter_has_next(&iter)) { auto elem = yyjson_mut_arr_iter_next(&iter); elem = yyjson_mut_val_mut_copy(dst_doc, elem); yyjson_mut_arr_append(dst, elem); } return; } if (yyjson_mut_is_obj(src)) { yyjson_mut_set_obj(dst); yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(src); while (yyjson_mut_obj_iter_has_next(&iter)) { auto key = yyjson_mut_obj_iter_next(&iter); auto value = yyjson_mut_obj_iter_get_val(key); // Copy ownership to our doc key = yyjson_mut_val_mut_copy(dst_doc, key); value = yyjson_mut_val_mut_copy(dst_doc, value); yyjson_mut_obj_add(dst, key, value); } return; } wf::dassert(false, "Unsupported JSON type?"); } json_reference_t& json_reference_t::operator =(const json_reference_t& other) { copy_yyjson(doc, v, other.v); return *this; } json_reference_t& json_reference_t::operator =(const json_reference_t&& other) { // FIXME: maybe we can check whether the docs are the same or something to make this faster? copy_yyjson(doc, v, other.v); return *this; } // --------------------------------------- Basic data types support ------------------------------------------ json_reference_t& json_reference_t::operator =(const int& v) { yyjson_mut_set_sint(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const uint& v) { yyjson_mut_set_uint(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const int64_t& v) { yyjson_mut_set_sint(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const uint64_t& v) { yyjson_mut_set_uint(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const bool& v) { yyjson_mut_set_bool(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const double& v) { yyjson_mut_set_real(this->v, v); return *this; } json_reference_t& json_reference_t::operator =(const std::string_view& v) { // copy ownership of string to doc auto our_v = yyjson_mut_strncpy(doc, v.data(), v.size()); yyjson_mut_set_str(this->v, yyjson_mut_get_str(our_v)); return *this; } json_reference_t& json_reference_t::operator =(const char *v) { // copy ownership of string to doc auto our_v = yyjson_mut_strcpy(doc, v); yyjson_mut_set_str(this->v, yyjson_mut_get_str(our_v)); return *this; } bool json_reference_t::is_int() const { if (yyjson_mut_is_sint(v)) { return (std::numeric_limits::min() <= yyjson_mut_get_sint(v)) && (yyjson_mut_get_sint(v) <= std::numeric_limits::max()); } if (yyjson_mut_is_uint(v)) { return (yyjson_mut_get_uint(v) <= std::numeric_limits::max()); } return false; } json_reference_t::operator int() const { wf::dassert(is_int()); return yyjson_mut_get_int(v); } int json_reference_t::as_int() const { return (int)(*this); } bool json_reference_t::is_int64() const { if (yyjson_mut_is_uint(v)) { return yyjson_mut_get_uint(v) <= std::numeric_limits::max(); } return yyjson_mut_is_sint(v); } json_reference_t::operator int64_t() const { wf::dassert(is_int64()); if (yyjson_mut_is_uint(v)) { return yyjson_mut_get_uint(v); } else { return yyjson_mut_get_sint(v); } } int64_t json_reference_t::as_int64() const { return (int64_t)(*this); } bool json_reference_t::is_uint() const { return yyjson_mut_is_uint(v) && (yyjson_mut_get_uint(v) <= std::numeric_limits::max()); } json_reference_t::operator uint() const { wf::dassert(is_uint()); return yyjson_mut_get_uint(v); } unsigned int json_reference_t::as_uint() const { return (unsigned int)(*this); } bool json_reference_t::is_uint64() const { return yyjson_mut_is_uint(v); } json_reference_t::operator uint64_t() const { wf::dassert(is_uint64()); return yyjson_mut_get_uint(v); } uint64_t json_reference_t::as_uint64() const { return (uint64_t)(*this); } bool json_reference_t::is_bool() const { return yyjson_mut_is_bool(v); } json_reference_t::operator bool() const { wf::dassert(is_bool()); return yyjson_mut_get_bool(v); } bool json_reference_t::as_bool() const { return (bool)(*this); } bool json_reference_t::is_double() const { return yyjson_mut_is_num(v); } json_reference_t::operator double() const { wf::dassert(is_double()); return yyjson_mut_get_num(v); } double json_reference_t::as_double() const { return (double)(*this); } bool json_reference_t::is_string() const { return yyjson_mut_is_str(v); } json_reference_t::operator std::string() const { wf::dassert(is_string()); return std::string(yyjson_mut_get_str(v)); } std::string json_reference_t::as_string() const { return (std::string)*this; } void json_t::init() { this->doc = yyjson_mut_doc_new(NULL); this->v = yyjson_mut_null(this->doc); yyjson_mut_doc_set_root(doc, v); } json_t::json_t() { init(); } json_t::~json_t() { yyjson_mut_doc_free(doc); } json_t::json_t(const json_reference_t& ref) : json_t() { copy_yyjson(doc, this->v, ref.get_raw_value()); } json_t::json_t(yyjson_mut_doc *doc) { this->doc = doc; this->v = yyjson_mut_doc_get_root(this->doc); } json_t::json_t(const json_t& other) : json_reference_t() { *this = other; } json_t& json_t::operator =(const json_t& other) { if (this != &other) { yyjson_mut_doc_free(doc); init(); copy_yyjson(doc, this->v, other.v); } return *this; } json_t::json_t(json_t&& other) { *this = std::move(other); } json_t& json_t::operator =(json_t&& other) { if (this != &other) { yyjson_mut_doc_free(doc); this->doc = other.doc; this->v = other.v; other.doc = NULL; other.v = NULL; } return *this; } json_t::json_t(int v) : json_t() { *this = v; } json_t::json_t(unsigned v) : json_t() { *this = v; } json_t::json_t(int64_t v) : json_t() { *this = v; } json_t::json_t(uint64_t v) : json_t() { *this = v; } json_t::json_t(double v) : json_t() { *this = v; } json_t::json_t(const std::string_view& v) : json_t() { *this = v; } json_t::json_t(const char *v) : json_t() { *this = v; } json_t::json_t(bool v) : json_t() { *this = v; } json_t json_t::array() { json_t r; yyjson_mut_set_arr(r.v); return r; } json_t json_t::null() { json_t r; yyjson_mut_set_null(r.v); return r; } std::optional json_t::parse_string(const std::string_view& source, json_t& result) { yyjson_read_err error; auto doc = yyjson_read_opts((char*)source.data(), source.length(), 0, NULL, &error); if (!doc) { return std::string("Failed to parse JSON, error ") + std::to_string(error.code) + ": " + error.msg + std::string("(at offset ") + std::to_string(error.pos) + ")"; } result = json_t{yyjson_doc_mut_copy(doc, NULL)}; yyjson_doc_free(doc); return std::nullopt; } void json_t::map_serialized(std::function callback) const { size_t len; char *result = yyjson_mut_write(this->doc, 0, &len); callback(result, len); free(result); } std::string json_t::serialize() const { std::string result; map_serialized([&] (const char *source, size_t length) { result.append(source, length); }); return result; } } // namespace wf wayfire-0.10.0/src/default-config-backend.cpp0000664000175000017500000001442415053502647020705 0ustar dkondordkondor#include "wayfire/debug.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include #include #include // Added for wl_timer #include #include #include #include #define INOT_BUF_SIZE (sizeof(inotify_event) + NAME_MAX + 1) static std::string config_dir, config_file; wf::config::config_manager_t *cfg_manager; static int handle_config_updated(int fd, uint32_t mask, void *data); static int wd_cfg_dir, wd_cfg_file; static void add_watch(int fd) { wd_cfg_dir = inotify_add_watch(fd, config_dir.c_str(), IN_CREATE | IN_MOVED_TO); wd_cfg_file = inotify_add_watch(fd, config_file.c_str(), IN_CLOSE_WRITE); } static void reload_config() { wf::config::load_configuration_options_from_file(*cfg_manager, config_file); } static const char *CONFIG_FILE_ENV = "WAYFIRE_CONFIG_FILE"; namespace wf { class dynamic_ini_config_t : public wf::config_backend_t { private: struct wl_event_source *inotify_evtsrc = nullptr; int inotify_fd = -1; wf::wl_timer reload_timer; wf::option_wrapper_t config_reload_delay; public: /** * Schedules a configuration reload after a delay. * If a reload is already scheduled, it will be reset. */ void schedule_config_reload() { uint32_t delay_ms = config_reload_delay; LOGD("Scheduling configuration file reload in ", delay_ms, "ms"); reload_timer.set_timeout(delay_ms, [this] () { this->do_reload_config(); }); } void init(wl_display *display, config::config_manager_t& config, const std::string& cfg_file) override { cfg_manager = &config; config_file = choose_cfg_file(cfg_file); std::filesystem::path path = std::filesystem::absolute(config_file); config_dir = path.parent_path(); LOGI("Using config file: ", config_file.c_str()); setenv(CONFIG_FILE_ENV, config_file.c_str(), 1); config = wf::config::build_configuration( get_xml_dirs(), SYSCONFDIR "/wayfire/defaults.ini", config_file); // Load option after building the config, as the option is not present before that. config_reload_delay.load_option("workarounds/config_reload_delay"); if (check_auto_reload_option()) { inotify_fd = inotify_init1(IN_CLOEXEC); add_watch(inotify_fd); inotify_evtsrc = wl_event_loop_add_fd(wl_display_get_event_loop(display), inotify_fd, WL_EVENT_READABLE, handle_config_updated, this); } } std::string choose_cfg_file(const std::string& cmdline_cfg_file) { std::string env_cfg_file = nonull(getenv(CONFIG_FILE_ENV)); if (!cmdline_cfg_file.empty()) { if ((env_cfg_file != nonull(NULL)) && (cmdline_cfg_file != env_cfg_file)) { LOGW("Wayfire config file specified in the environment is ", "overridden by the command line arguments!"); } return cmdline_cfg_file; } if (env_cfg_file != nonull(NULL)) { return env_cfg_file; } std::string env_cfg_home = getenv("XDG_CONFIG_HOME") ?: (std::string(nonull(getenv("HOME"))) + "/.config"); std::string vendored_cfg_file = env_cfg_home + "/wayfire/wayfire.ini"; if (std::filesystem::exists(vendored_cfg_file)) { return vendored_cfg_file; } return env_cfg_home + "/wayfire.ini"; } bool check_auto_reload_option() { wf::option_wrapper_t auto_reload_config{"workarounds/auto_reload_config"}; if (auto_reload_config) { return true; } else if (inotify_evtsrc != nullptr) { wl_event_source_remove(inotify_evtsrc); inotify_evtsrc = nullptr; close(inotify_fd); inotify_fd = -1; reload_timer.disconnect(); } return false; } /** * Performs the actual configuration reload and emits the signal. * This is called by the wl_timer after the delay. */ void do_reload_config() { LOGD("Reloading configuration file now!"); reload_config(); wf::reload_config_signal ev; wf::get_core().emit(&ev); check_auto_reload_option(); // Re-check auto-reload option after config has been reloaded } }; } static int handle_config_updated(int fd, uint32_t mask, void *data) { if ((mask & WL_EVENT_READABLE) == 0) { return 0; } char buf[INOT_BUF_SIZE] __attribute__((aligned(alignof(inotify_event)))); bool should_reload = false; inotify_event *event; // Reading from the inotify FD is guaranteed to not read partial events. // From inotify(7): // Each successful read(2) returns a buffer containing // one or more [..] structures auto len = read(fd, buf, INOT_BUF_SIZE); if (len < 0) { return 0; } const auto cfg_file_basename = std::filesystem::path(config_file).filename().string(); for (char *ptr = buf; ptr < (buf + len); ptr += sizeof(inotify_event) + event->len) { event = reinterpret_cast(ptr); // We reload in two cases: // // 1. The config file itself was modified, or... should_reload |= event->wd == wd_cfg_file; // 2. The config file was moved nto or created inside the parent directory. if (event->len > 0) { // This is UB unless event->len > 0. auto name_matches = cfg_file_basename == event->name; if (name_matches) { inotify_rm_watch(fd, wd_cfg_file); wd_cfg_file = inotify_add_watch(fd, (config_dir + "/" + cfg_file_basename).c_str(), IN_CLOSE_WRITE); } should_reload |= name_matches; } } if (should_reload) { LOGD("Detected configuration file change."); auto self = reinterpret_cast(data); self->schedule_config_reload(); } return 0; } DECLARE_WAYFIRE_CONFIG_BACKEND(wf::dynamic_ini_config_t); wayfire-0.10.0/src/output/0000775000175000017500000000000015053502647015260 5ustar dkondordkondorwayfire-0.10.0/src/output/workspace-stream.cpp0000664000175000017500000001272315053502647021260 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include #include #include #include #include namespace wf { class workspace_stream_node_t::workspace_stream_instance_t : public scene:: render_instance_t { workspace_stream_node_t *self; std::vector instances; // True for each instance generated from a desktop environment view. std::vector is_desktop_environment; wf::point_t get_offset() { auto g = self->output->get_relative_geometry(); auto cws = self->output->wset()->get_current_workspace(); return wf::point_t{ (self->ws.x - cws.x) * g.width, (self->ws.y - cws.y) * g.height, }; } public: workspace_stream_instance_t(workspace_stream_node_t *self, scene::damage_callback push_damage) { this->self = self; auto translate_and_push_damage = [this, push_damage] (wf::region_t damage) { damage += -get_offset(); push_damage(damage); }; for (auto& output_node : wf::collect_output_nodes(wf::get_core().scene(), self->output)) { for (auto& ch : output_node->get_children()) { if (ch->is_enabled()) { auto view = node_to_view(ch); const bool is_de = (view && (view->role == wf::VIEW_ROLE_DESKTOP_ENVIRONMENT)); size_t num_generated = this->instances.size(); // We push the damage as-is for desktop environment views, because they are visible on // every workspace. ch->gen_render_instances(this->instances, is_de ? push_damage : translate_and_push_damage, self->output); // Mark whether the instances were generated from a desktop environment view num_generated = this->instances.size() - num_generated; for (size_t i = 0; i < num_generated; i++) { is_desktop_environment.push_back(is_de); } } } wf::dassert(instances.size() == is_desktop_environment.size(), "Setting de flag is wrong!"); } } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { auto bbox = self->get_bounding_box(); auto our_damage = damage & bbox; if (!our_damage.empty()) { auto offset = get_offset(); wf::render_target_t subtarget = target.translated(offset); our_damage += offset; for (size_t i = 0; i < instances.size(); i++) { if (is_desktop_environment[i]) { // Special handling: move everything to 'current workspace' so that panels and backgrounds // render at the correct position. our_damage -= offset; instances[i]->schedule_instructions(instructions, target, our_damage); our_damage += offset; } else { instances[i]->schedule_instructions(instructions, subtarget, our_damage); } } our_damage += -offset; damage ^= bbox; // Subtract the workspace because it will be filled // with the background color, so nothing below it // should be repainted anyway. instructions.push_back(scene::render_instruction_t{ .instance = this, .target = target, .damage = our_damage, }); } } void render(const wf::scene::render_instruction_t& data) override { static wf::option_wrapper_t background_color_opt{ "core/background_color" }; auto color = self->background.value_or(background_color_opt); data.pass->clear(data.damage, color); } void presentation_feedback(wf::output_t *output) override { for (auto& ch : this->instances) { ch->presentation_feedback(output); } } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { scene::compute_visibility_from_list(instances, output, visible, -get_offset()); } }; workspace_stream_node_t::workspace_stream_node_t( wf::output_t *output, wf::point_t workspace) : scene::node_t(false), output(output), ws(workspace) {} wf::geometry_t workspace_stream_node_t::get_bounding_box() { return output->get_relative_geometry(); } void workspace_stream_node_t::gen_render_instances( std::vector& instances, scene::damage_callback push_damage, wf::output_t *output) { instances.push_back(std::make_unique(this, push_damage)); } std::string workspace_stream_node_t::stringify() const { return "workspace-stream of output " + output->to_string() + " workspace " + std::to_string(ws.x) + "," + std::to_string(ws.y); } } // namespace wf wayfire-0.10.0/src/output/promotion-manager.hpp0000664000175000017500000000664415053502647021441 0ustar dkondordkondor#pragma once #include "wayfire/toplevel-view.hpp" #include #include #include #include namespace wf { /** * This class encapsulates functionality related to handling fullscreen views on a given workspace set. * When a fullscreen view is at the top of the stack, it should be 'promoted' above the top layer, where * panels reside. This is done by temporarily disabling the top layer, and then re-enabling it when the * fullscreen view is no longer fullscreen or no longer on top of all other views. * * Note that only views from the workspace layer are promoted, and views in the layers above do not affect * the view promotion algorithm. */ class promotion_manager_t { public: promotion_manager_t(wf::output_t *output) { this->output = output; wf::get_core().scene()->connect(&on_root_node_updated); output->connect(&on_view_fullscreen); output->connect(&on_view_unmap); } private: wf::output_t *output; wf::signal::connection_t on_root_node_updated = [=] (auto) { update_promotion_state(); }; signal::connection_t on_view_unmap = [=] (view_unmapped_signal *ev) { update_promotion_state(); }; wf::signal::connection_t on_view_fullscreen = [=] (auto) { update_promotion_state(); }; wayfire_toplevel_view find_top_visible_view(wf::scene::node_ptr root) { if (auto view = wf::node_to_view(root)) { if (!view->is_mapped() || !toplevel_cast(view)) { return nullptr; } if (output->wset()->view_visible_on(toplevel_cast(view), output->wset()->get_current_workspace())) { return toplevel_cast(view); } } for (auto& ch : root->get_children()) { if (ch->is_enabled()) { if (auto result = find_top_visible_view(ch)) { return result; } } } return nullptr; } void update_promotion_state() { wayfire_toplevel_view candidate = find_top_visible_view(output->wset()->get_node()); if (candidate && candidate->toplevel()->current().fullscreen) { start_promotion(); } else { stop_promotion(); } } bool promotion_active = false; // When a fullscreen view is on top of the stack, it should be displayed above // nodes in the TOP layer. To achieve this effect, we hide the TOP layer. void start_promotion() { if (promotion_active) { return; } promotion_active = true; scene::set_node_enabled(output->node_for_layer(scene::layer::TOP), false); wf::fullscreen_layer_focused_signal ev; ev.has_promoted = true; output->emit(&ev); LOGD("autohide panels"); } void stop_promotion() { if (!promotion_active) { return; } promotion_active = false; scene::set_node_enabled(output->node_for_layer(scene::layer::TOP), true); wf::fullscreen_layer_focused_signal ev; ev.has_promoted = false; output->emit(&ev); LOGD("restore panels"); } }; } wayfire-0.10.0/src/output/workarea.cpp0000664000175000017500000000454515053502647017607 0ustar dkondordkondor#include #include "wayfire/geometry.hpp" #include "wayfire/signal-provider.hpp" #include #include #include struct wf::output_workarea_manager_t::impl { wf::geometry_t current_workarea; std::vector anchors; output_t *output; wf::signal::connection_t on_configuration_changed; }; wf::output_workarea_manager_t::output_workarea_manager_t(output_t *output) { priv = std::make_unique(); priv->output = output; priv->current_workarea = output->get_relative_geometry(); priv->on_configuration_changed = [=] (auto) { this->reflow_reserved_areas(); }; output->connect(&priv->on_configuration_changed); } wf::output_workarea_manager_t::~output_workarea_manager_t() = default; wf::geometry_t wf::output_workarea_manager_t::get_workarea() { return priv->current_workarea; } void wf::output_workarea_manager_t::add_reserved_area(anchored_area *area) { priv->anchors.push_back(area); } void wf::output_workarea_manager_t::remove_reserved_area(anchored_area *area) { auto it = std::remove(priv->anchors.begin(), priv->anchors.end(), area); priv->anchors.erase(it, priv->anchors.end()); } void wf::output_workarea_manager_t::reflow_reserved_areas() { auto old_workarea = priv->current_workarea; priv->current_workarea = priv->output->get_relative_geometry(); for (auto a : priv->anchors) { if (a->reflowed) { a->reflowed(priv->current_workarea); } switch (a->edge) { case ANCHORED_EDGE_TOP: priv->current_workarea.y += a->reserved_size; // fallthrough case ANCHORED_EDGE_BOTTOM: priv->current_workarea.height -= a->reserved_size; break; case ANCHORED_EDGE_LEFT: priv->current_workarea.x += a->reserved_size; // fallthrough case ANCHORED_EDGE_RIGHT: priv->current_workarea.width -= a->reserved_size; break; } } wf::workarea_changed_signal data; data.output = priv->output; data.old_workarea = old_workarea; data.new_workarea = priv->current_workarea; if (data.old_workarea != data.new_workarea) { priv->output->emit(&data); } } wayfire-0.10.0/src/output/output-impl.hpp0000664000175000017500000000522015053502647020267 0ustar dkondordkondor#pragma once #include "output/promotion-manager.hpp" #include "wayfire/bindings.hpp" #include "wayfire/output.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include #include #include #include #include #include namespace wf { class output_impl_t : public output_t { private: std::shared_ptr nodes[(size_t)wf::scene::layer::ALL_LAYERS]; std::shared_ptr current_wset; std::unique_ptr promotion_manager; std::map key_map; std::map axis_map; std::map button_map; std::map activator_map; private: std::unordered_multiset active_plugins; wf::dimensions_t effective_size; public: output_impl_t(wlr_output *output, const wf::dimensions_t& effective_size); virtual ~output_impl_t(); /** * Implementations of the public APIs */ std::shared_ptr wset() override; void set_workspace_set(std::shared_ptr wset) override; std::shared_ptr node_for_layer( wf::scene::layer layer) const override; bool can_activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) override; bool can_activate_plugin(uint32_t caps, uint32_t flags = 0) override; bool activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags = 0) override; bool deactivate_plugin(wf::plugin_activation_data_t *owner) override; void cancel_active_plugins() override; bool is_plugin_active(std::string owner_name) const override; wf::dimensions_t get_screen_size() const override; void add_key(option_sptr_t key, wf::key_callback*) override; void add_axis(option_sptr_t axis, wf::axis_callback*) override; void add_button(option_sptr_t button, wf::button_callback*) override; void add_activator(option_sptr_t activator, wf::activator_callback*) override; void rem_binding(void *callback) override; /** Set the effective resolution of the output */ void set_effective_size(const wf::dimensions_t& size); }; /** * Set the last focused timestamp of the view to now. */ void update_focus_timestamp(wayfire_view view); void priv_render_manager_clear_instances(wf::render_manager *manager); void priv_render_manager_start_rendering(wf::render_manager *manager); } wayfire-0.10.0/src/output/output.cpp0000664000175000017500000002657315053502647017341 0ustar dkondordkondor#include "output-impl.hpp" #include "wayfire/bindings.hpp" #include "wayfire/core.hpp" #include "wayfire/bindings-repository.hpp" #include "wayfire/debug.hpp" #include "wayfire/output.hpp" #include "wayfire/plugin.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene.hpp" #include "wayfire/view.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/render-manager.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/workspace-set.hpp" #include #include #include #include #include #include #include #include wf::output_t::output_t() = default; wf::output_impl_t::output_impl_t(wlr_output *handle, const wf::dimensions_t& effective_size) { this->set_effective_size(effective_size); this->handle = handle; auto& root = wf::get_core().scene(); for (size_t layer = 0; layer < (size_t)scene::layer::ALL_LAYERS; layer++) { nodes[layer] = std::make_shared(this); scene::add_back(root->layers[layer], nodes[layer]); } workarea = std::make_unique(this); this->set_workspace_set(workspace_set_t::create()); render = std::make_unique(this); promotion_manager = std::make_unique(this); } std::shared_ptr wf::output_impl_t::node_for_layer( wf::scene::layer layer) const { return nodes[(int)layer]; } std::shared_ptr wf::output_impl_t::wset() { return current_wset; } void wf::output_impl_t::set_workspace_set(std::shared_ptr wset) { if (current_wset == wset) { return; } wf::dassert(wset != nullptr, "Workspace set should not be null!"); if (this->current_wset) { this->current_wset->set_visible(false); } wset->attach_to_output(this); wset->set_visible(true); { // Delay freeing old_wset until we can safely report the new value auto old_wset = std::move(this->current_wset); this->current_wset = wset; } workspace_set_changed_signal data; data.new_wset = wset; data.output = this; this->emit(&data); wf::get_core().seat->refocus(); } std::string wf::output_t::to_string() const { return handle->name; } wf::output_t::~output_t() {} wf::output_impl_t::~output_impl_t() { auto& bindings = wf::get_core().bindings; for (auto& [_, act] : this->key_map) { bindings->rem_binding(&act); } for (auto& [_, act] : this->button_map) { bindings->rem_binding(&act); } for (auto& [_, act] : this->axis_map) { bindings->rem_binding(&act); } for (auto& [_, act] : this->activator_map) { bindings->rem_binding(&act); } for (auto& layer_root : nodes) { layer_root->set_children_list({}); scene::remove_child(layer_root); } priv_render_manager_clear_instances(render.get()); } void wf::output_impl_t::set_effective_size(const wf::dimensions_t& size) { this->effective_size = size; } wf::dimensions_t wf::output_impl_t::get_screen_size() const { return this->effective_size; } wf::geometry_t wf::output_t::get_relative_geometry() const { auto size = get_screen_size(); return { 0, 0, size.width, size.height }; } wf::geometry_t wf::output_t::get_layout_geometry() const { wlr_box box; wlr_output_layout_get_box( wf::get_core().output_layout->get_handle(), handle, &box); if (wlr_box_empty(&box)) { // Can happen when initializing the output return {0, 0, handle->width, handle->height}; } else { return box; } } void wf::output_t::ensure_pointer(bool center) const { auto ptr = wf::get_core().get_cursor_position(); if (!center && (get_layout_geometry() & wf::point_t{(int)ptr.x, (int)ptr.y})) { return; } auto lg = get_layout_geometry(); wf::pointf_t target = { lg.x + lg.width / 2.0, lg.y + lg.height / 2.0, }; wf::get_core().set_cursor("default"); wf::get_core().warp_cursor(target); } wf::pointf_t wf::output_t::get_cursor_position() const { auto og = get_layout_geometry(); auto gc = wf::get_core().get_cursor_position(); return {gc.x - og.x, gc.y - og.y}; } bool wf::output_t::ensure_visible(wayfire_view v) { auto bbox = v->get_bounding_box(); auto g = this->get_relative_geometry(); /* Compute the percentage of the view which is visible */ auto intersection = wf::geometry_intersection(bbox, g); double area = 1.0 * intersection.width * intersection.height; area /= 1.0 * bbox.width * bbox.height; if (area >= 0.1) /* View is somewhat visible, no need for anything special */ { return false; } /* Otherwise, switch the workspace so the view gets maximum exposure */ int dx = bbox.x + bbox.width / 2; int dy = bbox.y + bbox.height / 2; int dvx = std::floor(1.0 * dx / g.width); int dvy = std::floor(1.0 * dy / g.height); auto cws = wset()->get_current_workspace(); wset()->request_workspace(cws + wf::point_t{dvx, dvy}); return true; } bool wf::output_impl_t::can_activate_plugin(uint32_t caps, uint32_t flags) { if (this->inhibited && !(flags & wf::PLUGIN_ACTIVATION_IGNORE_INHIBIT)) { return false; } for (auto act_owner : active_plugins) { bool compatible = ((act_owner->capabilities & caps) == 0); if (!compatible) { return false; } } return true; } bool wf::output_impl_t::can_activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags) { if (!owner) { return false; } if (active_plugins.find(owner) != active_plugins.end()) { return flags & wf::PLUGIN_ACTIVATE_ALLOW_MULTIPLE; } return can_activate_plugin(owner->capabilities, flags); } bool wf::output_impl_t::activate_plugin(wf::plugin_activation_data_t *owner, uint32_t flags) { if (!can_activate_plugin(owner, flags)) { return false; } if (active_plugins.find(owner) != active_plugins.end()) { LOGD("output ", handle->name, ": activate plugin ", owner->name, " again"); } else { LOGD("output ", handle->name, ": activate plugin ", owner->name); wf::output_plugin_activated_changed_signal data; data.output = this; data.plugin_name = owner->name; data.activated = true; this->emit(&data); wf::get_core().emit(&data); } active_plugins.insert(owner); return true; } bool wf::output_impl_t::deactivate_plugin(wf::plugin_activation_data_t *owner) { auto it = active_plugins.find(owner); if (it == active_plugins.end()) { return true; } active_plugins.erase(it); LOGD("output ", handle->name, ": deactivate plugin ", owner->name); if (active_plugins.count(owner) == 0) { wf::output_plugin_activated_changed_signal data; data.output = this; data.plugin_name = owner->name; data.activated = false; this->emit(&data); wf::get_core().emit(&data); return true; } return false; } void wf::output_impl_t::cancel_active_plugins() { std::vector ifaces; for (auto p : active_plugins) { if (p->cancel) { ifaces.push_back(p); } } for (auto p : ifaces) { p->cancel(); } } bool wf::output_impl_t::is_plugin_active(std::string name) const { for (auto act : active_plugins) { if (act && (act->name == name)) { return true; } } return false; } void wf::output_t::set_inhibited(bool inhibited) { this->inhibited = inhibited; if (inhibited) { cancel_active_plugins(); } } bool wf::output_t::is_inhibited() const { return this->inhibited; } namespace wf { void output_impl_t::add_key(option_sptr_t key, wf::key_callback *callback) { this->key_map[callback] = [=] (const wf::keybinding_t& kb) { if (this != wf::get_core().seat->get_active_output()) { return false; } return (*callback)(kb); }; wf::get_core().bindings->add_key(key, &key_map[callback]); } void output_impl_t::add_axis(option_sptr_t axis, wf::axis_callback *callback) { this->axis_map[callback] = [=] (wlr_pointer_axis_event *ax) { if (this != wf::get_core().seat->get_active_output()) { return false; } return (*callback)(ax); }; wf::get_core().bindings->add_axis(axis, &axis_map[callback]); } void output_impl_t::add_button(option_sptr_t button, wf::button_callback *callback) { this->button_map[callback] = [=] (const wf::buttonbinding_t& kb) { if (this != wf::get_core().seat->get_active_output()) { return false; } return (*callback)(kb); }; wf::get_core().bindings->add_button(button, &button_map[callback]); } void output_impl_t::add_activator( option_sptr_t activator, wf::activator_callback *callback) { this->activator_map[callback] = [=] (const wf::activator_data_t& act) { if (act.source == activator_source_t::HOTSPOT) { auto pos = wf::get_core().get_cursor_position(); auto wo = wf::get_core().output_layout->get_output_at(pos.x, pos.y); if (this != wo) { return false; } } else if (this != wf::get_core().seat->get_active_output()) { return false; } return (*callback)(act); }; wf::get_core().bindings->add_activator(activator, &activator_map[callback]); } template void remove_binding(std::map& map, Type *cb) { if (map.count(cb)) { wf::get_core().bindings->rem_binding(&map[cb]); map.erase(cb); } } void wf::output_impl_t::rem_binding(void *callback) { remove_binding(key_map, (key_callback*)callback); remove_binding(button_map, (button_callback*)callback); remove_binding(axis_map, (axis_callback*)callback); remove_binding(activator_map, (activator_callback*)callback); } wayfire_view get_active_view_for_output(wf::output_t *output) { if (output == wf::get_core().seat->get_active_output()) { return wf::get_core().seat->get_active_view(); } return nullptr; } void collect_output_nodes_recursive(wf::scene::node_ptr root, wf::output_t *output, std::vector>& result) { if (!root->is_enabled()) { return; } if (auto output_node = std::dynamic_pointer_cast(root)) { if (output_node->get_output() == output) { result.push_back(output_node); } return; } for (auto& ch : root->get_children()) { collect_output_nodes_recursive(ch, output, result); } } std::vector> collect_output_nodes( wf::scene::node_ptr root, wf::output_t *output) { std::vector> res; collect_output_nodes_recursive(root, output, res); return res; } } // namespace wf wayfire-0.10.0/src/output/render-manager.cpp0000664000175000017500000011274315053502647020663 0ustar dkondordkondor#include "wayfire/render-manager.hpp" #include "pixman.h" #include "wayfire/config-backend.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/view.hpp" #include "wayfire/output.hpp" #include "wayfire/util.hpp" #include "../main.hpp" #include "wayfire/workspace-set.hpp" // IWYU pragma: keep #include #include #include #include #include #include #include #include #include namespace wf { /** * swapchain_damage_manager_t is responsible for tracking the damage and managing the swapchain on the * given output. */ struct swapchain_damage_manager_t { wf::option_wrapper_t force_frame_sync{"workarounds/force_frame_sync"}; wf::wl_listener_wrapper on_needs_frame; wf::wl_listener_wrapper on_damage; wf::wl_listener_wrapper on_gamma_changed; wf::region_t frame_damage; wlr_output *output; wlr_damage_ring damage_ring; output_t *wo; bool pending_gamma_lut = false; std::unique_ptr instance_manager; void start_rendering() { scene::damage_callback push_damage = [=] (wf::region_t region) { // Damage is pushed up to the root in root coordinate system, // we need it in output-buffer-local coordinate system. region += -wf::origin(wo->get_layout_geometry()); region = wo->render->get_target_framebuffer().framebuffer_region_from_geometry_region(region); this->damage_buffer(region, true); }; std::vector nodes; nodes.push_back(wf::get_core().scene()); instance_manager = std::make_unique(nodes, push_damage, wo); instance_manager->set_visibility_region(wo->get_layout_geometry()); } swapchain_damage_manager_t(output_t *output) { this->output = output->handle; this->wo = output; output->connect(&output_mode_changed); wlr_damage_ring_init(&damage_ring); on_needs_frame.set_callback([=] (void*) { schedule_repaint(); }); on_damage.set_callback([=] (void *data) { auto ev = static_cast(data); wf::region_t rotated{ev->damage}; int width, height; wlr_output_transformed_resolution(this->output, &width, &height); wlr_region_transform(rotated.to_pixman(), rotated.to_pixman(), wlr_output_transform_invert(this->output->transform), width, height); damage_buffer(rotated, true); }); on_gamma_changed.set_callback([=] (void *data) { auto event = (const wlr_gamma_control_manager_v1_set_gamma_event*)data; if (event->output == this->output) { pending_gamma_lut = true; schedule_repaint(); } }); on_needs_frame.connect(&output->handle->events.needs_frame); on_damage.connect(&output->handle->events.damage); on_gamma_changed.connect(&wf::get_core().protocols.gamma_v1->events.set_gamma); } ~swapchain_damage_manager_t() { wlr_damage_ring_finish(&damage_ring); } wf::signal::connection_t output_mode_changed = [=] (wf::output_configuration_changed_signal *ev) { if (!ev || !ev->changed_fields) { return; } schedule_repaint(); instance_manager->set_visibility_region(wo->get_layout_geometry()); }; /** * Damage the given region */ void damage_buffer(const wf::region_t& region, bool repaint) { if (region.empty()) { return; } frame_damage |= region; wlr_damage_ring_add(&damage_ring, region.to_pixman()); if (repaint) { schedule_repaint(); } } void damage_buffer(const wf::geometry_t& box, bool repaint) { if ((box.width <= 0) || (box.height <= 0)) { return; } /* Wlroots expects damage after scaling */ frame_damage |= box; wlr_damage_ring_add_box(&damage_ring, &box); if (repaint) { schedule_repaint(); } } int constant_redraw_counter = 0; void set_redraw_always(bool always) { constant_redraw_counter += (always ? 1 : -1); if (constant_redraw_counter > 1) /* no change, exit */ { return; } if (constant_redraw_counter < 0) { LOGE("constant_redraw_counter got below 0!"); constant_redraw_counter = 0; return; } schedule_repaint(); } // A struct which contains the necessary structures for painting one frame struct frame_object_t { wlr_output_state state; wlr_buffer *buffer = NULL; int buffer_age; frame_object_t() { wlr_output_state_init(&state); } ~frame_object_t() { wlr_output_state_finish(&state); } frame_object_t(const frame_object_t&) = delete; frame_object_t(frame_object_t&&) = delete; frame_object_t& operator =(const frame_object_t&) = delete; frame_object_t& operator =(frame_object_t&&) = delete; }; bool acquire_next_swapchain_buffer(frame_object_t& frame) { if (!wlr_output_configure_primary_swapchain(output, &frame.state, &output->swapchain)) { LOGE("Failed to configure primary output swapchain for output ", nonull(output->name)); return false; } frame.buffer = wlr_swapchain_acquire(output->swapchain); if (!frame.buffer) { LOGE("Failed to acquire buffer from the output swapchain!"); return false; } return true; } bool try_apply_gamma(frame_object_t& next_frame) { if (!pending_gamma_lut) { return true; } pending_gamma_lut = false; auto gamma_control = wlr_gamma_control_manager_v1_get_control(wf::get_core().protocols.gamma_v1, output); if (!wlr_gamma_control_v1_apply(gamma_control, &next_frame.state)) { LOGE("Failed to apply gamma to output state!"); return false; } if (!wlr_output_test_state(output, &next_frame.state)) { wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); } return true; } bool force_next_frame = false; /** * Start rendering a new frame. * If the operation could not be started, or if a new frame is not needed, the function returns false. * If the operation succeeds, true is returned, and the output (E)GL context is bound. */ std::unique_ptr start_frame() { auto buffer_extents = this->get_buffer_extents(); pixman_region32_intersect_rect(&damage_ring.current, &damage_ring.current, buffer_extents.x, buffer_extents.y, buffer_extents.width, buffer_extents.height); const bool needs_swap = force_next_frame | output->needs_frame | pixman_region32_not_empty(&damage_ring.current) | (constant_redraw_counter > 0); force_next_frame = false; if (!needs_swap) { return {}; } auto next_frame = std::make_unique(); next_frame->state.committed |= WLR_OUTPUT_STATE_DAMAGE; if (!try_apply_gamma(*next_frame)) { return {}; } if (!acquire_next_swapchain_buffer(*next_frame)) { return {}; } // Accumulate damage now, when we are sure we will render the frame. // Doing this earlier may mean that the damage from the previous frames // creeps into the current frame damage, if we had skipped a frame. accumulate_damage(next_frame.get()); return next_frame; } void swap_buffers(std::unique_ptr next_frame, const wf::region_t& swap_damage) { /* If force frame sync option is set, call glFinish to block until * the GPU finishes rendering. This can work around some driver * bugs, but may cause more resource usage. */ if (force_frame_sync) { wf::gles::run_in_context_if_gles([&] { GL_CALL(glFinish()); }); } frame_damage.clear(); wlr_output_state_set_buffer(&next_frame->state, next_frame->buffer); wlr_output_state_set_damage(&next_frame->state, swap_damage.to_pixman()); wlr_buffer_unlock(next_frame->buffer); if (!wlr_output_test_state(output, &next_frame->state)) { LOGE("Output test failed!"); return; } if (!wlr_output_commit_state(output, &next_frame->state)) { LOGE("Output commit failed!"); return; } } /** * Accumulate damage from last frame. * Needs to be called after make_current() */ void accumulate_damage(frame_object_t *next_frame) { wf::region_t ring_damage; wlr_damage_ring_rotate_buffer(&damage_ring, next_frame->buffer, ring_damage.to_pixman()); frame_damage |= ring_damage; if (runtime_config.no_damage_track) { frame_damage |= get_buffer_extents(); } } /** * Return the damage that has been scheduled for the next frame up to now, * or, if in a repaint, the damage for the current frame */ wf::region_t get_scheduled_damage(const wf::render_target_t& target) { return target.geometry_region_from_framebuffer_region(frame_damage) & target.geometry; } /** * Schedule a frame for the output */ void schedule_repaint() { wlr_output_schedule_frame(output); force_next_frame = true; } /** * Get the full size of the buffer for damage tracking in output-buffer-local coordinate system */ wlr_box get_buffer_extents() const { return {0, 0, output->width, output->height}; } /** * Same as render_manager::get_ws_box() */ wlr_box get_ws_box(wf::point_t ws) const { auto current = wo->wset()->get_current_workspace(); wlr_box box = wo->get_relative_geometry(); box.x = (ws.x - current.x) * box.width; box.y = (ws.y - current.y) * box.height; return box; } /** * Same as render_manager::damage_whole() */ void damage_whole() { damage_buffer(get_buffer_extents(), true); } wf::wl_idle_call idle_damage; /** * Same as render_manager::damage_whole_idle() */ void damage_whole_idle() { damage_whole(); if (!idle_damage.is_connected()) { idle_damage.run_once([&] () { damage_whole(); }); } } }; /** * Very simple class to manage effect hooks */ struct effect_hook_manager_t { using effect_container_t = wf::safe_list_t; effect_container_t effects[OUTPUT_EFFECT_TOTAL]; void add_effect(effect_hook_t *hook, output_effect_type_t type) { effects[type].push_back(hook); } bool can_scanout() const { return effects[OUTPUT_EFFECT_OVERLAY].size() == 0 && effects[OUTPUT_EFFECT_POST].size() == 0; } void rem_effect(effect_hook_t *hook) { for (int i = 0; i < OUTPUT_EFFECT_TOTAL; i++) { effects[i].remove_all(hook); } } void run_effects(output_effect_type_t type) { effects[type].for_each([] (auto effect) { (*effect)(); }); } }; /** * A class to manage and run postprocessing effects */ struct postprocessing_manager_t { using post_container_t = wf::safe_list_t; post_container_t post_effects; wf::auxilliary_buffer_t post_buffers[2]; /* Buffer to which other operations render to */ static constexpr uint32_t default_out_buffer = 0; output_t *output; uint32_t output_width, output_height; postprocessing_manager_t(output_t *output) { this->output = output; } wf::render_buffer_t final_target; void set_current_buffer(wlr_buffer *buffer) { final_target = wf::render_buffer_t{ buffer, wf::dimensions_t{output->handle->width, output->handle->height} }; } void allocate(int width, int height) { if (post_effects.size() == 0) { return; } output_width = width; output_height = height; for (auto& buffer : post_buffers) { buffer.allocate({width, height}); } } void add_post(post_hook_t *hook) { post_effects.push_back(hook); output->render->damage_whole_idle(); } void rem_post(post_hook_t *hook) { post_effects.remove_all(hook); output->render->damage_whole_idle(); } /* Run all postprocessing effects, rendering to alternating buffers and * finally to the screen. * * NB: 2 buffers just aren't enough. We render to the zero buffer, and then * we alternately render to the second and the third. The reason: We track * damage. So, we need to keep the whole buffer each frame. */ void run_post_effects() { int cur_idx = 0; post_effects.for_each([&] (auto post) -> void { int next_idx = 1 - cur_idx; wf::render_buffer_t dst_buffer = (post == post_effects.back() ? final_target : post_buffers[next_idx].get_renderbuffer()); (*post)(post_buffers[cur_idx], dst_buffer); cur_idx = next_idx; }); } wf::render_target_t get_target_framebuffer() const { wf::render_target_t fb{ post_effects.size() > 0 ? post_buffers[default_out_buffer].get_renderbuffer() : final_target }; fb.geometry = output->get_relative_geometry(); fb.wl_transform = output->handle->transform; fb.scale = output->handle->scale; return fb; } bool can_scanout() const { return post_effects.size() == 0; } }; /** * Responsible for attaching depth buffers to framebuffers. * It keeps at most 3 depth buffers at any given time to conserve * resources. */ class depth_buffer_manager_t { public: void ensure_depth_buffer(int fb, int width, int height) { /* If the backend doesn't have its own framebuffer, then the * framebuffer is created with a depth buffer. */ if (required_counter <= 0) { return; } attach_buffer(fb, width, height); } void frame_done() { if (currently_attached_fb == INVALID_FB) { return; } wf::gles::run_in_context_if_gles([&] { // Detach depth buffer GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, currently_attached_fb)); GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); }); currently_attached_fb = INVALID_FB; } void set_required(bool require) { required_counter += require ? 1 : -1; if (required_counter <= 0) { free_buffer(); } } depth_buffer_manager_t() = default; ~depth_buffer_manager_t() { free_buffer(); } depth_buffer_manager_t(const depth_buffer_manager_t &) = delete; depth_buffer_manager_t(depth_buffer_manager_t &&) = delete; depth_buffer_manager_t& operator =(const depth_buffer_manager_t&) = delete; depth_buffer_manager_t& operator =(depth_buffer_manager_t&&) = delete; private: int required_counter = 0; static constexpr int INVALID_FB = 0; static constexpr int INVALID_TEX = 0; int currently_attached_fb = INVALID_FB; struct depth_buffer_t { GLuint tex = INVALID_TEX; int width = 0; int height = 0; } buffer; void free_buffer() { currently_attached_fb = INVALID_FB; if (buffer.tex != INVALID_TEX) { wf::gles::run_in_context([&] { GL_CALL(glDeleteTextures(1, &buffer.tex)); buffer.tex = INVALID_TEX; }); } } void attach_buffer(int fb, int width, int height) { if ((buffer.width != width) || (buffer.height != height)) { free_buffer(); wf::gles::run_in_context_if_gles([&] { GL_CALL(glGenTextures(1, &buffer.tex)); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer.tex)); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); }); buffer.width = width; buffer.height = height; } wf::gles::run_in_context_if_gles([&] { GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer.tex)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, fb)); GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.tex, 0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); currently_attached_fb = fb; }); } }; /** * A struct which manages the repaint delay. * * The repaint delay is a technique to potentially lower the input latency. * * It works by delaying Wayfire's repainting after getting the next frame event. * During this time the clients have time to update and submit their buffers. * If they manage this on time, the next frame will contain the already new * application contents, otherwise, the changes are visible after 1 more frame. * * The repaint delay however should be chosen so that Wayfire's own rendering * starts early enough for the next vblank, otherwise, the framerate will suffer. * * Calculating the maximal time Wayfire needs for rendering is very hard, and * and can change depending on active plugins, number of opened windows, etc. * * Thus, we need to dynamically guess this time based on the previous frames. * Currently, the following algorithm is implemented: * * Initially, the repaint delay is zero. * * If at some point Wayfire skips a frame, the delay is assumed too big and * reduced by `2^i`, where `i` is the amount of consecutive skipped frames. * * If Wayfire renders in time for `increase_window` milliseconds, then the * delay is increased by one. If the next frame is delayed, then * `increase_window` is doubled, otherwise, it is halved * (but it must stay between `MIN_INCREASE_WINDOW` and `MAX_INCREASE_WINDOW`). */ struct repaint_delay_manager_t { repaint_delay_manager_t(wf::output_t *output) { on_present.set_callback([&] (void *data) { auto ev = static_cast(data); this->refresh_nsec = ev->refresh; }); on_present.connect(&output->handle->events.present); } /** * The next frame will be skipped. */ void skip_frame() { // Mark last frame as invalid, because we don't know how much time // will pass until next frame last_pageflip = -1; } /** * Starting a new frame. */ void start_frame() { if (last_pageflip == -1) { last_pageflip = get_current_time(); return; } const int64_t refresh = this->refresh_nsec / 1e6; const int64_t on_time_thresh = refresh * 1.5; const int64_t last_frame_len = get_current_time() - last_pageflip; if (last_frame_len <= on_time_thresh) { // We rendered last frame on time if (get_current_time() - last_increase >= increase_window) { increase_window = clamp(int64_t(increase_window * 0.75), MIN_INCREASE_WINDOW, MAX_INCREASE_WINDOW); update_delay(+1); reset_increase_timer(); // If we manage the next few frames, then we have reached a new // stable state expand_inc_window_on_miss = 20; } else { --expand_inc_window_on_miss; } // Stop exponential decrease consecutive_decrease = 1; } else { // We missed last frame. update_delay(-consecutive_decrease); // Next decrease should be faster consecutive_decrease = clamp(consecutive_decrease * 2, 1, 32); // Next increase should be tried after a longer interval if (expand_inc_window_on_miss >= 0) { increase_window = clamp(increase_window * 2, MIN_INCREASE_WINDOW, MAX_INCREASE_WINDOW); } reset_increase_timer(); } last_pageflip = get_current_time(); } /** * @return The delay in milliseconds for the current frame. */ int get_delay() { return delay; } private: int delay = 0; void update_delay(int delta) { int config_delay = std::max(0, (int)(this->refresh_nsec / 1e6) - max_render_time); int min = 0; int max = config_delay; if (max_render_time == -1) { max = 0; } else if (!dynamic_delay) { min = config_delay; max = config_delay; } delay = clamp(delay + delta, min, max); } void reset_increase_timer() { last_increase = get_current_time(); } static constexpr int64_t MIN_INCREASE_WINDOW = 200; // 200 ms static constexpr int64_t MAX_INCREASE_WINDOW = 30'000; // 30s int64_t increase_window = MIN_INCREASE_WINDOW; int64_t last_increase = 0; // > 0 => Increase increase_window int64_t expand_inc_window_on_miss = 0; // Expontential decrease in case of missed frames int32_t consecutive_decrease = 1; // Time of last frame int64_t last_pageflip = -1; // -1 is invalid int64_t refresh_nsec; wf::option_wrapper_t max_render_time{"core/max_render_time"}; wf::option_wrapper_t dynamic_delay{"workarounds/dynamic_repaint_delay"}; wf::wl_listener_wrapper on_present; }; class wf::render_manager::impl { public: wf::wl_listener_wrapper on_frame; wf::wl_timer repaint_timer; output_t *output; wf::region_t swap_damage; std::unique_ptr damage_manager; std::unique_ptr effects; std::unique_ptr postprocessing; std::unique_ptr depth_buffer_manager; std::unique_ptr delay_manager; wf::option_wrapper_t background_color_opt; std::unique_ptr current_pass; wf::option_wrapper_t icc_profile; wlr_color_transform *get_color_transform() { return icc_color_transform; } impl(output_t *o) : output(o), env_allow_scanout(check_scanout_enabled()) { damage_manager = std::make_unique(o); effects = std::make_unique(); postprocessing = std::make_unique(o); depth_buffer_manager = std::make_unique(); delay_manager = std::make_unique(o); on_frame.set_callback([&] (void*) { /* If the session is not active, don't paint. * This is the case when e.g. switching to another tty */ if (wf::get_core().session && !wf::get_core().session->active) { return; } delay_manager->start_frame(); auto repaint_delay = delay_manager->get_delay(); // Leave a bit of time for clients to render, see // https://github.com/swaywm/sway/pull/4588 if (repaint_delay < 1) { output->handle->frame_pending = false; paint(); } else { output->handle->frame_pending = true; repaint_timer.set_timeout(repaint_delay, [=] () { output->handle->frame_pending = false; paint(); }); } frame_done_signal ev; output->emit(&ev); }); on_frame.connect(&output->handle->events.frame); background_color_opt.load_option("core/background_color"); background_color_opt.set_callback([=] () { damage_manager->damage_whole_idle(); }); damage_manager->schedule_repaint(); auto section = wf::get_core().config_backend->get_output_section(output->handle); icc_profile.load_option(section->get_name() + "/icc_profile"); icc_profile.set_callback([=] () { reload_icc_profile(); damage_manager->damage_whole_idle(); }); reload_icc_profile(); } wlr_color_transform *icc_color_transform = NULL; wlr_buffer_pass_options pass_opts{}; void reload_icc_profile() { if (icc_profile.value().empty()) { set_icc_transform(nullptr); return; } if (!wf::get_core().is_vulkan()) { LOGW("ICC profiles in core are only supported with the vulkan renderer. " "For GLES2, make sure to enable the vk-color-management plugin."); } auto path = std::filesystem::path{icc_profile.value()}; if (std::filesystem::is_regular_file(path)) { // Read binary file into vector buffer std::ifstream file(icc_profile.value(), std::ios::binary); std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); auto transform = wlr_color_transform_init_linear_to_icc(buffer.data(), buffer.size()); if (!transform) { LOGE("Failed to load ICC transform from ", icc_profile.value()); set_icc_transform(nullptr); return; } else { LOGI("Loaded ICC transform from ", icc_profile.value(), " for output ", output->to_string()); } set_icc_transform(transform); } } void set_icc_transform(wlr_color_transform *transform) { if (icc_color_transform) { wlr_color_transform_unref(icc_color_transform); } icc_color_transform = transform; } ~impl() { set_icc_transform(nullptr); } const bool env_allow_scanout; static bool check_scanout_enabled() { const char *env_scanout = getenv("WAYFIRE_DISABLE_DIRECT_SCANOUT"); bool env_allow_scanout = (env_scanout == nullptr) || (!strcmp(env_scanout, "0")); if (!env_allow_scanout) { LOGC(SCANOUT, "Scanout disabled by environment variable."); } return env_allow_scanout; } int output_inhibit_counter = 0; void add_inhibit(bool add) { output_inhibit_counter += add ? 1 : -1; if (output_inhibit_counter == 0) { damage_manager->damage_whole_idle(); wf::output_start_rendering_signal data; data.output = output; output->emit(&data); } } /* Actual rendering functions */ /** * Try to directly scanout a view on the output, thereby skipping rendering * entirely. * * @return True if scanout was successful, False otherwise. */ bool do_direct_scanout() { const bool can_scanout = !output_inhibit_counter && effects->can_scanout() && postprocessing->can_scanout() && wlr_output_is_direct_scanout_allowed(output->handle) && (icc_color_transform == nullptr); if (!can_scanout || !env_allow_scanout) { return false; } auto result = scene::try_scanout_from_list( damage_manager->instance_manager->get_instances(), output); return result == scene::direct_scanout::SUCCESS; } /** * Return the swap damage if called from overlay or postprocessing * effect callbacks or empty region otherwise. */ wf::region_t get_swap_damage() { return swap_damage; } /** * Render an output. Either calls the built-in renderer, or the render hook * of a plugin * * @return The swap damage in buffer-local coordinates. */ wf::region_t start_output_pass( std::unique_ptr& next_frame) { render_pass_params_t params; params.instances = &damage_manager->instance_manager->get_instances(); params.target = postprocessing->get_target_framebuffer().translated( wf::origin(output->get_layout_geometry())); params.damage = damage_manager->get_scheduled_damage(params.target); params.background_color = background_color_opt; params.reference_output = this->output; params.renderer = output->handle->renderer; params.flags = RPASS_CLEAR_BACKGROUND | RPASS_EMIT_SIGNALS; pass_opts.timer = NULL; // TODO: do we care about this? could be useful for dynamic frame scheduling pass_opts.color_transform = icc_color_transform; params.pass_opts = &pass_opts; this->current_pass = std::make_unique(params); auto total_damage = current_pass->run_partial(); if (runtime_config.damage_debug) { /* Clear the screen to yellow, so that the repainted parts are visible */ wf::region_t yellow = params.target.geometry; yellow ^= total_damage; total_damage |= params.target.geometry; current_pass->clear(yellow, {1, 1, 0, 1}); } // Transform to buffer-local damage total_damage = params.target.framebuffer_region_from_geometry_region(total_damage); total_damage &= damage_manager->get_buffer_extents(); return total_damage; } void update_bound_output(wlr_buffer *buffer) { /* Make sure the default buffer has enough size */ postprocessing->allocate(output->handle->width, output->handle->height); postprocessing->set_current_buffer(buffer); if (wf::get_core().is_gles2()) { const auto& default_fb = postprocessing->get_target_framebuffer(); GLuint default_fb_id = gles::ensure_render_buffer_fb_id(default_fb); depth_buffer_manager->ensure_depth_buffer(default_fb_id, default_fb.get_size().width, default_fb.get_size().height); } } void unset_bound_output() { depth_buffer_manager->frame_done(); postprocessing->set_current_buffer(nullptr); } /** * Repaints the whole output, includes all effects and hooks */ void paint() { /* Part 1: frame setup: query damage, etc. */ effects->run_effects(OUTPUT_EFFECT_PRE); effects->run_effects(OUTPUT_EFFECT_DAMAGE); if (do_direct_scanout()) { // Yet another optimization: if we can directly scanout, we should // stop the rest of the repaint cycle. return; } auto next_frame = damage_manager->start_frame(); if (!next_frame) { // Optimization: the output doesn't need a new frame (so isn't damaged), so we can // just skip the whole repaint delay_manager->skip_frame(); return; } /* Part 2: call the renderer, which sets swap_damage and draws the scenegraph */ update_bound_output(next_frame->buffer); this->swap_damage = start_output_pass(next_frame); /* Part 3: overlay effects */ effects->run_effects(OUTPUT_EFFECT_OVERLAY); if (output_inhibit_counter) { current_pass->clear(current_pass->get_target().geometry, {0, 0, 0, 1}); } /* Part 4: we are done with the main scene. Submit the main render pass. */ const bool pass_status = current_pass->submit(); current_pass.reset(); if (!pass_status) { LOGE("Failed to submit render pass!"); wlr_buffer_unlock(next_frame->buffer); return; } effects->run_effects(OUTPUT_EFFECT_PASS_DONE); /* Part 5: finalize the scene: postprocessing effects */ if (postprocessing->post_effects.size()) { swap_damage |= damage_manager->get_buffer_extents(); } postprocessing->run_post_effects(); /* Part 6: render sw cursors We render software cursors after everything else * for consistency with hardware cursor planes */ render_sw_cursors(next_frame.get()); /* Part 7: finalize frame: swap buffers, send frame_done, etc */ damage_manager->swap_buffers(std::move(next_frame), swap_damage); unset_bound_output(); swap_damage.clear(); post_paint(); } void render_sw_cursors(swapchain_damage_manager_t::frame_object_t *next_frame) { auto sw_cursor_pass = wlr_renderer_begin_buffer_pass(output->handle->renderer, next_frame->buffer, nullptr); if (!sw_cursor_pass) { LOGE("Failed to render software cursors!"); return; } wlr_output_add_software_cursors_to_render_pass(output->handle, sw_cursor_pass, swap_damage.to_pixman()); wlr_render_pass_submit(sw_cursor_pass); } /** * Execute post-paint actions. */ void post_paint() { effects->run_effects(OUTPUT_EFFECT_POST); if (damage_manager->constant_redraw_counter) { damage_manager->schedule_repaint(); } } }; scene::direct_scanout scene::try_scanout_from_list( const std::vector& instances, wf::output_t *scanout) { for (auto& ch : instances) { auto res = ch->try_scanout(scanout); if (res != direct_scanout::SKIP) { return res; } } return direct_scanout::SKIP; } void scene::compute_visibility_from_list(const std::vector& instances, wf::output_t *output, wf::region_t& region, const wf::point_t& offset) { region -= offset; for (auto& ch : instances) { ch->compute_visibility(output, region); } region += offset; } render_manager::render_manager(output_t *o) : pimpl(new impl(o)) {} render_manager::~render_manager() = default; void render_manager::set_redraw_always(bool always) { pimpl->damage_manager->set_redraw_always(always); } wf::region_t render_manager::get_swap_damage() { return pimpl->get_swap_damage(); } void render_manager::schedule_redraw() { pimpl->damage_manager->schedule_repaint(); } void render_manager::add_inhibit(bool add) { pimpl->add_inhibit(add); } void render_manager::add_effect(effect_hook_t *hook, output_effect_type_t type) { pimpl->effects->add_effect(hook, type); } void render_manager::rem_effect(effect_hook_t *hook) { pimpl->effects->rem_effect(hook); } void render_manager::add_post(post_hook_t *hook) { pimpl->postprocessing->add_post(hook); } void render_manager::rem_post(post_hook_t *hook) { pimpl->postprocessing->rem_post(hook); } wf::region_t render_manager::get_scheduled_damage() { return pimpl->damage_manager->get_scheduled_damage(get_target_framebuffer()); } void render_manager::damage_whole() { pimpl->damage_manager->damage_whole(); } void render_manager::damage_whole_idle() { pimpl->damage_manager->damage_whole_idle(); } void render_manager::damage(const wlr_box& box, bool repaint) { auto fb = pimpl->postprocessing->get_target_framebuffer(); pimpl->damage_manager->damage_buffer(fb.framebuffer_box_from_geometry_box(box), repaint); } void render_manager::damage(const wf::region_t& region, bool repaint) { auto fb = pimpl->postprocessing->get_target_framebuffer(); pimpl->damage_manager->damage_buffer(fb.framebuffer_region_from_geometry_region(region), repaint); } wlr_box render_manager::get_ws_box(wf::point_t ws) const { return pimpl->damage_manager->get_ws_box(ws); } wlr_color_transform*render_manager::get_color_transform() { return pimpl->get_color_transform(); } wf::render_target_t render_manager::get_target_framebuffer() const { return pimpl->postprocessing->get_target_framebuffer(); } void render_manager::set_require_depth_buffer(bool require) { return pimpl->depth_buffer_manager->set_required(require); } wf::render_pass_t*render_manager::get_current_pass() { return pimpl->current_pass.get(); } void priv_render_manager_clear_instances(wf::render_manager *manager) { manager->pimpl->damage_manager->instance_manager.reset(); } void priv_render_manager_start_rendering(wf::render_manager *manager) { manager->pimpl->damage_manager->start_rendering(); } } // namespace wf /* End render_manager */ wayfire-0.10.0/src/output/workspace-impl.cpp0000664000175000017500000005531215053502647020727 0ustar dkondordkondor#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../view/view-impl.hpp" #include "wayfire/debug.hpp" #include "wayfire/geometry.hpp" #include "wayfire/nonstd/tracking-allocator.hpp" #include "wayfire/option-wrapper.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" namespace wf { /** * This class encapsulates functionality related to the management of the workspace grid size. */ struct grid_size_manager_t { wf::option_wrapper_t vwidth_opt{"core/vwidth"}; wf::option_wrapper_t vheight_opt{"core/vheight"}; workspace_set_t *set; grid_size_manager_t(workspace_set_t *wset) { this->set = wset; vwidth_opt.set_callback(update_cfg_grid_size); vheight_opt.set_callback(update_cfg_grid_size); this->grid = {vwidth_opt, vheight_opt}; } // Grid size was set by a plugin? bool has_custom_grid_size = false; // Current dimensions of the grid wf::dimensions_t grid = {0, 0}; std::function update_cfg_grid_size = [=] () { if (has_custom_grid_size) { return; } auto old_grid = grid; grid = {vwidth_opt, vheight_opt}; handle_grid_changed(old_grid); }; wf::point_t closest_valid_ws(wf::point_t workspace) { workspace.x = wf::clamp(workspace.x, 0, grid.width - 1); workspace.y = wf::clamp(workspace.y, 0, grid.height - 1); return workspace; } /** * Handle a change in the workspace grid size. * * When it happens, we need to ensure that each view is at least partly * visible on the remaining workspaces. */ void handle_grid_changed(wf::dimensions_t old_size) { wf::workspace_grid_changed_signal data; data.old_grid_size = old_size; data.new_grid_size = grid; set->emit(&data); } wf::dimensions_t get_workspace_grid_size() { return grid; } void set_workspace_grid_size(wf::dimensions_t new_grid) { auto old = this->grid; this->grid = new_grid; this->has_custom_grid_size = true; handle_grid_changed(old); } bool is_workspace_valid(wf::point_t ws) { if ((ws.x >= grid.width) || (ws.y >= grid.height) || (ws.x < 0) || (ws.y < 0)) { return false; } else { return true; } } }; static wf::scene::node_t *find_lca(wf::scene::node_t *a, wf::scene::node_t *b) { wf::scene::node_t *iter = a; std::set a_ancestors; while (iter) { a_ancestors.insert(iter); iter = iter->parent(); } iter = b; while (iter) { if (a_ancestors.count(iter)) { return iter; } iter = iter->parent(); } return nullptr; } static bool is_attached_to(wf::scene::node_t *a, wf::scene::node_t *root) { while (a) { if (a == root) { return true; } a = a->parent(); } return false; } static bool is_attached_to_scenegraph(wf::scene::node_t *a) { return is_attached_to(a, wf::get_core().scene().get()); } static size_t find_index_in_parent(wf::scene::node_t *x, wf::scene::node_t *parent) { while (x->parent() != parent) { x = x->parent(); } auto& children = parent->get_children(); auto it = std::find_if(children.begin(), children.end(), [&] (auto child) { return child.get() == x; }); return it - children.begin(); } class workspace_set_root_node_t : public wf::scene::floating_inner_node_t { uint64_t index; public: workspace_set_root_node_t(uint64_t index) : floating_inner_node_t(true) { this->index = index; } std::string stringify() const override { return "workspace-set id=" + std::to_string(index) + " " + stringify_flags(); } }; std::vector> workspace_set_t::get_all() { return tracking_allocator_t::get().get_all(); } struct workspace_set_t::impl { uint64_t index; /** * The geometry of the last output the workspace output was active on. */ std::optional workspace_geometry; wf::signal::connection_t output_geometry_changed = [=] (output_configuration_changed_signal *ev) { change_output_geometry(output->get_relative_geometry()); }; wf::signal::connection_t on_output_removed = [=] (output_removed_signal *ev) { if (ev->output == this->output) { attach_to_output(nullptr, OLD_OUTPUT_DESTROY); } }; void change_output_geometry(wf::geometry_t new_geometry) { if (!workspace_geometry) { workspace_geometry = new_geometry; return; } auto old_w = workspace_geometry->width, old_h = workspace_geometry->height; if (wf::dimensions(*workspace_geometry) == wf::dimensions(new_geometry)) { // No actual change, stop here return; } for (auto& view : get_views(WSET_MAPPED_ONLY)) { auto wm = view->get_geometry(); float px = 1. * wm.x / old_w; float py = 1. * wm.y / old_h; if (view->get_output() && view->toplevel()->current().fullscreen) { view->set_geometry(new_geometry); } else if (view->toplevel()->current().tiled_edges) { // Do nothing. This is taken care of, by the grid plugin. // If the user does not have grid enabled, we ignore it anyways. } else { view->set_geometry({ int(px * new_geometry.width), int(py * new_geometry.height), wm.width, wm.height }); } } workspace_geometry = new_geometry; } wf::signal::connection_t on_grid_changed = [=] (workspace_grid_changed_signal *ev) { if (!workspace_geometry) { return; } if (!grid.is_workspace_valid({current_vx, current_vy})) { set_workspace(grid.closest_valid_ws({current_vx, current_vy}), {}); } wf::geometry_t full_grid = { -current_vx * workspace_geometry->width, -current_vy * workspace_geometry->height, grid.grid.width * workspace_geometry->width, grid.grid.height * workspace_geometry->height }; for (auto view : get_views(WSET_MAPPED_ONLY)) { if (!(view->get_geometry() & full_grid)) { move_to_workspace(view, get_view_main_workspace(view)); } } }; wf::signal::connection_t> on_view_destruct = [=] (wf::destruct_signal *ev) { remove_view(toplevel_cast(ev->object)); }; bool visible = false; public: wf::output_t *output = nullptr; workspace_set_t *self; grid_size_manager_t grid; scene::floating_inner_ptr wnode; impl(workspace_set_t *self, int64_t index) : grid(self) { this->self = self; this->index = index; LOGC(WSET, "Creating new workspace set with id=", index); wnode = std::make_shared(index); wnode->set_enabled(false); self->connect(&on_grid_changed); wf::get_core().output_layout->connect(&on_output_removed); } ~impl() { LOGC(WSET, "Destroying workspace set with id=", index); attach_to_output(nullptr, SELF_DESTROY); for (auto view : wset_views) { view->priv->current_wset.reset(); } } enum attach_flags { // The current output is being destroyed OLD_OUTPUT_DESTROY = (1 << 0), // `this` is being freed. SELF_DESTROY = (1 << 1), }; void attach_to_output(wf::output_t *new_output, uint32_t flags) { if (new_output == output) { return; } LOGC(WSET, "Attaching workspace set id=", index, " to output ", (new_output ? new_output->to_string() : "null")); if (output) { output->disconnect(&output_geometry_changed); wf::scene::remove_child(wnode); } workspace_set_attached_signal data; data.old_output = output; data.set = self; output = new_output; if (new_output) { change_output_geometry(new_output->get_relative_geometry()); new_output->connect(&output_geometry_changed); scene::add_front(new_output->node_for_layer(scene::layer::WORKSPACE), wnode); } for (auto view : this->wset_views) { view->set_output(new_output); } if (!(flags & SELF_DESTROY)) { self->emit(&data); } } void set_visible(bool visible) { if (visible == this->visible) { return; } LOGC(WSET, "Changing visibility of workspace set id=", index, " visible=", visible); this->visible = visible; wf::scene::set_node_enabled(wnode, visible); for (auto& view : wset_views) { if (is_attached_to(view->get_root_node().get(), wnode.get())) { // Attached/detached state same as wnode continue; } wf::scene::set_node_enabled(view->get_root_node(), visible); } } void add_view(wayfire_toplevel_view view) { if (std::find(wset_views.begin(), wset_views.end(), view) != wset_views.end()) { return; } LOGC(WSET, "Adding view ", view, " to wset ", index); wset_views.push_back(view); view->connect(&on_view_destruct); view->priv->current_wset = self->weak_from_this(); view->set_output(this->output); } void remove_view(wayfire_toplevel_view view) { auto it = std::find(wset_views.begin(), wset_views.end(), view); if (it == wset_views.end()) { LOGW("Removing view ", view, " from wset id=", index, " but the view is not there!"); return; } LOGC(WSET, "Removing view ", view, " from id=", index); wset_views.erase(it); view->disconnect(&on_view_destruct); view->priv->current_wset.reset(); } std::vector get_views(uint32_t flags = 0, std::optional workspace = {}) { if (!flags && !workspace) { return wset_views; } if (flags & WSET_CURRENT_WORKSPACE) { workspace = get_current_workspace(); } auto views = wset_views; auto it = std::remove_if(views.begin(), views.end(), [&] (wayfire_toplevel_view view) { if ((flags & WSET_MAPPED_ONLY) && !view->is_mapped()) { return true; } if ((flags & WSET_EXCLUDE_MINIMIZED) && view->minimized) { return true; } if ((flags & WSET_SORT_STACKING) && !is_attached_to_scenegraph(view->get_root_node().get())) { return true; } if (workspace && !view_visible_on(view, *workspace)) { return true; } return false; }); views.erase(it, views.end()); if (flags & WSET_SORT_STACKING) { std::sort(views.begin(), views.end(), [] (wayfire_toplevel_view a, wayfire_view b) { wf::scene::node_t *x = a->get_root_node().get(); wf::scene::node_t *y = b->get_root_node().get(); wf::scene::node_t *lca = find_lca(x, y); wf::dassert(lca != nullptr, "LCA should always exist when the two nodes are in the scenegraph!"); wf::dassert((lca != x) && (lca != y), "LCA should not be equal to one of the nodes, this" "means nested views/dialogs have been added to the wset!"); const size_t idx_x = find_index_in_parent(x, lca); const size_t idx_y = find_index_in_parent(y, lca); return idx_x < idx_y; }); } return views; } private: std::vector wset_views; int current_vx = 0; int current_vy = 0; public: wf::point_t get_view_main_workspace(wayfire_toplevel_view view) { if (!workspace_geometry) { LOGW("Workspace-set id=", index, " does not have any output/geometry yet!"); return {0, 0}; } auto wm = view->get_geometry(); wf::point_t workspace = { current_vx + (int)std::floor((wm.x + wm.width / 2.0) / workspace_geometry->width), current_vy + (int)std::floor((wm.y + wm.height / 2.0) / workspace_geometry->height) }; return grid.closest_valid_ws(workspace); } /** * @param use_bbox When considering view visibility, whether to use the * bounding box or the wm geometry. * * @return true if the view is visible on the workspace vp */ bool view_visible_on(wayfire_toplevel_view view, wf::point_t vp) { if (!workspace_geometry) { LOGW("Workspace-set id=", index, " does not have any output/geometry yet!"); return false; } auto g = *workspace_geometry; if (!view->sticky) { g.x += (vp.x - current_vx) * g.width; g.y += (vp.y - current_vy) * g.height; } return g & view->get_geometry(); } /** * Moves view geometry so that it is visible on the given workspace */ void move_to_workspace(wayfire_toplevel_view view, wf::point_t ws) { if (!workspace_geometry) { LOGW("Workspace-set id=", index, " does not have any output/geometry yet!"); return; } // Sticky views are visible on all workspaces, so we just have to make // it visible on the current workspace if (view->sticky) { ws = {current_vx, current_vy}; } auto box = view->get_pending_geometry(); wf::geometry_t visible = *workspace_geometry; visible.x += (ws.x - current_vx) * visible.width; visible.y += (ws.y - current_vy) * visible.height; if (!(box & visible)) { /* center of the view */ int cx = box.x + box.width / 2; int cy = box.y + box.height / 2; int width = visible.width, height = visible.height; /* compute center coordinates when moved to the current workspace */ int local_cx = (cx % width + width) % width; int local_cy = (cy % height + height) % height; /* finally, calculate center coordinates in the target workspace */ int target_cx = local_cx + visible.x; int target_cy = local_cy + visible.y; view->move(box.x + target_cx - cx, box.y + target_cy - cy); } } wf::point_t get_current_workspace() { return {current_vx, current_vy}; } void set_workspace(wf::point_t nws, const std::vector& fixed_views) { if (!grid.is_workspace_valid(nws)) { LOGE("Attempt to set invalid workspace: ", nws, " workspace grid size is ", grid.grid.width, "x", grid.grid.height); return; } if (!workspace_geometry) { LOGW("Workspace-set id=", index, " does not have any output/geometry yet!"); return; } wf::workspace_changed_signal data; data.old_viewport = {current_vx, current_vy}; data.new_viewport = {nws.x, nws.y}; data.output = output; /* The part below is tricky, because with the current architecture * we cannot make the viewport change look atomic, i.e the workspace * is changed first, and then all views are moved. * * We first change the viewport, and then adjust the position of the * views. */ current_vx = nws.x; current_vy = nws.y; auto screen = wf::dimensions(*workspace_geometry); auto dx = (data.old_viewport.x - nws.x) * screen.width; auto dy = (data.old_viewport.y - nws.y) * screen.height; std::vector> old_fixed_view_workspaces; old_fixed_view_workspaces.reserve(fixed_views.size()); for (auto& view : wset_views) { const auto is_fixed = std::find(fixed_views.cbegin(), fixed_views.cend(), view) != fixed_views.end(); if (is_fixed) { old_fixed_view_workspaces.push_back({view, get_view_main_workspace(view)}); } else if (!view->sticky) { for (auto v : view->enumerate_views()) { v->move(v->get_pending_geometry().x + dx, v->get_pending_geometry().y + dy); } } } for (auto& [v, old_workspace] : old_fixed_view_workspaces) { wf::view_change_workspace_signal vdata; vdata.view = v; vdata.from = old_workspace; vdata.to = get_view_main_workspace(v); self->emit(&vdata); if (output) { output->emit(&vdata); wf::get_core().default_wm->focus_raise_view(v, false); } } self->emit(&data); if (output) { // Finally, do a refocus to update the keyboard focus wf::get_core().seat->refocus(); output->emit(&data); } // Don't forget to update the geometry of the wset, as the geometry of it has changed now. // FIXME: in theory this isn't enough, as there may be views outside, but in practice, nobody cares .. wf::scene::update(wnode, wf::scene::update_flag::GEOMETRY); } }; workspace_set_t::workspace_set_t(int64_t index) : pimpl(new impl(this, index)) {} workspace_set_t::~workspace_set_t() = default; void workspace_set_t::attach_to_output(wf::output_t *output) { pimpl->attach_to_output(output, 0); } wf::output_t*workspace_set_t::get_attached_output() { return pimpl->output; } void workspace_set_t::set_visible(bool visible) { pimpl->set_visible(visible); } /* Just pass to the appropriate function from above */ wf::point_t workspace_set_t::get_view_main_workspace(wayfire_toplevel_view view) { return pimpl->get_view_main_workspace(view); } bool workspace_set_t::view_visible_on(wayfire_toplevel_view view, wf::point_t ws) { return pimpl->view_visible_on(view, ws); } void workspace_set_t::move_to_workspace(wayfire_toplevel_view view, wf::point_t ws) { return pimpl->move_to_workspace(view, ws); } void workspace_set_t::add_view(wayfire_toplevel_view view) { pimpl->add_view(view); } std::vector workspace_set_t::get_views(uint32_t flags, std::optional ws) { return pimpl->get_views(flags, ws); } void workspace_set_t::remove_view(wayfire_toplevel_view view) { pimpl->remove_view(view); } void workspace_set_t::set_workspace(wf::point_t ws, const std::vector& fixed_views) { return pimpl->set_workspace(ws, fixed_views); } void workspace_set_t::request_workspace(wf::point_t ws, const std::vector& views) { if (!pimpl->output) { pimpl->set_workspace(ws, views); return; } wf::workspace_change_request_signal data; data.carried_out = false; data.old_viewport = pimpl->get_current_workspace(); data.new_viewport = ws; data.output = pimpl->output; data.fixed_views = views; pimpl->output->emit(&data); if (!data.carried_out) { pimpl->set_workspace(ws, views); } } wf::point_t workspace_set_t::get_current_workspace() { return pimpl->get_current_workspace(); } wf::dimensions_t workspace_set_t::get_workspace_grid_size() { return pimpl->grid.get_workspace_grid_size(); } void workspace_set_t::set_workspace_grid_size(wf::dimensions_t dim) { return pimpl->grid.set_workspace_grid_size(dim); } bool workspace_set_t::is_workspace_valid(wf::point_t ws) { return pimpl->grid.is_workspace_valid(ws); } scene::floating_inner_ptr workspace_set_t::get_node() const { return pimpl->wnode; } uint64_t workspace_set_t::get_index() const { return pimpl->index; } std::optional workspace_set_t::get_last_output_geometry() { if (pimpl->output) { return pimpl->output->get_relative_geometry(); } return pimpl->workspace_geometry; } void emit_view_pre_moved_to_wset_pre(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset) { view_pre_moved_to_wset_signal data; data.view = view; data.old_wset = old_wset; data.new_wset = new_wset; wf::get_core().emit(&data); } void emit_view_moved_to_wset(wayfire_toplevel_view view, std::shared_ptr old_wset, std::shared_ptr new_wset) { view_moved_to_wset_signal data; data.view = view; data.old_wset = old_wset; data.new_wset = new_wset; wf::get_core().emit(&data); } static int64_t choose_lowest_free_wset_id(int64_t hint_index) { auto all_wsets = workspace_set_t::get_all(); std::set used_indices; for (auto& wset : all_wsets) { used_indices.insert(wset->get_index()); } int64_t index = 1; if ((hint_index <= 0) || used_indices.count(hint_index)) { // Select lowest unused ID. for (index = 1; used_indices.count(index); index++) {} } else { index = hint_index; } return index; } std::shared_ptr workspace_set_t::create(int64_t index) { index = choose_lowest_free_wset_id(index); auto wset = wf::tracking_allocator_t::get().allocate(index); return wset; } } // namespace wf wayfire-0.10.0/src/version/0000775000175000017500000000000015053502647015405 5ustar dkondordkondorwayfire-0.10.0/src/version/git-commit.cpp.in0000664000175000017500000000015615053502647020571 0ustar dkondordkondor#include namespace wf { namespace version { std::string git_commit = "@GIT_COMMIT@"; } } wayfire-0.10.0/src/version/git-branch.cpp.in0000664000175000017500000000015615053502647020536 0ustar dkondordkondor#include namespace wf { namespace version { std::string git_branch = "@GIT_BRANCH@"; } } wayfire-0.10.0/src/geometry.cpp0000664000175000017500000001440315053502647016261 0ustar dkondordkondor#include #include #include /* Geometry helpers */ std::ostream& operator <<(std::ostream& stream, const wf::geometry_t& geometry) { stream << '(' << geometry.x << ',' << geometry.y << ' ' << geometry.width << 'x' << geometry.height << ')'; return stream; } std::ostream& operator <<(std::ostream& stream, const wlr_fbox& geometry) { stream << std::fixed << std::setprecision(2) << '(' << geometry.x << ',' << geometry.y << ' ' << geometry.width << 'x' << geometry.height << ')'; return stream; } std::ostream& wf::operator <<(std::ostream& stream, const wf::point_t& point) { stream << '(' << point.x << ',' << point.y << ')'; return stream; } std::ostream& wf::operator <<(std::ostream& stream, const wf::dimensions_t& dims) { stream << dims.width << "x" << dims.height; return stream; } std::ostream& wf::operator <<(std::ostream& stream, const wf::pointf_t& pointf) { stream << std::fixed << std::setprecision(4) << '(' << pointf.x << ',' << pointf.y << ')'; return stream; } wf::point_t wf::origin(const geometry_t& geometry) { return {geometry.x, geometry.y}; } wf::dimensions_t wf::dimensions(const geometry_t& geometry) { return {geometry.width, geometry.height}; } bool wf::operator ==(const wf::dimensions_t& a, const wf::dimensions_t& b) { return a.width == b.width && a.height == b.height; } bool wf::operator !=(const wf::dimensions_t& a, const wf::dimensions_t& b) { return !(a == b); } bool wf::operator ==(const wf::point_t& a, const wf::point_t& b) { return a.x == b.x && a.y == b.y; } bool wf::operator !=(const wf::point_t& a, const wf::point_t& b) { return !(a == b); } bool operator ==(const wf::geometry_t& a, const wf::geometry_t& b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; } bool operator !=(const wf::geometry_t& a, const wf::geometry_t& b) { return !(a == b); } wf::point_t wf::operator +(const wf::point_t& a, const wf::point_t& b) { return {a.x + b.x, a.y + b.y}; } wf::point_t wf::operator -(const wf::point_t& a, const wf::point_t& b) { return {a.x - b.x, a.y - b.y}; } wf::point_t operator +(const wf::point_t& a, const wf::geometry_t& b) { return {a.x + b.x, a.y + b.y}; } wf::geometry_t operator +(const wf::geometry_t & a, const wf::point_t& b) { return { a.x + b.x, a.y + b.y, a.width, a.height }; } wf::geometry_t operator -(const wf::geometry_t & a, const wf::point_t& b) { return a + -b; } wf::point_t wf::operator -(const wf::point_t& a) { return {-a.x, -a.y}; } wf::geometry_t operator *(const wf::geometry_t& box, double scale) { wlr_box scaled; scaled.x = std::floor(box.x * scale); scaled.y = std::floor(box.y * scale); /* Scale it the same way that regions are scaled, otherwise * we get numerical issues. */ scaled.width = std::ceil((box.x + box.width) * scale) - scaled.x; scaled.height = std::ceil((box.y + box.height) * scale) - scaled.y; return scaled; } wlr_fbox operator *(const wlr_fbox& box, double scale) { wlr_fbox scaled; scaled.x = box.x * scale; scaled.y = box.y * scale; scaled.width = box.width * scale; scaled.height = box.height * scale; return scaled; } double abs(const wf::point_t& p) { return std::sqrt(p.x * p.x + p.y * p.y); } bool operator &(const wf::geometry_t& rect, const wf::point_t& point) { return wlr_box_contains_point(&rect, point.x, point.y); } bool operator &(const wf::geometry_t& rect, const wf::pointf_t& point) { return wlr_box_contains_point(&rect, point.x, point.y); } bool operator &(const wf::geometry_t& r1, const wf::geometry_t& r2) { if ((r1.x + r1.width <= r2.x) || (r2.x + r2.width <= r1.x) || (r1.y + r1.height <= r2.y) || (r2.y + r2.height <= r1.y)) { return false; } return true; } wf::geometry_t wf::geometry_intersection(const wf::geometry_t& r1, const wf::geometry_t& r2) { wlr_box result; if (wlr_box_intersection(&result, &r1, &r2)) { return result; } return {0, 0, 0, 0}; } wf::geometry_t wf::clamp(wf::geometry_t window, wf::geometry_t output) { window.width = wf::clamp(window.width, 0, output.width); window.height = wf::clamp(window.height, 0, output.height); window.x = wf::clamp(window.x, output.x, output.x + output.width - window.width); window.y = wf::clamp(window.y, output.y, output.y + output.height - window.height); return window; } wf::geometry_t wf::construct_box( const wf::point_t& origin, const wf::dimensions_t& dimensions) { return { origin.x, origin.y, dimensions.width, dimensions.height }; } wf::geometry_t wf::scale_box( wf::geometry_t A, wf::geometry_t B, wf::geometry_t box) { wlr_fbox scaled_fbox = scale_fbox(geometry_to_fbox(A), geometry_to_fbox(B), geometry_to_fbox(box)); int x = (int)std::floor(scaled_fbox.x); int y = (int)std::floor(scaled_fbox.y); int x2 = (int)std::ceil(scaled_fbox.x + scaled_fbox.width); int y2 = (int)std::floor(scaled_fbox.y + scaled_fbox.height); return wf::geometry_t{ .x = x, .y = y, .width = x2 - x, .height = y2 - y, }; } wlr_fbox wf::scale_fbox(wlr_fbox A, wlr_fbox B, wlr_fbox box) { double scale_x = B.width / A.width; double scale_y = B.height / A.height; double x = B.x + scale_x * (box.x - A.x); double y = B.y + scale_y * (box.y - A.y); double width = scale_x * box.width; double height = scale_y * box.height; return wlr_fbox{ .x = x, .y = y, .width = width, .height = height, }; } wlr_fbox wf::geometry_to_fbox(const geometry_t& geometry) { return wlr_fbox{ .x = (double)geometry.x, .y = (double)geometry.y, .width = (double)geometry.width, .height = (double)geometry.height, }; } wf::geometry_t wf::fbox_to_geometry(const wlr_fbox& fbox) { int x = (int)std::floor(fbox.x); int y = (int)std::floor(fbox.y); int x2 = (int)std::ceil(fbox.x + fbox.width); int y2 = (int)std::ceil(fbox.y + fbox.height); return wf::geometry_t{ .x = x, .y = y, .width = x2 - x, .height = y2 - y, }; } wayfire-0.10.0/src/core/0000775000175000017500000000000015053502647014650 5ustar dkondordkondorwayfire-0.10.0/src/core/core.cpp0000664000175000017500000005344315053502647016315 0ustar dkondordkondor/* Needed for pipe2 */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #include "wayfire/core.hpp" #endif #include #include "wayfire/scene.hpp" #include #include "wayfire/scene-operations.hpp" #include "wayfire/txn/transaction-manager.hpp" #include "wayfire/bindings-repository.hpp" #include "wayfire/util.hpp" #include #include "wayfire/config-backend.hpp" // IWYU pragma: keep #include "plugin-loader.hpp" #include "seat/tablet.hpp" #include "wayfire/touch/touch.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include #include #include #include #include #include #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/scene-input.hpp" #include "opengl-priv.hpp" #include "seat/input-manager.hpp" #include "seat/input-method-relay.hpp" #include "seat/touch.hpp" #include "seat/pointer.hpp" #include "seat/cursor.hpp" #include "../view/view-impl.hpp" #include "main.hpp" #include #include "core-impl.hpp" struct wf_pointer_constraint { wf::wl_listener_wrapper on_destroy; wf_pointer_constraint(wlr_pointer_constraint_v1 *constraint) { // set correct constraint auto& lpointer = wf::get_core_impl().seat->priv->lpointer; auto focus = lpointer->get_focus(); if (focus) { wf::node_recheck_constraints_signal data; focus->emit(&data); } } }; struct wlr_idle_inhibitor_t : public wf::idle_inhibitor_t { wf::wl_listener_wrapper on_destroy; wlr_idle_inhibitor_t(wlr_idle_inhibitor_v1 *wlri) { on_destroy.set_callback([&] (void*) { delete this; }); on_destroy.connect(&wlri->events.destroy); } }; bool wf::compositor_core_t::is_gles2() const { return wlr_renderer_is_gles2(renderer); } bool wf::compositor_core_t::is_vulkan() const { #if WLR_HAS_VULKAN_RENDERER return wlr_renderer_is_vk(renderer); #else return false; #endif } bool wf::compositor_core_t::is_pixman() const { return wlr_renderer_is_pixman(renderer); } void wf::compositor_core_impl_t::init() { this->scene_root = std::make_shared(); this->tx_manager = std::make_unique(); this->default_wm = std::make_unique(); wlr_renderer_init_wl_display(renderer, display); /* Order here is important: * 1. init_desktop_apis() must come after wlr_compositor_create(), * since Xwayland initialization depends on the compositor * 2. input depends on output-layout * 3. weston toy clients expect xdg-shell before wl_seat, i.e * init_desktop_apis() should come before input. * 4. GTK expects primary selection early. */ compositor = wlr_compositor_create(display, 6, renderer); /* Needed for subsurfaces */ wlr_subcompositor_create(display); /* Legacy DRM */ if (runtime_config.legacy_wl_drm && wlr_renderer_get_texture_formats(renderer, WLR_BUFFER_CAP_DMABUF)) { wlr_drm_create(display, renderer); } protocols.data_device = wlr_data_device_manager_create(display); wf::option_wrapper_t disable_primary_selection{"workarounds/disable_primary_selection"}; if (disable_primary_selection) { protocols.primary_selection_v1 = nullptr; } else { protocols.primary_selection_v1 = wlr_primary_selection_v1_device_manager_create(display); } protocols.data_control = wlr_data_control_manager_v1_create(display); output_layout = std::make_unique(backend); init_desktop_apis(); /* Somehow GTK requires the tablet_v2 to be advertised pretty early */ protocols.tablet_v2 = wlr_tablet_v2_create(display); input = std::make_unique(); seat = std::make_unique(display, "default"); protocols.screencopy = wlr_screencopy_manager_v1_create(display); protocols.gamma_v1 = wlr_gamma_control_manager_v1_create(display); protocols.export_dmabuf = wlr_export_dmabuf_manager_v1_create(display); protocols.output_manager = wlr_xdg_output_manager_v1_create(display, output_layout->get_handle()); protocols.drm_v1 = wlr_drm_lease_v1_manager_create(display, backend); drm_lease_request.set_callback([&] (void *data) { auto req = static_cast(data); struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req); if (!lease) { wlr_drm_lease_request_v1_reject(req); } }); if (protocols.drm_v1) { drm_lease_request.connect(&protocols.drm_v1->events.request); } else { LOGI("Not using wlr_drm_lease_device_v1; VR will not be available!"); } /* idle-inhibit setup */ protocols.idle_notifier = wlr_idle_notifier_v1_create(display); protocols.idle_inhibit = wlr_idle_inhibit_v1_create(display); idle_inhibitor_created.set_callback([&] (void *data) { auto wlri = static_cast(data); /* will be freed by the destroy request */ new wlr_idle_inhibitor_t(wlri); }); idle_inhibitor_created.connect(&protocols.idle_inhibit->events.new_inhibitor); /* decoration_manager setup */ protocols.decorator_manager = wlr_server_decoration_manager_create(display); protocols.xdg_decorator = wlr_xdg_decoration_manager_v1_create(display); init_xdg_decoration_handlers(); protocols.vkbd_manager = wlr_virtual_keyboard_manager_v1_create(display); vkbd_created.set_callback([&] (void *data) { auto kbd = (wlr_virtual_keyboard_v1*)data; input->handle_new_input(&kbd->keyboard.base); }); vkbd_created.connect(&protocols.vkbd_manager->events.new_virtual_keyboard); protocols.vptr_manager = wlr_virtual_pointer_manager_v1_create(display); vptr_created.set_callback([&] (void *data) { auto event = (wlr_virtual_pointer_v1_new_pointer_event*)data; auto ptr = event->new_pointer; if (event->suggested_output && !ptr->pointer.output_name) { ptr->pointer.output_name = strdup(event->suggested_output->name); } input->handle_new_input(&ptr->pointer.base); }); vptr_created.connect(&protocols.vptr_manager->events.new_virtual_pointer); protocols.pointer_gestures = wlr_pointer_gestures_v1_create(display); protocols.relative_pointer = wlr_relative_pointer_manager_v1_create(display); protocols.pointer_constraints = wlr_pointer_constraints_v1_create(display); pointer_constraint_added.set_callback([&] (void *data) { // will delete itself when the constraint is destroyed new wf_pointer_constraint((wlr_pointer_constraint_v1*)data); }); pointer_constraint_added.connect( &protocols.pointer_constraints->events.new_constraint); wf::option_wrapper_t enable_input_method_v2{"workarounds/enable_input_method_v2"}; if (enable_input_method_v2) { protocols.input_method = wlr_input_method_manager_v2_create(display); protocols.text_input = wlr_text_input_manager_v3_create(display); } im_relay = std::make_unique(); // TODO: is v2 the correct version here? // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4858 protocols.presentation = wlr_presentation_create(display, backend, 2); protocols.viewporter = wlr_viewporter_create(display); protocols.foreign_registry = wlr_xdg_foreign_registry_create(display); protocols.foreign_v1 = wlr_xdg_foreign_v1_create(display, protocols.foreign_registry); protocols.foreign_v2 = wlr_xdg_foreign_v2_create(display, protocols.foreign_registry); wlr_fractional_scale_manager_v1_create(display, 1); wlr_single_pixel_buffer_manager_v1_create(display); this->bindings = std::make_unique(); image_io::init(); if (is_gles2()) { OpenGL::init(); } increase_nofile_limit(); this->state = compositor_state_t::START_BACKEND; } void wf::compositor_core_impl_t::increase_nofile_limit() { if (getrlimit(RLIMIT_NOFILE, &user_maxfiles) != 0) { LOGE("Failed to getrlimit(RLIMIT_NOFILE), not increasing maximum open file descriptors. Might cause" " crashes with many open views."); } else { struct rlimit max_files = user_maxfiles; max_files.rlim_cur = user_maxfiles.rlim_max; if (setrlimit(RLIMIT_NOFILE, &max_files) != 0) { LOGE("Failed to setrlimit(RLIMIT_NOFILE), not increasing maximum open file descriptors. Might " "cause crashes with many open views."); } } } void wf::compositor_core_impl_t::restore_nofile_limit() { if (setrlimit(RLIMIT_NOFILE, &user_maxfiles) != 0) { LOGE("Failed to setrlimit(RLIMIT_NOFILE), could not restore maximum open file descriptors."); } } void wf::compositor_core_impl_t::post_init() { discard_command_output.load_option("workarounds/discard_command_output"); core_backend_started_signal backend_started_ev; this->emit(&backend_started_ev); this->state = compositor_state_t::START_PLUGINS; plugin_mgr = std::make_unique(); this->bindings->reparse_extensions(); this->state = compositor_state_t::RUNNING; // Move pointer to the middle of the leftmost, topmost output wf::pointf_t p; wf::output_t *wo = wf::get_core().output_layout->get_output_coords_at({FLT_MIN, FLT_MIN}, p); // Output might be noop but guaranteed to not be null wo->ensure_pointer(true); seat->focus_output(wo); // Refresh device mappings when we have all outputs and devices input->configure_input_devices(); // Start processing cursor events seat->priv->cursor->setup_listeners(); core_startup_finished_signal startup_ev; this->emit(&startup_ev); } void wf::compositor_core_impl_t::shutdown() { if (this->state < compositor_state_t::RUNNING) { // During initialization, shut down is a bit more complicated. We can deallocate core, but since we // have not started the event loop, we can exit immediately. deallocate_core(); std::exit(0); } // We might get multiple signals in some scenarios. Shutdown only on the first instance. if (this->state != compositor_state_t::SHUTDOWN) { wl_display_terminate(wf::get_core().display); } } void wf::compositor_core_impl_t::disconnect_signals() { fini_desktop_apis(); fini_xdg_decoration_handlers(); drm_lease_request.disconnect(); input_inhibit_activated.disconnect(); input_inhibit_deactivated.disconnect(); idle_inhibitor_created.disconnect(); vkbd_created.disconnect(); vptr_created.disconnect(); pointer_constraint_added.disconnect(); } void wf::compositor_core_impl_t::fini() { this->state = compositor_state_t::SHUTDOWN; core_shutdown_signal ev; this->emit(&ev); LOGI("Unloading plugins..."); plugin_mgr.reset(); _clear_data(); // Shut down xwayland first, otherwise, wlroots will attempt to restart it when we kill it via // wl_display_destroy_clients(). wf::fini_xwayland(); LOGI("Stopping clients..."); wl_display_destroy_clients(static_core->display); LOGI("Freeing resources..."); priv_output_layout_fini(output_layout.get()); default_wm.reset(); bindings.reset(); scene_root.reset(); // General core stuff im_relay.reset(); seat.reset(); input.reset(); output_layout.reset(); tx_manager.reset(); OpenGL::fini(); disconnect_signals(); wl_display_destroy(static_core->display); } wf::compositor_state_t wf::compositor_core_impl_t::get_current_state() { return this->state; } wlr_seat*wf::compositor_core_impl_t::get_current_seat() { return seat->seat; } void wf::compositor_core_impl_t::set_cursor(std::string name) { seat->priv->cursor->set_cursor(name); } void wf::compositor_core_impl_t::unhide_cursor() { seat->priv->cursor->unhide_cursor(); } void wf::compositor_core_impl_t::hide_cursor() { seat->priv->cursor->hide_cursor(); } void wf::compositor_core_impl_t::warp_cursor(wf::pointf_t pos) { seat->priv->cursor->warp_cursor(pos); seat->priv->lpointer->update_cursor_position(get_current_time()); } void wf::compositor_core_impl_t::transfer_grab(wf::scene::node_ptr node) { seat->priv->transfer_grab(node); seat->priv->lpointer->transfer_grab(node); seat->priv->touch->transfer_grab(node); for (auto dev : this->get_input_devices()) { if (auto tablet = dynamic_cast(dev.get())) { for (auto& tool : tablet->tools_list) { tool->reset_grab(); } } } } wf::pointf_t wf::compositor_core_impl_t::get_cursor_position() { if (seat->priv->cursor) { return seat->priv->cursor->get_cursor_position(); } else { return {invalid_coordinate, invalid_coordinate}; } } wf::pointf_t wf::compositor_core_impl_t::get_touch_position(int id) { const auto& state = seat->priv->touch->get_state(); auto it = state.fingers.find(id); if (it != state.fingers.end()) { return {it->second.current.x, it->second.current.y}; } return {invalid_coordinate, invalid_coordinate}; } const wf::touch::gesture_state_t& wf::compositor_core_impl_t::get_touch_state() { return seat->priv->touch->get_state(); } wf::scene::node_ptr wf::compositor_core_impl_t::get_cursor_focus() { return seat->priv->lpointer->get_focus(); } wayfire_view wf::compositor_core_t::get_cursor_focus_view() { return node_to_view(get_cursor_focus()); } wayfire_view wf::compositor_core_t::get_view_at(wf::pointf_t point) { auto isec = scene()->find_node_at(point); return isec ? node_to_view(isec->node->shared_from_this()) : nullptr; } wf::scene::node_ptr wf::compositor_core_impl_t::get_touch_focus(int finger_id) { return seat->priv->touch->get_focus(finger_id); } wayfire_view wf::compositor_core_t::get_touch_focus_view() { return node_to_view(get_touch_focus()); } void wf::compositor_core_impl_t::add_touch_gesture( nonstd::observer_ptr gesture) { seat->priv->touch->add_touch_gesture(gesture); } void wf::compositor_core_impl_t::rem_touch_gesture( nonstd::observer_ptr gesture) { seat->priv->touch->rem_touch_gesture(gesture); } std::vector> wf::compositor_core_impl_t::get_input_devices() { std::vector> list; for (auto& dev : input->input_devices) { list.push_back(nonstd::make_observer(dev.get())); } return list; } wlr_cursor*wf::compositor_core_impl_t::get_wlr_cursor() { return seat->priv->cursor->cursor; } std::vector wf::compositor_core_t::get_all_views() { return wf::tracking_allocator_t::get().get_all(); } /** * Upon successful execution, returns the PID of the child process. * Returns 0 in case of failure. */ pid_t wf::compositor_core_impl_t::run(std::string command) { static constexpr size_t READ_END = 0; static constexpr size_t WRITE_END = 1; int pipe_fd[2]; int ret = pipe2(pipe_fd, O_CLOEXEC); if (ret == -1) { LOGE("wf::compositor_core_impl_t::run: failed to create pipe2: ", strerror(errno)); return 0; } /* The following is a "hack" for disowning the child processes, * otherwise they will simply stay as zombie processes */ pid_t pid = fork(); if (!pid) { restore_nofile_limit(); pid = fork(); if (!pid) { close(pipe_fd[READ_END]); close(pipe_fd[WRITE_END]); setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1); setenv("WAYLAND_DISPLAY", wayland_display.c_str(), 1); #if WF_HAS_XWAYLAND if (!xwayland_get_display().empty()) { setenv("DISPLAY", xwayland_get_display().c_str(), 1); } #endif if (discard_command_output) { int dev_null = open("/dev/null", O_WRONLY); dup2(dev_null, 1); dup2(dev_null, 2); close(dev_null); } _exit(execl("/bin/sh", "/bin/sh", "-c", command.c_str(), NULL)); } else { close(pipe_fd[READ_END]); int ret = write(pipe_fd[WRITE_END], (void*)(&pid), sizeof(pid)); close(pipe_fd[WRITE_END]); _exit(ret != sizeof(pid) ? 1 : 0); } } else { close(pipe_fd[WRITE_END]); int status; waitpid(pid, &status, 0); // Return 0 if the child process didn't run or didn't exit normally, or returns a non-zero return // value. pid_t child_pid{}; if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) { int ret = read(pipe_fd[READ_END], &child_pid, sizeof(child_pid)); if (ret != sizeof(child_pid)) { // This is consider to be an error (even though theoretically a partial read would require an // attempt to continue). child_pid = 0; if (ret == -1) { LOGE("wf::compositor_core_impl_t::run(\"", command, "\"): failed to read PID from pipe end: ", strerror(errno)); } else { LOGE("wf::compositor_core_impl_t::run(\"", command, "\"): short read of PID from pipe end, got ", std::to_string(ret), " bytes"); } } } close(pipe_fd[READ_END]); return child_pid; } } std::string wf::compositor_core_impl_t::get_xwayland_display() { return xwayland_get_display(); } void wf::start_move_view_to_wset(wayfire_toplevel_view v, std::shared_ptr new_wset) { emit_view_pre_moved_to_wset_pre(v, v->get_wset(), new_wset); if (v->get_wset()) { v->get_wset()->remove_view(v); wf::scene::remove_child(v->get_root_node()); } wf::scene::add_front(new_wset->get_node(), v->get_root_node()); new_wset->add_view(v); } void wf::move_view_to_output(wayfire_toplevel_view v, wf::output_t *new_output, bool reconfigure) { if (v->get_output() == new_output) { return; } wf::dassert(!v->parent, "Cannot move a dialog to a different output than its parent!"); auto old_output = v->get_output(); auto old_wset = v->get_wset(); uint32_t edges; bool fullscreen; wf::geometry_t view_g; wf::geometry_t old_output_g; wf::geometry_t new_output_g; int delta_x = 0; int delta_y = 0; if (reconfigure) { edges = v->pending_tiled_edges(); fullscreen = v->pending_fullscreen(); view_g = v->get_pending_geometry(); old_output_g = old_output->get_relative_geometry(); new_output_g = new_output->get_relative_geometry(); auto ratio_x = (double)new_output_g.width / old_output_g.width; auto ratio_y = (double)new_output_g.height / old_output_g.height; view_g.x *= ratio_x; view_g.y *= ratio_y; delta_x = view_g.x - v->get_pending_geometry().x; delta_y = view_g.y - v->get_pending_geometry().y; } assert(new_output); start_move_view_to_wset(v, new_output->wset()); if (new_output == wf::get_core().seat->get_active_output()) { wf::get_core().seat->focus_view(v); } if (reconfigure) { if (fullscreen) { wf::get_core().default_wm->fullscreen_request(v, new_output, true); } else if (edges) { wf::get_core().default_wm->tile_request(v, edges); } else { auto new_g = wf::clamp(view_g, new_output->workarea->get_workarea()); v->set_geometry(new_g); } for (auto& dialog : v->enumerate_views()) { if ((dialog != v) && (delta_x || delta_y)) { dialog->move(dialog->get_pending_geometry().x + delta_x, dialog->get_pending_geometry().y + delta_y); } } } emit_view_moved_to_wset(v, old_wset, new_output->wset()); } const std::shared_ptr& wf::compositor_core_impl_t::scene() { return scene_root; } wf::compositor_core_t::compositor_core_t() { this->config = std::make_unique(); } wf::compositor_core_t::~compositor_core_t() {} wf::compositor_core_impl_t::compositor_core_impl_t() {} wf::compositor_core_impl_t::~compositor_core_impl_t() { input.reset(); output_layout.reset(); } wf::compositor_core_t& wf::compositor_core_t::get() { return wf::compositor_core_impl_t::get(); } wf::compositor_core_t& wf::get_core() { return wf::compositor_core_t::get(); } wf::compositor_core_impl_t& wf::get_core_impl() { return wf::compositor_core_impl_t::get(); } wf::compositor_core_impl_t& wf::compositor_core_impl_t::allocate_core() { wf::dassert(!static_core, "Core already allocated"); static_core = std::make_unique(); return *static_core; } void wf::compositor_core_impl_t::deallocate_core() { static_core->fini(); static_core.reset(); } wf::compositor_core_impl_t& wf::compositor_core_impl_t::get() { return *static_core; } std::unique_ptr wf::compositor_core_impl_t::static_core; // TODO: move this to a better location wf_runtime_config runtime_config; std::shared_ptr wf::detail::load_raw_option(const std::string& name) { return wf::get_core().config->get_option(name); } wayfire-0.10.0/src/core/txn/0000775000175000017500000000000015053502647015461 5ustar dkondordkondorwayfire-0.10.0/src/core/txn/transaction-manager-impl.hpp0000664000175000017500000001037315053502647023072 0ustar dkondordkondor#include "wayfire/signal-provider.hpp" #include "wayfire/txn/transaction.hpp" #include #include #include static bool transactions_intersect(const wf::txn::transaction_uptr& a, const wf::txn::transaction_uptr& b) { const auto& obj_a = a->get_objects(); const auto& obj_b = b->get_objects(); return std::any_of(obj_a.begin(), obj_a.end(), [&] (const wf::txn::transaction_object_sptr& x) { return std::find(obj_b.begin(), obj_b.end(), x) != obj_b.end(); }); } struct wf::txn::transaction_manager_t::impl { impl() { idle_clear_done.set_callback([=] () { done.clear(); }); } void schedule_transaction(transaction_uptr tx) { LOGC(TXN, "Scheduling transaction ", tx.get()); // Step 1: add any objects which are directly or indirectly connected to the objects in tx coalesce_transactions(tx); // Step 2: remove any transactions we don't need anymore, as their objects were added to tx remove_conflicts(tx); // Step 3: schedule tx for execution. At this point, there are no conflicts in all pending txs pending.push_back(std::move(tx)); consider_commit(); } void coalesce_transactions(const transaction_uptr& tx) { while (true) { const size_t start_size = tx->get_objects().size(); for (auto& existing : pending) { if (transactions_intersect(existing, tx)) { for (auto& obj : existing->get_objects()) { tx->add_object(obj); } } } if (start_size == tx->get_objects().size()) { // No new objects were added in the last iteration => done break; } } } void remove_conflicts(const transaction_uptr& tx) { auto it = std::remove_if(pending.begin(), pending.end(), [&] (const transaction_uptr& existing) { return transactions_intersect(existing, tx); }); pending.erase(it, pending.end()); } // Try to commit as many transactions as possible void consider_commit() { idle_clear_done.run_once(); // Implementation note: the merging strategy guarantees no conflicts between pending transactions, // so we just need to check conflicts between committed and pending for (size_t idx = 0; idx < pending.size();) { if (can_commit_transaction(pending[idx])) { auto tx = std::move(pending[idx]); pending.erase(pending.begin() + idx); do_commit(std::move(tx)); // Note: the container may change after this operation, because some objects emit ready // directly inside commit(). } else { ++idx; } } } bool can_commit_transaction(const transaction_uptr& tx) { return std::none_of(committed.begin(), committed.end(), [&] (const transaction_uptr& comm) { return transactions_intersect(tx, comm); }); } void do_commit(transaction_uptr tx) { tx->connect(&on_tx_apply); committed.push_back(std::move(tx)); // Note: this might immediately trigger tx_apply if all objects are already ready! committed.back()->commit(); } std::vector done; // Temporary storage for transactions which are complete std::vector committed; std::vector pending; wf::wl_idle_call idle_clear_done; wf::signal::connection_t on_tx_apply = [&] (transaction_applied_signal *ev) { // Move transactions which are done from committed to done. // They will be freed on next idle. auto it = std::find_if(committed.begin(), committed.end(), [&] (auto& existing) { return existing.get() == ev->self; }); wf::dassert(it != committed.end(), "Transaction not found in committed list"); done.push_back(std::move(*it)); committed.erase(it); consider_commit(); }; }; wayfire-0.10.0/src/core/txn/transaction.cpp0000664000175000017500000000625515053502647020522 0ustar dkondordkondor#include "wayfire/option-wrapper.hpp" #include "wayfire/txn/transaction-object.hpp" #include #include #include std::string wf::txn::transaction_object_t::stringify() const { std::ostringstream out; out << this; return out.str(); } wf::txn::transaction_t::transaction_t(uint64_t timeout, timer_setter_t timer_setter) { this->timeout = timeout; this->timer_setter = timer_setter; this->on_object_ready = [=] (object_ready_signal *ev) { this->count_ready_objects++; LOGC(TXNI, "Transaction ", this, " object ", ev->self->stringify(), " became ready (", count_ready_objects, "/", this->objects.size(), ")"); wf::dassert(count_ready_objects <= (int)this->objects.size(), "object emitted ready multiple times?"); if (count_ready_objects == (int)this->objects.size()) { apply(false); } }; } const std::vector& wf::txn::transaction_t::get_objects() const { return this->objects; } void wf::txn::transaction_t::add_object(transaction_object_sptr object) { auto it = std::find(objects.begin(), objects.end(), object); if (it == objects.end()) { LOGC(TXNI, "Transaction ", this, " add object ", object->stringify()); objects.push_back(object); } } void wf::txn::transaction_t::commit() { LOGC(TXN, "Committing transaction ", this, " with timeout ", this->timeout); if (this->objects.empty()) { // Empty transaction, directly ready. apply(false); return; } for (auto& obj : this->objects) { obj->connect(&on_object_ready); obj->commit(); } timer_setter(this->timeout, [=] () { if (count_ready_objects < (int)this->objects.size()) { apply(true); } return false; }); } void wf::txn::transaction_t::apply(bool did_timeout) { on_object_ready.disconnect(); LOGC(TXN, "Applying transaction ", this, " timed_out: ", did_timeout); for (auto& obj : this->objects) { obj->apply(); } transaction_applied_signal ev; ev.self = this; ev.timed_out = did_timeout; this->emit(&ev); } /** * A transaction which uses wl_timer for timeouts. */ class wayfire_default_transaction_t : public wf::txn::transaction_t { public: wayfire_default_transaction_t(int64_t timeout) : transaction_t(timeout, get_timer_setter()) {} private: wf::wl_timer timer; timer_setter_t get_timer_setter() { return [this] (uint64_t timeout, wf::wl_timer::callback_t cb) { timer.set_timeout(timeout, cb); }; } }; std::unique_ptr wf::txn::transaction_t::create(int64_t timeout) { if (timeout == -1) { static wf::option_wrapper_t tx_timeout{"core/transaction_timeout"}; timeout = tx_timeout; } return std::make_unique(timeout); } void wf::txn::emit_object_ready(wf::txn::transaction_object_t *obj) { wf::txn::object_ready_signal data_ready; data_ready.self = obj; obj->emit(&data_ready); return; } wayfire-0.10.0/src/core/txn/transaction-manager.cpp0000664000175000017500000000272715053502647022132 0ustar dkondordkondor#include #include #include "transaction-manager-impl.hpp" #include "wayfire/debug.hpp" #include "wayfire/txn/transaction.hpp" wf::txn::transaction_manager_t::transaction_manager_t() { this->priv = std::make_unique(); } wf::txn::transaction_manager_t::~transaction_manager_t() = default; void wf::txn::transaction_manager_t::schedule_transaction(wf::txn::transaction_uptr tx) { new_transaction_signal ev; ev.tx = tx.get(); this->emit(&ev); priv->schedule_transaction(std::move(tx)); } void wf::txn::transaction_manager_t::schedule_object(transaction_object_sptr object) { auto tx = wf::txn::transaction_t::create(); tx->add_object(std::move(object)); schedule_transaction(std::move(tx)); } template static bool is_contained(const std::vector& objs, const T& object) { return std::find(objs.begin(), objs.end(), object) != objs.end(); } bool wf::txn::transaction_manager_t::is_object_pending(transaction_object_sptr object) const { return std::any_of(this->priv->pending.begin(), this->priv->pending.end(), [&] (auto& pending) { return is_contained(pending->get_objects(), object); }); } bool wf::txn::transaction_manager_t::is_object_committed(transaction_object_sptr object) const { return std::any_of(this->priv->committed.begin(), this->priv->committed.end(), [&] (auto& committed) { return is_contained(committed->get_objects(), object); }); } wayfire-0.10.0/src/core/matcher.cpp0000664000175000017500000000600015053502647016773 0ustar dkondordkondor#include #include #include #include #include #include #include class wf::view_matcher_t::impl { public: std::shared_ptr> option; wf::lexer_t lexer; wf::condition_parser_t parser; std::shared_ptr condition; bool try_parse(const std::string& value, const std::string& opt_name) { lexer.reset(value); try { condition = parser.parse(lexer); return true; } catch (std::runtime_error& error) { LOGE("Failed to parse condition ", value, " from option ", opt_name); LOGE("Reason for the failure: ", error.what()); condition.reset(); } return false; } wf::config::option_base_t::updated_callback_t update_condition = [=] () { if (!try_parse(option->get_value(), option->get_name())) { if (option->get_value() != option->get_default_value()) { try_parse(option->get_default_value(), option->get_name() + "(default)"); } } }; void connect_updated_handler() { if (this->option) { this->option->add_updated_handler(&update_condition); } } void disconnect_updated_handler() { if (this->option) { this->option->rem_updated_handler(&update_condition); } } void set_option(std::shared_ptr> option) { disconnect_updated_handler(); this->option = option; if (option) { connect_updated_handler(); update_condition(); } } impl() = default; ~impl() { disconnect_updated_handler(); } impl(const impl &) = delete; impl(impl &&) = delete; impl& operator =(const impl&) = delete; impl& operator =(impl&&) = delete; }; wf::view_matcher_t::view_matcher_t() { this->priv = std::make_unique(); } wf::view_matcher_t::view_matcher_t( std::shared_ptr> option) : view_matcher_t() { this->priv->set_option(option); } wf::view_matcher_t::view_matcher_t(const std::string& option_name) : view_matcher_t() { wf::option_wrapper_t option{option_name}; this->set_from_option(option); } void wf::view_matcher_t::set_from_option( std::shared_ptr> option) { this->priv->set_option(option); } bool wf::view_matcher_t::matches(wayfire_view view) { if (this->priv->condition) { bool ignored = false; wf::view_access_interface_t access_interface{view}; return this->priv->condition->evaluate(access_interface, ignored); } return false; } wf::view_matcher_t::~view_matcher_t() = default; wayfire-0.10.0/src/core/core-impl.hpp0000664000175000017500000000617515053502647017261 0ustar dkondordkondor#ifndef WF_CORE_CORE_IMPL_HPP #define WF_CORE_CORE_IMPL_HPP #include #include "core/plugin-loader.hpp" #include "wayfire/core.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/util.hpp" #include namespace wf { class seat_t; class input_manager_t; class input_method_relay; class compositor_core_impl_t : public compositor_core_t { public: // If NULL, we are not running in GLES mode. wlr_egl *egl = NULL; wlr_compositor *compositor; std::unique_ptr input; std::unique_ptr im_relay; std::unique_ptr plugin_mgr; /** * Initialize the compositor core. * Called only by main(). */ virtual void init(); /** * Finish initialization of core after the backend has started. * Called only by main(). */ virtual void post_init(); void disconnect_signals(); void fini(); static compositor_core_impl_t& get(); static compositor_core_impl_t& allocate_core(); static void deallocate_core(); wlr_seat *get_current_seat() override; void warp_cursor(wf::pointf_t pos) override; void transfer_grab(wf::scene::node_ptr node) override; void set_cursor(std::string name) override; void unhide_cursor() override; void hide_cursor() override; wf::pointf_t get_cursor_position() override; wf::pointf_t get_touch_position(int id) override; const wf::touch::gesture_state_t& get_touch_state() override; wf::scene::node_ptr get_cursor_focus() override; wf::scene::node_ptr get_touch_focus(int finger_id) override; void add_touch_gesture( nonstd::observer_ptr gesture) override; void rem_touch_gesture( nonstd::observer_ptr gesture) override; std::vector> get_input_devices() override; virtual wlr_cursor *get_wlr_cursor() override; std::string get_xwayland_display() override; pid_t run(std::string command) override; void shutdown() override; compositor_state_t get_current_state() override; const std::shared_ptr& scene() final; compositor_core_impl_t(); virtual ~compositor_core_impl_t(); protected: wf::wl_listener_wrapper vkbd_created; wf::wl_listener_wrapper vptr_created; wf::wl_listener_wrapper input_inhibit_activated; wf::wl_listener_wrapper input_inhibit_deactivated; wf::wl_listener_wrapper pointer_constraint_added; wf::wl_listener_wrapper idle_inhibitor_created; wf::wl_listener_wrapper drm_lease_request; std::shared_ptr scene_root; compositor_state_t state = compositor_state_t::UNKNOWN; struct rlimit user_maxfiles; void increase_nofile_limit(); void restore_nofile_limit(); private: wf::option_wrapper_t discard_command_output; static std::unique_ptr static_core; }; compositor_core_impl_t& get_core_impl(); void priv_output_layout_fini(wf::output_layout_t *layout); } #endif /* end of include guard: WF_CORE_CORE_IMPL_HPP */ wayfire-0.10.0/src/core/wm.hpp0000664000175000017500000000301615053502647016004 0ustar dkondordkondor#ifndef WM_H #define WM_H #include "wayfire/plugin.hpp" #include "wayfire/per-output-plugin.hpp" #include "wayfire/bindings.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/view.hpp" #include "wayfire/touch/touch.hpp" #include "wayfire/option-wrapper.hpp" class wayfire_close : public wf::per_output_plugin_instance_t { wf::activator_callback callback; wf::plugin_activation_data_t grab_interface = { .name = "builtin-close-view", .capabilities = wf::CAPABILITY_GRAB_INPUT, }; public: void init() override; void fini() override; }; class wayfire_focus : public wf::plugin_interface_t { wf::signal::connection_t> on_pointer_button; std::unique_ptr tap_gesture; // @return True if the focus has changed bool check_focus_surface(wayfire_view view); wf::option_wrapper_t focus_modifiers{"core/focus_button_with_modifiers"}; wf::option_wrapper_t pass_btns{"core/focus_buttons_passthrough"}; wf::option_wrapper_t focus_btns{"core/focus_buttons"}; wf::plugin_activation_data_t grab_interface = { .name = "_wf_focus", .capabilities = wf::CAPABILITY_MANAGE_DESKTOP, }; public: void init() override; void fini() override; }; class wayfire_exit : public wf::per_output_plugin_instance_t { wf::key_callback key; public: void init() override; void fini() override; }; #endif wayfire-0.10.0/src/core/window-manager.cpp0000664000175000017500000002116115053502647020274 0ustar dkondordkondor#include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include #include #include #include #include #include #include #include #include #include namespace wf { class windowed_geometry_data_t : public wf::custom_data_t { public: bool is_grabbed = false; /** Last geometry the view has had in non-tiled and non-fullscreen state. * -1 as width/height means that no such geometry has been stored. */ wf::geometry_t last_windowed_geometry = {0, 0, -1, -1}; /** * The workarea when last_windowed_geometry was stored. This is used * for ex. when untiling a view to determine its geometry relative to the * (potentially changed) workarea of its output. */ wf::geometry_t windowed_geometry_workarea = {0, 0, -1, -1}; }; void wf::window_manager_t::update_last_windowed_geometry(wayfire_toplevel_view view) { if (!view->is_mapped() || view->pending_tiled_edges() || view->pending_fullscreen()) { return; } auto windowed = view->get_data_safe(); if (windowed->is_grabbed) { return; } update_last_windowed_geometry(view, view->toplevel()->pending().geometry); } void window_manager_t::update_last_windowed_geometry(wayfire_toplevel_view view, wf::geometry_t windowed_geometry) { auto windowed = view->get_data_safe(); windowed->last_windowed_geometry = windowed_geometry; if (view->get_output()) { windowed->windowed_geometry_workarea = view->get_output()->workarea->get_workarea(); } else { windowed->windowed_geometry_workarea = {0, 0, -1, -1}; } } std::optional wf::window_manager_t::get_last_windowed_geometry(wayfire_toplevel_view view) { auto windowed = view->get_data_safe(); if ((windowed->windowed_geometry_workarea.width <= 0) || (windowed->last_windowed_geometry.width <= 0)) { return {}; } if (!view->get_output()) { return windowed->last_windowed_geometry; } const auto& geom = windowed->last_windowed_geometry; const auto& old_area = windowed->windowed_geometry_workarea; const auto& new_area = view->get_output()->workarea->get_workarea(); return wf::geometry_t{ .x = new_area.x + (geom.x - old_area.x) * new_area.width / old_area.width, .y = new_area.y + (geom.y - old_area.y) * new_area.height / old_area.height, .width = geom.width * new_area.width / old_area.width, .height = geom.height * new_area.height / old_area.height }; } void window_manager_t::set_view_grabbed(wayfire_toplevel_view view, bool grabbed) { auto windowed = view->get_data_safe(); windowed->is_grabbed = grabbed; } void window_manager_t::move_request(wayfire_toplevel_view view) { if (view->get_output()) { view_move_request_signal data; data.view = view; view->get_output()->emit(&data); } } void window_manager_t::resize_request(wayfire_toplevel_view view, uint32_t edges) { if (view->get_output()) { view_resize_request_signal data; data.view = view; data.edges = edges; view->get_output()->emit(&data); } } void window_manager_t::focus_request(wayfire_view view, bool self_request) { view_focus_request_signal data; data.view = view; data.self_request = self_request; view->emit(&data); wf::get_core().emit(&data); if (!data.carried_out) { focus_raise_view(view, true); } } void window_manager_t::focus_raise_view(wayfire_view view, bool allow_switch_ws) { if (!view) { wf::get_core().seat->focus_view(nullptr); return; } if (!view->get_output()) { LOGW("Attempting to give focus to a view without an output!"); return; } if (!view->get_keyboard_focus_surface()) { LOGW("Attempting to give focus to a view without focus surface!"); return; } if (auto toplevel = toplevel_cast(find_topmost_parent(view))) { if (toplevel->minimized) { minimize_request(toplevel, false); } } if (allow_switch_ws) { view->get_output()->ensure_visible(view); } view_bring_to_front(view); wf::get_core().seat->focus_output(view->get_output()); wf::get_core().seat->focus_view(view); } void window_manager_t::minimize_request(wayfire_toplevel_view view, bool minimized) { if ((view->minimized == minimized) || !view->is_mapped()) { return; } view_minimize_request_signal data; data.view = view; data.state = minimized; if (view->get_output()) { view->get_output()->emit(&data); } if (!data.carried_out) { /* Do the default minimization */ view->set_minimized(minimized); if (!minimized && view->get_output()) { view_bring_to_front(view); wf::get_core().seat->focus_view(view); } } } /** * Put a view on the given workspace. */ static void move_to_workspace(wayfire_toplevel_view view, wf::point_t workspace) { auto output = view->get_output(); auto wm_geometry = view->get_pending_geometry(); auto delta = workspace - output->wset()->get_current_workspace(); auto scr_size = output->get_screen_size(); wm_geometry.x += scr_size.width * delta.x; wm_geometry.y += scr_size.height * delta.y; view->move(wm_geometry.x, wm_geometry.y); } void window_manager_t::tile_request(wayfire_toplevel_view view, uint32_t tiled_edges, std::optional ws) { if (view->pending_fullscreen() || !view->get_output()) { return; } const wf::point_t workspace = ws.value_or(view->get_output()->wset()->get_current_workspace()); view_tile_request_signal data; data.view = view; data.edges = tiled_edges; data.workspace = workspace; data.desired_size = tiled_edges ? view->get_output()->workarea->get_workarea() : get_last_windowed_geometry(view).value_or(wf::geometry_t{0, 0, -1, -1}); update_last_windowed_geometry(view); view->toplevel()->pending().tiled_edges = tiled_edges; if (view->is_mapped()) { view->get_output()->emit(&data); } if (!data.carried_out) { if (data.desired_size.width > 0) { // set geometry will commit the state view->set_geometry(data.desired_size); move_to_workspace(view, workspace); } else { // Move will commit the tiled edges move_to_workspace(view, workspace); view->request_native_size(); } } } void window_manager_t::fullscreen_request(wayfire_toplevel_view view, wf::output_t *output, bool state, std::optional ws) { if ((view->toplevel()->pending().fullscreen == state) && (state == false)) { return; } wf::output_t *wo = output ?: (view->get_output() ?: wf::get_core().seat->get_active_output()); const wf::point_t workspace = ws.value_or(wo->wset()->get_current_workspace()); wf::dassert(wo != nullptr, "Fullscreening should not happen with null output!"); /* TODO: what happens if the view is moved to the other output, but not * fullscreened? We should make sure that it stays visible there */ // TODO: move_view_to_output seems like a good candidate for inclusion in window-manager wf::move_view_to_output(view, wo, false); view_fullscreen_request_signal data; data.view = view; data.state = state; data.workspace = workspace; data.desired_size = wo->get_relative_geometry(); if (!state) { data.desired_size = view->pending_tiled_edges() ? wo->workarea->get_workarea() : get_last_windowed_geometry(view).value_or(wf::geometry_t{0, 0, -1, -1}); } else { update_last_windowed_geometry(view); } view->toplevel()->pending().fullscreen = state; if (view->is_mapped()) { wo->emit(&data); } if (!data.carried_out) { if (data.desired_size.width > 0) { // set geometry will commit the state view->set_geometry(data.desired_size); } else { view->request_native_size(); wf::get_core().tx_manager->schedule_object(view->toplevel()); } move_to_workspace(view, workspace); } } } // namespace wf wayfire-0.10.0/src/core/view-access-interface.cpp0000664000175000017500000001106115053502647021522 0ustar dkondordkondor#include "wayfire/condition/access_interface.hpp" #include "wayfire/output.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view.hpp" #include "wayfire/view-access-interface.hpp" #include "wayfire/workspace-set.hpp" #include #include #include #include #include namespace wf { view_access_interface_t::view_access_interface_t() {} view_access_interface_t::view_access_interface_t(wayfire_view view) : _view(view) {} view_access_interface_t::~view_access_interface_t() {} variant_t view_access_interface_t::get(const std::string & identifier, bool & error) { variant_t out = std::string(""); // Default to empty string as output. error = false; // Assume things will go well. // Cannot operate if no view is set. if (_view == nullptr) { error = true; return out; } uint32_t view_tiled_edges = toplevel_cast(_view) ? toplevel_cast(_view)->pending_tiled_edges() : 0; if (identifier == "app_id") { out = _view->get_app_id(); } else if (identifier == "title") { out = _view->get_title(); } else if (identifier == "role") { switch (_view->role) { case VIEW_ROLE_TOPLEVEL: out = std::string("TOPLEVEL"); break; case VIEW_ROLE_UNMANAGED: out = std::string("UNMANAGED"); break; case VIEW_ROLE_DESKTOP_ENVIRONMENT: out = std::string("DESKTOP_ENVIRONMENT"); break; default: std::cerr << "View access interface: View has unsupported value for role: " << static_cast(_view->role) << std::endl; error = true; break; } } else if (identifier == "fullscreen") { out = toplevel_cast(_view) ? toplevel_cast(_view)->pending_fullscreen() : false; } else if (identifier == "activated") { out = toplevel_cast(_view) ? toplevel_cast(_view)->activated : false; } else if (identifier == "minimized") { out = toplevel_cast(_view) ? toplevel_cast(_view)->minimized : false; } else if (identifier == "focusable") { out = _view->is_focusable(); } else if (identifier == "mapped") { out = _view->is_mapped(); } else if (identifier == "tiled-left") { out = ((view_tiled_edges & WLR_EDGE_LEFT) > 0); } else if (identifier == "tiled-right") { out = ((view_tiled_edges & WLR_EDGE_RIGHT) > 0); } else if (identifier == "tiled-top") { out = ((view_tiled_edges & WLR_EDGE_TOP) > 0); } else if (identifier == "tiled-bottom") { out = ((view_tiled_edges & WLR_EDGE_BOTTOM) > 0); } else if (identifier == "maximized") { out = (view_tiled_edges == TILED_EDGES_ALL); } else if (identifier == "floating") { out = toplevel_cast(_view) ? (toplevel_cast(_view)->pending_tiled_edges() == 0) : false; } else if (identifier == "type") { do { if (_view->role == VIEW_ROLE_TOPLEVEL) { out = std::string("toplevel"); break; } if (_view->role == VIEW_ROLE_UNMANAGED) { #if WF_HAS_XWAYLAND auto surf = _view->get_wlr_surface(); if (surf && wlr_xwayland_surface_try_from_wlr_surface(surf)) { out = std::string("x-or"); break; } #endif out = std::string("unmanaged"); break; } if (!_view->get_output()) { out = std::string("unknown"); break; } auto layer = get_view_layer(_view); if ((layer == wf::scene::layer::BACKGROUND) || (layer == wf::scene::layer::BOTTOM)) { out = std::string("background"); } else if (layer == wf::scene::layer::TOP) { out = std::string("panel"); } else if (layer == wf::scene::layer::OVERLAY) { out = std::string("overlay"); } break; out = std::string("unknown"); } while (false); } else { std::cerr << "View access interface: Get operation triggered to" << " unsupported view property " << identifier << std::endl; } return out; } void view_access_interface_t::set_view(wayfire_view view) { _view = view; } } // End namespace wf. wayfire-0.10.0/src/core/scene-priv.hpp0000664000175000017500000000016015053502647017431 0ustar dkondordkondor#pragma once #include namespace wf { namespace scene { struct root_node_t::priv_t {}; } } wayfire-0.10.0/src/core/wm.cpp0000664000175000017500000000737315053502647016011 0ustar dkondordkondor#include "wm.hpp" #include "wayfire/output.hpp" #include "wayfire/view-helpers.hpp" #include "wayfire/view.hpp" #include "wayfire/core.hpp" #include "wayfire/workspace-set.hpp" #include "wayfire/output-layout.hpp" #include #include #include #include #include "../output/output-impl.hpp" #include "wayfire/signal-definitions.hpp" #include static void idle_shutdown(void *data) { wf::get_core().shutdown(); } void wayfire_exit::init() { wf::option_wrapper_t wayifre_exit_binding{"core/exit"}; key = [] (const wf::keybinding_t&) { auto output_impl = static_cast(wf::get_core().seat->get_active_output()); if (output_impl->is_inhibited()) { return false; } idle_shutdown(nullptr); return true; }; output->add_key(wayifre_exit_binding, &key); } void wayfire_exit::fini() { output->rem_binding(&key); } void wayfire_close::init() { wf::option_wrapper_t key("core/close_top_view"); callback = [=] (const wf::activator_data_t& ev) { if (!output->activate_plugin(&grab_interface)) { return false; } output->deactivate_plugin(&grab_interface); auto view = wf::get_core().seat->get_active_view(); if (view && (view->role == wf::VIEW_ROLE_TOPLEVEL)) { view->close(); } return true; }; output->add_activator(key, &callback); } void wayfire_close::fini() { output->rem_binding(&callback); } void wayfire_focus::init() { on_pointer_button = [=] (wf::input_event_signal *ev) { if ((ev->mode == wf::input_event_processing_mode_t::IGNORE) || (ev->event->state != WL_POINTER_BUTTON_STATE_PRESSED)) { return; } /* focus_btns->get_value() does not compile */ wf::option_sptr_t tmp = focus_btns; if ((!focus_modifiers && wf::get_core().seat->get_keyboard_modifiers()) || !tmp->get_value().has_match(wf::buttonbinding_t(0, ev->event->button))) { return; } bool changed_focus = this->check_focus_surface(wf::get_core().get_cursor_focus_view()); bool pass_through = (pass_btns || !changed_focus); ev->mode = pass_through ? wf::input_event_processing_mode_t::FULL : wf::input_event_processing_mode_t::NO_CLIENT; }; wf::get_core().connect(&on_pointer_button); // build touch gesture auto on_tap = std::make_unique(1, true); std::vector> actions; actions.emplace_back(std::move(on_tap)); const auto& on_tap_action = [this] () { this->check_focus_surface(wf::get_core().get_touch_focus_view()); }; this->tap_gesture = std::make_unique(std::move(actions), on_tap_action); wf::get_core().add_touch_gesture(tap_gesture); } bool wayfire_focus::check_focus_surface(wayfire_view view) { auto& core = wf::get_core(); if (!view || !view->is_mapped() || !core.seat->get_active_output() || !core.seat->get_active_output()->can_activate_plugin(grab_interface.capabilities)) { return false; } if (!view->get_keyboard_focus_surface()) { wf::view_bring_to_front(view); return false; } auto old_focus = core.seat->get_active_view(); core.default_wm->focus_raise_view(view); return core.seat->get_active_view() != old_focus; } void wayfire_focus::fini() { wf::get_core().rem_touch_gesture(tap_gesture); } wayfire-0.10.0/src/core/opengl.cpp0000664000175000017500000004673515053502647016657 0ustar dkondordkondor#include #include #include "opengl-priv.hpp" #include "wayfire/dassert.hpp" #include "wayfire/geometry.hpp" #include "core-impl.hpp" #include #include #include #include "shaders.tpp" const char *gl_error_string(const GLenum err) { switch (err) { case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION"; } return "UNKNOWN GL ERROR"; } static bool disable_gl_call = false; void gl_call(const char *func, uint32_t line, const char *glfunc) { GLenum err; if (disable_gl_call || ((err = glGetError()) == GL_NO_ERROR)) { return; } LOGE("gles2: function ", glfunc, " in ", func, " line ", line, ": ", gl_error_string(err)); if (OpenGL::exit_on_gles_error) { wf::print_trace(false); std::_Exit(-1); } } namespace OpenGL { /* * Different Context is kept for each output * Each of the following functions uses the currently bound context */ program_t program, color_program; GLuint compile_shader(std::string source, GLuint type) { GLuint shader = GL_CALL(glCreateShader(type)); const char *c_src = source.c_str(); GL_CALL(glShaderSource(shader, 1, &c_src, NULL)); int s; #define LENGTH 1024 * 128 char b1[LENGTH]; GL_CALL(glCompileShader(shader)); GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &s)); GL_CALL(glGetShaderInfoLog(shader, LENGTH, NULL, b1)); if (s == GL_FALSE) { LOGE("Failed to load shader:\n", source, "\nCompiler output:\n", b1); return -1; } return shader; } /* Create a very simple gl program from the given shader sources */ GLuint compile_program(std::string vertex_source, std::string frag_source) { auto vertex_shader = compile_shader(vertex_source, GL_VERTEX_SHADER); auto fragment_shader = compile_shader(frag_source, GL_FRAGMENT_SHADER); auto result_program = GL_CALL(glCreateProgram()); GL_CALL(glAttachShader(result_program, vertex_shader)); GL_CALL(glAttachShader(result_program, fragment_shader)); GL_CALL(glLinkProgram(result_program)); int s = GL_FALSE; #define LENGTH 1024 * 128 char log[LENGTH]; GL_CALL(glGetProgramiv(result_program, GL_LINK_STATUS, &s)); GL_CALL(glGetProgramInfoLog(result_program, LENGTH, NULL, log)); if (s == GL_FALSE) { LOGE("Failed to link vertex shader:\n", vertex_source, "\nFragment shader:\n", frag_source, "\nLinker output:\n", log); GL_CALL(glDeleteProgram(result_program)); } /* won't be really deleted until program is deleted as well */ GL_CALL(glDeleteShader(vertex_shader)); GL_CALL(glDeleteShader(fragment_shader)); return (s == GL_FALSE) ? 0 : result_program; } void init() { wf::gles::run_in_context_if_gles([&] { // enable_gl_synchronous_debug() program.compile(default_vertex_shader_source, default_fragment_shader_source); color_program.set_simple(compile_program(default_vertex_shader_source, color_rect_fragment_source)); }); } void fini() { wf::gles::run_in_context_if_gles([&] { program.free_resources(); color_program.free_resources(); }); } namespace { uint32_t current_output_fb = 0; } void bind_output(uint32_t fb) { current_output_fb = fb; } void unbind_output() { current_output_fb = 0; } bool exit_on_gles_error = false; std::vector vertexData; std::vector coordData; void render_transformed_texture(wf::gles_texture_t tex, const gl_geometry& g, const gl_geometry& texg, glm::mat4 model, glm::vec4 color, uint32_t bits) { // We don't expect any errors from us! disable_gl_call = true; program.use(tex.type); vertexData = { g.x1, g.y2, g.x2, g.y2, g.x2, g.y1, g.x1, g.y1, }; gl_geometry final_texg = (bits & TEXTURE_USE_TEX_GEOMETRY) ? texg : gl_geometry{0.0f, 0.0f, 1.0f, 1.0f}; if (bits & TEXTURE_TRANSFORM_INVERT_Y) { final_texg.y1 = 1.0 - final_texg.y1; final_texg.y2 = 1.0 - final_texg.y2; } if (bits & TEXTURE_TRANSFORM_INVERT_X) { final_texg.x1 = 1.0 - final_texg.x1; final_texg.x2 = 1.0 - final_texg.x2; } coordData = { final_texg.x1, final_texg.y1, final_texg.x2, final_texg.y1, final_texg.x2, final_texg.y2, final_texg.x1, final_texg.y2, }; program.set_active_texture(tex); program.attrib_pointer("position", 2, 0, vertexData.data()); program.attrib_pointer("uvPosition", 2, 0, coordData.data()); program.uniformMatrix4f("MVP", model); program.uniform4f("color", color); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); if (bits & RENDER_FLAG_CACHED) { return; } draw_cached(); clear_cached(); } void draw_cached() { GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); } void clear_cached() { disable_gl_call = false; program.deactivate(); } void render_transformed_texture(wf::gles_texture_t texture, const wf::geometry_t& geometry, glm::mat4 transform, glm::vec4 color, uint32_t bits) { bits &= ~TEXTURE_USE_TEX_GEOMETRY; gl_geometry gg; gg.x1 = geometry.x; gg.y1 = geometry.y; gg.x2 = gg.x1 + geometry.width; gg.y2 = gg.y1 + geometry.height; render_transformed_texture(texture, gg, {}, transform, color, bits); } void render_texture(wf::gles_texture_t texture, const wf::render_target_t& framebuffer, const wf::geometry_t& geometry, glm::vec4 color, uint32_t bits) { render_transformed_texture(texture, geometry, wf::gles::render_target_orthographic_projection(framebuffer), color, bits); } void render_rectangle(wf::geometry_t geometry, wf::color_t color, glm::mat4 matrix) { color_program.use(wf::TEXTURE_TYPE_RGBA); float x = geometry.x, y = geometry.y, w = geometry.width, h = geometry.height; GLfloat vertexData[] = { x, y + h, x + w, y + h, x + w, y, x, y, }; color_program.attrib_pointer("position", 2, 0, vertexData); color_program.uniformMatrix4f("MVP", matrix); color_program.uniform4f("color", {color.r, color.g, color.b, color.a}); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); color_program.deactivate(); } void clear(wf::color_t col, uint32_t mask) { GL_CALL(glClearColor(col.r, col.g, col.b, col.a)); GL_CALL(glClear(mask)); } } static bool egl_make_current(struct wlr_egl *egl) { if (!eglMakeCurrent(wlr_egl_get_display(egl), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(egl))) { LOGE("eglMakeCurrent failed"); return false; } return true; } static bool egl_is_current(struct wlr_egl *egl) { return eglGetCurrentContext() == wlr_egl_get_context(egl); } bool wf::gles::ensure_context(bool fail_on_error) { bool is_gles2 = wf::get_core().is_gles2(); if (fail_on_error && !is_gles2) { wf::dassert(false, "Wayfire not running with GLES renderer, no GL calls allowed!"); } if (!is_gles2) { return false; } if (!egl_is_current(wf::get_core_impl().egl)) { egl_make_current(wf::get_core_impl().egl); } return true; } [[maybe_unused]] static std::string framebuffer_status_to_str(GLuint status) { switch (status) { case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "incomplete attachment"; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "missing attachment"; case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: return "incomplete dimensions"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "incomplete multisample"; default: return "unknown"; } } GLuint wf::gles::ensure_render_buffer_fb_id(const render_buffer_t& buffer) { return wlr_gles2_renderer_get_buffer_fbo(wf::get_core().renderer, buffer.get_buffer()); } void wf::gles::bind_render_buffer(const wf::render_buffer_t& buffer) { GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ensure_render_buffer_fb_id(buffer))); GL_CALL(glViewport(0, 0, buffer.get_size().width, buffer.get_size().height)); } void wf::gles::scissor_render_buffer(const wf::render_buffer_t& buffer, wlr_box box) { GL_CALL(glEnable(GL_SCISSOR_TEST)); GL_CALL(glScissor(box.x, box.y, box.width, box.height)); } glm::mat4 wf::gles::render_target_orthographic_projection(const wf::render_target_t& target) { auto ortho = glm::ortho(1.0f * target.geometry.x, 1.0f * target.geometry.x + 1.0f * target.geometry.width, 1.0f * target.geometry.y + 1.0f * target.geometry.height, 1.0f * target.geometry.y); return gles::render_target_gl_to_framebuffer(target) * ortho; } glm::mat4 wf::gles::render_target_gl_to_framebuffer(const wf::render_target_t& target) { if (target.subbuffer) { auto sub = target.subbuffer.value(); float scale_x = 1.0 * sub.width / target.get_size().width; float scale_y = 1.0 * sub.height / target.get_size().height; // Translation is calculated between the midpoint of the whole buffer // and the midpoint of the subbuffer, then scaled to NDC. float half_w = target.get_size().width / 2.0; float half_h = target.get_size().height / 2.0; float translate_x = ((sub.x + sub.width / 2.0) - half_w) / half_w; float translate_y = ((sub.y + sub.height / 2.0) - half_h) / half_h; glm::mat4 scale = glm::scale(glm::mat4(1.0), glm::vec3(scale_x, scale_y, 1.0)); glm::mat4 translate = glm::translate(glm::mat4(1.0), glm::vec3(translate_x, translate_y, 0.0)); return translate * scale * gles::output_transform(target); } return gles::output_transform(target); } glm::mat4 wf::gles::output_transform(const render_target_t& target) { return get_output_matrix_from_transform( wlr_output_transform_compose(target.wl_transform, WL_OUTPUT_TRANSFORM_FLIPPED_180)); } void wf::gles::render_target_logic_scissor(const wf::render_target_t& target, wlr_box box) { wf::gles::scissor_render_buffer(target, target.framebuffer_box_from_geometry_box(box)); } /* look up the actual values of wl_output_transform enum * All _flipped transforms have values (regular_transform + 4) */ glm::mat4 get_output_matrix_from_transform(wl_output_transform transform) { glm::mat4 scale = glm::mat4(1.0); if (transform >= 4) { scale = glm::scale(scale, {-1, 1, 1}); } /* remove the third bit if it's set */ uint32_t rotation = transform & (~4); glm::mat4 rotation_matrix(1.0); if (rotation == WL_OUTPUT_TRANSFORM_90) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians(90.0f), {0, 0, 1}); } if (rotation == WL_OUTPUT_TRANSFORM_180) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians( 180.0f), {0, 0, 1}); } if (rotation == WL_OUTPUT_TRANSFORM_270) { rotation_matrix = glm::rotate(rotation_matrix, glm::radians( 270.0f), {0, 0, 1}); } return rotation_matrix * scale; } namespace wf { wf::gles_texture_t::gles_texture_t() {} wf::gles_texture_t::gles_texture_t(GLuint tex) { this->tex_id = tex; } wf::gles_texture_t::gles_texture_t(wf::texture_t tex) : wf::gles_texture_t(tex.texture, tex.source_box) {} wf::gles_texture_t::gles_texture_t(wlr_texture *texture, std::optional viewport) { wf::dassert(wlr_texture_is_gles2(texture)); wlr_gles2_texture_attribs attribs; wlr_gles2_texture_get_attribs(texture, &attribs); /* Wayfire works in inverted Y while wlroots doesn't, so we do invert here */ this->invert_y = true; this->target = attribs.target; this->tex_id = attribs.tex; if (this->target == GL_TEXTURE_2D) { this->type = attribs.has_alpha ? wf::TEXTURE_TYPE_RGBA : wf::TEXTURE_TYPE_RGBX; } else { this->type = wf::TEXTURE_TYPE_EXTERNAL; } if (viewport) { this->has_viewport = true; auto width = texture->width; auto height = texture->height; viewport_box.x1 = viewport->x / width; viewport_box.x2 = (viewport->x + viewport->width) / width; viewport_box.y1 = 1.0 - (viewport->y + viewport->height) / height; viewport_box.y2 = 1.0 - (viewport->y) / height; } } gles_texture_t gles_texture_t::from_aux(auxilliary_buffer_t& buffer, std::optional viewport) { wf::gles_texture_t tex{buffer.get_texture(), viewport}; gles::run_in_context([&] { GL_CALL(glBindTexture(tex.target, tex.tex_id)); GL_CALL(glTexParameteri(tex.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(tex.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glBindTexture(tex.target, 0)); }); return tex; } } // namespace wf namespace OpenGL { class program_t::impl { public: std::set active_attrs; std::set active_attrs_divisors; int active_program_idx = 0; int id[wf::TEXTURE_TYPE_ALL]; std::unordered_map uniforms[wf::TEXTURE_TYPE_ALL]; /** Find the uniform location for the currently bound program */ int find_uniform_loc(const std::string& name) { auto it = uniforms[active_program_idx].find(name); if (it != uniforms[active_program_idx].end()) { return it->second; } uniforms[active_program_idx][name] = GL_CALL(glGetUniformLocation(id[active_program_idx], name.c_str())); if (uniforms[active_program_idx][name] == -1) { LOGE("Uniform ", name, " not found in program"); } return uniforms[active_program_idx][name]; } std::map attribs[wf::TEXTURE_TYPE_ALL]; /** Find the attrib location for the currently bound program */ int find_attrib_loc(const std::string& name) { auto it = attribs[active_program_idx].find(name); if (it != attribs[active_program_idx].end()) { return it->second; } attribs[active_program_idx][name] = GL_CALL(glGetAttribLocation(id[active_program_idx], name.c_str())); return attribs[active_program_idx][name]; } }; program_t::program_t() { this->priv = std::make_unique(); for (int i = 0; i < wf::TEXTURE_TYPE_ALL; i++) { this->priv->id[i] = 0; } } void program_t::set_simple(GLuint program_id, wf::texture_type_t type) { free_resources(); wf::dassert(type < wf::TEXTURE_TYPE_ALL); this->priv->id[type] = program_id; } program_t::~program_t() {} static std::string replace_builtin_with(const std::string& source, const std::string& builtin, const std::string& with) { size_t pos = source.find(builtin); if (pos == std::string::npos) { return source; } return source.substr(0, pos) + with + source.substr(pos + builtin.length()); } static const std::string builtin = "@builtin@"; static const std::string builtin_ext = "@builtin_ext@"; struct texture_type_builtins { std::string builtin; std::string builtin_ext; }; std::map builtins = { {wf::TEXTURE_TYPE_RGBA, {builtin_rgba_source, ""}}, {wf::TEXTURE_TYPE_RGBX, {builtin_rgbx_source, ""}}, {wf::TEXTURE_TYPE_EXTERNAL, {builtin_external_source, builtin_ext_external_source}}, }; void program_t::compile(const std::string& vertex_source, const std::string& fragment_source) { free_resources(); for (const auto& program_type : builtins) { auto fragment = replace_builtin_with(fragment_source, builtin, program_type.second.builtin); fragment = replace_builtin_with(fragment, builtin_ext, program_type.second.builtin_ext); this->priv->id[program_type.first] = compile_program(vertex_source, fragment); } } void program_t::free_resources() { for (int i = 0; i < wf::TEXTURE_TYPE_ALL; i++) { if (this->priv->id[i]) { GL_CALL(glDeleteProgram(priv->id[i])); this->priv->id[i] = 0; } priv->uniforms[i].clear(); priv->attribs[i].clear(); } } void program_t::use(wf::texture_type_t type) { if (priv->id[type] == 0) { throw std::runtime_error("program_t has no program for type " + std::to_string(type)); } GL_CALL(glUseProgram(priv->id[type])); priv->active_program_idx = type; } int program_t::get_program_id(wf::texture_type_t type) { return priv->id[type]; } void program_t::uniform1i(const std::string& name, int value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform1i(loc, value)); } void program_t::uniform1f(const std::string& name, float value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform1f(loc, value)); } void program_t::uniform2f(const std::string& name, float x, float y) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform2f(loc, x, y)); } void program_t::uniform3f(const std::string& name, float x, float y, float z) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform3f(loc, x, y, z)); } void program_t::uniform4f(const std::string& name, const glm::vec4& value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniform4f(loc, value.r, value.g, value.b, value.a)); } void program_t::uniformMatrix4f(const std::string& name, const glm::mat4& value) { int loc = priv->find_uniform_loc(name); GL_CALL(glUniformMatrix4fv(loc, 1, GL_FALSE, &value[0][0])); } void program_t::attrib_pointer(const std::string& attrib, int size, int stride, const void *ptr, GLenum type) { int loc = priv->find_attrib_loc(attrib); priv->active_attrs.insert(loc); GL_CALL(glEnableVertexAttribArray(loc)); GL_CALL(glVertexAttribPointer(loc, size, type, GL_FALSE, stride, ptr)); } void program_t::attrib_divisor(const std::string& attrib, int divisor) { int loc = priv->find_attrib_loc(attrib); priv->active_attrs_divisors.insert(loc); GL_CALL(glVertexAttribDivisor(loc, divisor)); } void program_t::set_active_texture(const wf::gles_texture_t& texture) { GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(texture.target, texture.tex_id)); GL_CALL(glTexParameteri(texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glm::vec2 base{0.0f, 0.0f}; glm::vec2 scale{1.0f, 1.0f}; if (texture.has_viewport) { scale.x = texture.viewport_box.x2 - texture.viewport_box.x1; scale.y = texture.viewport_box.y2 - texture.viewport_box.y1; base.x = texture.viewport_box.x1; base.y = texture.viewport_box.y1; } if (texture.invert_y) { scale.y *= -1; base.y = 1.0 - base.y; } uniform2f("_wayfire_uv_base", base.x, base.y); uniform2f("_wayfire_uv_scale", scale.x, scale.y); } void program_t::deactivate() { for (int loc : priv->active_attrs_divisors) { GL_CALL(glVertexAttribDivisor(loc, 0)); } for (int loc : priv->active_attrs) { GL_CALL(glDisableVertexAttribArray(loc)); } priv->active_attrs_divisors.clear(); priv->active_attrs.clear(); GL_CALL(glUseProgram(0)); } } wayfire-0.10.0/src/core/object.cpp0000664000175000017500000000621015053502647016621 0ustar dkondordkondor#include "wayfire/object.hpp" #include #include #include struct wf::signal::provider_t::impl { std::unordered_map> typed_connections; }; wf::signal::provider_t::provider_t() { this->priv = std::make_unique(); } wf::signal::provider_t::~provider_t() { for (auto& [id, connected] : priv->typed_connections) { connected.for_each([&] (connection_base_t *base) { disconnect_other_side(base); }); } } void wf::signal::provider_t::disconnect_other_side(connection_base_t *callback) { auto it = std::remove(callback->connected_to.begin(), callback->connected_to.end(), this); callback->connected_to.erase(it, callback->connected_to.end()); } void wf::signal::provider_t::connect_base(std::type_index idx, connection_base_t *callback) { priv->typed_connections[idx].push_back(callback); callback->connected_to.push_back(this); } void wf::signal::provider_t::for_each_connection( std::type_index type, std::function func) { priv->typed_connections[type].for_each(func); } void wf::signal::connection_base_t::disconnect() { auto connected_copy = this->connected_to; for (auto& x : connected_copy) { x->disconnect(this); } } void wf::signal::provider_t::disconnect(connection_base_t *callback) { disconnect_other_side(callback); for (auto& [id, connected] : priv->typed_connections) { connected.remove_all(callback); } } class wf::object_base_t::obase_impl { public: std::unordered_map> data; uint32_t object_id; }; wf::object_base_t::object_base_t() { this->obase_priv = std::make_unique(); static uint32_t global_id = 0; obase_priv->object_id = global_id++; } wf::object_base_t::~object_base_t() = default; std::string wf::object_base_t::to_string() const { return std::to_string(get_id()); } uint32_t wf::object_base_t::get_id() const { return obase_priv->object_id; } bool wf::object_base_t::has_data(std::string name) { return _fetch_data(name) != nullptr; } void wf::object_base_t::erase_data(std::string name) { if (!has_data(name)) { return; } auto data = std::move(obase_priv->data[name]); obase_priv->data.erase(name); data.reset(); } wf::custom_data_t*wf::object_base_t::_fetch_data(std::string name) { auto it = obase_priv->data.find(name); if (it == obase_priv->data.end()) { return nullptr; } return it->second.get(); } wf::custom_data_t*wf::object_base_t::_fetch_erase(std::string name) { auto data = obase_priv->data[name].release(); erase_data(name); return data; } void wf::object_base_t::_store_data(std::unique_ptr data, std::string name) { obase_priv->data[name] = std::move(data); } void wf::object_base_t::_clear_data() { std::vector keys; for (auto const& [key, val] : obase_priv->data) { keys.push_back(key); } for (const auto& key : keys) { erase_data(key); } } wayfire-0.10.0/src/core/plugin-loader.hpp0000664000175000017500000000425415053502647020130 0ustar dkondordkondor#pragma once #include #include #include "wayfire/plugin.hpp" #include "wayfire/util.hpp" #include namespace wf { struct loaded_plugin_t { // A pointer to the plugin std::unique_ptr instance; // A handle returned by dlopen(). void *so_handle; // A path to the .so file of the plugin. std::string so_path; }; struct plugin_manager_t { plugin_manager_t(); ~plugin_manager_t(); void reload_dynamic_plugins(); wf::wl_idle_call idle_reload_plugins; bool is_loading_plugin() const { return is_loading; } private: wf::option_wrapper_t plugins_opt; wf::option_wrapper_t enable_so_unloading; std::unordered_map loaded_plugins; void deinit_plugins(bool unloadable); std::optional load_plugin_from_file(std::string path); void load_static_plugins(); void destroy_plugin(loaded_plugin_t& plugin); bool is_loading = false; }; /** Helper functions */ template B union_cast(A object) { union { A x; B y; } helper; helper.x = object; return helper.y; } /** * Open a plugin file and check the file for version errors. * * On success, return the handle from dlopen() and the pointer to the * newInstance of the plugin. * * @return (dlopen() handle, newInstance pointer) */ std::pair get_new_instance_handle(const std::string& path, bool can_unload_so); /** * List the locations where wayfire's plugins are installed. * This function takes care of env variable WAYFIRE_PLUGIN_PATH, * as well as the default location. */ std::vector get_plugin_paths(); /** * Search each path specified in @param plugin_paths for a plugin named @param * plugin_name * @param plugin_paths A list of locations where wayfire plugins are installed * @param plugin_name The plugin to be searched. If @param plugin_name is an * absolute path, then it is returned without modification. */ std::optional get_plugin_path_for_name( std::vector plugin_paths, std::string plugin_name); } wayfire-0.10.0/src/core/scene.cpp0000664000175000017500000004450015053502647016454 0ustar dkondordkondor#include #include #include #include #include #include #include "scene-priv.hpp" #include "wayfire/geometry.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/signal-provider.hpp" #include namespace wf { bool keyboard_focus_node_t::operator <(const keyboard_focus_node_t& other) const { if (this->importance == other.importance) { if (this->node && other.node) { auto ts_self = node->keyboard_interaction().last_focus_timestamp; auto ts_other = other.node->keyboard_interaction().last_focus_timestamp; return ts_self < ts_other; } if (!this->node && !other.node) { return false; } // Prefer to focus with node return !this->node; } return this->importance < other.importance; } namespace scene { // ---------------------------------- node_t ----------------------------------- node_t::~node_t() {} node_t::node_t(bool is_structure) { this->_is_structure = is_structure; } void node_t::set_enabled(bool is_active) { enabled_counter += (is_active ? 1 : -1); } std::string node_t::stringify_flags() const { std::string fl = ""; if (flags() & ((int)node_flags::DISABLED)) { fl += "d"; } if (flags() & ((int)node_flags::RAW_INPUT)) { fl += "R"; } return "(" + fl + ")"; } std::optional node_t::find_node_at(const wf::pointf_t& at) { auto local = this->to_local(at); for (auto& node : get_children()) { if (!node->is_enabled()) { continue; } auto child_node = node->find_node_at(local); if (child_node.has_value()) { return child_node; } } return {}; } wf::keyboard_focus_node_t node_t::keyboard_refocus(wf::output_t *output) { wf::keyboard_focus_node_t result; for (auto& ch : this->get_children()) { if (!ch->is_enabled()) { continue; } auto ch_focus = ch->keyboard_refocus(output); result = std::max(result, ch_focus); if (!ch_focus.allow_focus_below) { result.allow_focus_below = false; break; } } return result; } bool floating_inner_node_t::set_children_list(std::vector new_list) { set_children_unchecked(std::move(new_list)); return true; } void node_t::set_children_unchecked(std::vector new_list) { node_damage_signal data; data.region |= get_bounding_box(); for (auto& node : this->children) { node->_parent = nullptr; } for (auto& node : new_list) { wf::dassert(node->parent() == nullptr, "Adding a child node twice!"); node->_parent = this; } this->children = std::move(new_list); data.region |= get_bounding_box(); this->emit(&data); } static int get_layer_index(const wf::scene::node_t *node) { using namespace wf::scene; if (auto root = dynamic_cast(node->parent())) { for (int layer = 0; layer < (int)layer::ALL_LAYERS; layer++) { if (root->layers[layer].get() == node) { return layer; } } } return -1; } std::string node_t::stringify() const { std::string description = "node "; int layer_idx = get_layer_index(this); if (layer_idx >= 0) { static constexpr const char *layer_names[] = { "background", "bottom", "workspace", "top", "unmanaged", "overlay", "lock", "dwidget" }; static_assert((sizeof(layer_names) / sizeof(layer_names[0])) == (size_t)layer::ALL_LAYERS); description = "layer_"; description += layer_names[layer_idx]; } return description + " " + stringify_flags(); } wf::pointf_t node_t::to_local(const wf::pointf_t& point) { return point; } wf::pointf_t node_t::to_global(const wf::pointf_t& point) { return point; } // Just listen for damage from the node and push it upwards class default_render_instance_t : public render_instance_t { protected: damage_callback push_damage; wf::signal::connection_t on_main_node_damaged = [=] (node_damage_signal *data) { push_damage(data->region); }; public: default_render_instance_t(node_t *self, damage_callback callback) { this->push_damage = callback; self->connect(&on_main_node_damaged); } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { // nothing to render here } void render(const wf::scene::render_instruction_t& data) override { wf::dassert(false, "Rendering an inner node?"); } direct_scanout try_scanout(wf::output_t *output) override { // Nodes without actual visual content do not prevent further nodes // from being scanned out. return direct_scanout::SKIP; } }; void node_t::gen_render_instances(std::vector & instances, damage_callback push_damage, wf::output_t *output) { // Add self for damage tracking instances.push_back( std::make_unique(this, push_damage)); // Add children as a flat list to avoid multiple indirections for (auto& ch : this->children) { if (ch->is_enabled()) { ch->gen_render_instances(instances, push_damage, output); } } } wf::geometry_t node_t::get_children_bounding_box() { if (children.empty()) { return {0, 0, 0, 0}; } int min_x = std::numeric_limits::max(); int min_y = std::numeric_limits::max(); int max_x = std::numeric_limits::min(); int max_y = std::numeric_limits::min(); for (auto& ch : children) { auto bbox = ch->get_bounding_box(); min_x = std::min(min_x, bbox.x); min_y = std::min(min_y, bbox.y); max_x = std::max(max_x, bbox.x + bbox.width); max_y = std::max(max_y, bbox.y + bbox.height); } return {min_x, min_y, max_x - min_x, max_y - min_y}; } wf::geometry_t node_t::get_bounding_box() { return get_children_bounding_box(); } uint32_t node_t::optimize_update(uint32_t flags) { if (!this->is_enabled()) { return flags | update_flag::MASKED; } return flags; } // ------------------------------ output_node_t -------------------------------- struct output_node_t::priv_t { wf::output_t *output; bool auto_limits = true; wf::signal::connection_t on_changed; wf::option_wrapper_t remove_output_limits{"workarounds/remove_output_limits"}; void update_limits(std::optional& limit_region) { if (!auto_limits) { // Plugins will set this manually return; } if (remove_output_limits) { limit_region = std::nullopt; } else { limit_region = output->get_layout_geometry(); } } }; output_node_t::output_node_t(wf::output_t *output, bool auto_limits) : floating_inner_node_t(true) { this->priv = std::make_unique(); this->priv->auto_limits = auto_limits; this->priv->output = output; this->priv->on_changed = [=] (wf::output_configuration_changed_signal *data) { this->priv->update_limits(this->limit_region); wf::scene::update(shared_from_this(), scene::update_flag::INPUT_STATE); }; this->priv->remove_output_limits.set_callback([=] () { this->priv->update_limits(this->limit_region); }); this->priv->update_limits(this->limit_region); output->connect(&priv->on_changed); } output_node_t::~output_node_t() {} std::string output_node_t::stringify() const { return "output " + this->priv->output->to_string() + " " + stringify_flags(); } wf::pointf_t output_node_t::to_local(const wf::pointf_t& point) { auto offset = wf::origin(priv->output->get_layout_geometry()); return {point.x - offset.x, point.y - offset.y}; } wf::pointf_t output_node_t::to_global(const wf::pointf_t& point) { auto offset = wf::origin(priv->output->get_layout_geometry()); return {point.x + offset.x, point.y + offset.y}; } std::optional output_node_t::find_node_at(const wf::pointf_t& at) { if (limit_region && !(*limit_region & at)) { return {}; } return node_t::find_node_at(at); } class output_render_instance_t : public default_render_instance_t { wf::output_t *output; output_node_t *self; std::vector children; public: output_render_instance_t(output_node_t *self, damage_callback callback, wf::output_t *output, wf::output_t *shown_on) : default_render_instance_t(self, transform_damage(callback)) { this->self = self; this->output = output; // Children are stored as a sublist, because we need to translate every // time between global and output-local geometry. for (auto& child : self->get_children()) { if (child->is_enabled()) { child->gen_render_instances(children, transform_damage(callback), shown_on); } } } damage_callback transform_damage(damage_callback child_damage) { return [=] (const wf::region_t& damage) { child_damage(damage + wf::origin(output->get_layout_geometry())); }; } void schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { if (self->limit_region) { wf::region_t our_damage = damage & *self->limit_region; our_damage &= target.geometry; if (!our_damage.empty()) { _schedule_instructions(instructions, target, our_damage); // Inside limit region, damage is defined as the children // decided. damage ^= *self->limit_region; damage |= our_damage & *self->limit_region; } } else { _schedule_instructions(instructions, target, damage); } } void _schedule_instructions(std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) { // In principle, we just have to schedule the children. // However, we need to adjust the target's geometry and the damage to // fit with the coordinate system of the output. auto offset = wf::origin(output->get_layout_geometry()); wf::render_target_t new_target = target.translated(-offset); damage += -offset; for (auto& ch : children) { ch->schedule_instructions(instructions, new_target, damage); } damage += offset; } direct_scanout try_scanout(wf::output_t *scanout) override { if ((scanout != this->output) && this->self->limit_region) { // Can't scanout on a different output because it is outside // of the limit region return direct_scanout::SKIP; } for (auto& ch : children) { auto res = ch->try_scanout(scanout); if (res != direct_scanout::SKIP) { return res; } } return direct_scanout::SKIP; } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { auto offset = wf::origin(output->get_layout_geometry()); compute_visibility_from_list(children, output, visible, offset); } }; void output_node_t::gen_render_instances( std::vector & instances, damage_callback push_damage, wf::output_t *shown_on) { if (this->limit_region && shown_on && (shown_on != this->priv->output)) { // If the limit region is set and we are limiting the generation of // instances to a particular region (typically an output), make sure we // generate instances only if the output will be visible. return; } instances.push_back( std::make_unique(this, push_damage, priv->output, shown_on)); } wf::geometry_t output_node_t::get_bounding_box() { const auto bbox = node_t::get_bounding_box(); return bbox + wf::origin(priv->output->get_layout_geometry()); } wf::output_t*output_node_t::get_output() const { return priv->output; } // ------------------------------ root_node_t ---------------------------------- root_node_t::root_node_t() : floating_inner_node_t(true) { std::vector children; this->priv = std::make_unique(); for (int i = (int)layer::ALL_LAYERS - 1; i >= 0; i--) { layers[i] = std::make_shared(true); children.push_back(layers[i]); } set_children_unchecked(children); } root_node_t::~root_node_t() {} std::string root_node_t::stringify() const { return "root " + stringify_flags(); } // ---------------------- generic scenegraph functions ------------------------- void set_node_enabled(wf::scene::node_ptr node, bool enabled) { bool was_enabled = node->is_enabled(); node->set_enabled(enabled); if (was_enabled != node->is_enabled()) { if (node->parent()) { node_damage_signal ev; ev.region = node->get_bounding_box(); node->parent()->emit(&ev); } update(node, update_flag::ENABLED); } } void update(node_ptr changed_node, uint32_t flags) { if ((flags & update_flag::CHILDREN_LIST) || (flags & update_flag::ENABLED) || (flags & update_flag::GEOMETRY)) { flags |= update_flag::INPUT_STATE; } if (!changed_node->is_enabled() && !(flags & update_flag::ENABLED)) { flags |= update_flag::MASKED; } node_update_signal data; data.node = changed_node.get(); data.flags = flags; changed_node->emit(&data); if (changed_node == wf::get_core().scene()) { root_node_update_signal data; data.flags = flags; wf::get_core().scene()->emit(&data); return; } if (changed_node->parent()) { flags = changed_node->parent()->optimize_update(flags); if (!changed_node->parent()->is_enabled()) { flags |= update_flag::MASKED; } update(changed_node->parent()->shared_from_this(), flags); } } floating_inner_node_t::~floating_inner_node_t() { for (auto& node : this->children) { node->_parent = nullptr; } } uint32_t optimize_nested_render_instances(wf::scene::node_ptr node, uint32_t flags) { if (flags & (update_flag::CHILDREN_LIST | update_flag::ENABLED)) { // If we update the list of children, there is no need to notify the whole scenegraph. // Instead, we can do a local update, and only update visibility. flags &= ~update_flag::CHILDREN_LIST; flags &= ~update_flag::ENABLED; flags |= update_flag::GEOMETRY | update_flag::INPUT_STATE; node_regen_instances_signal data; node->emit(&data); } return flags; } render_instance_manager_t::render_instance_manager_t(std::vector nodes, damage_callback on_damage, wf::output_t *reference_output) : nodes(nodes), on_damage(on_damage), reference_output(reference_output) { regen_instances(); this->on_update = [=] (node_update_signal *ev) { if (ev->flags & scene::update_flag::MASKED) { // Update is masked, but the starting node is updated. So it is about a disabled child node, which // we can ignore. On the other hand, if the starting node is disabled, all updates will be masked. // However in some cases (move-drag-interface, for example), or when rendering minimized views or // similar, the starting nodes are disabled, but we still want to render them. if (ev->node->is_enabled()) { return; } } constexpr uint32_t recompute_instances_on = scene::update_flag::CHILDREN_LIST | scene::update_flag::ENABLED; constexpr uint32_t recompute_visibility_on = recompute_instances_on | scene::update_flag::GEOMETRY; const auto& output_name = [&] { return reference_output ? reference_output->to_string() : "none"; }; const auto& is_root = [&] { return nodes.size() > 0 && nodes[0] == wf::get_core().scene(); }; if (ev->flags & recompute_instances_on) { LOGC(RENDER, this, ": Output ", output_name(), ": regenerating instances from ", nodes.size(), " nodes (root=", is_root() ? "true" : "false", ")."); regen_instances(); } if (ev->flags & recompute_visibility_on) { idle_visibility.run_once([=] () { LOGC(RENDER, this, ": Output ", output_name(), ": recomputing visibility from ", nodes.size(), " nodes (root=", is_root() ? "true" : "false", ")."); update_visibility(); }); } }; for (auto& node : nodes) { node->connect(&on_update); } } void render_instance_manager_t::regen_instances() { instances.clear(); for (auto& node : nodes) { node->gen_render_instances(instances, on_damage, reference_output); } } void render_instance_manager_t::set_visibility_region(wf::region_t region) { this->visibility_region = region; update_visibility(); } void render_instance_manager_t::update_visibility() { if (!reference_output || !visibility_region.has_value()) { return; } wf::region_t visibility = this->visibility_region.value(); for (auto& instance : instances) { instance->compute_visibility(reference_output, visibility); } } std::vector& render_instance_manager_t::get_instances() { return this->instances; } } // namespace scene } wayfire-0.10.0/src/core/shaders.tpp0000664000175000017500000000333715053502647017034 0ustar dkondordkondorstatic const char *default_vertex_shader_source = R"(#version 100 attribute highp vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; })"; static const char *default_fragment_shader_source = R"(#version 100 @builtin_ext@ @builtin@ varying highp vec2 uvpos; uniform highp vec4 color; void main() { highp vec4 tex_color = get_pixel(uvpos); tex_color.rgb = tex_color.rgb * color.a; gl_FragColor = tex_color * color; })"; static const char *color_rect_fragment_source = R"(#version 100 varying highp vec2 uvpos; uniform highp vec4 color; void main() { gl_FragColor = color; })"; static const char *builtin_rgba_source = R"( uniform sampler2D _wayfire_texture; uniform highp vec2 _wayfire_uv_base; uniform highp vec2 _wayfire_uv_scale; highp vec4 get_pixel(highp vec2 uv) { uv = _wayfire_uv_base + _wayfire_uv_scale * uv; return texture2D(_wayfire_texture, uv); } )"; static const char *builtin_rgbx_source = R"( uniform sampler2D _wayfire_texture; uniform highp vec2 _wayfire_uv_base; uniform highp vec2 _wayfire_uv_scale; highp vec4 get_pixel(highp vec2 uv) { uv = _wayfire_uv_base + _wayfire_uv_scale * uv; return vec4(texture2D(_wayfire_texture, uv).rgb, 1.0); } )"; static const char *builtin_external_source = R"( uniform samplerExternalOES _wayfire_texture; uniform highp vec2 _wayfire_uv_base; uniform highp vec2 _wayfire_uv_scale; highp vec4 get_pixel(highp vec2 uv) { uv = _wayfire_uv_base + _wayfire_uv_scale * uv; return texture2D(_wayfire_texture, uv); } )"; static const char *builtin_ext_external_source = R"(#extension GL_OES_EGL_image_external : require )"; wayfire-0.10.0/src/core/img.cpp0000664000175000017500000002445215053502647016137 0ustar dkondordkondor#include #include #include #include "wayfire/img.hpp" #include "wayfire/opengl.hpp" #include "wayfire/core.hpp" #include #ifdef BUILD_WITH_IMAGEIO #include #include #include #endif #include #include #include #include #include #include #define TEXTURE_LOAD_ERROR 0 namespace image_io { using Loader = std::function; using Writer = std::function; namespace { std::unordered_map loaders; std::unordered_map writers; } bool load_data_as_cubemap(unsigned char *data, int width, int height, int channels) { width /= 4; height /= 3; int x, y, t; if (width != height) { LOGE("cubemap width / 4(", width, ") != height / 3(", height, ")"); return false; } /* * CUBEMAP IMAGE FORMAT * * 0 1 2 3 * _____________________ * 0 | X | T | X | X | * |____|____|____|____| * 1 | R | F | L | BA | * |____|____|____|____| * 2 | X | BO | X | X | * |____|____|____|____| * * WIDTH / 4 == HEIGHT / 3 * * X : UNUSED * T: TOP * R: RIGHT * F: FRONT * L: LEFT * BA: BACK * BO: BOTTOM * */ for (t = GL_TEXTURE_CUBE_MAP_POSITIVE_X; t < GL_TEXTURE_CUBE_MAP_POSITIVE_X + 6; t++) { switch (t) { case GL_TEXTURE_CUBE_MAP_POSITIVE_X: x = 2, y = 1; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: x = 0, y = 1; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: x = 1, y = 0; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: x = 1, y = 2; break; case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: x = 1, y = 1; break; case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: x = 3, y = 1; break; default: return false; break; } auto format = (channels == 4 ? GL_RGBA : GL_RGB); GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, width * 4)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_ROWS, y * height)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, x * width)); GL_CALL(glTexImage2D(t, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data)); } GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0)); GL_CALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0)); return true; } #ifdef BUILD_WITH_IMAGEIO /* All backend functions are taken from the internet. * If you want to be credited, contact me */ bool texture_from_png(const char *filename, GLuint target) { FILE *fp = fopen(filename, "rb"); int width, height; png_byte color_type; png_byte bit_depth; png_bytep *row_pointers; png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { fclose(fp); return false; } png_infop infos = png_create_info_struct(png); if (!infos) { fclose(fp); return false; } if (setjmp(png_jmpbuf(png))) { fclose(fp); return false; } png_init_io(png, fp); png_read_info(png, infos); width = png_get_image_width(png, infos); height = png_get_image_height(png, infos); color_type = png_get_color_type(png, infos); bit_depth = png_get_bit_depth(png, infos); if (bit_depth == 16) { png_set_strip_16(png); } if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png); } // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8)) { png_set_expand_gray_1_2_4_to_8(png); } if (png_get_valid(png, infos, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png); } // These color_type don't have an alpha channel then fill it with 0xff. if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_PALETTE)) { png_set_filler(png, 0xFF, PNG_FILLER_AFTER); } if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)) { png_set_gray_to_rgb(png); } png_read_update_info(png, infos); row_pointers = new png_bytep[height]; png_byte *data = new png_byte[height * png_get_rowbytes(png, infos)]; for (int i = 0; i < height; i++) { row_pointers[i] = data + i * png_get_rowbytes(png, infos); } png_read_image(png, row_pointers); if (target == GL_TEXTURE_CUBE_MAP) { if (!load_data_as_cubemap(data, width, height, png_get_channels(png, infos))) { png_destroy_read_struct(&png, &infos, NULL); delete[] row_pointers; delete[] data; fclose(fp); return false; } } else if (target == GL_TEXTURE_2D) { GL_CALL(glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)data)); } png_destroy_read_struct(&png, &infos, NULL); delete[] row_pointers; delete[] data; fclose(fp); return true; } void texture_to_png(const char *name, uint8_t *pixels, int w, int h, bool invert) { png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { return; } png_infop infot = png_create_info_struct(png); if (!infot) { png_destroy_write_struct(&png, &infot); return; } FILE *fp = fopen(name, "wb"); if (!fp) { png_destroy_write_struct(&png, &infot); return; } png_init_io(png, fp); png_set_IHDR(png, infot, w, h, 8 /* depth */, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_colorp palette = (png_colorp)png_malloc(png, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (!palette) { fclose(fp); png_destroy_write_struct(&png, &infot); return; } png_set_PLTE(png, infot, palette, PNG_MAX_PALETTE_LENGTH); png_write_info(png, infot); png_set_packing(png); png_bytepp rows = (png_bytepp)png_malloc(png, h * sizeof(png_bytep)); for (int i = 0; i < h; ++i) { if (invert) { rows[i] = (png_bytep)(pixels + (h - i - 1) * w * 4); } else { rows[i] = (png_bytep)(pixels + i * w * 4); } } png_write_image(png, rows); png_write_end(png, infot); png_free(png, palette); png_destroy_write_struct(&png, &infot); fclose(fp); png_free(png, rows); } bool texture_from_jpeg(const char *FileName, GLuint target) { unsigned long data_size; unsigned char *rowptr[1]; unsigned char *jdata; struct jpeg_decompress_struct infot; struct jpeg_error_mgr err; std::FILE *file = fopen(FileName, "rb"); infot.err = jpeg_std_error(&err); jpeg_create_decompress(&infot); if (!file) { LOGE("failed to read JPEG file ", FileName); return false; } jpeg_stdio_src(&infot, file); jpeg_read_header(&infot, TRUE); jpeg_start_decompress(&infot); data_size = infot.output_width * infot.output_height * 3; jdata = new unsigned char [data_size]; while (infot.output_scanline < infot.output_height) { rowptr[0] = (unsigned char*)jdata + 3 * infot.output_width * infot.output_scanline; jpeg_read_scanlines(&infot, rowptr, 1); } jpeg_finish_decompress(&infot); GLint width = infot.output_width; GLint height = infot.output_height; if (target == GL_TEXTURE_CUBE_MAP) { if (!load_data_as_cubemap(jdata, width, height, 3)) { fclose(file); delete[] jdata; return false; } } else if (target == GL_TEXTURE_2D) { GL_CALL(glTexImage2D(target, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, jdata)); } fclose(file); delete[] jdata; return true; } #endif bool load_from_file(std::string name, GLuint target) { if (access(name.c_str(), F_OK) == -1) { if (!name.empty()) { LOGE(__func__, "() cannot access ", name); } return false; } int len = name.length(); if ((len < 4) || (name[len - 4] != '.')) { LOGE( "load_from_file() called with file without extension or with invalid extension!"); return false; } auto ext = name.substr(len - 3, 3); for (int i = 0; i < 3; i++) { ext[i] = std::tolower(ext[i]); } auto it = loaders.find(ext); if (it == loaders.end()) { LOGE("load_from_file() called with unsupported extension ", ext); return false; } else { return it->second(name.c_str(), target); } } void write_to_file(std::string name, uint8_t *pixels, int w, int h, std::string type, bool invert) { auto it = writers.find(type); if (it == writers.end()) { LOGE("unsupported image_writer backend"); } else { it->second(name.c_str(), pixels, w, h, invert); } } void write_to_file(std::string name, const wf::render_buffer_t& fb) { auto tex = wlr_texture_from_buffer(wf::get_core().renderer, fb.get_buffer()); std::vector buffer(tex->width * tex->height * 4); wlr_texture_read_pixels_options opts{}; opts.data = buffer.data(); opts.format = DRM_FORMAT_ABGR8888; opts.stride = tex->width * 4; if (!wlr_texture_read_pixels(tex, &opts)) { LOGE("failed to read pixels from texture"); wlr_texture_destroy(tex); return; } write_to_file(name, (uint8_t*)buffer.data(), tex->width, tex->height, "png", false); wlr_texture_destroy(tex); } void init() { LOGD("init ImageIO"); #ifdef BUILD_WITH_IMAGEIO loaders["png"] = Loader(texture_from_png); loaders["jpg"] = Loader(texture_from_jpeg); writers["png"] = Writer(texture_to_png); #endif } } wayfire-0.10.0/src/core/plugin-loader.cpp0000664000175000017500000002361615053502647020126 0ustar dkondordkondor#include #include #include #include #include #include "config.h" #include "plugin-loader.hpp" #include "../core/wm.hpp" #include "wayfire/plugin.hpp" #include wf::plugin_manager_t::plugin_manager_t() { this->plugins_opt.load_option("core/plugins"); this->enable_so_unloading.load_option("workarounds/enable_so_unloading"); reload_dynamic_plugins(); load_static_plugins(); this->plugins_opt.set_callback([=] () { /* reload when config reload has finished */ idle_reload_plugins.run_once([&] () { reload_dynamic_plugins(); }); }); } void wf::plugin_manager_t::deinit_plugins(bool unloadable) { for (auto& [name, plugin] : loaded_plugins) { if (!plugin.instance) { continue; } if (plugin.instance->is_unloadable() == unloadable) { destroy_plugin(plugin); } } } wf::plugin_manager_t::~plugin_manager_t() { /* First remove unloadable plugins, then others */ deinit_plugins(true); deinit_plugins(false); loaded_plugins.clear(); } void wf::plugin_manager_t::destroy_plugin(wf::loaded_plugin_t& p) { LOGD("Unloading plugin ", p.so_path); p.instance->fini(); p.instance.reset(); /* dlopen()/dlclose() do reference counting, so we should close the plugin * as many times as we opened it. * * We also need to close the handle after deallocating the plugin, otherwise * we unload its destructor before calling it. * * Note however, that dlclose() is merely a "statement of intent" as per * POSIX[1]: * - On glibc[2], this decreases the reference count and potentially unloads * the binary. * - On musl-libc[3] this is a noop. * * [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html * [2]: https://man7.org/linux/man-pages/man3/dlclose.3.html * [3]: * https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries * */ if (p.so_handle && enable_so_unloading) { dlclose(p.so_handle); } } static bool check_plugin_api_version(const std::string& path, bool can_unload_so) { // First, open everything just locally and in a lazy way. // We want to check just the API/ABI version. // If we load with RTLD_NOW, if the API/ABI version is wrong, we may get a crash just by // dlopen()-ing the plugin. void *handle = dlopen(path.c_str(), RTLD_LOCAL | RTLD_LAZY); if (handle == NULL) { LOGE("error loading plugin [", path, "]: ", dlerror()); return false; } /* Check plugin version */ auto version_func_ptr = dlsym(handle, "getWayfireVersion"); if (version_func_ptr == NULL) { LOGE(path, ": missing getWayfireVersion()", path.c_str()); dlclose(handle); return false; } auto version_func = wf::union_cast(version_func_ptr); int32_t plugin_abi_version = version_func(); if (version_func() != WAYFIRE_API_ABI_VERSION) { LOGE(path, ": API/ABI version mismatch: Wayfire is ", WAYFIRE_API_ABI_VERSION, ", plugin built with ", plugin_abi_version); dlclose(handle); return false; } if (can_unload_so) { dlclose(handle); } return true; } std::pair wf::get_new_instance_handle(const std::string& path, bool can_unload_so) { if (!check_plugin_api_version(path, can_unload_so)) { return {nullptr, nullptr}; } // RTLD_GLOBAL is required for RTTI/dynamic_cast across plugins void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL); if (handle == NULL) { LOGE("error loading plugin [", path, "]: ", dlerror()); return {nullptr, nullptr}; } /* Check plugin version */ auto new_instance_func_ptr = dlsym(handle, "newInstance"); if (new_instance_func_ptr == NULL) { LOGE(path, ": missing newInstance(). ", dlerror()); dlclose(handle); return {nullptr, nullptr}; } LOGD("Loaded plugin ", path.c_str()); return {handle, new_instance_func_ptr}; } std::optional wf::plugin_manager_t::load_plugin_from_file(std::string path) { auto [handle, new_instance_func_ptr] = wf::get_new_instance_handle(path, enable_so_unloading); if (new_instance_func_ptr) { auto new_instance_func = union_cast(new_instance_func_ptr); loaded_plugin_t lp; try { lp.instance = std::unique_ptr(new_instance_func()); lp.so_handle = handle; lp.so_path = path; return lp; } catch (...) { LOGE("Failed to load plugin \"", path, "\". "); if (enable_so_unloading) { dlclose(handle); } } } return {}; } void wf::plugin_manager_t::reload_dynamic_plugins() { is_loading = true; std::string plugin_list = plugins_opt; if (plugin_list == "none") { LOGE("No plugins specified in the config file, or config file is " "missing. In this state the compositor is nearly unusable, please " "ensure your configuration file is set up properly."); } std::stringstream stream(plugin_list); std::vector next_plugins; std::vector plugin_paths = wf::get_plugin_paths(); std::string plugin_name; while (stream >> plugin_name) { if (plugin_name.size()) { auto plugin_path = wf::get_plugin_path_for_name(plugin_paths, plugin_name); if (plugin_path) { auto already_loaded = std::find(next_plugins.begin(), next_plugins.end(), plugin_path.value()); if (already_loaded != next_plugins.end()) { LOGE(plugin_name, " plugin found in the plugin list more than once, skipping"); continue; } next_plugins.push_back(plugin_path.value()); } else { LOGE("Failed to load plugin \"", plugin_name, "\". ", "Make sure it is installed in ", PLUGIN_PATH, " or in $WAYFIRE_PLUGIN_PATH."); } } } /* erase plugins that have been removed from the config */ auto it = loaded_plugins.begin(); while (it != loaded_plugins.end()) { /* skip built-in(static) plugins */ if (it->first.size() && (it->first[0] == '_')) { ++it; continue; } if ((std::find(next_plugins.begin(), next_plugins.end(), it->first) == next_plugins.end()) && it->second.instance->is_unloadable()) { LOGD("unload plugin ", it->first.c_str()); destroy_plugin(it->second); it = loaded_plugins.erase(it); } else { ++it; } } /* load new plugins */ std::vector> pending_initialize; for (auto plugin : next_plugins) { if (loaded_plugins.count(plugin)) { continue; } std::optional ptr = load_plugin_from_file(plugin); if (ptr) { pending_initialize.emplace_back(plugin, std::move(*ptr)); } } std::stable_sort(pending_initialize.begin(), pending_initialize.end(), [] (const auto& a, const auto& b) { return a.second.instance->get_order_hint() < b.second.instance->get_order_hint(); }); for (auto& [plugin, ptr] : pending_initialize) { try { ptr.instance->init(); loaded_plugins[plugin] = std::move(ptr); } catch (...) { // this will call fini(), the destructor and optionally unload the .so destroy_plugin(ptr); LOGE("Failed to init plugin \"", plugin_name, "\". "); } } is_loading = false; } template static wf::loaded_plugin_t create_plugin(std::string name) { wf::loaded_plugin_t lp; lp.instance = std::make_unique(); lp.so_handle = nullptr; lp.so_path = name; lp.instance->init(); return lp; } void wf::plugin_manager_t::load_static_plugins() { loaded_plugins["_exit"] = create_plugin>("_exit"); loaded_plugins["_focus"] = create_plugin("_focus"); loaded_plugins["_close"] = create_plugin>("_close"); } std::vector wf::get_plugin_paths() { std::vector plugin_prefixes; if (char *plugin_path = getenv("WAYFIRE_PLUGIN_PATH")) { std::stringstream ss(plugin_path); std::string entry; while (std::getline(ss, entry, ':')) { plugin_prefixes.push_back(entry); } } // also add XDG specific paths std::string xdg_data_dir; char *c_xdg_data_dir = std::getenv("XDG_DATA_HOME"); char *c_user_home = std::getenv("HOME"); if (c_xdg_data_dir != NULL) { xdg_data_dir = c_xdg_data_dir; } else if (c_user_home != NULL) { xdg_data_dir = (std::string)c_user_home + "/.local/share/"; } if (xdg_data_dir != "") { plugin_prefixes.push_back(xdg_data_dir + "/wayfire/plugins"); } plugin_prefixes.push_back(PLUGIN_PATH); return plugin_prefixes; } std::optional wf::get_plugin_path_for_name( std::vector plugin_paths, std::string plugin_name) { if (plugin_name.at(0) == '/') { return plugin_name; } for (std::filesystem::path plugin_prefix : plugin_paths) { auto plugin_path = plugin_prefix / ("lib" + plugin_name + ".so"); if (std::filesystem::exists(plugin_path)) { return plugin_path; } } return {}; } wayfire-0.10.0/src/core/seat/0000775000175000017500000000000015053502647015604 5ustar dkondordkondorwayfire-0.10.0/src/core/seat/pointer.hpp0000664000175000017500000001236115053502647020000 0ustar dkondordkondor#ifndef WF_SEAT_POINTER_HPP #define WF_SEAT_POINTER_HPP #include #include #include #include #include #include "wayfire/scene-input.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include namespace wf { class input_manager_t; class seat_t; /** * Represents the "mouse cursor" part of a wf_cursor, i.e functionality provided * by touchpads, regular mice, trackpoints and similar. * * It is responsible for managing the focused surface and processing input * events from the aforementioned devices. */ class pointer_t { public: pointer_t(nonstd::observer_ptr input, nonstd::observer_ptr seat); ~pointer_t(); /** * Enable/disable the logical pointer's focusing abilities. * The requests are counted, i.e if set_enable_focus(false) is called twice, * set_enable_focus(true) must be called also twice to restore focus. * * When a logical pointer is disabled, it means that no input surface can * receive pointer focus. */ void set_enable_focus(bool enabled = true); /** Get the currenntlly set cursor focus */ wf::scene::node_ptr get_focus() const; /** Handle events coming from the input devices */ void handle_pointer_axis(wlr_pointer_axis_event *ev, input_event_processing_mode_t mode); void handle_pointer_motion(wlr_pointer_motion_event *ev, input_event_processing_mode_t mode); void handle_pointer_motion_absolute(wlr_pointer_motion_absolute_event *ev, input_event_processing_mode_t mode); void handle_pointer_button(wlr_pointer_button_event *ev, input_event_processing_mode_t mode); /** Handle touchpad gestures detected by libinput */ void handle_pointer_swipe_begin(wlr_pointer_swipe_begin_event *ev, input_event_processing_mode_t mode); void handle_pointer_swipe_update(wlr_pointer_swipe_update_event *ev, input_event_processing_mode_t mode); void handle_pointer_swipe_end(wlr_pointer_swipe_end_event *ev, input_event_processing_mode_t mode); void handle_pointer_pinch_begin(wlr_pointer_pinch_begin_event *ev, input_event_processing_mode_t mode); void handle_pointer_pinch_update(wlr_pointer_pinch_update_event *ev, input_event_processing_mode_t mode); void handle_pointer_pinch_end(wlr_pointer_pinch_end_event *ev, input_event_processing_mode_t mode); void handle_pointer_hold_begin(wlr_pointer_hold_begin_event *ev, input_event_processing_mode_t mode); void handle_pointer_hold_end(wlr_pointer_hold_end_event *ev, input_event_processing_mode_t mode); void handle_pointer_frame(); /** Whether there are pressed buttons currently */ bool has_pressed_buttons() const; /** * Handle an update of the cursor's position, which includes updating the * surface currently under the pointer. * * @param time_msec The time when the event causing this update occurred */ void update_cursor_position(int64_t time_msec); /** * Transfer focus and pressed buttons to the given grab. */ void transfer_grab(scene::node_ptr node); private: nonstd::observer_ptr input; nonstd::observer_ptr seat; // Store the last motion / surface-local coords sent to the current focus. // This is useful to avoid sending repetitive motion events if we for ex. have scenegraph changes, but no // actual movement of the mouse. std::optional last_focus_coords; // Buttons sent to the client currently // Note that count_pressed_buttons also contains buttons not sent to the // client std::multiset currently_sent_buttons; wf::signal::connection_t on_root_node_updated; /** The surface which currently has cursor focus */ wf::scene::node_ptr cursor_focus = nullptr; /** Whether focusing is enabled */ int focus_enabled_count = 1; bool focus_enabled() const; void send_enter_to_focus(); /** * Set the pointer focus. */ void update_cursor_focus(wf::scene::node_ptr node); /** Number of currently-pressed mouse buttons */ int count_pressed_buttons = 0; /** Check whether an implicit grab should start/end */ void check_implicit_grab(); /** Implicitly grabbed node when a button is being held */ wf::scene::node_ptr grabbed_node = nullptr; /** Set the currently grabbed node * @param node The node to be grabbed, or nullptr to reset grab */ void grab_surface(wf::scene::node_ptr node); /** Send a button event to the currently active receiver, i.e to the * active input grab(if any), or to the focused surface */ void send_button(wlr_pointer_button_event *ev, bool has_binding); /** * Send a motion event to the currently active receiver, i.e to the * active grab or the focused surface. */ void send_motion(uint32_t time_msec); /** * Send synthetic button release events to the old cursor focus. */ void send_leave_to_focus(wf::scene::node_ptr old_focus); }; } #endif /* end of include guard: WF_SEAT_POINTER_HPP */ wayfire-0.10.0/src/core/seat/seat.cpp0000664000175000017500000004727515053502647017263 0ustar dkondordkondor#include "seat-impl.hpp" #include "cursor.hpp" #include "wayfire/config-backend.hpp" #include "wayfire/geometry.hpp" #include "../core-impl.hpp" #include "../view/view-impl.hpp" #include "keyboard.hpp" #include "pointer.hpp" #include "touch.hpp" #include "tablet.hpp" #include "input-manager.hpp" #include "wayfire/output-layout.hpp" #include #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include #include "wayfire/unstable/wlr-view-keyboard-interaction.hpp" #include "../../view/wlr-surface-pointer-interaction.hpp" #include "drag-icon.hpp" #include "wayfire/util.hpp" #include "wayfire/view.hpp" wf::seat_t::~seat_t() = default; void wf::seat_t::set_active_node(wf::scene::node_ptr node, wf::keyboard_focus_reason reason) { if (node) { timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); priv->last_timestamp = ts.tv_sec * 1'000'000'000ll + ts.tv_nsec; node->keyboard_interaction().last_focus_timestamp = priv->last_timestamp; } auto focus = wf::get_core().scene()->keyboard_refocus(priv->active_output); priv->set_keyboard_focus(focus.node ? focus.node->shared_from_this() : nullptr, reason); } wf::scene::node_ptr wf::seat_t::get_active_node() { return priv->keyboard_focus; } void wf::seat_t::focus_output(wf::output_t *wo) { if (priv->active_output == wo) { return; } priv->active_output = wo; if (wo) { LOGC(KBD, "focus output: ", wo->handle->name); refocus(); wf::output_gain_focus_signal data; data.output = wo; wo->emit(&data); wf::get_core().emit(&data); } else { /* On shutdown */ } } wf::output_t*wf::seat_t::get_active_output() { return priv->active_output; } uint64_t wf::seat_t::get_last_focus_timestamp() const { return priv->last_timestamp; } wayfire_view wf::seat_t::get_active_view() const { return priv->_last_active_view.lock(); } void wf::seat_t::impl::update_active_view(wf::scene::node_ptr new_focus) { static wf::option_wrapper_t keep_last_focus_activated{"workarounds/keep_last_toplevel_activated"}; auto view = node_to_view(new_focus); auto last_active = _last_active_view.lock(); if (view.get() == last_active.get()) { return; } LOGC(KBD, "Active view becomes ", view); if ((view == nullptr) || toplevel_cast(view) || !keep_last_focus_activated) { auto last_toplevel = _last_active_toplevel.lock(); if (last_toplevel.get() != view.get()) { if (last_toplevel) { last_toplevel->set_activated(false); } _last_active_toplevel.reset(); if (auto toplevel = toplevel_cast(view)) { toplevel->set_activated(true); _last_active_toplevel = toplevel->weak_from_this(); } } } if (view) { _last_active_view = view->weak_from_this(); } else { this->_last_active_view.reset(); } } static wayfire_view pick_topmost_focusable(wayfire_view view) { if (!wf::toplevel_cast(view)) { if (view->get_keyboard_focus_surface()) { return view; } return nullptr; } auto all_views = toplevel_cast(view)->enumerate_views(); auto it = std::find_if(all_views.begin(), all_views.end(), [] (wayfire_view v) { return v->get_keyboard_focus_surface() != NULL; }); if (it != all_views.end()) { return *it; } return nullptr; } void wf::seat_t::focus_view(wayfire_view v) { static wf::option_wrapper_t all_dialogs_modal{"workarounds/all_dialogs_modal"}; const auto& select_focus_view = [] (wayfire_view v) -> wayfire_view { if (v && v->is_mapped()) { if (all_dialogs_modal) { return pick_topmost_focusable(v); } return v; } else { return nullptr; } }; const auto& give_input_focus = [this] (wayfire_view view) { set_active_node(view ? view->get_surface_root_node() : nullptr, keyboard_focus_reason::USER); }; v = select_focus_view(v); if (!v || !v->is_mapped() || !v->get_keyboard_focus_surface()) { priv->update_active_view(nullptr); give_input_focus(nullptr); return; } priv->update_active_view(v->get_root_node()); give_input_focus(v); } void wf::seat_t::refocus() { if (!priv->active_output) { return; } auto focus = wf::get_core().scene()->keyboard_refocus(priv->active_output).node; LOGC(KBD, "Output ", priv->active_output->to_string(), " refocusing: choosing node ", focus); auto focus_sptr = focus ? focus->shared_from_this() : nullptr; if (wf::node_to_view(focus_sptr) || !focus) { priv->update_active_view(focus_sptr); } priv->set_keyboard_focus(focus_sptr, keyboard_focus_reason::REFOCUS); } uint32_t wf::seat_t::get_keyboard_modifiers() { return priv->get_modifiers(); } uint32_t wf::seat_t::modifier_from_keycode(uint32_t keycode) { if (priv->current_keyboard) { return priv->current_keyboard->mod_from_key(keycode); } return 0; } xkb_state*wf::seat_t::get_xkb_state() { if (priv->current_keyboard) { return priv->current_keyboard->handle->xkb_state; } return nullptr; } void wf::seat_t::notify_activity() { wlr_idle_notifier_v1_notify_activity(wf::get_core().protocols.idle_notifier, this->seat); seat_activity_signal data; wf::get_core().emit(&data); } std::vector wf::seat_t::get_pressed_keys() { std::vector pressed_keys{priv->pressed_keys.begin(), priv->pressed_keys.end()}; return pressed_keys; } /* ----------------------- wf::seat_t implementation ------------------------ */ wf::seat_t::seat_t(wl_display *display, std::string name) : seat(wlr_seat_create(display, name.c_str())) { priv = std::make_unique(); priv->seat = seat; priv->cursor = std::make_unique(this); priv->lpointer = std::make_unique( wf::get_core_impl().input, nonstd::make_observer(this)); priv->touch = std::make_unique(priv->cursor->cursor, seat, [] (const wf::pointf_t& global) -> wf::scene::node_ptr { auto value = wf::get_core().scene()->find_node_at(global); return value ? value->node->shared_from_this() : nullptr; }); priv->request_start_drag.set_callback([&] (void *data) { auto ev = static_cast(data); priv->validate_drag_request(ev); }); priv->request_start_drag.connect(&seat->events.request_start_drag); priv->start_drag.set_callback([&] (void *data) { auto d = static_cast(data); if (d->icon) { this->priv->drag_icon = std::make_unique(d->icon); } this->priv->drag_active = true; priv->end_drag.set_callback([&] (void*) { this->priv->drag_active = false; priv->end_drag.disconnect(); }); priv->end_drag.connect(&d->events.destroy); }); priv->start_drag.connect(&seat->events.start_drag); priv->request_set_selection.set_callback([&] (void *data) { auto ev = static_cast(data); wlr_seat_set_selection(wf::get_core().get_current_seat(), ev->source, ev->serial); }); priv->request_set_selection.connect(&seat->events.request_set_selection); priv->request_set_primary_selection.set_callback([&] (void *data) { auto ev = static_cast(data); wlr_seat_set_primary_selection(wf::get_core().get_current_seat(), ev->source, ev->serial); }); priv->request_set_primary_selection.connect(&seat->events.request_set_primary_selection); priv->on_wlr_keyboard_grab_end.set_callback([&] (void*) { if (priv->keyboard_focus && dynamic_cast(&priv->keyboard_focus->keyboard_interaction())) { priv->keyboard_focus->keyboard_interaction().handle_keyboard_enter(this); } }); priv->on_wlr_keyboard_grab_end.connect(&seat->events.keyboard_grab_end); priv->on_wlr_pointer_grab_end.set_callback([&] (void*) { if (priv->drag_active) { // Drag is handled separately. return; } if (auto focus = priv->lpointer->get_focus()) { if (dynamic_cast(&focus->pointer_interaction())) { wf::pointf_t local = get_node_local_coords(focus.get(), priv->cursor->get_cursor_position()); focus->pointer_interaction().handle_pointer_enter(local); } } }); priv->on_wlr_pointer_grab_end.connect(&seat->events.pointer_grab_end); priv->on_new_device = [&] (wf::input_device_added_signal *ev) { switch (ev->device->get_wlr_handle()->type) { case WLR_INPUT_DEVICE_KEYBOARD: this->priv->keyboards.emplace_back(std::make_unique( ev->device->get_wlr_handle())); if (this->priv->current_keyboard == nullptr) { priv->set_keyboard(priv->keyboards.back().get()); } break; case WLR_INPUT_DEVICE_TOUCH: case WLR_INPUT_DEVICE_POINTER: case WLR_INPUT_DEVICE_TABLET: this->priv->cursor->add_new_device(ev->device->get_wlr_handle()); break; default: break; } priv->update_capabilities(); }; priv->on_remove_device = [&] (wf::input_device_removed_signal *ev) { auto dev = ev->device->get_wlr_handle(); if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { bool current_kbd_destroyed = false; if (priv->current_keyboard && (priv->current_keyboard->device == dev)) { current_kbd_destroyed = true; } auto it = std::remove_if(priv->keyboards.begin(), priv->keyboards.end(), [=] (const std::unique_ptr& kbd) { return kbd->device == dev; }); priv->keyboards.erase(it, priv->keyboards.end()); if (current_kbd_destroyed && priv->keyboards.size()) { priv->set_keyboard(priv->keyboards.front().get()); } else { priv->set_keyboard(nullptr); } } priv->update_capabilities(); }; wf::get_core().connect(&priv->on_new_device); wf::get_core().connect(&priv->on_remove_device); priv->on_root_node_updated.set_callback([=] (scene::root_node_update_signal *ev) { if (ev->flags & scene::update_flag::REFOCUS) { refocus(); } }); wf::get_core().scene()->connect(&priv->on_root_node_updated); } void wf::seat_t::impl::update_capabilities() { uint32_t caps = 0; for (const auto& dev : wf::get_core().get_input_devices()) { switch (dev->get_wlr_handle()->type) { case WLR_INPUT_DEVICE_KEYBOARD: caps |= WL_SEAT_CAPABILITY_KEYBOARD; break; case WLR_INPUT_DEVICE_POINTER: caps |= WL_SEAT_CAPABILITY_POINTER; break; case WLR_INPUT_DEVICE_TOUCH: caps |= WL_SEAT_CAPABILITY_TOUCH; break; default: break; } } wlr_seat_set_capabilities(seat, caps); } void wf::seat_t::impl::validate_drag_request(wlr_seat_request_start_drag_event *ev) { auto seat = wf::get_core().get_current_seat(); if (wlr_seat_validate_pointer_grab_serial(seat, ev->origin, ev->serial)) { wlr_seat_start_pointer_drag(seat, ev->drag, ev->serial); return; } struct wlr_touch_point *point; if (wlr_seat_validate_touch_grab_serial(seat, ev->origin, ev->serial, &point)) { wlr_seat_start_touch_drag(seat, ev->drag, ev->serial, point); return; } LOGD("Ignoring start_drag request: ", "could not validate pointer or touch serial ", ev->serial); wlr_data_source_destroy(ev->drag->source); } void wf::seat_t::impl::update_drag_icon() { if (drag_icon) { drag_icon->update_position(); } } void wf::seat_t::impl::set_keyboard(wf::keyboard_t *keyboard) { this->current_keyboard = keyboard; wlr_seat_set_keyboard(seat, keyboard ? wlr_keyboard_from_input_device(keyboard->device) : NULL); } void wf::seat_t::impl::break_mod_bindings() { for (auto& kbd : this->keyboards) { kbd->mod_binding_key = 0; } } uint32_t wf::seat_t::impl::get_modifiers() { return current_keyboard ? current_keyboard->get_modifiers() : 0; } void wf::seat_t::impl::force_release_keys() { if (this->keyboard_focus) { // Release currently pressed buttons for (auto key : this->pressed_keys) { wlr_keyboard_key_event ev; ev.keycode = key; ev.state = WL_KEYBOARD_KEY_STATE_RELEASED; ev.update_state = true; ev.time_msec = get_current_time(); this->keyboard_focus->keyboard_interaction().handle_keyboard_key(wf::get_core().seat.get(), ev); } } } void wf::seat_t::impl::transfer_grab(wf::scene::node_ptr grab_node) { if (this->keyboard_focus == grab_node) { return; } if (this->keyboard_focus) { this->keyboard_focus->keyboard_interaction().handle_keyboard_leave(wf::get_core().seat.get()); } this->keyboard_focus = grab_node; grab_node->keyboard_interaction().handle_keyboard_enter(wf::get_core().seat.get()); wf::keyboard_focus_changed_signal data; data.new_focus = grab_node; data.reason = keyboard_focus_reason::GRAB; wf::get_core().emit(&data); } void wf::seat_t::impl::set_keyboard_focus(wf::scene::node_ptr new_focus, keyboard_focus_reason reason) { if (this->keyboard_focus == new_focus) { return; } LOGC(KBD, "Setting keyboard focus node to ", new_focus.get()); if (this->keyboard_focus) { this->keyboard_focus->keyboard_interaction().handle_keyboard_leave(wf::get_core().seat.get()); } this->keyboard_focus = new_focus; if (new_focus) { new_focus->keyboard_interaction().handle_keyboard_enter(wf::get_core().seat.get()); } wf::keyboard_focus_changed_signal data; data.new_focus = new_focus; data.reason = reason; wf::get_core().emit(&data); } namespace wf { wlr_input_device*input_device_t::get_wlr_handle() { return handle; } bool input_device_t::set_enabled(bool enabled) { if (enabled == is_enabled()) { return true; } if (!wlr_input_device_is_libinput(handle)) { return false; } auto dev = wlr_libinput_get_device_handle(handle); assert(dev); libinput_device_config_send_events_set_mode(dev, enabled ? LIBINPUT_CONFIG_SEND_EVENTS_ENABLED : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED); return true; } bool input_device_t::is_enabled() { /* Currently no support for enabling/disabling non-libinput devices */ if (!wlr_input_device_is_libinput(handle)) { return true; } auto dev = wlr_libinput_get_device_handle(handle); assert(dev); auto mode = libinput_device_config_send_events_get_mode(dev); return mode == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; } void touchscreen_device_t::reconfigure_device(std::shared_ptr device_section) { calibrate(device_section); map_to_output(device_section); } void touchscreen_device_t::calibrate(std::shared_ptr device_section) { auto section = wf::get_core().config_backend->get_input_device_section("input-device", get_wlr_handle()); auto calibration_matrix = section->get_option("calibration")->get_value_str(); if (calibration_matrix.empty()) { return; } wlr_input_device *dev = handle; float m[6]; auto libinput_dev = wlr_libinput_get_device_handle(dev); if (sscanf(calibration_matrix.c_str(), "%f %f %f %f %f %f", &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) == 6) { enum libinput_config_status status; status = libinput_device_config_calibration_set_matrix(libinput_dev, m); if (status != LIBINPUT_CONFIG_STATUS_SUCCESS) { LOGE("Failed to apply calibration for ", nonull(dev->name)); LOGE(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); } else { LOGI("Calibrated input device successfully: ", nonull(dev->name)); LOGI(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); } } else { LOGE("Incorrect calibration configuration for ", nonull(dev->name)); LOGI("Setting default matrix calibration: "); libinput_device_config_calibration_get_default_matrix(libinput_dev, m); LOGI(" ", m[0], " ", m[1], " ", m[2], " ", m[3], " ", m[4], " ", m[5]); libinput_device_config_calibration_set_matrix(libinput_dev, m); } } void tablet_t::reconfigure_device(std::shared_ptr device_section) { map_to_output(device_section); } input_device_t::input_device_t(wlr_input_device *handle) { this->handle = handle; } } // namespace wf wf::input_device_impl_t::input_device_impl_t(wlr_input_device *dev) : wf::input_device_t(dev) { on_destroy.set_callback([&] (void*) { wf::get_core_impl().input->handle_input_destroyed(this->get_wlr_handle()); }); on_destroy.connect(&dev->events.destroy); this->handle->data = this; } wf::input_device_impl_t::~input_device_impl_t() { this->handle->data = NULL; } void wf::input_device_impl_t::map_to_output(std::shared_ptr section) { auto mapped_output = section->get_option("output")->get_value_str(); auto cursor = wf::get_core_impl().get_wlr_cursor(); if (mapped_output.empty()) { if (handle->type == WLR_INPUT_DEVICE_POINTER) { mapped_output = nonull(wlr_pointer_from_input_device(handle)->output_name); } else if (handle->type == WLR_INPUT_DEVICE_TOUCH) { mapped_output = nonull(wlr_touch_from_input_device(handle)->output_name); } } auto wo = wf::get_core().output_layout->find_output(mapped_output); if (wo) { LOGC(INPUT_DEVICES, "Mapping input ", handle->name, " to output ", wo->to_string(), "."); wlr_cursor_map_input_to_output(cursor, handle, wo->handle); } else { LOGC(INPUT_DEVICES, "Mapping input ", handle->name, " to output null."); wlr_cursor_map_input_to_output(cursor, handle, nullptr); } } static wf::pointf_t to_local_recursive(wf::scene::node_t *node, wf::pointf_t point) { if (node->parent()) { return node->to_local(to_local_recursive(node->parent(), point)); } return node->to_local(point); } wf::pointf_t get_node_local_coords(wf::scene::node_t *node, const wf::pointf_t& point) { return to_local_recursive(node, point); } bool is_grabbed_node_alive(wf::scene::node_ptr node) { auto cur = node.get(); while (cur) { if (!cur->is_enabled()) { return false; } if (cur == wf::get_core().scene().get()) { return true; } cur = cur->parent(); } // We did not reach the scenegraph root => we cannot focus the node anymore, it was removed. return false; } wayfire-0.10.0/src/core/seat/input-method-popup.cpp0000664000175000017500000001202015053502647022061 0ustar dkondordkondor#include #include "../../view/view-impl.hpp" #include "wayfire/scene-operations.hpp" wf::text_input_v3_popup::text_input_v3_popup(wf::text_input_v3_im_relay_interface_t *rel, wlr_surface *in) : relay(rel), surface(in) { main_surface = std::make_shared(in, true); on_surface_destroy.set_callback([&] (void*) { on_map.disconnect(); on_unmap.disconnect(); on_destroy.disconnect(); on_surface_destroy.disconnect(); }); on_map.set_callback([&] (void*) { map(); }); on_unmap.set_callback([&] (void*) { unmap(); }); on_commit.set_callback([&] (void*) { update_geometry(); }); on_map.connect(&surface->events.map); on_unmap.connect(&surface->events.unmap); on_surface_destroy.connect(&surface->events.destroy); } std::shared_ptr wf::text_input_v3_popup::create( wf::text_input_v3_im_relay_interface_t *rel, wlr_surface *in) { auto self = view_interface_t::create(rel, in); auto translation_node = std::make_shared(); translation_node->set_children_list({std::make_unique(in, false)}); self->surface_root_node = translation_node; self->set_surface_root_node(translation_node); self->set_role(VIEW_ROLE_DESKTOP_ENVIRONMENT); return self; } void wf::text_input_v3_popup::map() { auto text_input = this->relay->find_focused_text_input_v3(); if (!text_input) { LOGE("trying to map IM popup surface without text input."); return; } auto view = wf::wl_surface_to_wayfire_view(text_input->focused_surface->resource); auto output = view->get_output(); if (!output) { LOGD("trying to map input method popup with a view not on an output."); return; } set_output(output); auto target_layer = wf::scene::layer::UNMANAGED; wf::scene::readd_front(get_output()->node_for_layer(target_layer), get_root_node()); priv->set_mapped_surface_contents(main_surface); priv->set_mapped(main_surface->get_surface()); priv->set_enabled(true); on_commit.connect(&surface->events.commit); update_geometry(); damage(); emit_view_map(); relay->connect(&on_text_input_commit); } void wf::text_input_v3_popup::unmap() { if (!is_mapped()) { return; } damage(); priv->unset_mapped_surface_contents(); emit_view_unmap(); priv->set_mapped(nullptr); priv->set_enabled(false); on_commit.disconnect(); relay->disconnect(&on_text_input_commit); } std::string wf::text_input_v3_popup::get_app_id() { return "input-method-popup"; } std::string wf::text_input_v3_popup::get_title() { return "input-method-popup"; } void wf::text_input_v3_popup::update_cursor_rect(wlr_box *cursor_rect) { if (old_cursor_rect == *cursor_rect) { return; } if (is_mapped()) { update_geometry(); } old_cursor_rect = *cursor_rect; } void wf::text_input_v3_popup::update_geometry() { auto text_input = this->relay->find_focused_text_input_v3(); if (!text_input) { LOGI("no focused text input"); return; } if (!is_mapped()) { LOGI("input method window not mapped"); return; } bool cursor_rect = text_input->current.features & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; auto cursor = text_input->current.cursor_rectangle; int x = 0, y = 0; if (cursor_rect) { x = cursor.x; y = cursor.y + cursor.height; } auto wlr_surface = text_input->focused_surface; auto view = wf::wl_surface_to_wayfire_view(wlr_surface->resource); if (!view) { return; } damage(); wf::pointf_t popup_offset = wf::place_popup_at(wlr_surface, surface, {x* 1.0, y * 1.0}); x = popup_offset.x; y = popup_offset.y; auto width = surface->current.width; auto height = surface->current.height; auto output = view->get_output(); auto g_output = output->get_layout_geometry(); // make sure right edge is on screen, sliding to the left when needed, // but keep left edge on screen nonetheless. x = std::max(0, std::min(x, g_output.width - width)); // down edge is going to be out of screen; flip upwards if (y + height > g_output.height) { y -= height; if (cursor_rect) { y -= cursor.height; } } // make sure top edge is on screen, sliding down and sacrificing down edge if unavoidable y = std::max(0, y); surface_root_node->set_offset({x, y}); geometry.x = x; geometry.y = y; geometry.width = width; geometry.height = height; damage(); wf::scene::update(get_surface_root_node(), wf::scene::update_flag::GEOMETRY); } bool wf::text_input_v3_popup::is_mapped() const { return priv->is_mapped; } wf::geometry_t wf::text_input_v3_popup::get_geometry() { return geometry; } wf::text_input_v3_popup::~text_input_v3_popup() {} wayfire-0.10.0/src/core/seat/switch.cpp0000664000175000017500000000116315053502647017612 0ustar dkondordkondor#include "switch.hpp" #include #include wf::switch_device_t::switch_device_t(wlr_input_device *dev) : input_device_impl_t(dev) { on_switch.set_callback([&] (void *data) { this->handle_switched((wlr_switch_toggle_event*)data); }); on_switch.connect(&wlr_switch_from_input_device(dev)->events.toggle); } void wf::switch_device_t::handle_switched(wlr_switch_toggle_event *ev) { wf::switch_signal data; data.device = nonstd::make_observer(this); data.state = (ev->switch_state == WLR_SWITCH_STATE_ON); wf::get_core().emit(&data); } wayfire-0.10.0/src/core/seat/cursor.cpp0000664000175000017500000001377315053502647017640 0ustar dkondordkondor#include "cursor.hpp" #include "pointer.hpp" #include "../core-impl.hpp" #include "../../view/view-impl.hpp" #include "input-manager.hpp" #include "wayfire/util.hpp" #include "wayfire/output-layout.hpp" #include "tablet.hpp" #include "wayfire/signal-definitions.hpp" wf::cursor_t::cursor_t(wf::seat_t *seat) { cursor = wlr_cursor_create(); this->seat = seat; wlr_cursor_attach_output_layout(cursor, wf::get_core().output_layout->get_handle()); wlr_cursor_map_to_output(cursor, NULL); wlr_cursor_warp(cursor, NULL, cursor->x, cursor->y); init_xcursor(); config_reloaded = [=] (auto) { init_xcursor(); }; wf::get_core().connect(&config_reloaded); request_set_cursor.set_callback([&] (void *data) { auto ev = static_cast(data); set_cursor(ev, true); }); request_set_cursor.connect(&seat->seat->events.request_set_cursor); } void wf::cursor_t::add_new_device(wlr_input_device *dev) { wlr_cursor_attach_input_device(cursor, dev); } void wf::cursor_t::setup_listeners() { /* Dispatch pointer events to the pointer_t */ on_frame.set_callback([&] (void*) { seat->priv->lpointer->handle_pointer_frame(); wf::get_core().seat->notify_activity(); }); on_frame.connect(&cursor->events.frame); #define setup_passthrough_callback(evname) \ on_ ## evname.set_callback([&] (void *data) { \ set_touchscreen_mode(false); \ auto ev = static_cast(data); \ auto mode = emit_device_event_signal(ev, &ev->pointer->base); \ if (mode != wf::input_event_processing_mode_t::IGNORE) \ { \ seat->priv->lpointer->handle_pointer_ ## evname(ev, mode); \ wf::get_core().seat->notify_activity(); \ } \ emit_device_post_event_signal(ev, &ev->pointer->base); \ }); \ on_ ## evname.connect(&cursor->events.evname); setup_passthrough_callback(button); setup_passthrough_callback(motion); setup_passthrough_callback(motion_absolute); setup_passthrough_callback(axis); setup_passthrough_callback(swipe_begin); setup_passthrough_callback(swipe_update); setup_passthrough_callback(swipe_end); setup_passthrough_callback(pinch_begin); setup_passthrough_callback(pinch_update); setup_passthrough_callback(pinch_end); setup_passthrough_callback(hold_begin); setup_passthrough_callback(hold_end); #undef setup_passthrough_callback /** * All tablet events are directly sent to the tablet device, it should * manage them */ #define setup_tablet_callback(evname) \ on_tablet_ ## evname.set_callback([&] (void *data) { \ set_touchscreen_mode(false); \ auto ev = static_cast(data); \ auto handling_mode = emit_device_event_signal(ev, &ev->tablet->base); \ if (ev->tablet->data) { \ auto tablet = \ static_cast(ev->tablet->data); \ tablet->handle_ ## evname(ev, handling_mode); \ } \ wf::get_core().seat->notify_activity(); \ emit_device_post_event_signal(ev, &ev->tablet->base); \ }); \ on_tablet_ ## evname.connect(&cursor->events.tablet_tool_ ## evname); setup_tablet_callback(tip); setup_tablet_callback(axis); setup_tablet_callback(button); setup_tablet_callback(proximity); #undef setup_tablet_callback } void wf::cursor_t::init_xcursor() { std::string theme = wf::option_wrapper_t("input/cursor_theme"); int size = wf::option_wrapper_t("input/cursor_size"); auto theme_ptr = (theme == "default") ? NULL : theme.c_str(); // Set environment variables needed for Xwayland and maybe other apps // which use them to determine the correct cursor size setenv("XCURSOR_SIZE", std::to_string(size).c_str(), 1); if (theme_ptr) { setenv("XCURSOR_THEME", theme_ptr, 1); } if (xcursor) { last_cursor_name.clear(); // make sure we set the new cursor image with the new xcursor_manager wlr_xcursor_manager_destroy(xcursor); } xcursor = wlr_xcursor_manager_create(theme_ptr, size); set_cursor("default"); } void wf::cursor_t::set_cursor(std::string name) { if (this->hide_ref_counter) { return; } if (this->touchscreen_mode_active) { return; } if (name == "default") { name = "left_ptr"; } if (name == last_cursor_name) { return; } last_cursor_name = name; idle_set_cursor.run_once([name, this] () { wlr_cursor_set_xcursor(cursor, xcursor, name.c_str()); }); } void wf::cursor_t::unhide_cursor() { if (this->hide_ref_counter && --this->hide_ref_counter) { return; } set_cursor("default"); } void wf::cursor_t::hide_cursor() { idle_set_cursor.disconnect(); wlr_cursor_set_surface(cursor, NULL, 0, 0); this->hide_ref_counter++; last_cursor_name.clear(); } void wf::cursor_t::warp_cursor(wf::pointf_t point) { wlr_cursor_warp_closest(cursor, NULL, point.x, point.y); } wf::pointf_t wf::cursor_t::get_cursor_position() { return {cursor->x, cursor->y}; } void wf::cursor_t::set_cursor( wlr_seat_pointer_request_set_cursor_event *ev, bool validate_request) { if (this->hide_ref_counter) { return; } if (this->touchscreen_mode_active) { return; } if (validate_request) { auto pointer_client = seat->seat->pointer_state.focused_client; if (pointer_client != ev->seat_client) { return; } } wlr_cursor_set_surface(cursor, ev->surface, ev->hotspot_x, ev->hotspot_y); last_cursor_name.clear(); } void wf::cursor_t::set_touchscreen_mode(bool enabled) { if (this->touchscreen_mode_active == enabled) { return; } this->touchscreen_mode_active = enabled; if (enabled) { hide_cursor(); } else { unhide_cursor(); } } wayfire-0.10.0/src/core/seat/pointer.cpp0000664000175000017500000003246115053502647017776 0ustar dkondordkondor#include "pointer.hpp" #include #include "cursor.hpp" #include "pointing-device.hpp" #include "input-manager.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-definitions.hpp" #include #include #include #include wf::pointer_t::pointer_t(nonstd::observer_ptr input, nonstd::observer_ptr seat) { this->input = input; this->seat = seat; on_root_node_updated = [=] (wf::scene::root_node_update_signal *data) { if (!(data->flags & wf::scene::update_flag::INPUT_STATE)) { return; } if (grabbed_node && !is_grabbed_node_alive(grabbed_node)) { grab_surface(nullptr); } update_cursor_position(get_current_time()); }; wf::get_core().scene()->connect(&on_root_node_updated); } wf::pointer_t::~pointer_t() {} bool wf::pointer_t::has_pressed_buttons() const { return this->count_pressed_buttons > 0; } /* ------------------------- Cursor focus functions ------------------------- */ void wf::pointer_t::set_enable_focus(bool enabled) { this->focus_enabled_count += enabled ? 1 : -1; if (focus_enabled_count > 1) { LOGI("LogicalPointer enabled more times than disabled?"); } // reset grab if (!focus_enabled()) { grab_surface(nullptr); this->update_cursor_focus(nullptr); } else { update_cursor_position(get_current_time()); } } bool wf::pointer_t::focus_enabled() const { return this->focus_enabled_count > 0; } void wf::pointer_t::update_cursor_position(int64_t time_msec) { wf::pointf_t gc = seat->priv->cursor->get_cursor_position(); /* If we have a grabbed surface, but no drag, we want to continue sending * events to the grabbed surface, even if the pointer goes outside of it. * This enables Xwayland DnD to work correctly, and also lets the user for * ex. grab a scrollbar and move their mouse freely. */ if (!grabbed_node && this->focus_enabled()) { const auto& scene = wf::get_core().scene(); auto isec = scene->find_node_at(gc); update_cursor_focus(isec ? isec->node->shared_from_this() : nullptr); } this->send_motion(time_msec); seat->priv->update_drag_icon(); } void wf::pointer_t::send_leave_to_focus(wf::scene::node_ptr old_focus) { if (old_focus) { if (!old_focus->wants_raw_input()) { // Make a copy of sent buttons: it could happen that we switch focus multiple times if we have // multiple grabs on multiple outputs. auto send_release = this->currently_sent_buttons; for (auto button : send_release) { LOGC(POINTER, "force-release button ", button); wlr_pointer_button_event event; event.pointer = NULL; event.button = button; event.state = WL_POINTER_BUTTON_STATE_RELEASED; event.time_msec = wf::get_current_time(); old_focus->pointer_interaction().handle_pointer_button(event); } } old_focus->pointer_interaction().handle_pointer_leave(); this->last_focus_coords = {}; } } void wf::pointer_t::transfer_grab(scene::node_ptr node) { if (node == cursor_focus) { LOGC(POINTER, "transfer grab ", cursor_focus.get(), " -> ", node.get(), ": do nothing"); // Node might already be focused, in case for example there was no input surface when the grab node // was added to the scenegraph. return; } LOGC(POINTER, "transfer grab ", cursor_focus.get(), " -> ", node.get()); auto old_focus = std::move(cursor_focus); cursor_focus = node; send_leave_to_focus(old_focus); // Send pointer_enter to the grab send_enter_to_focus(); if (!node->wants_raw_input()) { currently_sent_buttons.clear(); } if (currently_sent_buttons.size()) { grabbed_node = node; } else { grabbed_node = nullptr; } } void wf::pointer_t::send_enter_to_focus() { auto gc = wf::get_core().get_cursor_position(); auto local = get_node_local_coords(cursor_focus.get(), gc); cursor_focus->pointer_interaction().handle_pointer_enter(local); this->last_focus_coords = local; } void wf::pointer_t::update_cursor_focus(wf::scene::node_ptr new_focus) { if (cursor_focus == new_focus) { return; } LOGC(POINTER, "Change cursor focus ", cursor_focus.get(), " -> ", new_focus.get()); auto old_focus = std::move(cursor_focus); cursor_focus = new_focus; send_leave_to_focus(old_focus); currently_sent_buttons.clear(); if (cursor_focus) { send_enter_to_focus(); } else { // If there is focused surface, we should reset the cursor image to // avoid the last cursor image getting stuck outside of its surface. wf::get_core().set_cursor("default"); } wf::pointer_focus_changed_signal ev; ev.new_focus = cursor_focus; wf::get_core().emit(&ev); } wf::scene::node_ptr wf::pointer_t::get_focus() const { return this->cursor_focus; } /* -------------------------- Implicit grab --------------------------------- */ void wf::pointer_t::grab_surface(wf::scene::node_ptr node) { if (node == grabbed_node) { return; } if (node) { /* Start a new grab */ this->grabbed_node = node; return; } /* End grab */ grabbed_node = nullptr; update_cursor_position(get_current_time()); } /* ----------------------- Input event processing --------------------------- */ void wf::pointer_t::handle_pointer_button(wlr_pointer_button_event *ev, input_event_processing_mode_t mode) { seat->priv->break_mod_bindings(); bool handled_in_binding = (mode != input_event_processing_mode_t::FULL); if (ev->state == WL_POINTER_BUTTON_STATE_PRESSED) { count_pressed_buttons++; if (count_pressed_buttons == 1) { /* Focus only the first click, since then we also start an implicit * grab, and we don't want to suddenly change the output */ auto gc = seat->priv->cursor->get_cursor_position(); auto output = wf::get_core().output_layout->get_output_at(gc.x, gc.y); seat->focus_output(output); } handled_in_binding |= wf::get_core().bindings->handle_button( wf::buttonbinding_t{seat->priv->get_modifiers(), ev->button}); } else { count_pressed_buttons--; } send_button(ev, handled_in_binding); if (!handled_in_binding) { check_implicit_grab(); } } void wf::pointer_t::check_implicit_grab() { /* start a button held grab, so that the window will receive all the * subsequent events, no matter what happens */ if ((count_pressed_buttons >= 1) && cursor_focus) { grab_surface(cursor_focus); } /* end the button held grab. We need to to this here after we have send * the last button release event, so that buttons don't get stuck in clients */ if (count_pressed_buttons == 0) { grab_surface(nullptr); } } void wf::pointer_t::send_button(wlr_pointer_button_event *ev, bool has_binding) { /* Clients do not receive buttons for bindings */ if (has_binding) { return; } if (cursor_focus) { if ((ev->state == WL_POINTER_BUTTON_STATE_PRESSED) && cursor_focus) { LOGC(POINTER, "normal button press ", ev->button); this->currently_sent_buttons.insert(ev->button); cursor_focus->pointer_interaction().handle_pointer_button(*ev); } else if ((ev->state == WL_POINTER_BUTTON_STATE_RELEASED) && (currently_sent_buttons.count(ev->button) || cursor_focus->wants_raw_input())) { LOGC(POINTER, "normal button release ", ev->button); if (currently_sent_buttons.count(ev->button)) { this->currently_sent_buttons.erase(currently_sent_buttons.find(ev->button)); } cursor_focus->pointer_interaction().handle_pointer_button(*ev); } else { LOGC(POINTER, "ignoring button event ", ev->button, " ", ev->state); // Ignore buttons which the client has not received. // These are potentially buttons which were grabbed. } } else { LOGC(POINTER, "ignoring button event (null focus) ", ev->button, " ", ev->state); } } void wf::pointer_t::send_motion(uint32_t time_msec) { if (cursor_focus) { auto gc = wf::get_core().get_cursor_position(); auto local = get_node_local_coords(cursor_focus.get(), gc); if (!last_focus_coords.has_value() || (local.x != last_focus_coords->x) || (local.y != last_focus_coords->y)) { // NB: the pointer motion could go to a grab, which could trigger scenegraph changes, which // trigger re-focus. We need to set the last focus coordinates early, so that we break out of // infinite loops. last_focus_coords = local; cursor_focus->pointer_interaction().handle_pointer_motion(local, time_msec); } } } void wf::pointer_t::handle_pointer_motion(wlr_pointer_motion_event *ev, input_event_processing_mode_t mode) { /* XXX: maybe warp directly? */ wlr_cursor_move(seat->priv->cursor->cursor, &ev->pointer->base, ev->delta_x, ev->delta_y); update_cursor_position(ev->time_msec); } void wf::pointer_t::handle_pointer_motion_absolute( wlr_pointer_motion_absolute_event *ev, input_event_processing_mode_t mode) { // next coordinates double cx, cy; wlr_cursor_absolute_to_layout_coords(seat->priv->cursor->cursor, &ev->pointer->base, ev->x, ev->y, &cx, &cy); // send relative motion double dx = cx - seat->priv->cursor->cursor->x; double dy = cy - seat->priv->cursor->cursor->y; wlr_relative_pointer_manager_v1_send_relative_motion( wf::get_core().protocols.relative_pointer, seat->seat, (uint64_t)ev->time_msec * 1000, dx, dy, dx, dy); // TODO: indirection via wf_cursor wlr_cursor_warp_closest(seat->priv->cursor->cursor, NULL, cx, cy); update_cursor_position(ev->time_msec); } void wf::pointer_t::handle_pointer_axis(wlr_pointer_axis_event *ev, input_event_processing_mode_t mode) { bool handled_in_binding = wf::get_core().bindings->handle_axis( seat->priv->get_modifiers(), ev); seat->priv->break_mod_bindings(); /* Do not send scroll events to clients if an axis binding has used up the * event */ if (handled_in_binding) { return; } /* Calculate speed settings */ wf::pointing_device_t *pd = (wf::pointing_device_t*)ev->pointer->base.data; double mult = pd->get_scroll_speed(&ev->pointer->base, ev->source == WL_POINTER_AXIS_SOURCE_FINGER); ev->delta *= mult; ev->delta_discrete *= mult; if (cursor_focus) { cursor_focus->pointer_interaction().handle_pointer_axis(*ev); } } void wf::pointer_t::handle_pointer_swipe_begin(wlr_pointer_swipe_begin_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_swipe_begin( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->fingers); } void wf::pointer_t::handle_pointer_swipe_update( wlr_pointer_swipe_update_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_swipe_update( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->dx, ev->dy); } void wf::pointer_t::handle_pointer_swipe_end(wlr_pointer_swipe_end_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_swipe_end( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->cancelled); } void wf::pointer_t::handle_pointer_pinch_begin(wlr_pointer_pinch_begin_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_pinch_begin( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->fingers); } void wf::pointer_t::handle_pointer_pinch_update( wlr_pointer_pinch_update_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_pinch_update( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->dx, ev->dy, ev->scale, ev->rotation); } void wf::pointer_t::handle_pointer_pinch_end(wlr_pointer_pinch_end_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_pinch_end( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->cancelled); } void wf::pointer_t::handle_pointer_hold_begin(wlr_pointer_hold_begin_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_hold_begin( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->fingers); } void wf::pointer_t::handle_pointer_hold_end(wlr_pointer_hold_end_event *ev, input_event_processing_mode_t mode) { wlr_pointer_gestures_v1_send_hold_end( wf::get_core().protocols.pointer_gestures, seat->seat, ev->time_msec, ev->cancelled); } void wf::pointer_t::handle_pointer_frame() { wlr_seat_pointer_notify_frame(seat->seat); } wayfire-0.10.0/src/core/seat/input-manager.cpp0000664000175000017500000001061515053502647021062 0ustar dkondordkondor#include #include "pointer.hpp" #include "wayfire/core.hpp" #include "wayfire/signal-definitions.hpp" #include "../core-impl.hpp" #include "keyboard.hpp" #include "cursor.hpp" #include "input-manager.hpp" #include "wayfire/output-layout.hpp" #include "wayfire/view.hpp" #include "wayfire/config-backend.hpp" #include #include #include "switch.hpp" #include "tablet.hpp" #include "pointing-device.hpp" static std::unique_ptr create_wf_device_for_device( wlr_input_device *device) { switch (device->type) { case WLR_INPUT_DEVICE_SWITCH: return std::make_unique(device); case WLR_INPUT_DEVICE_POINTER: return std::make_unique(device); case WLR_INPUT_DEVICE_TABLET: return std::make_unique( wf::get_core_impl().seat->priv->cursor->cursor, device); case WLR_INPUT_DEVICE_TABLET_PAD: return std::make_unique(device); case WLR_INPUT_DEVICE_TOUCH: return std::make_unique(device); default: return std::make_unique(device); } } void wf::input_manager_t::handle_new_input(wlr_input_device *dev) { LOGI("handle new input: ", dev->name); input_devices.push_back(create_wf_device_for_device(dev)); wf::input_device_added_signal data; data.device = nonstd::make_observer(input_devices.back().get()); wf::get_core().emit(&data); configure_input_device(input_devices.back()); } void wf::input_manager_t::configure_input_device(std::unique_ptr & device) { auto section = wf::get_core().config_backend->get_input_device_section("input-device", device->get_wlr_handle()); device->reconfigure_device(section); } void wf::input_manager_t::configure_input_devices() { // Might trigger motion events which we want to avoid at other stages auto state = wf::get_core().get_current_state(); if (state != wf::compositor_state_t::RUNNING) { return; } for (auto& device : this->input_devices) { configure_input_device(device); } } void wf::input_manager_t::handle_input_destroyed(wlr_input_device *dev) { LOGI("remove input: ", dev->name); for (auto& device : input_devices) { if (device->get_wlr_handle() == dev) { wf::input_device_removed_signal data; data.device = {device}; wf::get_core().emit(&data); } } auto it = std::remove_if(input_devices.begin(), input_devices.end(), [=] (const std::unique_ptr& idev) { return idev->get_wlr_handle() == dev; }); input_devices.erase(it, input_devices.end()); } void load_locked_mods_from_config(xkb_mod_mask_t& locked_mods) { wf::option_wrapper_t numlock_state, capslock_state; numlock_state.load_option("input/kb_numlock_default_state"); capslock_state.load_option("input/kb_capslock_default_state"); if (numlock_state) { locked_mods |= wf::KB_MOD_NUM_LOCK; } if (capslock_state) { locked_mods |= wf::KB_MOD_CAPS_LOCK; } } wf::input_manager_t::input_manager_t() { load_locked_mods_from_config(locked_mods); input_device_created.set_callback([&] (void *data) { auto dev = static_cast(data); assert(dev); handle_new_input(dev); }); input_device_created.connect(&wf::get_core().backend->events.new_input); config_updated = [=] (auto) { for (auto& dev : input_devices) { dev->update_options(); } }; wf::get_core().connect(&config_updated); output_added.set_callback([=] (output_added_signal *ev) { if (exclusive_client != nullptr) { ev->output->set_inhibited(true); } configure_input_devices(); }); wf::get_core().output_layout->connect(&output_added); } void wf::input_manager_t::set_exclusive_focus(wl_client *client) { exclusive_client = client; for (auto& wo : wf::get_core().output_layout->get_outputs()) { wo->set_inhibited(client != nullptr); } /* We no longer have an exclusively focused client, so we should restore * focus to the topmost view */ if (!client) { wf::get_core().seat->refocus(); } } wayfire-0.10.0/src/core/seat/bindings-repository-impl.hpp0000664000175000017500000000235015053502647023266 0ustar dkondordkondor#pragma once #include "wayfire/bindings-repository.hpp" #include "hotspot-manager.hpp" #include "wayfire/signal-definitions.hpp" #include struct wf::bindings_repository_t::impl { /** * Recreate hotspots. * * The action will take place on the next idle. */ void recreate_hotspots() { this->idle_recreate_hotspots.run_once([=] () { if (enabled > 0) { hotspot_mgr.update_hotspots(activators); } else { hotspot_mgr.update_hotspots({}); } }); } void reparse_extensions(); binding_container_t keys; binding_container_t axes; binding_container_t buttons; binding_container_t activators; hotspot_manager_t hotspot_mgr; wf::signal::connection_t on_config_reload = [=] (wf::reload_config_signal *ev) { recreate_hotspots(); reparse_extensions(); }; wf::wl_idle_call idle_recreate_hotspots; wf::wl_idle_call idle_reparse_bindings; int enabled = 1; }; wayfire-0.10.0/src/core/seat/touch.cpp0000664000175000017500000004171015053502647017435 0ustar dkondordkondor#include #include #include "touch.hpp" #include "cursor.hpp" #include "input-manager.hpp" #include "../core-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/output.hpp" #include "wayfire/util.hpp" #include "wayfire/output-layout.hpp" #include class touch_timer_adapter_t : public wf::touch::timer_interface_t { public: wf::wl_timer timer; void set_timeout(uint32_t msec, std::function handler) { timer.set_timeout(msec, handler); } void reset() { timer.disconnect(); } }; wf::touch_interface_t::touch_interface_t(wlr_cursor *cursor, wlr_seat *seat, input_surface_selector_t surface_at) { this->cursor = cursor; this->seat = seat; this->surface_at = surface_at; // connect handlers on_down.set_callback([=] (void *data) { auto ev = static_cast(data); auto mode = emit_device_event_signal(ev, &ev->touch->base); if (mode != input_event_processing_mode_t::IGNORE) { double lx, ly; wlr_cursor_absolute_to_layout_coords(cursor, &ev->touch->base, ev->x, ev->y, &lx, &ly); wf::pointf_t point; wf::get_core().output_layout->get_output_coords_at({lx, ly}, point); handle_touch_down(ev->touch_id, ev->time_msec, point, mode); } wf::get_core().seat->notify_activity(); emit_device_post_event_signal(ev, &ev->touch->base); }); on_up.set_callback([=] (void *data) { auto ev = static_cast(data); auto mode = emit_device_event_signal(ev, &ev->touch->base); if (mode != input_event_processing_mode_t::IGNORE) { handle_touch_up(ev->touch_id, ev->time_msec, mode); } wf::get_core().seat->notify_activity(); emit_device_post_event_signal(ev, &ev->touch->base); }); on_motion.set_callback([=] (void *data) { auto ev = static_cast(data); auto mode = emit_device_event_signal(ev, &ev->touch->base); if (mode != input_event_processing_mode_t::IGNORE) { double lx, ly; wlr_cursor_absolute_to_layout_coords( wf::get_core_impl().seat->priv->cursor->cursor, &ev->touch->base, ev->x, ev->y, &lx, &ly); wf::pointf_t point; wf::get_core().output_layout->get_output_coords_at({lx, ly}, point); handle_touch_motion(ev->touch_id, ev->time_msec, point, true, mode); } wf::get_core().seat->notify_activity(); emit_device_post_event_signal(ev, &ev->touch->base); }); on_cancel.set_callback([=] (void *data) { wlr_touch_cancel_event *ev = (wlr_touch_cancel_event*)data; wlr_touch_up_event sim; sim.time_msec = ev->time_msec; sim.touch_id = ev->touch_id; sim.touch = ev->touch; this->on_up.emit(&sim); }); on_frame.set_callback([&] (void*) { wlr_seat_touch_notify_frame(wf::get_core().get_current_seat()); wf::get_core().seat->notify_activity(); }); on_up.connect(&cursor->events.touch_up); on_down.connect(&cursor->events.touch_down); on_frame.connect(&cursor->events.touch_frame); on_motion.connect(&cursor->events.touch_motion); on_cancel.connect(&cursor->events.touch_cancel); on_root_node_updated = [=] (wf::scene::root_node_update_signal *data) { if (!(data->flags & wf::scene::update_flag::INPUT_STATE)) { return; } for (auto& [finger_id, node] : this->focus) { if (node && !is_grabbed_node_alive(node)) { set_touch_focus(nullptr, finger_id, get_current_time(), {0, 0}); } } }; wf::get_core().scene()->connect(&on_root_node_updated); add_default_gestures(); } wf::touch_interface_t::~touch_interface_t() {} const wf::touch::gesture_state_t& wf::touch_interface_t::get_state() const { return this->finger_state; } wf::scene::node_ptr wf::touch_interface_t::get_focus(int finger_id) const { auto it = this->focus.find(finger_id); return (it == focus.end() ? nullptr : it->second); } void wf::touch_interface_t::add_touch_gesture( nonstd::observer_ptr gesture) { gesture->set_timer(std::make_unique()); this->gestures.emplace_back(gesture); } void wf::touch_interface_t::rem_touch_gesture( nonstd::observer_ptr gesture) { gestures.erase(std::remove(gestures.begin(), gestures.end(), gesture), gestures.end()); } void wf::touch_interface_t::set_touch_focus(wf::scene::node_ptr node, int id, int64_t time, wf::pointf_t point) { if (focus[id] == node) { return; } if (focus[id]) { focus[id]->touch_interaction().handle_touch_up(time, id, point); } focus[id] = node; if (node) { auto local = get_node_local_coords(node.get(), point); node->touch_interaction().handle_touch_down(time, id, local); } wf::touch_focus_changed_signal ev; ev.finger_id = id; ev.new_focus = node; wf::get_core().emit(&ev); } void wf::touch_interface_t::transfer_grab(scene::node_ptr grab_node) { auto new_focus = grab_node->wants_raw_input() ? grab_node : nullptr; for (auto& [id, focused_node] : this->focus) { if (focused_node && (focused_node != new_focus) && !focused_node->wants_raw_input()) { const auto lift_off_position = finger_state.fingers[id].current; focused_node->touch_interaction().handle_touch_up(get_current_time(), id, {lift_off_position.x, lift_off_position.y}); } focused_node = new_focus; } } void wf::touch_interface_t::update_gestures(const wf::touch::gesture_event_t& ev) { for (auto& gesture : this->gestures) { if ((this->finger_state.fingers.size() == 1) && (ev.type == touch::EVENT_TYPE_TOUCH_DOWN)) { gesture->reset(ev.time); } gesture->update_state(ev); } } void wf::touch_interface_t::handle_touch_down(int32_t id, uint32_t time, wf::pointf_t point, input_event_processing_mode_t mode) { auto& seat = wf::get_core_impl().seat; seat->priv->break_mod_bindings(); if (id == 0) { wf::get_core().seat->focus_output( wf::get_core().output_layout->get_output_at(point.x, point.y)); } // NB. We first update the focus, and then update the gesture, // except if the input is grabbed. // // This is necessary because wm-focus needs to know the touch focus at the // moment the tap happens wf::touch::gesture_event_t gesture_event = { .type = wf::touch::EVENT_TYPE_TOUCH_DOWN, .time = time, .finger = id, .pos = {point.x, point.y} }; finger_state.update(gesture_event); if (mode != input_event_processing_mode_t::FULL) { update_gestures(gesture_event); update_cursor_state(); return; } auto focus = this->surface_at(point); set_touch_focus(focus, id, time, point); seat->priv->update_drag_icon(); update_gestures(gesture_event); update_cursor_state(); } void wf::touch_interface_t::handle_touch_motion(int32_t id, uint32_t time, wf::pointf_t point, bool is_real_event, input_event_processing_mode_t mode) { // handle_touch_motion is called on both real motion events and when // touch focus should be updated. // // In case this is not a real event, we don't want to update gestures, // because focus change can happen even while some gestures are still // updating. if (is_real_event) { const wf::touch::gesture_event_t gesture_event = { .type = wf::touch::EVENT_TYPE_MOTION, .time = time, .finger = id, .pos = {point.x, point.y} }; update_gestures(gesture_event); finger_state.update(gesture_event); } if (focus[id]) { auto local = get_node_local_coords(focus[id].get(), point); focus[id]->touch_interaction().handle_touch_motion(time, id, local); } auto& seat = wf::get_core_impl().seat; seat->priv->update_drag_icon(); } void wf::touch_interface_t::handle_touch_up(int32_t id, uint32_t time, input_event_processing_mode_t mode) { const auto lift_off_position = finger_state.fingers[id].current; const wf::touch::gesture_event_t gesture_event = { .type = wf::touch::EVENT_TYPE_TOUCH_UP, .time = time, .finger = id, .pos = lift_off_position, }; update_gestures(gesture_event); finger_state.update(gesture_event); update_cursor_state(); set_touch_focus(nullptr, id, time, {lift_off_position.x, lift_off_position.y}); } void wf::touch_interface_t::update_cursor_state() { auto& seat = wf::get_core_impl().seat; /* just set the cursor mode, independent of how many fingers we have */ seat->priv->cursor->set_touchscreen_mode(true); } // Swipe params constexpr static int EDGE_SWIPE_THRESHOLD = 10; constexpr static double MIN_SWIPE_DISTANCE = 30; constexpr static double MAX_SWIPE_DISTANCE = 450; constexpr static double SWIPE_INCORRECT_DRAG_TOLERANCE = 150; // Pinch params constexpr static double PINCH_INCORRECT_DRAG_TOLERANCE = 200; constexpr static double PINCH_THRESHOLD = 1.5; // General constexpr static double GESTURE_INITIAL_TOLERANCE = 40; constexpr static uint32_t GESTURE_BASE_DURATION = 400; using namespace wf::touch; /** * swipe and with multiple fingers and directions */ class multi_action_t : public gesture_action_t { public: multi_action_t(bool pinch, double threshold) { this->pinch = pinch; this->threshold = threshold; } bool pinch; double threshold; bool last_pinch_was_pinch_in = false; double move_tolerance = 1e9; uint32_t target_direction = 0; int32_t cnt_fingers = 0; action_status_t update_state(const gesture_state_t& state, const gesture_event_t& event) override { if (event.type == wf::touch::EVENT_TYPE_TIMEOUT) { return wf::touch::ACTION_STATUS_CANCELLED; } if (event.type == EVENT_TYPE_TOUCH_UP) { return ACTION_STATUS_CANCELLED; } if (event.type == EVENT_TYPE_TOUCH_DOWN) { cnt_fingers = state.fingers.size(); for (auto& finger : state.fingers) { if (glm::length(finger.second.delta()) > GESTURE_INITIAL_TOLERANCE) { return ACTION_STATUS_CANCELLED; } } return ACTION_STATUS_RUNNING; } if (this->pinch) { if (glm::length(state.get_center().delta()) >= move_tolerance) { return ACTION_STATUS_CANCELLED; } double pinch = state.get_pinch_scale(); last_pinch_was_pinch_in = pinch <= 1.0; if ((pinch <= 1.0 / threshold) || (pinch >= threshold)) { return ACTION_STATUS_COMPLETED; } return ACTION_STATUS_RUNNING; } // swipe case if ((glm::length(state.get_center().delta()) >= MIN_SWIPE_DISTANCE) && (this->target_direction == 0)) { this->target_direction = state.get_center().get_direction(); } if (this->target_direction == 0) { return ACTION_STATUS_RUNNING; } for (auto& finger : state.fingers) { if (finger.second.get_incorrect_drag_distance(this->target_direction) > this->move_tolerance) { return ACTION_STATUS_CANCELLED; } } if (state.get_center().get_drag_distance(this->target_direction) >= threshold) { return ACTION_STATUS_COMPLETED; } return ACTION_STATUS_RUNNING; } void reset(uint32_t time) override { gesture_action_t::reset(time); target_direction = 0; } }; static uint32_t find_swipe_edges(wf::touch::point_t point) { auto output = wf::get_core().seat->get_active_output(); auto geometry = output->get_layout_geometry(); uint32_t edge_directions = 0; if (point.x <= geometry.x + EDGE_SWIPE_THRESHOLD) { edge_directions |= wf::GESTURE_DIRECTION_RIGHT; } if (point.x >= geometry.x + geometry.width - EDGE_SWIPE_THRESHOLD) { edge_directions |= wf::GESTURE_DIRECTION_LEFT; } if (point.y <= geometry.y + EDGE_SWIPE_THRESHOLD) { edge_directions |= wf::GESTURE_DIRECTION_DOWN; } if (point.y >= geometry.y + geometry.height - EDGE_SWIPE_THRESHOLD) { edge_directions |= wf::GESTURE_DIRECTION_UP; } return edge_directions; } static uint32_t wf_touch_to_wf_dir(uint32_t touch_dir) { uint32_t gesture_dir = 0; if (touch_dir & MOVE_DIRECTION_RIGHT) { gesture_dir |= wf::GESTURE_DIRECTION_RIGHT; } if (touch_dir & MOVE_DIRECTION_LEFT) { gesture_dir |= wf::GESTURE_DIRECTION_LEFT; } if (touch_dir & MOVE_DIRECTION_UP) { gesture_dir |= wf::GESTURE_DIRECTION_UP; } if (touch_dir & MOVE_DIRECTION_DOWN) { gesture_dir |= wf::GESTURE_DIRECTION_DOWN; } return gesture_dir; } void wf::touch_interface_t::add_default_gestures() { wf::option_wrapper_t sensitivity{"input/gesture_sensitivity"}; // Swipe gesture needs slightly less distance because it is usually // with many fingers and it is harder to move all of them auto swipe = std::make_unique(false, 0.75 * MAX_SWIPE_DISTANCE / sensitivity); swipe->set_duration(GESTURE_BASE_DURATION * sensitivity); swipe->move_tolerance = SWIPE_INCORRECT_DRAG_TOLERANCE * sensitivity; const double pinch_thresh = 1.0 + (PINCH_THRESHOLD - 1.0) / sensitivity; auto pinch = std::make_unique(true, pinch_thresh); pinch->set_duration(GESTURE_BASE_DURATION * 1.5 * sensitivity); pinch->move_tolerance = PINCH_INCORRECT_DRAG_TOLERANCE * sensitivity; // Edge swipe needs a quick release to be considered edge swipe auto edge_swipe = std::make_unique(false, MAX_SWIPE_DISTANCE / sensitivity); auto edge_release = std::make_unique(1, false); edge_swipe->set_duration(GESTURE_BASE_DURATION * sensitivity); edge_swipe->move_tolerance = SWIPE_INCORRECT_DRAG_TOLERANCE * sensitivity; // The release action needs longer duration to handle the case where the // gesture is actually longer than the max distance. edge_release->set_duration(GESTURE_BASE_DURATION * 1.5 * sensitivity); nonstd::observer_ptr swp_ptr = swipe; nonstd::observer_ptr pnc_ptr = pinch; nonstd::observer_ptr esw_ptr = edge_swipe; std::vector> swipe_actions, edge_swipe_actions, pinch_actions; swipe_actions.emplace_back(std::move(swipe)); pinch_actions.emplace_back(std::move(pinch)); edge_swipe_actions.emplace_back(std::move(edge_swipe)); edge_swipe_actions.emplace_back(std::move(edge_release)); auto ack_swipe = [swp_ptr, this] () { uint32_t possible_edges = find_swipe_edges(finger_state.get_center().origin); if (possible_edges) { return; } uint32_t direction = wf_touch_to_wf_dir(swp_ptr->target_direction); wf::touchgesture_t gesture{ GESTURE_TYPE_SWIPE, direction, swp_ptr->cnt_fingers }; wf::get_core().bindings->handle_gesture(gesture); }; auto ack_edge_swipe = [esw_ptr, this] () { uint32_t possible_edges = find_swipe_edges(finger_state.get_center().origin); uint32_t direction = wf_touch_to_wf_dir(esw_ptr->target_direction); possible_edges &= direction; if (possible_edges) { wf::touchgesture_t gesture{ GESTURE_TYPE_EDGE_SWIPE, direction, esw_ptr->cnt_fingers }; wf::get_core().bindings->handle_gesture(gesture); } }; auto ack_pinch = [pnc_ptr] () { wf::touchgesture_t gesture{GESTURE_TYPE_PINCH, pnc_ptr->last_pinch_was_pinch_in ? GESTURE_DIRECTION_IN : GESTURE_DIRECTION_OUT, pnc_ptr->cnt_fingers }; wf::get_core().bindings->handle_gesture(gesture); }; this->multiswipe = std::make_unique(std::move( swipe_actions), ack_swipe); this->edgeswipe = std::make_unique(std::move( edge_swipe_actions), ack_edge_swipe); this->multipinch = std::make_unique(std::move( pinch_actions), ack_pinch); this->add_touch_gesture(this->multiswipe); this->add_touch_gesture(this->edgeswipe); this->add_touch_gesture(this->multipinch); } wayfire-0.10.0/src/core/seat/switch.hpp0000664000175000017500000000060715053502647017621 0ustar dkondordkondor#ifndef WF_SEAT_SWITCH_HPP #define WF_SEAT_SWITCH_HPP #include "seat-impl.hpp" namespace wf { struct switch_device_t : public input_device_impl_t { wf::wl_listener_wrapper on_switch; void handle_switched(wlr_switch_toggle_event *ev); switch_device_t(wlr_input_device *dev); virtual ~switch_device_t() = default; }; } #endif /* end of include guard: WF_SEAT_SWITCH_HPP */ wayfire-0.10.0/src/core/seat/drag-icon.cpp0000664000175000017500000001450415053502647020157 0ustar dkondordkondor#include "drag-icon.hpp" #include "wayfire/unstable/wlr-surface-controller.hpp" #include "wayfire/unstable/wlr-surface-node.hpp" #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/region.hpp" #include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "../core-impl.hpp" #include "wayfire/signal-provider.hpp" #include #include "seat-impl.hpp" namespace wf { namespace scene { class dnd_root_icon_root_node_t : public floating_inner_node_t { class dnd_icon_root_render_instance_t : public render_instance_t { std::vector children; damage_callback push_damage; wf::signal::connection_t on_damage = [=] (node_damage_signal *data) { push_damage(data->region); }; std::weak_ptr _self; public: dnd_icon_root_render_instance_t(dnd_root_icon_root_node_t *self, damage_callback push_damage) { this->_self = std::dynamic_pointer_cast(self->shared_from_this()); this->push_damage = push_damage; self->connect(&on_damage); auto transformed_push_damage = [this] (wf::region_t region) { if (auto self = _self.lock()) { region += self->get_position(); this->push_damage(region); } }; for (auto& ch : self->get_children()) { if (ch->is_enabled()) { ch->gen_render_instances(children, transformed_push_damage); } } } void schedule_instructions( std::vector& instructions, const wf::render_target_t& target, wf::region_t& damage) override { auto self = _self.lock(); if (!self) { return; } wf::render_target_t our_target = target.translated(-self->get_position()); damage += -self->get_position(); for (auto& ch : this->children) { ch->schedule_instructions(instructions, our_target, damage); } damage += self->get_position(); } void render(const wf::scene::render_instruction_t& target) override { wf::dassert(false, "Rendering a drag icon root node?"); } void compute_visibility(wf::output_t *output, wf::region_t& visible) override { if (auto self = _self.lock()) { compute_visibility_from_list(children, output, visible, self->get_position()); } } }; public: wf::drag_icon_t *icon; dnd_root_icon_root_node_t(drag_icon_t *icon) : floating_inner_node_t(false) { this->icon = icon; } /** * Views currently gather damage, etc. manually from the surfaces, * and sometimes render them, sometimes not ... */ void gen_render_instances(std::vector& instances, damage_callback push_damage, wf::output_t *output) override { instances.push_back(std::make_unique(this, push_damage)); } std::optional find_node_at(const wf::pointf_t& at) override { // Don't allow focus going to the DnD surface itself return {}; } wf::geometry_t get_bounding_box() override { if (icon) { return icon->last_box; } else { return {0, 0, 0, 0}; } } std::string stringify() const override { return "dnd-icon " + stringify_flags(); } wf::point_t get_position() { if (icon) { return icon->get_position(); } else { return {0, 0}; } } }; } } /* ------------------------ Drag icon impl ---------------------------------- */ wf::drag_icon_t::drag_icon_t(wlr_drag_icon *ic) : icon(ic) { this->root_node = std::make_shared(this); // Sometimes, the drag surface is reused between two or more drags. // In this case, when the drag starts, the icon is already mapped. if (!icon->surface->mapped) { root_node->set_enabled(false); } on_map.set_callback([=] (void*) { wf::scene::set_node_enabled(root_node, true); wf::scene::damage_node(root_node, root_node->get_bounding_box()); }); on_unmap.set_callback([&] (void*) { wf::scene::damage_node(root_node, last_box); wf::scene::set_node_enabled(root_node, false); }); on_destroy.set_callback([&] (void*) { /* we don't dec_keep_count() because the surface memory is * managed by the unique_ptr */ wf::get_core().seat->priv->drag_icon = nullptr; }); on_map.connect(&icon->surface->events.map); on_unmap.connect(&icon->surface->events.unmap); on_destroy.connect(&icon->events.destroy); auto main_node = std::make_shared(icon->surface, true); root_node->set_children_list({main_node}); // Memory is auto-freed when the wlr_surface is destroyed wlr_surface_controller_t::create_controller(icon->surface, root_node); // Connect to the scenegraph wf::scene::readd_front(wf::get_core().scene(), root_node); } wf::drag_icon_t::~drag_icon_t() { wf::scene::damage_node(root_node, last_box); root_node->icon = nullptr; wf::scene::remove_child(root_node); } wf::point_t wf::drag_icon_t::get_position() { auto pos = icon->drag->grab_type == WLR_DRAG_GRAB_KEYBOARD_TOUCH ? wf::get_core().get_touch_position(icon->drag->touch_id) : wf::get_core().get_cursor_position(); if (root_node->is_enabled()) { pos.x += icon->surface->current.dx; pos.y += icon->surface->current.dy; } return {(int)pos.x, (int)pos.y}; } void wf::drag_icon_t::update_position() { // damage previous position wf::region_t dmg_region; dmg_region |= last_box; last_box = wf::construct_box(get_position(), {icon->surface->current.width, icon->surface->current.height}); dmg_region |= last_box; scene::damage_node(root_node, dmg_region); } wayfire-0.10.0/src/core/seat/input-method-relay.cpp0000664000175000017500000003551615053502647022051 0ustar dkondordkondor#include #include "input-method-relay.hpp" #include "../core-impl.hpp" #include "core/seat/seat-impl.hpp" #include #include #include wf::input_method_relay::input_method_relay() { on_text_input_new.set_callback([&] (void *data) { auto wlr_text_input = static_cast(data); text_inputs.push_back(std::make_unique(this, wlr_text_input)); // Sometimes text_input is created after the surface, so we failed to // set_focus when the surface is focused. Try once here. // // If no surface has been created, set_focus does nothing. // // Example apps (all GTK4): gnome-font-viewer, easyeffects auto& seat = wf::get_core_impl().seat; if (auto focus = seat->priv->keyboard_focus) { if (auto view = wf::node_to_view(focus)) { auto surface = wf::node_to_view(focus)->get_keyboard_focus_surface(); if (surface && (wl_resource_get_client(wlr_text_input->resource) == wl_resource_get_client(surface->resource))) { wlr_text_input_v3_send_enter(wlr_text_input, surface); } } } }); on_input_method_new.set_callback([&] (void *data) { auto new_input_method = static_cast(data); if (input_method != nullptr) { LOGI("Attempted to connect second input method"); wlr_input_method_v2_send_unavailable(new_input_method); return; } LOGD("new input method connected"); input_method = new_input_method; last_done_serial.reset(); next_done_serial = 0; on_input_method_commit.connect(&input_method->events.commit); on_input_method_destroy.connect(&input_method->events.destroy); on_grab_keyboard.connect(&input_method->events.grab_keyboard); on_new_popup_surface.connect(&input_method->events.new_popup_surface); auto *text_input = find_focusable_text_input(); if (text_input) { wlr_text_input_v3_send_enter( text_input->input, text_input->pending_focused_surface); text_input->set_pending_focused_surface(nullptr); } }); on_input_method_commit.set_callback([&] (void *data) { auto evt_input_method = static_cast(data); assert(evt_input_method == input_method); // When we switch focus, we send a done event to the IM. // The IM may need time to process further events and may send additional commits after switching // focus, which belong to the old text input. // // To prevent this, we simply ignore commits which do not acknowledge the newest done event from the // compositor. if (input_method->current_serial < last_done_serial.value_or(0)) { LOGD("focus just changed, ignore input method commit"); return; } auto *text_input = find_focused_text_input(); if (text_input == nullptr) { return; } if (input_method->current.preedit.text) { wlr_text_input_v3_send_preedit_string(text_input->input, input_method->current.preedit.text, input_method->current.preedit.cursor_begin, input_method->current.preedit.cursor_end); } if (input_method->current.commit_text) { wlr_text_input_v3_send_commit_string(text_input->input, input_method->current.commit_text); } if (input_method->current.delete_.before_length || input_method->current.delete_.after_length) { wlr_text_input_v3_send_delete_surrounding_text(text_input->input, input_method->current.delete_.before_length, input_method->current.delete_.after_length); } wlr_text_input_v3_send_done(text_input->input); }); on_input_method_destroy.set_callback([&] (void *data) { auto evt_input_method = static_cast(data); assert(evt_input_method == input_method); on_input_method_commit.disconnect(); on_input_method_destroy.disconnect(); on_grab_keyboard.disconnect(); on_grab_keyboard_destroy.disconnect(); on_new_popup_surface.disconnect(); input_method = nullptr; keyboard_grab = nullptr; auto *text_input = find_focused_text_input(); if (text_input != nullptr) { /* keyboard focus is still there, keep the surface at hand in case the IM * returns */ text_input->set_pending_focused_surface(text_input->input-> focused_surface); wlr_text_input_v3_send_leave(text_input->input); } }); on_grab_keyboard.set_callback([&] (void *data) { if (keyboard_grab != nullptr) { LOGW("Attempted to grab input method keyboard twice"); return; } keyboard_grab = static_cast(data); on_grab_keyboard_destroy.connect(&keyboard_grab->events.destroy); }); on_grab_keyboard_destroy.set_callback([&] (void *data) { on_grab_keyboard_destroy.disconnect(); keyboard_grab = nullptr; }); on_new_popup_surface.set_callback([&] (void *data) { auto popup = static_cast(data); popup_surfaces.push_back(wf::text_input_v3_popup::create(this, popup->surface)); popup_surfaces.back()->on_destroy.set_callback([this, view = popup_surfaces.back().get()] (void*) { auto it = std::remove_if(popup_surfaces.begin(), popup_surfaces.end(), [&] (const auto & suf) { return suf.get() == view; }); popup_surfaces.erase(it, popup_surfaces.end()); }); popup_surfaces.back()->on_destroy.connect(&popup->events.destroy); }); auto& core = wf::get_core(); if (core.protocols.text_input && core.protocols.input_method) { on_text_input_new.connect(&wf::get_core().protocols.text_input->events.text_input); on_input_method_new.connect(&wf::get_core().protocols.input_method->events.input_method); wf::get_core().connect(&keyboard_focus_changed); } } void wf::input_method_relay::send_im_state(wlr_text_input_v3 *input) { wlr_input_method_v2_send_surrounding_text( input_method, input->current.surrounding.text, input->current.surrounding.cursor, input->current.surrounding.anchor); wlr_input_method_v2_send_text_change_cause( input_method, input->current.text_change_cause); wlr_input_method_v2_send_content_type(input_method, input->current.content_type.hint, input->current.content_type.purpose); send_im_done(); } void wf::input_method_relay::send_im_done() { last_done_serial = next_done_serial; next_done_serial++; wlr_input_method_v2_send_done(input_method); } void wf::input_method_relay::disable_text_input(wlr_text_input_v3 *input) { if (input_method == nullptr) { LOGI("Disabling text input, but input method is gone"); return; } // Don't deactivate input method if the text input isn't in focus. // We may get several and posibly interwined enable/disable calls while // switching focus / closing windows; don't deactivate for the wrong one. auto focused_input = find_focused_text_input(); if (!focused_input || (input != focused_input->input)) { return; } wlr_input_method_v2_send_deactivate(input_method); send_im_state(input); } void wf::input_method_relay::remove_text_input(wlr_text_input_v3 *input) { auto it = std::remove_if(text_inputs.begin(), text_inputs.end(), [&] (const auto & inp) { return inp->input == input; }); text_inputs.erase(it, text_inputs.end()); } bool wf::input_method_relay::should_grab(wlr_keyboard *kbd) { if ((keyboard_grab == nullptr) || !find_focused_text_input()) { return false; } return !is_im_sent(kbd); } bool wf::input_method_relay::is_im_sent(wlr_keyboard *kbd) { struct wlr_virtual_keyboard_v1 *virtual_keyboard = wlr_input_device_get_virtual_keyboard(&kbd->base); if (!virtual_keyboard) { return false; } // We have already identified the device as IM-based device auto device_impl = (wf::input_device_impl_t*)kbd->base.data; if (device_impl->is_im_keyboard) { return true; } if (this->input_method) { // This is a workaround because we do not have sufficient information to know which virtual keyboards // are connected to IMs auto im_client = wl_resource_get_client(input_method->resource); auto vkbd_client = wl_resource_get_client(virtual_keyboard->resource); if (im_client == vkbd_client) { device_impl->is_im_keyboard = true; return true; } } return false; } bool wf::input_method_relay::handle_key(struct wlr_keyboard *kbd, uint32_t time, uint32_t key, uint32_t state) { if (!should_grab(kbd)) { return false; } wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, kbd); wlr_input_method_keyboard_grab_v2_send_key(keyboard_grab, time, key, state); return true; } bool wf::input_method_relay::handle_modifier(struct wlr_keyboard *kbd) { if (!should_grab(kbd)) { return false; } wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab, kbd); wlr_input_method_keyboard_grab_v2_send_modifiers(keyboard_grab, &kbd->modifiers); return true; } wf::text_input*wf::input_method_relay::find_focusable_text_input() { auto it = std::find_if(text_inputs.begin(), text_inputs.end(), [&] (const auto & text_input) { return text_input->pending_focused_surface != nullptr; }); if (it != text_inputs.end()) { return it->get(); } return nullptr; } wf::text_input*wf::input_method_relay::find_focused_text_input() { auto it = std::find_if(text_inputs.begin(), text_inputs.end(), [&] (const auto & text_input) { return text_input->input->focused_surface != nullptr; }); if (it != text_inputs.end()) { return it->get(); } return nullptr; } void wf::input_method_relay::set_focus(wlr_surface *surface) { for (auto & text_input : text_inputs) { if (text_input->pending_focused_surface != nullptr) { assert(text_input->input->focused_surface == nullptr); if (surface != text_input->pending_focused_surface) { text_input->set_pending_focused_surface(nullptr); } } else if (text_input->input->focused_surface != nullptr) { assert(text_input->pending_focused_surface == nullptr); if (surface != text_input->input->focused_surface) { disable_text_input(text_input->input); wlr_text_input_v3_send_leave(text_input->input); } else { LOGD("set_focus an already focused surface"); continue; } } if (surface && (wl_resource_get_client(text_input->input->resource) == wl_resource_get_client(surface->resource))) { if (input_method) { wlr_text_input_v3_send_enter(text_input->input, surface); } else { text_input->set_pending_focused_surface(surface); } } } } wf::input_method_relay::~input_method_relay() {} wf::text_input::text_input(wf::input_method_relay *rel, wlr_text_input_v3 *in) : relay(rel), input(in), pending_focused_surface(nullptr) { on_text_input_enable.set_callback([&] (void *data) { auto wlr_text_input = static_cast(data); assert(input == wlr_text_input); if (relay->input_method == nullptr) { LOGI("Enabling text input, but input method is gone"); return; } wlr_input_method_v2_send_activate(relay->input_method); relay->send_im_state(input); }); on_text_input_commit.set_callback([&] (void *data) { auto wlr_text_input = static_cast(data); assert(input == wlr_text_input); if (!input->current_enabled) { LOGI("Inactive text input tried to commit"); return; } if (relay->input_method == nullptr) { LOGI("Committing text input, but input method is gone"); return; } relay->send_im_state(input); wf::text_input_commit_signal sigdata; sigdata.cursor_rect = input->current.cursor_rectangle; relay->emit(&sigdata); }); on_text_input_disable.set_callback([&] (void *data) { auto wlr_text_input = static_cast(data); assert(input == wlr_text_input); relay->disable_text_input(input); }); on_text_input_destroy.set_callback([&] (void *data) { auto wlr_text_input = static_cast(data); assert(input == wlr_text_input); if (input->current_enabled) { relay->disable_text_input(wlr_text_input); } set_pending_focused_surface(nullptr); on_text_input_enable.disconnect(); on_text_input_commit.disconnect(); on_text_input_disable.disconnect(); on_text_input_destroy.disconnect(); // NOTE: the call destroys `this` relay->remove_text_input(wlr_text_input); }); on_pending_focused_surface_destroy.set_callback([&] (void *data) { auto surface = static_cast(data); assert(pending_focused_surface == surface); pending_focused_surface = nullptr; on_pending_focused_surface_destroy.disconnect(); }); on_text_input_enable.connect(&input->events.enable); on_text_input_commit.connect(&input->events.commit); on_text_input_disable.connect(&input->events.disable); on_text_input_destroy.connect(&input->events.destroy); } void wf::text_input::set_pending_focused_surface(wlr_surface *surface) { pending_focused_surface = surface; if (surface == nullptr) { on_pending_focused_surface_destroy.disconnect(); } else { on_pending_focused_surface_destroy.connect(&surface->events.destroy); } } wf::text_input::~text_input() {} wlr_text_input_v3*wf::input_method_relay::find_focused_text_input_v3() { auto focus = find_focused_text_input(); return focus ? focus->input : nullptr; } wayfire-0.10.0/src/core/seat/cursor.hpp0000664000175000017500000000414015053502647017631 0ustar dkondordkondor#ifndef CURSOR_HPP #define CURSOR_HPP #include "seat-impl.hpp" #include "wayfire/plugin.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/util.hpp" namespace wf { struct cursor_t { cursor_t(wf::seat_t *seat); ~cursor_t() = default; /** * Register a new input device. */ void add_new_device(wlr_input_device *device); /** * Set the cursor image from a wlroots event. * @param validate_request Whether to validate the request against the * currently focused pointer surface, or not. */ void set_cursor(wlr_seat_pointer_request_set_cursor_event *ev, bool validate_request); void set_cursor(std::string name); void unhide_cursor(); void hide_cursor(); int hide_ref_counter = 0; /** * Delay setting the cursor, in order to avoid setting the cursor * multiple times in a single frame and to avoid setting it in the middle * of the repaint loop (not allowed by wlroots). */ wf::wl_idle_call idle_set_cursor; /** * Start/stop touchscreen mode, which means the cursor will be hidden. * It will be shown again once a pointer or tablet event happens. */ void set_touchscreen_mode(bool enabled); /* Move the cursor to the given point */ void warp_cursor(wf::pointf_t point); wf::pointf_t get_cursor_position(); void init_xcursor(); void setup_listeners(); // Device event listeners wf::wl_listener_wrapper on_button, on_motion, on_motion_absolute, on_axis, on_swipe_begin, on_swipe_update, on_swipe_end, on_pinch_begin, on_pinch_update, on_pinch_end, on_hold_begin, on_hold_end, on_tablet_tip, on_tablet_axis, on_tablet_button, on_tablet_proximity, on_frame; // Seat events wf::wl_listener_wrapper request_set_cursor; wf::signal::connection_t config_reloaded; wf::seat_t *seat; wlr_cursor *cursor = NULL; wlr_xcursor_manager *xcursor = NULL; std::string last_cursor_name; bool touchscreen_mode_active = false; }; } #endif /* end of include guard: CURSOR_HPP */ wayfire-0.10.0/src/core/seat/pointing-device.cpp0000664000175000017500000002056615053502647021405 0ustar dkondordkondor#include "pointing-device.hpp" #include wf::pointing_device_t::pointing_device_t(wlr_input_device *dev) : input_device_impl_t(dev) { dev->data = this; load_options(); update_options(); } void wf::pointing_device_t::load_options() { auto section = wf::get_core().config_backend->get_input_device_section("input", get_wlr_handle()); auto section_name = section->get_name(); left_handed_mode.load_option(section_name + "/left_handed_mode"); middle_emulation.load_option(section_name + "/middle_emulation"); mouse_scroll_speed.load_option(section_name + "/mouse_scroll_speed"); mouse_cursor_speed.load_option(section_name + "/mouse_cursor_speed"); touchpad_cursor_speed.load_option(section_name + "/touchpad_cursor_speed"); touchpad_scroll_speed.load_option(section_name + "/touchpad_scroll_speed"); mouse_natural_scroll_enabled.load_option(section_name + "/mouse_natural_scroll"); touchpad_tap_enabled.load_option(section_name + "/tap_to_click"); touchpad_dwt_enabled.load_option(section_name + "/disable_touchpad_while_typing"); touchpad_dwmouse_enabled.load_option(section_name + "/disable_touchpad_while_mouse"); touchpad_natural_scroll_enabled.load_option(section_name + "/natural_scroll"); touchpad_tap_and_drag_enabled.load_option(section_name + "/tap_and_drag"); touchpad_drag_lock_enabled.load_option(section_name + "/drag_lock"); touchpad_3fg_drag.load_option(section_name + "/3fg_drag"); mouse_accel_profile.load_option(section_name + "/mouse_accel_profile"); touchpad_accel_profile.load_option(section_name + "/touchpad_accel_profile"); touchpad_click_method.load_option(section_name + "/click_method"); touchpad_scroll_method.load_option(section_name + "/scroll_method"); } static void set_libinput_accel_profile(libinput_device *dev, std::string name) { if (name == "default") { libinput_device_config_accel_set_profile(dev, libinput_device_config_accel_get_default_profile(dev)); } else if (name == "none") { libinput_device_config_accel_set_profile(dev, LIBINPUT_CONFIG_ACCEL_PROFILE_NONE); } else if (name == "adaptive") { libinput_device_config_accel_set_profile(dev, LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE); } else if (name == "flat") { libinput_device_config_accel_set_profile(dev, LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT); } else { LOGE("Invalid libinput acceleration profile."); } } void wf::pointing_device_t::update_options() { /* We currently support options only for libinput devices */ if (!wlr_input_device_is_libinput(get_wlr_handle())) { return; } auto dev = wlr_libinput_get_device_handle(get_wlr_handle()); assert(dev); libinput_device_config_left_handed_set(dev, left_handed_mode); libinput_device_config_middle_emulation_set_enabled(dev, middle_emulation ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED); /* we are configuring a touchpad */ if (libinput_device_config_tap_get_finger_count(dev) > 0) { libinput_device_config_accel_set_speed(dev, touchpad_cursor_speed); set_libinput_accel_profile(dev, touchpad_accel_profile); libinput_device_config_tap_set_enabled(dev, touchpad_tap_enabled ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED); if ((std::string)touchpad_click_method == "default") { libinput_device_config_click_set_method(dev, libinput_device_config_click_get_default_method(dev)); } else if ((std::string)touchpad_click_method == "none") { libinput_device_config_click_set_method(dev, LIBINPUT_CONFIG_CLICK_METHOD_NONE); } else if ((std::string)touchpad_click_method == "button-areas") { libinput_device_config_click_set_method(dev, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS); } else if ((std::string)touchpad_click_method == "clickfinger") { libinput_device_config_click_set_method(dev, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER); } else { LOGE("Invalid libinput click method."); } if ((std::string)touchpad_scroll_method == "default") { libinput_device_config_scroll_set_method(dev, libinput_device_config_scroll_get_default_method(dev)); } else if ((std::string)touchpad_scroll_method == "none") { libinput_device_config_scroll_set_method(dev, LIBINPUT_CONFIG_SCROLL_NO_SCROLL); } else if ((std::string)touchpad_scroll_method == "two-finger") { libinput_device_config_scroll_set_method(dev, LIBINPUT_CONFIG_SCROLL_2FG); } else if ((std::string)touchpad_scroll_method == "edge") { libinput_device_config_scroll_set_method(dev, LIBINPUT_CONFIG_SCROLL_EDGE); } else if ((std::string)touchpad_scroll_method == "on-button-down") { libinput_device_config_scroll_set_method(dev, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); } else { LOGE("Invalid libinput scroll method."); } libinput_device_config_dwt_set_enabled(dev, touchpad_dwt_enabled ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED); libinput_device_config_send_events_set_mode(dev, touchpad_dwmouse_enabled ? LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE : LIBINPUT_CONFIG_SEND_EVENTS_ENABLED); libinput_device_config_tap_set_drag_enabled(dev, touchpad_tap_and_drag_enabled ? LIBINPUT_CONFIG_DRAG_ENABLED : LIBINPUT_CONFIG_DRAG_DISABLED); libinput_device_config_tap_set_drag_lock_enabled(dev, touchpad_drag_lock_enabled ? LIBINPUT_CONFIG_DRAG_LOCK_ENABLED : LIBINPUT_CONFIG_DRAG_LOCK_DISABLED); if ((std::string)touchpad_3fg_drag == "default") { #if HAVE_LIBINPUT_3FG_DRAG libinput_device_config_3fg_drag_set_enabled(dev, libinput_device_config_3fg_drag_get_default_enabled(dev)); #endif } else if ((std::string)touchpad_3fg_drag == "none") { #if HAVE_LIBINPUT_3FG_DRAG libinput_device_config_3fg_drag_set_enabled(dev, LIBINPUT_CONFIG_3FG_DRAG_DISABLED); #else LOGW("Multi-finger drag not implemented with current libinput version."); #endif } else if ((std::string)touchpad_3fg_drag == "3fg-drag") { #if HAVE_LIBINPUT_3FG_DRAG libinput_device_config_3fg_drag_set_enabled(dev, LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG); #else LOGW("Multi-finger drag not implemented with current libinput version."); #endif } else if ((std::string)touchpad_3fg_drag == "4fg-drag") { #if HAVE_LIBINPUT_3FG_DRAG libinput_device_config_3fg_drag_set_enabled(dev, LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG); #else LOGW("Multi-finger drag not implemented with current libinput version."); #endif } else { LOGE("Invalid libinput multi-finger drag value."); } if (libinput_device_config_scroll_has_natural_scroll(dev) > 0) { libinput_device_config_scroll_set_natural_scroll_enabled(dev, (bool)touchpad_natural_scroll_enabled); } } else { libinput_device_config_accel_set_speed(dev, mouse_cursor_speed); set_libinput_accel_profile(dev, mouse_accel_profile); if (libinput_device_config_scroll_has_natural_scroll(dev) > 0) { libinput_device_config_scroll_set_natural_scroll_enabled(dev, (bool)mouse_natural_scroll_enabled); } } } double wf::pointing_device_t::get_scroll_speed(wlr_input_device *dev, bool touchpad) { if ((touchpad && (dev->type != WLR_INPUT_DEVICE_TABLET_PAD)) || (!touchpad && (dev->type != WLR_INPUT_DEVICE_POINTER))) { return 1.0; } return touchpad ? touchpad_scroll_speed : mouse_scroll_speed; } void wf::pointing_device_t::reconfigure_device(std::shared_ptr device_section) { map_to_output(device_section); } wayfire-0.10.0/src/core/seat/keyboard.hpp0000664000175000017500000000266215053502647020123 0ustar dkondordkondor#pragma once #include #include "seat-impl.hpp" #include "wayfire/signal-definitions.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/util.hpp" #include namespace wf { enum locked_mods_t { KB_MOD_NUM_LOCK = 1 << 0, KB_MOD_CAPS_LOCK = 1 << 1, }; /** * Represents a logical keyboard. */ class keyboard_t { public: keyboard_t(wlr_input_device *keyboard); ~keyboard_t(); wlr_keyboard *handle; wlr_input_device *device; /** Get the currently pressed modifiers */ uint32_t get_modifiers(); /* The keycode which triggered the modifier binding */ uint32_t mod_binding_key = 0; /** Convert a key to a modifier */ uint32_t mod_from_key(uint32_t key); private: wf::wl_listener_wrapper on_key, on_modifier; void setup_listeners(); wf::signal::connection_t on_config_reload; void reload_input_options(); wf::option_wrapper_t model, variant, layout, options, rules; wf::option_wrapper_t repeat_rate, repeat_delay; /** Options have changed in the config file */ bool dirty_options = true; std::chrono::steady_clock::time_point mod_binding_start; bool handle_keyboard_key(uint32_t key, uint32_t state); /** Get the current locked mods */ uint32_t get_locked_mods(); /** Check whether we have only modifiers pressed down */ bool has_only_modifiers(); }; } wayfire-0.10.0/src/core/seat/tablet.cpp0000664000175000017500000003632015053502647017567 0ustar dkondordkondor#include #include "tablet.hpp" #include "../core-impl.hpp" #include "pointer.hpp" #include "cursor.hpp" #include "view/view-impl.hpp" #include "wayfire/core.hpp" #include "wayfire/debug.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/view.hpp" #include #include #include #include #include #include /* --------------------- Tablet tool implementation ------------------------- */ wf::tablet_tool_t::tablet_tool_t(wlr_tablet_tool *tool, wlr_tablet_v2_tablet *tablet_v2) { this->tablet_v2 = tablet_v2; /* Initialize tool_v2 */ this->tool = tool; this->tool->data = this; auto& core = wf::get_core_impl(); this->tool_v2 = wlr_tablet_tool_create(core.protocols.tablet_v2, core.get_current_seat(), tool); /* Free memory when the tool is destroyed */ this->on_destroy.set_callback([=] (void*) { this->tool->data = nullptr; tablet->tools_list.erase( std::remove_if(tablet->tools_list.begin(), tablet->tools_list.end(), [this] (const auto& p) { return p.get() == this; }), tablet->tools_list.end()); }); this->on_destroy.connect(&tool->events.destroy); /* Ungrab surface, and update focused surface if a surface is unmapped, * we don't want to end up with a reference to unfocused or a destroyed * surface. */ on_root_node_updated = [=] (wf::scene::root_node_update_signal *data) { if (!(data->flags & wf::scene::update_flag::INPUT_STATE)) { return; } if (grabbed_node && !is_grabbed_node_alive(grabbed_node)) { reset_grab(); } update_tool_position(false); }; wf::get_core().scene()->connect(&on_root_node_updated); /* Just pass cursor set requests to core, but translate them to * regular pointer set requests */ on_set_cursor.set_callback([=] (void *data) { if (!this->is_active) { return; } auto ev = static_cast(data); // validate request wlr_seat_client *tablet_client = nullptr; if (tool_v2->focused_surface) { tablet_client = wlr_seat_client_for_wl_client( wf::get_core().get_current_seat(), wl_resource_get_client(tool_v2->focused_surface->resource)); } if (tablet_client != ev->seat_client) { return; } wlr_seat_pointer_request_set_cursor_event pev; pev.surface = ev->surface; pev.hotspot_x = ev->hotspot_x; pev.hotspot_y = ev->hotspot_y; pev.serial = ev->serial; pev.seat_client = ev->seat_client; wf::get_core_impl().seat->priv->cursor->set_cursor(&pev, false); }); on_set_cursor.connect(&tool_v2->events.set_cursor); } wf::tablet_tool_t::~tablet_tool_t() { tool->data = NULL; } static inline wlr_surface *wlr_surface_from_node(wf::scene::node_ptr node) { if (auto snode = dynamic_cast(node.get())) { return snode->get_surface(); } return nullptr; } void wf::tablet_tool_t::update_tool_position(bool real_update) { if (!is_active) { return; } auto& core = wf::get_core_impl(); auto gc = core.get_cursor_position(); /* Figure out what surface is under the tool */ wf::pointf_t local; // local to the surface wf::scene::node_ptr focus_node = nullptr; if (this->grabbed_node) { focus_node = this->grabbed_node; local = get_node_local_coords(focus_node.get(), gc); } else { auto input_node = wf::get_core().scene()->find_node_at(gc); if (input_node) { focus_node = input_node->node->shared_from_this(); local = input_node->local_coords; } } bool focus_changed = set_focus(focus_node); /* If focus is a wlr surface, send position */ wlr_surface *next_focus = wlr_surface_from_node(focus_node); if (next_focus && (real_update || focus_changed)) { wlr_tablet_v2_tablet_tool_notify_motion(tool_v2, local.x, local.y); } } bool wf::tablet_tool_t::set_focus(wf::scene::node_ptr surface) { bool focus_changed = surface != this->proximity_surface; /* Unfocus old surface */ if ((surface != this->proximity_surface) && this->proximity_surface) { wlr_tablet_v2_tablet_tool_notify_proximity_out(tool_v2); this->proximity_surface = nullptr; } /* Set the new focus, if it is a wlr surface */ wlr_surface *next_focus = wlr_surface_from_node(surface); if (next_focus) { wf::xwayland_bring_to_front(next_focus); } if (next_focus && wlr_surface_accepts_tablet_v2(next_focus, tablet_v2)) { this->proximity_surface = surface; wlr_tablet_v2_tablet_tool_notify_proximity_in(tool_v2, tablet_v2, next_focus); } if (!next_focus) { wf::get_core().set_cursor("default"); } return focus_changed; } void wf::tablet_tool_t::reset_grab() { this->grabbed_node = nullptr; } void wf::tablet_tool_t::passthrough_axis(wlr_tablet_tool_axis_event *ev) { if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) { wlr_tablet_v2_tablet_tool_notify_pressure(tool_v2, ev->pressure); } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) { wlr_tablet_v2_tablet_tool_notify_distance(tool_v2, ev->distance); } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) { wlr_tablet_v2_tablet_tool_notify_rotation(tool_v2, ev->rotation); } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) { wlr_tablet_v2_tablet_tool_notify_slider(tool_v2, ev->slider); } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) { wlr_tablet_v2_tablet_tool_notify_wheel(tool_v2, ev->wheel_delta, 0); } /* Update tilt, use old values if no new values are provided */ if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) { tilt_x = ev->tilt_x; } if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) { tilt_y = ev->tilt_y; } if (ev->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) { wlr_tablet_v2_tablet_tool_notify_tilt(tool_v2, tilt_x, tilt_y); } } void wf::tablet_tool_t::handle_tip(wlr_tablet_tool_tip_event *ev) { /* Nothing to do without a proximity surface */ if (!this->proximity_surface) { return; } if (ev->state == WLR_TABLET_TOOL_TIP_DOWN) { wlr_send_tablet_v2_tablet_tool_down(tool_v2); this->grabbed_node = this->proximity_surface; /* Try to focus the view under the tool */ auto view = wf::node_to_view(this->proximity_surface); wf::get_core().default_wm->focus_raise_view(view); } else { wlr_send_tablet_v2_tablet_tool_up(tool_v2); this->grabbed_node = nullptr; update_tool_position(false); } } void wf::tablet_tool_t::handle_button(wlr_tablet_tool_button_event *ev) { wlr_tablet_v2_tablet_tool_notify_button(tool_v2, (zwp_tablet_pad_v2_button_state)ev->button, (zwp_tablet_pad_v2_button_state)ev->state); } void wf::tablet_tool_t::handle_proximity(wlr_tablet_tool_proximity_event *ev) { if (ev->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { set_focus(nullptr); is_active = false; } else { is_active = true; update_tool_position(true); } } /* ----------------------- Tablet implementation ---------------------------- */ wf::tablet_t::tablet_t(wlr_cursor *cursor, wlr_input_device *dev) : input_device_impl_t(dev) { this->handle = wlr_tablet_from_input_device(dev); this->handle->data = this; this->cursor = cursor; auto& core = wf::get_core_impl(); tablet_v2 = wlr_tablet_create(core.protocols.tablet_v2, core.get_current_seat(), dev); } wf::tablet_t::~tablet_t() { this->handle->data = NULL; } wf::tablet_tool_t*wf::tablet_t::ensure_tool(wlr_tablet_tool *tool) { if (tool->data == NULL) { auto wtool = std::make_unique(tool, tablet_v2); wtool->tablet = this; auto ptr = wtool.get(); this->tools_list.push_back(std::move(wtool)); return ptr; } return (wf::tablet_tool_t*)tool->data; } void wf::tablet_t::handle_tip(wlr_tablet_tool_tip_event *ev, input_event_processing_mode_t mode) { if (should_use_absolute_positioning(ev->tool)) { wlr_cursor_warp_absolute(cursor, &ev->tablet->base, ev->x, ev->y); } auto& seat = wf::get_core_impl().seat; seat->priv->break_mod_bindings(); bool handled_in_binding = false; if (ev->state == WLR_TABLET_TOOL_TIP_DOWN) { auto gc = seat->priv->cursor->get_cursor_position(); auto output = wf::get_core().output_layout->get_output_at(gc.x, gc.y); wf::get_core().seat->focus_output(output); handled_in_binding |= wf::get_core().bindings->handle_button( wf::buttonbinding_t{seat->priv->get_modifiers(), BTN_LEFT}); } auto tool = ensure_tool(ev->tool); if (!handled_in_binding) { tool->handle_tip(ev); } } void wf::tablet_t::handle_axis(wlr_tablet_tool_axis_event *ev, input_event_processing_mode_t mode) { /* Update cursor position */ if (should_use_absolute_positioning(ev->tool)) { double x = (ev->updated_axes & WLR_TABLET_TOOL_AXIS_X) ? ev->x : NAN; double y = (ev->updated_axes & WLR_TABLET_TOOL_AXIS_Y) ? ev->y : NAN; wlr_cursor_warp_absolute(cursor, &ev->tablet->base, x, y); } else { wlr_cursor_move(cursor, &ev->tablet->base, ev->dx, ev->dy); } /* Update focus */ auto tool = ensure_tool(ev->tool); tool->update_tool_position(true); tool->passthrough_axis(ev); } void wf::tablet_t::handle_button(wlr_tablet_tool_button_event *ev, input_event_processing_mode_t mode) { /* Pass to the tool */ ensure_tool(ev->tool)->handle_button(ev); } void wf::tablet_t::handle_proximity(wlr_tablet_tool_proximity_event *ev, input_event_processing_mode_t mode) { if (should_use_absolute_positioning(ev->tool)) { wlr_cursor_warp_absolute(cursor, &ev->tablet->base, ev->x, ev->y); } ensure_tool(ev->tool)->handle_proximity(ev); auto& impl = wf::get_core_impl(); /* Show appropriate cursor */ if (ev->state == WLR_TABLET_TOOL_PROXIMITY_OUT) { impl.set_cursor("default"); impl.seat->priv->lpointer->set_enable_focus(true); } else { wf::get_core().set_cursor("crosshair"); impl.seat->priv->lpointer->set_enable_focus(false); } } /* ------------------------ Tablet pad implementation ----------------------- */ wf::tablet_pad_t::tablet_pad_t(wlr_input_device *pad) : input_device_impl_t(pad) { auto& core = wf::get_core(); this->pad_v2 = wlr_tablet_pad_create(core.protocols.tablet_v2, core.get_current_seat(), pad); on_device_added = [=] (auto) { select_default_tool(); }; on_device_removed = [=] (auto) { select_default_tool(); }; wf::get_core().connect(&on_device_added); wf::get_core().connect(&on_device_removed); on_keyboard_focus_changed.set_callback([=] (auto) { update_focus(); }); wf::get_core().connect(&on_keyboard_focus_changed); select_default_tool(); on_attach.set_callback([=] (void *data) { auto wlr_tool = static_cast(data); auto tool = (tablet_tool_t*)wlr_tool->data; if (tool) { attach_to_tablet(tool->tablet); } }); on_button.set_callback([=] (void *data) { auto ev = static_cast(data); wlr_tablet_v2_tablet_pad_notify_mode(pad_v2, ev->group, ev->mode, ev->time_msec); wlr_tablet_v2_tablet_pad_notify_button(pad_v2, ev->button, ev->time_msec, (zwp_tablet_pad_v2_button_state)ev->state); }); on_strip.set_callback([=] (void *data) { auto ev = static_cast(data); wlr_tablet_v2_tablet_pad_notify_strip(pad_v2, ev->strip, ev->position, ev->source == WLR_TABLET_PAD_STRIP_SOURCE_FINGER, ev->time_msec); }); on_ring.set_callback([=] (void *data) { auto ev = static_cast(data); wlr_tablet_v2_tablet_pad_notify_ring(pad_v2, ev->ring, ev->position, ev->source == WLR_TABLET_PAD_RING_SOURCE_FINGER, ev->time_msec); }); on_attach.connect(&wlr_tablet_pad_from_input_device(pad)->events.attach_tablet); on_button.connect(&wlr_tablet_pad_from_input_device(pad)->events.button); on_strip.connect(&wlr_tablet_pad_from_input_device(pad)->events.strip); on_ring.connect(&wlr_tablet_pad_from_input_device(pad)->events.ring); on_focus_destroy.set_callback([&] (auto) { update_focus(nullptr); }); } void wf::tablet_pad_t::update_focus() { auto focus_view = wf::get_core().seat->get_active_view(); auto focus_surface = focus_view ? focus_view->get_wlr_surface() : nullptr; update_focus(focus_surface); } void wf::tablet_pad_t::update_focus(wlr_surface *focus_surface) { if (focus_surface == old_focus) { return; } if (old_focus) { wlr_tablet_v2_tablet_pad_notify_leave(pad_v2, old_focus); } if (focus_surface && attached_to) { wlr_tablet_v2_tablet_pad_notify_enter(pad_v2, attached_to->tablet_v2, focus_surface); } on_focus_destroy.disconnect(); if (focus_surface) { on_focus_destroy.connect(&focus_surface->events.destroy); } old_focus = focus_surface; } void wf::tablet_pad_t::attach_to_tablet(tablet_t *tablet) { update_focus(nullptr); this->attached_to = nonstd::make_observer(tablet); update_focus(); } static libinput_device_group *get_group(wlr_input_device *dev) { if (wlr_input_device_is_libinput(dev)) { auto hnd = wlr_libinput_get_device_handle(dev); return libinput_device_get_device_group(hnd); } return nullptr; } void wf::tablet_pad_t::select_default_tool() { auto devices = wf::get_core().get_input_devices(); for (auto& dev : devices) { /* Remain as-is */ if (dev == attached_to) { return; } if (dev->get_wlr_handle()->type != WLR_INPUT_DEVICE_TABLET) { continue; } auto pad_gr = get_group(this->get_wlr_handle()); auto tab_gr = get_group(dev->get_wlr_handle()); if (pad_gr == tab_gr) { attach_to_tablet(static_cast(dev.get())); return; } } attach_to_tablet(nullptr); } bool wf::tablet_t::should_use_absolute_positioning(wlr_tablet_tool *tool) { static wf::option_wrapper_t tablet_motion_mode{"input/tablet_motion_mode"}; /* Update cursor position */ if ((std::string)tablet_motion_mode == "absolute") { return true; } else if ((std::string)tablet_motion_mode == "relative") { return false; } else { return tool->type != WLR_TABLET_TOOL_TYPE_MOUSE; } } wayfire-0.10.0/src/core/seat/seat-impl.hpp0000664000175000017500000001042515053502647020212 0ustar dkondordkondor#pragma once #include #include #include #include #include #include "wayfire/output.hpp" #include "wayfire/input-device.hpp" #include "wayfire/scene-input.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/toplevel-view.hpp" #include "wayfire/util.hpp" namespace wf { struct cursor_t; class keyboard_t; class input_device_impl_t : public wf::input_device_t { public: input_device_impl_t(wlr_input_device *dev); virtual ~input_device_impl_t(); wf::wl_listener_wrapper on_destroy; virtual void update_options() {} bool is_im_keyboard = false; virtual void reconfigure_device(std::shared_ptr device_section) {} /** * Map the device to the output configured in the config file. */ void map_to_output(std::shared_ptr device_section); }; struct touchscreen_device_t : public input_device_impl_t { using input_device_impl_t::input_device_impl_t; void reconfigure_device(std::shared_ptr device_section) override; private: void calibrate(std::shared_ptr device_section); }; class pointer_t; class touch_interface_t; class drag_icon_t; /** * A seat is a collection of input devices which work together, and have a * keyboard focus, etc. * * The seat is the place where a bit of the shared state of separate input devices * resides, and also contains: * * 1. Keyboards * 2. Logical pointer * 3. Touch interface * 4. Tablets * * In addition, each seat has its own clipboard, primary selection and DnD state. * Currently, Wayfire supports just a single seat. */ struct seat_t::impl { uint32_t get_modifiers(); void break_mod_bindings(); void set_keyboard_focus(wf::scene::node_ptr keyboard_focus, wf::keyboard_focus_reason reason); wf::scene::node_ptr keyboard_focus; // Keys sent to the current keyboard focus std::multiset pressed_keys; void transfer_grab(wf::scene::node_ptr new_focus); /** * Set the currently active keyboard on the seat. */ void set_keyboard(wf::keyboard_t *kbd); wlr_seat *seat = nullptr; std::unique_ptr cursor; std::unique_ptr lpointer; std::unique_ptr touch; // Current drag icon std::unique_ptr drag_icon; // Is dragging active. Note we can have a drag without a drag icon. bool drag_active = false; /** Update the position of the drag icon, if it exists */ void update_drag_icon(); /** The currently active keyboard device on the seat */ wf::keyboard_t *current_keyboard = nullptr; wf::wl_listener_wrapper request_start_drag, start_drag, end_drag, request_set_selection, request_set_primary_selection; wf::signal::connection_t on_new_device; wf::signal::connection_t on_remove_device; /** A list of all keyboards in this seat */ std::vector> keyboards; /** * Check if the drag request is valid, and if yes, start the drag operation. */ void validate_drag_request(wlr_seat_request_start_drag_event *ev); /** Send updated capabilities to clients */ void update_capabilities(); void force_release_keys(); wf::wl_listener_wrapper on_wlr_keyboard_grab_end; wf::wl_listener_wrapper on_wlr_pointer_grab_end; uint64_t last_timestamp = 0; wf::output_t *active_output = nullptr; std::weak_ptr _last_active_toplevel; std::weak_ptr _last_active_view; wf::signal::connection_t on_root_node_updated; void update_active_view(wf::scene::node_ptr new_focus); // Last serial used for button press, release, touch down/up and or tablet tip events. uint32_t last_press_release_serial = 0; }; } /** Convert the given point to a node-local point */ wf::pointf_t get_node_local_coords(wf::scene::node_t *node, const wf::pointf_t& point); /** Check whether a node with an implicit grab should still retain the grab. */ bool is_grabbed_node_alive(wf::scene::node_ptr node); wayfire-0.10.0/src/core/seat/hotspot-manager.cpp0000664000175000017500000001022015053502647021413 0ustar dkondordkondor#include "hotspot-manager.hpp" #include "wayfire/core.hpp" #include #include void wf::hotspot_instance_t::process_input_motion(wf::pointf_t gc) { const auto& reset_hotspot = [&] () { timer.disconnect(); this->armed = true; }; auto target = wf::get_core().output_layout->get_output_coords_at(gc, gc); if (target != last_output) { reset_hotspot(); last_output = target; recalc_geometry(); } if (!(hotspot_geometry[0] & gc) && !(hotspot_geometry[1] & gc)) { reset_hotspot(); return; } if (!timer.is_connected() && this->armed) { this->armed = false; timer.set_timeout(timeout_ms, [=] () { callback(this->edges); return false; }); } } wf::geometry_t wf::hotspot_instance_t::pin(wf::dimensions_t dim) noexcept { if (!last_output) { return {0, 0, 0, 0}; } auto og = last_output->get_layout_geometry(); wf::geometry_t result; result.width = dim.width; result.height = dim.height; if (this->edges & OUTPUT_EDGE_LEFT) { result.x = og.x; } else if (this->edges & OUTPUT_EDGE_RIGHT) { result.x = og.x + og.width - dim.width; } else { result.x = og.x + og.width / 2 - dim.width / 2; } if (this->edges & OUTPUT_EDGE_TOP) { result.y = og.y; } else if (this->edges & OUTPUT_EDGE_BOTTOM) { result.y = og.y + og.height - dim.height; } else { result.y = og.y + og.height / 2 - dim.height / 2; } // Need to clamp if the region is very wide return wf::clamp(result, og); } void wf::hotspot_instance_t::recalc_geometry() noexcept { uint32_t cnt_edges = __builtin_popcount(edges); if (cnt_edges == 2) { hotspot_geometry[0] = pin({away, along}); hotspot_geometry[1] = pin({along, away}); } else { wf::dimensions_t dim; if (edges & (OUTPUT_EDGE_LEFT | OUTPUT_EDGE_RIGHT)) { dim = {away, along}; } else { dim = {along, away}; } hotspot_geometry[0] = pin(dim); hotspot_geometry[1] = hotspot_geometry[0]; } } wf::hotspot_instance_t::hotspot_instance_t(uint32_t edges, uint32_t along, uint32_t away, int32_t timeout, std::function callback) { wf::get_core().connect(&on_motion_event); wf::get_core().connect(&on_motion_event); wf::get_core().connect(&on_touch_motion); this->edges = edges; this->along = along; this->away = away; this->timeout_ms = timeout; this->callback = callback; recalc_geometry(); // callbacks on_tablet_axis = [=] (wf::post_input_event_signal *ev) { process_input_motion(wf::get_core().get_cursor_position()); }; on_motion_event = [=] (auto) { process_input_motion(wf::get_core().get_cursor_position()); }; on_touch_motion = [=] (wf::post_input_event_signal *ev) { const auto& state = wf::get_core().get_touch_state(); if (state.fingers.count(0) && (ev->event->touch_id == 0)) { process_input_motion(wf::get_core().get_touch_position(0)); } }; } void wf::hotspot_manager_t::update_hotspots(const container_t& activators) { hotspots.clear(); for (const auto& opt : activators) { auto opt_hotspots = opt->activated_by->get_value().get_hotspots(); for (const auto& hs : opt_hotspots) { auto activator_cb = opt->callback; auto callback = [activator_cb] (uint32_t edges) { wf::activator_data_t data = { .source = activator_source_t::HOTSPOT, .activation_data = edges, }; (*activator_cb)(data); }; auto instance = std::make_unique(hs.get_edges(), hs.get_size_along_edge(), hs.get_size_away_from_edge(), hs.get_timeout(), callback); hotspots.push_back(std::move(instance)); } } } wayfire-0.10.0/src/core/seat/input-manager.hpp0000664000175000017500000000466515053502647021077 0ustar dkondordkondor#ifndef INPUT_MANAGER_HPP #define INPUT_MANAGER_HPP #include #include #include "seat-impl.hpp" #include "wayfire/signal-provider.hpp" #include "wayfire/core.hpp" #include "wayfire/signal-definitions.hpp" #include namespace wf { /** * input_manager is a class which manages high-level input state: * 1. Active grabs * 2. Exclusive clients * 3. Available input devices */ class input_manager_t { private: wf::wl_listener_wrapper input_device_created; wf::wl_idle_call idle_update_cursor; wf::signal::connection_t config_updated; wf::signal::connection_t output_added; public: /** * Locked mods are stored globally because the keyboard devices might be * destroyed and created again by wlroots. */ uint32_t locked_mods = 0; /** * Map a single input device to output as specified in the * config file or by hints in the wlroots backend. */ void configure_input_device(std::unique_ptr & device); /** * Go through all input devices and map them to outputs as specified in the * config file or by hints in the wlroots backend. */ void configure_input_devices(); input_manager_t(); ~input_manager_t() = default; /** Initialize a new input device */ void handle_new_input(wlr_input_device *dev); /** Destroy an input device */ void handle_input_destroyed(wlr_input_device *dev); wl_client *exclusive_client = NULL; /** * Set the exclusive client. * Only it can get pointer focus from now on. * Exceptions are allowed for special views like OSKs. */ void set_exclusive_focus(wl_client *client); std::vector> input_devices; }; } /** * Emit a signal for device events. */ template wf::input_event_processing_mode_t emit_device_event_signal(EventType *event, wlr_input_device *device) { wf::input_event_signal data; data.event = event; data.device = device; wf::get_core().emit(&data); return data.mode; } template void emit_device_post_event_signal(EventType *event, wlr_input_device *device) { wf::post_input_event_signal data; data.event = event; data.device = device; wf::get_core().emit(&data); } #endif /* end of include guard: INPUT_MANAGER_HPP */ wayfire-0.10.0/src/core/seat/touch.hpp0000664000175000017500000000520615053502647017442 0ustar dkondordkondor#ifndef TOUCH_HPP #define TOUCH_HPP #include #include #include "wayfire/scene-input.hpp" #include "wayfire/util.hpp" #include // TODO: tests namespace wf { using input_surface_selector_t = std::function; /** * Responsible for managing touch gestures and forwarding events to clients. */ class touch_interface_t { public: touch_interface_t(wlr_cursor *cursor, wlr_seat *seat, input_surface_selector_t surface_at); ~touch_interface_t(); touch_interface_t(const touch_interface_t &) = delete; touch_interface_t(touch_interface_t &&) = delete; touch_interface_t& operator =(const touch_interface_t&) = delete; touch_interface_t& operator =(touch_interface_t&&) = delete; /** Get the positions of the fingers */ const touch::gesture_state_t& get_state() const; /** Get the focused surface */ wf::scene::node_ptr get_focus(int finger_id = 0) const; /** * Register a new touchscreen gesture. */ void add_touch_gesture(nonstd::observer_ptr gesture); /** * Unregister a touchscreen gesture. */ void rem_touch_gesture(nonstd::observer_ptr gesture); /** * Transfer input focus to the given grab. */ void transfer_grab(scene::node_ptr grab_node); private: wlr_seat *seat; wlr_cursor *cursor; input_surface_selector_t surface_at; wf::wl_listener_wrapper on_down, on_up, on_motion, on_cancel, on_frame; void handle_touch_down(int32_t id, uint32_t time, wf::pointf_t current, input_event_processing_mode_t mode); void handle_touch_motion(int32_t id, uint32_t time, wf::pointf_t current, bool real_event, input_event_processing_mode_t mode); void handle_touch_up(int32_t id, uint32_t time, input_event_processing_mode_t mode); void set_touch_focus(wf::scene::node_ptr node, int32_t id, int64_t time, wf::pointf_t current); touch::gesture_state_t finger_state; /** Pressed a finger on a surface and dragging outside of it now */ std::map focus; void update_gestures(const wf::touch::gesture_event_t& event); std::vector> gestures; wf::signal::connection_t on_root_node_updated; std::unique_ptr multiswipe, edgeswipe, multipinch; void add_default_gestures(); /** Enable/disable cursor depending on how many touch points are there */ void update_cursor_state(); }; } #endif /* end of include guard: TOUCH_HPP */ wayfire-0.10.0/src/core/seat/hotspot-manager.hpp0000664000175000017500000000503315053502647021426 0ustar dkondordkondor#pragma once #include #include "wayfire/util.hpp" #include #include #include #include #include #include namespace wf { /** * Represents a binding with a plugin-provided callback and activation option. */ template struct binding_t { wf::option_sptr_t