diff --git a/clj-rn.conf.edn b/clj-rn.conf.edn index 5578a87075..53cb07e83d 100644 --- a/clj-rn.conf.edn +++ b/clj-rn.conf.edn @@ -74,6 +74,7 @@ "react-native-desktop-linking" "react-native-desktop-menu" "react-native-desktop-config" + "react-native-desktop-shortcuts" "react-native-desktop-notification" "text-encoding" "js-sha3" diff --git a/desktop_files/package.json.orig b/desktop_files/package.json.orig index d2e1e95a12..5b94632072 100644 --- a/desktop_files/package.json.orig +++ b/desktop_files/package.json.orig @@ -18,6 +18,7 @@ "modules/react-native-desktop-linking/desktop", "modules/react-native-desktop-menu/desktop", "modules/react-native-desktop-config/desktop", + "modules/react-native-desktop-shortcuts/desktop", "modules/react-native-desktop-notification/desktop", "node_modules/google-breakpad" ], diff --git a/modules/react-native-desktop-shortcuts/desktop/CMakeLists.txt b/modules/react-native-desktop-shortcuts/desktop/CMakeLists.txt new file mode 100755 index 0000000000..7d7cd958bf --- /dev/null +++ b/modules/react-native-desktop-shortcuts/desktop/CMakeLists.txt @@ -0,0 +1,9 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_TYPE_NAMES} + \"DesktopShortcuts\" PARENT_SCOPE) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC ${REACT_NATIVE_DESKTOP_EXTERNAL_MODULES_SRC} + ${CMAKE_CURRENT_SOURCE_DIR}/desktopshortcuts.cpp PARENT_SCOPE) + +include(${CMAKE_ROOT}/Modules/ExternalProject.cmake) diff --git a/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.cpp b/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.cpp new file mode 100644 index 0000000000..8b4a63dbb4 --- /dev/null +++ b/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.cpp @@ -0,0 +1,83 @@ +#include "desktopshortcuts.h" +#include "bridge.h" + +#include "eventdispatcher.h" +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(DESKTOPSHORTCUTS, "DesktopShortcuts") + +namespace { +struct RegisterQMLMetaType { + RegisterQMLMetaType() { qRegisterMetaType(); } +} registerMetaType; +} // namespace + +DesktopShortcuts::DesktopShortcuts(QObject *parent) + : QObject(parent) { + QCoreApplication::instance()->installEventFilter(this); + connect(this, &DesktopShortcuts::shortcutInvoked, this, &DesktopShortcuts::onShortcutInvoked); +} + +DesktopShortcuts::~DesktopShortcuts() { +} + +void DesktopShortcuts::setBridge(Bridge *bridge) { + this->bridge = bridge; +} + +QString DesktopShortcuts::moduleName() { return "DesktopShortcutsManager"; } + +QList DesktopShortcuts::methodsToExport() { + return QList{}; +} + +QVariantMap DesktopShortcuts::constantsToExport() { return QVariantMap(); } + +void DesktopShortcuts::registerShortcuts(const QStringList& shortcuts) { + //qCDebug(DESKTOPSHORTCUTS) << "registerShortcuts" << shortcuts << " " << shortcuts.size(); + this->registeredShortcuts = shortcuts; +} + +bool DesktopShortcuts::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* ke = static_cast(event); + + QString modifier; + + if (ke->modifiers() & Qt::ShiftModifier) { + modifier += "Shift+"; + } + if (ke->modifiers() & Qt::ControlModifier) { + modifier += "Ctrl+"; + } + if (ke->modifiers() & Qt::AltModifier) { + modifier += "Alt+"; + } + if (ke->modifiers() & Qt::MetaModifier) { + modifier += "Meta+"; + } + QString key = QKeySequence(ke->key()).toString(); + + //qCDebug(DESKTOPSHORTCUTS) << "### arrow " << key; + if (registeredShortcuts.contains(modifier+key)) { + emit shortcutInvoked(modifier+key); + return true; + } + else { + return false; + } + } + else { + return QObject::eventFilter(obj, event); + } +} + +void DesktopShortcuts::onShortcutInvoked(const QString& shortcut) { + //qCDebug(DESKTOPSHORTCUTS) << "onShortcutInvoked " << shortcut << " " << registeredShortcuts.size(); + bridge->eventDispatcher()->sendDeviceEvent("shortcutInvoked", QVariantList{shortcut}); +} + diff --git a/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.h b/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.h new file mode 100644 index 0000000000..58cfb95635 --- /dev/null +++ b/modules/react-native-desktop-shortcuts/desktop/desktopshortcuts.h @@ -0,0 +1,41 @@ +#ifndef DESKTOPSHORTCUTS_H +#define DESKTOPSHORTCUTS_H + +#include "moduleinterface.h" + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(SHORTCUTS) + +class DesktopShortcutsPrivate; +class DesktopShortcuts : public QObject, public ModuleInterface { + Q_OBJECT + Q_INTERFACES(ModuleInterface) + +public: + Q_INVOKABLE DesktopShortcuts(QObject* parent = 0); + virtual ~DesktopShortcuts(); + + void setBridge(Bridge* bridge) override; + + QString moduleName() override; + QList methodsToExport() override; + QVariantMap constantsToExport() override; + + Q_INVOKABLE void registerShortcuts(const QStringList& shortcuts); + +signals: + void shortcutInvoked(const QString& shortcut); + +public slots: + void onShortcutInvoked(const QString& shortcut); + +private: + Bridge* bridge; + + QStringList registeredShortcuts; + bool eventFilter(QObject* obj, QEvent* event) override; +}; + +#endif // DESKTOPSHORTCUTS_H diff --git a/modules/react-native-desktop-shortcuts/index.js b/modules/react-native-desktop-shortcuts/index.js new file mode 100644 index 0000000000..06a0f56fda --- /dev/null +++ b/modules/react-native-desktop-shortcuts/index.js @@ -0,0 +1,44 @@ +'use strict'; + +const NativeModules = require('react-native').NativeModules; +const NativeEventEmitter = require('react-native').NativeEventEmitter; + +type Shortcuts = Array<{ + shortcut?: string, + onPress?: ?Function, +}>; + +class DesktopShortcuts { + constructor() { + this.shortcuts = new Map(); + this.eventEmitter = new NativeEventEmitter(NativeModules.DesktopShortcutsManager); + this.eventEmitter.addListener('shortcutInvoked', this.handleShortcut.bind(this)); + } + + handleShortcut(shortcut) { + var fn;// = this.shortcuts.get(shortcut); + for (var [key, value] of this.shortcuts) { + if (shortcut == key) { + fn = value; + break; + } + } + if (fn) { + fn(); + }; + } + + register(shortcuts: Shortcuts): void { + //console.log('### register(shortcuts)' + JSON.stringify(shortcuts)); + this.shortcuts = new Map(); + + var shortcutKeys = shortcuts.map(s => s.shortcut); + for (let i = 0; i < shortcuts.length; ++i) { + this.shortcuts.set(shortcuts[i].shortcut, shortcuts[i].onPress); + } + + NativeModules.DesktopShortcutsManager.registerShortcuts(shortcutKeys); + } +} + +module.exports = new DesktopShortcuts(); diff --git a/modules/react-native-desktop-shortcuts/package.json b/modules/react-native-desktop-shortcuts/package.json new file mode 100644 index 0000000000..75bf6363e7 --- /dev/null +++ b/modules/react-native-desktop-shortcuts/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "nativePackage": true, + "name": "react-native-desktop-shortcuts", + "version": "1.0.0", + "description": "App-global keyboard shortcuts for Desktop", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "" +} diff --git a/react-native/src/desktop/status_im/react_native/js_dependencies.cljs b/react-native/src/desktop/status_im/react_native/js_dependencies.cljs index d6e6eb6cba..2706233c79 100644 --- a/react-native/src/desktop/status_im/react_native/js_dependencies.cljs +++ b/react-native/src/desktop/status_im/react_native/js_dependencies.cljs @@ -15,7 +15,7 @@ (def desktop-linking (.-DesktopLinking (.-NativeModules react-native))) (def desktop-menu (js/require "react-native-desktop-menu")) (def desktop-config (js/require "react-native-desktop-config")) - +(def desktop-shortcuts (js/require "react-native-desktop-shortcuts")) (def react-native-firebase #js {}) (def camera #js {:default #js {:constants {:Aspect "Portrait"}}}) (def status-keycard #js {:default #js {}}) diff --git a/react-native/src/mobile/status_im/react_native/js_dependencies.cljs b/react-native/src/mobile/status_im/react_native/js_dependencies.cljs index 78da0cf732..5fe9e647dd 100644 --- a/react-native/src/mobile/status_im/react_native/js_dependencies.cljs +++ b/react-native/src/mobile/status_im/react_native/js_dependencies.cljs @@ -29,3 +29,4 @@ (def desktop-linking #js {:addEventListener (fn [])}) (def desktop-menu #js {:addEventListener (fn [])}) (def desktop-config #js {:addEventListener (fn [])}) +(def desktop-shortcuts #js {:addEventListener (fn [])}) diff --git a/scripts/build-desktop.sh b/scripts/build-desktop.sh index 7bfe66d1ed..feef0a458e 100755 --- a/scripts/build-desktop.sh +++ b/scripts/build-desktop.sh @@ -34,6 +34,7 @@ external_modules_dir=( \ 'modules/react-native-desktop-linking/desktop' \ 'modules/react-native-desktop-menu/desktop' \ 'modules/react-native-desktop-config/desktop' \ + 'modules/react-native-desktop-shortcuts/desktop' \ 'modules/react-native-desktop-notification/desktop' \ ) diff --git a/src/status_im/desktop/core.cljs b/src/status_im/desktop/core.cljs index 7a53916b48..2618014c2f 100644 --- a/src/status_im/desktop/core.cljs +++ b/src/status_im/desktop/core.cljs @@ -7,6 +7,7 @@ status-im.ui.screens.subs status-im.data-store.core [reagent.impl.component :as reagent.component] + [status-im.ui.components.desktop.shortcuts :as shortcuts] [status-im.ui.screens.desktop.views :as views] [status-im.core :as core] [status-im.desktop.deep-links :as deep-links])) @@ -15,6 +16,7 @@ (reagent/create-class {:component-did-mount (fn [this] (re-frame/dispatch [:set-initial-props (reagent/props this)]) + (shortcuts/register-default-shortcuts) (deep-links/add-event-listener)) :reagent-render (fn [props] views/main)})) diff --git a/src/status_im/ui/components/desktop/shortcuts.cljs b/src/status_im/ui/components/desktop/shortcuts.cljs new file mode 100644 index 0000000000..10e30781bc --- /dev/null +++ b/src/status_im/ui/components/desktop/shortcuts.cljs @@ -0,0 +1,23 @@ +(ns status-im.ui.components.desktop.shortcuts + (:require [status-im.react-native.js-dependencies :refer [desktop-shortcuts]] + [status-im.ui.screens.desktop.main.tabs.home.views :as chat-list] + [re-frame.core :as re-frame] + [taoensso.timbre :as log] + [status-im.utils.utils :as utils])) + +(defn register-shortcut [shortcut on-press] + (.set desktop-shortcuts (clj->js {:shortcut shortcut + :onPress on-press}))) + +(defn register-default-shortcuts [] + (.register desktop-shortcuts + (clj->js (vector + {:shortcut "Ctrl+N" + :onPress #(re-frame/dispatch [:navigate-to :desktop/new-one-to-one])} + {:shortcut "Ctrl+G" + :onPress #(re-frame/dispatch [:navigate-to :desktop/new-group-chat])} + {:shortcut "Ctrl+P" + :onPress #(re-frame/dispatch [:navigate-to :desktop/new-public-chat])} + {:shortcut "Ctrl+F" + :onPress #(utils/show-popup "" "Ctrl+F")})))) + diff --git a/src/status_im/ui/screens/desktop/main/add_new/views.cljs b/src/status_im/ui/screens/desktop/main/add_new/views.cljs index 13a31772f8..611905b549 100644 --- a/src/status_im/ui/screens/desktop/main/add_new/views.cljs +++ b/src/status_im/ui/screens/desktop/main/add_new/views.cljs @@ -45,6 +45,7 @@ (when show-error-tooltip? [error-tooltip chat-error]) [react/text-input {:placeholder "name.stateofus.eth" + :auto-focus true :flex 1 :selection-color colors/blue :font :default @@ -108,6 +109,7 @@ [error-tooltip topic-error]) [react/text-input {:flex 1 + :auto-focus true :font :default :selection-color colors/blue :placeholder "" diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index f16864b5e4..95a2e2fc1c 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -33,6 +33,7 @@ (def react-native-firebase #js {:default #js {:notifications #js {:Notification Notification}}}) (def desktop-linking #js {:addEventListener (fn [])}) +(def desktop-shortcuts #js {:addEventListener (fn [])}) (def snoopy #js {:default #js {}}) (def snoopy-filter #js {:default #js {}})