Compare commits

...

277 commits
0.99.2 ... main

Author SHA1 Message Date
Peter Hutterer
e8994dfe17 util: add handling for an auto type
Because sometimes it's much easier and more readable, e.g.
 auto eis_device = eis_touchscreen_get_device(...)

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/373>
2026-01-07 22:24:43 +00:00
Peter Hutterer
180c9f2890 proto: add a blurb about events to send during stop_emulating
This should've been specified from day one but better late than never.
Since stop_emulating is supposed to signal termination of a client
emulating input, a logical assumption is that the device is set back to
neutral. Let's point it out that the real behavior is within EIS but
clients should behave predictably.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
2026-01-07 22:23:42 +00:00
Peter Hutterer
57064f6846 eis: end any ongoing touch in response to touch_end
Even if the client is no longer emulating (and we're not queuing an
event) our local touch must be ended. Otherwise our touch will live on
forever, despite the client thinking it has ended properly.

This can't be triggered with libei because we can't send touch events
while not emulating and the only way to get into this state is pausing
the device - which already resets the state. But let's add a test case
anyway, in the hope that one day it picks up a bug.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
2026-01-07 22:23:42 +00:00
Peter Hutterer
330b54d389 eis: keep track of touch IDs and don't allow duplicate ones
A client sending duplicate touch IDs will be disconnected but
motion or end events for touches that no longer exist will be silently
ignored.

The tracking state uses a uint64_t to store currently valid touch ids -
since the whole range of touch ids are uint32_t (including zero) this is
the simple way of using an out-of-range marker value (UINT64_MAX)

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
2026-01-07 22:23:42 +00:00
Peter Hutterer
84c23989e9 eis: quietly ignore double key presses and releases
This is only implemented on the EIS side of things because that side is
mostly what we want to protect (read: the compositors). The duplicates
are still sent on the protocol.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
2026-01-07 22:23:42 +00:00
Peter Hutterer
65c0b39c3e ei: match the EI_EVENT_DEVICE_PAUSED docs with the protocol
This is a behavior break if we're looking at the documentation only but
the protocol has required the logical reset of the device since libei
0.5 - this here is just stale documentation that didn't get updated.

And keeping the state across paused/resume is too hard to get right
anyway.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/368>
2026-01-07 22:23:41 +00:00
Peter Hutterer
9446918556 CI: update to Fedora 43
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/374>
2026-01-06 04:52:47 +00:00
Peter Hutterer
5f9e181073 proto: add support for requesting devices
Add support for a client to request the creation of a new device
from the EIS implementation. This is necessary in situations where the
devices created by the EIS implementation are not (or no longer)
suitable for the client to function correctly.

The primary use-case of this is the upcoming tablet tool support where a
client may need to create multiple tablet tools in response to a new
physical tool brought into proximity locally.

Other use-cases include a client closing a device but requiring that
device (or one with similar capabilities) later.

The implementation in libei is straightforward
- on the client side we have a new function to request the new device:
  ei_seat_request_device_with_capabilities()
- on the server side we have a new event EIS_EVENT_SEAT_DEVICE_REQUESTED
  that can make use of the existing eis_event_seat_has_capability() API

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/345>
2026-01-06 04:50:46 +00:00
Peter Hutterer
08d7d5918f util: free the test parameters after the run
And also ensure we have the null-termination even if
MUNIT_TEST_MAX_PARAMS are given.

Fixes: 2996a66b37 ("util: add support for parametrized tests")
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/372>
2025-12-16 00:32:21 +00:00
Peter Hutterer
d5198d0e53 proto: add a link to the online protocol docs
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/371>
2025-12-16 09:27:41 +10:00
Peter Hutterer
756f74ec73 test: declare the need_frame check as part of the context
Reproducible with something that produces a frame event:

   // queues e.g. pointer motion + frame
   peck_eis_dispatch_until_stable()
   with_server(peck) {
       // process the motion only
   }
   peck_eis_dispatch_until_stable()

The second peck_eis_dispatch_until_stable() triggers an assertion
because we still have the unhandled frame event pending but need_frame
was reset to false.

Keep this as a field in peck so it remembers across invocations.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/369>
2025-12-15 05:29:54 +00:00
Peter Hutterer
55381623f2 test: fix logic error checking for capabilities
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/370>
2025-12-15 11:48:41 +10:00
Peter Hutterer
73ec8dee9f eis: allow pausing the device when it is emulating
The whole point of pausing a device is to terminate the client's ability
to send events.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/367>
2025-12-15 10:51:16 +10:00
Peter Hutterer
fe47a0a1f7 Unref frame events when discarding an empty frame
Otherwise we leak that frame event.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/366>
2025-12-12 16:20:03 +10:00
Peter Hutterer
26e671f192 Swap the object id logging order
This gives us a same-sized prefix for messages, making visual parsing of
logs a bit easier.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/366>
2025-12-12 16:20:03 +10:00
Peter Hutterer
2996a66b37 util: add support for parametrized tests
munit supports test parameters (char* only) but the setup is a big
awkward/impossible in C to define statically. So lets simply pass the
parameters as one NULL-terminated string list and require names to be
prefixed with an @. During the test setup we can split that list up into
multiple parameters and pass those to the munit setup where they're
accessible via another wrapper macro:

MUNIT_TEST_WITH_PARAMS(test_foo, "@x", "1", "2", "@y", "100", "200") {
   const char *x = MUNIT_TEST_PARAM("@x");
   const char *y = MUNIT_TEST_PARAM("@y");
}

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/365>
2025-12-03 14:51:55 +10:00
Peter Hutterer
d55da9466c meson.build: apply consistent indentation
Mostly whitespace changes, the non-whitespace ones are adding trailing
commas on last arguments and moving the closing ) into a separate line
for multiline function calls.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/364>
2025-11-06 10:56:01 +10:00
Hongfei Shang
3f48d11958 tools: fix print for touch up in demo client
Signed-off-by: Hongfei Shang <shanghongfei@kylinos.cn>
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/363>
2025-11-05 11:31:38 +08:00
Peter Hutterer
2821282dc9 libei: include math.h for fmod
For correctness only, looks like math.h is already pulled in from
elsewhere anyway.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/362>
2025-11-03 15:27:38 +10:00
Jason Gerecke
acff519ac3 tools: Support EIS_EVENT_DEVICE_READY in eis-demo-server
We move the calls to resume and start emulation of new devices into a
handler that gets called only once a device is ready. We also set the
EIS_FLAG_DEVICE_READY flag to inform libeis that we want to process
these ready events.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/346>
2025-10-17 13:38:47 +10:00
Peter Hutterer
39a222868f proto: add a device ready request
The protocol currently supports a ei_device.done event to notify
the ei client that the initial description of the device is complete.
For some future use-cases the client may need to futher negotiate
properties of the device. For example for tablet tools the client may
narrow down capabilities of the tool.

The sequence with the new request is thus e.g.
     -> ei_seat.device
     -> ei_device.name
     -> ei_device.interface
     -> ei_device.interface
     -> ei_device.done
     <- ei_device.ready
     -> ei_device.resumed

In libei the request is sent automatically on unref of
the DEVICE_ADDED event. This makes clients immediately compatible
and for the typical (future) use-case of device configuration. Said
configuration will likely be handled in response to the DEVICE_ADDED
event anyway.

In libeis, a new EIS_EVENT_DEVICE_READY event that is sent when the client
sends that same event on the protocol, informing the EIS implementation
that this device is ready. For clients that do not support that version
the event is emulated immediately after sending ei_device.done.

This requires a flag bit to be long-term maintainable. The typical
EIS implementation currently calls eis_device_add() immediately
followed by eis_device_resume(). This doesn't leave any room to
wait for the client's ei_device.ready request.

One backwards-compatible solution could be to buffer the
eis_device_resume() until the ei_device.ready has been received but this
is fraught with hairy corner cases, e.g. if the client is a receiver
context we would also have to buffer all events immediately sent to the
client.

So instead, we have a flag in the context and if set by the caller, we
change the internal behavior to match ei_device interface version 3.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/346>
2025-10-17 13:38:47 +10:00
Peter Hutterer
7141566924 doc: two doxygen typo fixes
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/361>
2025-10-17 13:30:48 +10:00
Jason Gerecke
a646a4c19b eierpecken: Have peck_disable_ei[s]_behavior actually disable things
The peck_disable_eis_behavior and peck_disable_ei_behavior functions
would incorectly *enable* behaviors when called with certain arguments
that cover multiple behaviors (e.g. PECK_EIS_BEHAVIOR_ACCEPT_ALL).
This commit modifies the logic to instead disable the behaviors.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/359>
2025-10-03 08:48:49 -07:00
Peter Hutterer
2671079754 doc: drop the special sha for the hugo theme, update hugo instead
The special sha is no longer needed, let's bump to a recent version of
hugo instead and that should make it all work nicely (for a while).

Updating hugo requires changing to hugo.toml and forcing mermaid to be
enabled, without those changes the build failed with
 `failed to extract shortcode: template for shortcode "mermaid" not found`

This reverts commit 5909717700

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/358>
2025-08-28 14:35:59 +10:00
Jan Beich
4f11112be0 eis: implement getting client PID for BSDs after 70cfc6eed2
src/libeis-socket.c:192:15: error: variable has incomplete type 'struct ucred'
  192 |         struct ucred ucred;
      |                      ^
src/libeis-socket.c:192:9: note: forward declaration of 'struct ucred'
  192 |         struct ucred ucred;
      |                ^
src/libeis-socket.c:194:65: error: use of undeclared identifier 'SO_PEERCRED'
  194 |         int rc = getsockopt(source_get_fd(client->source), SOL_SOCKET, SO_PEERCRED, &ucred, &len);
      |                                                                        ^

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/357>
2025-08-26 09:33:24 +02:00
Peter Hutterer
19b6453540 libei 1.5.0
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2025-08-26 11:25:18 +10:00
Peter Hutterer
cae398c132 test: add a test for delaying the ping/sync
For both the tests send two pings, with a keyboard event in between.
Where the ping is processed, fetch all the events first up front, and
process them one by one in the right order.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-25 13:36:18 +02:00
Peter Hutterer
6c50e2f8a0 test: add support for disabling behaviors on the ei/eis context
Historically the preference for testing was to enable a bunch of
specific behaviors and then leave that as-is for the test. This can be
painful for some events, in particular sync/ping that are used
internally by libei's implementation.

Testing those events requires us to match the implementation-defined
internal setup which is a pain. Much easier to add a function
to allow disabling a specific behavior at some point during the test

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-25 13:40:49 +10:00
Peter Hutterer
7667d1fcd8 test: make some behavior enabling more expressive
Remove the non-obvious +1/-1 and use the enum values instead. Then group
the two together better and remove an unnecessary separate handling of
the flag setting.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-25 12:45:11 +10:00
Jonas Ådahl
5e57b1ed5f ei: Expose ei_event_ref()
As with eis, expose ei_event_ref() as well, for consistency.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-21 17:08:53 +02:00
Jonas Ådahl
42b9a89371 eis: Expose eis_event_ref()
This makes handling prolonged synchronization sequences easier, as one
doesn't have to work around only being allowed to hold one reference.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-21 17:08:53 +02:00
Jonas Ådahl
8d86ada12d ei: Send sync done event on last event unref
This allows the application to hold the sync event for a while longer,
e.g. to ensure previous events from ei have passed through relevant
plumbing and reached their internally intended targets, which might
happen synchronously between each processed ei event.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-21 17:08:53 +02:00
Jonas Ådahl
1010cdff3c eis: Send sync done event on last event unref
This allows the application to hold the sync event for a while longer,
e.g. to ensure previous events from EIS have passed through relevant
plumbing and reached their internally intended targets, which might
happen synchronously between each processed EIS event.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/340>
2025-08-21 17:03:27 +02:00
Axel Karjalainen
525d55a532 docs: Fix issues and make some language clearer
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/344>
2025-08-14 00:07:09 +00:00
Axel Karjalainen
70c3348dfe docs: Extend summaries
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/344>
2025-08-14 00:07:09 +00:00
Axel Karjalainen
4d999849ee docs: Make the description of context types more generic
The protocol should be separate from any specific implementation's own
terms.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/344>
2025-08-14 00:07:09 +00:00
Axel Karjalainen
f3f69e3a96 doc: Correct meaning of ei_connection.disconnected
Closes https://gitlab.freedesktop.org/libinput/libei/-/issues/85

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/344>
2025-08-14 00:07:08 +00:00
Peter Hutterer
3b98946b9a eis: only send scroll/button capabilities if the client supports them
Commit a902d5dbd8 ("protocol: replace the capabilities enum with an interface list")
added automatic handling for button/scroll interfaces on the protocol
because the libeis C API didn't have those as separate interfaces yet.
Any EIS implementation with POINTER/POINTER_ABSOLUTE would always
announce BUTTON/SCROLL capabilities.

Later in commit e6954b76d3 ("eis: change the API to match the protocol interfaces closer")
the required C APIs were added but this handling was never removed so an
EIS implementation always replied with button/scroll capabilities even
where not set. Fix this by removing this automatic announcement.

Fixes: e6954b76d3 ("eis: change the API to match the protocol interfaces closer")
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/353>
2025-08-05 07:02:57 +00:00
Peter Hutterer
7347aeacd2 eis: only send interface versions that the client announced
Do not reply with interfaces that the client never requested.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/353>
2025-08-05 07:02:57 +00:00
Peter Hutterer
1faaacda6e eis: send the interface version for the scroll interface
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/353>
2025-08-05 07:02:57 +00:00
Axel Karjalainen
e3e143ea42 test: fix false fail by actually looking for unbound capability mask
The previous implementation of
`test_seat_bind_invalid_caps_expect_disconnection`
didn't follow the protocol specification and assumed that `0x1` was
invalid.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/354>
2025-08-05 02:02:38 +03:00
Ian Douglas Scott
500ad0510f test: Advertise scroll/button interfaces in test_connect_receive_seat
This test was checking for the capabilities for `EI_SCROLL` and
`EI_BUTTON`, but not advertising those interfaces initially. Presumably
this was unintentional, given there's no comment that it's intentionally
advertising some protocols but not others.

Also, should the test have failed to find the capabilities when it
didn't advertise the protocol to to start with? This change just fixes
the test, anyway.

This was causing the test to fail for `reis` CI in a proposed change in
https://github.com/ids1024/reis/pull/11.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/352>
2025-07-31 19:33:26 -07:00
Peter Hutterer
45c526bcc9 ei: indentation fix
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/351>
2025-07-24 11:07:32 +10:00
Peter Hutterer
297f95efac utils: add the etrace macro
Same as the existing trace but prints to stderr. And #include stdio so
we can use this file as-is instead of needing extra includes.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/351>
2025-07-24 11:07:32 +10:00
Peter Hutterer
0831303a70 test: change peck_new_context to take varargs
This makes peck_new_context() take variable arguments in the style
key, v1, v2... where each value is defined by the free-form key.

What we need right now is the mode of the context, so let's add that.
In the future we will add more configurable bits here.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/351>
2025-07-24 11:07:32 +10:00
Peter Hutterer
0488b4b4d0 test: expand peck_new() to take varargs
This enables us to pass various configuration into the peck context
in the future without having to update every single test that doesn't
need that particular configuration.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/351>
2025-07-24 11:07:32 +10:00
Peter Hutterer
55335b9030 libei: fix the docs for the various _unref functions
Copy/paste from ei_unref but none of the others will disconnect the
context.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/349>
2025-07-24 00:54:06 +00:00
Peter Hutterer
12237f19ea ei: don't use the connection until we have one
If we're in EI_STATE_BACKEND ei->connection is NULL, causing a segfault
if in this state we unref our ei context.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/350>
2025-07-18 13:26:01 +10:00
Jonas Ådahl
6194880aa9 libeis: remove leftover debug log
Fixes: 73e0e8d339 ("eis: add EI_EVENT_SYNC as opaque event to correctly schedule callbacks")
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/348>
2025-07-16 13:42:13 +10:00
Peter Hutterer
5567524ecc Swap accidental trailing comma for a semicolon
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/347>
2025-07-11 00:59:06 +00:00
Jason Gerecke
883a60d4e6 oeffis: Correct spelling errors in log messages
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/343>
2025-07-02 14:03:34 -07:00
Jason Gerecke
6aa4dc0c7e doc: Correct spelling errors in the code documentation
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/343>
2025-07-02 14:03:34 -07:00
Jason Gerecke
ac9b92bbae proto: Correct spelling issues in the protocol documentation
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/343>
2025-07-02 14:03:33 -07:00
David Redondo
70cfc6eed2 Make it possible to fetch the pid of a client in socket mode
This is useful to get some more info about clients that try to
connect.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/339>
2025-07-01 08:50:48 +02:00
Kacper Piwiński
54e71e6dd5 util: don't call function in macro argument
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/342>
2025-06-29 07:48:45 +00:00
Kacper Piwiński
1b11d10ff2 util: use already computed strlen
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/342>
2025-06-29 07:43:34 +00:00
Peter Hutterer
edc8ea045a eis: don't warn about EPIPE, just debug-log it
While it's not the friendliest way of a client to exit, we do need to
expect clients to disconnect any time and that shouldn't trigger
warnings, it's somewhat expected behavior.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/337>
2025-06-16 11:20:21 +10:00
Peter Hutterer
8cd2b01bfa test: expose the ei socket fd to tests
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/337>
2025-06-16 11:20:21 +10:00
Peter Hutterer
98e445ebdb test: add ability to capture logs
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/337>
2025-06-16 11:20:21 +10:00
Peter Hutterer
dbeff9a90d test: add a bunch of strv helpers
Taken from libinput

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/337>
2025-06-16 11:20:21 +10:00
Peter Hutterer
efdc58e094 test: remove an unused function
Obsolete since 479bda259a ("Purge libreis from the repo")

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/338>
2025-06-13 16:49:03 +10:00
Jason Gerecke
50ff529a76 Fix MIT license header text
Corrects an issue where a wild search-and-replace mangled the license
texts on protocol implementations.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/335>
2025-05-29 23:07:48 +00:00
Jason Gerecke
6c5b486306 scanner: Avoid trailing full stops in regex search for protocol names
Continue to find nested ei_foo.bar.baz but avoid including a trailing
full stop, if it exists.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/336>
2025-05-29 14:35:19 -07:00
Jonas Ådahl
ee27dd5c92 ei-device: Don't leak fd when receiving the keymap
The ei_keymap dups the file descriptor, so lets close the one we
received from the demarshaller.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/334>
2025-05-22 22:43:35 +02:00
Peter Hutterer
851f935fe1 tools: print missing event types in the demo client
Instead of aborting, print something useful

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/332>
2025-05-19 11:34:54 +00:00
Peter Hutterer
2556ad38c6 tools: handle EI_EVENT_SYNC in the demo client
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/332>
2025-05-19 11:34:54 +00:00
Peter Hutterer
b2484c00d0 CI: bump to Fedora 42 and latest ci-templates
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/333>
2025-05-19 16:11:49 +10:00
Peter Hutterer
daba46a2ae eis: if a client is slow, queue up messages for future delivery
If a receiver client stops calling ei_dispatch for a while eventually
we fill up the send buffer, causing messages to be quietly dropped.
When the client resumes the message stream resumes with whatever we send
next but that can leave the client in an inconsistent state.

deskflow hit this in the server-side where our event sequence of pointer
motion+frames eventually filled up the buffer, causing
eis_device_stop_emulating() to be silently dropped. On the next
InputCapture sequence eis_device_start_emulating() was sent to an
already emulating client (as seen by the client).

This patch adds a secondary queue, if we fail to send a message with
EAGAIN queue it up and flush that queue whenever the next message is
sent. Meanwhile any newly added messages go straight into that queue.

The caveat here: a nonresponding client will eventually trigger OOM,
there is no upper limit on the messages yet

This is the libeis version of
commit 69e973e6b3 ("ei: queue unsent messages for later delivery if our buffer is full")

Closes: #79
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/331>
2025-05-02 15:40:47 +10:00
Peter Hutterer
daf0b24665 test: add a test for multiple start/stop emulating events
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/331>
2025-05-02 15:40:47 +10:00
Peter Hutterer
247a3d49d7 ei: print the sequence number on error for a start_emulating event
This may help debugging which sequence triggered an error.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/330>
2025-05-02 05:39:52 +00:00
Eric Long
e147d4311f test: increase protocol-test-valgrind timeout
Currently libei's valgrind test setup has 100x timeout (3000s), but the
standalone protocol-test-valgrind still has 30s timeout, which is not enough
for RISC-V hardware. On Milk-V Pioneer (SG2042) it takes ~70s to complete. In
addition, `kill_gently` sends SIGKILL before the process terminates and fails
the test.

Bump protocol-test-valgrind timeout to 300s and increase timeout between kill
signals to 3s to solve the issue.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/329>
2025-04-11 15:32:20 +08:00
Peter Hutterer
9e0413cbc7 libei 1.4.1
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2025-04-01 13:52:48 +10:00
Peter Hutterer
962863cbc4 scanner: ensure 'since' doesn't exceed the interface version
See https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/327

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/328>
2025-03-31 10:00:19 +10:00
Peter Hutterer
b0deafc641 scanner: convert the 'version' and 'since' arguments to int
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/328>
2025-03-31 10:00:05 +10:00
Ian Douglas Scott
fa21d765a1 proto: Fix version for ei_touchscreen
Fixes: b1c1c5d579 ("Add the ei_touchscreen.cancel event and ei_touch_cancel()")
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/327>
2025-03-31 09:11:56 +10:00
Corentin Noël
eda5b90760 meson.build: Use the correct name for the libraries overrides
The libraries are actually suffixed with the API number, use the same value as
their pkgconfig basenames.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/326>
2025-03-26 15:33:34 +01:00
Peter Hutterer
5d6d8e6590 libei 1.4.0
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2025-02-12 13:05:24 +10:00
Peter Hutterer
d25ceaff98 libei 1.4 RC1
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2025-02-03 13:33:25 +10:00
Peter Hutterer
76845ecdfe Reformat for ruff 0.9
New formatting rules, apparently.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/325>
2025-02-03 13:17:34 +10:00
Peter Hutterer
fb85496aa3 pre-commit: update to the latest version of our hooks
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/325>
2025-02-03 13:17:12 +10:00
Peter Hutterer
6068f3b14c CI: drop the python-black naming from the ruff job
Fixes: 0770fec433 ("Drop black, switch to ruff format")
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/325>
2025-02-03 13:13:59 +10:00
Peter Hutterer
6bbb3c5979 proto: fix a trailing whitespace
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/323>
2025-02-03 12:05:56 +10:00
Erik Jensen
1152c038ee Specify MODIFIERS to be sent for any state change.
Updates protocol and API documentation to specify that the modifiers
event should be sent by the EIS implementation every time the modifier
or group state changes, including when the change is triggered by key
events on the emulated keyboard.

The previous approach of expecting the client to track modifier state
using xkb_state_update_key() for injected keys resulted in multiple
opportunities for the client and server to get out of sync (both due to
unavoidable race conditions and due the client not having access to the
complete state used by the server to calculate state changes), with no
way for the client to ensure it had a correct modifier map short of
unbinding and rebinding the seat.

The new approach allows the client to track state solely by applying
modifiers events with xkb_state_update_mask(), simplifying client
implementation. Because the event is sent for all changes, the client
can use ei_connection.sync / ei_ping() to ensure that it has received
the latest state incorporating all key requests sent prior to the sync
request (along with any externally-caused modifier state changes that
may have occured up to the time the sync message was received).

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/318>
2024-12-18 04:41:11 +00:00
Peter Hutterer
564f14a739 ei: add EI_EVENT_SYNC as opaque event to correctly schedule callbacks
See the corresponding eis commit for details.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
73e0e8d339 eis: add EI_EVENT_SYNC as opaque event to correctly schedule callbacks
This event is required to fix an issue with the current
ei_callback.done handling in libeis: previously we would imediately send
the ei_callback.done upon receiving ei_connection.sync from the client.
This results in incorrect behavior as we may have events in the queue
(and/or pending a frame) that the EIS implementation hasn't seen yet.
For example a client sending:
- ei_pointer.motion
- ei_connection.sync
- ei_device.frame
- ei_connection.sync

Will queue a motion + frame event on the EIS side during eis_dispatch()
but immediately receive done events for both sync requests.

This could be handled purely internally by keeping the sync event in the
queue but hidden to the caller - and automatically calling done when
it's that events turn, i.e. something like:

```
  struct eis_event *eis_get_event(struct eis) {
      struct eis_event *e = first_event(eis);
      if (e == EIS_EVENT_SYNC) {
           eis_callback_send_done(e);
           eis_event_unref(e);
           e = next_event(eis);
      }
      return e;
  }
```

but that opens us up to a set of potential bugs detailed in
https://gitlab.freedesktop.org/libinput/libei/-/issues/71#note_2694603

So let's go the easy route by having a new event type that does nothing
other than eis_event_unref() in the EIS implementation. This way we can
queue the event and have everything behave in-order and as expected.

Closes #71

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
971013429d test: add a test for sync events during frames
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
e40796402b eis: add eis_ping() and the matching EIS_EVENT_PONG
Identical to "ei: add ei_ping() and the matching EI_EVENT_PONG" but for
libeis.

This wraps around ei_connection.ping to allow a C API user to add
synchronization points.

Closes #69

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
a278c7b371 ei: add ei_ping() and the matching EI_EVENT_PONG
This wraps around ei_connection.sync to allow a C API user to add
synchronization points.

Closes #69

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
2cc5e56e28 eis: revamp the internal sync callback
Identical to "ei: revamp the internal sync callback" but for libeis.

In prep work for exposing some of this to the caller, this adds a new
object that carries the our callbacks including the user data (if any).
This is an internal system only and is only used in the handshake
implementation where we don't have userdata anyway.

The new approach is: the callback has an object with a done() and
destroy() callback and the user data, done() is called when we receive
the message from the protocol, destroy() on destroy regardless whether
we got done() first.

This allows a caller to clean up user data even where the callback was
not triggered because we got disconnected first.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
95eb3c4bb3 ei: revamp the internal sync callback
In prep work for exposing some of this to the caller, this adds a new
object that carries the our callbacks including the user data (if any).
This is an internal system only and is only used in the handshake
implementation where we don't have userdata anyway.

The new approach is: the callback has an object with a done() and
destroy() callback and the user data, done() is called when we receive
the message from the protocol, destroy() on destroy regardless whether
we got done() first.

This allows a caller to clean up user data even where the callback was
not triggered because we got disconnected first.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/316>
2024-12-18 04:30:01 +00:00
Peter Hutterer
91e0b8961b doc: specifically mention that unknown events must be unref'd
And document the event type enums as non-exhaustive.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/322>
2024-12-17 16:00:59 +10:00
Peter Hutterer
3c88c0d3b2 tools/demo-server: note that aborting on unknown events is bad
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/322>
2024-12-17 15:59:02 +10:00
Peter Hutterer
9c35a57cfe doc: fix some linewrapping issues
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/322>
2024-12-17 15:59:02 +10:00
Peter Hutterer
b1c1c5d579 Add the ei_touchscreen.cancel event and ei_touch_cancel()
In the protocol it's a new request/event that is sent instead of the
touch up event.

In the library this is implemented as ei_touch_cancel() which
transparently sends cancel() or up depending on the EIS implementation
support. This is mirrored for the EIS implementation.

Where touch cancel is received as an event it is presented
as EI_EVENT_TOUCH_UP with an ei_event_touch_is_cancel() flag to
check if it was a cancel. This is required for backwards compatbility,
we cannot replace the TOUCH_UP event with a TOUCH_CANCEL event without
breaking existing callers. To add a new event type we would need clients
announcing support for those event types but that's an effort that's
better postponed until we have a stronger need for it (see #68).

Closes #60

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/308>
2024-12-17 15:12:56 +10:00
Peter Hutterer
739ea0f357 protocol: fix the description summary for the touch events
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/308>
2024-12-17 15:11:54 +10:00
Peter Hutterer
561cfd009e doc: unwinding state before stop_emulating is the caller's responsibility
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/308>
2024-12-11 10:09:34 +10:00
Peter Hutterer
cdec01dacd CI: force all auto features to enabled
Since we will automatically disable bits if required dependencies
aren't present let's make sure at least our CI complains.

Closes #70

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/319>
2024-12-10 05:06:49 +00:00
Peter Hutterer
d4b60a7d0d test: rename a variable to shut up ruff with default args
We disable that warning in the CI and pre-commit but it's a simple
fix here that makes sense.

        test/test_oeffis.py:185:9: E741 Ambiguous variable name: `l`

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/319>
2024-12-10 05:06:49 +00:00
Peter Hutterer
504afdea4a test: drop the use of attr
All our uses can be done with dataclasses so we don't need an external
package.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/319>
2024-12-10 05:06:49 +00:00
Peter Hutterer
f2811418dd proto: add two missing comments for consistency
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/321>
2024-12-10 09:39:04 +10:00
Peter Hutterer
beb1de6292 ei: indentation fix
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/321>
2024-12-10 09:37:45 +10:00
Peter Hutterer
2acf42db9c tools/debug-events: add missing linebreaks to the modifier event
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/320>
2024-12-09 12:41:24 +10:00
Peter Hutterer
9ee33b019f test: fix a spuriously failing timeout
When running repeated tests in parallel, this one eventually fails
because the ei.send() doesn't trigger the expected BrokenPipeError.
Use ei.dispatch() instaed and check whether the connection still exists.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
60bc264e75 test: ignore a failure to send a pong
Our automatic pong handler unconditionally sends a Pong back to the EIS
implementation. For some tests and depending on timing we may have
been disconnected already, resulting in a BrokenPipeError that fails
us the test. Ignore that error.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
96f20ae333 test: ignore a ConnectionResetError during our dispatch
If our server disconnects us with data still in the pipe we never picked
up on that data causing heisenbugs depending on whether we read things
(esp. the "disconnected" event) fast enough or not.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
7619bfa9ad test: switch a bool([...]) to any()
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
c169ac63b8 test: keep the announced interface as local variable
This will be more prominent as we add new interface versions and hooking
it up via the right signals is better than analyzing the calllog.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
0cf12a889d eis: remove pending events on eis_device_remove()
This fixes a memleak caused by anything having a device ref when
ei_device_remove() is called. This can happen e.g. if there is a pending
event that hasn't been flushed with a ei_device.frame yet.

Example Sequence:
- client sends an event, event is queued to pending with a ref to the
  device
- next event causes a client disconnect, queuing an emulated seat bind
  of 0
- server calls eis_device_remove()
- client calls eis_device_unref()

One ref is still held in the pending event that was never visible to the
server, preventing the device from being freed.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/317>
2024-12-06 16:48:48 +10:00
Peter Hutterer
62ae6f5edf ei: make ei_disconnect() public
Previously the only way to disconnect from the EIS implementation was
to call ei_unref() and release all resources. This makes it more
difficult for shared cleanup code - clients already have code in place
to deal with DEVICE_REMOVED, SEAT_REMOVED, etc. but that code has to
be triggered manually before ei_unref() is called.

OTOH where the server disconnects us, libei already unwound the state
so we would artificially generate these removed events, allowing the
client to clean up.

Make life easier for client by allowing them to ei_disconnect() and
get the benefits of our state unwinding. ei_disconnect() was already
used internally to disconnect on any error so this merely makes this
function public.

Closes #67

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/311>
2024-12-05 02:44:56 +00:00
Peter Hutterer
b33317cda0 util: Add the trace() debugging macro
Copied from libinput but pushed into util-macros with the color escape
codes expanded for simplicity.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/315>
2024-12-03 05:25:33 +00:00
Peter Hutterer
327dd4f8b1 test: fix string encoding in the python test bindings
The protocol encoding is the string including the null byte. The test
wrappers sent the right string length but only encoded strlen() bytes so
where we had a string that's a multiple of 4 long we ended up claiming
it's a byte longer than was on the wire.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/314>
2024-12-03 13:18:54 +10:00
Peter Hutterer
076e8bc670 proto: correct some documentation regarding ei_touchscreen
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/313>
2024-12-02 10:28:19 +10:00
Peter Hutterer
1f0cc83548 ei: declare struct ei_touch
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/313>
2024-12-02 10:28:19 +10:00
Peter Hutterer
247b6acd3c meson.build: allow disabling libei and libeis
This is primarily a development feature because it makes it easier to
develop a new feature for just one library without having to worry
about build errors in the other library (e.g. when new protocol parts
are added).

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/310>
2024-12-02 00:23:14 +00:00
Peter Hutterer
b3a4243924 test: make the c++ build test mirror the c build test
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/310>
2024-12-02 00:23:14 +00:00
Peter Hutterer
80bbcc67ed libei: fix an error message, spurious 'd'
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/312>
2024-11-29 12:27:48 +10:00
Peter Hutterer
11aa10393b tool: allow for touchscreen-only seat in the demo server
Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/312>
2024-11-29 12:27:48 +10:00
Peter Hutterer
1cf06d0af6 tools: fix colorprint in the demo server
This ended up as multiple print calls, better to compile the lot into a
single print message so its output can be collected correctly by pytest.

Part-of: <https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/312>
2024-11-29 12:27:48 +10:00
Peter Hutterer
a491580fc0 test: improve debugging for failed event type comparisons 2024-11-27 02:13:53 +00:00
Peter Hutterer
37ea216424 test: print the line no for peck_ei(s)_assert_no_events 2024-11-27 02:13:53 +00:00
Peter Hutterer
d593127c18 Revert "util: silence out-of-bounds read warning"
(t < &start_test_function_section) is never true so this commit ended up
disabling the unit tests altogether. That may shut up -fanalyze but
is somewhat of a regression in functionality...

This reverts commit 22c94fd916.
2024-11-26 15:33:36 +10:00
Peter Hutterer
0770fec433 Drop black, switch to ruff format 2024-10-17 16:18:51 +10:00
Peter Hutterer
dbc06510a1 scanner: switch to using dataclasses
This drops one dependency that we're not fully using anyway. Except for
the per-attribute validators that can be done in __post_init() we're not
using attrs for anything that dataclasses cannot do.
2024-09-12 00:39:09 +00:00
Peter Hutterer
a5826424f1 util: correct the fd validity check
Unlikely but the fd returned may be 0
2024-09-11 12:27:38 +10:00
Peter Hutterer
96609f82a0 util: do an allocation null check in strstrip 2024-09-11 12:27:38 +10:00
Peter Hutterer
55fe9303fb oeffis: rewrite sender_name to make the analyzer happy
Technically we need to check allocation failure of sender after xstrdup
but in doing so we might as well xalloc it and copy chars over
one-by-one.

No functional changes.
2024-09-11 12:27:38 +10:00
Peter Hutterer
005c4ac493 util: use xalloc instead of calloc to avoid NULL checks 2024-09-11 12:27:38 +10:00
Peter Hutterer
bbc1f8de1c util: fix a leaking fd in a test 2024-09-11 12:27:38 +10:00
Peter Hutterer
e3091a1802 util: fix a comment to use the right decimal marker for english 2024-09-11 12:27:38 +10:00
Peter Hutterer
22c94fd916 util: silence out-of-bounds read warning
gcc -fanalyze complains because it doesn't know the __stop section is
always greater than the __start section so it complains that we're
eventually reading past our __start section. Let's silence that with a
simple check.
2024-09-11 12:26:59 +10:00
Peter Hutterer
ec031bc4bf test: add a few non-null checks to make the static analyzer happy 2024-09-11 12:26:40 +10:00
Peter Hutterer
bf03c56300 Add a few missing va_end
Found by gcc -fanalyze
2024-09-11 12:25:41 +10:00
Peter Hutterer
37cc857a52 doc: demote the socket backend in the documentation
The socket backend is useful for debugging and testing but not for real
user-cases where the fd negotiation should be handled by the caller
(e.g. passing it down through the portal). Let's demote the socket
backend in favour of the fd backend.

Related https://gitlab.freedesktop.org/libinput/libei/-/issues/63
2024-09-03 04:00:33 +00:00
Peter Hutterer
6230e187fd README: reword the blurb about short lived applications
This particular wording dates to when libei created devices and the EIS
implementation would ack/nack those devices. This isn't the case anymore
so let's reword this a bit.

Closes #62
2024-08-22 12:12:20 +10:00
Peter Hutterer
997b7c0f37 libei 1.3.0 2024-08-08 10:18:24 +10:00
David Redondo
cf4ab5e73f Allow passing a fd to ei-demo-client
Like in ei-debug-events
2024-08-08 09:55:43 +10:00
David Redondo
ebde54f3b3 ei-demo-client: Remove obsolote portal option 2024-08-08 09:54:13 +10:00
Peter Hutterer
9f82bbf344 test: add tests for multiple regions
Adds the test for
commit 0f81114544 ("Fix region check for devices with multiple regions")
2024-08-07 10:51:12 +10:00
Peter Hutterer
e411b85a33 Print the event type name for invalid events 2024-08-07 10:47:25 +10:00
Peter Hutterer
917b79f83e proto: clarify the ei_keyboard.modifier event a bit
This is an event in response to "something change the modifier state"
where "something" was not an ei_keyboard.key event on this interface.
Examples are NumLock on after resumed, a nonzero XKB group, etc.

Closes #57
2024-08-01 02:09:29 +00:00
Peter Hutterer
54dd4353df proto: remove ambiguous wording hunk from the interface_version event
This wording is confusing, see
https://gitlab.freedesktop.org/libinput/libei/-/issues/59#note_2501641

Remove the explicit mention of interfaces with client-side created
objects so we fall back to the default "client announces, server
confirms" which is all we need here anyway.
2024-08-01 02:09:29 +00:00
Peter Hutterer
2f8872676d proto: add an extra reference to the interface_version event
Just in case, so it's more obvious that those two negotiate each other.
2024-08-01 02:09:29 +00:00
Peter Hutterer
712e9513c6 protocol: correct false references to interface_version
This is the handshake_version request/event, not the
ei_connection.interface_version.
2024-08-01 02:09:29 +00:00
Peter Hutterer
798b0966de proto: clarify that the scale factor is a multiplication factor 2024-08-01 02:09:29 +00:00
Peter Hutterer
d6a8a5e94a proto: clarify that a client bug means ignored and/or disconnected 2024-08-01 02:09:29 +00:00
Peter Hutterer
362c4c392a proto: remove vestiges of ei_device.capability
This is now obsolete after [1]. The ei_device.interface event announces
which interfaces are available on the device so we don't need
documentation to say "it's a bug to send this request if the device
doesn't have the capability" since the client won't have the interface
to begin with.

Fixes: a902d5dbd8 ("protocol: replace the capabilities enum with an interface list")
2024-08-01 02:09:29 +00:00
Peter Hutterer
438140feb2 proto: correct the ei_device.interface documentation
Remove leftovers from when this was an ei_device.capability event
but since removed, see [1]. Instead make clear that this needs to be
one of the bound interfaces.

[1] a902d5dbd8 ("protocol: replace the capabilities enum with an interface list")
2024-08-01 02:09:29 +00:00
Peter Hutterer
0b2f46e1a2 proto: note that a touch without a region is a EIS bug 2024-08-01 02:09:29 +00:00
Peter Hutterer
a5dd5a5ee5 proto: fix a wrong interface reference: callback -> pingpong 2024-08-01 02:09:29 +00:00
Peter Hutterer
5331dc0bf2 proto: the keymap event is optional, clarify that
A device may not have a keymap in which case it just sends scancodes
hoping they'll be mapped to something sensible.
2024-08-01 02:09:29 +00:00
Peter Hutterer
b67cdba809 CI: avoid duplicate pipelines 2024-08-01 11:56:23 +10:00
David Redondo
e8a844355f oeffis: Make ConnectToEIS call async
A blocking call can be  problematic when done from inside Xwayland
to the compositor as the compositor could be doing a blocking X call
at the same time. In this instance we found it's likely to happen
because this call will happen shortly after the user accepted the
This is not a problem for the other calls because the portal API
is async for these via request response signalling.
2024-07-31 07:05:48 +00:00
Peter Hutterer
865d7152e0 util: replace a strncpy with a memcpy
We know exactly the lengths of everything involved here so let's use
memcpy. This way we don't need the stringop-truncation warning (a pragma
clang doesn't support anyway).
2024-07-29 05:15:53 +00:00
Peter Hutterer
3e2e43e352 ei-demo-client: use xkb_keymap_new_from_buffer()
This is the more correct approach since we get a sized buffer from the
server and people may use this as reference code in their implementation.
Technically we cannot expect a true zero-terminated string from EIS.

Unfortunately this means we need to work around
libxkbcommon#307 to strip trailing zeroes from the buffer if any exist.
2024-07-29 14:54:31 +10:00
Peter Hutterer
a924888f0f ei-demo-client: use mmap to read the keymap
Closes #55
2024-07-29 14:53:35 +10:00
Peter Hutterer
6ea468c823 util/memfile: use MAP_SHARED to create the map
Using MAP_PRIVATE means copy-on-write so our data written into the map
immediately disappears again. This leads to a empty string when sending
a keymap to a client.
2024-07-29 14:48:34 +10:00
Peter Hutterer
dc153c50ed util/memfile: include stddef for size_t 2024-07-29 14:48:34 +10:00
Peter Hutterer
c4ac084159 util/memmap: add a helper wrapper around mmap
Makes this easier to use with our unref functions.
2024-07-29 14:48:34 +10:00
Peter Hutterer
eb1d5a7e1a test: drop a custom cleanup func in favor of _unref_ 2024-07-29 14:32:40 +10:00
Peter Hutterer
a424458f03 Devices without regions pass coordinates as-is
If a device doesn't have a region (e.g. physical device) the answer to
"is this in the region" is always yes.

Closes #56
2024-07-25 01:17:39 +00:00
Peter Hutterer
0f81114544 Fix region check for devices with multiple regions
Use the ei(s)_device_in_region() to check for any region.

Closes #56
2024-07-25 01:17:39 +00:00
Peter Hutterer
87705ef97c scanner: handle the allow-null attribute for arguments
Currently unused but it's exposed, so yay...
2024-07-25 11:11:07 +10:00
Peter Hutterer
267716a760 proto: mark the explanation in disconnect as nullable
This is the only string in the current protocol that is nullable and our
DTD allows for that so it's not even an API break. Yay.

Closes #54
2024-07-25 11:11:02 +10:00
Peter Hutterer
4059820391 CI: bump to Fedora 40 2024-07-24 20:19:44 +10:00
Peter Hutterer
9ee399c8be CI: Lock hugo to v0.111 to allow for distro updates
F40 now ships v0.121 which again breaks something in the relearn theme
so let's lock our version here to one we know works and move on with
life. I can't be bothered to relearn hugo and themes every few months
just for a single static website.
2024-07-24 17:39:04 +10:00
Peter Hutterer
da1fa204d5 meson.build: bump the meson version by one
This allows us to drop one version check
2024-07-24 12:29:48 +10:00
David Redondo
23f056433d Fix API docs for EIS_EVENT_CLIENT_CONNECT
The functions with event in their name don't exist.
2024-04-09 06:39:13 +00:00
Matt Turner
33b4a61995 test: Raise SIGALRM interval to 50µs
On some platforms, an interval of 5µs is short enough that the test
spends its time almost exclusively processing SIGALRMs and never
progresses otherwise. Raising the interval to 50µs allows the test to
pass in a fraction of a second.

Bug: https://bugs.gentoo.org/916777
Closes: https://gitlab.freedesktop.org/libinput/libei/-/issues/50
2024-04-08 12:21:09 -04:00
Peter Hutterer
54ca521d0a proto: highlight the region 'hight' typo in the documentation
Changing this would be an API-breaking change (depending on how
bindings are generated) so we'll have to live with this typo for the
foreseeable future.

Closes #53
2024-03-12 04:25:33 +00:00
David Redondo
93efd3d14e eis-demo-server: Send a sensible discrete scroll value
So it can be a good example for people and doesn't log about
server bugs.
2024-03-05 22:47:05 +00:00
Peter Hutterer
08f1d41085 libei 1.2.1 2024-02-05 13:33:04 +10:00
Peter Hutterer
d29778658a oeffis: OEFFIS_DEVICE_ALL_DEVICES should translate to "all"
As the portal documentation [1] says:
  Bitmask of what device types to request remote controlling of. Default is all.

The default is only triggered if we do not submit the types at all, the
current behavior of sending a value of 0 means "none". Fix this by
skipping the "types" key if we try to select for all devices.

[1] https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.RemoteDesktop.html
2023-12-14 00:58:18 +00:00
Peter Hutterer
6ce695db3b test: yield the proxy, not the subprocess for the liboeffis fixtures
There's virtually no use for the process, so let's yield the proxy
object instead because we can make use of that.
2023-12-14 00:58:18 +00:00
Peter Hutterer
870123f54b CI: move the minium meson version job to debian
See 55e5deedd8 where this job was removed.
2023-12-14 10:44:54 +10:00
Peter Hutterer
eaed3883d8 CI: fix a hardcoded fedora and replace it with the jinja var 2023-12-14 10:44:54 +10:00
Peter Hutterer
97d66604fb CI: drop the git depth for the ABI check job
We need to make sure that we can checkout whichever sha was the last ABI
break.
2023-12-14 10:41:19 +10:00
Peter Hutterer
55e5deedd8 ci: bump to use F39
And drop the job to test the minimum meson version in the process. F39
ships with python 3.12 which causes this error with meson 0.56.0

proto/meson.build:17:0: ERROR: <ExternalProgram 'python3' -> ['/usr/bin/python3']> is not a valid python or it is missing setuptools

We could put the effort into setting up the CI to fix this, but we
won't.
2023-12-13 17:55:16 +10:00
Peter Hutterer
4936316884 libei 1.2.0 2023-12-06 08:19:00 +10:00
Peter Hutterer
9697aa9c74 pre-commit: autoupdate and add two more useful checkers
We don't have symlinks but let's check for them anyway. And check for
merge conflict leftoers too.
2023-12-05 14:25:10 +10:00
Peter Hutterer
38132d6fc5 ei: improve debug messages for keymap failures
Let's be more informative about what failed.

Fixes #48
2023-11-09 12:55:56 +10:00
Peter Hutterer
924341e174 ei: keep a cache of defunct objects to avoid spamming the log
Because of the asynchronous protocol, we may get this interaction

   server sends A->destroyed()
       client sends A->foo()
       client receives A->destroyed()
   server receives A->foo()
   server sends invalid_object_id(A)
       client receives invalid_object_id(A)

Previously we dropped the object after destroyed() and were thus
guaranteed to warn about the invalid object id for that same object
later.

Fix this by keeping a cache of defunct object IDs that we know about and
ignoring errors for recently dropped objects.

Every 20 ei_dispatch() calls we drop any defunct objects that were
unregistered more than 5 seconds ago.

Fixes #49
2023-11-09 12:47:06 +10:00
Peter Hutterer
4e634aa76c test: add the ability to add offsets to ei_now()
Build a separate libei-eierpecken.so that is identical to libei.so but
allows adding an offset to ei_now() for the eierpecken tests. That
offset is added to the return value of ei_now(), removing the real-time
dependency of the tests.

In other words, we can call peck_ei_add_time_offset(peck, s2us(5)) to
add 5 seconds to the time and continue the test as if that time has
elapsed.
2023-11-09 12:47:06 +10:00
Peter Hutterer
7999483a34 test: make bug logs fatal by default
We don't want to paper over bugs in the implementation, so let's make
any ei/eis error message with a bug in it fatal by default.

This needs to be disabled where we test for known-buggy client/EIS
behavior.
2023-11-09 10:15:38 +10:00
Peter Hutterer
fae41aac08 test: hack a peck_debug() function
To make it easier to print something debuggy from a test.

This is the MVP, it always requires an argument after the format string
but it'll do for now.
2023-11-08 23:24:45 +00:00
Peter Hutterer
34c83370be eis: fix a typo 2023-11-08 23:24:45 +00:00
Peter Hutterer
d31b5a1ccf eis: only queue a seat bind event if the caps change
This triggered an internal bug message in test_ei_seat_bind_unbind_immediately.

If a client unbinds all capabilities this triggers an EIS_SEAT_BIND with
zero caps. peck would call eis_seat_remove() which calls eis_seat_drop().
That queued another EIS_EVENT_SEAT_BIND with zero capabilities, leading
peck to call eis_seat_remove() again on an already removed seat.
2023-11-08 23:24:45 +00:00
Peter Hutterer
44e7f2cbc4 test: fix a test missing frame events 2023-11-08 23:24:45 +00:00
Peter Hutterer
35832d9fe1 test: make peck_dispatch_until_stable() print more useful debug info 2023-11-08 23:24:45 +00:00
Peter Hutterer
f9c83ed9a6 test: expand log messages to accommodate for 4-digit line numbers 2023-11-08 23:24:45 +00:00
Peter Hutterer
57e7cd7972 doc: drop the git depth from the hugo theme
We need a specific SHA so let's not risk that one being outside that
depth that we clone to.
2023-11-08 16:04:33 +10:00
Peter Hutterer
05d79f6e58 test: set up a timer to trigger SIGALRM
Xwayland uses a timer for the scheduler which means any of our syscalls
can trigger EINTR. Let's make sure we may catch bugs related to that by
setting up our test suite to hammer us with timers.

Can't guarantee this will trigger all bugs but over time it may help or
at least ensure that the low-hanging fruit are all fixed.
2023-10-24 18:25:25 +10:00
Peter Hutterer
f558eedae2 util: add a global setup option to our munit wrapper
This allows us to handle/manipulate argv and/or pass a userdata to the
tests. Same ELF section approach as we already have with the MUNIT_TEST
macro but this time it's for a global setup.

Note that for the generated __start and __stop section variables we have
to have at least one entry in that section which we do with one
hardcoded (and ignored) one.
2023-10-24 18:25:25 +10:00
Peter Hutterer
6758b9970d util: add helper macros for the ELF section handling 2023-10-24 18:25:25 +10:00
Peter Hutterer
19c949a46b util: retry posix_fallocate() if it fails
We block SIGALRM since d4bf8840a4 so any
remaining EINTR is something we should probably retry with.
2023-10-24 18:25:25 +10:00
Peter Hutterer
a3c801097d util: don't check for EINTR on close()
See https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/261#note_2131802
2023-10-24 18:25:25 +10:00
Peter Hutterer
cdbd1d9eaf util: replace most of SIGALRM blocks with EINTR checks
SIGALRM *should* not really be an issue for us, most of our IO shouldn't
take long (posix_fallocate is the exception) so we can just retry it
instead of blocking someone's use of SIGALRM.

Doing it here mostly means that the rest of libei doesn't have to care
about EINTR, it'll just be handled transparently (just like before with
the SIGALRM blocks).
2023-10-24 18:25:25 +10:00
Peter Hutterer
2df570b203 tools: add an --iterations argument to ei-demo-client
Limits the number of poll() iterations before triggering a shutdown.
This allows us to use this tool for shutdown testing too - previously
it would just get killed and never performed a proper unwind.
2023-10-24 18:22:48 +10:00
Peter Hutterer
5dcdc696fb ei: remove the source if we get disconnected
If we get disconnected there's no point sending anything anymore, the
server may have already closed the connection.

Otherwise this produces a confusing warning  in the log:

 18:37:31 | INFO | Disconnected by EIS
      ... | WARN | failed to send message: Broken pipe

See https://gitlab.freedesktop.org/libinput/libei/-/merge_requests/262#note_2132619
2023-10-19 14:18:57 +10:00
Peter Hutterer
38244b8c66 ei: if sending a message fails, don't send any more
Any error will cause ei_disconnect() which in turn unwinds our context.
Each object removed will cause an ei_send_message() to notify the server
but that will only cause error messages - our socket is already gone.

Fix this by removing the source and checking if the fd is still valid
before we write to it.

Fixes #47
2023-10-18 09:44:01 +10:00
Peter Hutterer
3f768ecbfc CI: add missing dependencies line 2023-10-11 13:31:47 +10:00
Peter Hutterer
5909717700 doc: use a specific revision of the hugo template
Upstream commit 1250bf30a8c2e3c1bfac285489bc63f2c395b641 somehow breaks
things. Doesn't affect hugo v0.111 (F39) but it does break on F38 on
v0.101.

Let's not waste more time debugging this, hardcode the template and in a
few weeks time we can switch to F39 as the build host.
2023-10-11 13:09:31 +10:00
Peter Hutterer
43b383ebdb doc/protocol: fix some hugo warnings
WARN 2023/10/11 12:41:19 "_index.md": WARNING you must call the ref /
relref shortcode with '% %' instead of '< >' to work correctly for the
anchor target attribute
2023-10-11 12:42:47 +10:00
Peter Hutterer
b8884770f1 CI: add a separate job for the documentation build
The pages job is only run once we push to main, so any issues with the
documentation build won't be picked up until it's too late. Split those
up and build the doc in a separate job, the pages job simply takes the
artifacts from that job and publishes them.
2023-10-11 12:25:53 +10:00
Peter Hutterer
d4bf8840a4 util: block SIGALRM during posix_fallocate
posix_fallocate() may be interrupted (EINTR), e.g. by SIGALRM in the X
server's smart scheduler.  On slow systems and for large allocations
this means we cannot ever succeed so let's block SIGALRM like everyone
else does.

See e.g.
4cfee39872
and the same code is in libwayland, libdecor, etc.
2023-10-11 00:58:23 +00:00
Peter Hutterer
fd2999ffdb tools: add a --interval option to the demo server and client
Makes testing a few things easier.
2023-10-11 00:49:25 +00:00
Peter Hutterer
cefef7b1a5 Add a static assertion that EAGAIN == EWOULDBLOCK
Not sure what platforms this isn't the case on and I'm even less sure we
need to care so let's fail the build where this is the case and work it
out if someone complains.
2023-10-11 00:41:13 +00:00
Peter Hutterer
69e973e6b3 ei: queue unsent messages for later delivery if our buffer is full
If our write buffer is full, enqueue the events instead and try to flush
them out later when we can write again.

Fixes #46
2023-10-11 00:41:13 +00:00
Peter Hutterer
95d1107cea util: allow sources to have write notifications
In case our outbound buffers are full we need we need to be able to have a
notification when we're able to write again.

There are two approaches to do this: one is to duplicate the source
(and dup() the fd) and use that for write notifications only. The other
approach is to toggle the source's EPOLLOUT flag on demand, thus
sharing the source dispatch. I picked the latter for simplicity.
2023-10-11 00:41:13 +00:00
Peter Hutterer
8f6c355805 util: fix clobbered errno in sink_add_source 2023-10-11 00:41:13 +00:00
Peter Hutterer
381c6bea1f ei: improve a debug message, the number we print is the serial 2023-10-05 23:00:20 +00:00
Alban Browaeys
4929aeae61 Fix duplicate negation in log message
The test is meant for "not emulating", not "not not emulating".
2023-09-14 02:01:04 +02:00
Peter Hutterer
82cdbc9129 libei 1.1.0 2023-09-07 15:07:54 +10:00
Jason Gerecke
0820e29bd5 Correct documentation for ei_touch_(get|set)_user_data 2023-09-06 21:16:08 +00:00
Peter Hutterer
d9d4630567 tools/debug-events: print the region mapping id
This changes the region print format from a string to a dict
2023-09-01 12:18:20 +10:00
Peter Hutterer
c179b3dac1 tools: fix an indentation issue 2023-09-01 12:18:20 +10:00
Peter Hutterer
ac16ba77ff tools: start ei-debug-events for the fd we get from the oeffis demo tool
Once we get the fd, fork off ei-debug-events for that fd so we can debug
what the EIS implementation actually sends us.
2023-09-01 12:06:38 +10:00
Peter Hutterer
8713ef2d63 libei 1.1.0rc1 2023-08-31 14:06:49 +10:00
Peter Hutterer
16c7b1570b test: fix 64-bit pointer access for the object ids
memcpy this out so we can compare them.
2023-08-31 13:42:25 +10:00
Peter Hutterer
931632effd test: use munit_assert_uint64 for 64-bit values
Otherwise we only take the first 4 bytes which can fail on BE if we're
comparing an array directly (like we do for the string test).
2023-08-31 13:37:13 +10:00
Peter Hutterer
f9c6ea30fc brei: copy protocol strings out of the protocol buffers
With our strings being a 4-byte header followed by the string itself,
they're virtually guaranteed not to be 8-byte aligned. This causes an
issue on some architectures so we need to copy the string out before we
access it.

Since strings are the only protocol type with that extra buffer, let's
hack this in with the minimum effort approach - a null-terminated
char * pointer array that's filled with the strings as they appear in
on the wire. The brei_arg->s points to one of those strings as needed.

This means we can drop the brei_string struct, thanks to pointer alignment
issues this struct doesn't work on s390x, so let's drop it, it no longer
serves any meaningful value.

Fixes #41
2023-08-31 13:37:13 +10:00
Peter Hutterer
a223ce86da util: switch iobuf to uint8_t to avoid sign issues
char is signed or unsigned, so let's avoid this by using uint8_t.
2023-08-31 13:37:13 +10:00
Peter Hutterer
397ee6d79c brei: avoid pointer casts for 32/64 bit values
Copy those onto the buffer with a memcpy rather than via a
(const char *) cast to make sure we work on s390x too.
2023-08-31 13:37:13 +10:00
Peter Hutterer
90040096dd test: rename a variable to avoid clashes
Future changes will use slen further below because it's the best name,
so let's rename this one since it's less impactful.
2023-08-31 13:37:13 +10:00
Peter Hutterer
1bac0c28b4 CI: update the commit for the last ABI break
abe85e051 stopped the symbol leaks which will look like an ABI break.
2023-08-31 13:30:36 +10:00
Peter Hutterer
abe85e051e meson: build libutil with hidden symbols visibility
We were leaking some of the utility functions, let's not do that.

This is technically an ABI break but if you're relying on libei to
export those functions...well, don't.
2023-08-31 13:20:05 +10:00
Peter Hutterer
f235052f81 test: ensure munit debug messages are visible on failure 2023-08-31 12:41:13 +10:00
Peter Hutterer
8f911d5e41 test: log the buffers after reading from them
Should help debug some issues with the encoding
2023-08-31 12:41:13 +10:00
Peter Hutterer
8e95c7d8a5 util: add a strv_from_mem() helper function
Generates a strv with the buffer as hex string.
2023-08-31 12:41:13 +10:00
Peter Hutterer
4f2fd16186 test: close the sockets on exit 2023-08-31 12:41:13 +10:00
Peter Hutterer
7115e9c4c8 test: rework the oeffis dbus tests to be pytest-compatible
DBusMock is unittest based and the documentation points users to that
approach. That approach is limiting however because we can't use all
pytest features (see [1]). Luckily, the parent class in dbusmock doesn't
really do much so we can emulate the functionality ourselves - all we
need to do is call the same setUp/tearDowns and be done with it.

This means we can move the dbus-monitor and mainloop handling into
fixtures too which makes the code a fair bit nicer to read.

[1] https://docs.pytest.org/en/7.1.x/how-to/unittest.html#pytest-features-in-unittest-testcase-subclasses
2023-08-30 09:46:36 +10:00
Peter Hutterer
e03c047b5d proto: require Python 3.9
ei-scanner relies on some 3.9 features and since that has been out for
almost 3 years now, let's make it a requirement.

Fixes #39
2023-08-30 09:36:40 +10:00
Peter Hutterer
76652350cc Add a mapping_id to the regions
This allows a caller to match up a region with other data, e.g. in the
remote desktop case the same mapping_id can be assigned to the pipewire
stream that represents that output.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2023-08-30 09:18:26 +10:00
Peter Hutterer
2fbd22984f util: fix iobuf_append_fd OOB when we have too many fds
Cannot happen in deploymentts since we never have more than one anyway.

Fixes #43
2023-08-30 09:11:08 +10:00
Peter Hutterer
b7ab63c386 util: fix iobuf_take_fd invalid memmove for multiple fds
Wrong calculation resulted in memmoving only the first 32 bytes (i.e. 8
fds) instead of the first 32 fds, resulting in an infinite loop when
cleaning up an iobuf with more than 8 fds.
2023-08-30 09:11:08 +10:00
Peter Hutterer
7ddd70e9d8 Add ei_device_get_region_at() to obtain a region for a point
Helper function so clients don't need to loop through the regions to
find the matching one.
2023-08-29 11:39:20 +10:00
Peter Hutterer
36f1641125 meson.build: bump to use gnu11 over gnu99
static asserts require C11
2023-06-09 10:27:55 +10:00
Peter Hutterer
661f7665d7 libei 1.0.0
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2023-06-08 09:23:45 +10:00
Peter Hutterer
39e6c93c8b test: ensure all returned interface versions are 1
Even where our client pretends to have higher interface versions, we
always send back version 1 from the EIS implementation.

This test probably won't really trigger anything useful until we switch to
version 2 somewhere, so let's hope the code works...
2023-06-07 23:08:16 +00:00
Peter Hutterer
97a5538258 test: add test for correct ei_device_close behavior 2023-06-07 23:08:16 +00:00
Peter Hutterer
1f51c05897 test: don't over-ref the button/scroll devices
If we create a pointer and an absolute pointer in a test, we end up with
two devices that both have button and scroll capabilities.
Overwriting those results in a dangling ref to the device.
2023-06-07 23:08:16 +00:00
Peter Hutterer
b14405d3cd ei: fix a whitespace error 2023-06-07 23:08:16 +00:00
Peter Hutterer
22079d62dc test: add a unit test for ei_region_convert_point 2023-06-07 23:08:16 +00:00
Peter Hutterer
059d3e11b6 eis: send all our interface versions to the client
The protocol requires this only for interfaces that create client-side
objects but let's do it here anyway to ensure the code is tested.
2023-06-07 23:08:16 +00:00
Peter Hutterer
e0d60b062c ei: removed unused ei_callback_new_for_id
Leftover from an earlier version before callback and pingpong were split
into separate interfaces.
2023-06-07 23:08:16 +00:00
Peter Hutterer
befab06ea2 tests: add tests for button events
Those were missing, luckily the code works
2023-06-07 23:08:16 +00:00
Peter Hutterer
aa3c2eb763 ei: remove unused ei_pingpong_new()
Copy/paste from the callback interface I think, so let's remove it.
2023-06-07 23:08:16 +00:00
Peter Hutterer
6caf69a73c test: check that the width/height are set for physical devices 2023-06-07 23:08:16 +00:00
Peter Hutterer
a9edea65ca ci: add a check that our event values cannot diverge
See commit 168de89a "eis: sync event codes with libei", this script
should've prevented that issue.
2023-06-07 14:36:00 +10:00
Peter Hutterer
2d6b11833b CI: remove a copy/pasted MESON_ARGS from the abicheck job 2023-06-07 14:28:04 +10:00
Peter Hutterer
987997dc2c test: add tests for the eis region getters 2023-06-06 19:41:54 +10:00
Peter Hutterer
f0894aac67 test: drop ifdef'd out test
It's been ifdef'd out for long enough that clearly this test doesn't
matter anymore.
2023-06-06 10:56:53 +10:00
Peter Hutterer
4096d100ee ei: more docs that data is in pixels or mm
This depends on the device type and was-already documented in some
functions but not others.
2023-06-06 10:54:48 +10:00
Peter Hutterer
b5f0899352 eis: expose eis_region_contains
Same as the libei function, there's a use-case for this especially when
dealing with receiver contexts. libei filters those but we can't rely on
that in the server so using this is a workaround.
2023-06-06 00:50:05 +00:00
Peter Hutterer
fc79982682 eis: add eis_region_get_physical_scale
This can be set by the caller, so we should be able to get it later.
2023-06-06 00:50:05 +00:00
Peter Hutterer
1cf9412990 CI: fix the ABI check job, it was using the wrong HEAD
The ABI job git cloned from upstream and then compared HEAD with the
last ABI break. Problem though: the HEAD was origin/main, not the actual
HEAD from the merge request.

Fix this by adding upstream as a remote and fetching from it. And while
we're there update to the last ABI break commit sha and move the
abicheck bits into before_script.

Note that the remote has the CI job ID appended, this avoids conflicts
when the git repo is re-used betweenn jobs and the upstream remote
already exists.
2023-06-06 10:31:57 +10:00
Peter Hutterer
95366ea131 CI: use meson setup in the abi check job 2023-06-06 10:16:25 +10:00
Peter Hutterer
168de89a70 eis: sync event codes with libei
Commit da37da1308 "ei: change the API to match the protocol interfaces closer"
change the event type numbers for per-capability grouping but the
follow-up commit e6954b76d for eis didn't do the same. Right now the
event types are out sync.

This doesn't technically matter as this is a libeis implementation
detail (those types don't exist on the protocol) but it'd still be nice
to sync them before we ship 1.0.

This is an ABI break but not an API break.
2023-06-05 22:15:54 +00:00
Peter Hutterer
080864d82a Correct a meaningless comment 2023-06-05 12:19:19 +10:00
Peter Hutterer
7d0536f344 test: use the callback's version instead of hardcoding it a second time
Fixes f081e8e79f
2023-06-02 10:29:17 +10:00
Peter Hutterer
ed1acbbd7c eis: expose eis_device_get_context() and eis_seat_get_context()
For frame events on a device it's likely that we want to use eis_now()
which takes the context. So let's make this easy enough to access
without having to carry extra variables in the caller. And the same for
the seat as well.
2023-06-01 18:42:16 +10:00
Peter Hutterer
1175595acf tools: make the demo client send the correct discrete events
Multiples of 120 are needed
2023-06-01 15:12:07 +10:00
Peter Hutterer
cc053155a5 doc: change the doxygen oeffis group to liboeffis
This way the links are consistent with the libei/libeis groups.
2023-05-31 16:11:57 +10:00
Peter Hutterer
868a16df85 ei: ei_device_get_width/height need to be public APIs 2023-05-31 01:06:32 +00:00
Peter Hutterer
35ec414215 Remove unimplemented ei_device_get_keymap()
This is ei_device_keyboard_get_keymap() instead
2023-05-30 19:11:44 +10:00
Peter Hutterer
37e2bdebed CI: add a comment to the meson build helper
We now have an upstream for it so we can sync changes between projects.
2023-05-30 15:18:04 +10:00
Peter Hutterer
3ef22c8ed0 Log a bug if a client tries to send a discrete value 1
Discrete values are multiples of 120 and it's unlikely the device to be
emulated is *that* precise, so let's assume it's a bug and the client
wasn't aware of the 120-multiple requirement.
2023-05-30 11:14:57 +10:00
Peter Hutterer
3e908c03f4 log: remove trailing linebreaks from log messages
These are no longer needed or desired (see 37467881e6)
2023-05-29 17:54:20 +10:00
Peter Hutterer
1aedabe7c7 libeis: check incoming objects' version for correctness
If the server sends a protocol version higher than we support, fail.
2023-05-26 19:16:15 +10:00
Peter Hutterer
c99f4ffa2c libei: check incoming objects' version for correctness
If the server sends a protocol version higher than we support, fail.
2023-05-26 19:16:15 +10:00
Peter Hutterer
f081e8e79f proto: add a version argument to ei_connection.sync
This is the only request that creates a new object but doesn't specify
the version for that object, courtesy of copy/paste from the wayland
protocol. In libei/libeis this a bit was hidden away so it didn't get
noticed - but it was already buggy: libei would always hardcode to
version 1 but libeis would take whichever ei_callback version was agreed
upon during handshake. This version could be higher than 1.

This is a protocol break but we're still pre-1.0, there are very few
people that will be affected by this and it's better than having to
carry this bug around for years.

Fixes #35
2023-05-26 07:01:19 +00:00
Peter Hutterer
552f6dcbd0 ei-scanner: expose version_arg and version_arg_for
Points to the correspoding "version" argument, or points back to the
argument this version argument is for.
2023-05-26 16:56:13 +10:00
Ian Douglas Scott
1d8cd84c56 ei-scanner: Expose interface_arg, and also provide interface_arg_for
To make this practical to use in a template, we want relations in both
directions. And at least for consistency with other things, these fields
should contain the `Argument` instead of just its name string.

So we need to do this after are the arguments in the message have been
initially parsed. Adding these fields when parsing the request/event
close tag seems to work well enough.

Co-authored-by: Peter Hutterer <peter.hutterer@who-t.net>
2023-05-26 16:55:47 +10:00
Peter Hutterer
00226da59e scanner: add tests for the extra data arguments
Fixed in !218, let's test this so we don't break it again.
2023-05-25 10:09:28 +10:00
Peter Hutterer
3a9eb2d8b6 scanner: rework the main() function into something easier to test
Rename to scanner() and take an argument vector that can be passed to
ArgumentParser.parse_args(). This makes testing the scanner's CLI a lot
easier.
2023-05-25 09:51:42 +10:00
Ian Douglas Scott
091948e9ef ei-scanner: Fix --jinja-extra-data-file, and make mypy check pass 2023-05-24 11:33:12 -07:00
Peter Hutterer
016072507e CI: add a build test for our minimum meson version
Since meson is installed from pip, we'll use the latest stable version
when we build an image. Add an extra job with the fixed minimum version
so we ensure we actually build with that version.
2023-05-24 08:16:43 +10:00
Peter Hutterer
a75828aee0 meson.build: drop requirements back to 0.56
The bump to 0.60 was required for 2a2c4cdd but is no longer required
after 27ff400b.
2023-05-24 08:16:29 +10:00
122 changed files with 8288 additions and 1823 deletions

View file

@ -6,7 +6,7 @@
# #
########################################
.templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile
.templates_sha: &template_sha c6aeb16f86e32525fa630fb99c66c4f3e62fc3cb
include:
- project: 'freedesktop/ci-templates'
@ -24,8 +24,10 @@ stages:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_PIPELINE_SOURCE == 'push'
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
variables:
###############################################################################
@ -38,9 +40,9 @@ variables:
# See the documentation here: #
# https://wayland.freedesktop.org/libinput/doc/latest/building_libinput.html #
###############################################################################
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-attrs python3-pytest python3-dbusmock python3-jinja2 python3-pip hugo libabigail '
FEDORA_PACKAGES: 'git diffutils gcc gcc-c++ pkgconf-pkg-config systemd-devel libxkbcommon-devel libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-pyyaml golang libabigail '
FEDORA_PIP_PACKAGES: 'meson ninja structlog strenum '
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-attr python3-pytest python3-dbusmock python3-jinja2 python3-pip '
DEBIAN_PACKAGES: 'git gcc g++ pkg-config libsystemd-dev libxkbcommon-dev libxml2 doxygen python3-pytest python3-dbusmock python3-jinja2 python3-pip python3-yaml '
DEBIAN_PIP_PACKAGES: 'meson ninja structlog strenum '
############################ end of package lists #############################
@ -48,14 +50,14 @@ variables:
# changing these will force rebuilding the associated image
# Note: these tags have no meaning and are not tied to a particular
# libinput version
FEDORA_TAG: '2023-05-18.3'
DEBIAN_TAG: '2023-05-18.3'
FEDORA_TAG: '2025-05-19.1'
DEBIAN_TAG: '2025-05-19.1'
FDO_UPSTREAM_REPO: libinput/libei
MESON_BUILDDIR: "builddir"
NINJA_ARGS: ''
MESON_ARGS: ''
MESON_ARGS: '-Dauto_features=enabled'
MESON_TEST_ARGS: ''
GIT_DEPTH: 1
@ -136,18 +138,18 @@ check-merge-request:
junit: results.xml
allow_failure: true
# Format anything python with python-black
# Format anything python with ruff
#
python-black:
python-ruff-format:
extends:
- .fdo.ci-fairy
stage: prep
before_script:
- python3 -m venv venv
- source venv/bin/activate
- pip3 install black
- pip3 install ruff
script:
- black --check --diff . proto/ei-scanner
- ruff format --check --diff . proto/ei-scanner
# Lint with Ruff
#
@ -166,14 +168,14 @@ python-ruff:
# Build distribution-specific images used by the jobs in the build stage
#
fedora:38@container-prep:
fedora:43@container-prep:
extends:
- .fdo.container-build@fedora
- .policy
stage: prep
variables:
GIT_STRATEGY: none
FDO_DISTRIBUTION_VERSION: '38'
FDO_DISTRIBUTION_VERSION: '43'
FDO_DISTRIBUTION_PACKAGES: $FEDORA_PACKAGES
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
FDO_DISTRIBUTION_EXEC: 'pip install $FEDORA_PIP_PACKAGES'
@ -224,12 +226,12 @@ debian:bullseye@container-prep:
- .build@template
variables:
MESON_TEST_ARGS: '--no-suite=python'
FDO_DISTRIBUTION_VERSION: '38'
FDO_DISTRIBUTION_VERSION: '43'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "fedora:38@container-prep"
- "fedora:43@container-prep"
default-build-release@fedora:38:
default-build-release@fedora:43:
stage: distro
extends:
- .fedora-build@template
@ -237,21 +239,32 @@ default-build-release@fedora:38:
MESON_ARGS: "-Dbuildtype=release"
CFLAGS: "-Werror -Wno-error=vla-parameter" # munit triggers -Wvla-parameter
build-no-libxkcommon-nodeps@fedora:38:
build-no-libxkcommon-nodeps@fedora:43:
extends:
- .fedora-build@template
before_script:
- dnf remove -y libxkcommon-devel
build-no-doxygen@fedora:38:
build-no-doxygen@fedora:43:
extends:
- .fedora-build@template
variables:
MESON_ARGS: "-Ddocumentation=[]"
before_script:
- dnf remove -y doxygen hugo
- dnf remove -y doxygen
valgrind@fedora:38:
build-disable-features@fedora:43:
extends:
- .fedora-build@template
parallel:
matrix:
- FEATURE: libei
- FEATURE: libeis
- FEATURE: liboeffis
variables:
MESON_ARGS: '-D${FEATURE}=disabled'
valgrind@fedora:43:
extends:
- .fedora-build@template
variables:
@ -259,40 +272,69 @@ valgrind@fedora:38:
before_script:
- dnf install -y valgrind
pytest@fedora:38:
pytest@fedora:43:
extends:
- .fedora-build@template
variables:
MESON_TEST_ARGS: '--suite=python'
werror@fedora:38:
werror@fedora:43:
extends:
- .fedora-build@template
variables:
MESON_ARGS: '-Dwerror=true'
allow_failure: true
abicheck@fedora:38:
abicheck@fedora:43:
extends:
- .fedora-build@template
variables:
GIT_STRATEGY: none # We need the upstream repo instead
MESON_ARGS: '-Dwerror=true'
script:
before_script:
- git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi
- |
pushd check-abi
meson _build
meson setup _build
meson compile -C _build
meson install -C _build
popd
- git clone --depth=1 https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- cd libei
- git fetch --tags
- check-abi 0.99.1 HEAD
- pip install attrs # required by libei 1.0.0
script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID
- check-abi abe85e051e7029bfd2e7913ab980a9e0042b6d0d $CI_COMMIT_SHA
only:
- merge_requests
event-type-check@fedora:43:
extends:
- .fedora-build@template
script:
- .gitlab-ci/meson-build.sh --skip-test
- .gitlab-ci/check-event-values.py
variables:
PKG_CONFIG_PATH: $MESON_BUILDDIR/meson-uninstalled
only:
- merge_requests
.debian-build@template:
extends:
- .fdo.distribution-image@debian
- .build@template
variables:
MESON_TEST_ARGS: '--no-suite=python'
FDO_DISTRIBUTION_VERSION: 'bullseye'
FDO_DISTRIBUTION_TAG: $DEBIAN_TAG
needs:
- "debian:bullseye@container-prep"
minimum-meson@debian:bullseye:
extends:
- .debian-build@template
script:
- pip uninstall -y meson
- pip install "meson==0.57.0"
- .gitlab-ci/meson-build.sh --run-test
#################################################################
# #
@ -300,16 +342,38 @@ abicheck@fedora:38:
# #
#################################################################
fedora:38@default-build:
fedora:43@default-build:
stage: distro
extends:
- .build@template
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '38'
FDO_DISTRIBUTION_VERSION: '43'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
needs:
- "fedora:38@container-prep"
- "fedora:43@container-prep"
fedora:43@doc-build:
stage: distro
extends:
- .build@template
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '43'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
MESON_ARGS: "-Ddocumentation=protocol,api"
script:
# Staying up-to-date with the breakages between hugo and the relearn theme is annoying
# so let's lock the version for our doc build
- go install "github.com/gohugoio/hugo@v0.142"
- export PATH="$HOME/go/bin:$PATH"
- .gitlab-ci/meson-build.sh
- rm -rf public/
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
- mv "$MESON_BUILDDIR"/doc/html/ public/api
artifacts:
paths:
- public
debian:bullseye@default-build:
stage: distro
@ -323,21 +387,22 @@ debian:bullseye@default-build:
- "debian:bullseye@container-prep"
pages:
stage: deploy
extends:
- .build@template
- .fdo.distribution-image@fedora
variables:
FDO_DISTRIBUTION_VERSION: '38'
FDO_DISTRIBUTION_VERSION: '43'
FDO_DISTRIBUTION_TAG: $FEDORA_TAG
MESON_ARGS: "-Ddocumentation=protocol,api"
script:
- .gitlab-ci/meson-build.sh
- rm -rf public/
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
- mv "$MESON_BUILDDIR"/doc/html/ public/api
- echo "Nothing to do"
dependencies:
- "fedora:43@doc-build"
needs:
- "fedora:43@doc-build"
only:
refs:
- main

View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
#
# Check that the EI_EVENT_FOO and EIS_EVENT_FOO enum values match (for those events that
# exist in both libraries).
from typing import Iterator
from pathlib import Path
import re
import subprocess
import tempfile
template = """
#include <libei.h>
#include <libeis.h>
@@@
int main(void) {
return 0;
}
"""
assert_template = '_Static_assert(EIS_EVENT_{event} == EI_EVENT_{event}, "Mismatching event types for {event}");'
def extract(header: Path, prefix: str) -> Iterator[str]:
with open(header) as fd:
for line in fd:
match = re.match(rf"^\t{prefix}(\w+)", line)
if match:
yield match[1]
if __name__ == "__main__":
ei_events = extract(Path("src/libei.h"), prefix="EI_EVENT_")
eis_events = extract(Path("src/libeis.h"), prefix="EIS_EVENT_")
common = set(ei_events) & set(eis_events)
print("Shared events that need identical values:")
for e in common:
print(f" EI_EVENT_{e}")
asserts = (assert_template.format(event=e) for e in common)
with tempfile.NamedTemporaryFile(suffix=".c", delete=False) as fd:
fd.write(template.replace("@@@", "\n".join(asserts)).encode("utf-8"))
fd.flush()
pkgconfig = subprocess.run(
["pkg-config", "--cflags", "--libs", "libei-1.0", "libeis-1.0"],
check=True,
capture_output=True,
)
pkgconfig_args = pkgconfig.stdout.decode("utf-8").strip().split(" ")
try:
subprocess.run(
["gcc", "-o", "event-type-check", *pkgconfig_args, fd.name],
check=True,
capture_output=True,
)
print("Success. Event types are identical")
except subprocess.CalledProcessError as e:
print("Mismatching event types")
print(e.stdout.decode("utf-8"))
print(e.stderr.decode("utf-8"))
raise SystemExit(1)

View file

@ -8,7 +8,7 @@
# #
########################################
.templates_sha: &template_sha 9f0eb526291fe74651fe1430cbd2397f4c0a819b # see https://docs.gitlab.com/ee/ci/yaml/#includefile
.templates_sha: &template_sha c6aeb16f86e32525fa630fb99c66c4f3e62fc3cb
include:
- project: 'freedesktop/ci-templates'
@ -29,8 +29,10 @@ stages:
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- if: $CI_PIPELINE_SOURCE == 'push'
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI_COMMIT_BRANCH
variables:
###############################################################################
@ -61,7 +63,7 @@ variables:
MESON_BUILDDIR: "builddir"
NINJA_ARGS: ''
MESON_ARGS: ''
MESON_ARGS: '-Dauto_features=enabled'
MESON_TEST_ARGS: ''
GIT_DEPTH: 1
@ -142,18 +144,18 @@ check-merge-request:
junit: results.xml
allow_failure: true
# Format anything python with python-black
# Format anything python with ruff
#
python-black:
python-ruff-format:
extends:
- .fdo.ci-fairy
stage: prep
before_script:
- python3 -m venv venv
- source venv/bin/activate
- pip3 install black
- pip3 install ruff
script:
- black --check --diff . proto/ei-scanner
- ruff format --check --diff . proto/ei-scanner
# Lint with Ruff
#
@ -220,7 +222,7 @@ python-ruff:
{% set version = "{}".format(distro.versions|last()) %}
.{{distro.name}}-build@template:
extends:
- .fdo.distribution-image@fedora
- .fdo.distribution-image@{{distro.name}}
- .build@template
variables:
MESON_TEST_ARGS: '--no-suite=python'
@ -249,7 +251,18 @@ build-no-doxygen@{{distro.name}}:{{version}}:
variables:
MESON_ARGS: "-Ddocumentation=[]"
before_script:
- dnf remove -y doxygen hugo
- dnf remove -y doxygen
build-disable-features@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template
parallel:
matrix:
- FEATURE: libei
- FEATURE: libeis
- FEATURE: liboeffis
variables:
MESON_ARGS: '-D${FEATURE}=disabled'
valgrind@{{distro.name}}:{{version}}:
extends:
@ -275,24 +288,56 @@ werror@{{distro.name}}:{{version}}:
abicheck@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template
variables:
GIT_STRATEGY: none # We need the upstream repo instead
MESON_ARGS: '-Dwerror=true'
script:
before_script:
- git clone --depth=1 https://gitlab.freedesktop.org/hadess/check-abi
- |
pushd check-abi
meson _build
meson setup _build
meson compile -C _build
meson install -C _build
popd
- git clone --depth=1 https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- cd libei
- git fetch --tags
- check-abi {{last_abi_break}} HEAD
- pip install attrs # required by libei 1.0.0
script:
- git remote add upstream$CI_JOB_ID https://gitlab.freedesktop.org/$FDO_UPSTREAM_REPO
- git fetch --tags upstream$CI_JOB_ID
- check-abi {{last_abi_break}} $CI_COMMIT_SHA
only:
- merge_requests
event-type-check@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template
script:
- .gitlab-ci/meson-build.sh --skip-test
- .gitlab-ci/check-event-values.py
variables:
PKG_CONFIG_PATH: $MESON_BUILDDIR/meson-uninstalled
only:
- merge_requests
{% endfor %}
{% for distro in distributions if distro.use_for_minimal_meson_test %}
{% set version = "{}".format(distro.versions|last()) %}
.{{distro.name}}-build@template:
extends:
- .fdo.distribution-image@{{distro.name}}
- .build@template
variables:
MESON_TEST_ARGS: '--no-suite=python'
FDO_DISTRIBUTION_VERSION: '{{version}}'
FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG
needs:
- "{{distro.name}}:{{version}}@container-prep"
minimum-meson@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template
script:
- pip uninstall -y meson
- pip install "meson=={{minimum_meson_version}}"
- .gitlab-ci/meson-build.sh --run-test
{% endfor %}
#################################################################
@ -320,6 +365,30 @@ abicheck@{{distro.name}}:{{version}}:
needs:
- "{{distro.name}}:{{version}}@container-prep"
{% if distro.name == pages.distro %}
{{distro.name}}:{{version}}@doc-build:
stage: distro
extends:
- .build@template
- .fdo.distribution-image@{{distro.name}}
variables:
FDO_DISTRIBUTION_VERSION: '{{version}}'
FDO_DISTRIBUTION_TAG: ${{distro.name.upper()}}_TAG
MESON_ARGS: "-Ddocumentation=protocol,api"
script:
# Staying up-to-date with the breakages between hugo and the relearn theme is annoying
# so let's lock the version for our doc build
- go install "github.com/gohugoio/hugo@v0.142"
- export PATH="$HOME/go/bin:$PATH"
- .gitlab-ci/meson-build.sh
- rm -rf public/
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
- mv "$MESON_BUILDDIR"/doc/html/ public/api
artifacts:
paths:
- public
{% endif %}
{% endfor %}
{% endfor %}
@ -333,11 +402,11 @@ pages:
FDO_DISTRIBUTION_TAG: ${{pages.distro.upper()}}_TAG
MESON_ARGS: "-Ddocumentation=protocol,api"
script:
- .gitlab-ci/meson-build.sh
- rm -rf public/
- mv "$MESON_BUILDDIR"/doc/protocol/ei/public/ public/
- mv "$MESON_BUILDDIR"/doc/html/ public/api
- echo "Nothing to do"
dependencies:
- "{{pages.distro}}:{{pages.version}}@doc-build"
needs:
- "{{pages.distro}}:{{pages.version}}@doc-build"
only:
refs:
- main

View file

@ -3,15 +3,16 @@
#
# We're happy to rebuild all containers when one changes.
.default_tag: &default_tag '2023-05-18.3'
.default_tag: &default_tag '2025-05-19.1'
last_abi_break: 0.99.1
last_abi_break: abe85e051e7029bfd2e7913ab980a9e0042b6d0d
minimum_meson_version: 0.57.0
distributions:
- name: fedora
tag: *default_tag
versions:
- '38' # update the pages job when bumping the version
- '43' # update the pages job when bumping the version
use_for_custom_build_tests: true
packages:
- git
@ -23,12 +24,12 @@ distributions:
- libxkbcommon-devel
- libxml2
- doxygen
- python3-attrs
- python3-pytest
- python3-dbusmock
- python3-jinja2
- python3-pip
- hugo # for documentation only
- python3-pyyaml
- golang # for documentation only
- libabigail # for abidiff only
pips:
- meson
@ -37,6 +38,7 @@ distributions:
- strenum
- name: debian
tag: *default_tag
use_for_minimal_meson_test: true
versions:
- 'bullseye'
packages:
@ -48,11 +50,11 @@ distributions:
- libxkbcommon-dev
- libxml2
- doxygen
- python3-attr
- python3-pytest
- python3-dbusmock
- python3-jinja2
- python3-pip
- python3-yaml
pips:
- meson
- ninja
@ -61,4 +63,4 @@ distributions:
pages:
distro: fedora
version: 38
version: 43

View file

@ -1,5 +1,8 @@
#!/usr/bin/env bash
#
# This script is sourced from here:
# https://gitlab.freedesktop.org/whot/meson-helper
#
# SPDX-License-Identifier: MIT
set -x

View file

@ -1,21 +1,20 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-symlinks
- id: no-commit-to-branch
args: ['--branch', 'main']
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black
args: ['--check', '--diff', '.', 'proto/ei-scanner']
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.254
rev: v0.9.4
hooks:
- id: ruff
args: ['--ignore=E741,E501', '.', 'proto/ei-scanner']
- id: ruff-format
args: ['.', 'proto/ei-scanner']
- repo: local
hooks:
- id: ci-fairy-generate-template

View file

@ -319,19 +319,19 @@ like `xdotool`. It provides context and device negotiation between the
server and the client - the latter must be able to adjust to limitations the
server imposes.
The current implemtation of the protocol does not allow for a `libei` client
to send all requests in bulk and exit. The decision on whether to accept a
device is ultimately made by the caller implementation and
non-deterministic. For **libei** to support a batch request, *someone* would
have to wait. It cannot be the server as the exact requirements are unknown: do
we pause processing on the client altogether? We may miss a disconnect
event? Do we pause processing for one device only? But then we may be
re-ordering input events and cause havoc.
The current implementation of the protocol does not allow for a `libei` client
to connect, send all requests in bulk and exit. The decision on which devices
are available is made by the EIS implementation and that requires the `libei`
client to wait until such devices are available. On a technical level the
protocol is object-oriented and requests cannot be sent until the respective
device object is available.
It could be `libei` itself to implement these event queues but this too can
mess with the input order. And implementing an event queue is not hard, so
this issue is punted to the caller instead. Xwayland in its current
implementation already does this.
The duration until devices become available is non-deterministic, and so is
what types of devices are available to a client. For **libei** to support a
connect-send-exit approach, it would still require the client process to stay
alive until device negotiation is complete within libei and all events have
been sent. And since the client process must stay alive, we might as well
have the device negotiation handled in the caller.
### uinput vs libei

View file

@ -2,28 +2,30 @@ if 'api' not in get_option('documentation')
subdir_done()
endif
doxygen = find_program('doxygen', required : false)
doxygen = find_program('doxygen', required: false)
if not doxygen.found()
error('Program "doxygen" not found or not executable. Try building with -Ddocumentation=false')
error('Program "doxygen" not found or not executable. Try building with -Ddocumentation=false')
endif
mainpage = vcs_tag(command : ['git', 'log', '-1', '--format=%h'],
fallback : 'unknown',
input : 'mainpage.dox',
output : 'mainpage.dox',
replace_string: '__GIT_VERSION__')
mainpage = vcs_tag(command: ['git', 'log', '-1', '--format=%h'],
fallback: 'unknown',
input: 'mainpage.dox',
output: 'mainpage.dox',
replace_string: '__GIT_VERSION__',
)
src_doxygen = files(
# style files
'doxygen-awesome.css',
# style files
'doxygen-awesome.css',
) + [libei_headers, libeis_headers, liboeffis_headers]
doxyfiles = []
foreach f : src_doxygen
df = configure_file(input: f,
output: '@PLAINNAME@',
copy : true)
doxyfiles += [ df ]
foreach f: src_doxygen
df = configure_file(input: f,
output: '@PLAINNAME@',
copy: true,
)
doxyfiles += [df]
endforeach
doc_config = configuration_data()
@ -31,14 +33,16 @@ doc_config.set('PACKAGE_NAME', meson.project_name())
doc_config.set('PACKAGE_VERSION', meson.project_version())
doc_config.set('builddir', meson.current_build_dir())
doxyfile = configure_file(input : 'libei.doxygen.in',
output : 'libei.doxygen',
configuration : doc_config)
doxyfile = configure_file(input: 'libei.doxygen.in',
output: 'libei.doxygen',
configuration: doc_config,
)
custom_target('doxygen',
input : [ doxyfile, mainpage ] + src_doxygen + doxyfiles,
output : [ 'doc' ],
command : [ doxygen, doxyfile ],
install : false,
depends: [ mainpage ],
build_by_default : true)
input: [ doxyfile, mainpage ] + src_doxygen + doxyfiles,
output: [ 'doc' ],
command: [ doxygen, doxyfile ],
install: false,
depends: [ mainpage ],
build_by_default: true,
)

View file

@ -14,7 +14,7 @@ server side, typically a Wayland compositor, is called the **"EIS Implementation
This documentation details the protocol to communicate between the client side
and the EIS implementation.
A typical Compositor setup using the `libei` and `libeis` [C libraries]({{< relref "libraries" >}}) looks like this:
A typical Compositor setup using the `libei` and `libeis` [C libraries]({{% relref "libraries" %}}) looks like this:
{{< mermaid >}}
flowchart LR;
@ -69,7 +69,7 @@ If you are looking for easy-to-use C libraries instead, see:
- 🥚 [libei](https://libinput.pages.freedesktop.org/libei/api/group__libei.html) for the client side
- 🍦 [libeis](https://libinput.pages.freedesktop.org/libei/api/group__libeis.html) for the EIS implementation side
- 🚌 [liboeffis](https://libinput.pages.freedesktop.org/libei/api/group__oeffis.html) is an helper library for DBus communication with the
- 🚌 [liboeffis](https://libinput.pages.freedesktop.org/libei/api/group__liboeffis.html) is an helper library for DBus communication with the
XDG RemoteDesktop portal (`liboeffis`)

View file

@ -10,7 +10,7 @@ weight: 5
In the libei repo, we use the
[ei-scanner](https://gitlab.freedesktop.org/libinput/libei/-/blob/main/proto/ei-scanner)
to generate bindings for C and Python (for tests) as well the [interface
documentation]({{< relref "interfaces/" >}}) in this documentation.
documentation]({{% relref "interfaces/" %}}) in this documentation.
Note: these generated protocol bindings are **not** part of libei's API contract.

View file

@ -6,12 +6,12 @@ weight: 4
---
The initial connection is a two-step process:
An [ei_handshake]({{< relref "interfaces/ei_handshake" >}}) object with the special ID 0 is guaranteed to
An [ei_handshake]({{% relref "interfaces/ei_handshake" %}}) object with the special ID 0 is guaranteed to
exists. The client must send the appropriate requests to set up
its connection, followed by the `ei_handshake.finish` request. The EIS
implementation replies by creating the [ei_connection]({{< relref "interfaces/ei_connection" >}}) object with the
implementation replies by creating the [ei_connection]({{% relref "interfaces/ei_connection" %}}) object with the
client-requested version (or any lower version) that is the connection for the
remainder of this client (see [version negotiation]({{< relref "doc/specification#version-negotiation" >}}).
remainder of this client (see [version negotiation]({{% relref "doc/specification#version-negotiation" %}}).
Immediately after connecting, the EIS implementation must send the
`ei_handshake.handshake_version` event. The client replies with the

View file

@ -31,10 +31,10 @@ flowchart LR;
{{< /mermaid >}}
Objects are identified by a unique object ID, assigned at creation of the object.
The type of an object is defined by its [interface]({{< relref "interfaces" >}})
The type of an object is defined by its [interface]({{% relref "interfaces" %}})
and agreed on at object creation. Each object has exactly one interface, but
there may be multiple objects with that interface. For example, a compositor
may create multiple objects with the [`ei_device`]({{< relref "interfaces/ei_device" >}})
may create multiple objects with the [`ei_device`]({{% relref "interfaces/ei_device" %}})
interface.
All data on the protocol (e.g. object IDs) is private to that client's
@ -57,7 +57,7 @@ content: |object-id |length |opcode |...
Where:
- `object-id` is one 64-bit unsigned integer that uniquely identifies
the object sending the request/event. The `object-id`
0 is reserved for the special [`ei_handshake`]({{< relref "interfaces/ei_handshake" >}}) object.
0 is reserved for the special [`ei_handshake`]({{% relref "interfaces/ei_handshake" %}}) object.
- `length` is a 32-bit integer that specifies the length of the message in
bytes, including the 16 header bytes for `object-id`, `length` and `opcode`.
- `opcode` is a 32-bit integer that specifies the event or request-specific

View file

@ -9,7 +9,7 @@ The current protocol specification is available [in XML format here](https://git
In that protocol specification:
- a request or event with the XML attribute `type="destructor"` marks the message as [destructor]({{< relref "#destructors" >}}).
- a request or event with the XML attribute `type="destructor"` marks the message as [destructor]({{% relref "#destructors" %}}).
- an argument with an XML attribute `enum` carries a value of the corresponding enum
- an argument with an XML attribute `interface` attribute indicates that an object in the same message is
of that interface type

View file

@ -20,9 +20,9 @@ All types are encoded in the EIS implementation's native byte order.
| int64 | 64 | signed integer | `int64_t` | |
| float | 32 | IEEE-754 float | `float` | |
| fd | 0 | file descriptor | `int` | see [^1] |
| string | 32 + N| length + string | `int`, `char[]` | see [String Encoding]({{< ref "#string-encoding" >}}) |
| new_id | 64 | object id allocated by the caller | `uint64_t` | see [Object IDs]({{< ref "#object-ids" >}}) |
| object_id | 64 | previously allocated object id | `uint64_t` | see [Object IDs]({{< ref "#object-ids" >}}) |
| string | 32 + N| length + string | `int`, `char[]` | see [String Encoding]({{% ref "#string-encoding" %}}) |
| new_id | 64 | object id allocated by the caller | `uint64_t` | see [Object IDs]({{% ref "#object-ids" %}}) |
| object_id | 64 | previously allocated object id | `uint64_t` | see [Object IDs]({{% ref "#object-ids" %}}) |
[^1]: zero bytes in the message itself, transmitted in the overhead

View file

@ -60,7 +60,7 @@ else
git clone --depth=1 https://github.com/McShelby/hugo-theme-relearn "$SITEDIR/themes/hugo-theme-relearn"
fi
cp "$TEMPLATEDIR/config.toml" "$SITEDIR/"
cp "$TEMPLATEDIR/hugo.toml" "$SITEDIR/"
pushd "$TEMPLATEDIR" > /dev/null || exit 1
find . -type f -name "*.md"

View file

@ -19,3 +19,6 @@ name = "<i class='fas fa-bug'></i> Report a Bug"
identifier = "bugs"
url = "https://gitlab.freedesktop.org/libinput/libei/-/issues/"
weight = 10
[params]
mermaid = true

View file

@ -2,9 +2,9 @@ if 'protocol' not in get_option('documentation')
subdir_done()
endif
hugo = find_program('hugo', required : false)
hugo = find_program('hugo', required: false)
if not hugo.found()
error('Program "hugo" not found or not executable. Try building with -Ddocumentation=false')
error('Program "hugo" not found or not executable. Try building with -Ddocumentation=false')
endif
src_script = files('generate-protocol-docs.sh')
@ -12,14 +12,14 @@ src_script = files('generate-protocol-docs.sh')
hugo_script = find_program(src_script)
src_hugo = files(
'config.toml',
'hugo.toml',
'interface.md.tmpl',
) + src_script
custom_target('hugo',
input : src_hugo + [protocol_xml],
output : [ 'doc' ],
command : [ hugo_script, '--git-repo', meson.project_source_root(), '--output-dir', meson.current_build_dir() ],
install : false,
build_by_default : true,
input: src_hugo + [protocol_xml],
output: ['doc'],
command: [hugo_script, '--git-repo', meson.project_source_root(), '--output-dir', meson.current_build_dir()],
install: false,
build_by_default: true,
)

View file

@ -1,8 +1,9 @@
project('libei', 'c',
version: '0.99.2',
license: 'MIT',
default_options: [ 'c_std=gnu99', 'warning_level=2' ],
meson_version: '>= 0.60.0')
version: '1.5.0',
license: 'MIT',
default_options: [ 'c_std=gnu11', 'warning_level=2' ],
meson_version: '>= 0.57.0',
)
libei_version = meson.project_version().split('.')
libei_version_major = libei_version[0].to_int()
@ -20,17 +21,13 @@ libei_api_dir = 'libei-@0@'.format(libei_api_version)
# We use the same soname across all our libraries and they track the project
# version. If we have ABI incompatible changes, bump the project major version.
if libei_version_major < 1
soname = '1.0.0' # Remove after the 1.0 release
else
soname = meson.project_version()
endif
soname = meson.project_version()
pkgconfig = import('pkgconfig')
fs = import('fs')
cc = meson.get_compiler('c')
cflags =[
cflags = [
'-Wno-unused-parameter',
'-Wmissing-prototypes',
'-Wno-missing-field-initializers',
@ -74,7 +71,16 @@ config_h.set('_GNU_SOURCE', '1')
config_h.set_quoted('EI_VERSION', meson.project_version())
config_h.set_quoted('EIS_VERSION', meson.project_version())
if cc.has_function('memfd_create', prefix: '#define _GNU_SOURCE\n#include <sys/mman.h>')
config_h.set10('HAVE_MEMFD_CREATE', true)
config_h.set10('HAVE_MEMFD_CREATE', true)
endif
code = '''
#include <time.h>
void func() { auto foo = gmtime(NULL); foo->tm_sec = 0; }
'''
if cc.compiles(code, name: 'has C23 auto keyword')
config_h.set('HAVE_C23_AUTO', '1')
endif
dep_math = cc.find_library('m', required: false)
@ -105,6 +111,11 @@ if not get_option('liboeffis').disabled()
else
dep_sdbus = dependency('', required: false)
endif
build_oeffis = dep_sdbus.found()
build_libeis = not get_option('libeis').disabled()
build_libei = not get_option('libei').disabled()
config_h.set10('HAVE_LIBSYSTEMD', dep_sdbus.found() and dep_sdbus.name() == 'libsystemd')
config_h.set10('HAVE_LIBELOGIND', dep_sdbus.found() and dep_sdbus.name() == 'libelogind')
config_h.set10('HAVE_BASU', dep_sdbus.found() and dep_sdbus.name() == 'basu')
@ -117,18 +128,14 @@ subdir('tools')
subdir('test')
subdir('doc')
black = find_program('black', required: false)
if black.found()
test('python-black', black,
args: ['--check', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
suite: 'python',
)
endif
ruff = find_program('ruff', required: false)
if ruff.found()
test('python-ruff', ruff,
args: ['check', '--ignore=E741,E501', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
suite: 'python',
)
test('python-ruff-format', ruff,
args: ['format', meson.project_source_root(), meson.project_source_root() / 'proto' / 'ei-scanner'],
suite: 'python',
)
endif

View file

@ -2,3 +2,5 @@ option('documentation', type: 'array', value: [], choices: ['api', 'protocol'],
option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
option('tests', type: 'feature', value: 'auto', description: 'Enable/disable tests')
option('liboeffis', type: 'feature', value: 'auto', description: 'Build liboeffis.so')
option('libeis', type: 'feature', value: 'auto', description: 'Build libeis.so')
option('libei', type: 'feature', value: 'auto', description: 'Build libei.so')

View file

@ -17,12 +17,12 @@ Opcodes for events and request are assigned in order as they
appear in the XML file.
"""
from typing import Any, List, Optional, Tuple, Union
from typing import Any, Dict, List, Optional, Tuple, Union
from pathlib import Path
from textwrap import dedent
from dataclasses import dataclass, field
import argparse
import attr
import jinja2
import jinja2.environment
import os
@ -55,23 +55,53 @@ def snake2camel(s: str) -> str:
return s.replace("_", " ").title().replace(" ", "")
@attr.s
@dataclass
class Description:
summary: str = attr.ib(default="")
text: str = attr.ib(default="")
summary: str = ""
text: str = ""
@attr.s
@dataclass
class Argument:
"""
Argument to a request or a reply
"""
name: str = attr.ib()
protocol_type: str = attr.ib()
summary: str = attr.ib()
enum: Optional["Enum"] = attr.ib()
interface: Optional["Interface"] = attr.ib()
name: str
protocol_type: str
summary: str
enum: Optional["Enum"]
interface: Optional["Interface"]
interface_arg: Optional["Argument"] = None
"""
For an argument with "interface_arg", this field points to the argument that
contains the interface name.
"""
interface_arg_for: Optional["Argument"] = None
"""
For an argument referenced by another argument through "interface_name", this field
points to the other argument that references this argument.
"""
version_arg: Optional["Argument"] = None
"""
For an argument with type "new_id", this field points to the argument that
contains the version for this new object.
"""
version_arg_for: Optional["Argument"] = None
"""
For an argument referenced by another argument of type "new_id", this field
points to the other argument that references this argument.
"""
allow_null: bool = False
"""
For an argument of type string, specify if the argument may be NULL.
"""
def __post_init(self):
if self.protocol_type is None or self.protocol_type not in PROTOCOL_TYPES:
raise ValueError(f"Failed to parse protocol_type {self.protocol_type}")
if self.interface is not None and self.signature not in ["n", "o"]:
raise ValueError("Interface may only be set for object types")
@property
def signature(self) -> str:
@ -80,11 +110,6 @@ class Argument:
"""
return PROTOCOL_TYPES[self.protocol_type]
@interface.validator # type: ignore
def _validate_interface(self, attribute, value):
if value is not None and self.signature not in ["n", "o"]:
raise ValueError("Interface may only be set for object types")
@property
def as_c_arg(self) -> str:
return f"{self.c_type} {self.name}"
@ -103,12 +128,6 @@ class Argument:
"new_id": "new_id_t",
}[self.protocol_type]
@protocol_type.validator # type: ignore
def _validate_protocol_type(self, attribute, value):
assert (
value is not None and value in PROTOCOL_TYPES
), f"Failed to parse protocol_type {value}"
@classmethod
def create(
cls,
@ -117,6 +136,7 @@ class Argument:
summary: str = "",
enum: Optional["Enum"] = None,
interface: Optional["Interface"] = None,
allow_null: bool = False,
) -> "Argument":
return cls(
name=name,
@ -124,29 +144,29 @@ class Argument:
summary=summary,
enum=enum,
interface=interface,
allow_null=allow_null,
)
@attr.s
@dataclass
class Message:
"""
Parent class for a wire message (Request or Event).
"""
name: str = attr.ib()
since: int = attr.ib()
opcode: int = attr.ib()
interface: "Interface" = attr.ib()
description: Optional[Description] = attr.ib(default=None)
is_destructor: bool = attr.ib(default=False)
context_type: Optional[str] = attr.ib(default=None)
name: str
since: int
opcode: int
interface: "Interface"
description: Optional[Description] = None
is_destructor: bool = False
context_type: Optional[str] = None
arguments: List[Argument] = attr.ib(init=False, factory=list)
arguments: List[Argument] = field(init=False, default_factory=list)
@context_type.validator # type: ignore
def _context_type_validate(self, attr, value):
if value not in [None, "sender", "receiver"]:
raise ValueError(f"Invalid context type {value}")
def __post_init(self):
if self.context_type not in [None, "sender", "receiver"]:
raise ValueError(f"Invalid context type {self.context_type}")
def add_argument(self, arg: Argument) -> None:
if arg.name in [a.name for a in self.arguments]:
@ -165,8 +185,14 @@ class Message:
def camel_name(self) -> str:
return snake2camel(self.name)
def find_argument(self, name: str) -> Optional[Argument]:
for a in self.arguments:
if a.name == name:
return a
return None
@attr.s
@dataclass
class Request(Message):
@classmethod
def create(
@ -193,7 +219,7 @@ class Request(Message):
return f"{self.interface.name}_request_{self.name}"
@attr.s
@dataclass
class Event(Message):
@classmethod
def create(
@ -220,17 +246,17 @@ class Event(Message):
return f"{self.interface.name}_event_{self.name}"
@attr.s
@dataclass
class Entry:
"""
An enum entry
"""
name: str = attr.ib()
value: int = attr.ib()
enum: "Enum" = attr.ib()
summary: str = attr.ib()
since: int = attr.ib()
name: str
value: int
enum: "Enum"
summary: str
since: int
@classmethod
def create(
@ -246,15 +272,15 @@ class Entry:
return f"{self.enum.fqdn}_{self.name}"
@attr.s
@dataclass
class Enum:
name: str = attr.ib()
since: int = attr.ib()
interface: "Interface" = attr.ib()
is_bitfield: bool = attr.ib(default=False)
description: Optional[Description] = attr.ib(default=None)
name: str
since: int
interface: "Interface"
is_bitfield: bool = False
description: Optional[Description] = None
entries: List[Entry] = attr.ib(init=False, factory=list)
entries: List[Entry] = field(init=False, default_factory=list)
@classmethod
def create(
@ -297,16 +323,20 @@ class Enum:
return snake2camel(self.name)
@attr.s
@dataclass
class Interface:
protocol_name: str = attr.ib() # name as in the XML, e.g. ei_pointer
version: int = attr.ib()
requests: List[Request] = attr.ib(init=False, factory=list)
events: List[Event] = attr.ib(init=False, factory=list)
enums: List[Enum] = attr.ib(init=False, factory=list)
protocol_name: str # name as in the XML, e.g. ei_pointer
version: int
requests: List[Request] = field(init=False, default_factory=list)
events: List[Event] = field(init=False, default_factory=list)
enums: List[Enum] = field(init=False, default_factory=list)
mode: str = attr.ib(validator=attr.validators.in_(["ei", "eis", "brei"]))
description: Optional[Description] = attr.ib(default=None)
mode: str
description: Optional[Description] = None
def __post_init(self):
if self.mode not in ["ei", "eis", "brei"]:
raise ValueError(f"Invalid mode {self.mode}")
@property
def name(self) -> str:
@ -412,11 +442,11 @@ class Interface:
return cls(protocol_name=protocol_name, version=version, mode=mode)
@attr.s
@dataclass
class XmlError(Exception):
line: int = attr.ib()
column: int = attr.ib()
message: str = attr.ib()
line: int
column: int
message: str
def __str__(self) -> str:
return f"line {self.line}:{self.column}: {self.message}"
@ -426,29 +456,34 @@ class XmlError(Exception):
return cls(line=location[0], column=location[1], message=message)
@attr.s
@dataclass
class Copyright:
text: str = attr.ib(default="")
is_complete: bool = attr.ib(init=False, default=False)
text: str = ""
is_complete: bool = field(init=False, default=False)
@attr.s
@dataclass
class Protocol:
copyright: Optional[str] = attr.ib(default=None)
interfaces: List[Interface] = attr.ib(factory=list)
copyright: Optional[str] = None
interfaces: List[Interface] = field(default_factory=list)
@attr.s
@dataclass
class ProtocolParser(xml.sax.handler.ContentHandler):
component: str = attr.ib()
interfaces: List[Interface] = attr.ib(factory=list)
copyright: Optional[Copyright] = attr.ib(init=False, default=None)
component: str
interfaces: List[Interface] = field(default_factory=list)
copyright: Optional[Copyright] = field(init=False, default=None)
current_interface: Optional[Interface] = attr.ib(init=False, default=None)
current_message: Optional[Union[Message, Enum]] = attr.ib(init=False, default=None)
current_description: Optional[Description] = attr.ib(init=False, default=None)
current_interface: Optional[Interface] = field(init=False, default=None)
current_message: Optional[Union[Message, Enum]] = field(init=False, default=None)
current_description: Optional[Description] = field(init=False, default=None)
# A dict of arg name to interface_arg name mappings
current_interface_arg_names: Dict[str, str] = field(
init=False, default_factory=dict
)
current_new_id_arg: Optional[Argument] = field(init=False, default=None)
_run_counter: int = attr.ib(init=False, default=0, repr=False)
_run_counter: int = field(init=False, default=0, repr=False)
@property
def location(self) -> Tuple[int, int]:
@ -485,7 +520,7 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
try:
name = attrs["name"]
version = attrs["version"]
version = int(attrs["version"])
except KeyError as e:
raise XmlError.create(
f"Missing attribute {e} in element '{element}'",
@ -524,12 +559,17 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
)
try:
name = attrs["name"]
since = attrs["since"]
since = int(attrs["since"])
except KeyError as e:
raise XmlError.create(
f"Missing attribute {e} in element '{element}'",
self.location,
)
if since > self.current_interface.version:
raise XmlError.create(
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
self.location,
)
try:
is_bitfield = {
@ -577,12 +617,18 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
try:
name = attrs["name"]
since = attrs["since"]
since = int(attrs["since"])
except KeyError as e:
raise XmlError.create(
f"Missing attribute {e} in element '{element}'",
self.location,
)
if since > self.current_interface.version:
raise XmlError.create(
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
self.location,
)
is_destructor = attrs.get("type", "") == "destructor"
opcode = len(self.current_interface.requests)
request = Request.create(
@ -611,12 +657,17 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
)
try:
name = attrs["name"]
since = attrs["since"]
since = int(attrs["since"])
except KeyError as e:
raise XmlError.create(
f"Missing attribute {e} in element '{element}'",
self.location,
)
if since > self.current_interface.version:
raise XmlError.create(
f"Invalid 'since' {since} for '{self.current_interface.name}.{name}'",
self.location,
)
is_destructor = attrs.get("type", "") == "destructor"
opcode = len(self.current_interface.events)
@ -658,6 +709,13 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
interface = self.interface_by_name(interface_name)
else:
interface = None
# interface_arg is set to the name of some other arg that specifies the actual
# interface name for this argument
interface_arg_name = attrs.get("interface_arg", None)
if interface_arg_name is not None:
self.current_interface_arg_names[name] = interface_arg_name
enum_name = attrs.get("enum", None)
enum = None
if enum_name is not None:
@ -673,14 +731,24 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
f"Failed to find enum '{intf.name}.{enum_name}'",
self.location,
)
allow_null = attrs.get("allow-null", "false") == "true"
arg = Argument.create(
name=name,
protocol_type=proto_type,
summary=summary,
enum=enum,
interface=interface,
allow_null=allow_null,
)
self.current_message.add_argument(arg)
if proto_type == "new_id":
if self.current_new_id_arg is not None:
raise XmlError.create(
f"Multiple args of type '{proto_type}' for '{self.current_interface.name}.{self.current_message.name}'",
self.location,
)
self.current_new_id_arg = arg
elif element == "entry":
if self.current_interface is None:
raise XmlError.create(
@ -734,6 +802,40 @@ class ProtocolParser(xml.sax.handler.ContentHandler):
if self._run_counter <= 2:
return
# Populate `interface_arg` and `interface_arg_for`, now we have all arguments
if name in ["request", "event"]:
assert isinstance(self.current_message, Message)
assert isinstance(self.current_interface, Interface)
# obj is the argument of type object that the interface applies to
# iname is the argument of type "interface_name" that specifies the interface
for obj, iname in self.current_interface_arg_names.items():
obj_arg = self.current_message.find_argument(obj)
iname_arg = self.current_message.find_argument(iname)
assert obj_arg is not None
assert iname_arg is not None
obj_arg.interface_arg = iname_arg
iname_arg.interface_arg_for = obj_arg
self.current_interface_arg_names = {}
if self.current_new_id_arg is not None:
arg = self.current_new_id_arg
version_arg = self.current_message.find_argument("version")
if version_arg is None:
# Sigh, protocol bug: ei_connection.sync one doesn't have a version arg
if (
f"{self.current_interface.plainname}.{self.current_message.name}"
!= "connection.sync"
):
raise XmlError.create(
f"Unable to find a version argument for {self.current_interface.plainname}.{self.current_message.name}::{arg.name}",
self.location,
)
else:
arg.version_arg = version_arg
version_arg.version_arg_for = arg
self.current_new_id_arg = None
if name == "request":
assert isinstance(self.current_message, Request)
self.current_message = None
@ -789,6 +891,7 @@ def generate_source(
data["interfaces"] = proto.interfaces
data["extra"] = extra_data
loader: jinja2.BaseLoader
if template == "-":
loader = jinja2.FunctionLoader(lambda _: sys.stdin.read())
filename = "<stdin>"
@ -819,9 +922,7 @@ def generate_source(
import re
return re.sub(
rf"({component}[_-]\w*)(\.[.\w]*)?", rf"{quotes}\1\2{quotes}", str
)
return re.sub(rf"({component}[_-]\w*)((\.\w+)*)", rf"{quotes}\1\2{quotes}", str)
env.filters["c_type"] = filter_c_type
env.filters["as_c_arg"] = filter_as_c_arg
@ -831,7 +932,7 @@ def generate_source(
return jtemplate.stream(data)
def main() -> None:
def scanner(argv: list[str]) -> None:
parser = argparse.ArgumentParser(
description=dedent(
"""
@ -893,7 +994,7 @@ def main() -> None:
"template", type=str, help="The Jinja2 compatible template file"
)
ns = parser.parse_args()
ns = parser.parse_args(argv)
assert ns.protocol.exists()
try:
@ -913,17 +1014,17 @@ def main() -> None:
extra_data = json.loads(ns.jinja_extra_data)
elif ns.jinja_extra_data_file is not None:
if ns.jinja_extra_data.name.endswith(
if ns.jinja_extra_data_file.name.endswith(
".yml"
) or ns.jinja_extra_data.name.endswith(".yaml"):
) or ns.jinja_extra_data_file.name.endswith(".yaml"):
import yaml
with open(ns.jinja_extra_data) as fd:
with open(ns.jinja_extra_data_file) as fd:
extra_data = yaml.safe_load(fd)
elif ns.jinja_extra_data.name.endswith(".json"):
elif ns.jinja_extra_data_file.name.endswith(".json"):
import json
with open(ns.jinja_extra_data) as fd:
with open(ns.jinja_extra_data_file) as fd:
extra_data = json.load(fd)
else:
print("Unknown file format for jinja data", file=sys.stderr)
@ -940,4 +1041,4 @@ def main() -> None:
if __name__ == "__main__":
main()
scanner(sys.argv[1:])

View file

@ -8,10 +8,13 @@ protocol_dtd = files(protocol_dtd_path)
xmllint = find_program('xmllint', required: false)
if xmllint.found()
test('dtdcheck', xmllint,
args: ['--dtdvalid', protocol_dtd, protocol_xml]
)
args: ['--dtdvalid', protocol_dtd, protocol_xml],
)
endif
pymod = import('python')
required_python_modules = ['attr', 'jinja2']
pymod.find_installation('python3', modules: required_python_modules)
required_python_modules = ['jinja2']
python = pymod.find_installation('python3', modules: required_python_modules)
if python.language_version().version_compare('<3.9')
error('Python 3.9 or later required')
endif

File diff suppressed because it is too large Load diff

2
ruff.toml Normal file
View file

@ -0,0 +1,2 @@
# Same as Black.
line-length = 88

View file

@ -49,12 +49,6 @@ struct brei_header {
} _packed_;
static_assert(sizeof(struct brei_header) == 16, "Unexpected size for brei_header struct");
struct brei_string {
uint32_t len;
const char str[];
};
static_assert(sizeof(struct brei_string) == 4, "Unexpected size for brei_string struct");
/**
* For a given string length (including null byte) return
* the number of bytes needed on the protocol, including the
@ -63,12 +57,13 @@ static_assert(sizeof(struct brei_string) == 4, "Unexpected size for brei_string
static inline uint32_t
brei_string_proto_length(uint32_t slen)
{
uint32_t length = sizeof(struct brei_string) + slen;
uint32_t length = 4 + slen;
uint32_t protolen = (length + 3)/4 * 4;
assert(protolen % 4 == 0);
return protolen;
}
static void
brei_context_destroy(struct brei_context *ctx)
{
@ -183,7 +178,7 @@ brei_log_msg(struct brei_context *brei,
static struct brei_result *
brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signature,
size_t *nargs_out, union brei_arg **args_out)
size_t *nargs_out, union brei_arg **args_out, char ***strings_out)
{
size_t nargs = strlen(signature);
if (nargs > 256) {
@ -193,11 +188,15 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
/* This over-allocates if we have more than one char per type but meh */
_cleanup_free_ union brei_arg *args = xalloc(nargs * sizeof(*args));
/* This over-allocates since not all args are strings but meh.
Needs to be NULL-terminated for strv_freep to work */
_cleanup_(strv_freep) char **strings = xalloc((nargs + 1) * sizeof(*strings));
const char *s = signature;
union brei_arg *arg = args;
uint32_t *p = (uint32_t*)iobuf_data(buf);
uint32_t *end = (uint32_t*)iobuf_data_end(buf);
size_t nstrings = 0;
nargs = 0;
while (*s) {
@ -223,24 +222,29 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
arg->h = iobuf_take_fd(buf);
break;
case 's': {
struct brei_string *s = (struct brei_string *)p;
uint32_t slen = *p;
uint32_t remaining = end - p;
uint32_t protolen = brei_string_proto_length(s->len); /* in bytes */
uint32_t protolen = brei_string_proto_length(slen); /* in bytes */
uint32_t len32 = protolen/4; /* p and end are uint32_t* */
if (remaining < len32) {
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid string length %u, only %u bytes remaining", s->len, remaining * 4);
"Invalid string length %u, only %u bytes remaining", slen, remaining * 4);
}
if (s->len == 0) {
if (slen == 0) {
arg->s = NULL;
} else if (s->str[s->len - 1] != '\0') {
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Message string not zero-terminated");
} else {
arg->s = s->str;
_cleanup_free_ char *str = xalloc(slen);
memcpy(str, p + 1, slen);
if (str[slen - 1] != '\0') {
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Message string not zero-terminated");
}
strings[nstrings] = steal(&str);
arg->s = strings[nstrings];
nstrings++;
}
p += len32;
break;
@ -255,6 +259,7 @@ brei_demarshal(struct brei_context *brei, struct iobuf *buf, const char *signatu
}
*args_out = steal(&args);
*strings_out = steal(&strings);
*nargs_out = nargs;
return NULL;
@ -275,25 +280,25 @@ brei_marshal(struct brei_context *brei, struct iobuf *buf, const char *signature
switch (*s) {
case 'i':
i = va_arg(args, int32_t);
iobuf_append(buf, (const char*)(&i), 4);
iobuf_append_u32(buf, i);
break;
case 'u':
u = va_arg(args, uint32_t);
iobuf_append(buf, (const char*)(&u), 4);
iobuf_append_u32(buf, u);
break;
case 'x':
x = va_arg(args, int64_t);
iobuf_append(buf, (const char*)(&x), 8);
iobuf_append_u64(buf, x);
break;
case 'o':
case 'n':
case 't':
t = va_arg(args, uint64_t);
iobuf_append(buf, (const char*)(&t), 8);
iobuf_append_u64(buf, t);
break;
case 'f':
f = va_arg(args, double);
iobuf_append(buf, (const char*)(&f), 4);
iobuf_append_f32(buf, f);
break;
case 'h':
fd = va_arg(args, int);
@ -304,8 +309,8 @@ brei_marshal(struct brei_context *brei, struct iobuf *buf, const char *signature
const char *str = va_arg(args, const char*);
/* FIXME: nullable strings */
size_t slen = str ? strlen(str) + 1 : 0;
iobuf_append(buf, (const char*)&slen, 4);
uint32_t slen = str ? strlen(str) + 1 : 0;
iobuf_append_u32(buf, slen);
if (slen > 0) {
iobuf_append(buf, str, slen);
if (slen % 4)
@ -340,7 +345,7 @@ brei_marshal_message(struct brei_context *brei,
size_t message_len = iobuf_len(buf) + sizeof(struct brei_header);
uint32_t header[4] = {0, 0, message_len, opcode};
memcpy(header, &id, sizeof(id));
iobuf_prepend(buf, (const char *)header, sizeof(header));
iobuf_prepend(buf, header, sizeof(header));
return brei_result_new_success(steal(&buf));
}
@ -412,14 +417,15 @@ brei_dispatch(struct brei_context *brei,
iobuf_pop(buf, headersize);
/* Demarshal the protocol into a set of arguments */
_cleanup_free_ union brei_arg * args = NULL;
_cleanup_free_ union brei_arg *args = NULL;
_cleanup_(strv_freep) char **strings = NULL;
const char *signature = interface->incoming[opcode].signature;
size_t nargs = 0;
result = brei_demarshal(brei, buf, signature, &nargs, &args);
result = brei_demarshal(brei, buf, signature, &nargs, &args, &strings);
if (result)
goto error;
log_debug(brei, "dispatching %s.%s() on object %#" PRIx64 "", interface->name, interface->incoming[opcode].name, object_id);
log_debug(brei, "object %#" PRIx64 " dispatching %s.%s()", object_id, interface->name, interface->incoming[opcode].name);
/* Success! Let's pass this on to the
* context to process */
@ -491,8 +497,9 @@ MUNIT_TEST(test_brei_marshal)
}
_cleanup_free_ union brei_arg *args = NULL;
_cleanup_(strv_freep) char **strings = NULL;
size_t nargs = 0;
_unref_(brei_result) *result = brei_demarshal(brei, buf, "noiusf", &nargs, &args);
_unref_(brei_result) *result = brei_demarshal(brei, buf, "noiusf", &nargs, &args, &strings);
munit_assert_ptr_null(result);
munit_assert_int(nargs, ==, 6);
@ -503,6 +510,10 @@ MUNIT_TEST(test_brei_marshal)
munit_assert_string_equal(args[4].s, str);
munit_assert_double_equal(args[5].f, 1.45, 3 /* precision */);
/* make sure strings is filled in as expected and null-terminated */
munit_assert_ptr_equal(args[4].s, strings[0]);
munit_assert_ptr_null(strings[1]);
return MUNIT_OK;
}
@ -521,8 +532,9 @@ MUNIT_TEST(test_brei_marshal_bad_sig)
}
_cleanup_free_ union brei_arg *args = NULL;
_cleanup_(strv_freep) char **strings = NULL;
size_t nargs = 789;
_unref_(brei_result) *result = brei_demarshal(brei, buf, "nxoiusf", &nargs, &args);
_unref_(brei_result) *result = brei_demarshal(brei, buf, "nxoiusf", &nargs, &args, &strings);
munit_assert_ptr_not_null(result);
munit_assert_int(brei_result_get_reason(result), ==,
BREI_CONNECTION_DISCONNECT_REASON_PROTOCOL);
@ -571,14 +583,28 @@ bytes_to_int32(uint32_t count)
return (uint32_t)(((uint64_t)count + 3)/4);
}
static inline void
buffer_debug(const void *buffer, size_t sz)
{
const size_t stride = 8;
_cleanup_(strv_freep) char **strv = strv_from_mem(buffer, sz, stride);
munit_logf(MUNIT_LOG_DEBUG, "Logging buffer size %zu", sz);
for (size_t offset = 0; offset < sz; offset += stride) {
const char *s = strv[offset/stride];
munit_assert_ptr_not_null(s);
munit_logf(MUNIT_LOG_DEBUG, "%02zu: %s", offset, s);
}
}
MUNIT_TEST(test_brei_send_message)
{
int sv[2];
int rc = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, sv);
munit_assert_int(rc, ==, 0);
int sock_read = sv[0];
int sock_write = sv[1];
_cleanup_close_ int sock_read = sv[0];
_cleanup_close_ int sock_write = sv[1];
const int header_size = 16;
@ -594,6 +620,8 @@ MUNIT_TEST(test_brei_send_message)
int len = read(sock_read, buf, sizeof(buf));
munit_assert_int(len, ==, msglen);
buffer_debug(buf, len);
const struct brei_header *header = (const struct brei_header*)buf;
munit_assert_int(header->sender_id, ==, id);
munit_assert_int(header->msglen, ==, msglen);
@ -617,6 +645,8 @@ MUNIT_TEST(test_brei_send_message)
} ufloat;
munit_assert_int(len, ==, msglen);
buffer_debug(buf, len);
const struct brei_header *header = (const struct brei_header*)buf;
munit_assert_int(header->sender_id, ==, id);
munit_assert_int(header->msglen, ==, msglen);
@ -629,10 +659,10 @@ MUNIT_TEST(test_brei_send_message)
{
const char string[12] = "hello wor"; /* tests padding too */
int slen = bytes_to_int32(strlen0(string)) * 4;
munit_assert_int(slen, ==, sizeof(string));
uint32_t string_len = bytes_to_int32(strlen0(string)) * 4;
munit_assert_int(string_len, ==, sizeof(string));
const int msglen = header_size + 24 + slen; /* 4 bytes + 2 * 8 bytes + string length */
const int msglen = header_size + 24 + string_len; /* 4 bytes + 2 * 8 bytes + string length */
object_id_t id = 2;
uint32_t opcode = 3;
const char *signature = "ison";
@ -645,20 +675,28 @@ MUNIT_TEST(test_brei_send_message)
int len = read(sock_read, buf, sizeof(buf));
munit_assert_int(len, ==, msglen);
buffer_debug(buf, len);
const struct brei_header *header = (const struct brei_header*)buf;
munit_assert_int(header->sender_id, ==, id);
munit_assert_uint64(header->sender_id, ==, id);
munit_assert_int(header->msglen, ==, msglen);
munit_assert_int(header->opcode, ==, opcode);
munit_assert_int(buf[4], ==, -42);
const struct brei_string *s = (const struct brei_string *)&buf[5];
munit_assert_int(s->len, ==, strlen0(string));
munit_assert_string_equal(s->str, string);
munit_assert_int(memcmp(s->str, string, brei_string_proto_length(s->len) - 4), ==, 0);
uint32_t slen = buf[5];
munit_assert_int(slen, ==, strlen0(string));
char protostring[sizeof(string)] = {0};
assert(brei_string_proto_length(slen) - 4 == sizeof(protostring));
memcpy(protostring, &buf[6], brei_string_proto_length(slen) - 4);
munit_assert_string_equal(protostring, string);
munit_assert_int(memcmp(protostring, string, brei_string_proto_length(slen) - 4), ==, 0);
munit_assert_int(buf[6 + slen/4], ==, 0xab);
munit_assert_int(buf[8 + slen/4], ==, 0xcdef);
object_id_t a, b;
memcpy(&a, &buf[6 + string_len/4], sizeof(a));
memcpy(&b, &buf[8 + string_len/4], sizeof(b));
munit_assert_uint64(a, ==, 0xab);
munit_assert_uint64(b, ==, 0xcdef);
}
{
@ -676,20 +714,28 @@ MUNIT_TEST(test_brei_send_message)
int len = read(sock_read, buf, sizeof(buf));
munit_assert_int(len, ==, msglen);
buffer_debug(buf, len);
const struct brei_header *header = (const struct brei_header*)buf;
munit_assert_int(header->sender_id, ==, id);
munit_assert_uint64(header->sender_id, ==, id);
munit_assert_int(header->msglen, ==, msglen);
munit_assert_int(header->opcode, ==, opcode);
const struct brei_string *s1 = (const struct brei_string*)&buf[4];
munit_assert_int(s1->len, ==, strlen0(string1));
munit_assert_string_equal(s1->str, string1);
munit_assert_int(memcmp(s1->str, string1, brei_string_proto_length(s1->len) - 4), ==, 0);
uint32_t s1len = buf[4];
munit_assert_int(s1len, ==, strlen0(string1));
char protostring1[sizeof(string1)] = {0};
assert(brei_string_proto_length(s1len) - 4 == sizeof(protostring1));
memcpy(protostring1, &buf[5], brei_string_proto_length(s1len) - 4);
munit_assert_string_equal(protostring1, string1);
munit_assert_int(memcmp(protostring1, string1, brei_string_proto_length(s1len) - 4), ==, 0);
const struct brei_string *s2 = (const struct brei_string *)&buf[8];
munit_assert_int(s2->len, ==, strlen0(string2));
munit_assert_string_equal(s2->str, string2);
munit_assert_int(memcmp(s2->str, string2, brei_string_proto_length(s2->len) - 4), ==, 0);
uint32_t s2len = buf[8];
munit_assert_int(s2len, ==, strlen0(string2));
char protostring2[sizeof(string2)] = {0};
assert(brei_string_proto_length(s2len) - 4 == sizeof(protostring2));
memcpy(protostring2, &buf[9], brei_string_proto_length(s2len) - 4);
munit_assert_string_equal(protostring2, string2);
munit_assert_int(memcmp(protostring2, string2, brei_string_proto_length(s2len) - 4), ==, 0);
}
{
@ -720,7 +766,7 @@ MUNIT_TEST(test_brei_send_message)
uint32_t *buf = (uint32_t*)iobuf_data(recv);
munit_assert_int(len, ==, msglen);
const struct brei_header *header = (const struct brei_header*)buf;
munit_assert_int(header->sender_id, ==, id);
munit_assert_uint64(header->sender_id, ==, id);
munit_assert_int(header->msglen, ==, msglen);
munit_assert_int(header->opcode, ==, opcode);

View file

@ -127,7 +127,7 @@ struct brei_object {
/**
* Marshal the message for the given object id, opcode and signature into
* a struct iobuf. On succes, the returned result has that iobuf
* a struct iobuf. On success, the returned result has that iobuf
* as brei_result_get_data(), otherwise the result is the error.
*/
struct brei_result *

View file

@ -121,14 +121,14 @@ static struct brei_result *
case {{incoming.fqdn.upper()}}:
if (obj->version < {{incoming.fqdn.upper()}}_SINCE_VERSION)
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR,
"Opcode %u not supported in this interface version\n", opcode);
"Opcode %u not supported in this interface version", opcode);
assert(interface->{{incoming.name}} != NULL);
return interface->{{incoming.name}}({{interface.name}}{% for arg in incoming.arguments %}, (args + {{loop.index - 1}})->{{arg.signature}}{% endfor %});
{% endfor %}
}
{% endif %}
return brei_result_new(BREI_CONNECTION_DISCONNECT_REASON_ERROR,
"Opcode %u not supported in this interface version\n", opcode);
"Opcode %u not supported in this interface version", opcode);
}
static const struct brei_message {{interface.name}}_requests[] = {

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN button WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN button WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN callback WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
@ -68,6 +68,12 @@ ei_callback_get_id(struct ei_callback *callback)
return callback->proto_object.id;
}
uint32_t
ei_callback_get_version(struct ei_callback *callback)
{
return callback->proto_object.version;
}
static struct brei_result *
handle_msg_done(struct ei_callback *callback, uint64_t callback_data)
{
@ -101,18 +107,3 @@ ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data)
return callback; /* ref owned by caller */
}
struct ei_callback *
ei_callback_new_for_id(struct ei *ei, object_id_t id, uint32_t version)
{
struct ei_callback *callback = ei_callback_create(&ei->object);
callback->proto_object.id = id;
callback->proto_object.implementation = callback;
callback->proto_object.interface = &ei_callback_proto_interface;
callback->proto_object.version = version; /* FIXME */
ei_register_object(ei, &callback->proto_object);
list_init(&callback->link);
return callback; /* ref owned by caller */
}

View file

@ -19,7 +19,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN callback WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
@ -51,6 +51,7 @@ struct ei_callback {
OBJECT_DECLARE_GETTER(ei_callback, context, struct ei*);
OBJECT_DECLARE_GETTER(ei_callback, proto_object, const struct brei_object *);
OBJECT_DECLARE_GETTER(ei_callback, id, object_id_t);
OBJECT_DECLARE_GETTER(ei_callback, version, uint32_t);
OBJECT_DECLARE_GETTER(ei_callback, interface, const struct ei_callback_interface *);
OBJECT_DECLARE_GETTER(ei_callback, user_data, void *);
OBJECT_DECLARE_SETTER(ei_callback, user_data, void *);
@ -59,6 +60,3 @@ OBJECT_DECLARE_UNREF(ei_callback);
struct ei_callback *
ei_callback_new(struct ei *ei, ei_callback_func func, void *callback_data);
struct ei_callback *
ei_callback_new_for_id(struct ei *ei, object_id_t id, uint32_t version);

View file

@ -35,6 +35,7 @@
#include "util-version.h"
#include "libei-private.h"
#include "libei-connection.h"
#include "ei-proto.h"
static void
@ -43,12 +44,8 @@ ei_connection_destroy(struct ei_connection *connection)
struct ei *ei = ei_connection_get_context(connection);
ei_unregister_object(ei, &connection->proto_object);
struct ei_callback *cb;
list_for_each_safe(cb, &connection->pending_callbacks, link) {
list_remove(&cb->link);
free(ei_callback_get_user_data(cb));
ei_callback_unref(cb);
}
/* should be a noop */
ei_connection_remove_pending_callbacks(connection);
}
OBJECT_IMPLEMENT_REF(ei_connection);
@ -101,19 +98,57 @@ ei_connection_new(struct ei *ei, object_id_t id, uint32_t version)
return connection; /* ref owned by caller */
}
struct callback_user_data {
ei_connection_sync_callback_t cb;
void *user_data;
};
void
ei_connection_remove_pending_callbacks(struct ei_connection *connection)
{
struct ei_callback *cb;
list_for_each_safe(cb, &connection->pending_callbacks, link) {
list_remove(&cb->link);
ei_connection_sync_callback_unref(cb->user_data);
ei_callback_unref(cb);
}
}
static void
ei_connection_sync_callback_destroy(struct ei_connection_sync_callback *callback)
{
if (callback->destroy)
callback->destroy(callback);
}
static
OBJECT_IMPLEMENT_CREATE(ei_connection_sync_callback);
OBJECT_IMPLEMENT_REF(ei_connection_sync_callback);
OBJECT_IMPLEMENT_UNREF(ei_connection_sync_callback);
OBJECT_IMPLEMENT_GETTER(ei_connection_sync_callback, user_data, void *);
static
OBJECT_IMPLEMENT_PARENT(ei_connection_sync_callback, ei);
struct ei *
ei_connection_sync_callback_get_context(struct ei_connection_sync_callback *callback)
{
return ei_connection_sync_callback_parent(callback);
}
struct ei_connection_sync_callback *
ei_connection_sync_callback_new(struct ei *ei,
ei_connection_sync_callback_done_t done,
ei_connection_sync_callback_destroy_t destroy,
void *user_data)
{
struct ei_connection_sync_callback *callback = ei_connection_sync_callback_create(&ei->object);
callback->done = done;
callback->destroy = destroy;
callback->user_data = user_data;
return callback;
}
static void
sync_callback(struct ei_callback *callback, void *callback_data, uint64_t proto_data)
{
struct ei_connection *connection = callback_data;
_cleanup_free_ struct callback_user_data *data = ei_callback_get_user_data(callback);
if (data->cb)
data->cb(connection, data->user_data);
_unref_(ei_connection_sync_callback) *data = ei_callback_get_user_data(callback);
if (data->done)
data->done(data);
/* remove from pending callbacks */
list_remove(&callback->link);
@ -121,7 +156,8 @@ sync_callback(struct ei_callback *callback, void *callback_data, uint64_t proto_
}
void
ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback_t cb, void *user_data)
ei_connection_sync(struct ei_connection *connection,
struct ei_connection_sync_callback *callback)
{
struct ei *ei = ei_connection_get_context(connection);
@ -130,12 +166,9 @@ ei_connection_sync(struct ei_connection *connection, ei_connection_sync_callback
* then we extract the user_data on the object and call into the
* cb supplied to this function.
*/
struct ei_callback *callback = ei_callback_new(ei, sync_callback, connection);
struct callback_user_data *data = xalloc(sizeof *data);
data->cb = cb;
data->user_data = user_data;
ei_callback_set_user_data(callback, data);
list_append(&connection->pending_callbacks, &callback->link);
struct ei_callback *cb = ei_callback_new(ei, sync_callback, connection);
ei_callback_set_user_data(cb, ei_connection_sync_callback_ref(callback));
list_append(&connection->pending_callbacks, &cb->link);
ei_connection_request_sync(connection, ei_callback_get_id(callback));
ei_connection_request_sync(connection, ei_callback_get_id(cb), ei_callback_get_version(cb));
}

View file

@ -25,18 +25,21 @@
#pragma once
#include "util-mem.h"
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
struct ei;
struct ei_connection;
struct ei_connection_sync_callback;
/* This is a protocol-only object, not exposed in the API */
struct ei_connection {
struct object object;
struct brei_object proto_object;
/* struct ei_connection_sync_callback */
struct list pending_callbacks;
};
@ -51,14 +54,41 @@ OBJECT_DECLARE_UNREF(ei_connection);
struct ei_connection *
ei_connection_new(struct ei *ei, object_id_t id, uint32_t version);
void
ei_connection_remove_pending_callbacks(struct ei_connection *connection);
/**
* Called when the ei_callback.done event is received after
* an ei_connection_sync() request.
*/
typedef void (*ei_connection_sync_callback_t)(struct ei_connection *connection,
void *user_data);
typedef void (*ei_connection_sync_callback_done_t)(struct ei_connection_sync_callback *callback);
/**
* Called for each registered callback when the last reference to it is
* destroyed. This should be used to clean up user_data, if need be.
*
* This function is always called, even if disconnected and the done() function is never called.
*/
typedef void (*ei_connection_sync_callback_destroy_t)(struct ei_connection_sync_callback *callback);
struct ei_connection_sync_callback {
struct object object; /* parent is struct ei */
ei_connection_sync_callback_done_t done;
ei_connection_sync_callback_destroy_t destroy;
void *user_data;
};
struct ei_connection_sync_callback *
ei_connection_sync_callback_new(struct ei *ei,
ei_connection_sync_callback_done_t done,
ei_connection_sync_callback_destroy_t destroy,
void *user_data);
OBJECT_DECLARE_REF(ei_connection_sync_callback);
OBJECT_DECLARE_UNREF(ei_connection_sync_callback);
OBJECT_DECLARE_GETTER(ei_connection_sync_callback, context, struct ei*);
OBJECT_DECLARE_GETTER(ei_connection_sync_callback, user_data, void*);
DEFINE_UNREF_CLEANUP_FUNC(ei_connection_sync_callback);
void
ei_connection_sync(struct ei_connection *connection,
ei_connection_sync_callback_t callback,
void *user_data);
ei_connection_sync(struct ei_connection *connection, struct ei_connection_sync_callback *callback);

View file

@ -25,6 +25,7 @@
#include "config.h"
#include <errno.h>
#include <math.h>
#include "util-bits.h"
#include "util-macros.h"
@ -91,6 +92,7 @@ ei_device_destroy(struct ei_device *device)
ei_keyboard_unref(device->keyboard);
ei_seat_unref(seat);
free(device->name);
free(device->pending_region_mapping_id);
}
_public_
@ -110,7 +112,9 @@ _public_
OBJECT_IMPLEMENT_GETTER(ei_device, user_data, void *);
_public_
OBJECT_IMPLEMENT_SETTER(ei_device, user_data, void *);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, width, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_device, height, uint32_t);
OBJECT_IMPLEMENT_GETTER_AS_REF(ei_device, proto_object, const struct brei_object *);
@ -138,6 +142,9 @@ ei_device_in_region(struct ei_device *device, double x, double y)
{
struct ei_region *r;
if (list_empty(&device->regions))
return true;
list_for_each(r, &device->regions, link) {
if (ei_region_contains(r, x, y))
return true;
@ -192,11 +199,26 @@ handle_msg_region(struct ei_device *device, uint32_t x, uint32_t y,
ei_region_set_offset(r, x, y);
ei_region_set_size(r, w, h);
ei_region_set_physical_scale(r, scale);
_cleanup_free_ char *mapping_id = steal(&device->pending_region_mapping_id);
if (mapping_id)
ei_region_set_mapping_id(r, mapping_id);
ei_device_add_region(device, r);
return NULL;
}
static struct brei_result *
handle_msg_region_mapping_id(struct ei_device *device, const char *mapping_id)
{
if (device->pending_region_mapping_id)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"EIS sent the region mapping_id twice");
device->pending_region_mapping_id = xstrdup(mapping_id);
return NULL;
}
static struct brei_result *
handle_msg_done(struct ei_device *device)
{
@ -315,7 +337,9 @@ handle_msg_start_emulating(struct ei_device *device, uint32_t serial, uint32_t s
case EI_DEVICE_STATE_EMULATING:
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
result = brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a start_emulating event", device->state);
"Invalid device state %u for a start_emulating event (sequence %u)",
device->state,
sequence);
break;
case EI_DEVICE_STATE_RESUMED:
ei_queue_device_start_emulating_event(device, sequence);
@ -345,7 +369,7 @@ handle_msg_stop_emulating(struct ei_device *device, uint32_t serial)
case EI_DEVICE_STATE_RESUMED:
case EI_DEVICE_STATE_REMOVED_FROM_SERVER:
result = brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a stop_emulating event", device->state);
"Invalid device state %u for a stop_emulating event", device->state);
break;
case EI_DEVICE_STATE_EMULATING:
ei_queue_device_stop_emulating_event(device);
@ -378,7 +402,7 @@ maybe_error_on_device_state(struct ei_device *device, const char *event_type)
}
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a %s event", device->state, event_type);
"Invalid device state %u for a %s event", device->state, event_type);
}
static struct brei_result *
@ -402,33 +426,41 @@ handle_msg_interface(struct ei_device *device, object_id_t id, const char *name,
{
DISCONNECT_IF_INVALID_ID(device, id);
struct ei *ei = ei_device_get_context(device);
if (streq(name, EI_POINTER_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_pointer, id, version);
if (device->pointer)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_pointer interface object on device");
device->pointer = ei_pointer_new(device, id, version);
} else if (streq(name, EI_POINTER_ABSOLUTE_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_pointer_absolute, id, version);
if (device->pointer_absolute)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_pointer_absolute interface object on device");
device->pointer_absolute = ei_pointer_absolute_new(device, id, version);
} else if (streq(name, EI_SCROLL_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_scroll, id, version);
if (device->scroll)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_scroll interface object on device");
device->scroll = ei_scroll_new(device, id, version);
} else if (streq(name, EI_BUTTON_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_button, id, version);
if (device->button)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_button interface object on device");
device->button = ei_button_new(device, id, version);
} else if (streq(name, EI_KEYBOARD_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_keyboard, id, version);
if (device->keyboard)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_keyboard interface object on device");
device->keyboard = ei_keyboard_new(device, id, version);
} else if (streq(name, EI_TOUCHSCREEN_INTERFACE_NAME)) {
DISCONNECT_IF_INVALID_VERSION(ei, ei_touchscreen, id, version);
if (device->touchscreen)
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Duplicate ei_touchscreen interface object on device");
@ -454,6 +486,8 @@ static const struct ei_device_interface interface = {
.stop_emulating = handle_msg_stop_emulating,
.frame = handle_msg_frame,
.interface = handle_msg_interface,
/* v2 */
.region_mapping_id = handle_msg_region_mapping_id,
};
const struct ei_device_interface *
@ -692,6 +726,7 @@ handle_msg_keymap(struct ei_keyboard *keyboard, uint32_t keymap_type, uint32_t k
struct ei_device *device = ei_keyboard_get_device(keyboard);
ei_device_set_keymap(device, keymap_type, keymap_fd, keymap_sz);
xclose (keymap_fd);
return NULL;
}
@ -827,6 +862,31 @@ handle_msg_touch_up(struct ei_touchscreen *touchscreen, uint32_t touchid)
return maybe_error_on_device_state(device, "touch up");
}
static struct brei_result *
handle_msg_touch_cancel(struct ei_touchscreen *touchscreen, uint32_t touchid)
{
struct ei_device *device = ei_touchscreen_get_device(touchscreen);
DISCONNECT_IF_SENDER_CONTEXT(device);
if (!ei_device_has_capability(device, EI_DEVICE_CAP_TOUCH)) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch cancel event for non-touch device");
}
struct ei *ei = ei_device_get_context(device);
if (ei->interface_versions.ei_touchscreen < EI_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch cancel event for touchscreen version v1");
}
if (device->state == EI_DEVICE_STATE_EMULATING) {
ei_queue_touch_cancel_event(device, touchid);
return NULL;
}
return maybe_error_on_device_state(device, "touch cancel");
}
static struct brei_result *
handle_msg_touchscreen_destroy(struct ei_touchscreen *touchscreen, uint32_t serial)
{
@ -844,6 +904,7 @@ static const struct ei_touchscreen_interface touchscreen_interface = {
.down = handle_msg_touch_down,
.motion = handle_msg_touch_motion,
.up = handle_msg_touch_up,
.cancel = handle_msg_touch_cancel,
};
const struct ei_touchscreen_interface *
@ -930,7 +991,7 @@ ei_device_keyboard_get_keymap(struct ei_device *device)
}
static struct ei_keymap *
ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
ei_keymap_new(struct ei *ei, enum ei_keymap_type type, int fd, size_t size)
{
_unref_(ei_keymap) *keymap = ei_keymap_create(NULL);
@ -938,15 +999,20 @@ ei_keymap_new(enum ei_keymap_type type, int fd, size_t size)
case EI_KEYMAP_TYPE_XKB:
break;
default:
log_bug(ei, "Unsupported keymap type: %u", type);
return NULL;
}
if (fd < 0 || size == 0)
if (fd < 0 || size == 0) {
log_bug(ei, "Invalid keymap fd %d with size %zu", fd, size);
return NULL;
}
int newfd = xdup(fd);
if (newfd < 0)
if (newfd < 0) {
log_bug(ei, "Failed to dup keymap fd: %s", strerror(errno));
return NULL;
}
keymap->fd = newfd;
keymap->type = type;
@ -965,12 +1031,10 @@ ei_device_set_keymap(struct ei_device *device,
if (!type)
return;
_unref_(ei_keymap) *keymap = ei_keymap_new(type, keymap_fd, size);
if (!keymap) {
log_bug(ei_device_get_context(device),
"Failed to apply server-requested keymap");
struct ei *ei = ei_device_get_context(device);
_unref_(ei_keymap) *keymap = ei_keymap_new(ei, type, keymap_fd, size);
if (!keymap)
return; /* FIXME: ei_device_remove() here */
}
keymap->device = device;
device->keymap = ei_keymap_ref(keymap);
@ -1029,6 +1093,7 @@ ei_device_removed_by_server(struct ei_device *device)
{
struct ei_seat *seat = ei_device_get_seat(device);
struct ei *ei = ei_device_get_context(device);
struct ei_event *event;
switch (device->state) {
case EI_DEVICE_STATE_NEW:
@ -1039,6 +1104,11 @@ ei_device_removed_by_server(struct ei_device *device)
case EI_DEVICE_STATE_PAUSED:
case EI_DEVICE_STATE_RESUMED:
case EI_DEVICE_STATE_EMULATING:
list_for_each_safe(event, &device->pending_event_queue, link) {
list_remove(&event->link);
ei_event_unref(event);
}
/* in the case of ei_disconnect() we may fake the
* removal by the server, so we need to also remove the
* pointer/keyboard/touch interfaces
@ -1185,6 +1255,18 @@ ei_device_get_region(struct ei_device *device, size_t index)
return list_nth_entry(struct ei_region, &device->regions, link, index);
}
_public_ struct ei_region *
ei_device_get_region_at(struct ei_device *device, double x, double y)
{
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (ei_region_contains(r, x, y))
return r;
}
return NULL;
}
static inline void
ei_device_resume_scrolling(struct ei_device *device, double x, double y)
{
@ -1322,7 +1404,7 @@ ei_device_pointer_motion(struct ei_device *device,
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1341,7 +1423,7 @@ ei_device_pointer_motion_absolute(struct ei_device *device,
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1363,7 +1445,7 @@ ei_device_button_button(struct ei_device *device,
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1388,7 +1470,7 @@ ei_device_scroll_delta(struct ei_device *device, double x, double y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1408,7 +1490,7 @@ ei_device_scroll_stop(struct ei_device *device, bool x, bool y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1437,7 +1519,7 @@ ei_device_scroll_cancel(struct ei_device *device, bool x, bool y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1470,10 +1552,15 @@ ei_device_scroll_discrete(struct ei_device *device, int32_t x, int32_t y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
if (abs(x) == 1 || abs(y) == 1) {
log_bug_client(ei_device_get_context(device),
"%s: suspicious discrete event value 1, did you mean 120?", __func__);
}
ei_device_resume_scrolling(device, x, y);
ei_send_pointer_scroll_discrete(device, x, y);
@ -1508,7 +1595,7 @@ ei_device_keyboard_key(struct ei_device *device,
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1591,6 +1678,21 @@ ei_send_touch_up(struct ei_device *device, uint32_t tid)
return rc;
}
static int
ei_send_touch_cancel(struct ei_device *device, uint32_t tid)
{
struct ei *ei = ei_device_get_context(device);
if (ei->state == EI_STATE_NEW || ei->state == EI_STATE_DISCONNECTED)
return 0;
device->send_frame_event = true;
int rc = ei_touchscreen_request_cancel(device->touchscreen, tid);
if (rc)
ei_disconnect(ei);
return rc;
}
_public_ struct ei_touch *
ei_device_touch_new(struct ei_device *device)
@ -1615,7 +1717,7 @@ ei_touch_down(struct ei_touch *touch, double x, double y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1625,14 +1727,11 @@ ei_touch_down(struct ei_touch *touch, double x, double y)
return;
}
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (!ei_region_contains(r, x, y)) {
log_bug_client(ei_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
touch->state = TOUCH_IS_UP;
return;
}
if (!ei_device_in_region(device, x, y)) {
log_bug_client(ei_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
touch->state = TOUCH_IS_UP;
return;
}
touch->state = TOUCH_IS_DOWN;
@ -1647,7 +1746,7 @@ ei_touch_motion(struct ei_touch *touch, double x, double y)
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1657,14 +1756,11 @@ ei_touch_motion(struct ei_touch *touch, double x, double y)
return;
}
struct ei_region *r;
list_for_each(r, &device->regions, link) {
if (!ei_region_contains(r, x, y)) {
log_bug_client(ei_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
ei_touch_up(touch);
return;
}
if (!ei_device_in_region(device, x, y)) {
log_bug_client(ei_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
ei_touch_up(touch);
return;
}
ei_send_touch_motion(touch->device, touch->tracking_id, x, y);
@ -1676,7 +1772,7 @@ ei_touch_up(struct ei_touch *touch)
struct ei_device *device = ei_touch_get_device(touch);
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not not emulating", __func__);
"%s: device is not emulating", __func__);
return;
}
@ -1690,6 +1786,32 @@ ei_touch_up(struct ei_touch *touch)
ei_send_touch_up(touch->device, touch->tracking_id);
}
_public_ void
ei_touch_cancel(struct ei_touch *touch)
{
struct ei_device *device = ei_touch_get_device(touch);
if (device->state != EI_DEVICE_STATE_EMULATING) {
log_bug_client(ei_device_get_context(device),
"%s: device is not emulating", __func__);
return;
}
if (touch->state != TOUCH_IS_DOWN) {
log_bug_client(ei_device_get_context(device),
"%s: touch %u is not currently down", __func__, touch->tracking_id);
return;
}
touch->state = TOUCH_IS_UP;
struct ei *ei = ei_device_get_context(device);
if (ei->interface_versions.ei_touchscreen >= EI_TOUCHSCREEN_REQUEST_CANCEL_SINCE_VERSION)
ei_send_touch_cancel(touch->device, touch->tracking_id);
else
ei_send_touch_up(touch->device, touch->tracking_id);
}
_public_ void
ei_device_frame(struct ei_device *device, uint64_t time)
{

View file

@ -86,6 +86,8 @@ struct ei_device {
} scroll_state;
struct ei_keymap *keymap;
char *pending_region_mapping_id;
};
struct ei_keymap {

View file

@ -49,6 +49,8 @@ ei_event_type_to_string(enum ei_event_type type)
CASE_RETURN_STRING(EI_EVENT_DEVICE_PAUSED);
CASE_RETURN_STRING(EI_EVENT_DEVICE_RESUMED);
CASE_RETURN_STRING(EI_EVENT_KEYBOARD_MODIFIERS);
CASE_RETURN_STRING(EI_EVENT_PONG);
CASE_RETURN_STRING(EI_EVENT_SYNC);
CASE_RETURN_STRING(EI_EVENT_FRAME);
CASE_RETURN_STRING(EI_EVENT_DEVICE_START_EMULATING);
CASE_RETURN_STRING(EI_EVENT_DEVICE_STOP_EMULATING);
@ -71,12 +73,13 @@ ei_event_type_to_string(enum ei_event_type type)
static void
ei_event_destroy(struct ei_event *event)
{
struct ei *ei = ei_event_get_context(event);
switch (event->type) {
case EI_EVENT_CONNECT:
case EI_EVENT_DISCONNECT:
case EI_EVENT_SEAT_ADDED:
case EI_EVENT_SEAT_REMOVED:
case EI_EVENT_DEVICE_ADDED:
case EI_EVENT_DEVICE_REMOVED:
case EI_EVENT_DEVICE_PAUSED:
case EI_EVENT_DEVICE_RESUMED:
@ -96,6 +99,17 @@ ei_event_destroy(struct ei_event *event)
case EI_EVENT_TOUCH_UP:
case EI_EVENT_TOUCH_MOTION:
break;
case EI_EVENT_DEVICE_ADDED:
if (ei->interface_versions.ei_device >= EI_DEVICE_REQUEST_READY_SINCE_VERSION)
ei_device_request_ready(event->device);
break;
case EI_EVENT_PONG:
ei_ping_unref(event->pong.ping);
break;
case EI_EVENT_SYNC:
ei_sync_event_send_done(event);
ei_pingpong_unref(event->sync.pingpong);
break;
}
ei_device_unref(event->device);
ei_seat_unref(event->seat);
@ -154,7 +168,8 @@ check_event_type(struct ei_event *event,
if (!rc)
log_bug_client(ei_event_get_context(event),
"Invalid event type %u passed to %s()",
"Invalid event type (%s) %u passed to %s()",
ei_event_type_to_string(type),
type, function_name);
return rc;
@ -203,6 +218,14 @@ ei_event_keyboard_get_xkb_group(struct ei_event *event)
return event->modifiers.group;
}
_public_ struct ei_ping *
ei_event_pong_get_ping(struct ei_event *event)
{
require_event_type(event, NULL, EI_EVENT_PONG);
return event->pong.ping;
}
_public_ double
ei_event_pointer_get_dx(struct ei_event *event)
{
@ -346,6 +369,14 @@ ei_event_touch_get_y(struct ei_event *event)
return event->touch.y;
}
_public_ bool
ei_event_touch_get_is_cancel(struct ei_event *event)
{
require_event_type(event,false, EI_EVENT_TOUCH_UP);
return event->touch.is_cancel;
}
_public_ uint64_t
ei_event_get_time(struct ei_event *event)
{

View file

@ -64,10 +64,17 @@ struct ei_event {
struct {
uint32_t touchid;
double x, y;
bool is_cancel;
} touch;
struct {
uint32_t sequence;
} start_emulating;
struct {
struct ei_ping *ping;
} pong;
struct {
struct ei_pingpong *pingpong;
} sync;
};
};
@ -79,6 +86,3 @@ ei_event_new_for_device(struct ei_device *device);
struct ei *
ei_event_get_context(struct ei_event *event);
struct ei_event *
ei_event_ref(struct ei_event *event);

View file

@ -35,6 +35,7 @@
#include "util-version.h"
#include "libei-private.h"
#include "libei-connection.h"
#include "ei-proto.h"
static void
@ -150,9 +151,9 @@ handle_msg_interface_version(struct ei_handshake *setup, const char *name, uint3
}
static void
connected(struct ei_connection *connection, void *user_data)
on_connected(struct ei_connection_sync_callback *callback)
{
struct ei *ei = ei_connection_get_context(connection);
struct ei *ei = ei_connection_sync_callback_get_context(callback);
/* If we get here, the server didn't immediately disconnect us */
if (ei->state == EI_STATE_DISCONNECTED)
@ -169,14 +170,20 @@ handle_msg_connection(struct ei_handshake *setup, uint32_t serial, object_id_t i
/* we're done with our handshake, drop it */
ei_handshake_unref(steal(&ei->handshake));
DISCONNECT_IF_INVALID_VERSION(ei, ei_handshake, id, version);
ei->connection = ei_connection_new(ei, id, version);
ei->state = EI_STATE_CONNECTING;
ei_update_serial(ei, serial);
_unref_(ei_connection_sync_callback) *cb = ei_connection_sync_callback_new(ei,
on_connected,
NULL,
NULL);
/* Send a sync on the connection - EIS should immediately send a
* disconnect event where applicable, so if we get through to our
* sync callback, we didn't immediately get disconnected */
ei_connection_sync(ei->connection, connected, NULL);
ei_connection_sync(ei->connection, cb);
return NULL;
}

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN keyboard WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN keyboard WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

124
src/libei-ping.c Normal file
View file

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2024 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include "util-mem.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
#include "libei-private.h"
#include "libei-connection.h"
struct ei_ping {
struct object object;
uint64_t id;
void *user_data;
struct ei *context;
bool is_pending;
bool is_done;
};
static void
ei_ping_destroy(struct ei_ping *ping)
{
if (!ping->is_pending)
ei_unref(ping->context);
}
static
OBJECT_IMPLEMENT_CREATE(ei_ping);
_public_
OBJECT_IMPLEMENT_REF(ei_ping);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(ei_ping);
_public_
OBJECT_IMPLEMENT_GETTER(ei_ping, id, uint64_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_ping, user_data, void*);
_public_
OBJECT_IMPLEMENT_SETTER(ei_ping, user_data, void*);
static
OBJECT_IMPLEMENT_GETTER(ei_ping, context, struct ei*);
_public_ struct ei_ping *
ei_new_ping(struct ei *ei)
{
static uint64_t id = 0;
struct ei_ping *ping = ei_ping_create(NULL);
ping->id = ++id;
/* Ref our context while it's pending (i.e. only the caller has the ref).
* Once it's pending we no longer need the ref.
*/
ping->context = ei_ref(ei);
ping->is_pending = false;
ping->is_done = false;
return ping;
}
static void
on_pong(struct ei_connection_sync_callback *callback)
{
struct ei_ping *ping = ei_connection_sync_callback_get_user_data(callback);
ping->is_done = true;
struct ei *ei = ei_connection_sync_callback_get_context(callback);
ei_queue_pong_event(ei, ping);
/* ei_ping ref is removed in on_destroy */
}
static void
on_destroy(struct ei_connection_sync_callback *callback)
{
/* This is only called if we never received a pong */
_unref_(ei_ping) *ping = ei_connection_sync_callback_get_user_data(callback);
/* We never got a pong because we got disconnected. Queue a fake pong event */
if (!ping->is_done) {
struct ei *ei = ei_connection_sync_callback_get_context(callback);
ei_queue_pong_event(ei, ping);
}
}
_public_ void
ei_ping(struct ei_ping *ping)
{
struct ei *ei = ei_ping_get_context(ping);
ei_unref(ping->context);
ping->context = ei;
ping->is_pending = true;
_unref_(ei_connection_sync_callback) *cb = ei_connection_sync_callback_new(ei,
on_pong,
on_destroy,
ei_ping_ref(ping));
ei_connection_sync(ei->connection, cb);
}

View file

@ -76,24 +76,6 @@ ei_pingpong_get_interface(struct ei_pingpong *pingpong) {
return &interface;
}
struct ei_pingpong *
ei_pingpong_new(struct ei *ei, ei_pingpong_func func, void *pingpong_data)
{
struct ei_pingpong *pingpong = ei_pingpong_create(&ei->object);
pingpong->proto_object.id = ei_get_new_id(ei);
pingpong->proto_object.implementation = pingpong;
pingpong->proto_object.interface = &ei_pingpong_proto_interface;
pingpong->proto_object.version = VERSION_V(1);
pingpong->pingpong_data = pingpong_data;
ei_register_object(ei, &pingpong->proto_object);
pingpong->func = func;
list_init(&pingpong->link);
return pingpong; /* ref owned by caller */
}
struct ei_pingpong *
ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version)
{

View file

@ -32,8 +32,6 @@
struct ei;
struct ei_pingpong;
typedef void (*ei_pingpong_func)(struct ei_pingpong *pingpong, void *pingpong_data, uint32_t proto_data);
/* This is a protocol-only object, not exposed in the API */
struct ei_pingpong {
struct object object;
@ -42,7 +40,6 @@ struct ei_pingpong {
struct list link; /* for use by the callers, if needed */
ei_pingpong_func func;
void *pingpong_data; /* Note: pingpong-data is attached to the pingpong */
};
@ -55,8 +52,5 @@ OBJECT_DECLARE_SETTER(ei_pingpong, user_data, void *);
OBJECT_DECLARE_REF(ei_pingpong);
OBJECT_DECLARE_UNREF(ei_pingpong);
struct ei_pingpong *
ei_pingpong_new(struct ei *ei, ei_pingpong_func func, void *pingpong_data);
struct ei_pingpong *
ei_pingpong_new_for_id(struct ei *ei, object_id_t id, uint32_t version);

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -79,6 +79,17 @@ struct ei_interface_versions {
uint32_t ei_touchscreen;
};
struct ei_unsent {
struct list node;
struct iobuf *buf;
};
struct ei_defunct_object {
struct list node;
object_id_t object_id;
uint64_t time;
};
struct ei {
struct object object;
@ -86,6 +97,7 @@ struct ei {
struct ei_handshake *handshake;
struct ei_interface_versions interface_versions;
struct list proto_objects; /* brei_object list */
struct list defunct_objects;
object_id_t next_object_id;
uint32_t serial;
@ -94,6 +106,8 @@ struct ei {
struct brei_context *brei;
struct sink *sink;
struct source *source;
struct list unsent_queue;
struct ei_backend_interface backend_interface;
void *backend;
enum ei_state state;
@ -106,6 +120,8 @@ struct ei {
enum ei_log_priority priority;
} log;
ei_clock_now_func clock_now;
bool is_sender;
};
@ -115,9 +131,6 @@ ei_get_interface(struct ei *ei);
int
ei_set_socket(struct ei *ei, int fd);
void
ei_disconnect(struct ei *ei);
struct ei *
ei_get_context(struct ei *ei);
@ -143,6 +156,9 @@ int
ei_send_message(struct ei *ei, const struct brei_object *object,
uint32_t opcode, const char *signature, size_t nargs, ...);
int
ei_unsent_flush(struct ei* ei);
void
ei_connected(struct ei *ei);
@ -152,6 +168,12 @@ ei_queue_connect_event(struct ei *ei);
void
ei_queue_disconnect_event(struct ei *ei);
void
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping);
void
ei_queue_sync_event(struct ei *ei, struct ei_pingpong *ping);
void
ei_queue_device_removed_event(struct ei_device *device);
@ -218,6 +240,11 @@ ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
void
ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid);
void
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid);
void
ei_sync_event_send_done(struct ei_event *e);
_printf_(6, 7) void
ei_log_msg(struct ei *ei,
@ -244,3 +271,13 @@ ei_log_msg_va(struct ei *ei,
ei_log_msg((T_), EI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪳 libei bug: " __VA_ARGS__)
#define log_bug_client(T_, ...) \
ei_log_msg((T_), EI_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪲 Bug: " __VA_ARGS__)
#define DISCONNECT_IF_INVALID_VERSION(ei_, intf_, id_, version_) do { \
struct ei *_ei = (ei_); \
uint32_t _version = (version_); \
uint64_t _id = (id_); \
if (_ei->interface_versions.intf_ < _version) { \
log_bug(ei_, "Received invalid version %u for object id %#" PRIx64 ". Disconnecting", _version, _id); \
return brei_result_new(EI_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid version %u for object id %#" PRIx64 ".", _version, _id); \
} \
} while(0)

View file

@ -24,12 +24,14 @@
#include "config.h"
#include "util-strings.h"
#include "libei-private.h"
static void
ei_region_destroy(struct ei_region *region)
{
free(region->mapping_id);
list_remove(&region->link);
}
@ -54,6 +56,8 @@ OBJECT_IMPLEMENT_GETTER(ei_region, height, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, physical_scale, double);
OBJECT_IMPLEMENT_SETTER(ei_region, physical_scale, double);
_public_
OBJECT_IMPLEMENT_GETTER(ei_region, mapping_id, const char *);
struct ei_region *
ei_region_new(void)
@ -80,6 +84,12 @@ ei_region_set_size(struct ei_region *region, uint32_t w, uint32_t h)
region->height = h;
}
void
ei_region_set_mapping_id(struct ei_region *region, const char *mapping_id)
{
region->mapping_id = xstrdup(mapping_id);
}
_public_ bool
ei_region_contains(struct ei_region *r, double x, double y)
{
@ -98,3 +108,78 @@ ei_region_convert_point(struct ei_region *r, double *x, double *y)
return false;
}
#ifdef _enable_tests_
#include "util-munit.h"
MUNIT_TEST(test_region_setters)
{
_unref_(ei_region) *r = ei_region_new();
ei_region_set_size(r, 1, 2);
ei_region_set_offset(r, 3, 4);
ei_region_set_physical_scale(r, 5.6);
ei_region_set_mapping_id(r, "foo");
munit_assert_int(ei_region_get_width(r), ==, 1);
munit_assert_int(ei_region_get_height(r), ==, 2);
munit_assert_int(ei_region_get_x(r), ==, 3);
munit_assert_int(ei_region_get_y(r), ==, 4);
munit_assert_double(ei_region_get_physical_scale(r), ==, 5.6);
munit_assert_string_equal(ei_region_get_mapping_id(r), "foo");
return MUNIT_OK;
}
MUNIT_TEST(test_region_contains)
{
struct ei_region r = {0};
ei_region_set_size(&r, 100, 200);
ei_region_set_offset(&r, 300, 400);
munit_assert_true(ei_region_contains(&r, 300, 400));
munit_assert_true(ei_region_contains(&r, 399.9, 599.9));
munit_assert_false(ei_region_contains(&r, 299.9, 400));
munit_assert_false(ei_region_contains(&r, 300, 399.9));
munit_assert_false(ei_region_contains(&r, 400.1, 400));
munit_assert_false(ei_region_contains(&r, 400, 399.9));
munit_assert_false(ei_region_contains(&r, 299.9, 599.9));
munit_assert_false(ei_region_contains(&r, 300, 600.1));
munit_assert_false(ei_region_contains(&r, 400, 599.9));
munit_assert_false(ei_region_contains(&r, 399, 600));
return MUNIT_OK;
}
MUNIT_TEST(test_region_convert)
{
struct ei_region r = {0};
ei_region_set_size(&r, 640, 480);
ei_region_set_offset(&r, 100, 200);
double x = 100;
double y = 200;
munit_assert_true(ei_region_convert_point(&r, &x, &y));
munit_assert_double_equal(x, 0, 4 /* precision */);
munit_assert_double_equal(y, 0, 4 /* precision */);
x = 101.2;
y = 202.3;
munit_assert_true(ei_region_convert_point(&r, &x, &y));
munit_assert_double_equal(x, 1.2, 4 /* precision */);
munit_assert_double_equal(y, 2.3, 4 /* precision */);
x = 99.9;
y = 199.9;
munit_assert_false(ei_region_convert_point(&r, &x, &y));
munit_assert_double_equal(x, 99.9, 4 /* precision */);
munit_assert_double_equal(y, 199.9, 4 /* precision */);
return MUNIT_OK;
}
#endif

View file

@ -34,6 +34,7 @@ struct ei_region {
uint32_t x, y;
uint32_t width, height;
double physical_scale;
char *mapping_id;
};
struct ei_region *
@ -47,3 +48,6 @@ ei_region_set_offset(struct ei_region *region, uint32_t x, uint32_t y);
void
ei_region_set_physical_scale(struct ei_region *region, double scale);
void
ei_region_set_mapping_id(struct ei_region *region, const char *mapping_id);

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN scroll WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN scroll WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -145,6 +145,8 @@ handle_msg_device(struct ei_seat *seat, object_id_t id, uint32_t version)
struct ei *ei = ei_seat_get_context(seat);
DISCONNECT_IF_INVALID_VERSION(ei, ei_device, id, version);
log_debug(ei, "Added device %#" PRIx64 "@v%u", id, version);
/* device is in the seat's device list */
struct ei_device *device = ei_device_new(seat, id, version);
@ -231,10 +233,10 @@ ei_seat_has_capability(struct ei_seat *seat,
{
switch (cap) {
case EI_DEVICE_CAP_POINTER:
/* FIXME: a seat without pointer or pointer_absolute but button
/* FIXME: a seat without pointer or pointer_absolute but button
* and/or scroll should count as pointer here but that's niche
* enough that we can figure that out when needed */
return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] != 0;
return seat->capabilities.map[EI_POINTER_INTERFACE_INDEX] != 0;
case EI_DEVICE_CAP_POINTER_ABSOLUTE:
return seat->capabilities.map[EI_POINTER_ABSOLUTE_INTERFACE_INDEX] != 0;
case EI_DEVICE_CAP_KEYBOARD:
@ -303,6 +305,7 @@ ei_seat_bind_capabilities(struct ei_seat *seat, ...)
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
mask_add(mask,ei_seat_cap_mask(seat, cap));
}
va_end(args);
if (seat->capabilities.bound == mask)
return;
@ -330,6 +333,7 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
mask_remove(mask, ei_seat_cap_mask(seat, cap));
}
va_end(args);
if (seat->capabilities.bound == mask)
return;
@ -345,3 +349,37 @@ ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
ei_seat_send_bind(seat, seat->capabilities.bound);
}
_public_ void
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
{
switch (seat->state) {
case EI_SEAT_STATE_DONE:
break;
case EI_SEAT_STATE_NEW:
case EI_SEAT_STATE_REMOVED:
return;
}
uint64_t mask = 0;
enum ei_device_capability cap;
va_list args;
va_start(args, seat);
while ((cap = va_arg(args, enum ei_device_capability)) > 0) {
mask_add(mask, ei_seat_cap_mask(seat, cap));
}
va_end(args);
if (mask == 0)
return;
/* Check if requested capabilities are a subset of bound capabilities */
if (!mask_all(seat->capabilities.bound, mask)) {
struct ei *ei = ei_seat_get_context(seat);
log_bug_client(ei, "Requested capabilities are not a subset of the bound capabilities");
return;
}
ei_seat_request_request_device(seat, mask);
}

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN touch WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -50,6 +50,12 @@ _Static_assert(sizeof(enum ei_keymap_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_event_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum ei_log_priority) == sizeof(int), "Invalid enum size");
static void
ei_unsent_free(struct ei_unsent *unsent);
static void
ei_defunct_object_free(struct ei_defunct_object *obj);
static void
ei_destroy(struct ei *ei)
{
@ -59,14 +65,26 @@ ei_destroy(struct ei *ei)
while ((e = ei_get_event(ei)) != NULL)
ei_event_unref(e);
struct ei_unsent *unsent;
list_for_each_safe(unsent, &ei->unsent_queue, node) {
ei_unsent_free(unsent);
}
if (ei->backend_interface.destroy)
ei->backend_interface.destroy(ei, ei->backend);
ei->backend = NULL;
ei_handshake_unref(ei->handshake);
ei_connection_unref(ei->connection);
brei_context_unref(ei->brei);
sink_unref(ei->sink);
free(ei->name);
struct ei_defunct_object *obj;
list_for_each_safe(obj, &ei->defunct_objects, node) {
ei_defunct_object_free(obj);
}
}
static
@ -100,20 +118,22 @@ ei_create_context(bool is_sender, void *user_data)
list_init(&ei->event_queue);
list_init(&ei->seats);
list_init(&ei->proto_objects);
list_init(&ei->unsent_queue);
list_init(&ei->defunct_objects);
ei->interface_versions = (struct ei_interface_versions){
.ei_connection = VERSION_V(1),
.ei_handshake = VERSION_V(1),
.ei_callback = VERSION_V(1),
.ei_pingpong = VERSION_V(1),
.ei_seat = VERSION_V(1),
.ei_device = VERSION_V(1),
.ei_seat = VERSION_V(2),
.ei_device = VERSION_V(3),
.ei_pointer = VERSION_V(1),
.ei_pointer_absolute = VERSION_V(1),
.ei_scroll = VERSION_V(1),
.ei_button = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_touchscreen = VERSION_V(1),
.ei_touchscreen = VERSION_V(2),
};
/* This must be v1 until the server tells us otherwise */
ei->handshake = ei_handshake_new(ei, VERSION_V(1));
@ -151,15 +171,27 @@ ei_update_serial(struct ei *ei, uint32_t serial)
void
ei_register_object(struct ei *ei, struct brei_object *object)
{
log_debug(ei, "registering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
log_debug(ei, "object %#" PRIx64 " registering %s v%u", object->id, object->interface->name, object->version);
list_append(&ei->proto_objects, &object->link);
}
static void
ei_defunct_object_free(struct ei_defunct_object *obj)
{
list_remove(&obj->node);
free(obj);
}
void
ei_unregister_object(struct ei *ei, struct brei_object *object)
{
log_debug(ei, "deregistering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
log_debug(ei, "object %#" PRIx64 " deregistering %s v%u", object->id, object->interface->name, object->version);
list_remove(&object->link);
struct ei_defunct_object *obj = xalloc(sizeof *obj);
obj->object_id = object->id;
obj->time = ei_now(ei);
list_append(&ei->defunct_objects, &obj->node);
}
_public_ bool
@ -253,8 +285,10 @@ queue_event(struct ei *ei, struct ei_event *event)
break;
case EI_EVENT_FRAME:
/* silently discard empty frames */
if (list_empty(&device->pending_event_queue))
if (list_empty(&device->pending_event_queue)) {
ei_event_unref(event);
return;
}
struct ei_event *pending;
list_for_each_safe(pending, &device->pending_event_queue, link) {
@ -265,7 +299,7 @@ queue_event(struct ei *ei, struct ei_event *event)
break;
default:
if (device && !list_empty(&device->pending_event_queue))
ei_queue_frame_event(device, ei_now(ei));
ei_queue_frame_event(device, ei_now(ei));
break;
}
@ -294,6 +328,39 @@ ei_queue_disconnect_event(struct ei *ei)
queue_event(ei, e);
}
void
ei_queue_pong_event(struct ei *ei, struct ei_ping *ping)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_PONG;
e->pong.ping = ei_ping_ref(ping);
queue_event(ei, e);
}
void
ei_sync_event_send_done(struct ei_event *e)
{
struct ei *ei = ei_event_get_context(e);
_unref_(ei_pingpong) *pp = steal(&e->sync.pingpong);
log_debug(ei_event_get_context(e),
"object %#" PRIx64 ": ping pong done",
ei_pingpong_get_id(pp));
if (ei->state < EI_STATE_DISCONNECTED)
ei_pingpong_request_done(pp, 0);
}
void
ei_queue_sync_event(struct ei *ei, struct ei_pingpong *ping)
{
struct ei_event *e = ei_event_new(ei);
e->type = EI_EVENT_SYNC;
e->sync.pingpong = ei_pingpong_ref(ping);
queue_event(ei, e);
}
void
ei_queue_seat_added_event(struct ei_seat *seat)
{
@ -516,7 +583,7 @@ ei_queue_touch_down_event(struct ei_device *device, uint32_t touchid,
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_DOWN;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.x = x;
e->touch.y = y;
@ -530,7 +597,7 @@ ei_queue_touch_motion_event(struct ei_device *device, uint32_t touchid,
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_MOTION;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.x = x;
e->touch.y = y;
@ -543,12 +610,25 @@ ei_queue_touch_up_event(struct ei_device *device, uint32_t touchid)
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_UP;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.is_cancel = false;
queue_event(ei_device_get_context(device), e);
}
void
ei_queue_touch_cancel_event(struct ei_device *device, uint32_t touchid)
{
struct ei_event *e = ei_event_new_for_device(device);
e->type = EI_EVENT_TOUCH_UP;
e->touch.touchid = touchid;
e->touch.is_cancel = true;
queue_event(ei_device_get_context(device), e);
}
_public_ void
ei_disconnect(struct ei *ei)
{
if (ei->state == EI_STATE_DISCONNECTED ||
@ -566,8 +646,9 @@ ei_disconnect(struct ei *ei)
ei_seat_remove(seat);
}
if (state != EI_STATE_NEW) {
if (state != EI_STATE_NEW && state != EI_STATE_BACKEND) {
ei_connection_request_disconnect(ei->connection);
ei_connection_remove_pending_callbacks(ei->connection);
}
ei_queue_disconnect_event(ei);
ei->state = EI_STATE_DISCONNECTED;
@ -590,6 +671,9 @@ handle_msg_seat(struct ei_connection *connection, object_id_t seat_id, uint32_t
DISCONNECT_IF_INVALID_ID(connection, seat_id);
struct ei *ei = ei_connection_get_context(connection);
DISCONNECT_IF_INVALID_VERSION(ei, ei_seat, seat_id, version);
struct ei_seat *seat = ei_seat_new(ei, seat_id, version);
/* We might get the seat event before our callback finished, so make sure
@ -654,6 +738,10 @@ handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
if (reason == EI_CONNECTION_DISCONNECT_REASON_DISCONNECTED) {
log_info(ei, "Disconnected by EIS");
/* We got disconnected, disconnect our source because whatever
* we'd receive after this is garbage and the server won't
* want to hear anything from us anyway. */
source_remove(ei->source);
ei_disconnect(ei);
return NULL;
} else {
@ -664,11 +752,29 @@ handle_msg_disconnected(struct ei_connection *connection, uint32_t last_serial,
}
static struct brei_result *
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, object_id_t object)
handle_msg_invalid_object(struct ei_connection *connection, uint32_t last_serial, object_id_t object_id)
{
struct ei *ei = ei_connection_get_context(connection);
log_bug(ei, "Invalid object %#" PRIx64 " after %u, I don't yet know how to handle that", object, last_serial);
/* The protocol is async, so what can happen is:
*
* server sends A->destroyed()
* client sends A->foo()
* client receives A->destroyed()
* server receives A->foo()
* server sends invalid_object_id(A)
* client receives invalid_object_id(A)
*
* This is expected and we shouldn't have a problem with that.
*/
struct ei_defunct_object *defunct;
list_for_each_safe(defunct, &ei->defunct_objects, node) {
if (defunct->object_id == object_id)
return NULL;
}
log_bug(ei, "Invalid object %#" PRIx64 " after serial %u, I don't yet know how to handle that", object_id, last_serial);
return NULL;
}
@ -679,9 +785,11 @@ handle_msg_ping(struct ei_connection *connection, object_id_t id, uint32_t vers
DISCONNECT_IF_INVALID_ID(connection, id);
struct ei *ei = ei_connection_get_context(connection);
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
ei_pingpong_request_done(pingpong, 0);
DISCONNECT_IF_INVALID_VERSION(ei, ei_pingpong, id, version);
_unref_(ei_pingpong) *pingpong = ei_pingpong_new_for_id(ei, id, version);
ei_queue_sync_event(ei_connection_get_context(connection), pingpong);
return NULL;
}
@ -720,15 +828,33 @@ lookup_object(object_id_t object_id, struct brei_object **object, void *userdata
static void
connection_dispatch(struct source *source, void *userdata)
{
static uint8_t cleanup;
struct ei *ei = userdata;
enum ei_state old_state = ei->state;
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
lookup_object, ei);
if (result) {
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
brei_drain_fd(source_get_fd(source));
/* Flush any pending writes, if we have them */
int rc = ei_unsent_flush(ei);
if (rc < 0 && rc != -EAGAIN) {
log_warn(ei, "Error flushing unsent queue: %s", strerror(-rc));
ei_disconnect(ei);
} else {
_unref_(brei_result) *result = brei_dispatch(ei->brei, source_get_fd(source),
lookup_object, ei);
if (result) {
log_warn(ei, "Connection error: %s", brei_result_get_explanation(result));
brei_drain_fd(source_get_fd(source));
ei_disconnect(ei);
} else if (++cleanup % 20 == 0) {
uint64_t now = ei_now(ei);
struct ei_defunct_object *defunct;
list_for_each_safe(defunct, &ei->defunct_objects, node) {
/* Drop defunct objects after 5s */
if (now - defunct->time < s2us(5))
break;
ei_defunct_object_free(defunct);
}
}
}
static const char *states[] = {
@ -746,11 +872,50 @@ connection_dispatch(struct source *source, void *userdata)
states[ei->state]);
}
static void
ei_queue_unsent(struct ei *ei, struct source *source, struct iobuf *buf)
{
if (list_empty(&ei->unsent_queue)) {
source_enable_write(source, true);
}
struct ei_unsent *unsent = xalloc(sizeof *unsent);
unsent->buf = buf;
list_append(&ei->unsent_queue, &unsent->node);
}
static void
ei_unsent_free(struct ei_unsent *unsent)
{
list_remove(&unsent->node);
iobuf_free(unsent->buf);
free(unsent);
}
int
ei_unsent_flush(struct ei* ei)
{
if (list_empty(&ei->unsent_queue))
return 0;
struct source *source = ei->source;
int fd = source_get_fd(source);
struct ei_unsent *unsent;
list_for_each_safe(unsent, &ei->unsent_queue, node) {
int rc = iobuf_send(unsent->buf, fd);
if (rc < 0)
return rc;
ei_unsent_free(unsent);
}
source_enable_write(source, false);
return 0;
}
int
ei_send_message(struct ei *ei, const struct brei_object *object,
uint32_t opcode, const char *signature, size_t nargs, ...)
{
log_debug(ei, "sending: object %#" PRIx64 " (%s@v%u:%s(%u)) signature '%s'",
log_debug(ei, "object %#" PRIx64 " sending: (%s@v%u:%s(%u)) signature '%s'",
object->id,
object->interface->name,
object->interface->version,
@ -775,7 +940,19 @@ ei_send_message(struct ei *ei, const struct brei_object *object,
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
assert(buf);
int fd = source_get_fd(ei->source);
int rc = iobuf_send(buf, fd);
int rc = -EPIPE;
if (fd != -1) {
rc = ei_unsent_flush(ei);
if (rc >= 0)
rc = iobuf_send(buf, fd);
if (rc == -EAGAIN) {
ei_queue_unsent(ei, ei->source, steal(&buf));
rc = 0;
} else if (rc < 0){
log_warn(ei, "failed to send message: %s", strerror(-rc));
source_remove(ei->source);
}
}
return rc < 0 ? rc : 0;
}
@ -819,19 +996,30 @@ ei_configure_name(struct ei *ei, const char *name)
ei->name = xstrdup(name);
}
_public_ void
ei_clock_set_now_func(struct ei *ei, ei_clock_now_func func)
{
ei->clock_now = func;
}
_public_ uint64_t
ei_now(struct ei *ei)
{
uint64_t ts = 0;
int rc = now(&ts);
if (rc < 0) {
/* We should probably disconnect here but the chances of this
* happening are so slim it's not worth worrying about. Plus,
* if this fails we're likely to be inside eis_device_frame()
* so we should flush a frame event before disconnecting and... */
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
if (ei->clock_now)
ts = ei->clock_now(ei);
else {
int rc = now(&ts);
if (rc < 0) {
/* We should probably disconnect here but the chances of this
* happening are so slim it's not worth worrying about. Plus,
* if this fails we're likely to be inside eis_device_frame()
* so we should flush a frame event before disconnecting and... */
log_error(ei, "clock_gettime failed: %s", strerror(-rc));
}
}
return ts;
}

View file

@ -60,13 +60,11 @@ extern "C" {
* libei clients come in @ref ei_new_sender "sender" and @ref ei_new_receiver
* "receiver" modes, depending on whether the client sends or receives events
* from the EIS implementation. See @ref libei-sender and @ref libei-receiver
for
* API calls specific to either mode.
* for API calls specific to either mode.
*
* @note A libei context is restricted to either sender or receiver mode, not
* both. The EIS implementation however may accept both sender and receiver
clients,
* and will work as corresponding receiver or sender for this
* clients, and will work as corresponding receiver or sender for this
* client. It is up to the implementation to disconnect clients that it does not
* want to allow. See eis_client_is_sender() for details.
*
@ -92,7 +90,7 @@ extern "C" {
* The sender client API is available only to clients created with
* ei_new_sender(). For those clients the EIS implemententation creates
* devices and but it is the libei client that sends events **to** EIS
implementation.
* implementation.
* The primary use-case is input emulation from a client, akin to xdotool.
*
* It is a client bug to call any of these functions for a client created
@ -205,6 +203,28 @@ struct ei_keymap;
*/
struct ei_region;
/**
* @struct ei_touch
*
* A single touch initiated by a sender context.
*
* @see ei_device_touch_new
* @see ei_touch_down
* @see ei_touch_motion
* @see ei_touch_up
*/
struct ei_touch;
/**
* @struct ei_ping
*
* A callback struct returned by ei_new_ping() to handle
* roundtrips to the EIS implementation.
*
* @see ei_new_ping
*/
struct ei_ping;
/**
* @enum ei_device_type
* @ingroup libei-device
@ -294,6 +314,15 @@ enum ei_keymap_type {
EI_KEYMAP_TYPE_XKB = 1,
};
/**
* @enum ei_event_type
*
* This enum is not exhaustive, future versions of this library may add
* new event types.
*
* Unknown events must be released by the caller with ei_event_unref(),
* see ei_get_event().
*/
enum ei_event_type {
/**
* The server has approved the connection to this client. Where the
@ -367,13 +396,24 @@ enum ei_event_type {
/**
* Any events sent from this device will be discarded until the next
* resume. The state of a device is not expected to change between
* pause/resume - for any significant state changes the server is
* expected to remove the device instead.
* resume.
*
* Pausing a device resets the logical state of the device to neutral.
* This includes:
* - any buttons or keys logically down are released
* - any modifiers logically down are released
* - any touches logically down are released
*
* Sender clients must wait until @ref EI_EVENT_DEVICE_RESUMED
* before sending events.
*/
EI_EVENT_DEVICE_PAUSED,
/**
* The client may send events.
*
* Once resumed, a sender client may call ei_device_start_emulating()
* and begin emulating events.
*/
EI_EVENT_DEVICE_RESUMED,
@ -385,14 +425,58 @@ enum ei_event_type {
* ei_event_keyboard_get_xkb_mods_locked(), and
* ei_event_keyboard_get_xkb_group().
*
* This event is sent in response to an external modifier state
* change. Where the client triggers a modifier state change in
* response to ei_device_keyboard_key(), no such event is sent.
* This event is sent in response to any modifier state or effective
* group change, including where the change is triggered by a client
* call to ei_device_keyboard_key().
*
* For receiver clients, this will always be properly ordered with
* EI_EVENT_KEYBOARD_KEY events, so each key event should be
* interpreted should using the most recently received modifier
* state.
*
* For sender clients, the this event is not inherently synchronized
* with calls to ei_device_keyboard_key(), but the client may call
* ei_ping() when synchronization is required. When the corresponding
* EI_EVENT_PONG event is received, all key events sent prior to the
* sync request are guaranteed to have been processed, and any
* directly-resulting modifiers events are guaranteed to have been
* received. Note, however, that it is still possible for
* indirectly-triggered state changes, such as via a keyboard
* shortcut not encoded in the keymap, to be reported after the done
* event.
*
* This event may arrive while a device is paused.
*
* Note: It was previously specified that a where a sender client
* triggers a modifier state change in response to
* ei_device_keyboard_key(), no MODIFIERS event would be sent.
* Clients were expected to mix calls to xkb_state_update_key() and
* xkb_state_update_mask() to track the state with libxkbcommon,
* which could lead to disagreements between the client and server as
* to the current state.
*/
EI_EVENT_KEYBOARD_MODIFIERS,
/**
* Returned in response to ei_ping().
*
* Note that in the ei protocol, the matching client request is called `sync`
* but for consistency in the libeis/libei API the C API uses
* ei_ping() with `EI_EVENT_PONG` as return.
*/
EI_EVENT_PONG = 90,
/**
* This event represents a synchronization request (ping) from the EIS
* implementation. The corresponding reply (pong) will be sent when
* it is unref'd. It has no other API.
*
* The caller must ensure that any state changes triggered by messages
* received prior to this event have been resolved and communicated to the
* EIS implementation prior to calling ei_event_unref().
*/
EI_EVENT_SYNC,
/**
* "Hardware" frame event. This event **must** be sent by the server
* and notifies the client that the previous set of events belong to
@ -542,7 +626,7 @@ ei_new(void *user_data);
* receive events.
*
* A context supports exactly one backend, set up with one of
* ei_setup_backend_socket() or ei_setup_backend_fd().
* ei_setup_backend_fd() or ei_setup_backend_socket().
*
* @param user_data An opaque pointer to be returned with ei_get_user_data()
*
@ -562,7 +646,7 @@ ei_new_sender(void *user_data);
* send events.
*
* A context supports exactly one backend, set up with one of
* ei_setup_backend_socket() or ei_setup_backend_fd().
* ei_setup_backend_fd() or ei_setup_backend_socket().
*
* @param user_data An opaque pointer to be returned with ei_get_user_data()
*
@ -654,9 +738,9 @@ ei_log_context_get_func(struct ei_log_context *ctx);
* messages with a log level equal or greater than than the one set in
* ei_log_set_priority().
*
* The context passed to this function contains auxilary information about
* The context passed to this function contains auxiliary information about
* this log message such as the line number, file name and function name
* this message occured in. The log context is valid only within the current
* this message occurred in. The log context is valid only within the current
* invocation of the log handler.
*
* @param ei The EI context
@ -693,6 +777,24 @@ ei_log_set_priority(struct ei *ei, enum ei_log_priority priority);
enum ei_log_priority
ei_log_get_priority(const struct ei *ei);
/**
* Optional override function for ei_now().
*
* By default ei_now() returns the current timestamp in CLOCK_MONOTONIC. This
* may be overridden by a caller to provide a different timestamp.
*
* There is rarely a need to override this function. It exists for the libei-internal test suite.
*/
typedef uint64_t (*ei_clock_now_func)(struct ei *ei);
/**
* Override the function that returns the current time ei_now().
*
* There is rarely a need to override this function. It exists for the libei-internal test suite.
*/
void
ei_clock_set_now_func(struct ei *, ei_clock_now_func func);
/**
* Set the name for this client. This is a suggestion to the
* server only and may not be honored.
@ -702,17 +804,48 @@ ei_log_get_priority(const struct ei *ei);
* the EIS implementation.
*
* This function must be called immediately after ei_new() and before
* setting up a backend with ei_setup_backend_socket() or
* ei_setup_backend_fd().
* setting up a backend with ei_setup_backend_fd() or
* ei_setup_backend_socket().
*/
void
ei_configure_name(struct ei * ei, const char *name);
/**
* Initialize the ei context on the given socket file descriptor.
* The ei context will initiate the conversation with the EIS server listening
* on the other end of this socket.
*
* This is the preferred entry point for libei and should be the default
* used in new clients. It allows for private-by-default socket file descriptors
* and the policy on how the socket is created is delegated to the caller.
*
* If the connection was successful, an event of type @ref EI_EVENT_CONNECT
* or @ref EI_EVENT_DISCONNECT will become available after a future call to
* ei_dispatch().
*
* If the connection failed, use ei_unref() to release the data allocated
* for this context.
*
* This function takes ownership of the file descriptor, and will close it
* when tearing down.
*
* @return zero on success or a negative errno on failure
*/
int
ei_setup_backend_fd(struct ei *ei, int fd);
/**
* Set this ei context to use the socket backend. The ei context will
* connect to the socket at the given path and initiate the conversation
* with the EIS server listening on that socket.
*
* @note This backend requires the socket to be accessible
* to connect to and forces the EIS implementation to to identification and
* access control for clients. In almost all cases, the EIS implementation
* should use pre-created fds per client and the client should instead use
* ei_setup_backend_fd(). This backend is primarily useful for testing and
* debugging.
*
* If @a socketpath is `NULL`, the value of the environment variable
* `LIBEI_SOCKET` is used. If @a socketpath does not start with '/', it is
* relative to `$XDG_RUNTIME_DIR`. If `XDG_RUNTIME_DIR` is not set, this
@ -730,26 +863,6 @@ ei_configure_name(struct ei * ei, const char *name);
int
ei_setup_backend_socket(struct ei *ei, const char *socketpath);
/**
* Initialize the ei context on the given socket. The ei context will
* initiate the conversation with the EIS server listening on the other end
* of this socket.
*
* If the connection was successful, an event of type @ref EI_EVENT_CONNECT
* or @ref EI_EVENT_DISCONNECT will become available after a future call to
* ei_dispatch().
*
* If the connection failed, use ei_unref() to release the data allocated
* for this context.
*
* This function takes ownership of the file descriptor, and will close it
* when tearing down.
*
* @return zero on success or a negative errno on failure
*/
int
ei_setup_backend_fd(struct ei *ei, int fd);
/**
* libei keeps a single file descriptor for all events. This fd should be
* monitored for events by the caller's mainloop, e.g. using select(). When
@ -759,6 +872,94 @@ ei_setup_backend_fd(struct ei *ei, int fd);
int
ei_get_fd(struct ei *ei);
/**
* Create a new ei_ping object to trigger a round trip to the EIS implementation.
* See ei_ping() for details.
*
* The returned @ref ei_ping is refcounted, use ei_ping_unref() to
* drop the reference.
*
* @since 1.4
*/
struct ei_ping *
ei_new_ping(struct ei *ei);
/**
* Return a unique, increasing id for this struct. This ID is assigned at
* struct creation and constant for the lifetime of the struct. The ID
* does not exist at the protocol level.
*
* This is a convenience API to make it easier to put this struct into
* e.g. a hashtable without having to use the pointer value itself.
*
* The ID increases by an unspecified amount.
*
* @since 1.4
*/
uint64_t
ei_ping_get_id(struct ei_ping *ping);
/**
* Increase the refcount of this struct by one. Use ei_ping_unref() to decrease
* the refcount.
*
* @return the argument passed into the function
*
* @since 1.4
*/
struct ei_ping *
ei_ping_ref(struct ei_ping *ei_ping);
/**
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, all allocated resources for this struct are released.
*
* @return always NULL
*
* @since 1.4
*/
struct ei_ping *
ei_ping_unref(struct ei_ping *ei_ping);
/**
* Set a custom data pointer for this struct. libei will not look at or
* modify the pointer. Use ei_ping_get_user_data() to retrieve a previously set
* user data.
*
* @since 1.4
*/
void
ei_ping_set_user_data(struct ei_ping *ei_ping, void *user_data);
/**
* Return the custom data pointer for this struct. libei will not look at or
* modify the pointer. Use ei_ping_set_user_data() to change the user data.
*
* @since 1.4
*/
void *
ei_ping_get_user_data(struct ei_ping *ei_ping);
/**
* Issue a roundtrip request to the EIS implementation, resulting in
* an @ref EI_EVENT_PONG event when this roundtrip has been processed. Client
* requests are processed in-order by the EIS implementation so this
* function can be used as synchronization point between requests.
*
* If the client is disconnected before the roundtrip is complete,
* libei will emulate a @ref EI_EVENT_PONG event before @ref
* EI_EVENT_DISCONNECT.
*
* Note that in the ei protocol, the client request is `ei_connection.sync`
* followed by `ei_callback.done`. For consistency with the
* libeis API the C API uses ei_ping() with and @ref EI_EVENT_PONG as return
* event.
*
* @since 1.4
*/
void
ei_ping(struct ei_ping *ping);
/**
* Main event dispatching function. Reads events of the file descriptors
* and processes them internally. Use ei_get_event() to retrieve the
@ -774,7 +975,8 @@ ei_dispatch(struct ei *ei);
/**
* Return the next event from the event queue, removing it from the queue.
*
* The returned object must be released by the caller with ei_event_unref()
* The returned object must be released by the caller with ei_event_unref(),
* even if the event type is unknown to the caller.
*/
struct ei_event *
ei_get_event(struct ei *ei);
@ -802,12 +1004,30 @@ ei_peek_event(struct ei *ei);
* @returns a timestamp in microseconds for the current time to pass into
* ei_device_frame().
*
* In the current implementation, the returned timestamp is CLOCK_MONOTONIC
* for compatibility with evdev and libinput.
* By default, the returned timestamp is CLOCK_MONOTONIC for compatibility with
* evdev and libinput. This can be overridden with ei_clock_set_now_func().
*/
uint64_t
ei_now(struct ei *ei);
/**
* Disconnect the current ei context from the EIS implementation.
*
* After a call to ei_disconnect(), ei_get_event() will return
* events to remove resources (e.g. seats and devices) as if they
* had been removed by the EIS implementation. The last event
* returned by ei_get_event() is @ref EI_EVENT_DISCONNECT after
* which the context should be considered inert and any
* remaining resources released with ei_unref().
*
* This does not free the resources associated with the ei context, use
* ei_unref().
*
* @since 1.4
*/
void
ei_disconnect(struct ei *ei);
/**
* @ingroup libei-seat
*
@ -836,7 +1056,7 @@ ei_seat_get_name(struct ei_seat *seat);
/**
* @ingroup libei-seat
*
* Return true if the capabilitiy is available on this seat or false
* Return true if the capability is available on this seat or false
* otherwise. The return value of this function is not affected by
* ei_seat_confirm_capability().
*/
@ -868,7 +1088,7 @@ __attribute__((sentinel));
/**
* @ingroup libei-seat
*
* Unbind a seat's capabilities, terminatd by ``NULL``.
* Unbind a seat's capabilities, terminated by ``NULL``.
* This function indicates the the application is
* no longer interested in devices with the given capability.
*
@ -880,6 +1100,31 @@ void
ei_seat_unbind_capabilities(struct ei_seat *seat, ...)
__attribute__((sentinel));
/**
* @ingroup libei-seat
*
* Request a new device with (a subset of) the given capabilities from the EIS
* implementation. If the EIS implementation creates a device in response
* to this request the device will arrive via an event of type @ref
* EI_EVENT_DEVICE_ADDED.
*
* The device's capabilities may not match the requested capabilities. For
* example requesting a pointer + keyboard device may result in the creation
* of a pointer-only device or it may result in the creation of a pointer +
* keyboard + touch device. It is up to the caller to handle capability
* mismatches.
*
* This function should be used by a caller when the current set of devices
* is insufficient for the functionality the client requires. For example
* a client that has previously called ei_device_close() on a device may
* need a device again with similar capabilities.
*
* The capabilities must be a subset of the capabilities requested in
* ei_seat_bind_capabilities().
*/
void
ei_seat_request_device_with_capabilities(struct ei_seat *seat, ...)
__attribute__((sentinel));
/**
* @ingroup libei-seat
@ -902,13 +1147,24 @@ struct ei *
ei_seat_get_context(struct ei_seat *seat);
/**
* Release resources associated with this event. This function always
* returns NULL.
* Increase the refcount of this struct by one. Use ei_event_unref() to decrease
* the refcount. This function always returns the same event that the reference
* was added to.
*
* The caller cannot increase the refcount of an event. Events should be
* considered transient data and not be held longer than required.
* ei_event_unref() is provided for consistency (as opposed to, say,
* ei_event_free()).
* Events should be considered transient data and not be held longer than
* required.
*/
struct ei_event *
ei_event_ref(struct ei_event *event);
/**
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, all allocated resources for this struct are released.
*
* Events should be considered transient data and not be held longer than
* required.
*
* @return always NULL
*/
struct ei_event *
ei_event_unref(struct ei_event *event);
@ -958,8 +1214,7 @@ ei_device_ref(struct ei_device *device);
* @ingroup libei-device
*
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, the context disconnects from the server and all allocated resources
* are released.
* zero, all allocated resources for this struct are released.
*
* @return always NULL
*/
@ -1009,17 +1264,6 @@ ei_device_get_width(struct ei_device *device);
uint32_t
ei_device_get_height(struct ei_device *device);
/**
* @ingroup libei-device
*
* Return the keymap assigned to this device by the EIS implementation or NULL
* if no keymap is set.
*
* The keymap is constant for the life of the device.
*/
struct ei_keymap *
ei_device_get_keymap(struct ei_device *device);
/**
* @ingroup libei-keymap
*
@ -1075,8 +1319,7 @@ ei_keymap_ref(struct ei_keymap *keymap);
* @ingroup libei-keymap
*
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, the context disconnects from the server and all allocated resources
* are released.
* zero, all allocated resources for this struct are released.
*
* @return always NULL
*/
@ -1169,6 +1412,17 @@ ei_device_has_capability(struct ei_device *device,
struct ei_region *
ei_device_get_region(struct ei_device *device, size_t index);
/**
* @ingroup libei-device
*
* Return the region that contains the given point x/y (in desktop-wide
* coordinates) or NULL if the coordinates are outside all regions.
*
* @since 1.1
*/
struct ei_region *
ei_device_get_region_at(struct ei_device *device, double x, double y);
/**
* @ingroup libei-region
*/
@ -1217,6 +1471,36 @@ ei_region_get_width(struct ei_region *region);
uint32_t
ei_region_get_height(struct ei_region *region);
/**
* @ingroup libei-region
*
* Get the unique identifier (representing an external resource) that is
* attached to this region, if any. This is only available if the EIS
* implementation supports version 2 or later of the ei_device protocol
* interface *and* the EIS implementation chooses to attach such an identifier to
* the region.
*
* This ID can be used by the client to identify an external resource that has a
* relationship with this region.
*
* For example the client may receive a data stream with the video
* data that this region represents. By attaching the same identifier to the data
* stream and this region the EIS implementation can inform the client
* that the video data stream and the region represent paired data.
* Note that in this example use-case, if the stream is resized
* there may be a transition period where two regions have the same identifier -
* the old region and the new region with the updated size. A client must be
* able to handle the case where to mapping ids are identical.
*
* libei does not look at or modify the value of the mapping id. Because the ID is
* assigned by the caller libei makes no guarantee that the ID is unique
* and/or corresponds to any particular format.
*
* @since 1.1
*/
const char *
ei_region_get_mapping_id(struct ei_region *region);
/**
* @ingroup libei-region
*
@ -1385,6 +1669,10 @@ ei_device_start_emulating(struct ei_device *device, uint32_t sequence);
* Notify the EIS implementation that the given device is no longer sending
* events. See ei_device_start_emulating() for details.
*
* If the device is not logically in a neutral state, that state is left
* as-is. It is the caller's responsibility to release any buttons, keys, touch
* sequences, etc. when stopping emulation to avoid adverse side effects.
*
* This method is only available on an ei sender context.
*/
void
@ -1418,8 +1706,8 @@ ei_device_frame(struct ei_device *device, uint64_t time);
* This method is only available on an ei sender context.
*
* @param device The EI device
* @param x The x movement in logical pixels
* @param y The y movement in logical pixels
* @param x The x movement in logical pixels or mm, depending on the device type
* @param y The y movement in logical pixels or mm, depending on the device type
*/
void
ei_device_pointer_motion(struct ei_device *device, double x, double y);
@ -1436,8 +1724,8 @@ ei_device_pointer_motion(struct ei_device *device, double x, double y);
* This method is only available on an ei sender context.
*
* @param device The EI device
* @param x The x position in logical pixels
* @param y The y position in logical pixels
* @param x The x position in logical pixels or mm, depending on the device type
* @param y The y position in logical pixels or mm, depending on the device type
*/
void
ei_device_pointer_motion_absolute(struct ei_device *device,
@ -1474,8 +1762,8 @@ ei_device_button_button(struct ei_device *device,
* This method is only available on an ei sender context.
*
* @param device The EI device
* @param x The x scroll distance in logical pixels
* @param y The y scroll distance in logical pixels
* @param x The x scroll distance in logical pixels or mm, depending on the device type
* @param y The y scroll distance in logical pixels or mm, depending on the device type
*
* @see ei_device_scroll_discrete
*/
@ -1581,7 +1869,7 @@ ei_device_scroll_cancel(struct ei_device *device, bool cancel_x, bool cancel_y);
*
* Note that this is a keymap-independent key code, equivalent to the scancode
* a physical keyboard would produce. To generate a specific key symbol, a
* client must look at the keymap returned by ei_device_get_keymap() and
* client must look at the keymap returned by ei_device_keyboard_get_keymap() and
* generate the appropriate keycodes.
*
* This method is only available on an ei sender context.
@ -1642,6 +1930,20 @@ ei_touch_motion(struct ei_touch *touch, double x, double y);
void
ei_touch_up(struct ei_touch *touch);
/**
* @ingroup libei-sender
*
* Cancel this touch. After this call, the touch event becomes inert and
* no longer responds to either ei_touch_down(), ei_touch_motion() or
* ei_touch_up() and the caller should call ei_touch_unref().
*
* This is only available if the EIS implementation supports version 2
* or later of the ei_touchscreen protocol interface. Otherwise,
* this function is equivalent to calling ei_touch_up().
*/
void
ei_touch_cancel(struct ei_touch *touch);
/**
* @ingroup libei-sender
*
@ -1657,8 +1959,7 @@ ei_touch_ref(struct ei_touch *touch);
* @ingroup libei-sender
*
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, the context disconnects from the server and all allocated resources
* are released.
* zero, all allocated resources for this struct are released.
*
* @return always NULL
*/
@ -1668,8 +1969,9 @@ ei_touch_unref(struct ei_touch *touch);
/**
* @ingroup libei-sender
*
* Return the custom data pointer for this context. libei will not look at or
* modify the pointer. Use ei_touch_set_user_data() to change the user data.
* Set a custom data pointer for this context. libei will not look at or
* modify the pointer. Use ei_touch_get_user_data() to retrieve a previously
* set user data.
*/
void
ei_touch_set_user_data(struct ei_touch *touch, void *user_data);
@ -1677,9 +1979,8 @@ ei_touch_set_user_data(struct ei_touch *touch, void *user_data);
/**
* @ingroup libei-sender
*
* Set a custom data pointer for this context. libei will not look at or
* modify the pointer. Use ei_touch_get_user_data() to retrieve a previously
* set user data.
* Return the custom data pointer for this context. libei will not look at or
* modify the pointer. Use ei_touch_set_user_data() to change the user data.
*/
void *
ei_touch_get_user_data(struct ei_touch *touch);
@ -1704,6 +2005,18 @@ ei_touch_get_device(struct ei_touch *touch);
struct ei_seat *
ei_event_get_seat(struct ei_event *event);
/**
* Returns the associated @ref ei_ping struct with this event.
*
* For events of type other than @ref EI_EVENT_PONG this function
* returns NULL.
*
* This does not increase the refcount of the ei_pong. Use ei_pong_ref()
* to keep a reference beyond the immediate scope.
*/
struct ei_ping *
ei_event_pong_get_ping(struct ei_event *event);
/**
* @ingroup libei-receiver
*
@ -1720,7 +2033,7 @@ ei_event_emulating_get_sequence(struct ei_event *event);
*
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
* mask of currently logically pressed-down modifiers.
* See ei_device_get_keymap() for the corresponding keymap.
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
*/
uint32_t
ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event);
@ -1730,7 +2043,7 @@ ei_event_keyboard_get_xkb_mods_depressed(struct ei_event *event);
*
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
* mask of currently logically latched modifiers.
* See ei_device_get_keymap() for the corresponding keymap.
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
*/
uint32_t
ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event);
@ -1740,7 +2053,7 @@ ei_event_keyboard_get_xkb_mods_latched(struct ei_event *event);
*
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
* mask of currently logically locked modifiers.
* See ei_device_get_keymap() for the corresponding keymap.
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
*/
uint32_t
ei_event_keyboard_get_xkb_mods_locked(struct ei_event *event);
@ -1749,8 +2062,21 @@ ei_event_keyboard_get_xkb_mods_locked(struct ei_event *event);
* @ingroup libei-receiver
*
* For an event of type @ref EI_EVENT_KEYBOARD_MODIFIERS, get the
* logical group state.
* See ei_device_get_keymap() for the corresponding keymap.
* current effective group.
*
* This may be passed to xkb_state_update_mask() as either
* depressed_layout (effectively pretending the user is holding down some
* key for this group at all times) or locked_layout (treating it as a
* layout the user has switched to through some mechanism), but never
* both at the same time. The other two layout arguments must be set to
* zero.
*
* Note: Because the client only knows the current effective group and
* not the combination of state from which it was calculated, any attempt
* to predict how future key presses will impact the group state will
* necessarily be unreliable.
*
* See ei_device_keyboard_get_keymap() for the corresponding keymap.
*/
uint32_t
ei_event_keyboard_get_xkb_group(struct ei_event *event);
@ -1917,6 +2243,19 @@ ei_event_touch_get_x(struct ei_event *event);
double
ei_event_touch_get_y(struct ei_event *event);
/**
* @ingroup libei-receiver
*
* For an event of type @ref EI_EVENT_TOUCH_UP
* return true if the event was cancelled instead of
* logically released.
*
* Support for touch cancellation requires the EIS implementation and client to
* support version 2 or later of the ei_touchscreen protocol interface.
*/
bool
ei_event_touch_get_is_cancel(struct ei_event *event);
/**
* @}
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN button WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN button WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN callback WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -19,7 +19,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN callback WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -47,12 +47,25 @@
DEFINE_TRISTATE(started, finished, connected);
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
struct eis_unsent {
struct list node;
struct iobuf *buf;
};
static void
eis_unsent_free(struct eis_unsent *unsent);
static void
client_drop_seats(struct eis_client *client);
static void
eis_client_destroy(struct eis_client *client)
{
struct eis_unsent *unsent;
list_for_each_safe(unsent, &client->unsent_queue, node) {
eis_unsent_free(unsent);
}
client_drop_seats(client);
eis_handshake_unref(client->setup);
eis_connection_unref(client->connection);
@ -127,7 +140,7 @@ void
eis_client_register_object(struct eis_client *client, struct brei_object *object)
{
struct eis *eis = eis_client_get_context(client);
log_debug(eis, "registering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
log_debug(eis, "object %#" PRIx64 " registering %s v%u", object->id, object->interface->name, object->version);
list_append(&client->proto_objects, &object->link);
}
@ -135,7 +148,7 @@ void
eis_client_unregister_object(struct eis_client *client, struct brei_object *object)
{
struct eis *eis = eis_client_get_context(client);
log_debug(eis, "deregistering %s v%u object %#" PRIx64 "", object->interface->name, object->version, object->id);
log_debug(eis, "object %#" PRIx64 " deregistering %s v%u", object->id, object->interface->name, object->version);
list_remove(&object->link);
}
@ -151,13 +164,54 @@ eis_client_is_sender(struct eis_client *client)
return client->is_sender;
}
static void
eis_client_queue_unsent(struct eis_client *client,
struct source *source,
struct iobuf *buf)
{
if (list_empty(&client->unsent_queue)) {
source_enable_write(source, true);
}
struct eis_unsent *unsent = xalloc(sizeof *unsent);
unsent->buf = buf;
list_append(&client->unsent_queue, &unsent->node);
}
static void
eis_unsent_free(struct eis_unsent *unsent)
{
list_remove(&unsent->node);
iobuf_free(unsent->buf);
free(unsent);
}
static int
eis_client_unsent_flush(struct eis_client* client)
{
if (list_empty(&client->unsent_queue))
return 0;
struct source *source = client->source;
int fd = source_get_fd(source);
struct eis_unsent *unsent;
list_for_each_safe(unsent, &client->unsent_queue, node) {
int rc = iobuf_send(unsent->buf, fd);
if (rc < 0)
return rc;
eis_unsent_free(unsent);
}
source_enable_write(source, false);
return 0;
}
int
eis_client_send_message(struct eis_client *client, const struct brei_object *object,
uint32_t opcode, const char *signature, size_t nargs, ...)
{
struct eis *eis = eis_client_get_context(client);
log_debug(eis, "sending: object %#" PRIx64 " (%s@v%u:%s(%u)) signature '%s'",
log_debug(eis, "object %#" PRIx64 " sending (%s@v%u:%s(%u)) signature '%s'",
object->id,
object->interface->name,
object->interface->version,
@ -182,7 +236,23 @@ eis_client_send_message(struct eis_client *client, const struct brei_object *obj
_cleanup_iobuf_ struct iobuf *buf = brei_result_get_data(result);
assert(buf);
int fd = source_get_fd(client->source);
int rc = iobuf_send(buf, fd);
int rc = -EPIPE;
if (fd != -1) {
rc = eis_client_unsent_flush(client);
if (rc >= 0)
rc = iobuf_send(buf, fd);
if (rc == -EAGAIN) {
eis_client_queue_unsent(client, client->source, steal(&buf));
rc = 0;
} else if (rc < 0){
if (rc == -EPIPE) {
log_debug(eis, "failed to send message: %s", strerror(-rc));
} else {
log_warn(eis, "failed to send message: %s", strerror(-rc));
}
source_remove(client->source);
}
}
return rc < 0 ? rc : 0;
}
@ -247,6 +317,7 @@ client_disconnect(struct eis_client *client,
case EIS_CLIENT_STATE_CONNECTING:
case EIS_CLIENT_STATE_CONNECTED:
client_drop_seats(client);
eis_connection_remove_pending_callbacks(client->connection);
eis_queue_disconnect_event(client);
eis_connection_event_disconnected(client->connection,
client->last_client_serial,
@ -318,17 +389,16 @@ client_msg_disconnect(struct eis_connection *connection)
}
static struct brei_result *
client_msg_sync(struct eis_connection *connection, object_id_t new_id)
client_msg_sync(struct eis_connection *connection, object_id_t new_id, uint32_t version)
{
struct eis_client *client = eis_connection_get_client(connection);
DISCONNECT_IF_INVALID_ID(client, new_id);
DISCONNECT_IF_INVALID_VERSION(client, ei_connection, new_id, version);
struct eis_callback *callback = eis_callback_new(client, new_id, client->interface_versions.ei_callback);
log_debug(eis_client_get_context(client) , "object %#" PRIx64 ": connection sync done", new_id);
int rc = eis_callback_event_done(callback, 0);
eis_callback_unref(callback);
return brei_result_new_from_neg_errno(rc);
eis_queue_sync_event(client, new_id, version);
return 0;
}
static const struct eis_connection_interface intf_state_new = {
@ -390,18 +460,26 @@ client_dispatch(struct source *source, void *userdata)
_unref_(eis_client) *client = eis_client_ref(userdata);
enum eis_client_state old_state = client->state;
_unref_(brei_result) *result = brei_dispatch(client->brei, source_get_fd(source),
lookup_object, client);
if (result) {
if (old_state != EIS_CLIENT_STATE_REQUESTED_DISCONNECT ||
brei_result_get_reason(result) != BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT)
log_warn(eis_client_get_context(client), "Client error: %s",
brei_result_get_explanation(result));
/* Flush any pending writes, if we have them */
int rc = eis_client_unsent_flush(client);
if (rc < 0 && rc != -EAGAIN) {
log_warn(eis_client_get_context(client),
"Error flushing unsent queue: %s", strerror(-rc));
eis_client_disconnect(client);
} else {
_unref_(brei_result) *result = brei_dispatch(client->brei, source_get_fd(source),
lookup_object, client);
if (result) {
if (old_state != EIS_CLIENT_STATE_REQUESTED_DISCONNECT ||
brei_result_get_reason(result) != BREI_CONNECTION_DISCONNECT_REASON_TRANSPORT)
log_warn(eis_client_get_context(client), "Client error: %s",
brei_result_get_explanation(result));
brei_drain_fd(source_get_fd(source));
eis_client_disconnect_with_reason(client,
brei_result_get_reason(result),
brei_result_get_explanation(result));
brei_drain_fd(source_get_fd(source));
eis_client_disconnect_with_reason(client,
brei_result_get_reason(result),
brei_result_get_explanation(result));
}
}
static const char *client_states[] = {
@ -436,20 +514,21 @@ eis_client_new(struct eis *eis, int fd)
list_init(&client->seats);
list_init(&client->seats_pending);
list_init(&client->proto_objects);
list_init(&client->unsent_queue);
client->interface_versions = (struct eis_client_interface_versions){
.ei_connection = VERSION_V(1),
.ei_handshake = VERSION_V(1),
.ei_callback = VERSION_V(1),
.ei_pingpong = VERSION_V(1),
.ei_seat = VERSION_V(1),
.ei_device = VERSION_V(1),
.ei_seat = VERSION_V(2),
.ei_device = flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY) ? VERSION_V(3) : VERSION_V(2),
.ei_pointer = VERSION_V(1),
.ei_pointer_absolute = VERSION_V(1),
.ei_scroll = VERSION_V(1),
.ei_button = VERSION_V(1),
.ei_keyboard = VERSION_V(1),
.ei_touchscreen = VERSION_V(1),
.ei_touchscreen = VERSION_V(2),
};
struct source *s = source_new(fd, client_dispatch, client);
int rc = sink_add_source(eis->sink, s);

View file

@ -59,6 +59,8 @@ struct eis_client {
struct brei_context *brei;
struct eis_connection *connection;
struct list unsent_queue;
struct list proto_objects; /* struct brei_objects list */
object_id_t next_object_id;
object_id_t last_client_object_id;

View file

@ -43,12 +43,8 @@ eis_connection_destroy(struct eis_connection *connection)
struct eis_client *client = eis_connection_get_client(connection);
eis_client_unregister_object(client, &connection->proto_object);
struct eis_pingpong *cb;
list_for_each_safe(cb, &connection->pending_pingpongs, link) {
list_remove(&cb->link);
free(eis_pingpong_get_user_data(cb));
eis_pingpong_unref(cb);
}
/* Should be a noop */
eis_connection_remove_pending_callbacks(connection);
}
OBJECT_IMPLEMENT_REF(eis_connection);
@ -108,20 +104,71 @@ eis_connection_new(struct eis_client *client)
return connection; /* ref owned by caller */
}
struct pingpong_user_data {
eis_connection_ping_callback_t cb;
void *user_data;
};
void
eis_connection_remove_pending_callbacks(struct eis_connection *connection)
{
struct eis_callback *cb;
list_for_each_safe(cb, &connection->pending_pingpongs, link) {
list_remove(&cb->link);
eis_connection_ping_callback_unref(cb->user_data);
eis_callback_unref(cb);
}
}
static void
ping_pingpong(struct eis_pingpong *pingpong, void *pingpong_data,
uint64_t proto_data)
eis_connection_ping_callback_destroy(struct eis_connection_ping_callback *callback)
{
struct eis_connection *connection = pingpong_data;
if (callback->destroy)
callback->destroy(callback);
}
_cleanup_free_ struct pingpong_user_data *data = eis_pingpong_get_user_data(pingpong);
if (data->cb)
data->cb(connection, data->user_data);
static
OBJECT_IMPLEMENT_CREATE(eis_connection_ping_callback);
OBJECT_IMPLEMENT_REF(eis_connection_ping_callback);
OBJECT_IMPLEMENT_UNREF(eis_connection_ping_callback);
OBJECT_IMPLEMENT_GETTER(eis_connection_ping_callback, user_data, void *);
static
OBJECT_IMPLEMENT_PARENT(eis_connection_ping_callback, eis_connection);
struct eis_connection *
eis_connection_ping_callback_get_connection(struct eis_connection_ping_callback *callback)
{
return eis_connection_ping_callback_parent(callback);
}
struct eis_client *
eis_connection_ping_callback_get_client(struct eis_connection_ping_callback *callback)
{
struct eis_connection *connection = eis_connection_ping_callback_get_connection(callback);
return eis_connection_get_client(connection);
}
struct eis *
eis_connection_ping_callback_get_context(struct eis_connection_ping_callback *callback)
{
struct eis_connection *connection = eis_connection_ping_callback_get_connection(callback);
return eis_connection_get_context(connection);
}
struct eis_connection_ping_callback *
eis_connection_ping_callback_new(struct eis_connection *connection,
eis_connection_ping_callback_done_t done,
eis_connection_ping_callback_destroy_t destroy,
void *user_data)
{
struct eis_connection_ping_callback *callback = eis_connection_ping_callback_create(&connection->object);
callback->done = done;
callback->destroy = destroy;
callback->user_data = user_data;
return callback;
}
static void
pingpong_callback(struct eis_pingpong *pingpong, void *pingpong_data, uint64_t proto_data)
{
_unref_(eis_connection_ping_callback) *data = eis_pingpong_get_user_data(pingpong);
if (data->done)
data->done(data);
/* remove from pending callbacks */
list_remove(&pingpong->link);
@ -129,21 +176,18 @@ ping_pingpong(struct eis_pingpong *pingpong, void *pingpong_data,
}
void
eis_connection_ping(struct eis_connection *connection, eis_connection_ping_callback_t cb,
void *user_data)
eis_connection_ping(struct eis_connection *connection,
struct eis_connection_ping_callback *cb)
{
struct eis_client *client = eis_connection_get_client(connection);
/* This is double-wrapped because we only use this for debugging purposes for
* now. The actual callback calls sync_callback with our connection,
* now. The actual callback calls pingpong_callback with our connection,
* then we extract the user_data on the object and call into the
* cb supplied to this function.
*/
struct eis_pingpong *pingpong = eis_pingpong_new(client, ping_pingpong, connection);
struct pingpong_user_data *data = xalloc(sizeof *data);
data->cb = cb;
data->user_data = user_data;
eis_pingpong_set_user_data(pingpong, data);
struct eis_pingpong *pingpong = eis_pingpong_new(client, pingpong_callback, connection);
eis_pingpong_set_user_data(pingpong, eis_connection_ping_callback_ref(cb));
list_append(&connection->pending_pingpongs, &pingpong->link);
eis_connection_event_ping(connection, eis_pingpong_get_id(pingpong),

View file

@ -25,11 +25,13 @@
#pragma once
#include "util-mem.h"
#include "util-object.h"
#include "brei-shared.h"
struct eis;
struct eis_client;
struct eis_connection_ping_callback;
/* This is a protocol-only object, not exposed in the API */
struct eis_connection {
@ -51,6 +53,8 @@ OBJECT_DECLARE_UNREF(eis_connection);
struct eis_connection *
eis_connection_new(struct eis_client *client);
void
eis_connection_remove_pending_callbacks(struct eis_connection *connection);
/**
* Called when the ei_callback.done request is received after
@ -59,7 +63,40 @@ eis_connection_new(struct eis_client *client);
typedef void (*eis_connection_ping_callback_t)(struct eis_connection *connection,
void *user_data);
/**
* Called when the ei_callback.done event is received after
* an eis_connection_ping() request.
*/
typedef void (*eis_connection_ping_callback_done_t)(struct eis_connection_ping_callback *callback);
/**
* Called for each registered callback when the last reference to it is
* destroyed. This should be used to clean up user_data, if need be.
*
* This function is always called, even if disconnected and the done() function is never called.
*/
typedef void (*eis_connection_ping_callback_destroy_t)(struct eis_connection_ping_callback *callback);
struct eis_connection_ping_callback {
struct object object; /* parent is struct eis */
eis_connection_ping_callback_done_t done;
eis_connection_ping_callback_destroy_t destroy;
void *user_data;
};
struct eis_connection_ping_callback *
eis_connection_ping_callback_new(struct eis_connection *connection,
eis_connection_ping_callback_done_t done,
eis_connection_ping_callback_destroy_t destroy,
void *user_data);
OBJECT_DECLARE_REF(eis_connection_ping_callback);
OBJECT_DECLARE_UNREF(eis_connection_ping_callback);
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, connection, struct eis_connection *);
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, client, struct eis_client *);
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, context, struct eis *);
OBJECT_DECLARE_GETTER(eis_connection_ping_callback, user_data, void*);
DEFINE_UNREF_CLEANUP_FUNC(eis_connection_ping_callback);
void
eis_connection_ping(struct eis_connection *connection,
eis_connection_ping_callback_t callback,
void *user_data);
eis_connection_ping(struct eis_connection *connection, struct eis_connection_ping_callback *callback);

View file

@ -25,6 +25,7 @@
#include "config.h"
#include <errno.h>
#include <stdint.h>
#include "util-macros.h"
#include "util-bits.h"
@ -92,7 +93,7 @@ eis_device_new_keymap(struct eis_device *device,
return keymap;
}
struct eis *
_public_ struct eis *
eis_device_get_context(struct eis_device *device)
{
return eis_client_get_context(eis_device_get_client(device));
@ -193,6 +194,18 @@ eis_device_get_region(struct eis_device *device, size_t index)
return list_nth_entry(struct eis_region, &device->regions, link, index);
}
_public_ struct eis_region *
eis_device_get_region_at(struct eis_device *device, double x, double y)
{
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (eis_region_contains(r, x, y))
return r;
}
return NULL;
}
_public_ struct eis_client *
eis_device_get_client(struct eis_device *device)
{
@ -204,6 +217,9 @@ eis_device_in_region(struct eis_device *device, double x, double y)
{
struct eis_region *r;
if (list_empty(&device->regions))
return true;
list_for_each(r, &device->regions, link) {
if (eis_region_contains(r, x, y))
return true;
@ -242,9 +258,10 @@ client_msg_start_emulating(struct eis_device *device, uint32_t serial, uint32_t
case EIS_DEVICE_STATE_DEAD:
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
case EIS_DEVICE_STATE_NEW:
case EIS_DEVICE_STATE_AWAITING_READY:
case EIS_DEVICE_STATE_EMULATING:
result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a start_emulating event", device->state);
"Invalid device state %u for a start_emulating event", device->state);
break;
case EIS_DEVICE_STATE_RESUMED:
eis_queue_device_start_emulating_event(device, sequence);
@ -271,9 +288,10 @@ client_msg_stop_emulating(struct eis_device *device, uint32_t serial)
case EIS_DEVICE_STATE_DEAD:
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
case EIS_DEVICE_STATE_NEW:
case EIS_DEVICE_STATE_AWAITING_READY:
case EIS_DEVICE_STATE_RESUMED:
result = brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a stop_emulating event", device->state);
"Invalid device state %u for a stop_emulating event", device->state);
break;
case EIS_DEVICE_STATE_EMULATING:
eis_queue_device_stop_emulating_event(device);
@ -306,13 +324,14 @@ maybe_error_on_device_state(struct eis_device *device, const char *event_type)
case EIS_DEVICE_STATE_EMULATING:
return NULL;
case EIS_DEVICE_STATE_NEW:
case EIS_DEVICE_STATE_AWAITING_READY:
case EIS_DEVICE_STATE_CLOSED_BY_CLIENT:
case EIS_DEVICE_STATE_DEAD:
break;
}
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %ud for a %s event", device->state, event_type);
"Invalid device state %u for a %s event", device->state, event_type);
}
static struct brei_result *
@ -330,12 +349,25 @@ client_msg_frame(struct eis_device *device, uint32_t serial, uint64_t time)
return maybe_error_on_device_state(device, "frame");
}
static struct brei_result *
client_msg_ready(struct eis_device *device)
{
if (device->state != EIS_DEVICE_STATE_AWAITING_READY)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Invalid device state %u for a ready event", device->state);
device->state = EIS_DEVICE_STATE_PAUSED;
eis_queue_device_ready_event(device);
return NULL;
}
static const struct eis_device_interface interface = {
.release = client_msg_release,
.start_emulating = client_msg_start_emulating,
.stop_emulating = client_msg_stop_emulating,
.frame = client_msg_frame,
.ready = client_msg_ready,
};
const struct eis_device_interface *
@ -397,6 +429,13 @@ client_msg_button(struct eis_button *button, uint32_t btn, uint32_t state)
"Button event for non-button device");
}
if (btn >= KEY_CNT)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Button event for invalid button %x (KEY_CNT is %#x)", btn, KEY_CNT);
if (!eis_device_update_key_button_state(device, btn, state))
return NULL;
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_pointer_button_event(device, btn, !!state);
return NULL;
@ -567,6 +606,13 @@ client_msg_keyboard_key(struct eis_keyboard *keyboard, uint32_t key, uint32_t st
"Key event for non-keyboard device");
}
if (key >= KEY_CNT)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Key event for invalid key %x (KEY_CNT is %#x)", key, KEY_CNT);
if (!eis_device_update_key_button_state(device, key, state))
return NULL;
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_keyboard_key_event(device, key, !!state);
return NULL;
@ -596,6 +642,35 @@ eis_device_get_keyboard_interface(struct eis_device *device)
return &keyboard_interface;
}
/* Returns true and the position of the touch with the given ID, or
* false and the first position that is available
*/
static bool
find_touch(struct eis_device *device, uint32_t touchid, size_t *index)
{
ssize_t first_available = -1;
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
if (device->touch_state.down[i] != UINT64_MAX) {
if (device->touch_state.down[i] == touchid) {
if (index)
*index = i;
return true;
}
} else if (first_available < 0) {
first_available = i;
}
}
if (index) {
if (first_available < 0)
*index = EIS_MAX_TOUCHES;
else
*index = (size_t)first_available;
}
return false;
}
static struct brei_result *
client_msg_touch_down(struct eis_touchscreen *touchscreen,
uint32_t touchid, float x, float y)
@ -610,6 +685,17 @@ client_msg_touch_down(struct eis_touchscreen *touchscreen,
}
if (device->state == EIS_DEVICE_STATE_EMULATING) {
size_t first_available;
if (find_touch(device, touchid, &first_available)) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch down event for duplicated touch ID");
}
if (first_available >= EIS_MAX_TOUCHES)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_ERROR,
"Too many simultaneous touch events");
device->touch_state.down[first_available] = touchid;
eis_queue_touch_down_event(device, touchid, x, y);
return NULL;
}
@ -631,13 +717,26 @@ client_msg_touch_motion(struct eis_touchscreen *touchscreen,
}
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_touch_motion_event(device, touchid, x, y);
/* Silently ignore motion for non-existing touches */
if (find_touch(device, touchid, NULL))
eis_queue_touch_motion_event(device, touchid, x, y);
return NULL;
}
return maybe_error_on_device_state(device, "touch motion");
}
static bool
release_touch(struct eis_device *device, uint32_t touchid)
{
size_t index;
bool rc = find_touch(device, touchid, &index);
if (rc)
device->touch_state.down[index] = UINT64_MAX;
return rc;
}
static struct brei_result *
client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
{
@ -650,14 +749,46 @@ client_msg_touch_up(struct eis_touchscreen *touchscreen, uint32_t touchid)
"Touch up event for non-touch device");
}
if (device->state == EIS_DEVICE_STATE_EMULATING) {
eis_queue_touch_up_event(device, touchid);
/* End the touch locally even if we're not emulating, but
* silently ignore touch end/cancel for non-existing touches */
if (release_touch(device, touchid)) {
if (device->state == EIS_DEVICE_STATE_EMULATING)
eis_queue_touch_up_event(device, touchid);
return NULL;
}
return maybe_error_on_device_state(device, "touch up");
}
static struct brei_result *
client_msg_touch_cancel(struct eis_touchscreen *touchscreen, uint32_t touchid)
{
struct eis_device *device = eis_touchscreen_get_device(touchscreen);
DISCONNECT_IF_RECEIVER_CONTEXT(device);
if (!eis_device_has_capability(device, EIS_DEVICE_CAP_TOUCH)) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch cancel event for non-touch device");
}
struct eis_client *client = eis_device_get_client(device);
if (client->interface_versions.ei_touchscreen < EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION) {
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Touch cancel event for touchscreen version v1");
}
/* End the touch locally even if we're not emulating, but
* silently ignore touch end/cancel for non-existing touches */
if (release_touch(device, touchid)) {
if (device->state == EIS_DEVICE_STATE_EMULATING)
eis_queue_touch_cancel_event(device, touchid);
return NULL;
}
return maybe_error_on_device_state(device, "touch cancel");
}
static struct brei_result *
client_msg_touchscreen_release(struct eis_touchscreen *touchscreen)
{
@ -673,6 +804,7 @@ static const struct eis_touchscreen_interface touchscreen_interface = {
.down = client_msg_touch_down,
.motion = client_msg_touch_motion,
.up = client_msg_touch_up,
.cancel = client_msg_touch_cancel,
};
const struct eis_touchscreen_interface *
@ -704,6 +836,10 @@ eis_seat_new_device(struct eis_seat *seat)
list_append(&seat->devices, &device->link);
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
device->touch_state.down[i] = UINT64_MAX;
}
return eis_device_ref(device);
}
@ -760,7 +896,7 @@ _public_ void
eis_device_configure_size(struct eis_device *device, uint32_t width, uint32_t height)
{
if (device->type != EIS_DEVICE_TYPE_PHYSICAL) {
log_bug_client(eis_device_get_context(device), "Device type physical requird for size");
log_bug_client(eis_device_get_context(device), "Device type physical required for size");
return;
}
@ -788,7 +924,7 @@ eis_device_add(struct eis_device *device)
"%s: adding device without capabilities", __func__);
}
device->state = EIS_DEVICE_STATE_PAUSED;
device->state = EIS_DEVICE_STATE_AWAITING_READY;
eis_client_register_object(client, &device->proto_object);
eis_seat_event_device(seat, device->proto_object.id, device->proto_object.version);
int rc = eis_device_event_name(device, device->name);
@ -807,6 +943,17 @@ eis_device_add(struct eis_device *device)
if (device->type == EIS_DEVICE_TYPE_VIRTUAL) {
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (r->mapping_id) {
if (client->interface_versions.ei_device >= EIS_DEVICE_EVENT_REGION_MAPPING_ID_SINCE_VERSION) {
rc = eis_device_event_region_mapping_id(device, r->mapping_id);
if (rc < 0)
goto out;
} else {
/* If our client doesn't support mapping_id, drop it */
free(r->mapping_id);
r->mapping_id = NULL;
}
}
rc = eis_device_event_region(device, r->x, r->y, r->width, r->height, r->physical_scale);
if (rc < 0)
goto out;
@ -868,9 +1015,19 @@ eis_device_add(struct eis_device *device)
}
rc = eis_device_event_done(device);
if (rc < 0)
goto out;
struct eis *eis = eis_device_get_context(device);
if (client->interface_versions.ei_device < EIS_DEVICE_REQUEST_READY_SINCE_VERSION) {
device->state = EIS_DEVICE_STATE_PAUSED;
if (flag_is_set(eis->flags, EIS_FLAG_DEVICE_READY))
eis_queue_device_ready_event(device);
}
out:
if (rc < 0) {
log_error(eis_client_get_context(client), "Failed to add device, disconnecting client\n");
log_error(eis_client_get_context(client), "Failed to add device, disconnecting client");
eis_client_disconnect(client);
}
return;
@ -916,6 +1073,12 @@ eis_device_remove(struct eis_device *device)
if (device->state != EIS_DEVICE_STATE_NEW)
eis_device_event_destroyed(device, eis_client_get_next_serial(client));
struct eis_event *event;
list_for_each_safe(event, &device->pending_event_queue, link) {
list_remove(&event->link);
eis_event_unref(event);
}
device->state = EIS_DEVICE_STATE_DEAD;
eis_client_unregister_object(client, &device->proto_object);
list_remove(&device->link);
@ -1018,12 +1181,8 @@ eis_device_pointer_motion_absolute(struct eis_device *device,
if (device->state != EIS_DEVICE_STATE_EMULATING)
return;
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (!eis_region_contains(r, x, y)) {
return;
}
}
if (!eis_device_in_region(device, x, y))
return;
device->send_frame_event = true;
@ -1158,6 +1317,11 @@ eis_device_scroll_discrete(struct eis_device *device, int32_t x, int32_t y)
if (device->state != EIS_DEVICE_STATE_EMULATING)
return;
if (abs(x) == 1 || abs(y) == 1) {
log_bug_client(eis_device_get_context(device),
"%s: suspicious discrete event value 1, did you mean 120?", __func__);
}
eis_device_resume_scrolling(device, x, y);
device->send_frame_event = true;
@ -1235,14 +1399,11 @@ eis_touch_down(struct eis_touch *touch, double x, double y)
return;
}
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (!eis_region_contains(r, x, y)) {
log_bug_client(eis_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
touch->state = TOUCH_IS_UP;
return;
}
if (!eis_device_in_region(device, x, y)) {
log_bug_client(eis_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
touch->state = TOUCH_IS_UP;
return;
}
touch->state = TOUCH_IS_DOWN;
@ -1258,14 +1419,11 @@ eis_touch_motion(struct eis_touch *touch, double x, double y)
return;
struct eis_device *device = eis_touch_get_device(touch);
struct eis_region *r;
list_for_each(r, &device->regions, link) {
if (!eis_region_contains(r, x, y)) {
log_bug_client(eis_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
eis_touch_up(touch);
return;
}
if (!eis_device_in_region(device, x, y)) {
log_bug_client(eis_device_get_context(device),
"%s: touch %u has invalid x/y coordinates", __func__, touch->tracking_id);
eis_touch_up(touch);
return;
}
device->send_frame_event = true;
@ -1290,6 +1448,27 @@ eis_touch_up(struct eis_touch *touch)
eis_touchscreen_event_up(device->touchscreen, touch->tracking_id);
}
_public_ void
eis_touch_cancel(struct eis_touch *touch)
{
struct eis_device *device = eis_touch_get_device(touch);
if (touch->state != TOUCH_IS_DOWN) {
log_bug_client(eis_device_get_context(device),
"%s: touch %u is not currently down", __func__, touch->tracking_id);
return;
}
touch->state = TOUCH_IS_UP;
device->send_frame_event = true;
struct eis_client *client = eis_device_get_client(device);
if (client->interface_versions.ei_touchscreen >= EIS_TOUCHSCREEN_EVENT_CANCEL_SINCE_VERSION)
eis_touchscreen_event_cancel(device->touchscreen, touch->tracking_id);
else
eis_touchscreen_event_up(device->touchscreen, touch->tracking_id);
}
_public_ void
eis_device_frame(struct eis_device *device, uint64_t time)
{
@ -1319,6 +1498,9 @@ eis_device_closed_by_client(struct eis_device *device)
if (!eis_client_is_sender(eis_device_get_client(device)))
eis_queue_device_stop_emulating_event(device);
_fallthrough_;
case EIS_DEVICE_STATE_AWAITING_READY:
eis_queue_device_ready_event(device);
_fallthrough_;
case EIS_DEVICE_STATE_NEW:
case EIS_DEVICE_STATE_PAUSED:
case EIS_DEVICE_STATE_RESUMED:
@ -1333,11 +1515,22 @@ eis_device_pause(struct eis_device *device)
{
struct eis_client *client = eis_device_get_client(device);
if (device->state != EIS_DEVICE_STATE_RESUMED)
return;
switch (device->state) {
case EIS_DEVICE_STATE_RESUMED:
case EIS_DEVICE_STATE_EMULATING:
break;
default:
return;
}
device->state = EIS_DEVICE_STATE_PAUSED;
eis_device_event_paused(device, eis_client_get_next_serial(client));
memset(device->key_button_state.down, 0, sizeof(device->key_button_state.down));
for (size_t i = 0; i < ARRAY_LENGTH(device->touch_state.down); i++) {
device->touch_state.down[i] = UINT64_MAX;
}
}
_public_ void
@ -1345,6 +1538,12 @@ eis_device_resume(struct eis_device *device)
{
struct eis_client *client = eis_device_get_client(device);
if (device->state == EIS_DEVICE_STATE_AWAITING_READY) {
log_bug_client(eis_client_get_context(client),
"Attempting to resume a device before DEVICE_READY");
return;
}
if (device->state != EIS_DEVICE_STATE_PAUSED)
return;

View file

@ -26,12 +26,19 @@
#include "libeis.h"
#include "util-bits.h"
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
#define KEY_MAX 0x2ffU
#define KEY_CNT (KEY_MAX + 1)
#define EIS_MAX_TOUCHES 16
enum eis_device_state {
EIS_DEVICE_STATE_NEW,
EIS_DEVICE_STATE_AWAITING_READY,
EIS_DEVICE_STATE_PAUSED,
EIS_DEVICE_STATE_RESUMED,
EIS_DEVICE_STATE_EMULATING,
@ -74,6 +81,13 @@ struct eis_device {
bool x_is_cancelled, y_is_cancelled;
} scroll_state;
struct {
unsigned char down[NCHARS(KEY_CNT)];
} key_button_state;
struct {
uint64_t down[EIS_MAX_TOUCHES]; /* touch id */
} touch_state;
};
struct eis_touch {
@ -108,7 +122,6 @@ struct eis_xkb_modifiers {
};
OBJECT_DECLARE_GETTER(eis_device, id, object_id_t);
OBJECT_DECLARE_GETTER(eis_device, context, struct eis *);
OBJECT_DECLARE_GETTER(eis_device, proto_object, const struct brei_object *);
OBJECT_DECLARE_GETTER(eis_device, interface, const struct eis_device_interface *);
OBJECT_DECLARE_GETTER(eis_device, pointer_interface, const struct eis_pointer_interface *);
@ -118,6 +131,21 @@ OBJECT_DECLARE_GETTER(eis_device, button_interface, const struct eis_button_inte
OBJECT_DECLARE_GETTER(eis_device, keyboard_interface, const struct eis_keyboard_interface *);
OBJECT_DECLARE_GETTER(eis_device, touchscreen_interface, const struct eis_touchscreen_interface *);
static inline bool
eis_device_update_key_button_state(struct eis_device *device, uint32_t key_btn, uint32_t state)
{
if (state) {
if (bit_is_set(device->key_button_state.down, key_btn))
return false;
set_bit(device->key_button_state.down, key_btn);
} else {
if (!bit_is_set(device->key_button_state.down, key_btn))
return false;
clear_bit(device->key_button_state.down, key_btn);
}
return true;
}
void
eis_device_set_client_keymap(struct eis_device *device,
enum eis_keymap_type type,

View file

@ -41,9 +41,11 @@ eis_event_destroy(struct eis_event *event)
case EIS_EVENT_CLIENT_CONNECT:
case EIS_EVENT_CLIENT_DISCONNECT:
case EIS_EVENT_SEAT_BIND:
case EIS_EVENT_SEAT_DEVICE_REQUESTED:
case EIS_EVENT_DEVICE_CLOSED:
case EIS_EVENT_DEVICE_START_EMULATING:
case EIS_EVENT_DEVICE_STOP_EMULATING:
case EIS_EVENT_DEVICE_READY:
case EIS_EVENT_BUTTON_BUTTON:
case EIS_EVENT_POINTER_MOTION:
case EIS_EVENT_POINTER_MOTION_ABSOLUTE:
@ -58,6 +60,15 @@ eis_event_destroy(struct eis_event *event)
case EIS_EVENT_FRAME:
handled = true;
break;
case EIS_EVENT_PONG:
eis_ping_unref(event->pong.ping);
handled = true;
break;
case EIS_EVENT_SYNC:
eis_sync_event_send_done(event);
eis_callback_unref(event->sync.callback);
handled = true;
break;
}
if (!handled)
@ -109,9 +120,8 @@ eis_event_new_for_device(struct eis_device *device)
return e;
}
/* this one is not public */
_public_
OBJECT_IMPLEMENT_REF(eis_event);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_event);
_public_
@ -157,7 +167,8 @@ check_event_type(struct eis_event *event,
if (!rc)
log_bug_client(eis_event_get_context(event),
"Invalid event type %u passed to %s()",
"Invalid event type %s (%u) passed to %s()",
eis_event_type_to_string(type),
type, function_name);
return rc;
@ -187,10 +198,21 @@ eis_event_get_time(struct eis_event *event)
return event->timestamp;
}
_public_ struct eis_ping *
eis_event_pong_get_ping(struct eis_event *event)
{
require_event_type(event, NULL, EIS_EVENT_PONG);
return event->pong.ping;
}
_public_ bool
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap)
{
require_event_type(event, false, EIS_EVENT_SEAT_BIND);
require_event_type(event,
false,
EIS_EVENT_SEAT_BIND,
EIS_EVENT_SEAT_DEVICE_REQUESTED);
switch (cap) {
case EIS_DEVICE_CAP_POINTER:
@ -404,3 +426,11 @@ eis_event_touch_get_y(struct eis_event *event)
return event->touch.y;
}
_public_ bool
eis_event_touch_get_is_cancel(struct eis_event *event)
{
require_event_type(event, false, EIS_EVENT_TOUCH_UP);
return event->touch.is_cancel;
}

View file

@ -58,10 +58,17 @@ struct eis_event {
struct {
uint32_t touchid;
double x, y;
bool is_cancel;
} touch;
struct {
uint32_t sequence;
} start_emulating;
struct {
struct eis_ping *ping;
} pong;
struct {
struct eis_callback *callback;
} sync;
};
};
@ -76,6 +83,3 @@ eis_event_new_for_device(struct eis_device *device);
struct eis *
eis_event_get_context(struct eis_event *event);
struct eis_event*
eis_event_ref(struct eis_event *event);

View file

@ -76,9 +76,9 @@ eis_handshake_get_id(struct eis_handshake *setup)
}
static void
pong(struct eis_connection *connection, void *user_data)
on_pong(struct eis_connection_ping_callback *callback)
{
struct eis_client *client = eis_connection_get_client(connection);
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
eis_queue_connect_event(client);
}
@ -118,10 +118,27 @@ client_msg_finish(struct eis_handshake *setup)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL,
"Missing versions for required interfaces");
/* ei_callback needs a client-created object, so tell the client
* about our version */
eis_handshake_event_interface_version(setup, EIS_CALLBACK_INTERFACE_NAME,
setup->client_versions.ei_callback);
/* ei_callback needs a client-created object, so we must tell the client
* about our version. But for convenience and to make sure this all works
* send all our versions down the wire */
#define SEND_INTERFACE_VERSION(upper_name_, lower_name_) \
if (setup->client_versions.lower_name_) \
eis_handshake_event_interface_version(setup, upper_name_ ##_INTERFACE_NAME, \
setup->client_versions.lower_name_); \
SEND_INTERFACE_VERSION(EIS_CALLBACK, ei_callback);
SEND_INTERFACE_VERSION(EIS_CONNECTION, ei_connection);
SEND_INTERFACE_VERSION(EIS_PINGPONG, ei_pingpong);
SEND_INTERFACE_VERSION(EIS_SEAT, ei_seat);
SEND_INTERFACE_VERSION(EIS_DEVICE, ei_device);
SEND_INTERFACE_VERSION(EIS_POINTER, ei_pointer);
SEND_INTERFACE_VERSION(EIS_POINTER_ABSOLUTE, ei_pointer_absolute);
SEND_INTERFACE_VERSION(EIS_BUTTON, ei_button);
SEND_INTERFACE_VERSION(EIS_SCROLL, ei_scroll);
SEND_INTERFACE_VERSION(EIS_KEYBOARD, ei_keyboard);
SEND_INTERFACE_VERSION(EIS_TOUCHSCREEN, ei_touchscreen);
#undef SEND_INTERFACE_VERSION
eis_client_setup_done(client, setup->name, setup->is_sender, &setup->client_versions);
@ -137,9 +154,13 @@ client_msg_finish(struct eis_handshake *setup)
setup->client_versions.ei_device == 0) {
eis_client_disconnect(client);
} else {
_unref_(eis_connection_ping_callback) *cb = eis_connection_ping_callback_new(client->connection,
on_pong,
NULL,
NULL);
/* Force a ping/pong. This isn't necessary but it doesn't hurt much here
* and it ensures that any client implementation doesn't have that part missing */
eis_connection_ping(client->connection, pong, NULL);
eis_connection_ping(client->connection, cb);
}
client->setup = eis_handshake_unref(setup);
@ -256,7 +277,7 @@ eis_handshake_get_interface(struct eis_handshake *setup) {
struct eis_handshake *
eis_handshake_new(struct eis_client *client,
const struct eis_client_interface_versions *versions)
const struct eis_client_interface_versions *versions)
{
struct eis_handshake *setup = eis_handshake_create(&client->object);

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN keyboard WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN keyboard WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

124
src/libeis-ping.c Normal file
View file

@ -0,0 +1,124 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2024 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include "util-mem.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-list.h"
#include "brei-shared.h"
#include "libeis-private.h"
#include "libeis-connection.h"
struct eis_ping {
struct object object;
uint64_t id;
void *user_data;
struct eis_client *client;
bool is_pending;
bool is_done;
};
static void
eis_ping_destroy(struct eis_ping *ping)
{
if (!ping->is_pending)
eis_client_unref(ping->client);
}
static
OBJECT_IMPLEMENT_CREATE(eis_ping);
_public_
OBJECT_IMPLEMENT_REF(eis_ping);
_public_
OBJECT_IMPLEMENT_UNREF_CLEANUP(eis_ping);
_public_
OBJECT_IMPLEMENT_GETTER(eis_ping, id, uint64_t);
_public_
OBJECT_IMPLEMENT_GETTER(eis_ping, user_data, void*);
_public_
OBJECT_IMPLEMENT_SETTER(eis_ping, user_data, void*);
static
OBJECT_IMPLEMENT_GETTER(eis_ping, client, struct eis_client*);
_public_ struct eis_ping *
eis_client_new_ping(struct eis_client *client)
{
static uint64_t id = 0;
struct eis_ping *ping = eis_ping_create(NULL);
ping->id = ++id;
/* Ref our context while it's pending (i.e. only the caller has the ref).
* Once it's pending we no longer need the ref.
*/
ping->client = eis_client_ref(client);
ping->is_pending = false;
ping->is_done = false;
return ping;
}
static void
on_pong(struct eis_connection_ping_callback *callback)
{
struct eis_ping *ping = eis_connection_ping_callback_get_user_data(callback);
ping->is_done = true;
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
eis_queue_pong_event(client, ping);
/* eis_ping ref is removed in on_destroy */
}
static void
on_destroy(struct eis_connection_ping_callback *callback)
{
/* This is only called if we never receisved a pong */
_unref_(eis_ping) *ping = eis_connection_ping_callback_get_user_data(callback);
/* We never got a pong because we got disconnected. Queue a fake pong event */
if (!ping->is_done) {
struct eis_client *client = eis_connection_ping_callback_get_client(callback);
eis_queue_pong_event(client, ping);
}
}
_public_ void
eis_ping(struct eis_ping *ping)
{
struct eis_client *client = eis_ping_get_client(ping);
eis_client_unref(client);
ping->client = client;
ping->is_pending = true;
_unref_(eis_connection_ping_callback) *cb = eis_connection_ping_callback_new(client->connection,
on_pong,
on_destroy,
eis_ping_ref(ping));
eis_connection_ping(client->connection, cb);
}

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer_absolute WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN pointer WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -63,6 +63,8 @@ struct eis {
struct sink *sink;
struct list clients;
uint32_t flags;
struct eis_backend_interface backend_interface;
void *backend;
struct list event_queue;
@ -72,6 +74,8 @@ struct eis {
enum eis_log_priority priority;
} log;
eis_clock_now_func clock_now;
const struct eis_proto_requests *requests;
};
@ -90,12 +94,24 @@ eis_queue_connect_event(struct eis_client *client);
void
eis_queue_disconnect_event(struct eis_client *client);
void
eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping);
void
eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t version);
void
eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities);
void
eis_queue_seat_device_requested_event(struct eis_seat *seat, uint32_t capabilities);
void
eis_queue_device_closed_event(struct eis_device *device);
void
eis_queue_device_ready_event(struct eis_device *device);
void
eis_queue_frame_event(struct eis_device *device, uint64_t time);
@ -144,6 +160,12 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid,
void
eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid);
void
eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid);
void
eis_sync_event_send_done(struct eis_event *e);
_printf_(6, 7) void
eis_log_msg(struct eis *eis,
enum eis_log_priority priority,
@ -169,3 +191,14 @@ eis_log_msg_va(struct eis *eis,
eis_log_msg((T_), EIS_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪳 libeis bug: " __VA_ARGS__)
#define log_bug_client(T_, ...) \
eis_log_msg((T_), EIS_LOG_PRIORITY_ERROR, __FILE__, __LINE__, __func__, "🪲 Bug: " __VA_ARGS__)
#define DISCONNECT_IF_INVALID_VERSION(eis_client_, intf_, id_, version_) do { \
struct eis_client *_client = (eis_client_); \
uint32_t _version = (version_); \
uint64_t _id = (id_); \
if (_client->interface_versions.intf_ < _version) { \
struct eis *_eis = eis_client_get_context(_client); \
log_bug(_eis, "Received invalid version %u for object id %#" PRIx64 ". Disconnecting", _version, _id); \
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_PROTOCOL, "Received invalid version %u for object id %#" PRIx64 ".", _version, _id); \
} \
} while(0)

View file

@ -24,11 +24,13 @@
#include "config.h"
#include "util-strings.h"
#include "libeis-private.h"
static void
eis_region_destroy(struct eis_region *region)
{
free(region->mapping_id);
list_remove(&region->link);
if (!region->added_to_device)
eis_device_unref(region->device);
@ -50,6 +52,10 @@ _public_
OBJECT_IMPLEMENT_GETTER(eis_region, width, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(eis_region, height, uint32_t);
_public_
OBJECT_IMPLEMENT_GETTER(eis_region, physical_scale, double);
_public_
OBJECT_IMPLEMENT_GETTER(eis_region, mapping_id, const char *);
static
OBJECT_IMPLEMENT_CREATE(eis_region);
@ -107,6 +113,22 @@ eis_region_set_physical_scale(struct eis_region *region, double scale)
region->physical_scale = scale;
}
_public_ void
eis_region_set_mapping_id(struct eis_region *region, const char *mapping_id)
{
if (region->added_to_device)
return;
if (mapping_id == NULL) {
struct eis_device *device = region->device;
log_bug_client(eis_device_get_context(device),
"%s: a region's mapping_id must not be NULL", __func__);
return;
}
region->mapping_id = xstrdup(mapping_id);
}
_public_ void
eis_region_add(struct eis_region *region)
{
@ -130,9 +152,54 @@ eis_region_add(struct eis_region *region)
eis_device_unref(region->device);
}
bool
_public_ bool
eis_region_contains(struct eis_region *r, double x, double y)
{
return (x >= r->x && x < r->x + r->width &&
y >= r->y && y < r->y + r->height);
}
#ifdef _enable_tests_
#include "util-munit.h"
MUNIT_TEST(test_region_setters)
{
struct eis_region r = {0};
eis_region_set_size(&r, 1, 2);
eis_region_set_offset(&r, 3, 4);
eis_region_set_physical_scale(&r, 5.6);
munit_assert_int(eis_region_get_width(&r), ==, 1);
munit_assert_int(eis_region_get_height(&r), ==, 2);
munit_assert_int(eis_region_get_x(&r), ==, 3);
munit_assert_int(eis_region_get_y(&r), ==, 4);
munit_assert_double(eis_region_get_physical_scale(&r), ==, 5.6);
return MUNIT_OK;
}
MUNIT_TEST(test_region_contains)
{
struct eis_region r = {0};
eis_region_set_size(&r, 100, 200);
eis_region_set_offset(&r, 300, 400);
munit_assert_true(eis_region_contains(&r, 300, 400));
munit_assert_true(eis_region_contains(&r, 399.9, 599.9));
munit_assert_false(eis_region_contains(&r, 299.9, 400));
munit_assert_false(eis_region_contains(&r, 300, 399.9));
munit_assert_false(eis_region_contains(&r, 400.1, 400));
munit_assert_false(eis_region_contains(&r, 400, 399.9));
munit_assert_false(eis_region_contains(&r, 299.9, 599.9));
munit_assert_false(eis_region_contains(&r, 300, 600.1));
munit_assert_false(eis_region_contains(&r, 400, 599.9));
munit_assert_false(eis_region_contains(&r, 399, 600));
return MUNIT_OK;
}
#endif

View file

@ -38,7 +38,5 @@ struct eis_region {
uint32_t x, y;
uint32_t width, height;
double physical_scale;
char *mapping_id;
};
bool
eis_region_contains(struct eis_region *r, double x, double y);

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN scroll WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN scroll WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -86,15 +86,11 @@ client_msg_release(struct eis_seat *seat)
return NULL;
}
static struct brei_result *
client_msg_bind(struct eis_seat *seat, uint64_t caps)
static inline uint32_t
convert_capabilities(uint64_t caps)
{
uint32_t capabilities = 0;
if (caps & ~seat->capabilities.proto_mask)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
"Invalid capabilities %#" PRIx64, caps);
/* Convert from protocol capabilities to our C API capabilities */
if (caps & bit(EIS_POINTER_INTERFACE_INDEX))
capabilities |= EIS_DEVICE_CAP_POINTER;
@ -109,14 +105,41 @@ client_msg_bind(struct eis_seat *seat, uint64_t caps)
if (caps & bit(EIS_SCROLL_INTERFACE_INDEX))
capabilities |= EIS_DEVICE_CAP_SCROLL;
return capabilities;
}
static struct brei_result *
client_msg_bind(struct eis_seat *seat, uint64_t caps)
{
if (caps & ~seat->capabilities.proto_mask)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
"Invalid capabilities %#" PRIx64, caps);
uint32_t capabilities = convert_capabilities(caps);
eis_seat_bind(seat, capabilities);
return NULL;
}
static struct brei_result *
client_msg_request_device(struct eis_seat *seat, uint64_t caps)
{
if (caps == 0 || caps & ~seat->capabilities.proto_mask)
return brei_result_new(EIS_CONNECTION_DISCONNECT_REASON_VALUE,
"Invalid capabilities %#" PRIx64, caps);
uint32_t capabilities = convert_capabilities(caps);
eis_queue_seat_device_requested_event(seat, capabilities);
return NULL;
}
static const struct eis_seat_interface interface = {
.release = client_msg_release,
.bind = client_msg_bind,
.request_device = client_msg_request_device,
};
const struct eis_seat_interface *
@ -125,6 +148,12 @@ eis_seat_get_interface(struct eis_seat *seat)
return &interface;
}
_public_ struct eis *
eis_seat_get_context(struct eis_seat *seat)
{
return eis_client_get_context(eis_seat_get_client(seat));
}
_public_ struct eis_seat *
eis_client_new_seat(struct eis_client *client, const char *name)
{
@ -184,14 +213,16 @@ eis_seat_add(struct eis_seat *seat)
mask_add(seat->capabilities.proto_mask, mask);
}
if (seat->capabilities.c_mask & (EIS_DEVICE_CAP_POINTER|EIS_DEVICE_CAP_POINTER_ABSOLUTE) &&
(client->interface_versions.ei_pointer > 0 || client->interface_versions.ei_pointer_absolute > 0)) {
if (seat->capabilities.c_mask & EIS_DEVICE_CAP_SCROLL &&
client->interface_versions.ei_scroll > 0) {
uint64_t mask = bit(EIS_SCROLL_INTERFACE_INDEX);
eis_seat_event_capability(seat, mask,
EIS_SCROLL_INTERFACE_NAME);
mask_add(seat->capabilities.proto_mask, mask);
mask = bit(EIS_BUTTON_INTERFACE_INDEX);
}
if (seat->capabilities.c_mask & EIS_DEVICE_CAP_BUTTON &&
client->interface_versions.ei_button > 0) {
uint64_t mask = bit(EIS_BUTTON_INTERFACE_INDEX);
eis_seat_event_capability(seat, mask,
EIS_BUTTON_INTERFACE_NAME);
mask_add(seat->capabilities.proto_mask, mask);
@ -235,9 +266,12 @@ eis_seat_bind(struct eis_seat *seat, uint32_t caps)
}
caps &= seat->capabilities.c_mask;
seat->state = EIS_SEAT_STATE_BOUND;
eis_queue_seat_bind_event(seat, caps);
uint32_t old_caps = seat->capabilities.bound;
seat->capabilities.bound = caps;
if (old_caps != caps)
eis_queue_seat_bind_event(seat, caps);
}
void

View file

@ -51,6 +51,8 @@ struct eis_seat {
struct {
uint32_t c_mask; /* this is the C API bitmask */
uint64_t proto_mask; /* the protocol mask */
uint32_t bound; /* C API bitmask */
} capabilities;
struct list devices;

View file

@ -41,6 +41,28 @@
#include "libeis.h"
#include "libeis-private.h"
#if defined(__DragonFly__) || defined(__FreeBSD__)
#include <sys/ucred.h>
#define CRED_T xucred
#define CRED_LVL SOL_LOCAL
#define CRED_OPT LOCAL_PEERCRED
#define CRED_PID cr_pid
#elif defined(__NetBSD__)
#define CRED_T unpcbid
#define CRED_LVL SOL_LOCAL
#define CRED_OPT LOCAL_PEEREID
#define CRED_PID unp_pid
#else
#if defined(__OpenBSD__)
#define CRED_T sockpeercred
#else
#define CRED_T ucred
#endif
#define CRED_LVL SOL_SOCKET
#define CRED_OPT SO_PEERCRED
#define CRED_PID pid
#endif
struct eis_socket {
struct object object;
struct source *listener;
@ -180,3 +202,20 @@ eis_setup_backend_socket(struct eis *eis, const char *socketpath)
return rc;
}
_public_ pid_t
eis_backend_socket_get_client_pid(struct eis_client* client)
{
struct eis *eis = eis_client_get_context(client);
if (eis->backend_interface.destroy != interface_socket_destroy) {
log_bug_client(eis, "Not a socket backend");
return -EINVAL;
}
struct CRED_T ucred;
socklen_t len = sizeof(ucred);
int rc = getsockopt(source_get_fd(client->source), CRED_LVL, CRED_OPT, &ucred, &len);
if (rc < 0) {
return -errno;
}
return ucred.CRED_PID;
}

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -18,7 +18,7 @@
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN touchscreen WITH THE SOFTWARE OR THE USE OR OTHER
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

View file

@ -29,6 +29,7 @@
#include <stdio.h>
#include <unistd.h>
#include "util-bits.h"
#include "util-macros.h"
#include "util-object.h"
#include "util-sources.h"
@ -44,6 +45,9 @@ _Static_assert(sizeof(enum eis_keymap_type) == sizeof(int), "Invalid enum size")
_Static_assert(sizeof(enum eis_event_type) == sizeof(int), "Invalid enum size");
_Static_assert(sizeof(enum eis_log_priority) == sizeof(int), "Invalid enum size");
DEFINE_UNREF_CLEANUP_FUNC(brei_result);
DEFINE_UNREF_CLEANUP_FUNC(eis_callback);
static void
eis_destroy(struct eis *eis)
{
@ -92,6 +96,22 @@ eis_new(void *user_data)
return steal(&eis);
}
_public_ int
eis_set_flag(struct eis *eis, enum eis_flag flag)
{
if (eis->backend) {
log_bug_client(eis, "%s must be called before setting up a backend", __func__);
return -EBUSY;
}
if (flag > EIS_FLAG_DEVICE_READY)
return -EINVAL;
flag_set(eis->flags, flag);
return 0;
}
_public_ int
eis_get_fd(struct eis *eis)
{
@ -117,7 +137,11 @@ eis_event_type_to_string(enum eis_event_type type)
CASE_RETURN_STRING(EIS_EVENT_CLIENT_CONNECT);
CASE_RETURN_STRING(EIS_EVENT_CLIENT_DISCONNECT);
CASE_RETURN_STRING(EIS_EVENT_SEAT_BIND);
CASE_RETURN_STRING(EIS_EVENT_SEAT_DEVICE_REQUESTED);
CASE_RETURN_STRING(EIS_EVENT_DEVICE_CLOSED);
CASE_RETURN_STRING(EIS_EVENT_DEVICE_READY);
CASE_RETURN_STRING(EIS_EVENT_PONG);
CASE_RETURN_STRING(EIS_EVENT_SYNC);
CASE_RETURN_STRING(EIS_EVENT_DEVICE_START_EMULATING);
CASE_RETURN_STRING(EIS_EVENT_DEVICE_STOP_EMULATING);
CASE_RETURN_STRING(EIS_EVENT_POINTER_MOTION);
@ -193,8 +217,10 @@ eis_queue_event(struct eis_event *event)
break;
case EIS_EVENT_FRAME: {
/* silently discard empty frames */
if (list_empty(&device->pending_event_queue))
if (list_empty(&device->pending_event_queue)) {
eis_event_unref(event);
return;
}
struct eis_event *pending;
list_for_each_safe(pending, &device->pending_event_queue, link) {
@ -243,6 +269,16 @@ eis_queue_seat_bind_event(struct eis_seat *seat, uint32_t capabilities)
eis_queue_event(e);
}
void
eis_queue_seat_device_requested_event(struct eis_seat *seat,
uint32_t capabilities)
{
struct eis_event *e = eis_event_new_for_seat(seat);
e->type = EIS_EVENT_SEAT_DEVICE_REQUESTED;
e->bind.capabilities = capabilities;
eis_queue_event(e);
}
void
eis_queue_device_closed_event(struct eis_device *device)
{
@ -251,6 +287,50 @@ eis_queue_device_closed_event(struct eis_device *device)
eis_queue_event(e);
}
void
eis_queue_device_ready_event(struct eis_device *device)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_DEVICE_READY;
eis_queue_event(e);
}
void
eis_sync_event_send_done(struct eis_event *e)
{
_unref_(eis_callback) *callback = steal(&e->sync.callback);
log_debug(eis_event_get_context(e) ,
"object %#" PRIx64 ": connection sync done",
eis_callback_get_id(callback));
int rc = eis_callback_event_done(callback, 0);
_unref_(brei_result) *result = brei_result_new_from_neg_errno(rc);
if (result) {
struct eis_client *client = eis_event_get_client(e);
eis_client_disconnect_with_reason(client,
brei_result_get_reason(result),
brei_result_get_explanation(result));
}
}
void
eis_queue_sync_event(struct eis_client *client, object_id_t newid, uint32_t version)
{
struct eis_event *e = eis_event_new_for_client(client);
e->type = EIS_EVENT_SYNC;
e->sync.callback = eis_callback_new(client, newid, version);
eis_queue_event(e);
}
void
eis_queue_pong_event(struct eis_client *client, struct eis_ping *ping)
{
struct eis_event *e = eis_event_new_for_client(client);
e->type = EIS_EVENT_PONG;
e->pong.ping = eis_ping_ref(ping);
eis_queue_event(e);
}
void
eis_queue_frame_event(struct eis_device *device, uint64_t time)
{
@ -369,7 +449,7 @@ eis_queue_touch_down_event(struct eis_device *device, uint32_t touchid,
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TOUCH_DOWN;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.x = x;
e->touch.y = y;
eis_queue_event(e);
@ -381,7 +461,7 @@ eis_queue_touch_motion_event(struct eis_device *device, uint32_t touchid,
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TOUCH_MOTION;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.x = x;
e->touch.y = y;
eis_queue_event(e);
@ -392,7 +472,18 @@ eis_queue_touch_up_event(struct eis_device *device, uint32_t touchid)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TOUCH_UP;
e->touch.touchid = touchid,
e->touch.touchid = touchid;
e->touch.is_cancel = false;
eis_queue_event(e);
}
void
eis_queue_touch_cancel_event(struct eis_device *device, uint32_t touchid)
{
struct eis_event *e = eis_event_new_for_device(device);
e->type = EIS_EVENT_TOUCH_UP;
e->touch.touchid = touchid;
e->touch.is_cancel = true;
eis_queue_event(e);
}
@ -424,18 +515,30 @@ eis_add_client(struct eis *eis, struct eis_client *client)
list_append(&eis->clients, &client->link);
}
_public_ void
eis_clock_set_now_func(struct eis *eis, eis_clock_now_func func)
{
eis->clock_now = func;
}
_public_ uint64_t
eis_now(struct eis *eis)
{
uint64_t ts = 0;
int rc = now(&ts);
if (rc < 0) {
/* We should probably disconnect here but the chances of this
* happening are so slim it's not worth worrying about. Plus,
* if this fails we're likely to be inside ei_device_frame()
* so we should flush a frame event before disconnecting and... */
log_error(eis, "clock_gettime failed: %s", strerror(-rc));
if (eis->clock_now)
ts = eis->clock_now(eis);
else {
int rc = now(&ts);
if (rc < 0) {
/* We should probably disconnect here but the chances of this
* happening are so slim it's not worth worrying about. Plus,
* if this fails we're likely to be inside ei_device_frame()
* so we should flush a frame event before disconnecting and... */
log_error(eis, "clock_gettime failed: %s", strerror(-rc));
}
}
return ts;
}

View file

@ -32,6 +32,7 @@ extern "C" {
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
/**
* @defgroup libeis 🍦 EIS - The server API
@ -147,6 +148,16 @@ struct eis_keymap;
*/
struct eis_touch;
/**
* @struct eis_ping
*
* A callback struct returned by eis_new_ping() to handle
* roundtrips to the client.
*
* @see eis_new_ping
*/
struct eis_ping;
/**
* @struct eis_region
* @ingroup libeis-region
@ -227,13 +238,19 @@ enum eis_keymap_type {
/**
* @enum eis_event_type
*
* This enum is not exhaustive, future versions of this library may add
* new event types.
*
* Unknown events must be released by the caller with eis_event_unref(),
* see eis_get_event().
*/
enum eis_event_type {
/**
* A client has connected. This is the first event from any new
* client.
* The server is expected to either call eis_event_client_connect() or
* eis_event_client_disconnect().
* The server is expected to either call eis_client_connect() or
* eis_client_disconnect().
*/
EIS_EVENT_CLIENT_CONNECT = 1,
/**
@ -255,6 +272,47 @@ enum eis_event_type {
*/
EIS_EVENT_DEVICE_CLOSED,
/**
* The client has completed configuration of the device (if any) and
* the caller may call eis_device_resume().
*
* libei guarantees that this event even if the client
* does not support the underlying protocol.
*
* This event is only generated if the caller has called
* eis_set_flag() with the @ref EIS_FLAG_DEVICE_READY flag.
*
* @since 1.6
*/
EIS_EVENT_DEVICE_READY,
/**
* The client requested a device with the given capabilities
* on this seat.
*
* @since 1.6
*/
EIS_EVENT_SEAT_DEVICE_REQUESTED,
/**
* Returned in response to eis_ping().
*/
EIS_EVENT_PONG = 90,
/**
* This event represents a synchronization request (ping) from the client
* implementation. The corresponding reply (pong) will be sent when it
* is unref'd. It has no other API.
*
* The caller must ensure that any state changes triggered by messages
* received prior to this event have been resolved and communicated to the
* client prior to calling eis_event_unref(). E.g., if the preceding frame
* included a key event that caused shift to become logically depressed, the
* caller should ensure that the updated modifier state has been sent before
* calling unref.
*/
EIS_EVENT_SYNC,
/**
* "Hardware" frame event. This event **must** be sent by the client
* and notifies the server that the previous set of events belong to
@ -300,16 +358,16 @@ enum eis_event_type {
* An absolute motion event with absolute position within the device's
* regions or size, depending on the device type.
*/
EIS_EVENT_POINTER_MOTION_ABSOLUTE,
EIS_EVENT_POINTER_MOTION_ABSOLUTE = 400,
/**
* A button press or release event
*/
EIS_EVENT_BUTTON_BUTTON,
EIS_EVENT_BUTTON_BUTTON = 500,
/**
* A vertical and/or horizontal scroll event with logical-pixels
* or mm precision, depending on the device type.
*/
EIS_EVENT_SCROLL_DELTA,
EIS_EVENT_SCROLL_DELTA = 600,
/**
* An ongoing scroll sequence stopped.
*/
@ -327,7 +385,7 @@ enum eis_event_type {
/**
* A key press or release event
*/
EIS_EVENT_KEYBOARD_KEY = 400,
EIS_EVENT_KEYBOARD_KEY = 700,
/**
* Event for a single touch set down on the device's logical surface.
@ -335,7 +393,7 @@ enum eis_event_type {
* between. On multitouch capable devices, several touchs eqeuences
* may be active at any time.
*/
EIS_EVENT_TOUCH_DOWN = 500,
EIS_EVENT_TOUCH_DOWN = 800,
/**
* Event for a single touch released from the device's logical
* surface.
@ -362,6 +420,53 @@ eis_event_type_to_string(enum eis_event_type);
struct eis *
eis_new(void *user_data);
/**
* Context flags to enable EIS-specific behaviors.
*
* These flags must be set to take advantage of newer versions
* of the ei protocol. See the documentation for each flag
* for details.
*
* @see eis_set_flag
*
* @since 1.6
*/
enum eis_flag {
/**
* If set, libeis will announce ei_device protocol
* version 3 or later. A compatible client will send
* ei_device.ready in response to ei_device.done. See
* the protocol documentation for details.
*
* If set, after eis_device_add() an EIS implementation must wait
* until the @ref EIS_EVENT_DEVICE_READY has been received for
* that device before calling eis_device_resume().
*/
EIS_FLAG_DEVICE_READY = 1,
};
/**
* Change the behavior of the context according to the
* given flag. See @ref eis_flag for documentation on
* each indidividual flag.
*
* This function must be called before any of eis_setup_backend_fd()
* or eis_setup_backend_socket().
*
* This function may be called multiple times with different flags.
* Some flags may be mutually exclusive, consult the documentation
* on each individual flag for details.
*
* @param eis The EIS context
* @param flag **One** flag (not a bitmask) to set on this context
*
* @return 0 on success or a negative errno on failure
*
* @since 1.6
*/
int
eis_set_flag(struct eis *eis, enum eis_flag flag);
/**
* @ingroup libeis-log
*/
@ -405,7 +510,7 @@ eis_log_context_get_func(struct eis_log_context *ctx);
* messages with a log level equal or greater than than the one set in
* eis_log_set_priority().
*
* @param eis The EIs context
* @param eis The EIS context
* @param priority The log priority
* @param message The log message as a null-terminated string
* @param ctx A log message context for this message
@ -439,6 +544,24 @@ eis_log_set_priority(struct eis *eis, enum eis_log_priority priority);
enum eis_log_priority
eis_log_get_priority(const struct eis *eis);
/**
* Optional override function for eis_now().
*
* By default eis_now() returns the current timestamp in CLOCK_MONOTONIC. This
* may be overridden by a caller to provide a different timestamp.
*
* There is rarely a need to override this function. It exists for the libeis-internal test suite.
*/
typedef uint64_t (*eis_clock_now_func)(struct eis *eis);
/**
* Override the function that returns the current time eis_now().
*
* There is rarely a need to override this function. It exists for the libeis-internal test suite.
*/
void
eis_clock_set_now_func(struct eis *, eis_clock_now_func func);
struct eis *
eis_ref(struct eis *eis);
@ -452,14 +575,12 @@ void
eis_set_user_data(struct eis *eis, void *user_data);
/**
* Initialize the context with a UNIX socket name.
* If the path does not start with / it is relative to $XDG_RUNTIME_DIR.
*/
int
eis_setup_backend_socket(struct eis *ctx, const char *path);
/**
* Initialize the context that can take pre-configured sockets.
* Initialize the context that can take pre-configured socket file descriptors,
* see eis_backend_fd_add_client().
*
* This is the preferred backend to use for EIS implementations as it keeps
* the file descriptors private for each client and is not subject to race
* conditions or unauthorized clients attempting to connect.
*/
int
eis_setup_backend_fd(struct eis *ctx);
@ -472,6 +593,30 @@ eis_setup_backend_fd(struct eis *ctx);
int
eis_backend_fd_add_client(struct eis *ctx);
/**
* Initialize the context with a UNIX socket name.
* If the path does not start with / it is relative to $XDG_RUNTIME_DIR.
*
* The socket will be accessible by any client with permissions to do so and
* it is up to the EIS implementation to authenticate clients. In almost
* all cases, the EIS implementation should use eis_setup_backend_fd() instead.
* This backend is primarily useful for testing and debugging.
*/
int
eis_setup_backend_socket(struct eis *ctx, const char *path);
/**
* Return the pid of the client.
* The pid is retrieved via SO_PEERCRED.
* This function should only be called if the context was set up via
* eis_setup_backend_socket.
*
* @return The PID of the client or a negative errno on failure
*/
pid_t
eis_backend_socket_get_client_pid(struct eis_client *client);
/**
* libeis keeps a single file descriptor for all events. This fd should be
* monitored for events by the caller's mainloop, e.g. using select(). When
@ -493,12 +638,84 @@ eis_get_fd(struct eis *eis);
void
eis_dispatch(struct eis *eis);
/**
* Return a unique, increasing id for this struct. This ID is assigned at
* struct creation and constant for the lifetime of the struct. The ID
* does not exist at the protocol level.
*
* This is a convenience API to make it easier to put this struct into
* e.g. a hashtable without having to use the pointer value itself.
*
* The ID increases by an unspecified amount.
*
* @since 1.4
*/
uint64_t
eis_ping_get_id(struct eis_ping *ping);
/**
* Increase the refcount of this struct by one. Use eis_ping_unref() to decrease
* the refcount.
*
* @return the argument passed into the function
*
* @since 1.4
*/
struct eis_ping *
eis_ping_ref(struct eis_ping *eis_ping);
/**
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, the context disconnects from the server and all allocated resources
* are released.
*
* @return always NULL
*
* @since 1.4
*/
struct eis_ping *
eis_ping_unref(struct eis_ping *eis_ping);
/**
* Set a custom data pointer for this struct. libeis will not look at or
* modify the pointer. Use eis_ping_get_user_data() to retrieve a previously set
* user data.
*
* @since 1.4
*/
void
eis_ping_set_user_data(struct eis_ping *eis_ping, void *user_data);
/**
* Return the custom data pointer for this struct. libeis will not look at or
* modify the pointer. Use eis_ping_set_user_data() to change the user data.
*
* @since 1.4
*/
void *
eis_ping_get_user_data(struct eis_ping *eis_ping);
/**
* Issue a roundtrip request to the client, resulting in
* an @ref EIS_EVENT_PONG event when this roundtrip has been processed. Events
* are processed in-order by the client implementation so this
* function can be used as synchronization point between events.
*
* If the client is disconnected before the roundtrip is complete,
* libeis will emulate a @ref EIS_EVENT_PONG event before
* @ref EIS_EVENT_CLIENT_DISCONNECT.
*
* @since 1.4
*/
void
eis_ping(struct eis_ping *ping);
/**
* Returns the next event in the internal event queue (or NULL) and removes
* it from the queue.
*
* The returned event is refcounted, use eis_event_unref() to drop the
* reference.
* reference even if the event type is unknown to the caller.
*
* You must not call this function while holding a reference to an event
* returned by eis_peek_event().
@ -526,13 +743,24 @@ struct eis_event *
eis_peek_event(struct eis *eis);
/**
* Release resources associated with this event. This function always
* returns NULL.
* Increase the refcount of this struct by one. Use eis_event_unref() to decrease
* the refcount. This function always returns the same event that the reference
* was added to.
*
* The caller cannot increase the refcount of an event. Events should be
* considered transient data and not be held longer than required.
* eis_event_unref() is provided for consistency (as opposed to, say,
* eis_event_free()).
* Events should be considered transient data and not be held longer than
* required.
*/
struct eis_event *
eis_event_ref(struct eis_event *event);
/**
* Decrease the refcount of this struct by one. When the refcount reaches
* zero, all allocated resources for this struct are released.
*
* Events should be considered transient data and not be held longer than
* required.
*
* @return always NULL
*/
struct eis_event *
eis_event_unref(struct eis_event *event);
@ -547,11 +775,24 @@ struct eis_seat *
eis_event_get_seat(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_SEAT_BIND, return the capabilities
* requested by the client.
* Returns the associated @ref eis_ping struct with this event.
*
* This is the set of *all* capabilities bound by the client as of this event,
* not just the changed ones.
* For events of type other than @ref EIS_EVENT_PONG this function
* returns NULL.
*
* This does not increase the refcount of the eis_pong. Use eis_pong_ref()
* to ekeep a reference beyond the immediate scope.
*/
struct eis_ping *
eis_event_pong_get_ping(struct eis_event *event);
/**
* For an event of type @ref EIS_EVENT_SEAT_BIND or @ref
* EIS_EVENT_SEAT_DEVICE_REQUESTED, return the capabilities requested by the
* client.
*
* For events of type @ref EIS_EVENT_SEAT_BIND this is the set of *all*
* capabilities bound by the client as of this event, not just the changed ones.
*/
bool
eis_event_seat_has_capability(struct eis_event *event, enum eis_device_capability cap);
@ -577,7 +818,17 @@ eis_event_get_device(struct eis_event *event);
uint64_t
eis_event_get_time(struct eis_event *event);
/**
* Create a new eis_ping object to trigger a round trip to the client.
* See eis_ping() for details.
*
* The returned @ref eis_ping is refcounted, use eis_ping_unref() to
* drop the reference.
*
* @since 1.4
*/
struct eis_ping *
eis_client_new_ping(struct eis_client *client);
struct eis_client *
eis_client_ref(struct eis_client *client);
@ -723,6 +974,18 @@ eis_seat_add(struct eis_seat *seat);
void
eis_seat_remove(struct eis_seat *seat);
/**
* @ingroup libeis-seat
*/
struct eis *
eis_seat_get_context(struct eis_seat *seat);
/**
* @ingroup libeis-device
*/
struct eis *
eis_device_get_context(struct eis_device *device);
/**
* @ingroup libeis-device
*/
@ -908,6 +1171,53 @@ eis_region_set_offset(struct eis_region *region, uint32_t x, uint32_t y);
void
eis_region_set_physical_scale(struct eis_region *region, double scale);
/**
* @ingroup libeis-region
*
* Attach a unique identifier representing an external resource to this region.
* libeis does not look at or modify the value, it is passed through to the
* client if the client supports ei_device interface version 2 or later. Because
* the ID is assigned by the caller libei makes no guarantee that the ID is
* indeed unique and/or corresponds to any particular format.
*
* This ID can be used by the caller to identify an external resource
* that has a relationship with this region. For example, a caller may have
* a data stream with the video data that this region represents.
* By attaching the same identifier to the data stream and this region a client
* (who needs to be aware of this approach) can pair the video data stream
* with the region. Note that in this example use-case, if the stream is resized
* there may be a transition period where two regions have the same identifier -
* the old region and the new region with the updated size. A client must be
* able to handle the case where two mapping_ids are identical.
*
* This function has no effect if called after eis_device_add()
*
* @since 1.1
*/
void
eis_region_set_mapping_id(struct eis_region *region, const char *mapping_id);
/**
* @ingroup libeis-region
*
* Get the unique ID for this region previously set by this
* caller, if any, or NULL if the client does not support region mapping id
* or no region ID has been set.
*
* Region IDs require the client to support the ei_device interface
* version 2 or later. This function can be used to detect support
* for this interface: after eis_region_add() this region's ID is set
* to NULL if the client does not support that interface version.
*
* In other words, if a caller sets a region ID with
* eis_region_set_mapping_id() and that region ID is returned after eis_region_add(),
* the client has been notified of the region ID.
*
* @since 1.1
*/
const char *
eis_region_get_mapping_id(struct eis_region *region);
/**
* @ingroup libeis-region
*
@ -941,6 +1251,17 @@ eis_region_add(struct eis_region *region);
struct eis_region *
eis_device_get_region(struct eis_device *device, size_t index);
/**
* @ingroup libeis-device
*
* Return the region that contains the given point x/y (in desktop-wide
* coordinates) or NULL if the coordinates are outside all regions.
*
* @since 1.1
*/
struct eis_region *
eis_device_get_region_at(struct eis_device *device, double x, double y);
/**
* @ingroup libeis-region
*/
@ -989,6 +1310,20 @@ eis_region_get_width(struct eis_region *region);
uint32_t
eis_region_get_height(struct eis_region *region);
/**
* @ingroup libeis-region
*/
double
eis_region_get_physical_scale(struct eis_region *region);
/**
* @ingroup libeis-region
*
* @see ei_region_contains
*/
bool
eis_region_contains(struct eis_region *region, double x, double y);
/**
* @ingroup libeis-device
*
@ -1023,11 +1358,12 @@ eis_device_remove(struct eis_device *device);
* a number of events from a device after it has been paused and must
* update its internal state accordingly.
*
* Pause/resume should only be used for short-term event delaying, a client
* will expect that the device's state has not changed between pause and
* resume. Where a device's state changes on the EIS implementation side (e.g.
* buttons or keys are forcibly released), the device should be removed and
* re-added as new device.
* Pausing a device resets the logical state of the device to neutral.
* This includes:
* - any buttons or keys logically down are released
* - any modifiers logically down are released
* - any touches logically down are released
* No events will be sent for these releases back to a neutral state.
*
* @param device A connected device
*/
@ -1158,6 +1494,22 @@ eis_device_keyboard_get_keymap(struct eis_device *device);
* @ingroup libeis-device
*
* Notify the client of the current XKB modifier state.
*
* This should be called every time the modifier state or current
* effective group changes.
*
* When the state changes due to an incoming
* EIS_EVENT_KEYBOARD_KEY (for sender clients), this method should be
* called when the corresponding EIS_EVENT_FRAME is processed, before
* processing any subsequent events.
*
* When the state changes due to a key press with a receiver client, this
* method should be called immediately after the corresponding call to
* eis_device_frame(). If the change impacts multiple keyboards, this
* method should be called for all of them.
*
* For changes caused by other factors, this method should be called for
* all affected keyboards at the point the change occurs.
*/
void
eis_device_keyboard_send_xkb_modifiers(struct eis_device *device,
@ -1288,6 +1640,14 @@ eis_touch_motion(struct eis_touch *touch, double x, double y);
void
eis_touch_up(struct eis_touch *touch);
/**
* @ingroup libeis-receiver
*
* see @ref ei_touch_cancel
*/
void
eis_touch_cancel(struct eis_touch *touch);
/**
* @ingroup libeis-receiver
*
@ -1506,15 +1866,28 @@ eis_event_touch_get_x(struct eis_event *event);
double
eis_event_touch_get_y(struct eis_event *event);
/**
* @ingroup libeis-sender
*
* For an event of type @ref EIS_EVENT_TOUCH_UP
* return true if the touch was cancelled instead
* of logically released.
*
* Support for touch cancellation requires the EIS implementation and client to
* support version 2 or later of the ei_touchscreen protocol interface.
*/
bool
eis_event_touch_get_is_cancel(struct eis_event *event);
/**
* @returns a timestamp for the current time to pass into
* eis_device_frame().
*
* In the current implementation, the returned timestamp is CLOCK_MONOTONIC
* for compatibility with evdev and libinput.
* By default, the returned timestamp is CLOCK_MONOTONIC for compatibility with
* evdev and libinput. This can be overridden with eis_clock_set_now_func().
*/
uint64_t
eis_now(struct eis *ei);
eis_now(struct eis *eis);
/**
* @}

View file

@ -294,14 +294,14 @@ sender_name(sd_bus *bus)
_cleanup_free_ char *sender = NULL;
const char *name = NULL;
if (sd_bus_get_unique_name(bus, &name) != 0)
if ((sd_bus_get_unique_name(bus, &name) != 0) || strlen(name) < 1)
return NULL;
sender = xstrdup(name + 1); /* drop initial : */
name += 1; /* drop initial : */
sender = xalloc(strlen(name));
for (unsigned i = 0; sender[i]; i++) {
if (sender[i] == '.')
sender[i] = '_';
for (unsigned i = 0; name[i]; i++) {
sender[i] = name[i] == '.' ? '_' : name[i];
}
return steal(&sender);
@ -327,22 +327,61 @@ xdp_session_path(char *sender_name, char *token)
return xaprintf("/org/freedesktop/portal/desktop/session/%s/%s", sender_name, token);
}
static int
connect_to_eis_returned(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
int eisfd;
struct oeffis *oeffis = userdata;
int rc = sd_bus_message_get_errno(m);
if (rc > 0) {
oeffis_disconnect(oeffis, "Error calling ConnectToEIS: %s", strerror(rc));
return rc;
}
rc = sd_bus_message_read(m, "h", &eisfd);
if (rc < 0) {
oeffis_disconnect(oeffis, "Unable to get fd from portal: %s", strerror(-rc));
return -rc;
}
/* the fd is owned by the message */
rc = xerrno(xdup(eisfd));
if (rc < 0) {
oeffis_disconnect(oeffis, "Failed to dup fd: %s", strerror(-rc));
return -rc;
} else {
eisfd = rc;
int flags = fcntl(eisfd, F_GETFL, 0);
fcntl(eisfd, F_SETFL, flags | O_NONBLOCK);
}
log_debug("Got fd %d from portal", eisfd);
rc = oeffis_set_eis_fd(oeffis, eisfd);
if (rc < 0) {
oeffis_disconnect(oeffis, "Failed to set the fd: %s", strerror(-rc));
return -rc;
}
return 0;
}
static void
portal_connect_to_eis(struct oeffis *oeffis)
{
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_unref_(sd_bus_message) *response = NULL;
sd_bus *bus = oeffis->bus;
int eisfd;
int rc = 0;
with_signals_blocked(SIGALRM) {
rc = sd_bus_call_method(bus, oeffis->busname,
rc = sd_bus_call_method_async(bus, NULL, oeffis->busname,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.RemoteDesktop",
"ConnectToEIS",
&error,
&response,
&connect_to_eis_returned,
oeffis,
"oa{sv}",
oeffis->session_path,
0);
@ -352,29 +391,6 @@ portal_connect_to_eis(struct oeffis *oeffis)
oeffis_disconnect(oeffis, "Failed to call ConnectToEIS: %s", strerror(-rc));
return;
}
rc = sd_bus_message_read(response, "h", &eisfd);
if (rc < 0) {
oeffis_disconnect(oeffis, "Unable to get fd from portal: %s", strerror(-rc));
return;
}
/* the fd is owned by the message */
rc = xerrno(xdup(eisfd));
if (rc < 0) {
oeffis_disconnect(oeffis, "Failed to dup fd: %s", strerror(-rc));
return;
} else {
eisfd = rc;
int flags = fcntl(eisfd, F_GETFL, 0);
fcntl(eisfd, F_SETFL, flags | O_NONBLOCK);
}
log_debug("Got fd %d from portal\n", eisfd);
rc = oeffis_set_eis_fd(oeffis, eisfd);
if (rc < 0)
oeffis_disconnect(oeffis, "Failed to set the fd: %s", strerror(-rc));
}
static int
@ -448,7 +464,7 @@ portal_start_response_received(sd_bus_message *m, void *userdata, sd_bus_error *
return 0;
}
log_debug("Portal Start reponse is %u\n", response);
log_debug("Portal Start response is %u", response);
if (response != 0) {
oeffis_disconnect(oeffis, "Portal denied Start");
return 0;
@ -525,7 +541,7 @@ portal_select_devices_response_received(sd_bus_message *m, void *userdata, sd_bu
return 0;
}
log_debug("Portal SelectDevices reponse is %u\n", response);
log_debug("Portal SelectDevices response is %u", response);
if (response != 0) {
oeffis_disconnect(oeffis, "Portal denied SelectDevices");
return 0;
@ -560,7 +576,7 @@ portal_select_devices(struct oeffis *oeffis)
&response,
"oa{sv}",
oeffis->session_path,
2,
oeffis->devices == OEFFIS_DEVICE_ALL_DEVICES ? 1 : 2,
"handle_token", /* string key */
"s", token, /* variant string */
"types", /* string key */
@ -598,7 +614,7 @@ portal_create_session_response_received(sd_bus_message *m, void *userdata, sd_bu
return 0;
}
log_debug("Portal CreateSession reponse is %u\n", response);
log_debug("Portal CreateSession response is %u", response);
const char *session_handle = NULL;
if (response == 0) {
@ -666,7 +682,7 @@ portal_init(struct oeffis *oeffis, const char *busname)
oeffis_disconnect(oeffis, "RemoteDesktop.version is %u, we need 2", version);
return;
}
log_debug("RemoteDesktop.version is %u\n", version);
log_debug("RemoteDesktop.version is %u", version);
_cleanup_free_ char *token = NULL;
_unref_(sd_bus_slot) *request_slot = NULL;
@ -716,7 +732,7 @@ portal_init(struct oeffis *oeffis, const char *busname)
return;
}
log_debug("Portal Response object is %s\n", path);
log_debug("Portal Response object is %s", path);
_unref_(source) *s = source_new(sd_bus_get_fd(bus), dbus_dispatch, oeffis);
source_never_close_fd(s); /* the bus object handles the fd */

View file

@ -33,7 +33,7 @@ extern "C" {
#include <stddef.h>
/**
* @addtogroup oeffis 🚌 liboeffis - An XDG RemoteDesktop portal wrapper API
* @addtogroup liboeffis 🚌 liboeffis - An XDG RemoteDesktop portal wrapper API
*
* liboeffis is a helper library for applications that do not want to or cannot
* interact with the XDG RemoteDesktop DBus portal directly.
@ -143,6 +143,17 @@ oeffis_get_eis_fd(struct oeffis *oeffis);
* bitmask in the XDG RemoteDesktop portal.
*/
enum oeffis_device {
/**
* Request all devices. Note that this is not a simple mask of the
* other enum values here but it relies on the RemoteDesktop portal
* stack to use the default "all" value. The exact set of devices
* is thus dependent on the implementation - running against an
* older portal stack may mean a subset of the devices here, running
* against a portal stack more modern than liboeffis may mean
* selecting for devices unknown to liboeffis at compile time.
*
* If in doubt, use an explicit mask of desired devices instead.
*/
OEFFIS_DEVICE_ALL_DEVICES = 0,
OEFFIS_DEVICE_KEYBOARD = (1 << 0),
OEFFIS_DEVICE_POINTER = (1 << 1),

View file

@ -4,6 +4,7 @@ src_libutil = files(
'util-list.c',
'util-logger.c',
'util-memfile.c',
'util-memmap.c',
'util-sources.c',
'util-strings.c',
)
@ -12,6 +13,7 @@ lib_util = static_library('util',
src_libutil,
include_directories: [inc_builddir],
dependencies: [dep_math, dep_epoll],
gnu_symbol_visibility: 'hidden',
)
dep_libutil = declare_dependency(link_with: lib_util)
@ -23,127 +25,168 @@ brei_proto_h_template = files('brei-proto.h.tmpl')
brei_proto_headers = custom_target('brei-proto-headers',
input: protocol_xml,
output: ['brei-proto.h'],
command: [scanner, '--component=brei', '--output=@OUTPUT@', '@INPUT@', brei_proto_h_template])
ei_proto_headers = custom_target('ei-proto-headers',
input: protocol_xml,
output: ['ei-proto.h'],
command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@', proto_h_template])
ei_proto_sources = custom_target('ei-proto-sources',
input: protocol_xml,
output: ['ei-proto.c'],
command: [scanner, '--component=ei', '--output=@OUTPUT@', '@INPUT@',
'--jinja-extra-data={"headerfile": "ei-proto.h"}', proto_c_template])
src_libei = files(
'brei-shared.c',
'libei.c',
'libei-button.c',
'libei-callback.c',
'libei-connection.c',
'libei-device.c',
'libei-event.c',
'libei-fd.c',
'libei-handshake.c',
'libei-keyboard.c',
'libei-log.c',
'libei-pingpong.c',
'libei-pointer-absolute.c',
'libei-pointer.c',
'libei-region.c',
'libei-region.c',
'libei-scroll.c',
'libei-seat.c',
'libei-socket.c',
'libei-touchscreen.c',
) + [brei_proto_headers, ei_proto_headers, ei_proto_sources]
deps_libei = [
dep_libutil,
]
lib_libei = library('ei',
src_libei,
dependencies: deps_libei,
include_directories: [inc_builddir],
gnu_symbol_visibility: 'hidden',
version: soname,
install: true
)
libei_headers = files('libei.h')
install_headers(libei_headers, subdir: libei_api_dir)
dep_libei = declare_dependency(link_with: lib_libei,
include_directories: [inc_src])
meson.override_dependency('libei', dep_libei)
pkgconfig.generate(lib_libei,
filebase: 'libei-@0@'.format(libei_api_version),
name: 'libEI',
description: 'Emulated Input client library',
subdirs: libei_api_dir,
version: meson.project_version(),
libraries: lib_libei,
command: [
scanner,
'--component=brei',
'--output=@OUTPUT@',
'@INPUT@',
brei_proto_h_template
],
)
eis_proto_headers = custom_target('eis-proto-headers',
input: protocol_xml,
output: ['eis-proto.h'],
command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@', proto_h_template])
eis_proto_sources = custom_target('eis-proto-sources',
input: protocol_xml,
output: ['eis-proto.c'],
command: [scanner, '--component=eis', '--output=@OUTPUT@', '@INPUT@',
'--jinja-extra-data={"headerfile": "eis-proto.h"}', proto_c_template])
if build_libei
ei_proto_headers = custom_target('ei-proto-headers',
input: protocol_xml,
output: ['ei-proto.h'],
command: [
scanner,
'--component=ei',
'--output=@OUTPUT@',
'@INPUT@',
proto_h_template,
],
)
ei_proto_sources = custom_target('ei-proto-sources',
input: protocol_xml,
output: ['ei-proto.c'],
command: [
scanner,
'--component=ei',
'--output=@OUTPUT@',
'@INPUT@',
'--jinja-extra-data={"headerfile": "ei-proto.h"}',
proto_c_template
],
)
src_libei = files(
'brei-shared.c',
'libei.c',
'libei-button.c',
'libei-callback.c',
'libei-connection.c',
'libei-device.c',
'libei-event.c',
'libei-fd.c',
'libei-handshake.c',
'libei-keyboard.c',
'libei-log.c',
'libei-pingpong.c',
'libei-ping.c',
'libei-pointer-absolute.c',
'libei-pointer.c',
'libei-region.c',
'libei-region.c',
'libei-scroll.c',
'libei-seat.c',
'libei-socket.c',
'libei-touchscreen.c',
) + [brei_proto_headers, ei_proto_headers, ei_proto_sources]
src_libeis = files(
'brei-shared.c',
'libeis-button.c',
'libeis-callback.c',
'libeis-client.c',
'libeis-connection.c',
'libeis-device.c',
'libeis-event.c',
'libeis-fd.c',
'libeis-handshake.c',
'libeis-keyboard.c',
'libeis-log.c',
'libeis-pingpong.c',
'libeis-pointer-absolute.c',
'libeis-pointer.c',
'libeis-region.c',
'libeis-scroll.c',
'libeis-seat.c',
'libeis-socket.c',
'libeis-touchscreen.c',
'libeis.c',
) + [brei_proto_headers, eis_proto_headers, eis_proto_sources]
deps_libei = [
dep_libutil,
]
lib_libeis = library('eis',
src_libeis,
dependencies: [dep_libutil],
include_directories: [inc_builddir],
gnu_symbol_visibility: 'hidden',
version: soname,
install: true
)
libeis_headers = files('libeis.h')
install_headers(libeis_headers, subdir: libei_api_dir)
lib_libei = library('ei',
src_libei,
dependencies: deps_libei,
include_directories: [inc_builddir],
gnu_symbol_visibility: 'hidden',
version: soname,
install: true,
)
libei_headers = files('libei.h')
install_headers(libei_headers, subdir: libei_api_dir)
dep_libeis = declare_dependency(link_with: lib_libeis,
include_directories: [inc_src])
meson.override_dependency('libeis', dep_libeis)
dep_libei = declare_dependency(link_with: lib_libei,
include_directories: [inc_src],
)
meson.override_dependency('libei-@0@'.format(libei_api_version), dep_libei)
pkgconfig.generate(lib_libeis,
filebase: 'libeis-@0@'.format(libei_api_version),
name: 'libEIS',
description: 'Emulated Input server library',
subdirs: libei_api_dir,
version: meson.project_version(),
libraries: lib_libeis,
)
pkgconfig.generate(lib_libei,
filebase: 'libei-@0@'.format(libei_api_version),
name: 'libEI',
description: 'Emulated Input client library',
subdirs: libei_api_dir,
version: meson.project_version(),
libraries: lib_libei,
)
endif
if build_libeis
eis_proto_headers = custom_target('eis-proto-headers',
input: protocol_xml,
output: ['eis-proto.h'],
command: [
scanner,
'--component=eis',
'--output=@OUTPUT@',
'@INPUT@',
proto_h_template
],
)
eis_proto_sources = custom_target('eis-proto-sources',
input: protocol_xml,
output: ['eis-proto.c'],
command: [
scanner,
'--component=eis',
'--output=@OUTPUT@',
'@INPUT@',
'--jinja-extra-data={"headerfile": "eis-proto.h"}',
proto_c_template
],
)
src_libeis = files(
'brei-shared.c',
'libeis-button.c',
'libeis-callback.c',
'libeis-client.c',
'libeis-connection.c',
'libeis-device.c',
'libeis-event.c',
'libeis-fd.c',
'libeis-handshake.c',
'libeis-keyboard.c',
'libeis-log.c',
'libeis-pingpong.c',
'libeis-ping.c',
'libeis-pointer-absolute.c',
'libeis-pointer.c',
'libeis-region.c',
'libeis-scroll.c',
'libeis-seat.c',
'libeis-socket.c',
'libeis-touchscreen.c',
'libeis.c',
) + [brei_proto_headers, eis_proto_headers, eis_proto_sources]
lib_libeis = library('eis',
src_libeis,
dependencies: [dep_libutil],
include_directories: [inc_builddir],
gnu_symbol_visibility: 'hidden',
version: soname,
install: true,
)
libeis_headers = files('libeis.h')
install_headers(libeis_headers, subdir: libei_api_dir)
dep_libeis = declare_dependency(link_with: lib_libeis,
include_directories: [inc_src],
)
meson.override_dependency('libeis-@0@'.format(libei_api_version), dep_libeis)
pkgconfig.generate(lib_libeis,
filebase: 'libeis-@0@'.format(libei_api_version),
name: 'libEIS',
description: 'Emulated Input server library',
subdirs: libei_api_dir,
version: meson.project_version(),
libraries: lib_libeis,
)
endif
build_oeffis = dep_sdbus.found()
summary({'liboeffis': build_oeffis}, section: 'Conditional Features')
if build_oeffis
src_liboeffis = files('liboeffis.c')
deps_liboeffis = [dep_libutil, dep_sdbus]
@ -154,14 +197,15 @@ if build_oeffis
dependencies: deps_liboeffis,
gnu_symbol_visibility: 'hidden',
version: soname,
install: true
install: true,
)
liboeffis_headers = files('liboeffis.h')
install_headers(liboeffis_headers, subdir: libei_api_dir)
dep_liboeffis = declare_dependency(link_with: lib_liboeffis,
include_directories: [inc_src])
meson.override_dependency('liboeffis', dep_liboeffis)
include_directories: [inc_src],
)
meson.override_dependency('liboeffis-@0@'.format(libei_api_version), dep_liboeffis)
pkgconfig.generate(lib_liboeffis,
filebase: 'liboeffis-@0@'.format(libei_api_version),
@ -173,3 +217,9 @@ if build_oeffis
)
endif
summary({
'liboeffis': build_oeffis,
'libei': build_libei,
'libeis': build_libeis,
}, section: 'Conditional Features', bool_yn: true)

View file

@ -27,6 +27,8 @@
#include "util-macros.h"
#include "util-io.h"
_Static_assert(EAGAIN == EWOULDBLOCK, "Currently not supported, please file a bug");
int
xread_with_fds(int fd, void *buf, size_t count, int **fds)
{
@ -47,14 +49,12 @@ xread_with_fds(int fd, void *buf, size_t count, int **fds)
.msg_controllen = sizeof(control),
};
int received = 0;
with_signals_blocked(SIGALRM)
received = xerrno(recvmsg(fd, &msg, 0));
int received = xerrno(SYSCALL(recvmsg(fd, &msg, 0)));
if (received > 0) {
*fds = NULL;
_cleanup_free_ int *fd_return = calloc(MAX_FDS + 1, sizeof(int));
_cleanup_free_ int *fd_return = xalloc(MAX_FDS + 1 * sizeof(int));
size_t idx = 0;
for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; hdr = CMSG_NXTHDR(&msg, hdr)) {
@ -113,17 +113,14 @@ xsend_with_fd(int fd, const void *buf, size_t len, int *fds)
header->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(CMSG_FIRSTHDR(&msg)), fds, nfds * sizeof(int));
with_signals_blocked(SIGALRM)
return xerrno(sendmsg(fd, &msg, MSG_NOSIGNAL));
abort(); // can't be reached
return xerrno(SYSCALL(sendmsg(fd, &msg, MSG_NOSIGNAL)));
}
/* consider this struct opaque */
struct iobuf {
size_t sz;
size_t len;
char *data;
uint8_t *data;
int fds[32];
};
@ -131,7 +128,7 @@ struct iobuf *
iobuf_new(size_t size)
{
struct iobuf *buf = malloc(sizeof(*buf));
char *data = malloc(size);
uint8_t *data = malloc(size);
assert(buf);
assert(data);
@ -182,7 +179,7 @@ iobuf_pop(struct iobuf *buf, size_t nbytes)
* The returned pointer only valid in the immediate scope, any iobuf
* function may invalidate the pointer.
*/
const char *
const uint8_t *
iobuf_data(struct iobuf *buf)
{
return buf->data;
@ -194,7 +191,7 @@ iobuf_data(struct iobuf *buf)
* The returned pointer only valid in the immediate scope, any iobuf
* function may invalidate the pointer.
*/
const char *
const uint8_t *
iobuf_data_end(struct iobuf *buf)
{
return buf->data + buf->len;
@ -209,28 +206,36 @@ iobuf_take_fd(struct iobuf *buf)
{
int fd = buf->fds[0];
if (fd != -1)
memmove(buf->fds, buf->fds + 1, ARRAY_LENGTH(buf->fds) - 1);
memmove(buf->fds, buf->fds + 1, (ARRAY_LENGTH(buf->fds) - 1) * sizeof(*buf->fds));
return fd;
}
static inline void
iobuf_resize(struct iobuf *buf, size_t to_size)
{
char *newdata = realloc(buf->data, to_size);
uint8_t *newdata = realloc(buf->data, to_size);
assert(newdata);
buf->data = newdata;
buf->sz = to_size;
}
static inline void
iobuf_extend(struct iobuf *buf, size_t extra)
{
size_t newsize = buf->len + extra;
if (newsize > buf->sz)
iobuf_resize(buf, newsize);
}
/**
* Remove the data bytes from the buffer. The caller must free() the data.
* The buffer state is the same as iobuf_new() after this call.
*/
char *
uint8_t *
iobuf_take_data(struct iobuf *buf)
{
char *data = buf->data;
uint8_t *data = buf->data;
buf->data = NULL;
buf->len = 0;
@ -244,25 +249,51 @@ iobuf_take_data(struct iobuf *buf)
* size it is resized automatically.
*/
void
iobuf_append(struct iobuf *buf, const char *data, size_t len)
iobuf_append(struct iobuf *buf, const void *data, size_t len)
{
if (len == 0)
return;
if (buf->len + len > buf->sz) {
size_t newsize = buf->len + len;
iobuf_resize(buf, newsize);
}
iobuf_extend(buf, len);
memcpy(buf->data + buf->len, data, len);
buf->len += len;
}
void
iobuf_append_u32(struct iobuf *buf, uint32_t data)
{
size_t len = 4;
iobuf_extend(buf, len);
memcpy(buf->data + buf->len, &data, len);
buf->len += len;
}
void
iobuf_append_u64(struct iobuf *buf, uint64_t data)
{
size_t len = 8;
iobuf_extend(buf, len);
memcpy(buf->data + buf->len, &data, len);
buf->len += len;
}
void
iobuf_append_f32(struct iobuf *buf, float data)
{
size_t len = 4;
iobuf_extend(buf, len);
memcpy(buf->data + buf->len, &data, len);
buf->len += len;
}
/**
* Prepend the given data to the buffer.
*/
void
iobuf_prepend(struct iobuf *buf, const char *data, size_t len)
iobuf_prepend(struct iobuf *buf, const void *data, size_t len)
{
if (len == 0)
return;
@ -281,7 +312,7 @@ int
iobuf_append_fd(struct iobuf *buf, int fd)
{
/* Array must remain terminated by -1 */
for (size_t idx = 0; idx < sizeof(buf->fds) - 1; idx ++) {
for (size_t idx = 0; idx < ARRAY_LENGTH(buf->fds) - 1; idx ++) {
if (buf->fds[idx] == -1) {
int f = dup(fd);
if (f == -1)
@ -296,7 +327,7 @@ iobuf_append_fd(struct iobuf *buf, int fd)
/**
* Append all available data from the file descriptor to the pointer. The
* file descriptor shold be in O_NONBLOCK or this call will block. If the
* file descriptor should be in O_NONBLOCK or this call will block. If the
* data exceeds the current buffer size it is resized automatically.
*
* @return The number of bytes read or a negative errno on failure. Zero
@ -325,10 +356,12 @@ iobuf_append_from_fd(struct iobuf *buf, int fd)
/**
* Append all available data from the file descriptor to the pointer. The
* file descriptor shold be in O_NONBLOCK or this call will block. If the
* file descriptor should be in O_NONBLOCK or this call will block. If the
* data exceeds the current buffer size it is resized automatically.
*
* Any file descriptors passed through the fd are placed
* Any file descriptors passed through the fd are placed into the struct
* iobuf's file descriptor array and can be retrieved in-order with
* iobuf_take_fd().
*
* @return The number of bytes read or a negative errno on failure. Zero
* indicates EOF.
@ -425,6 +458,28 @@ MUNIT_TEST(test_iobuf_cleanup)
return MUNIT_OK;
}
MUNIT_TEST(test_iobuf_take_fd)
{
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
const size_t nfds = ARRAY_LENGTH(buf->fds);
int *last_fd = &buf->fds[nfds - 1]; /* always -1 */
for (size_t i = 0; i < nfds - 1; i++) {
buf->fds[i] = 10 + i;
}
for (size_t i = 0; i < nfds - 1; i++) {
int fd = iobuf_take_fd(buf);
munit_assert_int(fd, ==, 10 + i);
munit_assert_int(*last_fd, ==, -1);
}
int fd = iobuf_take_fd(buf);
munit_assert_int(fd, ==, -1);
return MUNIT_OK;
}
MUNIT_TEST(test_iobuf_append_prepend)
{
/* Test appending data */
@ -440,7 +495,7 @@ MUNIT_TEST(test_iobuf_append_prepend)
munit_assert_size(buf->sz, ==, 10);
/* we don't have a trailing \0 */
const char *bufdata = iobuf_data(buf);
const uint8_t *bufdata = iobuf_data(buf);
munit_assert_char(bufdata[0], ==, 'f');
munit_assert_char(bufdata[1], ==, 'o');
munit_assert_char(bufdata[2], ==, 'o');
@ -472,7 +527,7 @@ MUNIT_TEST(test_iobuf_append_prepend)
munit_assert_size(iobuf_len(buf), ==, expected_size);
munit_assert_size(buf->sz, ==, expected_size);
/* now we have a trailing \0 */
munit_assert_string_equal(iobuf_data(buf), "barfoodata forcing resize");
munit_assert_string_equal((const char *)iobuf_data(buf), "barfoodata forcing resize");
/* and again with prepending */
const char prepend_data2[] = "second resize";
@ -481,7 +536,48 @@ MUNIT_TEST(test_iobuf_append_prepend)
munit_assert_size(iobuf_len(buf), ==, expected_size);
munit_assert_size(buf->sz, ==, expected_size);
munit_assert_string_equal(iobuf_data(buf), "second resizebarfoodata forcing resize");
munit_assert_string_equal((const char *)iobuf_data(buf), "second resizebarfoodata forcing resize");
return MUNIT_OK;
}
MUNIT_TEST(test_iobuf_append_values)
{
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
iobuf_append_u32(buf, -1);
size_t expected_size = 4;
munit_assert_size(buf->len, ==, expected_size);
munit_assert_size(iobuf_len(buf), ==, expected_size);
munit_assert_size(buf->sz, ==, 10);
const uint8_t *bufdata = iobuf_data(buf);
munit_assert_int(bufdata[0], ==, 0xff);
munit_assert_int(bufdata[1], ==, 0xff);
munit_assert_int(bufdata[2], ==, 0xff);
munit_assert_int(bufdata[3], ==, 0xff);
free(iobuf_take_data(buf)); /* drops and replaces current buffer */
iobuf_append_u64(buf, 0xabababababababab);
expected_size = 8;
munit_assert_size(buf->len, ==, expected_size);
munit_assert_size(iobuf_len(buf), ==, expected_size);
munit_assert_size(buf->sz, ==, 10);
bufdata = iobuf_data(buf);
munit_assert_int((unsigned char)bufdata[0], ==, 0xab);
munit_assert_int((unsigned char)bufdata[1], ==, 0xab);
munit_assert_int((unsigned char)bufdata[2], ==, 0xab);
munit_assert_int((unsigned char)bufdata[3], ==, 0xab);
munit_assert_int((unsigned char)bufdata[4], ==, 0xab);
munit_assert_int((unsigned char)bufdata[5], ==, 0xab);
munit_assert_int((unsigned char)bufdata[6], ==, 0xab);
munit_assert_int((unsigned char)bufdata[7], ==, 0xab);
free(iobuf_take_data(buf));
return MUNIT_OK;
}
@ -500,7 +596,7 @@ MUNIT_TEST(test_iobuf_prepend_empty_buffer)
munit_assert_size(buf->sz, ==, 10);
/* we don't have a trailing \0 */
const char *bufdata = iobuf_data(buf);
const uint8_t *bufdata = iobuf_data(buf);
munit_assert_char(bufdata[0], ==, 'f');
munit_assert_char(bufdata[1], ==, 'o');
munit_assert_char(bufdata[2], ==, 'o');
@ -519,7 +615,7 @@ MUNIT_TEST(test_iobuf_pop)
munit_assert_size(iobuf_len(buf), ==, 3);
/* we don't have a trailing \0 */
const char *bufdata = iobuf_data(buf);
const uint8_t *bufdata = iobuf_data(buf);
munit_assert_char(bufdata[0], ==, 'b');
munit_assert_char(bufdata[1], ==, 'a');
munit_assert_char(bufdata[2], ==, 'r');
@ -532,7 +628,7 @@ MUNIT_TEST(test_iobuf_append_short)
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(10);
/* Append only the first few bytes out of a larger data field, i.e.
* make sure we honor the lenght parameter */
* make sure we honor the length parameter */
const char data[] = "foobar";
const char nullbyte = '\0';
iobuf_append(buf, data, 3);
@ -541,8 +637,7 @@ MUNIT_TEST(test_iobuf_append_short)
munit_assert_size(buf->len, ==, 4);
munit_assert_size(iobuf_len(buf), ==, 4);
munit_assert_size(buf->sz, ==, 10);
munit_assert_string_equal(iobuf_data(buf),
"foo");
munit_assert_string_equal((const char *)iobuf_data(buf), "foo");
return MUNIT_OK;
}
@ -572,7 +667,7 @@ MUNIT_TEST(test_iobuf_append_fd)
/* so we can do strcmp */
const char nullbyte = '\0';
iobuf_append(buf, &nullbyte, 1);
munit_assert_string_equal(iobuf_data(buf), "foob");
munit_assert_string_equal((const char *)iobuf_data(buf), "foob");
/* read when there's nothing waiting */
int blocking_read = iobuf_append_from_fd(buf, rd);
@ -605,6 +700,30 @@ MUNIT_TEST(test_iobuf_append_fd)
return MUNIT_OK;
}
MUNIT_TEST(test_iobuf_append_fd_too_many)
{
_cleanup_fclose_ FILE *fp = tmpfile();
munit_assert_ptr_not_null(fp);
int fd = fileno(fp);
_cleanup_iobuf_ struct iobuf *buf = iobuf_new(20);
const size_t nfds = ARRAY_LENGTH(buf->fds);
int *last_fd = &buf->fds[nfds - 1]; /* always -1 */
int err = 0;
size_t count = 0;
/* 32 fds hardcoded in the struct, last one is always -1 */
for (count = 0; err == 0 && count < nfds + 1; count++) {
err = iobuf_append_fd(buf, fd);
munit_assert_int(*last_fd, ==, -1);
}
munit_assert_int(count, ==, 32);
munit_assert_int(err, ==, -ENOMEM);
return MUNIT_OK;
}
MUNIT_TEST(test_iobuf_recv_fd)
{
int fds[2];
@ -614,6 +733,7 @@ MUNIT_TEST(test_iobuf_recv_fd)
_cleanup_close_ int left = fds[0];
_cleanup_close_ int right = fds[1];
_cleanup_fclose_ FILE *fp = tmpfile();
munit_assert_ptr_not_null(fp);
/* actual message data to be sent */
char data[] = "some data\n";
@ -664,7 +784,9 @@ MUNIT_TEST(test_pass_fd)
/* Write some data to the file on it's real fd */
for (size_t idx = 0; idx < ARRAY_LENGTH(fps); idx++) {
_cleanup_free_ char *buf = xaprintf("foo %zu\n", idx);
munit_assert_ptr_not_null(buf);
FILE *fp = fps[idx];
munit_assert_ptr_not_null(fp);
fwrite(buf, strlen(buf) + 1, 1, fp);
fflush(fp);
}

View file

@ -30,6 +30,7 @@
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@ -63,6 +64,8 @@ signals_block(int signal, ...)
}
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
va_end(sigs);
return old_mask;
}
@ -123,8 +126,8 @@ xerrno(int value) {
static inline int
xclose(int fd) {
if (fd != -1) {
with_signals_blocked(SIGALRM)
close(fd);
/* Not SYSCALL(), see libei MR!261#note_2131802 */
close(fd);
}
return -1;
@ -143,10 +146,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose);
static inline int
xread(int fd, void *buf, size_t count)
{
with_signals_blocked(SIGALRM)
return xerrno(read(fd, buf, count));
abort(); // can't be reached
return xerrno(SYSCALL(read(fd, buf, count)));
}
/**
@ -166,9 +166,7 @@ xread_with_fds(int fd, void *buf, size_t count, int **fds);
static inline int
xwrite(int fd, const void *buf, size_t count)
{
with_signals_blocked(SIGALRM)
return xerrno(write(fd, buf, count));
abort(); // can't be reached
return xerrno(SYSCALL(write(fd, buf, count)));
}
/**
@ -179,10 +177,7 @@ xwrite(int fd, const void *buf, size_t count)
static inline int
xsend(int fd, const void *buf, size_t len)
{
with_signals_blocked(SIGALRM)
return xerrno(send(fd, buf, len, MSG_NOSIGNAL));
abort(); // can't be reached
return xerrno(SYSCALL(send(fd, buf, len, MSG_NOSIGNAL)));
}
/**
@ -191,10 +186,7 @@ xsend(int fd, const void *buf, size_t len)
static inline int
xpipe2(int pipefd[2], int flags)
{
with_signals_blocked(SIGALRM)
return pipe2(pipefd, flags);
abort(); // can't be reached
return SYSCALL(pipe2(pipefd, flags));
}
/**
@ -203,10 +195,7 @@ xpipe2(int pipefd[2], int flags)
static inline int
xdup(int fd)
{
with_signals_blocked(SIGALRM)
return dup(fd);
abort(); // can't be reached
return SYSCALL(dup(fd));
}
/**
@ -232,17 +221,16 @@ xconnect(const char *path)
if (!xsnprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path))
return -EINVAL;
int sockfd = -1;
with_signals_blocked(SIGALRM) {
sockfd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0);
if (sockfd == -1)
break;
int sockfd = xerrno(SYSCALL(socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)));
if (sockfd < 0)
return sockfd;
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
break;
}
return sockfd == -1 ? -errno : sockfd;
int rc = xerrno(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)));
if (rc < 0)
return rc;
return sockfd;
}
/**
@ -265,7 +253,7 @@ iobuf_len(struct iobuf *buf);
* The returned pointer only valid in the immediate scope, any iobuf
* function may invalidate the pointer.
*/
const char *
const uint8_t *
iobuf_data(struct iobuf *buf);
/**
@ -274,7 +262,7 @@ iobuf_data(struct iobuf *buf);
* The returned pointer only valid in the immediate scope, any iobuf
* function may invalidate the pointer.
*/
const char *
const uint8_t *
iobuf_data_end(struct iobuf *buf);
/**
@ -288,7 +276,7 @@ iobuf_take_fd(struct iobuf *buf);
* Remove the data bytes from the buffer. The caller must free() the data.
* The buffer state is the same as iobuf_new() after this call.
*/
char *
uint8_t *
iobuf_take_data(struct iobuf *buf);
/**
@ -302,14 +290,35 @@ iobuf_pop(struct iobuf *buf, size_t nbytes);
* size it is resized automatically.
*/
void
iobuf_append(struct iobuf *buf, const char *data, size_t len);
iobuf_append(struct iobuf *buf, const void *data, size_t len);
/**
* Append one 32-bit value to the buffer. If the data exceeds the current buffer
* size it is resized automatically.
*/
void
iobuf_append_u32(struct iobuf *buf, uint32_t data);
/**
* Append one 64-bit value to the buffer. If the data exceeds the current buffer
* size it is resized automatically.
*/
void
iobuf_append_u64(struct iobuf *buf, uint64_t data);
/**
* Append one 32-bit float to the buffer. If the data exceeds the current buffer
* size it is resized automatically.
*/
void
iobuf_append_f32(struct iobuf *buf, float data);
/**
* Prepend len bytes to the buffer. If the data exceeds the current buffer
* size it is resized automatically.
*/
void
iobuf_prepend(struct iobuf *buf, const char *data, size_t len);
iobuf_prepend(struct iobuf *buf, const void *data, size_t len);
/**
* Append a file descriptor to the buffer. The file descriptor is dup()ed.
@ -321,7 +330,7 @@ iobuf_append_fd(struct iobuf *buf, int fd);
/**
* Append all available data from the file descriptor to the pointer. The
* file descriptor shold be in O_NONBLOCK or this call will block. If the
* file descriptor should be in O_NONBLOCK or this call will block. If the
* data exceeds the current buffer size it is resized automatically.
*
* @return The number of bytes read or a negative errno on failure. Zero
@ -332,7 +341,7 @@ iobuf_append_from_fd(struct iobuf *buf, int fd);
/**
* Append all available data from the file descriptor to the pointer. The
* file descriptor shold be in O_NONBLOCK or this call will block. If the
* file descriptor should be in O_NONBLOCK or this call will block. If the
* data exceeds the current buffer size it is resized automatically.
*
* Any file descriptors passed through the fd are placed

View file

@ -29,6 +29,11 @@
#include "config.h"
#include <unistd.h>
#include <stdio.h>
#ifndef HAVE_C23_AUTO
#define auto __auto_type
#endif
#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])
#define ARRAY_FOR_EACH(_arr, _elem) \
@ -42,6 +47,12 @@
*/
#define CASE_RETURN_STRING(_x) case _x: return #_x
#define SYSCALL(call) ({ \
int rc_; \
do { rc_ = call; } while (rc_ == -1 && errno == EINTR); \
rc_; })
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
@ -59,3 +70,17 @@
#define run_only_once \
static int _once_per_##__func__ = 0; \
for (; _once_per_##__func__ == 0; _once_per_##__func__ = 1)
#define trace(...) \
do { \
char buf_[1024]; \
snprintf(buf_, sizeof(buf_), __VA_ARGS__); \
printf("\x1B[0;34m" "%s():%d - " "\x1B[0;31m" "%s" "\x1B[0m" "\n", __func__, __LINE__, buf_); \
} while (0)
#define etrace(...) \
do { \
char buf_[1024]; \
snprintf(buf_, sizeof(buf_), __VA_ARGS__); \
fprintf(stderr, "\x1B[0;34m" "%s():%d - " "\x1B[0;31m" "%s" "\x1B[0m" "\n", __func__, __LINE__, buf_); \
} while (0)

View file

@ -99,7 +99,7 @@ xalloc(size_t size)
{
void *p;
/* We never need to alloc anything more than 1,5 MB so we can assume
/* We never need to alloc anything more than 1.5 MB so we can assume
* if we ever get above that something's going wrong */
if (size > 1536 * 1024)
assert(!"bug: internal malloc size limit exceeded");

View file

@ -66,7 +66,11 @@ memfile_new(const char *data, size_t sz) {
return NULL;
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
int rc = posix_fallocate(fd, 0, sz);
int rc;
with_signals_blocked(SIGALRM) {
rc = SYSCALL(posix_fallocate(fd, 0, sz));
}
if (rc < 0)
return NULL;
@ -74,7 +78,7 @@ memfile_new(const char *data, size_t sz) {
memfile->size = sz;
fd = -1;
void *map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, memfile->fd, 0);
void *map = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, memfile->fd, 0);
if (map == MAP_FAILED)
return NULL;

View file

@ -26,6 +26,8 @@
#include "config.h"
#include <stddef.h>
struct memfile;
#if HAVE_MEMFD_CREATE

70
src/util-memmap.c Normal file
View file

@ -0,0 +1,70 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2024 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <stddef.h>
#include <unistd.h>
#include <sys/mman.h>
#include "util-memmap.h"
#include "util-mem.h"
#include "util-object.h"
struct memmap {
struct object object;
void *data;
size_t size;
};
static void
memmap_destroy(struct memmap *memmap)
{
if (memmap->data) {
munmap(memmap->data, memmap->size);
}
}
static
OBJECT_IMPLEMENT_CREATE(memmap);
OBJECT_IMPLEMENT_UNREF_CLEANUP(memmap);
OBJECT_IMPLEMENT_REF(memmap);
OBJECT_IMPLEMENT_GETTER(memmap, size, size_t);
OBJECT_IMPLEMENT_GETTER(memmap, data, void*);
struct memmap *
memmap_new(int fd, size_t sz) {
_unref_(memmap) *memmap = memmap_create(NULL);
void *map = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED)
return NULL;
memmap->data = map;
memmap->size = sz;
return steal(&memmap);
}

46
src/util-memmap.h Normal file
View file

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright © 2024 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "config.h"
#include <stddef.h>
struct memmap;
struct memmap *
memmap_new(int fd, size_t sz);
struct memmap *
memmap_ref(struct memmap *memmap);
struct memmap *
memmap_unref(struct memmap *memmap);
size_t
memmap_get_size(struct memmap *memmap);
void *
memmap_get_data(struct memmap *memmap);

View file

@ -34,30 +34,81 @@
* __start and __stop point to the start and end of that section. See the
* __attribute__(section) documentation.
*/
extern const struct test_function __start_test_functions_section, __stop_test_functions_section;
DECLARE_TEST_SECTION();
DECLARE_GLOBAL_SETUP_SECTION();
/* If the tests don't use MUNIT_GLOBAL_SETUP the the above has no linkage, so
* let's always define a noop internal one.
*/
MUNIT_GLOBAL_SETUP(__internal)
{
}
int
munit_tests_run(int argc, char **argv)
{
size_t count = 1; /* For NULL-terminated entry */
for (const struct test_function *t = &__start_test_functions_section;
t < &__stop_test_functions_section;
t++)
foreach_test(t) {
count++;
}
_cleanup_free_ MunitTest *tests = calloc(count, sizeof(*tests));
size_t idx = 0;
for (const struct test_function *t = &__start_test_functions_section;
t < &__stop_test_functions_section;
t++) {
foreach_test(t) {
size_t nparams = 0;
MunitParameterEnum *parameters = calloc(MUNIT_TEST_MAX_PARAMS + 1, sizeof(*parameters)); /* null-terminated */
const char *name = NULL;
char **values = NULL;
for (size_t i = 0; t->params[i]; i++) {
if (t->params[i][0] == '@') {
if (name != NULL) {
assert(nparams < MUNIT_TEST_MAX_PARAMS);
assert(strv_len(values) > 0);
/* Not stripping the @! */
parameters[nparams].name = xstrdup(name);
parameters[nparams].values = steal(&values);
++nparams;
}
name = t->params[i];
} else {
assert(i > 0); /* First one must be a name */
values = strv_append_strdup(values, t->params[i]);
}
}
if (name != NULL) {
assert(nparams < MUNIT_TEST_MAX_PARAMS);
assert(strv_len(values) > 0);
/* Not stripping the @! */
parameters[nparams].name = xstrdup(name);
parameters[nparams].values = steal(&values);
++nparams;
}
MunitTest test = {
.name = xaprintf("%s", t->name),
.test = t->func,
.parameters = parameters,
};
tests[idx++] = test;
}
struct munit_setup setup = {
.argc = argc,
.argv = argv,
.userdata = NULL,
};
size_t setup_count = 0;
foreach_setup(s) {
if (!streq(s->name, "__internal")) {
assert(setup_count == 0); /* Only one setup func per suite */
s->func(&setup);
++setup_count;
}
}
MunitSuite suite = {
"",
tests,
@ -70,10 +121,19 @@ munit_tests_run(int argc, char **argv)
const struct rlimit corelimit = { 0, 0 };
setrlimit(RLIMIT_CORE, &corelimit);
int rc = munit_suite_main(&suite, NULL, argc, argv);
int rc = munit_suite_main(&suite, setup.userdata, setup.argc, setup.argv);
for (idx = 0; idx < count; idx++) {
MunitParameterEnum *parameters = tests[idx].parameters;
while (parameters && parameters->name) {
free(parameters->name);
strv_free(parameters->values);
parameters++;
}
for (idx = 0; idx < count; idx++)
free(tests[idx].name);
free(tests[idx].parameters);
}
return rc;
}

Some files were not shown because too many files have changed in this diff Show more