Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: implementation of xdg_activation_v1 because I want my auto focuses #3639

Merged
merged 8 commits into from
Oct 30, 2024
1 change: 1 addition & 0 deletions src/server/frontend_wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ set(
${PROJECT_SOURCE_DIR}/src/include/server/mir/frontend/buffer_stream.h
wp_viewporter.cpp wp_viewporter.h
fractional_scale_v1.cpp fractional_scale_v1.h
xdg_activation_v1.cpp xdg_activation_v1.h
)

add_custom_command(
Expand Down
6 changes: 4 additions & 2 deletions src/server/frontend_wayland/wayland_connector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ mf::WaylandConnector::WaylandConnector(
WaylandProtocolExtensionFilter const& extension_filter,
bool enable_key_repeat,
std::shared_ptr<scene::SessionLock> const& session_lock,
std::shared_ptr<mir::DecorationStrategy> const& decoration_strategy)
std::shared_ptr<mir::DecorationStrategy> const& decoration_strategy,
std::shared_ptr<shell::FocusController> const& focus_controller)
: extension_filter{extension_filter},
display{wl_display_create(), &cleanup_display},
pause_signal{eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE)},
Expand Down Expand Up @@ -328,7 +329,8 @@ mf::WaylandConnector::WaylandConnector(
main_loop,
desktop_file_manager,
session_lock_,
decoration_strategy});
decoration_strategy,
focus_controller});

shm_global = std::make_unique<WlShm>(display.get(), executor);

Expand Down
5 changes: 4 additions & 1 deletion src/server/frontend_wayland/wayland_connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class DisplayConfigurationObserver;
namespace shell
{
class Shell;
class FocusController;
}
namespace scene
{
Expand Down Expand Up @@ -113,6 +114,7 @@ class WaylandExtensions
std::shared_ptr<DesktopFileManager> desktop_file_manager;
std::shared_ptr<scene::SessionLock> session_lock;
std::shared_ptr<mir::DecorationStrategy> decoration_strategy;
std::shared_ptr<shell::FocusController> focus_controller;
};

WaylandExtensions() = default;
Expand Down Expand Up @@ -165,7 +167,8 @@ class WaylandConnector : public Connector
WaylandProtocolExtensionFilter const& extension_filter,
bool enable_key_repeat,
std::shared_ptr<scene::SessionLock> const& session_lock,
std::shared_ptr<DecorationStrategy> const& decoration_strategy);
std::shared_ptr<DecorationStrategy> const& decoration_strategy,
std::shared_ptr<shell::FocusController> const& focus_controller);

~WaylandConnector() override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "wl_seat.h"
#include "wl_shell.h"
#include "wlr_screencopy_v1.h"
#include "xdg_activation_v1.h"
#include "xdg-decoration-unstable-v1_wrapper.h"
#include "xdg_decoration_unstable_v1.h"
#include "xdg_output_v1.h"
Expand Down Expand Up @@ -226,6 +227,10 @@ std::vector<ExtensionBuilder> const internal_extension_builders = {
{
return mf::create_fractional_scale_v1(ctx.display);
}),
make_extension_builder<mw::XdgActivationV1>([](auto const& ctx)
{
return mf::create_xdg_activation_v1(ctx.display, ctx.shell, ctx.focus_controller, ctx.main_loop);
}),
};

ExtensionBuilder const xwayland_builder {
Expand Down Expand Up @@ -384,7 +389,8 @@ std::shared_ptr<mf::Connector>
wayland_extension_filter,
enable_repeat,
the_session_lock(),
the_decoration_strategy());
the_decoration_strategy(),
the_focus_controller());
});
}

Expand Down
312 changes: 312 additions & 0 deletions src/server/frontend_wayland/xdg_activation_v1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
/*
* Copyright © Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "xdg_activation_v1.h"
#include "wl_surface.h"
#include "wl_seat.h"
#include "mir/main_loop.h"
#include "mir/scene/surface.h"
#include "mir/shell/shell.h"
#include "mir/shell/focus_controller.h"
#include "mir/log.h"
#include <random>
#include <chrono>

namespace mf = mir::frontend;
namespace mw = mir::wayland;
namespace msh = mir::shell;

namespace mir::frontend
{
mattkae marked this conversation as resolved.
Show resolved Hide resolved
/// Time in milliseconds that the compositor will wait before invalidating a token
auto constexpr TIMEOUT_MS = std::chrono::milliseconds(3000);
mattkae marked this conversation as resolved.
Show resolved Hide resolved

std::string generate_token()
{
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
std::random_device rd;
std::mt19937 generator(rd());
std::shuffle(str.begin(), str.end(), generator);
return str.substr(0, 32);
}
mattkae marked this conversation as resolved.
Show resolved Hide resolved
mattkae marked this conversation as resolved.
Show resolved Hide resolved

class XdgActivationV1 : public wayland::XdgActivationV1::Global
{
public:
XdgActivationV1(
struct wl_display* display,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<msh::FocusController> const& focus_controller,
std::shared_ptr<MainLoop> const& main_loop);

std::string const& create_token();
bool try_consume_token(std::string const& token);

private:
class Instance : public wayland::XdgActivationV1
{
public:
Instance(
struct wl_resource* resource,
mf::XdgActivationV1* xdg_activation_v1,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<msh::FocusController> const& focus_controller,
std::shared_ptr<MainLoop> const& main_loop);

private:
void get_activation_token(struct wl_resource* id) override;
void activate(std::string const& token, struct wl_resource* surface) override;

mf::XdgActivationV1* xdg_activation_v1;
std::shared_ptr<msh::Shell> shell;
std::shared_ptr<msh::FocusController> const focus_controller;
std::shared_ptr<MainLoop> main_loop;
};

struct Token
{
std::string token;
std::unique_ptr<time::Alarm> alarm;
};

void bind(wl_resource* resource) override;

std::shared_ptr<msh::Shell> shell;
std::shared_ptr<msh::FocusController> const focus_controller;
std::shared_ptr<MainLoop> main_loop;
std::vector<Token> pending_tokens;
std::mutex lock;
mattkae marked this conversation as resolved.
Show resolved Hide resolved
};

class XdgActivationTokenV1 : public wayland::XdgActivationTokenV1
{
public:
XdgActivationTokenV1(
struct wl_resource* resource,
std::string const& token,
std::shared_ptr<msh::FocusController> const& focus_controller);

private:
void set_serial(uint32_t serial, struct wl_resource* seat) override;
void set_app_id(std::string const& app_id) override;
void set_surface(struct wl_resource* surface) override;
void commit() override;

std::string token;
std::shared_ptr<msh::FocusController> const focus_controller;
std::optional<uint32_t> serial;
std::optional<struct wl_resource*> seat;
std::optional<std::string> app_id;
std::optional<struct wl_resource*> requesting_surface;
};
}

auto mf::create_xdg_activation_v1(
struct wl_display* display,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<msh::FocusController> const& focus_controller,
std::shared_ptr<MainLoop> const& main_loop)
-> std::shared_ptr<mw::XdgActivationV1::Global>
{
return std::make_shared<mf::XdgActivationV1>(display, shell, focus_controller, main_loop);
}

mf::XdgActivationV1::XdgActivationV1(
struct wl_display* display,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<msh::FocusController> const& focus_controller,
std::shared_ptr<MainLoop> const& main_loop)
: Global(display, Version<1>()),
shell{shell},
focus_controller{focus_controller},
main_loop{main_loop}
{

}

std::string const& mf::XdgActivationV1::create_token()
{
auto generated = generate_token();
Token token{generated, main_loop->create_alarm([this, generated]()
{
try_consume_token(generated);
})};
token.alarm->reschedule_in(TIMEOUT_MS);

std::lock_guard guard(lock);
mattkae marked this conversation as resolved.
Show resolved Hide resolved
pending_tokens.emplace_back(std::move(token));
return pending_tokens[pending_tokens.size() - 1].token;
mattkae marked this conversation as resolved.
Show resolved Hide resolved
}

bool mf::XdgActivationV1::try_consume_token(std::string const& token)
{
std::lock_guard guard(lock);
for (auto it = pending_tokens.begin(); it != pending_tokens.end(); it++)
{
if (it->token == token)
{
pending_tokens.erase(it);
return true;
}
}

return false;
}

void mf::XdgActivationV1::bind(struct wl_resource* resource)
{
new Instance{resource, this, shell, focus_controller, main_loop};
}

mf::XdgActivationV1::Instance::Instance(
struct wl_resource* resource,
mf::XdgActivationV1* xdg_activation_v1,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<msh::FocusController> const& focus_controller,
std::shared_ptr<MainLoop> const& main_loop)
: XdgActivationV1(resource, Version<1>()),
xdg_activation_v1{xdg_activation_v1},
shell{shell},
focus_controller{focus_controller},
main_loop{main_loop}
{
}

void mf::XdgActivationV1::Instance::get_activation_token(struct wl_resource* id)
{
new XdgActivationTokenV1(id, xdg_activation_v1->create_token(), focus_controller);
}

void mf::XdgActivationV1::Instance::activate(std::string const& token, struct wl_resource* surface)
{
if (!xdg_activation_v1->try_consume_token(token))
{
mir::log_error("XdgActivationV1::activate invalid token: %s", token.c_str());
return;
}

main_loop->enqueue(this, [surface=surface, shell=shell]
{
auto const wl_surface = mf::WlSurface::from(surface);
if (!wl_surface)
{
mir::log_error("XdgActivationV1::activate wl_surface not found");
return;
}

auto const scene_surface_opt = wl_surface->scene_surface();
if (!scene_surface_opt)
{
mir::log_error("XdgActivationV1::activate scene_surface_opt not found");
return;
}

auto const& scene_surface = scene_surface_opt.value();
auto const now = std::chrono::steady_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds >(now).count();
shell->raise_surface(wl_surface->session, scene_surface, ns);
AlanGriffiths marked this conversation as resolved.
Show resolved Hide resolved
});
}

mf::XdgActivationTokenV1::XdgActivationTokenV1(
struct wl_resource* resource,
std::string const& token,
std::shared_ptr<msh::FocusController> const& focus_controller)
: wayland::XdgActivationTokenV1(resource, Version<1>()),
token{token},
focus_controller{focus_controller}
{
}

void mf::XdgActivationTokenV1::set_serial(uint32_t serial_, struct wl_resource* seat_)
{
serial = serial_;
seat = seat_;
}

void mf::XdgActivationTokenV1::set_app_id(std::string const& app_id_)
{
app_id = app_id_;
}

void mf::XdgActivationTokenV1::set_surface(struct wl_resource* surface)
{
requesting_surface = surface;
}

void mf::XdgActivationTokenV1::commit()
{
// In the event of a failure, we send 'done' with a new, invalid token
// which cannot be used later.
AlanGriffiths marked this conversation as resolved.
Show resolved Hide resolved

if (seat && serial)
{
// First, assert that the seat still exists.
auto const wl_seat = mf::WlSeat::from(seat.value());
if (!wl_seat)
{
mir::log_error("XdgActivationTokenV1::commit wl_seat not found");
send_done_event(generate_token());
return;
}

// TODO:
// Next, verify that the serial belongs to the seat
}

if (app_id)
{
// Check that the focused application has app_id
auto const& focused = focus_controller->focused_surface();
if (!focused)
{
mir::log_error("XdgActivationTokenV1::commit cannot find the focused surface");
send_done_event(generate_token());
return;
}

if (focused->application_id() != app_id)
{
mir::log_error("XdgActivationTokenV1::commit app_id != focused application id");
send_done_event(generate_token());
return;
}
}

if (requesting_surface)
{
// Check that the focused surface is the requesting_surface
auto const wl_surface = mf::WlSurface::from(requesting_surface.value());
auto const scene_surface_opt = wl_surface->scene_surface();
if (!scene_surface_opt)
{
mir::log_error("XdgActivationTokenV1::commit cannot find the provided scene surface");
send_done_event(generate_token());
return;
}

auto const& scene_surface = scene_surface_opt.value();

if (focus_controller->focused_surface() != scene_surface)
{
mir::log_error("XdgActivationTokenV1::commit the focused surface is not the surface that requesteda ctivation");
send_done_event(generate_token());
return;
}
}

send_done_event(token);
}
Loading
Loading