awesome icon indicating copy to clipboard operation
awesome copied to clipboard

Extend the clipboard API

Open Elv13 opened this issue 10 years ago • 14 comments

This issue is just some idea for some improvements to the clipboard selection API

  • The the selection buffer (aka, middle click paste buffer)
  • The the secondary clipboard buffer
  • Have a signal then the clipboard selection change
  • Be able to set the selection
  • Get the mime type(s)
  • Be asynchronous

I had an xsel command wrapper at some point to do this, but a native API would be nicer. This would also make possible to have a clipboard overview widget. I might do this at some point, but if anyone have other ideas, please comment here.

Elv13 avatar Oct 01 '15 15:10 Elv13

Can the API be asynchronous? This selections stuff just isn't synchronous and currently selection() kind-of starts its own main loop (but ignores everything except the XCB connection). Events are still handled normally and so lua could call selection() again in response to some other event. The result won't be pretty (it would be good if only the responses were mixed up; I guess instead this will never return to the normal main loop).

psychon avatar Oct 01 '15 15:10 psychon

Ok, I updated the description, if you have an idea of what has to be (technically) done, feel free to elaborate, you know XCB better than me

Elv13 avatar Oct 01 '15 15:10 Elv13

+1

cj1324 avatar Oct 22 '15 07:10 cj1324

@cj1324 What exactly do you want from the clipboard API? What's missing?

psychon avatar Oct 22 '15 08:10 psychon

Special conditions?

https://code.google.com/p/chromium/issues/detail?id=395077 https://communities.vmware.com/thread/481425

Temporary use xsel

cj1324 avatar Oct 22 '15 09:10 cj1324

I took a quick look at https://tronche.com/gui/x/icccm/sec-2.html and I already do not want to work on this anymore... Anyway, what should the API on the Lua-side look like? I propose some new selection object that can be used to request a selection. That way things can be asynchronous (signals can be emitted on the object) and I have something to store some state on in the C side. @Elv13 can you come up with some example code showing how things could work like? ICCCM specifies so much stuff that I am not sure how much of that is useful.

From you list in the initial posting here:

The the selection buffer (aka, middle click paste buffer) The the secondary clipboard buffer

So, just allow a "random name" to be given as the name of the selection? Can't be more generic than that.

Have a signal then the clipboard selection change

As far as I see, X11 does not allow to do this?!?

Edit: The XFIXES extension seems to allow this, it adds selection tracking: https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt

Be able to set the selection

Hm. Ok, but I guess that can be done with a different API then. How about having this issue for querying the selection and someone opens a new issue for an API to set the selection.

Get the mime type(s)

I do not see any MIME types in the protocol.

psychon avatar Jan 07 '17 12:01 psychon

Some random attempt at playing with selections: https://gist.github.com/psychon/91a1456eb86fca7735b1c988cbeaa138 (This prints some information about the CLIPBOARD selection to figure out how some things work) (Oh and I still do not like the INCR protocol)

psychon avatar Jan 07 '17 18:01 psychon

@Elv13 can you come up with some example code showing how things could work like?

Some more data point on this:

GTK looked at ICCCM and designed their API around it. The result is horrible. Qt designed a sane API and then made it work with ICCCM. The result looks (at first sight) sane. I want to avoid doing what GTK did, hence the question above (also, @Elv13 might have the best idea of what is meant exactly with "mime type support").

psychon avatar Jan 08 '17 17:01 psychon

Sorry, I forgot to answer this one yesterday.

So, just allow a "random name" to be given as the name of the selection? Can't be more generic than that.

I am not sure I understand you here. X11 has 3 selection buffers (clipboard, primary/selection and secondary) See https://linux.die.net/man/1/xsel they need to be exposed

Have a signal then the clipboard selection change

Klipper and other managers supports it, I don't think the use polling.

(also, @Elv13 might have the best idea of what is meant exactly with "mime type support").

The best example is to copy and paste something using ctrl+shirt+v in LibreOffice (or read the Qt doc about it). Each FreeDesktop clipboard content can contain multiple MIME payloads. For example, copy/pasting a contact in Evolution/KMail can paste the vCard or mailto-URLlist. In Gedit/Kate/GVim, it will paste an ACSII formated sumary and in LibreOffice an HTML one (this is an example, it doesn't behave exactly like that IRL). This works because the clipboard has multiple MIME payloads for the same content. The same is done for drag and drop and emails.

As for the low level API, it doesn't have to be extraordinary. A clipboard manager can be an higher level component (written later on)

  • Get and set the selection (per MIME type)
  • Be notified of changes
  • Have a C cache for the raw content (we don't want to do an extra lua copy by default, it wont scale and makes no sense)
    • That cache should be able to store mementos of old selection if manually requested
    • I think the selection memory doesn't have to belong to Awesome for the "live" selection
    • I don't know how feasible that would be in practice, but could it be done an a thread to the Lua API is async (either using coroutines or callbacks)

It could also be nicer and use a luaobject class to reference individual selections in Lua and use its reference counting/GC to free it from the cache (if #ref > 0 { auto_cache_in_worker_thead } else {free_if_cached}), but those would take longer to implement.

The "goal" of such an API would be to make it possible to implement well integrated clipboard management into Awesome.

  • It has to be "fast" so it doesn't slow Awesome down
  • It has to be possible to see the current selection (or a subset of it) without enormous memory copies (when avoidable, such as multi MIME)
  • It has to be able to store and swap previous selections
  • Swap the clipboard and primary/selection buffers
  • Knows the application that "owns" the current selections (the client, if possible)

I know the most we dig, the more work it is, but it's also the last big building block (and obviously also the least important of the big ones) to "complete" CAPI. After that (and dropping capi.dbus), I don't really see what else could be left to implement "whatever you can think of" (well, ok, xmodmap style APIs and gestures, but xmodmap/setxkbmap itself does a better job than whatever we could come up with).

Elv13 avatar Jan 08 '17 22:01 Elv13

Have a C cache for the raw content (we don't want to do an extra lua copy by default, it wont scale and makes no sense)

Why? Sooner or later, the selection content will have to be handed to Lua. If Lua didn't want the content, it should not have requested it in the first place.

That cache should be able to store mementos of old selection if manually requested

Can't that be implemented in Lua? I would think for the C API it only makes sense to expose the current selection.

It has to be able to store and swap previous selections Swap the clipboard and primary/selection buffers

Huh? Why would one need a special API for this? Can't that be implemented ontop of some "low-level" API. And no, I do not know of any tricks to make this go fast.


So, API-wise I would think (rough thinking here):

local sel = selection("PRIMARY")

We get an object representing the primary selection.

sel:connect_signal("changed", function() print("The primary selection changed") end)

This one should be clear: Callback when the selection owner changes.

local mimes = sel:available_mime_types()

Gets the available mime types for the PRIMARY selection.

local type = "text/plain"
if mimes[type] then
  obj:request(type, callback)
else
  print("Mime type " .. type .. " is not available")
end

Calls callback which the raw content of the selection.

"Should not slow down awesome"-wise: All of these require at most one round-trip with the X11 server. The only complicated operation uses a callback (guess which one that is! :-) ).


I wanted it to be in another issue, but ok, setting the selection:

selection:take_ownership(callback, "text/plain")

Takes ownership of the selection. The callback is used to query data (single argument: the mime type; single return value: String containing the data for the selection). All other arguments are the provided mime types.


TODO:

  • [ ] Error handling? Do we want e.g. some timeouts applied in case something goes wrong (e.g. selection owner crashes while we are transferring something)

psychon avatar Jan 09 '17 10:01 psychon

Huh? Why would one need a special API for this? Can't that be implemented ontop of some "low-level" API. And no, I do not know of any tricks to make this go fast.

My fear may be unfounded. The use case for this is a plugin that auto-cache the ~8 previous selections and allows them to be used later on (such as a "Klipper like" [1] managers). In 99% of cases, it wont be used. In those case, the copy to lua is unnecessary. The content can be cached in a "luaobject", but never accessed (no GC overhead beside the luaobject userdata). This requires a single copy and no lua VM locks (beside pushing the luaobject userdata into the stack and emitting the signal). A second copy will only happen if the content is used.

In case only a low level "flat" API is exposed, then Lua has to get its own copy of everything just in case it would be used.

Also, a luaobject based API where you have an userdata object for each selection allows to implement the "C cache" later on if my fears turn out to be justified while a low level Lua API wont (without breaking it).

[1] Klipper is just an example, there is hundreds of identical apps for linux/osx/windows

Elv13 avatar Jan 10 '17 06:01 Elv13

I do not want to write my own "this is a chunk of data" userdata, because there are already too many of them. So I would have to use Glib.Bytes. This means Lua would have to make sure the reference counts are right, meaning the same "fun" that gears.surface hides for cairo surfaces. I do not think that's quite a good idea...

Sorry, but no.

If getting the data into Lua is the slow part of this, then something is odd. Just from the ICCCM, the data will already have been copied around six times before it reaches Lua (source application copies it "into" the X11 socket, the server reads it off the socket, copies it into a property of some window, copies it into the X11 socket of the requesting client, the client reads things off the socket and for INCR the client has to re-assemble all the small data pieces into one buffer), so one more copy will not hurt. Also, as far as I know, Lua is quite fast at interning strings. Also, the last re-assembling step could be done in Lua so that only a single copy is added (copy each individual piece of the data into a Lua string).

Besides this "might be too slow", any other comments on this API idea?

psychon avatar Jan 10 '17 08:01 psychon

Besides this "might be too slow", any other comments on this API idea?

It would be a good start. Also, it's hard to see the exact API until it exists. I understand your concerns about the luaobject. I am not convinced a flat API is better yet, quite the contrary, but you are right about the copy issue being overstated. I might have done too much signal processing and real time tasks on ridiculously under powered embedded systems. I have 2 modes "screw the performance" or "over optimize". I mostly practiced the "screw the performance" with my inefficient OOP Awesome APIs and abstractions (and metatable abuses) so far. Sorry for bringing the triple (additional) copy issue up, it's a bit inconsistent with my other PRs.

As for the "this is a chunk of data" concern, I would say it is unnecessary. A luaobject could keep it as char* + size_t without tracking references and free it once the luaobject is collected. If the lua side requests it, it will trigger a char*->"luastring" copy, but it would be rare and negligible.

So you can go ahead with the flat API. I may hack it up a little to see if I can turn it into an OOP one or just implement it in pure Lua on top of the flat one.

Elv13 avatar Jan 13 '17 11:01 Elv13

Sigh. After looking at https://github.com/kfish/xsel/blob/master/xsel.c, I'm once again demotivated by this complex protocol (leaving this link here so that I can look at it again later).

psychon avatar Feb 19 '17 10:02 psychon