Mir

Merge lp:~raof/mir/persistent-surface-id into lp:mir

Proposed by Chris Halse Rogers
Status: Merged
Approved by: Alberto Aguirre
Approved revision: no longer in the source branch.
Merged at revision: 2645
Proposed branch: lp:~raof/mir/persistent-surface-id
Merge into: lp:mir
Diff against target: 1234 lines (+816/-3)
31 files modified
CMakeLists.txt (+1/-0)
debian/control (+1/-0)
include/client/mir_toolkit/client_types.h (+5/-0)
include/client/mir_toolkit/mir_surface.h (+40/-0)
include/server/mir/shell/persistent_surface_store.h (+131/-0)
src/client/mir_surface.cpp (+46/-0)
src/client/mir_surface.h (+16/-0)
src/client/mir_surface_api.cpp (+37/-0)
src/client/symbols.map (+4/-0)
src/include/server/mir/default_server_configuration.h (+3/-0)
src/include/server/mir/frontend/shell.h (+2/-0)
src/protobuf/mir_protobuf.proto (+9/-0)
src/server/CMakeLists.txt (+2/-0)
src/server/frontend/protobuf_message_processor.cpp (+4/-0)
src/server/frontend/session_mediator.cpp (+18/-0)
src/server/frontend/session_mediator.h (+6/-0)
src/server/frontend/shell_wrapper.cpp (+5/-0)
src/server/frontend/shell_wrapper.h (+2/-0)
src/server/shell/CMakeLists.txt (+2/-0)
src/server/shell/default_configuration.cpp (+12/-1)
src/server/shell/default_persistent_surface_store.cpp (+91/-0)
src/server/shell/default_persistent_surface_store.h (+44/-0)
src/server/shell/frontend_shell.cpp (+9/-0)
src/server/shell/frontend_shell.h (+10/-2)
src/server/shell/persistent_surface_store.cpp (+79/-0)
src/server/symbols.map (+1/-0)
tests/acceptance-tests/test_client_library.cpp (+20/-0)
tests/include/mir_test_doubles/mock_shell.h (+2/-0)
tests/unit-tests/shell/CMakeLists.txt (+2/-0)
tests/unit-tests/shell/test_default_persistent_surface_store.cpp (+124/-0)
tests/unit-tests/shell/test_persistent_surface_store_id.cpp (+88/-0)
To merge this branch: bzr merge lp:~raof/mir/persistent-surface-id
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alberto Aguirre (community) Approve
Alan Griffiths Approve
Daniel van Vugt Abstain
Kevin DuBois (community) Approve
Andreas Pokorny (community) Approve
Robert Carr (community) Approve
Review via email: mp+257843@code.launchpad.net

Commit message

Add mir_surface_request_persistent_id() API.

This requests an ID for a surface which can be referenced by other clients and is stable over time.

API for actually using these IDs will come as a followup.

Description of the change

First - slightly useless - half of persistent surface reference support.

Adds the ability for a client to get a MirSurfaceId, which *currently* does nothing, but in a follow-up will be convertible to a string, sent cross-process, and deserialised into a MirSurfaceId that can be set in an input method spec to do cross-process surface-relative positioning.

It will also grow into support for restoring surface position/state across application restarts.

To post a comment you must log in.
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Using UUIDs only makes sense if "persistent" also means persistent between runs, and persistent across all machines. Obviously a unique ID for the lifetime of the server process could be achieved with an int...

(1) So if you need a persistent ID across machines and server instances, what happens if you have multiple instances of a client running? They'd have the same IDs, which would be a problem right?

(2) What about simple apps/toolkits that don't support the UUID? For example X apps. Can we live with some windows not having IDs? In the past I've suggested the fallback should be some string that just describes the window title and default attributes, which would at least be consistent between runs. So that's "mir_surface_id_from_string"? But there's no good guarantee of uniqueness.

(3) Given we can't have a 100% water-tight guarantee that this new MirSurfaceId is unique (per (2)), it would need to co-exist with and never replace our internal integer IDs. So in that case it's not really the "surface ID" but should be called something else like "MirSurfaceSignature". So at least someone looking at the Mir source code knows the surface ID is not the surface signature.

(1) and (2) need info. (3) I think needs fixing.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

(4) Consider using the client executable binary path instead of UUID:
    readlink("/proc/self/exe", ...)

That would give a sufficiently unique string with the added bonus of completely solving issue (2) as no apps or toolkits would need modification at all. All clients would support it for free.

Revision history for this message
Chris Halse Rogers (raof) wrote :

(1) If you have multiple instances of a client running, their surfaces get different IDs.

(2) If an app doesn't need to do anything that requires an ID it doesn't get one. Simple apps won't do anything requiring an ID. We punt on trying to restore surface state for apps that don't explicitly opt-in (this actually isn't any different from X).

IM frameworks are an interesting case. For native Mir clients, it's fine - the IM framework has an in-process plugin that can request the surface ID. For XMir clients we need to do something special anyway - the IM framework doesn't have any useful knowlege in that case. I was thinking of having XMir ask for a ID for all its surfaces, then setting a _X_CANONICAL_MIR_ID property that the IM framework can read out.

We'll have to do something like that regardless of how we identify surfaces (at least for sensible ways of identifying surfaces), unless we integrate XMir more closely into Mir - at which point XID → MirSurface lookups become possible, and this still works.

(3) We can guarantee this MirSurfaceId is unique. I don't want to replace our internal integer IDs with it; they serve different purposes. But we don't expose those integer IDs to the client, so I think MirSurfaceId is a reasonable name for it, client-side. I'm happy to change it, although it really is a client-side ID, so I might wait until others weigh in.

(4) /proc/self/exe is one of many bad options. For example, all Java applications have the same /proc/self/exe (that of the JVM binary). This applies to Mono, Python, shell scripts, …

Revision history for this message
Robert Carr (robertcarr) wrote :

Looks basically ok to me though client API requires docs.

I have some questions about how we are going to implement persistence across shell restarts and if the client API is really sufficient though, lets talk on IRC perhaps?

Revision history for this message
Robert Carr (robertcarr) wrote :

Understand how the persistence model will work now:

16:32 < RAOF> racarr: Yo!
16:34 < racarr> YO
16:34 < RAOF> You wanted to talk about persistence across shell restarts and such?
16:35 < racarr> RAOF: Ah yes...uh...so how is that going to work? Wont the API have to be like
16:35 < racarr> well involve the client having to provide some sort of cookie
16:35 < racarr> otherwise yeah how to differentiate between multiple java apps etc
16:35 < RAOF> Yeah, this would be more obvious if I'd actually documented the client API, wouldn't it :)
16:36 < RAOF> And also not included bits that should be in a subsequent MP.
16:36 < racarr> ?
16:36 < RAOF> Well, for example, mir_surface_spec_set_id() doesn't do anything, nor does id_from_string() or as_string().
16:37 < RAOF> The reason being that I removed the acceptance tests which used them in order to do the minimal *get* an ID thing.
16:38 < RAOF> Anyway; my high-level idea of this is that any client which wishes to restore its surface location/state across starts needs to
              first acquire an ID for the relevant surface and save it's serialised representation somewhere.
16:39 < RAOF> Then, when it's next starting up, before it creates the relevant window it deserialises the ID it got and adds the ID to the
              surface spec.
16:39 < RAOF> If there's currently no surface with that ID active, the PersistentSurfaceStore will return the relevant dodads, and the new
              surface will reclaim the old ID.
16:40 < racarr> oh ok you add the ID to the surface spec
16:40 < RAOF> 43I+bool mir_surface_spec_set_id(MirSurfaceSpec* spec, MirSurfaceId* id);
16:40 < RAOF> :)
16:40 < RAOF> It's not hooked up to anything, but it's (accidentally!) there :)
16:40 < racarr> yeah I guess I had a different mental model so I
16:40 < racarr> filtered that out

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Already confused by whether you mean internal integer ID by "ID" or MirSurfaceId. Please fix (3) ASAP. Having two broadly unrelated meanings of "surface ID" is going to keep biting us.

(1) What I mean is consider if you have only one app installed and do this:
  * Open one instance A. Close instance A.
  * Open two instances B and C. Close instances B and C.
  * Open one instance D.
Now exactly which instances have the same MirSurfaceId's? They can't all be different as that would defeat the purpose of the exercise.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

On Fri, May 1, 2015 at 12:41 PM, Daniel van Vugt
<email address hidden> wrote:
> Already confused by whether you mean internal integer ID by "ID" or
> MirSurfaceId. Please fix (3) ASAP. Having two broadly unrelated
> meanings of "surface ID" is going to keep biting us.

In this we'll assume that this app wants to do surface position/state
restore, and so is calling mir_surface_request_persistent_id...

>
> (1) What I mean is consider if you have only one app installed and do
> this:
> * Open one instance A. Close instance A.

Instance A opens surface A' and requests an ID A*, which it serialises
to disc.

> * Open two instances B and C. Close instances B and C.

App instance B loads the serialised MirSurfaceId A* from disc, sets it
on the MirSurfaceSpec, and creates surface B'. Since there is no
current surface with Id A*, the MirSurfaceId of B' is A* and it gets
the state restoration treatment. App instance B calls
request_persistent_id, gets A* back and serialises it to disc.

App instance C loads the serialised MirSurfaceId A* from disc, sets it
on the MirSurfaceSpec, and creates surface C'. Since there *is* a
current surface with Id A*, C' gets a new MirSurfaceId C* and does not
get the state restoration treatment. App instance C calls
request_persistent_id, gets back C*, and serialises it to disc.

>
> * Open one instance D.
> Now exactly which instances have the same MirSurfaceId's? They can't
> all be different as that would defeat the purpose of the exercise.

App instance D loads the serialised MirSurfaceId C* from disc, sets it
on the MirSurfaceSpec, and creates surface D'. Since there is no
current surface with Id C*, D' gets Id C* and has C's state restored.

Note that more sophisticated clients could do more interesting things.
For example, if the application is a document editor then it can store
the serialised MirSurfaceId alongside the document and restore that
when appropriate. In that case, if instances A, C, D were opening the
same document and instance B opened a different document, A', C', and
D' would share ID and state, and B' would get a different ID and state.

Revision history for this message
Chris Halse Rogers (raof) wrote :

Oh, and FWIW - I've had some discussions with the GNOME Wayland guys, and got a vague “yeah, that seems sensible” about sticking the relevant client API into GTK.

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

(0) I'm concerned that we might have missed initial analysis/requirements. Last time I discussed all this with sabdfl I convinced him at the time that remembering things was a bad idea (based on complaints from users in Compiz bugs that windows opened on other desktops they had forgotten about). So the whole exercise of remembering anything is questionable, but that's not a blocking issue if "remembering" is an option you can turn off easily.

(1) OK so indeed there is only one MirSurfaceId being shared by the multiple instances of an app, as expected. Just confused because that's the opposite of your first reply to (1) depending on the meaning of "ID" (see (3)).

(2) Opt-in concerns me still. Is it really OK that only some toolkits/apps will have a nice unique UUID?

(3) I still vote for s/MirSurfaceId/MirSurfaceSignature/ as this discussion is suffering from the confusion of the current name already. Or s/Signature/Fingerprint/.

(4) You could solve the /proc/self/exe problem by using "/proc/self/exe + /proc/self/cmdline" instead. That would be different between Java apps, but also consistent and stable between shell-launched app instances.

(5) Mention of the word "disc" and files is also a concern. Last time those words came up, the consensus was that Mir should never touch disc/files because the context in which Mir is used will vary between shells and we don't want a shell having settings spread between multiple repositories. A more elegant solution is for Mir to specify just the serialization format (ie. a string) and let the shell choose the storage location (e.g. QSettings/GSettings/file) if any.

review: Needs Fixing
Revision history for this message
Chris Halse Rogers (raof) wrote :
Download full text (3.2 KiB)

On Fri, May 1, 2015 at 3:02 PM, Daniel van Vugt
<email address hidden> wrote:
> Review: Needs Fixing
>
> (0) I'm concerned that we might have missed initial
> analysis/requirements. Last time I discussed all this with sabdfl I
> convinced him at the time that remembering things was a bad idea
> (based on complaints from users in Compiz bugs that windows opened on
> other desktops they had forgotten about). So the whole exercise of
> remembering anything is questionable, but that's not a blocking issue
> if "remembering" is an option you can turn off easily.

Relevant bit of the Mir Surface spec:

https://docs.google.com/document/d/1L85DdfDd3lDbvchYbgQ45C_lJ1IeTMG4uc7Nuq_XdAE/edit#heading=h.c76tva1odjo

> (1) OK so indeed there is only one MirSurfaceId being shared by the
> multiple instances of an app, as expected. Just confused because
> that's the opposite of your first reply to (1) depending on the
> meaning of "ID" (see (3)).

I didn't understand the question in the same way you did. I thought it
was talking about multiple *concurrent* instances of an application; in
this case they do indeed get different IDs.

Multiple sequential instances of an application may share a surface ID,
as indeed that's one of the use-cases.

> (2) Opt-in concerns me still. Is it really OK that only some
> toolkits/apps will have a nice unique UUID?

Yes. This is no worse than it is today in X. And should be better,
because we'll actually be able to *use* the information the client
opts-into.

> (3) I still vote for s/MirSurfaceId/MirSurfaceSignature/ as this
> discussion is suffering from the confusion of the current name
> already. Or s/Signature/Fingerprint/.

I still don't particularly like either Signature or Fingerprint, but am
willing to be outvoted.

>
> (4) You could solve the /proc/self/exe problem by using
> "/proc/self/exe + /proc/self/cmdline" instead. That would be
> different between Java apps, but also consistent and stable between
> shell-launched app instances.

It is not stable between shell-launched app instances, at least if you
include content-hub as a part of the shell.

A substantial number of applications are not launched directly by the
shell, and it'd be particularly weird if, for example, your email app
consistently opened in a certain place when launched from the launcher
but in a different place when launched from a notification.

>
> (5) Mention of the word "disc" and files is also a concern. Last time
> those words came up, the consensus was that Mir should never touch
> disc/files because the context in which Mir is used will vary between
> shells and we don't want a shell having settings spread between
> multiple repositories. A more elegant solution is for Mir to specify
> just the serialization format (ie. a string) and let the shell choose
> the storage location (e.g. QSettings/GSettings/file) if any.

Every mention of “disc” in the example was a *client* writing to
disc.

In the current implementation there's (a) no write to disc shell-side,
and (b) a shell is perfectly able to implement the whole
PersistentSurfaceStore and its associated ID with whatever it likes; if
it w...

Read more...

Revision history for this message
Chris Halse Rogers (raof) wrote :

Hm. How about MirReference, rather than MirSurfaceId?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Robert Carr (robertcarr) wrote :

 >>string, stored or sent to another process, and then later deserialised to refer to the same
 >> MirSurface.

Or rather can be used to indicate that a new MirSurface refers to the same application window?

Revision history for this message
Robert Carr (robertcarr) wrote :

LGTM

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Needs Fixing (nit):

222+MirReference::MirReference(std::string const& string_id)
223+ : string_id{string_id}
...
910- explicit FrontendShell(std::shared_ptr<shell::Shell> const& wrapped) :
911- wrapped{wrapped} {}
912+ explicit FrontendShell(std::shared_ptr<shell::Shell> const& wrapped,
913+ std::shared_ptr<shell::PersistentSurfaceStore> const& surface_store)
914+ : wrapped{wrapped},
915+ surface_store{surface_store}

etc.

Not the approved layout:

http://unity.ubuntu.com/mir/cppguide/index.html?showone=Constructor_Initializer_Lists#Constructor_Initializer_Lists

~~~~

Needs clarification:

86+/**
87+ * \brief Free a MirReference
88+ * \param [in] id The MirReference to free
89+ */
90+void mir_reference_release(MirReference* id);

This is just freeing the local resource? It doesn't purge knowledge of the reference from the server?

~~~~

[non blocking opinion] I'm not convinced that "Reference" is as good a name as "ID".

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> Not the approved layout:
>
> http://unity.ubuntu.com/mir/cppguide/index.html?showone=Constructor_Initializer_Lists#Constructor_Initializer_Lists

Our code base contains some of the approved style and much more of the traditional style (i.e. what is used in this MP). I personally like the traditionally style better because with it a multi-line constructor parameter list is visually distinct from the initializer list [1]. Perhaps we should revisit the decision.

[1] http://paste.ubuntu.com/10990296/

Revision history for this message
Chris Halse Rogers (raof) wrote :

If you'd like to *ensure* that the style used is consistent, there's always https://code.launchpad.net/~raof/mir/clang-formatting-test/+merge/258219 :)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

(3) I agree MirSurfaceId probably needs to begin with "MirSurface" but the "Id" part is easily confused with surface IDs we already have in the server code and even visible in the client debug API. Still think MirSurfaceSignature or MirSurfaceFingerprint is better. Although come to think of it, any generic persistent identifier doesn't have to be just used for surfaces so maybe "Surface" should not be mentioned; just "MirSignature" or "MirFingerprint".

(4) "/proc/self/exe + /proc/self/cmdline + (subwindow ID?)" instead of UUID would not only work, but it would avoid the need to generate UUIDs somewhere and would solve problem (2) instead of declaring it unsolvable. The issue of where an app was spawned from changing its ID is not an issue. The vast majority of the time a user will launch an app in the same way yielding the same ID. Arguably it's even a feature for different launchers to yield different restore positions. Using UUIDs prevents us from being able to have state memories work for 100% of apps (issue (2) is never solved). At least as a fallback from UUIDs (apps/toolkits that don't implement them) it would be a good idea.

review: Needs Fixing
Revision history for this message
Chris Halse Rogers (raof) wrote :

On Wed, May 6, 2015 at 4:24 PM, Daniel van Vugt
<email address hidden> wrote:
> (4) "/proc/self/exe + /proc/self/cmdline + (subwindow ID?)" instead
> of UUID would not only work, but it would avoid the need to generate
> UUIDs somewhere and would solve problem (2) instead of declaring it
> unsolvable. The issue of where an app was spawned from changing its
> ID is not an issue. The vast majority of the time a user will launch
> an app in the same way yielding the same ID.

Actually, I think that for all but one of the applications I have open
*right now* /proc/self/cmdline will give the wrong result - Chrome has
been opened from xdg-open <some link>, which is not stable, QtCreator
has been opened with a file, but probably should not change positions
based on the initially opened file, geary was launched with arguments
from the indicator, …

One of the reasons why no major X window manager restores window state
is that the heuristics available are wrong too often to be useful.

I think that the foundation of your argument - that the user will
almost always launch an app with the same /proc/self/cmdline - is false.

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

looks good
nit:two nl in frontend_shell.cpp 876 and 891

review: Approve
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

(3) Specifically you now have these two types:
   mir::frontend::SurfaceId
   MirSurfaceId
which while they sound similar are vastly different. So a different name is required to avoid any confusion. Even in a semi-public client API we already have something called surface ID:
   int mir_debug_surface_id(MirSurface *surface);

(4) That's a reasonable argument. Also given item (0) people such as myself will disable the feature anyway so will have nothing to complain about. And so long as MirSurfaceId remains an opaque string the implementation is flexible.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I approve of the design. A few nits:

217+#include <uuid/uuid.h>
...
289+#include <uuid/uuid.h>

unused includes

~~~~

205+ ${UUID_LDFLAGS} ${UUID_LIBRARIES}

Dependency in the wrong place - it is libmirserver that uses uuid_*

~~~~

1063+ msh::DefaultPersistentSurfaceStore map;

"map" seems to reflect the implementation, not the usage. Would "store" or "pss" be better?

~~~~

225+MirSurfaceId::MirSurfaceId(std::string const& string_id)226+ : string_id{string_id}

Not the approved layout:

http://unity.ubuntu.com/mir/cppguide/index.html?showone=Constructor_Initializer_Lists#Constructor_Initializer_Lists

review: Needs Fixing
Revision history for this message
Kevin DuBois (kdub) wrote :

lgtm. I kept thinking that MirSurfaceTag might be another good name, but I'm okay with Id as well.
652+ UUID(UUID const& copy_from);
could have assignment defined too.

review: Approve
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

s/serialise/serialize?
s/serialise/serialize

+ what Alan said.

review: Needs Fixing
Revision history for this message
Chris Halse Rogers (raof) wrote :

Client type naming poll: I'm ok with MirSurfaceTag. I prefer the newly-thought-of MirSurfaceTicket.

Also ok with MirTag or MirTicket if we wanted the ability to be lazy in future and use these as generic identifiers; I'm weakly opposed to this on meaningful-type grounds.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> Client type naming poll: I'm ok with MirSurfaceTag. I prefer the newly-
> thought-of MirSurfaceTicket.
>
> Also ok with MirTag or MirTicket if we wanted the ability to be lazy in future
> and use these as generic identifiers; I'm weakly opposed to this on
> meaningful-type grounds.

I'm happy with MirSurfaceID.

If we actually think there is a chance of confusion between the useful-to-the-client MirSurfaceID and the mostly-internal mir::frontend::SurfaceId I think the latter should be rethought.

Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

My preference is for MirSurfaceSignature or MirSurfaceFingerprint or similar. In this context, I don't think the overly general term "ID" properly stresses the most important characheristic of the value, which is its persistence. If I saw the term "surface id" in some foreign code, a persistent, stable-between-invocations value would not be high on the list of concepts I would expect that term to represent.

Revision history for this message
Alberto Aguirre (albaguirre) wrote :

MirPersistentID?
MirCookie?

But I'm mostly ok with MirSurfaceID. MirSurfaceTag would be fine too.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Also against the name "MirSurfaceId" is the widespread knowledge people have about windowing systems where window instances usually have some sort of integer ID (as they do in Mir too). But that's definitely not what this is about.

Preferences (in order):
  MirSurfaceSignature
  MirSurfaceFingerprint
  MirSurfaceT{ag,icket}

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

145+class PersistentSurfaceStore

Should disable CopyAssign

~~~~

168+ /**
169+ * \brief Lookup Surface by ID.
170+ * \param [in] id ID of surface to lookup
171+ * \return The surface with ID \arg id.
172+ */
173+ virtual std::shared_ptr<scene::Surface> surface_for_id(Id const& id) const = 0;

What happens if, for example, the surface has been closed since the ID was created? I'd guess a nullptr result but it needs to be documented.

~~~~

181+ * \throw std::out_of_range if \arg buffer contains a valid serialized ID, but
182+ * the PersistentSurfaceStore has no Surface with that ID.

Is this actually a post-condition failure? It seems more like a possible result: AFAICS this depends on the state of external actions (e.g. the client innocently has an ID from a store that was wiped between server sessions, or for a surface that has been closed).

Perhaps the problem is that I'm not seeing the use(s) of this feature the same way that you are: surely, for the user, the point is whether the store can provide the linked data (either a surface or some surface properties) not whether the ID is recognized.

In that model is seems more reasonable for the deserialize_id() to succeed and the subsequent surface_for_id() to return nullptr. (And if that is right then maybe Ids should be a value type that validates on construction without needing to be constructed by the store?)

~~~~

Something that doesn't appear to be addressed is the destruction of IDs. Is there a mechanism for this? I realize that not invalidating IDs avoids invalid references being passed, but it seems wrong for a "persistent store" to lack such a mechanism.

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

148+ /**
149+ * An opaque identifier tied to this PersistentSurfaceStore
150+ */
151+ class Id
152+ {
153+ public:
154+ virtual ~Id() = default;
155+ virtual bool operator==(Id const& rhs) const = 0;
156+ };
...
167+ virtual Id const& id_for_surface(std::shared_ptr<scene::Surface> const& surface) = 0;
...
173+ virtual std::shared_ptr<scene::Surface> surface_for_id(Id const& id) const = 0;

I guess it is techincally OK (as we can't create sliced objects because of the pure member) but use of an interface combined passing references and allowing CopyAssign seems like a confusion of value and instance semantics. (I'd much rather see either a pointer or a value type being passed.)

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Yet another pre-existing definition of SurfaceId:
    mir_protobuf.proto:message SurfaceId {

Best not to keep using the same name for different unrelated things.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

For the moment just a critique of the class Id semantics. Shall we sort those first?

160 + /**
161 + * \brief Acquire ID for a Surface
162 + * \param [in] surface to query or generate an ID for
163 + * \return A reference to the ID of this surface. This reference is stable
164 + * for the lifetime of the PersistentSurfaceStore.
165 + * \note If \arg surface has not yet had an ID generated, this generates its ID.
166 + */
167 + virtual Id const& id_for_surface(std::shared_ptr<scene::Surface> const& surface) = 0;

Requiring the reference to remain valid is reasonable for the user, but it requires that every ID ever referenced remains in memory. For a persistant surface store that persists to disk this could be inconvenient.

The API is also error prone:

   auto const id = ps->id_for_surface(surface); // Captures by value
   ps->surface_for_id(id); // undefined behaviour

(UB because, e.g. the default implementation casts the passed reference to a UUID.)

We could break this example by giving Id a private destructor, but I think it would be better to return a value.

Making Id a value type admittedly doesn't allow the store to chose its own ID implementation, but I think is a better trade-off.

~~~~

168 + /**
169 + * \brief Lookup Surface by ID.
170 + * \param [in] id ID of surface to lookup
171 + * \return The surface with ID \arg id.
172 + */
173 + virtual std::shared_ptr<scene::Surface> surface_for_id(Id const& id) const = 0;

What happens if, for example, the surface has been closed since the ID was created? I'd guess a nullptr result but it needs to be documented.

~~~~

184 + virtual Id const& deserialize_id(std::vector<uint8_t> const& buffer) const = 0;
...
191 + virtual std::vector<uint8_t> serialize_id(Id const& id) const = 0;

Do these need to be on the PersistentSurfaceStore interface? Or could they be functions on Id?

review: Needs Fixing
Revision history for this message
Chris Halse Rogers (raof) wrote :

On Wed, May 20, 2015 at 9:46 AM, Alan Griffiths <email address hidden>
wrote:
> Review: Needs Fixing
>
> For the moment just a critique of the class Id semantics. Shall we
> sort those first?
>
> 160 + /**
> 161 + * \brief Acquire ID for a Surface
> 162 + * \param [in] surface to query or generate an ID for
> 163 + * \return A reference to the ID of this surface.
> This reference is stable
> 164 + * for the lifetime of the
> PersistentSurfaceStore.
> 165 + * \note If \arg surface has not yet had an ID generated,
> this generates its ID.
> 166 + */
> 167 + virtual Id const&
> id_for_surface(std::shared_ptr<scene::Surface> const& surface) = 0;
>
> Requiring the reference to remain valid is reasonable for the user,
> but it requires that every ID ever referenced remains in memory. For
> a persistant surface store that persists to disk this could be
> inconvenient.
>
> The API is also error prone:
>
> auto const id = ps->id_for_surface(surface); // Captures by value
> ps->surface_for_id(id); // undefined
> behaviour
>
> (UB because, e.g. the default implementation casts the passed
> reference to a UUID.)
>
> We could break this example by giving Id a private destructor, but I
> think it would be better to return a value.
>
> Making Id a value type admittedly doesn't allow the store to chose
> its own ID implementation, but I think is a better trade-off.

Yeah, I'm part-way through making Id a concrete value type backed by a
uuid_t. Stores will need to deal with it.

>
>
> ~~~~
>
> 168 + /**
> 169 + * \brief Lookup Surface by ID.
> 170 + * \param [in] id ID of surface to lookup
> 171 + * \return The surface with ID \arg id.
> 172 + */
> 173 + virtual std::shared_ptr<scene::Surface> surface_for_id(Id
> const& id) const = 0;
>
> What happens if, for example, the surface has been closed since the
> ID was created? I'd guess a nullptr result but it needs to be
> documented.

Either that or throwing; I guess returning nullptr is more idiomatic.

Will document.
>
> ~~~~
>
> 184 + virtual Id const& deserialize_id(std::vector<uint8_t> const&
> buffer) const = 0;
> ...
> 191 + virtual std::vector<uint8_t> serialize_id(Id const& id)
> const = 0;
>
> Do these need to be on the PersistentSurfaceStore interface? Or could
> they be functions on Id?

Yeah, now that Id is concrete they're moving to it.

Revision history for this message
Chris Halse Rogers (raof) wrote :

It is resolved!

MirPersistentId is to be the name.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

A mix *Needs Discussion* and Nits:

If users are to customize the persistent id store then this needs to be configurable through mir::Server. I presume that (and corresponding acceptance) happens in the second half of persistent surface reference support?

~~~~~

168 + * \throws std::out_of_range if there is no surface with \arg id.

Is this really a postcondition failure?

I would have expected it to be "normal" not "exceptional" for an ID key supplied by another process to have no matching surface. (E.g. because the surface closed since the ID was generated.) AFAICS there is no way to "validate" the ID before making this call.

~~~~

162 + virtual Id id_for_surface(std::shared_ptr<scene::Surface> const& surface) = 0;
163 + /**

Could probably do with another newline.

~~~~

196 + std::vector<uint8_t> serialize_id() const;
197 + static Id deserialize_id(std::vector<uint8_t> const& buffer);

These are members of Id, so "_id" is unnecessary repetition.

~~~~

202 + Id(std::array<char, 37> const& buffer);

Using std::array<char, ...> in the private section seems odd when the public interface is std::vector<uint8_t>. This leads to this odd copying:

919 + std::array<char, 37> null_terminated_buffer;
920 + std::copy(buffer.begin(), buffer.end(), null_terminated_buffer.data());
921 + null_terminated_buffer[36] = '\0';

I'm not sure why we don't just have a public constructor with the same parameter and semantics as deserialize_id()? (Nor why we use char internally and uint8_t publicly. Indeed, isn't std::string a better fit?)

~~~~

261 +MirWaitHandle* MirSurface::request_persistent_id(mir_surface_id_callback callback, void* context)
...
278 + surface.set_error(std::string{"Error invoking create surface: "} +
279 + boost::diagnostic_information(ex));

"Error invoking create surface" is misleading.

~~~~

793 +

Spurious whitespace

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

On Mon, Jun 1, 2015 at 7:47 PM, Alan Griffiths <email address hidden>
wrote:
> Review: Needs Information
>
> A mix *Needs Discussion* and Nits:
>
> If users are to customize the persistent id store then this needs to
> be configurable through mir::Server. I presume that (and
> corresponding acceptance) happens in the second half of persistent
> surface reference support?

I think it'll actually happen later; there's no reason for shells to
want to configure this until there's a client API that uses
across-shell-restart persistence. The immediate follow up branch -
input-methods-can-specify-foreign-parents - has no need for shell
customisation of the store.

>
> ~~~~~
>
> 168 + * \throws std::out_of_range if there is no surface with
> \arg id.
>
> Is this really a postcondition failure?
>
> I would have expected it to be "normal" not "exceptional" for an ID
> key supplied by another process to have no matching surface. (E.g.
> because the surface closed since the ID was generated.) AFAICS there
> is no way to "validate" the ID before making this call.

In the case of a closed surface the method is documented as returning
nullptr.

Generally, I think it's worth being able to distinguish between “this
client sent you garbage” and “this client was the victim of an
unavoidable race condition”. It's reasonable to kill the client in
the former, but not the latter.

>
> 202 + Id(std::array<char, 37> const& buffer);
>
> Using std::array<char, ...> in the private section seems odd when the
> public interface is std::vector<uint8_t>. This leads to this odd
> copying:
>
> 919 + std::array<char, 37> null_terminated_buffer;
> 920 + std::copy(buffer.begin(), buffer.end(),
> null_terminated_buffer.data());
> 921 + null_terminated_buffer[36] = '\0';
>
> I'm not sure why we don't just have a public constructor with the
> same parameter and semantics as deserialize_id()? (Nor why we use
> char internally and uint8_t publicly. Indeed, isn't std::string a
> better fit?)

I don't think std::string is a good fit for the
serialisation/deserialisation API.

I like vector<uint8_t> as it makes it clear that we're dealing with
blobs of data, not strings.

Hm. That said, serialising to a string rather than bytestream is a
perfectly natural thing to do, and is how we currently use these. I'll
rename to serialize_to_string() and return a std::string, and take a
std::string for deserialisation.

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Seems like my primary concern is addressed. Haven't re-reviewed yet but I don't need to block this.

review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > I would have expected it to be "normal" not "exceptional" for an ID
> > key supplied by another process to have no matching surface. (E.g.
> > because the surface closed since the ID was generated.) AFAICS there
> > is no way to "validate" the ID before making this call.
>
> In the case of a closed surface the method is documented as returning
> nullptr.
>
> Generally, I think it's worth being able to distinguish between “this
> client sent you garbage” and “this client was the victim of an
> unavoidable race condition”. It's reasonable to kill the client in
> the former, but not the latter.

It sounds as though I've been confused by the term "persistent" - I was thinking the ID might be used across sequential executions of the same client[1]. In which case the client can't know if the server happens to have been restarted (and lost its list of closed surface IDs).

Are these actually serializable IDs rather than persistent ones?

[1] I know that identifying surfaces doesn't make sense across different executions of a client, but we do have use cases (the WM storing defaults for a surface) where a persistent ID is needed. Are we going to have a separate "persistent surface properties ID" for that?

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

No, I don't think you've been confused, but perhaps the comment is a little confusing. Or maybe the partially completed code is a bit confusing.

The PersistentSurfaceStore will need to grow a separate method for the different-executions-of-a-client case - surface_for_id can't return a std::shared_ptr<Surface> for the get-last-used-defaults case, because the whole point is that there isn't a currently active Surface with that id.

At the moment they are indeed serializable IDs; when we implement the persistent bit they'll become persistent :).

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

So, six minutes to start glmark and then timed out after an hour. Probably unrelated to these changes.

[timestamp] Start time : 2015-06-03T08:47:27+0000
...
[timestamp] Start : mir_performance_tests 2015-06-03T08:53:52+0000
+ grep -q mir_demo_client_
+ grep -q mir_privileged_tests
+ phablet-test-run -x -s 00693fd555c9186a -v umockdev-run -- mir_performance_tests
running umockdev-run -- mir_performance_tests
Running main() from command_line_server_configuration.cpp
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from GLMark2Test
[ RUN ] GLMark2Test.benchmark_fullscreen_default
[1433321633.435408] mirplatform: Found graphics driver: dummy
[1433321633.438794] mirplatform: Found graphics driver: android
[1433321633.439770] mirplatform: Found graphics driver: mesa
Build timed out (after 60 minutes). Marking the build as failed.
Build was aborted

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

What the hell, mako?

/me fires up his mako to test locally.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

Ok. This works fine on *my* mako. Let's just try prodding CI again.

Revision history for this message
Chris Halse Rogers (raof) wrote :

...!

Ok. So, on more detailed inspection this *does* fail on my mako. glmark2-es2-mir, and as far as I've tested, *only* glmark2-es2-mir hangs in a syscall before displaying anything. mir_demo_client_multiwin works, as does mir_demo_clent_eglplasma and eglflash.

Um...

Revision history for this message
Chris Halse Rogers (raof) wrote :

Oh, that's fun.

So, this is libmirprotobuf0 breakage. Because I've added an RPC call this has re-arranged the protobuf::DisplayServer vtable so when libmirclient8 calls what it thinks is server->exchange_buffer it's actually calling server->stop_prompt_session.

On desktop it just happens to work.

Now, I've moved the request_persistent_id to the end of the DisplayServer .proto definition. Hopefully that'll get protoc to generate a class with request_persistent_id at the end. Although we have derived classes of DisplayServer, they're defined in libmirprotobuf0, so this might work!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

^--Looks like device setup failure - not related to this MP. Retriggering.

Revision history for this message
Alberto Aguirre (albaguirre) wrote :

LGTM.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-06-02 19:34:13 +0000
3+++ CMakeLists.txt 2015-06-09 07:21:31 +0000
4@@ -173,6 +173,7 @@
5 find_package(LTTngUST REQUIRED)
6 pkg_check_modules(UDEV REQUIRED libudev)
7 pkg_check_modules(GLIB REQUIRED glib-2.0)
8+pkg_check_modules(UUID REQUIRED uuid)
9
10 include_directories (${GLESv2_INCLUDE_DIRS})
11 include_directories (${EGL_INCLUDE_DIRS})
12
13=== modified file 'debian/control'
14--- debian/control 2015-06-08 08:48:29 +0000
15+++ debian/control 2015-06-09 07:21:31 +0000
16@@ -40,6 +40,7 @@
17 libfreetype6-dev,
18 abi-compliance-checker,
19 libevdev-dev,
20+ uuid-dev,
21 Standards-Version: 3.9.4
22 Homepage: https://launchpad.net/mir
23 # If you aren't a member of ~mir-team but need to upload packaging changes,
24
25=== modified file 'include/client/mir_toolkit/client_types.h'
26--- include/client/mir_toolkit/client_types.h 2015-05-26 22:56:02 +0000
27+++ include/client/mir_toolkit/client_types.h 2015-06-09 07:21:31 +0000
28@@ -43,6 +43,7 @@
29 typedef struct MirScreencast MirScreencast;
30 typedef struct MirPromptSession MirPromptSession;
31 typedef struct MirBufferStream MirBufferStream;
32+typedef struct MirPersistentId MirPersistentId;
33
34 /**
35 * Returned by asynchronous functions. Must not be free'd by
36@@ -123,6 +124,10 @@
37 typedef void (*mir_client_fd_callback)(
38 MirPromptSession *prompt_session, size_t count, int const* fds, void* context);
39
40+
41+typedef void (*mir_surface_id_callback)(
42+ MirSurface* surface, MirPersistentId* id, void* context);
43+
44 /**
45 * MirBufferUsage specifies how a surface can and will be used. A "hardware"
46 * surface can be used for OpenGL accelerated rendering. A "software" surface
47
48=== modified file 'include/client/mir_toolkit/mir_surface.h'
49--- include/client/mir_toolkit/mir_surface.h 2015-05-27 11:37:38 +0000
50+++ include/client/mir_toolkit/mir_surface.h 2015-06-09 07:21:31 +0000
51@@ -582,6 +582,46 @@
52 */
53 void mir_surface_apply_spec(MirSurface* surface, MirSurfaceSpec* spec);
54
55+/**
56+ * \brief Request an ID for the surface that can be shared cross-process and
57+ * across restarts.
58+ *
59+ * This call acquires a MirPersistentId for this MirSurface. This MirPersistentId
60+ * can be serialized to a string, stored or sent to another process, and then
61+ * later deserialized to refer to the same surface.
62+ *
63+ * \param [in] surface The surface to acquire a persistent reference to.
64+ * \param [in] callback Callback to invoke when the request completes.
65+ * \param [in,out] context User data passed to completion callback.
66+ * \return A MirWaitHandle that can be used in mir_wait_for to await completion.
67+ */
68+MirWaitHandle* mir_surface_request_persistent_id(MirSurface* surface, mir_surface_id_callback callback, void* context);
69+
70+/**
71+ * \brief Request a persistent ID for a surface and wait for the result
72+ * \param [in] surface The surface to acquire a persistent ID for.
73+ * \return A MirPersistentId. This MirPersistentId is owned by the calling code, and must
74+ * be freed with a call to mir_persistent_id_release()
75+ */
76+MirPersistentId* mir_surface_request_persistent_id_sync(MirSurface *surface);
77+
78+/**
79+ * \brief Check the validity of a MirPersistentId
80+ * \param [in] id The MirPersistentId
81+ * \return True iff the MirPersistentId contains a valid ID value.
82+ *
83+ * \note This does not guarantee that the ID refers to a currently valid object.
84+ */
85+bool mir_persistent_id_is_valid(MirPersistentId* id);
86+
87+/**
88+ * \brief Free a MirPersistentId
89+ * \param [in] id The MirPersistentId to free
90+ * \note This frees only the client-side representation; it has no effect on the
91+ * object referred to by \arg id.
92+ */
93+void mir_persistent_id_release(MirPersistentId* id);
94+
95 #ifdef __cplusplus
96 }
97 /**@}*/
98
99=== added file 'include/server/mir/shell/persistent_surface_store.h'
100--- include/server/mir/shell/persistent_surface_store.h 1970-01-01 00:00:00 +0000
101+++ include/server/mir/shell/persistent_surface_store.h 2015-06-09 07:21:31 +0000
102@@ -0,0 +1,131 @@
103+/*
104+ * Copyright © 2015 Canonical Ltd.
105+ *
106+ * This program is free software: you can redistribute it and/or modify
107+ * it under the terms of the GNU General Public License version 3 as
108+ * published by the Free Software Foundation.
109+ *
110+ * This program is distributed in the hope that it will be useful,
111+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
112+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
113+ * GNU General Public License for more details.
114+ *
115+ * You should have received a copy of the GNU General Public License
116+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
117+ *
118+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
119+ */
120+
121+#ifndef MIR_SHELL_PERSISTENT_SURFACE_STORE_H_
122+#define MIR_SHELL_PERSISTENT_SURFACE_STORE_H_
123+
124+#include <memory>
125+#include <vector>
126+#include <array>
127+#include <uuid/uuid.h>
128+
129+
130+namespace mir
131+{
132+namespace scene
133+{
134+class Surface;
135+}
136+
137+namespace shell
138+{
139+/**
140+ * A store for Surface information divorced from the lifetime of any given Session
141+ *
142+ * This provides the backing for persistent references to Surface objects,
143+ * both across client Sessions and across shell restarts, for features such as
144+ * surface position/size restoration.
145+ *
146+ * \todo Persistence across shell restarts is as-yet unimplemented.
147+ */
148+class PersistentSurfaceStore
149+{
150+public:
151+ class Id;
152+
153+ virtual ~PersistentSurfaceStore() = default;
154+
155+ /**
156+ * \brief Acquire ID for a Surface
157+ * \param [in] surface Surface to query or generate an ID for
158+ * \return The ID for this surface.
159+ * \note If \arg surface has not yet had an ID generated, this generates its ID.
160+ * \note This does not extend the lifetime of \arg surface.
161+ */
162+ virtual Id id_for_surface(std::shared_ptr<scene::Surface> const& surface) = 0;
163+
164+ /**
165+ * \brief Lookup Surface by ID.
166+ * \param [in] id ID of surface to lookup
167+ * \return The surface with ID \arg id. If this surface has been destroyed,
168+ * but the store retains a reference, returns nullptr.
169+ * \throws std::out_of_range if the store has no reference for a surface with \arg id.
170+ */
171+ virtual std::shared_ptr<scene::Surface> surface_for_id(Id const& id) const = 0;
172+};
173+}
174+}
175+
176+namespace std
177+{
178+template<>
179+struct hash<mir::shell::PersistentSurfaceStore::Id>;
180+}
181+
182+namespace mir
183+{
184+namespace shell
185+{
186+
187+class PersistentSurfaceStore::Id final
188+{
189+public:
190+ /**
191+ * \brief Generate a new, unique Id.
192+ */
193+ Id();
194+
195+ /**
196+ * \brief Construct an Id from its serialized string form
197+ * \param serialized_form [in] The previously-serialized Id
198+ * \throw std::invalid_argument if \arg serialized_form is not parseable as an Id.
199+ */
200+ Id(std::string const& serialized_form);
201+
202+ Id(Id const& rhs);
203+ Id& operator=(Id const& rhs);
204+
205+ bool operator==(Id const& rhs) const;
206+
207+ /**
208+ * \brief Serialize to a UTF-8 string
209+ * \return A string representation of the Id; this is guaranteed to be valid UTF-8
210+ */
211+ std::string serialize_to_string() const;
212+private:
213+ friend struct std::hash<Id>;
214+
215+ uuid_t uuid;
216+};
217+
218+}
219+}
220+
221+namespace std
222+{
223+template<>
224+struct hash<mir::shell::PersistentSurfaceStore::Id>
225+{
226+ typedef mir::shell::PersistentSurfaceStore::Id argument_type;
227+ typedef std::size_t result_type;
228+
229+ result_type operator()(argument_type const& uuid) const;
230+};
231+}
232+
233+#endif // MIR_SHELL_PERSISTENT_SURFACE_STORE_H_
234
235=== modified file 'src/client/mir_surface.cpp'
236--- src/client/mir_surface.cpp 2015-05-21 19:25:51 +0000
237+++ src/client/mir_surface.cpp 2015-06-09 07:21:31 +0000
238@@ -127,6 +127,16 @@
239 return message;
240 }
241
242+MirPersistentId::MirPersistentId(std::string const& string_id)
243+ : string_id{string_id}
244+{
245+}
246+
247+std::string const&MirPersistentId::as_string()
248+{
249+ return string_id;
250+}
251+
252 MirSurface::MirSurface(std::string const& error)
253 {
254 surface.set_error(error);
255@@ -220,6 +230,42 @@
256 return false;
257 }
258
259+void MirSurface::acquired_persistent_id(mir_surface_id_callback callback, void* context)
260+{
261+ if (!persistent_id.has_error())
262+ {
263+ callback(this, new MirPersistentId{persistent_id.value()}, context);
264+ }
265+ else
266+ {
267+ callback(this, nullptr, context);
268+ }
269+ persistent_id_wait_handle.result_received();
270+}
271+
272+MirWaitHandle* MirSurface::request_persistent_id(mir_surface_id_callback callback, void* context)
273+{
274+ std::lock_guard<decltype(mutex)> lock{mutex};
275+
276+ if (persistent_id.has_value())
277+ {
278+ callback(this, new MirPersistentId{persistent_id.value()}, context);
279+ return nullptr;
280+ }
281+
282+ persistent_id_wait_handle.expect_result();
283+ try
284+ {
285+ server->request_persistent_surface_id(0, &surface.id(), &persistent_id, gp::NewCallback(this, &MirSurface::acquired_persistent_id, callback, context));
286+ }
287+ catch (std::exception const& ex)
288+ {
289+ surface.set_error(std::string{"Failed to acquire a persistent ID from the server: "} +
290+ boost::diagnostic_information(ex));
291+ }
292+ return &persistent_id_wait_handle;
293+}
294+
295 MirWaitHandle* MirSurface::get_create_wait_handle()
296 {
297 return &create_wait_handle;
298
299=== modified file 'src/client/mir_surface.h'
300--- src/client/mir_surface.h 2015-05-21 19:25:51 +0000
301+++ src/client/mir_surface.h 2015-06-09 07:21:31 +0000
302@@ -99,6 +99,17 @@
303 mir::optional_value<AspectRatio> max_aspect;
304 };
305
306+struct MirPersistentId
307+{
308+public:
309+ MirPersistentId(std::string const& string_id);
310+
311+ std::string const& as_string();
312+
313+private:
314+ std::string const string_id;
315+};
316+
317 struct MirSurface
318 {
319 public:
320@@ -153,18 +164,22 @@
321 MirWaitHandle* modify(MirSurfaceSpec const& changes);
322
323 static bool is_valid(MirSurface* query);
324+
325+ MirWaitHandle* request_persistent_id(mir_surface_id_callback callback, void* context);
326 private:
327 mutable std::mutex mutex; // Protects all members of *this
328
329 void on_configured();
330 void on_cursor_configured();
331 void created(mir_surface_callback callback, void* context);
332+ void acquired_persistent_id(mir_surface_id_callback callback, void* context);
333 MirPixelFormat convert_ipc_pf_to_geometry(google::protobuf::int32 pf) const;
334
335 mir::protobuf::DisplayServer::Stub* server{nullptr};
336 mir::protobuf::Debug::Stub* debug{nullptr};
337 mir::protobuf::Surface surface;
338 mir::protobuf::BufferRequest buffer_request;
339+ mir::protobuf::PersistentSurfaceId persistent_id;
340 std::string error_message;
341 std::string name;
342 mir::protobuf::Void void_response;
343@@ -178,6 +193,7 @@
344 MirWaitHandle create_wait_handle;
345 MirWaitHandle configure_wait_handle;
346 MirWaitHandle configure_cursor_wait_handle;
347+ MirWaitHandle persistent_id_wait_handle;
348
349 std::shared_ptr<mir::client::ClientBufferStreamFactory> const buffer_stream_factory;
350 std::shared_ptr<mir::client::ClientBufferStream> buffer_stream;
351
352=== modified file 'src/client/mir_surface_api.cpp'
353--- src/client/mir_surface_api.cpp 2015-05-27 11:37:38 +0000
354+++ src/client/mir_surface_api.cpp 2015-06-09 07:21:31 +0000
355@@ -562,3 +562,40 @@
356 MIR_LOG_UNCAUGHT_EXCEPTION(ex);
357 return false;
358 }
359+
360+MirWaitHandle* mir_surface_request_persistent_id(MirSurface* surface, mir_surface_id_callback callback, void* context)
361+{
362+ mir::require(mir_surface_is_valid(surface));
363+
364+ return surface->request_persistent_id(callback, context);
365+}
366+
367+namespace
368+{
369+void assign_surface_id_result(MirSurface*, MirPersistentId* id, void* context)
370+{
371+ void** result_ptr = reinterpret_cast<void**>(context);
372+ *result_ptr = id;
373+}
374+}
375+
376+MirPersistentId* mir_surface_request_persistent_id_sync(MirSurface *surface)
377+{
378+ mir::require(mir_surface_is_valid(surface));
379+
380+ MirPersistentId* result = nullptr;
381+ mir_wait_for(mir_surface_request_persistent_id(surface,
382+ &assign_surface_id_result,
383+ &result));
384+ return result;
385+}
386+
387+bool mir_persistent_id_is_valid(MirPersistentId* id)
388+{
389+ return id != nullptr;
390+}
391+
392+void mir_persistent_id_release(MirPersistentId* id)
393+{
394+ delete id;
395+}
396
397=== modified file 'src/client/symbols.map'
398--- src/client/symbols.map 2015-05-27 11:44:54 +0000
399+++ src/client/symbols.map 2015-06-09 07:21:31 +0000
400@@ -76,6 +76,8 @@
401 mir_omnidirectional_resize_cursor_name;
402 mir_open_hand_cursor_name;
403 mir_orientation_event_get_direction;
404+ mir_persistent_id_is_valid;
405+ mir_persistent_id_release;
406 mir_platform_message_create;
407 mir_platform_message_get_data;
408 mir_platform_message_get_fds;
409@@ -118,6 +120,8 @@
410 mir_surface_is_valid;
411 mir_surface_release;
412 mir_surface_release_sync;
413+ mir_surface_request_persistent_id;
414+ mir_surface_request_persistent_id_sync;
415 mir_surface_set_event_handler;
416 mir_surface_set_preferred_orientation;
417 mir_surface_set_state;
418
419=== modified file 'src/include/server/mir/default_server_configuration.h'
420--- src/include/server/mir/default_server_configuration.h 2015-05-21 19:25:51 +0000
421+++ src/include/server/mir/default_server_configuration.h 2015-06-09 07:21:31 +0000
422@@ -83,6 +83,7 @@
423 class DisplayLayout;
424 class HostLifecycleEventListener;
425 class Shell;
426+class PersistentSurfaceStore;
427 namespace detail { class FrontendShell; }
428 }
429 namespace time
430@@ -268,6 +269,7 @@
431 virtual std::shared_ptr<scene::PromptSessionListener> the_prompt_session_listener();
432 virtual std::shared_ptr<scene::PromptSessionManager> the_prompt_session_manager();
433 virtual std::shared_ptr<shell::HostLifecycleEventListener> the_host_lifecycle_event_listener();
434+ virtual std::shared_ptr<shell::PersistentSurfaceStore> the_persistent_surface_store();
435
436 /** @} */
437
438@@ -440,6 +442,7 @@
439 CachedPtr<scene::CoordinateTranslator> coordinate_translator;
440 CachedPtr<EmergencyCleanup> emergency_cleanup;
441 CachedPtr<shell::HostLifecycleEventListener> host_lifecycle_event_listener;
442+ CachedPtr<shell::PersistentSurfaceStore> surface_store;
443 CachedPtr<SharedLibraryProberReport> shared_library_prober_report;
444 CachedPtr<shell::Shell> shell;
445
446
447=== modified file 'src/include/server/mir/frontend/shell.h'
448--- src/include/server/mir/frontend/shell.h 2015-04-08 07:51:14 +0000
449+++ src/include/server/mir/frontend/shell.h 2015-06-09 07:21:31 +0000
450@@ -25,6 +25,7 @@
451 #include <sys/types.h>
452
453 #include <memory>
454+#include <vector>
455
456 namespace mir
457 {
458@@ -62,6 +63,7 @@
459 virtual SurfaceId create_surface(std::shared_ptr<Session> const& session, scene::SurfaceCreationParameters const& params) = 0;
460 virtual void modify_surface(std::shared_ptr<Session> const& session, SurfaceId surface, shell::SurfaceSpecification const& modifications) = 0;
461 virtual void destroy_surface(std::shared_ptr<Session> const& session, SurfaceId surface) = 0;
462+ virtual std::string persistent_id_for(std::shared_ptr<Session> const& session, SurfaceId surface) = 0;
463
464 virtual int set_surface_attribute(
465 std::shared_ptr<Session> const& session,
466
467=== modified file 'src/protobuf/mir_protobuf.proto'
468--- src/protobuf/mir_protobuf.proto 2015-06-05 06:35:11 +0000
469+++ src/protobuf/mir_protobuf.proto 2015-06-09 07:21:31 +0000
470@@ -85,6 +85,12 @@
471 required int32 value = 1;
472 };
473
474+message PersistentSurfaceId {
475+ optional string value = 1;
476+
477+ optional string error = 127;
478+};
479+
480 message BufferStreamId {
481 required int32 value = 1;
482 };
483@@ -321,7 +327,10 @@
484 rpc start_prompt_session(PromptSessionParameters) returns (Void);
485 rpc stop_prompt_session(Void) returns (Void);
486 rpc exchange_buffer(BufferRequest) returns (Buffer);
487+
488 rpc submit_buffer(BufferRequest) returns (Void);
489+
490+ rpc request_persistent_surface_id(SurfaceId) returns (PersistentSurfaceId);
491 }
492
493 message CoordinateTranslationRequest {
494
495=== modified file 'src/server/CMakeLists.txt'
496--- src/server/CMakeLists.txt 2015-05-19 21:34:34 +0000
497+++ src/server/CMakeLists.txt 2015-06-09 07:21:31 +0000
498@@ -76,6 +76,7 @@
499 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}
500 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}
501 ${GLIB_LDFLAGS} ${GLIB_LIBRARIES}
502+ ${UUID_LDFLAGS} ${UUID_LIBRARIES}
503 )
504
505 set(MIR_SERVER_OBJECTS ${MIR_SERVER_OBJECTS} PARENT_SCOPE)
506@@ -101,6 +102,7 @@
507 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}
508 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}
509 ${GLIB_LDFLAGS} ${GLIB_LIBRARIES}
510+ ${UUID_LDFLAGS} ${UUID_LIBRARIES}
511 )
512
513 install(TARGETS mirserver
514
515=== modified file 'src/server/frontend/protobuf_message_processor.cpp'
516--- src/server/frontend/protobuf_message_processor.cpp 2015-06-08 13:43:27 +0000
517+++ src/server/frontend/protobuf_message_processor.cpp 2015-06-09 07:21:31 +0000
518@@ -312,6 +312,10 @@
519 report->exception_handled(display_server.get(), invocation.id(), err);
520 }
521 }
522+ else if ("request_persistent_surface_id" == invocation.method_name())
523+ {
524+ invoke(this, display_server.get(), &protobuf::DisplayServer::request_persistent_surface_id, invocation);
525+ }
526 else
527 {
528 report->unknown_method(display_server.get(), invocation.id(), invocation.method_name());
529
530=== modified file 'src/server/frontend/session_mediator.cpp'
531--- src/server/frontend/session_mediator.cpp 2015-06-05 08:29:23 +0000
532+++ src/server/frontend/session_mediator.cpp 2015-06-09 07:21:31 +0000
533@@ -878,6 +878,24 @@
534 done->Run();
535 }
536
537+void mf::SessionMediator::request_persistent_surface_id(
538+ ::google::protobuf::RpcController*,
539+ mir::protobuf::SurfaceId const* request,
540+ mir::protobuf::PersistentSurfaceId* response,
541+ google::protobuf::Closure* done)
542+{
543+ auto const session = weak_session.lock();
544+
545+ if (!session)
546+ BOOST_THROW_EXCEPTION(std::logic_error("Invalid application session"));
547+
548+ auto buffer = shell->persistent_id_for(session, mf::SurfaceId{request->value()});
549+
550+ *response->mutable_value() = std::string{buffer.begin(), buffer.end()};
551+
552+ done->Run();
553+}
554+
555 void mf::SessionMediator::pack_protobuf_buffer(
556 protobuf::Buffer& protobuf_buffer,
557 graphics::Buffer* graphics_buffer,
558
559=== modified file 'src/server/frontend/session_mediator.h'
560--- src/server/frontend/session_mediator.h 2015-06-01 14:37:00 +0000
561+++ src/server/frontend/session_mediator.h 2015-06-09 07:21:31 +0000
562@@ -214,6 +214,12 @@
563 ::mir::protobuf::CoordinateTranslationResponse* response,
564 ::google::protobuf::Closure *done) override;
565
566+ void request_persistent_surface_id(
567+ ::google::protobuf::RpcController* controller,
568+ ::mir::protobuf::SurfaceId const* request,
569+ ::mir::protobuf::PersistentSurfaceId* response,
570+ ::google::protobuf::Closure* done) override;
571+
572 private:
573 void pack_protobuf_buffer(protobuf::Buffer& protobuf_buffer,
574 graphics::Buffer* graphics_buffer,
575
576=== modified file 'src/server/frontend/shell_wrapper.cpp'
577--- src/server/frontend/shell_wrapper.cpp 2015-04-08 07:51:14 +0000
578+++ src/server/frontend/shell_wrapper.cpp 2015-06-09 07:21:31 +0000
579@@ -68,6 +68,11 @@
580 wrapped->destroy_surface(session, surface);
581 }
582
583+std::string mf::ShellWrapper::persistent_id_for(std::shared_ptr<Session> const& session, mf::SurfaceId surface)
584+{
585+ return wrapped->persistent_id_for(session, surface);
586+}
587+
588 int mf::ShellWrapper::set_surface_attribute(
589 std::shared_ptr<Session> const& session,
590 SurfaceId surface_id,
591
592=== modified file 'src/server/frontend/shell_wrapper.h'
593--- src/server/frontend/shell_wrapper.h 2015-04-08 07:51:14 +0000
594+++ src/server/frontend/shell_wrapper.h 2015-06-09 07:21:31 +0000
595@@ -57,6 +57,8 @@
596
597 void destroy_surface(std::shared_ptr<Session> const& session, SurfaceId surface) override;
598
599+ std::string persistent_id_for(std::shared_ptr<Session> const& session, SurfaceId surface) override;
600+
601 int set_surface_attribute(
602 std::shared_ptr<Session> const& session,
603 SurfaceId surface_id,
604
605=== modified file 'src/server/shell/CMakeLists.txt'
606--- src/server/shell/CMakeLists.txt 2015-05-19 21:42:19 +0000
607+++ src/server/shell/CMakeLists.txt 2015-06-09 07:21:31 +0000
608@@ -10,6 +10,8 @@
609 default_window_manager.cpp
610 shell_wrapper.cpp
611 surface_ready_observer.cpp
612+ default_persistent_surface_store.cpp
613+ persistent_surface_store.cpp
614 )
615
616 add_library(
617
618=== modified file 'src/server/shell/default_configuration.cpp'
619--- src/server/shell/default_configuration.cpp 2015-05-19 21:34:34 +0000
620+++ src/server/shell/default_configuration.cpp 2015-06-09 07:21:31 +0000
621@@ -24,6 +24,7 @@
622 #include "mir/shell/canonical_window_manager.h"
623 #include "mir/input/composite_event_filter.h"
624 #include "mir/shell/abstract_shell.h"
625+#include "default_persistent_surface_store.h"
626 #include "frontend_shell.h"
627 #include "graphics_display_layout.h"
628
629@@ -62,12 +63,22 @@
630 return wrapped;
631 }
632
633+std::shared_ptr<msh::PersistentSurfaceStore>
634+mir::DefaultServerConfiguration::the_persistent_surface_store()
635+{
636+ return surface_store([]()
637+ {
638+ return std::make_shared<msh::DefaultPersistentSurfaceStore>();
639+ });
640+}
641+
642 std::shared_ptr<mf::Shell>
643 mir::DefaultServerConfiguration::the_frontend_shell()
644 {
645 return frontend_shell([this]
646 {
647- return std::make_shared<msh::detail::FrontendShell>(the_shell());
648+ return std::make_shared<msh::detail::FrontendShell>(the_shell(),
649+ the_persistent_surface_store());
650 });
651 }
652
653
654=== added file 'src/server/shell/default_persistent_surface_store.cpp'
655--- src/server/shell/default_persistent_surface_store.cpp 1970-01-01 00:00:00 +0000
656+++ src/server/shell/default_persistent_surface_store.cpp 2015-06-09 07:21:31 +0000
657@@ -0,0 +1,91 @@
658+/*
659+ * Copyright © 2015 Canonical Ltd.
660+ *
661+ * This program is free software: you can redistribute it and/or modify
662+ * it under the terms of the GNU General Public License version 3 as
663+ * published by the Free Software Foundation.
664+ *
665+ * This program is distributed in the hope that it will be useful,
666+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
667+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
668+ * GNU General Public License for more details.
669+ *
670+ * You should have received a copy of the GNU General Public License
671+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
672+ *
673+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
674+ */
675+
676+#include "default_persistent_surface_store.h"
677+#include <uuid/uuid.h>
678+#include <algorithm>
679+#include <unordered_map>
680+#include <boost/throw_exception.hpp>
681+
682+namespace msh = mir::shell;
683+namespace ms = mir::scene;
684+
685+class msh::DefaultPersistentSurfaceStore::SurfaceIdBimap
686+{
687+public:
688+ using Id = PersistentSurfaceStore::Id;
689+
690+ Id const& insert_or_retrieve(std::shared_ptr<scene::Surface> const& surface);
691+
692+ std::shared_ptr<scene::Surface> operator[](Id const& id) const;
693+ Id const& operator[](std::shared_ptr<scene::Surface> const& surface) const;
694+
695+private:
696+ std::unordered_map<Id, std::weak_ptr<scene::Surface>> id_to_surface;
697+ std::unordered_map<scene::Surface const*, Id const*> surface_to_id;
698+};
699+
700+auto msh::DefaultPersistentSurfaceStore::SurfaceIdBimap::insert_or_retrieve(std::shared_ptr<scene::Surface> const& surface)
701+ -> Id const&
702+{
703+ auto element = surface_to_id.insert(std::make_pair(surface.get(), nullptr));
704+ if (!element.second)
705+ {
706+ // Surface already exists in our map, return it.
707+ return *element.first->second;
708+ }
709+ else
710+ {
711+ auto new_element = id_to_surface.insert(std::make_pair(Id{}, surface));
712+ // Update the surface_to_id map element from nullptr to our new Id
713+ element.first->second = &new_element.first->first;
714+ return *element.first->second;
715+ }
716+}
717+
718+auto msh::DefaultPersistentSurfaceStore::SurfaceIdBimap::operator[](std::shared_ptr<scene::Surface> const& surface) const
719+ -> Id const&
720+{
721+ return *surface_to_id.at(surface.get());
722+}
723+
724+auto msh::DefaultPersistentSurfaceStore::SurfaceIdBimap::operator[](Id const& id) const
725+ -> std::shared_ptr<scene::Surface>
726+{
727+ return id_to_surface.at(id).lock();
728+}
729+
730+msh::DefaultPersistentSurfaceStore::DefaultPersistentSurfaceStore()
731+ : store{std::make_unique<SurfaceIdBimap>()}
732+{
733+}
734+
735+msh::DefaultPersistentSurfaceStore::~DefaultPersistentSurfaceStore()
736+{
737+}
738+
739+auto msh::DefaultPersistentSurfaceStore::id_for_surface(std::shared_ptr<scene::Surface> const& surface)
740+ -> Id
741+{
742+ return store->insert_or_retrieve(surface);
743+}
744+
745+std::shared_ptr<ms::Surface> msh::DefaultPersistentSurfaceStore::surface_for_id(Id const& id) const
746+{
747+ return (*store)[id];
748+}
749
750=== added file 'src/server/shell/default_persistent_surface_store.h'
751--- src/server/shell/default_persistent_surface_store.h 1970-01-01 00:00:00 +0000
752+++ src/server/shell/default_persistent_surface_store.h 2015-06-09 07:21:31 +0000
753@@ -0,0 +1,44 @@
754+/*
755+ * Copyright © 2015 Canonical Ltd.
756+ *
757+ * This program is free software: you can redistribute it and/or modify
758+ * it under the terms of the GNU General Public License version 3 as
759+ * published by the Free Software Foundation.
760+ *
761+ * This program is distributed in the hope that it will be useful,
762+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
763+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
764+ * GNU General Public License for more details.
765+ *
766+ * You should have received a copy of the GNU General Public License
767+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
768+ *
769+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
770+ */
771+
772+#ifndef MIR_SHELL_DEFAULT_PERSISTENT_SURFACE_STORE_H_
773+#define MIR_SHELL_DEFAULT_PERSISTENT_SURFACE_STORE_H_
774+
775+#include "mir/shell/persistent_surface_store.h"
776+
777+namespace mir
778+{
779+namespace shell
780+{
781+class DefaultPersistentSurfaceStore : public PersistentSurfaceStore
782+{
783+public:
784+ DefaultPersistentSurfaceStore();
785+ ~DefaultPersistentSurfaceStore() override;
786+
787+ Id id_for_surface(std::shared_ptr<scene::Surface> const& surface) override;
788+ std::shared_ptr<scene::Surface> surface_for_id(Id const& id) const override;
789+
790+private:
791+ class SurfaceIdBimap;
792+ std::unique_ptr<SurfaceIdBimap> const store;
793+};
794+}
795+}
796+
797+#endif // MIR_SHELL_DEFAULT_PERSISTENT_SURFACE_STORE_H_
798
799=== modified file 'src/server/shell/frontend_shell.cpp'
800--- src/server/shell/frontend_shell.cpp 2015-04-08 07:51:14 +0000
801+++ src/server/shell/frontend_shell.cpp 2015-06-09 07:21:31 +0000
802@@ -17,6 +17,7 @@
803 */
804
805 #include "frontend_shell.h"
806+#include "mir/shell/persistent_surface_store.h"
807 #include "mir/shell/shell.h"
808
809 #include "mir/scene/session.h"
810@@ -93,6 +94,14 @@
811 wrapped->destroy_surface(scene_session, surface);
812 }
813
814+std::string msh::FrontendShell::persistent_id_for(std::shared_ptr<mf::Session> const& session, mf::SurfaceId surface_id)
815+{
816+ auto const scene_session = std::dynamic_pointer_cast<ms::Session>(session);
817+ auto const surface = scene_session->surface(surface_id);
818+
819+ return surface_store->id_for_surface(surface).serialize_to_string();
820+}
821+
822 int msh::FrontendShell::set_surface_attribute(
823 std::shared_ptr<mf::Session> const& session,
824 mf::SurfaceId surface_id,
825
826=== modified file 'src/server/shell/frontend_shell.h'
827--- src/server/shell/frontend_shell.h 2015-04-08 07:51:14 +0000
828+++ src/server/shell/frontend_shell.h 2015-06-09 07:21:31 +0000
829@@ -29,6 +29,7 @@
830 namespace shell
831 {
832 class Shell;
833+class PersistentSurfaceStore;
834
835 namespace detail
836 {
837@@ -36,9 +37,14 @@
838 struct FrontendShell : mf::Shell
839 {
840 std::shared_ptr<shell::Shell> const wrapped;
841+ std::shared_ptr<shell::PersistentSurfaceStore> const surface_store;
842
843- explicit FrontendShell(std::shared_ptr<shell::Shell> const& wrapped) :
844- wrapped{wrapped} {}
845+ explicit FrontendShell(std::shared_ptr<shell::Shell> const& wrapped,
846+ std::shared_ptr<shell::PersistentSurfaceStore> const& surface_store)
847+ : wrapped{wrapped},
848+ surface_store{surface_store}
849+ {
850+ }
851
852 std::shared_ptr<mf::Session> open_session(
853 pid_t client_pid,
854@@ -63,6 +69,8 @@
855
856 void destroy_surface(std::shared_ptr<mf::Session> const& session, mf::SurfaceId surface) override;
857
858+ std::string persistent_id_for(std::shared_ptr<mf::Session> const& session, mf::SurfaceId surface) override;
859+
860 int set_surface_attribute(
861 std::shared_ptr<mf::Session> const& session,
862 mf::SurfaceId surface_id,
863
864=== added file 'src/server/shell/persistent_surface_store.cpp'
865--- src/server/shell/persistent_surface_store.cpp 1970-01-01 00:00:00 +0000
866+++ src/server/shell/persistent_surface_store.cpp 2015-06-09 07:21:31 +0000
867@@ -0,0 +1,79 @@
868+/*
869+ * Copyright © 2015 Canonical Ltd.
870+ *
871+ * This program is free software: you can redistribute it and/or modify
872+ * it under the terms of the GNU General Public License version 3 as
873+ * published by the Free Software Foundation.
874+ *
875+ * This program is distributed in the hope that it will be useful,
876+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
877+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
878+ * GNU General Public License for more details.
879+ *
880+ * You should have received a copy of the GNU General Public License
881+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
882+ *
883+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
884+ */
885+
886+#include "mir/shell/persistent_surface_store.h"
887+
888+#include <boost/throw_exception.hpp>
889+
890+namespace msh = mir::shell;
891+using Id = mir::shell::PersistentSurfaceStore::Id;
892+
893+Id::Id()
894+{
895+ uuid_generate(uuid);
896+}
897+
898+Id::Id(std::string const& serialized_form)
899+{
900+ using namespace std::literals::string_literals;
901+ if (serialized_form.size() != 36)
902+ {
903+ BOOST_THROW_EXCEPTION((std::invalid_argument{"Failed to parse: "s + serialized_form +
904+ " (has invalid length: "s + std::to_string(serialized_form.size()) +
905+ " expected 36)"}));
906+ }
907+
908+ if (uuid_parse(serialized_form.c_str(), uuid) != 0)
909+ {
910+ BOOST_THROW_EXCEPTION((std::invalid_argument{"Failed to parse: "s + serialized_form}));
911+ }
912+}
913+
914+Id::Id(Id const& rhs)
915+{
916+ std::copy(rhs.uuid, rhs.uuid + sizeof(rhs.uuid), uuid);
917+}
918+
919+Id& Id::operator=(Id const& rhs)
920+{
921+ std::copy(rhs.uuid, rhs.uuid + sizeof(rhs.uuid), uuid);
922+ return *this;
923+}
924+
925+bool Id::operator==(Id const& rhs) const
926+{
927+ return uuid_compare(uuid, rhs.uuid) == 0;
928+}
929+
930+std::string Id::serialize_to_string() const
931+{
932+ // uuid_unparse adds a trailing null; allocate enough memory for it...
933+ char buffer[37];
934+ uuid_unparse(uuid, buffer);
935+
936+ return buffer;
937+}
938+
939+auto std::hash<Id>::operator()(argument_type const &uuid) const
940+ -> result_type
941+{
942+ // uuid_t is defined in the header to be a char[16]; hash this as two 64-byte integers
943+ // and mix the result with XOR.
944+ return std::hash<uint64_t>()(*reinterpret_cast<uint64_t const*>(uuid.uuid)) ^
945+ std::hash<uint64_t>()(*reinterpret_cast<uint64_t const*>(uuid.uuid + sizeof(uint64_t)));
946+}
947
948=== modified file 'src/server/symbols.map'
949--- src/server/symbols.map 2015-06-02 13:41:40 +0000
950+++ src/server/symbols.map 2015-06-09 07:21:31 +0000
951@@ -581,6 +581,7 @@
952 mir::DefaultServerConfiguration::the_mediating_display_changer*;
953 mir::DefaultServerConfiguration::the_message_processor_report*;
954 mir::DefaultServerConfiguration::the_options*;
955+ mir::DefaultServerConfiguration::the_persistent_surface_store*;
956 mir::DefaultServerConfiguration::the_pixel_buffer*;
957 mir::DefaultServerConfiguration::the_placement_strategy*;
958 mir::DefaultServerConfiguration::the_prompt_connection_creator*;
959
960=== modified file 'tests/acceptance-tests/test_client_library.cpp'
961--- tests/acceptance-tests/test_client_library.cpp 2015-05-27 11:37:38 +0000
962+++ tests/acceptance-tests/test_client_library.cpp 2015-06-09 07:21:31 +0000
963@@ -885,3 +885,23 @@
964 mir_surface_release_sync(surface);
965 mir_connection_release(connection);
966 }
967+
968+TEST_F(ClientLibrary, can_get_persistent_surface_id)
969+{
970+ auto connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
971+
972+ auto surface_spec = mir_connection_create_spec_for_normal_surface(connection,
973+ 800, 600,
974+ mir_pixel_format_argb_8888);
975+ auto surface = mir_surface_create_sync(surface_spec);
976+ mir_surface_spec_release(surface_spec);
977+
978+ ASSERT_THAT(surface, IsValid());
979+
980+ auto surface_id = mir_surface_request_persistent_id_sync(surface);
981+ EXPECT_TRUE(mir_persistent_id_is_valid(surface_id));
982+
983+ mir_surface_release_sync(surface);
984+ mir_persistent_id_release(surface_id);
985+ mir_connection_release(connection);
986+}
987
988=== modified file 'tests/include/mir_test_doubles/mock_shell.h'
989--- tests/include/mir_test_doubles/mock_shell.h 2015-04-08 07:51:14 +0000
990+++ tests/include/mir_test_doubles/mock_shell.h 2015-06-09 07:21:31 +0000
991@@ -54,6 +54,8 @@
992 MOCK_METHOD2(create_surface, frontend::SurfaceId(std::shared_ptr<frontend::Session> const&, scene::SurfaceCreationParameters const& params));
993 MOCK_METHOD3(modify_surface, void(std::shared_ptr<frontend::Session> const&, frontend::SurfaceId, shell::SurfaceSpecification const&));
994 MOCK_METHOD2(destroy_surface, void(std::shared_ptr<frontend::Session> const&, frontend::SurfaceId));
995+ MOCK_METHOD2(persistent_id_for, std::string(std::shared_ptr<frontend::Session> const&, frontend::SurfaceId));
996+
997
998 MOCK_METHOD4(set_surface_attribute, int(
999 std::shared_ptr<frontend::Session> const& session, frontend::SurfaceId surface_id,
1000
1001=== modified file 'tests/unit-tests/shell/CMakeLists.txt'
1002--- tests/unit-tests/shell/CMakeLists.txt 2015-03-31 02:35:42 +0000
1003+++ tests/unit-tests/shell/CMakeLists.txt 2015-06-09 07:21:31 +0000
1004@@ -2,6 +2,8 @@
1005 ${CMAKE_CURRENT_SOURCE_DIR}/test_default_placement_strategy.cpp
1006 ${CMAKE_CURRENT_SOURCE_DIR}/test_default_window_manager.cpp
1007 ${CMAKE_CURRENT_SOURCE_DIR}/test_graphics_display_layout.cpp
1008+ ${CMAKE_CURRENT_SOURCE_DIR}/test_persistent_surface_store_id.cpp
1009+ ${CMAKE_CURRENT_SOURCE_DIR}/test_default_persistent_surface_store.cpp
1010 )
1011
1012 set(
1013
1014=== added file 'tests/unit-tests/shell/test_default_persistent_surface_store.cpp'
1015--- tests/unit-tests/shell/test_default_persistent_surface_store.cpp 1970-01-01 00:00:00 +0000
1016+++ tests/unit-tests/shell/test_default_persistent_surface_store.cpp 2015-06-09 07:21:31 +0000
1017@@ -0,0 +1,124 @@
1018+/*
1019+ * Copyright © 2015 Canonical Ltd.
1020+ *
1021+ * This program is free software: you can redistribute it and/or modify
1022+ * it under the terms of the GNU General Public License version 3 as
1023+ * published by the Free Software Foundation.
1024+ *
1025+ * This program is distributed in the hope that it will be useful,
1026+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1027+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1028+ * GNU General Public License for more details.
1029+ *
1030+ * You should have received a copy of the GNU General Public License
1031+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1032+ *
1033+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1034+ */
1035+
1036+#include "mir/shell/persistent_surface_store.h"
1037+
1038+#include "mir/scene/surface.h"
1039+
1040+#include "src/server/shell/default_persistent_surface_store.h"
1041+
1042+#include "mir_test_doubles/mock_surface.h"
1043+
1044+
1045+#include <gmock/gmock.h>
1046+#include <gtest/gtest.h>
1047+
1048+namespace ms = mir::scene;
1049+namespace msh = mir::shell;
1050+namespace mtd = mir::test::doubles;
1051+
1052+TEST(DefaultPersistentSurfaceStore, id_for_surface_is_idempotent)
1053+{
1054+ using namespace testing;
1055+
1056+ msh::DefaultPersistentSurfaceStore store;
1057+
1058+ auto surface = std::make_shared<NiceMock<mtd::MockSurface>>();
1059+
1060+ auto id_one = store.id_for_surface(surface);
1061+ auto id_two = store.id_for_surface(surface);
1062+
1063+ EXPECT_THAT(id_one, Eq(std::ref(id_two)));
1064+}
1065+
1066+TEST(DefaultPersistentSurfaceStore, id_is_stable_under_aliasing)
1067+{
1068+ using namespace testing;
1069+
1070+ msh::DefaultPersistentSurfaceStore store;
1071+
1072+ auto surface = std::make_shared<NiceMock<mtd::MockSurface>>();
1073+ auto surface_alias = surface;
1074+
1075+ auto id_one = store.id_for_surface(surface);
1076+ auto id_two = store.id_for_surface(surface_alias);
1077+
1078+ EXPECT_THAT(id_one, Eq(std::ref(id_two)));
1079+}
1080+
1081+TEST(DefaultPersistentSurfaceStore, can_lookup_surface_by_id)
1082+{
1083+ using namespace testing;
1084+
1085+ msh::DefaultPersistentSurfaceStore store;
1086+
1087+ auto surface = std::make_shared<NiceMock<mtd::MockSurface>>();
1088+
1089+ auto id = store.id_for_surface(surface);
1090+
1091+ auto looked_up_surface = store.surface_for_id(id);
1092+
1093+ EXPECT_THAT(looked_up_surface, Eq(surface));
1094+}
1095+
1096+TEST(DefaultPersistentSurfaceStore, retrieves_correct_surface)
1097+{
1098+ using namespace testing;
1099+
1100+ msh::DefaultPersistentSurfaceStore store;
1101+
1102+ auto surface_one = std::make_shared<NiceMock<mtd::MockSurface>>();
1103+ auto surface_two = std::make_shared<NiceMock<mtd::MockSurface>>();
1104+
1105+ auto id_one = store.id_for_surface(surface_one);
1106+ auto id_two = store.id_for_surface(surface_two);
1107+
1108+ auto looked_up_surface_one = store.surface_for_id(id_one);
1109+ auto looked_up_surface_two = store.surface_for_id(id_two);
1110+
1111+ EXPECT_THAT(looked_up_surface_one, Eq(surface_one));
1112+ EXPECT_THAT(looked_up_surface_two, Eq(surface_two));
1113+}
1114+
1115+TEST(DefaultPersistentSurfaceStore, looking_up_destroyed_surface_returns_nullptr)
1116+{
1117+ using namespace testing;
1118+
1119+ msh::DefaultPersistentSurfaceStore store;
1120+
1121+ auto surface = std::make_shared<NiceMock<mtd::MockSurface>>();
1122+
1123+ auto id = store.id_for_surface(surface);
1124+
1125+ surface.reset();
1126+
1127+ auto looked_up_surface = store.surface_for_id(id);
1128+
1129+ EXPECT_THAT(looked_up_surface, Eq(nullptr));
1130+}
1131+
1132+TEST(DefaultPersistentSurfaceStore, looking_up_nonexistent_surface_throws)
1133+{
1134+ using namespace testing;
1135+
1136+ msh::DefaultPersistentSurfaceStore store;
1137+
1138+ msh::PersistentSurfaceStore::Id id;
1139+
1140+ EXPECT_THROW(store.surface_for_id(id), std::out_of_range);
1141+}
1142
1143=== added file 'tests/unit-tests/shell/test_persistent_surface_store_id.cpp'
1144--- tests/unit-tests/shell/test_persistent_surface_store_id.cpp 1970-01-01 00:00:00 +0000
1145+++ tests/unit-tests/shell/test_persistent_surface_store_id.cpp 2015-06-09 07:21:31 +0000
1146@@ -0,0 +1,88 @@
1147+/*
1148+ * Copyright © 2015 Canonical Ltd.
1149+ *
1150+ * This program is free software: you can redistribute it and/or modify
1151+ * it under the terms of the GNU General Public License version 3 as
1152+ * published by the Free Software Foundation.
1153+ *
1154+ * This program is distributed in the hope that it will be useful,
1155+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1156+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1157+ * GNU General Public License for more details.
1158+ *
1159+ * You should have received a copy of the GNU General Public License
1160+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1161+ *
1162+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
1163+ */
1164+
1165+#include "mir/shell/persistent_surface_store.h"
1166+
1167+#include <gmock/gmock.h>
1168+#include <gtest/gtest.h>
1169+
1170+namespace ms = mir::scene;
1171+namespace msh = mir::shell;
1172+//namespace mtd = mir::test::doubles;
1173+
1174+using Id = msh::PersistentSurfaceStore::Id;
1175+
1176+TEST(PersistentSurfaceStoreId, deserialising_wildly_incorrect_buffer_raises_exception)
1177+{
1178+ EXPECT_THROW(Id{"bob"}, std::invalid_argument);
1179+}
1180+
1181+TEST(PersistentSurfaceStoreId, deserialising_invalid_buffer_raises_exception)
1182+{
1183+ // This is the right size, but isn't a UUID because it lacks the XX-XX-XX structure
1184+ EXPECT_THROW(Id{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, std::invalid_argument);
1185+}
1186+
1187+TEST(PersistentSurfaceStoreId, serialization_roundtrips_with_deserialization)
1188+{
1189+ using namespace testing;
1190+ Id first_id;
1191+
1192+ auto const buf = first_id.serialize_to_string();
1193+ Id const second_id{buf};
1194+
1195+ EXPECT_THAT(second_id, Eq(first_id));
1196+}
1197+
1198+TEST(PersistentSurfaceStoreId, ids_assigned_evaluate_equal)
1199+{
1200+ using namespace testing;
1201+
1202+ Id const first_id;
1203+
1204+ auto const second_id = first_id;
1205+
1206+ EXPECT_THAT(second_id, Eq(first_id));
1207+}
1208+
1209+TEST(PersistentSurfaceStoreId, equal_ids_hash_equally)
1210+{
1211+ using namespace testing;
1212+
1213+ auto const uuid_string = "0744caf3-c8d9-4483-a005-3375c1954287";
1214+
1215+ Id const first_id{uuid_string};
1216+ Id const second_id{uuid_string};
1217+
1218+ EXPECT_THAT(std::hash<Id>()(second_id), Eq(std::hash<Id>()(first_id)));
1219+}
1220+
1221+TEST(PersistentSurfaceStoreId, can_assign_ids)
1222+{
1223+ using namespace testing;
1224+
1225+ Id first_id;
1226+ Id second_id;
1227+
1228+ // Technically, there's a roughly 1-in-2^128 chance of a false fail here.
1229+ EXPECT_THAT(second_id, Not(Eq(first_id)));
1230+
1231+ second_id = first_id;
1232+
1233+ EXPECT_THAT(second_id, Eq(first_id));
1234+}

Subscribers

People subscribed via source and target branches