Skip to content

Commit

Permalink
Wayland server side decorations (#3425)
Browse files Browse the repository at this point in the history
Closes #3371
  • Loading branch information
AlanGriffiths authored Jul 2, 2024
2 parents 5f8b842 + faa1e16 commit 6f465ad
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 36 deletions.
1 change: 1 addition & 0 deletions src/server/frontend_wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ set(
text_input_v1.cpp text_input_v1.h
primary_selection_v1.cpp primary_selection_v1.h
session_lock_v1.cpp session_lock_v1.h
xdg_decoration_unstable_v1.cpp xdg_decoration_unstable_v1.h
${PROJECT_SOURCE_DIR}/src/include/server/mir/frontend/wayland.h
${CMAKE_CURRENT_BINARY_DIR}/wayland_frontend.tp.c
${CMAKE_CURRENT_BINARY_DIR}/wayland_frontend.tp.h
Expand Down
6 changes: 6 additions & 0 deletions src/server/frontend_wayland/wayland_default_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
#include "wl_seat.h"
#include "wl_shell.h"
#include "wlr_screencopy_v1.h"
#include "xdg-decoration-unstable-v1_wrapper.h"
#include "xdg_decoration_unstable_v1.h"
#include "xdg_output_v1.h"
#include "xdg_shell_stable.h"
#include "xdg_shell_v6.h"
Expand Down Expand Up @@ -214,6 +216,10 @@ std::vector<ExtensionBuilder> const internal_extension_builders = {
{
return mf::create_mir_shell_v1(ctx.display);
}),
make_extension_builder<mw::XdgDecorationManagerV1>([](auto const& ctx)
{
return mf::create_xdg_decoration_unstable_v1(ctx.display);
})
};

ExtensionBuilder const xwayland_builder {
Expand Down
198 changes: 198 additions & 0 deletions src/server/frontend_wayland/xdg_decoration_unstable_v1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* 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_decoration_unstable_v1.h"

#include "mir/log.h"
#include "mir/shell/surface_specification.h"
#include "mir/wayland/client.h"
#include "mir/wayland/protocol_error.h"

#include "xdg-decoration-unstable-v1_wrapper.h"
#include "xdg_output_v1.h"
#include "xdg_shell_stable.h"

#include <memory>
#include <unordered_set>

namespace mir
{
namespace frontend
{
class ToplevelsWithDecorations
{
public:
ToplevelsWithDecorations() = default;
ToplevelsWithDecorations(ToplevelsWithDecorations const&) = delete;
ToplevelsWithDecorations& operator=(ToplevelsWithDecorations const&) = delete;

/// \return true if no duplicates existed before insertion, false otherwise.
bool register_toplevel(wl_resource* toplevel)
{
auto [_, inserted] = toplevels_with_decorations.insert(toplevel);
return inserted;
}

/// \return true if the toplevel was still registered, false otherwise.
bool unregister_toplevel(wl_resource* toplevel)
{
return toplevels_with_decorations.erase(toplevel) > 0;
}

private:
std::unordered_set<wl_resource*> toplevels_with_decorations;
};

class XdgDecorationManagerV1 : public wayland::XdgDecorationManagerV1
{
public:
XdgDecorationManagerV1(wl_resource* resource);

class Global : public wayland::XdgDecorationManagerV1::Global
{
public:
Global(wl_display* display);

private:
void bind(wl_resource* new_zxdg_decoration_manager_v1) override;
};

private:
void get_toplevel_decoration(wl_resource* id, wl_resource* toplevel) override;
std::shared_ptr<ToplevelsWithDecorations> const toplevels_with_decorations;
};

class XdgToplevelDecorationV1 : public wayland::XdgToplevelDecorationV1
{
public:
XdgToplevelDecorationV1(wl_resource* id, mir::frontend::XdgToplevelStable* toplevel);

void set_mode(uint32_t mode) override;
void unset_mode() override;

private:
void update_mode(uint32_t new_mode);

static uint32_t const default_mode = Mode::client_side;

mir::frontend::XdgToplevelStable* toplevel;
uint32_t mode;
};
} // namespace frontend
} // namespace mir

auto mir::frontend::create_xdg_decoration_unstable_v1(wl_display* display)
-> std::shared_ptr<mir::wayland::XdgDecorationManagerV1::Global>
{
return std::make_shared<XdgDecorationManagerV1::Global>(display);
}

mir::frontend::XdgDecorationManagerV1::Global::Global(wl_display* display) :
wayland::XdgDecorationManagerV1::Global::Global{display, Version<1>{}}
{
}

void mir::frontend::XdgDecorationManagerV1::Global::bind(wl_resource* new_zxdg_decoration_manager_v1)
{
new XdgDecorationManagerV1{new_zxdg_decoration_manager_v1};
}

mir::frontend::XdgDecorationManagerV1::XdgDecorationManagerV1(wl_resource* resource) :
mir::wayland::XdgDecorationManagerV1{resource, Version<1>{}},
toplevels_with_decorations{std::make_shared<ToplevelsWithDecorations>()}
{
}

void mir::frontend::XdgDecorationManagerV1::get_toplevel_decoration(wl_resource* id, wl_resource* toplevel)
{
using Error = mir::frontend::XdgToplevelDecorationV1::Error;

auto* tl = mir::frontend::XdgToplevelStable::from(toplevel);
if (!tl)
{
BOOST_THROW_EXCEPTION(std::runtime_error("Invalid toplevel pointer"));
}

auto decoration = new XdgToplevelDecorationV1{id, tl};
if (!toplevels_with_decorations->register_toplevel(toplevel))
{
BOOST_THROW_EXCEPTION(mir::wayland::ProtocolError(
resource, Error::already_constructed, "Decoration already constructed for this toplevel"));
}

decoration->add_destroy_listener(
[toplevels_with_decorations = this->toplevels_with_decorations, toplevel]()
{
toplevels_with_decorations->unregister_toplevel(toplevel);
});

tl->add_destroy_listener(
[toplevels_with_decorations = this->toplevels_with_decorations, client = this->client, toplevel]()
{
// Under normal conditions, decorations should be destroyed before
// toplevels. Causing `unregister_toplevel` to return false.
//
// If the attached decoration is not destroyed before its toplevel,
// then its a protocol error. This can happen in two cases: A
// protocol violation caused by the client, or another error
// triggering wayland cleanup code which destroys wayland objects
// with no guaranteed order.
const auto orphaned_decoration = toplevels_with_decorations->unregister_toplevel(toplevel);
if (!client->is_being_destroyed() && orphaned_decoration)
{
mir::log_warning("Toplevel destroyed before attached decoration!");
// https://github.com/canonical/mir/issues/3452
/* BOOST_THROW_EXCEPTION(mir::wayland::ProtocolError( */
/* resource, Error::orphaned, "Toplevel destroyed before its attached decoration")); */
}
});
}

mir::frontend::XdgToplevelDecorationV1::XdgToplevelDecorationV1(
wl_resource* id, mir::frontend::XdgToplevelStable* toplevel) :
wayland::XdgToplevelDecorationV1{id, Version<1>{}},
toplevel{toplevel}
{
}

void mir::frontend::XdgToplevelDecorationV1::update_mode(uint32_t new_mode)
{
auto spec = shell::SurfaceSpecification{};

mode = new_mode;
switch (mode)
{
case Mode::client_side:
spec.server_side_decorated = false;
break;
case Mode::server_side:
spec.server_side_decorated = true;
break;
}

this->toplevel->apply_spec(spec);
send_configure_event(mode);
}

void mir::frontend::XdgToplevelDecorationV1::set_mode(uint32_t mode)
{
update_mode(mode);
}

void mir::frontend::XdgToplevelDecorationV1::unset_mode()
{
update_mode(default_mode);
}
30 changes: 30 additions & 0 deletions src/server/frontend_wayland/xdg_decoration_unstable_v1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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/>.
*/

#ifndef MIR_FRONTEND_XDG_DECORATION_UNSTABLE_V1_H
#define MIR_FRONTEND_XDG_DECORATION_UNSTABLE_V1_H

#include "xdg-decoration-unstable-v1_wrapper.h"

namespace mir
{
namespace frontend
{
auto create_xdg_decoration_unstable_v1(wl_display* display) -> std::shared_ptr<wayland::XdgDecorationManagerV1::Global>;
}
} // namespace mir

#endif // MIR_FRONTEND_RELATIVE_POINTER_UNSTABLE_V1_H
36 changes: 0 additions & 36 deletions src/server/frontend_wayland/xdg_shell_stable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,42 +73,6 @@ class XdgSurfaceStable : public mw::XdgSurface
XdgShellStable const& xdg_shell;
};

class XdgToplevelStable : mw::XdgToplevel, public WindowWlSurfaceRole
{
public:
XdgToplevelStable(
wl_resource* new_resource,
XdgSurfaceStable* xdg_surface,
WlSurface* surface);

void set_parent(std::optional<struct wl_resource*> const& parent) override;
void set_title(std::string const& title) override;
void set_app_id(std::string const& app_id) override;
void show_window_menu(struct wl_resource* seat, uint32_t serial, int32_t x, int32_t y) override;
void move(struct wl_resource* seat, uint32_t serial) override;
void resize(struct wl_resource* seat, uint32_t serial, uint32_t edges) override;
void set_max_size(int32_t width, int32_t height) override;
void set_min_size(int32_t width, int32_t height) override;
void set_maximized() override;
void unset_maximized() override;
void set_fullscreen(std::optional<struct wl_resource*> const& output) override;
void unset_fullscreen() override;
void set_minimized() override;

void handle_commit() override {};
void handle_state_change(MirWindowState /*new_state*/) override;
void handle_active_change(bool /*is_now_active*/) override;
void handle_resize(std::optional<geometry::Point> const& new_top_left, geometry::Size const& new_size) override;
void handle_close_request() override;

private:
static XdgToplevelStable* from(wl_resource* surface);
void send_toplevel_configure();
void destroy_role() const override;

mw::Weak<XdgSurfaceStable> const xdg_surface;
};

class XdgPositionerStable : public mw::XdgPositioner, public shell::SurfaceSpecification
{
public:
Expand Down
36 changes: 36 additions & 0 deletions src/server/frontend_wayland/xdg_shell_stable.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,42 @@ class XdgPopupStable : public wayland::XdgPopup, public WindowWlSurfaceRole
auto popup_rect() const -> std::optional<geometry::Rectangle>;
};

class XdgToplevelStable : mir::wayland::XdgToplevel, public WindowWlSurfaceRole
{
public:
XdgToplevelStable(
wl_resource* new_resource,
XdgSurfaceStable* xdg_surface,
WlSurface* surface);

void set_parent(std::optional<struct wl_resource*> const& parent) override;
void set_title(std::string const& title) override;
void set_app_id(std::string const& app_id) override;
void show_window_menu(struct wl_resource* seat, uint32_t serial, int32_t x, int32_t y) override;
void move(struct wl_resource* seat, uint32_t serial) override;
void resize(struct wl_resource* seat, uint32_t serial, uint32_t edges) override;
void set_max_size(int32_t width, int32_t height) override;
void set_min_size(int32_t width, int32_t height) override;
void set_maximized() override;
void unset_maximized() override;
void set_fullscreen(std::optional<struct wl_resource*> const& output) override;
void unset_fullscreen() override;
void set_minimized() override;

void handle_commit() override {};
void handle_state_change(MirWindowState /*new_state*/) override;
void handle_active_change(bool /*is_now_active*/) override;
void handle_resize(std::optional<geometry::Point> const& new_top_left, geometry::Size const& new_size) override;
void handle_close_request() override;

static XdgToplevelStable* from(wl_resource* surface);

private:
void send_toplevel_configure();
void destroy_role() const override;

mir::wayland::Weak<XdgSurfaceStable> const xdg_surface;
};
}
}

Expand Down
1 change: 1 addition & 0 deletions src/wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ mir_generate_protocol_wrapper(mirwayland "z" wlr-screencopy-unstable-v1.xml)
mir_generate_protocol_wrapper(mirwayland "zwlr_" wlr-virtual-pointer-unstable-v1.xml)
mir_generate_protocol_wrapper(mirwayland "ext_" ext-session-lock-v1.xml)
mir_generate_protocol_wrapper(mirwayland "zmir_" mir-shell-unstable-v1.xml)
mir_generate_protocol_wrapper(mirwayland "z" xdg-decoration-unstable-v1.xml)

target_link_libraries(mirwayland
PUBLIC
Expand Down
4 changes: 4 additions & 0 deletions tests/acceptance-tests/wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ set(EXPECTED_FAILURES

# Not yet implemented, see https://github.com/MirServer/mir/issues/2861
XdgPopupTest.when_parent_surface_is_moved_a_reactive_popup_is_moved

# Should report an `orphaned` protocol error/violation , but we log instead
# because of https://github.com/canonical/mir/issues/3452
XdgDecorationV1Test.destroying_toplevel_before_decoration_throws_orphaned
)

if (MIR_SIGBUS_HANDLER_ENVIRONMENT_BROKEN)
Expand Down
Loading

0 comments on commit 6f465ad

Please sign in to comment.