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

[WIP] Basic WM #14

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

Conversation

charafau
Copy link
Contributor

@charafau charafau commented May 31, 2022

Done:

  • Remove circular dependency between compositor and surface
  • Add equals methods to Surface class
  • Change single surface view to multiple views in demo project
  • Commented out focusing code in surface for now
  • Refocusing view from dart compositor
  • Add isPopUp variable to surface object
  • Actual floating
  • View resizing in demo wm

Todo:
Get actual surface size from wlroots view is actually resized when first appearing (SizeBox defines size) - we should probably check if that size or alright.

  • Fix pop up positions

Add equals methods to Surface class
Change single surface view to multiple views in demo project
Commented out focusing code in surface for now
Comment on lines -116 to +143
surfaces[surface.handle] = surface;
surfaces.putIfAbsent(surface.handle, () => surface);

surfaceMapped.add(surface);
});

platform.addHandler("surface_unmap", (call) async {
int handle = call.arguments["handle"];
Surface surface = surfaces[handle]!;
surfaces.remove(handle);
surfaceUnmapped.add(surface);
if (surfaces.containsKey(handle)) {
Surface surface = surfaces[handle]!;
surfaces.remove(handle);
surfaceUnmapped.add(surface);
}
Copy link
Member

Choose a reason for hiding this comment

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

For any given surface, I believe map and unmap should be only called strictly interspersed in a sequence:

map, unmap, map, unmap, ...

This should mean that:

  • When map is called, there should never be a surface of that handle in the surfaces map, we add it
  • When unmap is called, there should always be a surface of that handle in the surfaces, we remove it

We should likely add assertions for this, if this assumption is broken it would be a bug in the native code

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if the fact that things fail here on multiple surfaces means there is an error somewhere in my code where multiple surfaces get the same handle id? I think there are some debug prints for when a surface is mapped with its handle id, are those different?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I've checked, handles were different but I think it was the way we managed compositor - now it's created in and managed in main.

Surface({
required this.handle,
required this.pid,
required this.gid,
required this.uid,
required this.compositor,
Copy link
Member

Choose a reason for hiding this comment

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

Should we keep a reference to Compositor from Surfaces or not?

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 think it's not necassry. Compositor will be created in main, and probably main should keep track of it. Is there actually any use case where we might have more than 1 compositor ?

Copy link
Member

Choose a reason for hiding this comment

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

There will never be more than one compositor at a time in a given isolate, but the Compositor it belongs to is still part of the state conceptually in my mind.

At the very least it prevents us from having to pass it around everywhere along with the surface. Most times you have a Surface you need access to the compositor as well.

In my mind we have a few options:

  1. Pass the compositor object around alongside every surface. Might result in quite a bit of boilerplate as the dart side library expands in size.
  2. Just rely on the fact that there will only ever be one compositor object. Access it through a global everywhere. Not sure I like this, I like to keep state local even with singletons.
  3. Store the Compositor object within each surface. Only downside I can think of here is memory, but that's only a pointer.

Copy link
Contributor

Choose a reason for hiding this comment

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

what about providing the compositor using an InheritedWidget?
the app in order to use the compositor will need to use as root a Compositor widget of some sort which will then be used to create, maintain and dispose the compositor instance and also expose it to descendants using Compositor.of(context).
This way a Surface won't need to hold a compositor instance but instead only the SurfaceView will need to get it, using the of method indeed.
It would also be possible to detect if the app is running with the compositor or not because of Compositor.maybeOf(context) returning null if no compositor is available for example

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer not using the widget tree to pass around the compositor instance.

The main reason for this is that the compositor instance inherently has another lifetime compared to something in the widget tree.

It creates a platform channel and uses this to communicate with native code on the other side for the lifetime of the isolate. It also accumulates and maintains state for its whole lifetime, something conceptually incompatible with being maintained as part of the widget tree. It is analogous to the various Bindings in the flutter framework. (WidgetBinding for instance)

It's just inherently a singleton with a global lifetime, and I think we would save ourselves some pain and complexity in the long run by just treating it as such from the beginning.

That being said, whether we should pass it around as a part of the surface objects or access it as a global every time is still an open question for me. Having written this out and thought through it a bit more I'm leaning towards the global route.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair point then, i'm more leaning towards the global approach myself too

src/surface.c Outdated
// seat->keyboard_state.focused_surface);
// assert(previous->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
// wlr_xdg_toplevel_set_activated(previous->toplevel, false);
// }
Copy link
Member

Choose a reason for hiding this comment

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

Focus should definitely be managed by the dart side, but does setting more than one surface as focused violate a wlroots invariant?

In which case it seems like a good idea to keep guard code that makes sure the previous surface is unfocused when we attempt to set a new focus.

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 get exception from wlroots when I try to show second view and this if statement is here. Needs some fixes when we have proper focus management

Copy link
Member

Choose a reason for hiding this comment

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

Do you remember what sort of exception you get?

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 will check today and let you know

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry for late response. when I leave code as it was I get:

00:00:11.553 [../src/surface.c:74] viewhandle 2
simple: types/xdg_shell/wlr_xdg_toplevel.c:516: wlr_xdg_toplevel_set_activated: Assertion `surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL' failed.
00:00:11.553 [../src/flutter_wlroots.c:196] DART [flutter] handled call surface_map

Add changing focus for demo WM
@HrX03
Copy link
Contributor

HrX03 commented Jun 2, 2022

I wonder if it would be appropriate to introduce a proper event api in this pr or it would be better to have it as a different pr. If a different pr would be optimal then i could work on it once this pr gets eventually merged

@hansihe
Copy link
Member

hansihe commented Jun 2, 2022

How do you think the best way would be to make a proper event API?

@HrX03
Copy link
Contributor

HrX03 commented Jun 2, 2022

I already did an event api for utopia and i think we could lean towards that kind of architecture. Basically, you add a listener of sorts to the event source which is nothing more than a callback method that gets called each time any event happens. It has one single params which is a subclass of an abstract Event class that represents the kind of event that was generated. You just have to do some type checking to see which event was generated, eventually cast the param (tho usually it's automatically done as long as it's locally scoped) and then access the instance params and do your thing.

Basically it would be something like Compositor.addEventHandler(callback) where callback is of type void Function(Event)

Comment on lines 36 to 57
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is Surface &&
other.handle == handle &&
other.pid == pid &&
other.gid == gid &&
other.uid == uid &&
other.isPopup == isPopup &&
other.parentHandle == parentHandle;
}

@override
int get hashCode {
return handle.hashCode ^
pid.hashCode ^
gid.hashCode ^
uid.hashCode ^
isPopup.hashCode ^
parentHandle.hashCode;
}
Copy link
Member

Choose a reason for hiding this comment

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

If everything else is functioning as it should, there should be one and only one Surface class instance for every handle on the native side.

It should be safe to compare Surface classes by identity, so we probably don't need these implementations.

@charafau
Copy link
Contributor Author

There's some breakage after merge. Need to fix that

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.

3 participants