Hi friends, it's Hudy. In this tutorial, I'm so excited to show you how I built a keystroke application for Windows that was inspired by KeyCastr. You may wonder why I built a keystroke app when there are so many similar ones for Windows. The reason is simple: I find their UI/UX unappealing. This tutorial is a basic version. To access the full source code and guide, please visit the homepage below to download and access my repo. Source code Here is the homepage and GitHub repo: https://hudy9x.github.io/keyreader/ Feel free to download, fork, or contribute to it Video version I made a video version as well, you guys can watch it here https://www.youtube.com/watch?v=hhwjGIICLuA&t=10s&embedable=true Prerequisites Well, I've used some tech for building this app: or - I use yarn Yarn Npm - a toolkit for building desktop application Tauri Reactjs Tailwindcss - a rust crate for listening to keystroke events rdev Typescript basics Make sure that you have and installed. Now, let's get started yarn tauri Implementation Open your terminal and create a new Tauri project called . The project use - means Reactjs with typescript and yarn as a package manager keyreader react-ts $ yarn create tauri-app keyreader --template react-ts --manager yarn $ cd keyreader $ yarn $ yarn tauri dev After running the above commands (It'll take a few minutes). The following UI shown means we created the Tauri app successfully Remove redundant files in the source base Just keep the main files and remove redundant files. I just removed files inside and folder. public src Install tailwindcss (optional) I'd love to use tailwindcss for styling cuz it's so useful. But you guys don't need to. Please take a look at the installation instruction or follow the guide below here $ yarn add --dev tailwindcss postcss autoprefixer $ yarn tailwindcss init -p Add the paths to all of your template files in your file. tailwind.config.js /** @type {import('tailwindcss').Config} */ export default { content: ["./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], } Now, add the directives for each of Tailwind’s layers to your ./src/styles.css file. @tailwind @tailwind base; @tailwind components; @tailwind utilities; Open and add some class src/App.tsx function App() { return <div className="text-red-400">Hello world</div>; } export default App; Then run the app again, if text turns to red that means it works $ yarn tauri dev Keystroke listener This is the core of our app. I'll create a file for watching keystrokes that users typed. To do that, we use crate. keyboard_listener.rs rdev Install the crate by opening and add to section src-tauri/Cargo.toml rdev [dependencies] [build-dependencies] tauri-build = { version = "1.2", features = [] } [dependencies] # ... rdev = "0.5.2" Time to create the listener // ./src-tauri/src/keyboard_listener.rs use rdev::{listen, Event, EventType}; pub fn run_listener<F>(emit: F) where F: Fn(&str, &str) + 'static, { if let Err(error) = listen(move |event| callback(event, &emit)) { println!("Error: {:?}", error) } } fn callback<F: Fn(&str, &str)>(event: Event, emit: &F) { match event.name { Some(string) => { println!("Some: {}", string); emit("Some", &string); } None => { match event.event_type { EventType::KeyPress(key) => { println!("KeyPress: {:?}", key); let key_str = format!("{:?}", key); emit("KeyPress", &key_str); } EventType::KeyRelease(key) => { let key_str = format!("{:?}", key); emit("KeyRelease", &key_str); } EventType::MouseMove { .. } => { // Ignore MouseMove event type } _ => { // println!("None: {:?}", event.event_type); // let event_type_str = format!("{:?}", event.event_type); // emit(&event_type_str); } } } } } The above script is simply using method from to watch keystrokes. All alphabet characters return in event and the others like Ctrl, Alt, Caplocks, ... return in . So we've to read it by comparing with type. listen rdev Some None event.event_type EventType One more thing, open and use the listener src-tauri/src/main.rs // ./src-tauri/src/main.rs // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod keyboard_listener; use std::thread; use tauri::Manager; #[derive(Clone, serde::Serialize)] struct Payload { mode: String, message: String, } // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .setup(move |app| { let wv = app.get_window("main").unwrap(); thread::spawn(move || { keyboard_listener::run_listener(move |s: &str, s1: &str| { if let Err(err) = wv.emit( "keypress", Payload { mode: String::from(s), message: String::from(s1), }, ) { eprintln!("Error while emitting event: {:?}", err); } }) }); Ok(()) }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } Run the app again and try to type something. If your got logs are shown in the terminal that means works. rdev Display keystrokes on Reactjs For now, register an event listener on the view. Open and register a event as follow src/App.tsx keypress import { useEffect, useState } from "react"; import { listen } from "@tauri-apps/api/event"; export default function App() { const [alphabeticKeys, setAlphabeticKeys] = useState<string[]>(["NONE"]); useEffect(() => { listen("keypress", ({ payload }) => { let { message, mode } = payload as { message: string; mode: string }; if (mode === "Some") { setAlphabeticKeys((prevTickers) => { let max = 15; let newTickers = []; const charCode = message.charCodeAt(0); if (charCode === 32) { message = "␣"; } newTickers = [...prevTickers, ...[message]]; const currLen = newTickers.length; newTickers = currLen > max ? newTickers.slice(1) : newTickers; return newTickers; }); console.log(message); } }); }, []); return ( <div data-tauri-drag-region className="bg-black"> <div className=" px-4 py-3 text-3xl text-white flex items-center gap-1 pointer-events-none"> {alphabeticKeys.map((key, index) => { return <div key={index}>{key}</div>; })} </div> </div> ); } I use Inter font for fixing UX bugs that make Esc character shorter than the others. /*.........*/ @font-face { font-family: "Inter"; src: url("/fonts/Inter-Regular.ttf") format("truetype"); font-weight: normal; font-style: normal; } @font-face { font-family: "Inter"; src: url("/fonts/Inter-Bold.ttf") format("truetype"); font-weight: bold; font-style: normal; } :root { font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; font-weight: 400; color: #0f0f0f; background-color: transparent; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } body { @apply flex items-center justify-center h-screen overflow-hidden; } Re-run the app one more time. It's testing time guys Make the background transparent and hide the title bar Last thing to do for better UI is hide the title bar and make the background transparent. It's so simple, just open and add some config fields. src-tauri/tauri.conf.json { // ... "tauri": { "allowlist": { // .... For custom titlebar to drag, close, ... window "window": { "all": false, "close": true, "hide": true, "show": true, "maximize": true, "minimize": true, "unmaximize": true, "unminimize": true, "startDragging": true } }, "bundle": { // ..... Rename to build app "identifier": "com.keyreader.app", }, // .... "windows": [ { "fullscreen": false, "resizable": true, "title": "KeyReader", "width": 400, "height": 200, "decorations": false, // turn of titlebar "transparent": true // make background transparency } ] } } Wait for a few minutes to rebuild the app. And here is our result: Modifier keys Just now, I show you how to display alphabet keys. So what if modifier keys like Ctrl, Enter, Space, Alt, etc...? You guys just use variable for checking these keys. For examples. mode listen("keypress", ({ payload }) => { let { message, mode } = payload as { message: string; mode: string }; let charCode = message.charCodeAt(0) if (mode === "Some") {/*...*/} if (mode === "KeyPress") { if (message=== "ControlRight") { message = "⌃" } // update state } } Still don’t get it? Scroll to the top and refer to my video or repo Conclusion So far, I just show you how to listen to keystrokes event from the keyboard using . And send it to Tauri view by registering an event. Hope you guys learn something new about building desktop applications using Reacjts and Tauri. rdev