diff --git a/.gitignore b/.gitignore index 09172ff..622b687 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ **/*.qmd-pure /applications_root/ /testing_extensions/ +/.vscode +/shim/build +/xovi/* +!/xovi/template +!/xovi/make.sh +.qmake.stash +appload +Makefile diff --git a/resources/qml/appload.qml b/resources/qml/appload.qml index 2a4f91f..dbd367d 100644 --- a/resources/qml/appload.qml +++ b/resources/qml/appload.qml @@ -179,6 +179,7 @@ Rectangle { win.appName = modelData.name; win.supportsScaling = modelData.supportsScaling; + win.supportsVirtualKeyboard = modelData.supportsVirtualKeyboard; win.disablesWindowedMode = modelData.disablesWindowedMode; win.globalWidth = Qt.binding(function() { return _appLoadView.width; }) diff --git a/resources/qml/window.qml b/resources/qml/window.qml index 73de1b7..4f158fd 100644 --- a/resources/qml/window.qml +++ b/resources/qml/window.qml @@ -24,6 +24,7 @@ FocusScope { property alias appName: _appName.text property bool supportsScaling: false + property bool supportsVirtualKeyboard: false property var qtfbKey: -1 property int appPid: -1 property bool minimized: false @@ -288,6 +289,69 @@ FocusScope { } } + Rectangle { + id: virtualKeyboardButton + width: parent.height + height: parent.height + anchors.left: parent.left + border.width: 2 + border.color: "black" + color: parent.color + visible: supportsVirtualKeyboard + + TextArea { + anchors.fill: parent + color: "white" + text: " " + + function sendVirtualKeyCode(code) { + windowCanvas.virtualKeyboardKeyDown(code); + + // Hold each key press for 100 milliseconds + (function(c) { + var t = Qt.createQmlObject('import QtQuick 2.5; Timer { interval: 100; repeat: false }', virtualKeyboardButton); + + t.triggered.connect(function() { + windowCanvas.virtualKeyboardKeyUp(c); + + t.destroy(); + }); + + t.start(); + })(code); + } + + onFocusChanged: { + if (focus) { + cursorPosition = 1; + sendVirtualKeyCode(0xf001) // Arbitrary unused keycode + } else { + sendVirtualKeyCode(0xf000) + } + } + + onTextChanged: { + if (text.length == 0) { // Backspace pressed + sendVirtualKeyCode(8) + } else if (text.indexOf('\n') != -1) { // Enter pressed + sendVirtualKeyCode(13) + } else if (text.length == 2) { // Regular key pressed + var lastChar = text.charAt(text.length - 1) + + if (text[0] != ' ') { + lastChar = text[0] + } + + sendVirtualKeyCode(lastChar.charCodeAt(0)) + } + + text = " "; + + cursorPosition = 1; + } + } + } + Rectangle { width: parent.width height: 2 diff --git a/shim/src/input-shim.cpp b/shim/src/input-shim.cpp index 3e46e33..1197098 100644 --- a/shim/src/input-shim.cpp +++ b/shim/src/input-shim.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "qtfb-client/qtfb-client.h" @@ -54,7 +55,7 @@ extern qtfb::ClientConnection *clientConnection; extern int shimInputType; -extern std::set *identDigitizer, *identTouchScreen, *identButtons, *identNull; +extern std::set *identDigitizer, *identTouchScreen, *identButtons, *identVirtualKeyboard, *identNull; struct TouchSlotState { int x, y; @@ -65,7 +66,8 @@ std::map touchStates; #define QUEUE_TOUCH 1 #define QUEUE_PEN 2 #define QUEUE_BUTTONS 3 -#define QUEUE_NULL 4 +#define QUEUE_VIRTUALKEYBOARD 4 +#define QUEUE_NULL 5 struct PIDEventQueue *pidEventQueue; @@ -98,6 +100,50 @@ static int mapKey(int x) { return 0; } +const bool isShifted(int ascii) { + if (ascii >= '!' && ascii <= '&') return true; + if (ascii >= '(' && ascii <= '+') return true; + if (ascii >= ':' && ascii <= ':') return true; + if (ascii >= '<' && ascii <= '<') return true; + if (ascii >= '>' && ascii <= 'Z') return true; + if (ascii >= '^' && ascii <= '_') return true; + if (ascii >= '{' && ascii <= '~') return true; + + if (ascii >= 0xA2 && ascii <= 0xA3) return true; // ¢ to £ + if (ascii >= 0xA6 && ascii <= 0xA8) return true; // ¦ to ¨ + if (ascii >= 0xAF && ascii <= 0xB0) return true; // ¯ to ° + if (ascii >= 0xB9 && ascii <= 0xB0) return true; // ¹ to ° + if (ascii >= 0xC0 && ascii <= 0xD6) return true; // À to Ö + if (ascii >= 0xD8 && ascii <= 0xDE) return true; // Ø to Þ + + return false; +} + +std::tuple mapAsciiToX11Key(int ascii) { + const bool shifted = isShifted(ascii); + + // All ASCII printable characters + if (ascii >= ' ' && ascii <= '~') { + return {ascii, shifted}; + } + + // All ASCII printable extended characters + if (ascii >= 0x00a0 && ascii <= 0x00ff) { + return {ascii, shifted}; + } + + // ASCII unprintable characters + switch (ascii) { + case 8: return {0xff08, false}; // Backspace + case 9: return {0xff09, false}; // Tab + case 13: return {0xff0d, false}; // Enter/Return + case 27: return {0xff1b, false}; // Escape + case 127: return {0xffff, false}; // Delete + } + + return {0x0000, false}; +} + static void pushToAll(int queueType, struct input_event evt) { struct PIDEventQueue *current = pidEventQueue; while(current != NULL) { @@ -217,6 +263,7 @@ static void pollInputUpdates() { pushToAll(QUEUE_PEN, evt(EV_ABS, ABS_PRESSURE, dTranslate)); pushToAll(QUEUE_PEN, evt(EV_SYN, SYN_REPORT, 0)); break; + case INPUT_BTN_PRESS: pushToAll(QUEUE_BUTTONS, evt(EV_KEY, mapKey(message.userInput.x), 1)); pushToAll(QUEUE_BUTTONS, evt(EV_SYN, SYN_REPORT, 0)); @@ -225,6 +272,42 @@ static void pollInputUpdates() { pushToAll(QUEUE_BUTTONS, evt(EV_KEY, mapKey(message.userInput.x), 0)); pushToAll(QUEUE_BUTTONS, evt(EV_SYN, SYN_REPORT, 0)); break; + + case INPUT_VKB_PRESS: { + auto [keySym, isShifted] = mapAsciiToX11Key(message.userInput.x); + + if (keySym != 0x0000) { + if (isShifted) { + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, 0xffe1, 1)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + } + + usleep(10000); + + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, keySym, 1)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + } + + break; + } + case INPUT_VKB_RELEASE: { + auto [keySym, isShifted] = mapAsciiToX11Key(message.userInput.x); + + if (keySym != 0x0000) { + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, keySym, 0)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + + usleep(10000); + + if (isShifted) { + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, 0xffe1, 0)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + } + } + + break; + } + default: break; } } @@ -267,6 +350,7 @@ int inputShimOpen(fileident_t identity, int flags, mode_t mode) { e("dig", *identDigitizer); e("tch", *identTouchScreen); e("btn", *identButtons); + e("vkb", *identVirtualKeyboard); e("null", *identNull); #undef e if(identDigitizer->find(identity) != identDigitizer->end()) { @@ -285,6 +369,11 @@ int inputShimOpen(fileident_t identity, int flags, mode_t mode) { CERR << "Open buttons " << fd << std::endl; return fd; } + if(identVirtualKeyboard->find(identity) != identVirtualKeyboard->end()) { + int fd = createInEventMap(QUEUE_VIRTUALKEYBOARD, flags); + CERR << "Open virtual keyboard " << fd << std::endl; + return fd; + } if(identNull->find(identity) != identNull->end()) { int fd = createInEventMap(QUEUE_NULL, flags); CERR << "Open null " << fd << std::endl; diff --git a/shim/src/shim.cpp b/shim/src/shim.cpp index 372adec..b6a5d4d 100644 --- a/shim/src/shim.cpp +++ b/shim/src/shim.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "shim.h" #include "fb-shim.h" #include "input-shim.h" @@ -40,7 +41,7 @@ bool shimModel; bool shimInput; bool shimFramebuffer; int shimInputType = SHIM_INPUT_RM1; -std::set *identDigitizer, *identTouchScreen, *identButtons, *identNull; +std::set *identDigitizer, *identTouchScreen, *identButtons, *identVirtualKeyboard, *identNull; int realDeviceType; void readRealDeviceType() { @@ -108,6 +109,7 @@ void __attribute__((constructor)) __construct () { identDigitizer = new std::set(); identTouchScreen = new std::set(); identButtons = new std::set(); + identVirtualKeyboard = new std::set(); identNull = new std::set(); readRealDeviceType(); @@ -192,7 +194,10 @@ void __attribute__((constructor)) __construct () { CERR << "Configured FB type to " << shimType << ", input to " << shimInputType << std::endl; - const char *pathDigitizer, *pathTouchScreen, *pathButtons, *pathNull; + const char *pathDigitizer, *pathTouchScreen, *pathButtons, *pathVirtualKeyboard, *pathNull; + + pathVirtualKeyboard = "/dev/input/virtual_keyboard"; + std::ofstream(pathVirtualKeyboard).close(); switch(shimInputType) { case SHIM_INPUT_RM1: @@ -233,6 +238,11 @@ void __attribute__((constructor)) __construct () { } iterStringCollectToIdentities(identButtons, temp); + if((temp = getenv("QTFB_SHIM_INPUT_PATH_KEYS")) == NULL) { + temp = pathVirtualKeyboard; + } + iterStringCollectToIdentities(identVirtualKeyboard, temp); + if((temp = getenv("QTFB_SHIM_INPUT_PATH_NULL")) == NULL) { temp = pathNull; } @@ -247,6 +257,9 @@ void __attribute__((constructor)) __construct () { for(const auto e : *identButtons) { CERR << "Ident btn: " << e << std::endl; } + for(const auto e : *identVirtualKeyboard) { + CERR << "Ident vkb: " << e << std::endl; + } for(const auto e : *identNull) { CERR << "Ident null: " << e << std::endl; } diff --git a/src/AppLibrary.h b/src/AppLibrary.h index 5470927..4c65eca 100644 --- a/src/AppLibrary.h +++ b/src/AppLibrary.h @@ -24,6 +24,7 @@ class AppLoadApplication : public QObject { Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString icon READ icon CONSTANT) Q_PROPERTY(bool supportsScaling READ supportsScaling) + Q_PROPERTY(bool supportsVirtualKeyboard READ supportsVirtualKeyboard) Q_PROPERTY(bool canHaveMultipleFrontends READ canHaveMultipleFrontends) Q_PROPERTY(int externalType READ externalType) // 0 - not external, 1 - external (non-graphics), 2 - external (qtfb) Q_PROPERTY(QString aspectRatio READ aspectRatio CONSTANT) @@ -32,14 +33,15 @@ class AppLoadApplication : public QObject { public: explicit AppLoadApplication(QObject *parent = nullptr) : QObject(parent) {} - AppLoadApplication(const QString &id, const QString &name, const QString &icon, bool supportsScaling, bool canHaveMultipleFrontends, int externalType, appload::library::AspectRatio aspectRatio, bool disablesWindowedMode, QObject *parent = nullptr) - : QObject(parent), _id(id), _name(name), _icon(icon), _supportsScaling(supportsScaling), _canHaveMultipleFrontends(canHaveMultipleFrontends), _externalType(externalType), _aspectRatio(aspectRatio), _disablesWindowedMode(disablesWindowedMode) {} + AppLoadApplication(const QString &id, const QString &name, const QString &icon, bool supportsScaling, bool supportsVirtualKeyboard, bool canHaveMultipleFrontends, int externalType, appload::library::AspectRatio aspectRatio, bool disablesWindowedMode, QObject *parent = nullptr) + : QObject(parent), _id(id), _name(name), _icon(icon), _supportsScaling(supportsScaling), _supportsVirtualKeyboard(supportsVirtualKeyboard), _canHaveMultipleFrontends(canHaveMultipleFrontends), _externalType(externalType), _aspectRatio(aspectRatio), _disablesWindowedMode(disablesWindowedMode) {} QString id() const { return _id; } QString name() const { return _name; } QString icon() const { return _icon; } QString aspectRatio() const { return appload::library::aspectRatioToString(_aspectRatio); } bool supportsScaling() const { return _supportsScaling; } + bool supportsVirtualKeyboard() const { return _supportsVirtualKeyboard; } bool canHaveMultipleFrontends() const { return _canHaveMultipleFrontends; } int externalType() const { return _externalType; } bool disablesWindowedMode() const { return _disablesWindowedMode; } @@ -49,6 +51,7 @@ class AppLoadApplication : public QObject { QString _name; QString _icon; bool _supportsScaling; + bool _supportsVirtualKeyboard; bool _canHaveMultipleFrontends; int _externalType; appload::library::AspectRatio _aspectRatio; @@ -107,6 +110,7 @@ class AppLoadLibrary : public QObject { entry.second->getAppName(), entry.second->getIconPath(), entry.second->supportsScaling(), + false, entry.second->canHaveMultipleFrontends(), INTERNAL, appload::library::AspectRatio::AUTO, @@ -118,6 +122,7 @@ class AppLoadLibrary : public QObject { entry.second->getAppName(), entry.second->getIconPath(), false, + entry.second->supportsVirtualKeyboard(), true, entry.second->isQTFB() ? EXTERNAL_QTFB : EXTERNAL_NOGUI, entry.second->getAspectRatio(), diff --git a/src/library.h b/src/library.h index c81736f..17c9c2c 100644 --- a/src/library.h +++ b/src/library.h @@ -48,6 +48,7 @@ namespace appload::library { bool isQTFB() const; AspectRatio getAspectRatio() const; bool disablesWindowedMode() const; + bool supportsVirtualKeyboard() const; bool valid = false; @@ -62,6 +63,7 @@ namespace appload::library { std::map environment; bool _isQTFB; bool _disablesWindowedMode; + bool _supportsVirtualKeyboard; AspectRatio aspectRatio; void parseManifest(); diff --git a/src/libraryexternals.cpp b/src/libraryexternals.cpp index 55b1a95..36d5485 100644 --- a/src/libraryexternals.cpp +++ b/src/libraryexternals.cpp @@ -28,8 +28,6 @@ void appload::library::removeGlobalLibraryHandle(AppLoadLibrary *ptr) { } } - - void appload::library::ExternalApplication::parseManifest() { QString filePath = root + "/external.manifest.json"; QFile file(filePath); @@ -55,6 +53,7 @@ void appload::library::ExternalApplication::parseManifest() { // Optional: _isQTFB = jsonObject.value("qtfb").toBool(false); _disablesWindowedMode = jsonObject.value("disablesWindowedMode").toBool(false); + _supportsVirtualKeyboard = jsonObject.value("supportsVirtualKeyboard").toBool(false); workingDirectory = jsonObject.value("workingDirectory").toString(root); args = jsonObject.value("args").toVariant().toStringList(); QJsonObject env = jsonObject.value("environment").toObject(); @@ -137,6 +136,10 @@ bool appload::library::ExternalApplication::disablesWindowedMode() const { return _disablesWindowedMode; } +bool appload::library::ExternalApplication::supportsVirtualKeyboard() const { + return _supportsVirtualKeyboard; +} + void appload::library::terminateExternal(qint64 pid) { kill(pid, SIGTERM); sendPidDiedMessage(pid); diff --git a/src/qtfb/FBController.cpp b/src/qtfb/FBController.cpp index 8653892..f022c33 100644 --- a/src/qtfb/FBController.cpp +++ b/src/qtfb/FBController.cpp @@ -137,7 +137,7 @@ void FBController::mouseMoveEvent(QMouseEvent *me) { me->accept(); } -static inline void sendSpecialKey(int key, int pkt, qtfb::FBKey _framebufferID) { +static inline void sendKeyEvent(int key, int pkt, qtfb::FBKey _framebufferID) { if(_framebufferID != -1) { qtfb::UserInputContents packet { .inputType = pkt, @@ -150,12 +150,20 @@ static inline void sendSpecialKey(int key, int pkt, qtfb::FBKey _framebufferID) } } +void FBController::virtualKeyboardKeyDown(int key) { + sendKeyEvent(key, INPUT_VKB_PRESS, _framebufferID); +} + +void FBController::virtualKeyboardKeyUp(int key) { + sendKeyEvent(key, INPUT_VKB_RELEASE, _framebufferID); +} + void FBController::specialKeyDown(int key) { - sendSpecialKey(key, INPUT_BTN_PRESS, _framebufferID); + sendKeyEvent(key, INPUT_BTN_PRESS, _framebufferID); } void FBController::specialKeyUp(int key) { - sendSpecialKey(key, INPUT_BTN_RELEASE, _framebufferID); + sendKeyEvent(key, INPUT_BTN_RELEASE, _framebufferID); } void FBController::mouseReleaseEvent(QMouseEvent *me) { diff --git a/src/qtfb/FBController.h b/src/qtfb/FBController.h index 54f3560..aa46b73 100644 --- a/src/qtfb/FBController.h +++ b/src/qtfb/FBController.h @@ -52,6 +52,8 @@ class FBController : public QQuickPaintedItem virtual void keyPressEvent(QKeyEvent *ke) override; virtual void keyReleaseEvent(QKeyEvent *ke) override; + Q_INVOKABLE void virtualKeyboardKeyDown(int key); + Q_INVOKABLE void virtualKeyboardKeyUp(int key); Q_INVOKABLE void specialKeyDown(int key); Q_INVOKABLE void specialKeyUp(int key); diff --git a/src/qtfb/common.h b/src/qtfb/common.h index 72ac6f1..cc1bb64 100644 --- a/src/qtfb/common.h +++ b/src/qtfb/common.h @@ -50,6 +50,9 @@ #define INPUT_BTN_PRESS 0x30 #define INPUT_BTN_RELEASE 0x31 +#define INPUT_VKB_PRESS 0x40 +#define INPUT_VKB_RELEASE 0x41 + #define INPUT_BTN_X_LEFT 0 #define INPUT_BTN_X_HOME 1 #define INPUT_BTN_X_RIGHT 2 diff --git a/xovi/make.sh b/xovi/make.sh old mode 100644 new mode 100755