diff --git a/_start.qml b/_start.qml index 6b53ab9..121f608 100644 --- a/_start.qml +++ b/_start.qml @@ -2,6 +2,8 @@ import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 2.5 +import net.asivery.AppLoad 1.0 + Window { visible: true title: qsTr("AppLoad - PC emulator") @@ -30,7 +32,24 @@ Window { anchors.centerIn: parent - onLoaded: loader.item.visible = true + onLoaded: { + loader.item.visible = true; + loader.item.virtualKeyboardRef = keyboardLoader; + } + } + } + Loader { + property var layout: null + property var config: null + id: keyboardLoader + source: "qrc:/appload/qml/virtualKeyboard/Keyboard.qml" + active: false + width: parent.width + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + onLoaded: () => { + keyboardLoader.item.rebuildKeyboard(keyboardLoader.layout, keyboardLoader.config); } } } diff --git a/appload.pro b/appload.pro index 4c8e16d..086a18f 100644 --- a/appload.pro +++ b/appload.pro @@ -4,9 +4,11 @@ TARGET = appload TEMPLATE = app SOURCES += src/main.cpp src/management.cpp src/AppLoad.cpp src/AppLoadCoordinator.cpp src/library.cpp src/libraryexternals.cpp \ - src/qtfb/fbmanagement.cpp src/qtfb/FBController.cpp + src/qtfb/fbmanagement.cpp src/qtfb/FBController.cpp \ + src/keyboard/layout.cpp HEADERS += src/AppLoad.h src/AppLoadCoordinator.h src/library.h src/AppLibrary.h \ - src/qtfb/FBController.h src/qtfb/fbmanagement.h src/Launcher.h + src/qtfb/FBController.h src/qtfb/fbmanagement.h \ + src/keyboard/layout.h src/Launcher.h RESOURCES += resources/resources.qrc diff --git a/resources/icons/keyboard.svg b/resources/icons/keyboard.svg new file mode 100644 index 0000000..08dcada --- /dev/null +++ b/resources/icons/keyboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/keyboard/backspace.png b/resources/keyboard/backspace.png new file mode 100644 index 0000000..2bc3dc3 Binary files /dev/null and b/resources/keyboard/backspace.png differ diff --git a/resources/keyboard/default.layout.json b/resources/keyboard/default.layout.json new file mode 100644 index 0000000..314bb76 --- /dev/null +++ b/resources/keyboard/default.layout.json @@ -0,0 +1,78 @@ +[ + [ + ["esc", 27, "", 0], + [">", 62, "<", 60], + ["|", 124, "&", 38], + ["!", 33, "?", 63], + ["\"", 34, "$", 36], + ["'", 39, "`", 96], + ["_", 95, "@", 64], + ["=", 61, "~", 126], + ["/", 47, "\\", 92], + ["*", 42, "del", 127], + [":backspace", 128, "", 0] + ], + [ + [":tab", 9, "", 0], + ["1", 49, "+", 43], + ["2", 50, "^", 94], + ["3", 51, "#", 35], + ["4", 52, "%", 37], + ["5", 53, "(", 40], + ["6", 54, ")", 41], + ["7", 55, "[", 91], + ["8", 56, "]", 93], + ["9", 57, "{", 123], + ["0", 48, "}", 125] + ], + [ + ["q", 81, "", 0], + ["w", 87, "", 0], + ["e", 69, "", 0], + ["r", 82, "", 0], + ["t", 84, "", 0], + ["y", 89, "", 0], + ["u", 85, "", 0], + ["i", 73, "", 0], + ["o", 79, "", 0], + ["p", 80, "", 0], + [":enter", 13, "", 0] + ], + [ + ["a", 65, "", 0], + ["s", 83, "", 0], + ["d", 68, "", 0], + ["f", 70, "", 0], + ["g", 71, "", 0], + ["h", 72, "", 0], + ["j", 74, "", 0], + ["k", 75, "", 0], + ["l", 76, "", 0], + [":", 58, "", 0], + ["pgup", 129, "home", 135] + ], + [ + [":shift", 1048576, "", 0], + ["z", 90, "", 0], + ["x", 88, "", 0], + ["c", 67, "", 0], + ["v", 86, "", 0], + ["b", 66, "", 0], + ["n", 78, "", 0], + ["m", 77, "", 0], + [";", 59, "", 0], + [":up", 132, "", 0], + ["pgdn", 130, "end", 136] + ], + [ + ["ctrl", 2097152, "", 0], + ["alt", 4194304, "", 0], + ["-", 45, "", 0], + [",", 44, "", 0], + [" ", 32, "", 0, 3], + [".", 46, "", 0], + [":left", 133, "", 0], + [":down", 131, "", 0], + [":right", 134, "", 0] + ] +] \ No newline at end of file diff --git a/resources/keyboard/down.png b/resources/keyboard/down.png new file mode 100644 index 0000000..8ac3a8b Binary files /dev/null and b/resources/keyboard/down.png differ diff --git a/resources/keyboard/enter.png b/resources/keyboard/enter.png new file mode 100644 index 0000000..7e96698 Binary files /dev/null and b/resources/keyboard/enter.png differ diff --git a/resources/keyboard/left.png b/resources/keyboard/left.png new file mode 100644 index 0000000..e757e22 Binary files /dev/null and b/resources/keyboard/left.png differ diff --git a/resources/keyboard/menu.png b/resources/keyboard/menu.png new file mode 100644 index 0000000..49b62b5 Binary files /dev/null and b/resources/keyboard/menu.png differ diff --git a/resources/keyboard/right.png b/resources/keyboard/right.png new file mode 100644 index 0000000..d320c63 Binary files /dev/null and b/resources/keyboard/right.png differ diff --git a/resources/keyboard/shift.png b/resources/keyboard/shift.png new file mode 100644 index 0000000..7266a0c Binary files /dev/null and b/resources/keyboard/shift.png differ diff --git a/resources/keyboard/tab.png b/resources/keyboard/tab.png new file mode 100644 index 0000000..8540f23 Binary files /dev/null and b/resources/keyboard/tab.png differ diff --git a/resources/keyboard/up.png b/resources/keyboard/up.png new file mode 100644 index 0000000..e1ec790 Binary files /dev/null and b/resources/keyboard/up.png differ diff --git a/resources/qml/appload.qml b/resources/qml/appload.qml index b9f398e..f73a0b4 100644 --- a/resources/qml/appload.qml +++ b/resources/qml/appload.qml @@ -11,6 +11,7 @@ Rectangle { property var windowArchetype: Qt.createComponent("window.qml") property var absoluteRoot: _appLoadView + property var virtualKeyboardRef: null signal requestClose @@ -56,8 +57,11 @@ Rectangle { win.appName = modelData.name; win.supportsScaling = modelData.supportsScaling; + win.virtualKeyboardLayout = modelData.virtualKeyboardLayout; win.disablesWindowedMode = modelData.disablesWindowedMode; + win.virtualKeyboardRef = _appLoadView.virtualKeyboardRef; + win.globalWidth = Qt.binding(function() { return _appLoadView.width; }) win.globalHeight = Qt.binding(function() { return _appLoadView.height; }) let deviceAspectRatio, applicationAspectRatio = modelData.aspectRatio; diff --git a/resources/qml/virtualKeyboard/Key.qml b/resources/qml/virtualKeyboard/Key.qml new file mode 100644 index 0000000..95ea898 --- /dev/null +++ b/resources/qml/virtualKeyboard/Key.qml @@ -0,0 +1,211 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This work is free software. you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This work is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this work. If not, see . +*/ + +import QtQuick 2.0 + +Rectangle { + id: key + required property var vkb + + property string label + property string label_alt + property int code + property int code_alt + property int currentCode: (shiftActive && label_alt != '') ? code_alt : code + property string currentLabel: (shiftActive && label_alt != '') ? label_alt : label + property bool sticky // can key be stickied? + property bool becomesSticky // will this become sticky after release? + property int stickiness // current stickiness status + property real labelOpacity: 1.0 + + // mouse input handling + property int clickThreshold: 20 + property bool isClick + property int pressMouseY + property int pressMouseX + property bool shiftActive: (vkb.keyModifiers & 0x100000) && !sticky + + width: vkb.keyWidth // some default + height: vkb.keyHeight + color: label=="" ? "transparent" : vkb.keyBgColor + border.color: label=="" ? "transparent" : vkb.keyBorderColor + border.width: 1 + radius: 2 + + Image { + id: keyImage + anchors.centerIn: parent + opacity: key.labelOpacity + source: { if(key.label.length>1 && key.label.charAt(0)==':') return "qrc:/appload/keyboard/"+key.label.substring(1)+".png"; else return ""; } + } + + Column { + visible: keyImage.source == "" + anchors.centerIn: parent + spacing: -17 + + Text { + id: keyAltLabel + property bool highlighted: key.shiftActive + + anchors.horizontalCenter: parent.horizontalCenter + + text: key.label_alt + color: vkb.keyFgColor + + opacity: key.labelOpacity * (highlighted ? 1.0 : 0.2) + Behavior on opacity { NumberAnimation { duration: 100 } } + + font.pointSize: 24 * (text.length > 1 ? 0.5 : 1.0) + Behavior on font.pointSize { NumberAnimation { duration: 100 } } + } + + Text { + id: keyLabel + property bool highlighted: key.label_alt == '' || !key.shiftActive + + anchors.horizontalCenter: parent.horizontalCenter + + text: { + if (key.label.length == 1 && key.label_alt == '') { + if (key.shiftActive) { + return key.label.toUpperCase(); + } else { + return key.label.toLowerCase(); + } + } + + return key.label; + } + + color: vkb.keyFgColor + + opacity: key.labelOpacity * (highlighted ? 1.0 : 0.2) + Behavior on opacity { NumberAnimation { duration: 100 } } + + font.pointSize: 24 * (text.length > 1 ? 0.5 : 1.0) + Behavior on font.pointSize { NumberAnimation { duration: 100 } } + } + } + + Rectangle { + id: stickIndicator + visible: sticky && stickiness>0 + color: vkb.keyHilightBgColor + anchors.fill: parent + radius: key.radius + opacity: 0.5 + anchors.topMargin: key.height/2 + } + + function handlePress(touchArea, x, y) { + console.log("Press " + label) + isClick = true; + pressMouseX = x; + pressMouseY = y; + + key.color = vkb.keyHilightBgColor + + vkb.config.keyDown(currentCode | vkb.keyModifiers); + + if (sticky) { + vkb.keyModifiers |= code; + key.becomesSticky = true; + vkb.currentStickyPressed = key; + } else { + if (vkb.currentStickyPressed != null) { + // Pressing a non-sticky key while a sticky key is pressed: + // the sticky key will not become sticky when released + vkb.currentStickyPressed.becomesSticky = false; + } + } + } + + function handleMove(touchArea, x, y) { + var mappedPoint = key.mapFromItem(touchArea, x, y) + if (!key.contains(Qt.point(mappedPoint.x, mappedPoint.y))) { + key.handleRelease(touchArea, x, y); + return false; + } + + if (key.isClick) { + if (Math.abs(x - key.pressMouseX) > key.clickThreshold + || Math.abs(y - key.pressMouseY) > key.clickThreshold) { + key.isClick = false + } + } + + return true; + } + + function handleRelease(touchArea, x, y) { + key.color = vkb.keyBgColor + + if (sticky && !becomesSticky) { + vkb.keyModifiers &= ~code + vkb.currentStickyPressed = null + } + if(!sticky) { + vkb.config.keyUp(currentCode | vkb.keyModifiers); + } + + if (vkb.keyAt(x, y) == key) { + if (key.sticky && key.becomesSticky) { + setStickiness(-1) + } + + // first non-sticky press will cause the sticky to be released + if (!sticky && vkb.resetSticky && vkb.resetSticky !== key) { + resetSticky.setStickiness(0) + } + } + } + + function setStickiness(val) + { + if(sticky) { + if( vkb.resetSticky && vkb.resetSticky !== key ) { + resetSticky.setStickiness(0) + } + + if(val===-1) + stickiness = (stickiness+1) % 3 + else + stickiness = val + + // stickiness == 0 -> not pressed + // stickiness == 1 -> release after next keypress + // stickiness == 2 -> keep pressed + + if(stickiness>0) { + vkb.keyModifiers |= code + } else { + vkb.config.keyUp(currentCode); + vkb.keyModifiers &= ~code + } + + vkb.resetSticky = null + + if(stickiness==1) { + stickIndicator.anchors.topMargin = key.height/2 + vkb.resetSticky = key + } else if(stickiness==2) { + stickIndicator.anchors.topMargin = 0 + } + } + } +} diff --git a/resources/qml/virtualKeyboard/Keyboard.qml b/resources/qml/virtualKeyboard/Keyboard.qml new file mode 100644 index 0000000..4861c30 --- /dev/null +++ b/resources/qml/virtualKeyboard/Keyboard.qml @@ -0,0 +1,149 @@ +/* + Copyright 2011-2012 Heikki Holstila + + This work is free software. you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This work is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this work. If not, see . +*/ + +import QtQuick 2.0 + +Rectangle { + id: keyboard + + property int keyModifiers + property Key resetSticky + property Key currentStickyPressed + property string keyFgColor: "#000000" + property string keyBgColor: "#ffffff" + property string keyHilightBgColor: "#000000" + property string keyBorderColor: "#000000" + + + property int outmargins: 2 + property int keyspacing: 6 + property int keysPerRow: layout?.columns || 0 + property real keywidth: (keyboard.width - keyspacing*keysPerRow - outmargins*2)/keysPerRow; + property int keyWidth: width / (layout.columns + 1) + property int keyHeight: window.height / 14 < 55 ? window.height / 14 : 55 + property var config: null + + property var layout: null + + width: parent.width + height: keyboard.outmargins + keyboard.keyspacing * (layout?.rows ?? 0 - 1) + keyboard.keyHeight * (layout?.rows ?? 0) + + Component { + id: keyboardContents + Column { + id: col + + x: (keyboard.width-width)/2 + spacing: keyboard.keyspacing + + Repeater { + id: rowRepeater + + model: layout?.rows || 0 + delegate: Row { + spacing: keyboard.keyspacing + Repeater { + id: colRepeater + property int rowIndex: index + + model: layout.columnsInRow(index) + delegate: Key { + required property int index + property var keydata: layout.key(index, colRepeater.rowIndex) + vkb: keyboard + + label: keydata.label + code: keydata.code + label_alt: keydata.altLabel + code_alt: keydata.altCode + width: keyboard.keywidth * keydata.width + ((keydata.width-1)*keyboard.keyspacing) + 1 + sticky: keydata.isModifier && (keyboard.config?.enableStickyness ?? true) + } + } + } + } + } + } + + Loader { + id: keyboardLoader + anchors.fill: parent + } + + function rebuildKeyboard(layoutData, config) { + keyboard.config = config; + keyboard.layout = layoutData; + keyboardLoader.sourceComponent = undefined; + keyboardLoader.sourceComponent = keyboardContents; + } + + //borrowed from nemo-keyboard + //Parameters: (x, y) in view coordinates + function keyAt(x, y) { + var item = keyboardLoader.item + x -= keyboard.x + y -= keyboard.y + + while ((item = item.childAt(x, y)) != null) { + //return the first "Key" element we find + if (typeof item.currentCode !== 'undefined') { + return item + } + + // Cheaper mapToItem, assuming we're not using anything fancy. + x -= item.x + y -= item.y + } + + return null + } + + MultiPointTouchArea { + id: multiTouchArea + anchors.fill: parent + property var pressedKeys: ({}) + + onPressed: (touchPoints) => { + touchPoints.forEach(function (touchPoint) { + var key = keyboard.keyAt(touchPoint.x, touchPoint.y) + if (key != null) { + key.handlePress(multiTouchArea, touchPoint.x, touchPoint.y) + } + multiTouchArea.pressedKeys[touchPoint.pointId] = key + }) + } + onUpdated: (touchPoints) => { + touchPoints.forEach(function (touchPoint) { + var key = multiTouchArea.pressedKeys[touchPoint.pointId] + if (key != null) { + if (!key.handleMove(multiTouchArea, touchPoint.x, touchPoint.y)) { + delete multiTouchArea.pressedKeys[touchPoint.pointId]; + } + } + }) + } + onReleased: (touchPoints) => { + touchPoints.forEach(function (touchPoint) { + var key = multiTouchArea.pressedKeys[touchPoint.pointId] + if (key != null) { + key.handleRelease(multiTouchArea, touchPoint.x, touchPoint.y) + } + delete multiTouchArea.pressedKeys[touchPoint.pointId] + }) + } + } +} diff --git a/resources/qml/window.qml b/resources/qml/window.qml index 73de1b7..e8b9c92 100644 --- a/resources/qml/window.qml +++ b/resources/qml/window.qml @@ -33,6 +33,15 @@ FocusScope { property var _height: root.globalHeight / 3 property var beforeFullscreenData: null + // Keyboard handling: + property var virtualKeyboardLayout: null + property var virtualKeyboardRef: null + property var keyboardConfig: ({ + enableStickyness: true, + keyUp: code => windowCanvas.virtualKeyboardKeyUp(code), + keyDown: code => windowCanvas.virtualKeyboardKeyDown(code), + }) + // External I/O from this component: signal closed function loadApplication(appId) { @@ -288,6 +297,39 @@ FocusScope { } } + Rectangle { + id: virtualKeyboardButton + width: parent.height + height: parent.height + anchors.left: parent.left + border.width: 2 + border.color: "black" + color: parent.color + visible: virtualKeyboardLayout !== null + + Image { + source: "qrc:/appload/icons/keyboard" + sourceSize.width: 120 + sourceSize.height: 120 + anchors.fill: parent + anchors.margins: 10 + } + + MouseArea { + anchors.fill: parent + onClicked: () => { + if(root.virtualKeyboardRef.active && root.virtualKeyboardRef.config === root.keyboardConfig) { + root.virtualKeyboardRef.active = false; + } else { + root.virtualKeyboardRef.config = root.keyboardConfig; + root.virtualKeyboardRef.layout = root.virtualKeyboardLayout; + root.virtualKeyboardRef.active = false; + root.virtualKeyboardRef.active = true; + } + } + } + } + Rectangle { width: parent.width height: 2 @@ -374,6 +416,7 @@ FocusScope { unloadingFunction = loaderScaled.item?.unloading; } if(unloadingFunction) unloadingFunction(); + root.virtualKeyboardRef.active = false; root.closed(); } } diff --git a/resources/resources.qrc b/resources/resources.qrc index 1db00f3..41af136 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -3,8 +3,22 @@ icons/appload.svg icons/reload.svg icons/exit.svg + icons/keyboard.svg qml/appload.qml qml/window.qml + qml/virtualKeyboard/Keyboard.qml + qml/virtualKeyboard/Key.qml + + keyboard/default.layout.json + keyboard/backspace.png + keyboard/down.png + keyboard/enter.png + keyboard/left.png + keyboard/menu.png + keyboard/right.png + keyboard/shift.png + keyboard/tab.png + keyboard/up.png diff --git a/shim/src/input-shim.cpp b/shim/src/input-shim.cpp index 3e46e33..6630949 100644 --- a/shim/src/input-shim.cpp +++ b/shim/src/input-shim.cpp @@ -54,7 +54,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 +65,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 +99,42 @@ static int mapKey(int x) { return 0; } +int mapAsciiToX11Key(int ascii) { + // Pure modifiers: + switch(ascii){ + case INPUT_VKB_SHIFTMOD: return 0xffe1; + case INPUT_VKB_CTRLMOD: return 0xffe3; + case INPUT_VKB_ALTMOD: return 0xffe9; + } + // So it's not a pure modifier. + // ASCII unprintable characters + switch (ascii & 0xFF) { + case 8: return 0xff08; // Backspace + case 9: return 0xff09; // Tab + case 13: return 0xff0d; // Enter/Return + case 27: return 0xff1b; // Escape + case 127: return 0xffff; // Delete + case INPUT_VKB_LEFT: return 0xff51; + case INPUT_VKB_UP: return 0xff52; + case INPUT_VKB_RIGHT: return 0xff53; + case INPUT_VKB_DOWN: return 0xff54; + case INPUT_VKB_HOME: return 0xff50; + case INPUT_VKB_END: return 0xff57; + case INPUT_VKB_PGUP: return 0xff55; + case INPUT_VKB_PGDOWN: return 0xff56; + } + + + // If shift key pressed, uppercase the ascii + if((ascii & INPUT_VKB_SHIFTMOD) && (ascii >= 'a') && (ascii <= 'z')) { + ascii -= ' '; + } + // Mask the pure ascii: + ascii &= 0xFF; + + return 0; +} + static void pushToAll(int queueType, struct input_event evt) { struct PIDEventQueue *current = pidEventQueue; while(current != NULL) { @@ -225,6 +262,20 @@ 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: { + int code = mapAsciiToX11Key(message.userInput.x); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, code, 1)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + break; + } + case INPUT_VKB_RELEASE: { + int code = mapAsciiToX11Key(message.userInput.x); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_KEY, code, 0)); + pushToAll(QUEUE_VIRTUALKEYBOARD, evt(EV_SYN, SYN_REPORT, 0)); + break; + } + default: break; } } @@ -267,6 +318,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 +337,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 007b2fb..18a82b3 100644 --- a/src/AppLibrary.h +++ b/src/AppLibrary.h @@ -13,6 +13,7 @@ #include #include "library.h" +#include "keyboard/layout.h" #define INTERNAL 0 #define EXTERNAL_NOGUI 1 @@ -29,17 +30,39 @@ class AppLoadApplication : public QObject { Q_PROPERTY(int externalType READ externalType) // 0 - not external, 1 - external (non-graphics), 2 - external (qtfb) Q_PROPERTY(QString aspectRatio READ aspectRatio CONSTANT) Q_PROPERTY(bool disablesWindowedMode READ disablesWindowedMode CONSTANT) + Q_PROPERTY(const appload::vk::Layout *const virtualKeyboardLayout READ virtualKeyboardLayout CONSTANT) 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 canHaveMultipleFrontends, + int externalType, + appload::library::AspectRatio aspectRatio, + bool disablesWindowedMode, + const appload::vk::Layout *vkLayout, + QObject *parent = nullptr + ): + QObject(parent), + _id(id), + _name(name), + _icon(icon), + _supportsScaling(supportsScaling), + _canHaveMultipleFrontends(canHaveMultipleFrontends), + _externalType(externalType), + _aspectRatio(aspectRatio), + _disablesWindowedMode(disablesWindowedMode), + _virtualKeyboardLayout(vkLayout) {} QString id() const { return _id; } QString name() const { return _name; } QString icon() const { return _icon; } QString aspectRatio() const { return appload::library::aspectRatioToString(_aspectRatio); } + const appload::vk::Layout *virtualKeyboardLayout() const { return _virtualKeyboardLayout; } bool supportsScaling() const { return _supportsScaling; } bool canHaveMultipleFrontends() const { return _canHaveMultipleFrontends; } int externalType() const { return _externalType; } @@ -50,15 +73,18 @@ class AppLoadApplication : public QObject { QString _name; QString _icon; bool _supportsScaling; + bool _supportsVirtualKeyboard; bool _canHaveMultipleFrontends; int _externalType; appload::library::AspectRatio _aspectRatio; bool _disablesWindowedMode; + appload::vk::Layout const *_virtualKeyboardLayout; }; class AppLoadLibrary : public QObject { Q_OBJECT Q_PROPERTY(QQmlListProperty applications READ applications NOTIFY applicationsChanged) + Q_PROPERTY(const appload::vk::Layout *defaultLayout READ defaultLayout CONSTANT) public: explicit AppLoadLibrary(QObject *parent = nullptr) : QObject(parent) { appload::library::addGlobalLibraryHandle(this); } @@ -105,6 +131,8 @@ class AppLoadLibrary : public QObject { appload::library::terminateExternal(pid); } + const appload::vk::Layout *defaultLayout() const { return appload::library::defaultLayout; } + void loadList() { clearApplications(); for (const auto &entry : appload::library::getRef()) { @@ -116,6 +144,7 @@ class AppLoadLibrary : public QObject { INTERNAL, appload::library::AspectRatio::AUTO, false, + NULL, this)); } for (const auto &entry : appload::library::getExternals()) { @@ -127,6 +156,7 @@ class AppLoadLibrary : public QObject { entry.second->isQTFB() ? EXTERNAL_QTFB : EXTERNAL_NOGUI, entry.second->getAspectRatio(), entry.second->disablesWindowedMode(), + entry.second->getVirtualKeyboardLayout(), this)); } } diff --git a/src/keyboard/layout.cpp b/src/keyboard/layout.cpp new file mode 100644 index 0000000..525c065 --- /dev/null +++ b/src/keyboard/layout.cpp @@ -0,0 +1,55 @@ +#include "layout.h" +#include "../log.h" + +#include + +// Row 0 must not have any non-1 width keys! +int appload::vk::Layout::rows() const { return _layout.size(); } +int appload::vk::Layout::columns() const { return _layout.empty() ? 0 : _layout[0].size(); } +int appload::vk::Layout::columnsInRow(int r) const { return (int) _layout.size() <= r ? 0 : _layout[r].size(); } + +const appload::vk::KeyData *appload::vk::Layout::key(int coln, int rown) const { + if((int) _layout.size() <= rown) return NULL; + const auto &row = _layout[rown]; + return (int) row.size() <= coln ? NULL : &row[coln]; +} + +void appload::vk::Layout::load(const QByteArray &src) { + QJsonParseError err; + QJsonDocument doc = QJsonDocument::fromJson(src, &err); + if(err.error != QJsonParseError::NoError) { + QDEBUG << "Failed to load keyboard layout" << err.errorString() << "@" << err.offset; + return; + } + if(!doc.isArray()) { + QDEBUG << "Expected an array as the root object of keyboard layout"; + return; + } + QJsonArray jsonRows = doc.array(); + for(const auto &row : jsonRows) { + if(!row.isArray()) { + QDEBUG << "Expected the row to be an array of keys"; + _layout.erase(_layout.begin(), _layout.end()); + return; + } + auto &newRow = _layout.emplace_back(); + for(const auto &key : row.toArray()) { + if(!key.isArray()) { + QDEBUG << "Expected the key definition tso be an array"; + _layout.erase(_layout.begin(), _layout.end()); + return; + } + const auto &keyArr = key.toArray(); + int keyCode = keyArr[1].toInt(); + newRow.emplace_back( + keyArr[0].toString(), + keyCode, + keyArr[2].toString(), + keyArr[3].toInt(), + keyArr[4].toInt(1), + keyCode >= 0x100000, + this + ); + } + } +} diff --git a/src/keyboard/layout.h b/src/keyboard/layout.h new file mode 100644 index 0000000..1126c54 --- /dev/null +++ b/src/keyboard/layout.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +namespace appload::vk { +class KeyData : public QObject{ + Q_OBJECT + Q_PROPERTY(QString label READ label CONSTANT) + Q_PROPERTY(int code READ code CONSTANT) + Q_PROPERTY(QString altLabel READ altLabel CONSTANT) + Q_PROPERTY(int altCode READ altCode CONSTANT) + Q_PROPERTY(int width READ width CONSTANT) + Q_PROPERTY(bool isModifier READ isModifier CONSTANT) +public: + QString label() const { return _label; } + QString altLabel() const { return _altLabel; } + int code() const { return _code; } + int altCode() const { return _altCode; } + int width() const { return _width; } + bool isModifier() const { return _isModifier; } + explicit KeyData(QObject *parent = nullptr): QObject(parent) {} + + KeyData(QString label, int code, QString altLabel, int altCode, int width, int isModifier, QObject *parent): + QObject(parent), + _label(label), + _code(code), + _altLabel(altLabel), + _altCode(altCode), + _width(width), + _isModifier(isModifier) {} + + KeyData(const appload::vk::KeyData& copy): + QObject(copy.parent()), + _label(copy._label), + _code(copy._code), + _altLabel(copy._altLabel), + _altCode(copy._altCode), + _width(copy._width), + _isModifier(copy._isModifier) {} + + +private: + QString _label; + int _code; + QString _altLabel; + int _altCode; + int _width; + bool _isModifier; +}; + +class Layout : public QObject{ + Q_OBJECT + // No `NOTIFY'. Once initialized, the Layout object is static. + Q_PROPERTY(int columns READ columns CONSTANT) + Q_PROPERTY(int rows READ rows CONSTANT) +public: + explicit Layout(QObject *parent = 0): QObject(parent) {}; + void load(const QByteArray &source); + + Q_INVOKABLE const KeyData *key(int col, int row) const; + Q_INVOKABLE int columnsInRow(int row) const; + int columns() const; + int rows() const; +private: + std::vector> _layout; +}; + +} \ No newline at end of file diff --git a/src/library.cpp b/src/library.cpp index 29b4b1f..85184f1 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -152,9 +152,17 @@ appload::library::LoadedApplication::~LoadedApplication() { } } static std::mutex loadingMutex; +appload::vk::Layout *appload::library::defaultLayout = NULL; int appload::library::loadApplications() { loadingMutex.lock(); + defaultLayout = new appload::vk::Layout(NULL); + QResource defaultKeyboardLayout(QString(":/appload/keyboard/default.layout.json")); + if(!defaultKeyboardLayout.isValid()) { + QDEBUG << "Invalid data in default layout!"; + } else { + defaultLayout->load(defaultKeyboardLayout.uncompressedData()); + } // Make sure all apps are unloaded: for(auto entry : appload::library::applications) { if(entry.second->isFrontendRunning()) { diff --git a/src/library.h b/src/library.h index 4441967..fdcffd4 100644 --- a/src/library.h +++ b/src/library.h @@ -30,6 +30,7 @@ #include #include #include "config.h" +#include "keyboard/layout.h" class AppLoadLibrary; @@ -38,6 +39,7 @@ namespace appload::library { ORIGINAL, MOVE, AUTO, }; QString aspectRatioToString(AspectRatio ratio); + extern appload::vk::Layout *defaultLayout; class ExternalApplication { public: @@ -48,6 +50,8 @@ namespace appload::library { bool isQTFB() const; AspectRatio getAspectRatio() const; bool disablesWindowedMode() const; + bool supportsVirtualKeyboard() const; + const appload::vk::Layout *getVirtualKeyboardLayout() const; bool valid = false; @@ -62,6 +66,7 @@ namespace appload::library { std::map environment; bool _isQTFB; bool _disablesWindowedMode; + const appload::vk::Layout *_virtualKeyboardLayout; AspectRatio aspectRatio; void parseManifest(); diff --git a/src/libraryexternals.cpp b/src/libraryexternals.cpp index 64318fa..1888f9d 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,10 @@ void appload::library::ExternalApplication::parseManifest() { // Optional: _isQTFB = jsonObject.value("qtfb").toBool(false); _disablesWindowedMode = jsonObject.value("disablesWindowedMode").toBool(false); + bool supportsVirtualKeyboard = jsonObject.value("supportsVirtualKeyboard").toBool(false); + if(supportsVirtualKeyboard) { + this->_virtualKeyboardLayout = appload::library::defaultLayout; + } workingDirectory = jsonObject.value("workingDirectory").toString(root); args = jsonObject.value("args").toVariant().toStringList(); QJsonObject env = jsonObject.value("environment").toObject(); @@ -97,7 +99,6 @@ qint64 appload::library::ExternalApplication::launch(int qtfbKey, QStringList ex process->setWorkingDirectory(workingDirectory); process->setProcessChannelMode(QProcess::ForwardedChannels); QStringList finalArgs = args + extraArgs; - for(const auto &arg : extraArgs) finalArgs.append(arg); process->start(execPath, finalArgs); if (!process->waitForStarted()) { @@ -142,6 +143,10 @@ bool appload::library::ExternalApplication::disablesWindowedMode() const { return _disablesWindowedMode; } +const appload::vk::Layout *appload::library::ExternalApplication::getVirtualKeyboardLayout() const { + return _virtualKeyboardLayout; +} + 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..4f4a2ba 100644 --- a/src/qtfb/common.h +++ b/src/qtfb/common.h @@ -50,10 +50,26 @@ #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 +#define INPUT_VKB_SHIFTMOD 0x100000 +#define INPUT_VKB_CTRLMOD 0x200000 +#define INPUT_VKB_ALTMOD 0x400000 +#define INPUT_VKB_DEL 0x7f +#define INPUT_VKB_PGUP 0x80 +#define INPUT_VKB_PGDOWN 0x81 +#define INPUT_VKB_DOWN 0x82 +#define INPUT_VKB_UP 0x83 +#define INPUT_VKB_LEFT 0x84 +#define INPUT_VKB_RIGHT 0x85 +#define INPUT_VKB_HOME 0x86 +#define INPUT_VKB_END 0x87 + namespace qtfb { typedef int FBKey; diff --git a/xovi/make.sh b/xovi/make.sh old mode 100644 new mode 100755 diff --git a/xovi/template/appload.pro b/xovi/template/appload.pro index 7c8c9ed..2f9c00b 100644 --- a/xovi/template/appload.pro +++ b/xovi/template/appload.pro @@ -4,7 +4,7 @@ TARGET = appload TEMPLATE = lib CONFIG += shared plugin no_plugin_name_prefix -SOURCES += src/main.cpp xovi.cpp src/management.cpp src/AppLoad.cpp src/AppLoadCoordinator.cpp src/library.cpp src/libraryexternals.cpp src/qtfb/fbmanagement.cpp src/qtfb/FBController.cpp +SOURCES += src/main.cpp xovi.cpp src/management.cpp src/AppLoad.cpp src/AppLoadCoordinator.cpp src/library.cpp src/libraryexternals.cpp src/qtfb/fbmanagement.cpp src/qtfb/FBController.cpp src/keyboard/layout.cpp HEADERS += src/AppLoad.h src/AppLoadCoordinator.h src/library.h src/AppLibrary.h \ - src/qtfb/FBController.h src/qtfb/fbmanagement.h src/Launcher.h + src/qtfb/FBController.h src/qtfb/fbmanagement.h src/keyboard/layout.h src/Launcher.h diff --git a/xovi/template/appload.qmd b/xovi/template/appload.qmd index bd0df1c..40a5ac3 100644 --- a/xovi/template/appload.qmd +++ b/xovi/template/appload.qmd @@ -25,7 +25,8 @@ AFFECT [[8850026134298937527]] ~&7083172477658&~: "qrc:/appload/qml/appload.qml" ~&7713401454751279&~: { - _appLoadView.~&6503936152&~.absoluteRoot = ~&6504254477&~; + _appLoadView.~&6503936152&~.absoluteRoot = windowParent; + _appLoadView.item.virtualKeyboardRef = _apploadVirtualKeyboard; } } @@ -37,6 +38,30 @@ AFFECT [[8850026134298937527]] } } } + LOCATE AFTER ALL + INSERT { + Item { + id: windowParent + anchors.fill: parent + } + Loader { + id: _apploadVirtualKeyboard + + property var layout: null + property var config: null + width: parent.width + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + active: false + visible: active + source: "qrc:/appload/qml/virtualKeyboard/Keyboard.qml" + + onLoaded: () => { + console.log("Rebuild keyboard data: ", _apploadVirtualKeyboard.layout, _apploadVirtualKeyboard.config); + _apploadVirtualKeyboard.item.rebuildKeyboard(_apploadVirtualKeyboard.layout, _apploadVirtualKeyboard.config); + } + } + } END TRAVERSE END AFFECT diff --git a/xovi/template/src/main.cpp b/xovi/template/src/main.cpp index c343994..a744b8c 100644 --- a/xovi/template/src/main.cpp +++ b/xovi/template/src/main.cpp @@ -20,8 +20,6 @@ extern "C" { static const char *applicationRoot; void _xovi_construct() { applicationRoot = Environment->getExtensionDirectory("appload"); - appload::library::loadApplications(); - qtfb::management::start(); qmlRegisterType("net.asivery.AppLoad", 1, 0, "AppLoad"); qmlRegisterType("net.asivery.AppLoad", 1, 0, "AppLoadCoordinator"); @@ -40,6 +38,9 @@ extern "C" { qt_resource_rebuilder$qmldiff_disable_slots_while_processing(); qRegisterResourceData(3, qt_resource_struct, qt_resource_name, qt_resource_data); qt_resource_rebuilder$qmldiff_enable_slots_while_processing(); + + appload::library::loadApplications(); + qtfb::management::start(); } }