Browse Source

wallet: add configurable shortcuts and make enter send the text loool

darkfi 1 year ago
parent
commit
3edeafce70

+ 6 - 0
bin/darkwallet/src/app/mod.rs

@@ -45,6 +45,8 @@ mod node;
 use node::create_darkirc;
 mod schema;
 
+const PLUGINS_ENABLED: bool = true;
+
 //fn print_type_of<T>(_: &T) {
 //    println!("{}", std::any::type_name::<T>())
 //}
@@ -169,6 +171,10 @@ impl App {
         let plugin = Arc::new(SceneNode3::new("plugin", SceneNodeType3::PluginRoot));
         self.sg_root.clone().link(plugin.clone());
 
+        if !PLUGINS_ENABLED {
+            return
+        }
+
         let darkirc = create_darkirc("darkirc");
         let darkirc = darkirc
             .setup(|me| async {

+ 16 - 2
bin/darkwallet/src/app/node.rs

@@ -92,6 +92,22 @@ pub fn create_button(name: &str) -> SceneNode {
     node
 }
 
+pub fn create_shortcut(name: &str) -> SceneNode {
+    debug!(target: "app", "create_shortcut({name})");
+    let mut node = SceneNode::new(name, SceneNodeType::Shortcut);
+
+    let mut prop = Property::new("key", PropertyType::Str, PropertySubType::Null);
+    prop.allow_null_values();
+    node.add_property(prop).unwrap();
+
+    let prop = Property::new("priority", PropertyType::Uint32, PropertySubType::Null);
+    node.add_property(prop).unwrap();
+
+    node.add_signal("shortcut", "Shortcut triggered", vec![]).unwrap();
+
+    node
+}
+
 pub fn create_image(name: &str) -> SceneNode {
     debug!(target: "app", "create_image({name})");
     let mut node = SceneNode::new(name, SceneNodeType::Image);
@@ -262,8 +278,6 @@ pub fn create_editbox(name: &str) -> SceneNode {
     let prop = Property::new("debug", PropertyType::Bool, PropertySubType::Null);
     node.add_property(prop).unwrap();
 
-    node.add_signal("enter_pressed", "Enter key pressed", vec![]).unwrap();
-
     node
 }
 

+ 60 - 23
bin/darkwallet/src/app/schema/chat.rs

@@ -25,7 +25,7 @@ use crate::{
     app::{
         node::{
             create_button, create_chatedit, create_chatview, create_editbox, create_emoji_picker,
-            create_image, create_layer, create_text, create_vector_art,
+            create_image, create_layer, create_shortcut, create_text, create_vector_art,
         },
         populate_tree, App,
     },
@@ -42,7 +42,7 @@ use crate::{
     text::TextShaperPtr,
     ui::{
         emoji_picker, Button, ChatEdit, ChatView, EditBox, EmojiPicker, Image, Layer, ShapeVertex,
-        Text, VectorArt, VectorShape, Window,
+        Shortcut, Text, VectorArt, VectorShape, Window,
     },
     util::unixtime,
     ExecutorPtr,
@@ -881,19 +881,24 @@ pub async fn make(
     prop.set_f32(Role::App, 2, SENDBTN_BOX[2]).unwrap();
     prop.set_f32(Role::App, 3, SENDBTN_BOX[3]).unwrap();
 
-    let (slot, recvr) = Slot::new("send_clicked");
-    node.register("click", slot).unwrap();
-    let editz_text2 = editz_text.clone();
-    let channel2 = channel.to_string();
-    let sg_root2 = app.sg_root.clone();
-    let listen_click = app.ex.spawn(async move {
-        let channel = format!("#{channel2}");
-        while let Ok(_) = recvr.recv().await {
-            let text = editz_text2.get();
-            info!(target: "app::chat", "Send '{text}' to channel: #{channel2}");
-            editz_text2.set("");
+    #[derive(Clone)]
+    struct SendMsg {
+        channel: String,
+        editz_text: PropertyStr,
+        sg_root: SceneNodePtr,
+        chatview_node: SceneNodePtr,
+    }
+
+    impl SendMsg {
+        async fn send(&self) {
+            let text = self.editz_text.get();
+            info!(target: "app::chat", "Send '{text}' to channel: #{}", self.channel);
+            self.editz_text.set("");
 
-            let darkirc = sg_root2.clone().lookup_node("/plugin/darkirc").unwrap();
+            let Some(darkirc) = self.sg_root.clone().lookup_node("/plugin/darkirc") else {
+                error!(target: "app::chat", "DarkIrc plugin has not been loaded");
+                return
+            };
 
             if text.starts_with("/nick") {
                 let nick = text.split_whitespace().nth(1).unwrap_or("anon");
@@ -908,21 +913,21 @@ pub async fn make(
                 id.encode(&mut data).unwrap();
                 "NOTICE".encode(&mut data).unwrap();
                 msg.encode(&mut data).unwrap();
-                chatview_node.call_method("insert_line", data).await.unwrap();
+                self.chatview_node.call_method("insert_line", data).await.unwrap();
 
-                continue
+                return
             }
 
             let timest = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
             let nick = darkirc.get_property_str("nick").unwrap();
-            let msg = darkirc::Privmsg::new(channel.clone(), nick, text);
+            let msg = darkirc::Privmsg::new(self.channel.clone(), nick, text);
 
-            let mut data = vec![];
-            timest.encode(&mut data).unwrap();
-            msg.msg_id(timest).encode(&mut data).unwrap();
-            msg.nick.encode(&mut data).unwrap();
-            msg.msg.encode(&mut data).unwrap();
-            chatview_node.call_method("insert_unconf_line", data).await.unwrap();
+            //let mut data = vec![];
+            //timest.encode(&mut data).unwrap();
+            //msg.msg_id(timest).encode(&mut data).unwrap();
+            //msg.nick.encode(&mut data).unwrap();
+            //msg.msg.encode(&mut data).unwrap();
+            //chatview_node.call_method("insert_unconf_line", data).await.unwrap();
 
             let mut data = vec![];
             timest.encode(&mut data).unwrap();
@@ -930,12 +935,44 @@ pub async fn make(
             msg.msg.encode(&mut data).unwrap();
             darkirc.call_method("send", data).await.unwrap();
         }
+    }
+
+    let sendmsg = SendMsg {
+        editz_text: editz_text.clone(),
+        channel: format!("#{channel}"),
+        sg_root: app.sg_root.clone(),
+        chatview_node,
+    };
+
+    let (slot, recvr) = Slot::new("send_clicked");
+    node.register("click", slot).unwrap();
+    let sendmsg2 = sendmsg.clone();
+    let listen_click = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            sendmsg2.send().await;
+        }
     });
     app.tasks.lock().unwrap().push(listen_click);
 
     let node = node.setup(|me| Button::new(me, app.ex.clone())).await;
     layer_node.clone().link(node);
 
+    // Create shortcut to send as well
+    let node = create_shortcut("send_shortcut");
+    node.set_property_str(Role::App, "key", "enter").unwrap();
+
+    let (slot, recvr) = Slot::new("enter_pressed");
+    node.register("shortcut", slot).unwrap();
+    let listen_enter = app.ex.spawn(async move {
+        while let Ok(_) = recvr.recv().await {
+            sendmsg.send().await;
+        }
+    });
+    app.tasks.lock().unwrap().push(listen_enter);
+
+    let node = node.setup(|me| Shortcut::new(me)).await;
+    layer_node.clone().link(node);
+
     // Create the emoji button
     let node = create_button("emoji_btn");
     node.set_property_bool(Role::App, "is_active", true).unwrap();

+ 6 - 2
bin/darkwallet/src/logger.rs

@@ -6,7 +6,7 @@ use simplelog::{
 };
 use std::{path::PathBuf, thread::sleep, time::Duration};
 
-const LOGS_ENABLED: bool = true;
+const LOGS_ENABLED: bool = false;
 // Measured in bytes
 const LOGFILE_MAXSIZE: usize = 5_000_000;
 
@@ -43,7 +43,11 @@ mod android {
     impl Log for AndroidLoggerWrapper {
         fn enabled(&self, metadata: &Metadata<'_>) -> bool {
             let target = metadata.target();
-            if target.starts_with("sled") || target.starts_with("rustls") {
+            if target.starts_with("sled") ||
+                target.starts_with("rustls") ||
+                target.starts_with("net::") ||
+                target.starts_with("event_graph")
+            {
                 return false
             }
             if metadata.level() > self.level {

+ 3 - 1
bin/darkwallet/src/scene.rs

@@ -114,7 +114,8 @@ pub enum SceneNodeType {
     ChatEdit = 18,
     Image = 19,
     Button = 20,
-    EmojiPicker = 21,
+    Shortcut = 21,
+    EmojiPicker = 22,
     PluginRoot = 100,
     Plugin = 101,
 }
@@ -476,6 +477,7 @@ pub enum Pimpl {
     ChatView(ui::ChatViewPtr),
     Image(ui::ImagePtr),
     Button(ui::ButtonPtr),
+    Shortcut(ui::ShortcutPtr),
     EmojiPicker(ui::EmojiPickerPtr),
     DarkIrc(plugin::DarkIrcPtr),
 }

+ 60 - 16
bin/darkwallet/src/ui/chatedit.rs

@@ -1162,7 +1162,7 @@ impl ChatEdit {
         false
     }
 
-    async fn handle_key(&self, key: &KeyCode, mods: &KeyMods) {
+    async fn handle_key(&self, key: &KeyCode, mods: &KeyMods) -> bool {
         debug!(target: "ui::chatedit", "handle_key({:?}, {:?})", key, mods);
         match key {
             KeyCode::Left => {
@@ -1170,54 +1170,95 @@ impl ChatEdit {
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
+                return true
             }
             KeyCode::Right => {
                 self.adjust_cursor(mods.shift, |editable| editable.move_cursor(1));
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
+                return true
+            }
+            KeyCode::Kp0 => {
+                self.insert_char('0').await;
+                return true
+            }
+            KeyCode::Kp1 => {
+                self.insert_char('1').await;
+                return true
+            }
+            KeyCode::Kp2 => {
+                self.insert_char('2').await;
+                return true
+            }
+            KeyCode::Kp3 => {
+                self.insert_char('3').await;
+                return true
+            }
+            KeyCode::Kp4 => {
+                self.insert_char('4').await;
+                return true
+            }
+            KeyCode::Kp5 => {
+                self.insert_char('5').await;
+                return true
+            }
+            KeyCode::Kp6 => {
+                self.insert_char('6').await;
+                return true
+            }
+            KeyCode::Kp7 => {
+                self.insert_char('7').await;
+                return true
+            }
+            KeyCode::Kp8 => {
+                self.insert_char('8').await;
+                return true
+            }
+            KeyCode::Kp9 => {
+                self.insert_char('9').await;
+                return true
+            }
+            KeyCode::KpDecimal => {
+                self.insert_char('.').await;
+                return true
             }
-            KeyCode::Kp0 => self.insert_char('0').await,
-            KeyCode::Kp1 => self.insert_char('1').await,
-            KeyCode::Kp2 => self.insert_char('2').await,
-            KeyCode::Kp3 => self.insert_char('3').await,
-            KeyCode::Kp4 => self.insert_char('4').await,
-            KeyCode::Kp5 => self.insert_char('5').await,
-            KeyCode::Kp6 => self.insert_char('6').await,
-            KeyCode::Kp7 => self.insert_char('7').await,
-            KeyCode::Kp8 => self.insert_char('8').await,
-            KeyCode::Kp9 => self.insert_char('9').await,
-            KeyCode::KpDecimal => self.insert_char('.').await,
             KeyCode::Enter | KeyCode::KpEnter => {
-                let node = self.node.upgrade().unwrap();
-                node.trigger("enter_pressed", vec![]).await.unwrap();
+                if mods.shift {
+                    // Does nothing for now. Later will enable multiline.
+                }
             }
             KeyCode::Delete => {
                 self.delete(0, 1);
                 self.clamp_scroll(&mut self.text_wrap.lock());
                 self.pause_blinking();
                 self.redraw().await;
+                return true
             }
             KeyCode::Backspace => {
                 self.delete(1, 0);
                 self.clamp_scroll(&mut self.text_wrap.lock());
                 self.pause_blinking();
                 self.redraw().await;
+                return true
             }
             KeyCode::Home => {
                 self.adjust_cursor(mods.shift, |editable| editable.move_start());
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
+                return true
             }
             KeyCode::End => {
                 self.adjust_cursor(mods.shift, |editable| editable.move_end());
                 self.pause_blinking();
                 //self.apply_cursor_scrolling();
                 self.redraw().await;
+                return true
             }
             _ => {}
         }
+        false
     }
 
     fn delete(&self, before: usize, after: usize) {
@@ -1946,10 +1987,13 @@ impl UIObject for ChatEdit {
         /*if actions > 0 {
             debug!(target: "ui::chatedit", "Key {:?} has {} actions", key, actions);
         }*/
+        let mut is_handled = false;
         for _ in 0..actions {
-            self.handle_key(&key, &mods).await;
+            if self.handle_key(&key, &mods).await {
+                is_handled = true;
+            }
         }
-        true
+        is_handled
     }
 
     async fn handle_mouse_btn_down(&self, btn: MouseButton, mut mouse_pos: Point) -> bool {

+ 6 - 2
bin/darkwallet/src/ui/mod.rs

@@ -49,6 +49,8 @@ pub use vector_art::{
 };
 mod layer;
 pub use layer::{Layer, LayerPtr};
+mod shortcut;
+pub use shortcut::{Shortcut, ShortcutPtr};
 mod text;
 pub use text::{Text, TextPtr};
 mod win;
@@ -188,7 +190,8 @@ pub fn get_ui_object_ptr(node: &SceneNode3) -> Arc<dyn UIObject + Send> {
         Pimpl::Image(obj) => obj.clone(),
         Pimpl::Button(obj) => obj.clone(),
         Pimpl::EmojiPicker(obj) => obj.clone(),
-        _ => panic!("unhandled type for get_ui_object"),
+        Pimpl::Shortcut(obj) => obj.clone(),
+        _ => panic!("unhandled type for get_ui_object: {node:?}"),
     }
 }
 pub fn get_ui_object3<'a>(node: &'a SceneNode3) -> &'a dyn UIObject {
@@ -202,7 +205,8 @@ pub fn get_ui_object3<'a>(node: &'a SceneNode3) -> &'a dyn UIObject {
         Pimpl::Image(obj) => obj.as_ref(),
         Pimpl::Button(obj) => obj.as_ref(),
         Pimpl::EmojiPicker(obj) => obj.as_ref(),
-        _ => panic!("unhandled type for get_ui_object"),
+        Pimpl::Shortcut(obj) => obj.as_ref(),
+        _ => panic!("unhandled type for get_ui_object: {node:?}"),
     }
 }
 

+ 235 - 0
bin/darkwallet/src/ui/shortcut.rs

@@ -0,0 +1,235 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use async_trait::async_trait;
+use miniquad::{KeyCode, KeyMods};
+use std::sync::Arc;
+
+use crate::{
+    prop::{
+        PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+        PropertyUint32, Role,
+    },
+    scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
+};
+
+use super::UIObject;
+
+fn vec_to_string(v: Vec<&str>) -> Vec<String> {
+    v.into_iter().map(|s| s.to_string()).collect()
+}
+
+pub type ShortcutPtr = Arc<Shortcut>;
+
+pub struct Shortcut {
+    node: SceneNodeWeak,
+    key: PropertyPtr,
+    priority: PropertyUint32,
+}
+
+impl Shortcut {
+    pub async fn new(node: SceneNodeWeak) -> Pimpl {
+        debug!(target: "ui::button", "Button::new()");
+
+        let node_ref = &node.upgrade().unwrap();
+        let key = node_ref.get_property("key").unwrap();
+        let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
+
+        let self_ = Arc::new(Self { node, key, priority });
+
+        Pimpl::Shortcut(self_)
+    }
+
+    fn get_key_combo(&self) -> Option<Vec<String>> {
+        let Ok(key) = self.key.get_str(0) else { return None };
+        let keys: Vec<&str> = key.split('+').collect();
+        Some(vec_to_string(keys))
+    }
+}
+
+#[async_trait]
+impl UIObject for Shortcut {
+    fn priority(&self) -> u32 {
+        self.priority.get()
+    }
+
+    async fn handle_key_down(&self, key: KeyCode, mods: KeyMods, repeat: bool) -> bool {
+        if repeat {
+            return false
+        }
+
+        let Some(mut shortcut) = self.get_key_combo() else { return false };
+        let mut keys = keycode_to_strs(key, mods);
+
+        shortcut.sort();
+        keys.sort();
+
+        if shortcut != keys {
+            return false
+        }
+
+        let node = self.node.upgrade().unwrap();
+        node.trigger("shortcut", vec![]).await.unwrap();
+
+        true
+    }
+}
+
+fn keycode_to_strs(key: KeyCode, mods: KeyMods) -> Vec<String> {
+    let mut keys = vec![];
+    if mods.shift {
+        keys.push("shift");
+    }
+    if mods.ctrl {
+        keys.push("ctrl");
+    }
+    if mods.alt {
+        keys.push("alt");
+    }
+    if mods.logo {
+        keys.push("logo");
+    }
+
+    match key {
+        KeyCode::Space => keys.push("space"),
+        KeyCode::Apostrophe => keys.push("'"),
+        KeyCode::Comma => keys.push(","),
+        KeyCode::Minus => keys.push("-"),
+        KeyCode::Period => keys.push("."),
+        KeyCode::Slash => keys.push("/"),
+        KeyCode::Key0 => keys.push("0"),
+        KeyCode::Key1 => keys.push("1"),
+        KeyCode::Key2 => keys.push("2"),
+        KeyCode::Key3 => keys.push("3"),
+        KeyCode::Key4 => keys.push("4"),
+        KeyCode::Key5 => keys.push("5"),
+        KeyCode::Key6 => keys.push("6"),
+        KeyCode::Key7 => keys.push("7"),
+        KeyCode::Key8 => keys.push("8"),
+        KeyCode::Key9 => keys.push("9"),
+        KeyCode::Semicolon => keys.push(";"),
+        KeyCode::Equal => keys.push("="),
+        KeyCode::A => keys.push("a"),
+        KeyCode::B => keys.push("b"),
+        KeyCode::C => keys.push("c"),
+        KeyCode::D => keys.push("d"),
+        KeyCode::E => keys.push("e"),
+        KeyCode::F => keys.push("f"),
+        KeyCode::G => keys.push("g"),
+        KeyCode::H => keys.push("h"),
+        KeyCode::I => keys.push("i"),
+        KeyCode::J => keys.push("j"),
+        KeyCode::K => keys.push("k"),
+        KeyCode::L => keys.push("l"),
+        KeyCode::M => keys.push("m"),
+        KeyCode::N => keys.push("n"),
+        KeyCode::O => keys.push("o"),
+        KeyCode::P => keys.push("p"),
+        KeyCode::Q => keys.push("q"),
+        KeyCode::R => keys.push("r"),
+        KeyCode::S => keys.push("s"),
+        KeyCode::T => keys.push("t"),
+        KeyCode::U => keys.push("u"),
+        KeyCode::V => keys.push("v"),
+        KeyCode::W => keys.push("w"),
+        KeyCode::X => keys.push("x"),
+        KeyCode::Y => keys.push("y"),
+        KeyCode::Z => keys.push("z"),
+        KeyCode::LeftBracket => keys.push("("),
+        KeyCode::Backslash => keys.push("\\"),
+        KeyCode::RightBracket => keys.push(")"),
+        KeyCode::GraveAccent => keys.push("graveaccent"),
+        KeyCode::World1 => keys.push("world1"),
+        KeyCode::World2 => keys.push("world2"),
+        KeyCode::Escape => keys.push("esc"),
+        KeyCode::Enter => keys.push("enter"),
+        KeyCode::Tab => keys.push("tab"),
+        KeyCode::Backspace => keys.push("backspace"),
+        KeyCode::Insert => keys.push("ins"),
+        KeyCode::Delete => keys.push("del"),
+        KeyCode::Right => keys.push("right"),
+        KeyCode::Left => keys.push("left"),
+        KeyCode::Down => keys.push("down"),
+        KeyCode::Up => keys.push("up"),
+        KeyCode::PageUp => keys.push("pageup"),
+        KeyCode::PageDown => keys.push("pagedown"),
+        KeyCode::Home => keys.push("home"),
+        KeyCode::End => keys.push("end"),
+        KeyCode::CapsLock => keys.push("capslock"),
+        KeyCode::ScrollLock => keys.push("scrolllock"),
+        KeyCode::NumLock => keys.push("numlock"),
+        KeyCode::PrintScreen => keys.push("printscreen"),
+        KeyCode::Pause => keys.push("pause"),
+        KeyCode::F1 => keys.push("f1"),
+        KeyCode::F2 => keys.push("f2"),
+        KeyCode::F3 => keys.push("f3"),
+        KeyCode::F4 => keys.push("f4"),
+        KeyCode::F5 => keys.push("f5"),
+        KeyCode::F6 => keys.push("f6"),
+        KeyCode::F7 => keys.push("f7"),
+        KeyCode::F8 => keys.push("f8"),
+        KeyCode::F9 => keys.push("f9"),
+        KeyCode::F10 => keys.push("f10"),
+        KeyCode::F11 => keys.push("f11"),
+        KeyCode::F12 => keys.push("f12"),
+        KeyCode::F13 => keys.push("f13"),
+        KeyCode::F14 => keys.push("f14"),
+        KeyCode::F15 => keys.push("f15"),
+        KeyCode::F16 => keys.push("f16"),
+        KeyCode::F17 => keys.push("f17"),
+        KeyCode::F18 => keys.push("f18"),
+        KeyCode::F19 => keys.push("f19"),
+        KeyCode::F20 => keys.push("f20"),
+        KeyCode::F21 => keys.push("f21"),
+        KeyCode::F22 => keys.push("f22"),
+        KeyCode::F23 => keys.push("f23"),
+        KeyCode::F24 => keys.push("f24"),
+        KeyCode::F25 => keys.push("f25"),
+        KeyCode::Kp0 => keys.push("kp0"),
+        KeyCode::Kp1 => keys.push("kp1"),
+        KeyCode::Kp2 => keys.push("kp2"),
+        KeyCode::Kp3 => keys.push("kp3"),
+        KeyCode::Kp4 => keys.push("kp4"),
+        KeyCode::Kp5 => keys.push("kp5"),
+        KeyCode::Kp6 => keys.push("kp6"),
+        KeyCode::Kp7 => keys.push("kp7"),
+        KeyCode::Kp8 => keys.push("kp8"),
+        KeyCode::Kp9 => keys.push("kp9"),
+        KeyCode::KpDecimal => keys.push("kpdecimal"),
+        KeyCode::KpDivide => keys.push("kpdivide"),
+        KeyCode::KpMultiply => keys.push("kpmultiply"),
+        KeyCode::KpSubtract => keys.push("kpsubtract"),
+        KeyCode::KpAdd => keys.push("kpadd"),
+        KeyCode::KpEnter => keys.push("kpenter"),
+        KeyCode::KpEqual => keys.push("kpequal"),
+        KeyCode::LeftShift => keys.push("leftshift"),
+        KeyCode::LeftControl => keys.push("leftcontrol"),
+        KeyCode::LeftAlt => keys.push("leftalt"),
+        KeyCode::LeftSuper => keys.push("leftsuper"),
+        KeyCode::RightShift => keys.push("rightshift"),
+        KeyCode::RightControl => keys.push("rightcontrol"),
+        KeyCode::RightAlt => keys.push("rightalt"),
+        KeyCode::RightSuper => keys.push("rightsuper"),
+        KeyCode::Menu => keys.push("menu"),
+        KeyCode::Back => keys.push("back"),
+        KeyCode::Unknown => {
+            // Do nothing...
+        }
+    }
+    vec_to_string(keys)
+}