| /******************************************************************** |
| Copyright 2019 Aleix Pol Gonzalez <aleixpol@kde.org> |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) version 3, or any |
| later version accepted by the membership of KDE e.V. (or its |
| successor approved by the membership of KDE e.V.), which shall |
| act as a proxy defined in Section 6 of version 3 of the license. |
| |
| This library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| *********************************************************************/ |
| #include "tablet_interface.h" |
| #include "display.h" |
| #include "resource_p.h" |
| #include "seat_interface.h" |
| #include "surface_interface.h" |
| |
| #include "qwayland-server-tablet-unstable-v2.h" |
| #include <QHash> |
| |
| using namespace KWayland; |
| using namespace Server; |
| |
| static int s_version = 1; |
| |
| class TabletInterface::Private : public QtWaylandServer::zwp_tablet_v2 |
| { |
| public: |
| Private(TabletInterface* q, uint32_t vendorId, uint32_t productId, const QString name, const QStringList &paths) |
| : zwp_tablet_v2() |
| , q(q) |
| , m_vendorId(vendorId) |
| , m_productId(productId) |
| , m_name(name) |
| , m_paths(paths) |
| { |
| } |
| |
| wl_resource *resourceForSurface(SurfaceInterface *surface) const |
| { |
| ClientConnection *client = surface->client(); |
| QtWaylandServer::zwp_tablet_v2::Resource *r = resourceMap().value(*client); |
| return r ? r->handle : nullptr; |
| } |
| |
| void zwp_tablet_v2_destroy_resource(QtWaylandServer::zwp_tablet_v2::Resource * resource) override |
| { |
| Q_UNUSED(resource); |
| if (removed && resourceMap().isEmpty()) { |
| delete q; |
| } |
| } |
| |
| TabletInterface *const q; |
| const uint32_t m_vendorId; |
| const uint32_t m_productId; |
| const QString m_name; |
| const QStringList m_paths; |
| bool removed = false; |
| }; |
| |
| TabletInterface::TabletInterface(uint32_t vendorId, uint32_t productId, |
| const QString &name, const QStringList &paths, |
| QObject *parent) |
| : QObject(parent) |
| , d(new Private(this, vendorId, productId, name, paths)) |
| { |
| } |
| |
| TabletInterface::~TabletInterface() = default; |
| |
| bool TabletInterface::isSurfaceSupported(SurfaceInterface *surface) const |
| { |
| return d->resourceForSurface(surface); |
| } |
| |
| void TabletInterface::sendRemoved() |
| { |
| d->removed = true; |
| for (QtWaylandServer::zwp_tablet_v2::Resource *resource : d->resourceMap()) { |
| d->send_removed(resource->handle); |
| } |
| } |
| |
| class TabletCursor::Private |
| { |
| public: |
| Private(TabletCursor* q) : q(q) {} |
| |
| void update(quint32 serial, SurfaceInterface *surface, const QPoint &hotspot) |
| { |
| const bool diff = m_serial != serial && m_surface != surface && m_hotspot != hotspot; |
| m_serial = serial; |
| m_surface = surface; |
| m_hotspot = hotspot; |
| if (diff) |
| Q_EMIT q->changed(); |
| } |
| |
| TabletCursor* const q; |
| |
| quint32 m_serial = 0; |
| SurfaceInterface* m_surface = nullptr; |
| QPoint m_hotspot; |
| }; |
| |
| TabletCursor::TabletCursor() |
| : QObject() |
| , d(new Private(this)) |
| { |
| } |
| |
| TabletCursor::~TabletCursor() = default; |
| |
| QPoint TabletCursor::hotspot() const |
| { |
| return d->m_hotspot; |
| } |
| |
| quint32 TabletCursor::enteredSerial() const |
| { |
| return d->m_serial; |
| } |
| |
| SurfaceInterface* TabletCursor::surface() const |
| { |
| return d->m_surface; |
| } |
| |
| class TabletToolInterface::Private : public QtWaylandServer::zwp_tablet_tool_v2 |
| { |
| public: |
| Private(TabletToolInterface* q, Display *display, Type type, uint32_t hsh, uint32_t hsl, uint32_t hih, |
| uint32_t hil, const QVector<Capability>& capabilities) |
| : zwp_tablet_tool_v2() |
| , m_display(display) |
| , m_type(type) |
| , m_hardwareSerialHigh(hsh) |
| , m_hardwareSerialLow(hsl) |
| , m_hardwareIdHigh(hih) |
| , m_hardwareIdLow(hil) |
| , m_capabilities(capabilities) |
| , q(q) |
| { |
| } |
| |
| |
| wl_resource *targetResource() |
| { |
| ClientConnection *client = m_surface->client(); |
| const Resource *r = resourceMap().value(*client); |
| return r ? r->handle : nullptr; |
| } |
| |
| quint64 hardwareId() const |
| { |
| return quint64(quint64(m_hardwareIdHigh) << 32) + m_hardwareIdLow; |
| } |
| quint64 hardwareSerial() const |
| { |
| return quint64(quint64(m_hardwareSerialHigh) << 32) + m_hardwareSerialLow; |
| } |
| |
| void zwp_tablet_tool_v2_bind_resource(QtWaylandServer::zwp_tablet_tool_v2::Resource * resource) override |
| { |
| TabletCursor *&c = m_cursors[resource->handle]; |
| if (!c) |
| c = new TabletCursor; |
| } |
| |
| void zwp_tablet_tool_v2_set_cursor(Resource * resource, uint32_t serial, struct ::wl_resource * _surface, int32_t hotspot_x, int32_t hotspot_y) override |
| { |
| TabletCursor *c = m_cursors[resource->handle]; |
| c->d->update(serial, SurfaceInterface::get(_surface), {hotspot_x, hotspot_y}); |
| if (resource->handle == targetResource()) |
| q->cursorChanged(c); |
| } |
| |
| void zwp_tablet_tool_v2_destroy_resource(Resource * resource) override { |
| delete m_cursors.take(resource->handle); |
| } |
| |
| Display *const m_display; |
| bool m_cleanup = false; |
| QPointer<SurfaceInterface> m_surface; |
| QPointer<TabletInterface> m_lastTablet; |
| const uint32_t m_type; |
| const uint32_t m_hardwareSerialHigh, m_hardwareSerialLow; |
| const uint32_t m_hardwareIdHigh, m_hardwareIdLow; |
| const QVector<Capability> m_capabilities; |
| QHash<wl_resource *, TabletCursor *> m_cursors; |
| TabletToolInterface *const q; |
| }; |
| |
| TabletToolInterface::TabletToolInterface(Display *display, Type type, uint32_t hsh, |
| uint32_t hsl, uint32_t hih, uint32_t hil, |
| const QVector<Capability>& capabilities, |
| QObject *parent) |
| : QObject(parent) |
| , d(new Private(this, display, type, hsh, hsl, hih, hil, capabilities)) |
| { |
| } |
| |
| TabletToolInterface::~TabletToolInterface() = default; |
| |
| void TabletToolInterface::setCurrentSurface(SurfaceInterface *surface) |
| { |
| if (d->m_surface == surface) |
| return; |
| |
| TabletInterface *const lastTablet = d->m_lastTablet; |
| if (d->m_surface && d->resourceMap().contains(*d->m_surface->client())) { |
| sendProximityOut(); |
| sendFrame(0); |
| } |
| |
| d->m_surface = surface; |
| |
| if (lastTablet && lastTablet->d->resourceForSurface(surface)) { |
| sendProximityIn(lastTablet); |
| } else { |
| d->m_lastTablet = lastTablet; |
| } |
| |
| Q_EMIT cursorChanged(d->m_cursors.value(d->targetResource())); |
| } |
| |
| bool TabletToolInterface::isClientSupported() const |
| { |
| return d->m_surface && d->targetResource(); |
| } |
| |
| void TabletToolInterface::sendButton(uint32_t button, bool pressed) |
| { |
| d->send_button(d->targetResource(), d->m_display->nextSerial(), button, |
| pressed ? QtWaylandServer::zwp_tablet_tool_v2::button_state_pressed |
| : QtWaylandServer::zwp_tablet_tool_v2::button_state_released); |
| } |
| |
| void TabletToolInterface::sendMotion(const QPointF &pos) |
| { |
| d->send_motion(d->targetResource(), wl_fixed_from_double(pos.x()), |
| wl_fixed_from_double(pos.y())); |
| } |
| |
| void TabletToolInterface::sendDistance(uint32_t distance) |
| { |
| d->send_distance(d->targetResource(), distance); |
| } |
| |
| void TabletToolInterface::sendFrame(uint32_t time) |
| { |
| d->send_frame(d->targetResource(), time); |
| |
| if (d->m_cleanup) { |
| d->m_surface = nullptr; |
| d->m_lastTablet = nullptr; |
| d->m_cleanup = false; |
| } |
| } |
| |
| void TabletToolInterface::sendPressure(uint32_t pressure) |
| { |
| d->send_pressure(d->targetResource(), pressure); |
| } |
| |
| void TabletToolInterface::sendRotation(qreal rotation) |
| { |
| d->send_rotation(d->targetResource(), wl_fixed_from_double(rotation)); |
| } |
| |
| void TabletToolInterface::sendSlider(int32_t position) |
| { |
| d->send_slider(d->targetResource(), position); |
| } |
| |
| void TabletToolInterface::sendTilt(qreal degreesX, qreal degreesY) |
| { |
| d->send_tilt(d->targetResource(), wl_fixed_from_double(degreesX), |
| wl_fixed_from_double(degreesY)); |
| } |
| |
| void TabletToolInterface::sendWheel(int32_t degrees, int32_t clicks) |
| { |
| d->send_wheel(d->targetResource(), degrees, clicks); |
| } |
| |
| void TabletToolInterface::sendProximityIn(TabletInterface *tablet) |
| { |
| wl_resource* tabletResource = tablet->d->resourceForSurface(d->m_surface); |
| d->send_proximity_in(d->targetResource(), d->m_display->nextSerial(), |
| tabletResource, d->m_surface->resource()); |
| d->m_lastTablet = tablet; |
| } |
| |
| void TabletToolInterface::sendProximityOut() |
| { |
| d->send_proximity_out(d->targetResource()); |
| d->m_cleanup = true; |
| } |
| |
| void TabletToolInterface::sendDown() |
| { |
| d->send_down(d->targetResource(), d->m_display->nextSerial()); |
| } |
| |
| void TabletToolInterface::sendUp() |
| { |
| d->send_up(d->targetResource()); |
| } |
| |
| void TabletToolInterface::sendRemoved() |
| { |
| for (QtWaylandServer::zwp_tablet_tool_v2::Resource *resource : d->resourceMap()) { |
| d->send_removed(resource->handle); |
| } |
| } |
| |
| class TabletSeatInterface::Private : public QtWaylandServer::zwp_tablet_seat_v2 |
| { |
| public: |
| Private(Display *display, TabletSeatInterface *q) |
| : zwp_tablet_seat_v2() |
| , q(q) |
| , m_display(display) |
| { |
| } |
| |
| void zwp_tablet_seat_v2_bind_resource(Resource *resource) override |
| { |
| for (auto iface : qAsConst(m_tablets)) { |
| sendTabletAdded(resource, iface); |
| } |
| |
| for (auto *tool : qAsConst(m_tools)) { |
| sendToolAdded(resource, tool); |
| } |
| } |
| |
| void sendToolAdded(Resource *resource, TabletToolInterface *tool) |
| { |
| wl_resource *toolResource = tool->d->add(resource->client(), resource->version())->handle; |
| send_tool_added(resource->handle, toolResource); |
| |
| tool->d->send_type(toolResource, tool->d->m_type); |
| tool->d->send_hardware_serial(toolResource, tool->d->m_hardwareSerialHigh, |
| tool->d->m_hardwareSerialLow); |
| tool->d->send_hardware_id_wacom(toolResource, tool->d->m_hardwareIdHigh, |
| tool->d->m_hardwareIdLow); |
| for (uint32_t cap : qAsConst(tool->d->m_capabilities)) { |
| tool->d->send_capability(toolResource, cap); |
| } |
| tool->d->send_done(toolResource); |
| } |
| void sendTabletAdded(Resource *resource, TabletInterface *tablet) |
| { |
| wl_resource *tabletResource = tablet->d->add(resource->client(), resource->version())->handle; |
| send_tablet_added(resource->handle, tabletResource); |
| |
| tablet->d->send_name(tabletResource, tablet->d->m_name); |
| if (tablet->d->m_vendorId && tablet->d->m_productId) { |
| tablet->d->send_id(tabletResource, tablet->d->m_vendorId, tablet->d->m_productId); |
| } |
| for (const QString &path : qAsConst(tablet->d->m_paths)) { |
| tablet->d->send_path(tabletResource, path); |
| } |
| tablet->d->send_done(tabletResource); |
| } |
| |
| TabletSeatInterface *const q; |
| QVector<TabletToolInterface *> m_tools; |
| QHash<QString, TabletInterface *> m_tablets; |
| Display *const m_display; |
| }; |
| |
| TabletSeatInterface::TabletSeatInterface(Display *display, QObject *parent) |
| : QObject(parent) |
| , d(new Private(display, this)) |
| { |
| } |
| |
| TabletSeatInterface::~TabletSeatInterface() = default; |
| |
| TabletToolInterface *TabletSeatInterface::addTool(TabletToolInterface::Type type, |
| quint64 hardwareSerial, |
| quint64 hardwareId, |
| const QVector<TabletToolInterface::Capability> &capabilities) |
| { |
| constexpr auto MAX_UINT_32 = std::numeric_limits<quint32>::max(); |
| auto tool = new TabletToolInterface(d->m_display, |
| type, hardwareSerial >> 32, hardwareSerial & MAX_UINT_32, |
| hardwareId >> 32, hardwareId & MAX_UINT_32, capabilities, this); |
| for (QtWaylandServer::zwp_tablet_seat_v2::Resource *resource : d->resourceMap()) { |
| d->sendToolAdded(resource, tool); |
| } |
| |
| d->m_tools.append(tool); |
| QObject::connect(tool, &QObject::destroyed, this, [this](QObject *object) { |
| auto tti = static_cast<TabletToolInterface *>(object); |
| tti->d->send_removed(); |
| d->m_tools.removeAll(tti); |
| }); |
| return tool; |
| } |
| |
| TabletInterface *TabletSeatInterface::addTablet(uint32_t vendorId, uint32_t productId, |
| const QString &sysname, |
| const QString &name, |
| const QStringList &paths) |
| { |
| auto iface = new TabletInterface(vendorId, productId, name, paths, this); |
| |
| for (QtWaylandServer::zwp_tablet_seat_v2::Resource *r : d->resourceMap()) { |
| d->sendTabletAdded(r, iface); |
| } |
| |
| d->m_tablets[sysname] = iface; |
| return iface; |
| } |
| |
| void TabletSeatInterface::removeTablet(const QString &sysname) |
| { |
| auto tablet = d->m_tablets.take(sysname); |
| if (tablet) { |
| tablet->sendRemoved(); |
| } |
| } |
| |
| TabletToolInterface *TabletSeatInterface::toolByHardwareId(quint64 hardwareId) const |
| { |
| for (TabletToolInterface *tool : d->m_tools) { |
| if (tool->d->hardwareId() == hardwareId) |
| return tool; |
| } |
| return nullptr; |
| } |
| |
| TabletToolInterface *TabletSeatInterface::toolByHardwareSerial(quint64 hardwareSerial) const |
| { |
| for (TabletToolInterface *tool : d->m_tools) { |
| if (tool->d->hardwareSerial() == hardwareSerial) |
| return tool; |
| } |
| return nullptr; |
| } |
| |
| TabletInterface *TabletSeatInterface::tabletByName(const QString &name) const |
| { |
| return d->m_tablets.value(name); |
| } |
| |
| class TabletManagerInterface::Private : public QtWaylandServer::zwp_tablet_manager_v2 |
| { |
| public: |
| Private(Display *display, TabletManagerInterface *q) |
| : zwp_tablet_manager_v2(*display, s_version) |
| , q(q) |
| , m_display(display) |
| { |
| } |
| |
| void zwp_tablet_manager_v2_get_tablet_seat(Resource *resource, uint32_t tablet_seat, |
| struct ::wl_resource *seat_resource) override { |
| SeatInterface* seat = SeatInterface::get(seat_resource); |
| TabletSeatInterface *tsi = get(seat); |
| tsi->d->add(resource->client(), tablet_seat, s_version); |
| } |
| |
| TabletSeatInterface *get(SeatInterface *seat) |
| { |
| TabletSeatInterface *&tabletSeat = m_seats[seat]; |
| if (!tabletSeat) { |
| tabletSeat = new TabletSeatInterface(m_display, q); |
| } |
| return tabletSeat; |
| } |
| |
| TabletManagerInterface *const q; |
| Display *const m_display; |
| QHash<SeatInterface *, TabletSeatInterface *> m_seats; |
| }; |
| |
| TabletManagerInterface::TabletManagerInterface(Display *display, QObject *parent) |
| : QObject(parent) |
| , d(new Private(display, this)) |
| { |
| } |
| |
| TabletSeatInterface *TabletManagerInterface::seat(SeatInterface *seat) const |
| { |
| return d->get(seat); |
| } |
| |
| TabletManagerInterface::~TabletManagerInterface() = default; |