Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
69ba76e82e | |||
995dedbe82 |
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -8,7 +8,7 @@
|
|||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug",
|
"name": "Debug",
|
||||||
"program": "${workspaceFolder}/src-tauri/target/debug/keydisplay",
|
"program": "${workspaceFolder}/src-tauri/target/debug/copybot",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
|
15
README.md
15
README.md
@ -1,6 +1,13 @@
|
|||||||
# keydisplay
|
# copybot
|
||||||
|
|
||||||
ligma balls
|
you may need to install libxdo-dev, check https://github.com/Enigo-rs/Enigo for instructions
|
||||||
|
if it still doesnt work, you may need to add yourself to the input group on linux
|
||||||
you may need to add yourself to the input group for the key listener to work
|
|
||||||
`usermod -a -G input $(whoami)`
|
`usermod -a -G input $(whoami)`
|
||||||
|
|
||||||
|
## config
|
||||||
|
- default: remove default config notice
|
||||||
|
- theme: choose between grey / night / day / catppuccin_mocha
|
||||||
|
- bind: a key from https://docs.rs/rdev/latest/rdev/enum.Key.html
|
||||||
|
- shift_enter_newline: if true, hold shift while pressing enter for a new line. this is useful for preparing a whole message for things like chat apps (also, when false, delay/spam timeouts may cut off some messages/characters)
|
||||||
|
|
||||||
|
config is stored in a folder in your os config directory (found using https://crates.io/crates/dirs)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "keydisplay",
|
"name": "copybot",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "keydisplay"
|
name = "copybot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A Tauri App"
|
|
||||||
authors = ["starlight"]
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
repository = "https://git.stardust.wtf/starlight/copybot"
|
||||||
|
license = "GPL-2.0-only"
|
||||||
|
authors = ["starlight"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ edition = "2021"
|
|||||||
# The `_lib` suffix may seem redundant but it is necessary
|
# The `_lib` suffix may seem redundant but it is necessary
|
||||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||||
name = "keydisplay_lib"
|
name = "copybot_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
@ -27,3 +28,5 @@ dirs = "5.0"
|
|||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
tauri-plugin-fs = { version = "2.0.0", features = ["watch"] }
|
tauri-plugin-fs = { version = "2.0.0", features = ["watch"] }
|
||||||
rdev = { version = "0.5.3", features = ["serde", "serialize"] }
|
rdev = { version = "0.5.3", features = ["serde", "serialize"] }
|
||||||
|
fastrand = "2.3.0"
|
||||||
|
enigo = "0.3.0"
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"fs:default",
|
"fs:default",
|
||||||
{
|
{
|
||||||
"identifier": "fs:allow-app-read-recursive",
|
"identifier": "fs:allow-app-read-recursive",
|
||||||
"allow": [{"path": "$CONFIG/keydisplay"}]
|
"allow": [{"path": "$CONFIG/copybot"}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -22,8 +21,9 @@ pub struct Config {
|
|||||||
// theme = grey / night / day / catppuccin_mocha
|
// theme = grey / night / day / catppuccin_mocha
|
||||||
pub default: bool,
|
pub default: bool,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
pub listen: ListenConfig,
|
// bind = "Delete"
|
||||||
pub display: DisplayConfig,
|
pub bind: String,
|
||||||
|
pub shift_enter_newline: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is hack as fvck
|
// this is hack as fvck
|
||||||
@ -38,44 +38,14 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [listen]
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct ListenConfig {
|
|
||||||
// keys = ["KeyZ", "KeyX"]
|
|
||||||
pub keys: Vec<String>,
|
|
||||||
// mouse = ["LeftButton", "RightButton"]
|
|
||||||
pub mouse: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// [display]
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct DisplayConfig {
|
|
||||||
// "instant" or "ease"
|
|
||||||
pub press: String,
|
|
||||||
// display mouse buttons as a key or as an svg of a mouse
|
|
||||||
pub mouse: String,
|
|
||||||
// multiply default key length for the key by the float value
|
|
||||||
pub size: Vec<HashMap<String, f64>>,
|
|
||||||
// which keys to linebreak after
|
|
||||||
pub r#break: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
// Provide default values
|
// Provide default values
|
||||||
pub fn default() -> Self {
|
pub fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
default: true,
|
default: true,
|
||||||
theme: "grey".to_string(),
|
theme: "grey".to_string(),
|
||||||
listen: ListenConfig {
|
bind: "Delete".to_string(),
|
||||||
keys: vec!["KeyZ".to_string(), "KeyX".to_string(), "MetaLeft".to_string()],
|
shift_enter_newline: true,
|
||||||
mouse: vec!["Left".to_string(), "Right".to_string()],
|
|
||||||
},
|
|
||||||
display: DisplayConfig {
|
|
||||||
press: "ease".to_string(),
|
|
||||||
mouse: "key".to_string(),
|
|
||||||
size: vec![HashMap::new()],
|
|
||||||
r#break: vec![],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
use config::Config;
|
use config::Config;
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
use std::{collections::HashMap, sync::Mutex};
|
use enigo::{
|
||||||
use tauri::{ipc::Channel, Manager, State};
|
Direction::{Press, Release},
|
||||||
use rdev::{Event, EventType, listen};
|
Enigo, Key, Keyboard,
|
||||||
|
};
|
||||||
|
use rdev::{listen, Event, EventType};
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use tauri::{ipc::Channel, Manager, State};
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
@ -21,14 +25,9 @@ pub fn run() {
|
|||||||
update_config,
|
update_config,
|
||||||
get_default,
|
get_default,
|
||||||
get_theme,
|
get_theme,
|
||||||
get_keys,
|
get_bind,
|
||||||
get_mouse_buttons,
|
|
||||||
get_mouse_display,
|
|
||||||
get_press_display,
|
|
||||||
get_size_display,
|
|
||||||
get_breaks,
|
|
||||||
start_listener,
|
start_listener,
|
||||||
label_from_keycode
|
paste_text
|
||||||
])
|
])
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let config = config::Config::load_or_create(get_config_path()).unwrap();
|
let config = config::Config::load_or_create(get_config_path()).unwrap();
|
||||||
@ -39,9 +38,7 @@ pub fn run() {
|
|||||||
let themes = vec!["grey", "night", "day", "catppuccin_mocha"];
|
let themes = vec!["grey", "night", "day", "catppuccin_mocha"];
|
||||||
if themes.contains(&theme.as_str()) {
|
if themes.contains(&theme.as_str()) {
|
||||||
println!("Setting theme to: {}", theme);
|
println!("Setting theme to: {}", theme);
|
||||||
app.manage(Mutex::new(AppState {
|
app.manage(Mutex::new(AppState { config: config }));
|
||||||
config: config,
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
@ -49,7 +46,6 @@ pub fn run() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
@ -62,7 +58,7 @@ pub fn run() {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_config_path() -> std::path::PathBuf {
|
fn get_config_path() -> std::path::PathBuf {
|
||||||
let config_dir = dirs::config_dir().expect("Config directory not found");
|
let config_dir = dirs::config_dir().expect("Config directory not found");
|
||||||
config_dir.join("keydisplay")
|
config_dir.join("copybot")
|
||||||
}
|
}
|
||||||
// update the app state with new config
|
// update the app state with new config
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -72,7 +68,7 @@ fn update_config(state: State<'_, Mutex<AppState>>) {
|
|||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_default(state: State<'_, Mutex<AppState>>) -> Result<bool, bool> {
|
fn get_default(state: State<'_, Mutex<AppState>>) -> Result<bool, bool> {
|
||||||
let default = state.lock().unwrap().config.default.clone();
|
let default = state.lock().unwrap().config.default;
|
||||||
Ok(default)
|
Ok(default)
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -81,126 +77,48 @@ fn get_theme(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
|||||||
Ok(theme)
|
Ok(theme)
|
||||||
}
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_keys(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
fn get_bind(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
||||||
let keys = state.lock().unwrap().config.listen.keys.clone();
|
let bind = state.lock().unwrap().config.bind.clone();
|
||||||
Ok(keys)
|
Ok(bind)
|
||||||
}
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_mouse_buttons(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
|
||||||
let mouse_buttons = state.lock().unwrap().config.listen.mouse.clone();
|
|
||||||
Ok(mouse_buttons)
|
|
||||||
}
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_press_display(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
|
||||||
let press_display = state.lock().unwrap().config.display.press.clone();
|
|
||||||
Ok(press_display)
|
|
||||||
}
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_size_display(state: State<'_, Mutex<AppState>>) -> Result<Vec<HashMap<String, f64>>, Vec<HashMap<String, f64>>> {
|
|
||||||
let size_display = state.lock().unwrap().config.display.size.clone();
|
|
||||||
Ok(size_display)
|
|
||||||
}
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_mouse_display(state: State<'_, Mutex<AppState>>) -> Result<String, String> {
|
|
||||||
let mouse_display = state.lock().unwrap().config.display.mouse.clone();
|
|
||||||
Ok(mouse_display)
|
|
||||||
}
|
|
||||||
#[tauri::command]
|
|
||||||
fn get_breaks(state: State<'_, Mutex<AppState>>) -> Result<Vec<String>, Vec<String>> {
|
|
||||||
let breaks = state.lock().unwrap().config.display.r#break.clone();
|
|
||||||
Ok(breaks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input events
|
// Input events
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn start_listener(keys: Vec<String>, m_buttons: Vec<String>, channel: Channel<Event>) {
|
fn start_listener(bind: String, channel: Channel<Event>) {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
println!("Started listening for keys: {:?} and buttons: {:?}", keys, m_buttons);
|
println!("Started listening for bind: {:?}", bind);
|
||||||
listen(move |event| {
|
listen(move |event| match event.event_type {
|
||||||
match event.event_type {
|
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
||||||
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
if bind == serde_json::to_string(&key).unwrap().replace("\"", "") {
|
||||||
if keys.contains(&serde_json::to_string(&key).unwrap().replace("\"", "")) { channel.send(event).unwrap() };
|
channel.send(event).unwrap()
|
||||||
},
|
};
|
||||||
EventType::ButtonPress(button) | EventType::ButtonRelease(button) => {
|
|
||||||
if m_buttons.contains(&serde_json::to_string(&button).unwrap().replace("\"", "")) { channel.send(event).unwrap() };
|
|
||||||
},
|
|
||||||
EventType::MouseMove { x, y } => (),
|
|
||||||
EventType::Wheel { delta_x, delta_y } => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other rust
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn label_from_keycode(code: &str) -> &str {
|
fn paste_text(state: State<'_, Mutex<AppState>>, text: String) {
|
||||||
match code {
|
println!("{}", text);
|
||||||
// Keyboard
|
let mut enigo = Enigo::new(&enigo::Settings::default()).unwrap();
|
||||||
// Fine as-is
|
|
||||||
"Alt"|"AltGr"|"End"|"Home"|"Pause" => code,
|
|
||||||
// All alphabetical keys
|
|
||||||
c if c.starts_with("Key")
|
|
||||||
&& c.len() == 4
|
|
||||||
&& c.chars().nth(3).map_or(false, |c| c.is_ascii_alphabetic()) => &c[3..],
|
|
||||||
// Number row
|
|
||||||
c if c.starts_with("Num")
|
|
||||||
&& c.len() == 4
|
|
||||||
&& c.chars().nth(3).map_or(false, |c| c.is_numeric()) => &c[3..],
|
|
||||||
// All F keys
|
|
||||||
c if c.starts_with("F")
|
|
||||||
&& c.len() == 2
|
|
||||||
&& c.chars().nth(1).map_or(false, |c| c.is_numeric()) => c,
|
|
||||||
// Numpad numbers
|
|
||||||
c if c.starts_with("Kp")
|
|
||||||
&& c.len() == 3
|
|
||||||
&& c.chars().nth(2).map_or(false, |c| c.is_numeric()) => &c[2..],
|
|
||||||
// Individual mappings
|
|
||||||
"Backspace" => "🡐",
|
|
||||||
"CapsLock" => "Caps",
|
|
||||||
"ControlLeft"|"ControlRight" => "Ctrl",
|
|
||||||
"Delete"|"KpDelete" => "Del",
|
|
||||||
"DownArrow" => "⯆",
|
|
||||||
"Escape" => "Esc",
|
|
||||||
"LeftArrow" => "⯇",
|
|
||||||
"MetaLeft"|"MetaRight" => "Super",
|
|
||||||
"PageDown" => "PgDn",
|
|
||||||
"PageUp" => "PgUp",
|
|
||||||
"Return" => "⮠",
|
|
||||||
"RightArrow" => "⯈",
|
|
||||||
"ShiftLeft"|"ShiftRight" => "Shift",
|
|
||||||
// needs an obscure blank character (U+E002D) - css acts weird if its a space or anything the program interprets as one
|
|
||||||
"Space" => "",
|
|
||||||
"Tab" => "⇌",
|
|
||||||
"UpArrow" => "⯅",
|
|
||||||
"PrintScreen" => "PrtSc",
|
|
||||||
"ScrollLock" => "ScrLk",
|
|
||||||
"NumLock" => "Num",
|
|
||||||
"BackQuote" => "`",
|
|
||||||
"Minus" => "-",
|
|
||||||
"Equal" => "=",
|
|
||||||
"LeftBracket" => "(",
|
|
||||||
"RightBracket" => ")",
|
|
||||||
"SemiColon" => ";",
|
|
||||||
"Quote" => "\"",
|
|
||||||
"BackSlash"|"IntlBackslash" => "\\",
|
|
||||||
"Comma" => ",",
|
|
||||||
"Dot" => ".",
|
|
||||||
"Slash"|"KpDivide" => "/",
|
|
||||||
"Insert" => "Ins",
|
|
||||||
"KpReturn" => "↲",
|
|
||||||
"KpPlus" => "+",
|
|
||||||
"KpMultiply" => "*",
|
|
||||||
"Function" => "Fn",
|
|
||||||
|
|
||||||
// Mouse
|
if state.lock().unwrap().config.shift_enter_newline {
|
||||||
"Left" => "M1",
|
for c in text.chars() {
|
||||||
"Right" => "M2",
|
if c == '\n' {
|
||||||
"Middle" => "M3",
|
// Press Shift+Enter combination
|
||||||
|
enigo.key(Key::Shift, Press).unwrap();
|
||||||
&_ => {
|
enigo.key(Key::Return, Press).unwrap();
|
||||||
println!("Error creating frontend label for keycode: {}, displaying as Unknown", code);
|
enigo.key(Key::Return, Release).unwrap();
|
||||||
"Unknown"
|
enigo.key(Key::Shift, Release).unwrap();
|
||||||
},
|
} else {
|
||||||
|
// Type regular characters normally
|
||||||
|
enigo.text(&c.to_string()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
enigo.text(text.as_str()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,5 +4,5 @@
|
|||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
keydisplay_lib::run()
|
copybot_lib::run()
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "keydisplay",
|
"productName": "COPYBOT",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"identifier": "whatisthisanandroidapp.keydisplay.starlight",
|
"identifier": "whatisthisanandroidapp.copybot.starlight",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn dev",
|
"beforeDevCommand": "yarn dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:1420",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "keydisplay",
|
"title": "copybot",
|
||||||
"width": 800,
|
"width": 800,
|
||||||
"height": 600
|
"height": 600
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>keydisplay</title>
|
<title>copybot</title>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invoke, Channel } from "@tauri-apps/api/core";
|
import { invoke, Channel } from "@tauri-apps/api/core";
|
||||||
import Key from "./Key.svelte";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { watchImmediate } from "@tauri-apps/plugin-fs";
|
import { watchImmediate } from "@tauri-apps/plugin-fs";
|
||||||
@ -20,22 +19,18 @@
|
|||||||
| { Wheel: { delta_x: number, delta_y: number } }
|
| { Wheel: { delta_x: number, delta_y: number } }
|
||||||
}
|
}
|
||||||
|
|
||||||
// type with props for the Key component
|
// app state
|
||||||
type KeyElement = {
|
let content_tmp: string = $state("");
|
||||||
label: string,
|
let content: string = $state("");
|
||||||
code: string,
|
|
||||||
pressed: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// scoping
|
// scoping
|
||||||
let keys: Array<string>, mouse_buttons: Array<string>;
|
let bind: string = $state("");
|
||||||
let key_elements: Array<KeyElement>, mouse_button_elements: Array<KeyElement>;
|
let bind_pressed: boolean;
|
||||||
|
|
||||||
let default_config: boolean;
|
let default_config: boolean;
|
||||||
let config_path: string;
|
let config_path: string;
|
||||||
|
|
||||||
let breaks: Set<string>;
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// on launch
|
// on launch
|
||||||
await handle_config();
|
await handle_config();
|
||||||
@ -60,7 +55,6 @@
|
|||||||
// create listener for events
|
// create listener for events
|
||||||
const KeyListener = new Channel<KeyEvent>();
|
const KeyListener = new Channel<KeyEvent>();
|
||||||
|
|
||||||
// handle key event by modifying the key_elements and mouse_button_elements arrays
|
|
||||||
KeyListener.onmessage = (message) => {
|
KeyListener.onmessage = (message) => {
|
||||||
switch (Object.keys(message.event_type)[0]) {
|
switch (Object.keys(message.event_type)[0]) {
|
||||||
case 'KeyPress':
|
case 'KeyPress':
|
||||||
@ -71,14 +65,6 @@
|
|||||||
handleKeyEvent(Object.values(message.event_type)[0], false);
|
handleKeyEvent(Object.values(message.event_type)[0], false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ButtonPress':
|
|
||||||
handleMouseEvent(Object.values(message.event_type)[0], true);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ButtonRelease':
|
|
||||||
handleMouseEvent(Object.values(message.event_type)[0], false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn("Unhandled event: ", message);
|
console.warn("Unhandled event: ", message);
|
||||||
}
|
}
|
||||||
@ -86,28 +72,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function handleKeyEvent(code: string, isPressed: boolean) {
|
async function handleKeyEvent(code: string, isPressed: boolean) {
|
||||||
key_elements = key_elements.map(key => {
|
if(isPressed && !bind_pressed) {
|
||||||
if (key.code === code) {
|
bind_pressed = true;
|
||||||
return { ...key, pressed: isPressed}
|
console.log(`${code} was pressed, pasting..`)
|
||||||
}
|
await invoke('paste_text', { text: content });
|
||||||
return key;
|
} else if(!isPressed) {
|
||||||
});
|
bind_pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMouseEvent(code: string, isPressed: boolean) {
|
|
||||||
mouse_button_elements = mouse_button_elements.map(button => {
|
|
||||||
if (button.code === code) {
|
|
||||||
return { ...button, pressed: isPressed}
|
|
||||||
}
|
|
||||||
return button;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoke backend key listener that will send the events
|
// invoke backend key listener that will send the events
|
||||||
await invoke('start_listener', {
|
await invoke('start_listener', {
|
||||||
keys: keys,
|
bind: bind,
|
||||||
// named mButtons and not m_buttons because weird naming """conventions""" (WHO CARES)
|
|
||||||
mButtons: mouse_buttons,
|
|
||||||
channel: KeyListener
|
channel: KeyListener
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -134,83 +110,26 @@
|
|||||||
console.error(`Failed to run get_default from the rust backend: ${error}`);
|
console.error(`Failed to run get_default from the rust backend: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set display for keypresses
|
// load the bind
|
||||||
try {
|
try {
|
||||||
let press_display = await invoke('get_press_display');
|
bind = await invoke<string>('get_bind');
|
||||||
switch (press_display) {
|
|
||||||
case "instant":
|
|
||||||
case "ease":
|
|
||||||
root?.setAttribute("data-press-display", press_display);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.warn('"press" field in [display] of the config is an invalid value, falling back to "ease"');
|
|
||||||
root?.setAttribute("data-press-display", "ease");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to run get_press_display from the rust backend: ${error}`);
|
console.error("Failed to process bind: ", error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get linebreaks
|
async function set_text() {
|
||||||
try {
|
content = content_tmp
|
||||||
breaks = new Set(await invoke<Array<string>>("get_breaks"));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to get "break" config field from rust backend: ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the keys/mousebuttons
|
|
||||||
try {
|
|
||||||
keys = await invoke<Array<string>>('get_keys');
|
|
||||||
mouse_buttons = await invoke<Array<string>>('get_mouse_buttons');
|
|
||||||
key_elements = await Promise.all(keys.map(async key => {
|
|
||||||
return {
|
|
||||||
label: await invoke<string>("label_from_keycode", { code: key }),
|
|
||||||
code: key,
|
|
||||||
pressed: false
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
mouse_button_elements = await Promise.all(mouse_buttons.map(async button => {
|
|
||||||
return {
|
|
||||||
label: await invoke<string>("label_from_keycode", { code: button }),
|
|
||||||
code: button,
|
|
||||||
pressed: false
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to process keys: ", error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/global.css">
|
<link rel="stylesheet" href="/global.css">
|
||||||
<main class="container">
|
<main class="container">
|
||||||
{#if default_config}
|
{#if default_config}
|
||||||
<div id="default_config">Docs for configuring this program are at <a href="https://git.stardust.wtf/starlight/keydisplay/src/branch/main/README.md">https://git.stardust.wtf/starlight/keydisplay/src/branch/main/README.md</a><br>You can disable this annoying notice by removing the <code>default = true</code> line from <code>{config_path}</code> :)</div>
|
<div id="default_config">Docs for configuring this program are at <a href="https://git.stardust.wtf/starlight/copybot/src/branch/main/README.md">https://git.stardust.wtf/starlight/copybot/src/branch/main/README.md</a><br>You can disable this annoying notice by removing the <code>default = true</code> line from <code>{config_path}</code> :)</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div id="keys">
|
<h1>copybot</h1>
|
||||||
{#each key_elements as key (key.code)}
|
<textarea autocorrect="off" rows="6" bind:value={content_tmp}></textarea>
|
||||||
<Key
|
<button id="set_text" onclick={() => content = content_tmp}>Set</button>
|
||||||
label={key.label}
|
<p>Current bind: <b>{bind}</b></p>
|
||||||
code={key.code}
|
<p>Set to current textarea content: <b>{content == content_tmp}</b></p>
|
||||||
pressed={key.pressed}
|
|
||||||
>
|
|
||||||
</Key>
|
|
||||||
{#if breaks.has(key.code)}
|
|
||||||
<br>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div id="mouse_buttons">
|
|
||||||
{#each mouse_button_elements as button (button.code)}
|
|
||||||
<Key
|
|
||||||
label={button.label}
|
|
||||||
code={button.code}
|
|
||||||
pressed={button.pressed}
|
|
||||||
>
|
|
||||||
</Key>
|
|
||||||
{#if breaks.has(button.code)}
|
|
||||||
<br>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
@ -1,14 +0,0 @@
|
|||||||
<!-- generic "key" -->
|
|
||||||
<script>
|
|
||||||
export let label = "";
|
|
||||||
export let code = "";
|
|
||||||
export let pressed = false;
|
|
||||||
|
|
||||||
// Optional: Add press animation
|
|
||||||
import { quintOut } from "svelte/easing";
|
|
||||||
import { crossfade } from "svelte/transition";
|
|
||||||
</script>
|
|
||||||
<link rel="stylesheet" href="/global.css">
|
|
||||||
<div class="key" class:pressed={pressed} data-keycode={code}>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
@ -32,30 +32,30 @@ img {
|
|||||||
:root,
|
:root,
|
||||||
[data-selected-theme="grey"] {
|
[data-selected-theme="grey"] {
|
||||||
--color-app-background: #2F2F2F;
|
--color-app-background: #2F2F2F;
|
||||||
--color-key-background: #363636;
|
--color-app-background-2: #4D4D4D;
|
||||||
--color-key-background-pressed: #212121;
|
|
||||||
--color-text: #fff;
|
--color-text: #fff;
|
||||||
|
--color-button-pressed: #212121;
|
||||||
--color-accent: #737373;
|
--color-accent: #737373;
|
||||||
}
|
}
|
||||||
[data-selected-theme="night"] {
|
[data-selected-theme="night"] {
|
||||||
--color-app-background: #000;
|
--color-app-background: #000;
|
||||||
--color-key-background: #0f0f0f;
|
--color-app-background-2: #1B1B1B;
|
||||||
--color-key-background-pressed: #212121;
|
|
||||||
--color-text: #eee;
|
--color-text: #eee;
|
||||||
|
--color-button-pressed: #212121;
|
||||||
--color-accent: #525151;
|
--color-accent: #525151;
|
||||||
}
|
}
|
||||||
[data-selected-theme="day"] {
|
[data-selected-theme="day"] {
|
||||||
--color-app-background: #e0e0e0;
|
--color-app-background: #e0e0e0;
|
||||||
--color-key-background: #fff;
|
--color-app-background-2: #f0f0f0;
|
||||||
--color-key-background-pressed: #eee;
|
|
||||||
--color-text: #000;
|
--color-text: #000;
|
||||||
|
--color-button-pressed: #000;
|
||||||
--color-accent: #fff;
|
--color-accent: #fff;
|
||||||
}
|
}
|
||||||
[data-selected-theme="catppuccin_mocha"] {
|
[data-selected-theme="catppuccin_mocha"] {
|
||||||
--color-app-background: #181825;
|
--color-app-background: #181825;
|
||||||
--color-key-background: #1e1e2e;
|
--color-app-background-2: #1e1e2e;
|
||||||
--color-key-background-pressed: #313244;
|
|
||||||
--color-text: #cdd6f4;
|
--color-text: #cdd6f4;
|
||||||
|
--color-button-pressed: #313244;
|
||||||
--color-accent: #f5e0dc;
|
--color-accent: #f5e0dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,22 +87,28 @@ button {
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
padding: 0.6em 1.2em;
|
padding: 0.6em 1.2em;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
color: #0f0f0f;
|
color: var(--color-text);
|
||||||
background-color: #ffffff;
|
background-color: var(--color-app-background-2);
|
||||||
transition: border-color 0.25s;
|
transition: border-color 0.25s;
|
||||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button#set_text {
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 25vw;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
border-color: #396cd8;
|
border-color: #396cd8;
|
||||||
}
|
}
|
||||||
button:active {
|
button:active {
|
||||||
border-color: #396cd8;
|
border-color: #396cd8;
|
||||||
background-color: #e8e8e8;
|
background-color: var(--color-button-pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -125,34 +131,13 @@ div#default_config {
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#keys,
|
textarea {
|
||||||
div#mouse_buttons {
|
background-color: var(--color-app-background-2);
|
||||||
/* horizontal spacing between keys is dependent on font-size of the parent div? */
|
border: 3px solid var(--color-accent);
|
||||||
font-size: 0;
|
border-radius: 5px;
|
||||||
}
|
font-size: 16px;
|
||||||
|
width: 80vw;
|
||||||
.key {
|
margin-top: 5px;
|
||||||
width: 100px;
|
margin-inline: auto;
|
||||||
height: 100px;
|
color: var(--color-text);
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20px;
|
|
||||||
margin: 5px;
|
|
||||||
border: 5px solid var(--color-accent);
|
|
||||||
border-radius: 5px;
|
|
||||||
transition: var(--pressed-transition);
|
|
||||||
background-color: var(--color-key-background);
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-press-display="ease"] {
|
|
||||||
--pressed-transition: background-color 0.1s ease, transform 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pressed {
|
|
||||||
background-color: var(--color-key-background-pressed) !important;
|
|
||||||
transform: scale(0.95);
|
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user