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

Add APIs to override decoration managers and customize decorations (Take 2) #3666

Draft
wants to merge 45 commits into
base: main
Choose a base branch
from

Conversation

tarek-y-ismail
Copy link
Contributor

@tarek-y-ismail tarek-y-ismail commented Nov 12, 2024

Attempt 1: Naive. Trying to expose an internal (an unstable) mir API to users.
Attempt 2: Bottom up. Trying to bring out the API from msd::BasicDecoration and expose it via adapters.
Attempt 3 (we are here): Top down. Trying to extract the minimal API needed for users to implement at least something similar to msd::BasicDecoration (The code is nasty on purpose)

Copy link
Collaborator

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposing the interface for review without "dragging implementation code along" is good.

However, I would rather have focused first on separating out the "policy" part of decorations as otherwise the miral::*Decoration* interface is somewhat speculative. (In particular, there's no way to "hook up" input in the current draft.)

examples/miral-shell/decoration/CMakeLists.txt Outdated Show resolved Hide resolved
include/miral/miral/decoration_manager_builder.h Outdated Show resolved Hide resolved
src/include/server/mir/default_server_configuration.h Outdated Show resolved Hide resolved
src/miral/custom_decorations.cpp Outdated Show resolved Hide resolved
src/miral/custom_decorations.cpp Outdated Show resolved Hide resolved
src/server/shell/default_configuration.cpp Outdated Show resolved Hide resolved
src/server/symbols.map Outdated Show resolved Hide resolved
examples/miral-shell/shell_main.cpp Outdated Show resolved Hide resolved
examples/miral-shell/shell_main.cpp Outdated Show resolved Hide resolved
include/miral/miral/decoration_manager_builder.h Outdated Show resolved Hide resolved
Copy link
Collaborator

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, you said this was "ready to review", but I now see it change whilst reviewing. I'll leave it for now and just post what I've seen so far

examples/miral-shell/decoration/CMakeLists.txt Outdated Show resolved Hide resolved
@@ -14,6 +14,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "decoration/decoration.h"
#include "miral/decoration.h"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "miral/decoration.h"

Comment on lines +43 to +44
#include <string>
#include <vector>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <string>
#include <vector>

examples/miral-shell/shell_main.cpp Outdated Show resolved Hide resolved
include/miral/miral/decoration_manager_builder.h Outdated Show resolved Hide resolved
@tarek-y-ismail tarek-y-ismail force-pushed the redo-custom-server-side-decorations branch from bad7442 to 8786b40 Compare November 21, 2024 15:16
tarek-y-ismail and others added 23 commits November 21, 2024 17:17
This should guarantee that the lifetime is extended since `self` is a
shared ptr
The implementation is now called `UserDecoration` and is implemented as
an external class in miral-shell. The interface does book keeping of the
necessary data members.
Respects floating/maximized/minimized states.
Should make it easier and less messy to edit things, a lot more readable
too
Should give a bit more context than just
`miral::{WindowState,StaticGeometry}`
Removed the usage of the pimpl pattern as well since it's internal
anyway.
`miral::decoration`

And move some renderer specific variables into `Renderer` instead of the
outer namespace.
Took the chance to revert some shared -> unique ptr changes done
earlier, as well as fixing some lifetime issues.
@tarek-y-ismail tarek-y-ismail force-pushed the redo-custom-server-side-decorations branch from 8786b40 to 6d461ef Compare November 21, 2024 15:18
@tarek-y-ismail tarek-y-ismail force-pushed the redo-custom-server-side-decorations branch from 6d461ef to 2613f31 Compare November 22, 2024 07:49
Comment on lines +40 to +46
for (auto const& widget : widgets)
{
if (widget->button)
button_rects.push_back(widget);
}

return button_rects;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would've been nice to do this using the ranges API but it seems not ready yet...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"not ready yet"? There's not much code using ranges in our codebase, but we have used it

Copy link
Contributor Author

@tarek-y-ismail tarek-y-ismail Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I'm who's not ready. I spent about 20 minutes trying to filter and collect but couldn't figure it out.

Something like:

widgets 
    | std::ranges::views::filter([](auto& widget){ return widget->button; })
    | std::ranges::to<std::vector<Widget const>>();

But it always complained about piping widgets into std::ranges::views::filter, I tried looking at cppreference, but couldn't figure it out.

Copy link
Collaborator

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that "expose everything so that a consumer-side implementation works, then try to hide the stuff that shouldn't be exposed" is a long route to the destination.

First separating the stuff that should be exposed and then exposing it would be easier

Comment on lines 93 to 121
struct InputContext
{
InputContext(
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<ms::Session> const& session,
std::shared_ptr<ms::Surface> const& window_surface,
std::shared_ptr<MirEvent const> const& latest_event,
std::shared_ptr<mir::input::CursorImages> const& cursor_images,
std::shared_ptr<ms::Surface> const& decoration_surface);

void request_move() const;

void request_resize(MirResizeEdge edge) const;

void set_cursor(std::string const& cursor_image_name) const;

void request_close() const;

void request_toggle_maximize() const;

void request_minimize() const;
private:
std::shared_ptr<msh::Shell> const shell;
std::shared_ptr<ms::Session> const session;
std::shared_ptr<ms::Surface> const window_surface;
std::shared_ptr<const MirEvent> const latest_event;
std::shared_ptr<mir::input::CursorImages> const cursor_images;
std::shared_ptr<ms::Surface> const decoration_surface;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good abstraction to have in the miral API: if we change the implementation then there's an ABI break.

The API should be more like:

Suggested change
struct InputContext
{
InputContext(
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<ms::Session> const& session,
std::shared_ptr<ms::Surface> const& window_surface,
std::shared_ptr<MirEvent const> const& latest_event,
std::shared_ptr<mir::input::CursorImages> const& cursor_images,
std::shared_ptr<ms::Surface> const& decoration_surface);
void request_move() const;
void request_resize(MirResizeEdge edge) const;
void set_cursor(std::string const& cursor_image_name) const;
void request_close() const;
void request_toggle_maximize() const;
void request_minimize() const;
private:
std::shared_ptr<msh::Shell> const shell;
std::shared_ptr<ms::Session> const session;
std::shared_ptr<ms::Surface> const window_surface;
std::shared_ptr<const MirEvent> const latest_event;
std::shared_ptr<mir::input::CursorImages> const cursor_images;
std::shared_ptr<ms::Surface> const decoration_surface;
};
struct InputContext
{
void request_move() const;
void request_resize(MirResizeEdge edge) const;
void set_cursor(std::string const& cursor_image_name) const;
void request_close() const;
void request_toggle_maximize() const;
void request_minimize() const;
private:
// TODO there needs to be a constructor usable by the API implementation
struct Self;
std::shared_ptr<Self> self;
};

Comment on lines +129 to +133
using Pixel = uint32_t;

// Can we use even more keywords?
static inline auto const buffer_format = mir_pixel_format_argb_8888;
static inline auto const bytes_per_pixel = 4;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we are only supporting argb-8888 s/w rendering? Might the user want more choice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know this is the most flexible set up at the moment. It wastes memory if you don't need transparency and performance if you want to draw using the GPU, but those are concessions I'm willing to make while I'm experimenting around

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these restrictions were part of the example, I would be fine with it. If we're telling shell developers "this is the only way" then I'm not. Our objective is an API we are willing to support indefinitely, that's very different to "while I'm experimenting around".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How flexible do we want this to be? If we stick to software rendering, we can allow the user to determine the pixel format, and that would modify buffer allocation and pixel access (for render_row, draw_rect, and any other functions that we might want to add in the future).

If we want things to be even more generic (allowing GPU rendering), then I'm a bit lost.

I think sticking to software rendering at the moment would be a good idea since it still allows for GPU rendering with a bit off inefficiency (render and copy to CPU)


namespace miral
{
namespace decoration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not convinced a namespace is appropriate. (I would probably nest auxiliary types in CustomDecoration.

Comment on lines 51 to 52
auto location() const -> mir::geometry::Point;
auto pressed() const -> bool;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think shell authors may want more information:

  • what modifier keys are pressed
  • touch count, and individual touches
  • which mouse buttons are pressed

include/miral/miral/decoration.h Show resolved Hide resolved
std::function<void()> on_redraw;
};

class Decoration // Placeholder names
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Placeholder names"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more of a note to myself. When I was getting the code to work at the start, I wrote down the first name that came to mind without giving it much thought. So I think giving that some time once everything settles might be good

Comment on lines +62 to +67
namespace msh = mir::shell;
namespace msd = msh::decoration;
namespace ms = mir::scene;
namespace mc = mir::compositor;
namespace mg = mir::graphics;
namespace geometry = mir::geometry;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulling far too much implementation detail into the API

Comment on lines +241 to +247
void init(
std::shared_ptr<ms::Surface> const& window_surface,
std::shared_ptr<ms::Surface> const& decoration_surface,
std::shared_ptr<mg::GraphicBufferAllocator> const& buffer_allocator,
std::shared_ptr<msh::Shell> const& shell,
std::shared_ptr<mir::input::CursorImages> const& cursor_images
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used by the example code, and looks like an implementation detail being leaked.


void set_custom_geometry(std::shared_ptr<miral::decoration::StaticGeometry> geometry);

auto to_decoration() -> std::unique_ptr<msd::Decoration>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used by the example code, and looks like an implementation detail being leaked

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_custom_decoration is, to_decoration isn't.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that to_decoration() was what I was commenting on

Comment on lines +209 to +235
DecorationAdapter(
std::shared_ptr<DecorationRedrawNotifier> const& redraw_notifier,

// Called by user code in response to input events
Renderer::RenderingCallback render_titlebar,
Renderer::RenderingCallback render_left_border,
Renderer::RenderingCallback render_right_border,
Renderer::RenderingCallback render_bottom_border,

// Called by Mir to inform user code of input events
OnProcessEnter process_enter,
OnProcessLeave process_leave,
OnProcessDown process_down,
OnProcessUp process_up,
OnProcessMove process_move,
OnProcessDrag process_drag,

// Called by Mir to inform user code of changes to the window being decorated
OnWindowAttribChanged attrib_changed,
OnWindowResized window_resized_to,
OnWindowRenamed window_renamed,

// Called by Mir to inform user code that the display scale changed
OnScaleChanged scale_changed,

// Called whenever the internal WindowState is updated
OnWindowStateUpdated update_decoration_window_state);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A long list of parameters like this is not maintainable: C.f. discussion about using builders to help

return std::nullopt;
}

void UserDecoration::InputManager::process_drag(miral::DeviceEvent& device, InputContext ctx)
Copy link
Contributor Author

@tarek-y-ismail tarek-y-ismail Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the context could be integrated into the input manager? Would require some sort of interface between the internal machinery and the custom decoration to initialize the input manager. Maybe passing the decoration manager to DecorationAdapter during construction?

(low priority, the main reason behind this suggestion is that most meaningful actions require a context, but passing it isn't bad as well)

}
decoration->redraw_notifier()->notify();
},
[decoration](auto...)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (window_resized_to) might not be very useful for the user as we already take care of everything related to resizing for them.

static auto create_manager(mir::Server&)
-> std::shared_ptr<miral::DecorationManagerAdapter>;

struct InputManager
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the definition of the input manager here is unnecessary

{
public:
DeviceEvent(mir::shell::decoration::DeviceEvent);
operator mir::shell::decoration::DeviceEvent() const;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary, we get inputs as msd::DeviceEvents and only need to convert them to miral::DeviceEvent


private:
struct Impl;
std::shared_ptr<Impl> impl;
Copy link
Contributor Author

@tarek-y-ismail tarek-y-ismail Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use unique_ptr?

Edit: The reason I'm not already using unique_ptr is because it has some issues compiling with incomplete classes. Googling for a bit shows that the spec allows it, but I haven't looked deep enough to make it work.

Comment on lines 83 to 100
void md::InputContext::request_close() const{
window_surface->request_client_surface_close();
}

void md::InputContext::request_toggle_maximize() const{
msh::SurfaceSpecification spec;
if (window_surface->state() == mir_window_state_maximized)
spec.state = mir_window_state_restored;
else
spec.state = mir_window_state_maximized;
shell->modify_surface(session, window_surface, spec);
}

void md::InputContext::request_minimize() const{
msh::SurfaceSpecification spec;
spec.state = mir_window_state_minimized;
shell->modify_surface(session, window_surface, spec);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs formatting

md::OnWindowAttribChanged attrib_changed,
md::OnWindowResized window_resized_to,
md::OnWindowRenamed window_renamed) :
on_attrib_changed{attrib_changed},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use braces to match the style of the rest of the code

Comment on lines +693 to +715
auto window_spec = [this]
{
msh::SurfaceSpecification spec;

window_surface->set_window_margins(
as_delta(window_state->titlebar_height()), geom::DeltaX{}, geom::DeltaY{}, geom::DeltaX{});

if (window_state->window_size().width.as_value())
spec.width = window_state->window_size().width;
if (window_state->window_size().height.as_value())
spec.height = window_state->window_size().height;

// Could probably be `decoration->get_input_shape(window_state)`.
// This will do for now.
spec.input_shape = {
window_state->titlebar_rect(),
window_state->bottom_border_rect(),
window_state->left_border_rect(),
window_state->right_border_rect(),
};

return spec;
}();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be moved closer to the place of usage

void undecorate(std::shared_ptr<mir::scene::Surface> const& surface) override;
void undecorate_all() override;

virtual ~DecorationManagerAdapter();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember why I added this virtual keyword. This class isn't meant to be inherited from.

Comment on lines +104 to +105
// TODO: probably only frees one pointer, but not the whole set of things
// pointing at the decoration.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old comment from when this was a shared_ptr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants