diff --git a/.github/actions/web/action.yaml b/.github/actions/web/action.yaml index 005f3448f..7a7b87097 100644 --- a/.github/actions/web/action.yaml +++ b/.github/actions/web/action.yaml @@ -27,6 +27,10 @@ runs: - name: Build website shell: bash run: cd web && make prod + - name: Generate libblight docs + shell: bash + run: | + cd shared/libblight && make prod - name: Generate liboxide docs shell: bash run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5422aba3..e79165477 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,6 +93,8 @@ jobs: with: setup: | set -ex + opkg update + opkg install python3 # needed for reboot-guard wget https://github.com/Eeems-Org/sysfs_preload/releases/download/1.0.0/packages.zip -O /tmp/packages.zip unzip /tmp/packages.zip -d /tmp opkg install /tmp/rmall/sysfs_preload_1.0.0-1_rmall.ipk @@ -103,11 +105,13 @@ jobs: set -ex echo Y | toltecctl generate-opkg-conf opkg update - opkg install oxide + opkg install oxide oxide-tests # systemctl disable --now xochitl # systemctl enable --now tarnish - # TODO install and run tests - opkg remove --force-removal-of-dependent-packages liboxide + find /opt/share/tests -type f | while read -r test;do + $test; + done + opkg remove --force-removal-of-dependent-packages liboxide libblight path: ${{ steps.download.outputs.download-path }} release: needs: [build,test,lint] diff --git a/.github/workflows/web.yaml b/.github/workflows/web.yaml index ef9448ae8..cea4fe2e3 100644 --- a/.github/workflows/web.yaml +++ b/.github/workflows/web.yaml @@ -15,6 +15,7 @@ on: paths: - 'web/**' - 'shared/liboxide/**' + - 'shared/libblight/**' - '.github/actions/web/**' jobs: web: diff --git a/.gitmodules b/.gitmodules index 0196f22e6..94a2f5787 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "shared/liboxide/doxygen-awesome-css"] - path = shared/liboxide/doxygen-awesome-css +[submodule "shared/doxygen-awesome-css"] + path = shared/doxygen-awesome-css url = https://github.com/jothepro/doxygen-awesome-css.git diff --git a/Makefile b/Makefile index 7db25d85e..ed73d6ff0 100644 --- a/Makefile +++ b/Makefile @@ -24,10 +24,14 @@ clean: clean-base rm -rf $(BUILD) release: clean-base build $(DIST) +ifneq ($(filter sentry,$(FEATURES)),) # Force sentry makefile to regenerate so that install targets get when being build in toltecmk cd $(BUILD)/oxide/shared/sentry && make qmake +endif # Force liboxide makefile to regenerate so that install targets get when being build in toltecmk cd $(BUILD)/oxide/shared/liboxide && make qmake + # Force libblight makefile to regenerate so that install targets get when being build in toltecmk + cd $(BUILD)/oxide/shared/libblight && make qmake INSTALL_ROOT=$(DIST) $(MAKE) --output-sync=target -C $(BUILD)/oxide install build: $(OBJ) @@ -96,7 +100,7 @@ $(BUILD)/package/oxide.tar.gz: $(BUILD)/package/package $(PKG_OBJ) oxide.pro \ Makefile -SRC_FILES = $(shell find -name '*.sh' | grep -v shared/sentry) +SRC_FILES = $(shell find -name '*.sh' | grep -v shared/sentry | grep -v shared/doxygen-awesome-css) SRC_FILES += package lint: diff --git a/applications/applications.pro b/applications/applications.pro index 5af838250..fc748b434 100644 --- a/applications/applications.pro +++ b/applications/applications.pro @@ -10,7 +10,6 @@ SUBDIRS = \ lockscreen \ notify-send \ process-manager \ - screenshot-tool \ screenshot-viewer \ settings-manager \ system-service \ @@ -20,13 +19,15 @@ SUBDIRS = \ xdg-desktop-menu \ xdg-icon-resource \ xdg-open \ - xdg-settings + xdg-settings \ + display-server \ + fbinfo \ + xclip launcher.depends = system-service update-desktop-database lockscreen.depends = system-service notify-send.depends = system-service process-manager.depends = -screenshot-tool.depends = system-service screenshot-viewer.depends = system-service settings-manager.depends = system-service system-service.depends = @@ -41,5 +42,7 @@ xdg-icon-resource.depends = system-service desktop-file-edit.depends = desktop-file-install.depends = inject_evdev.depends = - +display-server.depends = system-service +fbinfo.depends = +xclip.depends = system-service INSTALLS += $$SUBDIRS diff --git a/applications/display-server/Surface.qml b/applications/display-server/Surface.qml new file mode 100644 index 000000000..8cc85d12c --- /dev/null +++ b/applications/display-server/Surface.qml @@ -0,0 +1,24 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import codes.eeems.blight 1.0 as Blight + +Item{ + Blight.Surface{ + identifier: parent.objectName + anchors.fill: parent + } + // MouseArea{ + // anchors.fill: parent + // } + // MultiPointTouchArea{ + // anchors.fill: parent + // } + // Keys.onPressed: function(event){ + // console.log(event); + // event.accepted = true; + // } + // Keys.onReleased: function(event){ + // console.log(event); + // event.accepted = true; + // } +} diff --git a/applications/display-server/Workspace.qml b/applications/display-server/Workspace.qml new file mode 100644 index 000000000..ed8df8e1a --- /dev/null +++ b/applications/display-server/Workspace.qml @@ -0,0 +1,25 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 + +ApplicationWindow { + id: workspace + objectName: "Workspace" + width: Screen.width + height: Screen.height + x: 0 + y: 0 + visible: true + function loadComponent(url: string, props): string{ + var comp = Qt.createComponent(url, Component.PreferSynchronous); + if(comp.status === Component.Error){ + return comp.errorString(); + } + return comp.createObject(workspace, props).objectName; + } + background: Rectangle{ + color: "white" + anchors.fill: parent + } + contentData: [] +} diff --git a/applications/display-server/blight-client b/applications/display-server/blight-client new file mode 100755 index 000000000..ae7accb48 --- /dev/null +++ b/applications/display-server/blight-client @@ -0,0 +1,2 @@ +#!/bin/bash +LD_PRELOAD=/opt/lib/libblight_client.so:$LD_PRELOAD RM2FB_SHIM=1 RM2FB_DISABLE=1 exec -a "$1" "$@" diff --git a/applications/display-server/connection.cpp b/applications/display-server/connection.cpp new file mode 100644 index 000000000..27fa74ebd --- /dev/null +++ b/applications/display-server/connection.cpp @@ -0,0 +1,617 @@ +#include "connection.h" +#include "dbusinterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EPAPER +#include "guithread.h" +#include +#endif +#include "surface.h" + +#define C_DEBUG(msg) O_DEBUG("[" << id() << "]" << msg) +#define C_WARNING(msg) O_WARNING("[" << id() << "]" << msg) +#define C_INFO(msg) O_INFO("[" << id() << "]" << msg) + +#ifndef SYS_pidfd_open +# define SYS_pidfd_open 434 +#endif + +static int pidfd_open(pid_t pid, unsigned int flags){ + return syscall(SYS_pidfd_open, pid, flags); +} + +Connection::Connection(pid_t pid, pid_t pgid) +: QObject(), + m_pid{pid}, + m_pgid{pgid}, + m_closed{false}, + pingId{0}, + m_surfaceId{0} +{ + m_pidFd = pidfd_open(m_pid, 0); + if(m_pidFd < 0){ + O_WARNING(std::strerror(errno)); + }else{ + connect(&m_pidNotifier, &QLocalSocket::disconnected, this, &Connection::close); + m_pidNotifier.setSocketDescriptor(m_pidFd, QLocalSocket::ConnectedState, QLocalSocket::ReadOnly); + } + int fds[2]; + if(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1){ + C_WARNING("Unable to open socket pair:" << strerror(errno)); + } + m_clientFd = fds[0]; + m_serverFd = fds[1]; + m_notifier = new QSocketNotifier(m_serverFd, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, &Connection::readSocket); + + if(::socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == -1){ + C_WARNING("Unable to open input socket pair:" << strerror(errno)); + } + m_clientInputFd = fds[0]; + m_serverInputFd = fds[1]; + + + m_pingTimer.setTimerType(Qt::PreciseTimer); + m_pingTimer.setInterval(1000); + m_pingTimer.setSingleShot(true); + connect(&m_pingTimer, &QTimer::timeout, this, &Connection::ping); + m_pingTimer.start(); + + m_notRespondingTimer.setTimerType(Qt::PreciseTimer); + m_notRespondingTimer.setInterval(m_pingTimer.interval() * 2); + m_notRespondingTimer.setSingleShot(true); + connect(&m_notRespondingTimer, &QTimer::timeout, this, &Connection::notResponding); + m_notRespondingTimer.start(); + + C_INFO("Connection created"); +} + +Connection::~Connection(){ + close(); + surfaces.clear(); + processRemovedSurfaces(); + if(m_notifier != nullptr){ + m_notifier->deleteLater(); + m_notifier = nullptr; + } + ::close(m_clientFd); + ::close(m_serverFd); + ::close(m_pidFd); + C_INFO("Connection destroyed"); +} + +void Connection::processRemovedSurfaces(){ + removedMutex.lock(); + if(!removedSurfaces.empty()){ + C_DEBUG("Cleaning up old surfaces"); + removedSurfaces.clear(); + } + removedMutex.unlock(); +} + +QString Connection::id(){ return QString("connection/%1").arg(m_pid); } + +pid_t Connection::pid() const{ return m_pid; } + +pid_t Connection::pgid() const{ return m_pgid; } + +int Connection::socketDescriptor(){ return m_clientFd; } + +int Connection::inputSocketDescriptor(){ return m_clientInputFd; } + +bool Connection::isValid(){ return m_clientFd > 0 && m_serverFd > 0 && isRunning(); } + +bool Connection::isRunning(){ return getpgid(m_pid) != -1; } + +bool Connection::isStopped(){ + QFile file(QString("/proc/%1/stat").arg(m_pid)); + if(!file.open(QFile::ReadOnly)){ + O_WARNING("Failed checking process status" << file.errorString()); + return false; + } + return file.readAll().split(' ')[2] == "T"; +} + +bool Connection::signal(int signal){ + if(!isRunning()){ + errno = ESRCH; + return false; + } + return ::kill(m_pid, signal) != -1; +} + +bool Connection::signalGroup(int signal){ + if(!isRunning()){ + errno = ESRCH; + return false; + } + return ::kill(-m_pid, signal) != -1; +} + +void Connection::pause(){ + // TODO - Send pause request over socket instead + QElapsedTimer timer; + bool replied = false; + auto conn = connect(signalHandler, &Oxide::SignalHandler::sigUsr2, [&timer, &replied]{ + replied = true; + timer.invalidate(); + }); + signalGroup(SIGUSR2); + timer.start(); + while(timer.isValid() && !timer.hasExpired(1000)){ + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + } + signalHandler->disconnect(conn); + if(replied){ + return; + } + signalGroup(SIGSTOP); + siginfo_t info; + waitid(P_PID, m_pid, &info, WSTOPPED); +} + +void Connection::resume(){ + // TODO - Send resume request over socket instead + QElapsedTimer timer; + bool replied = false; + auto conn = connect(signalHandler, &Oxide::SignalHandler::sigUsr1, [&timer, &replied]{ + replied = true; + timer.invalidate(); + }); + signalGroup(SIGUSR1); + timer.start(); + while(timer.isValid() && !timer.hasExpired(1000)){ + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + } + signalHandler->disconnect(conn); + if(replied){ + return; + } + signalGroup(SIGCONT); + siginfo_t info; + waitid(P_PID, m_pid, &info, WCONTINUED); +} + +void Connection::close(){ + if(!m_closed.test_and_set()){ + m_pingTimer.stop(); + m_notRespondingTimer.stop(); + for(auto& item : surfaces){ + item.second->removed(); + } + emit finished(); + } +} + +std::shared_ptr Connection::addSurface(int fd, QRect geometry, int stride, QImage::Format format){ + // TODO - add validation that id is never 0, and that it doesn't point to an existsing surface + auto id = ++m_surfaceId; + auto surface = std::shared_ptr(new Surface(this, fd, id, geometry, stride, format)); + if(!surface->isValid()){ + return surface; + } + surfaces.insert_or_assign(id, surface); + dbusInterface->sortZ(); + surface->repaint(); + return surface; +} + +std::shared_ptr Connection::getSurface(QString identifier){ + for(auto& item: surfaces){ + auto& surface = item.second; + if(surface == nullptr){ + continue; + } + if(surface->id() == identifier){ + return surface; + } + } + return nullptr; +} + +std::shared_ptr Connection::getSurface(Blight::surface_id_t id){ + if(!surfaces.contains(id)){ + return nullptr; + } + return surfaces[id]; +} + +QStringList Connection::getSurfaceIdentifiers(){ + QStringList identifiers; + for(auto& item: surfaces){ + auto& surface = item.second; + if(surface == nullptr){ + continue; + } + identifiers.append(surface->id()); + } + return identifiers; +} + +const QList> Connection::getSurfaces(){ + QList> values; + for(auto& item: surfaces){ + auto& surface = item.second; + if(surface != nullptr){ + values.append(surface); + } + } + return values; +} + +void Connection::inputEvents(unsigned int device, const std::vector& events){ + if(!isRunning() || isStopped()){ + dbusInterface->sortZ(); + return; + } + // TODO - Allow non-blocking send. + // If the client isn't reading input, the buffer will fill up, and then the entire + // display server will freeze until the client reads it's first event. + // It's probably worth adding some sort of acking mechanism to this to ensure that + // events are being read, as well as don't send anything until the client tells you + // that it's ready to start reading events. + C_DEBUG("Writing" << events.size() << "input events"); + std::vector data(events.size()); + for(unsigned int i = 0; i < events.size(); i++){ + auto& ie = events[i]; + auto& packet = data[i]; + packet.device = device; + auto& event = packet.event; + event.type = ie.type; + event.code = ie.code; + event.value = ie.value; + } + if(!Blight::send_blocking( + m_serverInputFd, + reinterpret_cast(data.data()), + sizeof(Blight::event_packet_t) * data.size() + )){ + C_WARNING("Failed to write input event: " << std::strerror(errno)); + }else{ + C_DEBUG("Write finished"); + } +} + +bool Connection::has(const QString& flag){ return flags.contains(flag); } + +void Connection::set(const QString& flag){ + if(!has(flag)){ + flags.append(flag); + } +} + +void Connection::unset(const QString& flag){ flags.removeAll(flag); } + +void Connection::readSocket(){ + if(m_notifier == nullptr){ + C_DEBUG("Connection already closed, discarding data"); + return; + } + m_notifier->setEnabled(false); +#ifdef ACK_DEBUG + C_DEBUG("Data received"); +#endif + while(true){ + auto message = Blight::message_t::from_socket(m_serverFd); + if(message->header.type == Blight::MessageType::Invalid){ + break; + } +#ifndef ACK_DEBUG + if( + message->header.type != Blight::MessageType::Ping + && message->header.type != Blight::MessageType::Ack + ){ +#endif + C_DEBUG( + "Handling message:" + << "type=" << message->header.type + << ", ackid=" << message->header.ackid + << ", size=" << message->header.size + << ", socket=" << m_serverFd + ); +#ifndef ACK_DEBUG + } +#endif + bool do_ack = true; + unsigned int ack_size = 0; + Blight::data_t ack_data = nullptr; + std::function ack_free = NULL; + switch(message->header.type){ + case Blight::MessageType::Repaint:{ + auto repaint = Blight::repaint_t::from_message(message.get()); + C_DEBUG( + "Repaint requested:" + << QString("%6 (%1,%2) %3x%4 %5 %7") + .arg(repaint.x) + .arg(repaint.y) + .arg(repaint.width) + .arg(repaint.height) + .arg(repaint.waveform) + .arg(repaint.identifier) + .arg(repaint.marker) + .toStdString() + .c_str() + ); + auto surface = getSurface(repaint.identifier); + if(surface == nullptr){ + C_WARNING("Could not find surface" << repaint.identifier); + break; + } + QRect rect( + repaint.x, + repaint.y, + repaint.width, + repaint.height + ); +#ifdef EPAPER + guiThread->enqueue( + surface, + rect, + repaint.waveform, + repaint.marker, + false, + [message, this]{ ack(message, 0, nullptr); } + ); + do_ack = false; +#else + emit surface->update(rect); +#endif + break; + } + case Blight::MessageType::Move:{ + auto move = Blight::move_t::from_message(message.get()); + C_DEBUG( + "Move requested:" + << QString("%3 (%1,%2)") + .arg(move.x) + .arg(move.y) + .arg(move.identifier) + .toStdString() + .c_str() + ); + auto surface = getSurface(move.identifier); + if(surface == nullptr){ + C_WARNING("Could not find surface" << move.identifier); + break; + } +#ifdef EPAPER + auto rect = surface->geometry(); + surface->move(move.x, move.y); + guiThread->enqueue( + surface, + surface->rect(), + Blight::HighQualityGrayscale, + message->header.ackid, + false, + [message, this]{ ack(message, 0, nullptr); } + ); + guiThread->enqueue( + nullptr, + rect, + Blight::HighQualityGrayscale, + message->header.ackid, + true, + nullptr + ); + do_ack = false; +#else + surface->move(move.header.x, move.header.y); +#endif + break; + } + case Blight::MessageType::Info:{ + auto identifier = (Blight::surface_id_t)*message->data.get(); + C_DEBUG("Info requested:" << identifier); + if(!surfaces.contains(identifier)){ + C_WARNING("Could not find surface" << identifier); + break; + } + auto surface = surfaces[identifier]; + auto geometry = surface->geometry(); + ack_data = (Blight::data_t)new Blight::surface_info_t{ + .x = geometry.x(), + .y = geometry.y(), + .width = geometry.width(), + .height = geometry.height(), + .stride = surface->stride(), + .format = (Blight::Format)surface->format(), + }; + ack_size = sizeof(Blight::surface_info_t); + break; + } + case Blight::MessageType::Delete:{ + auto identifier = (Blight::surface_id_t)*message->data.get(); + C_DEBUG("Delete requested:" << identifier); + if(!surfaces.contains(identifier)){ + C_WARNING("Could not find surface" << identifier); + break; + } + removedMutex.lock(); + auto surface = surfaces[identifier]; + surface->removed(); + removedSurfaces.push_back(surface); + removedMutex.unlock(); + surfaces.erase(identifier); + guiThread->notify(); + break; + } + case Blight::MessageType::List:{ + C_DEBUG("List requested"); + std::vector list; + for(auto& item: surfaces){ + if(item.second != nullptr){ + list.push_back(item.first); + } + } + ack_size = list.size() * sizeof(decltype(list)::value_type); + ack_data = (Blight::data_t)malloc(ack_size); + memcpy(ack_data, list.data(), ack_size); + ack_free = [&ack_data]{ free(ack_data); }; + break; + } + case Blight::MessageType::Raise:{ + auto identifier = (Blight::surface_id_t)*message->data.get(); + C_DEBUG("Raise requested:" << identifier); + if(!surfaces.contains(identifier)){ + C_WARNING("Could not find surface" << identifier); + break; + } + auto surface = surfaces[identifier]; + surface->setVisible(true); + surface->setZ(std::numeric_limits::max()); + dbusInterface->sortZ(); +#ifdef EPAPER + guiThread->enqueue( + surface, + surface->rect(), + Blight::HighQualityGrayscale, + message->header.ackid, + false, + [message, this]{ ack(message, 0, nullptr); } + ); + do_ack = false; +#endif + break; + } + case Blight::MessageType::Lower:{ + auto identifier = (Blight::surface_id_t)*message->data.get(); + C_DEBUG("Lower requested:" << identifier); + if(!surfaces.contains(identifier)){ + C_WARNING("Could not find surface" << identifier); + break; + } + auto surface = surfaces[identifier]; + surface->setVisible(false); + dbusInterface->sortZ(); +#ifdef EPAPER + guiThread->enqueue( + nullptr, + surface->geometry(), + Blight::HighQualityGrayscale, + message->header.ackid, + true, + [message, this]{ ack(message, 0, nullptr); } + ); + do_ack = false; +#endif + break; + } + case Blight::MessageType::Wait:{ +#ifdef EPAPER + auto marker = (unsigned int)*message->data.get(); + C_DEBUG("Wait requested:" << marker); + mxcfb_update_marker_data data{ marker, 0 }; + ioctl(guiThread->framebuffer(), MXCFB_WAIT_FOR_UPDATE_COMPLETE, &data); +#endif + break; + } + case Blight::MessageType::Focus:{ + C_DEBUG("Focus requested"); + dbusInterface->setFocus(this); + break; + } + case Blight::MessageType::Ping:{ +#ifdef ACK_DEBUG + O_DEBUG("Pong" << message->header.ackid); +#endif + break; + } + case Blight::MessageType::Ack: + do_ack = false; + if(message->header.ackid == pingId){ +#ifdef ACK_DEBUG + O_DEBUG("Pong recieved" << message->header.ackid); +#endif + m_notRespondingTimer.stop(); + m_pingTimer.stop(); + m_pingTimer.start(); + m_notRespondingTimer.start(); + break; + } + C_WARNING("Unexpected ack from client" << message->header.ackid); + break; + default: + C_WARNING("Unexpected message type" << message->header.type); + } + if(ack_size && ack_data == nullptr){ + C_WARNING("Ack expected data, but none sent"); + ack_size = 0; + } + if(do_ack){ + ack(message, ack_size, ack_data); + } + if(ack_free){ + ack_free(); + }else if(ack_data != nullptr){ + delete ack_data; + } + }; + m_notifier->setEnabled(true); +} + +void Connection::notResponding(){ + if(!isRunning()){ + close(); + m_pingTimer.stop(); + return; + } + if(!isStopped()){ + C_WARNING("Connection failed to respond to ping in time:" << id()); + }else if(this == dbusInterface->focused()){ + dbusInterface->sortZ(); + } + m_notRespondingTimer.start(); +} + +void Connection::ack(Blight::message_ptr_t message, unsigned int size, Blight::data_t data){ + auto ack = Blight::message_t::create_ack(message.get(), size); + if(!Blight::send_blocking( + m_serverFd, + reinterpret_cast(&ack), + sizeof(Blight::header_t) + )){ + C_WARNING("Failed to write ack header to socket:" << strerror(errno)); + }else if(size && !Blight::send_blocking(m_serverFd, data, size)){ + C_WARNING("Failed to write ack data to socket:" << strerror(errno)); + }else{ + C_DEBUG("Acked:" << ack.ackid); + } +} + +void Connection::ping(){ + if(!isRunning()){ + return; + } + if(!isStopped()){ + Blight::header_t ping{ + .type = Blight::MessageType::Ping, + .ackid = ++pingId, + .size = 0 + }; + if(!Blight::send_blocking( + m_serverFd, + reinterpret_cast(&ping), + sizeof(Blight::header_t) + )){ + C_WARNING("Failed to write to socket:" << strerror(errno)); + }else{ +#ifdef ACK_DEBUG + C_DEBUG("Ping" << ping.ackid); +#endif + } + } + m_pingTimer.start(); +} +#include "moc_connection.cpp" diff --git a/applications/display-server/connection.h b/applications/display-server/connection.h new file mode 100644 index 000000000..24be18a89 --- /dev/null +++ b/applications/display-server/connection.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "surface.h" +class Surface; + +#include "../../shared/liboxide/meta.h" + +class Connection : public QObject { + Q_OBJECT + Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) + +public: + Connection(pid_t pid, pid_t pgid); + ~Connection(); + + void processRemovedSurfaces(); + QString id(); + pid_t pid() const; + pid_t pgid() const; + int socketDescriptor(); + int inputSocketDescriptor(); + bool isValid(); + bool isRunning(); + bool isStopped(); + bool signal(int signal); + bool signalGroup(int signal); + void pause(); + void resume(); + std::shared_ptr addSurface(int fd, QRect geometry, int stride, QImage::Format format); + std::shared_ptr getSurface(QString identifier); + std::shared_ptr getSurface(Blight::surface_id_t id); + QStringList getSurfaceIdentifiers(); + const QList> getSurfaces(); + void inputEvents(unsigned int device, const std::vector& events); + bool has(const QString& flag); + void set(const QString& flag); + void unset(const QString& flag); + +signals: + void finished(); + void focused(); + +public slots: + void close(); + +private slots: + void readSocket(); + void notResponding(); + void ping(); + +private: + pid_t m_pid; + pid_t m_pgid; + int m_pidFd; + QFile m_process; + int m_clientFd; + int m_serverFd; + int m_clientInputFd; + int m_serverInputFd; + QSocketNotifier* m_notifier; + QLocalSocket m_pidNotifier; + std::map> surfaces; + QMutex removedMutex; + std::vector> removedSurfaces; + std::atomic_flag m_closed; + QTimer m_notRespondingTimer; + QTimer m_pingTimer; + std::atomic_uint pingId; + std::atomic_ushort m_surfaceId; + QStringList flags; + + void ack(Blight::message_ptr_t message, unsigned int size, Blight::data_t data); +}; diff --git a/applications/display-server/dbusinterface.cpp b/applications/display-server/dbusinterface.cpp new file mode 100644 index 000000000..d0e884e55 --- /dev/null +++ b/applications/display-server/dbusinterface.cpp @@ -0,0 +1,660 @@ +#include "dbusinterface.h" +#include "evdevhandler.h" +#include "guithread.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef EPAPER +#include +#endif + +DbusInterface::DbusInterface(QObject* parent) +: QObject(parent), + m_focused(nullptr), + m_exlusiveMode{false} +{ +#ifdef EPAPER + guiThread; +#else + engine.load(QUrl(QStringLiteral("qrc:/Workspace.qml"))); + if(engine.rootObjects().isEmpty()){ + qFatal("Failed to load main layout"); + } +#endif + // Needed to trick QtDBus into exposing the object + setProperty("__init__", true); +#ifdef EPAPER + auto bus = QDBusConnection::systemBus(); +#else + auto bus = QDBusConnection::sessionBus(); +#endif + if(!bus.isConnected()){ + qFatal("Failed to connect to system bus."); + } + QDBusConnectionInterface* interface = bus.interface(); + if(!bus.registerObject( + "/", + this, + QDBusConnection::ExportAllContents | QDBusConnection::ExportChildObjects + )){ + qFatal("Unable to register interface: %s", bus.lastError().message().toStdString().c_str()); + } + if(!bus.objectRegisteredAt("/")){ + qFatal("Object not appearing!"); + } + auto reply = interface->registerService(BLIGHT_SERVICE); + bus.registerService(BLIGHT_SERVICE); + if(!reply.isValid()){ + QDBusError ex = reply.error(); + qFatal("Unable to register service: %s", ex.message().toStdString().c_str()); + } + connect( + interface, + &QDBusConnectionInterface::serviceOwnerChanged, + this, + &DbusInterface::serviceOwnerChanged + ); + qRegisterMetaType>("std::vector"); + O_DEBUG("Connected service to bus"); +} + +DbusInterface* DbusInterface::singleton(){ + static DbusInterface* instance = nullptr; + if(instance == nullptr){ + instance = Oxide::dispatchToMainThread([]{ + O_DEBUG("Initializing DBus interface"); + return new DbusInterface(qApp); + }); + } + return instance; +} + +int DbusInterface::pid(){ return qApp->applicationPid(); } + +#ifndef EPAPER +QObject* DbusInterface::loadComponent(QString url, QString identifier, QVariantMap properties){ + auto object = engine.findChild(identifier); + if(object != nullptr){ + for(auto i = properties.cbegin(), end = properties.cend(); i != end; ++i){ + object->setProperty(i.key().toStdString().c_str(), i.value()); + } + return object; + } + properties["objectName"] = properties["id"] = identifier; + auto root = workspace(); + if(root == nullptr){ + O_WARNING("Failed to get workspace"); + return nullptr; + } + QString objectName; + if(!QMetaObject::invokeMethod( + root, + "loadComponent", + Q_RETURN_ARG(QString, objectName), + Q_ARG(QString, url), + Q_ARG(QVariant, QVariant::fromValue(properties)) + )){ + O_WARNING("Failed to queue loadComponent call"); + return nullptr; + } + if(objectName != identifier){ + O_WARNING(objectName); + return nullptr; + } + object = root->findChild(objectName); + if(object == nullptr){ + O_WARNING("Component" << objectName << "missing!"); + } + O_DEBUG("Created component" << objectName); + return object; +} +#endif + +void DbusInterface::processClosingConnections(){ + closingMutex.lock(); + if(!closingConnections.isEmpty()){ + O_DEBUG("Cleaning up old connections"); + while(!closingConnections.isEmpty()){ + closingConnections.takeFirst()->deleteLater(); + } + } + closingMutex.unlock(); +} + +void DbusInterface::processRemovedSurfaces(){ + for(auto connection : qAsConst(connections)){ + connection->processRemovedSurfaces(); + } +} + +std::shared_ptr DbusInterface::getSurface(QString identifier){ + for(auto connection : qAsConst(connections)){ + if(!connection->isRunning()){ + continue; + } + auto surface = connection->getSurface(identifier); + if(surface != nullptr){ + return surface; + } + } + return nullptr; +} + +QDBusUnixFileDescriptor DbusInterface::open(QDBusMessage message){ + pid_t pid = connection().interface()->servicePid(message.service()); + pid_t pgid = ::getpgid(pid); + O_INFO("Open connection for: " << pid << pgid); + auto connection = getConnection(message); + if(connection != nullptr){ + O_DEBUG("Found existing for: " << connection->pid()); + return QDBusUnixFileDescriptor(connection->socketDescriptor()); + } + connection = createConnection(pid); + if(connection == nullptr){ + sendErrorReply(QDBusError::InternalError, "Unable to create connection"); + return QDBusUnixFileDescriptor(); + } + O_DEBUG("success" << connection->socketDescriptor()); + return QDBusUnixFileDescriptor(connection->socketDescriptor()); +} + +QDBusUnixFileDescriptor DbusInterface::openInput(QDBusMessage message){ + auto connection = getConnection(message); + if(connection == nullptr){ + sendErrorReply(QDBusError::AccessDenied, "You must first open a connection"); + return QDBusUnixFileDescriptor(); + } + O_INFO("Open input for: " << connection->pid()); + return QDBusUnixFileDescriptor(connection->inputSocketDescriptor()); +} + +Blight::surface_id_t DbusInterface::addSurface( + QDBusUnixFileDescriptor fd, + int x, + int y, + int width, + int height, + int stride, + int format, + QDBusMessage message +){ + if(!fd.isValid()){ + sendErrorReply(QDBusError::InvalidArgs, "Invalid file descriptor"); + return 0; + } + auto connection = getConnection(message); + if(connection == nullptr){ + sendErrorReply(QDBusError::AccessDenied, "You must first open a connection"); + return 0; + } + auto dfd = dup(fd.fileDescriptor()); + if(dfd == -1){ + sendErrorReply(QDBusError::InternalError, strerror(errno)); + return 0; + } + auto surface = connection->addSurface( + dfd, + QRect(x, y, width, height), + stride, + (QImage::Format)format + ); + if(surface == nullptr){ + sendErrorReply(QDBusError::InternalError, "Unable to create surface"); + return 0; + } + if(!surface->isValid()){ + sendErrorReply(QDBusError::InternalError, "Unable to create surface"); + return 0; + } + return surface->identifier(); +} + +void DbusInterface::repaint(QString identifier, QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + auto surface = getSurface(identifier); + if(surface == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Surface not found"); + return; + } + surface->repaint(); +} + +QDBusUnixFileDescriptor DbusInterface::getSurface(Blight::surface_id_t identifier, QDBusMessage message){ + auto connection = getConnection(message); + if(connection == nullptr){ + sendErrorReply(QDBusError::AccessDenied, "You must first open a connection"); + return QDBusUnixFileDescriptor(); + } + auto surface = connection->getSurface(identifier); + if(surface == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Surface not found"); + return QDBusUnixFileDescriptor(); + } + return QDBusUnixFileDescriptor(surface->fd()); +} + +void DbusInterface::setFlags(QString identifier, const QStringList& flags, QDBusMessage message){ + if(connections.count() > 1){ + // Only validate system flag if there is more than one connection + // TODO - also validate that executable is allowed to make this call + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + } + auto connection = getConnection(identifier); + if(connection != nullptr){ + for(auto& flag : flags){ + O_DEBUG("Flag" << flag << "set for connection" << identifier); + connection->set(flag); + } + sortZ(); + return; + } + auto surface = getSurface(identifier); + if(surface == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Connection or Surface not found"); + return; + } + for(auto& flag : flags){ + surface->set(flag); + } + sortZ(); +} + +QStringList DbusInterface::getSurfaces(QDBusMessage message){ + auto connection = getConnection(message); + QStringList surfaces; + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return surfaces; + } + for(auto connection : qAsConst(connections)){ + if(!connection->isRunning()){ + continue; + } + for(auto& surface : connection->getSurfaces()){ + if(surface != nullptr){ + surfaces.append(surface->id()); + } + } + } + return surfaces; +} + +QDBusUnixFileDescriptor DbusInterface::frameBuffer(QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return QDBusUnixFileDescriptor(); + } + return QDBusUnixFileDescriptor(guiThread->framebuffer()); +} + +void DbusInterface::lower(QString identifier, QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + auto surface = getSurface(identifier); + if(surface != nullptr){ + surface->setVisible(false); + sortZ(); + surface->repaint(); + return; + } + auto childConnection = getConnection(identifier); + if(childConnection == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Connection or Surface not found"); + return; + } + if(m_focused == childConnection){ + setFocus(nullptr); + } + for(auto& surface : childConnection->getSurfaces()){ + surface->setVisible(false); + surface->repaint(); + } + sortZ(); +} + +void DbusInterface::raise(QString identifier, QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + auto surface = getSurface(identifier); + if(surface != nullptr){ + surface->setVisible(true); + surface->setZ(std::numeric_limits::max()); + sortZ(); + surface->repaint(); + return; + } + auto childConnection = getConnection(identifier); + if(childConnection == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Connection or Surface not found"); + return; + } + for(auto& surface : childConnection->getSurfaces()){ + surface->setVisible(true); + surface->setZ(std::numeric_limits::max()); + surface->repaint(); + } + sortZ(); +} + +void DbusInterface::focus(QString identifier, QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + auto childConnection = getConnection(identifier); + if(childConnection == nullptr){ + sendErrorReply(QDBusError::BadAddress, "Connection not found"); + return; + } + setFocus(childConnection); +} + +void DbusInterface::waitForNoRepaints(QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } +#ifdef EPAPER + QEventLoop loop; + connect(guiThread, &GUIThread::settled, &loop, &QEventLoop::quit); + loop.exec(); +#endif +} + +void DbusInterface::enterExclusiveMode(QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + O_INFO("Entering exclusive mode"); + m_exlusiveMode = true; + waitForNoRepaints(message); + evdevHandler->clear_buffers(); +} + +void DbusInterface::exitExclusiveMode(QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } + O_INFO("Exiting exclusive mode"); + m_exlusiveMode = false; +#ifdef EPAPER + guiThread->enqueue( + nullptr, + EPFrameBuffer::instance()->framebuffer()->rect(), + Blight::HighQualityGrayscale, + 0, + true + ); +#endif + waitForNoRepaints(message); +} + +void DbusInterface::exclusiveModeRepaint(QDBusMessage message){ + auto connection = getConnection(message); + if(!connection->has("system")){ + sendErrorReply(QDBusError::AccessDenied, "Must be system connection"); + return; + } +#ifdef EPAPER + guiThread->sendUpdate( + EPFrameBuffer::instance()->framebuffer()->rect(), + Blight::HighQualityGrayscale, + 0 + ); +#endif +} + +Connection* DbusInterface::focused(){ return m_focused; } + +void DbusInterface::setFocus(Connection* connection){ + m_focused = connection; + if(m_focused != nullptr){ + O_INFO(m_focused->id() << "has focus"); + }else{ + O_INFO("Nothing is in focus"); + } +} + +void DbusInterface::serviceOwnerChanged(const QString& name, const QString& oldOwner, const QString& newOwner){ + Q_UNUSED(oldOwner); + if(!newOwner.isEmpty()){ + return; + } + Q_UNUSED(name); + // TODO - keep track of things this name owns and remove them +} + +void DbusInterface::inputEvents(unsigned int device, const std::vector& events){ + if(m_focused != nullptr){ + m_focused->inputEvents(device, events); + } + for(auto connection : qAsConst(connections)){ + if(connection->has("system")){ + connection->inputEvents(device, events); + } + } +} + +bool DbusInterface::inExclusiveMode(){ return m_exlusiveMode; } + +Connection* DbusInterface::getConnection(QDBusMessage message){ + pid_t pid = connection().interface()->servicePid(message.service());; + for(auto connection : qAsConst(connections)){ + if(!connection->isRunning()){ + continue; + } + if(connection->pid() == pid){ + return connection; + } + } + return nullptr; +} + +Connection* DbusInterface::getConnection(QString identifier){ + for(auto connection : qAsConst(connections)){ + if(connection->id() == identifier){ + return connection; + } + } + return nullptr; +} + +QObject* DbusInterface::workspace(){ + QListIterator i(engine.rootObjects()); + while(i.hasNext()){ + auto item = i.next(); + if(item->objectName() == "Workspace"){ + O_DEBUG("Found workspace"); + return item; + } + } + O_WARNING("Failed to get workspace"); + return nullptr; +} + +Connection* DbusInterface::createConnection(int pid){ + pid_t pgid = ::getpgid(pid); + auto connection = new Connection(pid, pgid); + if(!connection->isValid()){ + connection->deleteLater(); + return nullptr; + } + connect(connection, &Connection::finished, this, [this, connection]{ + O_INFO("Connection" << connection->pid() << "closed"); + auto found = false; + for(auto& ptr : qAsConst(connections)){ + if(ptr == connection){ + found = true; + connections.removeAll(ptr); + closingMutex.lock(); + closingConnections.append(ptr); + closingMutex.unlock(); + guiThread->notify(); + break; + } + } + if(!found){ + O_WARNING("Could not find connection to remove!"); + } + if(m_focused != nullptr && m_focused == connection){ + setFocus(nullptr); + } + sortZ(); + }); + connect(connection, &Connection::focused, this, [this, connection]{ + for(auto& ptr : qAsConst(connections)){ + if(ptr == connection && !ptr->has("system")){ + setFocus(ptr); + break; + } + } + }); + connections.append(connection); + return connection; +} + +QList> DbusInterface::surfaces(){ + QList> surfaces; + for(auto& connection : connections){ + for(auto& surface : connection->getSurfaces()){ + surfaces.append(surface); + } + } + return surfaces; +} + +QList> DbusInterface::sortedSurfaces(){ + auto sorted = surfaces().toVector(); + std::sort( + sorted.begin(), + sorted.end(), + [](std::shared_ptr surface0, std::shared_ptr surface1){ + if(surface0 == nullptr || !surface0->visible()){ + return false; + } + if(surface1 == nullptr || !surface1->visible()){ + return true; + } + if(surface0->has("system")){ + return false; + } + if(surface1->has("system")){ + return true; + } + return surface0->z() < surface1->z(); + } + ); + return QList>::fromVector(sorted); +} + +QList> DbusInterface::visibleSurfaces(){ + QList> surfaces; + for(auto& surface : sortedSurfaces()){ + auto connection = surface->connection(); + if( + surface->visible() + && connection->isRunning() + ){ + surfaces.append(surface); + } + } + return surfaces; +} + +const QByteArray& DbusInterface::clipboard(){ return clipboards.clipboard; } + +void DbusInterface::setClipboard(const QByteArray& data){ + clipboards.clipboard = data; + emit clipboardChanged(clipboard()); +} + +const QByteArray& DbusInterface::selection(){ return clipboards.selection; } + +void DbusInterface::setSelection(const QByteArray& data){ + clipboards.selection = data; + emit selectionChanged(selection()); +} + +const QByteArray& DbusInterface::secondary(){ return clipboards.secondary; } + +void DbusInterface::setSecondary(const QByteArray& data){ + clipboards.secondary = data; + emit clipboardChanged(secondary()); +} + +void DbusInterface::sortZ(){ + auto sorted = sortedSurfaces(); + int z = 0; + for(auto surface : sorted){ + if(surface == nullptr){ + continue; + } + surface->setZ(z++); + } + if( + m_focused != nullptr + && ( + !m_focused->isRunning() + || m_focused->isStopped() + ) + ){ + setFocus(nullptr); + }else if(m_focused != nullptr || sorted.empty()){ + return; + } + std::reverse(sorted.begin(), sorted.end()); + for(auto& surface : sorted){ + if(surface == nullptr){ + continue; + } + auto connection = surface->connection(); + if( + !connection->isRunning() + || connection->isStopped() + || connection->has("system") + ){ + continue; + } + for(auto& ptr : connections){ + if(ptr == connection){ + setFocus(ptr); + break; + } + } + break; + } +} + +#include "moc_dbusinterface.cpp" diff --git a/applications/display-server/dbusinterface.h b/applications/display-server/dbusinterface.h new file mode 100644 index 000000000..0126abf5e --- /dev/null +++ b/applications/display-server/dbusinterface.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "connection.h" + +// Use direct values to make sure that cpp2xml works +#include "../../shared/liboxide/meta.h" +#include "../../shared/libblight/meta.h" + +#define dbusInterface DbusInterface::singleton() + +class DbusInterface : public QObject, public QDBusContext { + Q_OBJECT + Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) + Q_CLASSINFO("D-Bus Interface", BLIGHT_INTERFACE) + Q_PROPERTY(int pid READ pid CONSTANT) + Q_PROPERTY(QByteArray clipboard READ clipboard WRITE setClipboard NOTIFY clipboardChanged) + Q_PROPERTY(QByteArray selection READ selection WRITE setSelection NOTIFY selectionChanged) + Q_PROPERTY(QByteArray secondary READ secondary WRITE setSecondary NOTIFY secondaryChanged) + +public: + static DbusInterface* singleton(); + + int pid(); +#ifndef EPAPER + QObject* loadComponent(QString url, QString identifier, QVariantMap properties = QVariantMap()); +#endif + void processClosingConnections(); + void processRemovedSurfaces(); + std::shared_ptr getSurface(QString identifier); + QList> surfaces(); + QList> sortedSurfaces(); + QList> visibleSurfaces(); + void sortZ(); + Connection* focused(); + void setFocus(Connection* connection); + void inputEvents(unsigned int device, const std::vector& events); + bool inExclusiveMode(); + + // Property getter/setters + const QByteArray& clipboard(); + void setClipboard(const QByteArray& data); + const QByteArray& selection(); + void setSelection(const QByteArray& data); + const QByteArray& secondary(); + void setSecondary(const QByteArray& data); + +public slots: + QDBusUnixFileDescriptor open(QDBusMessage message); + QDBusUnixFileDescriptor openInput(QDBusMessage message); + ushort addSurface( + QDBusUnixFileDescriptor fd, + int x, + int y, + int width, + int height, + int stride, + int format, + QDBusMessage message + ); + void repaint(QString identifier, QDBusMessage message); + QDBusUnixFileDescriptor getSurface(ushort identifier, QDBusMessage message); + void setFlags(QString identifier, const QStringList& flags, QDBusMessage message); + QStringList getSurfaces(QDBusMessage message); + QDBusUnixFileDescriptor frameBuffer(QDBusMessage message); + void lower(QString identifier, QDBusMessage message); + void raise(QString identifier, QDBusMessage message); + void focus(QString identifier, QDBusMessage message); + void waitForNoRepaints(QDBusMessage message); + void enterExclusiveMode(QDBusMessage message); + void exitExclusiveMode(QDBusMessage message); + void exclusiveModeRepaint(QDBusMessage message); + +signals: + void clipboardChanged(const QByteArray& data); + void selectionChanged(const QByteArray& data); + void secondaryChanged(const QByteArray& data); + +private slots: + void serviceOwnerChanged(const QString& name, const QString& oldOwner, const QString& newOwner); + +private: + DbusInterface(QObject* parent); + QQmlApplicationEngine engine; + QList connections; + QMutex closingMutex; + QList closingConnections; + Connection* m_focused; + struct { + QByteArray clipboard; + QByteArray selection; + QByteArray secondary; + } clipboards; + bool m_exlusiveMode; + + Connection* getConnection(QDBusMessage message); + Connection* getConnection(QString identifier); + QObject* workspace(); + Connection* createConnection(int pid); +}; diff --git a/applications/display-server/display-server.pro b/applications/display-server/display-server.pro new file mode 100644 index 000000000..464170b7d --- /dev/null +++ b/applications/display-server/display-server.pro @@ -0,0 +1,76 @@ +QT += dbus +QT += quick +QT += gui +QT += input_support-private + +CONFIG += c++17 +CONFIG -= app_bundle +CONFIG += qml_debug +CONFIG += qtquickcompiler +CONFIG += qmltypes + +QT_CONFIG -= no-pkg-config +CONFIG += link_pkgconfig +PKGCONFIG += libevdev + +QML_IMPORT_NAME = codes.eeems.blight +QML_IMPORT_PATH += . +QML_IMPORT_MAJOR_VERSION = 1 + +QMAKE_CFLAGS += -std=c99 + +SOURCES += \ + connection.cpp \ + dbusinterface.cpp \ + evdevdevice.cpp \ + evdevhandler.cpp \ + guithread.cpp \ + main.cpp \ + surface.cpp \ + surfacewidget.cpp + +configFile.files = ../../assets/etc/dbus-1/system.d/codes.eeems.blight.conf +configFile.path = /etc/dbus-1/system.d/ +INSTALLS += configFile + +service.files = ../../assets/etc/systemd/system/blight.service +service.path = /etc/systemd/system/ +INSTALLS += service + +client.files = blight-client +client.path = /opt/bin/ +INSTALLS += client + +TARGET = blight +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +HEADERS += \ + connection.h \ + dbusinterface.h \ + evdevdevice.h \ + evdevhandler.h \ + guithread.h \ + surface.h \ + surfacewidget.h + +INCLUDEPATH += ../../shared/mxcfb + +include(../../qmake/liboxide.pri) +include(../../qmake/libblight.pri) +include(../../qmake/epaper.pri) + +QMAKE_POST_LINK += sh $$_PRO_FILE_PWD_/generate_xml.sh + + +!contains(DEFINES, EPAPER){ + RESOURCES += \ + qml.qrc +} + +DISTFILES += \ + ../../assets/etc/dbus-1/system.d/codes.eeems.blight.conf \ + ../../assets/etc/systemd/system/blight.service \ + generate_xml.sh \ + blight-client diff --git a/applications/display-server/evdevdevice.cpp b/applications/display-server/evdevdevice.cpp new file mode 100644 index 000000000..cfc25a23e --- /dev/null +++ b/applications/display-server/evdevdevice.cpp @@ -0,0 +1,130 @@ +#include "evdevdevice.h" + +#include +#include +#include +#include +#include + +EvDevDevice::EvDevDevice(QThread* handler, const event_device& device) +: QObject(handler), + device(device), + sys("/sys/class/input/" + devName() + "/device/") +{ + O_DEBUG(device.device.c_str() << this->device.fd); + _name = sys.strProperty("name").c_str(); + notifier = new QSocketNotifier(this->device.fd, QSocketNotifier::Read, this); + connect(notifier, &QSocketNotifier::activated, this, &EvDevDevice::readEvents); + notifier->setEnabled(true); + libevdev_new_from_fd(this->device.fd, &dev); +} + +EvDevDevice::~EvDevDevice(){ + notifier->setEnabled(false); + notifier->deleteLater(); + unlock(); + libevdev_free(dev); +} + +QString EvDevDevice::devName(){ return QFileInfo(path()).baseName(); } + +QString EvDevDevice::name(){ return _name; } + +QString EvDevDevice::path(){ + return QFileInfo(device.device.c_str()).canonicalFilePath(); +} + +QString EvDevDevice::id(){ + return QString("%1:%2").arg( + sys.strProperty("id/vendor").c_str(), + sys.strProperty("id/product").c_str() + ); +} + +unsigned int EvDevDevice::number(){ return devName().midRef(5).toInt(); } + +bool EvDevDevice::exists(){ return QFile::exists(path()); } + +void EvDevDevice::lock(){ exists() && device.lock(); } + +void EvDevDevice::unlock(){ exists() && device.locked && device.unlock(); } + +void EvDevDevice::clear_buffer(){ + auto flood = build_flood(); + ::write(device.fd, flood, 512 * 8 * 4 * sizeof(input_event)); + delete flood; +} + +void EvDevDevice::readEvents(){ + Oxide::dispatchToThread(thread(), [this]{ + notifier->setEnabled(false); + int res; + bool sync = false; + do{ + input_event event; + res = libevdev_next_event( + dev, + sync ? LIBEVDEV_READ_FLAG_SYNC : LIBEVDEV_READ_FLAG_NORMAL, + &event + ); + sync = res == LIBEVDEV_READ_STATUS_SYNC; + if(res != LIBEVDEV_READ_STATUS_SUCCESS && res != LIBEVDEV_READ_STATUS_SYNC){ + continue; + } + events.push_back(event); + if(event.type == EV_SYN && event.code == SYN_REPORT){ + emit inputEvents(events); + events.clear(); + } + }while(res == LIBEVDEV_READ_STATUS_SUCCESS || res == LIBEVDEV_READ_STATUS_SYNC); + emitSomeEvents(); + if(res == -ENODEV){ + // Device went away + O_WARNING("Device dissapeared while reading events"); + return; + } + if(res != -EAGAIN){ + O_WARNING("Failed to read input:" << strerror(errno)); + } + notifier->setEnabled(true); + }); +} + +void EvDevDevice::emitSomeEvents(){ + if(events.empty()){ + return; + } + for(auto i = events.rbegin(); i != events.rend(); ++i){ + auto& event = *i; + if(event.type == EV_SYN && event.code == SYN_REPORT){ + auto index = std::next(i-1).base(); + auto some = std::vector(events.begin(), index); + emit inputEvents(some); + events.erase(events.begin(), index); + break; + } + } +} + +input_event EvDevDevice::createEvent(ushort type, ushort code, int value){ + struct input_event event; + event.type = type; + event.code = code; + event.value = value; + return event; +} + +input_event* EvDevDevice::build_flood(){ + auto n = 512 * 8; + auto num_inst = 4; + input_event* ev = (input_event*)malloc(sizeof(struct input_event) * n * num_inst); + memset(ev, 0, sizeof(input_event) * n * num_inst); + auto i = 0; + while (i < n) { + ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 1); + ev[i++] = createEvent(EV_SYN, 0, 0); + ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 2); + ev[i++] = createEvent(EV_SYN, 0, 0); + } + return ev; +} diff --git a/applications/display-server/evdevdevice.h b/applications/display-server/evdevdevice.h new file mode 100644 index 000000000..6d196502b --- /dev/null +++ b/applications/display-server/evdevdevice.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace Oxide; + +class EvDevDevice : public QObject{ + Q_OBJECT + +public: + EvDevDevice(QThread* handler, const event_device& device); + ~EvDevDevice(); + QString devName(); + QString name(); + QString path(); + QString id(); + unsigned int number(); + bool exists(); + void lock(); + void unlock(); + void clear_buffer(); + +signals: + void inputEvents(const std::vector events); + +public slots: + void readEvents(); + +private: + event_device device; + SysObject sys; + QString _name; + QSocketNotifier* notifier; + std::vector events; + libevdev* dev; + + void emitSomeEvents(); + input_event createEvent(ushort type, ushort code, int value); + input_event* build_flood(); +}; diff --git a/applications/display-server/evdevhandler.cpp b/applications/display-server/evdevhandler.cpp new file mode 100644 index 000000000..4fd20a381 --- /dev/null +++ b/applications/display-server/evdevhandler.cpp @@ -0,0 +1,81 @@ +#include "evdevhandler.h" +#include "dbusinterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +EvDevHandler* EvDevHandler::init(){ + static EvDevHandler* instance; + if(instance != nullptr){ + return instance; + } + instance = new EvDevHandler(); + instance->moveToThread(instance); + Oxide::startThreadWithPriority(instance, QThread::HighestPriority); + return instance; +} + +EvDevHandler::EvDevHandler() +: QThread(), + m_clearing{false} +{ + setObjectName("EvDevHandler"); + reloadDevices(); + auto deviceDiscovery = QDeviceDiscovery::create(QDeviceDiscovery::Device_InputMask, this); + connect(deviceDiscovery, &QDeviceDiscovery::deviceDetected, this, &EvDevHandler::reloadDevices); + connect(deviceDiscovery, &QDeviceDiscovery::deviceRemoved, this, &EvDevHandler::reloadDevices); +} + +EvDevHandler::~EvDevHandler(){} + +void EvDevHandler::clear_buffers(){ + m_clearing = true; + for(auto input : qAsConst(devices)){ + input->clear_buffer(); + } + m_clearing = false; +} + +bool EvDevHandler::hasDevice(event_device device){ + for(auto input : qAsConst(devices)){ + if(device.device.c_str() == input->path()){ + return true; + } + } + return false; +} + +void EvDevHandler::reloadDevices(){ + O_DEBUG("Reloading devices"); + for(auto& device : deviceSettings.inputDevices()){ + if(!hasDevice(device) && device.fd > 0){ + auto input = new EvDevDevice(this, device); + connect(input, &EvDevDevice::inputEvents, this, [this, input](auto events){ + if(!m_clearing){ + dbusInterface->inputEvents(input->number(), events); + } + }, Qt::QueuedConnection); + O_DEBUG(input->name() << "added"); + devices.append(input); + input->readEvents(); + } + } + QMutableListIterator i(devices); + while(i.hasNext()){ + EvDevDevice* device = i.next(); + if(device->exists()){ + continue; + } + O_DEBUG(device->name() << "removed"); + i.remove(); + delete device; + } +} diff --git a/applications/display-server/evdevhandler.h b/applications/display-server/evdevhandler.h new file mode 100644 index 000000000..9264905e8 --- /dev/null +++ b/applications/display-server/evdevhandler.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "evdevdevice.h" + +using namespace Oxide; + +#define evdevHandler EvDevHandler::init() + +class EvDevHandler : public QThread{ + Q_OBJECT + +public: + static EvDevHandler* init(); + EvDevHandler(); + ~EvDevHandler(); + void clear_buffers(); + +private: + QList devices; + bool m_clearing; + bool hasDevice(event_device device); + void reloadDevices(); +}; diff --git a/applications/display-server/generate_xml.sh b/applications/display-server/generate_xml.sh new file mode 100755 index 000000000..80bda05a5 --- /dev/null +++ b/applications/display-server/generate_xml.sh @@ -0,0 +1,9 @@ +#!/bin/sh +cd "$(dirname "$0")" +mkdir -p ../../interfaces +p() { + echo "qdbuscpp2xml $1 -> $2.xml" + qdbuscpp2xml -A "$1" -o ../../interfaces/"$2".xml +} + +p dbusinterface.h blight diff --git a/applications/display-server/guithread.cpp b/applications/display-server/guithread.cpp new file mode 100644 index 000000000..b04609a9b --- /dev/null +++ b/applications/display-server/guithread.cpp @@ -0,0 +1,307 @@ +#include "guithread.h" +#ifdef EPAPER +#include "connection.h" +#include "dbusinterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void GUIThread::run(){ + O_DEBUG("Thread started"); + clearFrameBuffer(); + QTimer::singleShot(0, this, [this]{ + Q_ASSERT(QThread::currentThread() == (QThread*)this); + m_repaintMutex.lock(); + forever{ + // New repaint request each loop as we have a shared pointer we need to clear + RepaintRequest event; + if(!m_repaintEvents.try_dequeue(event)){ + dbusInterface->processRemovedSurfaces(); + dbusInterface->processClosingConnections(); + if(!m_repaintEvents.try_dequeue(event)){ + emit settled(); + // Wait for up to 500ms before trying again + m_repaintWait.wait(&m_repaintMutex, 500); + auto found = m_repaintEvents.try_dequeue(event); + if(!found){ + // Woken by something needing to cleanup connections/surfaces + continue; + } + } + } + forever{ + redraw(event); + if(event.callback != nullptr){ + event.callback(); + } + if(!m_repaintEvents.try_dequeue(event)){ + break; + } + } + eventDispatcher()->processEvents(QEventLoop::AllEvents); + if(isInterruptionRequested()){ + O_DEBUG("Interruption requested, leaving loop"); + break; + } + } + m_repaintMutex.unlock(); + }); + clearFrameBuffer(); + auto res = exec(); + O_DEBUG("Thread stopped with exit code:" << res); +} + +GUIThread* GUIThread::singleton(){ + static GUIThread* instance = nullptr; + if(instance == nullptr){ + instance = new GUIThread(EPFrameBuffer::instance()->framebuffer()->rect()); + Oxide::startThreadWithPriority(instance, QThread::TimeCriticalPriority); + } + return instance; +} + +GUIThread::GUIThread(QRect screenGeometry) +: QThread(), + m_screenGeometry{screenGeometry}, + m_screenOffset{screenGeometry.topLeft()}, + m_screenRect{m_screenGeometry.translated(-m_screenOffset)} +{ + m_frameBufferFd = open("/dev/fb0", O_RDWR); + if(m_frameBufferFd == -1){ + qFatal("Failed to open framebuffer"); + } + moveToThread(this); +} + +GUIThread::~GUIThread(){ + RepaintRequest event; + while(m_repaintEvents.try_dequeue(event)); + requestInterruption(); + quit(); + wait(); +} + +void GUIThread::enqueue( + std::shared_ptr surface, + QRect region, + Blight::WaveformMode waveform, + unsigned int marker, + bool global, + std::function callback +){ + if(isInterruptionRequested() || dbusInterface->inExclusiveMode()){ + if(callback != nullptr){ + callback(); + } + return; + } + Q_ASSERT(global || surface != nullptr); + QRect intersected; + if(global){ + intersected = region; + }else{ + if(!surface->visible()){ + O_WARNING("Surface is not currently visible" << surface->id()); + if(callback != nullptr){ + callback(); + } + return; + } + auto surfaceGeometry = surface->geometry(); + intersected = region + .translated(surfaceGeometry.topLeft()) + .intersected(surfaceGeometry) + .intersected(m_screenRect); + } + if(intersected.isEmpty()){ + O_WARNING("Region does not intersect with screen" << surface->id() << region); + if(callback != nullptr){ + callback(); + } + return; + } + auto visibleSurfaces = this->visibleSurfaces(); + QRegion repaintRegion(intersected); + if(!global){ + // Don't repaint portions covered by another surface that doesn't have alpha channel + auto i = visibleSurfaces.constEnd(); + while(i != visibleSurfaces.constBegin()){ + --i; + auto _surface = *i; + if(surface == _surface){ + break; + } + if(_surface->image()->hasAlphaChannel()){ + continue; + } + auto geometry = _surface->geometry(); + repaintRegion -= region + .intersected(geometry) + .translated(-geometry.topLeft()) + .intersected(m_screenRect); + } + } + if(repaintRegion.isEmpty()){ + O_WARNING("Region is currently covered" << surface->id() << region); + if(callback != nullptr){ + callback(); + } + return; + } + m_repaintEvents.enqueue(RepaintRequest{ + .surface = surface, + .region = repaintRegion, + .waveform = waveform, + .marker = marker, + .global = global, + .callback = callback + }); + notify(); +} + +void GUIThread::notify(){ + if(!dbusInterface->inExclusiveMode()){ + m_repaintWait.notify_one(); + } +} + +void GUIThread::clearFrameBuffer(){ + EPFrameBuffer::instance()->framebuffer()->fill(Qt::white); + EPFrameBuffer::sendUpdate( + m_screenGeometry, + EPFrameBuffer::Initialize, + EPFrameBuffer::FullUpdate, + true + ); + EPFrameBuffer::sendUpdate( + m_screenGeometry, + EPFrameBuffer::HighQualityGrayscale, + EPFrameBuffer::FullUpdate, + true + ); +} + +int GUIThread::framebuffer(){ return m_frameBufferFd; } + +void GUIThread::repaintSurface(QPainter* painter, QRect* rect, std::shared_ptr surface){ + const QRect surfaceGeometry = surface->geometry(); + const QRect surfaceGlobalRect = surfaceGeometry.translated(-m_screenGeometry.topLeft()); + const QRect imageRect = rect + ->translated(-surfaceGlobalRect.left(), -surfaceGlobalRect.top()) + .intersected(surface->image()->rect()); + const QRect sourceRect = rect->intersected(surfaceGlobalRect); + if(imageRect.isEmpty() || !imageRect.isValid() || sourceRect.isEmpty() || !sourceRect.isValid()){ + return; + } + O_DEBUG("Repaint surface" << surface->id() << sourceRect << imageRect); + // TODO - See if there is a way to detect if there is just transparency in the region + // and don't mark this as repainted. + painter->drawImage(sourceRect, *surface->image().get(), imageRect); +} + +void GUIThread::redraw(RepaintRequest& event){ + Q_ASSERT(QThread::currentThread() == (QThread*)this); + if(dbusInterface->inExclusiveMode()){ + O_DEBUG("In exclusive mode, skipping redraw"); + return; + } + if(!event.global){ + if(event.surface == nullptr){ + O_WARNING("surface missing"); + return; + } + if(event.surface->isRemoved()){ + return; + } + } + auto& region = event.region; + if(region.isEmpty()){ + O_WARNING("Empty repaint region" << region); + return; + } + Blight::ClockWatch cw; + // Get visible region on the screen to repaint + O_DEBUG("Repainting" << region.boundingRect()); + auto frameBuffer = EPFrameBuffer::instance()->framebuffer(); + Qt::GlobalColor colour = frameBuffer->hasAlphaChannel() ? Qt::transparent : Qt::white; + QPainter painter(frameBuffer); + while(!painter.isActive()){ + eventDispatcher()->processEvents(QEventLoop::AllEvents); + painter.begin(frameBuffer); + } + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + for(QRect rect : event.region){ + if(event.global || event.surface->image()->hasAlphaChannel()){ + painter.fillRect(rect, colour); + for(auto& surface : visibleSurfaces()){ + if(surface != nullptr){ + repaintSurface(&painter, &rect, surface); + } + } + }else{ + repaintSurface(&painter, &rect, event.surface); + } + sendUpdate(rect, event.waveform, event.marker); + } + painter.end(); + O_DEBUG("Repaint" << region.boundingRect() << "done in" << region.rectCount() << "paints, and" << cw.elapsed() << "seconds"); +} + +void GUIThread::sendUpdate(const QRect& rect, Blight::WaveformMode waveform, unsigned int marker){ + auto mode = rect == m_screenRect + ? EPFrameBuffer::FullUpdate + : EPFrameBuffer::PartialUpdate; + O_DEBUG("Sending screen update" << rect << waveform << mode); + mxcfb_update_data data{ + .update_region = mxcfb_rect{ + .top = (unsigned int)rect.top(), + .left = (unsigned int)rect.left(), + .width = (unsigned int)rect.width(), + .height = (unsigned int)rect.height(), + }, + .waveform_mode = waveform, + .update_mode = mode, + .update_marker = marker ? marker : ++m_currentMarker, + // TODO allow this to be passed through + .temp = 0x0018, + .flags = 0, + .dither_mode = 0, + .quant_bit = 0, + // TODO - explore this + .alt_buffer_data = mxcfb_alt_buffer_data{}, + }; + ioctl(m_frameBufferFd, MXCFB_SEND_UPDATE, &data); +} + +QList> GUIThread::visibleSurfaces(){ + auto visibleSurfaces = dbusInterface->visibleSurfaces(); + visibleSurfaces.erase( + std::remove_if( + visibleSurfaces.begin(), + visibleSurfaces.end(), + [](std::shared_ptr surface){ + auto connection = surface->connection(); + if(!surface->has("system") && getpgid(connection->pgid()) < 0){ + O_WARNING(surface->id() << "With no running process"); + return true; + } + auto image = surface->image(); + if(image->isNull()){ + O_WARNING(surface->id() << "Null framebuffer"); + return true; + } + return false; + } + ), + visibleSurfaces.end() + ); + return visibleSurfaces; +} +#endif diff --git a/applications/display-server/guithread.h b/applications/display-server/guithread.h new file mode 100644 index 000000000..70887d69b --- /dev/null +++ b/applications/display-server/guithread.h @@ -0,0 +1,65 @@ +#pragma once +#ifdef EPAPER +#include +#include +#include +#include +#include +#include + +#include "surface.h" + +#define guiThread GUIThread::singleton() + +struct RepaintRequest{ + std::shared_ptr surface; + QRegion region; + Blight::WaveformMode waveform; + unsigned int marker; + bool global; + std::function callback; +}; + +class GUIThread : public QThread{ + Q_OBJECT + +protected: + void run() override; + +public: + static GUIThread* singleton(); + ~GUIThread(); + +signals: + void settled(); + +public slots: + void enqueue( + std::shared_ptr surface, + QRect region, + Blight::WaveformMode waveform, + unsigned int marker, + bool global = false, + std::function callback = nullptr + ); + void notify(); + int framebuffer(); + void sendUpdate(const QRect& rect, Blight::WaveformMode waveform, unsigned int marker); + +private: + GUIThread(QRect screenGeometry); + moodycamel::ConcurrentQueue m_repaintEvents; + int m_frameBufferFd; + QAtomicInteger m_currentMarker; + QMutex m_repaintMutex; + QWaitCondition m_repaintWait; + QRect m_screenGeometry; + QPoint m_screenOffset; + QRect m_screenRect; + + void clearFrameBuffer(); + void repaintSurface(QPainter* painter, QRect* rect, std::shared_ptr surface); + void redraw(RepaintRequest& event); + QList> visibleSurfaces(); +}; +#endif diff --git a/applications/display-server/main.cpp b/applications/display-server/main.cpp new file mode 100755 index 000000000..032bb66b4 --- /dev/null +++ b/applications/display-server/main.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef EPAPER +#include +Q_IMPORT_PLUGIN(QsgEpaperPlugin) +#endif + +#include "dbusinterface.h" +#include "evdevhandler.h" + +using namespace std; +using namespace Oxide::Sentry; + +const std::string runPath = "/run/oxide"; +const char* pidPath = "/run/oxide/blight.pid"; +const char* lockPath = "/run/oxide/blight.lock"; + +bool stopProcess(pid_t pid){ + if(pid <= 1){ + return false; + } + qDebug() << "Waiting for other instance to stop..."; + kill(pid, SIGTERM); + int tries = 0; + while(0 == kill(pid, 0)){ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if(++tries == 50){ + qDebug() << "Instance is taking too long, killing..."; + kill(pid, SIGKILL); + }else if(tries == 60){ + qDebug() << "Unable to kill process"; + return false; + } + } + return true; +} + +int main(int argc, char* argv[]){ +#ifdef __arm__ + if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ + // TODO - also detect if rm2fb-server is running and start it if it isn't + if(getenv("RM2FB_ACTIVE") == nullptr){ + bool enabled = Oxide::debugEnabled(); + if(!enabled){ + qputenv("DEBUG", "1"); + } + O_WARNING("rm2fb not detected. Running xochitl instead!"); + execl("/usr/bin/xochitl", "/usr/bin/xochitl", NULL); + O_WARNING("Failed to run xochitl: " << std::strerror(errno)); + return errno; + } + } +#endif + qputenv("XDG_CURRENT_DESKTOP", "OXIDE"); + QThread::currentThread()->setObjectName("main"); +#ifdef EPAPER + // Force use of offscreen as we'll be using EPFrameBuffer directly + qputenv("QMLSCENE_DEVICE", ""); + qputenv("QT_QUICK_BACKEND",""); + qputenv("QT_QPA_PLATFORM", "offscreen"); + // qputenv("QT_QPA_PLATFORM", "vnc:size=1404x1872"); +#endif + QGuiApplication app(argc, argv); + sentry_init("blight", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("blight"); + app.setApplicationVersion(OXIDE_INTERFACE_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Oxide display service"); + parser.addHelpOption(); + parser.applicationDescription(); + parser.addVersionOption(); + QCommandLineOption breakLockOption( + {"f", "break-lock"}, + "Break existing locks and force startup if another version of blight is already running" + ); + parser.addOption(breakLockOption); + parser.process(app); + const QStringList args = parser.positionalArguments(); + if(!args.isEmpty()){ + parser.showHelp(EXIT_FAILURE); + } + auto actualPid = QString::number(app.applicationPid()); + QString pid = Oxide::execute( + "systemctl", + QStringList() << "--no-pager" << "show" << "--property" << "MainPID" << "--value" << "blight" + ).trimmed(); + if(pid != "0" && pid != "QML debugging is enabled. Only use this in a safe environment.\n0" && pid != actualPid){ + if(!parser.isSet(breakLockOption)){ + qDebug() << "blight.service is already running" << pid; + return EXIT_FAILURE; + } + if(QProcess::execute("systemctl", QStringList() << "stop" << "blight")){ + qDebug() << "blight.service is already running"; + qDebug() << "Unable to stop service"; + return EXIT_FAILURE; + } + } + qDebug() << "Creating lock file" << lockPath; + if(!QFile::exists(QString::fromStdString(runPath)) && !std::filesystem::create_directories(runPath)){ + qDebug() << "Failed to create" << runPath.c_str(); + return EXIT_FAILURE; + } + int lock = Oxide::tryGetLock(lockPath); + if(lock < 0){ + qDebug() << "Unable to establish lock on" << lockPath << strerror(errno); + if(!parser.isSet(breakLockOption)){ + return EXIT_FAILURE; + } + qDebug() << "Attempting to stop all other instances of blight" << lockPath; + for(auto lockingPid : Oxide::lsof(lockPath)){ + if(Oxide::processExists(lockingPid)){ + stopProcess(lockingPid); + } + } + lock = Oxide::tryGetLock(lockPath); + if(lock < 0){ + qDebug() << "Unable to establish lock on" << lockPath << strerror(errno); + return EXIT_FAILURE; + } + } + + QObject::connect(&app, &QGuiApplication::aboutToQuit, [lock]{ + qDebug() << "Releasing lock " << lockPath; + Oxide::releaseLock(lock, lockPath); + }); + QFile pidFile(pidPath); + if(!pidFile.open(QFile::ReadWrite)){ + qWarning() << "Unable to create " << pidPath; + return app.exec(); + } + pidFile.seek(0); + pidFile.write(actualPid.toUtf8()); + pidFile.close(); + QObject::connect(&app, &QGuiApplication::aboutToQuit, []{ + remove(pidPath); + }); + dbusInterface; + evdevHandler; + ::signal(SIGPIPE, SIG_IGN); + ::signal(SIGBUS, SIG_IGN); + return app.exec(); +} diff --git a/applications/display-server/qml.qrc b/applications/display-server/qml.qrc new file mode 100644 index 000000000..3d2986275 --- /dev/null +++ b/applications/display-server/qml.qrc @@ -0,0 +1,6 @@ + + + Workspace.qml + Surface.qml + + diff --git a/applications/display-server/surface.cpp b/applications/display-server/surface.cpp new file mode 100644 index 000000000..95ffbbc31 --- /dev/null +++ b/applications/display-server/surface.cpp @@ -0,0 +1,220 @@ +#include "surface.h" +#include "connection.h" +#include "dbusinterface.h" +#ifdef EPAPER +#include "guithread.h" +#else +#include "surfacewidget.h" +#endif + +#include +#include +#include + +#define S_DEBUG(msg) O_DEBUG("[" << id() << "]" << msg) +#define S_WARNING(msg) O_WARNING("[" << id() << "]" << msg) +#define S_INFO(msg) O_INFO("[" << id() << "]" << msg) + +Surface::Surface( + Connection* connection, + int fd, + Blight::surface_id_t identifier, + QRect geometry, + int stride, + QImage::Format format +) +: QObject(), + m_connection(connection), + m_identifier{identifier}, + m_geometry(geometry), + m_stride(stride), + m_format(format), + m_fd{fd}, + m_removed{false} +{ + m_id = QString("%1/surface/%2").arg(connection->id()).arg(identifier); + data = reinterpret_cast(mmap( + NULL, + m_stride * m_geometry.height(), + PROT_READ | PROT_WRITE, + MAP_SHARED_VALIDATE, + fd, + 0 + )); + if(data == MAP_FAILED){ + S_WARNING("Failed to map buffer"); + return; + } + m_image = std::shared_ptr(new QImage( + data, + geometry.width(), + geometry.height(), + stride, + format + )); +#ifndef EPAPER + component = dynamic_cast(dbusInterface->loadComponent( + "qrc:/Surface.qml", + id(), + QVariantMap{ + {"x", geometry.x()}, + {"y", geometry.y()}, + {"width", geometry.width()}, + {"height", geometry.height()}, + {"visible", true}, + } + )); + if(component == nullptr){ + S_WARNING("Failed to create surface widget"); + return; + } + auto widget = component->findChild("Surface"); + if(widget == nullptr){ + S_WARNING("Failed to get surface widget"); + component->deleteLater(); + component = nullptr; + return; + } + widget->forceActiveFocus(); + connect(this, &Surface::update, widget, &SurfaceWidget::update); + connect(widget, &SurfaceWidget::activeFocusChanged, this, &Surface::activeFocusChanged); +#endif + emit connection->focused(); + setVisible(true); + setZ(std::numeric_limits::max()); + S_INFO("Surface created"); +} + +Surface::~Surface(){ + S_INFO("Surface destroyed"); + if(data != MAP_FAILED){ + ::munmap(data, m_stride * m_geometry.height()); + } + ::close(m_fd); + setVisible(false); +#ifdef EPAPER + repaint(); +#else + if(component != nullptr){ + component->deleteLater(); + component = nullptr; + } +#endif +} + +QString Surface::id(){ return m_id; } + +bool Surface::isValid(){ +#ifdef EPAPER + return data != MAP_FAILED; +#else + return component != nullptr; +#endif +} + +std::shared_ptr Surface::image(){ return m_image; } + +void Surface::repaint(QRect rect){ + if(rect.isEmpty()){ + rect = this->rect(); + } +#ifdef EPAPER + guiThread->enqueue( + nullptr, + geometry(), + Blight::HighQualityGrayscale, + 0, + true + ); +#else + emit update(rect); +#endif +} + +int Surface::fd(){ return m_fd; } + +const QRect& Surface::geometry(){ return m_geometry; } + +const QSize Surface::size(){ return m_geometry.size(); } + +const QRect Surface::rect(){ return QRect(QPoint(0, 0), size()); } + +int Surface::stride(){ return m_stride; } + +QImage::Format Surface::format(){ return m_format; } + +void Surface::move(int x, int y){ + m_geometry.moveTopLeft(QPoint(x, y)); +#ifndef EPAPER + component->setX(x); + component->setY(y); +#endif +} + +void Surface::setVisible(bool visible){ +#ifdef EPAPER + setProperty("visible", visible); +#else + if(component != nullptr){ + component->setVisible(visible); + } +#endif +} + +bool Surface::visible(){ +#ifdef EPAPER + return property("visible").toBool(); +#else + if(component == nullptr){ + return false; + } + return component->isVisible(); +#endif +} + +int Surface::z(){ +#ifdef EPAPER + return property("z").toInt(); +#else + if(component == nullptr){ + return -1; + } + return component->z(); +#endif +} + +void Surface::setZ(int z){ +#ifdef EPAPER + setProperty("z", z); +#else + if(component != nullptr){ + component->setZ(z); + } +#endif +} + +bool Surface::has(const QString& flag){ return flags.contains(flag); } + +void Surface::set(const QString& flag){ + if(!has(flag)){ + flags.append(flag); + } +} + +void Surface::unset(const QString& flag){ flags.removeAll(flag); } + +Connection* Surface::connection(){ return m_connection; } + +bool Surface::isRemoved(){ return m_removed; } + +void Surface::removed(){ m_removed = true; } + +#ifndef EPAPER +void Surface::activeFocusChanged(bool focus){ + if(focus){ + emit m_connection->focused(); + } +} +#endif + +#include "moc_surface.cpp" diff --git a/applications/display-server/surface.h b/applications/display-server/surface.h new file mode 100644 index 000000000..4034b455a --- /dev/null +++ b/applications/display-server/surface.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "connection.h" +class Connection; + +#include "../../shared/liboxide/meta.h" + +class Surface : public QObject { + Q_OBJECT + Q_CLASSINFO("Version", OXIDE_INTERFACE_VERSION) + +public: + Surface( + Connection* connection, + int fd, + Blight::surface_id_t identifier, + QRect geometry, + int stride, + QImage::Format format + ); + ~Surface(); + QString id(); + Blight::surface_id_t identifier(){ return m_identifier; } + bool isValid(); + std::shared_ptr image(); + void repaint(QRect rect = QRect()); + int fd(); + const QRect& geometry(); + const QSize size(); + const QRect rect(); + int stride(); + QImage::Format format(); + void move(int x, int y); + bool visible(); + void setVisible(bool visible); + int z(); + void setZ(int z); + bool has(const QString& flag); + void set(const QString& flag); + void unset(const QString& flag); + Connection* connection(); + bool isRemoved(); + void removed(); + +signals: + void update(const QRect& geometry); + +private slots: +#ifndef EPAPER + void activeFocusChanged(bool focus); +#endif + +private: + Connection* m_connection; + Blight::surface_id_t m_identifier; + QRect m_geometry; + int m_stride; + QImage::Format m_format; + int m_fd; + uchar* data; + std::shared_ptr m_image; +#ifndef EPAPER + QQuickItem* component; +#endif + QString m_id; + QStringList flags; + bool m_removed; +}; diff --git a/applications/display-server/surfacewidget.cpp b/applications/display-server/surfacewidget.cpp new file mode 100644 index 000000000..29bcf9aa0 --- /dev/null +++ b/applications/display-server/surfacewidget.cpp @@ -0,0 +1,59 @@ +#include "surfacewidget.h" +#include "dbusinterface.h" +#include "guithread.h" + +#include +#include + +#ifndef EPAPER +SurfaceWidget::SurfaceWidget(QQuickItem* parent) +: QQuickItem(parent) +{ + setObjectName("Surface"); + setFlag(QQuickItem::ItemHasContents); +} + +QString SurfaceWidget::identifier(){ return m_identifier; } + +void SurfaceWidget::setIdentifier(QString identifier){ + m_identifier = identifier; + emit identifierChanged(m_identifier); + updated(); +} + +void SurfaceWidget::updated(){ update(); } + +QSGNode* SurfaceWidget::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*){ + if(!oldNode){ + oldNode = window()->createImageNode(); + } + QRectF rect = boundingRect(); + if (rect.isEmpty()) { + return oldNode; + } + auto image = this->image(); + if(image == nullptr || image->isNull()){ + return oldNode; + } + QSGTexture* texture = window()->createTextureFromImage(*image); + if(!texture){ + return oldNode; + } + auto node = static_cast(oldNode); + node->setTexture(texture); + node->setRect(rect); + return oldNode; +} + +std::shared_ptr SurfaceWidget::surface(){ + return dbusInterface->getSurface(identifier()); +} + +std::shared_ptr SurfaceWidget::image(){ + auto surface = this->surface(); + if(surface == nullptr){ + return nullptr; + } + return surface->image(); +} +#endif diff --git a/applications/display-server/surfacewidget.h b/applications/display-server/surfacewidget.h new file mode 100644 index 000000000..3e7baf13b --- /dev/null +++ b/applications/display-server/surfacewidget.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include "surface.h" + +#ifndef EPAPER +class SurfaceWidget +: public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged) + QML_NAMED_ELEMENT(Surface) + +public: + SurfaceWidget(QQuickItem* parent = nullptr); + QString identifier(); + void setIdentifier(QString identifier); + +signals: + void identifierChanged(QString); + +protected slots: + void updated(); + +protected: + QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*); + std::shared_ptr surface(); + std::shared_ptr image(); + +private: + QString m_identifier; +}; +#endif diff --git a/applications/fbinfo/fbinfo.pro b/applications/fbinfo/fbinfo.pro new file mode 100644 index 000000000..7bc54c624 --- /dev/null +++ b/applications/fbinfo/fbinfo.pro @@ -0,0 +1,18 @@ +QT -= gui + +CONFIG += c++11 +CONFIG += console +CONFIG -= app_bundle + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = fbinfo +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +INCLUDEPATH += ../../shared/mxcfb + diff --git a/applications/fbinfo/main.cpp b/applications/fbinfo/main.cpp new file mode 100644 index 000000000..7c8b924e9 --- /dev/null +++ b/applications/fbinfo/main.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../shared/liboxide/meta.h" + +QDebug operator<<(QDebug debug, const fb_bitfield& bitfield){ + QDebugStateSaver saver(debug); + Q_UNUSED(saver); + return debug.nospace() + << "offset=" << bitfield.offset + << " length=" << bitfield.length + << " msb_right=" << bitfield.msb_right; +} + +QDebug operator<<(QDebug debug, const fb_var_screeninfo& vinfo){ + QDebugStateSaver saver(debug); + Q_UNUSED(saver); + return debug.nospace() + << "xres: " << vinfo.xres << Qt::endl + << "yres: " << vinfo.yres << Qt::endl + << "xres_virtual: " << vinfo.xres_virtual << Qt::endl + << "yres_virtual: " << vinfo.yres_virtual << Qt::endl + << "xoffset: " << vinfo.xoffset << Qt::endl + << "yoffset: " << vinfo.yoffset << Qt::endl + << "bits_per_pixel: " << vinfo.bits_per_pixel << Qt::endl + << "grayscale: " << vinfo.grayscale << Qt::endl + << "red: " << vinfo.red << Qt::endl + << "green: " << vinfo.green << Qt::endl + << "blue: " << vinfo.blue << Qt::endl + << "transp: " << vinfo.transp << Qt::endl + << "nonstd: " << vinfo.nonstd << Qt::endl + << "activate: " << vinfo.activate << Qt::endl + << "width: " << vinfo.width << Qt::endl + << "height: " << vinfo.height << Qt::endl + << "accel_flags: " << vinfo.accel_flags << Qt::endl + << "pixclock: " << vinfo.pixclock << Qt::endl + << "left_margin: " << vinfo.left_margin << Qt::endl + << "right_margin: " << vinfo.right_margin << Qt::endl + << "upper_margin: " << vinfo.upper_margin << Qt::endl + << "lower_margin: " << vinfo.lower_margin << Qt::endl + << "hsync_len: " << vinfo.hsync_len << Qt::endl + << "vsync_len: " << vinfo.vsync_len << Qt::endl + << "sync: " << vinfo.sync << Qt::endl + << "vmode: " << vinfo.vmode << Qt::endl + << "rotate: " << vinfo.rotate << Qt::endl + << "colorspace: " << vinfo.colorspace << Qt::endl + << "reserved 0: " << vinfo.reserved[0] << Qt::endl + << "reserved 1: " << vinfo.reserved[1] << Qt::endl + << "reserved 2: " << vinfo.reserved[2] << Qt::endl + << "reserved 3: " << vinfo.reserved[3] << Qt::endl; +} + +QString to_hex(unsigned long x){ return "0x" + QString::number(x, 16).toUpper(); } + +QDebug operator<<(QDebug debug, const fb_fix_screeninfo& finfo){ + QDebugStateSaver saver(debug); + Q_UNUSED(saver); + return debug.nospace().noquote() + << "id: " << finfo.id << Qt::endl + << "smem_start: " << to_hex(finfo.smem_start) << Qt::endl + << "smem_len: " << finfo.smem_len << Qt::endl + << "type: " << finfo.type << Qt::endl + << "type_aux: " << finfo.type_aux << Qt::endl + << "visual: " << finfo.visual << Qt::endl + << "xpanstep: " << finfo.xpanstep << Qt::endl + << "ypanstep: " << finfo.ypanstep << Qt::endl + << "ywrapstep: " << finfo.ywrapstep << Qt::endl + << "line_length: " << finfo.line_length << Qt::endl + << "mmio_start: " << to_hex(finfo.mmio_start) << Qt::endl + << "mmio_len: " << finfo.mmio_len << Qt::endl + << "accel: " << finfo.accel << Qt::endl + << "capabilities: " << to_hex(finfo.capabilities) << Qt::endl + << "reserved 0: " << finfo.reserved[0] << Qt::endl + << "reserved 1: " << finfo.reserved[1] << Qt::endl; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("fbinfo"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("a tool to get framebuffer information"); + parser.addVersionOption(); + auto path = "/dev/fb0"; + if(QString::fromLatin1(path).isEmpty()){ + qDebug() << "Unable to find framebuffer"; + return EXIT_FAILURE; + } + auto fd = open(path, O_RDONLY); + if(fd == -1){ + qDebug() << "Failed to open framebuffer:" << strerror(errno); + return errno; + } + fb_var_screeninfo vinfo; + auto res = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); + auto err = errno; + if(res == -1){ + qDebug() << "Failed to get variable screen info:" << strerror(err); + return err; + } + fb_fix_screeninfo finfo; + res = ioctl(fd, FBIOGET_FSCREENINFO, &finfo); + err = errno; + close(fd); + if(res == -1){ + qDebug() << "Failed to get fixed screen info:" << strerror(err); + return err; + } + QFile file; + file.open(stdout, QIODevice::WriteOnly); + QDebug(&file).nospace() + << "[framebuffer]" << Qt::endl + << "path: " << path << Qt::endl + << Qt::endl + << "[fb_var_screeninfo]" << Qt::endl + << vinfo + << Qt::endl + << "[fb_fix_screeninfo]" << Qt::endl + << finfo; + return EXIT_SUCCESS; +} diff --git a/applications/launcher/controller.cpp b/applications/launcher/controller.cpp index 0a2564219..6d8960289 100644 --- a/applications/launcher/controller.cpp +++ b/applications/launcher/controller.cpp @@ -296,18 +296,6 @@ inline void updateUI(QObject* ui, const char* name, const QVariant& value){ ui->setProperty(name, value); } } -std::string Controller::exec(const char* cmd) { - std::array buffer; - std::string result; - std::unique_ptr pipe(popen(cmd, "r"), pclose); - if (!pipe) { - throw std::runtime_error("popen() failed!"); - } - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { - result += buffer.data(); - } - return result; -} void Controller::updateUIElements(){} void Controller::setAutomaticSleep(bool state){ m_automaticSleep = state; diff --git a/applications/launcher/controller.h b/applications/launcher/controller.h index 510d38585..ff1388525 100644 --- a/applications/launcher/controller.h +++ b/applications/launcher/controller.h @@ -58,7 +58,6 @@ class Controller : public QObject Q_PROPERTY(int maxTouchWidth READ maxTouchWidth) Q_PROPERTY(int maxTouchHeight READ maxTouchHeight) public: - static std::string exec(const char* cmd); QObject* stateController; QObject* root = nullptr; explicit Controller(QObject* parent = 0) diff --git a/applications/launcher/main.qml b/applications/launcher/main.qml index 6f524a012..a0a99e86a 100755 --- a/applications/launcher/main.qml +++ b/applications/launcher/main.qml @@ -11,7 +11,6 @@ OxideWindow { visible: true title: qsTr("Oxide") focus: true - backgroundColor: "white" FontLoader { id: iconFont; source: "/font/icomoon.ttf" } onAfterSynchronizing: { if (stateController.state == "loading") { diff --git a/applications/lockscreen/main.cpp b/applications/lockscreen/main.cpp index c71313c6e..85c4282ed 100644 --- a/applications/lockscreen/main.cpp +++ b/applications/lockscreen/main.cpp @@ -15,8 +15,6 @@ using namespace Oxide; using namespace Oxide::QML; using namespace Oxide::Sentry; -#define DEBUG_EVENTS - int main(int argc, char *argv[]){ deviceSettings.setupQtEnvironment(); QGuiApplication app(argc, argv); diff --git a/applications/lockscreen/main.qml b/applications/lockscreen/main.qml index aecee5bb3..11ce2d06d 100644 --- a/applications/lockscreen/main.qml +++ b/applications/lockscreen/main.qml @@ -10,6 +10,7 @@ OxideWindow { objectName: "window" visible: stateController.state != "loading" title: qsTr("Oxide") + color: "black" property int itemPadding: 10 FontLoader { id: iconFont; source: "/font/icomoon.ttf" } Component.onCompleted: { diff --git a/applications/process-manager/main.qml b/applications/process-manager/main.qml index 88fe76f26..7029b94e3 100755 --- a/applications/process-manager/main.qml +++ b/applications/process-manager/main.qml @@ -8,7 +8,6 @@ import "widgets" OxideWindow { id: window visible: true - backgroundColor: "white" onAfterSynchronizing: { if (stateController.state == "loading") { stateController.state = "loaded"; diff --git a/applications/process-manager/process-manager.pro b/applications/process-manager/process-manager.pro index a0b1c9fc5..303dbbb96 100755 --- a/applications/process-manager/process-manager.pro +++ b/applications/process-manager/process-manager.pro @@ -22,11 +22,6 @@ icons.path = /opt/usr/share/icons/oxide/48x48/apps/ icons.files += ../../assets/opt/usr/share/icons/oxide/48x48/apps/erode.png INSTALLS += icons -splash.path = /opt/usr/share/icons/oxide/702x702/splash/ -splash.files += ../../assets/opt/usr/share/icons/oxide/702x702/splash/erode.png -INSTALLS += splash - - HEADERS += \ controller.h \ taskitem.h \ diff --git a/applications/screenshot-tool/main.cpp b/applications/screenshot-tool/main.cpp deleted file mode 100644 index afc9b9fdb..000000000 --- a/applications/screenshot-tool/main.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -using namespace codes::eeems::oxide1; -using namespace Oxide::Sentry; - -void unixSignalHandler(int signal){ - qDebug() << "Recieved signal" << signal; - exit(EXIT_SUCCESS); -} -void onExit(){ - qDebug() << "Exiting"; -} - -void addNotification(Notifications* notifications, QString text, QString icon = ""){ - auto guid = QUuid::createUuid().toString(); - qDebug() << "Adding notification" << guid; - QDBusObjectPath path = notifications->add(guid, "codes.eeems.fret", text, icon); - if(path.path() != "/"){ - auto notification = new Notification(OXIDE_SERVICE, path.path(), QDBusConnection::systemBus(), notifications->parent()); - qDebug() << "Displaying notification" << guid; - notification->display().waitForFinished(); - QObject::connect(notification, &Notification::clicked, [notification]{ - qDebug() << "Notification clicked" << notification->identifier(); - notification->remove(); - notification->deleteLater(); - }); - }else{ - qDebug() << "Failed to add notification"; - } -} - -int main(int argc, char *argv[]){ - QCoreApplication app(argc, argv); - sentry_init("fret", argv); - atexit(onExit); - signal(SIGTERM, unixSignalHandler); - signal(SIGSEGV, unixSignalHandler); - signal(SIGABRT, unixSignalHandler); - signal(SIGSYS, unixSignalHandler); - app.setOrganizationName("Eeems"); - app.setOrganizationDomain(OXIDE_SERVICE); - app.setApplicationName("fret"); - app.setApplicationVersion(APP_VERSION); - auto bus = QDBusConnection::systemBus(); - qDebug() << "Waiting for tarnish to start up..."; - while(!bus.interface()->registeredServiceNames().value().contains(OXIDE_SERVICE)){ - struct timespec args{ - .tv_sec = 1, - .tv_nsec = 0, - }, res; - nanosleep(&args, &res); - } - General api(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus, &app); - qDebug() << "Requesting system API..."; - QDBusObjectPath path = api.requestAPI("system"); - if(path.path() == "/"){ - qDebug() << "Unable to get system API"; - return EXIT_FAILURE; - } - System system(OXIDE_SERVICE, path.path(), bus, &app); - qDebug() << "Requesting screen API..."; - path = api.requestAPI("screen"); - if(path.path() == "/"){ - qDebug() << "Unable to get screen API"; - return EXIT_FAILURE; - } - Screen screen(OXIDE_SERVICE, path.path(), bus, &app); - qDebug() << "Requesting notification API..."; - path = api.requestAPI("notification"); - if(path.path() == "/"){ - qDebug() << "Unable to get notification API"; - return EXIT_FAILURE; - } - Notifications notifications(OXIDE_SERVICE, path.path(), bus, &app); - qDebug() << "Connecting signal listener..."; - QObject::connect(&system, &System::rightAction, [&screen, ¬ifications, bus, &app]{ - qDebug() << "Taking screenshot"; - auto reply = screen.screenshot(); - reply.waitForFinished(); - if(reply.isError()){ - qDebug() << "Failed to take screenshot"; - addNotification(¬ifications, "Screenshot failed: " + reply.error().message()); - return; - } - auto qPath = reply.value().path(); - if(qPath == "/"){ - qDebug() << "Failed to take screenshot"; - addNotification(¬ifications, "Screenshot failed: Unknown reason"); - return; - } - Screenshot screenshot(OXIDE_SERVICE, qPath, bus, &app); - if(QFile("/tmp/.screenshot").exists()){ - // Then execute the contents of /tmp/.screenshot - qDebug() << "Screenshot file exists."; - QProcess::execute("/bin/bash", QStringList() << "/tmp/.screenshot" << screenshot.path()); - } - addNotification(¬ifications, "Screenshot taken", screenshot.path()); - qDebug() << "Screenshot done."; - }); - qDebug() << "Waiting for signals..."; - return app.exec(); -} diff --git a/applications/screenshot-tool/screenshot-tool.pro b/applications/screenshot-tool/screenshot-tool.pro deleted file mode 100644 index a50d25589..000000000 --- a/applications/screenshot-tool/screenshot-tool.pro +++ /dev/null @@ -1,19 +0,0 @@ -QT -= gui -QT += dbus - -CONFIG += c++11 console -CONFIG -= app_bundle # disables all the APIs deprecated before Qt 6.0.0 - -SOURCES += \ - main.cpp - -TARGET = fret -include(../../qmake/common.pri) -target.path = /opt/bin -INSTALLS += target - -applications.files = ../../assets/opt/usr/share/applications/codes.eeems.fret.oxide -applications.path = /opt/usr/share/applications/ -INSTALLS += applications - -include(../../qmake/liboxide.pri) diff --git a/applications/screenshot-viewer/main.qml b/applications/screenshot-viewer/main.qml index e889b2148..d20d24517 100644 --- a/applications/screenshot-viewer/main.qml +++ b/applications/screenshot-viewer/main.qml @@ -9,7 +9,6 @@ OxideWindow { id: window objectName: "window" visible: stateController.state !== "loading" - backgroundColor: "white" title: { if(stateController.state !== "viewing" || !viewer.model){ return Qt.application.displayName; diff --git a/applications/screenshot-viewer/screenshot-viewer.pro b/applications/screenshot-viewer/screenshot-viewer.pro index fb4ce475f..0c9a683b0 100644 --- a/applications/screenshot-viewer/screenshot-viewer.pro +++ b/applications/screenshot-viewer/screenshot-viewer.pro @@ -23,10 +23,6 @@ icons.files += ../../assets/opt/usr/share/icons/oxide/48x48/apps/image.png icons.path = /opt/usr/share/icons/oxide/48x48/apps INSTALLS += icons -splash.files += ../../assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png -splash.path = /opt/usr/share/icons/oxide/702x702/splash -INSTALLS += splash - HEADERS += \ controller.h \ screenshotlist.h diff --git a/applications/settings-manager/main.cpp b/applications/settings-manager/main.cpp index d514aff16..fe48bfdeb 100644 --- a/applications/settings-manager/main.cpp +++ b/applications/settings-manager/main.cpp @@ -3,8 +3,10 @@ #include #include +#include using namespace codes::eeems::oxide1; +using namespace codes::eeems::blight1; using namespace Oxide::Sentry; using namespace Oxide::JSON; @@ -27,7 +29,7 @@ int main(int argc, char *argv[]){ parser.addHelpOption(); parser.applicationDescription(); parser.addVersionOption(); - parser.addPositionalArgument("api", "settings\nwifi\npower\napps\nsystem\nscreen\nnotification"); + parser.addPositionalArgument("api", "settings\nwifi\npower\napps\nsystem\nscreen\nnotification\ncompositor"); parser.addPositionalArgument("action","get\nset\nlisten\ncall"); QCommandLineOption objectOption( {"o", "object"}, @@ -51,7 +53,7 @@ int main(int argc, char *argv[]){ parser.showHelp(EXIT_FAILURE); } auto apiName = args.at(0); - if(!(QSet {"settings", "power", "wifi", "apps", "system", "screen", "notification"}).contains(apiName)){ + if(!(QSet {"settings", "power", "wifi", "apps", "system", "screen", "notification", "compositor"}).contains(apiName)){ qDebug() << "Unknown API" << apiName; #ifdef SENTRY sentry_breadcrumb("error", "Unknown API"); @@ -128,7 +130,7 @@ int main(int argc, char *argv[]){ return qExit(EXIT_FAILURE); } QString path = ""; - if(apiName != "settings"){ + if(apiName != "settings" && apiName != "compositor"){ General generalApi(OXIDE_SERVICE, OXIDE_SERVICE_PATH, bus); auto reply = generalApi.requestAPI(apiName); reply.waitForFinished(); @@ -293,6 +295,18 @@ int main(int argc, char *argv[]){ return qExit(EXIT_FAILURE); } } + }else if(apiName == "compositor"){ +#ifdef SENTRY + sentry_breadcrumb("api", "compositor"); +#endif + api = new Compositor(BLIGHT_SERVICE, "/", bus); + if(parser.isSet("object")){ + qDebug() << "Paths are not valid for the compositor API"; +#ifdef SENTRY + sentry_breadcrumb("error", "invalid arguments"); +#endif + return qExit(EXIT_FAILURE); + } }else{ qDebug() << "API not initialized? Please log a bug."; #ifdef SENTRY diff --git a/applications/system-service/apibase.cpp b/applications/system-service/apibase.cpp index d3b393bac..3a30b8279 100644 --- a/applications/system-service/apibase.cpp +++ b/applications/system-service/apibase.cpp @@ -1,6 +1,11 @@ #include "apibase.h" #include "appsapi.h" +#include +#include +#include +#include + int APIBase::hasPermission(QString permission, const char* sender){ if(getpgid(getpid()) == getSenderPgid()){ return true; @@ -29,4 +34,109 @@ int APIBase::getSenderPid() { } int APIBase::getSenderPgid() { return getpgid(getSenderPid()); } +QImage* getFrameBuffer(){ + static QImage* image = nullptr; + static QFile* file = nullptr; + if(image == nullptr){ + file = new QFile(); + auto compositor = getCompositorDBus(); + auto reply = compositor->frameBuffer(); + reply.waitForFinished(); + if(reply.isError()){ + O_WARNING("Failed to get framebuffer fd" << reply.error().message()); + return nullptr; + } + QDBusUnixFileDescriptor qfd = reply.value(); + if(!qfd.isValid()){ + O_WARNING("Framebuffer fd is not valid"); + return nullptr; + } + int fd = dup(qfd.fileDescriptor()); + if(fd < 0){ + O_WARNING("Failed to get framebuffer fd" << std::strerror(errno)); + return nullptr; + } + if(!file->open(fd, QFile::ReadWrite)){ + O_WARNING("Failed to open framebuffer" << file->errorString()); + ::close(fd); + delete file; + file = nullptr; + return nullptr; + } + auto stride = deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1 ? 2816 : 2808; + uchar* data = file->map(0, stride * 1872); + if(data == nullptr){ + O_WARNING("Failed to map framebuffer" << file->errorString()); + file->close(); + delete file; + file = nullptr; + return nullptr; + } + image = new QImage(data, 1404, 1872, stride, QImage::Format_RGB16); + if(image->isNull()){ + O_WARNING("Framebuffer is null" << image->size()); + delete image; + image = nullptr; + file->unmap(data); + file->close(); + delete file; + file = nullptr; + }else if(image->size().isEmpty()){ + O_WARNING("Image is empty" << image->size()); + delete image; + image = nullptr; + file->unmap(data); + file->close(); + delete file; + file = nullptr; + } + } + return image; +} + +Compositor* getCompositorDBus(){ + static auto compositor = new Compositor( + BLIGHT_SERVICE, + "/", +#ifdef EPAPER + QDBusConnection::systemBus(), +#else + QDBusConnection::sessionBus(), +#endif + qApp + ); + return compositor; +} + +Blight::shared_buf_t createBuffer(const QRect& rect, unsigned int stride, Blight::Format format){ + return Blight::createBuffer( + rect.x(), + rect.y(), + rect.width(), + rect.height(), + stride, + format + ).value_or(nullptr); +} + +Blight::shared_buf_t createBuffer(){ + auto frameBuffer = getFrameBuffer(); + return createBuffer( + frameBuffer->rect(), + frameBuffer->bytesPerLine(), + (Blight::Format)frameBuffer->format() + ); +} + #include "moc_apibase.cpp" + +void addSystemBuffer(Blight::shared_buf_t buffer){ + if(buffer != nullptr){ + Blight::addSurface(buffer); + auto compositor = getCompositorDBus(); + compositor->setFlags( + QString("connection/%1/surface/%2").arg(getpid()).arg(buffer->surface), + QStringList() << "system" + ); + } +} diff --git a/applications/system-service/apibase.h b/applications/system-service/apibase.h index 05c773efd..bbfcd6e45 100644 --- a/applications/system-service/apibase.h +++ b/applications/system-service/apibase.h @@ -6,14 +6,19 @@ #include #include #include +#include -#include #include +#include +#include +#include #ifdef Q_MOC_RUN #include "../../shared/liboxide/meta.h" #endif +using namespace codes::eeems::blight1; + class APIBase : public QObject, protected QDBusContext { Q_OBJECT @@ -27,5 +32,10 @@ class APIBase : public QObject, protected QDBusContext { int getSenderPid(); int getSenderPgid(); }; +QImage* getFrameBuffer(); +Compositor* getCompositorDBus(); +Blight::shared_buf_t createBuffer(const QRect& rect, unsigned int stride, Blight::Format format); +Blight::shared_buf_t createBuffer(); +void addSystemBuffer(Blight::shared_buf_t buffer); #endif // APIBASE_H diff --git a/applications/system-service/application.cpp b/applications/system-service/application.cpp index b4fd75129..ea47b107a 100644 --- a/applications/system-service/application.cpp +++ b/applications/system-service/application.cpp @@ -4,15 +4,16 @@ #include #include +#include +#include +#include #include "application.h" #include "appsapi.h" #include "systemapi.h" -#include "digitizerhandler.h" #include "notificationapi.h" -#include "mxcfb.h" #include "screenapi.h" -#include "buttonhandler.h" +#include "apibase.h" using namespace Oxide::Applications; @@ -49,22 +50,29 @@ void Application::launchNoSecurityCheck(){ O_INFO("Launching " << path()); appsAPI->pauseAll(); if(!flags().contains("nosplash")){ - showSplashScreen(); + m_notification = notificationAPI->add( + QUuid::createUuid().toString(), + "codes.eeems.tarnish", + "codes.eeems.tarnish", + QString("Loading %1...").arg(displayName()), + icon() + ); + m_notification->display(); + } + if(flags().contains("exclusive")){ + auto compositor = getCompositorDBus(); + compositor->enterExclusiveMode().waitForFinished(); + auto frameBuffer = getFrameBuffer(); + QPainter p(frameBuffer); + p.fillRect(frameBuffer->rect(), Qt::white); + p.end(); + compositor->exclusiveModeRepaint().waitForFinished(); } if(m_process->program() != bin()){ m_process->setProgram(bin()); } updateEnvironment(); - umountAll(); - if(chroot()){ - mountAll(); - m_process->setChroot(chrootPath()); - m_process->setWorkingDirectory(chrootPath() + "/" + workingDirectory()); - }else{ - m_process->setChroot(""); - m_process->setWorkingDirectory(workingDirectory()); - } - m_process->setUser(user()); + m_process->setWorkingDirectory(workingDirectory()); m_process->setGroup(group()); if(p_stdout == nullptr){ p_stdout_fd = sd_journal_stream_fd(name().toStdString().c_str(), LOG_INFO, 1); @@ -104,6 +112,10 @@ void Application::launchNoSecurityCheck(){ startSpan("background", "Application is in the background"); }else{ startSpan("foreground", "Application is in the foreground"); + if(flags().contains("exclusive")){ + auto compositor = getCompositorDBus(); + compositor->focus(id()); + } } }); } @@ -132,9 +144,6 @@ void Application::pauseNoSecurityCheck(bool startIfNone){ Q_UNUSED(t); #endif interruptApplication(); - if(!flags().contains("nosavescreen")){ - saveScreen(); - } if(startIfNone){ appsAPI->resumeIfNone(); } @@ -159,6 +168,9 @@ void Application::interruptApplication(){ #else Q_UNUSED(t); #endif + if(flags().contains("exclusive")){ + m_backup = new QImage(getFrameBuffer()->copy()); + } if(!onPause().isEmpty()){ Oxide::Sentry::sentry_span(t, "onPause", "Run onPause action", [this](){ system(onPause().toStdString().c_str()); @@ -197,6 +209,9 @@ void Application::interruptApplication(){ startSpan("stopped", "Application is stopped"); } }); + if(flags().contains("exclusive")){ + getCompositorDBus()->exitExclusiveMode().waitForFinished(); + } }); } void Application::waitForPause(){ @@ -214,6 +229,8 @@ void Application::waitForResume(){ waitid(P_PID, m_process->processId(), &info, WCONTINUED); } +QString Application::id(){ return QString("connection/%1").arg(m_process->processId()); } + void Application::sigUsr1(){ timer.invalidate(); } @@ -247,9 +264,6 @@ void Application::resumeNoSecurityCheck(){ appsAPI->recordPreviousApplication(); O_INFO("Resuming " << path()); appsAPI->pauseAll(); - if(!flags().contains("nosavescreen") && (type() != Backgroundable || stateNoSecurityCheck() == Paused)){ - recallScreen(); - } uninterruptApplication(); waitForResume(); emit resumed(); @@ -266,6 +280,21 @@ void Application::uninterruptApplication(){ return; } Oxide::Sentry::sentry_transaction("Uninterrupt Application", "uninterrupt", [this](Oxide::Sentry::Transaction* t){ + if(flags().contains("exclusive")){ + auto compositor = getCompositorDBus(); + compositor->enterExclusiveMode().waitForFinished(); + auto frameBuffer = getFrameBuffer(); + QPainter p(frameBuffer); + if(m_backup == nullptr){ + p.fillRect(frameBuffer->rect(), Qt::white); + }else{ + p.drawImage(frameBuffer->rect(), *m_backup, m_backup->rect()); + delete m_backup; + m_backup = nullptr; + } + p.end(); + compositor->exclusiveModeRepaint().waitForFinished(); + } #ifdef SENTRY if(t != nullptr){ sentry_transaction_set_tag(t->inner, "application", name().toStdString().c_str()); @@ -283,7 +312,6 @@ void Application::uninterruptApplication(){ case Background: case Backgroundable: if(stateNoSecurityCheck() == Paused){ - systemAPI->clearDeviceBuffers(); kill(-m_process->processId(), SIGCONT); } O_INFO("Waiting for SIGUSR1 ack"); @@ -302,12 +330,14 @@ void Application::uninterruptApplication(){ break; case Foreground: default: - systemAPI->clearDeviceBuffers(); kill(-m_process->processId(), SIGCONT); startSpan("foreground", "Application is in the foreground"); } }); }); + auto compositor = getCompositorDBus(); + compositor->raise(id()); + compositor->focus(id()); } void Application::stop(){ if(!hasPermission("apps")){ @@ -335,10 +365,10 @@ void Application::stopNoSecurityCheck(){ O_INFO("exit code: " << QProcess::execute(onStop(), QStringList())); }); } + getCompositorDBus()->lower(QString("connection/%1").arg(m_process->processId())); Application* pausedApplication = nullptr; if(state == Paused){ Oxide::Sentry::sentry_span(t, "resume", "Resume paused application", [this, &pausedApplication](){ - systemAPI->clearDeviceBuffers(); auto currentApplication = appsAPI->currentApplicationNoSecurityCheck(); if(currentApplication.path() != path()){ pausedApplication = appsAPI->getApplication(currentApplication); @@ -492,31 +522,10 @@ void Application::setWorkingDirectory(const QString& workingDirectory){ emit workingDirectoryChanged(workingDirectory); } -bool Application::chroot(){ return flags().contains("chroot"); } - QString Application::user(){ return value("user", getuid()).toString(); } QString Application::group(){ return value("group", getgid()).toString(); } -QStringList Application::directories() { return value("directories", QStringList()).toStringList(); } - -void Application::setDirectories(QStringList directories){ - if(!hasPermission("permissions")){ - return; - } - setValue("directories", directories); - emit directoriesChanged(directories); -} - -QByteArray Application::screenCapture(){ - if(!hasPermission("permissions")){ - return QByteArray(); - } - return screenCaptureNoSecurityCheck(); -} - -QByteArray Application::screenCaptureNoSecurityCheck(){ return qUncompress(*m_screenCapture); } - const QVariantMap& Application::getConfig(){ return m_config; } void Application::setConfig(const QVariantMap& config){ auto oldBin = bin(); @@ -531,43 +540,47 @@ void Application::setConfig(const QVariantMap& config){ setValue("bin", oldBin); } } - -void Application::saveScreen(){ - if(m_screenCapture != nullptr){ - return; - } - Oxide::Sentry::sentry_transaction("Save screen for Application", "saveScreen", [this](Oxide::Sentry::Transaction* t){ - O_INFO("Saving screen..."); - QByteArray bytes; - Oxide::Sentry::sentry_span(t, "save", "Save the framebuffer", [&bytes]{ - QBuffer buffer(&bytes); - buffer.open(QIODevice::WriteOnly); - dispatchToMainThread([&buffer]{ - if(!EPFrameBuffer::framebuffer()->save(&buffer, "JPG", 100)){ - O_WARNING("Failed to save buffer"); - } - }); - }); - O_DEBUG("Compressing data..."); - Oxide::Sentry::sentry_span(t, "compress", "Compress the framebuffer", [this, bytes]{ - m_screenCapture = new QByteArray(qCompress(bytes)); - }); - O_INFO("Screen saved " << m_screenCapture->size() << "bytes"); - }); -} void Application::started(){ + if(m_notification != nullptr){ + m_notification->remove(); + m_notification = nullptr; + } emit launched(); emit appsAPI->applicationLaunched(qPath()); } void Application::finished(int exitCode){ O_INFO("Application" << name() << "exit code" << exitCode); + getCompositorDBus()->lower(QString("connection/%1").arg(m_process->processId())); + if( + exitCode != EXIT_SUCCESS + && exitCode != SIGINT + && exitCode != SIGTERM + && exitCode != SIGKILL + && exitCode != 128 + EXIT_SUCCESS + && exitCode != 128 + SIGINT + && exitCode != 128 + SIGTERM + ){ + notificationAPI->add( + QUuid::createUuid().toString(), + "codes.eeems.tarnish", + "codes.eeems.tarnish", + QString("%1 crashed (%2)").arg(displayName()).arg(exitCode), + icon() + )->display(); + } emit exited(exitCode); + if(flags().contains("exclusive")){ + getCompositorDBus()->exitExclusiveMode().waitForFinished(); + } appsAPI->resumeIfNone(); emit appsAPI->applicationExited(qPath(), exitCode); - umountAll(); if(transient()){ unregister(); } + if(m_backup != nullptr){ + delete m_backup; + m_backup = nullptr; + } } void Application::readyReadStandardError(){ @@ -629,15 +642,23 @@ void Application::stateChanged(QProcess::ProcessState state){ } void Application::errorOccurred(QProcess::ProcessError error){ switch(error){ - case QProcess::FailedToStart: + case QProcess::FailedToStart:{ O_INFO("Application" << name() << "failed to start."); emit exited(-1); emit appsAPI->applicationExited(qPath(), -1); + notificationAPI->add( + QUuid::createUuid().toString(), + "codes.eeems.tarnish", + "codes.eeems.tarnish", + QString("%1 failed to start").arg(displayName()), + icon() + )->display(); if(transient()){ unregister(); } break; - case QProcess::Crashed: + } + case QProcess::Crashed: // Let the finished slot handle this O_INFO("Application" << name() << "crashed."); break; case QProcess::Timedout: @@ -675,342 +696,33 @@ void Application::updateEnvironment(){ } env.insert("PATH", envPath.join(":")); auto preload = env.value("LD_PRELOAD", "").split(":"); - QString sysfs_preload("/opt/lib/libsysfs_preload.so"); - if(!preload.contains(sysfs_preload)){ - preload.append(sysfs_preload); - } - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ - QString rm2fb_client("/opt/lib/librm2fb_client.so"); - if(!preload.contains(rm2fb_client)){ - preload.append(rm2fb_client); + if(!flags().contains("nopreload") && !flags().contains("nopreload.sysfs")){ + QString sysfs_preload("/opt/lib/libsysfs_preload.so"); + if(!preload.contains(sysfs_preload)){ + preload.append(sysfs_preload); } } - env.insert("LD_PRELOAD", preload.join(":")); - for(auto key : environment().keys()){ - env.insert(key, environment().value(key, "").toString()); - } - m_process->setEnvironment(env.toStringList()); -} - -void Application::mkdirs(const QString& path, mode_t mode){ - QDir dir(path); - if(!dir.exists()){ - QString subpath = ""; - for(auto part : path.split("/")){ - subpath += "/" + part; - QDir dir(subpath); - if(!dir.exists()){ - mkdir(subpath.toStdString().c_str(), mode); - } + if(!flags().contains("nopreload") && !flags().contains("nopreload.compositor")){ + QString blight_client("/opt/lib/libblight_client.so"); + if(!preload.contains(blight_client)){ + preload.append(blight_client); } - } -} - -void Application::bind(const QString& source, const QString& target, bool readOnly){ - umount(target); - if(QFileInfo(source).isDir()){ - mkdirs(target, 744); - }else{ - mkdirs(QFileInfo(target).dir().path(), 744); - } - auto ctarget = target.toStdString(); - auto csource = source.toStdString(); - O_DEBUG("mount" << source << target); - if(mount(csource.c_str(), ctarget.c_str(), NULL, MS_BIND, NULL)){ - O_WARNING("Failed to create bindmount: " << ::strerror(errno)); - return; - } - if(!readOnly){ - return; - } - if(mount(csource.c_str(), ctarget.c_str(), NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL)){ - O_WARNING("Failed to remount bindmount read only: " << ::strerror(errno)); - } - O_DEBUG("mount ro" << source << target); -} - -void Application::sysfs(const QString& path){ - mkdirs(path, 744); - umount(path); - O_DEBUG("sysfs" << path); - if(mount("none", path.toStdString().c_str(), "sysfs", 0, "")){ - O_WARNING("Failed to mount sysfs: " << ::strerror(errno)); - } -} - -void Application::ramdisk(const QString& path){ - mkdirs(path, 744); - umount(path); - O_DEBUG("ramdisk" << path); - if(mount("tmpfs", path.toStdString().c_str(), "tmpfs", 0, "size=249m,mode=755")){ - O_WARNING("Failed to create ramdisk: " << ::strerror(errno)); - } -} - -void Application::umount(const QString& path){ - if(!isMounted(path)){ - return; - } - auto cpath = path.toStdString(); - auto ret = ::umount2(cpath.c_str(), MNT_DETACH); - if((ret && ret != EINVAL && ret != ENOENT) || isMounted(path)){ - O_WARNING("umount failed" << path); - return; - } - QDir dir(path); - if(dir.exists()){ - rmdir(cpath.c_str()); - } - O_DEBUG("umount" << path); -} - -FifoHandler* Application::mkfifo(const QString& name, const QString& target){ - if(isMounted(target)){ - O_WARNING(target << "Already mounted"); - return fifos.contains(name) ? fifos[name] : nullptr; - } - auto source = resourcePath() + "/" + name; - if(!QFile::exists(source)){ - if(::mkfifo(source.toStdString().c_str(), 0644)){ - O_WARNING("Failed to create " << name << " fifo: " << ::strerror(errno)); + if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ + env.insert("RM2FB_DISABLE", "1"); } } - if(!QFile::exists(source)){ - O_WARNING("No fifo for " << name); - return fifos.contains(name) ? fifos[name] : nullptr; - } - bind(source, target); - if(!fifos.contains(name)){ - O_DEBUG("Creating fifo thread for" << source); - auto handler = new FifoHandler(name, source.toStdString().c_str(), this); - O_DEBUG("Connecting fifo thread events for" << source); - connect(handler, &FifoHandler::finished, [this, name]{ - if(fifos.contains(name)){ - fifos.take(name); - } - }); - fifos[name] = handler; - O_DEBUG("Starting fifo thread for" << source); - handler->start(); - O_DEBUG("Fifo thread for " << source << "started"); - } - return fifos[name]; -} - -void Application::symlink(const QString& source, const QString& target){ - if(QFile::exists(source)){ - return; + if(flags().contains("exclusive")){ + env.insert("OXIDE_PRELOAD_EXPOSE_FB", "1"); + env.insert("OXIDE_PRELOAD_ALLOW_RM2FB", "1"); } - O_DEBUG("symlink" << source << target); - if(::symlink(target.toStdString().c_str(), source.toStdString().c_str())){ - O_WARNING("Failed to create symlink: " << ::strerror(errno)); - return; + env.insert("LD_PRELOAD", preload.join(":")); + for(auto key : environment().keys()){ + env.insert(key, environment().value(key, "").toString()); } + env.remove("OXIDE_PRELOAD_DISABLE_INPUT"); + m_process->setEnvironment(env.toStringList()); } -const QString Application::resourcePath() { return "/tmp/tarnish-chroot/" + name(); } - -const QString Application::chrootPath() { return resourcePath() + "/chroot"; } - -void Application::mountAll(){ - Oxide::Sentry::sentry_transaction("Mount directories for Application", "mount", [this](Oxide::Sentry::Transaction* t){ -#ifdef SENTRY - if(t != nullptr){ - sentry_transaction_set_tag(t->inner, "application", name().toStdString().c_str()); - } -#endif - auto path = chrootPath(); - O_DEBUG("Setting up chroot" << path); - Oxide::Sentry::sentry_span(t, "bind", "Bind directories", [this, path]{ - // System tmpfs folders - bind("/dev", path + "/dev"); - bind("/proc", path + "/proc"); - sysfs(path + "/sys"); - // Folders required to run things - bind("/bin", path + "/bin", true); - bind("/sbin", path + "/sbin", true); - bind("/lib", path + "/lib", true); - bind("/usr/lib", path + "/usr/lib", true); - bind("/usr/bin", path + "/usr/bin", true); - bind("/usr/sbin", path + "/usr/sbin", true); - bind("/opt/bin", path + "/opt/bin", true); - bind("/opt/lib", path + "/opt/lib", true); - bind("/opt/usr/bin", path + "/opt/usr/bin", true); - bind("/opt/usr/lib", path + "/opt/usr/lib", true); - }); - Oxide::Sentry::sentry_span(t, "ramdisk", "Create ramdisks", [this, path]{ - // tmpfs folders - mkdirs(path + "/tmp", 744); - if(!QFile::exists(path + "/run")){ - ramdisk(path + "/run"); - } - if(!QFile::exists(path + "/var/volatile")){ - ramdisk(path + "/var/volatile"); - } - }); - Oxide::Sentry::sentry_span(t, "configured", "Bind configured directories", [this, path]{ - // Configured folders - for(auto directory : directories()){ - bind(directory, path + directory); - } - }); - Oxide::Sentry::sentry_span(t, "fifo", "Create fifos", [this, path]{ - // Fake sys devices - auto fifo = mkfifo("powerState", path + "/sys/power/state"); - connect(fifo, &FifoHandler::dataRecieved, this, &Application::powerStateDataRecieved); - }); - Oxide::Sentry::sentry_span(t, "symlink", "Create symlinks", [this, path]{ - // Missing symlinks - symlink(path + "/var/run", "../run"); - symlink(path + "/var/lock", "../run/lock"); - symlink(path + "/var/tmp", "volatile/tmp"); - }); - }); -} - -void Application::umountAll(){ - Oxide::Sentry::sentry_transaction("Unmount directories for Application", "umount", [this](Oxide::Sentry::Transaction* t){ -#ifdef SENTRY - if(t != nullptr){ - sentry_transaction_set_tag(t->inner, "application", name().toStdString().c_str()); - } -#endif - auto path = chrootPath(); - Oxide::Sentry::sentry_span(t, "fifos", "Remove fifos", [this]{ - for(auto name : fifos.keys()){ - auto fifo = fifos.take(name); - fifo->quit(); - fifo->deleteLater(); - } - }); - QDir dir(path); - if(!dir.exists()){ - return; - } - O_DEBUG("Tearing down chroot" << path); - Oxide::Sentry::sentry_span(t, "dirs", "Remove directories", [dir]{ - for(auto file : dir.entryList(QDir::Files)){ - QFile::remove(file); - } - }); - Oxide::Sentry::sentry_span(t, "umount", "Unmount all mounts", [this]{ - for(auto mount : getActiveApplicationMounts()){ - umount(mount); - } - }); - if(!getActiveApplicationMounts().isEmpty()){ - O_WARNING("Some items are still mounted in chroot" << path); - return; - } - Oxide::Sentry::sentry_span(t, "rm", "Remove final folder", [&dir]{ - dir.removeRecursively(); - }); - }); -} - -bool Application::isMounted(const QString& path){ return getActiveMounts().contains(path); } - -QStringList Application::getActiveApplicationMounts(){ - auto path = chrootPath() + "/"; - QStringList activeMounts = getActiveMounts().filter(QRegularExpression("^" + QRegularExpression::escape(path) + ".*")); - activeMounts.sort(Qt::CaseSensitive); - std::reverse(std::begin(activeMounts), std::end(activeMounts)); - return activeMounts; -} - -QStringList Application::getActiveMounts(){ - QFile mounts("/proc/mounts"); - if(!mounts.open(QIODevice::ReadOnly)){ - O_WARNING("Unable to open /proc/mounts"); - return QStringList(); - } - QString line; - QStringList activeMounts; - while(!(line = mounts.readLine()).isEmpty()){ - auto mount = line.section(' ', 1, 1); - if(mount.startsWith("/")){ - activeMounts.append(mount); - } - } - mounts.close(); - activeMounts.sort(Qt::CaseSensitive); - std::reverse(std::begin(activeMounts), std::end(activeMounts)); - return activeMounts; -} -void Application::showSplashScreen(){ - auto frameBuffer = EPFrameBuffer::framebuffer(); - O_DEBUG("Waiting for other painting to finish..."); - Oxide::Sentry::sentry_transaction("Show Application Splash Screen", "showSplashScreen", [this, frameBuffer](Oxide::Sentry::Transaction* t){ -#ifdef SENTRY - if(t != nullptr){ - sentry_transaction_set_tag(t->inner, "application", name().toStdString().c_str()); - } -#else - Q_UNUSED(t); -#endif - Oxide::Sentry::sentry_span(t, "wait", "Wait for screen to be ready", [frameBuffer](){ - dispatchToMainThread([frameBuffer]{ - while(frameBuffer->paintingActive()){ - EPFrameBuffer::waitForLastUpdate(); - } - }); - }); - O_INFO("Displaying splashscreen for" << name()); - Oxide::Sentry::sentry_span(t, "paint", "Draw splash screen", [this, frameBuffer](){ - dispatchToMainThread([this, frameBuffer]{ - QPainter painter(frameBuffer); - auto size = frameBuffer->size(); - auto rect = frameBuffer->rect(); - auto fm = painter.fontMetrics(); - painter.fillRect(rect, Qt::white); - QString splashPath = splash(); - if(splashPath.isEmpty() || !QFile::exists(splashPath)){ - splashPath = icon(); - } - if(!splashPath.isEmpty() && QFile::exists(splashPath)){ - O_INFO("Using image" << splashPath); - int splashWidth = size.width() / 2; - QSize splashSize(splashWidth, splashWidth); - QImage splash = QImage(splashPath); - if(systemAPI->landscape()){ - splash = splash.transformed(QTransform().rotate(90.0)); - } - splash = splash.scaled(splashSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QRect splashRect( - QPoint( - (size.width() / 2) - (splashWidth / 2), - (size.height() / 2) - (splashWidth / 2) - ), - splashSize - ); - painter.drawImage(splashRect, splash, splash.rect()); - EPFrameBuffer::sendUpdate(frameBuffer->rect(), EPFrameBuffer::HighQualityGrayscale, EPFrameBuffer::FullUpdate, true); - } - painter.end(); - notificationAPI->drawNotificationText("Loading " + displayName() + "..."); - }); - }); - O_DEBUG("Waiting for screen to finish..."); - Oxide::Sentry::sentry_span(t, "wait", "Wait for screen finish updating", [](){ - dispatchToMainThread([]{ - EPFrameBuffer::waitForLastUpdate(); - }); - }); - }); - O_INFO("Finished painting splash screen for" << name()); -} -void Application::powerStateDataRecieved(FifoHandler* handler, const QString& data){ - Q_UNUSED(handler); - if(!permissions().contains("power")){ - O_WARNING("Denied powerState request"); - return; - } - if((QStringList() << "mem" << "freeze" << "standby").contains(data)){ - systemAPI->suspend(); - }else{ - O_WARNING("Unknown power state call: " << data); - } -} void Application::startSpan(std::string operation, std::string description){ if(!sharedSettings.applicationUsage()){ return; @@ -1026,49 +738,6 @@ void Application::startSpan(std::string operation, std::string description){ span = Oxide::Sentry::start_span(transaction, operation, description); } -void Application::recallScreen() { - if (m_screenCapture == nullptr) { - return; - } - Oxide::Sentry::sentry_transaction( - "Recall Application Screen", "recallScreen", [this](Oxide::Sentry::Transaction *t) { - O_DEBUG("Uncompressing screen..."); - QImage img; - Oxide::Sentry::sentry_span( - t, "decompress", "Decompress the framebuffer", [this, &img] { - img = QImage::fromData(screenCaptureNoSecurityCheck(), "JPG"); - }); - if (img.isNull()) { - O_WARNING("Screen capture was corrupt"); - O_DEBUG(m_screenCapture->size()); - delete m_screenCapture; - return; - } - O_INFO("Recalling screen..."); - Oxide::Sentry::sentry_span( - t, "recall", "Recall the screen", [this, img] { - dispatchToMainThread([img] { - auto size = EPFrameBuffer::framebuffer()->size(); - QRect rect(0, 0, size.width(), size.height()); - auto frameBuffer = EPFrameBuffer::framebuffer(); - QPainter painter(frameBuffer); - painter.drawImage(rect, img); - painter.end(); - EPFrameBuffer::sendUpdate( - rect, - EPFrameBuffer::HighQualityGrayscale, - EPFrameBuffer::FullUpdate, - true - ); - EPFrameBuffer::waitForLastUpdate(); - }); - delete m_screenCapture; - m_screenCapture = nullptr; - }); - O_INFO("Screen recalled."); - }); -} - void Application::waitForFinished(){ if(m_process->processId()){ m_process->waitForFinished(); @@ -1077,14 +746,13 @@ void Application::waitForFinished(){ Application::Application(QString path, QObject *parent) : QObject(parent), m_path(path), - m_backgrounded(false), - fifos() + m_backgrounded(false) { - m_process = new SandBoxProcess(this); - connect(m_process, &SandBoxProcess::started, this, &Application::started); + m_process = new ApplicationProcess(this); + connect(m_process, &ApplicationProcess::started, this, &Application::started); connect( m_process, - QOverload::of(&SandBoxProcess::finished), + QOverload::of(&ApplicationProcess::finished), [this](int exitCode, QProcess::ExitStatus status) { Q_UNUSED(status); finished(exitCode); @@ -1092,25 +760,25 @@ Application::Application(QString path, QObject *parent) ); connect( m_process, - &SandBoxProcess::readyReadStandardError, + &ApplicationProcess::readyReadStandardError, this, &Application::readyReadStandardError ); connect( m_process, - &SandBoxProcess::readyReadStandardOutput, + &ApplicationProcess::readyReadStandardOutput, this, &Application::readyReadStandardOutput ); connect( m_process, - &SandBoxProcess::stateChanged, + &ApplicationProcess::stateChanged, this, &Application::stateChanged ); connect( m_process, - &SandBoxProcess::errorOccurred, + &ApplicationProcess::errorOccurred, this, &Application::errorOccurred ); @@ -1121,7 +789,6 @@ Application::~Application() { if (m_screenCapture != nullptr) { delete m_screenCapture; } - umountAll(); if (p_stdout != nullptr) { p_stdout->flush(); delete p_stdout; @@ -1241,7 +908,14 @@ int Application::state(){ return stateNoSecurityCheck(); } -bool SandBoxProcess::setUser(const QString& name){ +ApplicationProcess::ApplicationProcess(QObject* parent) +: QProcess(parent), + m_gid(0), + m_uid(0), + m_mask(0) +{} + +bool ApplicationProcess::setUser(const QString& name){ try{ m_uid = Oxide::getUID(name); return true; @@ -1251,7 +925,7 @@ bool SandBoxProcess::setUser(const QString& name){ } } -bool SandBoxProcess::setGroup(const QString& name){ +bool ApplicationProcess::setGroup(const QString& name){ try{ m_gid = Oxide::getGID(name); return true; @@ -1261,30 +935,13 @@ bool SandBoxProcess::setGroup(const QString& name){ } } -bool SandBoxProcess::setChroot(const QString& path){ - if(path.isEmpty() || path == "/"){ - m_chroot = ""; - return true; - } - QDir dir(path); - if(dir.exists()){ - m_chroot = path; - return true; - } - return false; -} - -void SandBoxProcess::setMask(mode_t mask){ +void ApplicationProcess::setMask(mode_t mask){ m_mask = mask; } -void SandBoxProcess::setupChildProcess() { +void ApplicationProcess::setupChildProcess() { // Drop all privileges in the child process setgroups(0, 0); - if(!m_chroot.isEmpty()){ - // enter a chroot jail. - chroot(m_chroot.toStdString().c_str()); - } // Change to correct user setresgid(m_gid, m_gid, m_gid); setresuid(m_uid, m_uid, m_uid); diff --git a/applications/system-service/application.h b/applications/system-service/application.h index a752abc66..535d4a885 100644 --- a/applications/system-service/application.h +++ b/applications/system-service/application.h @@ -30,24 +30,24 @@ #include #include #include +#include -#include "fifohandler.h" +class Notification; +#include "notification.h" // Must be included so that generate_xml.sh will work #include "../../shared/liboxide/meta.h" #define DEFAULT_PATH "/opt/bin:/opt/sbin:/opt/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" -class SandBoxProcess : public QProcess{ +class ApplicationProcess : public QProcess{ Q_OBJECT public: - SandBoxProcess(QObject* parent = nullptr) - : QProcess(parent), m_gid(0), m_uid(0), m_chroot(""), m_mask(0) {} + ApplicationProcess(QObject* parent = nullptr); bool setUser(const QString& name); bool setGroup(const QString& name); - bool setChroot(const QString& path); void setMask(mode_t mask); protected: @@ -56,7 +56,6 @@ class SandBoxProcess : public QProcess{ private: gid_t m_gid; uid_t m_uid; - QString m_chroot; mode_t m_mask; }; @@ -83,11 +82,8 @@ class Application : public QObject{ Q_PROPERTY(QString splash READ splash WRITE setSplash NOTIFY splashChanged) Q_PROPERTY(QVariantMap environment READ environment NOTIFY environmentChanged) Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged) - Q_PROPERTY(bool chroot READ chroot) Q_PROPERTY(QString user READ user) Q_PROPERTY(QString group READ group) - Q_PROPERTY(QStringList directories READ directories WRITE setDirectories NOTIFY directoriesChanged) - Q_PROPERTY(QByteArray screenCapture READ screenCapture) public: Application(QDBusObjectPath path, QObject* parent) : Application(path.path(), parent) {} @@ -143,18 +139,11 @@ class Application : public QObject{ Q_INVOKABLE void setEnvironment(QVariantMap environment); QString workingDirectory(); void setWorkingDirectory(const QString& workingDirectory); - bool chroot(); QString user(); QString group(); - QStringList directories(); - void setDirectories(QStringList directories); - QByteArray screenCapture(); - QByteArray screenCaptureNoSecurityCheck(); const QVariantMap& getConfig(); void setConfig(const QVariantMap& config); - void saveScreen(); - void recallScreen(); void waitForFinished(); void signal(int signal); QVariant value(QString name, QVariant defaultValue = QVariant()); @@ -163,6 +152,7 @@ class Application : public QObject{ void uninterruptApplication(); void waitForPause(); void waitForResume(); + QString id(); signals: void launched(); @@ -187,47 +177,32 @@ public slots: void sigUsr2(); private slots: - void showSplashScreen(); void started(); void finished(int exitCode); void readyReadStandardError(); void readyReadStandardOutput(); void stateChanged(QProcess::ProcessState state); void errorOccurred(QProcess::ProcessError error); - void powerStateDataRecieved(FifoHandler* handler, const QString& data); private: QVariantMap m_config; QString m_path; - SandBoxProcess* m_process; + ApplicationProcess* m_process; bool m_backgrounded; QByteArray* m_screenCapture = nullptr; QElapsedTimer timer; - QMap fifos; Oxide::Sentry::Transaction* transaction = nullptr; Oxide::Sentry::Span* span = nullptr; int p_stdout_fd = -1; QTextStream* p_stdout = nullptr; int p_stderr_fd = -1; QTextStream* p_stderr = nullptr; + Notification* m_notification = nullptr; + QImage* m_backup = nullptr; bool hasPermission(QString permission, const char* sender = __builtin_FUNCTION()); void delayUpTo(int milliseconds); void updateEnvironment(); - void mkdirs(const QString& path, mode_t mode = 0700); - void bind(const QString& source, const QString& target, bool readOnly = false); - void sysfs(const QString& path); - void ramdisk(const QString& path); - void umount(const QString& path); - FifoHandler* mkfifo(const QString& name, const QString& target); - void symlink(const QString& source, const QString& target); - const QString resourcePath(); - const QString chrootPath(); - void mountAll(); - void umountAll(); - bool isMounted(const QString& path); - QStringList getActiveApplicationMounts(); - QStringList getActiveMounts(); void startSpan(std::string operation, std::string description); }; diff --git a/applications/system-service/appsapi.cpp b/applications/system-service/appsapi.cpp index 554182658..1384a3652 100644 --- a/applications/system-service/appsapi.cpp +++ b/applications/system-service/appsapi.cpp @@ -1,8 +1,11 @@ #include +#include +#include #include "appsapi.h" #include "notificationapi.h" #include "systemapi.h" +#include "dbusservice.h" using namespace Oxide; @@ -726,22 +729,22 @@ void AppsAPI::readApplications(){ continue; } QVariantMap properties { - {"name", name}, - {"displayName", displayName}, - {"description", settings.value("description", displayName).toString()}, - {"bin", bin}, - {"type", type}, - {"flags", settings.value("flags", QStringList()).toStringList()}, - {"icon", settings.value("icon", "").toString()}, - {"onPause", settings.value("onPause", "").toString()}, - {"onResume", settings.value("onResume", "").toString()}, - {"onStop", settings.value("onStop", "").toString()}, - {"environment", settings.value("environment", QVariantMap()).toMap()}, - {"workingDirectory", settings.value("workingDirectory", "").toString()}, - {"directories", settings.value("directories", QStringList()).toStringList()}, - {"permissions", settings.value("permissions", QStringList()).toStringList()}, - {"splash", settings.value("splash", "").toString()}, - }; + {"name", name}, + {"displayName", displayName}, + {"description", settings.value("description", displayName).toString()}, + {"bin", bin}, + {"type", type}, + {"flags", settings.value("flags", QStringList()).toStringList()}, + {"icon", settings.value("icon", "").toString()}, + {"onPause", settings.value("onPause", "").toString()}, + {"onResume", settings.value("onResume", "").toString()}, + {"onStop", settings.value("onStop", "").toString()}, + {"environment", settings.value("environment", QVariantMap()).toMap()}, + {"workingDirectory", settings.value("workingDirectory", "").toString()}, + {"directories", settings.value("directories", QStringList()).toStringList()}, + {"permissions", settings.value("permissions", QStringList()).toStringList()}, + {"splash", settings.value("splash", "").toString()}, + }; if(settings.contains("user")){ properties.insert("user", settings.value("user", "").toString()); } @@ -836,57 +839,59 @@ void AppsAPI::ensureForegroundApp() { }); } -AppsAPI::~AppsAPI() { +void AppsAPI::shutdown() { m_stopping = true; writeApplications(); settings.sync(); - dispatchToMainThread([this] { - auto frameBuffer = EPFrameBuffer::framebuffer(); - O_DEBUG("Waiting for other painting to finish..."); - while (frameBuffer->paintingActive()) { - EPFrameBuffer::waitForLastUpdate(); - } - QPainter painter(frameBuffer); - auto rect = frameBuffer->rect(); - auto fm = painter.fontMetrics(); - O_INFO("Clearing screen..."); - painter.setPen(Qt::white); - painter.fillRect(rect, Qt::black); - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::FullUpdate, true); - EPFrameBuffer::waitForLastUpdate(); - painter.end(); - O_DEBUG("Stopping applications..."); - for (auto app : applications) { + dispatchToMainThread([this]{ + Blight::shared_buf_t buffer = createBuffer(); + if(buffer != nullptr){ + auto image = Oxide::QML::getImageForSurface(buffer); + QPainter painter(&image); + qDebug() << "Clearing screen..."; + painter.setPen(Qt::white); + painter.fillRect(image.rect(), Qt::black); + painter.end(); + addSystemBuffer(buffer); + } + O_DEBUG("Stopping applicaLtions..."); + auto notification = notificationAPI->paintNotification("", ""); + for(auto app : applications){ if (app->stateNoSecurityCheck() != Application::Inactive) { auto text = "Stopping " + app->displayName() + "..."; O_DEBUG(text.toStdString().c_str()); - notificationAPI->drawNotificationText(text, Qt::white, Qt::black); - EPFrameBuffer::waitForLastUpdate(); + notification->setProperty("text", text); } app->stopNoSecurityCheck(); } O_INFO("Ensuring all applications have stopped..."); - for (auto app : applications) { + for(auto app : applications){ app->waitForFinished(); app->deleteLater(); } applications.clear(); - QPainter painter2(frameBuffer); - O_INFO("Displaying final quit message..."); - painter2.fillRect(rect, Qt::black); - painter2.setPen(Qt::white); - if(systemAPI->landscape()){ - auto x = rect.width() / 2; - auto y = rect.height() / 2; - painter2.translate(x, y); - painter2.rotate(90.0); - painter2.translate(-x, -y); - } - painter2.drawText(rect, Qt::AlignCenter, "Goodbye!"); - EPFrameBuffer::waitForLastUpdate(); - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::FullUpdate, true); - painter2.end(); - EPFrameBuffer::waitForLastUpdate(); + notification->setProperty("notificationVisible", false); + if(buffer != nullptr){ + auto image = Oxide::QML::getImageForSurface(buffer); + QPainter painter(&image); + O_INFO("Displaying final quit message..."); + auto rect = image.rect(); + painter.fillRect(rect, Qt::black); + painter.setPen(Qt::white); + if(systemAPI->landscape()){ + auto x = rect.width() / 2; + auto y = rect.height() / 2; + painter.translate(x, y); + painter.rotate(90.0); + painter.translate(-x, -y); + } + painter.drawText(rect, Qt::AlignCenter, "Goodbye!"); + painter.end(); + auto maybe = Blight::connection()->repaint(buffer); + if(maybe.has_value()){ + maybe.value()->wait(); + } + } }); } #include "moc_appsapi.cpp" diff --git a/applications/system-service/appsapi.h b/applications/system-service/appsapi.h index f5dbe60ea..14b087248 100644 --- a/applications/system-service/appsapi.h +++ b/applications/system-service/appsapi.h @@ -12,7 +12,6 @@ #include #include #include -#include #include "apibase.h" #include "application.h" @@ -41,16 +40,15 @@ class AppsAPI : public APIBase { public: static AppsAPI* singleton(AppsAPI* self = nullptr); AppsAPI(QObject* parent); - ~AppsAPI(); + void shutdown(); void startup(); - int state() { return 0; } // Ignore this, it's a kludge to get the xml to generate + int state(){ return 0; } // Ignore this, it's a kludge to get the xml to generate void setEnabled(bool enabled); Q_INVOKABLE QDBusObjectPath registerApplication(QVariantMap properties); QDBusObjectPath registerApplicationNoSecurityCheck(QVariantMap properties); Q_INVOKABLE bool unregisterApplication(QDBusObjectPath path); - Q_INVOKABLE void reload(); QDBusObjectPath startupApplication(); diff --git a/applications/system-service/bss.cpp b/applications/system-service/bss.cpp index 56c260eb1..a7fbc9d84 100644 --- a/applications/system-service/bss.cpp +++ b/applications/system-service/bss.cpp @@ -22,7 +22,7 @@ void BSS::registerPath(){ if(bus.registerObject(path(), this, QDBusConnection::ExportAllContents)){ O_DEBUG("Registered" << path() << OXIDE_BSS_INTERFACE); }else{ - O_WARNING("Failed to register" << path()); + O_DEBUG("Failed to register" << path()); } } diff --git a/applications/system-service/buttonhandler.cpp b/applications/system-service/buttonhandler.cpp deleted file mode 100644 index 9c1892817..000000000 --- a/applications/system-service/buttonhandler.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "buttonhandler.h" - - -using namespace Oxide; - -void button_exit_handler(){ - // Release lock - buttons.unlock(); - buttons.close(); -} - -void flush_stream(istream* stream){ - // Skip the next event - input_event ie; - streamsize sie = static_cast(sizeof(struct input_event)); - stream->read((char*)&ie, sie); -} -void press_button(event_device& evdev, int code, istream* stream){ - O_DEBUG("inject button " << code); - evdev.unlock(); - evdev.write(EV_KEY, code, 1); - flush_stream(stream); - evdev.ev_syn(); - flush_stream(stream); - evdev.write(EV_KEY, code, 0); - flush_stream(stream); - evdev.ev_syn(); - flush_stream(stream); - evdev.lock(); -} - -ButtonHandler* ButtonHandler::init(){ - static ButtonHandler* instance; - if(instance != nullptr){ - return instance; - } - // Get event devices - if(buttons.fd == -1){ - O_WARNING("Failed to open event device: " << buttons.device.c_str()); - throw QException(); - } - if(atexit(button_exit_handler)){ - button_exit_handler(); - throw QException(); - } - instance = new ButtonHandler(); - instance->start(); - return instance; -} - -ButtonHandler::ButtonHandler() : QThread(), filebuf(buttons.fd, ios::in), stream(&filebuf), pressed(), timer(this), m_enabled(true) { - flood = build_flood(); - timer.setInterval(100); - timer.setSingleShot(false); - connect(&timer, &QTimer::timeout, this, &ButtonHandler::timeout); - timer.start(); -} - -void ButtonHandler::setEnabled(bool enabled){ - m_enabled = enabled; -} - -void ButtonHandler::clear_buffer(){ - if(buttons.fd == -1){ - return; - } - O_DEBUG("Clearing event buffer on" << buttons.device.c_str()); - ::write(buttons.fd, flood, EVENT_FLOOD_SIZE); -} - -input_event* ButtonHandler::event_flood(){ return flood; } - -void ButtonHandler::run(){ - char name[256]; - memset(name, 0, sizeof(name)); - ioctl(buttons.fd, EVIOCGNAME(sizeof(name)), name); - O_INFO("Reading From : " << buttons.device.c_str() << " (" << name << ")"); - buttons.lock(); - O_DEBUG("Registering exit handler..."); - // Mapping the correct button IDs. - unordered_map map; - map[105] = PressRecord("Left", Qt::Key_Left); - map[102] = PressRecord("Middle", Qt::Key_Home); - map[106] = PressRecord("Right", Qt::Key_Right); - map[116] = PressRecord("Power", Qt::Key_PowerOff); - - O_DEBUG("Listening for keypresses..."); - // Get the size of an input event in the right format! - input_event ie; - streamsize sie = static_cast(sizeof(struct input_event)); - - while(stream.read((char*)&ie, sie)){ - // TODO - Properly pass through non-button presses - // Read for non-zero event codes. - emit rawEvent(ie); - if(ie.code != 0){ - emit activity(); - // Toggle the button state. - map[ie.code].pressed = !map[ie.code].pressed; - if(!map[ie.code].pressed && map[ie.code].name == "Unknown"){ - press_button(buttons, ie.code, &stream); - }else if(map[ie.code].pressed){ - emit keyDown(map[ie.code].keyCode); - }else{ - emit keyUp(map[ie.code].keyCode); - } - }else{ - qApp->processEvents(QEventLoop::AllEvents, 100); - yieldCurrentThread(); - } - } -} - -input_event* ButtonHandler::build_flood(){ - auto n = 512 * 8; - auto num_inst = 4; - input_event* ev = (input_event *)malloc(sizeof(struct input_event) * n * num_inst); - memset(ev, 0, sizeof(input_event) * n * num_inst); - auto i = 0; - while (i < n) { - ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 1); - ev[i++] = createEvent(EV_SYN, 0, 0); - ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 2); - ev[i++] = createEvent(EV_SYN, 0, 0); - } - return ev; -} - -input_event ButtonHandler::createEvent(ushort type, ushort code, int value){ - struct input_event event; - event.type = type; - event.code = code; - event.value = value; - return event; -} -void ButtonHandler::pressKey(Qt::Key key){ - int code; - switch(key){ - case Qt::Key_Left: - code = 105; - break; - case Qt::Key_Home: - code = 102; - break; - case Qt::Key_Right: - code = 106; - break; - case Qt::Key_PowerOff: - code = 116; - break; - default: - return; - } - press_button(buttons, code, &stream); -} - -void ButtonHandler::keyDown(Qt::Key key){ - if(!m_enabled){ - return; - } - O_DEBUG("Down" << key); - if(validKeys.contains(key) && !pressed.contains(key)){ - QElapsedTimer timer; - timer.start(); - pressed.insert(key, timer); - } -} - -void ButtonHandler::keyUp(Qt::Key key){ - if(!m_enabled){ - return; - } - O_DEBUG("Up" << key); - if(!pressed.contains(key)){ - // This should never happen - return; - } - auto value = pressed.value(key); - pressed.remove(key); - if(value.hasExpired(700)){ - // Held event already fired - return; - } - if(key == Qt::Key_PowerOff){ - emit powerPress(); - return; - } - pressKey(key); -} - -void ButtonHandler::timeout(){ - if(!m_enabled){ - return; - } - for(auto key : pressed.keys()){ - // If the key has been held for a while - if(!pressed.value(key).hasExpired(700)){ - continue; - } - O_DEBUG("Key held" << key); - switch(key){ - case Qt::Key_Left: - emit leftHeld(); - break; - case Qt::Key_Home: - emit homeHeld(); - break; - case Qt::Key_Right: - emit rightHeld(); - break; - case Qt::Key_PowerOff: - emit powerHeld(); - break; - default: - continue; - } - pressed.remove(key); - } -} - -#include "moc_buttonhandler.cpp" diff --git a/applications/system-service/buttonhandler.h b/applications/system-service/buttonhandler.h deleted file mode 100644 index 8ae1b0545..000000000 --- a/applications/system-service/buttonhandler.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef BUTTONHANDLER_H -#define BUTTONHANDLER_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace Oxide; - -#define buttonHandler ButtonHandler::init() -#define EVENT_FLOOD_SIZE 512 * 8 * 4 * sizeof(input_event) - -struct PressRecord { - bool pressed = false; - struct timeval pressTime; - string name = "Unknown"; - Qt::Key keyCode = Qt::Key_unknown; - PressRecord (string name, Qt::Key keyCode) : name(name), keyCode(keyCode) {} - PressRecord() : PressRecord("Unknown", Qt::Key_unknown){} -}; - -static event_device buttons(deviceSettings.getButtonsDevicePath(), O_RDWR); - - -class ButtonHandler : public QThread { - Q_OBJECT - -public: - static ButtonHandler* init(); - - ButtonHandler(); - void setEnabled(bool enabled); - void clear_buffer(); - input_event* event_flood(); - -public slots: - void pressKey(Qt::Key); - -private slots: - void keyDown(Qt::Key key); - void keyUp(Qt::Key key); - void timeout(); - -signals: - void leftHeld(); - void homeHeld(); - void rightHeld(); - void powerHeld(); - void powerPress(); - void activity(); - void rawEvent(const input_event&); - -protected: - void run(); - __gnu_cxx::stdio_filebuf filebuf; - istream stream; - QMap pressed; - QTimer timer; - const QSet validKeys { Qt::Key_Left, Qt::Key_Home, Qt::Key_Right, Qt::Key_PowerOff }; - bool m_enabled; - input_event* flood; - input_event* build_flood(); - inline static input_event createEvent(ushort type, ushort code, int value); -}; - -#endif // BUTTONHANDLER_H diff --git a/applications/system-service/controller.h b/applications/system-service/controller.h index b95d47bae..ecf9164c3 100644 --- a/applications/system-service/controller.h +++ b/applications/system-service/controller.h @@ -1,19 +1,43 @@ #pragma once #include +#include #include "appsapi.h" #include "screenapi.h" +#include "systemapi.h" class Controller : public QObject{ Q_OBJECT + Q_PROPERTY(QString deviceName READ deviceName) + Q_PROPERTY(bool leftSwipeEnabled READ leftSwipeEnabled NOTIFY leftSwipeEnabledChanged) + Q_PROPERTY(bool rightSwipeEnabled READ rightSwipeEnabled NOTIFY rightSwipeEnabledChanged) + Q_PROPERTY(bool upSwipeEnabled READ upSwipeEnabled NOTIFY upSwipeEnabledChanged) + Q_PROPERTY(bool downSwipeEnabled READ downSwipeEnabled NOTIFY downSwipeEnabledChanged) + Q_PROPERTY(int leftSwipeLength READ leftSwipeLength NOTIFY leftSwipeLengthChanged) + Q_PROPERTY(int rightSwipeLength READ rightSwipeLength NOTIFY rightSwipeLengthChanged) + Q_PROPERTY(int upSwipeLength READ upSwipeLength NOTIFY upSwipeLengthChanged) + Q_PROPERTY(int downSwipeLength READ downSwipeLength NOTIFY downSwipeLengthChanged) public: - explicit Controller(QObject* parent = nullptr) : QObject(parent){} + static Controller* singleton(){ + static Controller* instance = new Controller(qApp); + return instance; + } + bool enabled = true; // Used to disable sending input when suspending Q_INVOKABLE void screenshot(){ screenAPI->screenshot(); } Q_INVOKABLE void taskSwitcher(){ appsAPI->openTaskSwitcher(); } Q_INVOKABLE void processManager(){ appsAPI->openTaskManager(); } - Q_INVOKABLE void back(){ appsAPI->previousApplication(); } + Q_INVOKABLE void back(){ + auto currentApplication = appsAPI->getApplication(appsAPI->currentApplicationNoSecurityCheck()); + if(currentApplication != nullptr && currentApplication->path() == appsAPI->lockscreenApplication().path()){ + O_DEBUG("Back cancelled. On lockscreen"); + return; + } + if(!appsAPI->previousApplicationNoSecurityCheck()){ + appsAPI->openDefaultApplication(); + } + } Q_INVOKABLE void lock(){ appsAPI->openLockScreen(); } Q_INVOKABLE void terminal(){ appsAPI->openTerminal(); } Q_INVOKABLE void close(){ @@ -39,4 +63,89 @@ class Controller : public QObject{ } currentApplication->stop(); } + Q_INVOKABLE void toggleSwipes(){ systemAPI->toggleSwipes(); } + Q_INVOKABLE void suspend(){ systemAPI->suspend(); } + + QString deviceName(){ return deviceSettings.getDeviceName(); } + bool leftSwipeEnabled(){ return systemAPI->getSwipeEnabled(SystemAPI::Left); } + bool rightSwipeEnabled(){ return systemAPI->getSwipeEnabled(SystemAPI::Right); } + bool upSwipeEnabled(){ return systemAPI->getSwipeEnabled(SystemAPI::Up); } + bool downSwipeEnabled(){ return systemAPI->getSwipeEnabled(SystemAPI::Down); } + int leftSwipeLength(){ return systemAPI->getSwipeLength(SystemAPI::Left); } + int rightSwipeLength(){ return systemAPI->getSwipeLength(SystemAPI::Right); } + int upSwipeLength(){ return systemAPI->getSwipeLength(SystemAPI::Up); } + int downSwipeLength(){ return systemAPI->getSwipeLength(SystemAPI::Down); } + +signals: + void keyPressed(int, int, const QString&); + void keyReleased(int, int, const QString&); + void leftSwipeEnabledChanged(bool); + void rightSwipeEnabledChanged(bool); + void upSwipeEnabledChanged(bool); + void downSwipeEnabledChanged(bool); + void leftSwipeLengthChanged(int); + void rightSwipeLengthChanged(int); + void upSwipeLengthChanged(int); + void downSwipeLengthChanged(int); + +private: + explicit Controller(QObject* parent = nullptr) : QObject(parent){ + connect(systemAPI, &SystemAPI::swipeLengthChanged, this, [this](int swipe, int length){ + switch((SystemAPI::SwipeDirection)swipe){ + case SystemAPI::Left: + emit leftSwipeLengthChanged(length); + break; + case SystemAPI::Right: + emit rightSwipeLengthChanged(length); + break; + case SystemAPI::Up: + emit upSwipeLengthChanged(length); + break; + case SystemAPI::Down: + emit downSwipeLengthChanged(length); + break; + default: + break; + } + }); + connect(systemAPI, &SystemAPI::swipeEnabledChanged, this, [this](int swipe, bool enabled){ + switch((SystemAPI::SwipeDirection)swipe){ + case SystemAPI::Left: + emit leftSwipeEnabledChanged(enabled); + break; + case SystemAPI::Right: + emit rightSwipeEnabledChanged(enabled); + break; + case SystemAPI::Up: + emit upSwipeEnabledChanged(enabled); + break; + case SystemAPI::Down: + emit downSwipeEnabledChanged(enabled); + break; + default: + break; + } + }); + eventListener->append([this](QObject* object, QEvent* event){ + if(!enabled){ + return false; + } + Q_UNUSED(object); + switch(event->type()){ + case QEvent::KeyPress:{ + auto keyEvent = static_cast(event); + emit keyPressed(keyEvent->key(), keyEvent->modifiers(), keyEvent->text()); + return true; + } + case QEvent::KeyRelease:{ + auto keyEvent = static_cast(event); + emit keyReleased(keyEvent->key(), keyEvent->modifiers(), keyEvent->text()); + return true; + } + default: + break; + } + return false; + }); + } }; diff --git a/applications/system-service/dbusservice.cpp b/applications/system-service/dbusservice.cpp index 331fb198b..b47fac684 100644 --- a/applications/system-service/dbusservice.cpp +++ b/applications/system-service/dbusservice.cpp @@ -5,25 +5,17 @@ #include "systemapi.h" #include "screenapi.h" #include "notificationapi.h" -#include "buttonhandler.h" -#include "digitizerhandler.h" + +#include + +using namespace std::chrono; DBusService* DBusService::singleton(){ static DBusService* instance; if(instance == nullptr){ qRegisterMetaType>(); - O_INFO("Creating DBusService instance"); + O_DEBUG("Creating DBusService instance"); instance = new DBusService(qApp); - connect(qApp, &QGuiApplication::aboutToQuit, [=]{ - if(instance == nullptr){ - return; - } - emit instance->aboutToQuit(); - O_INFO("Killing dbus service"); - delete instance; - qApp->processEvents(); - instance = nullptr; - }); auto bus = QDBusConnection::systemBus(); if(!bus.isConnected()){ #ifdef SENTRY @@ -32,7 +24,7 @@ DBusService* DBusService::singleton(){ qFatal("Failed to connect to system bus."); } QDBusConnectionInterface* interface = bus.interface(); - O_INFO("Registering service..."); + O_DEBUG("Registering service..."); auto reply = interface->registerService(OXIDE_SERVICE); bus.registerService(OXIDE_SERVICE); if(!reply.isValid()){ @@ -49,14 +41,60 @@ DBusService* DBusService::singleton(){ #endif qFatal("Unable to register interface: %s", bus.lastError().message().toStdString().c_str()); } - connect(bus.interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), - instance, SLOT(serviceOwnerChanged(QString,QString,QString))); + connect( + bus.interface(), + SIGNAL(serviceOwnerChanged(QString,QString,QString)), + instance, + SLOT(serviceOwnerChanged(QString,QString,QString)) + ); O_DEBUG("Registered"); } return instance; } -DBusService::DBusService(QObject* parent) : APIBase(parent), apis(){ +DBusService::DBusService(QObject* parent) +: APIBase(parent), + apis(), + m_exiting{false} +{ + uint64_t time; + int res = sd_watchdog_enabled(0, &time); + if(res > 0){ + auto usec = microseconds(time); + auto hrs = duration_cast(usec); + auto mins = duration_cast(usec - hrs); + auto secs = duration_cast(usec - hrs - mins); + auto ms = duration_cast(usec - hrs - mins - secs); + qInfo() + << "Watchdog notification expected every" + << QString("%1:%2:%3.%4") + .arg(hrs.count()) + .arg(mins.count()) + .arg(secs.count()) + .arg(ms.count()); + usec = usec / 2; + hrs = duration_cast(usec); + mins = duration_cast(usec - hrs); + secs = duration_cast(usec - hrs - mins); + ms = duration_cast(usec - hrs - mins - secs); + qInfo() + << "Watchdog scheduled for every " + << QString("%1:%2:%3.%4") + .arg(hrs.count()) + .arg(mins.count()) + .arg(secs.count()) + .arg(ms.count()); + m_watchdogTimer = startTimer(duration_cast(usec).count(), Qt::PreciseTimer); + if(m_watchdogTimer == 0){ + qCritical() << "Watchdog timer failed to start"; + }else{ + qInfo() << "Watchdog timer running"; + } + }else if(res < 0){ + qWarning() << "Failed to detect if watchdog timer is expected:" << strerror(-res); + }else{ + qInfo() << "No watchdog timer required"; + } #ifdef SENTRY sentry_breadcrumb("dbusservice", "Initializing APIs", "info"); #endif @@ -64,82 +102,49 @@ DBusService::DBusService(QObject* parent) : APIBase(parent), apis(){ Oxide::Sentry::sentry_span(t, "apis", "Initialize APIs", [this](Oxide::Sentry::Span* s){ Oxide::Sentry::sentry_span(s, "wifi", "Initialize wifi API", [this]{ apis.insert("wifi", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/wifi", - .dependants = new QStringList(), - .instance = new WifiAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/wifi", + .dependants = new QStringList(), + .instance = new WifiAPI(this), + }); }); Oxide::Sentry::sentry_span(s, "system", "Initialize system API", [this]{ apis.insert("system", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/system", - .dependants = new QStringList(), - .instance = new SystemAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/system", + .dependants = new QStringList(), + .instance = new SystemAPI(this), + }); }); Oxide::Sentry::sentry_span(s, "power", "Initialize power API", [this]{ apis.insert("power", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/power", - .dependants = new QStringList(), - .instance = new PowerAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/power", + .dependants = new QStringList(), + .instance = new PowerAPI(this), + }); }); Oxide::Sentry::sentry_span(s, "screen", "Initialize screen API", [this]{ apis.insert("screen", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/screen", - .dependants = new QStringList(), - .instance = new ScreenAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/screen", + .dependants = new QStringList(), + .instance = new ScreenAPI(this), + }); }); Oxide::Sentry::sentry_span(s, "apps", "Initialize apps API", [this]{ apis.insert("apps", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/apps", - .dependants = new QStringList(), - .instance = new AppsAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/apps", + .dependants = new QStringList(), + .instance = new AppsAPI(this), + }); }); Oxide::Sentry::sentry_span(s, "notification", "Initialize notification API", [this]{ apis.insert("notification", APIEntry{ - .path = QString(OXIDE_SERVICE_PATH) + "/notification", - .dependants = new QStringList(), - .instance = new NotificationAPI(this), - }); + .path = QString(OXIDE_SERVICE_PATH) + "/notification", + .dependants = new QStringList(), + .instance = new NotificationAPI(this), + }); }); }); -#ifdef SENTRY - sentry_breadcrumb("dbusservice", "Connecting button handler events", "info"); -#endif Oxide::Sentry::sentry_span(t, "connect", "Connect events", []{ - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){ - connect(buttonHandler, &ButtonHandler::leftHeld, systemAPI, &SystemAPI::leftAction); - connect(buttonHandler, &ButtonHandler::homeHeld, systemAPI, &SystemAPI::homeAction); - connect(buttonHandler, &ButtonHandler::rightHeld, systemAPI, &SystemAPI::rightAction); - } - connect(buttonHandler, &ButtonHandler::powerHeld, systemAPI, &SystemAPI::powerAction); - connect(buttonHandler, &ButtonHandler::powerPress, systemAPI, &SystemAPI::suspend); - connect(buttonHandler, &ButtonHandler::activity, systemAPI, &SystemAPI::activity); -#ifdef SENTRY - sentry_breadcrumb("dbusservice", "Connecting power events", "info"); -#endif connect(powerAPI, &PowerAPI::chargerStateChanged, systemAPI, &SystemAPI::activity); -#ifdef SENTRY - sentry_breadcrumb("dbusservice", "Connecting system events", "info"); -#endif - connect(systemAPI, &SystemAPI::leftAction, appsAPI, []{ - if(notificationAPI->locked()){ - return; - } - auto currentApplication = appsAPI->getApplication(appsAPI->currentApplicationNoSecurityCheck()); - if(currentApplication != nullptr && currentApplication->path() == appsAPI->lockscreenApplication().path()){ - O_DEBUG("Left Action cancelled. On lockscreen"); - return; - } - if(!appsAPI->previousApplicationNoSecurityCheck()){ - appsAPI->openDefaultApplication(); - } - }); - connect(systemAPI, &SystemAPI::homeAction, appsAPI, &AppsAPI::openTaskManager); - connect(systemAPI, &SystemAPI::bottomAction, appsAPI, &AppsAPI::openTaskSwitcher); - connect(systemAPI, &SystemAPI::topAction, systemAPI, &SystemAPI::toggleSwipes); }); #ifdef SENTRY sentry_breadcrumb("dbusservice", "Cleaning up", "info"); @@ -156,24 +161,7 @@ DBusService::DBusService(QObject* parent) : APIBase(parent), apis(){ }); } -DBusService::~DBusService(){ -#ifdef SENTRY - sentry_breadcrumb("dbusservice", "Disconnecting APIs", "info"); -#endif - O_INFO("Removing all APIs"); - auto bus = QDBusConnection::systemBus(); - for(auto api : apis){ - api.instance->setEnabled(false); - bus.unregisterObject(api.path); - emit apiUnavailable(QDBusObjectPath(api.path)); - delete api.instance; - delete api.dependants; - } - apis.clear(); -#ifdef SENTRY - sentry_breadcrumb("dbusservice", "APIs disconnected", "info"); -#endif -} +DBusService::~DBusService(){} void DBusService::setEnabled(bool enabled){ Q_UNUSED(enabled); } @@ -184,6 +172,8 @@ QObject* DBusService::getAPI(QString name){ return apis[name].instance; } +QQmlApplicationEngine* DBusService::engine(){ return m_engine; } + int DBusService::tarnishPid(){ return qApp->applicationPid(); } QDBusObjectPath DBusService::requestAPI(QString name, QDBusMessage message) { @@ -202,7 +192,7 @@ QDBusObjectPath DBusService::requestAPI(QString name, QDBusMessage message) { bus.registerObject(api.path, api.instance, QDBusConnection::ExportAllContents); } if(!api.dependants->size()){ - O_INFO("Registering " << api.path); + O_DEBUG("Registering " << api.path); api.instance->setEnabled(true); emit apiAvailable(QDBusObjectPath(api.path)); } @@ -221,7 +211,7 @@ void DBusService::releaseAPI(QString name, QDBusMessage message) { auto client = message.service(); api.dependants->removeAll(client); if(!api.dependants->size()){ - O_INFO("Unregistering " << api.path); + O_DEBUG("Unregistering " << api.path); api.instance->setEnabled(false); QDBusConnection::systemBus().unregisterObject(api.path, QDBusConnection::UnregisterNode); emit apiUnavailable(QDBusObjectPath(api.path)); @@ -242,11 +232,60 @@ QVariantMap DBusService::APIs(){ return result; } -void DBusService::startup(){ +void DBusService::startup(QQmlApplicationEngine* engine){ #ifdef SENTRY sentry_breadcrumb("dbusservice", "startup", "navigation"); #endif + sd_notify(0, "STATUS=startup"); + m_engine = engine; + notificationAPI->startup(); appsAPI->startup(); + sd_notify(0, "STATUS=running"); + sd_notify(0, "READY=1"); +} + +void DBusService::exit(int exitCode){ + if(calledFromDBus()){ + return; + } + if(m_exiting){ + O_WARNING("Already shutting down, forcing stop"); + kill(getpid(), SIGKILL); + return; + } + m_exiting = true; + sd_notify(0, "STATUS=stopping"); + sd_notify(0, "STOPPING=1"); + emit aboutToQuit(); +#ifdef SENTRY + sentry_breadcrumb("dbusservice", "Disconnecting APIs", "info"); +#endif + O_DEBUG("Removing all APIs"); + auto bus = QDBusConnection::systemBus(); + for(auto key : apis.keys()){ + auto api = apis[key]; + api.instance->setEnabled(false); + bus.unregisterObject(api.path, QDBusConnection::UnregisterNode); + emit apiUnavailable(QDBusObjectPath(api.path)); + } + powerAPI->shutdown(); + appsAPI->shutdown(); + wifiAPI->shutdown(); + notificationAPI->shutdown(); + systemAPI->shutdown(); + bus.unregisterService(OXIDE_SERVICE); +#ifdef SENTRY + sentry_breadcrumb("dbusservice", "APIs disconnected", "info"); +#endif + O_DEBUG("Exiting"); + std::exit(exitCode); +} + +void DBusService::timerEvent(QTimerEvent* event){ + if(event->timerId() == m_watchdogTimer){ + O_DEBUG("Watchdog keepalive sent"); + sd_notify(0, "WATCHDOG=1"); + } } void DBusService::serviceOwnerChanged(const QString& name, const QString& oldOwner, const QString& newOwner){ @@ -257,7 +296,7 @@ void DBusService::serviceOwnerChanged(const QString& name, const QString& oldOwn auto api = apis[key]; api.dependants->removeAll(name); if(!api.dependants->size() && bus.objectRegisteredAt(api.path) != nullptr){ - O_INFO("Automatically unregistering " << api.path); + O_DEBUG("Automatically unregistering " << api.path); api.instance->setEnabled(false); bus.unregisterObject(api.path, QDBusConnection::UnregisterNode); apiUnavailable(QDBusObjectPath(api.path)); diff --git a/applications/system-service/dbusservice.h b/applications/system-service/dbusservice.h index 4f2ff626b..041f87000 100644 --- a/applications/system-service/dbusservice.h +++ b/applications/system-service/dbusservice.h @@ -10,8 +10,12 @@ #include #include #include +#include + #include +#include "apibase.h" + // Must be included so that generate_xml.sh will work #include "../../shared/liboxide/meta.h" @@ -35,18 +39,19 @@ class DBusService : public APIBase { static DBusService* singleton(); DBusService(QObject* parent); ~DBusService(); - void setEnabled(bool enabled);; + void setEnabled(bool enabled); QObject* getAPI(QString name); + QQmlApplicationEngine* engine(); int tarnishPid(); + void startup(QQmlApplicationEngine* engine); + void exit(int exitCode); public slots: - QDBusObjectPath requestAPI(QString name, QDBusMessage message);; - Q_NOREPLY void releaseAPI(QString name, QDBusMessage message);; - QVariantMap APIs();; - - void startup(); + QDBusObjectPath requestAPI(QString name, QDBusMessage message); + Q_NOREPLY void releaseAPI(QString name, QDBusMessage message); + QVariantMap APIs(); signals: @@ -54,11 +59,17 @@ public slots: void apiUnavailable(QDBusObjectPath api); void aboutToQuit(); +protected: + void timerEvent(QTimerEvent* event) override; + private slots: void serviceOwnerChanged(const QString& name, const QString& oldOwner, const QString& newOwner); private: + QQmlApplicationEngine* m_engine; QMap apis; + bool m_exiting; + int m_watchdogTimer; }; #endif // DBUSSERVICE_H diff --git a/applications/system-service/digitizerhandler.cpp b/applications/system-service/digitizerhandler.cpp deleted file mode 100644 index e32711fd3..000000000 --- a/applications/system-service/digitizerhandler.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include "digitizerhandler.h" - -DigitizerHandler* DigitizerHandler::singleton_touchScreen(){ - static DigitizerHandler* instance; - if(instance != nullptr){ - return instance; - } - // Get event devices - event_device touchScreen_device(deviceSettings.getTouchDevicePath(), O_RDWR); - if(touchScreen_device.fd == -1){ - O_WARNING("Failed to open event device: " << touchScreen_device.device.c_str()); - throw QException(); - } - instance = new DigitizerHandler(touchScreen_device); - instance->start(); - return instance; -} - -DigitizerHandler* DigitizerHandler::singleton_wacom(){ - static DigitizerHandler* instance; - if(instance != nullptr){ - return instance; - } - // Get event devices - event_device wacom_device(deviceSettings.getWacomDevicePath(), O_RDWR); - if(wacom_device.fd == -1){ - O_WARNING("Failed to open event device: " << wacom_device.device.c_str()); - throw QException(); - } - instance = new DigitizerHandler(wacom_device); - instance->start(); - return instance; -} - -DigitizerHandler::DigitizerHandler(event_device& device) - : QThread(), - filebuf(device.fd, ios::in), - stream(&filebuf), - m_enabled(true), - device(device) { - flood = build_flood(); -} - -DigitizerHandler::~DigitizerHandler(){ - if(device.fd == -1){ - return; - } - if(device.locked){ - ungrab(); - } - close(device.fd); -} - -void DigitizerHandler::setEnabled(bool enabled){ - m_enabled = enabled; -} - -void DigitizerHandler::grab(){ - if(!grabbed()){ - device.lock(); - } -} - -void DigitizerHandler::ungrab(){ - if(grabbed()){ - device.unlock(); - } -} - -bool DigitizerHandler::grabbed() { return device.locked; } - -void DigitizerHandler::write(ushort type, ushort code, int value){ - auto event = createEvent(type, code, value); - ::write(device.fd, &event, sizeof(input_event)); - O_DEBUG("Emitted event " << event.time.tv_sec << event.time.tv_usec << type << code << value); -} - -void DigitizerHandler::write(input_event* events, size_t size){ - ::write(device.fd, events, size); -} - -void DigitizerHandler::syn(){ - write(EV_SYN, SYN_REPORT, 0); -} - -void DigitizerHandler::clear_buffer(){ - if(device.fd == -1){ - return; - } - O_DEBUG("Clearing event buffer on" << device.device.c_str()); - write(flood, 512 * 8 * 4 * sizeof(input_event)); -} - -input_event DigitizerHandler::createEvent(ushort type, ushort code, int value){ - struct input_event event; - event.type = type; - event.code = code; - event.value = value; - return event; -} - -input_event* DigitizerHandler::build_flood(){ - auto n = 512 * 8; - auto num_inst = 4; - input_event* ev = (input_event*)malloc(sizeof(struct input_event) * n * num_inst); - memset(ev, 0, sizeof(input_event) * n * num_inst); - auto i = 0; - while (i < n) { - ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 1); - ev[i++] = createEvent(EV_SYN, 0, 0); - ev[i++] = createEvent(EV_ABS, ABS_DISTANCE, 2); - ev[i++] = createEvent(EV_SYN, 0, 0); - } - return ev; -} - -void DigitizerHandler::run(){ - char name[256]; - memset(name, 0, sizeof(name)); - ioctl(device.fd, EVIOCGNAME(sizeof(name)), name); - O_INFO("Reading From : " << device.device.c_str() << " (" << name << ")"); - O_DEBUG("Listening for events..."); - while(handle_events()){ - qApp->processEvents(QEventLoop::AllEvents, 100); - QThread::yieldCurrentThread(); - } -} - -bool DigitizerHandler::handle_events(){ - vector event_buffer; - bool success = true; - while(true){ - input_event event; - if(!read(&event)){ - success = false; - goto exitLoop; - } - event_buffer.push_back(event); - switch(event.type){ - case EV_SYN: - switch(event.code){ - case SYN_DROPPED: - event_buffer.clear(); - skip_event(); - case SYN_REPORT: - case SYN_MT_REPORT: - goto exitLoop; - break; - } - break; - } - } -exitLoop: - if(event_buffer.size()){ - for(input_event event : event_buffer){ - emit inputEvent(event); - } - emit activity(); - } - return success; -} - -void DigitizerHandler::skip_event(){ - input_event event; - while(read(&event)){ - if(event.type != EV_SYN){ - continue; - } - if(event.code == SYN_REPORT || event.code == SYN_MT_REPORT){ - break; - } - } -} - -bool DigitizerHandler::read(input_event* ie){ - return (bool)stream.read((char*)ie, static_cast(sizeof(struct input_event))); -} diff --git a/applications/system-service/digitizerhandler.h b/applications/system-service/digitizerhandler.h deleted file mode 100644 index ddadaf42b..000000000 --- a/applications/system-service/digitizerhandler.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once -#ifndef DIGITIZERHANDLER_H -#define DIGITIZERHANDLER_H - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace Oxide; - -#define touchHandler DigitizerHandler::singleton_touchScreen() -#define wacomHandler DigitizerHandler::singleton_wacom() - -class DigitizerHandler : public QThread { - Q_OBJECT - -public: - static DigitizerHandler* singleton_touchScreen(); - static DigitizerHandler* singleton_wacom(); - - DigitizerHandler(event_device& device); - ~DigitizerHandler(); - void setEnabled(bool enabled); - void grab(); - void ungrab(); - bool grabbed(); - void write(ushort type, ushort code, int value); - void write(input_event* events, size_t size); - void syn(); - void clear_buffer(); - static input_event createEvent(ushort type, ushort code, int value); - -signals: - void activity(); - void inputEvent(const input_event& event); - -protected: - input_event* flood; - input_event* build_flood(); - void run(); - bool handle_events(); - void skip_event(); - __gnu_cxx::stdio_filebuf filebuf; - istream stream; - bool m_enabled; - event_device device; - bool read(input_event* ie); -}; - -#endif // DIGITIZERHANDLER_H diff --git a/applications/system-service/fifohandler.cpp b/applications/system-service/fifohandler.cpp deleted file mode 100644 index f55d691b4..000000000 --- a/applications/system-service/fifohandler.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "fifohandler.h" - -FifoHandler::FifoHandler(QString name, QString path, QObject* host) - : QObject(), - host(host), - _thread(this), - timer(this), - _name(name), - path(path), - in(), - out() { - connect(host, &QObject::destroyed, this, &QObject::deleteLater); - connect(&_thread, &QThread::started, [this]{ - emit started(); - in.open(this->path.toStdString().c_str(), std::ifstream::in); - if(!in.good()){ - O_WARNING("Unable to open fifi (in)" << ::strerror(errno)); - } - timer.start(10); - }); - connect(&_thread, &QThread::finished, this, [this]{ - timer.stop(); - emit finished(); - }); - connect(&timer, &QTimer::timeout, this, &FifoHandler::run); - QThread::create([this]{ - out.open(this->path.toStdString().c_str(), std::ifstream::out); - if(!out.good()){ - O_WARNING("Unable to open fifi (out)" << ::strerror(errno)); - } - })->start(); - moveToThread(&_thread); -} - -FifoHandler::~FifoHandler(){ - if(in.is_open()){ - in.close(); - } - if(out.is_open()){ - out.close(); - } - quit(); -} - -void FifoHandler::start() { _thread.start(); } - -void FifoHandler::quit(){ _thread.quit();} - -void FifoHandler::write(const void* data, size_t size){ - if(out.is_open()){ - out.write((char*)data, size); - } -} - -const QString& FifoHandler::name() { return _name; } - -void FifoHandler::run() { - if(!in.is_open()){ - quit(); - return; - } - std::string data; - while(getline_async(in, data)){ - QString line(data.c_str()); - line = line.trimmed(); - emit dataRecieved(this, line); - qApp->processEvents(QEventLoop::AllEvents, 100); - QThread::yieldCurrentThread(); - } - if(in.eof()){ - in.clear(); - } - QThread::yieldCurrentThread(); -} - -bool FifoHandler::getline_async(std::istream& is, std::string& str, char delim) { - static std::string lineSoFar; - char inChar; - int charsRead = 0; - bool lineRead = false; - str = ""; - - do { - charsRead = is.readsome(&inChar, 1); - if (charsRead == 1) { - // if the delimiter is read then return the string so far - if (inChar == delim) { - str = lineSoFar; - lineSoFar = ""; - lineRead = true; - } else { // otherwise add it to the string so far - lineSoFar.append(1, inChar); - } - } - } while (charsRead != 0 && !lineRead); - - return lineRead; -} diff --git a/applications/system-service/fifohandler.h b/applications/system-service/fifohandler.h deleted file mode 100644 index dd2653019..000000000 --- a/applications/system-service/fifohandler.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef FIFOHANDLER_H -#define FIFOHANDLER_H - -#include -#include -#include - -#include -#include - -class FifoHandler : public QObject { - Q_OBJECT - -public: - FifoHandler(QString name, QString path, QObject* host); - ~FifoHandler(); - void start(); - void quit(); - void write(const void* data, size_t size); - const QString& name(); - -signals: - void started(); - void finished(); - void dataRecieved(FifoHandler* handler, const QString& data); - -protected: - void run(); - -private: - QObject* host; - QThread _thread; - QTimer timer; - QString _name; - QString path; - std::ifstream in; - std::ofstream out; - bool getline_async(std::istream& is, std::string& str, char delim = '\n'); -}; - -#endif // FIFOHANDLER_H diff --git a/applications/system-service/keyboarddevice.cpp b/applications/system-service/keyboarddevice.cpp deleted file mode 100644 index f77fe671b..000000000 --- a/applications/system-service/keyboarddevice.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "keyboarddevice.h" -#include "buttonhandler.h" -#include "keyboardhandler.h" - -KeyboardDevice::KeyboardDevice(QThread* handler, event_device device) -: QObject(handler), - device(device), - sys("/sys/class/input/" + devName() + "/device/") -{ - _name = sys.strProperty("name").c_str(); - device.lock(); - notifier = new QSocketNotifier(device.fd, QSocketNotifier::Read, this); - connect(notifier, &QSocketNotifier::activated, this, &KeyboardDevice::readEvents); - notifier->setEnabled(true); -} - -KeyboardDevice::~KeyboardDevice(){ - if(exists() && device.locked){ - device.unlock(); - } -} - -QString KeyboardDevice::devName(){ return QFileInfo(device.device.c_str()).baseName(); } - -QString KeyboardDevice::name(){ return _name; } - -QString KeyboardDevice::path(){ return device.device.c_str(); } - -QString KeyboardDevice::id(){ - return QString("%1:%2") - .arg(sys.strProperty("id/vendor").c_str()) - .arg(sys.strProperty("id/product").c_str()); -} - -bool KeyboardDevice::exists(){ return QFile::exists(path()); } - -void KeyboardDevice::flood(){ - if(device.fd == -1 || !device.locked){ - return; - } - ::write(device.fd, buttonHandler->event_flood(), EVENT_FLOOD_SIZE); -} - -void KeyboardDevice::readEvents(){ - notifier->setEnabled(false); - input_event event; - while(::read(device.fd, &event, sizeof(input_event)) > 0){ - switch(event.type){ - case EV_KEY: - pressed[event.code] = event.value; - break; - case EV_SYN: - switch(event.code){ - case SYN_DROPPED: - pressed.clear(); - break; - case SYN_REPORT: - auto handler = static_cast(parent()); - for(auto code : pressed.keys()){ - handler->writeEvent(EV_KEY, code, pressed[code]); - } - handler->writeEvent(EV_SYN, SYN_REPORT, 0); - pressed.clear(); - break; - } - break; - } - } - notifier->setEnabled(true); -} diff --git a/applications/system-service/keyboarddevice.h b/applications/system-service/keyboarddevice.h deleted file mode 100644 index 986df9c64..000000000 --- a/applications/system-service/keyboarddevice.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -using namespace Oxide; - -class KeyboardDevice : public QObject{ - Q_OBJECT - -public: - KeyboardDevice(QThread* handler, event_device device); - ~KeyboardDevice(); - QString devName(); - QString name(); - QString path(); - QString id(); - bool exists(); - void flood(); - -public slots: - void readEvents(); - -private: - event_device device; - SysObject sys; - QString _name; - QSocketNotifier* notifier; - QMap pressed; -}; diff --git a/applications/system-service/keyboardhandler.cpp b/applications/system-service/keyboardhandler.cpp deleted file mode 100644 index 4a22139d9..000000000 --- a/applications/system-service/keyboardhandler.cpp +++ /dev/null @@ -1,679 +0,0 @@ -#include "keyboardhandler.h" - -#include -#include -#include - -KeyboardHandler* KeyboardHandler::init(){ - static KeyboardHandler* instance; - if(instance != nullptr){ - return instance; - } - instance = new KeyboardHandler(); - instance->moveToThread(instance); - instance->start(); - return instance; -} - -KeyboardHandler::KeyboardHandler(){ - setObjectName("OxideVirtKeyboard"); - fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); - if(fd == -1){ - O_WARNING("Failed to open uinput!"); - }else{ - ioctl(fd, UI_SET_EVBIT, EV_REP); - ioctl(fd, UI_SET_EVBIT, EV_LED); - ioctl(fd, UI_SET_EVBIT, EV_KEY); - ioctl(fd, UI_SET_EVBIT, EV_SYN); - - ioctl(fd, UI_SET_KEYBIT, KEY_RESERVED); - ioctl(fd, UI_SET_KEYBIT, KEY_ESC); - ioctl(fd, UI_SET_KEYBIT, KEY_1); - ioctl(fd, UI_SET_KEYBIT, KEY_2); - ioctl(fd, UI_SET_KEYBIT, KEY_3); - ioctl(fd, UI_SET_KEYBIT, KEY_4); - ioctl(fd, UI_SET_KEYBIT, KEY_5); - ioctl(fd, UI_SET_KEYBIT, KEY_6); - ioctl(fd, UI_SET_KEYBIT, KEY_7); - ioctl(fd, UI_SET_KEYBIT, KEY_8); - ioctl(fd, UI_SET_KEYBIT, KEY_9); - ioctl(fd, UI_SET_KEYBIT, KEY_0); - ioctl(fd, UI_SET_KEYBIT, KEY_MINUS); - ioctl(fd, UI_SET_KEYBIT, KEY_EQUAL); - ioctl(fd, UI_SET_KEYBIT, KEY_BACKSPACE); - ioctl(fd, UI_SET_KEYBIT, KEY_TAB); - ioctl(fd, UI_SET_KEYBIT, KEY_Q); - ioctl(fd, UI_SET_KEYBIT, KEY_W); - ioctl(fd, UI_SET_KEYBIT, KEY_E); - ioctl(fd, UI_SET_KEYBIT, KEY_R); - ioctl(fd, UI_SET_KEYBIT, KEY_T); - ioctl(fd, UI_SET_KEYBIT, KEY_Y); - ioctl(fd, UI_SET_KEYBIT, KEY_U); - ioctl(fd, UI_SET_KEYBIT, KEY_I); - ioctl(fd, UI_SET_KEYBIT, KEY_O); - ioctl(fd, UI_SET_KEYBIT, KEY_P); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFTBRACE); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTBRACE); - ioctl(fd, UI_SET_KEYBIT, KEY_ENTER); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFTCTRL); - ioctl(fd, UI_SET_KEYBIT, KEY_A); - ioctl(fd, UI_SET_KEYBIT, KEY_S); - ioctl(fd, UI_SET_KEYBIT, KEY_D); - ioctl(fd, UI_SET_KEYBIT, KEY_F); - ioctl(fd, UI_SET_KEYBIT, KEY_G); - ioctl(fd, UI_SET_KEYBIT, KEY_H); - ioctl(fd, UI_SET_KEYBIT, KEY_J); - ioctl(fd, UI_SET_KEYBIT, KEY_K); - ioctl(fd, UI_SET_KEYBIT, KEY_L); - ioctl(fd, UI_SET_KEYBIT, KEY_SEMICOLON); - ioctl(fd, UI_SET_KEYBIT, KEY_APOSTROPHE); - ioctl(fd, UI_SET_KEYBIT, KEY_GRAVE); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFTSHIFT); - ioctl(fd, UI_SET_KEYBIT, KEY_BACKSLASH); - ioctl(fd, UI_SET_KEYBIT, KEY_Z); - ioctl(fd, UI_SET_KEYBIT, KEY_X); - ioctl(fd, UI_SET_KEYBIT, KEY_C); - ioctl(fd, UI_SET_KEYBIT, KEY_V); - ioctl(fd, UI_SET_KEYBIT, KEY_B); - ioctl(fd, UI_SET_KEYBIT, KEY_N); - ioctl(fd, UI_SET_KEYBIT, KEY_M); - ioctl(fd, UI_SET_KEYBIT, KEY_COMMA); - ioctl(fd, UI_SET_KEYBIT, KEY_DOT); - ioctl(fd, UI_SET_KEYBIT, KEY_SLASH); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTSHIFT); - ioctl(fd, UI_SET_KEYBIT, KEY_KPASTERISK); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFTALT); - ioctl(fd, UI_SET_KEYBIT, KEY_SPACE); - ioctl(fd, UI_SET_KEYBIT, KEY_CAPSLOCK); - ioctl(fd, UI_SET_KEYBIT, KEY_F1); - ioctl(fd, UI_SET_KEYBIT, KEY_F2); - ioctl(fd, UI_SET_KEYBIT, KEY_F3); - ioctl(fd, UI_SET_KEYBIT, KEY_F4); - ioctl(fd, UI_SET_KEYBIT, KEY_F5); - ioctl(fd, UI_SET_KEYBIT, KEY_F6); - ioctl(fd, UI_SET_KEYBIT, KEY_F7); - ioctl(fd, UI_SET_KEYBIT, KEY_F8); - ioctl(fd, UI_SET_KEYBIT, KEY_F9); - ioctl(fd, UI_SET_KEYBIT, KEY_F10); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMLOCK); - ioctl(fd, UI_SET_KEYBIT, KEY_SCROLLLOCK); - ioctl(fd, UI_SET_KEYBIT, KEY_KP7); - ioctl(fd, UI_SET_KEYBIT, KEY_KP8); - ioctl(fd, UI_SET_KEYBIT, KEY_KP9); - ioctl(fd, UI_SET_KEYBIT, KEY_KPMINUS); - ioctl(fd, UI_SET_KEYBIT, KEY_KP4); - ioctl(fd, UI_SET_KEYBIT, KEY_KP5); - ioctl(fd, UI_SET_KEYBIT, KEY_KP6); - ioctl(fd, UI_SET_KEYBIT, KEY_KPPLUS); - ioctl(fd, UI_SET_KEYBIT, KEY_KP1); - ioctl(fd, UI_SET_KEYBIT, KEY_KP2); - ioctl(fd, UI_SET_KEYBIT, KEY_KP3); - ioctl(fd, UI_SET_KEYBIT, KEY_KP0); - ioctl(fd, UI_SET_KEYBIT, KEY_KPDOT); - ioctl(fd, UI_SET_KEYBIT, KEY_ZENKAKUHANKAKU); - ioctl(fd, UI_SET_KEYBIT, KEY_102ND); - ioctl(fd, UI_SET_KEYBIT, KEY_F11); - ioctl(fd, UI_SET_KEYBIT, KEY_F12); - ioctl(fd, UI_SET_KEYBIT, KEY_RO); - ioctl(fd, UI_SET_KEYBIT, KEY_KATAKANA); - ioctl(fd, UI_SET_KEYBIT, KEY_HIRAGANA); - ioctl(fd, UI_SET_KEYBIT, KEY_HENKAN); - ioctl(fd, UI_SET_KEYBIT, KEY_KATAKANAHIRAGANA); - ioctl(fd, UI_SET_KEYBIT, KEY_MUHENKAN); - ioctl(fd, UI_SET_KEYBIT, KEY_KPJPCOMMA); - ioctl(fd, UI_SET_KEYBIT, KEY_KPENTER); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTCTRL); - ioctl(fd, UI_SET_KEYBIT, KEY_KPSLASH); - ioctl(fd, UI_SET_KEYBIT, KEY_SYSRQ); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTALT); - ioctl(fd, UI_SET_KEYBIT, KEY_LINEFEED); - ioctl(fd, UI_SET_KEYBIT, KEY_HOME); - ioctl(fd, UI_SET_KEYBIT, KEY_UP); - ioctl(fd, UI_SET_KEYBIT, KEY_PAGEUP); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFT); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHT); - ioctl(fd, UI_SET_KEYBIT, KEY_END); - ioctl(fd, UI_SET_KEYBIT, KEY_DOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_PAGEDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_INSERT); - ioctl(fd, UI_SET_KEYBIT, KEY_DELETE); - ioctl(fd, UI_SET_KEYBIT, KEY_MACRO); - ioctl(fd, UI_SET_KEYBIT, KEY_MUTE); - ioctl(fd, UI_SET_KEYBIT, KEY_VOLUMEDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_VOLUMEUP); - ioctl(fd, UI_SET_KEYBIT, KEY_POWER); - ioctl(fd, UI_SET_KEYBIT, KEY_KPEQUAL); - ioctl(fd, UI_SET_KEYBIT, KEY_KPPLUSMINUS); - ioctl(fd, UI_SET_KEYBIT, KEY_PAUSE); - ioctl(fd, UI_SET_KEYBIT, KEY_SCALE); - ioctl(fd, UI_SET_KEYBIT, KEY_KPCOMMA); - ioctl(fd, UI_SET_KEYBIT, KEY_HANGEUL); - ioctl(fd, UI_SET_KEYBIT, KEY_HANGUEL); - ioctl(fd, UI_SET_KEYBIT, KEY_HANJA); - ioctl(fd, UI_SET_KEYBIT, KEY_YEN); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFTMETA); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHTMETA); - ioctl(fd, UI_SET_KEYBIT, KEY_COMPOSE); - ioctl(fd, UI_SET_KEYBIT, KEY_STOP); - ioctl(fd, UI_SET_KEYBIT, KEY_AGAIN); - ioctl(fd, UI_SET_KEYBIT, KEY_PROPS); - ioctl(fd, UI_SET_KEYBIT, KEY_UNDO); - ioctl(fd, UI_SET_KEYBIT, KEY_FRONT); - ioctl(fd, UI_SET_KEYBIT, KEY_COPY); - ioctl(fd, UI_SET_KEYBIT, KEY_OPEN); - ioctl(fd, UI_SET_KEYBIT, KEY_PASTE); - ioctl(fd, UI_SET_KEYBIT, KEY_FIND); - ioctl(fd, UI_SET_KEYBIT, KEY_CUT); - ioctl(fd, UI_SET_KEYBIT, KEY_HELP); - ioctl(fd, UI_SET_KEYBIT, KEY_MENU); - ioctl(fd, UI_SET_KEYBIT, KEY_CALC); - ioctl(fd, UI_SET_KEYBIT, KEY_SETUP); - ioctl(fd, UI_SET_KEYBIT, KEY_SLEEP); - ioctl(fd, UI_SET_KEYBIT, KEY_WAKEUP); - ioctl(fd, UI_SET_KEYBIT, KEY_FILE); - ioctl(fd, UI_SET_KEYBIT, KEY_SENDFILE); - ioctl(fd, UI_SET_KEYBIT, KEY_DELETEFILE); - ioctl(fd, UI_SET_KEYBIT, KEY_XFER); - ioctl(fd, UI_SET_KEYBIT, KEY_PROG1); - ioctl(fd, UI_SET_KEYBIT, KEY_PROG2); - ioctl(fd, UI_SET_KEYBIT, KEY_WWW); - ioctl(fd, UI_SET_KEYBIT, KEY_MSDOS); - ioctl(fd, UI_SET_KEYBIT, KEY_COFFEE); - ioctl(fd, UI_SET_KEYBIT, KEY_SCREENLOCK); - ioctl(fd, UI_SET_KEYBIT, KEY_ROTATE_DISPLAY); - ioctl(fd, UI_SET_KEYBIT, KEY_DIRECTION); - ioctl(fd, UI_SET_KEYBIT, KEY_CYCLEWINDOWS); - ioctl(fd, UI_SET_KEYBIT, KEY_MAIL); - ioctl(fd, UI_SET_KEYBIT, KEY_BOOKMARKS); - ioctl(fd, UI_SET_KEYBIT, KEY_COMPUTER); - ioctl(fd, UI_SET_KEYBIT, KEY_BACK); - ioctl(fd, UI_SET_KEYBIT, KEY_FORWARD); - ioctl(fd, UI_SET_KEYBIT, KEY_CLOSECD); - ioctl(fd, UI_SET_KEYBIT, KEY_EJECTCD); - ioctl(fd, UI_SET_KEYBIT, KEY_EJECTCLOSECD); - ioctl(fd, UI_SET_KEYBIT, KEY_NEXTSONG); - ioctl(fd, UI_SET_KEYBIT, KEY_PLAYPAUSE); - ioctl(fd, UI_SET_KEYBIT, KEY_PREVIOUSSONG); - ioctl(fd, UI_SET_KEYBIT, KEY_STOPCD); - ioctl(fd, UI_SET_KEYBIT, KEY_RECORD); - ioctl(fd, UI_SET_KEYBIT, KEY_REWIND); - ioctl(fd, UI_SET_KEYBIT, KEY_PHONE); - ioctl(fd, UI_SET_KEYBIT, KEY_ISO); - ioctl(fd, UI_SET_KEYBIT, KEY_CONFIG); - ioctl(fd, UI_SET_KEYBIT, KEY_HOMEPAGE); - ioctl(fd, UI_SET_KEYBIT, KEY_REFRESH); - ioctl(fd, UI_SET_KEYBIT, KEY_EXIT); - ioctl(fd, UI_SET_KEYBIT, KEY_MOVE); - ioctl(fd, UI_SET_KEYBIT, KEY_EDIT); - ioctl(fd, UI_SET_KEYBIT, KEY_SCROLLUP); - ioctl(fd, UI_SET_KEYBIT, KEY_SCROLLDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_KPLEFTPAREN); - ioctl(fd, UI_SET_KEYBIT, KEY_KPRIGHTPAREN); - ioctl(fd, UI_SET_KEYBIT, KEY_NEW); - ioctl(fd, UI_SET_KEYBIT, KEY_REDO); - ioctl(fd, UI_SET_KEYBIT, KEY_F13); - ioctl(fd, UI_SET_KEYBIT, KEY_F14); - ioctl(fd, UI_SET_KEYBIT, KEY_F15); - ioctl(fd, UI_SET_KEYBIT, KEY_F16); - ioctl(fd, UI_SET_KEYBIT, KEY_F17); - ioctl(fd, UI_SET_KEYBIT, KEY_F18); - ioctl(fd, UI_SET_KEYBIT, KEY_F19); - ioctl(fd, UI_SET_KEYBIT, KEY_F20); - ioctl(fd, UI_SET_KEYBIT, KEY_F21); - ioctl(fd, UI_SET_KEYBIT, KEY_F22); - ioctl(fd, UI_SET_KEYBIT, KEY_F23); - ioctl(fd, UI_SET_KEYBIT, KEY_F24); - ioctl(fd, UI_SET_KEYBIT, KEY_PLAYCD); - ioctl(fd, UI_SET_KEYBIT, KEY_PAUSECD); - ioctl(fd, UI_SET_KEYBIT, KEY_PROG3); - ioctl(fd, UI_SET_KEYBIT, KEY_PROG4); - ioctl(fd, UI_SET_KEYBIT, KEY_DASHBOARD); - ioctl(fd, UI_SET_KEYBIT, KEY_SUSPEND); - ioctl(fd, UI_SET_KEYBIT, KEY_CLOSE); - ioctl(fd, UI_SET_KEYBIT, KEY_PLAY); - ioctl(fd, UI_SET_KEYBIT, KEY_FASTFORWARD); - ioctl(fd, UI_SET_KEYBIT, KEY_BASSBOOST); - ioctl(fd, UI_SET_KEYBIT, KEY_PRINT); - ioctl(fd, UI_SET_KEYBIT, KEY_HP); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA); - ioctl(fd, UI_SET_KEYBIT, KEY_SOUND); - ioctl(fd, UI_SET_KEYBIT, KEY_QUESTION); - ioctl(fd, UI_SET_KEYBIT, KEY_EMAIL); - ioctl(fd, UI_SET_KEYBIT, KEY_CHAT); - ioctl(fd, UI_SET_KEYBIT, KEY_SEARCH); - ioctl(fd, UI_SET_KEYBIT, KEY_CONNECT); - ioctl(fd, UI_SET_KEYBIT, KEY_FINANCE); - ioctl(fd, UI_SET_KEYBIT, KEY_SPORT); - ioctl(fd, UI_SET_KEYBIT, KEY_SHOP); - ioctl(fd, UI_SET_KEYBIT, KEY_ALTERASE); - ioctl(fd, UI_SET_KEYBIT, KEY_CANCEL); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESSDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESSUP); - ioctl(fd, UI_SET_KEYBIT, KEY_MEDIA); - ioctl(fd, UI_SET_KEYBIT, KEY_SWITCHVIDEOMODE); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDILLUMTOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDILLUMDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDILLUMUP); - ioctl(fd, UI_SET_KEYBIT, KEY_SEND); - ioctl(fd, UI_SET_KEYBIT, KEY_REPLY); - ioctl(fd, UI_SET_KEYBIT, KEY_FORWARDMAIL); - ioctl(fd, UI_SET_KEYBIT, KEY_SAVE); - ioctl(fd, UI_SET_KEYBIT, KEY_DOCUMENTS); - ioctl(fd, UI_SET_KEYBIT, KEY_BATTERY); - ioctl(fd, UI_SET_KEYBIT, KEY_BLUETOOTH); - ioctl(fd, UI_SET_KEYBIT, KEY_WLAN); - ioctl(fd, UI_SET_KEYBIT, KEY_UWB); - ioctl(fd, UI_SET_KEYBIT, KEY_UNKNOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_VIDEO_NEXT); - ioctl(fd, UI_SET_KEYBIT, KEY_VIDEO_PREV); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_CYCLE); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_AUTO); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_ZERO); - ioctl(fd, UI_SET_KEYBIT, KEY_DISPLAY_OFF); - ioctl(fd, UI_SET_KEYBIT, KEY_WWAN); - ioctl(fd, UI_SET_KEYBIT, KEY_WIMAX); - ioctl(fd, UI_SET_KEYBIT, KEY_RFKILL); - ioctl(fd, UI_SET_KEYBIT, KEY_MICMUTE); - ioctl(fd, UI_SET_KEYBIT, BTN_MISC); - ioctl(fd, UI_SET_KEYBIT, BTN_0); - ioctl(fd, UI_SET_KEYBIT, BTN_1); - ioctl(fd, UI_SET_KEYBIT, BTN_2); - ioctl(fd, UI_SET_KEYBIT, BTN_3); - ioctl(fd, UI_SET_KEYBIT, BTN_4); - ioctl(fd, UI_SET_KEYBIT, BTN_5); - ioctl(fd, UI_SET_KEYBIT, BTN_6); - ioctl(fd, UI_SET_KEYBIT, BTN_7); - ioctl(fd, UI_SET_KEYBIT, BTN_8); - ioctl(fd, UI_SET_KEYBIT, BTN_9); - ioctl(fd, UI_SET_KEYBIT, BTN_MOUSE); - ioctl(fd, UI_SET_KEYBIT, BTN_LEFT); - ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT); - ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE); - ioctl(fd, UI_SET_KEYBIT, BTN_SIDE); - ioctl(fd, UI_SET_KEYBIT, BTN_EXTRA); - ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD); - ioctl(fd, UI_SET_KEYBIT, BTN_BACK); - ioctl(fd, UI_SET_KEYBIT, BTN_TASK); - ioctl(fd, UI_SET_KEYBIT, BTN_JOYSTICK); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER); - ioctl(fd, UI_SET_KEYBIT, BTN_THUMB); - ioctl(fd, UI_SET_KEYBIT, BTN_THUMB2); - ioctl(fd, UI_SET_KEYBIT, BTN_TOP); - ioctl(fd, UI_SET_KEYBIT, BTN_TOP2); - ioctl(fd, UI_SET_KEYBIT, BTN_PINKIE); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE2); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE3); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE4); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE5); - ioctl(fd, UI_SET_KEYBIT, BTN_BASE6); - ioctl(fd, UI_SET_KEYBIT, BTN_DEAD); - ioctl(fd, UI_SET_KEYBIT, BTN_GAMEPAD); - ioctl(fd, UI_SET_KEYBIT, BTN_SOUTH); - ioctl(fd, UI_SET_KEYBIT, BTN_A); - ioctl(fd, UI_SET_KEYBIT, BTN_EAST); - ioctl(fd, UI_SET_KEYBIT, BTN_B); - ioctl(fd, UI_SET_KEYBIT, BTN_C); - ioctl(fd, UI_SET_KEYBIT, BTN_NORTH); - ioctl(fd, UI_SET_KEYBIT, BTN_X); - ioctl(fd, UI_SET_KEYBIT, BTN_WEST); - ioctl(fd, UI_SET_KEYBIT, BTN_Y); - ioctl(fd, UI_SET_KEYBIT, BTN_Z); - ioctl(fd, UI_SET_KEYBIT, BTN_TL); - ioctl(fd, UI_SET_KEYBIT, BTN_TR); - ioctl(fd, UI_SET_KEYBIT, BTN_TL2); - ioctl(fd, UI_SET_KEYBIT, BTN_TR2); - ioctl(fd, UI_SET_KEYBIT, BTN_SELECT); - ioctl(fd, UI_SET_KEYBIT, BTN_START); - ioctl(fd, UI_SET_KEYBIT, BTN_MODE); - ioctl(fd, UI_SET_KEYBIT, BTN_THUMBL); - ioctl(fd, UI_SET_KEYBIT, BTN_THUMBR); - ioctl(fd, UI_SET_KEYBIT, BTN_DIGI); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_BRUSH); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PENCIL); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_AIRBRUSH); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_FINGER); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_MOUSE); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_LENS); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_QUINTTAP); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS3); - ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS); - ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_DOUBLETAP); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_TRIPLETAP); - ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_QUADTAP); - ioctl(fd, UI_SET_KEYBIT, BTN_WHEEL); - ioctl(fd, UI_SET_KEYBIT, BTN_GEAR_DOWN); - ioctl(fd, UI_SET_KEYBIT, BTN_GEAR_UP); - ioctl(fd, UI_SET_KEYBIT, KEY_OK); - ioctl(fd, UI_SET_KEYBIT, KEY_SELECT); - ioctl(fd, UI_SET_KEYBIT, KEY_GOTO); - ioctl(fd, UI_SET_KEYBIT, KEY_CLEAR); - ioctl(fd, UI_SET_KEYBIT, KEY_POWER2); - ioctl(fd, UI_SET_KEYBIT, KEY_OPTION); - ioctl(fd, UI_SET_KEYBIT, KEY_INFO); - ioctl(fd, UI_SET_KEYBIT, KEY_TIME); - ioctl(fd, UI_SET_KEYBIT, KEY_VENDOR); - ioctl(fd, UI_SET_KEYBIT, KEY_ARCHIVE); - ioctl(fd, UI_SET_KEYBIT, KEY_PROGRAM); - ioctl(fd, UI_SET_KEYBIT, KEY_CHANNEL); - ioctl(fd, UI_SET_KEYBIT, KEY_FAVORITES); - ioctl(fd, UI_SET_KEYBIT, KEY_EPG); - ioctl(fd, UI_SET_KEYBIT, KEY_PVR); - ioctl(fd, UI_SET_KEYBIT, KEY_MHP); - ioctl(fd, UI_SET_KEYBIT, KEY_LANGUAGE); - ioctl(fd, UI_SET_KEYBIT, KEY_TITLE); - ioctl(fd, UI_SET_KEYBIT, KEY_SUBTITLE); - ioctl(fd, UI_SET_KEYBIT, KEY_ANGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_FULL_SCREEN); - ioctl(fd, UI_SET_KEYBIT, KEY_ZOOM); - ioctl(fd, UI_SET_KEYBIT, KEY_MODE); - ioctl(fd, UI_SET_KEYBIT, KEY_KEYBOARD); - ioctl(fd, UI_SET_KEYBIT, KEY_ASPECT_RATIO); - ioctl(fd, UI_SET_KEYBIT, KEY_SCREEN); - ioctl(fd, UI_SET_KEYBIT, KEY_PC); - ioctl(fd, UI_SET_KEYBIT, KEY_TV); - ioctl(fd, UI_SET_KEYBIT, KEY_TV2); - ioctl(fd, UI_SET_KEYBIT, KEY_VCR); - ioctl(fd, UI_SET_KEYBIT, KEY_VCR2); - ioctl(fd, UI_SET_KEYBIT, KEY_SAT); - ioctl(fd, UI_SET_KEYBIT, KEY_SAT2); - ioctl(fd, UI_SET_KEYBIT, KEY_CD); - ioctl(fd, UI_SET_KEYBIT, KEY_TAPE); - ioctl(fd, UI_SET_KEYBIT, KEY_RADIO); - ioctl(fd, UI_SET_KEYBIT, KEY_TUNER); - ioctl(fd, UI_SET_KEYBIT, KEY_PLAYER); - ioctl(fd, UI_SET_KEYBIT, KEY_TEXT); - ioctl(fd, UI_SET_KEYBIT, KEY_DVD); - ioctl(fd, UI_SET_KEYBIT, KEY_AUX); - ioctl(fd, UI_SET_KEYBIT, KEY_MP3); - ioctl(fd, UI_SET_KEYBIT, KEY_AUDIO); - ioctl(fd, UI_SET_KEYBIT, KEY_VIDEO); - ioctl(fd, UI_SET_KEYBIT, KEY_DIRECTORY); - ioctl(fd, UI_SET_KEYBIT, KEY_LIST); - ioctl(fd, UI_SET_KEYBIT, KEY_MEMO); - ioctl(fd, UI_SET_KEYBIT, KEY_CALENDAR); - ioctl(fd, UI_SET_KEYBIT, KEY_RED); - ioctl(fd, UI_SET_KEYBIT, KEY_GREEN); - ioctl(fd, UI_SET_KEYBIT, KEY_YELLOW); - ioctl(fd, UI_SET_KEYBIT, KEY_BLUE); - ioctl(fd, UI_SET_KEYBIT, KEY_CHANNELUP); - ioctl(fd, UI_SET_KEYBIT, KEY_CHANNELDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_FIRST); - ioctl(fd, UI_SET_KEYBIT, KEY_LAST); - ioctl(fd, UI_SET_KEYBIT, KEY_AB); - ioctl(fd, UI_SET_KEYBIT, KEY_NEXT); - ioctl(fd, UI_SET_KEYBIT, KEY_RESTART); - ioctl(fd, UI_SET_KEYBIT, KEY_SLOW); - ioctl(fd, UI_SET_KEYBIT, KEY_SHUFFLE); - ioctl(fd, UI_SET_KEYBIT, KEY_BREAK); - ioctl(fd, UI_SET_KEYBIT, KEY_PREVIOUS); - ioctl(fd, UI_SET_KEYBIT, KEY_DIGITS); - ioctl(fd, UI_SET_KEYBIT, KEY_TEEN); - ioctl(fd, UI_SET_KEYBIT, KEY_TWEN); - ioctl(fd, UI_SET_KEYBIT, KEY_VIDEOPHONE); - ioctl(fd, UI_SET_KEYBIT, KEY_GAMES); - ioctl(fd, UI_SET_KEYBIT, KEY_ZOOMIN); - ioctl(fd, UI_SET_KEYBIT, KEY_ZOOMOUT); - ioctl(fd, UI_SET_KEYBIT, KEY_ZOOMRESET); - ioctl(fd, UI_SET_KEYBIT, KEY_WORDPROCESSOR); - ioctl(fd, UI_SET_KEYBIT, KEY_EDITOR); - ioctl(fd, UI_SET_KEYBIT, KEY_SPREADSHEET); - ioctl(fd, UI_SET_KEYBIT, KEY_GRAPHICSEDITOR); - ioctl(fd, UI_SET_KEYBIT, KEY_PRESENTATION); - ioctl(fd, UI_SET_KEYBIT, KEY_DATABASE); - ioctl(fd, UI_SET_KEYBIT, KEY_NEWS); - ioctl(fd, UI_SET_KEYBIT, KEY_VOICEMAIL); - ioctl(fd, UI_SET_KEYBIT, KEY_ADDRESSBOOK); - ioctl(fd, UI_SET_KEYBIT, KEY_MESSENGER); - ioctl(fd, UI_SET_KEYBIT, KEY_DISPLAYTOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_SPELLCHECK); - ioctl(fd, UI_SET_KEYBIT, KEY_LOGOFF); - ioctl(fd, UI_SET_KEYBIT, KEY_DOLLAR); - ioctl(fd, UI_SET_KEYBIT, KEY_EURO); - ioctl(fd, UI_SET_KEYBIT, KEY_FRAMEBACK); - ioctl(fd, UI_SET_KEYBIT, KEY_FRAMEFORWARD); - ioctl(fd, UI_SET_KEYBIT, KEY_CONTEXT_MENU); - ioctl(fd, UI_SET_KEYBIT, KEY_MEDIA_REPEAT); - ioctl(fd, UI_SET_KEYBIT, KEY_10CHANNELSUP); - ioctl(fd, UI_SET_KEYBIT, KEY_10CHANNELSDOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_IMAGES); - ioctl(fd, UI_SET_KEYBIT, KEY_DEL_EOL); - ioctl(fd, UI_SET_KEYBIT, KEY_DEL_EOS); - ioctl(fd, UI_SET_KEYBIT, KEY_INS_LINE); - ioctl(fd, UI_SET_KEYBIT, KEY_DEL_LINE); - ioctl(fd, UI_SET_KEYBIT, KEY_FN); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_ESC); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F1); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F2); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F3); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F4); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F5); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F6); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F7); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F8); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F9); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F10); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F11); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F12); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_1); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_2); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_D); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_E); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_F); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_S); - ioctl(fd, UI_SET_KEYBIT, KEY_FN_B); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT1); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT2); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT3); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT4); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT5); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT6); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT7); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT8); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT9); - ioctl(fd, UI_SET_KEYBIT, KEY_BRL_DOT10); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_0); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_1); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_2); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_3); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_4); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_5); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_6); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_7); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_8); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_9); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_STAR); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_POUND); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_A); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_B); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_C); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_D); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_FOCUS); - ioctl(fd, UI_SET_KEYBIT, KEY_WPS_BUTTON); - ioctl(fd, UI_SET_KEYBIT, KEY_TOUCHPAD_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_TOUCHPAD_ON); - ioctl(fd, UI_SET_KEYBIT, KEY_TOUCHPAD_OFF); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_ZOOMIN); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_ZOOMOUT); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_UP); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_DOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_LEFT); - ioctl(fd, UI_SET_KEYBIT, KEY_CAMERA_RIGHT); - ioctl(fd, UI_SET_KEYBIT, KEY_ATTENDANT_ON); - ioctl(fd, UI_SET_KEYBIT, KEY_ATTENDANT_OFF); - ioctl(fd, UI_SET_KEYBIT, KEY_ATTENDANT_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_LIGHTS_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_UP); - ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_DOWN); - ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_LEFT); - ioctl(fd, UI_SET_KEYBIT, BTN_DPAD_RIGHT); - ioctl(fd, UI_SET_KEYBIT, KEY_ALS_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_ROTATE_LOCK_TOGGLE); - ioctl(fd, UI_SET_KEYBIT, KEY_BUTTONCONFIG); - ioctl(fd, UI_SET_KEYBIT, KEY_TASKMANAGER); - ioctl(fd, UI_SET_KEYBIT, KEY_JOURNAL); - ioctl(fd, UI_SET_KEYBIT, KEY_CONTROLPANEL); - ioctl(fd, UI_SET_KEYBIT, KEY_APPSELECT); - ioctl(fd, UI_SET_KEYBIT, KEY_SCREENSAVER); - ioctl(fd, UI_SET_KEYBIT, KEY_VOICECOMMAND); - ioctl(fd, UI_SET_KEYBIT, KEY_ASSISTANT); - ioctl(fd, UI_SET_KEYBIT, KEY_KBD_LAYOUT_NEXT); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_MIN); - ioctl(fd, UI_SET_KEYBIT, KEY_BRIGHTNESS_MAX); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_PREV); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_NEXT); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_PREVGROUP); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_NEXTGROUP); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_ACCEPT); - ioctl(fd, UI_SET_KEYBIT, KEY_KBDINPUTASSIST_CANCEL); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHT_UP); - ioctl(fd, UI_SET_KEYBIT, KEY_RIGHT_DOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFT_UP); - ioctl(fd, UI_SET_KEYBIT, KEY_LEFT_DOWN); - ioctl(fd, UI_SET_KEYBIT, KEY_ROOT_MENU); - ioctl(fd, UI_SET_KEYBIT, KEY_MEDIA_TOP_MENU); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_11); - ioctl(fd, UI_SET_KEYBIT, KEY_NUMERIC_12); - ioctl(fd, UI_SET_KEYBIT, KEY_AUDIO_DESC); - ioctl(fd, UI_SET_KEYBIT, KEY_3D_MODE); - ioctl(fd, UI_SET_KEYBIT, KEY_NEXT_FAVORITE); - ioctl(fd, UI_SET_KEYBIT, KEY_STOP_RECORD); - ioctl(fd, UI_SET_KEYBIT, KEY_PAUSE_RECORD); - ioctl(fd, UI_SET_KEYBIT, KEY_VOD); - ioctl(fd, UI_SET_KEYBIT, KEY_UNMUTE); - ioctl(fd, UI_SET_KEYBIT, KEY_FASTREVERSE); - ioctl(fd, UI_SET_KEYBIT, KEY_SLOWREVERSE); - ioctl(fd, UI_SET_KEYBIT, KEY_DATA); - ioctl(fd, UI_SET_KEYBIT, KEY_ONSCREEN_KEYBOARD); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY1); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY2); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY3); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY4); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY5); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY6); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY7); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY8); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY9); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY10); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY11); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY12); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY13); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY14); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY15); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY16); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY17); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY18); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY19); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY20); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY21); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY22); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY23); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY24); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY25); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY26); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY27); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY28); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY29); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY30); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY31); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY32); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY33); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY34); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY35); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY36); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY37); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY38); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY39); - ioctl(fd, UI_SET_KEYBIT, BTN_TRIGGER_HAPPY40); - for(int i = LED_NUML; i <= LED_MISC; i++){ - if(ioctl(fd, UI_SET_LEDBIT, i)){ - perror("ioctl set_ledbit"); - exit(-1); - } - } - ioctl(fd, UI_SET_KEYBIT, KEY_ZOOM); - memset(&usetup, 0, sizeof(usetup)); - usetup.id.bustype = BUS_USB; - usetup.id.vendor = 0x0000; - usetup.id.product = 0xFFFF; - strcpy(usetup.name, "Oxide Virtual Keyboard"); - - ioctl(fd, UI_DEV_SETUP, &usetup); - ioctl(fd, UI_DEV_CREATE); - } - reloadDevices(); - deviceSettings.onKeyboardAttachedChanged([this]{ reloadDevices(); }); -} - -KeyboardHandler::~KeyboardHandler(){ - if(fd != -1){ - ioctl(fd, UI_DEV_DESTROY); - close(fd); - } -} - -void KeyboardHandler::flood(){ - O_DEBUG("Flooding"); - for(int i = 0; i < 512 * 8; i+=4){ - writeEvent(EV_KEY, KEY_ROTATE_LOCK_TOGGLE, 1); - writeEvent(EV_SYN, SYN_REPORT, 0); - writeEvent(EV_KEY, KEY_ROTATE_LOCK_TOGGLE, 0); - writeEvent(EV_SYN, SYN_REPORT, 0); - } -} - -void KeyboardHandler::writeEvent(int type, int code, int val){ - input_event ie; - ie.type = type; - ie.code = code; - ie.value = val; - // timestamp values below are ignored - ie.time.tv_sec = 0; - ie.time.tv_usec = 0; - ::write(fd, &ie, sizeof(input_event)); -} - -void KeyboardHandler::keyEvent(int code, int value){ - writeEvent(EV_KEY, code, value); - writeEvent(EV_SYN, SYN_REPORT, 0); -} - -bool KeyboardHandler::hasDevice(event_device device){ - for(auto keyboard : devices){ - if(device.device.c_str() == keyboard->path()){ - return true; - } - } - return false; -} - -void KeyboardHandler::reloadDevices(){ - O_DEBUG("Reloading keyboards"); - for(auto device : deviceSettings.keyboards()){ - if(device.device == deviceSettings.getButtonsDevicePath()){ - continue; - } - if(!hasDevice(device) && device.fd != -1){ - auto keyboard = new KeyboardDevice(this, device); - O_DEBUG(keyboard->name() << "added"); - devices.append(keyboard); - keyboard->readEvents(); - } - } - QMutableListIterator i(devices); - while(i.hasNext()){ - KeyboardDevice* device = i.next(); - if(device->exists()){ - continue; - } - O_DEBUG(device->name() << "removed"); - i.remove(); - delete device; - } -} diff --git a/applications/system-service/keyboardhandler.h b/applications/system-service/keyboardhandler.h deleted file mode 100644 index e103100d8..000000000 --- a/applications/system-service/keyboardhandler.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include - -#include "keyboarddevice.h" - -using namespace Oxide; - -#define keyboardHandler KeyboardHandler::init() - -class KeyboardHandler : public QThread{ - Q_OBJECT - -public: - static KeyboardHandler* init(); - KeyboardHandler(); - ~KeyboardHandler(); - void flood(); - void writeEvent(int type, int code, int val); - -private slots: - void keyEvent(int code, int value); - -private: - int fd; - uinput_setup usetup; - - QList devices; - bool hasDevice(event_device device); - void reloadDevices(); -}; diff --git a/applications/system-service/main.cpp b/applications/system-service/main.cpp index c362f7dc9..c5eb7a662 100755 --- a/applications/system-service/main.cpp +++ b/applications/system-service/main.cpp @@ -9,21 +9,21 @@ #include #include #include +#include +#include +#include #include "dbusservice.h" #include "controller.h" using namespace std; using namespace Oxide::Sentry; +using namespace Oxide::QML; const std::string runPath = "/run/oxide"; const char* pidPath = "/run/oxide/oxide.pid"; const char* lockPath = "/run/oxide/oxide.lock"; -void sigHandler(int signal){ - ::signal(signal, SIG_DFL); - qApp->quit(); -} bool stopProcess(pid_t pid){ if(pid <= 1){ return false; @@ -45,17 +45,29 @@ bool stopProcess(pid_t pid){ } int main(int argc, char* argv[]){ - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2 && getenv("RM2FB_ACTIVE") == nullptr){ +#ifdef EPAPER + auto connected = Blight::connect(true); +#else + auto connected = Blight::connect(false); +#endif + if(!connected){ + // TODO - attempt to start display server instance bool enabled = Oxide::debugEnabled(); if(!enabled){ qputenv("DEBUG", "1"); } - O_WARNING("rm2fb not detected. Running xochitl instead!"); + O_WARNING("Display server not available. Running xochitl instead!"); if(!enabled){ qputenv("DEBUG", "0"); } return QProcess::execute("/usr/bin/xochitl", QStringList()); } + Blight::connection()->onDisconnect([](int res){ + // TODO - attempt to reconnect + if(res){ + qApp->exit(res); + } + }); qputenv("XDG_CURRENT_DESKTOP", "OXIDE"); QThread::currentThread()->setObjectName("main"); deviceSettings.setupQtEnvironment(false); @@ -83,7 +95,14 @@ int main(int argc, char* argv[]){ auto actualPid = QString::number(app.applicationPid()); QString pid = Oxide::execute( "systemctl", - QStringList() << "--no-pager" << "show" << "--property" << "MainPID" << "--value" << "tarnish" + QStringList() + << "--no-pager" + << "show" + << "--property" + << "MainPID" + << "--value" + << "tarnish", + false ).trimmed(); if(pid != "0" && pid != actualPid){ if(!parser.isSet(breakLockOption)){ @@ -119,43 +138,74 @@ int main(int argc, char* argv[]){ return EXIT_FAILURE; } } - QObject::connect(&app, &QGuiApplication::aboutToQuit, [lock]{ O_INFO("Releasing lock " << lockPath); Oxide::releaseLock(lock, lockPath); }); - - signal(SIGINT, sigHandler); - signal(SIGSEGV, sigHandler); - signal(SIGTERM, sigHandler); - - dbusService; - QTimer::singleShot(0, []{ - dbusService->startup(); + QTimer::singleShot(0, &app, []{ + QObject::connect(signalHandler, &SignalHandler::sigTerm, qApp, []{ + dbusService->exit(SIGTERM); + }); + QObject::connect(signalHandler, &SignalHandler::sigInt, qApp, []{ + dbusService->exit(SIGINT); + }); + QObject::connect(signalHandler, &SignalHandler::sigSegv, qApp, []{ + dbusService->exit(SIGSEGV); + }); + QObject::connect(signalHandler, &SignalHandler::sigBus, qApp, []{ + dbusService->exit(SIGBUS); + }); }); + QFile pidFile(pidPath); if(!pidFile.open(QFile::ReadWrite)){ qWarning() << "Unable to create " << pidPath; - return app.exec(); + return EXIT_FAILURE; } pidFile.seek(0); pidFile.write(actualPid.toUtf8()); pidFile.close(); - QObject::connect(&app, &QGuiApplication::aboutToQuit, []{ - remove(pidPath); - }); + QObject::connect(&app, &QGuiApplication::aboutToQuit, []{ remove(pidPath); }); + + dbusService; + auto compositor = getCompositorDBus(); + compositor->setFlags(QString("connection/%1").arg(::getpid()), QStringList() << "system"); + Blight::shared_buf_t buffer = createBuffer(); + if(buffer != nullptr){ + auto size = getFrameBuffer()->size(); + int splashWidth = size.width() / 2; + QSize splashSize(splashWidth, splashWidth); + QRect splashRect(QPoint( + (size.width() / 2) - (splashWidth / 2), + (size.height() / 2) - (splashWidth / 2) + ), splashSize); + auto image = Oxide::QML::getImageForSurface(buffer); + QPainter painter(&image); + painter.setPen(Qt::white); + painter.fillRect(image.rect(), Qt::white); + QString path("/opt/usr/share/icons/oxide/702x702/splash/oxide.png"); + if(QFileInfo(path).exists()){ + auto splash = QImage(path).scaled(splashSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if(deviceSettings.keyboardAttached()){ + splash = splash.transformed(QTransform().rotate(90)); + } + painter.drawImage(splashRect, splash, splash.rect()); + } + painter.end(); + addSystemBuffer(buffer); + } + QQmlApplicationEngine engine; + registerQML(&engine); QQmlContext* context = engine.rootContext(); - Controller controller(&app); - context->setContextProperty("controller", &controller); + context->setContextProperty("controller", Controller::singleton()); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - if (engine.rootObjects().isEmpty()){ - QScreen* screen = app.primaryScreen(); - QWindow window(screen); - window.resize(screen->size()); - window.setPosition(0, 0); - window.setOpacity(0); - window.show(); + if(engine.rootObjects().isEmpty()){ + qFatal("Failed to load main layout"); } + QTimer::singleShot(0, [&engine, &buffer]{ + dbusService->startup(&engine); + Blight::connection()->remove(buffer); + }); return app.exec(); } diff --git a/applications/system-service/main.qml b/applications/system-service/main.qml index 2e7ba608f..e382b511c 100644 --- a/applications/system-service/main.qml +++ b/applications/system-service/main.qml @@ -2,38 +2,193 @@ import QtQuick 2.15 import QtQuick.Window 2.15 Window{ - width: Screen.width - height: Screen.width + id: window x: 0 y: 0 - opacity: 0 + width: Screen.width + height: Screen.height visible: true + color: "transparent" + property bool enableHeldKeys: controller.deviceName === "reMarkable 1" + + Connections{ + target: controller + property alias enableHeldKeys: window.enableHeldKeys + function onKeyPressed(key, modifiers, text){ + switch(key){ + case Qt.Key_Left: + if(enableHeldKeys){ + leftHeldTimer.start(); + } + break; + case Qt.Key_Right: + if(enableHeldKeys){ + rightHeldTimer.start(); + } + break; + case Qt.Key_Home: + if(enableHeldKeys){ + homeHeldTimer.start(); + } + break; + case Qt.Key_PowerOff: + case Qt.Key_Suspend: + powerHeldTimer.start(); + break; + } + } + function onKeyReleased(key, modifiers, text){ + switch(key){ + case Qt.Key_Left: + if(enableHeldKeys){ + leftHeldTimer.stop(); + } + break; + case Qt.Key_Right: + if(enableHeldKeys){ + rightHeldTimer.stop(); + } + break; + case Qt.Key_Home: + if(enableHeldKeys){ + homeHeldTimer.stop(); + } + break; + case Qt.Key_PowerOff: + case Qt.Key_Suspend: + if(powerHeldTimer.running){ + powerHeldTimer.stop(); + controller.suspend(); + } + break; + } + } + } + Timer{ + id: leftHeldTimer + running: false + repeat: false + interval: 700 + onTriggered: controller.back() + } + Timer{ + id: rightHeldTimer + running: false + repeat: false + interval: 700 + onTriggered: controller.screenshot() + } + Timer{ + id: homeHeldTimer + running: false + repeat: false + interval: 700 + onTriggered: controller.processManager() + } + Timer{ + id: powerHeldTimer + running: false + repeat: false + interval: 700 + onTriggered:{ + // TODO - power menu app + } + } + + Item{ + anchors.fill: parent + MultiPointTouchArea{ + anchors.fill: parent + minimumTouchPoints: 1 + maximumTouchPoints: 5 + mouseEnabled: false + property int offset: 100 + + function isSwipeLow(start, end, portraitLength, landscapeLength){ + return start < offset + && end > start + (Oxide.landscape ? landscapeLength : portraitLength); + } + function isSwipeHigh(start, end, portraitLength, landscapeLength, size){ + return start > size - offset + && end < start - (Oxide.landscape ? landscapeLength : portraitLength); + } + function isEnabled(swiped, portraitEnabled, landscapeEnabled){ + return swiped && (Oxide.landscape ? landscapeEnabled : portraitEnabled); + } + function swipeAction(portraitFn, landscapeFn){ + if(Oxide.landscape){ + landscapeFn(); + }else{ + portraitFn(); + } + } + + onReleased: function(touchPoints){ + if(touchPoints.length !== 1){ + return; + } + var point = touchPoints[0]; + if(isEnabled( + isSwipeHigh(point.startX, point.x, controller.leftSwipeLength, controller.downSwipeLength, window.width), + controller.leftSwipeEnabled, + controller.downSwipeEnabled + )){ + swipeAction(controller.screenshot, controller.toggleSwipes); + }else if(isEnabled( + isSwipeLow(point.startX, point.x, controller.rightSwipeLength, controller.upSwipeLength), + controller.rightSwipeEnabled, + controller.upSwipeEnabled + )){ + swipeAction(controller.back, controller.taskSwitcher); + }else if(isEnabled( + isSwipeHigh(point.startY, point.y, controller.upSwipeLength, controller.leftSwipeLength, window.height), + controller.upSwipeEnabled, + controller.leftSwipeEnabled + )){ + swipeAction(controller.taskSwitcher, controller.screenshot); + }else if(isEnabled( + isSwipeLow(point.startY, point.y, controller.downSwipeLength, controller.rightSwipeLength), + controller.downSwipeEnabled, + controller.rightSwipeEnabled + )){ + swipeAction(controller.toggleSwipes, controller.back); + } + } + } + } Shortcut{ sequences: ["Alt+Tab"] + context: Qt.ApplicationShortcut onActivated: controller.taskSwitcher() } Shortcut{ - sequences: ["Shift+Meta+S", "Shift+End+S", StandardKey.Print] + sequences: ["Meta+Shift+S", "End+Shift+S", StandardKey.Print] + context: Qt.ApplicationShortcut onActivated: controller.screenshot() } Shortcut{ - sequences: ["Ctrl+Shift+Esc", "Ctrl+Shift+1", Qt.Key_Launch0] + sequences: ["Ctrl+Shift+Esc", "Ctrl+Shift+1"] + context: Qt.ApplicationShortcut onActivated: controller.processManager() } Shortcut{ - sequences: ["Meta+Backspace", "End+Backspace"] + sequences: ["Meta+Backspace", "End+Backspace", StandardKey.Back] + context: Qt.ApplicationShortcut onActivated: controller.back() } Shortcut{ - sequences: [Qt.Key_LogOff, Qt.Key_ScreenSaver, "End+L", "Meta+L"] + sequences: ["LogOff", "ScreenSaver", "End+L", "Meta+L"] + context: Qt.ApplicationShortcut onActivated: controller.lock() } Shortcut{ - sequences: [Qt.Key_Terminal, "Ctrl+Alt+T"] + sequences: ["Terminal", "Ctrl+Alt+T"] + context: Qt.ApplicationShortcut onActivated: controller.terminal() } Shortcut{ - sequences: ["End+Alt+4", "Alt+F4"] + sequences: ["End+Alt+4", "Alt+F4", "Meta+Alt+4"] + context: Qt.ApplicationShortcut onActivated: controller.close() } } diff --git a/applications/system-service/network.cpp b/applications/system-service/network.cpp index 6e6cac1be..094879fe5 100644 --- a/applications/system-service/network.cpp +++ b/applications/system-service/network.cpp @@ -2,6 +2,7 @@ #include "network.h" #include "wifiapi.h" +#include QSet none{ "NONE", @@ -52,7 +53,7 @@ void Network::registerPath(){ auto bus = QDBusConnection::systemBus(); bus.unregisterObject(path(), QDBusConnection::UnregisterTree); if(bus.registerObject(path(), this, QDBusConnection::ExportAllContents)){ - O_DEBUG("Registered" << path() << OXIDE_NETWORK_INTERFACE); + O_INFO("Registered" << path() << OXIDE_NETWORK_INTERFACE); }else{ O_WARNING("Failed to register" << path()); } diff --git a/applications/system-service/notification.cpp b/applications/system-service/notification.cpp index d18ea3e31..9574b2d8f 100644 --- a/applications/system-service/notification.cpp +++ b/applications/system-service/notification.cpp @@ -4,22 +4,33 @@ #include "notificationapi.h" #include "appsapi.h" #include "screenapi.h" - -Notification::Notification(const QString& path, const QString& identifier, const QString& owner, const QString& application, const QString& text, const QString& icon, QObject* parent) - : QObject(parent), - m_path(path), - m_identifier(identifier), - m_owner(owner), - m_application(application), - m_text(text), - m_icon(icon) { +#include "dbusservice.h" + +#include + +Notification::Notification( + const QString& path, + const QString& identifier, + const QString& owner, + const QString& application, + const QString& text, + const QString& icon, + QObject* parent +) +: QObject(parent), + m_path(path), + m_identifier(identifier), + m_owner(owner), + m_application(application), + m_text(text), + m_icon(icon) +{ m_created = QDateTime::currentSecsSinceEpoch(); - if(!icon.isEmpty()){ - return; - } - auto app = appsAPI->getApplication(m_application); - if(app != nullptr && !app->icon().isEmpty()){ - m_icon = app->icon(); + if(icon.isEmpty()){ + auto app = appsAPI->getApplication(m_application); + if(app != nullptr && !app->icon().isEmpty()){ + m_icon = app->icon(); + } } } @@ -109,16 +120,8 @@ void Notification::display(){ return; } notificationAPI->lock(); - Oxide::dispatchToMainThread([this]{ - O_INFO("Displaying notification" << identifier()); - auto path = appsAPI->currentApplicationNoSecurityCheck(); - Application* resumeApp = nullptr; - if(path.path() != "/"){ - resumeApp = appsAPI->getApplication(path); - resumeApp->interruptApplication(); - } - paintNotification(resumeApp); - }); + O_INFO("Displaying notification" << identifier()); + paintNotification(); } void Notification::remove(){ @@ -136,8 +139,6 @@ void Notification::click(){ emit clicked(); } - - void Notification::setIcon(QString icon){ if(!hasPermission("notification")){ return; @@ -171,40 +172,25 @@ void Notification::setOwner(QString owner){ emit changed(result); } -bool Notification::hasPermission(QString permission, const char* sender){ return notificationAPI->hasPermission(permission, sender); } +bool Notification::hasPermission(QString permission, const char* sender){ + return notificationAPI->hasPermission(permission, sender); +} -void Notification::paintNotification(Application *resumeApp) { +void Notification::paintNotification(){ O_INFO("Painting notification" << identifier()); - dispatchToMainThread([this] { screenBackup = screenAPI->copy(); }); - updateRect = notificationAPI->paintNotification(text(), m_icon); + auto notification = notificationAPI->paintNotification(m_text, m_icon); O_INFO("Painted notification" << identifier()); emit displayed(); - QTimer::singleShot(2000, [this, resumeApp] { - dispatchToMainThread([this] { - auto frameBuffer = EPFrameBuffer::framebuffer(); - QPainter painter(frameBuffer); - painter.drawImage(updateRect, screenBackup, updateRect); - painter.end(); - EPFrameBuffer::sendUpdate( - updateRect, - EPFrameBuffer::Mono, - EPFrameBuffer::FullUpdate, - true - ); - O_INFO("Finished displaying notification" << identifier()); - EPFrameBuffer::waitForLastUpdate(); - }); - if (!notificationAPI->notificationDisplayQueue.isEmpty()) { - Oxide::dispatchToMainThread([resumeApp] { - notificationAPI - ->notificationDisplayQueue - .takeFirst() - ->paintNotification(resumeApp); - }); + QTimer::singleShot(2000, [this, notification] { + O_INFO("Finished displaying notification" << identifier()); + if(!notificationAPI->notificationDisplayQueue.isEmpty()){ + notificationAPI + ->notificationDisplayQueue + .takeFirst() + ->paintNotification(); return; - } - if (resumeApp != nullptr) { - resumeApp->uninterruptApplication(); + }else{ + notification->setProperty("notificationVisible", false); } notificationAPI->unlock(); }); diff --git a/applications/system-service/notification.h b/applications/system-service/notification.h index 70627d02d..ce35e0e06 100644 --- a/applications/system-service/notification.h +++ b/applications/system-service/notification.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -23,7 +24,14 @@ class Notification : public QObject{ Q_PROPERTY(QString icon READ icon WRITE setIcon) public: - Notification(const QString& path, const QString& identifier, const QString& owner, const QString& application, const QString& text, const QString& icon, QObject* parent); + Notification( + const QString& path, + const QString& identifier, + const QString& owner, + const QString& application, + const QString& text, + const QString& icon, QObject* parent + ); ~Notification(); QString path(); QDBusObjectPath qPath(); @@ -45,7 +53,7 @@ class Notification : public QObject{ Q_INVOKABLE void display(); Q_INVOKABLE void remove(); Q_INVOKABLE void click(); - void paintNotification(Application *resumeApp); + void paintNotification(); signals: void changed(QVariantMap); @@ -61,8 +69,6 @@ class Notification : public QObject{ QString m_application; QString m_text; QString m_icon; - QImage screenBackup; - QRect updateRect; bool hasPermission(QString permission, const char* sender = __builtin_FUNCTION()); }; diff --git a/applications/system-service/notification.qml b/applications/system-service/notification.qml new file mode 100644 index 000000000..c8aefe759 --- /dev/null +++ b/applications/system-service/notification.qml @@ -0,0 +1,70 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import "qrc:/codes.eeems.oxide" + +Window{ + property alias text: text.text + property alias image: image.source + property alias notificationVisible: notification.visible + property bool landscape: Oxide.landscape + + id: window + objectName: "window" + flags: Qt.FramelessWindowHint + visible: true + x: landscape ? 0 : Screen.width - width - background.border.width + y: Screen.height - height - background.border.width + width: 500 + height: 500 + function orientationWidth(){ return landscape ? height : width; } + function orientationHeight(){ return landscape ? width : height; } + contentOrientation: landscape ? Qt.LandscapeOrientation : Qt.PortraitOrientation + color: "transparent" + + Item{ + id: notification + // Must centerIn and specify width/height to force rotation to actually work + width: window.orientationWidth() + height: window.orientationHeight() + anchors.centerIn: parent + rotation: landscape ? 90 : 0 + clip: true + + Rectangle{ + id: background + color: "white" + border.color: "black" + border.width: 2 + radius: 10 + anchors.fill: rowLayout + } + RowLayout{ + id: rowLayout + width: parent.width + anchors.right: parent.right + anchors.bottom: parent.bottom + Image{ + id: image + visible: status === Image.Ready + Layout.preferredHeight: 50 + Layout.preferredWidth: 50 + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + Label{ + id: text + visible: this.text.length + color: "black" + wrapMode: Text.WordWrap + Layout.margins: 10 + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } +} diff --git a/applications/system-service/notificationapi.cpp b/applications/system-service/notificationapi.cpp index f88de2e50..57798210b 100644 --- a/applications/system-service/notificationapi.cpp +++ b/applications/system-service/notificationapi.cpp @@ -2,6 +2,10 @@ #include "notificationapi.h" #include "systemapi.h" +#include "dbusservice.h" + +#include +#include NotificationAPI* NotificationAPI::singleton(NotificationAPI* self){ static NotificationAPI* instance; @@ -11,7 +15,13 @@ NotificationAPI* NotificationAPI::singleton(NotificationAPI* self){ return instance; } -NotificationAPI::NotificationAPI(QObject* parent) : APIBase(parent), notificationDisplayQueue(), m_enabled(false), m_notifications(), m_lock() { +NotificationAPI::NotificationAPI(QObject* parent) +: APIBase(parent), + notificationDisplayQueue(), + m_enabled(false), + m_notifications(), + m_lock() +{ Oxide::Sentry::sentry_transaction("Notification API init", "init", [this](Oxide::Sentry::Transaction* t){ Oxide::Sentry::sentry_span(t, "singleton", "Setup singleton", [this]{ singleton(this); @@ -19,7 +29,11 @@ NotificationAPI::NotificationAPI(QObject* parent) : APIBase(parent), notificatio }); } -NotificationAPI::~NotificationAPI(){} +void NotificationAPI::shutdown(){ + m_window->close(); + delete m_window; + m_window = nullptr; +} bool NotificationAPI::enabled(){ return m_enabled; } @@ -35,6 +49,22 @@ void NotificationAPI::setEnabled(bool enabled){ } } +void NotificationAPI::startup(){ + auto engine = dbusService->engine(); + engine->load("qrc:/notification.qml"); + m_window = static_cast(engine->rootObjects().last()); + m_window->setProperty("WA_WAVEFORM", Blight::WaveformMode::Mono); + auto buffer = Oxide::QML::getSurfaceForWindow(m_window); + getCompositorDBus()->setFlags( + QString("connection/%1/surface/%2") + .arg(getpid()) + .arg(buffer->surface), + QStringList() << "system" + ); + m_window->setProperty("notificationVisible", false); + m_window->lower(); +} + QDBusObjectPath NotificationAPI::get(QString identifier){ if(!hasPermission("notification")){ return QDBusObjectPath("/"); @@ -95,137 +125,22 @@ Notification* NotificationAPI::getByIdentifier(const QString& identifier){ return m_notifications.value(identifier); } -QRect NotificationAPI::paintNotification(const QString &text, const QString &iconPath){ - QImage notification = notificationImage(text, iconPath); - return dispatchToMainThread([¬ification]{ - O_DEBUG("Painting to framebuffer..."); - auto frameBuffer = EPFrameBuffer::framebuffer(); - QPainter painter(frameBuffer); - QPoint pos(0, frameBuffer->height() - notification.height()); - if(systemAPI->landscape()){ - notification = notification.transformed(QTransform().rotate(90.0)); - pos.setX(0); - pos.setY(frameBuffer->height() - notification.height()); - } - auto updateRect = notification.rect().translated(pos); - painter.drawImage(updateRect, notification); - painter.end(); - O_DEBUG("Updating screen " << updateRect << "..."); - EPFrameBuffer::sendUpdate( - updateRect, - EPFrameBuffer::Grayscale, - EPFrameBuffer::PartialUpdate, - true - ); - EPFrameBuffer::waitForLastUpdate(); - return updateRect; - }); -} -void NotificationAPI::errorNotification(const QString &text) { - dispatchToMainThread([] { - auto frameBuffer = EPFrameBuffer::framebuffer(); - O_DEBUG("Waiting for other painting to finish..."); - while (frameBuffer->paintingActive()) { - EPFrameBuffer::waitForLastUpdate(); - } - O_DEBUG("Displaying error text"); - QPainter painter(frameBuffer); - painter.fillRect(frameBuffer->rect(), Qt::white); - painter.end(); - EPFrameBuffer::sendUpdate( - frameBuffer->rect(), - EPFrameBuffer::Mono, - EPFrameBuffer::FullUpdate, - true - ); - }); - notificationAPI->paintNotification(text, ""); -} -QImage NotificationAPI::notificationImage(const QString& text, const QString& iconPath){ - auto padding = 10; - auto radius = 10; - auto frameBuffer = EPFrameBuffer::framebuffer(); - auto size = frameBuffer->size(); - auto boundingRect = QPainter(frameBuffer).fontMetrics().boundingRect( - QRect(0, 0, size.width() / 2, size.height() / 8), - Qt::AlignCenter | Qt::TextWordWrap, text - ); - QImage icon(iconPath); - auto iconSize = icon.isNull() ? 0 : 50; - auto width = boundingRect.width() + iconSize + (padding * 3); - auto height = max(boundingRect.height(), iconSize) + (padding * 2); - QImage image(width, height, QImage::Format_ARGB32_Premultiplied); - image.fill(Qt::transparent); - QPainter painter(&image); - painter.setRenderHint(QPainter::Antialiasing); - QPainterPath path; - path.addRoundedRect(image.rect(), radius, radius); - QPen pen(Qt::white, 1); - painter.setPen(pen); - painter.fillPath(path, Qt::black); - painter.drawPath(path); - painter.setPen(Qt::white); - QRect textRect( - padding, - padding, - image.width() - iconSize - (padding * 2), - image.height() - padding - ); - painter.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap, text); - if(!icon.isNull()){ - QRect iconRect( - image.width() - iconSize - padding, - padding, - iconSize, - iconSize - ); - painter.fillRect(iconRect, Qt::white); - painter.drawImage(iconRect, icon); +QQuickWindow* NotificationAPI::paintNotification(const QString& text, const QString& iconPath){ + m_window->setProperty("text", text); + if(!iconPath.isEmpty() && QFileInfo(iconPath).exists()){ + m_window->setProperty("image", QUrl::fromLocalFile(iconPath)); + }else{ + m_window->setProperty("image", ""); } - painter.end(); - return image; + m_window->show(); + m_window->raise(); + m_window->setProperty("notificationVisible", true); + return m_window; } -void NotificationAPI::drawNotificationText(const QString& text, QColor color, QColor background){ - auto frameBuffer = EPFrameBuffer::framebuffer(); - dispatchToMainThread([frameBuffer, text, color, background]{ - QPainter painter(frameBuffer); - int padding = 10; - auto size = frameBuffer->size(); - auto fm = painter.fontMetrics(); - int textHeight = fm.height() + padding; - QImage textImage( - size.width() - padding * 2, - textHeight, - background == Qt::transparent - ? QImage::Format_ARGB32_Premultiplied - : QImage::Format_RGB16 - ); - textImage.fill(Qt::transparent); - QPainter painter2(&textImage); - painter2.setPen(color); - painter2.drawText( - textImage.rect(), - Qt::AlignVCenter | Qt::AlignRight, - text - ); - painter2.end(); - QPoint textPos(0 + padding, size.height() - textHeight); - if(systemAPI->landscape()){ - textImage = textImage.transformed(QTransform().rotate(90.0)); - textPos.setX(0); - textPos.setY(size.height() - textImage.height() - padding); - } - auto textRect = textImage.rect().translated(textPos); - painter.drawImage(textRect, textImage); - EPFrameBuffer::sendUpdate( - textRect, - EPFrameBuffer::Grayscale, - EPFrameBuffer::PartialUpdate, - true - ); - painter.end(); - }); +void NotificationAPI::errorNotification(const QString &text){ + O_DEBUG("Displaying error text"); + notificationAPI->paintNotification(text, ""); } QDBusObjectPath NotificationAPI::add(const QString& identifier, const QString& application, const QString& text, const QString& icon, QDBusMessage message){ diff --git a/applications/system-service/notificationapi.h b/applications/system-service/notificationapi.h index 065956480..2461270dd 100644 --- a/applications/system-service/notificationapi.h +++ b/applications/system-service/notificationapi.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -22,9 +23,10 @@ class NotificationAPI : public APIBase { public: static NotificationAPI* singleton(NotificationAPI* self = nullptr); NotificationAPI(QObject* parent); - ~NotificationAPI(); + void shutdown(); bool enabled(); void setEnabled(bool enabled); + void startup(); Q_INVOKABLE QDBusObjectPath get(QString identifier); @@ -34,10 +36,8 @@ class NotificationAPI : public APIBase { Notification* add(const QString& identifier, const QString& owner, const QString& application, const QString& text, const QString& icon); Notification* getByIdentifier(const QString& identifier); - QRect paintNotification(const QString& text, const QString& iconPath); + QQuickWindow* paintNotification(const QString& text, const QString& iconPath); void errorNotification(const QString& text); - QImage notificationImage(const QString& text, const QString& iconPath); - void drawNotificationText(const QString& text, QColor color = Qt::black, QColor background = Qt::transparent); public slots: QDBusObjectPath add(const QString& identifier, const QString& application, const QString& text, const QString& icon, QDBusMessage message); @@ -58,6 +58,7 @@ public slots: bool m_enabled; QMap m_notifications; QMutex m_lock; + QQuickWindow* m_window = nullptr; QString getPath(QString id); }; diff --git a/applications/system-service/powerapi.cpp b/applications/system-service/powerapi.cpp index 3f4773f90..c563d8e80 100644 --- a/applications/system-service/powerapi.cpp +++ b/applications/system-service/powerapi.cpp @@ -41,7 +41,7 @@ PowerAPI::PowerAPI(QObject* parent) }); } -PowerAPI::~PowerAPI(){ +void PowerAPI::shutdown(){ if(timer != nullptr){ qDebug() << "Killing timer"; timer->stop(); diff --git a/applications/system-service/powerapi.h b/applications/system-service/powerapi.h index 67ec17113..3341de0cd 100644 --- a/applications/system-service/powerapi.h +++ b/applications/system-service/powerapi.h @@ -26,7 +26,7 @@ class PowerAPI : public APIBase { public: static PowerAPI* singleton(PowerAPI* self = nullptr); PowerAPI(QObject* parent); - ~PowerAPI(); + void shutdown(); void setEnabled(bool enabled) override; diff --git a/applications/system-service/qml.qrc b/applications/system-service/qml.qrc index 5f6483ac3..edbd23c23 100644 --- a/applications/system-service/qml.qrc +++ b/applications/system-service/qml.qrc @@ -1,5 +1,6 @@ main.qml + notification.qml diff --git a/applications/system-service/screenapi.cpp b/applications/system-service/screenapi.cpp index 0e47c61bd..91bd0a4e9 100644 --- a/applications/system-service/screenapi.cpp +++ b/applications/system-service/screenapi.cpp @@ -2,7 +2,7 @@ #include "notificationapi.h" #include "systemapi.h" -#include +#include QDBusObjectPath ScreenAPI::screenshot(){ if(!hasPermission("screen")){ @@ -11,46 +11,40 @@ QDBusObjectPath ScreenAPI::screenshot(){ O_INFO("Taking screenshot"); auto filePath = getNextPath(); O_DEBUG("Using path" << filePath); - return dispatchToMainThread([this, filePath]{ - QImage screen = copy(); - QRect rect = notificationAPI->paintNotification("Taking Screenshot...", ""); - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::Mono, EPFrameBuffer::PartialUpdate, true); - QDBusObjectPath path("/"); - bool saved = ( - systemAPI->landscape() - ? screen.transformed(QTransform().rotate(270.0)) - : screen - ).save(filePath); - if(!saved){ - O_WARNING("Failed to take screenshot"); + auto notification = notificationAPI->add( + QUuid::createUuid().toString(), + "codes.eeems.tarnish", + "codes.eeems.tarnish", + "Taking Screenshot...", + "" + ); + notification->display(); + auto screen = getFrameBuffer(); + QDBusObjectPath path("/"); + bool saved = false; + if(screen == nullptr || screen->size().isEmpty()){ + O_WARNING("Could not get copy of screen"); + }else{ + if(systemAPI->landscape()){ + saved = screen->transformed(QTransform().rotate(270.0)).save(filePath); }else{ - path = addScreenshot(filePath)->qPath(); + saved = screen->save(filePath); } - auto frameBuffer = EPFrameBuffer::framebuffer(); - QPainter painter(frameBuffer); - painter.drawImage(rect, screen, rect); - painter.end(); - EPFrameBuffer::sendUpdate(rect, EPFrameBuffer::HighQualityGrayscale, EPFrameBuffer::PartialUpdate, true); - notificationAPI->add( - QUuid::createUuid().toString(), - "codes.eeems.tarnish", - "codes.eeems.tarnish", - saved ? "Screenshot taken" : "Failed to take screenshot", - saved ? filePath : "" - )->display(); - return path; - }); -} - -QImage ScreenAPI::copy(){ - return Oxide::dispatchToMainThread([]{ - auto frameBuffer = EPFrameBuffer::framebuffer(); - O_DEBUG("Waiting for other painting to finish..."); - while(frameBuffer->paintingActive()){ - EPFrameBuffer::waitForLastUpdate(); + if(saved){ + path = addScreenshot(filePath)->qPath(); + }else if(!saved){ + O_WARNING("Failed to save screenshot"); } - return frameBuffer->copy(); - }); + } + notification->remove(); + notificationAPI->add( + QUuid::createUuid().toString(), + "codes.eeems.tarnish", + "codes.eeems.tarnish", + saved ? "Screenshot taken" : "Failed to take screenshot", + saved ? filePath : "" + )->display(); + return path; } QDBusObjectPath ScreenAPI::addScreenshot(QByteArray blob){ @@ -62,7 +56,7 @@ QDBusObjectPath ScreenAPI::addScreenshot(QByteArray blob){ auto filePath = getNextPath(); QFile file(filePath); if(!file.open(QIODevice::WriteOnly)){ - qDebug("Failed to add screenshot"); + O_WARNING("Failed to add screenshot"); mutex.unlock(); return QDBusObjectPath(""); } @@ -153,8 +147,6 @@ ScreenAPI::ScreenAPI(QObject* parent) : APIBase(parent), m_screenshots(), m_enab }); } -ScreenAPI::~ScreenAPI(){} - void ScreenAPI::setEnabled(bool enabled){ m_enabled = enabled; O_INFO("Screen API" << enabled); @@ -184,55 +176,4 @@ QList ScreenAPI::screenshots(){ } return list; } - -bool ScreenAPI::drawFullscreenImage(QString path, double rotate) { - if (!hasPermission("screen")) { - return false; - } - if (!QFile(path).exists()) { - O_WARNING("Can't find image" << path); - return false; - } - QImage img(path); - if (img.isNull()) { - O_WARNING("Image data invalid" << path); - return false; - } - if(rotate){ - img = img.transformed(QTransform().rotate(rotate)); - } - Oxide::Sentry::sentry_transaction( - "Draw Fullscreen Image", "drawFullscrenImage", - [img, path](Oxide::Sentry::Transaction *t) { - Q_UNUSED(t); - Oxide::dispatchToMainThread([img] { - auto frameBuffer = EPFrameBuffer::framebuffer(); - QRect rect = frameBuffer->rect(); - QPainter painter(frameBuffer); - painter.fillRect(rect, Qt::white); - painter.setRenderHints( - QPainter::Antialiasing | QPainter::SmoothPixmapTransform, - 1 - ); - QPixmap pxmap; - QPoint center(rect.width() / 2, rect.height() / 2); - painter.translate(center); - painter.scale( - 1* (rect.width() / qreal(img.height())), - 1 * (rect.width() / qreal(img.height())) - ); - painter.translate(0 - img.width() / 2, 0 - img.height() / 2); - painter.drawPixmap(img.rect(), QPixmap::fromImage(img)); - painter.end(); - EPFrameBuffer::sendUpdate( - frameBuffer->rect(), - EPFrameBuffer::HighQualityGrayscale, - EPFrameBuffer::FullUpdate, - true - ); - EPFrameBuffer::waitForLastUpdate(); - }); - }); - return true; -} #include "moc_screenapi.cpp" diff --git a/applications/system-service/screenapi.h b/applications/system-service/screenapi.h index 365272ef5..f0832ac4a 100644 --- a/applications/system-service/screenapi.h +++ b/applications/system-service/screenapi.h @@ -34,15 +34,10 @@ class ScreenAPI : public APIBase { public: static ScreenAPI* singleton(ScreenAPI* self = nullptr); ScreenAPI(QObject* parent); - ~ScreenAPI(); void setEnabled(bool enabled); bool enabled(); QList screenshots(); - - Q_INVOKABLE bool drawFullscreenImage(QString path, double rotate = 0); - Q_INVOKABLE QDBusObjectPath screenshot(); - QImage copy(); public slots: QDBusObjectPath addScreenshot(QByteArray blob); diff --git a/applications/system-service/system-service.pro b/applications/system-service/system-service.pro index c2d817352..b9cfdfdaa 100644 --- a/applications/system-service/system-service.pro +++ b/applications/system-service/system-service.pro @@ -13,13 +13,8 @@ SOURCES += \ application.cpp \ appsapi.cpp \ bss.cpp \ - buttonhandler.cpp \ dbusservice.cpp \ - digitizerhandler.cpp \ eventlistener.cpp \ - fifohandler.cpp \ - keyboarddevice.cpp \ - keyboardhandler.cpp \ network.cpp \ notification.cpp \ notificationapi.cpp \ @@ -70,14 +65,9 @@ HEADERS += \ application.h \ appsapi.h \ bss.h \ - buttonhandler.h \ controller.h \ dbusservice.h \ - digitizerhandler.h \ eventlistener.h \ - fifohandler.h \ - keyboarddevice.h \ - keyboardhandler.h \ network.h \ notification.h \ notificationapi.h \ @@ -101,11 +91,16 @@ LIBS += -lz DISTFILES += \ fi.w1.wpa_supplicant1.xml \ generate_xml.sh \ - org.freedesktop.login1.xml + org.freedesktop.login1.xml \ + ../../assets/etc/dbus-1/system.d/codes.eeems.oxide.conf \ + ../../assets/etc/systemd/system/tarnish.service \ + ../../assets/opt/usr/share/applications/xochitl.oxide \ + ../../assets/opt/etc/keyd/oxide.conf INCLUDEPATH += ../../shared/mxcfb include(../../qmake/liboxide.pri) +include(../../qmake/libblight.pri) QMAKE_POST_LINK += sh $$_PRO_FILE_PWD_/generate_xml.sh diff --git a/applications/system-service/systemapi.cpp b/applications/system-service/systemapi.cpp index 0b74bafb9..2ab9d1034 100644 --- a/applications/system-service/systemapi.cpp +++ b/applications/system-service/systemapi.cpp @@ -1,11 +1,12 @@ #include +#include #include "systemapi.h" #include "appsapi.h" #include "powerapi.h" #include "wifiapi.h" #include "notificationapi.h" -#include "keyboardhandler.h" +#include "controller.h" QDebug operator<<(QDebug debug, const Touch& touch){ QDebugStateSaver saver(debug); @@ -19,14 +20,62 @@ QDebug operator<<(QDebug debug, Touch* touch){ } void SystemAPI::PrepareForSleep(bool suspending){ - auto device = deviceSettings.getDeviceType(); if(suspending){ - Oxide::Sentry::sentry_transaction("Suspend System", "suspend", [this, device](Oxide::Sentry::Transaction* t){ + Controller::singleton()->enabled = false; + Oxide::Sentry::sentry_transaction("Suspend System", "suspend", [this](Oxide::Sentry::Transaction* t){ if(autoLock()){ lockTimestamp = QDateTime::currentMSecsSinceEpoch() + lockTimer.remainingTime(); O_DEBUG("Auto Lock timestamp:" << lockTimestamp); } O_INFO("Preparing for suspend..."); + Oxide::Sentry::sentry_span(t, "screen", "Update screen with suspend image", [this]{ + QString path("/usr/share/remarkable/sleeping.png"); + if(!QFile::exists(path)){ + path = "/usr/share/remarkable/suspended.png"; + } + if(!QFile::exists(path)){ + return; + } + m_buffer = createBuffer(); + if(m_buffer == nullptr){ + return; + } + QImage img(path); + if(img.isNull()){ + return; + } + if(landscape()){ + img = img.transformed(QTransform().rotate(90)); + } + auto image = Oxide::QML::getImageForSurface(m_buffer); + image.fill(Qt::white); + QPainter painter(&image); + painter.setRenderHints( + QPainter::Antialiasing | QPainter::SmoothPixmapTransform, + 1 + ); + auto rect = image.rect(); + if(rect == img.rect()){ + painter.drawImage(rect, img, rect); + }else{ + QPoint center(rect.width() / 2, rect.height() / 2); + painter.translate(center); + painter.scale( + 1 * (rect.width() / qreal(img.height())), + 1 * (rect.width() / qreal(img.height())) + ); + painter.translate(0 - img.width() / 2, 0 - img.height() / 2); + painter.drawPixmap(img.rect(), QPixmap::fromImage(img)); + painter.end(); + } + addSystemBuffer(m_buffer); + auto maybe = Blight::connection()->raise(m_buffer); + // Repaint to attempt to reduce ghosting + Blight::connection()->repaint(m_buffer, Blight::HighQualityGrayscale, m_buffer->surface); + if(maybe.has_value()){ + maybe.value()->wait(); + } + }); Oxide::Sentry::sentry_span(t, "prepare", "Prepare for suspend", [this]{ wifiAPI->stopUpdating(); emit deviceSuspending(); @@ -41,35 +90,29 @@ void SystemAPI::PrepareForSleep(bool suspending){ resumeApp = nullptr; } }); - auto rotate = landscape() ? 90 : 0; - Oxide::Sentry::sentry_span(t, "screen", "Update screen with suspend image", [rotate]{ - if(QFile::exists("/usr/share/remarkable/sleeping.png")){ - screenAPI->drawFullscreenImage("/usr/share/remarkable/sleeping.png", rotate); - }else{ - screenAPI->drawFullscreenImage("/usr/share/remarkable/suspended.png", rotate); + Oxide::Sentry::sentry_span(t, "disable", "Disable various services", [this]{ + if(wifiAPI->state() != WifiAPI::State::Off){ + wifiWasOn = true; + wifiAPI->disable(); } - }); - Oxide::Sentry::sentry_span(t, "disable", "Disable various services", [this, device]{ - buttonHandler->setEnabled(false); - if(device == Oxide::DeviceSettings::DeviceType::RM2){ - if(wifiAPI->state() != WifiAPI::State::Off){ - wifiWasOn = true; - wifiAPI->disable(); - } - system("/sbin/rmmod brcmfmac"); + getCompositorDBus()->waitForNoRepaints().waitForFinished(); + if(m_buffer != nullptr){ + Blight::connection()->waitForMarker(m_buffer->surface); } releaseSleepInhibitors(); }); - Oxide::Sentry::sentry_span(t, "clear-input", "Clear input buffers", [this]{ - clearDeviceBuffers(); - }); O_INFO("Suspending..."); }); }else{ - Oxide::Sentry::sentry_transaction("Resume System", "resume", [this, device](Oxide::Sentry::Transaction* t){ + Oxide::Sentry::sentry_transaction("Resume System", "resume", [this](Oxide::Sentry::Transaction* t){ + qDebug() << "Resuming..."; Oxide::Sentry::sentry_span(t, "inhibit", "Inhibit sleep", [this]{ inhibitSleep(); }); + if(m_buffer != nullptr){ + Blight::connection()->remove(m_buffer); + m_buffer = nullptr; + } O_INFO("Resuming..."); Oxide::Sentry::sentry_span(t, "process", "Process events", []{ QCoreApplication::processEvents(QEventLoop::AllEvents, 100); @@ -103,8 +146,7 @@ void SystemAPI::PrepareForSleep(bool suspending){ O_WARNING("Unable to find an app to resume"); } }); - Oxide::Sentry::sentry_span(t, "enable", "Enable various services", [this, device]{ - buttonHandler->setEnabled(true); + Oxide::Sentry::sentry_span(t, "enable", "Enable various services", [this]{ emit deviceResuming(); if(autoSleep() && powerAPI->chargerState() != PowerAPI::ChargerConnected){ O_DEBUG("Suspend timer re-enabled due to resume"); @@ -114,15 +156,13 @@ void SystemAPI::PrepareForSleep(bool suspending){ O_DEBUG("Lock timer re-enabled due to resume"); lockTimer.start(autoLock() * 60 * 1000); } - if(device == Oxide::DeviceSettings::DeviceType::RM2){ - system("/sbin/modprobe brcmfmac"); - if(wifiWasOn){ - wifiAPI->enable(); - } + if(wifiWasOn){ + wifiAPI->enable(); } wifiAPI->resumeUpdating(); }); }); + Controller::singleton()->enabled = true; } } SystemAPI* SystemAPI::singleton(SystemAPI* self){ @@ -134,16 +174,16 @@ SystemAPI* SystemAPI::singleton(SystemAPI* self){ } SystemAPI::SystemAPI(QObject* parent) - : APIBase(parent), - suspendTimer(this), - lockTimer(this), - settings(this), - sleepInhibitors(), - powerOffInhibitors(), - mutex(), - touches(), - swipeStates(), - swipeLengths() { +: APIBase(parent), + suspendTimer(this), + lockTimer(this), + settings(this), + sleepInhibitors(), + powerOffInhibitors(), + mutex(), + swipeStates(), + swipeLengths() +{ Oxide::Sentry::sentry_transaction("System API Init", "init", [this](Oxide::Sentry::Transaction* t){ Oxide::Sentry::sentry_span(t, "settings", "Sync settings", [this](Oxide::Sentry::Span* s){ Oxide::Sentry::sentry_span(s, "swipes", "Default swipe values", [this]{ @@ -249,13 +289,13 @@ SystemAPI::SystemAPI(QObject* parent) }); qRegisterMetaType(); Oxide::Sentry::sentry_span(t, "input", "Connect input events", [this]{ - connect(touchHandler, &DigitizerHandler::inputEvent, this, &SystemAPI::touchEvent); - connect(wacomHandler, &DigitizerHandler::inputEvent, this, &SystemAPI::penEvent); eventListener->append([this](QObject* object, QEvent* event){ Q_UNUSED(object); switch(event->type()){ - case QEvent::KeyRelease: case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::Shortcut: + case QEvent::ShortcutOverride: case QEvent::TabletPress: case QEvent::TabletMove: case QEvent::TabletRelease: @@ -266,6 +306,22 @@ SystemAPI::SystemAPI(QObject* parent) case QEvent::TouchCancel: case QEvent::TouchEnd: case QEvent::TouchUpdate: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + case QEvent::MouseTrackingChange: + case QEvent::Wheel: + case QEvent::Pointer: + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + case QEvent::Gesture: + case QEvent::GestureOverride: + case QEvent::NonClientAreaMouseButtonDblClick: + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::NonClientAreaMouseButtonRelease: + case QEvent::NonClientAreaMouseMove: activity(); break; default: @@ -276,13 +332,12 @@ SystemAPI::SystemAPI(QObject* parent) deviceSettings.onKeyboardAttachedChanged([this]{ emit landscapeChanged(landscape()); }); - keyboardHandler; }); O_INFO("System API ready to use"); }); } -SystemAPI::~SystemAPI(){ +void SystemAPI::shutdown(){ O_INFO("Removing all inhibitors"); rguard(false); QMutableListIterator i(inhibitors); @@ -385,14 +440,6 @@ void SystemAPI::lock(){ mutex.lock(); } void SystemAPI::unlock() { mutex.unlock(); } -void SystemAPI::clearDeviceBuffers(){ - touchHandler->clear_buffer(); - wacomHandler->clear_buffer(); - buttonHandler->clear_buffer(); - clearKeyboardBuffers(); -} -void SystemAPI::clearKeyboardBuffers(){ keyboardHandler->flood(); } - void SystemAPI::setSwipeEnabled(int direction, bool enabled){ if(!hasPermission("system")){ return; @@ -433,6 +480,7 @@ void SystemAPI::setSwipeEnabled(SwipeDirection direction, bool enabled){ } sharedSettings.endArray(); sharedSettings.sync(); + emit swipeEnabledChanged(direction, enabled); } bool SystemAPI::getSwipeEnabled(int direction){ @@ -641,112 +689,6 @@ void SystemAPI::lockTimeout(){ } } -void SystemAPI::touchEvent(const input_event& event){ - switch(event.type){ - case EV_SYN: - switch(event.code){ - case SYN_REPORT: - // Always mark the current slot as modified - auto touch = getEvent(currentSlot); - touch->modified = true; - QList released; - QList pressed; - QList moved; - for(auto touch : touches.values()){ - if(touch->id == -1){ - touch->active = false; - released.append(touch); - }else if(!touch->active){ - released.append(touch); - }else if(!touch->existing){ - pressed.append(touch); - }else if(touch->modified){ - moved.append(touch); - } - } - if(!penActive){ - if(pressed.length()){ - touchDown(pressed); - } - if(moved.length()){ - touchMove(moved); - } - if(released.length()){ - touchUp(released); - } - }else if(swipeDirection != None){ - O_DEBUG("Swiping cancelled due to pen activity"); - swipeDirection = None; - } - // Cleanup released touches - for(auto touch : released){ - if(!touch->active){ - touches.remove(touch->slot); - delete touch; - } - } - // Setup touches for next event set - for(auto touch : touches.values()){ - touch->modified = false; - touch->existing = touch->existing || (touch->x != NULL_TOUCH_COORD && touch->y != NULL_TOUCH_COORD); - } - break; - } - break; - case EV_ABS: - if(currentSlot == -1 && event.code != ABS_MT_SLOT){ - return; - } - switch(event.code){ - case ABS_MT_SLOT:{ - currentSlot = event.value; - auto touch = getEvent(currentSlot); - touch->modified = true; - }break; - case ABS_MT_TRACKING_ID:{ - auto touch = getEvent(currentSlot); - touch->active = event.value != -1; - if(touch->active){ - touch->id = event.value; - } - }break; - case ABS_MT_POSITION_X:{ - auto touch = getEvent(currentSlot); - touch->x = event.value; - }break; - case ABS_MT_POSITION_Y:{ - auto touch = getEvent(currentSlot); - touch->y = event.value; - }break; - case ABS_MT_PRESSURE:{ - auto touch = getEvent(currentSlot); - touch->pressure = event.value; - }break; - case ABS_MT_TOUCH_MAJOR:{ - auto touch = getEvent(currentSlot); - touch->major = event.value; - }break; - case ABS_MT_TOUCH_MINOR:{ - auto touch = getEvent(currentSlot); - touch->minor = event.value; - }break; - case ABS_MT_ORIENTATION:{ - auto touch = getEvent(currentSlot); - touch->orientation = event.value; - }break; - } - break; - } -} - -void SystemAPI::penEvent(const input_event& event){ - if(event.type != EV_KEY || event.code != BTN_TOOL_PEN){ - return; - } - penActive = event.value; - O_DEBUG("Pen state: " << (penActive ? "Active" : "Inactive")); -} - void SystemAPI::inhibitSleep(){ inhibitors.append(Inhibitor(systemd, "sleep", qApp->applicationName(), "Handle sleep screen")); } @@ -786,259 +728,6 @@ void SystemAPI::rguard(bool install){ QProcess::execute("/opt/bin/rguard", QStringList() << (install ? "-1" : "-0")); } -Touch* SystemAPI::getEvent(int slot){ - if(slot == -1){ - return nullptr; - } - if(!touches.contains(slot)){ - touches.insert(slot, new Touch{ - .slot = slot - }); - } - return touches.value(slot); -} - -int SystemAPI::getCurrentFingers(){ - return std::count_if(touches.begin(), touches.end(), [](Touch* touch){ - return touch->active; - }); -} - -void SystemAPI::touchDown(QList touches){ - if(penActive){ - return; - } - O_DEBUG("DOWN" << touches); - if(getCurrentFingers() != 1){ - return; - } - auto touch = touches.first(); - if(swipeDirection != None || touch->x == NULL_TOUCH_COORD || touch->y == NULL_TOUCH_COORD){ - return; - } - int offset = 20; - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ - offset = 40; - } - if(touch->y <= offset){ - swipeDirection = Up; - }else if(touch->y > (deviceSettings.getTouchHeight() - offset)){ - swipeDirection = Down; - }else if(touch->x <= offset){ - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ - swipeDirection = Right; - }else{ - swipeDirection = Left; - } - }else if(touch->x > (deviceSettings.getTouchWidth() - offset)){ - if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2){ - swipeDirection = Left; - }else{ - swipeDirection = Right; - } - }else{ - return; - } - O_DEBUG("Swipe started" << swipeDirection); - startLocation = location = QPoint(touch->x, touch->y); -} - -void SystemAPI::touchUp(QList touches){ - O_DEBUG("UP" << touches); - if(swipeDirection == None){ - O_DEBUG("Not swiping"); - if(touchHandler->grabbed()){ - for(auto touch : touches){ - writeTouchUp(touch); - } - touchHandler->ungrab(); - } - return; - } - if(getCurrentFingers()){ - O_DEBUG("Still swiping"); - if(touchHandler->grabbed()){ - for(auto touch : touches){ - writeTouchUp(touch); - } - } - return; - } - if(touches.length() > 1){ - O_DEBUG("Too many fingers"); - if(touchHandler->grabbed()){ - for(auto touch : touches){ - writeTouchUp(touch); - } - touchHandler->ungrab(); - } - swipeDirection = None; - return; - } - auto touch = touches.first(); - if(touch->x == NULL_TOUCH_COORD || touch->y == NULL_TOUCH_COORD){ - O_DEBUG("Invalid touch event"); - swipeDirection = None; - return; - } - if(swipeDirection == Up){ - if(!swipeStates[Up] || touch->y < location.y() || touch->y - startLocation.y() < swipeLengths[Up]){ - // Must end swiping up and having gone far enough - cancelSwipe(touch); - return; - } - if(landscape()){ - emit rightAction(); - }else{ - emit bottomAction(); - } - }else if(swipeDirection == Down){ - if(!swipeStates[Down] || touch->y > location.y() || startLocation.y() - touch->y < swipeLengths[Down]){ - // Must end swiping down and having gone far enough - cancelSwipe(touch); - return; - } - if(landscape()){ - emit leftAction(); - }else{ - emit topAction(); - } - }else if(swipeDirection == Right || swipeDirection == Left){ - auto isRM2 = deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM2; - auto invalidLeft = !swipeStates[Left] || touch->x < location.x() || touch->x - startLocation.x() < swipeLengths[Left]; - auto invalidRight = !swipeStates[Right] || touch->x > location.x() || startLocation.x() - touch->x < swipeLengths[Right]; - if(swipeDirection == Right && (isRM2 ? invalidLeft : invalidRight)){ - // Must end swiping right and having gone far enough - cancelSwipe(touch); - return; - }else if(swipeDirection == Left && (isRM2 ? invalidRight : invalidLeft)){ - // Must end swiping left and having gone far enough - cancelSwipe(touch); - return; - } - if(swipeDirection == Left){ - if(landscape()){ - emit topAction(); - }else{ - emit rightAction(); - } - }else{ - if(landscape()){ - emit bottomAction(); - }else{ - emit leftAction(); - } - } - } - swipeDirection = None; - touchHandler->ungrab(); - touch->x = -1; - touch->y = -1; - writeTouchUp(touch); - O_DEBUG("Swipe direction" << swipeDirection); -} - -void SystemAPI::touchMove(QList touches){ - O_DEBUG("MOVE" << touches); - if(swipeDirection == None){ - if(touchHandler->grabbed()){ - for(auto touch : touches){ - writeTouchMove(touch); - } - touchHandler->ungrab(); - } - return; - } - if(touches.length() > 1){ - O_DEBUG("Too many fingers"); - if(touchHandler->grabbed()){ - for(auto touch : touches){ - writeTouchMove(touch); - } - touchHandler->ungrab(); - } - swipeDirection = None; - return; - } - auto touch = touches.first(); - if(touch->y > location.y()){ - location = QPoint(touch->x, touch->y); - } -} - -void SystemAPI::cancelSwipe(Touch* touch){ - O_DEBUG("Swipe Cancelled"); - swipeDirection = None; - touchHandler->ungrab(); - writeTouchUp(touch); -} - -void SystemAPI::writeTouchUp(Touch* touch){ - bool grabbed = touchHandler->grabbed(); - if(grabbed){ - touchHandler->ungrab(); - } - writeTouchMove(touch); - O_DEBUG("Write touch up" << touch); - int size = sizeof(input_event) * 3; - input_event* events = (input_event*)malloc(size); - events[0] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_SLOT, touch->slot); - events[1] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); - events[2] = DigitizerHandler::createEvent(EV_SYN, 0, 0); - touchHandler->write(events, size); - free(events); - if(grabbed){ - touchHandler->grab(); - } -} - -void SystemAPI::writeTouchMove(Touch* touch){ - bool grabbed = touchHandler->grabbed(); - if(grabbed){ - touchHandler->ungrab(); - } - O_DEBUG("Write touch move" << touch); - int count = 8; - if(touch->x == NULL_TOUCH_COORD){ - count--; - } - if(touch->y == NULL_TOUCH_COORD){ - count--; - } - int size = sizeof(input_event) * count; - input_event* events = (input_event*)malloc(size); - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_SLOT, touch->slot); - if(touch->x != NULL_TOUCH_COORD){ - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_POSITION_X, touch->x); - } - if(touch->y != NULL_TOUCH_COORD){ - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_POSITION_Y, touch->y); - } - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_PRESSURE, touch->pressure); - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_TOUCH_MAJOR, touch->major); - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_TOUCH_MINOR, touch->minor); - events[2] = DigitizerHandler::createEvent(EV_ABS, ABS_MT_ORIENTATION, touch->orientation); - events[2] = DigitizerHandler::createEvent(EV_SYN, 0, 0); - touchHandler->write(events, size); - free(events); - if(grabbed){ - touchHandler->grab(); - } -} - -void SystemAPI::fn(){ - auto n = 512 * 8; - auto num_inst = 4; - input_event* ev = (input_event *)malloc(sizeof(struct input_event) * n * num_inst); - memset(ev, 0, sizeof(input_event) * n * num_inst); - auto i = 0; - while (i < n) { - ev[i++] = DigitizerHandler::createEvent(EV_ABS, ABS_DISTANCE, 1); - ev[i++] = DigitizerHandler::createEvent(EV_SYN, 0, 0); - ev[i++] = DigitizerHandler::createEvent(EV_ABS, ABS_DISTANCE, 2); - ev[i++] = DigitizerHandler::createEvent(EV_SYN, 0, 0); - } -} void SystemAPI::toggleSwipes(){ bool state = !swipeStates[Up]; setSwipeEnabled(Left, state); diff --git a/applications/system-service/systemapi.h b/applications/system-service/systemapi.h index 3436c17e8..b72e8fc09 100644 --- a/applications/system-service/systemapi.h +++ b/applications/system-service/systemapi.h @@ -9,10 +9,8 @@ #include #include "apibase.h" -#include "buttonhandler.h" #include "application.h" #include "screenapi.h" -#include "digitizerhandler.h" #include "eventlistener.h" #include "login1_interface.h" @@ -69,7 +67,7 @@ class SystemAPI : public APIBase { Q_ENUM(SwipeDirection) static SystemAPI* singleton(SystemAPI* self = nullptr); SystemAPI(QObject* parent); - ~SystemAPI(); + void shutdown(); void setEnabled(bool enabled); int autoSleep(); void setAutoSleep(int _autoSleep); @@ -87,8 +85,6 @@ class SystemAPI : public APIBase { void startLockTimer(); void lock(); void unlock(); - void clearDeviceBuffers(); - void clearKeyboardBuffers(); Q_INVOKABLE void setSwipeEnabled(int direction, bool enabled); void setSwipeEnabled(SwipeDirection direction, bool enabled); Q_INVOKABLE bool getSwipeEnabled(int direction); @@ -124,6 +120,7 @@ public slots: void autoLockChanged(int); void lockOnSuspendChanged(bool); void swipeLengthChanged(int, int); + void swipeEnabledChanged(int, bool); void landscapeChanged(bool); void deviceSuspending(); void deviceResuming(); @@ -132,8 +129,6 @@ private slots: void PrepareForSleep(bool suspending); void suspendTimeout(); void lockTimeout(); - void touchEvent(const input_event& event); - void penEvent(const input_event& event); private: Manager* systemd; @@ -146,30 +141,15 @@ private slots: QStringList sleepInhibitors; QStringList powerOffInhibitors; QMutex mutex; - QMap touches; - int currentSlot = 0; bool wifiWasOn = false; - bool penActive = false; - int swipeDirection = None; - QPoint location; - QPoint startLocation; QMap swipeStates; QMap swipeLengths; + Blight::shared_buf_t m_buffer = nullptr; void inhibitSleep(); void inhibitPowerOff(); void releaseSleepInhibitors(bool block = false); void releasePowerOffInhibitors(bool block = false); void rguard(bool install); - Touch* getEvent(int slot); - int getCurrentFingers(); - - void touchDown(QList touches); - void touchUp(QList touches); - void touchMove(QList touches); - void cancelSwipe(Touch* touch); - void writeTouchUp(Touch* touch); - void writeTouchMove(Touch* touch); - void fn(); }; #endif // SYSTEMAPI_H diff --git a/applications/system-service/tarnish_stable.h b/applications/system-service/tarnish_stable.h index 056b4f721..77ac59c52 100644 --- a/applications/system-service/tarnish_stable.h +++ b/applications/system-service/tarnish_stable.h @@ -69,7 +69,5 @@ #include #include -#include "apibase.h" -#include "mxcfb.h" #include "supplicant.h" #endif diff --git a/applications/system-service/wifiapi.cpp b/applications/system-service/wifiapi.cpp index 8274b72c8..deb9aab2a 100644 --- a/applications/system-service/wifiapi.cpp +++ b/applications/system-service/wifiapi.cpp @@ -30,6 +30,7 @@ WifiAPI::WifiAPI(QObject* parent) Wlan* item = new Wlan(dir.path() + "/" + path, this); if(!item->hasDirectory("wireless")){ O_DEBUG(" Not a wireless device"); + item->deleteLater(); continue; } O_DEBUG(" Wireless device found!"); @@ -147,7 +148,7 @@ WifiAPI::WifiAPI(QObject* parent) }); } -WifiAPI::~WifiAPI(){ +void WifiAPI::shutdown(){ O_DEBUG("Unregistering all networks"); while(!networks.isEmpty()){ networks.takeFirst()->deleteLater(); @@ -209,6 +210,9 @@ bool WifiAPI::enable(){ if(!hasPermission("wifi")){ return false; } + if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::DeviceType::RM2){ + system("/sbin/modprobe brcmfmac"); + } O_INFO("Turning wifi on"); if(m_state == Off){ setState(Disconnected); @@ -245,6 +249,9 @@ void WifiAPI::disable(){ O_INFO("Failed to disable " + wlan->iface()); } } + if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::DeviceType::RM2){ + system("/sbin/rmmod brcmfmac"); + } if(system("/usr/sbin/rfkill block wifi")){ O_INFO("Failed to disable wifi devices"); } diff --git a/applications/system-service/wifiapi.h b/applications/system-service/wifiapi.h index 4a10e7e54..8ac8c7f51 100644 --- a/applications/system-service/wifiapi.h +++ b/applications/system-service/wifiapi.h @@ -32,7 +32,7 @@ class WifiAPI : public APIBase { public: static WifiAPI* singleton(WifiAPI* self = nullptr); WifiAPI(QObject* parent); - ~WifiAPI(); + void shutdown(); void setEnabled(bool enabled); bool getEnabled(); QList getWlans(); diff --git a/applications/system-service/wlan.cpp b/applications/system-service/wlan.cpp index b92d37d45..b5df2b6be 100644 --- a/applications/system-service/wlan.cpp +++ b/applications/system-service/wlan.cpp @@ -132,6 +132,7 @@ signed int Wlan::rssi(){ }catch(const std::runtime_error&){ return 0; } + return -100; } diff --git a/applications/task-switcher/controller.h b/applications/task-switcher/controller.h index 5a1f3d1ae..2da6ea0d8 100644 --- a/applications/task-switcher/controller.h +++ b/applications/task-switcher/controller.h @@ -10,7 +10,6 @@ #include #include -#include "screenprovider.h" #include "appitem.h" using namespace codes::eeems::oxide1; @@ -28,10 +27,8 @@ class Controller : public QObject { Q_OBJECT public: - Controller(QObject* parent, ScreenProvider* screenProvider) + Controller(QObject* parent) : QObject(parent),applications() { - blankImage = new QImage(qApp->primaryScreen()->geometry().size(), QImage::Format_Mono); - this->screenProvider = screenProvider; auto bus = QDBusConnection::systemBus(); qDebug() << "Waiting for tarnish to start up..."; while(!bus.interface()->registeredServiceNames().value().contains(OXIDE_SERVICE)){ @@ -47,16 +44,8 @@ class Controller : public QObject { connect(signalHandler, &SignalHandler::sigUsr1, this, &Controller::sigUsr1); connect(signalHandler, &SignalHandler::sigUsr2, this, &Controller::sigUsr2); - qDebug() << "Requesting screen API..."; - QDBusObjectPath path = api->requestAPI("screen"); - if(path.path() == "/"){ - qDebug() << "Unable to get screen API"; - throw ""; - } - screenApi = new Screen(OXIDE_SERVICE, path.path(), bus, this); - qDebug() << "Requesting apps API..."; - path = api->requestAPI("apps"); + QDBusObjectPath path = api->requestAPI("apps"); if(path.path() == "/"){ qDebug() << "Unable to get apps API"; throw ""; @@ -66,8 +55,6 @@ class Controller : public QObject { connect(appsApi, &Apps::applicationRegistered, this, &Controller::registerApplication); connect(appsApi, &Apps::applicationLaunched, this, &Controller::reload); connect(appsApi, &Apps::applicationExited, this, &Controller::reload); - - updateImage(); } ~Controller(){} @@ -190,43 +177,6 @@ class Controller : public QObject { } stateControllerUI->setProperty("state", state); } - void updateImage(){ - qDebug() << "Updating background..."; - Oxide::Sentry::sentry_transaction("Update Task Switcher Background Image", "updateImage", [this](Oxide::Sentry::Transaction* t){ - QImage* img = nullptr; - Oxide::Sentry::sentry_span(t, "previousApplications", "Get image from previous application", [this, &img](Oxide::Sentry::Span* s){ - auto previousApplications = appsApi->previousApplications(); - while(img == nullptr && !previousApplications.isEmpty()){ - auto name = previousApplications.takeLast(); - Oxide::Sentry::sentry_span(s, name.toStdString(), "Load image from application", [this, &img, previousApplications, name]{ - auto path = ((QDBusObjectPath)appsApi->getApplicationPath(name)).path(); - if(path == "/"){ - O_WARNING("Unable to get save screen for" << name); - return; - } - auto bus = QDBusConnection::systemBus(); - Application app(OXIDE_SERVICE, path, bus, this); - auto data = app.screenCapture(); - auto image = QImage::fromData(data, "JPG"); - if(image.isNull()){ - O_WARNING("Image for " << name << " is corrupt, trying next application"); - return; - } - img = new QImage(image); - qDebug() << "Using save screen from " << name; - }); - } - }); - Oxide::Sentry::sentry_span(t, "update", "Update image", [this, img]{ - if(img != nullptr){ - screenProvider->updateImage(img); - return; - } - qWarning() << "No previous application. Using blank screen"; - screenProvider->updateImage(blankImage); - }); - }); - } void setRoot(QObject* root){ this->root = root; } Apps* getAppsApi() { return appsApi; } @@ -239,7 +189,6 @@ private slots: ::kill(tarnishPid(), SIGUSR1); qDebug() << "Sent to the foreground..."; setState("loading"); - updateImage(); } void sigUsr2(){ qDebug() << "Sent to the background..."; @@ -268,13 +217,10 @@ private slots: private: General* api; - Screen* screenApi; Apps* appsApi; QObject* root = nullptr; QObject* stateControllerUI = nullptr; - ScreenProvider* screenProvider; QList applications; - QImage* blankImage; int tarnishPid() { return api->tarnishPid(); } QObject* getStateControllerUI(){ diff --git a/applications/task-switcher/corrupt_stable.h b/applications/task-switcher/corrupt_stable.h index e36b85be2..d49ced4b7 100644 --- a/applications/task-switcher/corrupt_stable.h +++ b/applications/task-switcher/corrupt_stable.h @@ -18,7 +18,4 @@ #include #include #include - -#include "controller.h" -#include "screenprovider.h" #endif diff --git a/applications/task-switcher/main.cpp b/applications/task-switcher/main.cpp index d749bd2df..882c25530 100644 --- a/applications/task-switcher/main.cpp +++ b/applications/task-switcher/main.cpp @@ -6,13 +6,9 @@ #include #include #include -#include #include #include "controller.h" - -#include "screenprovider.h" - using namespace std; using namespace Oxide; using namespace Oxide::QML; @@ -20,7 +16,7 @@ using namespace Oxide::Sentry; void sigHandler(int signal){ ::signal(signal, SIG_DFL); - qApp->quit(); + qApp->exit(signal); } int main(int argc, char *argv[]){ @@ -31,22 +27,18 @@ int main(int argc, char *argv[]){ app.setOrganizationDomain(OXIDE_SERVICE); app.setApplicationName("corrupt"); app.setApplicationVersion(APP_VERSION); - auto screenProvider = new ScreenProvider(&app); - Controller controller(&app, screenProvider); + Controller controller(&app); QQmlApplicationEngine engine; registerQML(&engine); QQmlContext* context = engine.rootContext(); context->setContextProperty("apps", QVariant::fromValue(controller.getApps())); context->setContextProperty("controller", &controller); - engine.rootContext()->setContextProperty("screenProvider", screenProvider); - engine.addImageProvider("screen", screenProvider); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()){ qDebug() << "Nothing to display"; return -1; } auto root = engine.rootObjects().first(); - root->installEventFilter(new EventFilter(&app)); controller.setRoot(root); signal(SIGINT, sigHandler); diff --git a/applications/task-switcher/main.qml b/applications/task-switcher/main.qml index c76aaf436..094f3b92e 100644 --- a/applications/task-switcher/main.qml +++ b/applications/task-switcher/main.qml @@ -8,17 +8,14 @@ import "./widgets" OxideWindow { id: window objectName: "window" - visible: stateController.state === "loaded" - backgroundColor: "transparent" + flags: Qt.FramelessWindowHint title: qsTr("Corrupt") - property int itemPadding: 10 - Connections{ - target: screenProvider - onImageChanged: background.reload() - } + color: "transparent" + Component.onCompleted: controller.startup() + visible: true Connections { target: controller - onReload: { + function onReload(){ appsView.model = controller.getApps(); if(appsView.currentIndex > appsView.count){ appsView.currentIndex = appsView.count @@ -26,49 +23,21 @@ OxideWindow { } } Shortcut{ - sequence: [StandardKey.Cancel, Qt.Key_Backspace] + sequences: [StandardKey.Cancel, "Backspace"] context: Qt.ApplicationShortcut onActivated: controller.previousApplication() } - - Component.onCompleted: { - controller.startup(); - } - background: Rectangle { - Image { - id: background - objectName: "background" - anchors.fill: parent - cache: false - source: "image://screen/image" - visible: stateController.state === "loaded" - property bool counter: false - function reload(){ - controller.breadcrumb("background", "reload"); - console.log("Reloading background"); - counter = !counter - source = "image://screen/image?id=" + counter - } - } - } - initialItem: MouseArea { + initialItem: MouseArea{ anchors.fill: parent - enabled: stateController.state === "loaded" - onClicked: { - controller.breadcrumb("background", "click", "ui"); - controller.previousApplication(); - } + onClicked: controller.previousApplication() } - page.header: Item{} page.footer: Rectangle { id: footer color: "white" border.color: "black" border.width: 1 + width: parent.width height: 150 - anchors.left: parent.left - anchors.right: parent.right - visible: stateController.state === "loaded" clip: true RowLayout { anchors.fill: parent @@ -131,9 +100,6 @@ OxideWindow { onLongPress: { controller.breadcrumb("appsView.app", "longPress", "ui"); model.modelData.stop(); - if(index === 0){ - background.source = ""; - } } } function pageSize(){ @@ -194,9 +160,19 @@ OxideWindow { ScriptAction { script: { controller.breadcrumb("navigation", "loading", "navigation"); console.log("Loading display"); + window.raise(); controller.startup(); } } } + }, + Transition { + from: "*"; to: "hidden" + SequentialAnimation { + ScriptAction { script: { + controller.breadcrumb("navigation", "hidden", "navigation"); + window.lower(); + } } + } } ] } diff --git a/applications/task-switcher/screenprovider.h b/applications/task-switcher/screenprovider.h deleted file mode 100644 index 22f32eb30..000000000 --- a/applications/task-switcher/screenprovider.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef SCREENPROVIDER_H -#define SCREENPROVIDER_H - -#include - -class ScreenProvider: public QObject, public QQuickImageProvider -{ - Q_OBJECT -public: - ScreenProvider(QObject* parent) : QObject(parent), QQuickImageProvider(QQuickImageProvider::Image), image() {}; - -public slots: - void updateImage(QImage* image){ - this->image = image->copy(image->rect()); - emit imageChanged(); - } - QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override { - Q_UNUSED(id); - if(size){ - *size = image.size(); - } - if(requestedSize.width() > 0 && requestedSize.height() > 0) { - image = image.scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio); - } - return image; - } - -signals: - void imageChanged(); - -private: - QImage image; -}; - -#endif // SCREENPROVIDER_H diff --git a/applications/task-switcher/task-switcher.pro b/applications/task-switcher/task-switcher.pro index 39f225c57..f006ccd09 100644 --- a/applications/task-switcher/task-switcher.pro +++ b/applications/task-switcher/task-switcher.pro @@ -23,8 +23,7 @@ INSTALLS += applications INCLUDEPATH += ../../shared HEADERS += \ appitem.h \ - controller.h \ - screenprovider.h + controller.h RESOURCES += \ qml.qrc diff --git a/applications/screenshot-tool/.gitignore b/applications/xclip/.gitignore similarity index 100% rename from applications/screenshot-tool/.gitignore rename to applications/xclip/.gitignore diff --git a/applications/xclip/main.cpp b/applications/xclip/main.cpp new file mode 100644 index 000000000..971a90ad7 --- /dev/null +++ b/applications/xclip/main.cpp @@ -0,0 +1,149 @@ +#include + +#include +#include +#include +#include + +using namespace Oxide::Sentry; + +QTextStream& qStdOut(){ + static QTextStream ts(stdout); + return ts; +} + +void readClipboard(Blight::clipboard_t& clipboard, bool rmlastnlOption){ + QByteArray data; + data.setRawData(reinterpret_cast(clipboard.data.get()), clipboard.size); + if(rmlastnlOption && data.right(1) == "\n"){ + data.chop(1); + } + qStdOut() << data; +} +int setClipboard(const QString& selection, QByteArray& data, bool verbose, bool rmlastnlOption){ + if(verbose){ + qDebug() << "Setting selection:" << selection; + } + if(rmlastnlOption && data.right(1) == "\n"){ + data.chop(1); + } + auto name = selection.toStdString(); + if(name != "secondary" && name != "clipboard"){ + name = "selection"; + } + Blight::clipboard_t clipboard(name, (Blight::data_t)data.data(), data.size()); + if(Blight::setClipboard(clipboard)){ + return EXIT_SUCCESS; + } + qDebug() << "Failed to get clipboard:" << std::strerror(errno); + return EXIT_FAILURE; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("xclip", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("xclip"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser.setApplicationDescription("Command line interface for the clipboard"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption inOption({"i", "in"}, "Read text into the clipboard from standard input or files (default)"); + parser.addOption(inOption); + QCommandLineOption outOption({"o", "out"}, "Prints the selection to standard out (generally for piping to a file or program)"); + parser.addOption(outOption); + QCommandLineOption filterOption({"f", "filter"}, "NOT IMPLEMENTED"); + parser.addOption(filterOption); + QCommandLineOption rmlastnlOption({"r", "rmlastnl"}, "When the last character is a newline character, remove it."); + parser.addOption(rmlastnlOption); + QCommandLineOption loopsOption({"l", "loops"}, "NOT IMPLEMENTED", "loops"); + parser.addOption(loopsOption); + QCommandLineOption targetOption({"t", "target"}, "NOT IMPLEMENTED", "target"); + parser.addOption(targetOption); + QCommandLineOption displayOption({"d", "display"}, "NOT IMPLEMENTED", "display"); + parser.addOption(displayOption); + QCommandLineOption selectionOption( + {"sel", "selection"}, + "Specify which selection to use. Options are \"primary\" (default), \"secondary\", and \"clipboard\".", + "selection", + "primary" + ); + parser.addOption(selectionOption); + QCommandLineOption silentOption("silent", "forks into the background to wait for requests, no informational output, errors only (default)"); + parser.addOption(silentOption); + QCommandLineOption verboseOption("verbose", "Provide a running commentary of what xclip is doing"); + parser.addOption(verboseOption); + QCommandLineOption noutf8Option("noutf8", "NOT IMPLEMENTED"); + parser.addOption(noutf8Option); + parser.addPositionalArgument("files", "File(s) to read from", "[FILE]..."); + parser.process(app); + auto verbose = parser.isSet(verboseOption); + if(verbose){ + qputenv("DEBUG", "1"); + } +#ifdef EPAPER + auto connected = Blight::connect(true); +#else + auto connected = Blight::connect(false); +#endif + if(!connected){ + qDebug() << "Failed to connect to DBus"; + return EXIT_FAILURE; + } + QStringList args = parser.positionalArguments(); + if(parser.isSet(outOption)){ + auto selection = parser.value(selectionOption); + if(verbose){ + qDebug() << "Getting selection:" << selection; + } + if(selection == "secondary"){ + auto maybe = Blight::secondary(); + if(!maybe.has_value()){ + qDebug() << "Failed to get clipboard:" << std::strerror(errno); + return EXIT_FAILURE; + } + readClipboard(maybe.value(), parser.isSet(rmlastnlOption)); + }else if(selection == "clipboard"){ + auto maybe = Blight::clipboard(); + if(!maybe.has_value()){ + qDebug() << "Failed to get clipboard:" << std::strerror(errno); + return EXIT_FAILURE; + } + readClipboard(maybe.value(), parser.isSet(rmlastnlOption)); + }else{ + auto maybe = Blight::selection(); + if(!maybe.has_value()){ + qDebug() << "Failed to get clipboard:" << std::strerror(errno); + return EXIT_FAILURE; + } + readClipboard(maybe.value(), parser.isSet(rmlastnlOption)); + } + return EXIT_SUCCESS; + } + if(args.empty()){ + auto data = QTextStream(stdin).readAll().toUtf8(); + auto selection = parser.value(selectionOption); + return setClipboard(selection, data, verbose, parser.isSet(rmlastnlOption)); + } + for(auto path : args){ + if(!QFile::exists(path)){ + qDebug().nospace() << "xclip: " << path.toStdString().c_str() << ": No such file or directory"; + return EXIT_FAILURE; + } + } + QByteArray data; + for(auto path : args){ + QFile file(path); + if(!file.open(QIODevice::ReadOnly)){ + qDebug().nospace() << "xclip: " << path.toStdString().c_str() << ": " << file.errorString(); + return EXIT_FAILURE; + } + data += file.readAll(); + } + auto selection = parser.value(selectionOption); + return setClipboard(selection, data, verbose, parser.isSet(rmlastnlOption)); +} diff --git a/applications/xclip/xclip.pro b/applications/xclip/xclip.pro new file mode 100644 index 000000000..77ac3431c --- /dev/null +++ b/applications/xclip/xclip.pro @@ -0,0 +1,17 @@ +QT -= gui + +CONFIG += c++11 console +CONFIG -= app_bundle + +SOURCES += \ + main.cpp + +HEADERS += + +TARGET = xclip +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/libblight.pri) diff --git a/assets/etc/dbus-1/system.d/codes.eeems.blight.conf b/assets/etc/dbus-1/system.d/codes.eeems.blight.conf new file mode 100644 index 000000000..38a5d7f53 --- /dev/null +++ b/assets/etc/dbus-1/system.d/codes.eeems.blight.conf @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/assets/etc/systemd/system/blight.service b/assets/etc/systemd/system/blight.service new file mode 100644 index 000000000..80f84b570 --- /dev/null +++ b/assets/etc/systemd/system/blight.service @@ -0,0 +1,21 @@ +[Unit] +Description=Oxide display server +After=home.mount +After=rm2fb.service +Wants=rm2fb.service +StartLimitInterval=30 +StartLimitBurst=5 +OnFailure=remarkable-fail.service +Before=remarkable-reboot.service remarkable-shutdown.service +Conflicts=xochitl.service +Conflicts=sync.service + +[Service] +Type=dbus +BusName=codes.eeems.blight1 +ExecStart=/opt/bin/rm2fb-client /opt/bin/blight --break-lock +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/assets/etc/systemd/system/tarnish.service b/assets/etc/systemd/system/tarnish.service index 2889afeb2..c2a5dcfd1 100644 --- a/assets/etc/systemd/system/tarnish.service +++ b/assets/etc/systemd/system/tarnish.service @@ -1,12 +1,14 @@ [Unit] -Description=oxide system service +Description=Oxide system service After=home.mount +After=blight.service StartLimitInterval=30 StartLimitBurst=5 -OnFailure=remarkable-fail.service +#OnFailure=remarkable-fail.service Before=remarkable-reboot.service remarkable-shutdown.service Conflicts=xochitl.service Conflicts=sync.service +Wants=blight.service [Service] Type=dbus @@ -14,9 +16,11 @@ BusName=codes.eeems.oxide1 ExecStart=/opt/bin/tarnish --break-lock Restart=on-failure RestartSec=5 +WatchdogSec=1min Environment="HOME=/home/root" Environment="PATH=/opt/bin:/opt/sbin:/opt/usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" [Install] Alias=launcher.service +Alias=oxide.service WantedBy=multi-user.target diff --git a/assets/opt/etc/keyd/oxide.conf b/assets/opt/etc/keyd/oxide.conf index 15726d8c1..5cd996e72 100644 --- a/assets/opt/etc/keyd/oxide.conf +++ b/assets/opt/etc/keyd/oxide.conf @@ -21,8 +21,6 @@ right = end left = home up = pageup down = pagedown -backspace = macro(end+backspace) -l = macro(end+l) [meta+alt] 4 = macro(leftalt+f4) @@ -31,9 +29,8 @@ l = macro(end+l) \ = | , = { . = } -s = macro(leftshift+end+s) [ctrl:C] [ctrl+shift] -1 = computer +1 = macro(ctrl+shift+esc) diff --git a/assets/opt/share/launcherctl/oxide b/assets/opt/share/launcherctl/oxide old mode 100644 new mode 100755 index 97ff4a73e..328b64d33 --- a/assets/opt/share/launcherctl/oxide +++ b/assets/opt/share/launcherctl/oxide @@ -27,13 +27,23 @@ case "$1" in systemctl disable tarnish.service ;; apps) - rot apps get applications | jq -r 'keys | .[]' + if ! "$0" is-active; then + \ls /opt/usr/share/applications/*.oxide | xargs basename -s .oxide + exit + fi + rot apps get applications 2>/dev/null | jq -r 'keys | .[]' ;; running) - rot apps get runningApplications | jq -r 'keys | .[]' + if ! "$0" is-active; then + exit + fi + rot apps get runningApplications 2>/dev/null | jq -r 'keys | .[]' ;; paused) - rot apps get pausedApplications | jq -r 'keys | .[]' + if ! "$0" is-active; then + exit + fi + rot apps get pausedApplications 2>/dev/null | jq -r 'keys | .[]' ;; launch | resume) rot apps get applications \ diff --git a/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide index 050aad4d8..601540da4 100644 --- a/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.anxiety.oxide @@ -1,10 +1,9 @@ { - "displayName": "Screenshots", - "description": "View and manage screenshots", - "bin": "/opt/bin/anxiety", - "icon": "oxide:image-48", - "splash": "oxide:splash:anxiety-702", - "flags": [], - "type": "foreground", - "permissions": ["screen"] + "displayName": "Screenshots", + "description": "View and manage screenshots", + "bin": "/opt/bin/anxiety", + "icon": "oxide:image-48", + "type": "foreground", + "permissions": ["screen"], + "flags": ["nopreload"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.corrupt.oxide b/assets/opt/usr/share/applications/codes.eeems.corrupt.oxide index 53d48d2bd..bc9f8d7f4 100644 --- a/assets/opt/usr/share/applications/codes.eeems.corrupt.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.corrupt.oxide @@ -1,8 +1,8 @@ { - "displayName": "Task Switcher", - "description": "Handles switching applications", - "bin": "/opt/bin/corrupt", - "flags": ["hidden", "nosavescreen", "nosplash"], - "type": "backgroundable", - "permissions": ["apps", "screen"] + "displayName": "Task Switcher", + "description": "Handles switching applications", + "bin": "/opt/bin/corrupt", + "flags": ["hidden", "nosavescreen", "nosplash", "nopreload"], + "type": "backgroundable", + "permissions": ["apps"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.decay.oxide b/assets/opt/usr/share/applications/codes.eeems.decay.oxide index 38ccdffb1..c1eaf065c 100644 --- a/assets/opt/usr/share/applications/codes.eeems.decay.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.decay.oxide @@ -1,9 +1,8 @@ { - "displayName": "Startup application", - "description": "Handles the password screen on resume", - "bin": "/opt/bin/decay", - "flags": ["hidden"], - "type": "foreground", - "splash": "oxide:splash:oxide-702", - "permissions": ["apps", "power", "system", "wifi"] + "displayName": "Startup application", + "description": "Handles the password screen on resume", + "bin": "/opt/bin/decay", + "flags": ["hidden", "nopreload"], + "type": "foreground", + "permissions": ["apps", "power", "system", "wifi"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.erode.oxide b/assets/opt/usr/share/applications/codes.eeems.erode.oxide index 25b459b2a..6ef16e5dd 100644 --- a/assets/opt/usr/share/applications/codes.eeems.erode.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.erode.oxide @@ -1,7 +1,7 @@ { - "displayName": "Process Manager", - "description": "List and kill running processes", - "icon": "oxide:erode-48", - "splash": "oxide:splash:erode-702", - "bin": "/opt/bin/erode" + "displayName": "Process Manager", + "description": "List and kill running processes", + "icon": "oxide:erode-48", + "bin": "/opt/bin/erode", + "flags": ["nopreload"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.fret.oxide b/assets/opt/usr/share/applications/codes.eeems.fret.oxide index 0d8710f2c..f3dbc1e39 100644 --- a/assets/opt/usr/share/applications/codes.eeems.fret.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.fret.oxide @@ -1,9 +1,8 @@ { - "displayName": "Screenshot daemon", - "description": "Takes screenshots and displays notifications", - "bin": "/opt/bin/fret", - "flags": ["autoStart", "hidden"], - "type": "background", - "splash": "oxide:splash:oxide-702", - "permissions": ["notification", "screen", "system"] + "displayName": "Screenshot daemon", + "description": "Takes screenshots and displays notifications", + "bin": "/opt/bin/fret", + "flags": ["autoStart", "hidden", "nopreload"], + "type": "background", + "permissions": ["notification", "screen", "system"] } diff --git a/assets/opt/usr/share/applications/codes.eeems.oxide.oxide b/assets/opt/usr/share/applications/codes.eeems.oxide.oxide index 46b761778..357178aa6 100644 --- a/assets/opt/usr/share/applications/codes.eeems.oxide.oxide +++ b/assets/opt/usr/share/applications/codes.eeems.oxide.oxide @@ -1,9 +1,8 @@ { - "displayName": "Launcher", - "description": "Application launcher", - "bin": "/opt/bin/oxide", - "type": "foreground", - "flags": ["hidden"], - "splash": "oxide:splash:oxide-702", - "permissions": ["apps", "wifi", "power", "system", "notification"] + "displayName": "Launcher", + "description": "Application launcher", + "bin": "/opt/bin/oxide", + "type": "foreground", + "flags": ["hidden", "nopreload"], + "permissions": ["apps", "wifi", "power", "system", "notification"] } diff --git a/assets/opt/usr/share/applications/test_app.oxide b/assets/opt/usr/share/applications/test_app.oxide new file mode 100644 index 000000000..ddfddb29a --- /dev/null +++ b/assets/opt/usr/share/applications/test_app.oxide @@ -0,0 +1,8 @@ +{ + "displayName": "Test application", + "description": "Oxide Test Application", + "bin": "/opt/bin/test_app", + "flags": ["hidden", "nopreload"], + "type": "foreground", + "permissions": ["apps", "power", "system", "wifi"] +} diff --git a/assets/opt/usr/share/applications/xochitl.oxide b/assets/opt/usr/share/applications/xochitl.oxide index d6da3256e..5336c2eb6 100644 --- a/assets/opt/usr/share/applications/xochitl.oxide +++ b/assets/opt/usr/share/applications/xochitl.oxide @@ -3,5 +3,5 @@ "description": "Read documents and take notes", "bin": "/opt/bin/xochitl", "icon": "oxide:xochitl-48", - "flags": ["nosplash"], + "flags": ["nosplash", "exclusive"] } diff --git a/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png b/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png deleted file mode 100644 index fb2ba07ef..000000000 Binary files a/assets/opt/usr/share/icons/oxide/702x702/splash/anxiety.png and /dev/null differ diff --git a/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png b/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png deleted file mode 100644 index 966906e2c..000000000 Binary files a/assets/opt/usr/share/icons/oxide/702x702/splash/erode.png and /dev/null differ diff --git a/interfaces/application.xml b/interfaces/application.xml index 1192d0145..3788ac27a 100644 --- a/interfaces/application.xml +++ b/interfaces/application.xml @@ -22,11 +22,8 @@ - - - diff --git a/interfaces/blight.xml b/interfaces/blight.xml new file mode 100644 index 000000000..08b3f55ce --- /dev/null +++ b/interfaces/blight.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interfaces/dbusservice.xml b/interfaces/dbusservice.xml index e50ba6570..8beeaf2fc 100644 --- a/interfaces/dbusservice.xml +++ b/interfaces/dbusservice.xml @@ -22,7 +22,5 @@ - - diff --git a/interfaces/screenapi.xml b/interfaces/screenapi.xml index 0ed088ce4..2ce8b3322 100644 --- a/interfaces/screenapi.xml +++ b/interfaces/screenapi.xml @@ -16,15 +16,6 @@ - - - - - - - - - diff --git a/interfaces/systemapi.xml b/interfaces/systemapi.xml index d01f4ef9a..1e1fab0eb 100644 --- a/interfaces/systemapi.xml +++ b/interfaces/systemapi.xml @@ -38,6 +38,10 @@ + + + + diff --git a/package b/package index c04021e9e..3043d0198 100644 --- a/package +++ b/package @@ -2,7 +2,18 @@ # Copyright (c) 2020 The Toltec Contributors # SPDX-License-Identifier: MIT -pkgnames=(oxide oxide-extra oxide-utils inject_evdev liboxide liboxide-dev libsentry) +pkgnames=( + fbinfo + inject_evdev + liboxide + liboxide-dev + libsentry + oxide + oxide-display + oxide-extra + oxide-utils + oxide-tests +) _oxidever=$(grep 'VERSION =' qmake/common.pri | awk '{print $3}') pkgver="$_oxidever~VERSION~" _sentryver=0.7.9 @@ -26,7 +37,7 @@ build() { oxide() { pkgdesc="Launcher application" section="launchers" - installdepends=("oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentryver" reboot-guard jq display launcherctl) + installdepends=("oxide-utils=$pkgver" "liboxide=$pkgver" "libsentry=$_sentryver" "oxide-display=$pkgver" reboot-guard sysfs_preload jq launcherctl) replaces=(erode tarnish decay corrupt) conflicts=(erode tarnish decay corrupt) @@ -38,7 +49,6 @@ oxide() { # Task manager install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/erode install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/48x48/apps "$srcdir"/release/opt/usr/share/icons/oxide/48x48/apps/erode.png - install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/702x702/splash "$srcdir"/release/opt/usr/share/icons/oxide/702x702/splash/erode.png install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.erode.oxide # Launcher install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/oxide @@ -51,6 +61,8 @@ oxide() { # Task switcher install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/corrupt install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.corrupt.oxide + # Keyd configuration + install -D -m 644 -t "$pkgdir"/opt/etc/keyd "$srcdir"/release/opt/etc/keyd/oxide.conf # launcherctl registration install -D -T -m 755 "$srcdir"/release/opt/share/launcherctl/oxide "$pkgdir"/opt/share/launcherctl/oxide } @@ -78,6 +90,23 @@ oxide() { } } +oxide-display() { + pkgdesc="Oxide display server" + section="devel" + installdepends=("liboxide=$pkgver" "libsentry=$_sentryver") + + package() { + # Display server + install -D -m 644 -t "$pkgdir"/etc/dbus-1/system.d "$srcdir"/release/etc/dbus-1/system.d/codes.eeems.blight.conf + install -D -m 644 -t "$pkgdir"/lib/systemd/system "$srcdir"/release/etc/systemd/system/blight.service + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/blight + # Client + install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/libblight_client.so* + # QPA + install -D -m 755 -t "$pkgdir"/opt/usr/lib/plugins/platforms "$srcdir"/release/opt/usr/lib/plugins/platforms/liboxide.so* + } +} + oxide-extra() { pkgdesc="Extra applications for oxide" section="utils" @@ -86,14 +115,10 @@ oxide-extra() { conflicts=(fret anxiety) package() { - # Screenshot daemon - install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/fret - install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.fret.oxide # Screenshot viewer install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/anxiety install -D -m 644 -t "$pkgdir"/opt/usr/share/applications "$srcdir"/release/opt/usr/share/applications/codes.eeems.anxiety.oxide install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/48x48/apps "$srcdir"/release/opt/usr/share/icons/oxide/48x48/apps/image.png - install -D -m 644 -t "$pkgdir"/opt/usr/share/icons/oxide/702x702/splash "$srcdir"/release/opt/usr/share/icons/oxide/702x702/splash/anxiety.png } } @@ -105,15 +130,16 @@ oxide-utils() { conflicts=(rot notify-send update-desktop-database desktop-file-validate) package() { - install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/rot + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/desktop-file-validate + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/gio install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/notify-send + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/rot install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/update-desktop-database - install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/desktop-file-validate - install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-desktop-menu + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xclip install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-desktop-icon + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-desktop-menu install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-open install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/xdg-settings - install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/gio } } @@ -127,6 +153,15 @@ inject_evdev() { } } +fbinfo() { + pkgdesc="Print out framebuffer info" + section="utils" + + package() { + install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/release/opt/bin/fbinfo + } +} + liboxide() { pkgdesc="Shared library for oxide applications" section="devel" @@ -134,6 +169,7 @@ liboxide() { package() { install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/liboxide.so* install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/libqsgepaper.so* + install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/libblight.so* } } @@ -161,3 +197,14 @@ libsentry() { install -D -m 755 -t "$pkgdir"/opt/lib "$srcdir"/release/opt/lib/libsentry.so } } + +oxide-tests() { + pkgdesc="Automated tests for oxide" + section="devel" + installdepends=("oxide-display=$pkgver") + + package() { + install -D -m 755 -t "$pkgdir"/opt/share/tests "$srcdir"/release/opt/share/tests/libblight + install -D -m 755 -t "$pkgdir"/opt/share/tests "$srcdir"/release/opt/share/tests/liboxide + } +} diff --git a/qmake/common.pri b/qmake/common.pri index 4ff176348..99785ac2c 100644 --- a/qmake/common.pri +++ b/qmake/common.pri @@ -1,15 +1,15 @@ -VERSION = 2.8.4 +VERSION = 3.0 !contains(DEFINES, QT_DEPRECATED_WARNINGS){ DEFINES += QT_DEPRECATED_WARNINGS } -!contains(DEFINES, QT_DISABLE_DEPRECATED_BEFORE){ - isEmpty(QT_DISABLE_DEPRECATED_BEFORE){ - DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 - }else{ - DEFINES += QT_DISABLE_DEPRECATED_BEFORE=$${QT_DISABLE_DEPRECATED_BEFORE} - } +isEmpty(QT_DISABLE_DEPRECATED_BEFORE){ + QT_DISABLE_DEPRECATED_BEFORE = 0x060000 +}else{ + message("Using override deprecation value") } +DEFINES ~= s/QT_DISABLE_DEPRECATED_BEFORE=.+/ +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=$${QT_DISABLE_DEPRECATED_BEFORE} CONFIG(debug, debug|release){ LIBS += -lunwind contains(DEFINES, SANITIZER){ @@ -25,6 +25,10 @@ CONFIG(debug, debug|release){ } } +linux-oe-g++{ + DEFINES += EPAPER +} + QMAKE_RPATHDIR += /lib /usr/lib /opt/lib /opt/usr/lib DEFINES += APP_VERSION=\\\"$$VERSION\\\" diff --git a/qmake/libblight.pri b/qmake/libblight.pri new file mode 100644 index 000000000..445c717a3 --- /dev/null +++ b/qmake/libblight.pri @@ -0,0 +1,10 @@ +contains(DEFINES, LIBBLIGHT_PRIVATE){ + LIBS_PRIVATE += -L$$OUT_PWD/../../shared/libblight -lblight +}else{ + LIBS += -L$$OUT_PWD/../../shared/libblight -lblight +} +INCLUDEPATH += $$OUT_PWD/../../shared/libblight/include + +linux-oe-g++{ + DEFINES += EPAPER +} diff --git a/qmake/liboxide.pri b/qmake/liboxide.pri index 439ef57eb..da092bd2d 100644 --- a/qmake/liboxide.pri +++ b/qmake/liboxide.pri @@ -10,4 +10,4 @@ QT += gui QML_IMPORT_PATH += qrc:/codes.eeems.oxide include(sentry.pri) -include(epaper.pri) +include(libblight.pri) diff --git a/qmake/sentry.pri b/qmake/sentry.pri index 20dd8cf17..20d162217 100644 --- a/qmake/sentry.pri +++ b/qmake/sentry.pri @@ -1,8 +1,10 @@ -contains(DEFINES, SENTRY){ - LIBSENTRY_ROOT = $$OUT_PWD/../../shared/sentry - LIBSENTRY_LIB = $$LIBSENTRY_ROOT/lib - LIBSENTRY_INC = $$LIBSENTRY_ROOT/include - LIBS_PRIVATE += -L$$LIBSENTRY_LIB -lsentry -ldl -lcurl -lbreakpad_client - INCLUDEPATH += $$LIBSENTRY_INC - DEPENDPATH += $$LIBSENTRY_LIB +linux-oe-g++{ + contains(DEFINES, SENTRY){ + LIBSENTRY_ROOT = $$OUT_PWD/../../shared/sentry + LIBSENTRY_LIB = $$LIBSENTRY_ROOT/lib + LIBSENTRY_INC = $$LIBSENTRY_ROOT/include + LIBS_PRIVATE += -L$$LIBSENTRY_LIB -lsentry -ldl -lcurl -lbreakpad_client + INCLUDEPATH += $$LIBSENTRY_INC + DEPENDPATH += $$LIBSENTRY_LIB + } } diff --git a/shared/doxygen-awesome-css b/shared/doxygen-awesome-css new file mode 160000 index 000000000..df88fe4fd --- /dev/null +++ b/shared/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit df88fe4fdd97714fadfd3ef17de0b4401f804052 diff --git a/shared/liboxide/header.html b/shared/doxygen-header.html similarity index 100% rename from shared/liboxide/header.html rename to shared/doxygen-header.html diff --git a/shared/epaper/epaper.pro b/shared/epaper/epaper.pro index aadad6f46..2a09bee00 100644 --- a/shared/epaper/epaper.pro +++ b/shared/epaper/epaper.pro @@ -3,12 +3,14 @@ } TEMPLATE = lib QT += core +QT += quick include(../../qmake/common.pri) VERSION = 1.0 -DISTFILES = epframebuffer.h +DISTFILES = \ + epframebuffer.h TARGET = qsgepaper target.path = /opt/lib @@ -25,7 +27,14 @@ LIBS += -Wl,--whole-archive $$libqsgepaper.target -Wl,--no-whole-archive epframebuffer_h.target = epframebuffer.h epframebuffer_h.commands = cp $$PWD/epframebuffer.h $$OUT_PWD - QMAKE_EXTRA_TARGETS += epframebuffer_h -PRE_TARGETDEPS += $$epframebuffer_h.target -QMAKE_CLEAN += $$epframebuffer_h.target + +linux-oe-g++{ + epframebuffer_h_install.files = epframebuffer.h + epframebuffer_h_install.path = /opt/include + INSTALLS += epframebuffer_h_install +} + +PRE_TARGETDEPS += $$epframebuffer_h.target $$epimagenode_h.target +QMAKE_CLEAN += $$epframebuffer_h.target $$epimagenode_h.target + diff --git a/shared/libblight/.gitignore b/shared/libblight/.gitignore new file mode 100644 index 000000000..00ce98c8b --- /dev/null +++ b/shared/libblight/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +html/ +latex/ diff --git a/shared/libblight/Doxyfile b/shared/libblight/Doxyfile new file mode 100644 index 000000000..b551592dd --- /dev/null +++ b/shared/libblight/Doxyfile @@ -0,0 +1,25 @@ +PROJECT_NAME = libblight +GENERATE_QHP = YES +GENERATE_LATEX = NO +QCH_FILE = libblight.qch +QHP_NAMESPACE = libblight +QHP_VIRTUAL_FOLDER = doc +QHG_LOCATION = qhelpgenerator +GENERATE_TREEVIEW = YES +HTML_EXTRA_STYLESHEET = ../doxygen-awesome-css/doxygen-awesome.css \ + ../doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ + ../doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css +HTML_EXTRA_FILES = ../doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ + ../doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js \ + ../doxygen-awesome-css/doxygen-awesome-paragraph-link.js +HTML_HEADER = ../doxygen-header.html +PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS +PREDEFINED += IN_DOXYGEN +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +MACRO_EXPANSION = YES +EXAMPLE_PATH = examples +GENERATE_TAGFILE = html/libblight.tag +ALLEXTERNALS = Yes +ALIASES = "accessors=\par Accessors:^^" +ALIASES += "notifier=\par Notifier signal:^^" diff --git a/shared/libblight/Makefile b/shared/libblight/Makefile new file mode 100644 index 000000000..5baa44687 --- /dev/null +++ b/shared/libblight/Makefile @@ -0,0 +1,20 @@ +DIST=../../web/dist/libblight + +all: prod + +html: + doxygen Doxyfile + +$(DIST): html + cp -r html $(DIST) + +prod: $(DIST) + +dev: clean prod + while inotifywait -re close_write,create,delete .; do $(MAKE) clean prod; done + +clean: + rm -rf $(DIST) + rm -rf html + +.PHONY: all prod dev clean diff --git a/shared/libblight/clock.cpp b/shared/libblight/clock.cpp new file mode 100644 index 000000000..56143f4c4 --- /dev/null +++ b/shared/libblight/clock.cpp @@ -0,0 +1,16 @@ +#include "clock.h" + +Blight::ClockWatch::ClockWatch() +: t1{std::chrono::high_resolution_clock::now()} +{} + +std::chrono::duration Blight::ClockWatch::diff(){ + auto t2 = std::chrono::high_resolution_clock::now(); + if(t2 <= t1){ + return std::chrono::duration(0); + } + auto diff = t2 - t1; + return std::chrono::duration_cast>(diff); +} + +double Blight::ClockWatch::elapsed(){ return diff().count(); } diff --git a/shared/libblight/clock.h b/shared/libblight/clock.h new file mode 100644 index 000000000..959724423 --- /dev/null +++ b/shared/libblight/clock.h @@ -0,0 +1,37 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once +#include "libblight_global.h" +#include + +namespace Blight{ + /*! + * \brief The ClockWatch class + * + * A simple timer class for determening how long code takes. + */ + class LIBBLIGHT_EXPORT ClockWatch { + public: + /*! + * \brief Create a new instance and start the timer. + */ + ClockWatch(); + /*! + * \brief Get the duration since the ClockWatch was created. + * \return Duration since the ClockWatch was created. + */ + std::chrono::duration diff(); + /*! + * \brief Get the elapsed seconds since the ClockWatch was created. + * \return The elapsed seconds since the ClockWatch was created. + */ + double elapsed(); + + private: + std::chrono::high_resolution_clock::time_point t1; + }; +} +/*! @} */ diff --git a/shared/libblight/concurrentqueue.h b/shared/libblight/concurrentqueue.h new file mode 100644 index 000000000..99caefc05 --- /dev/null +++ b/shared/libblight/concurrentqueue.h @@ -0,0 +1,3747 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading +#include // used for thread exit synchronization + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { namespace details { + template struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + static thread_id_hash_t prehash(thread_id_t const& x) { return x; } + }; +} } +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(__MVS__) || defined(MOODYCAMEL_NO_THREAD_LOCAL) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) && !defined(__MVS__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // tentatively enabled for now; years ago several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template struct identity { typedef T type; }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif +} } + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #undef MOODYCAMEL_NO_TSAN + #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) + #endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + static inline bool (likely)(bool x) { return x; } + static inline bool (unlikely)(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 10000; + + // Whether to recycle dynamically-allocated blocks into an internal free list or + // not. If false, only pre-allocated blocks (controlled by the constructor + // arguments) will be recycled, and all others will be `free`d back to the heap. + // Note that blocks consumed by explicit producers are only freed on destruction + // of the queue (not following destruction of the token) regardless of this trait. + static const bool RECYCLE_ALLOCATED_BLOCKS = false; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); + // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 + // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + class ThreadExitNotifier; + + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier + }; + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + std::lock_guard guard(mutex()); + listener->next = tlsInst.tail; + listener->chain = &tlsInst; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + std::lock_guard guard(mutex()); + if (!listener->chain) { + return; // race with ~ThreadExitNotifier + } + auto& tlsInst = *listener->chain; + listener->chain = nullptr; + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + std::lock_guard guard(mutex()); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->chain = nullptr; + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + static inline std::mutex& mutex() + { + // Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called + static std::mutex mutex; + return mutex; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 32 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static constexpr bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), dynamicallyAllocated(true) + { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) + { + } + + virtual ~ProducerBase() { } + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + this->parent->add_block_to_free_list(block); + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index()) { + return false; + } + else { + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / static_cast::type>(BLOCK_SIZE)); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + if (!Traits::RECYCLE_ALLOCATED_BLOCKS && block->dynamicallyAllocated) { + destroy(block); + } + else { + freeList.add(block); + } + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } + else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + return ptr; + } + } + } + + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1u; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed) || + mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#else + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + size_t newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + auto producer = static_cast(recycle_or_create_producer(false)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); // already counted as a used slot + mainHash->entries[index].value = producer; + break; + } +#endif + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1u; + probedKey = id; + if (hash->entries[index].key.compare_exchange_strong(probedKey, details::invalid_thread_id2, std::memory_order_seq_cst, std::memory_order_relaxed)) { + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void* aligned_malloc(size_t size) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); + if (!raw) + return nullptr; + char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void* ptr) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + U* p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new (p + i) U(); + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U* create() + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif diff --git a/shared/libblight/connection.cpp b/shared/libblight/connection.cpp new file mode 100644 index 000000000..882d69df0 --- /dev/null +++ b/shared/libblight/connection.cpp @@ -0,0 +1,581 @@ +#include "connection.h" +#include "libblight.h" +#include "debug.h" +#include "socket.h" +#include "concurrentqueue.h" +#include "clock.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace Blight{ + static std::atomic ackid; + static moodycamel::ConcurrentQueue acks; + ackid_t::ackid_t( + Connection* connection, + unsigned int ackid, + unsigned int data_size, + data_t data + ) + : ackid(ackid), + done(false), + data_size(data_size), + data(data) + { +#ifdef ACK_DEBUG + _DEBUG("Ack created: %u", ackid); +#endif + } + + ackid_t::~ackid_t(){ + if(!ackid){ + return; + } +#ifdef ACK_DEBUG + _DEBUG("Ack deleted: %u", ackid); +#endif + notify_all(); + } + + bool ackid_t::wait(int timeout){ + if(!ackid){ +#ifdef ACK_DEBUG + _DEBUG("Can't wait for invalid ack"); +#endif + return true; + } +#ifdef ACK_DEBUG + _DEBUG("Waiting for ack: %u", ackid); +#endif + std::unique_lock lock(mutex); + if(timeout <= 0){ + condition.wait(lock, [this]{ return done; }); +#ifdef ACK_DEBUG + _DEBUG("Ack returned: %u", ackid); +#endif + lock.unlock(); + return true; + } + bool res = condition.wait_for( + lock, + timeout * 1ms, + [this]{ return done; } + ); +#ifdef ACK_DEBUG + _DEBUG("Ack returned: %u", ackid); +#endif + lock.unlock(); + return res; + } + + void ackid_t::notify_all(){ +#ifdef ACK_DEBUG + _DEBUG("Ack notified: %u", ackid); +#endif + std::lock_guard lock(mutex); + condition.notify_all(); + } + + Connection::Connection(int fd) + : m_fd(fcntl(fd, F_DUPFD_CLOEXEC, 3)), + m_inputFd{-1}, + stop_requested(false), + thread(run, this) + { + int flags = fcntl(fd, F_GETFD, NULL); + fcntl(fd, F_SETFD, flags | O_NONBLOCK); + } + + Connection::~Connection(){ + stop_requested = true; + if(thread.joinable()){ + thread.join(); + } + ::close(m_fd); + } + + int Connection::handle(){ return m_fd; } + + int Connection::input_handle(){ + if(m_inputFd < 0){ + m_inputFd = Blight::open_input(); + if(m_inputFd < 0){ + _CRIT("Failed to open input stream: %s", std::strerror(errno)); + } + } + return m_inputFd; + } + + void Connection::onDisconnect(std::function callback){ + disconnectCallbacks.push_back(callback); + } + + std::optional Connection::read_event(){ + // Use input_handle() since we don't know if it's open yet + auto fd = input_handle(); + if(fd < 0){ + _WARN("Failed to read event: %s", "Input stream not open"); + return {}; + } + auto maybe = Blight::recv(fd, sizeof(event_packet_t)); + if(!maybe.has_value()){ + if(errno != EAGAIN && errno != EINTR){ + _WARN("Failed to read event: %s", std::strerror(errno)); + } + return {}; + } + event_packet_t data; + memcpy(&data, maybe.value(), sizeof(event_packet_t)); + delete[] maybe.value(); + return data; + } + + message_ptr_t Connection::read(){ return message_t::from_socket(m_fd); } + + maybe_ackid_ptr_t Connection::send( + MessageType type, + data_t data, + size_t size, + unsigned int __ackid + ){ + auto _ackid = __ackid ? __ackid : ++ackid; + auto ack = ackid_ptr_t(new ackid_t(this, _ackid)); + if(type != MessageType::Ack){ + // Adding acks to queue to make sure it's there by the time a response + // comes back from the server + acks.enqueue(ack); +#ifdef ACK_DEBUG + _DEBUG("Ack enqueued: %u", _ackid); +#endif + }else{ +#ifdef ACK_DEBUG + _DEBUG("No ack enqueue needed: %u", _ackid); +#endif + } + header_t header{ + .type = type, + .ackid = _ackid, + .size = size + }; + if(!Blight::send_blocking( + m_fd, + reinterpret_cast(&header), + sizeof(header_t) + )){ + _WARN("Failed to write connection message header: %s", std::strerror(errno)); + return {}; + } + if(size && !Blight::send_blocking(m_fd, data, size)){ + _WARN("Failed to write connection message data: %s", std::strerror(errno)); + return {}; + } +#ifdef ACK_DEBUG + _DEBUG("Sent: %u %d", _ackid, type); +#endif + if(type == MessageType::Ack){ + // Clear the ackid so that it will not block on wait + ack->ackid = 0; + } + return ack; + } + + std::optional> Connection::ping(int timeout){ + ClockWatch cw; + auto ack = send(MessageType::Ping, nullptr, 0); + if(!ack.has_value()){ + _WARN("Failed to ping: %s", std::strerror(errno)); + return {}; + } + if(!ack.value()->wait(timeout)){ + _WARN("Ping timed out"); + return {}; + } + auto duration = cw.diff(); + _DEBUG("ping in %fs", duration.count()); + return duration; + } + + void Connection::waitForMarker(unsigned int marker){ + auto maybe = send(MessageType::Wait, reinterpret_cast(&marker), sizeof(marker)); + if(maybe.has_value()){ + maybe.value()->wait(); + } + } + + maybe_ackid_ptr_t Connection::repaint( + surface_id_t identifier, + int x, + int y, + int width, + int height, + WaveformMode waveform, + unsigned int marker + ){ + if(!identifier){ + errno = EINVAL; + return {}; + } + repaint_t repaint{ + .x = x, + .y = y, + .width = width, + .height = height, + .waveform = waveform, + .marker = marker, + .identifier = identifier, + }; + auto ackid = send(MessageType::Repaint, (data_t)&repaint, sizeof(repaint)); + if(!ackid.has_value()){ + return {}; + } + return ackid; + } + + void Connection::move(shared_buf_t buf, int x, int y){ + auto ack = move(buf->surface, x, y); + if(ack.has_value()){ + ack.value()->wait(); + buf->x = x; + buf->y = y; + } + } + + std::optional Connection::resize( + shared_buf_t buf, + int width, + int height, + int stride, + data_t new_data + ){ + if(!buf->surface){ + errno = EINVAL; + return {}; + } + auto res = Blight::createBuffer( + buf->x, + buf->y, + width, + height, + stride, + buf->format + ); + if(!res.has_value()){ + return {}; + } + auto buf2 = res.value(); + memcpy(buf2->data, new_data, buf2->size()); + Blight::addSurface(buf2); + if(!buf2->surface){ + return {}; + } + auto ack = remove(buf); + if(ack.has_value()){ + ack.value()->wait(); + } + return buf2; + } + + maybe_ackid_ptr_t Connection::raise(surface_id_t identifier){ + if(!identifier){ + errno = EINVAL; + return {}; + } + return send(MessageType::Raise, (data_t)&identifier, sizeof(surface_id_t)); + } + + maybe_ackid_ptr_t Connection::raise(shared_buf_t buf){ + if(buf == nullptr){ + errno = EINVAL; + return {}; + } + return raise(buf->surface); + } + + maybe_ackid_ptr_t Connection::lower(surface_id_t identifier){ + if(!identifier){ + errno = EINVAL; + return {}; + } + return send(MessageType::Lower, (data_t)&identifier, sizeof(surface_id_t)); + } + + maybe_ackid_ptr_t Connection::lower(shared_buf_t buf){ + if(buf == nullptr){ + errno = EINVAL; + return {}; + } + return lower(buf->surface); + } + + maybe_ackid_ptr_t Connection::move(surface_id_t identifier, int x, int y){ + if(!identifier){ + errno = EINVAL; + return {}; + } + move_t move{ + .identifier = identifier, + .x = x, + .y = y, + }; + return send(MessageType::Move, (data_t)&move, sizeof(move)); + } + + std::optional Connection::getBuffer(surface_id_t identifier){ + if(!identifier){ + _WARN("Failed to get buffer for surface %hu: Invalid identifier", identifier); + return {}; + } + int fd = getSurface(identifier); + if(fd == -1){ + _WARN("Failed to get buffer for surface %hu: %s", identifier, std::strerror(errno)); + return {}; + } + auto maybe = send(MessageType::Info, (data_t)&identifier, sizeof(surface_id_t)); + if(!maybe.has_value()){ + ::close(fd); + _WARN("Failed to get info for surface %hu: %s", identifier, std::strerror(errno)); + return {}; + } + auto ack = maybe.value(); + ack->wait(); + if(!ack->data_size){ + ::close(fd); + _WARN("Failed to get info for surface %hu: It does not exist", identifier); + return {}; + } + if(ack->data_size < sizeof(surface_info_t)){ + _WARN( + "Surface %d info size mismatch: size=%hu, expected=%u", + identifier, + ack->data_size, + sizeof(surface_info_t) + ); + ::close(fd); + return {}; + } + auto info = reinterpret_cast(ack->data.get()); + auto buf = new buf_t{ + .fd = fd, + .x = info->x, + .y = info->y, + .width = info->width, + .height = info->height, + .stride = info->stride, + .format = info->format, + .data = nullptr, + .uuid = "", + .surface = identifier + }; + buf->data = new unsigned char[buf->size()]; + auto res = mmap(NULL, buf->size(), PROT_READ, MAP_SHARED_VALIDATE, fd, 0); + if(res == MAP_FAILED){ + _WARN( + "Failed to map buffer for surface: %hu error: %s", + identifier, + std::strerror(errno) + ); + ::close(fd); + delete buf->data; + delete buf; + return {}; + } + buf->data = reinterpret_cast(res); + return shared_buf_t(buf); + } + + std::vector Connection::buffers(){ + std::vector buffers; + for(const auto& identifier : surfaces()){ + auto maybe = getBuffer(identifier); + if(!maybe.has_value()){ + continue; + } + auto buf = maybe.value(); + if(buf->surface && buf->data != nullptr){ + buffers.push_back(maybe.value()); + } + } + return buffers; + } + + maybe_ackid_ptr_t Connection::remove(shared_buf_t buf){ + if(buf == nullptr){ + return {}; + } + auto ack = remove(buf->surface); + if(!ack.has_value()){ + return {}; + } + buf->surface = 0; + return ack; + } + + maybe_ackid_ptr_t Connection::remove(surface_id_t identifier){ + if(!identifier){ + errno = EINVAL; + return {}; + } + return send(MessageType::Delete, (data_t)&identifier, sizeof(surface_id_t)); + } + + std::vector Connection::surfaces(){ + auto maybe = send(MessageType::List, nullptr, 0); + if(!maybe.has_value()){ + return std::vector(); + } + auto ack = maybe.value(); + ack->wait(); + if(!ack->data_size){ + return std::vector(); + } + if(ack->data == nullptr){ + _WARN("Missing list data pointer!"); + return std::vector(); + } + auto buf = ack->data.get(); + return std::vector(buf, buf + (ack->data_size / sizeof(surface_id_t))); + } + + void Connection::focused(){ + auto maybe = send(MessageType::Focus, nullptr, 0); + if(!maybe.has_value()){ + return; + } + auto ack = maybe.value(); + ack->wait(); + return; + } + + static std::atomic running = false; + + void Connection::run(Connection* connection){ + prctl(PR_SET_NAME, "BlightWorker\0", 0, 0, 0); + if(running){ + _WARN("Already running"); + return; + } + running = true; + _INFO("Starting"); + std::vector> completed; + std::map waiting; + int error = 0; + while(!connection->stop_requested){ + // Get any new waiting items + { + // Contain scope to avoid longer lived shared pointer + ackid_ptr_t ptr; + while(acks.try_dequeue(ptr)){ + waiting[ptr->ackid] = ptr; +#ifdef ACK_DEBUG + _DEBUG("Ack dequeued: %u", ptr->ackid); +#endif + } + } +#ifdef ACK_DEBUG + { + std::string msg; + auto iter = waiting.begin(); + while(iter != waiting.end()){ + auto ackid = (*iter).first; + msg += std::to_string(ackid) + ","; + ++iter; + } + _DEBUG("Waiting acks: [%s]", msg.c_str()); + } + { + std::string msg; + for(auto i = completed.begin(); i != completed.end(); ++i){ + msg += std::to_string((*i)->header.ackid) + ","; + } + _DEBUG("Completed acks: [%s]", msg.c_str()); + } +#endif + // Handle any pending items that are now being waited on + if(!completed.empty()){ +#ifdef ACK_DEBUG + _DEBUG("Resolving %u waiting acks", completed.size()); +#endif + auto iter = completed.begin(); + while(iter != completed.end()){ + auto message = *iter; + auto ackid = message->header.ackid; + + if(!waiting.contains(ackid)){ + ++iter; + continue; + } + ackid_ptr_t& ack = waiting[ackid]; + if(message->header.size){ + ack->data = message->data; + ack->data_size = message->header.size; + } + ack->done = true; + ack->notify_all(); + iter = completed.erase(iter); + waiting.erase(ackid); +#ifdef ACK_DEBUG + _DEBUG("Ack handled: %u", ackid); +#endif + } + } + auto message = connection->read(); + if(message->header.type == MessageType::Invalid){ + if(errno == EAGAIN){ + continue; + } + _WARN("Failed to read message: %s", std::strerror(errno)); + error = errno; + break; + } + switch(message->header.type){ + case MessageType::Ping:{ +#ifdef ACK_DEBUG + _DEBUG("pong %u", message->header.ackid); +#endif + auto maybe = connection->send( + MessageType::Ack, + nullptr, + 0, + message->header.ackid + ); + if(!maybe.has_value()){ + _WARN("Failed to ack ping: %s", std::strerror(errno)); + } + break; + } + case MessageType::Ack:{ +#ifdef ACK_DEBUG + auto ackid = message->header.ackid; + _DEBUG("Ack recieved: %u", ackid); +#endif + completed.push_back(message); + break; + } + default: + _WARN("Unexpected message type: %d", message->header.type); + } + } + _INFO("Quitting"); + ackid_ptr_t ptr; + while(acks.try_dequeue(ptr)){ + ptr->notify_all(); + } + auto iter = waiting.begin(); + while(iter != waiting.end()){ + auto ack = (*iter).second; + ack->notify_all(); + iter = waiting.erase(iter); + } + for(auto& callback : connection->disconnectCallbacks){ + callback(error); + } + running = false; + } +} diff --git a/shared/libblight/connection.h b/shared/libblight/connection.h new file mode 100644 index 000000000..c55ff2e33 --- /dev/null +++ b/shared/libblight/connection.h @@ -0,0 +1,267 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once +#include "libblight_global.h" +#include "types.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Blight { + class Connection; + /*! + * \brief Handle used for waiting for a response from the display server + */ + typedef struct ackid_t{ + /*! + * \brief Identifier + */ + unsigned int ackid; + bool done; + /*! + * \brief Return data size + */ + unsigned int data_size; + /*! + * \brief Return data + */ + shared_data_t data; + std::mutex mutex; + std::condition_variable condition; + ackid_t( + Connection* connection, + unsigned int ackid = 0, + unsigned int data_size = 0, + data_t data = nullptr + ); + ~ackid_t(); + /*! + * \brief Wait for the reply to return + * \param timeout Timeout + */ + bool wait(int timeout = 0); + void notify_all(); + } ackid_t; + /*! + * \brief Shared pointer to ackid_t + */ + typedef std::shared_ptr ackid_ptr_t; + /*! + * \brief Optional response of ackid_ptr_t + */ + typedef std::optional maybe_ackid_ptr_t; + /*! + * \brief Display server connection + */ + class LIBBLIGHT_EXPORT Connection { + public: + /*! + * \brief Create a new connection instance + * \param fd Connection socket descriptor + */ + Connection(int fd); + ~Connection(); + /*! + * \brief Connection socket descriptor + * \return Connection socket descriptor + */ + int handle(); + /*! + * \brief input_handle input event socket descriptor + * \return input event socket descriptor + */ + int input_handle(); + /*! + * \brief Run a callback when the socket disconnects from the display server + * \param callback Callback to run. + */ + void onDisconnect(std::function callback); + /*! + * \brief Read a message from the display server connection + * \return A message + */ + message_ptr_t read(); + /*! + * \brief Read an input event from the display server input events socket + * \return Input event packet + */ + std::optional read_event(); + /*! + * \brief Send a message to the display server + * \param type Type of message + * \param data Data to send + * \param size Size of data + * \param __ackid Unique identifier to use, will automatically generate if set to 0 + * \return ack_ptr_t if the message was sent + */ + maybe_ackid_ptr_t send(MessageType type, data_t data, size_t size, unsigned int __ackid = 0); + /*! + * \brief Ping the server + * \param timeout Timeout + * \return Duration of ping + */ + std::optional> ping(int timeout = 0); + /*! + * \brief Wait for a marker to complete repainting on the display server + * \param marker Maker to wait on + */ + void waitForMarker(unsigned int marker); + /*! + * \brief Repaint a portion of a surface + * \param identifier Surface identifier + * \param x X offset on the surface + * \param y Y offset on the surface + * \param width Width to repaint + * \param height Height to repaint + * \param waveform Waveform to use + * \param marker Marker + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t repaint( + surface_id_t identifier, + int x, + int y, + int width, + int height, + WaveformMode waveform = WaveformMode::HighQualityGrayscale, + unsigned int marker = 0 + ); + /*! + * \brief Repaint a portion of a surface + * \param buf Buffer representing surface + * \param x X offset on the surface + * \param y Y offset on the surface + * \param width Width to repaint + * \param height Height to repaint + * \param waveform Waveform to use + * \param marker Marker + * \return ack_ptr_t if there was no error + */ + inline maybe_ackid_ptr_t repaint( + shared_buf_t buf, + int x, + int y, + int width, + int height, + WaveformMode waveform = WaveformMode::HighQualityGrayscale, + unsigned int marker = 0 + ){ return repaint(buf->surface, x, y, width, height, waveform, marker); } + /*! + * \brief Repaint a surface + * \param buf Buffer representing surface + * \param waveform Waveform to use + * \param marker Marker + * \return ack_ptr_t if there was no error + */ + inline maybe_ackid_ptr_t repaint( + shared_buf_t buf, + WaveformMode waveform = WaveformMode::HighQualityGrayscale, + unsigned int marker = 0 + ){ return repaint(buf->surface, buf->x, buf->y, buf->width, buf->height, waveform, marker); } + /*! + * \brief Move a surface + * \param buf Buffer representing surface + * \param x X coordinate to move to + * \param y Y coordinate to move to + */ + void move(shared_buf_t buf, int x, int y); + /*! + * \brief move + * \param identifier Surface identifier + * \param x X coordinate to move to + * \param y Y coordinate to move to + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t move(surface_id_t identifier, int x, int y); + /*! + * \brief Resize a surface + * \param buf Buffer representing surface + * \param width Width of new surface + * \param height Height of new surface + * \param stride Bytes per line in the buffer + * \param new_data New data to use for the buffer + * \return New shared_buf_t if there was no error + */ + std::optional resize( + shared_buf_t buf, + int width, + int height, + int stride, + data_t new_data + ); + /*! + * \brief Raise a surface + * \param identifier Surface identifier + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t raise(surface_id_t identifier); + /*! + * \brief Raise a surface + * \param buf Buffer representing surface + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t raise(shared_buf_t buf); + /*! + * \brief Lower a surface + * \param identifier Surface identifier + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t lower(surface_id_t identifier); + /*! + * \brief Lower a surface + * \param buf Buffer representing surface + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t lower(shared_buf_t buf); + /*! + * \brief Get a buffer + * \param identifier Surface identifier + * \return ack_ptr_t if there was no error + */ + std::optional getBuffer(surface_id_t identifier); + /*! + * \brief Get the list of buffers for the connection + * \return List of buffers for the connection + */ + std::vector buffers(); + /*! + * \brief Remove a surface + * \param buf Buffer representing surface + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t remove(shared_buf_t buf); + /*! + * \brief Remove a surface + * \param identifier Surface identifier + * \return ack_ptr_t if there was no error + */ + maybe_ackid_ptr_t remove(surface_id_t identifier); + /*! + * \brief Get the list of surfaces for the connection + * \return List of surfaces identifiers + */ + std::vector surfaces(); + /*! + * \brief Make the current connection the focused connection, which recieves input events + */ + void focused(); + + private: + int m_fd; + int m_inputFd; + std::atomic stop_requested; + std::vector> disconnectCallbacks; + std::thread thread; + static void run(Connection* connection); + }; +} +/*! @} */ diff --git a/shared/libblight/dbus.cpp b/shared/libblight/dbus.cpp new file mode 100644 index 000000000..a062f9a63 --- /dev/null +++ b/shared/libblight/dbus.cpp @@ -0,0 +1,120 @@ +#include "dbus.h" +#include +#include + +#define XCONCATENATE(x, y) x ## y +#define CONCATENATE(x, y) XCONCATENATE(x, y) +#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) +#define UNIQ __COUNTER__ +#define _STRV_FOREACH(s, l, i) for(typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) +#define STRV_FOREACH(s, l) _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ)) + +char** strv_free(char** v) { + if(!v){ + return NULL; + } + for(char** i = v; *i; i++){ + free(*i); + } + free(v); + return NULL; +} + +namespace Blight { + DBusReply::DBusReply() + : error(SD_BUS_ERROR_NULL), + return_value{0} + {} + + Blight::DBusReply::~DBusReply(){ + if(message != nullptr){ + sd_bus_message_unref(message); + } + sd_bus_error_free(&error); + } + + std::string Blight::DBusReply::error_message(){ + if(error.message != NULL){ + return error.message; + } + return std::strerror(-return_value); + } + + bool Blight::DBusReply::isError(){ return return_value < 0; } + + DBusException::DBusException(const std::string& message) + : std::runtime_error(message.c_str()) + {} + + DBus::DBus(bool use_system) + : m_bus(nullptr) + { + int res; + if(use_system){ + res = sd_bus_default_system(&m_bus); + }else{ + res = sd_bus_default_user(&m_bus); + } + if(res < 0){ + throw DBusException("Could not connect to bus"); + } + } + + DBus::~DBus(){ + sd_bus_unref(m_bus); + } + + sd_bus* DBus::bus(){ return m_bus; } + + std::vector DBus::services(){ + char** names = NULL; + char** activatable = NULL; + std::vector services; + if(sd_bus_list_names(m_bus, &names, &activatable) < 0){ + strv_free(names); + strv_free(activatable); + return services; + } + STRV_FOREACH(i, names){ + services.push_back(std::string(*i)); + } + STRV_FOREACH(i, activatable){ + auto name = std::string(*i); + if(std::find(services.begin(), services.end(), name) != services.end()){ + services.push_back(name); + } + } + strv_free(names); + strv_free(activatable); + return services; + } + + bool DBus::has_service(std::string service){ + auto services = this->services(); + return std::find(services.begin(), services.end(), service) != services.end(); + } + + dbus_reply_t DBus::get_property( + const std::string& service, + const std::string& path, + const std::string& interface, + const std::string& member, + const std::string& property_type + ){ + auto res = dbus_reply_t(new DBusReply()); + res->return_value = sd_bus_get_property( + m_bus, + service.c_str(), + path.c_str(), + interface.c_str(), + member.c_str(), + &res->error, + &res->message, + property_type.c_str() + ); + if(res->isError()){ + errno = -res->return_value; + } + return res; + } +} diff --git a/shared/libblight/dbus.h b/shared/libblight/dbus.h new file mode 100644 index 000000000..567efd454 --- /dev/null +++ b/shared/libblight/dbus.h @@ -0,0 +1,178 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once +#include "libblight_global.h" + +#include +#include +#include + +namespace Blight { + /*! + * \brief The DBusReply class + */ + class LIBBLIGHT_EXPORT DBusReply{ + public: + DBusReply(); + ~DBusReply(); + + /*! + * \brief Message descriptor + */ + sd_bus_message* message = nullptr; + /*! + * \brief Error descriptor + */ + sd_bus_error error; + /*! + * \brief Return value of the last operation + */ + int return_value; + /*! + * \brief Error message as a string + * \return + */ + std::string error_message(); + /*! + * \brief If this reply is an error + * \return + */ + bool isError(); + + /*! + * \brief Read a value from the reply message + * \param argument_type Type of data to read from the reply message + * \return + */ + template + std::optional read_value(const char* argument_type){ + T argument; + return_value = sd_bus_message_read(message, argument_type, &argument); + if(isError()){ + errno = -return_value; + return {}; + } + return argument; + } + }; + /*! + * \brief Shared pointer of a DBusReply + */ + LIBBLIGHT_EXPORT typedef std::shared_ptr dbus_reply_t; + /*! + * \brief An exception raised by DBus + * \sa Blight::DBus::DBus(bool) + */ + class LIBBLIGHT_EXPORT DBusException : public std::runtime_error{ + public: + DBusException(const std::string& message); + }; + /*! + * \brief A helper class for dealing with sd-bus + */ + class LIBBLIGHT_EXPORT DBus { + public: + /*! + * \brief Create a new DBus connection + * \param use_system Use the system bus instead of the session bus + * \exception Blight::DBusException Failed to connect to the bus + */ + DBus(bool use_system = false); + ~DBus(); + /*! + * \brief underlying sd_bus object + * \return The sd_bus object used by sd-bus + */ + sd_bus* bus(); + /*! + * \brief All the known services on the bus + * \return All the known services on the bus + * \sa has_service(std::string) + */ + std::vector services(); + /*! + * \brief Check if a service is available on the bus. + * \param service Service name. + * \return If the service is available. + * \sa services() + */ + bool has_service(std::string service); + /*! + * \brief Call a DBus method + * \param service Service name + * \param path Path + * \param interface Interface name + * \param member Method name + * \param argument_types Argument types + * \param args Arguments + * \return DBusReply + */ + template + dbus_reply_t call_method( + const std::string& service, + const std::string& path, + const std::string& interface, + const std::string& member, + const std::string& argument_types, + Args... args + ){ + auto res = dbus_reply_t(new DBusReply()); + if(argument_types.size() != sizeof...(args)){ + res->return_value = EINVAL; + return res; + } + res->return_value = sd_bus_call_method( + m_bus, + service.c_str(), + path.c_str(), + interface.c_str(), + member.c_str(), + &res->error, + &res->message, + argument_types.c_str(), + args... + ); + if(res->isError()){ + errno = -res->return_value; + } + return res; + } + /*! + * \brief Call a DBus method + * \param service Service name + * \param path Path + * \param interface Interface name + * \param member Method name + * \return DBusReply + */ + inline dbus_reply_t call_method( + const std::string& service, + const std::string& path, + const std::string& interface, + const std::string& member + ){ return call_method(service, path, interface, member, ""); } + /*! + * \brief Get a property from DBus + * \param service Service name + * \param path Path + * \param interface Interface name + * \param member Property name + * \param property_type Property type + * \return DBusReply + */ + dbus_reply_t get_property( + const std::string& service, + const std::string& path, + const std::string& interface, + const std::string& member, + const std::string& property_type + ); + + private: + sd_bus* m_bus; + }; +} +/*! @} */ diff --git a/shared/libblight/debug.cpp b/shared/libblight/debug.cpp new file mode 100644 index 000000000..65c937cc0 --- /dev/null +++ b/shared/libblight/debug.cpp @@ -0,0 +1,42 @@ +#include "debug.h" + +void __printf_header(int priority){ + std::string level; + switch(priority){ + case LOG_INFO: + level = "Info"; + break; + case LOG_WARNING: + level = "Warning"; + break; + case LOG_CRIT: + level = "Critical"; + break; + default: + level = "Debug"; + } + char name[16]; + prctl(PR_GET_NAME, name); + auto selfpath = realpath("/proc/self/exe", NULL); + fprintf( + stderr, + "[%i:%i:%i %s - %s] %s: ", + getpgrp(), + getpid(), + gettid(), + selfpath, + name, + level.c_str() + ); + free(selfpath); +} + +void __printf_footer(const char* file, unsigned int line, const char* func){ + fprintf( + stderr, + " (%s:%u, %s)\n", + file, + line, + func + ); +} diff --git a/shared/libblight/debug.h b/shared/libblight/debug.h new file mode 100644 index 000000000..ebb9caa0b --- /dev/null +++ b/shared/libblight/debug.h @@ -0,0 +1,57 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +static std::mutex __log_mutex; +/*! + * \brief BLIGHT_DEBUG_LOGGING + * + * Used to filter debug messages based on the priority. + */ +static int BLIGHT_DEBUG_LOGGING = 4; +void __printf_header(int priority); +void __printf_footer(char const* file, unsigned int line, char const* func); + +/*! + * \brief Log a message to stderr + */ +#define _PRINTF(priority, ...) \ + if(priority <= BLIGHT_DEBUG_LOGGING){ \ + __log_mutex.lock(); \ + __printf_header(priority); \ + fprintf(stderr, __VA_ARGS__); \ + __printf_footer(__FILE__, __LINE__, __PRETTY_FUNCTION__); \ + __log_mutex.unlock(); \ + } + +/*! + * \brief Log a debug statement to stderrr + */ +#define _DEBUG(...) _PRINTF(LOG_DEBUG, __VA_ARGS__) +/*! + * \brief Log a warning statement to stderrr + */ +#define _WARN(...) _PRINTF(LOG_WARNING, __VA_ARGS__) +/*! + * \brief Log an informational statement to stderrr + */ +#define _INFO(...) _PRINTF(LOG_INFO, __VA_ARGS__) +/*! + * \brief Log a critical exception statement to stderrr + */ +#define _CRIT(...) _PRINTF(LOG_CRIT, __VA_ARGS__) +/*! + * \brief Log a debug line indicating that a certain line of code has been reached + */ +#define __RIGHT_HERE__ fprintf(stderr, "<============================ %s:%d\n", __FILE__, __LINE__) +/*! @} */ diff --git a/shared/libblight/libblight.cpp b/shared/libblight/libblight.cpp new file mode 100644 index 000000000..590587c44 --- /dev/null +++ b/shared/libblight/libblight.cpp @@ -0,0 +1,438 @@ +#include "libblight.h" +#include "dbus.h" +#include "meta.h" +#include "types.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +namespace Blight{ + static DBus* dbus = nullptr; + + std::optional getClipboard(const std::string& name){ + if(!exists()){ + errno = EAGAIN; + return {}; + } + auto reply = dbus->get_property( + BLIGHT_SERVICE, + "/", + BLIGHT_INTERFACE, + name, + "ay" + ); + if(reply->isError()){ + _WARN( + "[Blight::%s()::get_property(...)] Error: %s", + name.c_str(), + reply->error_message().c_str() + ); + return {}; + } + const void* clipboard = nullptr; + ::size_t size = 0; + auto res = sd_bus_message_read_array( + reply->message, + 'y', + &clipboard, + &size + ); + if(res < 0){ + _WARN( + "[Blight::%s()::sd_bus_message_read_array(...)] Error: %s", + name.c_str(), + reply->error_message().c_str() + ); + return {}; + } + auto data = new unsigned char[size]; + memcpy(data, clipboard, size); + return clipboard_t(name, data, static_cast(size)); + } + + bool connect(bool use_system){ + if(dbus != nullptr){ + return true; + } + try{ + dbus = new DBus(use_system); + return true; + }catch(const Blight::DBusException& e){ + std::cerr << e.what() << std::endl; + return false; + } + } + + bool exists(){ return connect() && dbus->has_service(BLIGHT_SERVICE); } + + Connection* connection(){ + static Connection* instance = nullptr; + if(instance == nullptr){ + int fd = open(); + if(fd < 0){ + return nullptr; + } + instance = new Connection(fd); + } + return instance; + } + + int open(){ + if(!exists()){ + errno = EAGAIN; + return -EAGAIN; + } + auto reply = dbus->call_method(BLIGHT_SERVICE, "/", BLIGHT_INTERFACE, "open"); + if(reply->isError()){ + _WARN("[Blight::open()::call_method(...)] Error: %s", reply->error_message().c_str()); + return reply->return_value; + } + auto fd = reply->read_value("h"); + if(!fd.has_value()){ + _WARN("[Blight::open()::read_value(\"h\")] Error: %s", reply->error_message().c_str()); + return reply->return_value; + } + int dfd = fcntl(fd.value(), F_DUPFD_CLOEXEC, 3); + if(dfd < 0){ + _WARN("[Blight::open()::dup(%d)] Error: %s", fd.value(), reply->error_message().c_str()); + errno = -dfd; + } + return dfd; + } + + int open_input(){ + if(!exists()){ + errno = EAGAIN; + return -EAGAIN; + } + auto reply = dbus->call_method(BLIGHT_SERVICE, "/", BLIGHT_INTERFACE, "openInput"); + if(reply->isError()){ + _WARN("[Blight::open_input()::call_method(...)] Error: %s", reply->error_message().c_str()); + return reply->return_value; + } + auto fd = reply->read_value("h"); + if(!fd.has_value()){ + _WARN("[Blight::open_input()::read_value(\"h\")] Error: %s", reply->error_message().c_str()); + return reply->return_value; + } + int dfd = fcntl(fd.value(), F_DUPFD_CLOEXEC, 3); + if(dfd < 0){ + _WARN("[Blight::open_input()::dup(%d)] Error: %s", fd.value(), reply->error_message().c_str()); + errno = -dfd; + } + return dfd; + } + + std::optional createBuffer(int x, int y, int width, int height, int stride, Format format){ + auto buf = new buf_t{ + .fd = -1, + .x = x, + .y = y, + .width = width, + .height = height, + .stride = stride, + .format = format, + .data = nullptr, + .uuid = buf_t::new_uuid(), + .surface = 0 + }; + buf->fd = memfd_create(buf->uuid.c_str(), MFD_ALLOW_SEALING); + if(buf->fd == -1){ + _WARN("[Blight::createBuffer()::memfd_create(...)] Error: %s", std::strerror(errno)); + delete buf; + return {}; + } + if(ftruncate(buf->fd, buf->size())){ + _WARN( + "[Blight::createBuffer()::ftruncate(%d)] Error: %s", + buf->size(), + std::strerror(errno) + ); + int e = errno; + buf->close(); + delete buf; + errno = e; + return {}; + } + void* data = mmap( + NULL, + buf->size(), + PROT_READ | PROT_WRITE, + MAP_SHARED_VALIDATE, + buf->fd, + 0 + ); + if(data == MAP_FAILED || data == nullptr){ + _WARN( + "[Blight::createBuffer()::mmap(%d)] Error: %s", + buf->size(), + std::strerror(errno) + ); + int e = errno; + buf->close(); + delete buf; + errno = e; + return {}; + } + buf->data = reinterpret_cast(data); + return shared_buf_t(buf); + } + + std::optional addSurface( + int fd, + int x, + int y, + int width, + int height, + int stride, + Format format + ){ + if(!exists()){ + errno = EAGAIN; + return {}; + } + auto reply = dbus->call_method( + BLIGHT_SERVICE, + "/", + BLIGHT_INTERFACE, + "addSurface", + "hiiiiii", + fd, + x, + y, + width, + height, + stride, + format + ); + if(reply->isError()){ + _WARN("[Blight::addSurface()::call_method(...)] Error: %s", reply->error_message().c_str()); + return {}; + } + auto identifier = reply->read_value("q"); + if(!identifier.has_value()){ + _WARN("[Blight::addSurface()::read_value(\"q\")] Error: %s", reply->error_message().c_str()); + return {}; + } + return identifier.value(); + } + + int repaint(const std::string& identifier){ + if(!exists()){ + errno = EAGAIN; + return -errno; + } + if(identifier.empty()){ + errno = EINVAL; + return -errno; + } + auto reply = dbus->call_method( + BLIGHT_SERVICE, + "/", + BLIGHT_INTERFACE, + "repaint", + "s", + identifier + ); + if(reply->isError()){ + _WARN( + "[Blight::repaint(%s)::call_method(...)] Error: %s", + identifier.c_str(), + reply->error_message().c_str() + ); + } + return reply->return_value; + } + + int getSurface(surface_id_t identifier){ + if(!exists()){ + errno = EAGAIN; + return -1; + } + if(!identifier){ + errno = EINVAL; + return -1; + } + auto reply = dbus->call_method( + BLIGHT_SERVICE, + "/", + BLIGHT_INTERFACE, + "getSurface", + "q", + identifier + ); + if(reply->isError()){ + _WARN( + "[Blight::getSurface(%d)::call_method(...)] Error: %s", + identifier, + reply->error_message().c_str() + ); + return reply->return_value; + } + auto fd = reply->read_value("h"); + if(!fd.has_value()){ + _WARN( + "[Blight::addSurface(%d)::read_value(\"h\")] Error: %s", + identifier, + reply->error_message().c_str() + ); + return reply->return_value; + } + int dfd = dup(fd.value()); + if(dfd == -1){ + _WARN( + "[Blight::addSurface(%d)::dup(%d)] Error: %s", + identifier, + fd.value(), + reply->error_message().c_str() + ); + return -errno; + } + return dfd; + } + + std::optional clipboard(){ return getClipboard("clipboard"); } + + std::optional selection(){ return getClipboard("selection"); } + + std::optional secondary(){ return getClipboard("secondary"); } + + + bool setClipboard(clipboard_t& clipboard){ + if( + clipboard.name != "clipboard" + && clipboard.name != "selection" + && clipboard.name != "secondary" + ){ + _WARN("[Blight::setClipboard(\"%s\")] Error: Invalid clipboard name", clipboard.name.c_str()); + errno = EINVAL; + return false; + } + char buf[sizeof(clipboard.size) + clipboard.size]; + memcpy(buf, &clipboard.size, sizeof(clipboard.size)); + memcpy(&buf[sizeof(clipboard.size)], clipboard.data.get(), sizeof(clipboard.size)); + sd_bus_message* message; + auto res = sd_bus_message_new_method_call( + dbus->bus(), + &message, + BLIGHT_SERVICE, + "/", + "org.freedesktop.DBus.Properties", + "Set" + ); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_message_new_method_call()] Error: %s", + clipboard.name.c_str(), + std::strerror(-res) + ); + if(message != nullptr){ + sd_bus_message_unref(message); + } + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + res = sd_bus_message_append( + message, + "ss", + BLIGHT_INTERFACE, + // sd_bus_message_append seems to destroy the string, + // so a copy is needed + std::string(clipboard.name).c_str() + ); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_message_append()] Error: %s", + clipboard.name.c_str(), + std::strerror(-res) + ); + sd_bus_message_unref(message); + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + res = sd_bus_message_open_container(message, 'v', "ay"); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_message_open_container()] Error: %s", + clipboard.name.c_str(), + std::strerror(-res) + ); + sd_bus_message_unref(message); + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + res = sd_bus_message_append_array( + message, + 'y', + clipboard.data.get(), + clipboard.size + ); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_message_append_array()] Error: %s", + clipboard.name.c_str(), + std::strerror(-res) + ); + sd_bus_message_unref(message); + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + res = sd_bus_message_close_container(message); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_message_close_container()] Error: %s", + clipboard.name.c_str(), + std::strerror(-res) + ); + sd_bus_message_unref(message); + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + sd_bus_error error = SD_BUS_ERROR_NULL; + res = sd_bus_call(dbus->bus(), message, 0, &error, NULL); + if(res < 0){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_call()] Error: %s", + clipboard.name.c_str(), + (error.message != NULL ? error.message : std::strerror(-res)) + ); + sd_bus_message_unref(message); + sd_bus_error_free(&error); + // Attempt to reset to the current value + updateClipboard(clipboard); + return false; + } + sd_bus_message_unref(message); + sd_bus_error_free(&error); + return true; + } + + bool updateClipboard(clipboard_t& clipboard){ + if( + clipboard.name != "clipboard" + && clipboard.name != "selection" + && clipboard.name != "secondary" + ){ + _WARN( + "[Blight::setClipboard(\"%s\")::sd_bus_call()] Error: Invalid clipboard name", + clipboard.name.c_str() + ); + errno = EINVAL; + return false; + } + // Reload data just in case; + auto newClipboard = getClipboard(clipboard.name); + clipboard.size = newClipboard->size; + clipboard.data = newClipboard->data; + return true; + } +} diff --git a/shared/libblight/libblight.h b/shared/libblight/libblight.h new file mode 100644 index 000000000..e025fdacb --- /dev/null +++ b/shared/libblight/libblight.h @@ -0,0 +1,149 @@ +/*! + * \addtogroup Blight + * \brief The Blight module + * @{ + * \file + */ +#include "libblight_global.h" +#include "types.h" +#include "connection.h" +#include + +/*! + * \brief Blight namespace + */ +namespace Blight{ + /*! + * \brief Connect to DBus + * \param use_system Use the system bus instead of the session bus. + * \return If the connection was successful. + * \retval true Connected successfully. + * \retval false An error occurred. + */ + LIBBLIGHT_EXPORT bool connect(bool use_system = true); + /*! + * \brief If the display server service can be found over DBus + * \return If the display server service can be found over DBus + * \retval true The service exists. + * \retval false The service does not exist, or an error occurred. + */ + LIBBLIGHT_EXPORT bool exists(); + /*! + * \brief Get or create the Connection instance to the display server. + * \return The connection instance is returned. + * \retval nullptr an error occurred + */ + LIBBLIGHT_EXPORT Connection* connection(); + /*! + * \brief Open a connection to the display server + * \return File descriptor for socket to communicate with the display server. + * \retval -EAGAIN Failed to connect to DBus, or the display server service does not exist. + * \sa Blight::Connection::Connection(int) + * \sa Blight::Connection::handle() + */ + LIBBLIGHT_EXPORT int open(); + /*! + * \brief Open a connection to the display server to recieve input events. + * \return File descriptor for socket that the display server will use to send input events + * \sa Blight::Connection::input_handle() + * \sa Blight::Connection::read_event() + */ + LIBBLIGHT_EXPORT int open_input(); + /*! + * \brief Get the clipboard + * \return Clipboard instance + */ + LIBBLIGHT_EXPORT std::optional clipboard(); + /*! + * \brief Get the current selection + * \return Current selection instance + */ + LIBBLIGHT_EXPORT std::optional selection(); + /*! + * \brief Get the secondary selection + * \return Secondary selection instance + */ + LIBBLIGHT_EXPORT std::optional secondary(); + /*! + * \brief Set a clipboard to a new value + * \param clipboard Clipboard instance to set + * \return If the clipboard was set + * \retval true The clipboard was set + * \retval false There was an error setting the clipboard + */ + LIBBLIGHT_EXPORT bool setClipboard(clipboard_t& clipboard); + /*! + * \brief Update a clipboard with the current data + * \param clipboard Clipboard instance to update + * \return If the clipboard was updated + * \retval true The clipboard was updated + * \retval false There was an error updating the clipboard + */ + LIBBLIGHT_EXPORT bool updateClipboard(clipboard_t& clipboard); + /*! + * \brief createBuffer + * \param x X coordinate of the buffer + * \param y Y coordinate of the buffer + * \param width Width of the buffer + * \param height Height of the buffer + * \param stride Bytes per line in the buffer + * \param format Format of the buffer + * \return The new buffer + */ + LIBBLIGHT_EXPORT std::optional createBuffer( + int x, + int y, + int width, + int height, + int stride, + Format format + ); + /*! + * \brief Add a new surface to the display server + * \param fd File descriptor that points to the buffer data for the surface + * \param x X coordinate of the surface + * \param y Y coordinate of the surface + * \param width Width of the surface + * \param height Height of the surface + * \param stride Bytes per line in the buffer for the surface + * \param format Format of the buffer for the surface + * \return Identifier for the surface + */ + LIBBLIGHT_EXPORT std::optional addSurface( + int fd, + int x, + int y, + int width, + int height, + int stride, + Format format + ); + /*! + * \brief Add a new surface to the display server + * \param buf Buffer to use for the surface + */ + LIBBLIGHT_EXPORT inline void addSurface(shared_buf_t buf){ + buf->surface = addSurface( + buf->fd, + buf->x, + buf->y, + buf->width, + buf->height, + buf->stride, + buf->format + ).value_or(0); + } + /*! + * \brief Repaint a surface, or all surfaces for a connection + * \param identifier Identifier of the surface or connection. + * \return Negative number if there was an error + */ + LIBBLIGHT_EXPORT int repaint(const std::string& identifier); + /*! + * \brief Get the file descriptor of the buffer for a surface + * \param identifier Surface idnetifier + * \return Negative number if there was an error. Otherwise the file descriptor of the buffer for the surface + */ + LIBBLIGHT_EXPORT int getSurface(surface_id_t identifier); +} +/*! @} */ diff --git a/shared/libblight/libblight.pro b/shared/libblight/libblight.pro new file mode 100644 index 000000000..b2143e4e2 --- /dev/null +++ b/shared/libblight/libblight.pro @@ -0,0 +1,80 @@ +QT = +TEMPLATE = lib +DEFINES += LIBBLIGHT_LIBRARY + +CONFIG += c++11 +CONFIG += warn_on +CONFIG += precompile_header +CONFIG += create_pc +CONFIG += create_prl +CONFIG += no_install_prl + +SOURCES += \ + clock.cpp \ + connection.cpp \ + dbus.cpp \ + debug.cpp \ + libblight.cpp \ + socket.cpp \ + types.cpp + +HEADERS += \ + clock.h \ + connection.h \ + concurrentqueue.h \ + dbus.h \ + debug.h \ + libblight_global.h \ + libblight.h \ + meta.h \ + socket.h \ + types.h + +PRECOMPILED_HEADER = \ + libblight_stable.h + +LIBS += -lsystemd + +include(../../qmake/common.pri) +RELATIVE_PWD = $$system(realpath --canonicalize-missing --relative-to $$OUT_PWD $$PWD) + +libblight_libblight_h.target = include/libblight/libblight.h +for(h, HEADERS){ + libblight_libblight_h.depends += $${RELATIVE_PWD}/$${h} +} +libblight_libblight_h.commands = \ + mkdir -p include/libblight && \ + echo $$HEADERS | xargs -rn1 | xargs -rI {} cp $$PWD/{} include/libblight/ && \ + echo $$DBUS_INTERFACES | xargs -rn1 | xargs -rI {} basename \"{}\" .xml | xargs -rI {} cp $$OUT_PWD/\"{}\"_interface.h include/libblight/ + +libblight_h.target = include/libblight.h +libblight_h.depends = libblight_libblight_h +libblight_h.commands = \ + echo \\$$LITERAL_HASH"pragma once" > include/libblight.h && \ + echo \"$$LITERAL_HASH"include \\\"libblight/libblight.h\\\"\"" >> include/libblight.h + +libblight_h_install.files = \ + include/libblight.h \ + include/libblight +libblight_h_install.depends = libblight_h +libblight_h_install.path = /opt/include/ +INSTALLS += libblight_h_install + +QMAKE_EXTRA_TARGETS += libblight_libblight_h libblight_h libblight_h_install +POST_TARGETDEPS += $$libblight_libblight_h.target $$libblight_h.target +QMAKE_CLEAN += $$libblight_h.target include/libblight/*.h + +TARGET = blight +target.path = /opt/lib +INSTALLS += target + +QMAKE_PKGCONFIG_NAME = libblight +QMAKE_PKGCONFIG_DESCRIPTION = Shared library for using Oxide\'s screen compositor +QMAKE_PKGCONFIG_VERSION = $$VERSION +QMAKE_PKGCONFIG_PREFIX = /opt +QMAKE_PKGCONFIG_LIBDIR = /opt/lib +QMAKE_PKGCONFIG_INCDIR = /opt/include +QMAKE_PKGCONFIG_DESTDIR = pkgconfig + +DISTFILES += \ + Doxyfile diff --git a/shared/libblight/libblight_global.h b/shared/libblight/libblight_global.h new file mode 100644 index 000000000..7f06c2f04 --- /dev/null +++ b/shared/libblight/libblight_global.h @@ -0,0 +1,17 @@ +#pragma once + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +# if defined(LIBOXIDE_LIBRARY) +# define LIBBLIGHT_EXPORT __attribute__((visibility("default"))) +# else +# define LIBBLIGHT_EXPORT +# endif +#else +# define DEBUG +# define LIBBLIGHT_EXPORT +#endif +#if defined(__clang__) || defined (__GNUC__) +# define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#else +# define ATTRIBUTE_NO_SANITIZE_ADDRESS +#endif diff --git a/shared/libblight/libblight_stable.h b/shared/libblight/libblight_stable.h new file mode 100644 index 000000000..514ef3856 --- /dev/null +++ b/shared/libblight/libblight_stable.h @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/shared/libblight/meta.h b/shared/libblight/meta.h new file mode 100644 index 000000000..cf43c463d --- /dev/null +++ b/shared/libblight/meta.h @@ -0,0 +1,16 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once + +/*! + * \brief Name of the DBus service that the display server is available at. + */ +#define BLIGHT_SERVICE "codes.eeems.blight1" +/*! + * \brief Name of the DBus interface that the display server is available at. + */ +#define BLIGHT_INTERFACE BLIGHT_SERVICE ".Compositor" +/*! @} */ diff --git a/shared/libblight/socket.cpp b/shared/libblight/socket.cpp new file mode 100644 index 000000000..143db4ba1 --- /dev/null +++ b/shared/libblight/socket.cpp @@ -0,0 +1,159 @@ +#include "socket.h" +#include "debug.h" + +void short_pause(){ + timespec remaining; + timespec requested{ + .tv_sec = 0, + .tv_nsec = 5000 + }; + nanosleep(&requested, &remaining); +} + +std::optional Blight::recv( + int fd, + ssize_t size, + unsigned int attempts, + unsigned int timeout +){ + auto data = new unsigned char[size]; + ssize_t res = -1; + unsigned int count = 0; + while(res < 0 && count < attempts){ + if(!wait_for_read(fd, timeout)){ + if(errno == EAGAIN || errno == EINTR){ + count++; + continue; + } + delete[] data; + return {}; + } + // Recieve the data + res = ::recv(fd, data, size, MSG_WAITALL | MSG_DONTWAIT); + if(res > -1){ + break; + } + // We got an unexpected error + if(errno != EAGAIN && errno != EINTR){ + delete[] data; + return {}; + } + // Temporary error, try again + count++; + } + // We retried too many times + if(count == attempts){ + delete[] data; + errno = EAGAIN; + return {}; + } + // We started getting data, but didn't get all of it, try getting some more + if(res && res < size){ + _WARN("Partial read (%d), waiting for the rest of the data (%d)", res, size - res); + // TODO this should be in the main loop + auto maybe = recv_blocking(fd, size - res); + if(maybe.has_value()){ + memcpy(&data[res], maybe.value(), size - res); + delete[] maybe.value(); + return data; + } + } + // The data we recieved isn't the same size as what we expected + if(res != size){ + _WARN("recv %d != %d", size, res); + delete[] data; + errno = EBADMSG; + return {}; + } + return data; +} + +std::optional Blight::recv_blocking(int fd, ssize_t size){ + auto data = new unsigned char[size]; + ssize_t res = -1; + while(res < 0){ + res = ::recv(fd, data, size, MSG_WAITALL); + // Something was recieved + if(res > 0){ + break; + } + // We had an unexpected error + if(errno != EAGAIN && errno != EINTR){ + delete[] data; + return {}; + } + // Temporary error, try again + short_pause(); + } + // The data we recieved isn't the same size as what we expected + if(res != size){ + _WARN("recv_blocking %d != %d", size, res); + delete[] data; + errno = EBADMSG; + return {}; + } + return data; +} + +bool Blight::send_blocking(int fd, const data_t data, ssize_t size){ + // TODO explore MSG_ZEROCOPY, this will require owning the buffer instead of allowing + // the user to pass in one we wont touch. As we'll need to ensure we don't delete + // it until the kernel tells us it's done using it. + int res = -1; + while(res < 0){ + res = ::send(fd, data, size, MSG_EOR | MSG_NOSIGNAL); + // We sent something + if(res > -1){ + break; + } + // We had an unexpected error + if(errno != EAGAIN && errno != EINTR){ + return false; + } + // Temporary error, try again + short_pause(); + } + // The data we sent isn't the same size as what we expected + if(res != size){ + _WARN("send_blocking %d != %d", size, res); + errno = EMSGSIZE; + return false; + } + return true; +} + +bool wait_for(int fd, int timeout, int event){ + while(true){ + pollfd pfd; + pfd.fd = fd; + pfd.events = event; + auto res = poll(&pfd, 1, timeout); + if(res < 0){ + // Temporary error, try again + // TODO keep track of timeout and try again with only remaining time + if(errno == EAGAIN || errno == EINTR){ + continue; + } + return false; + } + // We timed out + if(res == 0){ + errno = EAGAIN; + return false; + } + // Socket disconnected + if(pfd.revents & POLLHUP){ + errno = ECONNRESET; + return false; + } + // Event triggered + if(pfd.revents & event){ + return true; + } + // This should never happen, but just in case try again + } +} + +bool Blight::wait_for_send(int fd, int timeout){ return wait_for(fd, timeout, POLLOUT); } + +bool Blight::wait_for_read(int fd, int timeout){ return wait_for(fd, timeout, POLLIN); } diff --git a/shared/libblight/socket.h b/shared/libblight/socket.h new file mode 100644 index 000000000..d85aeb657 --- /dev/null +++ b/shared/libblight/socket.h @@ -0,0 +1,63 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once +#include "libblight_global.h" + +#include "types.h" + +namespace Blight{ + /*! + * \brief Non-blocking recieve data from a socket. + * \param fd The socket. + * \param size Size of the data to recieve. + * \param attempts Number of attempts to recieve data. + * \param timeout Timeout to wait for data per attempt. + * \return The data if it was recieved. + */ + LIBBLIGHT_EXPORT std::optional recv( + int fd, + ssize_t size, + unsigned int attempts = 5, + unsigned int timeout = 50 + ); + /*! + * \brief Recieve data from a socket + * \param fd The socket. + * \param size Size of the data to recieve + * \return The data if it was recieved without error. + */ + LIBBLIGHT_EXPORT std::optional recv_blocking( + int fd, + ssize_t size + ); + /*! + * \brief Send data to a socket. + * \param fd The socket + * \param data The data to send. + * \param size The size of the data to send. + * \return If the data was sent without error. + */ + LIBBLIGHT_EXPORT bool send_blocking( + int fd, + const data_t data, + ssize_t size + ); + /*! + * \brief Wait until a socket is ready to send data. + * \param fd The socket + * \param timeout Timeout + * \return If the socket is ready to send data. + */ + LIBBLIGHT_EXPORT bool wait_for_send(int fd, int timeout = -1); + /*! + * \brief Wait until a socket is ready for us to recieve data. + * \param fd The socket + * \param timeout Timeout + * \return If the socket is ready for us to recieve data. + */ + LIBBLIGHT_EXPORT bool wait_for_read(int fd, int timeout = -1); +} +/*! @} */ diff --git a/shared/libblight/types.cpp b/shared/libblight/types.cpp new file mode 100644 index 000000000..371669135 --- /dev/null +++ b/shared/libblight/types.cpp @@ -0,0 +1,223 @@ +#include "types.h" + +#include +#include +#include + +#include "debug.h" +#include "libblight.h" +#include "socket.h" + +std::string generate_uuid_v4(){ + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution<> dis(0, 15); + static std::uniform_int_distribution<> dis2(8, 11); + std::stringstream ss; + int i; + ss << std::hex; + for (i = 0; i < 8; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 4; i++) { + ss << dis(gen); + } + ss << "-4"; + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + ss << dis2(gen); + for (i = 0; i < 3; i++) { + ss << dis(gen); + } + ss << "-"; + for (i = 0; i < 12; i++) { + ss << dis(gen); + }; + return ss.str(); +} + +Blight::size_t Blight::buf_t::size(){ + return static_cast(stride) * static_cast(height); +} + +int Blight::buf_t::close(){ + if(data != nullptr){ + munmap(data, size()); + data = nullptr; + } + if(fd != -1){ + int res = ::close(fd); + fd = -1; + return res; + } + return 0; +} + +Blight::buf_t::~buf_t(){ close(); } + +std::optional Blight::buf_t::clone(){ + auto res = Blight::createBuffer(x, y, width, height, stride, format); + if(!res.has_value()){ + return {}; + } + auto buf = res.value(); + memcpy(buf->data, data, size()); + return buf; +} + +Blight::shared_buf_t Blight::buf_t::new_ptr(){ + return shared_buf_t(new buf_t{ + .fd = -1, + .x = 0, + .y = 0, + .width = 0, + .height = 0, + .stride = 0, + .format = Format::Format_Invalid, + .data = nullptr, + .uuid = new_uuid(), + .surface = 0 + }); +} + +std::string Blight::buf_t::new_uuid(){ return generate_uuid_v4(); } + +Blight::header_t Blight::header_t::from_data(data_t data){ + header_t header; + memcpy(&header, data, sizeof(header_t)); + return header; +} + +Blight::header_t Blight::header_t::from_data(char* data){ + return from_data(reinterpret_cast(data)); +} + +Blight::header_t Blight::header_t::from_data(void* data){ + return from_data(reinterpret_cast(data)); +} + +Blight::header_t Blight::header_t::new_invalid(){ + return header_t{ + .type = MessageType::Invalid, + .ackid = 0, + .size = 0, + }; +} + +Blight::message_t Blight::message_t::from_data(data_t _data){ + message_t message; + message.header = header_t::from_data(_data); + message.data = shared_data_t(new unsigned char[message.header.size]); + memcpy(message.data.get(), &_data[sizeof(message.header)], message.header.size); + return message; +} + +Blight::message_t Blight::message_t::from_data(char* data){ + return from_data(reinterpret_cast(data)); +} + +Blight::message_t Blight::message_t::from_data(void* data){ + return from_data(reinterpret_cast(data)); +} + +Blight::header_t Blight::message_t::create_ack(message_t* message, size_t size){ + return create_ack(*message, size); +} + +Blight::header_t Blight::message_t::create_ack(const message_t& message, size_t size){ + return header_t{ + .type = MessageType::Ack, + .ackid = message.header.ackid, + .size = size + }; +} + +Blight::message_ptr_t Blight::message_t::from_socket(int fd){ + auto message = message_t::new_ptr(); + auto maybe = Blight::recv(fd, sizeof(header_t)); + if(!maybe.has_value()){ + if(errno != EAGAIN && errno != EINTR){ + _WARN( + "Failed to read connection message header: %s socket=%d", + std::strerror(errno), + fd + ); + } + return message; + } + memcpy(&message->header, maybe.value(), sizeof(header_t)); + delete[] maybe.value(); + if(!message->header.size){ + return message; + } + maybe = Blight::recv_blocking(fd, message->header.size); + if(!maybe.has_value()){ + _WARN( + "Failed to read connection message data: %s " + "socket=%d, " + "ackid=%u, " + "type=%d, " + "size=%ld", + std::strerror(errno), + fd, + message->header.ackid, + message->header.type, + (long int)message->header.size + ); + message->header.type = MessageType::Invalid; + return message; + } + message->data = shared_data_t(maybe.value()); + return message; +} + +Blight::message_ptr_t Blight::message_t::new_ptr(){ + return message_ptr_t(new message_t{ + .header = header_t::new_invalid(), + .data = shared_data_t() + }); +} + +Blight::repaint_t Blight::repaint_t::from_message(const message_t* message){ + const data_t data = message->data.get(); + repaint_t repaint; + memcpy(&repaint, data, sizeof(repaint)); + return repaint; +} + +Blight::move_t Blight::move_t::from_message(const message_t* message){ + move_t move; + memcpy(&move, message->data.get(), sizeof(move_t)); + return move; +} + +Blight::surface_info_t Blight::surface_info_t::from_data(data_t data){ + surface_info_t header; + memcpy(&header, data, sizeof(surface_info_t)); + return header; +} + +Blight::clipboard_t::clipboard_t(const std::string name, data_t data, size_t size) +: data{data}, + size{size}, + name{name} +{} + +const std::string Blight::clipboard_t::to_string(){ + std::string res; + if(data != nullptr && size){ + res.assign(reinterpret_cast(data.get()), size); + } + return res; +} + +bool Blight::clipboard_t::update(){ return Blight::updateClipboard(*this); } + +bool Blight::clipboard_t::set(shared_data_t data, size_t size){ + this->data = data; + this->size = size; + return Blight::setClipboard(*this); +} diff --git a/shared/libblight/types.h b/shared/libblight/types.h new file mode 100644 index 000000000..b401c4b43 --- /dev/null +++ b/shared/libblight/types.h @@ -0,0 +1,481 @@ +/*! + * \addtogroup Blight + * @{ + * \file + */ +#pragma once +#include "libblight_global.h" +#include +#include +#include +#include +#include + +namespace Blight{ + /*! + * \brief Image format of a buffer + */ + enum Format{ + Format_Invalid, + Format_Mono, + Format_MonoLSB, + Format_Indexed8, + Format_RGB32, + Format_ARGB32, + Format_ARGB32_Premultiplied, + Format_RGB16, + Format_ARGB8565_Premultiplied, + Format_RGB666, + Format_ARGB6666_Premultiplied, + Format_RGB555, + Format_ARGB8555_Premultiplied, + Format_RGB888, + Format_RGB444, + Format_ARGB4444_Premultiplied, + Format_RGBX8888, + Format_RGBA8888, + Format_RGBA8888_Premultiplied, + Format_BGR30, + Format_A2BGR30_Premultiplied, + Format_RGB30, + Format_A2RGB30_Premultiplied, + Format_Alpha8, + Format_Grayscale8, + Format_RGBX64, + Format_RGBA64, + Format_RGBA64_Premultiplied, + Format_Grayscale16, + Format_BGR888, + }; + /*! + * \brief Possible waveforms + */ + enum Waveform { + INIT = 0, + DU = 1, + GC16 = 2, + GL16 = 3, + GLR16 = 4, + GLD16 = 5, + A2 = 6, + DU4 = 7, + UNKNOWN = 8, + INIT2 = 9 + }; + /*! + * \brief Waveform to use for a repaint + */ + enum WaveformMode{ + Initialize = Waveform::INIT, + Mono = Waveform::DU, + Grayscale = Waveform::GL16, + HighQualityGrayscale = Waveform::GC16, + Highlight = Waveform::UNKNOWN + }; + /*! + * \brief Size type + */ + LIBBLIGHT_EXPORT typedef unsigned int size_t; + /*! + * \brief Surface identifier + */ + LIBBLIGHT_EXPORT typedef unsigned short surface_id_t; + /*! + * \brief Partial input event + */ + LIBBLIGHT_EXPORT typedef struct{ + /*! + * \brief Input event type + */ + __u16 type; + /*! + * \brief Input event code + */ + __u16 code; + /*! + * \brief Input event value + */ + __s32 value; + } partial_input_event_t; + /*! + * \brief Input event packet + */ + LIBBLIGHT_EXPORT typedef struct { + /*! + * \brief Device that this packet is for + */ + unsigned int device; + /*! + * \brief Partial input event + */ + partial_input_event_t event; + } event_packet_t; + /*! + * \brief Generic data pointer + */ + LIBBLIGHT_EXPORT typedef unsigned char* data_t; + /*! + * \brief Shared pointer to generic data + */ + LIBBLIGHT_EXPORT typedef std::shared_ptr shared_data_t; + struct buf_t; + /*! + * \brief Shared pointer to buffer + */ + LIBBLIGHT_EXPORT typedef std::shared_ptr shared_buf_t; + /*! + * \brief Clipboard instance + */ + LIBBLIGHT_EXPORT typedef struct clipboard_t { + /*! + * \brief Data + */ + shared_data_t data; + /*! + * \brief Size of data + */ + size_t size; + /*! + * \brief Name of clipboard + */ + std::string name; + /*! + * \brief Create a new clipboard instance + * \param name Name of clipboard + * \param data Data + * \param size Data size + */ + clipboard_t(const std::string name, data_t data = nullptr, size_t size = 0); + /*! + * \brief Convert the data to a string + * \return The clipboard data as a string + */ + const std::string to_string(); + /*! + * \brief Get the latest data for the clipboard + * \return If the clipboard was able to update + * \retval true The clipboard was updated + * \retval false An error occurred + */ + bool update(); + /*! + * \brief Set the data for the clipboard + * \param data Data + * \param size Size of data + * \return If the clipboard was able to be set + * \retval true The clipboard data was set + * \retval false An error occurred + */ + bool set(shared_data_t data, size_t size); + /*! + * \brief Set the data for the clipboard + * \param data Data + * \param size Size of data + * \return If the clipboard was able to be set + * \retval true The clipboard data was set + * \retval false An error occurred + */ + inline bool set(const char* data, size_t size){ + auto buf = new unsigned char[size]; + memcpy(buf, data, size); + return set(shared_data_t(buf), size); + } + /*! + * \brief Set the data for the clipboard + * \param data Data + * \param size Size of data + * \return If the clipboard was able to be set + * \retval true The clipboard data was set + * \retval false An error occurred + */ + inline bool set(const data_t data, size_t size){ return set((char*)data, size); } + /*! + * \brief Set the data for the clipboard to a string + * \param data String to set the data to + * \return If the clipboard was able to be set + * \retval true The clipboard data was set + * \retval false An error occurred + */ + inline bool set(const std::string& data){ return set(data.data(), data.size()); } + } clipboard_t; + /*! + * \brief A buffer used to represent a surface + */ + LIBBLIGHT_EXPORT typedef struct buf_t{ + /*! + * \brief File descriptor for the buffer + */ + int fd; + /*! + * \brief X coordinate of the buffer + */ + int x; + /*! + * \brief Y coordinate of the buffer + */ + int y; + /*! + * \brief Width of the buffer + */ + int width; + /*! + * \brief Height of the buffer + */ + int height; + /*! + * \brief Bytes per line in the buffer + */ + int stride; + /*! + * \brief Image format of the buffer + */ + Format format; + /*! + * \brief Memory mapped data of the buffer + */ + data_t data; + /*! + * \brief UUID of the buffer + */ + std::string uuid; + /*! + * \brief Surface identifier + */ + surface_id_t surface; + /*! + * \brief Size of the buffer + * \return + */ + size_t size(); + /*! + * \brief Close the buffer + * \return Negative number if there was an error + * \retval 0 No error while closing the buffer + */ + int close(); + ~buf_t(); + /*! + * \brief Clone a buffer + * \return Cloned buffer if there was no error + */ + std::optional clone(); + static shared_buf_t new_ptr(); + static std::string new_uuid(); + } buf_t; + /*! + * \brief Message type + */ + enum MessageType{ + Invalid, + Ack, + Ping, + Repaint, + Move, + Info, + Delete, + List, + Raise, + Lower, + Wait, + Focus + }; + /*! + * \brief Message header + */ + LIBBLIGHT_EXPORT typedef struct header_t{ + /*! + * \brief Message type + */ + MessageType type; + /*! + * \brief Unique identifier for this message + */ + unsigned int ackid; + /*! + * \brief Size of data + */ + unsigned long size; + /*! + * \brief Get a header from data + * \param data Data + * \return Header + */ + static header_t from_data(data_t data); + /*! + * \brief Get a header from data + * \param data Data + * \return Header + */ + static header_t from_data(char* data); + /*! + * \brief Get a header from data + * \param data Data + * \return header + */ + static header_t from_data(void* data); + /*! + * \brief Create a new invalid header + * \return Invalid header object + */ + static header_t new_invalid(); + } header_t; + struct message_t; + /*! + * \brief Shared pointer to a message + */ + LIBBLIGHT_EXPORT typedef std::shared_ptr message_ptr_t; + /*! + * \brief Message object + */ + LIBBLIGHT_EXPORT typedef struct message_t{ + /*! + * \brief Message header + */ + header_t header; + /*! + * \brief Message data + */ + shared_data_t data; + /*! + * \brief Get a message from data + * \param _data Data + * \return + */ + static message_t from_data(data_t _data); + /*! + * \brief Get a message from data + * \param data Data + * \return + */ + static message_t from_data(char* data); + /*! + * \brief Get a message from data + * \param data Data + * \return + */ + static message_t from_data(void* data); + /*! + * \brief Create an Ack header from a message + * \param message Message to create ack from + * \param size Size of response data + * \return Ack header + */ + static header_t create_ack(message_t* message, size_t size = 0); + /*! + * \brief Create an Ack header from a message + * \param message Message to create ack from + * \param size Size of response data + * \return Ack header + */ + static header_t create_ack(const message_t& message, size_t size = 0); + /*! + * \brief Read a message from a socket + * \param fd Socket descriptor + * \return Message + */ + static message_ptr_t from_socket(int fd); + /*! + * \brief Create a new invalid and empty message + * \return Invalid and empty message + */ + static message_ptr_t new_ptr(); + } message_t; + /*! + * \brief Repaint message data + */ + LIBBLIGHT_EXPORT typedef struct repaint_t{ + /*! + * \brief x X offset + */ + int x; + /*! + * \brief y Y offset + */ + int y; + /*! + * \brief width Width + */ + int width; + /*! + * \brief height Height + */ + int height; + /*! + * \brief waveform Waveform to use + */ + WaveformMode waveform; + /*! + * \brief marker Marker to use + */ + unsigned int marker; + /*! + * \brief identifier Surface identifier + */ + surface_id_t identifier; + /*! + * \brief Get the repaint message data from a message + * \param message Message + * \return Repaint message data + */ + static repaint_t from_message(const message_t* message); + } repaint_t; + /*! + * \brief Move message data + */ + LIBBLIGHT_EXPORT typedef struct move_t{ + /*! + * \brief identifier Surface identifier + */ + surface_id_t identifier; + /*! + * \brief x X coordinate + */ + int x; + /*! + * \brief y Y coordinate + */ + int y; + /*! + * \brief Get the move message data from a message + * \param message Message + * \return Move message data + */ + static move_t from_message(const message_t* message); + } move_t; + /*! + * \brief Surface information message data + */ + LIBBLIGHT_EXPORT typedef struct surface_info_t{ + /*! + * \brief x X coordinate + */ + int x; + /*! + * \brief y Y coordiante + */ + int y; + /*! + * \brief width Width + */ + int width; + /*! + * \brief height Height + */ + int height; + /*! + * \brief stride Bytes per line + */ + int stride; + /*! + * \brief format Image format + */ + Format format; + /*! + * \brief Get the surface information message data from data + * \param data Data + * \return Surface message data + */ + static surface_info_t from_data(data_t data); + } surface_info_t; +} +/*! @} */ diff --git a/shared/libblight_client/libblight_client.pro b/shared/libblight_client/libblight_client.pro new file mode 100644 index 000000000..f4726e76a --- /dev/null +++ b/shared/libblight_client/libblight_client.pro @@ -0,0 +1,32 @@ +TARGET = blight_client +TEMPLATE = lib + +include(../../qmake/common.pri) + +CONFIG += hide_symbols + +QT = + +HEADERS += + +SOURCES += main.cpp + +LIBS += -lrt -ldl -Wl,--exclude-libs,ALL +LIBS += -lsystemd + +QMAKE_LFLAGS += -Wl,--as-needed +QMAKE_LFLAGS += -Wl,--version-script $$_PRO_FILE_PWD_/main.map +QMAKE_CFLAGS += -fno-builtin +QMAKE_CXXFLAGS += -fno-builtin + +target.path += /opt/lib +INSTALLS += target + +INCLUDEPATH += ../../shared/mxcfb + +DEFINES += SHIM_INPUT_FOR_PRELOAD + +include(../../qmake/libblight.pri) + +DISTFILES += \ + main.map diff --git a/shared/libblight_client/main.cpp b/shared/libblight_client/main.cpp new file mode 100644 index 000000000..ab71399df --- /dev/null +++ b/shared/libblight_client/main.cpp @@ -0,0 +1,1072 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + static bool IS_INITIALIZED = false; + static bool FAILED_INIT = true; + static bool DO_HANDLE_FB = true; + static bool FAKE_RM1 = false; + static unsigned int INPUT_BATCH_SIZE = 0; + static Blight::shared_buf_t blightBuffer = Blight::buf_t::new_ptr(); + static Blight::Connection* blightConnection = nullptr; + static std::map inputFds; + static std::map inputDeviceMap; + static ssize_t(*func_write)(int, const void*, size_t); + static ssize_t(*func_writev)(int, const iovec*, int); + static ssize_t(*func_writev64)(int, const iovec*, int); + static ssize_t(*func_pwrite)(int, const void*, size_t, int); + static ssize_t(*func_pwrite64)(int, const void*, size_t, int); + static ssize_t(*func_pwritev)(int, const iovec*, int, int); + static ssize_t(*func_pwritev64)(int, const iovec*, int, int); + static int(*func_open)(const char*, int, ...); + static int(*func_ioctl)(int, unsigned long, ...); + static int(*func_close)(int); + static int(*func_msgget)(key_t, int); + static void*(*func_mmap)(void*, size_t, int, int, int, __off_t); + static int msgq = -1; + + bool __is_fb(int fd){ return DO_HANDLE_FB && blightBuffer->fd > 0 && blightBuffer->fd == fd; } + + int __open_input_socketpair(int flags, int fds[2]){ + int socketFlags = SOCK_STREAM; + if(flags & O_NONBLOCK || flags & O_NDELAY){ + socketFlags |= SOCK_NONBLOCK; + } + return ::socketpair(AF_UNIX, socketFlags, 0, fds); + } + + void __sendEvents(unsigned int device, std::vector& queue){ + if(queue.empty()){ + return; + } + timeval time; + ::gettimeofday(&time, NULL); + std::vector data(queue.size()); + bool allow_power_button = getenv("OXIDE_PRELOAD_ALLOW_POWER_BUTTON") != nullptr; + for(unsigned int i = 0; i < queue.size(); i++){ + auto& event = queue[i]; + if( + !allow_power_button + && event.type == EV_KEY + && event.code == KEY_POWER + ){ + event.value = 0; + } + data[i] = input_event{ + .time = time, + .type = event.type, + .code = event.code, + .value = event.value + }; + } + auto fd = inputFds[device][0]; + // if(!Blight::send_blocking( + // fd, + // reinterpret_cast(data.data()), + // sizeof(input_event) * data.size() + // )){ + // _CRIT("%d input events failed to send: %s", data.size(), std::strerror(errno)); + // }else{ + // _DEBUG("Sent %d input events", data.size()); + // } + auto res = func_write(fd, data.data(), sizeof(input_event) * data.size()); + if(res < 0){ + _CRIT("%d input events failed to send: %s", data.size(), std::strerror(errno)); + }else{ + _DEBUG("Sent %d input events", data.size()); + } + queue.clear(); + } + + void __readInput(){ + _INFO("%s", "InputWorker starting"); + prctl(PR_SET_NAME, "InputWorker\0", 0, 0, 0); + nice(-10); + auto fd = blightConnection->input_handle(); + std::map> events; + while(fd > 0 && blightConnection != nullptr && getenv("OXIDE_PRELOAD_DISABLE_INPUT") == nullptr){ + auto maybe = blightConnection->read_event(); + if(!maybe.has_value()){ + if(errno != EAGAIN && errno != EINTR){ + _WARN("[InputWorker] Failed to read message %s", std::strerror(errno)); + break; + } + for(auto& item : events){ + __sendEvents(item.first, item.second); + } + _DEBUG("Waiting for next input event"); + if(!Blight::wait_for_read(fd) && errno != EAGAIN){ + _WARN("[InputWorker] Failed to wait for next input event %s", std::strerror(errno)); + break; + } + continue; + } + const auto& device = maybe.value().device; + if(!inputFds.contains(device)){ + _INFO("Ignoring event for unopened device: %d", device); + continue; + } + if(inputFds[device][0] < 0){ + _INFO("Ignoring event for invalid device: %d", device); + continue; + } + auto& event = maybe.value().event; + auto& queue = events[device]; + queue.push_back(event); + // TODO - input can get stuck if INPUT_BATCH_SIZE is set and not enough events have come through. + // Some sort of timeout should be used to force the queue to flush. + if( + ( + INPUT_BATCH_SIZE + && queue.size() >= INPUT_BATCH_SIZE + ) + || ( + !INPUT_BATCH_SIZE + && event.type == EV_SYN + && event.code == SYN_REPORT + ) + ){ + __sendEvents(device, queue); + } + } + _INFO("%s", "InputWorker quitting"); + } + + int __fb_send_update(mxcfb_update_data* update){ + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_SEND_UPDATE") + Blight::ClockWatch cw; + if(!blightBuffer->surface){ + Blight::addSurface(blightBuffer); + } + if(!blightBuffer->surface){ + _CRIT("Failed to create surface: %s", std::strerror(errno)); + std::exit(errno); + } + auto region = update->update_region; + auto maybe = blightConnection->repaint( + blightBuffer, + region.left, + region.top, + region.width, + region.height, + (Blight::WaveformMode)update->waveform_mode, + update->update_marker + ); + if(maybe.has_value()){ + maybe.value()->wait(); + } + _DEBUG("ioctl /dev/fb0 MXCFB_SEND_UPDATE done: %f", cw.elapsed()) + // TODO - notify on rM2 for screensharing + return 0; + } + + int __fb_wait(mxcfb_update_marker_data* update){ + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_WAIT_FOR_UPDATE_COMPLETE"); + Blight::ClockWatch cw; + blightConnection->waitForMarker(update->update_marker); + _DEBUG("ioctl /dev/fb0 MXCFB_WAIT_FOR_UPDATE_COMPLETE done: %f", cw.elapsed()); + return 0; + } + + int __fb_get_vscreeninfo(fb_var_screeninfo* screenInfo){ + _DEBUG("%s", "ioctl /dev/fb0 FBIOGET_VSCREENINFO"); + screenInfo->xres = blightBuffer->width; + screenInfo->yres = blightBuffer->height; + screenInfo->xoffset = 0; + screenInfo->yoffset = 0; + screenInfo->xres_virtual = blightBuffer->width; + screenInfo->yres_virtual = blightBuffer->height; + screenInfo->bits_per_pixel = blightBuffer->stride / blightBuffer->width * 8; + // TODO - determine the following from the buffer format + // Format_RGB16 / RGB565 + screenInfo->grayscale = 0; + screenInfo->red.offset = 11; + screenInfo->red.length = 5; + screenInfo->red.msb_right = 0; + screenInfo->green.offset = 5; + screenInfo->green.length = 6; + screenInfo->green.msb_right = 0; + screenInfo->blue.offset = 0; + screenInfo->blue.length = 5; + screenInfo->blue.msb_right = 0; + // TODO what should this even be? + screenInfo->nonstd = 0; + screenInfo->activate = FB_ACTIVATE_FORCE; + // It would be cool to have the actual mm width/height here + screenInfo->height = 0; + screenInfo->width = 0; + screenInfo->accel_flags = 0; + // screenInfo->pixclock = 28800; // Stick with what is reported + screenInfo->left_margin = 0; + screenInfo->right_margin = 0; + screenInfo->upper_margin = 0; + screenInfo->lower_margin = 0; + screenInfo->hsync_len = 1; + screenInfo->vsync_len = 1; + screenInfo->sync = 0; + screenInfo->vmode = 0; + screenInfo->rotate = 0; + screenInfo->colorspace = 0; + screenInfo->reserved[0] = 0; + screenInfo->reserved[1] = 0; + screenInfo->reserved[2] = 0; + screenInfo->reserved[3] = 0; + return 0; + } + + int __fb_get_fscreeninfo(fb_fix_screeninfo* screenInfo){ + _DEBUG("%s", "ioctl /dev/fb0 FBIOGET_FSCREENINFO"); + constexpr char fb_id[] = "mxcfb"; + memcpy(screenInfo->id, fb_id, sizeof(fb_id)); + // TODO determine all the values dynamically + screenInfo->smem_len = blightBuffer->size(); + screenInfo->smem_start = (unsigned long)blightBuffer->data; + screenInfo->type = 0; + screenInfo->type_aux = 0; + screenInfo->visual = FB_VISUAL_TRUECOLOR; + screenInfo->xpanstep = 8; + screenInfo->ypanstep = 0; + screenInfo->ywrapstep = 5772; + screenInfo->line_length = blightBuffer->stride; + screenInfo->mmio_start = 0; + screenInfo->mmio_len = 0; + screenInfo->accel = 0; + screenInfo->reserved[0] = 0; + screenInfo->reserved[1] = 0; + return 0; + } + + int __fb_put_vscreeninfo(fb_var_screeninfo* screenInfo){ + _DEBUG("%s", "ioctl /dev/fb0 FBIOPUT_VSCREENINFO"); + // TODO - Explore allowing some screen info updating + _DEBUG( + "\n" + "res: %dx%d\n" + "res_virtual: %dx%d\n" + "offset: %d, %d\n" + "bits_per_pixel: %d\n" + "grayscale: %d\n" + "red: %d, %d, %d\n" + "green: %d, %d, %d\n" + "blue: %d, %d, %d\n" + "tansp: %d, %d, %d\n" + "nonstd: %d\n" + "activate: %d\n" + "size: %dx%dmm\n" + "accel_flags: %d\n" + "pixclock: %d\n" + "margins: %d %d %d %d\n" + "hsync_len: %d\n" + "vsync_len: %d\n" + "sync: %d\n" + "vmode: %d\n" + "rotate: %d\n" + "colorspace: %d\n" + "reserved: %d, %d, %d, %d\n", + screenInfo->xres, + screenInfo->yres, + screenInfo->xres_virtual, + screenInfo->yres_virtual, + screenInfo->xoffset, + screenInfo->yoffset, + screenInfo->bits_per_pixel, + screenInfo->grayscale, + screenInfo->red.offset, + screenInfo->red.length, + screenInfo->red.msb_right, + screenInfo->green.offset, + screenInfo->green.length, + screenInfo->green.msb_right, + screenInfo->blue.offset, + screenInfo->blue.length, + screenInfo->blue.msb_right, + screenInfo->transp.offset, + screenInfo->transp.length, + screenInfo->transp.msb_right, + screenInfo->nonstd, + screenInfo->activate, + screenInfo->height, + screenInfo->width, + screenInfo->accel_flags, + screenInfo->pixclock, + screenInfo->left_margin, + screenInfo->right_margin, + screenInfo->upper_margin, + screenInfo->lower_margin, + screenInfo->hsync_len, + screenInfo->vsync_len, + screenInfo->sync, + screenInfo->vmode, + screenInfo->rotate, + screenInfo->colorspace, + screenInfo->reserved[0], + screenInfo->reserved[1], + screenInfo->reserved[2], + screenInfo->reserved[3] + ); + // TODO allow resizing? + return 0; + } + + int __fb_ioctl(unsigned long request, char* ptr){ + switch(request){ + // Look at linux/fb.h and mxcfb.h for more possible request types + // https://www.kernel.org/doc/html/latest/fb/api.html + case MXCFB_SEND_UPDATE:{ + return __fb_send_update(reinterpret_cast(ptr)); + } + case MXCFB_WAIT_FOR_UPDATE_COMPLETE:{ + return __fb_wait(reinterpret_cast(ptr)); + } + case FBIOGET_VSCREENINFO:{ + int fd = func_open("/dev/fb0", O_RDONLY, 0); + if(fd == -1){ + return -1; + } + if(func_ioctl(fd, request, ptr) == -1){ + return -1; + } + return __fb_get_vscreeninfo(reinterpret_cast(ptr)); + } + case FBIOGET_FSCREENINFO:{ + int fd = func_open("/dev/fb0", O_RDONLY, 0); + if(fd == -1){ + return -1; + } + if(func_ioctl(fd, request, ptr) == -1){ + return -1; + } + return __fb_get_fscreeninfo(reinterpret_cast(ptr)); + } + case FBIOPUT_VSCREENINFO:{ + return __fb_put_vscreeninfo(reinterpret_cast(ptr)); + } + case MXCFB_SET_AUTO_UPDATE_MODE: + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_SET_AUTO_UPDATE_MODE"); + return 0; + case MXCFB_SET_UPDATE_SCHEME: + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_SET_UPDATE_SCHEME"); + return 0; + case MXCFB_ENABLE_EPDC_ACCESS: + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_ENABLE_EPDC_ACCESS"); + return 0; + case MXCFB_DISABLE_EPDC_ACCESS: + _DEBUG("%s", "ioctl /dev/fb0 MXCFB_DISABLE_EPDC_ACCESS"); + return 0; + default: + _WARN( + "UNHANDLED Fb IOCTL %lu %c %lu %lu %lu", + _IOC_DIR(request), + (char)_IOC_TYPE(request), + _IOC_NR(request), + _IOC_SIZE(request), + request + ); + return 0; + } + } + + int __input_ioctlv(int fd, unsigned long request, char* ptr){ + switch(request){ + case EVIOCGRAB: return 0; + case EVIOCREVOKE: return 0; + default: + return func_ioctl(fd, request, ptr); + } + } + + void __realpath(const char* pathname, std::string& path){ + auto absolutepath = realpath(pathname, NULL); + path = absolutepath; + free(absolutepath); + } + + int __open(const char* pathname, int flags){ + if(!IS_INITIALIZED){ + return -2; + } + std::string actualpath(pathname); + if(std::filesystem::exists(actualpath)){ + __realpath(pathname, actualpath); + } + int res = -2; + if(FAKE_RM1 && actualpath == "/sys/devices/soc0/machine"){ + int fd = memfd_create("machine", MFD_ALLOW_SEALING); + std::string data("reMarkable 1.0"); + // Don't include trailing null + func_write(fd, data.data(), data.size()); + fcntl( + fd, + F_ADD_SEALS, + F_SEAL_SEAL + | F_SEAL_SHRINK + | F_SEAL_GROW + | F_SEAL_WRITE + | F_SEAL_FUTURE_WRITE + ); + lseek(fd, 0, SEEK_SET); + return fd; + }else if( + DO_HANDLE_FB + && ( + actualpath == "/dev/fb0" + || actualpath == "/dev/shm/swtfb.01" + ) + ){ + if(blightBuffer->format != Blight::Format::Format_Invalid){ + return blightBuffer->fd; + } + auto surfaceIds = blightConnection->surfaces(); + if(!surfaceIds.empty()){ + for(auto& identifier : surfaceIds){ + auto maybe = blightConnection->getBuffer(identifier); + if(!maybe.has_value()){ + continue; + } + auto buffer = maybe.value(); + if(buffer->data == nullptr){ + continue; + } + if( + buffer->x != 0 + || buffer->y != 0 + || buffer->width != 1404 + || buffer->height != 1872 + || buffer->stride != 2808 + || buffer->format != Blight::Format::Format_RGB16 + ){ + continue; + } + _INFO("Reusing existing surface: %s", identifier); + blightBuffer = buffer; + break; + } + } + if(blightBuffer->format == Blight::Format::Format_Invalid){ + /// Emulate rM1 screen + auto maybe = Blight::createBuffer( + 0, + 0, + 1404, + 1872, + 2808, + Blight::Format::Format_RGB16 + ); + if(!maybe.has_value()){ + return -1; + } + blightBuffer = maybe.value(); + // We don't ever plan on resizing, and we shouldn't let anything try + int flags = fcntl(blightBuffer->fd, F_GET_SEALS); + fcntl( + blightBuffer->fd, + F_ADD_SEALS, flags | F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW + ); + res = blightBuffer->fd; + if(res < 0){ + _CRIT("Failed to create buffer: %s", std::strerror(errno)); + std::exit(errno); + } + // Initialize the buffer with white + memset((std::uint16_t*)blightBuffer->data, std::uint16_t(0xFFFFFFFF), 2808 * 1872); + _INFO("Created buffer %s on fd %d", blightBuffer->uuid.c_str(), blightBuffer->fd); + return res; + } + }else if(actualpath.starts_with("/dev/input/event")){ + _INFO("Opening event device: %s", actualpath.c_str()); + if(blightConnection->input_handle() > 0){ + if(inputFds.empty() && getenv("OXIDE_PRELOAD_DISABLE_INPUT") == nullptr){ + new std::thread(__readInput); + } + std::string path(basename(actualpath.c_str())); + try{ + unsigned int device = stol(path.substr(5)); + if(inputFds.contains(device)){ + res = inputFds[device][1]; + }else{ + int fds[2]; + // TODO - what if they open it a second time with different flags? + res = __open_input_socketpair(flags, fds); + if(res < 0){ + _WARN("Failed to open event socket stream %s", std::strerror(errno)); + }else{ + inputFds[device][0] = fds[0]; + res = inputFds[device][1] = fds[1]; + inputDeviceMap[res] = func_open(actualpath.c_str(), O_RDWR, 0); + } + } + } + catch(std::invalid_argument&){ + _WARN("Resolved event device name invalid: %s", path.c_str()); + res = -1; + } + catch(std::out_of_range&){ + _WARN("Resolved event device number out of range: %s", path.c_str()); + res = -1; + } + }else{ + _WARN("Could not open connection input stream", ""); + res = -1; + } + } + if(res == -1){ + errno = EIO; + } + return res; + } +} +namespace swtfb { + struct xochitl_data { + int x1; + int y1; + int x2; + int y2; + + int waveform; + int flags; + }; + struct wait_sem_data { + char sem_name[512]; + }; + struct swtfb_update { + long mtype; + struct { + union { + xochitl_data xochitl_update; + struct mxcfb_update_data update; + wait_sem_data wait_update; + }; + } mdata; + }; +} +extern "C" { + __attribute__((visibility("default"))) + int msgget(key_t key, int msgflg){ + static const auto func_msgget = (int(*)(key_t, int))dlsym(RTLD_NEXT, "msgget"); + if(!IS_INITIALIZED || !DO_HANDLE_FB){ + return func_msgget(key, msgflg); + } + // Catch rm2fb ipc + if(key == 0x2257c){ + // inject our own ipc here + if(msgq == -1){ + msgq = func_msgget(0x2257d, msgflg); + } + return msgq; + } + return func_msgget(key, msgflg); + } + + __attribute__((visibility("default"))) + int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg){ + static const auto func_msgsnd = (int(*)(int, const void*, size_t, int))dlsym(RTLD_NEXT, "msgsnd"); + if(!IS_INITIALIZED || !DO_HANDLE_FB){ + return func_msgsnd(msqid, msgp, msgsz, msgflg); + } + if(msqid == msgq){ + if(!blightBuffer->surface){ + Blight::addSurface(blightBuffer); + } + if(!blightBuffer->surface){ + _CRIT("Failed to create surface: %s", std::strerror(errno)); + std::exit(errno); + return -1; + } + _DEBUG("%s", "rm2fb ipc repaint"); + auto buf = (swtfb::swtfb_update*)msgp; + auto region = buf->mdata.update.update_region; + blightConnection->repaint( + blightBuffer, + region.left, + region.top, + region.width, + region.height + ); + return 0; + } + return func_msgsnd(msqid, msgp, msgsz, msgflg); + } + + __attribute__((visibility("default"))) + int open64(const char* pathname, int flags, ...){ + static const auto func_open64 = (int(*)(const char*, int, ...))dlsym(RTLD_NEXT, "open64"); + if(!IS_INITIALIZED){ + va_list args; + va_start(args, flags); + int fd = func_open64(pathname, flags, args); + va_end(args); + return fd; + } + _DEBUG("open64 %s", pathname); + int fd = __open(pathname, flags); + if(fd == -2){ + va_list args; + va_start(args, flags); + fd = func_open64(pathname, flags, args); + va_end(args); + } + _DEBUG("opened %s with fd %d", pathname, fd); + return fd; + } + + __attribute__((visibility("default"))) + int openat(int dirfd, const char* pathname, int flags, ...){ + static const auto func_openat = (int(*)(int, const char*, int, ...))dlsym(RTLD_NEXT, "openat"); + if(!IS_INITIALIZED){ + va_list args; + va_start(args, flags); + int fd = func_openat(dirfd, pathname, flags, args); + va_end(args); + return fd; + } + _DEBUG("openat %s", pathname); + int fd = __open(pathname, flags); + if(fd == -2){ + DIR* save = opendir("."); + fchdir(dirfd); + char path[PATH_MAX+1]; + getcwd(path, PATH_MAX); + fchdir(::dirfd(save)); + closedir(save); + std::string fullpath(path); + fullpath += "/"; + fullpath += pathname; + fd = __open(fullpath.c_str(), flags); + } + if(fd == -2){ + va_list args; + va_start(args, flags); + fd = func_openat(dirfd, pathname, flags, args); + va_end(args); + } + _DEBUG("opened %s with fd %d", pathname, fd); + return fd; + } + + __attribute__((visibility("default"))) + int open(const char* pathname, int flags, ...){ + if(!IS_INITIALIZED){ + va_list args; + va_start(args, flags); + int fd = func_open(pathname, flags, args);; + va_end(args); + return fd; + } + _DEBUG("open %s", pathname); + int fd = __open(pathname, flags); + if(fd == -2){ + va_list args; + va_start(args, flags); + fd = func_open(pathname, flags, args); + va_end(args); + } + _DEBUG("opened %s with fd %d", pathname, fd); + return fd; + } + + __attribute__((visibility("default"))) + int close(int fd){ + if(IS_INITIALIZED){ + _DEBUG("close %d", fd); + if(__is_fb(fd)){ + // Maybe actually close it? + return 0; + } + if(inputDeviceMap.contains(fd)){ + // Maybe actually close it? + return 0; + } + } + static const auto func_close = (int(*)(int))dlsym(RTLD_NEXT, "close"); + return func_close(fd); + } + + __attribute__((visibility("default"))) + int ioctl(int fd, unsigned long request, ...){ + va_list args; + va_start(args, request); + char* ptr = va_arg(args, char*); + if(IS_INITIALIZED){ + if(__is_fb(fd)){ + int res = __fb_ioctl(request, ptr); + va_end(args); + return res; + } + if(inputDeviceMap.contains(fd)){ + int res = __input_ioctlv(inputDeviceMap[fd], request, ptr); + va_end(args); + return res; + } + } + int res = func_ioctl(fd, request, ptr); + va_end(args); + return res; + } + + __attribute__((visibility("default"))) + void* mmap(void* addr, size_t len, int prot, int flags, int fd, __off_t offset){ + _DEBUG( + "mmap 0x%u %lld 0x%02x 0x%02x %d %d", + static_cast(reinterpret_cast(addr)), + len, + prot, + flags, + fd, + offset + ); + // if(IS_INITIALIZED && __is_fb(fd)){ + // unsigned long size = len + offset; + // if(size > blightBuffer->size()){ + // _CRIT( + // "Requested size + offset is larger than the buffer: %d < %d + %d", + // blightBuffer->size(), + // size, + // offset + // ); + // errno = EINVAL; + // return MAP_FAILED; + // } + // return &blightBuffer->data[offset]; + // } + return func_mmap(addr, len, prot, flags, fd, offset); + } + + __attribute__((visibility("default"))) + int munmap(void* addr, size_t len){ +// _DEBUG( +// "munmap 0x%u %s", +// (unsigned)addr, +// len +// ); +// if(IS_INITIALIZED){ +// // TODO - handle when pointer + len are inside the data +// if(DO_HANDLE_FB && addr == blightBuffer->data){ +// // Maybe actually close it? +// return 0; +// } +// } + static const auto func_munmap = (int(*)(void*, size_t))dlsym(RTLD_NEXT, "munmap"); + return func_munmap(addr, len); + } + + __attribute__((visibility("default"))) + ssize_t _write(int fd, const void* buf, size_t n){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_write(fd, buf, n); + } + if(IS_INITIALIZED){ + _DEBUG("write %d %zu", fd, n); + if(__is_fb(fd)){ + auto res = func_write(fd, buf, n); + return res; + } + } + return func_write(fd, buf, n); + } + __asm__(".symver _write, write@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _writev(int fd, const iovec* iov, int iovcnt){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_writev(fd, iov, iovcnt); + } + if(IS_INITIALIZED){ + _DEBUG("writev %d %d", fd, iovcnt); + if(__is_fb(fd)){ + auto res = func_writev(fd, iov, iovcnt); + return res; + } + } + return func_writev(fd, iov, iovcnt); + } + __asm__(".symver _writev, writev@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _writev64(int fd, const iovec* iov, int iovcnt){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_writev64(fd, iov, iovcnt); + } + if(IS_INITIALIZED){ + _DEBUG("writv64 %d %d", fd, iovcnt); + if(__is_fb(fd)){ + auto res = func_writev64(fd, iov, iovcnt); + return res; + } + } + return func_writev64(fd, iov, iovcnt); + } + __asm__(".symver _writev64, writev64@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _pwrite(int fd, const void* buf, size_t n, int offset){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_pwrite(fd, buf, n, offset); + } + if(IS_INITIALIZED){ + _DEBUG("pwrite %d %zu %d", fd, n, offset); + if(__is_fb(fd)){ + auto res = func_pwrite(fd, buf, n, offset); + return res; + } + } + return func_pwrite(fd, buf, n, offset); + } + __asm__(".symver _pwrite, pwrite@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _pwrite64(int fd, const void* buf, size_t n, int offset){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_pwrite64(fd, buf, n, offset); + } + if(IS_INITIALIZED){ + _DEBUG("pwrite64 %d %zu %d", fd, n, offset); + if(__is_fb(fd)){ + auto res = func_pwrite64(fd, buf, n, offset); + return res; + } + } + return func_pwrite64(fd, buf, n, offset); + } + __asm__(".symver _pwrite64, pwrite64@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _pwritev(int fd, const iovec* iov, int iovcnt, int offset){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_pwritev(fd, iov, iovcnt, offset); + } + if(IS_INITIALIZED){ + _DEBUG("pwritev %d %d %d", fd, iovcnt, offset); + if(__is_fb(fd)){ + auto res = func_pwritev(fd, iov, iovcnt, offset); + return res; + } + } + return func_pwritev(fd, iov, iovcnt, offset); + } + __asm__(".symver _pwritev, pwritev@GLIBC_2.4"); + + __attribute__((visibility("default"))) + ssize_t _pwritev64(int fd, const iovec* iov, int iovcnt, int offset){ + if(fd < 3){ + // No need to handle stdout/stderr writes + return func_pwritev64(fd, iov, iovcnt, offset); + } + if(IS_INITIALIZED){ + _DEBUG("pwritev64 %d %d %d", fd, iovcnt, offset); + if(__is_fb(fd)){ + auto res = func_pwritev64(fd, iov, iovcnt, offset); + return res; + } + } + return func_pwritev64(fd, iov, iovcnt, offset); + } + __asm__(".symver _pwritev64, pwritev64@GLIBC_2.4"); + + __attribute__((visibility("default"))) + int setenv(const char* name, const char* value, int overwrite){ + static const auto orig_setenv = (bool(*)(const char*, const char*, int))dlsym(RTLD_NEXT, "setenv"); + if(IS_INITIALIZED && getenv("OXIDE_PRELOAD_FORCE_QT") != nullptr){ + if( + strcmp(name, "QMLSCENE_DEVICE") == 0 + || strcmp(name, "QT_QUICK_BACKEND") == 0 + || strcmp(name, "QT_QPA_PLATFORM") == 0 + || strcmp(name, "QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS") == 0 + || strcmp(name, "QT_QPA_GENERIC_PLUGINS") == 0 + || strcmp(name, "QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS") == 0 + || strcmp(name, "QT_QPA_EVDEV_MOUSE_PARAMETERS") == 0 + || strcmp(name, "QT_QPA_EVDEV_KEYBOARD_PARAMETERS") == 0 + || strcmp(name, "QT_PLUGIN_PATH") == 0 + ){ + _DEBUG("IGNORED setenv", name, value); + return 0; + } + } + _DEBUG("setenv", name, value); + return orig_setenv(name, value, overwrite); + } + + __attribute__((visibility("default"))) + void _ZN6QImageC1EiiNS_6FormatE(void* data, int width, int height, int format) { + static bool FIRST_ALLOC = true; + static const auto qImageCtor = ( + void(*)(void*, int, int, int) + )dlsym(RTLD_NEXT, "_ZN6QImageC1EiiNS_6FormatE"); + static const auto qImageCtorWithBuffer = ( + void(*)(void*, uint8_t*, int32_t, int32_t, int32_t, int, void(*)(void*), void*) + )dlsym(RTLD_NEXT, "_ZN6QImageC1EPhiiiNS_6FormatEPFvPvES2_"); + if(width == blightBuffer->width && height == blightBuffer->height && FIRST_ALLOC) { + _INFO("Replacing image with buffer"); + FIRST_ALLOC = false; + qImageCtorWithBuffer( + data, + (uint8_t *)blightBuffer->data, + blightBuffer->width, + blightBuffer->height, + blightBuffer->stride, + format, + nullptr, + nullptr + ); + return; + } + qImageCtor(data, width, height, format); + } + + void __attribute__ ((constructor)) init(void); + void init(void){ + func_write = (ssize_t(*)(int, const void*, size_t))dlvsym(RTLD_NEXT, "write", "GLIBC_2.4"); + func_writev = (ssize_t(*)(int, const iovec*, int))dlvsym(RTLD_NEXT, "writev", "GLIBC_2.4"); + func_writev64 = (ssize_t(*)(int, const iovec*, int))dlvsym(RTLD_NEXT, "writev64", "GLIBC_2.4"); + func_pwrite = (ssize_t(*)(int, const void*, size_t, int))dlvsym(RTLD_NEXT, "pwrite", "GLIBC_2.4"); + func_pwrite64 = (ssize_t(*)(int, const void*, size_t, int))dlvsym(RTLD_NEXT, "pwrite64", "GLIBC_2.4"); + func_pwritev = (ssize_t(*)(int, const iovec*, int, int))dlvsym(RTLD_NEXT, "pwritev", "GLIBC_2.4"); + func_pwritev64 = (ssize_t(*)(int, const iovec*, int, int))dlvsym(RTLD_NEXT, "pwritev64", "GLIBC_2.4"); + func_open = (int(*)(const char*, int, ...))dlsym(RTLD_NEXT, "open"); + func_ioctl = (int(*)(int, unsigned long, ...))dlsym(RTLD_NEXT, "ioctl"); + func_close = (int(*)(int))dlsym(RTLD_NEXT, "close"); + func_msgget = (int(*)(key_t, int))dlsym(RTLD_NEXT, "msgget"); + func_mmap = (void*(*)(void*, size_t, int, int, int, __off_t))dlsym(RTLD_NEXT, "mmap"); + } + + static void _libhook_init() __attribute__((constructor)); + static void _libhook_init(){ + auto debugLevel = getenv("OXIDE_PRELOAD_DEBUG"); + if(debugLevel != nullptr){ + try{ + BLIGHT_DEBUG_LOGGING = std::stoi(debugLevel); + } + catch(std::invalid_argument&){} + catch(std::out_of_range&){} + } + auto batch_size = getenv("OXIDE_INPUT_BATCH_SIZE"); + if(batch_size != nullptr){ + try{ + INPUT_BATCH_SIZE = std::stoi(batch_size); + } + catch(std::invalid_argument&){} + catch(std::out_of_range&){} + } + std::string path; + __realpath("/proc/self/exe", path); + if( + path != "/usr/bin/xochitl" + && ( + !path.starts_with("/opt") + || path.starts_with("/opt/sbin") + ) + && ( + !path.starts_with("/home") + || path.starts_with("/home/root/.entware/sbin") + ) + ){ + // We ignore this executable + BLIGHT_DEBUG_LOGGING = 0; + FAILED_INIT = false; + return; + } + if(getenv("OXIDE_PRELOAD_FORCE_RM1") != nullptr){ + FAKE_RM1 = true; + } + DO_HANDLE_FB = getenv("OXIDE_PRELOAD_EXPOSE_FB") == nullptr; + _DEBUG("Handle framebuffer: %d", DO_HANDLE_FB); + auto pid = getpid(); + _DEBUG("Connecting %d to blight", pid); +#ifdef __arm__ + bool connected = Blight::connect(true); +#else + bool connected = Blight::connect(false); +#endif + if(!connected){ + _CRIT("%s", "Could not connect to display server: %s", std::strerror(errno)); + std::quick_exit(EXIT_FAILURE); + } + blightConnection = Blight::connection(); + if(blightConnection == nullptr){ + _WARN("Failed to connect to display server: %s", std::strerror(errno)); + std::exit(errno); + } + blightConnection->onDisconnect([](int res){ + _WARN("Disconnected from display server: %s", std::strerror(res)); + // TODO - handle reconnect + std::exit(res); + }); + _DEBUG("Connected %d to blight on %d", pid, blightConnection->handle()); + setenv("OXIDE_PRELOAD", std::to_string(getpgrp()).c_str(), true); + + std::ios_base::Init i; + std::ifstream device_id_file{"/sys/devices/soc0/machine"}; + std::string device_id; + std::getline(device_id_file, device_id); + if(device_id == "reMarkable 2.0"){ + setenv("RM2FB_ACTIVE", "1", true); + setenv("RM2FB_SHIM", "1", true); + if(path != "/usr/bin/xochitl" && getenv("OXIDE_PRELOAD_ALLOW_RM2FB") == nullptr){ + setenv("RM2FB_DISABLE", "1", true); + }else{ + unsetenv("RM2FB_DISABLE"); + } + } + if(getenv("OXIDE_PRELOAD_FORCE_QT") != nullptr){ + setenv("QMLSCENE_DEVICE", "software", 1); + setenv("QT_QUICK_BACKEND", "software", 1); + setenv("QT_QPA_PLATFORM", "oxide:enable_fonts", 1); + setenv("QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS", "", 1); + setenv("QT_QPA_EVDEV_MOUSE_PARAMETERS", "", 1); + setenv("QT_QPA_EVDEV_KEYBOARD_PARAMETERS", "", 1); + setenv("QT_PLUGIN_PATH", "/opt/usr/lib/plugins", 1); + } + FAILED_INIT = false; + IS_INITIALIZED = true; + blightConnection->focused(); + } + + __attribute__((visibility("default"))) + int __libc_start_main( + int(*_main)(int, char**, char**), + int argc, + char** argv, + int(*init)(int, char**, char**), + void(*fini)(void), + void(*rtld_fini)(void), + void* stack_end + ){ + if(FAILED_INIT){ + return EXIT_FAILURE; + } + auto func_main = (decltype(&__libc_start_main))dlsym(RTLD_NEXT, "__libc_start_main"); + _DEBUG("Starting main(%d, ...)", argc); + return func_main(_main, argc, argv, init, fini, rtld_fini, stack_end); + } +} diff --git a/shared/libblight_client/main.map b/shared/libblight_client/main.map new file mode 100644 index 000000000..a2f7505ae --- /dev/null +++ b/shared/libblight_client/main.map @@ -0,0 +1,12 @@ +GLIBC_2.4 { + write; + writev; + writev64; + pwrite; + pwrite64; + pwritev; + pwritev64; + open64; + openat; + open; +}; diff --git a/shared/liboxide/Doxyfile b/shared/liboxide/Doxyfile index 0883299f3..fec86e929 100644 --- a/shared/liboxide/Doxyfile +++ b/shared/liboxide/Doxyfile @@ -6,15 +6,21 @@ QHP_NAMESPACE = liboxide QHP_VIRTUAL_FOLDER = doc QHG_LOCATION = qhelpgenerator GENERATE_TREEVIEW = YES -HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \ - doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ - doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css -HTML_EXTRA_FILES = doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ - doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js \ - doxygen-awesome-css/doxygen-awesome-paragraph-link.js -HTML_HEADER = header.html -PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS +HTML_EXTRA_STYLESHEET = ../doxygen-awesome-css/doxygen-awesome.css \ + ../doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ + ../doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css +HTML_EXTRA_FILES = ../doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js \ + ../doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js \ + ../doxygen-awesome-css/doxygen-awesome-paragraph-link.js +HTML_HEADER = ../doxygen-header.html +PREDEFINED += DOXYGEN_SHOULD_SKIP_THIS +PREDEFINED += IN_DOXYGEN HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES MACRO_EXPANSION = YES EXAMPLE_PATH = examples +GENERATE_TAGFILE = html/liboxide.tag +ALLEXTERNALS = Yes +TAGFILES = ../libblight/html/libblight.tag=../libblight/ +ALIASES += "accessors=\par Accessors:^^" +ALIASES += "notifier=\par Notifier signal:^^" diff --git a/shared/liboxide/OxideWindow.qml b/shared/liboxide/OxideWindow.qml index 7b8761aac..a92a9eeda 100644 --- a/shared/liboxide/OxideWindow.qml +++ b/shared/liboxide/OxideWindow.qml @@ -12,12 +12,11 @@ ApplicationWindow { property alias centerMenu: centerMenu.children property alias stack: stack property alias initialItem: stack.initialItem - property alias backgroundColor: background.color property alias headerBackgroundColor: header.color property bool landscape: Oxide.landscape Component.onCompleted: stack.forceActiveFocus() function orientationWidth(){ return landscape ? height : width; } - function orientationHeight(){ return landscape ? width : height; } + function orientationHeight(){ return landscape ? width : height; } signal keyPressed(var event) signal keyReleased(var event) width: Screen.width @@ -28,7 +27,7 @@ ApplicationWindow { focus: true title: window.title visible: window.visible - rotation: landscape ? 90 : 0 + rotation: window.landscape ? 90 : 0 // Must centerIn and specify width/height to force rotation to actually work anchors.centerIn: parent width: window.orientationWidth() @@ -55,7 +54,7 @@ ApplicationWindow { } background: Rectangle { id: background - color: "black" + color: window.color } contentData: [ StackView { diff --git a/shared/liboxide/applications.cpp b/shared/liboxide/applications.cpp index 063a21b32..9d5d1876e 100644 --- a/shared/liboxide/applications.cpp +++ b/shared/liboxide/applications.cpp @@ -15,11 +15,17 @@ const QList SystemFlags { }; const QList Flags { "autoStart", - "chroot", "hidden", "nosplash", "nosavescreen", "system", + "nopreload", + "nopreload.sysfs", + "nopreload.compositor", + "exclusive" +}; +const QList DeprecatedFlags { + "chroot" }; namespace Oxide::Applications{ @@ -152,7 +158,7 @@ namespace Oxide::Applications{ "Value \"%1\" for key \"bin\" is a path to a file that is not executable" ).arg(bin)); } - } else shouldExit + }else shouldExit QStringList flags; if(isArray("flags", ErrorLevel::Critical, false)){ auto flagsArray = app["flags"].toArray(); @@ -171,7 +177,11 @@ namespace Oxide::Applications{ ).arg(flagsJson)); continue; } - if(SystemFlags.contains(value)){ + if(DeprecatedFlags.contains(value)){ + addError(ErrorLevel::Deprecation, QString( + "Value \"%1\" for key \"flags\" contains a deprecated entry \"%2\"" + ).arg(flagsJson, value)); + }else if(SystemFlags.contains(value)){ addError(ErrorLevel::Warning, QString( "Value \"%1\" for key \"flags\" contains an entry that should only be used by the system \"%2\"" ).arg(flagsJson, value)); @@ -185,7 +195,10 @@ namespace Oxide::Applications{ if(type == "background" && flags.contains("nosavescreen")){ addError(ErrorLevel::Hint, "Key \"flags\" contains \"nosavescreen\" while \"type\" has value \"background\""); } - } else shouldExit + }else shouldExit + if(app.contains("directories")){ + addError(ErrorLevel::Deprecation, QString("Key \"directories\" is no longer used")); + }else shouldExit if(isArray("directories", ErrorLevel::Critical, false)){ auto directories = app["directories"].toArray(); for(int i = 0; i < directories.count(); i++){ @@ -203,7 +216,7 @@ namespace Oxide::Applications{ ).arg(Oxide::JSON::toJson(directories), QString::number(i), directory)); } } - } else shouldExit else if(flags.contains("chroot")){ + }else shouldExit else if(flags.contains("chroot")){ addError(ErrorLevel::Hint, "Key \"flags\" contains \"chroot\" while \"directories\" is missing"); } if(isArray("permissions", ErrorLevel::Critical, false)){ @@ -216,7 +229,7 @@ namespace Oxide::Applications{ ).arg(Oxide::JSON::toJson(permissions), QString::number(i), Oxide::JSON::toJson(entry))); } } - } else shouldExit + }else shouldExit isFile("workingDirectory", ErrorLevel::Error, false); shouldExit isString("displayName", false); shouldExit isString("description", false); shouldExit @@ -229,7 +242,7 @@ namespace Oxide::Applications{ "Value \"%1\" for key \"user\" is not a valid user: \"%2\"" ).arg(user, e.what())); } - } else shouldExit + }else shouldExit if(isString("group", false)){ auto group = app["group"].toString(); try{ @@ -239,11 +252,14 @@ namespace Oxide::Applications{ "Value \"%1\" for key \"group\" is not a valid group: \"%2\"" ).arg(group, e.what())); } - } else shouldExit + }else shouldExit isIcon("icon", ErrorLevel::Warning, false); if(isIcon("splash", ErrorLevel::Warning, false) && flags.contains("nosplash")){ addError(ErrorLevel::Hint, "Key \"splash\" provided while \"flags\" contains \"nosplash\" value"); - } else shouldExit + }else shouldExit + if(app.contains("splash")){ + addError(ErrorLevel::Deprecation, "Key \"splash\" is no longer used"); + }else shouldExit if(registrationToMap(app, name).isEmpty()){ addError(ErrorLevel::Critical, "Unable to convert registration to QVariantMap"); } diff --git a/shared/liboxide/dbus.h b/shared/liboxide/dbus.h index 59e3dcfbb..6ffaa2aab 100644 --- a/shared/liboxide/dbus.h +++ b/shared/liboxide/dbus.h @@ -10,7 +10,61 @@ // This must be here to make precompiled headers happy #ifndef LIBOXIDE_DBUS_H #define LIBOXIDE_DBUS_H - +#ifdef IN_DOXYGEN +/*! + * \brief System service DBus interfaces + */ +namespace codes::eeems::oxide1{ + /*! + * \brief General API interface + */ + class General : public QDBusAbstractInterface{}; + /*! + * \brief The Power class + */ + class Power : public QDBusAbstractInterface{}; + /*! + * \brief The Wifi class + */ + class Wifi : public QDBusAbstractInterface{}; + /*! + * \brief The Network class + */ + class Network : public QDBusAbstractInterface{}; + /*! + * \brief The BSS class + */ + class BSS : public QDBusAbstractInterface{}; + /*! + * \brief The Apps class + */ + class Apps : public QDBusAbstractInterface{}; + /*! + * \brief The Application class + */ + class Application : public QDBusAbstractInterface{}; + /*! + * \brief The System class + */ + class System : public QDBusAbstractInterface{}; + /*! + * \brief The Screen class + */ + class Screen : public QDBusAbstractInterface{}; + /*! + * \brief The Screenshot class + */ + class Screenshot : public QDBusAbstractInterface{}; + /*! + * \brief The Notifications class + */ + class Notifications : public QDBusAbstractInterface{}; + /*! + * \brief The Notification class + */ + class Notification : public QDBusAbstractInterface{}; +} +#endif #include "dbusservice_interface.h" #include "powerapi_interface.h" #include "wifiapi_interface.h" @@ -23,6 +77,17 @@ #include "screenshot_interface.h" #include "notificationapi_interface.h" #include "notification_interface.h" - +#ifdef IN_DOXYGEN +/*! + * \brief Display server DBus interfaces + */ +namespace codes::eeems::blight1{ + /*! + * \brief Display server compositor API + */ + class Compositor : public QDBusAbstractInterface{}; +} +#endif +#include "blight_interface.h" #endif // LIBOXIDE_DBUS_H /*! @} */ diff --git a/shared/liboxide/debug.cpp b/shared/liboxide/debug.cpp index f885ced16..92b442614 100644 --- a/shared/liboxide/debug.cpp +++ b/shared/liboxide/debug.cpp @@ -1,6 +1,11 @@ #include "debug.h" +#include +#include +#include +#include +#include -namespace Oxide { +namespace Oxide{ bool debugEnabled(){ if(getenv("DEBUG") == NULL){ return false; @@ -8,4 +13,62 @@ namespace Oxide { QString env = qgetenv("DEBUG"); return !(QStringList() << "0" << "n" << "no" << "false").contains(env.toLower()); } + + std::string getAppName(bool ignoreQApp){ + if(!ignoreQApp && !QCoreApplication::startingUp()){ + return qApp->applicationName().toStdString().c_str(); + } + static std::string name; + if(!name.empty()){ + return name.c_str(); + } + QFile file("/proc/self/comm"); + if(file.open(QIODevice::ReadOnly)){ + name = file.readAll().trimmed().toStdString(); + } + if(!name.empty()){ + return name.c_str(); + } + name = QFileInfo("/proc/self/exe").canonicalFilePath().trimmed().toStdString(); + if(name.empty()){ + return "unknown"; + } + return name.c_str(); + } + + std::string getDebugApplicationInfo(){ + return QString("[%1:%2:%3 %4 - %5]") + .arg(::getpgrp()) + .arg(::getpid()) + .arg(::gettid()) + .arg(getAppName(false).c_str()) + .arg(getThreadName().c_str()) + .toStdString(); + } + + std::string getDebugLocation(const char* file, unsigned int line, const char* function){ + return QString("(%1:%2, %3)") + .arg(file) + .arg(line) + .arg(function) + .toStdString(); + } + + std::vector backtrace(unsigned short depth){ + void* array[depth]; + size_t size = ::backtrace(array, depth); + char** messages = ::backtrace_symbols(array, size); + std::vector stack; + stack.reserve(size); + for(size_t i = 1; i < size && messages != NULL; ++i){ + stack.push_back(messages[i]); + } + return stack; + } + + std::string getThreadName(){ + char name[16]; + prctl(PR_GET_NAME, name); + return std::string(name); + } } diff --git a/shared/liboxide/debug.h b/shared/liboxide/debug.h index a1647080b..47be9a21d 100644 --- a/shared/liboxide/debug.h +++ b/shared/liboxide/debug.h @@ -6,20 +6,52 @@ #pragma once #include "liboxide_global.h" +#include #include + /*! - * \def O_DEBUG(msg) - * \brief Log a debug message if compiled with DEBUG mode, and debugging is enabled - * \param msg Debug message to log + * \def DEBUG + * \brief If this is defined, debug code will be compiled in + * \note If QT_NO_DEBUG is not defined, DEBUG will be automatically defined */ #ifndef DEBUG #ifndef QT_NO_DEBUG #define DEBUG #endif #endif +/*! + * \def __RIGHT_HERE__ + * \brief Log the current file and line number + * \note This will only do anything if DEBUG is defined + */ +#ifdef __RIGHT_HERE__ +#undef __RIGHT_HERE__ +#endif +#ifdef DEBUG +#define __RIGHT_HERE__ qDebug() << "<============================" << __FILE__ << ":" << __LINE__; +#else +#define __RIGHT_HERE__ +#endif +/*! + * \def __DEBUG_LOCATION__ + * \brief Get the current debug location + * \note this is automatically included in O_DEBUG, O_WARNING, O_INFO, and O_EVENT + */ +#define __DEBUG_LOCATION__ Oxide::getDebugLocation(__FILE__, __LINE__, __PRETTY_FUNCTION__).c_str() +/*! + * \def __DEBUG_APPLICATION_INFO__ + * \brief Get the current application information string + * \note this is automatically included in O_DEBUG, O_WARNING, O_INFO, and O_EVENT + */ +#define __DEBUG_APPLICATION_INFO__ Oxide::getDebugApplicationInfo().c_str() +/*! + * \def O_DEBUG(msg) + * \brief Log a debug message if compiled with DEBUG mode, and debugging is enabled + * \param msg Debug message to log + */ #ifdef DEBUG -#define O_DEBUG(msg) if(Oxide::debugEnabled()){ qDebug() << msg; } +#define O_DEBUG(msg) if(Oxide::debugEnabled()){ qDebug() << __DEBUG_APPLICATION_INFO__ << "Debug:" << msg << __DEBUG_LOCATION__; } #else #define O_DEBUG(msg) #endif @@ -28,20 +60,69 @@ * \brief Log a warning message if debugging is enabled * \param msg Warning message to log */ -#define O_WARNING(msg) if(Oxide::debugEnabled()){ qWarning() << msg; } +#define O_WARNING(msg) if(Oxide::debugEnabled()){ qWarning() << __DEBUG_APPLICATION_INFO__ << "Warning:" << msg << __DEBUG_LOCATION__; } + +/*! + * \def O_EVENT(msg) + * \brief Debug log an input_event if debugging is enabled + * \param event input_event to log + */ +#ifdef DEBUG_EVENTS +#ifdef input_event_sec +#define input_event_sec time.tv_sec +#endif +#ifdef input_event_usec +#define input_event_usec time.tv_usec +#endif +#define O_EVENT(event) O_DEBUG(__DEBUG_APPLICATION_INFO__ << event.input_event_sec << event.input_event_usec << event.type << event.code << event.value << __DEBUG_LOCATION__); +#else +#define O_EVENT(event) +#endif /*! * \def O_INFO(msg) * \brief Log an informational message * \param msg Informational message to log */ -#define O_INFO(msg) qInfo() << msg; +#define O_INFO(msg) qInfo() << __DEBUG_APPLICATION_INFO__ << "Info:" << msg << __DEBUG_LOCATION__; namespace Oxide { + /*! + * \brief Get a formatted application information string + * \note this is automatically included in O_DEBUG, O_WARNING, O_INFO, and O_EVENT + * \return Formatted string containing information about the application and current thread + */ + LIBOXIDE_EXPORT std::string getDebugApplicationInfo(); + /*! + * \brief Get a formatted debug information string + * \note this is automatically included in O_DEBUG, O_WARNING, O_INFO, and O_EVENT + * \param file Name of file + * \param line Line number in file + * \param function Function information + * \return Formatted debug location string + */ + LIBOXIDE_EXPORT std::string getDebugLocation(const char* file, unsigned int line, const char* function); /*! * \brief Return the state of debugging * \return Debugging state * \snippet examples/oxide.cpp debugEnabled */ LIBOXIDE_EXPORT bool debugEnabled(); + /*! + * \brief Get the name of the application + * \param ignoreQApp Don't use qApp's application name + * \return The name of the application + */ + LIBOXIDE_EXPORT std::string getAppName(bool ignoreQApp); + /*! + * \brief Get the current backtrace + * \param depth Maximum number of items to return + * \return The current stack trace + */ + LIBOXIDE_EXPORT std::vector backtrace(unsigned short depth); + /*! + * \brief Get the current thread name + * \return The current thread name + */ + LIBOXIDE_EXPORT std::string getThreadName(); } /*! @} */ diff --git a/shared/liboxide/devicesettings.cpp b/shared/liboxide/devicesettings.cpp index 8ca926f51..47870b759 100644 --- a/shared/liboxide/devicesettings.cpp +++ b/shared/liboxide/devicesettings.cpp @@ -1,10 +1,11 @@ #include "devicesettings.h" -#include -#include - #include "debug.h" #include "liboxide.h" +#include "signalhandler.h" + +#include +#include #define BITS_PER_LONG (sizeof(long) * 8) #define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) @@ -172,10 +173,8 @@ namespace Oxide { } return qEnvironmentVariable("LANG", "C"); } - void DeviceSettings::setLocale(const QString& locale) { - if(debugEnabled()){ - qDebug() << "Setting locale:" << locale; - } + void DeviceSettings::setLocale(const QString& locale){ + O_DEBUG("Setting locale:" << locale); qputenv("LANG", locale.toUtf8()); QProcess::execute("localectl", QStringList() << "set-locale" << locale); } @@ -194,9 +193,7 @@ namespace Oxide { return "UTC"; } void DeviceSettings::setTimezone(const QString& timezone) { - if(debugEnabled()){ - qDebug() << "Setting timezone:" << timezone; - } + O_DEBUG("Setting timezone:" << timezone); QProcess::execute("timedatectl", QStringList() << "set-timezone" << timezone); } void DeviceSettings::setupQtEnvironment(bool touch){ @@ -204,37 +201,42 @@ namespace Oxide { if (strcmp(qt_version, QT_VERSION_STR) != 0){ qDebug() << "Version mismatch, Runtime: " << qt_version << ", Build: " << QT_VERSION_STR; } - #ifdef __arm__ - qputenv("QMLSCENE_DEVICE", "epaper"); - qputenv("QT_QUICK_BACKEND","epaper"); - qputenv("QT_QPA_PLATFORM", "epaper:enable_fonts"); - #endif + QCoreApplication::addLibraryPath("/opt/usr/lib/plugins"); + qputenv("QMLSCENE_DEVICE", "software"); + qputenv("QT_QUICK_BACKEND","software"); + QString platform("oxide:enable_fonts:freetype:freetype"); if(touch){ - qputenv("QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS", deviceSettings.getTouchEnvSetting()); - qputenv("QT_QPA_GENERIC_PLUGINS", "evdevtablet"); + qputenv( + "QT_QPA_PLATFORM", + QString("%1:%2") + .arg(platform) + .arg(deviceSettings.getTouchEnvSetting()) + .toUtf8() + ); + }else{ + qputenv("QT_QPA_PLATFORM", platform.toUtf8()); } } + bool DeviceSettings::keyboardAttached(){ return !physicalKeyboards().empty(); } + void DeviceSettings::onKeyboardAttachedChanged(std::function callback){ - auto manager = QGuiApplicationPrivate::inputDeviceManager(); - QObject::connect(manager, &QInputDeviceManager::deviceListChanged, [callback](QInputDeviceManager::DeviceType type){ - if(type == QInputDeviceManager::DeviceTypeKeyboard){ + bool initialValue = keyboardAttached(); + onInputDevicesChanged([this, callback, initialValue]{ + static bool attached = initialValue; + bool nowAttached = keyboardAttached(); + if(attached != nowAttached){ callback(); } + attached = nowAttached; }); } - QList DeviceSettings::keyboards(){ - QList keyboards; + + QList DeviceSettings::inputDevices(){ + QList devices; QDir dir("/dev/input"); for(auto path : dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::System)){ QString fullPath(dir.path() + "/" + path); - if( - fullPath == QString::fromStdString(buttonsPath) - || fullPath == QString::fromStdString(wacomPath) - || fullPath == QString::fromStdString(touchPath) - ){ - continue; - } QFile device(fullPath); device.open(QIODevice::ReadOnly); int fd = device.handle(); @@ -242,6 +244,31 @@ namespace Oxide { if(ioctl(fd, EVIOCGVERSION, &version)){ continue; } + devices.append(event_device(fullPath.toStdString(), O_RDWR | O_NONBLOCK)); + } + return devices; + } + + void DeviceSettings::onInputDevicesChanged(std::function callback){ + callbacks.push_back(callback); + auto manager = QGuiApplicationPrivate::inputDeviceManager(); + QObject::connect(manager, &QInputDeviceManager::deviceListChanged, qApp, [callback](QInputDeviceManager::DeviceType type){ + Q_UNUSED(type); + callback(); + }); + } + + QList DeviceSettings::keyboards(){ + QList keyboards; + for(auto device : inputDevices()){ + if( + device.device == buttonsPath + || device.device == wacomPath + || device.device == touchPath + ){ + continue; + } + int fd = device.fd; unsigned long bit[EV_MAX]; ioctl(fd, EVIOCGBIT(0, EV_MAX), bit); if(!test_bit(EV_KEY, bit)){ @@ -250,7 +277,8 @@ namespace Oxide { if(checkBitSet(fd, EV_KEY, BTN_STYLUS) && test_bit(EV_ABS, bit)){ continue; } - SysObject sys("/sys/class/input/" + path + "/device"); + auto name = QFileInfo(device.device.c_str()).baseName(); + SysObject sys("/sys/class/input/" + name + "/device"); auto vendor = sys.strProperty("id/vendor"); if(vendor == "0000"){ continue; @@ -259,7 +287,7 @@ namespace Oxide { if(product == "0000"){ continue; } - keyboards.append(event_device(fullPath.toStdString(), O_RDWR | O_NONBLOCK)); + keyboards.append(device); } return keyboards; } diff --git a/shared/liboxide/devicesettings.h b/shared/liboxide/devicesettings.h index 8193d7e2d..e7ad2e833 100644 --- a/shared/liboxide/devicesettings.h +++ b/shared/liboxide/devicesettings.h @@ -101,7 +101,7 @@ namespace Oxide{ QString getTimezone(); /*! * \brief Set the current timezone - * \param locale Timezone to set + * \param timezone Timezone to set */ void setTimezone(const QString& timezone); /*! @@ -119,6 +119,16 @@ namespace Oxide{ * \param callback Callback to run */ void onKeyboardAttachedChanged(std::function callback); + /*! + * \brief Get the list of all evdev devices + * \return All input devices + */ + QList inputDevices(); + /*! + * \brief Run a callback when inputDevices changes + * \param callback callback to run + */ + void onInputDevicesChanged(std::function callback); /*! * \brief Get the list of all keyboard evdev devices * \return All keyboard devices @@ -145,6 +155,7 @@ namespace Oxide{ std::string buttonsPath = ""; std::string wacomPath = ""; std::string touchPath = ""; + std::vector> callbacks; }; } /*! @} */ diff --git a/shared/liboxide/doxygen-awesome-css b/shared/liboxide/doxygen-awesome-css deleted file mode 160000 index 4cd62308d..000000000 --- a/shared/liboxide/doxygen-awesome-css +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4cd62308d825fe0396d2f66ffbab45d0e247724c diff --git a/shared/liboxide/epaper.h b/shared/liboxide/epaper.h deleted file mode 100644 index c3f02b1a2..000000000 --- a/shared/liboxide/epaper.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#if defined(LIBOXIDE_LIBRARY) -#include "liboxide_global.h" -#else -#include -#endif -#ifdef EPAPER -#ifdef __arm__ -Q_IMPORT_PLUGIN(QsgEpaperPlugin) -#endif -#include -#else -#include -#include - -class LIBOXIDE_EXPORT EPFrameBuffer : public QObject{ - Q_OBJECT - -public: - enum WaveformMode { - Initialize = 0, - Mono = 1, - Grayscale = 3, - HighQualityGrayscale = 2, - Highlight = 8 - }; - enum UpdateMode { - PartialUpdate = 0x0, - FullUpdate = 0x1 - }; - static EPFrameBuffer* instance(){ - static EPFrameBuffer* instance; - if(instance == nullptr){ - instance = new EPFrameBuffer(); - } - return instance; - } - static QImage* framebuffer() { return &instance()->m_fb; } - Q_INVOKABLE static void setForceFull(bool force) { instance()->m_forceFull = force; } - static bool isForceFull() { return instance()->m_forceFull; } - int lastUpdateId() const { return m_lastUpdateId; } - void setSuspended(bool suspended) { m_suspended = suspended; } - bool isSuspended() const { - std::lock_guard locker(fbMutex); - return m_suspended; - } - mutable std::mutex fbMutex; - qint64 timeSinceLastUpdate() const{ return QDateTime::currentMSecsSinceEpoch(); } - -public slots: - static void clearScreen(){ instance()->m_fb.fill(Qt::white); } - static void sendUpdate(QRect rect, WaveformMode waveform, UpdateMode mode, bool sync = false){ - Q_UNUSED(rect); - Q_UNUSED(waveform); - Q_UNUSED(mode); - Q_UNUSED(sync); - } - static void waitForLastUpdate(){} - -private: - EPFrameBuffer() : m_fb(1000, 1000, QImage::Format_ARGB32_Premultiplied){} - QImage m_fb; - bool m_forceFull = false; - bool m_suspended = false; - int m_lastUpdateId = 0; -}; -#endif diff --git a/shared/liboxide/event_device.cpp b/shared/liboxide/event_device.cpp index cf004300b..d9d533653 100644 --- a/shared/liboxide/event_device.cpp +++ b/shared/liboxide/event_device.cpp @@ -13,20 +13,24 @@ namespace Oxide { event_device::event_device(const string& path, int flags) : device(path), flags(flags){ this->open(); } + + event_device::event_device(const event_device& other) + : device(other.device), + flags(other.flags), + fd(::dup(other.fd)) + {} event_device::~event_device(){ this->close(); } void event_device::open(){ - if(fd > 0){ - this->close(); - } + this->close(); fd = ::open(device.c_str(), flags); error = fd < 0 ? errno : 0; } void event_device::close(){ - if(fd < 0){ + if(fd > 0){ ::close(fd); } fd = 0; diff --git a/shared/liboxide/event_device.h b/shared/liboxide/event_device.h index b5ababe88..cf4482bd2 100644 --- a/shared/liboxide/event_device.h +++ b/shared/liboxide/event_device.h @@ -17,73 +17,98 @@ using namespace std; namespace Oxide { /*! - * \brief The event_device class + * \brief A class to simplify managing a /dev/event* file */ class LIBOXIDE_EXPORT event_device { public: + /*! + * \brief Create an input_event + * \param type Input event type + * \param code Input event code + * \param value Input event value + * \return + */ static input_event create_event(ushort type, ushort code, int value); /*! - * \brief event_device - * \param path - * \param flags + * \brief Instantiate a new instance + * \param path Path to the event device + * \param flags Flags to use when opening the event device */ event_device(const string& path, int flags); + /*! + * \brief Create a copy of an event_device instance + * \param other Instance to copy + */ + event_device(const event_device& other); ~event_device(); /*! - * \brief close + * \brief Close the event device + * \sa open() */ void close(); /*! - * \brief open + * \brief Open the event device + * \sa fd, error, close() */ void open(); /*! - * \brief lock - * \return + * \brief Grab all input from this device + * \return If grabbing was successful + * \retval 0 Successfully grabbed all input + * \retval EBUSY The event device is grabbed by another process + * \sa locked unlock() */ int lock(); /*! - * \brief unlock - * \return + * \brief Ungrab all input from this device + * \return If ungrabbing was successful + * \retval 0 Successfully ungrabbed all input + * \sa locked, lock() */ int unlock(); /*! - * \brief write - * \param ie + * \brief Write an input event to the event device + * \param ie Input event to write + * \sa write(ushort, ushort, int) */ void write(input_event ie); /*! - * \brief write_event - * \param device - * \param type - * \param code - * \param value + * \brief Write an input event to the event device + * \param type Input event type + * \param code Input event code + * \param value Input event value + * \sa write(input_event) */ void write(ushort type, ushort code, int value); /*! - * \brief ev_syn + * \brief Write an EV_SYN SYN_REPORT event to the event device */ void ev_syn(); /*! - * \brief ev_dropped + * \brief Write an EV_SYN SYN_DROPPED event to the event device */ void ev_dropped(); /*! - * \brief error + * \brief Errno reported when opening the event device + * \sa open() */ int error; /*! - * \brief fd + * \brief File descriptor returned when opening the event device + * \sa open(), close() */ int fd; /*! - * \brief device + * \brief Path to the event device + * \sa event_device(const string&, int) */ string device; /*! - * \brief locked + * \brief If input is currently grabbed + * \sa lock(), unlock() */ bool locked = false; + private: int flags; }; diff --git a/shared/liboxide/eventfilter.h b/shared/liboxide/eventfilter.h index 17260a799..f79f5817c 100644 --- a/shared/liboxide/eventfilter.h +++ b/shared/liboxide/eventfilter.h @@ -30,8 +30,7 @@ namespace Oxide{ * \param parent The parent object. Usually should be qApp */ explicit EventFilter(QObject* parent = nullptr); - signals: - void suspend(); + protected: bool eventFilter(QObject* obj, QEvent* ev); }; diff --git a/shared/liboxide/json.cpp b/shared/liboxide/json.cpp index d1cd7d9bd..96697f03d 100644 --- a/shared/liboxide/json.cpp +++ b/shared/liboxide/json.cpp @@ -2,7 +2,6 @@ #include "debug.h" #include -#include static bool qIsNumericType(uint tp){ static const qulonglong numericTypeBits = diff --git a/shared/liboxide/json.h b/shared/liboxide/json.h index ebfa536cf..b8776eceb 100644 --- a/shared/liboxide/json.h +++ b/shared/liboxide/json.h @@ -11,6 +11,7 @@ #include #include #include +#include /*! * \brief The JSON namespace */ diff --git a/shared/liboxide/liboxide.cpp b/shared/liboxide/liboxide.cpp index 81cc510c8..f8be97690 100644 --- a/shared/liboxide/liboxide.cpp +++ b/shared/liboxide/liboxide.cpp @@ -13,12 +13,16 @@ #include namespace Oxide { - QString execute(const QString& program, const QStringList& args){ + QString execute( + const QString& program, + const QStringList& args, + bool readStderr + ){ QString output; QProcess p; p.setProgram(program); p.setArguments(args); - p.setProcessChannelMode(QProcess::MergedChannels); + p.setProcessChannelMode(readStderr ? QProcess::MergedChannels : QProcess::SeparateChannels); p.connect(&p, &QProcess::readyReadStandardOutput, [&p, &output]{ output += (QString)p.readAllStandardOutput(); }); @@ -89,12 +93,6 @@ namespace Oxide { } return pids; } - void dispatchToMainThread(std::function callback){ - dispatchToMainThread([callback]{ - callback(); - return 0; - }); - } uid_t getUID(const QString& name){ char buffer[1024]; struct passwd user; diff --git a/shared/liboxide/liboxide.h b/shared/liboxide/liboxide.h index da3979cf3..401e589ed 100644 --- a/shared/liboxide/liboxide.h +++ b/shared/liboxide/liboxide.h @@ -21,7 +21,7 @@ #include "devicesettings.h" #include "xochitlsettings.h" #include "sharedsettings.h" -#include "epaper.h" +#include "threading.h" #if defined(LIBOXIDE_LIBRARY) #include "oxide_sentry.h" #else @@ -54,10 +54,15 @@ namespace Oxide { * \brief Execute a program and return it's output * \param program Program to run * \param args Arguments to pass to the program + * \param readStderr Include stderr in the output * \return Output if it ran. * \retval NULL Program was not able to execute */ - LIBOXIDE_EXPORT QString execute(const QString& program, const QStringList& args); + LIBOXIDE_EXPORT QString execute( + const QString& program, + const QStringList& args, + bool readStderr = true + ); /*! * \brief Try to get a lock * \param lockName Path to the lock file @@ -82,37 +87,6 @@ namespace Oxide { * \return list of pids that have the file open */ LIBOXIDE_EXPORT QList lsof(const QString& path); - /*! - * \brief Run code on the main Qt thread - * \param callback The code to run on the main thread - * - * \snippet examples/oxide.cpp dispatchToMainThread - */ - LIBOXIDE_EXPORT void dispatchToMainThread(std::function callback); - /*! - * \brief Run code on the main Qt thread - * \param callback The code to run on the main thread - * \return Return value of callback - * - * \snippet examples/oxide.cpp dispatchToMainThread - */ - template LIBOXIDE_EXPORT T dispatchToMainThread(std::function callback){ - if(QThread::currentThread() == qApp->thread()){ - return callback(); - } - // any thread - QTimer* timer = new QTimer(); - timer->moveToThread(qApp->thread()); - timer->setSingleShot(true); - T result; - QObject::connect(timer, &QTimer::timeout, [timer, &result, callback](){ - // main thread - result = callback(); - timer->deleteLater(); - }); - QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); - return result; - } /*! * \brief Get the UID for a username * \param name Username to search for diff --git a/shared/liboxide/liboxide.pro b/shared/liboxide/liboxide.pro index 73bbf319f..912047f01 100644 --- a/shared/liboxide/liboxide.pro +++ b/shared/liboxide/liboxide.pro @@ -32,8 +32,10 @@ SOURCES += \ settingsfile.cpp \ sharedsettings.cpp \ slothandler.cpp \ + socketpair.cpp \ sysobject.cpp \ signalhandler.cpp \ + threading.cpp \ udev.cpp \ xochitlsettings.cpp @@ -42,7 +44,6 @@ HEADERS += \ dbus.h \ debug.h \ devicesettings.h \ - epaper.h \ event_device.h \ eventfilter.h \ liboxide_global.h \ @@ -55,8 +56,10 @@ HEADERS += \ settingsfile.h \ sharedsettings.h \ slothandler.h \ + socketpair.h \ sysobject.h \ signalhandler.h \ + threading.h \ udev.h \ xochitlsettings.h @@ -75,13 +78,18 @@ DBUS_INTERFACES += \ ../../interfaces/screenapi.xml \ ../../interfaces/screenshot.xml \ ../../interfaces/notificationapi.xml \ - ../../interfaces/notification.xml + ../../interfaces/notification.xml \ + ../../interfaces/blight.xml LIBS += -lsystemd -ludev include(../../qmake/common.pri) +RELATIVE_PWD = $$system(realpath --canonicalize-missing --relative-to $$OUT_PWD $$PWD) liboxide_liboxide_h.target = include/liboxide/liboxide.h +for(h, HEADERS){ + liboxide_liboxide_h.depends += $${RELATIVE_PWD}/$${h} +} liboxide_liboxide_h.commands = \ mkdir -p include/liboxide && \ sed -i \'s/define OXIDE_VERSION .*/define OXIDE_VERSION \"$$VERSION\"/\' $$_PRO_FILE_PWD_/meta.h && \ @@ -90,14 +98,11 @@ liboxide_liboxide_h.commands = \ echo $$DBUS_INTERFACES | xargs -rn1 | xargs -rI {} basename \"{}\" .xml | xargs -rI {} cp $$OUT_PWD/\"{}\"_interface.h include/liboxide/ liboxide_h.target = include/liboxide.h -liboxide_h.depends += liboxide_liboxide_h +liboxide_h.depends = liboxide_liboxide_h liboxide_h.commands = \ echo \\$$LITERAL_HASH"pragma once" > include/liboxide.h && \ echo \"$$LITERAL_HASH"include \\\"liboxide/liboxide.h\\\"\"" >> include/liboxide.h -clean_headers.target = include/.clean-target -clean_headers.commands = rm -rf include - liboxide_h_install.files = \ include/liboxide.h \ include/liboxide @@ -105,14 +110,7 @@ liboxide_h_install.depends = liboxide_h liboxide_h_install.path = /opt/include/ INSTALLS += liboxide_h_install -linux-oe-g++{ - epframebuffer_h_install.files = ../epaper/epframebuffer.h - epframebuffer_h_install.path = /opt/include - INSTALLS += epframebuffer_h_install -} - -QMAKE_EXTRA_TARGETS += liboxide_liboxide_h liboxide_h clean_headers liboxide_h_install -PRE_TARGETDEPS += $$clean_headers.target +QMAKE_EXTRA_TARGETS += liboxide_liboxide_h liboxide_h liboxide_h_install POST_TARGETDEPS += $$liboxide_liboxide_h.target $$liboxide_h.target QMAKE_CLEAN += $$liboxide_h.target include/liboxide/*.h @@ -120,10 +118,13 @@ TARGET = oxide target.path = /opt/lib INSTALLS += target -INCLUDEPATH += ../../shared/mxcfb - -include(../../qmake/epaper.pri) +linux-oe-g++{ + INCLUDEPATH += ../../shared/mxcfb + include(../../qmake/epaper.pri) +} include(../../qmake/sentry.pri) +DEFINES += LIBBLIGHT_PRIVATE +include(../../qmake/libblight.pri) QMAKE_PKGCONFIG_NAME = liboxide QMAKE_PKGCONFIG_DESCRIPTION = Shared library for Oxide application development @@ -137,5 +138,6 @@ RESOURCES += \ oxide.qrc DISTFILES += \ + Doxyfile \ OxideMenu.qml \ OxideWindow.qml diff --git a/shared/liboxide/liboxide_global.h b/shared/liboxide/liboxide_global.h index 6901768c3..e1b4f9167 100644 --- a/shared/liboxide/liboxide_global.h +++ b/shared/liboxide/liboxide_global.h @@ -1,8 +1,3 @@ -/*! - * \addtogroup Oxide - * @{ - * \file - */ #pragma once #include @@ -23,4 +18,3 @@ #else # define ATTRIBUTE_NO_SANITIZE_ADDRESS #endif -/*! @} */ diff --git a/shared/liboxide/meta.h b/shared/liboxide/meta.h index 59c47f22a..b7f587d41 100644 --- a/shared/liboxide/meta.h +++ b/shared/liboxide/meta.h @@ -30,7 +30,7 @@ * \def OXIDE_VERSION * \brief Version of Tarnish and liboxide */ -#define OXIDE_VERSION "2.8.4" +#define OXIDE_VERSION "3.0" /*! * \def OXIDE_INTERFACE_VERSION * \brief Version of Tarnish and liboxide for dbus diff --git a/shared/liboxide/oxideqml.cpp b/shared/liboxide/oxideqml.cpp index 8cd0867bf..e07e14327 100644 --- a/shared/liboxide/oxideqml.cpp +++ b/shared/liboxide/oxideqml.cpp @@ -1,6 +1,18 @@ #include "oxideqml.h" #include "liboxide.h" +#include +#include +#include +#include + +#include +#include +#include + + +static std::atomic marker = 0; + namespace Oxide { namespace QML{ OxideQml::OxideQml(QObject *parent) : QObject{parent}{ @@ -11,6 +23,14 @@ namespace Oxide { bool OxideQml::landscape(){ return deviceSettings.keyboardAttached(); } + QString OxideQml::deviceName(){ + return deviceSettings.getDeviceName(); + } + + QBrush OxideQml::brushFromColor(const QColor& color){ + return QBrush(color, Qt::SolidPattern); + } + OxideQml* getSingleton(){ static OxideQml* instance = new OxideQml(qApp); return instance; @@ -19,7 +39,144 @@ namespace Oxide { void registerQML(QQmlApplicationEngine* engine){ QQmlContext* context = engine->rootContext(); context->setContextProperty("Oxide", getSingleton()); - engine->addImportPath( "qrc:/codes.eeems.oxide" ); + engine->addImportPath( "qrc:/codes.eeems.oxide"); + qmlRegisterType("codes.eeems.oxide", 3, 0, "Canvas"); + } + + Canvas::Canvas(QQuickItem* parent) + : QQuickPaintedItem(parent), + m_brush{Qt::black}, + m_penWidth{6} + { + setAcceptedMouseButtons(Qt::AllButtons); + m_drawn = QImage(width(), height(), QImage::Format_ARGB32_Premultiplied); + m_drawn.fill(Qt::transparent); + } + + void Canvas::paint(QPainter* painter){ + painter->drawImage(boundingRect(), m_drawn); + } + + QBrush Canvas::brush(){ return m_brush; } + + void Canvas::setBrush(QBrush brush){ + m_brush = brush; + emit brushChanged(brush); + } + + qreal Canvas::penWidth(){ return m_penWidth; } + + void Canvas::setPenWidth(qreal penWidth){ + m_penWidth = penWidth; + emit penWidthChanged(penWidth); + } + + QImage* Canvas::image(){ return &m_drawn; } + + void Canvas::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry){ + Q_UNUSED(oldGeometry); + auto size = newGeometry.size().toSize(); + if(size.isEmpty()){ + return; + } + QImage image(size, QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + QPainter p(&image); + p.drawImage(image.rect(), m_drawn, m_drawn.rect()); + p.end(); + m_drawn = image; + } + + void Canvas::mousePressEvent(QMouseEvent* event){ + if(!isEnabled()){ + return; + } + m_lastPoint = event->localPos(); + emit drawStart(); + } + + void Canvas::mouseMoveEvent(QMouseEvent* event){ + if( + !isEnabled() + || !contains(event->localPos()) + ){ + return; + } + QPainter p(&m_drawn); + QPen pen(m_brush, m_penWidth); + p.setPen(pen); + p.drawLine(m_lastPoint, event->localPos()); + p.end(); + const QPoint globalStart = mapToScene(m_lastPoint).toPoint(); + const QPoint globalEnd = event->globalPos(); + auto rect = QRect(globalStart, globalEnd) + .normalized() + .marginsAdded(QMargins(24, 24, 24, 24)); + { + auto image = getImageForWindow(window()); + QPainter p(&image); + p.setClipRect(mapRectToScene(boundingRect())); + p.setPen(pen); + p.drawLine(globalStart, globalEnd); + repaint(window(), rect, Blight::WaveformMode::Mono); + } + m_lastPoint = event->localPos(); + } + + void Canvas::mouseReleaseEvent(QMouseEvent* event){ + Q_UNUSED(event); + auto color = m_brush.color(); + if( + !m_brush.isOpaque() + && ( + color != Qt::black || color != Qt::white + ) + ){ + repaint(window(), mapRectToScene(boundingRect())); + } + emit drawDone(); + } + + Blight::shared_buf_t getSurfaceForWindow(QWindow* window){ + static auto fn = (Blight::shared_buf_t(*)(QWindow*))qGuiApp->platformFunction("getSurfaceForWindow"); + if(fn == nullptr){ + qFatal("Could not get getSurfaceForWindow"); + } + return fn(window); + } + + QImage getImageForWindow(QWindow* window){ + return getImageForSurface(getSurfaceForWindow(window)); + } + + void repaint(QWindow* window, QRectF rect, Blight::WaveformMode waveform, bool sync){ + auto buf = getSurfaceForWindow(window); + unsigned int _marker = 0; + if(sync){ + _marker = ++marker; + } + auto maybe = Blight::connection()->repaint( + buf, + rect.x(), + rect.y(), + rect.width(), + rect.height(), + waveform, + _marker + ); + if(sync && maybe.has_value()){ + Blight::connection()->waitForMarker(_marker); + } + } + + QImage getImageForSurface(Blight::shared_buf_t buffer){ + return QImage( + buffer->data, + buffer->width, + buffer->height, + buffer->stride, + (QImage::Format)buffer->format + ); } } } diff --git a/shared/liboxide/oxideqml.h b/shared/liboxide/oxideqml.h index 9e4feb7d1..83a5aa3eb 100644 --- a/shared/liboxide/oxideqml.h +++ b/shared/liboxide/oxideqml.h @@ -1,25 +1,237 @@ +/*! + * \addtogroup QML + * \brief The QML module + * @{ + * \file + */ #pragma once #include +#include #include #include #include +#include +#include + +#include namespace Oxide { + /*! + * \brief The Oxide::QML namespace + */ namespace QML{ + /*! + * \brief An object available as Oxide in qml that provides useful properties and methods + * + * Example: + * + * ```qml + * Item { + * enabled: Oxide.landscape && Oxide.deviceName === "reMarkable 2" + * } + * ``` + */ class OxideQml : public QObject{ Q_OBJECT - Q_PROPERTY(bool landscape READ landscape NOTIFY landscapeChanged REVISION 1) + /*! + * \brief If the device should be in landscape or not + * \accessors landscape() + * \notifier landscapeChanged(bool) + */ + Q_PROPERTY(bool landscape READ landscape NOTIFY landscapeChanged) + /*! + * \brief The name of the device + * \accessors deviceName() + */ + Q_PROPERTY(QString deviceName READ deviceName CONSTANT) QML_NAMED_ELEMENT(Oxide) QML_SINGLETON + public: explicit OxideQml(QObject *parent = nullptr); + /*! + * \brief Check if the device should be in landscape mode or not + * \return If the device should be in landscape or not + * \sa landscape, landscapeChanged(bool) + */ bool landscape(); + /*! + * \brief Get the name of the device + * \return The name of the device + * \retval "reMarkable 1" + * \retval "reMarkable 2" + * \retval "Unknown" + * \sa deviceName + */ + QString deviceName(); + /*! + * \brief Get a QBrush for a colour + * \param color Colour + * \return QBrush for a colour + */ + Q_INVOKABLE QBrush brushFromColor(const QColor& color); signals: + /*! + * \brief The value of landscape changed + * \sa landscape, landscape() + */ void landscapeChanged(bool); }; + /*! + * \brief A canvas widget + * + * Example: + * ```qml + * import "qrc://codes.eeems.oxide" + * import codes.eeems.oxide 3.0 + * + * OxideWindow{ + * initialItem: Canvas{ + * anchors.fill: parent + * brush: Oxide.brushFromColor(Qt.black) + * penWidth: 12 + * } + * } + * ``` + */ + class Canvas : public QQuickPaintedItem { + Q_OBJECT + /*! + * \property brush + * \brief Current brush used when drawing + * \accessors brush(), setBrush(QBrush) + * \notifier brushChanged(const QBrush&) + */ + Q_PROPERTY(QBrush brush READ brush WRITE setBrush NOTIFY brushChanged) + /*! + * \property penWidth + * \brief Current pen width used when drawing + * \accessors penWidth(), setPenWidth(qreal) + * \notifier penWidthChanged(qreal) + */ + Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged) + QML_ELEMENT + + public: + /*! + * \brief Create a new canvas instance + * \param parent Parent widget + */ + Canvas(QQuickItem* parent = nullptr); + void paint(QPainter* painter) override; + /*! + * \brief Current brush used when drawing + * \return The current brush + * \sa brush, setBrush(QBrush), brushChanged(const QBrush&) + */ + QBrush brush(); + /*! + * \brief Set the current brush that is used for drawing + * \param brush Brush to use + * \sa brush, brush(), brushChanged(const QBrush&) + */ + void setBrush(QBrush brush); + /*! + * \brief Current pen width used when drawing + * \return The current pen with + * \sa penWidth, setPenWidth(QBrush), penWidthChanged(const QBrush&) + */ + qreal penWidth(); + /*! + * \brief Set the current pen width used for drawing + * \param penWidth Pen width to use + * \sa penWidth, penWidth(), penWidthChanged(qreal) + */ + void setPenWidth(qreal penWidth); + /*! + * \brief QImage instance of the current canvas + * \return QImage instanceof the current canvas + */ + QImage* image(); + + signals: + /*! + * \brief The user has started drawing on the canvas + */ + void drawStart(); + /*! + * \brief The user has finished drawing on the canvas + */ + void drawDone(); + /*! + * \brief The current brush used for drawing has been changed + * \param brush New brush + * \sa brush, brush(), setBrush(QBrush) + */ + void brushChanged(const QBrush& brush); + /*! + * \brief The current pen width used for drawing has been changed + * \param pendWidth New pen width + * \sa penWidth, penWidth(), setPenWidth(qreal) + */ + void penWidthChanged(qreal pendWidth); + + protected: + void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + + private: + QPointF m_lastPoint; + QImage m_drawn; + QBrush m_brush; + qreal m_penWidth; + }; + /*! + * \brief Get the display server buffer that represents a surface for a QWindow + * \param window The Window to get the surface buffer for + * \return The buffer that represents the display server surface + */ + Blight::shared_buf_t getSurfaceForWindow(QWindow* window); + /*! + * \brief Get the a QImage that can be used to manipulate a display server buffer + * \param buffer The buffer to create the QImage instance for + * \return A QImage instance that can be used to manipulate a display server buffer + */ + QImage getImageForSurface(Blight::shared_buf_t buffer); + /*! + * \brief Get a QImage instance that can be used to manipulate a display server buffer for a QWindow + * \param window The QWindow instance + * \return A QImage instance that can be used to manipulate a display server buffer for the QWindow + */ + QImage getImageForWindow(QWindow* window); + /*! + * \brief Repaint a surface for a QWindow on the display server + * \param window The QWindow instance to repaint + * \param rect The area of the QWindow to repaint + * \param waveform The waveform to use for repainting + * \param sync If the method should wait for the repaint to finish before continuing + */ + void repaint( + QWindow* window, + QRectF rect, + Blight::WaveformMode waveform = Blight::WaveformMode::HighQualityGrayscale, + bool sync = false + ); + /*! + * \brief Get the OxideQML singleton instance + * \return The OxideQML singleton instance + * \sa OxideQML + */ OxideQml* getSingleton(); + /*! + * \brief Register the %QML extensions that liboxide provides + * \param engine The QQmlApplicationEngine instance that this application is using + * \sa OxideQml, Canvas + * + * This registers the OxideQML singleton so that it can be accessed via Oxide in QML. + * It also registers the built in QML classes that liboxide provides so that they can be imported with `import "qrc://codes.eeems.oxide"`. + * Other classes are available for import with `import codes.eeems.oxide 3.0` + */ void registerQML(QQmlApplicationEngine* engine); } } +/*! @} */ diff --git a/shared/liboxide/settingsfile.h b/shared/liboxide/settingsfile.h index 5de0183df..316dd0f89 100644 --- a/shared/liboxide/settingsfile.h +++ b/shared/liboxide/settingsfile.h @@ -92,30 +92,31 @@ O_SETTINGS_PROPERTY_BODY_2, \ O_SETTINGS_PROPERTY_BODY_1, \ ) +#ifdef IN_DOXYGEN /*! - * \def O_SETTINGS_PROPERTY * \brief Add a property to a SettingsFile derived class - * \param _type Type of property + * \param type Type of property * \param group Group name for property. This usually should be ``General`` * \param member Property name - * \param _default Optional default value + * \param default Optional default value * \sa O_SETTINGS, O_SETTINGS_PROPERTY_BODY, Oxide::SettingsFile */ -#define O_SETTINGS_PROPERTY(...) O_SETTINGS_PROPERTY_X(__VA_ARGS__)(__VA_ARGS__) +#define O_SETTINGS_PROPERTY(type, group, member, ...) O_SETTINGS_PROPERTY_X(type, group, member, __VA_ARGS__)(type, group, member, __VA_ARGS__) /*! - * \def O_SETTINGS_PROPERTY_BODY * \brief Add the body for a property on a SettingsFile derived class - * \param _class Class name - * \param _type Type of property + * \param class Class name + * \param type Type of property * \param group Group name for property. This usually should be ``General`` * \param member Property name - * \param _default Optional default value + * \param default Optional default value * \sa O_SETTINGS, O_SETTINGS_PROPERTY, Oxide::SettingsFile */ +#define O_SETTINGS_PROPERTY_BODY(class, type, group, member, ...) O_SETTINGS_PROPERTY_BODY_X(class, type, group, member, __VA_ARGS__)(class, type, group, member, __VA_ARGS__) +#else +#define O_SETTINGS_PROPERTY(...) O_SETTINGS_PROPERTY_X(__VA_ARGS__)(__VA_ARGS__) #define O_SETTINGS_PROPERTY_BODY(...) O_SETTINGS_PROPERTY_BODY_X(__VA_ARGS__)(__VA_ARGS__) - +#endif /*! - * \def O_SETTINGS * \brief Define the instance() and constructor methods for a SettingsFile derived class * \param _type Class name * \param path Path to file on disk that stores the settings @@ -143,7 +144,11 @@ namespace Oxide { */ class LIBOXIDE_EXPORT SettingsFile : public QSettings { Q_OBJECT + signals: + /*! + * \brief The settings file has changed + */ void changed(); private slots: diff --git a/shared/liboxide/signalhandler.cpp b/shared/liboxide/signalhandler.cpp index 5c4413981..eabd00bc5 100644 --- a/shared/liboxide/signalhandler.cpp +++ b/shared/liboxide/signalhandler.cpp @@ -1,4 +1,5 @@ #include "signalhandler.h" +#include "debug.h" #include @@ -7,80 +8,97 @@ #include #include -static int sigUsr1Fd[2]; -static int sigUsr2Fd[2]; +static bool initialized = false; namespace Oxide { int SignalHandler::setup_unix_signal_handlers(){ - if(!signalHandler){ - new SignalHandler(qApp); + struct sigaction action; + action.sa_handler = SignalHandler::handleSignal; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_flags |= SA_RESTART; +#define _sigaction(signal) \ + if(sigaction(signal, &action, 0)){ \ + return signal; \ } - struct sigaction usr1, usr2; - - usr1.sa_handler = SignalHandler::usr1SignalHandler; - sigemptyset(&usr1.sa_mask); - usr1.sa_flags = 0; - usr1.sa_flags |= SA_RESTART; - if(sigaction(SIGUSR1, &usr1, 0)){ - return 1; - } - - usr2.sa_handler = SignalHandler::usr2SignalHandler; - sigemptyset(&usr2.sa_mask); - usr2.sa_flags = 0; - usr2.sa_flags |= SA_RESTART; - if(sigaction(SIGUSR2, &usr2, 0)){ - return 2; - } - + _sigaction(SIGTERM); + _sigaction(SIGINT); + _sigaction(SIGUSR1); + _sigaction(SIGUSR2); + _sigaction(SIGCONT); + _sigaction(SIGPIPE); + _sigaction(SIGSEGV); + _sigaction(SIGBUS); +#undef _sigaction + initialized = true; return 0; } - SignalHandler* SignalHandler::singleton(SignalHandler* self){ + SignalHandler* SignalHandler::__singleton(){ static SignalHandler* instance; - if(self != nullptr){ - instance = self; + if(instance == nullptr){ + instance = new SignalHandler(qApp); + } + if(!initialized){ + auto res = setup_unix_signal_handlers(); + if(res){ + qFatal(QString("Failed to setup signal handlers: %1").arg(res).toStdString().c_str()); + } } return instance; } SignalHandler::SignalHandler(QObject *parent) : QObject(parent){ - singleton(this); - if(::socketpair(AF_UNIX, SOCK_STREAM, 0, sigUsr1Fd)){ - qFatal("Couldn't create USR1 socketpair"); - } - if(::socketpair(AF_UNIX, SOCK_STREAM, 0, sigUsr2Fd)){ - qFatal("Couldn't create USR2 socketpair"); - } - - snUsr1 = new QSocketNotifier(sigUsr1Fd[1], QSocketNotifier::Read, this); - connect(snUsr1, &QSocketNotifier::activated, this, &SignalHandler::handleSigUsr1, Qt::QueuedConnection); - snUsr2 = new QSocketNotifier(sigUsr2Fd[1], QSocketNotifier::Read, this); - connect(snUsr2, &QSocketNotifier::activated, this, &SignalHandler::handleSigUsr2, Qt::QueuedConnection); + addNotifier(SIGTERM, "sigTerm"); + addNotifier(SIGINT, "sigInt"); + addNotifier(SIGUSR1, "sigUsr1"); + addNotifier(SIGUSR2, "sigUsr2"); + addNotifier(SIGCONT, "sigCont"); + addNotifier(SIGPIPE, "sigPipe"); + addNotifier(SIGSEGV, "sigSegv"); + addNotifier(SIGBUS, "sigBus"); } - SignalHandler::~SignalHandler(){} - void SignalHandler::usr1SignalHandler(int unused){ - Q_UNUSED(unused) - char a = 1; - ::write(sigUsr1Fd[0], &a, sizeof(a)); + SignalHandler::~SignalHandler(){ + while(!notifiers.isEmpty()){ + auto notifier = notifiers.take(notifiers.firstKey()); + delete notifier.notifier; + } } - void SignalHandler::usr2SignalHandler(int unused){ - Q_UNUSED(unused) + void SignalHandler::handleSignal(int signal){ + if(!notifiers.contains(signal)){ + ::signal(signal, SIG_DFL); + return; + } + O_DEBUG("Signal recieved:" << strsignal(signal)); + auto item = notifiers.value(signal); char a = 1; - ::write(sigUsr2Fd[0], &a, sizeof(a)); - } - void SignalHandler::handleSigUsr1(){ - snUsr1->setEnabled(false); - char tmp; - ::read(sigUsr1Fd[1], &tmp, sizeof(tmp)); - emit sigUsr1(); - snUsr1->setEnabled(true); + ::write(item.fd, &a, sizeof(a)); } - void SignalHandler::handleSigUsr2(){ - snUsr2->setEnabled(false); - char tmp; - ::read(sigUsr2Fd[1], &tmp, sizeof(tmp)); - emit sigUsr2(); - snUsr2->setEnabled(true); + void SignalHandler::addNotifier(int signal, const char* name){ + if(!notifiers.contains(signal)){ + int fds[2]; + if(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds)){ + qFatal("Couldn't create socketpair"); + } + auto socket = new QLocalSocket(); + if(!socket->setSocketDescriptor(fds[1], QLocalSocket::ConnectedState, QLocalSocket::ReadOnly | QLocalSocket::Unbuffered)){ + qFatal("Couldn't connect QLocalSocket to socket descriptor"); + } + notifiers.insert(signal, NotifierItem{ + .notifier = socket, + .fd = fds[0] + }); + } + auto notifier = notifiers.value(signal).notifier; + connect(notifier, &QLocalSocket::readyRead, this, [this, signal, notifier, name]{ + while(!notifier->atEnd()){ + notifier->read(sizeof(char)); + if(!QMetaObject::invokeMethod(this, name, Qt::QueuedConnection)){ + O_WARNING("Failed to emit" << name); + } + O_DEBUG("emitted" << name); + } + }, Qt::QueuedConnection); } + QMap SignalHandler::notifiers = QMap(); } #include "moc_signalhandler.cpp" diff --git a/shared/liboxide/signalhandler.h b/shared/liboxide/signalhandler.h index ac45f3d6a..b89f92ef4 100644 --- a/shared/liboxide/signalhandler.h +++ b/shared/liboxide/signalhandler.h @@ -8,15 +8,17 @@ #include "liboxide_global.h" #include -#include +#include +#include /*! - * \brief signalHandler() + * \brief signalHandler + * \note This should not be called outside of the Qt event loop */ -#define signalHandler Oxide::SignalHandler::singleton() +#define signalHandler Oxide::SignalHandler::__singleton() namespace Oxide { /*! - * \brief A class that allows handling SIGUSR1 and SIGUSR2 + * \brief A class that allows handling various signals * \snippet examples/oxide.cpp SignalHandler */ class LIBOXIDE_EXPORT SignalHandler : public QObject @@ -24,9 +26,15 @@ namespace Oxide { Q_OBJECT public: /*! - * \brief Setup the unix signal handlers to listen to SIGUSR1 and SIGUSR2 - * \retval 1 Failed to setup SIGUSR1 - * \retval 2 Failed to setup SIGUSR2 + * \brief Setup the unix signal handlers to listen to supported signals + * \retval SIGTERM Failed to setup SIGTERM + * \retval SIGINT Failed to setup SIGINT + * \retval SIGUSR1 Failed to setup SIGUSR1 + * \retval SIGUSR2 Failed to setup SIGUSR2 + * \retval SIGCONT Failed to setup SIGCONT + * \retval SIGPIPE Failed to setup SIGPIPE + * \retval SIGSEGV Failed to setup SIGSEGV + * \retval SIGBUS Failed to setup SIGBUS * \retval 0 Successfully setup both signal handlers * * This method will automatically create and register the singleton with a parent of qApp. @@ -36,24 +44,30 @@ namespace Oxide { * \brief Get the static instance of this class. You should use the signalHandler macro instead. * \return The static instance * \sa signalHandler + * \note This should not be called outside of the Qt event loop */ - static SignalHandler* singleton(SignalHandler* self = nullptr); + static SignalHandler* __singleton(); /*! * \brief Create an instance of SignalHandler. * \param parent Optional QObject parent * * Manually constructing this class will attempt to register it as the singleton instance. + * + * \note This should not be called outside of the Qt event loop */ SignalHandler(QObject *parent = 0); ~SignalHandler(); - static void usr1SignalHandler(int unused); - static void usr2SignalHandler(int unused); - - public slots: - void handleSigUsr1(); - void handleSigUsr2(); + static void handleSignal(int signal); signals: + /*! + * \brief The process has recieved a SIGINT + */ + void sigTerm(); + /*! + * \brief The process has recieved a SIGINT + */ + void sigInt(); /*! * \brief The process has recieved a SIGUSR1 */ @@ -62,10 +76,30 @@ namespace Oxide { * \brief The process has recieved a SIGUSR2 */ void sigUsr2(); + /*! + * \brief The process has recieved a SIGCONT + */ + void sigCont(); + /*! + * \brief The process has recieved a SIGPIPE + */ + void sigPipe(); + /*! + * \brief The process has recieved a SIGSEGV + */ + void sigSegv(); + /*! + * \brief The process has recieved a SIGBUS + */ + void sigBus(); private: - QSocketNotifier* snUsr1; - QSocketNotifier* snUsr2; + void addNotifier(int signal, const char* name); + struct NotifierItem { + QLocalSocket* notifier; + int fd; + }; + static QMap notifiers; }; } /*! @} */ diff --git a/shared/liboxide/socketpair.cpp b/shared/liboxide/socketpair.cpp new file mode 100644 index 000000000..600fd1a76 --- /dev/null +++ b/shared/liboxide/socketpair.cpp @@ -0,0 +1,95 @@ +#include "socketpair.h" +#include "debug.h" + +#include + +namespace Oxide{ + SocketPair::SocketPair(bool allowWriteSocketRead) + : QObject{nullptr}, + m_readSocket{this}, + m_writeSocket{this}, + m_enabled{false}, + m_allowWriteSocketRead{allowWriteSocketRead} + { + int fds[2]; + if(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1){ + O_WARNING("Unable to open socket pair:" << strerror(errno)); + } + if(!m_readSocket.setSocketDescriptor(fds[1], QLocalSocket::ConnectedState, QLocalSocket::ReadOnly | QLocalSocket::Unbuffered)){ + O_WARNING("Unable to open socket pair read socket:" << m_readSocket.errorString()); + } + auto mode = allowWriteSocketRead ? QLocalSocket::ReadWrite : QLocalSocket::WriteOnly; + if(!m_writeSocket.setSocketDescriptor(fds[0], QLocalSocket::ConnectedState, mode | QLocalSocket::Unbuffered)){ + O_WARNING("Unable to open socket pair write socket:" << m_writeSocket.errorString()); + } + if(allowWriteSocketRead){ + connect(&m_writeSocket, &QLocalSocket::readyRead, this, &SocketPair::_readyRead); + } + connect(&m_writeSocket, &QLocalSocket::disconnected, this, &SocketPair::disconnected); + connect(&m_writeSocket, &QLocalSocket::bytesWritten, this, &SocketPair::bytesWritten); + } + + SocketPair::~SocketPair(){ close(); } + + bool SocketPair::isValid(){ return m_readSocket.isValid() && m_writeSocket.isValid(); } + + bool SocketPair::isReadable(){ return m_enabled && m_allowWriteSocketRead && m_writeSocket.isReadable(); } + + bool SocketPair::isOpen(){ return m_readSocket.isOpen() && m_writeSocket.isOpen(); } + + QLocalSocket* SocketPair::readSocket(){ return &m_readSocket; } + + QLocalSocket* SocketPair::writeSocket(){ return &m_writeSocket; } + + void SocketPair::setEnabled(bool enabled){ m_enabled = enabled; } + + bool SocketPair::enabled(){ return m_enabled; } + + bool SocketPair::atEnd(){ return !m_enabled || m_writeSocket.atEnd(); } + + QByteArray SocketPair::readLine(qint64 maxlen){ return m_allowWriteSocketRead ? m_writeSocket.readLine(maxlen) : QByteArray(); } + + QByteArray SocketPair::readAll(){ return readLine(); } + + QByteArray SocketPair::read(qint64 maxlen){ return m_allowWriteSocketRead ? m_writeSocket.read(maxlen) : QByteArray(); } + + qint64 SocketPair::bytesAvailable(){ return m_allowWriteSocketRead ? m_writeSocket.bytesAvailable() : 0; } + + qint64 SocketPair::_write(const char* data, qint64 size){ return m_writeSocket.write(data, size); } + + qint64 SocketPair::write(const char* data, qint64 size){ + if(!m_enabled){ + return 0; + } + return _write(data, size); + } + + qint64 SocketPair::_write(QByteArray data){ return m_writeSocket.write(data); } + + qint64 SocketPair::write(QByteArray data){ + if(!m_enabled){ + return 0; + } + return _write(data); + } + + QString SocketPair::errorString(){ + if(!m_enabled){ + return "SocketPair not enabled"; + } + return m_writeSocket.errorString(); + } + + void SocketPair::close(){ + m_readSocket.close(); + m_writeSocket.close(); + } + + void SocketPair::_readyRead(){ + if(m_enabled){ + emit readyRead(); + }else{ + while(m_writeSocket.skip(1024) > 0){} + } + } +} diff --git a/shared/liboxide/socketpair.h b/shared/liboxide/socketpair.h new file mode 100644 index 000000000..713c1590e --- /dev/null +++ b/shared/liboxide/socketpair.h @@ -0,0 +1,138 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ +#pragma once +#include "liboxide_global.h" +#include + +namespace Oxide{ + /*! + * \brief A socket pair used for two way communication + */ + class LIBOXIDE_EXPORT SocketPair : public QObject{ + Q_OBJECT + + public: + /*! + * \brief Create a new socket pair + * \param allowWriteSocketRead Allow reading from the write socket, this allows messages to be sent by the other end of the socket + */ + SocketPair(bool allowWriteSocketRead = false); + ~SocketPair(); + /*! + * \brief Is this socket pair valid + * \return If this socket pair valid + */ + bool isValid(); + /*! + * \brief Is this socket pair readable + * \return If this socket pair readable + */ + bool isReadable(); + /*! + * \brief Is this socket pair open + * \return If this socket pair open + */ + bool isOpen(); + /*! + * \brief The QLocalSocket instance of the read socket + * \return The QLocalSocket instance of the read socket + */ + QLocalSocket* readSocket(); + /*! + * \brief The QLocalSocket instance of the write socket + * \return The QLocalSocket instance of the write socket + */ + QLocalSocket* writeSocket(); + /*! + * \brief Enable or disable reading and writing to this socket pair + * \param enabled If reading and writing to this socket pair is enabled + */ + void setEnabled(bool enabled); + /*! + * \brief Is reading and writing to this socket pair is enabled + * \return If reading and writing to this socket pair is enabled + */ + bool enabled(); + /*! + * \brief Is there more data available to read from the read socket + * \return If there more data available to read from the read socket + */ + bool atEnd(); + /*! + * \brief Read a line from the read socket + * \param maxlen Maxiumum amount of data to read + * \return The line of data + */ + QByteArray readLine(qint64 maxlen = 0); + /*! + * \brief Read all available data on the read socket + * \return The data + */ + QByteArray readAll(); + /*! + * \brief Read data from the read socket + * \param maxlen maximum amount of data to read + * \return The data + */ + QByteArray read(qint64 maxlen = 0); + /*! + * \brief How many bytes are available to read from the read socket + * \return How many bytes that are available to read from the read socket + */ + qint64 bytesAvailable(); + qint64 _write(const char* data, qint64 size); + /*! + * \brief Write data to the write socket + * \param data Data to write + * \param size Size of data to write + * \return Amount of data written + */ + qint64 write(const char* data, qint64 size); + qint64 _write(QByteArray data); + /*! + * \brief Write data to the write socket + * \param data Data to write + * \return Amount of data written + */ + qint64 write(QByteArray data); + /*! + * \brief Error string if the last operation on the write socket errored + * \return Error string if the last operation on the write socket errored + */ + QString errorString(); + + signals: + /*! + * \brief The read socket has data available to read + */ + void readyRead(); + /*! + * \brief Data was written to the write socket + * \param bytes Amount of data written + */ + void bytesWritten(qint64 bytes); + /*! + * \brief The write socket has disconnected + */ + void disconnected(); + + public slots: + /*! + * \brief Close both the read and write sockets + */ + void close(); + + private slots: + void _readyRead(); + + private: + QLocalSocket m_readSocket; + QLocalSocket m_writeSocket; + bool m_enabled; + bool m_allowWriteSocketRead; + }; +} +/*! @} */ diff --git a/shared/liboxide/threading.cpp b/shared/liboxide/threading.cpp new file mode 100644 index 000000000..d0fc0b0e2 --- /dev/null +++ b/shared/liboxide/threading.cpp @@ -0,0 +1,104 @@ +#include "threading.h" +#include "debug.h" + +namespace Oxide{ + void startThreadWithPriority(QThread* thread, QThread::Priority priority){ + O_DEBUG(thread << "Starting thread"); + #ifndef QT_HAS_THREAD_PRIORITY_SCHEDULING + QObject::connect(thread, &QThread::started, thread, [priority]{ + switch(priority){ + case QThread::IdlePriority: + nice(19); // 19 is the max + break; + case QThread::LowestPriority: + nice(10); + break; + case QThread::LowPriority: + nice(5); + break; + case QThread::InheritPriority: + case QThread::NormalPriority: + nice(0); + break; + case QThread::HighPriority: + nice(-5); + break; + case QThread::HighestPriority: + nice(-10); + break; + case QThread::TimeCriticalPriority: + nice(-20); + break; + } + }, Qt::QueuedConnection); + #endif + thread->start(priority); + } + + void dispatchToMainThread(std::function callback){ + dispatchToMainThread([callback]{ + callback(); + return 0; + }); + } + + void dispatchToThread(QThread* thread, std::function callback){ + if(QThread::currentThread() == thread){ + // Already on the correct thread + return callback(); + } + // We are on a different thread + Q_ASSERT(!QCoreApplication::startingUp()); + O_DEBUG("Dispatching to thread" << thread); + QTimer* timer = new QTimer(); + timer->setSingleShot(true); + timer->moveToThread(thread); + bool finished = false; + QObject::connect(timer, &QTimer::timeout, thread, [callback, thread, &finished]{ + Q_ASSERT(QThread::currentThread() == thread); + // This runs on the correct thread + callback(); + finished = true; + }); + QMetaObject::invokeMethod(timer, "start", Qt::BlockingQueuedConnection, Q_ARG(int, 0)); + // Don't use an event loop to avoid deadlocks + while(!finished){ + QCoreApplication::processEvents(QEventLoop::AllEvents, 10); + } + timer->deleteLater(); + O_DEBUG("Thread dispatch finished" << thread); + } + + void runLater(QThread* thread, std::function callback){ + O_DEBUG("Run later on thread" << thread); + QTimer* timer = new QTimer(); + timer->moveToThread(thread); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, thread, [timer, callback, thread]{ + callback(); + timer->deleteLater(); + O_DEBUG("Ran on thread" << thread); + }); + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); + } + + void runLaterInMainThread(std::function callback){ runLater(qApp->thread(), callback); } + + void runInEventLoop(std::function)> callback){ + QEventLoop loop; + QTimer timer; + O_DEBUG("Running in event loop"); + QObject::connect(&timer, &QTimer::timeout, qApp, [&loop, &timer, callback]{ + if(!loop.isRunning()){ + return; + } + timer.stop(); + callback([&loop]{ + loop.quit(); + O_DEBUG("Finished running in event loop"); + }); + }); + timer.start(0); + loop.exec(); + } +} diff --git a/shared/liboxide/threading.h b/shared/liboxide/threading.h new file mode 100644 index 000000000..81907399e --- /dev/null +++ b/shared/liboxide/threading.h @@ -0,0 +1,74 @@ +/*! + * \addtogroup Oxide + * \brief The main Oxide module + * @{ + * \file + */ +#pragma once +#include "liboxide_global.h" +#include +#include +#include + +namespace Oxide { + /*! + * \brief Start a QThread with a specific priority. This will work even if Qt doesn't support priority scheduling + * \param thread Thread to start + * \param priority Priority to start with + */ + LIBOXIDE_EXPORT void startThreadWithPriority(QThread* thread, QThread::Priority priority); + /*! + * \brief Run code on the main Qt thread + * \param callback The code to run on the main thread + * * \snippet examples/oxide.cpp dispatchToMainThread + */ + LIBOXIDE_EXPORT void dispatchToMainThread(std::function callback); + /*! + * \brief Run code on the main Qt thread + * \param callback The code to run on the main thread + * \return Return value of callback + * * \snippet examples/oxide.cpp dispatchToMainThread + */ + template LIBOXIDE_EXPORT T dispatchToMainThread(std::function callback){ + return dispatchToThread(qApp->thread(), callback); + } + /*! + * \brief Run code on a specific thread + * \param thread The thread to run the callback in + * \param callback The code to run on the thread + */ + LIBOXIDE_EXPORT void dispatchToThread(QThread* thread, std::function callback); + /*! + * \brief Run code on a specific thread + * \param thread The thread to run the callback in + * \param callback The code to run on the thread + * \return Return value of callback + */ + template LIBOXIDE_EXPORT T dispatchToThread( + QThread* thread, + std::function callback + ){ + T result; + dispatchToThread(thread, [callback, &result]{ + result = callback(); + }); + return result; + } + /*! + * \brief Run code on a specific thread at some point in the near future + * \param thread The thread to run the callback in + * \param callback The code to run on the thread + */ + LIBOXIDE_EXPORT void runLater(QThread* thread, std::function callback); + /*! + * \brief Run code on the main thread at some point in the near future + * \param callback The code to run on the thread + */ + LIBOXIDE_EXPORT void runLaterInMainThread(std::function callback); + /*! + * \brief Run code in a Qt event loop + * \param callback The code to run inside an event loop + */ + LIBOXIDE_EXPORT void runInEventLoop(std::function)> callback); +} +/*! @} */ diff --git a/shared/qpa/default_keymap.h b/shared/qpa/default_keymap.h new file mode 100644 index 000000000..53837531a --- /dev/null +++ b/shared/qpa/default_keymap.h @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include "oxideeventhandler.h" +#include + +QT_BEGIN_NAMESPACE + +enum Flags { + None = 0x00, + IsDead = 0x01, + IsLetter = 0x02, + IsModifier = 0x04, + IsSystem = 0x08, + IsCapsLockException = 0x10, +}; + +enum Modifiers { + ModPlain = 0x00, + ModShift = 0x01, + ModAltGr = 0x02, + ModControl = 0x04, + ModAlt = 0x08, + ModShiftL = 0x10, + ModShiftR = 0x20, + ModCtrlL = 0x40, + ModCtrlR = 0x80, + ModMeta = 0x100, +}; + +const QEvdevKeyboardMap::Mapping OxideEventHandler::s_keymap_default[] = { + { KEY_ESC, 0xffff, 0x01000000, 0x00, Flags::None, 0x0000 }, + { KEY_1, 0x0031, 0x00000031, 0x00, Flags::None, 0x0000 }, + { KEY_1, 0x0021, 0x00000021, 0x01, Flags::None, 0x0000 }, + { KEY_2, 0x0032, 0x00000032, 0x00, Flags::None, 0x0000 }, + { KEY_2, 0x0040, 0x00000040, 0x01, Flags::None, 0x0000 }, + { KEY_2, 0x0040, 0x00000040, 0x02, Flags::None, 0x0000 }, + { KEY_3, 0x0033, 0x00000033, 0x00, Flags::None, 0x0000 }, + { KEY_3, 0x0023, 0x00000023, 0x01, Flags::None, 0x0000 }, + { KEY_3, 0xffff, 0x01000000, 0x04, Flags::None, 0x0000 }, + { KEY_4, 0x0034, 0x00000034, 0x00, Flags::None, 0x0000 }, + { KEY_4, 0x0024, 0x00000024, 0x01, Flags::None, 0x0000 }, + { KEY_4, 0x0024, 0x00000024, 0x02, Flags::None, 0x0000 }, + { KEY_4, 0x005c, 0x0400005c, 0x04, Flags::None, 0x0000 }, + { KEY_5, 0x0035, 0x00000035, 0x00, Flags::None, 0x0000 }, + { KEY_5, 0x0025, 0x00000025, 0x01, Flags::None, 0x0000 }, + { KEY_5, 0x005d, 0x0400005d, 0x04, Flags::None, 0x0000 }, + { KEY_6, 0x0036, 0x00000036, 0x00, Flags::None, 0x0000 }, + { KEY_6, 0x005e, 0x0000005e, 0x01, Flags::None, 0x0000 }, + { KEY_6, 0x005e, 0x01001252, 0x02, Flags::IsDead, 0x0000 }, + { KEY_6, 0x005e, 0x0400005e, 0x04, Flags::None, 0x0000 }, + { KEY_7, 0x0037, 0x00000037, 0x00, Flags::None, 0x0000 }, + { KEY_7, 0x0026, 0x00000026, 0x01, Flags::None, 0x0000 }, + { KEY_7, 0x007b, 0x0000007b, 0x02, Flags::None, 0x0000 }, + { KEY_7, 0x005f, 0x0400005f, 0x04, Flags::None, 0x0000 }, + { KEY_8, 0x0038, 0x00000038, 0x00, Flags::None, 0x0000 }, + { KEY_8, 0x002a, 0x0000002a, 0x01, Flags::None, 0x0000 }, + { KEY_8, 0x005b, 0x0000005b, 0x02, Flags::None, 0x0000 }, + { KEY_8, 0xffff, 0x01000003, 0x04, Flags::None, 0x0000 }, + { KEY_9, 0x0039, 0x00000039, 0x00, Flags::None, 0x0000 }, + { KEY_9, 0x0028, 0x00000028, 0x01, Flags::None, 0x0000 }, + { KEY_9, 0x005d, 0x0000005d, 0x02, Flags::None, 0x0000 }, + { KEY_0, 0x0030, 0x00000030, 0x00, Flags::None, 0x0000 }, + { KEY_0, 0x0029, 0x00000029, 0x01, Flags::None, 0x0000 }, + { KEY_0, 0x007d, 0x0000007d, 0x02, Flags::None, 0x0000 }, + { KEY_MINUS, 0x002d, 0x0000002d, 0x00, Flags::None, 0x0000 }, + { KEY_MINUS, 0x005f, 0x0000005f, 0x01, Flags::None, 0x0000 }, + { KEY_MINUS, 0x005c, 0x0000005c, 0x02, Flags::None, 0x0000 }, + { KEY_MINUS, 0x005f, 0x0400005f, 0x04, Flags::None, 0x0000 }, + { KEY_MINUS, 0x005f, 0x0400005f, 0x05, Flags::None, 0x0000 }, + { KEY_EQUAL, 0x003d, 0x0000003d, 0x00, Flags::None, 0x0000 }, + { KEY_EQUAL, 0x002b, 0x0000002b, 0x01, Flags::None, 0x0000 }, + { KEY_BACKSPACE, 0xffff, 0x01000003, 0x00, Flags::None, 0x0000 }, + { KEY_BACKSPACE, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0300 }, + { KEY_TAB, 0xffff, 0x01000001, 0x00, Flags::None, 0x0000 }, + { KEY_Q, 0x0071, 0x00000051, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0051, 0x00000051, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x00000051, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0051, 0x00000051, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x04000051, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x04000051, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x04000051, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x04000051, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x08000051, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x08000051, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x08000051, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x08000051, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x0c000051, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x0c000051, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x0c000051, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_Q, 0x0071, 0x0c000051, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x00000057, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0057, 0x00000057, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x00000057, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0057, 0x00000057, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x04000057, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x04000057, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x04000057, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x04000057, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x08000057, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x08000057, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x08000057, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x08000057, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x0c000057, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x0c000057, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x0c000057, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_W, 0x0077, 0x0c000057, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x00000045, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0045, 0x00000045, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x00000045, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0045, 0x00000045, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x04000045, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x04000045, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x04000045, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x04000045, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x08000045, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x08000045, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x08000045, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x08000045, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x0c000045, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x0c000045, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x0c000045, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_E, 0x0065, 0x0c000045, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x00000052, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0052, 0x00000052, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x00000052, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0052, 0x00000052, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x04000052, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x04000052, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x04000052, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x04000052, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x08000052, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x08000052, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x08000052, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x08000052, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x0c000052, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x0c000052, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x0c000052, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_R, 0x0072, 0x0c000052, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x00000054, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0054, 0x00000054, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x00000054, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0054, 0x00000054, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x04000054, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x04000054, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x04000054, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x04000054, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x08000054, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x08000054, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x08000054, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x08000054, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x0c000054, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x0c000054, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x0c000054, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_T, 0x0074, 0x0c000054, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x00000059, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0059, 0x00000059, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x00000059, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0059, 0x00000059, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x04000059, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x04000059, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x04000059, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x04000059, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x08000059, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x08000059, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x08000059, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x08000059, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x0c000059, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x0c000059, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x0c000059, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_Y, 0x0079, 0x0c000059, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x00000055, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0055, 0x00000055, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x00000055, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0055, 0x00000055, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x04000055, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x04000055, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x04000055, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x04000055, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x08000055, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x08000055, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x08000055, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x08000055, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x0c000055, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x0c000055, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x0c000055, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_U, 0x0075, 0x0c000055, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x00000049, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0049, 0x00000049, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x00000049, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0049, 0x00000049, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x04000049, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x04000049, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x04000049, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x04000049, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x08000049, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x08000049, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x08000049, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x08000049, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x0c000049, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x0c000049, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x0c000049, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_I, 0x0069, 0x0c000049, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0000004f, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x004f, 0x0000004f, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0000004f, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x004f, 0x0000004f, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0400004f, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0400004f, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0400004f, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0400004f, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0800004f, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0800004f, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0800004f, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0800004f, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0c00004f, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0c00004f, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0c00004f, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_O, 0x006f, 0x0c00004f, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x00000050, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0050, 0x00000050, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x00000050, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0050, 0x00000050, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x04000050, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x04000050, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x04000050, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x04000050, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x08000050, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x08000050, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x08000050, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x08000050, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x0c000050, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x0c000050, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x0c000050, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_P, 0x0070, 0x0c000050, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_LEFTBRACE, 0x005b, 0x0000005b, 0x00, Flags::None, 0x0000 }, + { KEY_LEFTBRACE, 0x007b, 0x0000007b, 0x01, Flags::None, 0x0000 }, + { KEY_LEFTBRACE, 0xffff, 0x01000000, 0x04, Flags::None, 0x0000 }, + { KEY_RIGHTBRACE, 0x005d, 0x0000005d, 0x00, Flags::None, 0x0000 }, + { KEY_RIGHTBRACE, 0x007d, 0x0000007d, 0x01, Flags::None, 0x0000 }, + { KEY_RIGHTBRACE, 0x007e, 0x0000007e, 0x02, Flags::None, 0x0000 }, + { KEY_RIGHTBRACE, 0x005d, 0x0400005d, 0x04, Flags::None, 0x0000 }, + { KEY_ENTER, 0xffff, 0x01000004, 0x00, Flags::None, 0x0000 }, + { KEY_ENTER, 0x006d, 0x0c00004d, 0x08, Flags::None, 0x0000 }, + { KEY_LEFTCTRL, 0xffff, 0x01000021, 0x00, Flags::IsModifier, Modifiers::ModControl | Modifiers::ModCtrlL }, + { KEY_A, 0x0061, 0x00000041, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0041, 0x00000041, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x00000041, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0041, 0x00000041, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x04000041, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x04000041, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x04000041, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x04000041, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x08000041, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x08000041, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x08000041, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x08000041, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x0c000041, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x0c000041, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x0c000041, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_A, 0x0061, 0x0c000041, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x00000053, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0053, 0x00000053, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x00000053, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0053, 0x00000053, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x04000053, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x04000053, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x04000053, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x04000053, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x08000053, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x08000053, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x08000053, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x08000053, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x0c000053, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x0c000053, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x0c000053, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_S, 0x0073, 0x0c000053, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x00000044, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0044, 0x00000044, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x00000044, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0044, 0x00000044, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x04000044, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x04000044, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x04000044, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x04000044, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x08000044, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x08000044, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x08000044, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x08000044, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x0c000044, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x0c000044, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x0c000044, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_D, 0x0064, 0x0c000044, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x00000046, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0046, 0x00000046, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x00000046, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0046, 0x00000046, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x04000046, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x04000046, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x04000046, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x04000046, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x08000046, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x08000046, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x08000046, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x08000046, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x0c000046, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x0c000046, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x0c000046, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_F, 0x0066, 0x0c000046, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x00000047, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0047, 0x00000047, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x00000047, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0047, 0x00000047, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x04000047, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x04000047, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x04000047, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x04000047, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x08000047, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x08000047, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x08000047, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x08000047, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x0c000047, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x0c000047, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x0c000047, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_G, 0x0067, 0x0c000047, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x00000048, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0048, 0x00000048, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x00000048, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0048, 0x00000048, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x04000048, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x04000048, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x04000048, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x04000048, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x08000048, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x08000048, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x08000048, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x08000048, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x0c000048, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x0c000048, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x0c000048, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_H, 0x0068, 0x0c000048, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0000004a, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x004a, 0x0000004a, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0000004a, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x004a, 0x0000004a, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0400004a, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0400004a, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0400004a, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0400004a, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0800004a, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0800004a, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0800004a, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0800004a, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0c00004a, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0c00004a, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0c00004a, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_J, 0x006a, 0x0c00004a, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0000004b, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x004b, 0x0000004b, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0000004b, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x004b, 0x0000004b, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0400004b, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0400004b, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0400004b, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0400004b, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0800004b, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0800004b, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0800004b, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0800004b, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0c00004b, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0c00004b, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0c00004b, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_K, 0x006b, 0x0c00004b, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0000004c, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x004c, 0x0000004c, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0000004c, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x004c, 0x0000004c, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0400004c, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0400004c, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0400004c, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0400004c, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0800004c, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0800004c, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0800004c, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0800004c, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0c00004c, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0c00004c, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0c00004c, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_L, 0x006c, 0x0c00004c, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_SEMICOLON, 0x003b, 0x0000003b, 0x00, Flags::None, 0x0000 }, + { KEY_SEMICOLON, 0x003a, 0x0000003a, 0x01, Flags::None, 0x0000 }, + { KEY_APOSTROPHE, 0x0027, 0x00000027, 0x00, Flags::None, 0x0000 }, + { KEY_APOSTROPHE, 0x0022, 0x00000022, 0x01, Flags::None, 0x0000 }, + { KEY_APOSTROPHE, 0x0027, 0x01001251, 0x02, Flags::IsDead, 0x0000 }, + { KEY_APOSTROPHE, 0x0022, 0x01001257, 0x03, Flags::IsDead, 0x0000 }, + { KEY_APOSTROPHE, 0x0067, 0x04000047, 0x04, Flags::None, 0x0000 }, + { KEY_GRAVE, 0x0060, 0x00000060, 0x00, Flags::None, 0x0000 }, + { KEY_GRAVE, 0x007e, 0x0000007e, 0x01, Flags::None, 0x0000 }, + { KEY_GRAVE, 0x0060, 0x01001250, 0x02, Flags::IsDead, 0x0000 }, + { KEY_GRAVE, 0x007e, 0x01001253, 0x03, Flags::IsDead, 0x0000 }, + { KEY_LEFTSHIFT, 0xffff, 0x01000020, 0x00, Flags::IsModifier, Modifiers::ModShift | Modifiers::ModShiftL }, + { KEY_BACKSLASH, 0x005c, 0x0000005c, 0x00, Flags::None, 0x0000 }, + { KEY_BACKSLASH, 0x007c, 0x0000007c, 0x01, Flags::None, 0x0000 }, + { KEY_BACKSLASH, 0x005c, 0x0400005c, 0x04, Flags::None, 0x0000 }, + { KEY_Z, 0x007a, 0x0000005a, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x005a, 0x0000005a, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0000005a, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x005a, 0x0000005a, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0400005a, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0400005a, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0400005a, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0400005a, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0800005a, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0800005a, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0800005a, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0800005a, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0c00005a, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0c00005a, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0c00005a, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_Z, 0x007a, 0x0c00005a, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x00000058, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0058, 0x00000058, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x00000058, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0058, 0x00000058, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x04000058, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x04000058, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x04000058, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x04000058, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x08000058, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x08000058, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x08000058, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x08000058, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x0c000058, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x0c000058, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x0c000058, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_X, 0x0078, 0x0c000058, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x00000043, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0043, 0x00000043, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x00000043, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0043, 0x00000043, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x04000043, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x04000043, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x04000043, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x04000043, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x08000043, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x08000043, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x08000043, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x08000043, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x0c000043, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x0c000043, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x0c000043, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_C, 0x0063, 0x0c000043, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x00000056, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0056, 0x00000056, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x00000056, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0056, 0x00000056, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x04000056, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x04000056, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x04000056, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x04000056, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x08000056, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x08000056, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x08000056, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x08000056, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x0c000056, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x0c000056, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x0c000056, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_V, 0x0076, 0x0c000056, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x00000042, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0042, 0x00000042, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x00000042, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0042, 0x00000042, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x04000042, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x04000042, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x04000042, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x04000042, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x08000042, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x08000042, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x08000042, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x08000042, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x0c000042, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x0c000042, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x0c000042, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_B, 0x0062, 0x0c000042, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0000004e, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x004e, 0x0000004e, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0000004e, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x004e, 0x0000004e, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0400004e, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0400004e, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0400004e, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0400004e, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0800004e, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0800004e, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0800004e, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0800004e, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0c00004e, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0c00004e, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0c00004e, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_N, 0x006e, 0x0c00004e, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0000004d, 0x00, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x004d, 0x0000004d, 0x01, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0000004d, 0x02, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x004d, 0x0000004d, 0x03, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0400004d, 0x04, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0400004d, 0x05, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0400004d, 0x06, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0400004d, 0x07, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0800004d, 0x08, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0800004d, 0x09, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0800004d, 0x0a, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0800004d, 0x0b, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0c00004d, 0x0c, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0c00004d, 0x0d, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0c00004d, 0x0e, Flags::IsLetter, 0x0000 }, + { KEY_M, 0x006d, 0x0c00004d, 0x0f, Flags::IsLetter, 0x0000 }, + { KEY_COMMA, 0x002c, 0x0000002c, 0x00, Flags::None, 0x0000 }, + { KEY_COMMA, 0x003c, 0x0000003c, 0x01, Flags::None, 0x0000 }, + { KEY_COMMA, 0x002c, 0x0100125b, 0x02, Flags::IsDead, 0x0000 }, + { KEY_DOT, 0x002e, 0x0000002e, 0x00, Flags::None, 0x0000 }, + { KEY_DOT, 0x003e, 0x0000003e, 0x01, Flags::None, 0x0000 }, + { KEY_DOT, 0xffff, 0x01001120, 0x02, Flags::None, 0x0000 }, + { KEY_SLASH, 0x002f, 0x0000002f, 0x00, Flags::None, 0x0000 }, + { KEY_SLASH, 0x003f, 0x0000003f, 0x01, Flags::None, 0x0000 }, + { KEY_SLASH, 0xffff, 0x01000003, 0x04, Flags::None, 0x0000 }, + { KEY_RIGHTSHIFT, 0xffff, 0x01000020, 0x00, Flags::IsModifier, Modifiers::ModShift | Modifiers::ModShiftR }, + { KEY_KPASTERISK, 0x002a, 0x2000002a, 0x00, Flags::None, 0x0000 }, + { KEY_LEFTALT, 0xffff, 0x01000023, 0x00, Flags::IsModifier, Modifiers::ModAlt }, + { KEY_SPACE, 0x0020, 0x00000020, 0x00, Flags::None, 0x0000 }, + { KEY_CAPSLOCK, 0xffff, 0x01000024, 0x00, Flags::None, 0x0000 }, + { KEY_F1, 0xffff, 0x01000030, 0x00, Flags::None, 0x0000 }, + { KEY_F1, 0xffff, 0x0100003c, 0x01, Flags::None, 0x0000 }, + { KEY_F1, 0xffff, 0x01000048, 0x04, Flags::None, 0x0000 }, + { KEY_F1, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0100 }, + { KEY_F2, 0xffff, 0x01000031, 0x00, Flags::None, 0x0000 }, + { KEY_F2, 0xffff, 0x0100003d, 0x01, Flags::None, 0x0000 }, + { KEY_F2, 0xffff, 0x01000049, 0x04, Flags::None, 0x0000 }, + { KEY_F2, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0101 }, + { KEY_F3, 0xffff, 0x01000032, 0x00, Flags::None, 0x0000 }, + { KEY_F3, 0xffff, 0x0100003e, 0x01, Flags::None, 0x0000 }, + { KEY_F3, 0xffff, 0x0100004a, 0x04, Flags::None, 0x0000 }, + { KEY_F3, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0102 }, + { KEY_F4, 0xffff, 0x01000033, 0x00, Flags::None, 0x0000 }, + { KEY_F4, 0xffff, 0x0100003f, 0x01, Flags::None, 0x0000 }, + { KEY_F4, 0xffff, 0x0100004b, 0x04, Flags::None, 0x0000 }, + { KEY_F4, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0103 }, + { KEY_F5, 0xffff, 0x01000034, 0x00, Flags::None, 0x0000 }, + { KEY_F5, 0xffff, 0x01000040, 0x01, Flags::None, 0x0000 }, + { KEY_F5, 0xffff, 0x0100004c, 0x04, Flags::None, 0x0000 }, + { KEY_F5, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0104 }, + { KEY_F6, 0xffff, 0x01000035, 0x00, Flags::None, 0x0000 }, + { KEY_F6, 0xffff, 0x01000041, 0x01, Flags::None, 0x0000 }, + { KEY_F6, 0xffff, 0x0100004d, 0x04, Flags::None, 0x0000 }, + { KEY_F6, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0105 }, + { KEY_F7, 0xffff, 0x01000036, 0x00, Flags::None, 0x0000 }, + { KEY_F7, 0xffff, 0x01000042, 0x01, Flags::None, 0x0000 }, + { KEY_F7, 0xffff, 0x0100004e, 0x04, Flags::None, 0x0000 }, + { KEY_F7, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0106 }, + { KEY_F8, 0xffff, 0x01000037, 0x00, Flags::None, 0x0000 }, + { KEY_F8, 0xffff, 0x01000043, 0x01, Flags::None, 0x0000 }, + { KEY_F8, 0xffff, 0x0100004f, 0x04, Flags::None, 0x0000 }, + { KEY_F8, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0107 }, + { KEY_F9, 0xffff, 0x01000038, 0x00, Flags::None, 0x0000 }, + { KEY_F9, 0xffff, 0x01000044, 0x01, Flags::None, 0x0000 }, + { KEY_F9, 0xffff, 0x01000050, 0x04, Flags::None, 0x0000 }, + { KEY_F9, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0108 }, + { KEY_F10, 0xffff, 0x01000039, 0x00, Flags::None, 0x0000 }, + { KEY_F10, 0xffff, 0x01000045, 0x01, Flags::None, 0x0000 }, + { KEY_F10, 0xffff, 0x01000051, 0x04, Flags::None, 0x0000 }, + { KEY_F10, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0109 }, + { KEY_NUMLOCK, 0xffff, 0x01000025, 0x00, Flags::None, 0x0000 }, + { KEY_SCROLLLOCK, 0xffff, 0x01000026, 0x00, Flags::None, 0x0000 }, + { KEY_SCROLLLOCK, 0xffff, 0x01000026, 0x08, Flags::None, 0x0000 }, + { KEY_KP7, 0x0037, 0x20000037, 0x00, Flags::None, 0x0000 }, + { KEY_KP8, 0x0038, 0x20000038, 0x00, Flags::None, 0x0000 }, + { KEY_KP9, 0x0039, 0x20000039, 0x00, Flags::None, 0x0000 }, + { KEY_KPMINUS, 0x002d, 0x2000002d, 0x00, Flags::None, 0x0000 }, + { KEY_KP4, 0x0034, 0x20000034, 0x00, Flags::None, 0x0000 }, + { KEY_KP5, 0x0035, 0x20000035, 0x00, Flags::None, 0x0000 }, + { KEY_KP6, 0x0036, 0x20000036, 0x00, Flags::None, 0x0000 }, + { KEY_KPPLUS, 0x002b, 0x2000002b, 0x00, Flags::None, 0x0000 }, + { KEY_KP1, 0x0031, 0x20000031, 0x00, Flags::None, 0x0000 }, + { KEY_KP2, 0x0032, 0x20000032, 0x00, Flags::None, 0x0000 }, + { KEY_KP3, 0x0033, 0x20000033, 0x00, Flags::None, 0x0000 }, + { KEY_KP0, 0x0030, 0x20000030, 0x00, Flags::None, 0x0000 }, + { KEY_KPDOT, 0x002e, 0x2000002e, 0x00, Flags::None, 0x0000 }, + { KEY_KPDOT, 0xffff, 0x01000000, 0x06, Flags::IsSystem, 0x0200 }, + { KEY_KPDOT, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0200 }, + { KEY_102ND, 0x003c, 0x0000003c, 0x00, Flags::None, 0x0000 }, + { KEY_102ND, 0x003e, 0x0000003e, 0x01, Flags::None, 0x0000 }, + { KEY_102ND, 0x007c, 0x0000007c, 0x02, Flags::None, 0x0000 }, + { KEY_F11, 0xffff, 0x0100003a, 0x00, Flags::None, 0x0000 }, + { KEY_F11, 0xffff, 0x01000046, 0x01, Flags::None, 0x0000 }, + { KEY_F11, 0xffff, 0x01000052, 0x04, Flags::None, 0x0000 }, + { KEY_F11, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x010a }, + { KEY_F12, 0xffff, 0x0100003b, 0x00, Flags::None, 0x0000 }, + { KEY_F12, 0xffff, 0x01000047, 0x01, Flags::None, 0x0000 }, + { KEY_F12, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x010b }, + { KEY_KPENTER, 0xffff, 0x21000005, 0x00, Flags::None, 0x0000 }, + { KEY_RIGHTCTRL, 0xffff, 0x01000021, 0x00, Flags::IsModifier, Modifiers::ModControl | Modifiers::ModCtrlR }, + { KEY_KPSLASH, 0x002f, 0x2000002f, 0x00, Flags::None, 0x0000 }, + { KEY_SYSRQ, 0x005c, 0x0400005c, 0x00, Flags::None, 0x0000 }, + { KEY_RIGHTALT, 0xffff, 0x01001103, 0x00, Flags::IsModifier, Modifiers::ModAltGr }, + { KEY_HOME, 0xffff, 0x01000010, 0x00, Flags::None, 0x0000 }, + { KEY_UP, 0xffff, 0x01000013, 0x00, Flags::None, 0x0000 }, + { KEY_PAGEUP, 0xffff, 0x01000016, 0x00, Flags::None, 0x0000 }, + { KEY_LEFT, 0xffff, 0x01000012, 0x00, Flags::None, 0x0000 }, + { KEY_LEFT, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0180 }, + { KEY_RIGHT, 0xffff, 0x01000014, 0x00, Flags::None, 0x0000 }, + { KEY_RIGHT, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0181 }, + { KEY_END, 0xffff, 0x01000011, 0x00, Flags::None, 0x0000 }, + { KEY_DOWN, 0xffff, 0x01000015, 0x00, Flags::None, 0x0000 }, + { KEY_PAGEDOWN, 0xffff, 0x01000017, 0x00, Flags::None, 0x0000 }, + { KEY_INSERT, 0xffff, 0x01000006, 0x00, Flags::None, 0x0000 }, + { KEY_DELETE, 0xffff, 0x01000007, 0x00, Flags::None, 0x0000 }, + { KEY_DELETE, 0xffff, 0x01000000, 0x06, Flags::IsSystem, 0x0200 }, + { KEY_DELETE, 0xffff, 0x01000000, 0x0c, Flags::IsSystem, 0x0200 }, + + // 113 -> 248 + { KEY_MUTE, 0xffff, Qt::Key_VolumeMute, 0x00, Flags::None, 0x0000 }, + { KEY_VOLUMEDOWN, 0xffff, Qt::Key_VolumeDown, 0x00, Flags::None, 0x0000 }, + { KEY_VOLUMEUP, 0xffff, Qt::Key_VolumeUp, 0x00, Flags::None, 0x0000 }, + { KEY_POWER, 0xffff, Qt::Key_PowerOff, 0x00, Flags::None, 0x0000 }, + { KEY_PAUSE, 0xffff, Qt::Key_Pause, 0x00, Flags::None, 0x0000 }, + + { KEY_LEFTMETA, 0xffff, Qt::Key_Meta, 0x00, Flags::IsModifier, Modifiers::ModMeta }, + { KEY_RIGHTMETA, 0xffff, Qt::Key_Meta, 0x00, Flags::IsModifier, Modifiers::ModMeta }, + + { KEY_STOP, 0xffff, Qt::Key_Stop, 0x00, Flags::None, 0x0000 }, + { KEY_SETUP, 0xffff, Qt::Key_Settings, 0x00, Flags::None, 0x0000 }, + { KEY_RECORD, 0xffff, Qt::Key_MediaRecord, 0x00, Flags::None, 0x0000 }, + { KEY_REWIND, 0xffff, Qt::Key_AudioRewind, 0x00, Flags::None, 0x0000 }, + { KEY_PLAYPAUSE, 0xffff, Qt::Key_MediaTogglePlayPause, 0x00, Flags::None, 0x0000 }, + { KEY_PLAY, 0xffff, Qt::Key_MediaPlay, 0x00, Flags::None, 0x0000 }, + { KEY_FASTFORWARD, 0xffff, Qt::Key_AudioForward, 0x00, Flags::None, 0x0000 }, + { KEY_CANCEL, 0xffff, Qt::Key_Cancel, 0x00, Flags::None, 0x0000 }, + { KEY_MICMUTE, 0xffff, Qt::Key_MicMute, 0x00, Flags::None, 0x0000 }, + // 0x160 -> + { KEY_SELECT, 0xffff, Qt::Key_Select, 0x00, Flags::None, 0x0000 }, + { KEY_CLEAR, 0xffff, Qt::Key_Clear, 0x00, Flags::None, 0x0000 }, + { KEY_EXIT, 0xffff, Qt::Key_Exit, 0x00, Flags::None, 0x0000 }, + { KEY_INFO, 0xffff, Qt::Key_Info, 0x00, Flags::None, 0x0000 }, + { KEY_PROGRAM, 0xffff, Qt::Key_Guide, 0x00, Flags::None, 0x0000 }, + { KEY_CALENDAR, 0xffff, Qt::Key_Calendar, 0x00, Flags::None, 0x0000 }, + { KEY_RED, 0xffff, Qt::Key_Red, 0x00, Flags::None, 0x0000 }, + { KEY_GREEN, 0xffff, Qt::Key_Green, 0x00, Flags::None, 0x0000 }, + { KEY_YELLOW, 0xffff, Qt::Key_Yellow, 0x00, Flags::None, 0x0000 }, + { KEY_BLUE, 0xffff, Qt::Key_Blue, 0x00, Flags::None, 0x0000 }, + { KEY_CHANNELUP, 0xffff, Qt::Key_ChannelUp, 0x00, Flags::None, 0x0000 }, + { KEY_CHANNELDOWN, 0xffff, Qt::Key_ChannelDown, 0x00, Flags::None, 0x0000 }, + }; + +const QEvdevKeyboardMap::Composing OxideEventHandler::s_keycompose_default[] = { + { 0x0060, 0x0041, 0x00c0 }, + { 0x0060, 0x0061, 0x00e0 }, + { 0x0027, 0x0041, 0x00c1 }, + { 0x0027, 0x0061, 0x00e1 }, + { 0x005e, 0x0041, 0x00c2 }, + { 0x005e, 0x0061, 0x00e2 }, + { 0x007e, 0x0041, 0x00c3 }, + { 0x007e, 0x0061, 0x00e3 }, + { 0x0022, 0x0041, 0x00c4 }, + { 0x0022, 0x0061, 0x00e4 }, + { 0x002d, 0x0061, 0x00aa }, + { 0x002d, 0x0041, 0x00aa }, + { 0x004f, 0x0041, 0x00c5 }, + { 0x006f, 0x0061, 0x00e5 }, + { 0x0030, 0x0041, 0x00c5 }, + { 0x0030, 0x0061, 0x00e5 }, + { 0x0041, 0x0041, 0x00c5 }, + { 0x0061, 0x0061, 0x00e5 }, + { 0x00b0, 0x0041, 0x00c5 }, + { 0x00b0, 0x0061, 0x00e5 }, + { 0x0041, 0x0045, 0x00c6 }, + { 0x0061, 0x0065, 0x00e6 }, + { 0x002c, 0x0043, 0x00c7 }, + { 0x002c, 0x0063, 0x00e7 }, + { 0x005e, 0x0043, 0x00c7 }, + { 0x005e, 0x0063, 0x00e7 }, + { 0x0060, 0x0045, 0x00c8 }, + { 0x0060, 0x0065, 0x00e8 }, + { 0x0027, 0x0045, 0x00c9 }, + { 0x0027, 0x0065, 0x00e9 }, + { 0x005e, 0x0045, 0x00ca }, + { 0x005e, 0x0065, 0x00ea }, + { 0x0022, 0x0045, 0x00cb }, + { 0x0022, 0x0065, 0x00eb }, + { 0x0060, 0x0049, 0x00cc }, + { 0x0060, 0x0069, 0x00ec }, + { 0x0027, 0x0049, 0x00cd }, + { 0x0027, 0x0069, 0x00ed }, + { 0x005e, 0x0049, 0x00ce }, + { 0x005e, 0x0069, 0x00ee }, + { 0x0022, 0x0049, 0x00cf }, + { 0x0022, 0x0069, 0x00ef }, + { 0x002d, 0x0044, 0x00d0 }, + { 0x002d, 0x0064, 0x00f0 }, + { 0x005e, 0x0044, 0x00d0 }, + { 0x005e, 0x0064, 0x00f0 }, + { 0x007e, 0x004e, 0x00d1 }, + { 0x007e, 0x006e, 0x00f1 }, + { 0x005e, 0x004e, 0x00d1 }, + { 0x005e, 0x006e, 0x00f1 }, + { 0x0060, 0x004f, 0x00d2 }, + { 0x0060, 0x006f, 0x00f2 }, + { 0x0027, 0x004f, 0x00d3 }, + { 0x0027, 0x006f, 0x00f3 }, + { 0x005e, 0x004f, 0x00d4 }, + { 0x005e, 0x006f, 0x00f4 }, + { 0x007e, 0x004f, 0x00d5 }, + { 0x007e, 0x006f, 0x00f5 }, + { 0x0022, 0x004f, 0x00d6 }, + { 0x0022, 0x006f, 0x00f6 }, + { 0x002f, 0x004f, 0x00d8 }, + { 0x002f, 0x006f, 0x00f8 }, + { 0x002d, 0x006f, 0x00ba }, + { 0x002d, 0x004f, 0x00ba }, + { 0x0060, 0x0055, 0x00d9 }, + { 0x0060, 0x0075, 0x00f9 }, + { 0x0027, 0x0055, 0x00da }, + { 0x0027, 0x0075, 0x00fa }, + { 0x005e, 0x0055, 0x00db }, + { 0x005e, 0x0075, 0x00fb }, + { 0x0022, 0x0055, 0x00dc }, + { 0x0022, 0x0075, 0x00fc }, + { 0x0027, 0x0059, 0x00dd }, + { 0x0027, 0x0079, 0x00fd }, + { 0x0054, 0x0048, 0x00de }, + { 0x0074, 0x0068, 0x00fe }, + { 0x0073, 0x0073, 0x00df }, + { 0x0022, 0x0079, 0x00ff }, + { 0x0073, 0x007a, 0x00df }, + { 0x006e, 0x006e, 0x00f1 }, + { 0x006e, 0x0068, 0x00f1 }, + { 0x004e, 0x0059, 0x00d1 }, + { 0x004e, 0x004e, 0x00d1 }, + { 0x004e, 0x0048, 0x00d1 }, + { 0x004e, 0x0079, 0x00d1 }, + { 0x004e, 0x006e, 0x00d1 }, + { 0x004e, 0x0068, 0x00d1 }, + { 0x002d, 0x004c, 0x00a3 }, + { 0x003c, 0x003c, 0x00ab }, + { 0x003e, 0x003e, 0x00bb }, + { 0x003f, 0x003f, 0x00bf }, + { 0x005e, 0x003f, 0x00bf }, + { 0x0021, 0x0021, 0x00a1 }, + { 0x005e, 0x0021, 0x00a1 }, + { 0x005e, 0x0031, 0x00b9 }, + { 0x005e, 0x0032, 0x00b2 }, + { 0x005e, 0x0033, 0x00b3 }, + { 0x002b, 0x002d, 0x00b1 }, + { 0x0063, 0x003d, 0x00a2 }, + { 0x0063, 0x002f, 0x00a2 }, + { 0x002f, 0x0063, 0x00a2 }, + { 0x002d, 0x0063, 0x00a2 }, + { 0x002d, 0x0043, 0x00a2 }, + { 0x004c, 0x003d, 0x00a3 }, + { 0x002d, 0x004c, 0x00a3 }, + { 0x002d, 0x006c, 0x00a3 }, + { 0x005e, 0x002a, 0x00d7 }, + { 0x005e, 0x0078, 0x00d7 }, + { 0x0078, 0x0078, 0x00d7 }, + { 0x005e, 0x002e, 0x00b7 }, + { 0x002e, 0x002e, 0x00b7 }, + { 0x005e, 0x002f, 0x00f7 }, + { 0x005e, 0x003a, 0x00f7 }, + { 0x002d, 0x003a, 0x00f7 }, + { 0x003a, 0x002d, 0x00f7 }, + { 0x0059, 0x003d, 0x00a5 }, + { 0x002d, 0x0059, 0x00a5 }, + { 0x002d, 0x006c, 0x00a5 }, + { 0x0028, 0x0063, 0x00a9 }, + { 0x0022, 0x0063, 0x00a9 }, + { 0x002d, 0x0061, 0x00aa }, + { 0x002d, 0x0041, 0x00aa }, + { 0x002d, 0x006f, 0x00ba }, + { 0x002d, 0x004f, 0x00ba }, + { 0x0028, 0x0072, 0x00ae }, + { 0x0022, 0x0072, 0x00ae }, + { 0x006d, 0x0075, 0x00b5 }, + { 0x0031, 0x0034, 0x0152 }, + { 0x0031, 0x0032, 0x0153 }, + { 0x0033, 0x0034, 0x0178 }, + { 0x0065, 0x003d, 0x20ac }, + { 0x002d, 0x0065, 0x20ac }, + { 0x002d, 0x0045, 0x20ac }, + { 0x0076, 0x0053, 0x0160 }, + { 0x005e, 0x0053, 0x0160 }, + { 0x0076, 0x0073, 0x0161 }, + { 0x005e, 0x0073, 0x0161 }, + { 0x0076, 0x005a, 0x017d }, + { 0x005e, 0x005a, 0x017d }, + { 0x0076, 0x007a, 0x017e }, + { 0x005e, 0x007a, 0x017e }, + { 0x004f, 0x0045, 0x0152 }, + { 0x004f, 0x0065, 0x0152 }, + { 0x006f, 0x0065, 0x0153 }, + { 0x0022, 0x0059, 0x0178 }, + { 0x0069, 0x006a, 0x00ff }, + { 0x0049, 0x004a, 0x0178 }, + }; diff --git a/shared/qpa/main.cpp b/shared/qpa/main.cpp new file mode 100644 index 000000000..689ac4b56 --- /dev/null +++ b/shared/qpa/main.cpp @@ -0,0 +1,27 @@ +#include +#include "oxideintegration.h" + +QT_BEGIN_NAMESPACE + + +class OxideIntegrationPlugin : public QPlatformIntegrationPlugin{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "oxide.json") + +public: + QPlatformIntegration* create(const QString&, const QStringList&) override; +}; + +QPlatformIntegration* OxideIntegrationPlugin::create( + const QString& system, + const QStringList& paramList +){ + if(!system.compare(QLatin1String("oxide"), Qt::CaseInsensitive)){ + return new OxideIntegration(paramList); + } + return nullptr; +} + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/shared/qpa/oxide.json b/shared/qpa/oxide.json new file mode 100644 index 000000000..a96937cbd --- /dev/null +++ b/shared/qpa/oxide.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "oxide" ] +} diff --git a/shared/qpa/oxidebackingstore.cpp b/shared/qpa/oxidebackingstore.cpp new file mode 100644 index 000000000..bd7c60065 --- /dev/null +++ b/shared/qpa/oxidebackingstore.cpp @@ -0,0 +1,145 @@ +#include "oxidebackingstore.h" +#include "oxideintegration.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +OxideBackingStore::OxideBackingStore(QWindow* window) +: QPlatformBackingStore(window) +{ + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideBackingStore::OxideBackingStore:" << (quintptr)this; + } + if(window != nullptr && window->handle()){ + (static_cast(window->handle()))->setBackingStore(this); + } +} + +OxideBackingStore::~OxideBackingStore(){ + Blight::connection()->remove(mBuffer); +} + +QPaintDevice* OxideBackingStore::paintDevice(){ return ℑ } + +void OxideBackingStore::flush(QWindow* window, const QRegion& region, const QPoint& offset){ + Q_UNUSED(offset); + Q_UNUSED(window); + if(mBuffer == nullptr || region.isEmpty()){ + return; + } + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideBackingStore::repaint:" << mBuffer->surface << offset << region; + } + bool ok; + auto waveform = (Blight::WaveformMode)window->property("WA_WAVEFORM").toInt(&ok); + if(!ok || !waveform){ + waveform = Blight::HighQualityGrayscale; + } + for(auto rect : region){ + Blight::connection()->repaint( + mBuffer, + rect.x(), + rect.y(), + rect.width(), + rect.height(), + waveform + ); + } +} + +void OxideBackingStore::resize(const QSize& size, const QRegion& region){ + Q_UNUSED(region) + if(image.size() == size){ + return; + } + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideBackingStore::resize:" << (mBuffer == nullptr ? 0 : mBuffer->surface) << size << region; + } + bool ok; + auto format = (QImage::Format)window()->property("WA_FORMAT").toInt(&ok); + if(!ok || !format){ + format = QImage::Format_ARGB32_Premultiplied; + } + QImage blankImage(size, format); + if(mBuffer == nullptr){ + auto maybe = Blight::createBuffer( + window()->x(), + window()->y(), + blankImage.width(), + blankImage.height(), + blankImage.bytesPerLine(), + (Blight::Format)blankImage.format() + ); + if(!maybe.has_value()){ + qWarning() << "Failed to create buffer:" << strerror(errno); + return; + } + auto buffer = maybe.value(); + QImage( + buffer->data, + buffer->width, + buffer->height, + (QImage::Format)buffer->format + ).fill(Qt::transparent); + Blight::addSurface(buffer); + if(!buffer->surface){ + qWarning() << "Failed to create surface:" << strerror(errno); + return; + } + mBuffer = buffer; + }else{ + blankImage.fill(Qt::transparent); + QPainter p(&blankImage); + p.drawImage(blankImage.rect(), image, image.rect()); + p.end(); + auto maybe = Blight::connection()->resize( + mBuffer, + blankImage.width(), + blankImage.height(), + blankImage.bytesPerLine(), + (Blight::data_t)blankImage.constBits() + ); + if(!maybe.has_value()){ + qWarning() << "Failed to resize surface:" << strerror(errno); + return; + } + mBuffer = maybe.value(); + } + image = QImage( + mBuffer->data, + mBuffer->width, + mBuffer->height, + (QImage::Format)mBuffer->format + ); + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideBackingStore::resized" << mBuffer->surface; + } +} + +bool OxideBackingStore::scroll(const QRegion& area, int dx, int dy){ + Q_UNUSED(area) + Q_UNUSED(dx) + Q_UNUSED(dy) + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideBackingStore::scroll"; + } + return false; +} + +QImage OxideBackingStore::toImage() const{ return image; } + +const QImage& OxideBackingStore::getImageRef() const{ return image; } + +QPlatformGraphicsBuffer* OxideBackingStore::graphicsBuffer() const{ return nullptr; } + +Blight::shared_buf_t OxideBackingStore::buffer(){ return mBuffer; } + +QT_END_NAMESPACE diff --git a/shared/qpa/oxidebackingstore.h b/shared/qpa/oxidebackingstore.h new file mode 100644 index 000000000..098d1a2a2 --- /dev/null +++ b/shared/qpa/oxidebackingstore.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class OxideBackingStore : public QPlatformBackingStore +{ +public: + OxideBackingStore(QWindow* window); + ~OxideBackingStore(); + + QPaintDevice* paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + void resize(const QSize &size, const QRegion &staticContents) override; + bool scroll(const QRegion &area, int dx, int dy) override; + QImage toImage() const override; + const QImage& getImageRef() const; + QPlatformGraphicsBuffer* graphicsBuffer() const override; + Blight::shared_buf_t buffer(); + +private: + QImage image; + Blight::shared_buf_t mBuffer = nullptr; +}; + +QT_END_NAMESPACE diff --git a/shared/qpa/oxideeventfilter.cpp b/shared/qpa/oxideeventfilter.cpp new file mode 100644 index 000000000..a6e48f449 --- /dev/null +++ b/shared/qpa/oxideeventfilter.cpp @@ -0,0 +1,105 @@ +#include "oxideeventfilter.h" + +#include +#include +#include +#include +#include +#include + +OxideEventFilter::OxideEventFilter(QObject* parent) : QObject(parent){ } + +QVector ignoreTypes{ + QEvent::MetaCall, + QEvent::ThreadChange, + QEvent::SockAct, + QEvent::Timer, + QEvent::ChildAdded, + QEvent::ChildRemoved, + QEvent::DeferredDelete +}; + +bool OxideEventFilter::eventFilter(QObject* obj, QEvent* ev){ + if( + qEnvironmentVariableIsSet("OXIDE_QPA_DEBUG_ALL_EVENTS") + || ( + qEnvironmentVariableIsSet("OXIDE_QPA_DEBUG_EVENTS") + && !ignoreTypes.contains(ev->type()) + ) + ){ + qDebug() << obj << ev; + } + bool filtered = QObject::eventFilter(obj, ev); + if(isEnabled() && !filtered && !ev->isAccepted()){ + auto type = ev->type(); + switch(type){ + case QEvent::TabletPress:{ + ev->accept(); + auto tabletEvent = static_cast(ev); + QWindowSystemInterface::handleMouseEvent( + nullptr, + transpose(tabletEvent->posF()), + transpose(tabletEvent->globalPosF()), + tabletEvent->buttons(), + tabletEvent->button(), + QEvent::MouseButtonPress, + Qt::NoModifier, + Qt::MouseEventSynthesizedByQt + ); + return true; + } + case QEvent::TabletRelease:{ + ev->accept(); + auto tabletEvent = static_cast(ev); + QWindowSystemInterface::handleMouseEvent( + nullptr, + transpose(tabletEvent->posF()), + transpose(tabletEvent->globalPosF()), + tabletEvent->buttons(), + tabletEvent->button(), + QEvent::MouseButtonRelease, + Qt::NoModifier, + Qt::MouseEventSynthesizedByQt + ); + return true; + } + case QEvent::TabletMove:{ + ev->accept(); + auto tabletEvent = static_cast(ev); + QWindowSystemInterface::handleMouseEvent( + nullptr, + transpose(tabletEvent->posF()), + transpose(tabletEvent->globalPosF()), + tabletEvent->buttons(), + tabletEvent->button(), + QEvent::MouseMove, + Qt::NoModifier, + Qt::MouseEventSynthesizedByQt + ); + return true; + } + default: break; + } + } + return filtered; +} + +#define DISPLAYWIDTH 1404 +#define DISPLAYHEIGHT 1872.0 +#define WACOM_X_SCALAR (float(DISPLAYWIDTH) / float(DISPLAYHEIGHT)) +#define WACOM_Y_SCALAR (float(DISPLAYHEIGHT) / float(DISPLAYWIDTH)) + +QPointF OxideEventFilter::transpose(QPointF pointF){ + pointF = swap(pointF); + // Handle scaling from wacom to screensize + pointF.setX(pointF.x() * WACOM_X_SCALAR); + pointF.setY((DISPLAYWIDTH - pointF.y()) * WACOM_Y_SCALAR); + return pointF; +} + +QPointF OxideEventFilter::swap(QPointF pointF){ return QPointF(pointF.y(), pointF.x()); } + +bool OxideEventFilter::isEnabled(){ + return !qEnvironmentVariableIsSet("OXIDE_QPA_DISBLE_TABLET_SYNTHESIZE") + && qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents); +} diff --git a/shared/qpa/oxideeventfilter.h b/shared/qpa/oxideeventfilter.h new file mode 100644 index 000000000..ccab3002e --- /dev/null +++ b/shared/qpa/oxideeventfilter.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +class OxideEventFilter : public QObject{ + Q_OBJECT + +public: + OxideEventFilter(QObject* parent); + +protected: + bool eventFilter(QObject* obj, QEvent* ev) override; + +private: + static QPointF transpose(QPointF pointF); + static QPointF swap(QPointF pointF); + bool isEnabled(); +}; diff --git a/shared/qpa/oxideeventhandler.cpp b/shared/qpa/oxideeventhandler.cpp new file mode 100644 index 000000000..50b69ffae --- /dev/null +++ b/shared/qpa/oxideeventhandler.cpp @@ -0,0 +1,1037 @@ +#include "oxideeventhandler.h" +#include "oxideeventmanager.h" +#include "default_keymap.h" + +#include +#include +#include +#include +#include +#include + +typedef struct KeyboardData { + quint16 m_modifiers; + quint8 m_locks[3]; + int m_composing; + quint16 m_dead_unicode; +} KeyboardData; + +typedef struct rect_t { + int x, y, p, d; +} rect_t; + +typedef struct TabletData { + rect_t minValues; + rect_t maxValues; + struct { + int x, y, p, d; + bool down, lastReportDown; + int tool, lastReportTool; + QPointF lastReportPos; + } state; + int lastEventType; + + TabletData() + : lastEventType{0} + { + memset(&minValues, 0, sizeof(minValues)); + memset(&maxValues, 0, sizeof(maxValues)); + memset(&maxValues, 0, sizeof(maxValues)); + memset((void*)&state, 0, (sizeof(int) * 6) + (sizeof(bool) * 2)); + } +} TabletData; + +typedef struct Contact { + int trackingId = -1; + int x = 0; + int y = 0; + int maj = -1; + int pressure = 0; + Qt::TouchPointState state = Qt::TouchPointPressed; + QTouchEvent::TouchPoint::InfoFlags flags; +} Contact; + +typedef struct TouchData { + int lastEventType; + Contact currentData; + int currentSlot; + double timeStamp; + double lastTimeStamp; + int minX; + int maxX; + int minY; + int maxY; + int minPressure; + int maxPressure; + bool isTypeB; + bool singleTouch; + QList touchPoints; + QList lastTouchPoints; + QHash contacts; + QHash lastContacts; + QTouchDevice* device = nullptr; + TouchData() + : lastEventType{-1}, + currentSlot{0}, + timeStamp{0}, + lastTimeStamp{0}, + minX{0}, + maxX{0}, + minY{0}, + maxY{0}, + minPressure{0}, + maxPressure{0}, + isTypeB{false}, + singleTouch{false}, + touchPoints{}, + lastTouchPoints{}, + contacts{}, + lastContacts{} + {} + ~TouchData(){ + if(device != nullptr){ + QWindowSystemInterface::unregisterTouchDevice(device); + delete device; + } + } +} TouchData; + +typedef struct PointerData { + // TODO - populate +} PointerData; + +DeviceData::DeviceData() : DeviceData(0, QInputDeviceManager::DeviceTypeUnknown){} + +#define LONG_BITS (sizeof(long) << 3) +#define NUM_LONGS(bits) (((bits) + LONG_BITS - 1) / LONG_BITS) +static inline bool testBit(long bit, const long *array){ + return (array[bit / LONG_BITS] >> bit % LONG_BITS) & 1; +} + +DeviceData::DeviceData(unsigned int device, QInputDeviceManager::DeviceType type) +: device(device), + type(type) +{ + auto path = QString("/dev/input/event%1").arg(device); + auto fd = ::open(path.toStdString().c_str(), O_RDWR); + char name[1024]; + QString hw_name; + if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0){ + hw_name = QString::fromLocal8Bit(name); + } + switch(type){ + case QInputDeviceManager::DeviceTypeKeyboard:{ + auto keyboardData = set(); + keyboardData->m_dead_unicode = 0xffff; + break; + } + case QInputDeviceManager::DeviceTypeTablet:{ + auto tabletData = new TabletData(); + m_data = tabletData; + input_absinfo absInfo; + memset(&absInfo, 0, sizeof(input_absinfo)); + if(ioctl(fd, EVIOCGABS(ABS_X), &absInfo) >= 0){ + tabletData->minValues.x = absInfo.minimum; + tabletData->maxValues.x = absInfo.maximum; + } + if(ioctl(fd, EVIOCGABS(ABS_Y), &absInfo) >= 0){ + tabletData->minValues.y = absInfo.minimum; + tabletData->maxValues.y = absInfo.maximum; + } + if(ioctl(fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0){ + tabletData->minValues.p = absInfo.minimum; + tabletData->maxValues.p = absInfo.maximum; + } + if(ioctl(fd, EVIOCGABS(ABS_DISTANCE), &absInfo) >= 0){ + tabletData->minValues.d = absInfo.minimum; + tabletData->maxValues.d = absInfo.maximum; + } + break; + } + case QInputDeviceManager::DeviceTypeTouch:{ + auto touchData = new TouchData(); + m_data = touchData; + long absbits[NUM_LONGS(ABS_CNT)]; + if(ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) >= 0){ + touchData->isTypeB = testBit(ABS_MT_SLOT, absbits); + touchData->singleTouch = !testBit(ABS_MT_POSITION_X, absbits); + } + input_absinfo absInfo; + memset(&absInfo, 0, sizeof(input_absinfo)); + if(ioctl(fd, EVIOCGABS((touchData->singleTouch ? ABS_X : ABS_MT_POSITION_X)), &absInfo) >= 0){ + touchData->minX = absInfo.minimum; + touchData->maxX = absInfo.maximum; + } + if(ioctl(fd, EVIOCGABS((touchData->singleTouch ? ABS_Y : ABS_MT_POSITION_Y)), &absInfo) >= 0){ + touchData->minY = absInfo.minimum; + touchData->maxY = absInfo.maximum; + } + if(ioctl(fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0 && absInfo.maximum > absInfo.minimum){ + touchData->minPressure = absInfo.minimum; + touchData->maxPressure = absInfo.maximum; + } + auto touchDevice = new QTouchDevice; + touchDevice->setName(hw_name); + touchDevice->setType(QTouchDevice::TouchScreen); + touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area); + if(touchData->maxPressure > touchData->minPressure){ + touchDevice->setCapabilities(touchDevice->capabilities() | QTouchDevice::Pressure); + } + if(ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &absInfo)){ + touchDevice->setMaximumTouchPoints(absInfo.maximum); + } + touchData->device = touchDevice; + QWindowSystemInterface::registerTouchDevice(touchDevice); + break; + } + case QInputDeviceManager::DeviceTypePointer: + set(); + break; + default: + break; + } + ::close(fd); +} + +DeviceData::~DeviceData(){ + switch(type){ + case QInputDeviceManager::DeviceTypeKeyboard: + delete get(); + break; + case QInputDeviceManager::DeviceTypeTablet: + delete get(); + break; + case QInputDeviceManager::DeviceTypeTouch: + delete get(); + break; + case QInputDeviceManager::DeviceTypePointer: + delete get(); + break; + default: + break; + } +} + + +OxideEventHandler::OxideEventHandler(OxideEventManager* manager, const QStringList& parameters) +: QObject(), + m_manager(manager), + m_fd(Blight::connection()->input_handle()), + m_keymap{0}, + m_keymap_size{0}, + m_keycompose{0}, + m_keycompose_size{0} +{ + setObjectName("OxideInput"); + parseKeyParams(parameters); + parseTouchParams(QString(deviceSettings.getTouchEnvSetting()).split(QLatin1Char(':'))); + parseTouchParams(parameters); + m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, &OxideEventHandler::readyRead); +} + +OxideEventHandler::~OxideEventHandler(){} + +void OxideEventHandler::add(unsigned int number, QInputDeviceManager::DeviceType type){ + if(!m_devices.contains(number)){ + m_devices.insert(number, new DeviceData(number, type)); + } +} + +void OxideEventHandler::remove(unsigned int number, QInputDeviceManager::DeviceType type){ + if(m_devices.contains(number) && m_devices[number]->type == type){ + auto device = m_devices[number]; + m_devices.remove(number); + delete device; + } +} + +void OxideEventHandler::readyRead(){ + Q_ASSERT(thread() == QThread::currentThread()); + m_notifier->setEnabled(false); + auto connection = Blight::connection(); + while(true){ + auto maybe = connection->read_event(); + if(!maybe.has_value()){ + if(errno != EAGAIN && errno != EINTR){ + O_WARNING("Failed reading input stream" << strerror(errno)); + return; + } + break; + } + const auto& device = maybe.value().device; + if(!m_devices.contains(device)){ + continue; + } + auto& event = maybe.value().event; + auto data = m_devices[device]; + switch(data->type){ + case QInputDeviceManager::DeviceTypeKeyboard: + processKeyboardEvent(data, &event); + break; + case QInputDeviceManager::DeviceTypeTablet: + processTabletEvent(data, &event); + break; + case QInputDeviceManager::DeviceTypeTouch: + processTouchEvent(data, &event); + break; + case QInputDeviceManager::DeviceTypePointer: + processPointerEvent(data, &event); + break; + default: + continue; + } + } + m_notifier->setEnabled(true); +} + +void OxideEventHandler::processKeyboardEvent( + DeviceData* data, + Blight::partial_input_event_t* event +){ + Q_ASSERT(data->type == QInputDeviceManager::DeviceTypeKeyboard); + // Ignore EV_SYN etc + if(event->type != EV_KEY){ + return; + } + auto keyboardData = data->get(); + auto& keycode = event->code; + bool pressed = event->value; + bool autorepeat = event->value == 2; + + bool first_press = pressed && !autorepeat; + const QEvdevKeyboardMap::Mapping* map_plain = 0; + const QEvdevKeyboardMap::Mapping* map_withmod = 0; + quint8 modifiers = keyboardData->m_modifiers; + + for(int i = 0; i < m_keymap_size && !(map_plain && map_withmod); ++i){ + const QEvdevKeyboardMap::Mapping* m = m_keymap + i; + if(m->keycode == keycode){ + if(m->modifiers == 0){ + map_plain = m; + } + quint8 testmods = keyboardData->m_modifiers; + if( + keyboardData->m_locks[0] /*CapsLock*/ + && (m->flags & QEvdevKeyboardMap::IsLetter) + ){ + testmods ^= QEvdevKeyboardMap::ModShift; + } + if(m->modifiers == testmods){ + map_withmod = m; + } + } + } + + if( + keyboardData->m_locks[0] /*CapsLock*/ + && map_withmod + && (map_withmod->flags & QEvdevKeyboardMap::IsLetter) + ){ + modifiers ^= QEvdevKeyboardMap::ModShift; + } + const QEvdevKeyboardMap::Mapping* it = map_withmod ? map_withmod : map_plain; + // we couldn't even find a plain mapping + if(!it){ + O_WARNING("No mapping for keycode found:" << keycode); + return; + } + quint16 unicode = it->unicode; + quint32 qtcode = it->qtcode; + if( + it->flags & QEvdevKeyboardMap::IsModifier + && it->special + ){ + // this is a modifier, i.e. Shift, Alt, ... + if(pressed){ + keyboardData->m_modifiers |= it->special; + }else{ + keyboardData->m_modifiers &= ~it->special; + } + } else if(qtcode >= Qt::Key_CapsLock && qtcode <= Qt::Key_ScrollLock){ + // (Caps|Num|Scroll)Lock + if(first_press){ + quint8& lock = keyboardData->m_locks[qtcode - Qt::Key_CapsLock]; + lock ^= 1; + } + }else if((it->flags & QEvdevKeyboardMap::IsSystem) && it->special && first_press){ + switch(it->special){ + case QEvdevKeyboardMap::SystemZap: + if(!m_no_zap){ + qApp->quit(); + } + break; + } + return; + }else if(qtcode == Qt::Key_Multi_key && m_do_compose){ + // the Compose key was pressed + if(first_press){ + keyboardData->m_composing = 2; + } + return; + } else if(it->flags & QEvdevKeyboardMap::IsDead && m_do_compose){ + // a Dead key was pressed twice + if( + first_press + && keyboardData->m_composing == 1 + && keyboardData->m_dead_unicode == unicode + ){ + keyboardData->m_composing = 0; + // otherwise it would be Qt::Key_Dead... + qtcode = Qt::Key_unknown; + } else if(first_press && unicode != 0xffff){ + keyboardData->m_dead_unicode = unicode; + keyboardData->m_composing = 1; + return; + }else{ + return; + } + } + // a normal key was pressed + const int modmask = Qt::ShiftModifier + | Qt::ControlModifier + | Qt::AltModifier + | Qt::MetaModifier + | Qt::KeypadModifier; + // we couldn't find a specific mapping for the current modifiers, + // or that mapping didn't have special modifiers: + // so just report the plain mapping with additional modifiers. + if( + ( + it == map_plain + && it != map_withmod + ) + || ( + map_withmod + && !(map_withmod->qtcode & modmask) + ) + ) { + qtcode |= toQtModifiers(modifiers); + } + if( + keyboardData->m_composing == 2 + && first_press + && !(it->flags & QEvdevKeyboardMap::IsModifier) + ){ + // the last key press was the Compose key + if(unicode != 0xffff){ + int idx = 0; + // check if this code is in the compose table at all + for( ; idx < m_keycompose_size; ++idx){ + if(m_keycompose[idx].first == unicode){ + break; + } + } + if(idx < m_keycompose_size){ + // found it -> simulate a Dead key press + keyboardData->m_dead_unicode = unicode; + unicode = 0xffff; + keyboardData->m_composing = 1; + return; + }else{ + keyboardData->m_composing = 0; + } + }else{ + keyboardData->m_composing = 0; + } + }else if( + keyboardData->m_composing == 1 + && first_press + && !(it->flags & QEvdevKeyboardMap::IsModifier) + ){ + // the last key press was a Dead key + bool valid = false; + if(unicode != 0xffff){ + int idx = 0; + // check if this code is in the compose table at all + for( ; idx < m_keycompose_size; ++idx){ + if( + m_keycompose[idx].first == keyboardData->m_dead_unicode + && m_keycompose[idx].second == unicode + ){ + break; + } + } + if(idx < m_keycompose_size){ + quint16 composed = m_keycompose[idx].result; + if(composed != 0xffff){ + unicode = composed; + qtcode = Qt::Key_unknown; + valid = true; + } + } + } + if(!valid){ + unicode = keyboardData->m_dead_unicode; + qtcode = Qt::Key_unknown; + } + keyboardData->m_composing = 0; + } + // Up until now qtcode contained both the key and modifiers. Split it. + Qt::KeyboardModifiers qtmods = Qt::KeyboardModifiers(qtcode & modmask); + qtcode &= ~modmask; + // If NumLockOff and keypad key pressed remap event sent + if( + !keyboardData->m_locks[1] + && qtmods & Qt::KeypadModifier + && keycode >= 71 + && keycode <= 83 + && keycode != 74 + && keycode != 78 + ){ + unicode = 0xffff; + switch(keycode){ + case 71: //7 --> Home + qtcode = Qt::Key_Home; + break; + case 72: //8 --> Up + qtcode = Qt::Key_Up; + break; + case 73: //9 --> PgUp + qtcode = Qt::Key_PageUp; + break; + case 75: //4 --> Left + qtcode = Qt::Key_Left; + break; + case 76: //5 --> Clear + qtcode = Qt::Key_Clear; + break; + case 77: //6 --> right + qtcode = Qt::Key_Right; + break; + case 79: //1 --> End + qtcode = Qt::Key_End; + break; + case 80: //2 --> Down + qtcode = Qt::Key_Down; + break; + case 81: //3 --> PgDn + qtcode = Qt::Key_PageDown; + break; + case 82: //0 --> Ins + qtcode = Qt::Key_Insert; + break; + case 83: //, --> Del + qtcode = Qt::Key_Delete; + break; + } + } + // Map SHIFT + Tab to SHIFT + Backtab, QShortcutMap knows about this translation + if( + qtcode == Qt::Key_Tab + && (qtmods & Qt::ShiftModifier) == Qt::ShiftModifier + ){ + qtcode = Qt::Key_Backtab; + } + // Generate the QPA event. + if(!autorepeat){ + QGuiApplicationPrivate::inputDeviceManager()->setKeyboardModifiers( + toQtModifiers(keyboardData->m_modifiers) + ); + } + QEvent::Type type = pressed ? QEvent::KeyPress : QEvent::KeyRelease; + QString text = unicode != 0xffff ? QString(QChar(unicode)) : QString(); + QWindowSystemInterface::handleExtendedKeyEvent( + nullptr, + type, + qtcode, + toQtModifiers(keyboardData->m_modifiers), + keycode + 8, + 0, + int(modifiers), + text, + autorepeat + ); +} + +void OxideEventHandler::processTabletEvent( + DeviceData* data, + Blight::partial_input_event_t* event +){ + Q_ASSERT(data->type == QInputDeviceManager::DeviceTypeTablet); + auto device = data->device; + auto tabletData = data->get(); + // TODO handle tilt + if(event->type == EV_ABS){ + switch (event->code){ + case ABS_X: + tabletData->state.x = event->value; + break; + case ABS_Y: + tabletData->state.y = event->value; + break; + case ABS_PRESSURE: + tabletData->state.p = event->value; + break; + case ABS_DISTANCE: + tabletData->state.d = event->value; + break; + default: + break; + } + }else if(event->type == EV_KEY){ + // code BTN_TOOL_* value 1 -> proximity enter + // code BTN_TOOL_* value 0 -> proximity leave + // code BTN_TOUCH value 1 -> contact with screen + // code BTN_TOUCH value 0 -> no contact + switch (event->code) { + case BTN_TOUCH: + tabletData->state.down = event->value != 0; + break; + case BTN_TOOL_PEN: + tabletData->state.tool = event->value ? QTabletEvent::Pen : 0; + break; + case BTN_TOOL_RUBBER: + tabletData->state.tool = event->value ? QTabletEvent::Eraser : 0; + break; + default: + break; + } + }else if(event->type == EV_SYN && event->code == SYN_REPORT && tabletData->lastEventType != event->type){ + if(!tabletData->state.lastReportTool && tabletData->state.tool){ + QWindowSystemInterface::handleTabletEnterProximityEvent( + QTabletEvent::Stylus, + tabletData->state.tool, + device + ); + } + qreal nx = (tabletData->state.x - tabletData->minValues.x) / qreal(tabletData->maxValues.x - tabletData->minValues.x); + qreal ny = (tabletData->state.y - tabletData->minValues.y) / qreal(tabletData->maxValues.y - tabletData->minValues.y); + + QRect winRect = QGuiApplication::primaryScreen()->geometry(); + QPointF globalPos(nx * winRect.width(), ny * winRect.height()); + int pointer = tabletData->state.tool; + // Prevent sending confusing values of 0 when moving the pen outside the active area. + if(!tabletData->state.down && tabletData->state.lastReportDown){ + globalPos = tabletData->state.lastReportPos; + pointer = tabletData->state.lastReportTool; + } + int pressureRange = tabletData->maxValues.p - tabletData->minValues.p; + qreal pressure = pressureRange + ? (tabletData->state.p - tabletData->minValues.p) / qreal(pressureRange) + : qreal(1); + if(tabletData->state.down || tabletData->state.lastReportDown){ + auto button = tabletData->state.down ? Qt::LeftButton : Qt::NoButton; + QWindowSystemInterface::handleTabletEvent( + nullptr, + QPointF(), + globalPos, + QTabletEvent::Stylus, + pointer, + button, + pressure, + 0, + 0, + 0, + 0, + 0, + device, + qGuiApp->keyboardModifiers() + ); + } + if(tabletData->state.lastReportTool && !tabletData->state.tool){ + QWindowSystemInterface::handleTabletLeaveProximityEvent( + QTabletEvent::Stylus, + tabletData->state.tool, + device + ); + } + tabletData->state.lastReportDown = tabletData->state.down; + tabletData->state.lastReportTool = tabletData->state.tool; + tabletData->state.lastReportPos = globalPos; + } + tabletData->lastEventType = event->type; +} + +void addTouchPoint(QTransform rotate, TouchData* touchData, const Contact& contact, Qt::TouchPointStates* combinedStates){ + QWindowSystemInterface::TouchPoint tp; + tp.id = contact.trackingId; + tp.flags = contact.flags; + tp.state = contact.state; + *combinedStates |= tp.state; + + // Store the HW coordinates for now, will be updated later. + tp.area = QRectF(0, 0, contact.maj, contact.maj); + tp.area.moveCenter(QPoint(contact.x, contact.y)); + tp.pressure = contact.pressure; + + // Get a normalized position in range 0..1. + tp.normalPosition = QPointF( + (contact.x - touchData->minX) + / qreal(touchData->maxX - touchData->minX), + (contact.y - touchData->minY) + / qreal(touchData->maxY - touchData->minY) + ); + if(!rotate.isIdentity()){ + tp.normalPosition = rotate.map(tp.normalPosition); + } + tp.rawPositions.append(QPointF(contact.x, contact.y)); + touchData->touchPoints.append(tp); +} + +int findClosestContact(const QHash &contacts, int x, int y, int *dist){ + int minDist = -1, id = -1; + for(QHash::const_iterator it = contacts.constBegin(), ite = contacts.constEnd(); + it != ite; ++it) { + const Contact &contact(it.value()); + int dx = x - contact.x; + int dy = y - contact.y; + int dist = dx * dx + dy * dy; + if(minDist == -1 || dist < minDist){ + minDist = dist; + id = contact.trackingId; + } + } + if(dist){ + *dist = minDist; + } + return id; +} + +void OxideEventHandler::processTouchEvent( + DeviceData* data, + Blight::partial_input_event_t* event +){ + Q_ASSERT(data->type == QInputDeviceManager::DeviceTypeTouch); + auto touchData = data->get(); + if(event->type == EV_ABS){ + if(event->code == ABS_MT_POSITION_X || (touchData->singleTouch && event->code == ABS_X)){ + touchData->currentData.x = qBound(touchData->minX, event->value, touchData->maxX); + if(touchData->singleTouch){ + touchData->contacts[touchData->currentSlot].x = touchData->currentData.x; + } + if(touchData->isTypeB){ + touchData->contacts[touchData->currentSlot].x = touchData->currentData.x; + if(touchData->contacts[touchData->currentSlot].state == Qt::TouchPointStationary){ + touchData->contacts[touchData->currentSlot].state = Qt::TouchPointMoved; + } + } + }else if(event->code == ABS_MT_POSITION_Y || (touchData->singleTouch && event->code == ABS_Y)){ + touchData->currentData.y = qBound(touchData->minY, event->value, touchData->maxY); + if(touchData->singleTouch){ + touchData->contacts[touchData->currentSlot].y = touchData->currentData.y; + } + if(touchData->isTypeB){ + touchData->contacts[touchData->currentSlot].y = touchData->currentData.y; + if(touchData->contacts[touchData->currentSlot].state == Qt::TouchPointStationary){ + touchData->contacts[touchData->currentSlot].state = Qt::TouchPointMoved; + } + } + }else if(event->code == ABS_MT_TRACKING_ID){ + touchData->currentData.trackingId = event->value; + if(touchData->isTypeB){ + if(touchData->currentData.trackingId == -1){ + touchData->contacts[touchData->currentSlot].state = Qt::TouchPointReleased; + }else{ + touchData->contacts[touchData->currentSlot].state = Qt::TouchPointPressed; + touchData->contacts[touchData->currentSlot].trackingId = touchData->currentData.trackingId; + } + } + }else if(event->code == ABS_MT_TOUCH_MAJOR){ + touchData->currentData.maj = event->value; + if(event->value == 0){ + touchData->currentData.state = Qt::TouchPointReleased; + } + if(touchData->isTypeB){ + touchData->contacts[touchData->currentSlot].maj = touchData->currentData.maj; + } + }else if(event->code == ABS_PRESSURE || event->code == ABS_MT_PRESSURE){ + touchData->currentData.pressure = qBound(touchData->minPressure, event->value, touchData->maxPressure); + if(touchData->isTypeB || touchData->singleTouch){ + touchData->contacts[touchData->currentSlot].pressure = touchData->currentData.pressure; + } + }else if(event->code == ABS_MT_SLOT){ + touchData->currentSlot = event->value; + } + }else if(event->type == EV_KEY && !touchData->isTypeB){ + if(event->code == BTN_TOUCH && event->value == 0){ + touchData->contacts[touchData->currentSlot].state = Qt::TouchPointReleased; + } + }else if(event->type == EV_SYN && event->code == SYN_MT_REPORT && touchData->lastEventType != EV_SYN){ + // If there is no tracking id, one will be generated later. + // Until that use a temporary key. + int key = touchData->currentData.trackingId; + if(key == -1){ + key = touchData->contacts.count(); + } + touchData->contacts.insert(key, touchData->currentData); + touchData->currentData = Contact(); + }else if(event->type == EV_SYN && event->code == SYN_REPORT){ + // Ensure valid IDs even when the driver does not report ABS_MT_TRACKING_ID. + if(!touchData->contacts.isEmpty() && touchData->contacts.constBegin().value().trackingId == -1){ + QHash candidates = touchData->lastContacts, pending = touchData->contacts, newContacts; + int maxId = -1; + QHash::iterator it, ite, bestMatch; + while(!pending.isEmpty() && !candidates.isEmpty()){ + int bestDist = -1, bestId = 0; + for(it = pending.begin(), ite = pending.end(); it != ite; ++it){ + int dist; + int id = findClosestContact(candidates, it->x, it->y, &dist); + if(id >= 0 && (bestDist == -1 || dist < bestDist)){ + bestDist = dist; + bestId = id; + bestMatch = it; + } + } + if(bestDist >= 0){ + bestMatch->trackingId = bestId; + newContacts.insert(bestId, *bestMatch); + candidates.remove(bestId); + pending.erase(bestMatch); + if(bestId > maxId){ + maxId = bestId; + } + } + } + if(candidates.isEmpty()){ + for(it = pending.begin(), ite = pending.end(); it != ite; ++it){ + it->trackingId = ++maxId; + newContacts.insert(it->trackingId, *it); + } + } + touchData->contacts = newContacts; + } + // update timestamps + touchData->lastTimeStamp = touchData->timeStamp; + touchData->timeStamp = std::chrono::duration( + std::chrono::high_resolution_clock::now().time_since_epoch() + ).count(); + + touchData->lastTouchPoints = touchData->touchPoints; + touchData->touchPoints.clear(); + Qt::TouchPointStates combinedStates; + bool hasPressure = false; + for(auto i = touchData->contacts.begin(), end = touchData->contacts.end(); i != end; /*erasing*/){ + auto it = i++; + Contact &contact(it.value()); + if(!contact.state){ + continue; + } + int key = touchData->isTypeB ? it.key() : contact.trackingId; + if(!touchData->isTypeB && touchData->lastContacts.contains(key)){ + const Contact &prev(touchData->lastContacts.value(key)); + if(contact.state == Qt::TouchPointReleased){ + // Copy over the previous values for released points, just in case. + contact.x = prev.x; + contact.y = prev.y; + contact.maj = prev.maj; + }else{ + contact.state = prev.x == contact.x && prev.y == contact.y + ? Qt::TouchPointStationary + : Qt::TouchPointMoved; + } + } + // Avoid reporting a contact in released state more than once. + if(!touchData->isTypeB && contact.state == Qt::TouchPointReleased && !touchData->lastContacts.contains(key)){ + touchData->contacts.erase(it); + continue; + } + if(contact.pressure){ + hasPressure = true; + } + addTouchPoint(m_rotate, touchData, contact, &combinedStates); + } + // Now look for contacts that have disappeared since the last sync. + for(auto it = touchData->lastContacts.begin(), end = touchData->lastContacts.end(); it != end; ++it){ + Contact &contact(it.value()); + int key = touchData->isTypeB ? it.key() : contact.trackingId; + if(touchData->isTypeB){ + if(contact.trackingId != touchData->contacts[key].trackingId && contact.state){ + contact.state = Qt::TouchPointReleased; + addTouchPoint(m_rotate, touchData, contact, &combinedStates); + } + }else if(!touchData->contacts.contains(key)){ + contact.state = Qt::TouchPointReleased; + addTouchPoint(m_rotate, touchData, contact, &combinedStates); + } + } + // Remove contacts that have just been reported as released. + for(auto i = touchData->contacts.begin(), end = touchData->contacts.end(); i != end; /*erasing*/){ + auto it = i++; + Contact &contact(it.value()); + if(!contact.state){ + continue; + } + if(contact.state != Qt::TouchPointReleased){ + contact.state = Qt::TouchPointStationary; + }else if(touchData->isTypeB){ + contact.state = static_cast(0); + }else{ + touchData->contacts.erase(it); + } + } + touchData->lastContacts = touchData->contacts; + if(!touchData->isTypeB && !touchData->singleTouch){ + touchData->contacts.clear(); + } + if(!touchData->touchPoints.isEmpty() && (hasPressure || combinedStates != Qt::TouchPointStationary)){ + auto screen = QGuiApplication::primaryScreen(); + QRect winRect = QHighDpi::toNativePixels(screen->geometry(), screen); + if(winRect.isNull()){ + return; + } + const int hw_w = touchData->maxX - touchData->minX; + const int hw_h = touchData->maxY - touchData->minY; + // Map the coordinates based on the normalized position. QPA expects 'area' + // to be in screen coordinates. + const int pointCount = touchData->touchPoints.count(); + for(int i = 0; i < pointCount; ++i){ + QWindowSystemInterface::TouchPoint &tp(touchData->touchPoints[i]); + // Generate a screen position that is always inside the active window + // or the primary screen. Even though we report this as a QRectF, internally + // Qt uses QRect/QPoint so we need to bound the size to winRect.size() - QSize(1, 1) + const qreal wx = winRect.left() + tp.normalPosition.x() * (winRect.width() - 1); + const qreal wy = winRect.top() + tp.normalPosition.y() * (winRect.height() - 1); + const qreal sizeRatio = (winRect.width() + winRect.height()) / qreal(hw_w + hw_h); + if(tp.area.width() == -1){ + // touch major was not provided + tp.area = QRectF(0, 0, 8, 8); + }else{ + tp.area = QRectF(0, 0, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio); + } + tp.area.moveCenter(QPointF(wx, wy)); + // Calculate normalized pressure. + if(!touchData->minPressure && !touchData->maxPressure){ + tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1; + }else{ + tp.pressure = (tp.pressure - touchData->minPressure) / qreal(touchData->maxPressure - touchData->minPressure); + } + } + // Let qguiapp pick the target window. + QWindowSystemInterface::handleTouchEvent(nullptr, touchData->device, touchData->touchPoints); + } + } + touchData->lastEventType = event->type; +} + +void OxideEventHandler::processPointerEvent( + DeviceData* data, + Blight::partial_input_event_t* event +){ + Q_ASSERT(data->type == QInputDeviceManager::DeviceTypePointer); + auto device = data->device; + auto pointerData = data->get(); +} + +Qt::KeyboardModifiers OxideEventHandler::toQtModifiers(quint16 mod){ + Qt::KeyboardModifiers qtmod = Qt::NoModifier; + if(mod & (Modifiers::ModShift | Modifiers::ModShiftL | Modifiers::ModShiftR)){ + qtmod |= Qt::ShiftModifier; + } + if(mod & (Modifiers::ModControl | Modifiers::ModCtrlL | Modifiers::ModCtrlR)){ + qtmod |= Qt::ControlModifier; + } + if(mod & Modifiers::ModAlt || mod & Modifiers::ModAltGr){ + qtmod |= Qt::AltModifier; + } + if(mod & Modifiers::ModMeta){ + qtmod |= Qt::MetaModifier; + } + return qtmod; +} + +void OxideEventHandler::unloadKeymap(){ + if(m_keymap && m_keymap != s_keymap_default){ + delete [] m_keymap; + } + if(m_keycompose && m_keycompose != s_keycompose_default){ + delete [] m_keycompose; + } + m_keymap = s_keymap_default; + m_keymap_size = sizeof(s_keymap_default) / sizeof(s_keymap_default[0]); + m_keycompose = s_keycompose_default; + m_keycompose_size = sizeof(s_keycompose_default) / sizeof(s_keycompose_default[0]); +} + +bool OxideEventHandler::loadKeymap(const QString& file){ + QFile f(file); + if(!f.open(QIODevice::ReadOnly)){ + O_WARNING("Could not open keymap file" << qUtf16Printable(file)); + return false; + } + quint32 qmap_magic, qmap_version, qmap_keymap_size, qmap_keycompose_size; + QDataStream ds(&f); + ds >> qmap_magic >> qmap_version >> qmap_keymap_size >> qmap_keycompose_size; + if( + ds.status() != QDataStream::Ok + || qmap_magic != QEvdevKeyboardMap::FileMagic + || qmap_version != 1 + || qmap_keymap_size == 0 + ){ + O_WARNING(qUtf16Printable(file) << " is not a valid .qmap keymap file"); + return false; + } + QEvdevKeyboardMap::Mapping *qmap_keymap = new QEvdevKeyboardMap::Mapping[qmap_keymap_size]; + QEvdevKeyboardMap::Composing *qmap_keycompose = qmap_keycompose_size + ? new QEvdevKeyboardMap::Composing[qmap_keycompose_size] + : 0; + for(quint32 i = 0; i < qmap_keymap_size; ++i){ + ds >> qmap_keymap[i]; + } + for(quint32 i = 0; i < qmap_keycompose_size; ++i){ + ds >> qmap_keycompose[i]; + } + if(ds.status() != QDataStream::Ok){ + delete [] qmap_keymap; + delete [] qmap_keycompose; + O_WARNING("Keymap file" << qUtf16Printable(file) << "cannot be loaded."); + return false; + } + // unload currently active and clear state + unloadKeymap(); + + m_keymap = qmap_keymap; + m_keymap_size = qmap_keymap_size; + m_keycompose = qmap_keycompose; + m_keycompose_size = qmap_keycompose_size; + m_do_compose = true; + return true; +} + +void OxideEventHandler::parseKeyParams(const QStringList& parameters){ + QString keymap; + for(const QString& param : parameters){ + if(param.startsWith(QLatin1String("keymap="), Qt::CaseInsensitive)){ + keymap = param.section(QLatin1Char('='), 1, 1); + } + } + if(keymap.isEmpty() || !loadKeymap(keymap)){ + unloadKeymap(); + } +} + +void OxideEventHandler::parseTouchParams(const QStringList& parameters){ + int rotationAngle = 0; + bool invertx = false; + bool inverty = false; + for(const QString& param : parameters){ + if(param.startsWith(QLatin1String("rotate"))){ + QString rotateArg = param.section(QLatin1Char('='), 1, 1); + bool ok; + uint argValue = rotateArg.toUInt(&ok); + if(ok){ + switch(argValue){ + case 90: + case 180: + case 270: + rotationAngle = argValue; + default: + break; + } + } + }else if(param == QLatin1String("invertx")){ + invertx = true; + }else if(param == QLatin1String("inverty")){ + inverty = true; + } + } + if(rotationAngle){ + m_rotate = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5); + } + if(invertx){ + m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5); + } + if(inverty){ + m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5); + } +} diff --git a/shared/qpa/oxideeventhandler.h b/shared/qpa/oxideeventhandler.h new file mode 100644 index 000000000..997dbf7e2 --- /dev/null +++ b/shared/qpa/oxideeventhandler.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include +#include + +class OxideEventManager; + + +class DeviceData{ +public: + DeviceData(); + DeviceData(unsigned int device, QInputDeviceManager::DeviceType type); + ~DeviceData(); + unsigned int device; + QInputDeviceManager::DeviceType type; + template T* get(){ return reinterpret_cast(m_data); } + +private: + template T* set(){ + m_data = new T; + memset(m_data, 0, sizeof(T)); + return get(); + } + void* m_data = nullptr; +}; + +class OxideEventHandler : public QObject { +public: + OxideEventHandler(OxideEventManager* manager, const QStringList& parameters); + ~OxideEventHandler(); + + void add(unsigned int number, QInputDeviceManager::DeviceType type); + void remove(unsigned int number, QInputDeviceManager::DeviceType type); + +private slots: + void readyRead(); + void processKeyboardEvent(DeviceData* data, Blight::partial_input_event_t* event); + void processTabletEvent(DeviceData* data, Blight::partial_input_event_t* event); + void processTouchEvent(DeviceData* data, Blight::partial_input_event_t* event); + void processPointerEvent(DeviceData* data, Blight::partial_input_event_t* event); + +private: + OxideEventManager* m_manager; + QMap m_devices; + int m_fd; + QSocketNotifier* m_notifier; + const QEvdevKeyboardMap::Mapping* m_keymap; + int m_keymap_size; + const QEvdevKeyboardMap::Composing* m_keycompose; + int m_keycompose_size; + bool m_no_zap; + bool m_do_compose; + QTransform m_rotate; + + static const QEvdevKeyboardMap::Mapping s_keymap_default[]; + static const QEvdevKeyboardMap::Composing s_keycompose_default[]; + static Qt::KeyboardModifiers toQtModifiers(quint16 mod); + + void unloadKeymap(); + bool loadKeymap(const QString& file); + void parseKeyParams(const QStringList& parameters); + void parseTouchParams(const QStringList& parameters); +}; diff --git a/shared/qpa/oxideeventmanager.cpp b/shared/qpa/oxideeventmanager.cpp new file mode 100644 index 000000000..9507d9912 --- /dev/null +++ b/shared/qpa/oxideeventmanager.cpp @@ -0,0 +1,64 @@ +#include "oxideeventmanager.h" + +#include +#include + +#include +#include +#include + +OxideEventManager::OxideEventManager(const QStringList& parameters) +: QObject(), + m_devices(), + m_handler(this, parameters) +{ + setup(QDeviceDiscovery::Device_Tablet, QInputDeviceManager::DeviceTypeTablet); + setup(QDeviceDiscovery::Device_Touchscreen, QInputDeviceManager::DeviceTypeTouch); + setup( + QDeviceDiscovery::Device_Mouse | QDeviceDiscovery::Device_Touchpad, + QInputDeviceManager::DeviceTypePointer + ); + setup(QDeviceDiscovery::Device_Keyboard, QInputDeviceManager::DeviceTypeKeyboard); +} + +void OxideEventManager::setup( + QDeviceDiscovery::QDeviceTypes privateTypes, + QInputDeviceManager::DeviceType publicType +){ + if(auto deviceDiscovery = QDeviceDiscovery::create(privateTypes, this)){ + auto devices = deviceDiscovery->scanConnectedDevices(); + for(const QString& device : qAsConst(devices)){ + deviceDetected(publicType, device); + } + connect( + deviceDiscovery, + &QDeviceDiscovery::deviceDetected, + this, + [this, publicType](const QString& device){ deviceDetected(publicType, device); } + ); + connect( + deviceDiscovery, + &QDeviceDiscovery::deviceRemoved, + this, + [this, publicType](const QString& device){ deviceRemoved(publicType, device); } + ); + } +} + +void OxideEventManager::deviceDetected(QInputDeviceManager::DeviceType type, const QString& device){ + m_devices[type].append(device); + auto manager = QGuiApplicationPrivate::inputDeviceManager(); + QInputDeviceManagerPrivate::get(manager)->setDeviceCount(type, m_devices[type].count()); + auto number = QFileInfo(device).baseName().midRef(5).toInt(); + m_handler.add(number, type); +} + +void OxideEventManager::deviceRemoved(QInputDeviceManager::DeviceType type, const QString& device){ + m_devices[type].removeAll(device); + auto manager = QGuiApplicationPrivate::inputDeviceManager(); + QInputDeviceManagerPrivate::get(manager)->setDeviceCount(type, m_devices[type].count()); + auto number = QFileInfo(device).baseName().midRef(5).toInt(); + m_handler.remove(number, type); +} + + diff --git a/shared/qpa/oxideeventmanager.h b/shared/qpa/oxideeventmanager.h new file mode 100644 index 000000000..94e30a91c --- /dev/null +++ b/shared/qpa/oxideeventmanager.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "private/qdevicediscovery_p.h" +#include +#include + +#include "oxideeventhandler.h" + +class OxideEventManager : public QObject { + Q_OBJECT +public: + OxideEventManager(const QStringList& parameters); + +private slots: + void deviceDetected(QInputDeviceManager::DeviceType type, const QString& device); + void deviceRemoved(QInputDeviceManager::DeviceType type, const QString& device); + void deviceListChanged(QInputDeviceManager::DeviceType type); + +private: + void setup( + QDeviceDiscovery::QDeviceTypes privateTypes, + QInputDeviceManager::DeviceType publicType + ); + QMap m_devices; + OxideEventHandler m_handler; +}; diff --git a/shared/qpa/oxideintegration.cpp b/shared/qpa/oxideintegration.cpp new file mode 100644 index 000000000..893035f42 --- /dev/null +++ b/shared/qpa/oxideintegration.cpp @@ -0,0 +1,356 @@ +#include "oxideintegration.h" +#include "oxidebackingstore.h" +#include "oxideeventfilter.h" +#include "oxideeventmanager.h" +#include "oxidescreen.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace codes::eeems::blight1; + +QT_BEGIN_NAMESPACE + +class QCoreTextFontEngine; + +#define debugQPAEnvironmentVariable "OXIDE_QPA_DEBUG" + + +static inline OxideIntegration::Options parseOptions(const QStringList& paramList){ + OxideIntegration::Options options{{}}; + for(const QString& param : paramList){ + if(!param.compare(QLatin1String("enable_fonts"), Qt::CaseInsensitive)){ + options |= OxideIntegration::EnableFonts; + }else if(!param.compare(QLatin1String("freetype"), Qt::CaseInsensitive)){ + options |= OxideIntegration::FreeTypeFontDatabase; + }else if(!param.compare(QLatin1String("fontconfig"), Qt::CaseInsensitive)){ + options |= OxideIntegration::FontconfigDatabase; + } + } + if( + qEnvironmentVariableIsSet(debugQPAEnvironmentVariable) + && qEnvironmentVariableIntValue(debugQPAEnvironmentVariable) > 0 + ){ + options |= OxideIntegration::DebugQPA | OxideIntegration::EnableFonts; + } + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(true); + return options; +} + +OxideIntegration::OxideIntegration(const QStringList& parameters) +: m_fontDatabase(nullptr), + m_options(parseOptions(parameters)), + m_debug(false), + m_parameters(parameters) +{ + setenv("OXIDE_PRELOAD_DISABLE_INPUT", "1", true); + m_debug = m_options & DebugQPA; + if(m_debug){ + qDebug() << "OxideIntegration::OxideIntegration"; + } +} + +OxideIntegration::~OxideIntegration(){ + if(m_debug){ + qDebug() << "OxideIntegration::~OxideIntegration"; + } +} + +bool OxideIntegration::hasCapability(QPlatformIntegration::Capability cap) const{ + if(m_debug){ + qDebug() << "OxideIntegration::hasCapability"; + } + switch(cap){ + case ThreadedPixmaps: return true; + case MultipleWindows: return true; + default: return QPlatformIntegration::hasCapability(cap); + } +} + +void OxideIntegration::initialize(){ + if(m_debug){ + qDebug() << "OxideIntegration::initialize"; + } +#ifdef EPAPER + Blight::connect(true); +#else + Blight::connect(false); +#endif + auto connection = Blight::connection(); + if(connection == nullptr){ + qFatal("Could not connect to display server: %s", std::strerror(errno)); + } + connection->onDisconnect([](int res){ + if(res){ + qDebug() << "Lost connection to display server:" << res; + qApp->exit(res); + } + }); + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(true); + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents); + qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents); +#ifndef QT_NO_CLIPBOARD + m_clipboard = new QMimeData(); + m_selection = new QMimeData(); +#endif + m_primaryScreen = new OxideScreen(); + // rM1 geometry as default + m_primaryScreen->setGeometry(QRect(0, 0, 1404, 1872)); + QWindowSystemInterface::handleScreenAdded(m_primaryScreen, true); + qApp->installEventFilter(new OxideEventFilter(qApp)); + m_inputContext = QPlatformInputContextFactory::create(); + new OxideEventManager(m_parameters); +#ifndef QT_NO_CLIPBOARD + auto compositor = new Compositor( + BLIGHT_SERVICE, + "/", +#ifdef EPAPER + QDBusConnection::systemBus() +#else + QDBusConnection::sessionBus() +#endif + ); + connect(compositor, &Compositor::clipboardChanged, this, [this](const QByteArray& data){ + m_clipboard->setData("application/octet-stream", data); + }); + m_clipboard->setData("application/octet-stream", compositor->clipboard()); + connect(compositor, &Compositor::selectionChanged, this, [this](const QByteArray& data){ + m_selection->setData("application/octet-stream", data); + }); + m_selection->setData("application/octet-stream", compositor->selection()); +#endif +} + +void OxideIntegration::destroy(){ + if(m_debug){ + qDebug() << "OxideIntegration::destroy"; + } + QWindowSystemInterface::handleScreenRemoved(m_primaryScreen); +} + +void OxideIntegration::sync(){ + // TODO - get sync state with display server +} + +void OxideIntegration::beep() const{ + // TODO - show notification? +} + + +// Dummy font database that does not scan the fonts directory to be +// used for command line tools like qmlplugindump that do not create windows +// unless DebugBackingStore is activated. +class DummyFontDatabase +: public QPlatformFontDatabase +#ifndef QT_NO_CLIPBOARD + , public QPlatformClipboard +#endif +{ +public: + void populateFontDatabase() override {} +}; + +QPlatformFontDatabase* OxideIntegration::fontDatabase() const{ + if(!m_fontDatabase){ + if(m_debug){ + qDebug() << "OxideIntegration::fontDatabase"; + } + if(m_options & EnableFonts){ +#if QT_CONFIG(fontconfig) + m_fontDatabase = new QGenericUnixFontDatabase; +#else + m_fontDatabase = QPlatformIntegration::fontDatabase(); +#endif + }else{ + m_fontDatabase = new DummyFontDatabase; + } + } + return m_fontDatabase; +} + +#ifndef QT_NO_CLIPBOARD +QPlatformClipboard* OxideIntegration::clipboard() const{ + if(m_debug){ + qDebug() << "OxideIntegration::nativeInterface"; + } + return const_cast(this); +} +#endif + +QPlatformInputContext* OxideIntegration::inputContext() const{ + if(m_debug){ + qDebug() << "OxideIntegration::inputContext"; + } + return m_inputContext; +} + +QPlatformWindow* OxideIntegration::createPlatformWindow(QWindow *window) const{ + if(m_debug){ + qDebug() << "OxideIntegration::createPlatformWindow"; + } + OxideWindow* w = new OxideWindow(window); + w->requestActivateWindow(); + return w; +} + +QPlatformBackingStore* OxideIntegration::createPlatformBackingStore(QWindow *window) const{ + if(m_debug){ + qDebug() << "OxideIntegration::createPlatformBackingStore"; + } + return new OxideBackingStore(window); +} + +QAbstractEventDispatcher* OxideIntegration::createEventDispatcher() const{ + if(m_debug){ + qDebug() << "OxideIntegration::createEventDispatcher"; + } + return createUnixEventDispatcher(); +} + +QPlatformNativeInterface* OxideIntegration::nativeInterface() const{ + if(m_debug){ + qDebug() << "OxideIntegration::nativeInterface"; + } + return const_cast(this); +} + +QPlatformServices* OxideIntegration::services() const{ + if(m_debug){ + qDebug() << "OxideIntegration::services"; + } + return const_cast(this); +} + +Blight::shared_buf_t OxideIntegration::getSurfaceForWindowStatic(QWindow* qwindow){ + auto screen = static_cast(OxideScreen::platformScreenForWindow(qwindow)); + auto window = screen->getWindow(qwindow->winId()); + return window->backingStore()->buffer(); +} + +QFunctionPointer OxideIntegration::platformFunction(const QByteArray& function) const{ + if(m_debug){ + qDebug() << "OxideIntegration::platformFunction"; + } + if(function == "getSurfaceForWindow"){ + return QFunctionPointer((void(*))getSurfaceForWindowStatic); + } + return nullptr; +} + +OxideScreen* OxideIntegration::primaryScreen(){ return m_primaryScreen; } + +OxideIntegration::Options OxideIntegration::options() const { return m_options; } + +QStringList OxideIntegration::themeNames() const{ + // TODO - implement custom theme + return QPlatformIntegration::themeNames(); +} + +QPlatformTheme* OxideIntegration::createPlatformTheme(const QString& name) const{ + // TODO - implement custom theme + return QPlatformIntegration::createPlatformTheme(name); +} + +bool OxideIntegration::openUrl(const QUrl& url){ return openDocument(url); } + +bool OxideIntegration::openDocument(const QUrl& url){ + QStringList args = QProcess::splitCommand("xdg-open " + QLatin1String(url.toEncoded())); + bool ok = false; + if (!args.isEmpty()) { + QString program = args.takeFirst(); + ok = QProcess::startDetached(program, args); + } + return ok; +} + +QByteArray OxideIntegration::desktopEnvironment() const{ return qgetenv("XDG_CURRENT_DESKTOP"); } + +#ifndef Q_NO_CLIPBOARD +QMimeData* OxideIntegration::mimeData(QClipboard::Mode mode){ + switch(mode){ + case QClipboard::Clipboard: + return m_clipboard.data(); + case QClipboard::Selection: + return m_selection.data(); + default: + return nullptr; + } +} + +void OxideIntegration::setMimeData(QMimeData* data, QClipboard::Mode mode){ + switch(mode){ + case QClipboard::Clipboard:{ + m_clipboard = data; + auto clipboard = Blight::clipboard(); + auto constData = data->data(""); + clipboard->set(constData, constData.size()); + QPlatformClipboard::emitChanged(mode); + break; + } + case QClipboard::Selection:{ + m_selection = data; + auto selection = Blight::selection(); + auto constData = data->data(""); + selection->set(constData, constData.size()); + QPlatformClipboard::emitChanged(mode); + break; + } + default: + break; + } +} + +bool OxideIntegration::supportsMode(QClipboard::Mode mode) const{ + return mode == QClipboard::Clipboard || mode == QClipboard::Selection; +} + +bool OxideIntegration::ownsMode(QClipboard::Mode mode) const{ + Q_UNUSED(mode) + return false; +} +#endif + +OxideIntegration* OxideIntegration::instance(){ + auto instance = static_cast(QGuiApplicationPrivate::platformIntegration()); + if(instance->m_debug){ + qDebug() << "OxideIntegration::instance"; + } + return instance; +} + +void OxideIntegration::connectSignal(QObject* sender, QString signal, QObject* reciever, QString slot){ + auto signalIndex = sender->metaObject()->indexOfSignal(signal.toStdString().c_str()); + if(signalIndex == -1){ + qFatal("The index of the signal: %s", signal.toStdString().c_str()); + } + auto signalMethod = sender->metaObject()->method(signalIndex); + if(!signalMethod.isValid()){ + qFatal("Cannot find signal from index: %s", signal.toStdString().c_str()); + } + auto slotIndex = reciever->metaObject()->indexOfSlot(slot.toStdString().c_str()); + if(slotIndex == -1){ + qFatal("The index of the slot: %s", slot.toStdString().c_str()); + } + auto slotMethod = reciever->metaObject()->method(slotIndex); + if(!slotMethod.isValid()){ + qFatal("Cannot find slot from index: %s", slot.toStdString().c_str()); + } + QObject::connect(sender, signalMethod, reciever, slotMethod); +} + +QT_END_NAMESPACE diff --git a/shared/qpa/oxideintegration.h b/shared/qpa/oxideintegration.h new file mode 100644 index 000000000..e820b28d0 --- /dev/null +++ b/shared/qpa/oxideintegration.h @@ -0,0 +1,86 @@ +#pragma once +#include "oxidescreen.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_DECL_EXPORT OxideIntegration +: public QPlatformIntegration, + public QPlatformNativeInterface, + public QPlatformServices +#ifndef QT_NO_CLIPBOARD + , public QPlatformClipboard +#endif +{ +public: + enum Option{ // Options to be passed on command line or determined from environment + DebugQPA = 1, + EnableFonts = 2, + FreeTypeFontDatabase = 4, + FontconfigDatabase = 8 + }; + Q_DECLARE_FLAGS(Options, Option) + + explicit OxideIntegration(const QStringList ¶meters); + ~OxideIntegration(); + + bool hasCapability(QPlatformIntegration::Capability cap) const override; + void initialize() override; + void destroy() override; + void sync() override; + void beep() const override; + QPlatformFontDatabase* fontDatabase() const override; +#ifndef QT_NO_CLIPBOARD + QPlatformClipboard* clipboard() const override; +#endif + QPlatformInputContext* inputContext() const override; + QPlatformWindow* createPlatformWindow(QWindow* window) const override; + QPlatformBackingStore* createPlatformBackingStore(QWindow* window) const override; + QAbstractEventDispatcher* createEventDispatcher() const override; + QPlatformNativeInterface* nativeInterface() const override; + QPlatformServices* services() const override; + QFunctionPointer platformFunction(const QByteArray& function) const override; + OxideScreen* primaryScreen(); + OxideIntegration::Options options() const; + QStringList themeNames() const override; + QPlatformTheme* createPlatformTheme(const QString& name) const override; + bool openUrl(const QUrl& url) override; + bool openDocument(const QUrl& url) override; + QByteArray desktopEnvironment() const override; +#ifndef QT_NO_CLIPBOARD + QMimeData* mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override; + void setMimeData(QMimeData* data, QClipboard::Mode mode = QClipboard::Clipboard) override; + bool supportsMode(QClipboard::Mode mode) const override; + bool ownsMode(QClipboard::Mode mode) const override; +#endif + static OxideIntegration* instance(); + static Blight::shared_buf_t getSurfaceForWindowStatic(QWindow* qwindow); + static QImage getImageForWindowStatic(QWindow* qwindow); + +private: + mutable QPlatformFontDatabase* m_fontDatabase = nullptr; + QPlatformInputContext* m_inputContext = nullptr; + QPointer m_primaryScreen; +#ifndef QT_NO_CLIPBOARD + QPointer m_clipboard; + QPointer m_selection; +#endif + Options m_options; + bool m_debug; + QMutex m_mutex; + QStringList m_parameters; + static void connectSignal(QObject* sender, QString signal, QObject* reciever, QString slot); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(OxideIntegration::Options) + +QT_END_NAMESPACE diff --git a/shared/qpa/oxidescreen.cpp b/shared/qpa/oxidescreen.cpp new file mode 100644 index 000000000..0c5107595 --- /dev/null +++ b/shared/qpa/oxidescreen.cpp @@ -0,0 +1,58 @@ +#include "oxidescreen.h" + +#include +#include +#include + +OxideScreen::OxideScreen() {} + +QRect OxideScreen::geometry() const { return m_geometry; } + +static QTransform m_tabletTransform = QTransform::fromTranslate(0.5, 0.5).rotate(90).translate(-0.5, -0.5); + +void OxideScreen::setGeometry(QRect geometry){ + if(m_geometry != geometry){ + m_geometry = geometry; + qDebug() << "Screen geometry set to:" << geometry << screen(); + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry, geometry); + } +} + +int OxideScreen::depth() const { return 16; } + +QImage::Format OxideScreen::format() const { return QImage::Format_ARGB32_Premultiplied; } + +QSizeF OxideScreen::physicalSize() const{ + static const int dpi = 228; + return QSizeF(geometry().size()) / dpi * qreal(25.4); +} + +Qt::ScreenOrientation OxideScreen::orientation() const{ + // TODO - allow rotation + return Qt::PortraitOrientation; +} + +Qt::ScreenOrientation OxideScreen::nativeOrientation() const{ return Qt::PortraitOrientation; } + +QString OxideScreen::name() const{ return "Oxide"; } + +void OxideScreen::addWindow(OxideWindow* window){ + m_windows.append(window); + window->requestActivateWindow(); + if(m_windows.length() == 1){ + window->raise(); + } +} +void OxideScreen::removeWindow(OxideWindow* window){ + m_windows.removeAll(window); + window->requestActivateWindow(); +} + +OxideWindow* OxideScreen::getWindow(const WId& winId){ + for(auto& window : m_windows){ + if(window->winId() == winId){ + return window; + } + } + return nullptr; +} diff --git a/shared/qpa/oxidescreen.h b/shared/qpa/oxidescreen.h new file mode 100644 index 000000000..d04c83748 --- /dev/null +++ b/shared/qpa/oxidescreen.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include "oxidewindow.h" + +class OxideWindow; + +class Q_DECL_EXPORT OxideScreen : public QObject, public QPlatformScreen{ + Q_OBJECT + +public: + OxideScreen(); + QRect geometry() const override; + void setGeometry(QRect geometry); + int depth() const override; + QImage::Format format() const override; + QSizeF physicalSize() const override; + Qt::ScreenOrientation orientation() const override; + Qt::ScreenOrientation nativeOrientation() const override; + QString name() const override; + void addWindow(OxideWindow* window); + void removeWindow(OxideWindow* window); + OxideWindow* getWindow(const WId& winId); + +private: + QRect m_geometry; + QList m_windows; +}; diff --git a/shared/qpa/oxidewindow.cpp b/shared/qpa/oxidewindow.cpp new file mode 100644 index 000000000..47c130940 --- /dev/null +++ b/shared/qpa/oxidewindow.cpp @@ -0,0 +1,91 @@ +#include "oxidewindow.h" +#include "oxideintegration.h" + +#include +#include +#include + +OxideWindow::OxideWindow(QWindow* window) : QPlatformWindow(window), mBackingStore(nullptr){ } + +OxideWindow::~OxideWindow(){ } + +void OxideWindow::setBackingStore(OxideBackingStore* store){ + mBackingStore = store; +} + +OxideBackingStore* OxideWindow::backingStore() const { return mBackingStore; } + +OxideScreen* OxideWindow::platformScreen() const{ + auto window = this->window(); + if(window == nullptr){ + return nullptr; + } + auto screen = window->screen(); + if(screen == nullptr){ + return nullptr; + } + auto handle = screen->handle(); + if(handle == nullptr){ + return nullptr; + } + return static_cast(handle); +} + +void OxideWindow::setGeometry(const QRect& rect){ + auto buffer = mBackingStore->buffer(); + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideWindow::setGeometry:" << mBackingStore->buffer()->surface << geometry() << rect; + } + QPlatformWindow::setGeometry(rect); + Blight::connection()->move(buffer, rect.x(), rect.y()); +} + +void OxideWindow::setVisible(bool visible){ + QPlatformWindow::setVisible(visible); + auto screen = platformScreen(); + if(screen != nullptr){ + if(visible){ + screen->addWindow(this); + raise(); + }else{ + screen->removeWindow(this); + lower(); + } + } +} + +void OxideWindow::raise(){ + if(mBackingStore == nullptr){ + return; + } + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideWindow::raise" << mBackingStore->buffer()->surface; + } + auto buffer = mBackingStore->buffer(); + auto maybe = Blight::connection()->raise(buffer); + if(maybe.has_value()){ + maybe.value()->wait(); + } +} + +void OxideWindow::lower(){ + if(mBackingStore == nullptr){ + return; + } + if(OxideIntegration::instance()->options().testFlag(OxideIntegration::DebugQPA)){ + qDebug() << "OxideWindow::lower" << mBackingStore->buffer()->surface; + } + auto buffer = mBackingStore->buffer(); + auto maybe = Blight::connection()->lower(buffer); + if(maybe.has_value()){ + maybe.value()->wait(); + } +} + +bool OxideWindow::close(){ + if(QPlatformWindow::close()){ + Blight::connection()->remove(mBackingStore->buffer()); + return true; + } + return false; +} diff --git a/shared/qpa/oxidewindow.h b/shared/qpa/oxidewindow.h new file mode 100644 index 000000000..e4566ce2c --- /dev/null +++ b/shared/qpa/oxidewindow.h @@ -0,0 +1,27 @@ +#pragma once +#include "oxidebackingstore.h" +#include "oxidescreen.h" + +#include +#include +#include + +class OxideScreen; + +class OxideWindow : public QPlatformWindow { +public: + OxideWindow(QWindow* window); + ~OxideWindow(); + + void setBackingStore(OxideBackingStore* store); + OxideBackingStore* backingStore() const; + OxideScreen* platformScreen() const; + void setGeometry(const QRect& rect) override; + void setVisible(bool visible) override; + void raise() override; + void lower() override; + bool close() override; + +protected: + OxideBackingStore* mBackingStore; +}; diff --git a/shared/qpa/qpa.pro b/shared/qpa/qpa.pro new file mode 100644 index 000000000..e9c15b19f --- /dev/null +++ b/shared/qpa/qpa.pro @@ -0,0 +1,49 @@ +TARGET = oxide +TEMPLATE = lib + +include(../../qmake/common.pri) + +CONFIG += plugin +CONFIG += qpa/genericunixfontdatabase + +QT += core-private +QT += core +QT += gui-private +QT += input_support-private + +qtHaveModule(fontdatabase_support-private) { + QT += fontdatabase_support-private +} +qtHaveModule(eventdispatcher_support-private) { + QT += eventdispatcher_support-private +} + +SOURCES = main.cpp \ + oxidebackingstore.cpp \ + oxideeventfilter.cpp \ + oxideeventhandler.cpp \ + oxideeventmanager.cpp \ + oxideintegration.cpp \ + oxidescreen.cpp \ + oxidewindow.cpp +HEADERS = oxidebackingstore.h \ + default_keymap.h \ + oxideeventfilter.h \ + oxideeventhandler.h \ + oxideeventmanager.h \ + oxideintegration.h \ + oxidescreen.h \ + oxidewindow.h + +OTHER_FILES += oxide.json + +target.path += /opt/usr/lib/plugins/platforms +INSTALLS += target + +DISTFILES += \ + oxide.json + +DEFINES += LIBOXIDE_PRIVATE +include(../../qmake/liboxide.pri) +DEFINES += LIBBLIGHT_PRIVATE +include(../../qmake/libblight.pri) diff --git a/shared/shared.pro b/shared/shared.pro index 284b9d039..b8cb30d9a 100644 --- a/shared/shared.pro +++ b/shared/shared.pro @@ -1,6 +1,14 @@ TEMPLATE = subdirs -SUBDIRS = liboxide +SUBDIRS = \ + liboxide \ + libblight \ + libblight_client \ + qpa + +liboxide.depends = libblight +libblight_client.depends = libblight +qpa.depends = libblight liboxide contains(DEFINES, SENTRY){ SUBDIRS += sentry liboxide.depends += sentry diff --git a/tests/libblight/autotest.h b/tests/libblight/autotest.h new file mode 100644 index 000000000..5dfc42dd6 --- /dev/null +++ b/tests/libblight/autotest.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include + +namespace AutoTest{ +typedef QList TestList; +inline TestList& testList(){ + static TestList list; + return list; +} +inline bool findObject(QObject* object){ + TestList& list = testList(); + if(list.contains(object)){ + return true; + } + foreach(QObject* test, list){ + if(test->objectName() == object->objectName()){ + return true; + } + } + return false; +} +inline void addTest(QObject* object){ + TestList& list = testList(); + if(!findObject(object)){ + list.append(object); + } +} +inline int run(int argc, char *argv[]){ + int ret = 0; + foreach(QObject* test, testList()){ + ret += QTest::qExec(test, argc, argv); + } + return ret; +} +} + +template +class Test{ +public: + QSharedPointer child; + + Test(const QString& name) : child(new T){ + child->setObjectName(name); + AutoTest::addTest(child.data()); + } +}; + +#define DECLARE_TEST(className) static Test t(#className); diff --git a/tests/libblight/libblight.pro b/tests/libblight/libblight.pro new file mode 100644 index 000000000..f1caba7fb --- /dev/null +++ b/tests/libblight/libblight.pro @@ -0,0 +1,30 @@ +QT += testlib +QT -= gui + +target.path = /opt/share/tests +INSTALLS += target + +CONFIG += qt +CONFIG += console +CONFIG += warn_on +CONFIG += depend_includepath +CONFIG += testcase +CONFIG += no_testcase_installs +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += \ + main.cpp \ + test_clock.cpp \ + test_socket.cpp \ + test_types.cpp + +HEADERS += \ + autotest.h \ + test_clock.h \ + test_socket.h \ + test_types.h + +include(../../qmake/common.pri) +include(../../qmake/libblight.pri) diff --git a/tests/libblight/main.cpp b/tests/libblight/main.cpp new file mode 100644 index 000000000..65ecd2267 --- /dev/null +++ b/tests/libblight/main.cpp @@ -0,0 +1,14 @@ +#include "autotest.h" +#include +#include +#include + +int main(int argc, char* argv[]){ + QThread::currentThread()->setObjectName("main"); + QCoreApplication app(argc, argv); + app.setAttribute(Qt::AA_Use96Dpi, true); + QTimer::singleShot(0, [&app, argc, argv]{ + app.exit(AutoTest::run(argc, argv)); + }); + return app.exec(); +} diff --git a/tests/libblight/test_clock.cpp b/tests/libblight/test_clock.cpp new file mode 100644 index 000000000..fe7b0baae --- /dev/null +++ b/tests/libblight/test_clock.cpp @@ -0,0 +1,18 @@ +#include "test_clock.h" + +#include + +test_Clock::test_Clock(){ } +test_Clock::~test_Clock(){ } + +void test_Clock::test_diff(){ + auto cw = Blight::ClockWatch(); + QTRY_VERIFY(cw.diff().count() > 0); +} + +void test_Clock::test_elapsed(){ + auto cw = Blight::ClockWatch(); + QTRY_VERIFY(cw.elapsed() > 0); +} + +DECLARE_TEST(test_Clock) diff --git a/tests/libblight/test_clock.h b/tests/libblight/test_clock.h new file mode 100644 index 000000000..6fbec2e66 --- /dev/null +++ b/tests/libblight/test_clock.h @@ -0,0 +1,14 @@ +#pragma once +#include "autotest.h" + +class test_Clock : public QObject{ + Q_OBJECT + +public: + test_Clock(); + ~test_Clock(); + +private slots: + void test_diff(); + void test_elapsed(); +}; diff --git a/tests/libblight/test_socket.cpp b/tests/libblight/test_socket.cpp new file mode 100644 index 000000000..d5dab3b3a --- /dev/null +++ b/tests/libblight/test_socket.cpp @@ -0,0 +1,29 @@ +#include "test_socket.h" + +#include +#include + +test_Socket::test_Socket(){ } +test_Socket::~test_Socket(){ } + +void test_Socket::test_recv(){ + // TODO - add test +} + +void test_Socket::test_recv_blocking(){ + // TODO - add test +} + +void test_Socket::test_send_blocking(){ + // TODO - add test +} + +void test_Socket::test_wait_for_send(){ + // TODO - add test +} + +void test_Socket::test_wait_for_read(){ + // TODO - add test +} + +DECLARE_TEST(test_Socket) diff --git a/tests/libblight/test_socket.h b/tests/libblight/test_socket.h new file mode 100644 index 000000000..5c8549208 --- /dev/null +++ b/tests/libblight/test_socket.h @@ -0,0 +1,17 @@ +#pragma once +#include "autotest.h" + +class test_Socket : public QObject{ + Q_OBJECT + +public: + test_Socket(); + ~test_Socket(); + +private slots: + void test_recv(); + void test_recv_blocking(); + void test_send_blocking(); + void test_wait_for_send(); + void test_wait_for_read(); +}; diff --git a/tests/libblight/test_types.cpp b/tests/libblight/test_types.cpp new file mode 100644 index 000000000..ea9796d75 --- /dev/null +++ b/tests/libblight/test_types.cpp @@ -0,0 +1,118 @@ +#include "test_types.h" + +#include + +test_Types::test_Types(){ } +test_Types::~test_Types(){ } + +void test_Types::test_clipboard_t(){ + // TODO - test to_string() + // TODO - test update() + // TODO - test set() +} + +void test_Types::test_buf_t(){ + // TODO - test use of buffer with QImage + // TODO - test size() + // TODO - test close() + // TODO - test clone() + // TODO - test new_ptr() + // TODO - test new_uuid() +} + +void test_Types::test_header_t(){ + // TODO - test from_data() + // TODO - test new_invalid() +} + +void test_Types::test_message_t(){ + auto message = Blight::message_t::new_ptr(); + QVERIFY(message != nullptr); + QCOMPARE(sizeof(*message.get()), sizeof(Blight::message_t)); + auto header = Blight::message_t::create_ack(message.get(), 0); + QCOMPARE(header.size, 0); + QCOMPARE(Blight::Ack, header.type); + QCOMPARE(header.ackid, message->header.ackid); + std::string data("test"); + header = Blight::message_t::create_ack(message.get(), data.size()); + QCOMPARE(header.size, data.size()); + QCOMPARE(header.type, Blight::Ack); + QCOMPARE(header.ackid, message->header.ackid); + // TODO - test from_socket() + // TODO - test from_data() +} + +void test_Types::test_repaint_t(){ + auto header = new Blight::repaint_t{ + .x = 10, + .y = 10, + .width = 10, + .height = 10, + .waveform = Blight::Mono, + .marker = 1, + .identifier = 1, + }; + Blight::shared_data_t data(reinterpret_cast(header)); + Blight::message_t message{ + .header = { + .type = Blight::Repaint, + .ackid = 10, + .size = sizeof(Blight::repaint_t) + }, + .data = data + }; + auto header2 = Blight::repaint_t::from_message(&message); + QCOMPARE(sizeof(header2), message.header.size); + QCOMPARE(header2.x, header->x); + QCOMPARE(header2.y, header->y); + QCOMPARE(header2.width, header->width); + QCOMPARE(header2.height, header->height); + QCOMPARE(header2.waveform, header->waveform); + QCOMPARE(header2.marker, header->marker); + QCOMPARE(header2.identifier, header->identifier); +} + +void test_Types::test_move_t(){ + auto header = new Blight::move_t{ + .identifier = 1, + .x = 10, + .y = 10 + }; + Blight::shared_data_t data(reinterpret_cast(header)); + Blight::message_t message{ + .header = { + .type = Blight::Move, + .ackid = 10, + .size = sizeof(Blight::move_t) + }, + .data = data + }; + auto header2 = Blight::move_t::from_message(&message); + QCOMPARE(sizeof(header2), message.header.size); + QCOMPARE(header2.identifier, header->identifier); + QCOMPARE(header2.x, header->x); + QCOMPARE(header2.y, header->y); +} + +void test_Types::test_surface_info_t(){ + Blight::surface_info_t header{ + .x = 10, + .y = 10, + .width = 10, + .height = 10, + .stride = 20, + .format = Blight::Format_RGB16 + }; + auto header2 = Blight::surface_info_t::from_data( + reinterpret_cast(&header) + ); + QCOMPARE(sizeof(header2), sizeof(Blight::surface_info_t)); + QCOMPARE(header2.x, header.x); + QCOMPARE(header2.y, header.y); + QCOMPARE(header2.width, header.width); + QCOMPARE(header2.height, header.height); + QCOMPARE(header2.stride, header.stride); + QCOMPARE(header2.format, header.format); +} + +DECLARE_TEST(test_Types) diff --git a/tests/libblight/test_types.h b/tests/libblight/test_types.h new file mode 100644 index 000000000..9056d0f99 --- /dev/null +++ b/tests/libblight/test_types.h @@ -0,0 +1,19 @@ +#pragma once +#include "autotest.h" + +class test_Types : public QObject{ + Q_OBJECT + +public: + test_Types(); + ~test_Types(); + +private slots: + void test_clipboard_t(); + void test_buf_t(); + void test_header_t(); + void test_message_t(); + void test_repaint_t(); + void test_move_t(); + void test_surface_info_t(); +}; diff --git a/tests/liboxide/autotest.h b/tests/liboxide/autotest.h new file mode 100644 index 000000000..332e65e30 --- /dev/null +++ b/tests/liboxide/autotest.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include + +namespace AutoTest{ + typedef QList TestList; + inline TestList& testList(){ + static TestList list; + return list; + } + inline bool findObject(QObject* object){ + TestList& list = testList(); + if(list.contains(object)){ + return true; + } + foreach(QObject* test, list){ + if(test->objectName() == object->objectName()){ + return true; + } + } + return false; + } + inline void addTest(QObject* object){ + TestList& list = testList(); + if(!findObject(object)){ + list.append(object); + } + } + inline int run(int argc, char *argv[]){ + int ret = 0; + foreach(QObject* test, testList()){ + ret += QTest::qExec(test, argc, argv); + } + return ret; + } +} + +template +class Test{ +public: + QSharedPointer child; + + Test(const QString& name) : child(new T){ + child->setObjectName(name); + AutoTest::addTest(child.data()); + } +}; + +#define DECLARE_TEST(className) static Test t(#className); diff --git a/tests/liboxide/liboxide.pro b/tests/liboxide/liboxide.pro new file mode 100644 index 000000000..edb3a35ab --- /dev/null +++ b/tests/liboxide/liboxide.pro @@ -0,0 +1,32 @@ +QT += testlib +QT += gui + +target.path = /opt/share/tests +INSTALLS += target + +CONFIG += qt +CONFIG += console +CONFIG += warn_on +CONFIG += depend_includepath +CONFIG += testcase +CONFIG += no_testcase_installs +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += \ + main.cpp \ + test_Debug.cpp \ + test_Event_Device.cpp \ + test_Json.cpp \ + test_Threading.cpp + +include(../../qmake/common.pri) +include(../../qmake/liboxide.pri) + +HEADERS += \ + autotest.h \ + test_Debug.h \ + test_Event_Device.h \ + test_Json.h \ + test_Threading.h diff --git a/tests/liboxide/main.cpp b/tests/liboxide/main.cpp new file mode 100644 index 000000000..65ecd2267 --- /dev/null +++ b/tests/liboxide/main.cpp @@ -0,0 +1,14 @@ +#include "autotest.h" +#include +#include +#include + +int main(int argc, char* argv[]){ + QThread::currentThread()->setObjectName("main"); + QCoreApplication app(argc, argv); + app.setAttribute(Qt::AA_Use96Dpi, true); + QTimer::singleShot(0, [&app, argc, argv]{ + app.exit(AutoTest::run(argc, argv)); + }); + return app.exec(); +} diff --git a/tests/liboxide/test_Debug.cpp b/tests/liboxide/test_Debug.cpp new file mode 100644 index 000000000..c0deff7fb --- /dev/null +++ b/tests/liboxide/test_Debug.cpp @@ -0,0 +1,56 @@ +#include "test_Debug.h" + +#include + +test_Debug::test_Debug(){ } +test_Debug::~test_Debug(){ } + +void test_Debug::test_getDebugApplicationInfo(){ + QCOMPARE( + QString::fromStdString(Oxide::getDebugApplicationInfo()), + QString("[%1:%2:%3 liboxide - liboxide]") + .arg(::getpgrp()) + .arg(::getpid()) + .arg(::gettid()) + ); +} + +void test_Debug::test_getDebugLocation(){ + QCOMPARE(QString::fromStdString(Oxide::getDebugLocation("file", 0, "function")), "(file:0, function)"); + QCOMPARE(QString::fromStdString(Oxide::getDebugLocation("file", 1, "function")), "(file:1, function)"); +} + +void test_Debug::test_debugEnabled(){ + unsetenv("DEBUG"); + QVERIFY(!Oxide::debugEnabled()); + ::setenv("DEBUG", "0", true); + QVERIFY(!Oxide::debugEnabled()); + ::setenv("DEBUG", "n", true); + QVERIFY(!Oxide::debugEnabled()); + ::setenv("DEBUG", "no", true); + QVERIFY(!Oxide::debugEnabled()); + ::setenv("DEBUG", "false", true); + QVERIFY(!Oxide::debugEnabled()); + ::setenv("DEBUG", "1", true); + QVERIFY(Oxide::debugEnabled()); +} + +void test_Debug::test_getAppName(){ + QCOMPARE(QString::fromStdString(Oxide::getAppName(false)), QString("liboxide")); + qApp->setApplicationName("liboxide-test"); + QCOMPARE(QString::fromStdString(Oxide::getAppName(false)), QString("liboxide-test")); + qApp->setApplicationName("liboxide"); + QCOMPARE(QString::fromStdString(Oxide::getAppName(false)), QString("liboxide")); + QCOMPARE(QString::fromStdString(Oxide::getAppName(true)), QString("liboxide")); + // TODO - test /proc/self/comm changing + // TODO - test /proc/self/exe changing + // TODO - test /proc/self/{comm,exe} both being null +} + +void test_Debug::test_backtrace(){ + auto trace = Oxide::backtrace(2); + QCOMPARE(trace.size(), 1); + QCOMPARE(QString::fromStdString(trace[0].substr(0, 27)), "/opt/share/tests/liboxide()"); +} + +DECLARE_TEST(test_Debug) diff --git a/tests/liboxide/test_Debug.h b/tests/liboxide/test_Debug.h new file mode 100644 index 000000000..a68505c11 --- /dev/null +++ b/tests/liboxide/test_Debug.h @@ -0,0 +1,19 @@ +#pragma once + +#pragma once +#include "autotest.h" + +class test_Debug : public QObject{ + Q_OBJECT + +public: + test_Debug(); + ~test_Debug(); + +private slots: + void test_getDebugApplicationInfo(); + void test_getDebugLocation(); + void test_debugEnabled(); + void test_getAppName(); + void test_backtrace(); +}; diff --git a/tests/liboxide/test_Event_Device.cpp b/tests/liboxide/test_Event_Device.cpp new file mode 100644 index 000000000..0fcc0a558 --- /dev/null +++ b/tests/liboxide/test_Event_Device.cpp @@ -0,0 +1,36 @@ +#include "test_Event_Device.h" + +#include + +test_Event_Device::test_Event_Device(){ } +test_Event_Device::~test_Event_Device(){ } + +void test_Event_Device::test_create_device(){ + auto ev = Oxide::event_device::create_event(0, 0, 0); + QCOMPARE(ev.type, 0); + QCOMPARE(ev.code, 0); + QCOMPARE(ev.value, 0); + ev = Oxide::event_device::create_event(1, 1, 1); + QCOMPARE(ev.type, 1); + QCOMPARE(ev.code, 1); + QCOMPARE(ev.value, 1); +} + +void test_Event_Device::test_event_device(){ + if(!QFileInfo::exists("/dev/input/event0")){ + QSKIP("Missing event0"); + } + Oxide::event_device event0("/dev/input/event0", O_RDWR); + QCOMPARE(event0.device, "/dev/input/event0"); + QVERIFY(event0.fd > 0); + QVERIFY(!event0.locked); + if(event0.lock() != EBUSY){ + QVERIFY(event0.locked); + event0.unlock(); + QVERIFY(!event0.locked); + } + event0.close(); + QCOMPARE(event0.fd, 0); +} + +DECLARE_TEST(test_Event_Device) diff --git a/tests/liboxide/test_Event_Device.h b/tests/liboxide/test_Event_Device.h new file mode 100644 index 000000000..5a74923d3 --- /dev/null +++ b/tests/liboxide/test_Event_Device.h @@ -0,0 +1,16 @@ +#pragma once + +#pragma once +#include "autotest.h" + +class test_Event_Device : public QObject{ + Q_OBJECT + +public: + test_Event_Device(); + ~test_Event_Device(); + +private slots: + void test_create_device(); + void test_event_device(); +}; diff --git a/tests/liboxide/test_Json.cpp b/tests/liboxide/test_Json.cpp new file mode 100644 index 000000000..f55d34c81 --- /dev/null +++ b/tests/liboxide/test_Json.cpp @@ -0,0 +1,73 @@ +#include "test_Json.h" + +#include +#include + +test_Json::test_Json(){ } +test_Json::~test_Json(){ } + +void test_Json::test_decodeDBusArgument(){ + // TODO - Oxide::JSON::decodeDBusArgument(); +} + +void test_Json::test_sanitizeForJson(){ + // TODO - Oxide::JSON::sanitizeForJson(); +} + +void test_Json::test_toJson(){ + QCOMPARE(Oxide::JSON::toJson(QVariant()), "null"); + QCOMPARE(Oxide::JSON::toJson(true), "true"); + QCOMPARE(Oxide::JSON::toJson(false), "false"); + QCOMPARE(Oxide::JSON::toJson(10), "10"); + QCOMPARE(Oxide::JSON::toJson(10.1), "10.1"); + QCOMPARE(Oxide::JSON::toJson("10"), "\"10\""); + QJsonObject obj; + obj.insert("value", 10); + QCOMPARE(Oxide::JSON::toJson(obj), "{\"value\":10}"); + QCOMPARE(Oxide::JSON::toJson(obj, QJsonDocument::Indented), "{\n \"value\": 10\n}\n"); + QJsonArray arr; + arr.append(10); + arr.append("10"); + QCOMPARE(Oxide::JSON::toJson(arr), "[10,\"10\"]"); + QCOMPARE(Oxide::JSON::toJson(arr, QJsonDocument::Indented), "[\n 10,\n \"10\"\n]\n"); +} + +void test_Json::test_fromJson(){ + QVariant variant = Oxide::JSON::fromJson("null"); + QVERIFY(variant.isNull()); + variant = Oxide::JSON::fromJson("true"); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QCOMPARE(variant.toBool(), true); + variant = Oxide::JSON::fromJson("false"); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QCOMPARE(variant.toBool(), false); + variant = Oxide::JSON::fromJson("1"); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QCOMPARE(variant.toInt(), 1); + variant = Oxide::JSON::fromJson("\"1\""); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QCOMPARE(variant.toString(), "1"); + variant = Oxide::JSON::fromJson("{\"value\": 10}"); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QJsonObject obj = variant.toJsonObject(); + QVERIFY(obj.keys().contains("value")); + QCOMPARE(obj.value("value"), 10); + variant = Oxide::JSON::fromJson("[10, \"10\"]"); + QVERIFY(variant.isValid()); + QVERIFY(!variant.isNull()); + QJsonArray arr = variant.toJsonArray(); + QCOMPARE(arr.count(), 2); + QJsonValue value = arr.first(); + QVERIFY(value.isDouble()); + QCOMPARE(value.toDouble(), 10); + value = arr.last(); + QVERIFY(value.isString()); + QCOMPARE(value.toString(), "10"); +} + +DECLARE_TEST(test_Json) diff --git a/tests/liboxide/test_Json.h b/tests/liboxide/test_Json.h new file mode 100644 index 000000000..7660b8a08 --- /dev/null +++ b/tests/liboxide/test_Json.h @@ -0,0 +1,18 @@ +#pragma once + +#pragma once +#include "autotest.h" + +class test_Json : public QObject{ + Q_OBJECT + +public: + test_Json(); + ~test_Json(); + +private slots: + void test_decodeDBusArgument(); + void test_sanitizeForJson(); + void test_toJson(); + void test_fromJson(); +}; diff --git a/tests/liboxide/test_Threading.cpp b/tests/liboxide/test_Threading.cpp new file mode 100644 index 000000000..6ec89999b --- /dev/null +++ b/tests/liboxide/test_Threading.cpp @@ -0,0 +1,162 @@ +#include "test_Threading.h" +#include +#include +#include + +using namespace Oxide; + +test_Threading::test_Threading(){} +test_Threading::~test_Threading(){} + +void test_Threading::test_startThreadWithPriority(){ +#ifndef QT_HAS_THREAD_PRIORITY_SCHEDULING + auto thread = new QThread(); + thread->setObjectName("TimeCriticalPriority"); + startThreadWithPriority(thread, QThread::TimeCriticalPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), -20); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("HighestPriority"); + startThreadWithPriority(thread, QThread::HighestPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), -10); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("HighPriority"); + startThreadWithPriority(thread, QThread::HighPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), -5); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("NormalPriority"); + startThreadWithPriority(thread, QThread::NormalPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), 0); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("InheritPriority"); + startThreadWithPriority(thread, QThread::InheritPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), 0); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("LowPriority"); + startThreadWithPriority(thread, QThread::LowPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), 5); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("LowestPriority"); + startThreadWithPriority(thread, QThread::LowestPriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), 10); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); + + thread = new QThread(); + thread->setObjectName("IdlePriority"); + startThreadWithPriority(thread, QThread::IdlePriority); + thread->moveToThread(thread); + dispatchToThread(thread, [this]{ QCOMPARE(getNice(), 19); }); + thread->quit(); + thread->wait(); + thread->deleteLater(); +#endif +} + +void test_Threading::test_dispatchToThread(){ + auto thread = new QThread(); + thread->setObjectName("dispatchToThread"); + thread->start(); + thread->moveToThread(thread); + QVERIFY(thread->isRunning()); + QVERIFY(QThread::currentThread() != thread); + + dispatchToThread(thread, [thread]{ + QCOMPARE(QThread::currentThread(), thread); + dispatchToMainThread([thread]{ + QVERIFY(QThread::currentThread() != thread); + QCOMPARE(QThread::currentThread(), qApp->thread()); + }); + QVERIFY(dispatchToMainThread([]{ return true; })); + QVERIFY(!dispatchToMainThread([]{ return false; })); + QCOMPARE(dispatchToMainThread([]{ return "Hello World!"; }), "Hello World!"); + QCOMPARE(dispatchToMainThread([]{ return "Hello World!"; }), "Hello World!"); + QCOMPARE(dispatchToMainThread([]{ return "Hello World!"; }), QString("Hello World!")); + }); + QVERIFY(dispatchToThread(thread, []{ return true; })); + QVERIFY(!dispatchToThread(thread, []{ return false; })); + QCOMPARE(dispatchToThread(thread, []{ return "Hello World!"; }), "Hello World!"); + QCOMPARE(dispatchToThread(thread, []{ return "Hello World!"; }), "Hello World!"); + QCOMPARE(dispatchToThread(thread, []{ return "Hello World!"; }), QString("Hello World!")); + + thread->quit(); + thread->wait(); + QVERIFY(thread->isFinished()); + thread->deleteLater(); +} + +void test_Threading::test_runLater(){ + auto thread = new QThread(); + thread->setObjectName("runLater"); + thread->start(); + thread->moveToThread(thread); + QVERIFY(thread->isRunning()); + QVERIFY(QThread::currentThread() != thread); + + bool ok = false; + QEventLoop loop; + runLater(thread, [&ok, &loop, thread]{ + QCOMPARE(QThread::currentThread(), thread); + ok = true; + runLaterInMainThread([ok]{ + QCOMPARE(QThread::currentThread(), qApp->thread()); + QVERIFY(ok); + }); + loop.quit(); + }); + QVERIFY(!ok); + loop.exec(); + QVERIFY(ok); + + thread->quit(); + thread->wait(); + QVERIFY(thread->isFinished()); + thread->deleteLater(); +} + +void test_Threading::test_runInEventLoop(){ + bool ok = false; + runInEventLoop([&ok](std::function quit){ + QTimer::singleShot(0, [&ok, quit]{ + ok = true; + quit(); + }); + QVERIFY(!ok); + }); + QVERIFY(ok); +} + +int test_Threading::getNice(){ return getpriority(PRIO_PROCESS, gettid()); } + +DECLARE_TEST(test_Threading) diff --git a/tests/liboxide/test_Threading.h b/tests/liboxide/test_Threading.h new file mode 100644 index 000000000..b69f8fe47 --- /dev/null +++ b/tests/liboxide/test_Threading.h @@ -0,0 +1,19 @@ +#pragma once +#include "autotest.h" + +class test_Threading : public QObject{ + Q_OBJECT + +public: + test_Threading(); + ~test_Threading(); + +private slots: + void test_startThreadWithPriority(); + void test_dispatchToThread(); + void test_runLater(); + void test_runInEventLoop(); + +private: + static int getNice(); +}; diff --git a/tests/test_app/main.cpp b/tests/test_app/main.cpp new file mode 100644 index 000000000..51cb9ab6a --- /dev/null +++ b/tests/test_app/main.cpp @@ -0,0 +1,277 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Oxide; +using namespace Oxide::QML; +using namespace Oxide::Sentry; + +#define DEBUG_EVENTS + +int main(int argc, char *argv[]){ + qputenv("QMLSCENE_DEVICE", "software"); + qputenv("QT_QUICK_BACKEND","software"); + qputenv("QT_QPA_PLATFORM", "oxide:enable_fonts"); + QCoreApplication::addLibraryPath("/opt/usr/lib/plugins"); + QGuiApplication app(argc, argv); + sentry_init("test_app", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("test_app"); + app.setApplicationVersion(APP_VERSION); + if(Oxide::debugEnabled()){ + BLIGHT_DEBUG_LOGGING = 7; + } + if(!qEnvironmentVariableIsSet("SKIP_TEST")){ +#ifdef EPAPER + Blight::connect(true); +#else + Blight::connect(false); +#endif + if(!Blight::exists()){ + O_WARNING("Service not found!"); + return EXIT_FAILURE; + } + { + auto maybe = Blight::clipboard(); + if(!maybe.has_value()){ + O_WARNING("Could not read clipboard!"); + return EXIT_FAILURE; + } + auto clipboard = maybe.value(); + O_DEBUG(clipboard.name.c_str() << QString::fromStdString(clipboard.to_string())); + O_DEBUG("Updating clipboard"); + if(!clipboard.set("Test")){ + O_WARNING("Could not update clipboard!"); + return EXIT_FAILURE; + } + O_DEBUG(clipboard.name.c_str() << QString::fromStdString(clipboard.to_string())); + } + auto connection = Blight::connection(); + if(connection == nullptr){ + O_WARNING(std::strerror(errno)); + return EXIT_FAILURE; + } + connection->onDisconnect([](int res){ + O_DEBUG("Connection closed:" << res); + if(res){ + std::exit(res); + } + }); + O_DEBUG("Connection socket descriptor:" << connection->handle()); + O_DEBUG("Input socket descriptor:" << connection->input_handle()); + QRect geometry(0, 0, 100, 100); + QImage blankImage(geometry.size(), QImage::Format_RGB16); + blankImage.fill(Qt::black); + auto maybe = Blight::createBuffer( + geometry.x(), + geometry.y(), + geometry.width(), + geometry.height(), + blankImage.bytesPerLine(), + Blight::Format::Format_RGB16 + ); + if(!maybe.has_value()){ + O_WARNING("Failed to create buffer:" << strerror(errno)); + return EXIT_FAILURE; + } + auto buffer = maybe.value(); + if(buffer->fd == -1 || buffer->data == nullptr){ + O_WARNING("Failed to create buffer:" << strerror(errno)); + return EXIT_FAILURE; + } + memcpy(buffer->data, blankImage.constBits(), buffer->size()); + QImage image( + buffer->data, + blankImage.width(), + blankImage.height(), + blankImage.bytesPerLine(), + blankImage.format() + ); + if(image.isNull()){ + O_WARNING("Null image"); + } + if(image.size() != geometry.size()){ + O_WARNING("Invalid size" << image.size()); + } + Blight::addSurface(buffer); + if(!buffer->surface){ + O_WARNING("No identifier provided"); + return EXIT_FAILURE; + } + O_DEBUG("Surface added:" << buffer->surface); + sleep(1); + O_DEBUG("Switching to gray"); + image.fill(Qt::gray); + O_DEBUG("Repainting" << buffer->surface); + { + auto ack = connection->repaint( + buffer, + 0, + 0, + geometry.width(), + geometry.height() + ); + if(!ack.has_value()){ + O_WARNING("Failed to repaint"); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Repainting done"); + sleep(1); + O_DEBUG("Moving " << buffer->surface); + geometry.setTopLeft(QPoint(100, 100)); + connection->move(buffer, geometry.x(), geometry.y()); + O_DEBUG("Move done" << buffer->surface); + sleep(1); + O_DEBUG("Switching to black"); + image.fill(Qt::black); + O_DEBUG("Repainting" << buffer->surface); + { + auto ack = connection->repaint( + buffer, + 0, + 0, + geometry.width(), + geometry.height() + ); + if(!ack.has_value()){ + O_WARNING("Failed to repaint"); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Repainting done"); + sleep(1); + O_DEBUG("Resizing" << buffer->surface); + geometry.setSize(QSize(300, 300)); + auto resizedImage = image.scaled(geometry.size()); + auto res = connection->resize( + buffer, + geometry.width(), + geometry.height(), + resizedImage.bytesPerLine(), + (Blight::data_t)resizedImage.constBits() + ); + if(res.has_value()){ + buffer = res.value(); + } + O_DEBUG("Resize done" << buffer->surface); + sleep(1); + O_DEBUG("Lowering" << buffer->surface); + { + auto ack = connection->lower(buffer); + if(!ack.has_value()){ + O_WARNING("Could not lower surface!"); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Lowering done" << buffer->surface); + sleep(1); + O_DEBUG("Raising" << buffer->surface); + { + auto ack = connection->raise(buffer); + if(!ack.has_value()){ + O_WARNING("Could not raise surface!"); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Raising done" << buffer->surface); + sleep(1); + O_DEBUG("Adding transparent hole"); + image = QImage( + buffer->data, + resizedImage.width(), + resizedImage.height(), + resizedImage.bytesPerLine(), + (QImage::Format)buffer->format + ); + QPainter p(&image); + p.fillRect(QRect(10, 10, 50, 50), Qt::transparent); + p.end(); + O_DEBUG("Repainting" << buffer->surface); + { + auto ack = connection->repaint( + buffer, + 0, + 0, + geometry.width(), + geometry.height() + ); + if(!ack.has_value()){ + O_WARNING("Failed to repaint"); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Repainting done"); + sleep(1); + O_DEBUG("Validating surface count"); + auto surfaces = connection->surfaces(); + if(surfaces.size() != 1){ + O_WARNING("There should only be one surface!" << surfaces.size()); + return EXIT_FAILURE; + } + if(surfaces.front() != buffer->surface){ + O_WARNING("Surface identifier does not match!" << surfaces.front()); + return EXIT_FAILURE; + } + auto buffers = connection->buffers(); + if(buffers.size() != 1){ + O_WARNING("There should only be one buffer!" << buffers.size()); + return EXIT_FAILURE; + } + if(buffers.front()->surface != buffer->surface){ + O_WARNING("Buffer surface identifier does not match!" << buffers.front()->surface); + return EXIT_FAILURE; + } + O_DEBUG("Removing surface"); + { + auto ack = connection->remove(buffer); + if(!ack.has_value()){ + O_WARNING("Failed to remove buffer:" << std::strerror(errno)); + return EXIT_FAILURE; + } + ack.value()->wait(); + } + O_DEBUG("Surface removed"); + if(!connection->surfaces().empty()){ + O_WARNING("There are still surfaces even though it was removed!"); + return EXIT_FAILURE; + } + O_DEBUG("Test passes"); + } + QQmlApplicationEngine engine; + registerQML(&engine); + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + if(engine.rootObjects().isEmpty()){ + O_WARNING("Nothing to display"); + return EXIT_FAILURE; + } + engine.load(QUrl(QStringLiteral("qrc:/notification.qml"))); + auto notification = static_cast(engine.rootObjects().last()); + notification->setProperty("WA_WAVEFORM", Blight::WaveformMode::Mono); + notification->setProperty("text", "Testing"); + notification->setProperty("image", QUrl::fromLocalFile("/opt/usr/share/icons/oxide/702x702/splash/oxide.png")); + notification->setProperty("notificationVisible", true); + notification->raise(); + return app.exec(); +} diff --git a/tests/test_app/main.qml b/tests/test_app/main.qml new file mode 100644 index 000000000..c41a56a0d --- /dev/null +++ b/tests/test_app/main.qml @@ -0,0 +1,123 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.0 +import codes.eeems.oxide 3.0 +import "qrc:/codes.eeems.oxide" + +OxideWindow{ + id: window + objectName: "window" + visible: true + focus: true + Shortcut{ + sequences: [StandardKey.Quit, StandardKey.Cancel, "Backspace", "Ctrl+Q", "Ctrl+W"] + context: Qt.ApplicationShortcut + autoRepeat: false + onActivated: Qt.quit() + } + leftMenu: [ + Button{ + text: "Click to Quit" + onClicked: Qt.quit() + } + ] + centerMenu: [ + Label{ + color: "white" + text: "Hello World!" + } + + ] + rightMenu: [ + Label{ + color: "white" + text: "Tap to Quit" + Layout.leftMargin: 10 + Layout.rightMargin: 10 + TapHandler { + onTapped: Qt.quit() + } + } + ] + initialItem: Item{ + anchors.fill: parent + focus: true + Rectangle{ + color: "white" + anchors.fill: parent + } + component MyButton: Button{ + id: control + contentItem: Text { + text: control.text + font: control.font + color: control.enabled ? "black" : "white" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + background: Rectangle{ + color: control.enabled ? "white" : "black" + border.color: "black" + border.width: 1 + radius: 2 + } + } + RowLayout{ + id: buttons + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + MyButton{ + text: "Black Pen" + enabled: canvas.brush !== Oxide.brushFromColor("black") + onClicked: canvas.brush = Oxide.brushFromColor("black") + } + MyButton{ + text: "White Pen" + enabled: canvas.brush !== Oxide.brushFromColor("white") + onClicked: canvas.brush = Oxide.brushFromColor("white") + } + Item{ + Layout.fillWidth: true + } + MyButton{ + text: "3px" + enabled: canvas.penWidth !== 3 + onClicked: canvas.penWidth = 3 + } + MyButton{ + text: "6px" + enabled: canvas.penWidth !== 6 + onClicked: canvas.penWidth = 6 + } + MyButton{ + text: "12px" + enabled: canvas.penWidth !== 12 + onClicked: canvas.penWidth = 12 + } + MyButton{ + text: "24px" + enabled: canvas.penWidth !== 24 + onClicked: canvas.penWidth = 24 + } + } + Label{ + id: message + text: "Ctrl-Q, Ctrl-W, Backspace, or Escape to quit" + color: "black" + anchors.centerIn: parent + } + Canvas{ + id: canvas + anchors.left: parent.left + anchors.right: parent.right + anchors.top: buttons.bottom + anchors.bottom: parent.bottom + onDrawStart: message.visible = false + onDrawDone: message.visible = true + } + } +} diff --git a/tests/test_app/notification.qml b/tests/test_app/notification.qml new file mode 100644 index 000000000..c8aefe759 --- /dev/null +++ b/tests/test_app/notification.qml @@ -0,0 +1,70 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import "qrc:/codes.eeems.oxide" + +Window{ + property alias text: text.text + property alias image: image.source + property alias notificationVisible: notification.visible + property bool landscape: Oxide.landscape + + id: window + objectName: "window" + flags: Qt.FramelessWindowHint + visible: true + x: landscape ? 0 : Screen.width - width - background.border.width + y: Screen.height - height - background.border.width + width: 500 + height: 500 + function orientationWidth(){ return landscape ? height : width; } + function orientationHeight(){ return landscape ? width : height; } + contentOrientation: landscape ? Qt.LandscapeOrientation : Qt.PortraitOrientation + color: "transparent" + + Item{ + id: notification + // Must centerIn and specify width/height to force rotation to actually work + width: window.orientationWidth() + height: window.orientationHeight() + anchors.centerIn: parent + rotation: landscape ? 90 : 0 + clip: true + + Rectangle{ + id: background + color: "white" + border.color: "black" + border.width: 2 + radius: 10 + anchors.fill: rowLayout + } + RowLayout{ + id: rowLayout + width: parent.width + anchors.right: parent.right + anchors.bottom: parent.bottom + Image{ + id: image + visible: status === Image.Ready + Layout.preferredHeight: 50 + Layout.preferredWidth: 50 + Layout.alignment: Qt.AlignCenter + Layout.leftMargin: 10 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + } + Label{ + id: text + visible: this.text.length + color: "black" + wrapMode: Text.WordWrap + Layout.margins: 10 + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } +} diff --git a/tests/test_app/qml.qrc b/tests/test_app/qml.qrc new file mode 100644 index 000000000..edbd23c23 --- /dev/null +++ b/tests/test_app/qml.qrc @@ -0,0 +1,6 @@ + + + main.qml + notification.qml + + diff --git a/tests/test_app/test_app.pro b/tests/test_app/test_app.pro new file mode 100644 index 000000000..1b30afd4b --- /dev/null +++ b/tests/test_app/test_app.pro @@ -0,0 +1,27 @@ +QT += gui +QT += quick +QT += dbus + +CONFIG += c++11 +CONFIG += qml_debug +CONFIG += qtquickcompiler + +SOURCES += \ + main.cpp + +TARGET = test_app +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +applications.files = ../../assets/opt/usr/share/applications/test_app.oxide +applications.path = /opt/usr/share/applications/ +INSTALLS += applications + +RESOURCES += \ + qml.qrc + +include(../../qmake/liboxide.pri) +include(../../qmake/libblight.pri) + +HEADERS += diff --git a/tests/tests.pro b/tests/tests.pro index 5d93081a3..6ad363b71 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -1,5 +1,8 @@ TEMPLATE = subdirs -SUBDIRS = +SUBDIRS = \ + libblight \ + liboxide \ + test_app INSTALLS += $$SUBDIRS diff --git a/web/src/documentation/01_usage.rst b/web/src/documentation/01_usage.rst index 845527052..3a3dbe935 100644 --- a/web/src/documentation/01_usage.rst +++ b/web/src/documentation/01_usage.rst @@ -238,6 +238,7 @@ ____ system screen notification + compositor name Signal to listen to. arguments Arguments to pass to the method using the following format: :. e.g. QString:Test @@ -318,3 +319,17 @@ These examples assume you have `jq` installed. # Disable telemetry rot settings set telemetry false + +.. _blight_client: + +Display Server Client (blight-client) +===================================== + +When running an application from the command line, you can force it to use the display server by running it with ``blight-client``. The client supports configuration with the following environment variables: + +- ``OXIDE_PRELOAD_FORCE_QT`` Force a Qt application to use the Oxide QPA. +- ``OXIDE_PRELOAD_FORCE_RM1`` Force an application to believe it is running on a reMarkable 1 tablet. +- ``OXIDE_PRELOAD_EXPOSE_FB`` If set, the framebuffer will be directly exposed to the application. It is not recommended to use this, as the display server is not expecting applications to have direct access outside of exclusive mode. +- ``OXIDE_INPUT_BATCH_SIZE`` Number of input events without a EV_SYN SYN_REPORT to queue before flushing them through to the application. Defaults to 16 +- ``OXIDE_PRELOAD_DEBUG`` Debug level. Defaults to 4. +- ``OXIDE_PRELOAD_DISABLE_INPUT`` Disable input handling. diff --git a/web/src/documentation/02_oxide-utils.rst b/web/src/documentation/02_oxide-utils.rst index 6dbd9663c..e7e034eb9 100644 --- a/web/src/documentation/02_oxide-utils.rst +++ b/web/src/documentation/02_oxide-utils.rst @@ -16,6 +16,11 @@ update-desktop-database https://man.archlinux.org/man/update-desktop-database.1.en +xclip +===== + +https://man.archlinux.org/man/xclip.1.en + xdg-desktop-menu ================ @@ -77,3 +82,9 @@ Example usage: EV_SYN SYN_MT_REPORT EV_SYN SYN_REPORT EOF + + +fbinfo +====== + +This application will output information about the framebuffer in a human readable format. diff --git a/web/src/documentation/03_application_registration_format.rst b/web/src/documentation/03_application_registration_format.rst index cbefeabfa..844d18bca 100644 --- a/web/src/documentation/03_application_registration_format.rst +++ b/web/src/documentation/03_application_registration_format.rst @@ -34,204 +34,118 @@ Below is an example file: Properties ========== -+------------------+--------------+----------+-----------------------+ -| Property | Type | Required | Description | -+==================+==============+==========+=======================+ -| bin | string | Yes | The path to the | -| | | | application. If | -| | | | this file doesn't | -| | | | exist, the | -| | | | application will | -| | | | not be registered. | -+------------------+--------------+----------+-----------------------+ -| type | string | Yes | The type of | -| | | | application this | -| | | | is. | -| | | | | -| | | | ``"background"``: | -| | | | This application | -| | | | runs in the | -| | | | background and | -| | | | does not display | -| | | | anything on the | -| | | | screen. | -| | | | | -| | | | ``"foreground"``: | -| | | | This application | -| | | | runs in the | -| | | | foreground and | -| | | | must be paused | -| | | | when it's not the | -| | | | active | -| | | | application. | -| | | | | -| | | | ``"backgroundable"``: | -| | | | This application | -| | | | runs in both the | -| | | | foreground and the | -| | | | background and | -| | | | supports being | -| | | | notified with | -| | | | ``SIGUSR1`` and | -| | | | ``SIGUSR2`` when | -| | | | being swapped | -| | | | between the two | -| | | | states by the | -| | | | user. | -+------------------+--------------+----------+-----------------------+ -| flags | string array | No | An array of flags | -| | | | for the | -| | | | application. | -| | | | | -| | | | ``"hidden"``: | -| | | | Do not display | -| | | | this application | -| | | | to the | -| | | | user. | -| | | | | -| | | | ``"autoStart"``: | -| | | | Start this | -| | | | application | -| | | | automatically when | -| | | | tarnish starts up. | -| | | | Should only be | -| | | | used on | -| | | | applications with | -| | | | a type of | -| | | | ``"backgroundable"`` | -| | | | or | -| | | | ``"background"``. | -| | | | | -| | | | ``"chroot"``: | -| | | | Run this | -| | | | application in a | -| | | | chroot. | -| | | | | -| | | | ``"system"``: | -| | | | This is a system | -| | | | application and | -| | | | cannot be removed | -| | | | by the user. This | -| | | | is set by default | -| | | | as applications | -| | | | using an | -| | | | application | -| | | | registration file | -| | | | are intended to be | -| | | | removed by a | -| | | | package manager. | -+------------------+--------------+----------+-----------------------+ -| displayName | string | No | The human readable | -| | | | name to display to | -| | | | the user. | -+------------------+--------------+----------+-----------------------+ -| description | string | No | Description of the | -| | | | app meant to be | -| | | | displayed to the | -| | | | user. | -+------------------+--------------+----------+-----------------------+ -| icon | string | No | Path to an image | -| | | | file to use as the | -| | | | icon for this | -| | | | application. Or an | -| | | | icon spec. | -+------------------+--------------+----------+-----------------------+ -| splash | string | No | Path to an image | -| | | | file to use as the | -| | | | splash screen for | -| | | | this application. Or | -| | | | an icon spec. | -+------------------+--------------+----------+-----------------------+ -| user | string | No | User to run this | -| | | | application as. | -| | | | Default is | -| | | | ``root``. | -+------------------+--------------+----------+-----------------------+ -| group | string | No | Group to run this | -| | | | application in. | -| | | | Default is | -| | | | ``root``. | -+------------------+--------------+----------+-----------------------+ -| workingDirectory | string | No | Directory to set | -| | | | as the current | -| | | | working directory | -| | | | of the | -| | | | application. | -| | | | Default is ``/``. | -+------------------+--------------+----------+-----------------------+ -| directories | string array | No | When an | -| | | | application is | -| | | | running in a | -| | | | chroot, also map | -| | | | these directories | -| | | | into the chroot | -| | | | with read/write | -| | | | privileges. | -+------------------+--------------+----------+-----------------------+ -| permissions | string array | No | API permissions to | -| | | | grant this | -| | | | application. | -| | | | Without these, any | -| | | | calls to the API | -| | | | will be refused or | -| | | | return incorrect | -| | | | results. | -| | | | | -| | | | ``"permissions"``: | -| | | | Application can | -| | | | modify | -| | | | applications | -| | | | se | -| | | | ttings/permissions | -| | | | in the Apps API. | -| | | | | -| | | | ``"apps"``: | -| | | | Apps API | -| | | | access | -| | | | | -| | | | ``"notification"``: | -| | | | Notification API | -| | | | access | -| | | | | -| | | | ``"power"``: | -| | | | Power API | -| | | | access | -| | | | | -| | | | ``"screen"``: | -| | | | Screen API | -| | | | access | -| | | | | -| | | | ``"system"``: | -| | | | System API | -| | | | access | -| | | | | -| | | | ``"wifi"``: | -| | | | Wifi API access | -+------------------+--------------+----------+-----------------------+ -| environment | string map | No | Extra environment | -| | | | variables to set | -| | | | before running the | -| | | | application. | -+------------------+--------------+----------+-----------------------+ -| events | string map | No | Custom commands to | -| | | | run on an | -| | | | event. | -| | | | | -| | | | ``"resume"``: | -| | | | Run when the | -| | | | application is | -| | | | resumed | -| | | | | -| | | | ``"pause"``: | -| | | | Run when the | -| | | | application is | -| | | | pause | -| | | | | -| | | | ``"stop"``: | -| | | | Run when the | -| | | | application is | -| | | | stopped | -+------------------+--------------+----------+-----------------------+ +bin +--- +Required string + +The path to the application. If this file doesn't exist, the application will not be registered. + +type +---- +Required string + +The type of application this is. + +- ``"background"``: This application runs in the background and does not display anything on the screen. +- ``"foreground"``: This application runs in the foreground and must be paused when it's not the active application. +- ``"backgroundable"``: This application runs in both the foreground and the background and supports being notified with ``SIGUSR1`` and ``SIGUSR2`` when being swapped between the two states by the user. + +flags +----- +Optional string array + +An array of flags for the application. + +- ``"hidden"``: Do not display this application to the user. +- ``"autoStart"``: Start this application automatically when tarnish starts up. Should only be used on applications with a type of ``"backgroundable"`` or ``"background"``. +- ``"system"``: This is a system application and cannot be removed by the user. This is set by default as applications using an application registration file are intended to be removed by a package manager. +- ``"nopreload"``: Do not automatically add any of the preload libraries to ``LD_PRELOAD`` when running this application. +- ``"nopreload.compositor"``: Do not automatically add ``libblight_client.so`` to ``LD_PRELOAD`` when running this application. +- ``"nopreload.sysfs"``: Do not automatically add ``libsysfs_preload.so`` to ``LD_PRELOAD`` when running this application. +- ``"exclusive"``: Application is to be run in exclusive mode. This means that the compositor will not be sending input to the application, or handling displaying anything to the screen while this application is in the foreground. + +displayName +----------- + +Optional string + +The human readable name to display to the user. + +description +----------- + +Optional string + +Description of the app meant to be displayed to the user. + +icon +---- + +Optional string + +Path to an image file to use as the icon for this application, or an :ref:`icon-spec`. + +user +---- + +Optional string + +User to run this application as. + +Default is ``root``. + +group +----- + +Optional string + +Group to run this application in. + +Default is ``root``. + +workingDirectory +---------------- + +Optional string + +Directory to set as the current working directory of the application. + +Default is ``/``. + +permissions +----------- + +Optional string array + +API permissions to grant this application. Without these, any calls to the API will be refused or return incorrect results. + +- ``"permissions"``: Application can modify applications settings/permissions in the Apps API. +- ``"apps"``: Apps API access +- ``"notification"``: Notification API access +- ``"power"``: Power API access +- ``"screen"``: Screen API access +- ``"system"``: System API access +- ``"wifi"``: Wifi API access + +environment +----------- + +Optional string map + +Extra environment variables to set before running the application. + +events +------ + +Optional string map + +Custom commands to run on an event. + +- ``"resume"``: Run when the application is resumed +- ``"pause"``: Run when the application is pause +- ``"stop"``: Run when the application is stopped + +.. _icon-spec: Icon Spec ========= @@ -240,7 +154,7 @@ Icon specifications can be in the following format: ``[theme:][context:]{name}-{ Some examples: -- ``oxide:splash:xochitl-702`` +- ``oxide:splash:oxide-702`` - ``oxide:apps:xochitl-48`` - ``oxide:xochitl-48`` - ``xochitl-48`` diff --git a/web/src/documentation/api/00_overview.rst b/web/src/documentation/api/00_overview.rst index d49d507e9..d151d1dd8 100644 --- a/web/src/documentation/api/00_overview.rst +++ b/web/src/documentation/api/00_overview.rst @@ -40,3 +40,33 @@ Oxide also produces a shared library that can be used for application developmen not yet fully encompass the full API provide through D-Bus, but will slowly work towards feature parity. `You can find the documentation here <../../liboxide/index.html>`__ + + +Libblight Shared Library +======================== + +Oxide provides a shared library for interacting with the display server. + +`You can find the documentation here <../../libblight/index.html>`__ + +Oxide Qt Platform Abstraction (QPA) +=================================== + +Oxide ships a QPA for Qt applications to use. This will automatically use the display server for updating the screen, as well as user input. + +If you are using liboxide, a call to `deviceSettings.setupQtEnvironment()` will setup the application to use the Oxide QPA. To manualy use the QPA, you will need to use the following code: + +.. code:: cpp + + void setup(){ + QCoreApplication::addLibraryPath("/opt/usr/lib/plugins"); + qputenv("QMLSCENE_DEVICE", "software"); + qputenv("QT_QUICK_BACKEND","software"); + QString platform( + "oxide:enable_fonts:freetype:freetype:rotate=180" + ); + if(is_on_rm2){ + platform += ":invertx" + } + qputenv("QT_QPA_PLATFORM", platform); + } diff --git a/web/src/documentation/api/01_general.rst b/web/src/documentation/api/01_general.rst index b4e7f3774..83e36d06f 100644 --- a/web/src/documentation/api/01_general.rst +++ b/web/src/documentation/api/01_general.rst @@ -16,13 +16,14 @@ all usage of the API away. | | | ``SIGUSR2`` signals. | +----------------+-------------------------+-------------------------+ | apiAvailable | signal | Signal sent when an API | -| | - (out) api | has been successfully | -| | ``OBJECT_PATH`` | requested by something. | +| | | has been successfully | +| | - (out) api | requested by something. | +| | ``OBJECT_PATH`` | | +----------------+-------------------------+-------------------------+ | apiUnavailable | signal | Signal sent when an API | -| | - (out) api | is no longer available. | -| | ``OBJECT_PATH`` | This can happen for one | -| | | of the following | +| | | is no longer available. | +| | - (out) api | This can happen for one | +| | ``OBJECT_PATH`` | of the following | | | | reasons: | | | | 1. All applications | | | | that have requested the | @@ -39,9 +40,9 @@ all usage of the API away. | | | to gracefully exit. | +----------------+-------------------------+-------------------------+ | requestAPI | method | Request access to an | -| | - (in) name ``STRING`` | API. | -| | - (out) ``OBJECT_PATH`` | Refused requests will | -| | | return an | +| | | API. | +| | - (in) name ``STRING`` | Refused requests will | +| | - (out) ``OBJECT_PATH`` | return an | | | | ``OBJECT_PATH`` of | | | | ``/``. | | | | Successful requests | @@ -51,15 +52,15 @@ all usage of the API away. | | | API. | +----------------+-------------------------+-------------------------+ | releaseAPI | method | Release your request | -| | - (in) name ``STRING`` | for access to an API. | -| | | This allows Tarnish to | +| | | for access to an API. | +| | - (in) name ``STRING`` | This allows Tarnish to | | | | unregister the API if | | | | nothing is using it. | +----------------+-------------------------+-------------------------+ | APIs | method | Request the list of | -| | - (out) | available APIs on the | -| | ``ARRA | system. | -| | Y{STRING OBJECT_PATH}`` | | +| | | available APIs on the | +| | - (out) ``ARRAY{ | system. | +| | STRING OBJECT_PATH}`` | | +----------------+-------------------------+-------------------------+ .. _example-usage-1: @@ -69,9 +70,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "systemapi_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/02_apps.rst b/web/src/documentation/api/02_apps.rst index d70627d26..c8651a8ac 100644 --- a/web/src/documentation/api/02_apps.rst +++ b/web/src/documentation/api/02_apps.rst @@ -47,30 +47,30 @@ Apps API | | property (read) | applications. | +-------------------------+-------------------------------+----------------------+ | applicationRegistered | signal | Signal sent when a | -| | - (out) | new application is | -| | ``OBJECT_PATH`` | registered with | +| | | new application is | +| | - (out) ``OBJECT_PATH`` | registered with | | | | tarnish. | +-------------------------+-------------------------------+----------------------+ | applicationLaunched | signal | Signal sent when an | -| | - (out) | application has been | -| | ``OBJECT_PATH`` | launched. | +| | | application has been | +| | - (out) ``OBJECT_PATH`` | launched. | +-------------------------+-------------------------------+----------------------+ | applicationUnregistered | signal | Signal sent when an | -| | - (out) | application has been | -| | ``OBJECT_PATH`` | removed from | +| | | application has been | +| | - (out) ``OBJECT_PATH`` | removed from | | | | tarnish. | +-------------------------+-------------------------------+----------------------+ | applicationPaused | signal | Signal sent when an | -| | - (out) | application has been | -| | ``OBJECT_PATH`` | paused. | +| | | application has been | +| | - (out) ``OBJECT_PATH`` | paused. | +-------------------------+-------------------------------+----------------------+ | applicationResumed | signal | Signal sent when an | -| | - (out) | application has been | -| | ``OBJECT_PATH`` | resumed. | +| | | application has been | +| | - (out) ``OBJECT_PATH`` | resumed. | +-------------------------+-------------------------------+----------------------+ | applicationExited | signal | Signal sent when an | -| | - (out) | application has | -| | ``OBJECT_PATH`` | exited. | +| | | application has | +| | - (out) ``OBJECT_PATH`` | exited. | | | - (out) ``INT32`` | The second output | | | | parameter contains | | | | the exit code. | @@ -91,31 +91,31 @@ Apps API | | | kscreenApplication`` | +-------------------------+-------------------------------+----------------------+ | registerApplication | method | Register a new | -| | - (in) properties | application with | -| | ``ARRAY{STRING VARIANT}`` | tarnish. | -| | - (out) | | -| | ``OBJECT_PATH`` | | +| | | application with | +| | - (in) properties | tarnish. | +| | ``ARRAY{STRING VARIANT}`` | | +| | - (out) ``OBJECT_PATH`` | | | | | | +-------------------------+-------------------------------+----------------------+ | unregisterApplication | method | Remove an | -| | - (in) path | application from | -| | ``OBJECT_PATH`` | tarnish. | +| | | application from | +| | - (out) ``OBJECT_PATH`` | tarnish. | +-------------------------+-------------------------------+----------------------+ | reload | method | Reload applications | | | | registered with | | | | tarnish from disk. | +-------------------------+-------------------------------+----------------------+ | getApplicationPath | method | Returns the D-Bus | -| | - (in) name | path for an | -| | ``STRING`` | application based on | -| | - (out) | it's name. | -| | ``OBJECT_PATH`` | Will return ``/`` if | +| | | path for an | +| | - (in) name ``STRING`` | application based on | +| | - (out) ``OBJECT_PATH`` | it's name. | +| | | Will return ``/`` if | | | | the application does | | | | not exist. | +-------------------------+-------------------------------+----------------------+ | previousApplication | method | Launch or resume the | -| | - (out) ``BOOLEAN`` | previous application | -| | | from | +| | | previous application | +| | - (out) ``BOOLEAN`` | from | | | | ``pre | | | | viousApplications``. | | | | Will also remove the | @@ -130,9 +130,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "appsapi_interface.h" + #include using namespace codes::eeems::oxide1; @@ -255,10 +253,6 @@ Application Object | | | directory for the | | | | application. | +----------------------+----------------------+----------------------+ -| chroot | ``BOOLEAN`` property | If this application | -| | (read) | should be run in a | -| | | chroot or not. | -+----------------------+----------------------+----------------------+ | user | ``STRING`` property | User the application | | | (read) | will be run as. | +----------------------+----------------------+----------------------+ @@ -266,10 +260,6 @@ Application Object | | (read) | application will be | | | | run as. | +----------------------+----------------------+----------------------+ -| directories | ``ARRAY STRING`` | Directories mapped | -| | property | into the chroot as | -| | (read/write) | read/write. | -+----------------------+----------------------+----------------------+ | launched | signal | Signal sent when the | | | | application starts. | +----------------------+----------------------+----------------------+ @@ -287,52 +277,55 @@ Application Object | | | tarnish. | +----------------------+----------------------+----------------------+ | exited | signal | Signal sent when the | -| | - (out) ``INT32`` | application exits. | -| | | First signal | +| | | application exits. | +| | - (out) ``INT32`` | First signal | | | | parameter is the | | | | exit code of the | | | | application. | +----------------------+----------------------+----------------------+ | permissionsChanged | signal | Signal sent when the | -| | - (out) | permissions of the | -| | ``ARRAY STRING`` | application changes. | +| | | permissions of the | +| | - (out) | application changes. | +| | ``ARRAY STRING`` | | +----------------------+----------------------+----------------------+ | displayNameChanged | signal | Signal sent when the | -| | - (out) ``STRING`` | displayName of the | -| | | application changes. | +| | | displayName of the | +| | - (out) ``STRING`` | application changes. | +----------------------+----------------------+----------------------+ | onPauseChanged | signal | Signal sent when the | -| | - (out) ``STRING`` | onPause of the | -| | | application changes. | +| | | onPause of the | +| | - (out) ``STRING`` | application changes. | +----------------------+----------------------+----------------------+ | onResumeChanged | signal | Signal sent when the | -| | - (out) ``STRING`` | onResume of the | -| | | application changes. | +| | | onResume of the | +| | - (out) ``STRING`` | application changes. | +----------------------+----------------------+----------------------+ | onStopChanged | signal | Signal sent when the | | | - (out) ``STRING`` | onStop of the | | | | application changes. | +----------------------+----------------------+----------------------+ | autoStartChanged | signal | Signal sent when | -| | - (out) ``BOOLEAN`` | autoStart for the | -| | | application chagnes. | +| | | autoStart for the | +| | - (out) ``BOOLEAN`` | application chagnes. | +----------------------+----------------------+----------------------+ | iconChanged | signal | Signal sent when the | -| | - (out) ``STRING`` | icon of the | -| | | application changes. | +| | | icon of the | +| | - (out) ``STRING`` | application changes. | +----------------------+----------------------+----------------------+ | environmentChanged | signal | Signal sent when the | -| | - (out) | environment of the | -| | ``ARRAY STRING`` | application changes. | +| | | environment of the | +| | - (out) | application changes. | +| | ``ARRAY STRING`` | | +----------------------+----------------------+----------------------+ | wor | signal | Signal sent when the | -| kingDirectoryChanged | - (out) ``STRING`` | working directory of | -| | | the application | +| kingDirectoryChanged | | working directory of | +| | - (out) ``STRING`` | the application | | | | changes. | +----------------------+----------------------+----------------------+ | directoriesChanged | signal | Signal sent when the | -| | - (out) | directories of the | -| | ``ARRAY STRING`` | application changes. | +| | | directories of the | +| | - (out) | application changes. | +| | ``ARRAY STRING`` | | +----------------------+----------------------+----------------------+ | launch | method | Launch or resume the | | | | application. | @@ -361,10 +354,10 @@ Application Object | | | tarnish. | +----------------------+----------------------+----------------------+ | setEnvironment | method | Change the | -| | - (in) environment | environment of the | -| | ``ARR | application. | -| | AY{STRING VARIANT}`` | Changes will be | -| | | applied after the | +| | | environment of the | +| | - (in) environment | application. | +| | ``ARR | Changes will be | +| | AY{STRING VARIANT}`` | applied after the | | | | application | | | | restarts. | +----------------------+----------------------+----------------------+ @@ -376,10 +369,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "appsapi_interface.h" - #include "application_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/02_notification.rst b/web/src/documentation/api/02_notification.rst index fbe0d7fd9..89a3b274b 100644 --- a/web/src/documentation/api/02_notification.rst +++ b/web/src/documentation/api/02_notification.rst @@ -16,16 +16,19 @@ Notification API | | | longer running. | +----------------------+----------------------+----------------------+ | notificationAdded | signal | Signal sent when a | -| | - (out) | notification has | -| | ``OBJECT_PATH`` | been added. | +| | | notification has | +| | - (out) | been added. | +| | ``OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ | notificationRemoved | signal | Signal sent when a | -| | - (out) | notification has | -| | ``OBJECT_PATH`` | been removed. | +| | | notification has | +| | - (out) | been removed. | +| | ``OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ | notificationChanged | signal | Signal sent when a | -| | - (out) | notification has | -| | ``OBJECT_PATH`` | been modified. | +| | | notification has | +| | - (out) | been modified. | +| | ``OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ | add | method | Add a new | | | - (in) identifier | notification. | @@ -40,19 +43,22 @@ Notification API | | ``OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ | take | method | Take ownership of a | -| | - (in) identifier | notification. | +| | | notification. | +| | - (in) identifier | | | | ``STRING`` | | | | - (out) ``BOOLEAN`` | | +----------------------+----------------------+----------------------+ | notifications | method | List of all | | | - (out) | notifications owned | -| | ` | by the current | -| | `ARRAY OBJECT_PATH`` | application. | +| | - (out) | by the current | +| | ` | application. | +| | `ARRAY OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ | get | method | Get the object path | -| | - (in) identifier | for a notification | -| | ``STRING`` | based on it's | -| | - (out) | identifier. | +| | | for a notification | +| | - (in) identifier | based on it's | +| | ``STRING`` | identifier. | +| | - (out) | | | | ``OBJECT_PATH`` | | +----------------------+----------------------+----------------------+ @@ -63,10 +69,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "notificationapi_interface.h" - #include "notification_interface.h" + #include using namespace codes::eeems::oxide1; @@ -115,10 +118,10 @@ Notification Object | | (read/write) | notification. | +-------------+--------------------------+--------------------------+ | changed | signal | Signal sent when | -| | - (out) | something on the | -| | ` | notification has | -| | `ARRAY{STRING VARIANT}`` | changed. | -| | | The first output | +| | | something on the | +| | - (out) | notification has | +| | ` | changed. | +| | `ARRAY{STRING VARIANT}`` | The first output | | | | property contains a map | | | | of changed properties | | | | and their values. | @@ -159,10 +162,7 @@ Example Usage .. code:: cpp #include - #include - #include "dbusservice_interface.h" - #include "notificationapi_interface.h" - #include "notification_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/02_power.rst b/web/src/documentation/api/02_power.rst index 5ce80023e..e7eeb32ac 100644 --- a/web/src/documentation/api/02_power.rst +++ b/web/src/documentation/api/02_power.rst @@ -33,24 +33,24 @@ Power API | | | Connected | +----------------------+----------------------+----------------------+ | stateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | requested power | -| | | state has changed. | +| | | requested power | +| | - (out) ``INT32`` | state has changed. | +----------------------+----------------------+----------------------+ | batteryStateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | battery state has | -| | | changed. | +| | | battery state has | +| | - (out) ``INT32`` | changed. | +----------------------+----------------------+----------------------+ | batteryLevelChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | battery level has | -| | | changed. | +| | | battery level has | +| | - (out) ``INT32`` | changed. | +----------------------+----------------------+----------------------+ | batte | signal | Signal sent when the | -| ryTemperatureChanged | - (out) ``INT32`` | battery temperature | -| | | has changed. | +| ryTemperatureChanged | | battery temperature | +| | - (out) ``INT32`` | has changed. | +----------------------+----------------------+----------------------+ | chargerStateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | charger state has | -| | | changed. | +| | | charger state has | +| | - (out) ``INT32`` | changed. | +----------------------+----------------------+----------------------+ | batteryWarning | signal | Signal sent when a | | | | battery warning has | @@ -72,9 +72,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "powerapi_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/02_screenshot.rst b/web/src/documentation/api/02_screenshot.rst index c231f914b..4e51a63fd 100644 --- a/web/src/documentation/api/02_screenshot.rst +++ b/web/src/documentation/api/02_screenshot.rst @@ -10,29 +10,29 @@ Screen API | | property (read) | device. | +---------------------+----------------------+----------------------+ | screenshotAdded | signal | Signal sent when a | -| | - (out) | screenshot is added. | +| | | screenshot is added. | +| | - (out) | | | | ``OBJECT_PATH`` | | +---------------------+----------------------+----------------------+ | screenshotRemoved | signal | Signal sent when a | -| | - (out) | screenshot is | -| | ``OBJECT_PATH`` | removed. | +| | | screenshot is | +| | - (out) | removed. | +| | ``OBJECT_PATH`` | | +---------------------+----------------------+----------------------+ | screenshotModified | signal | Signal sent when a | -| | - (out) | screenshot is | -| | ``OBJECT_PATH`` | modified. | +| | | screenshot is | +| | - (out) | modified. | +| | ``OBJECT_PATH`` | | +---------------------+----------------------+----------------------+ | addScreenshot | method | Add a screenshot | -| | - (in) blob | taken by an | -| | ``ARRAY BYTE`` | application. | +| | | taken by an | +| | - (in) blob | application. | +| | ``ARRAY BYTE`` | | | | - (out) | | | | ``OBJECT_PATH`` | | +---------------------+----------------------+----------------------+ -| drawFullScreenImage | method | Draw an image to the | -| | - (in) path | screen. | -| | ``STRING`` | | -| | - (out) ``BOOLEAN`` | | -+---------------------+----------------------+----------------------+ | screenshot | method | Take a screenshot. | +| | | | | | - (out) | | | | ``OBJECT_PATH`` | | +---------------------+----------------------+----------------------+ @@ -44,9 +44,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "screenapi_interface.h" + #include using namespace codes::eeems::oxide1; @@ -113,10 +111,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "screenapi_interface.h" - #include "screenshot_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/05_system.rst b/web/src/documentation/api/05_system.rst index 0acbc0e60..d4b40a5e6 100644 --- a/web/src/documentation/api/05_system.rst +++ b/web/src/documentation/api/05_system.rst @@ -19,34 +19,17 @@ System API | powerOffInhibited | ``BOOLEAN`` property | If power off is | | | (read) | inhibited or not. | +----------------------+----------------------+----------------------+ -| leftAction | signal | Signal sent when a | -| | | long press on the | -| | | left button happens. | -+----------------------+----------------------+----------------------+ -| homeAction | signal | Signal sent when a | -| | | long press on the | -| | | home button happens. | -+----------------------+----------------------+----------------------+ -| rightAction | signal | Signal sent when a | -| | | long press on the | -| | | right button | -| | | happens. | -+----------------------+----------------------+----------------------+ -| powerAction | signal | Signal sent when a | -| | | long press on the | -| | | right button | -| | | happens. | -+----------------------+----------------------+----------------------+ | s | signal | Signal sent when | -| leepInhibitedChanged | - (out) ``BOOLEAN`` | sleepInhibited | -| | | changes. | +| leepInhibitedChanged | | sleepInhibited | +| | - (out) ``BOOLEAN`` | changes. | +----------------------+----------------------+----------------------+ | powe | signal | Signal sent when | -| rOffInhibitedChanged | - (out) ``BOOLEAN`` | powerOffInhibited | -| | | changes. | +| rOffInhibitedChanged | | powerOffInhibited | +| | - (out) ``BOOLEAN`` | changes. | +----------------------+----------------------+----------------------+ | autoSleepChanged | signal | Signal sent when | -| | - (out) ``BOOLEAN`` | autoSleep changes. | +| | | autoSleep changes. | +| | - (out) ``BOOLEAN`` | | +----------------------+----------------------+----------------------+ | deviceSuspending | signal | Signal sent when | | | | system is entering a | @@ -111,9 +94,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "systemapi_interface.h" + #include using namespace codes::eeems::oxide1; diff --git a/web/src/documentation/api/06_wifi.rst b/web/src/documentation/api/06_wifi.rst index 54b6ec0c6..8fe2fd0e4 100644 --- a/web/src/documentation/api/06_wifi.rst +++ b/web/src/documentation/api/06_wifi.rst @@ -42,23 +42,27 @@ context here: https://w1.fi/wpa_supplicant/devel/dbus.html | | | networks. | +------------------+------------------------+------------------------+ | stateChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | wifi state changes. | +| | | wifi state changes. | +| | - (out) ``INT32`` | | +------------------+------------------------+------------------------+ | linkChanged | signal | Signal sent when the | -| | - (out) ``INT32`` | wifi link quality | -| | | changes. | +| | | wifi link quality | +| | - (out) ``INT32`` | changes. | +------------------+------------------------+------------------------+ | networkAdded | signal | Signal sent when a | -| | - (out) | Network is added. | +| | | Network is added. | +| | - (out) | | | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | networkRemoved | signal | Signal sent when a | -| | - (out) | Network is removed. | +| | | Network is removed. | +| | - (out) | | | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | networkConnected | signal | Signal sent when a | -| | - (out) | Network has been | -| | ``OBJECT_PATH`` | connected to. | +| | | Network has been | +| | - (out) | connected to. | +| | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | disconnected | signal | Signal sent when the | | | | device is disconnected | @@ -70,21 +74,23 @@ context here: https://w1.fi/wpa_supplicant/devel/dbus.html | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | bssRemoved | signal | Signal sent when a BSS | -| | - (out) | expires from the | -| | ``OBJECT_PATH`` | cache. | +| | | expires from the | +| | - (out) | cache. | +| | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | scanningChanged | signal | Signal sent when | -| | - (out) ``BOOLEAN`` | scanning state | -| | | changes. | +| | | scanning state | +| | - (out) ``BOOLEAN`` | changes. | +------------------+------------------------+------------------------+ | enable | method | Enable wifi. | -| | - (out) ``BOOLEAN`` | Will return if it was | -| | | successful or not. | +| | | Will return if it was | +| | - (out) ``BOOLEAN`` | successful or not. | +------------------+------------------------+------------------------+ | disable | method | Disable wifi. | +------------------+------------------------+------------------------+ | addNetwork | method | Add a Network that can | -| | - (in) ssid ``STRING`` | be connected to. | +| | | be connected to. | +| | - (in) ssid ``STRING`` | | | | - (in) properties | | | | ``A | | | | RRAY{STRING VARIANT}`` | | @@ -92,23 +98,25 @@ context here: https://w1.fi/wpa_supplicant/devel/dbus.html | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | getNetwork | method | Get a Network that | -| | - (in) properties | matches all the | -| | ``A | supplied properties. | +| | | matches all the | +| | - (in) properties | supplied properties. | +| | ``A | | | | RRAY{STRING VARIANT}`` | | | | - (out) | | | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | getBSS | method | Get a BSS that matches | -| | - (in) properties | all the supplied | -| | ``A | properties. | +| | | all the supplied | +| | - (in) properties | properties. | +| | ``A | | | | RRAY{STRING VARIANT}`` | | | | - (out) | | | | ``OBJECT_PATH`` | | +------------------+------------------------+------------------------+ | scan | method | Scan for networks. | -| | - (in) active | If the first argument | -| | ``BOOLEAN`` | is ``true``, this will | -| | | be an active scan. | +| | | If the first argument | +| | - (in) active | is ``true``, this will | +| | ``BOOLEAN`` | be an active scan. | | | | The first argument | | | | defaults to ``false``. | +------------------+------------------------+------------------------+ @@ -123,22 +131,25 @@ context here: https://w1.fi/wpa_supplicant/devel/dbus.html | | | current Network. | +------------------+------------------------+------------------------+ | flushBSSCache | method | Flush all BSS items | -| | - (in) age ``UINT32`` | from the cache older | -| | | than a certain age. | +| | | from the cache older | +| | - (in) age ``UINT32`` | than a certain age. | | | | If age is ``0``, all | | | | BSS items will be | | | | removed. | +------------------+------------------------+------------------------+ | addBlob | method | Add a blob to the | -| | - (in) name ``STRING`` | wireless interface. | +| | | wireless interface. | +| | - (in) name ``STRING`` | | | | - (in) blob | | | | ``ARRAY BYTE`` | | +------------------+------------------------+------------------------+ | removeBlob | method | Remove a blob from the | -| | - (in) name ``STRING`` | wireless interface. | +| | | wireless interface. | +| | - (in) name ``STRING`` | | +------------------+------------------------+------------------------+ | getBlob | method | Get a blob from the | -| | - (in) name ``STRING`` | wireless interface. | +| | | wireless interface. | +| | - (in) name ``STRING`` | | | | - (out) ``ARRAY BYTE`` | | +------------------+------------------------+------------------------+ @@ -149,9 +160,7 @@ Example Usage .. code:: cpp - #include - #include "dbusservice_interface.h" - #include "wifiapi_interface.h" + #include using namespace codes::eeems::oxide1; @@ -213,13 +222,14 @@ BSS +-------------------+-----------------------+-----------------------+ | propertiesChanged | signal | Signal sent when | | | - (out) | properties change on | -| | `` | the BSS. | +| | - (out) | the BSS. | +| | `` | | | | ARRAY{STRING VALUE}`` | | +-------------------+-----------------------+-----------------------+ | connect | method | Attempt to connect to | | | - (out) | a BSS. | -| | ``OBJECT_PATH`` | Returns the | -| | | ``OBJECT_PATH`` for | +| | - (out) | Returns the | +| | ``OBJECT_PATH`` | ``OBJECT_PATH`` for | | | | the network. | | | | If none exists a new | | | | network will be | @@ -266,12 +276,13 @@ Network | | | configuration file. | +-------------------+-----------------------+-----------------------+ | stateChanged | signal | Signal sent when the | -| | - (out) ``BOOLEAN`` | enabled property | -| | | changes. | +| | | enabled property | +| | - (out) ``BOOLEAN`` | changes. | +-------------------+-----------------------+-----------------------+ | propertiesChanged | signal | Signal sent when the | -| | - (out) | properties of the | -| | ``AR | Network change. | +| | | properties of the | +| | - (out) | Network change. | +| | ``AR | | | | RAY{STRING VARIANT}`` | | +-------------------+-----------------------+-----------------------+ | connected | signal | Signal sent when the | diff --git a/web/src/documentation/api/07_compositor.rst b/web/src/documentation/api/07_compositor.rst new file mode 100644 index 000000000..336d9ec1c --- /dev/null +++ b/web/src/documentation/api/07_compositor.rst @@ -0,0 +1,129 @@ +============== +Compositor API +============== + +This API handles communication with the display server. If possible it +is recommended to use `use libblight instead <../../libblight/index.html>`__ + ++-------------------+------------------------+-----------------------------------+ +| Name | Specification | Description | ++===================+========================+===================================+ +| pid | ``INT32`` property | Get the PID of the display server | +| | (read) | process. | ++-------------------+------------------------+-----------------------------------+ +| clipboard | ``ARRAY BYTE`` | Get the contents of the clipboard | +| | property (read) | | ++-------------------+------------------------+-----------------------------------+ +| selection | ``ARRAY BYTE`` | Get the contents of the primary | +| | property (read) | selection | ++-------------------+------------------------+-----------------------------------+ +| secondary | ``ARRAY BYTE`` | Get the contents of the secondary | +| | property (read) | selection | ++-------------------+------------------------+-----------------------------------+ +| clipboardChanged | signal | The clipboard contents changed | +| | | | +| | - (out) ``ARRAY BYTE`` | | ++-------------------+------------------------+-----------------------------------+ +| selectionChanged | signal | The primary selection contents | +| | | changed | +| | - (out) ``ARRAY BYTE`` | | ++-------------------+------------------------+-----------------------------------+ +| secondaryChanged | signal | The secondary selection contents | +| | | changed | +| | - (out) ``ARRAY BYTE`` | | ++-------------------+------------------------+-----------------------------------+ +| open | method | Get the socket descriptor for the | +| | | current process' display server | +| | - (out) ``UNIX_FD`` | connection | ++-------------------+------------------------+-----------------------------------+ +| openInput | method | Get the socket descriptor for the | +| | | current process' input event | +| | - (out) ``UNIX_FD`` | stream | +| | | | ++-------------------+------------------------+-----------------------------------+ +| addSurface | method | Add a surface to the display | +| | | server | +| | - (out) ``UINT16`` | | +| | - (in) fd ``UNIX_FD`` | | +| | - (in) x ``INT32`` | | +| | - (in) y ``INT32`` | | +| | - (in) width ``INT32`` | | +| | - (in) height ``INT32``| | +| | - (in) stride ``INT32``| | +| | - (in) format ``INT32``| | ++-------------------+------------------------+-----------------------------------+ +| repaint | method | Repaint a surface or all the | +| | | surfaces for a connection. | +| | - (in) ``STRING`` | | ++-------------------+------------------------+-----------------------------------+ +| getSurface | method | Get the file descriptor for the | +| | | buffer of a surface | +| | - (in) identifier | | +| | ``UINT16`` | | +| | - (out) ``UINT16`` | | ++-------------------+------------------------+-----------------------------------+ +| setFlags | method | Set flags on a surface or | +| | | connection | +| | - (in) identifier | | +| | ``STRING`` | | +| | - (in) flags | | +| | ``STRING ARRAY`` | | ++-------------------+------------------------+-----------------------------------+ +| getSurfaces | method | Get a list of all surfaces | +| | | currently on the display server | +| | - (out) ``STRING | | +| | ARRAY`` | | ++-------------------+------------------------+-----------------------------------+ +| frameBuffer | method | Get the file descriptor of the | +| | | device frame buffer | +| | - (out) ``UNIX_FD`` | | ++-------------------+------------------------+-----------------------------------+ +| lower | method | Lower a surface or all the | +| | | surfaces for a connection. | +| | - (in) ``STRING`` | | ++-------------------+------------------------+-----------------------------------+ +| raise | method | Raise a surface or all the | +| | | surfaces for a connection. | +| | - (in) ``STRING`` | | ++-------------------+------------------------+-----------------------------------+ +| focus | method | Focus a connection so that it | +| | | recieves input events | +| | - (in) ``STRING`` | | ++-------------------+------------------------+-----------------------------------+ +| focus | method | Focus a connection so that it | ++-------------------+------------------------+-----------------------------------+ +| waitForNoRepaints | method | Wait for all pending repaints to | +| | | complete | ++-------------------+------------------------+-----------------------------------+ +|enterExclusiveMode | method | Enter exlcusive mode | ++-------------------+------------------------+-----------------------------------+ +| exitExclusiveMode | method | Exit exclusive move | ++-------------------+------------------------+-----------------------------------+ +|exlusiveModeRepaint| method | Repaint the framebuffer | ++-------------------+------------------------+-----------------------------------+ + +.. _example-usage-11: + +Example Usage +~~~~~~~~~~~~~ + +.. code:: cpp + + #include + #include + + using namespace codes::eeems::blight1; + + int main(int argc, char* argv[]){ + auto bus = QDBusConnection::systemBus(); + Compositor compositor(BLIGHT_SERVICE, "/", bus); + qDebug() << "Display server PID:" << compositor.pid(); + return EXIT_SUCCESS; + } + +.. code:: shell + + #!/bin/bash + echo "Display server PID: $(rot compositor get pid)" + echo "Display server surfaces:" + rot compositor call getSurfaces | jq -r '.[]'