keyviz Documentation

Keyviz is a free and open-source tool to visualize your keystrokes โŒจ๏ธ and ๐Ÿ–ฑ๏ธ mouse actions in real-time.

9,244 stars TypeScript TypeScript 1 file ยท ~2,578 tokens #keypress#keystroke#tauri-rust
RAW Doc

File: README.md

[Keyviz](https://keyviz.org)

Keyviz is a free and open-source tool that visualizes your keypresses and mouse actions in real-time. Let your audience know what handy shortcuts you're pressing during tutorials, presentations, collaborations, or whenever you need.

โŒจ๏ธ Keypress & ๐Ÿ–ฑ๏ธ Mouse Actions

Along with normal keys, you can also visualize mouse actions like Cmd + Click, Alt + Drag, etc.

Display mouse clicks and scroll wheel movements alongside your cursor.

โš™๏ธ Full Customization

Don't settle for defaults. Every aspect of the visualization is under your control:

  • Styling: Change colors (modifier vs. regular keys), size, layout, border, and background.
  • Filtering: Control which keys are shown using hotkey or custom filters.
  • History: Keep a visual trail of your recent inputs.
  • Position: Move the visualization to any part of your screen.
  • Animations: Customize how inputs appear and disappear with preset entry and exit animations.

๐Ÿ“ฅ Installation

Windows & macOS

You can download the latest version of Keyviz from the GitHub Releases page.

  • Windows: Download the .msi installer, run it, and follow the steps.
  • macOS: Download the .dmg.
    Note: Keyviz requires Input Monitoring and Accessibility permissions. Enable them here:
    Settings > Privacy & Security > Input Monitoring & Accessibility

Linux (x11)

Keyviz is compatible with Linux using the X11 protocol. Currently, you can try it out by following the build instructions below.

๐Ÿ› ๏ธ Build Instructions

If you want to contribute or build the latest features from the source, ensure you have Node.js and Tauri set up on your system.

  1. Clone the repository:

    bash
    git clone https://github.com/mulaRahul/keyviz.git
    cd keyviz
  2. Install dependencies:

    bash
    npm install
  3. Build the executable:

    bash
    npx tauri build

๐Ÿ’– Support the Project

  • Star the Repo: It helps others discover the project!
  • GitHub Sponsors: Sponsor @mularahul
  • Keyviz Pro: Get access to exclusive features while supporting the development of this open-source project.

๐Ÿ‘‰ Upgrade to Pro at keyviz.org/pro


Built with ๐Ÿฆ€ and โค๏ธ using Tauri.


File: src-tauri/crates/rdev/README.md


rdev

Simple library to listen and send events globally to keyboard and mouse on MacOS, Windows and Linux
(x11).

You can also check out Enigo which is another
crate which helped me write this one.

This crate is so far a pet project for me to understand the rust ecosystem.

Listening to global events

rust
use rdev::{listen, Event};

// This will block.
if let Err(error) = listen(callback) {
    println!("Error: {:?}", error)
}

fn callback(event: Event) {
    println!("My callback {:?}", event);
    match event.name {
        Some(string) => println!("User wrote {:?}", string),
        None => (),
    }
}

OS Caveats:

When using the listen function, the following caveats apply:

Mac OS

The process running the blocking listen function (loop) needs to be the parent process (no fork before).
The process needs to be granted access to the Accessibility API (ie. if you're running your process
inside Terminal.app, then Terminal.app needs to be added in
System Preferences > Security & Privacy > Privacy > Accessibility)
If the process is not granted access to the Accessibility API, MacOS will silently ignore rdev's
listen calleback and will not trigger it with events. No error will be generated.

Linux

The listen function uses X11 APIs, and so will not work in Wayland or in the linux kernel virtual console

Sending some events

rust
use rdev::{simulate, Button, EventType, Key, SimulateError};
use std::{thread, time};

fn send(event_type: &EventType) {
    let delay = time::Duration::from_millis(20);
    match simulate(event_type) {
        Ok(()) => (),
        Err(SimulateError) => {
            println!("We could not send {:?}", event_type);
        }
    }
    // Let ths OS catchup (at least MacOS)
    thread::sleep(delay);
}

send(&EventType::KeyPress(Key::KeyS));
send(&EventType::KeyRelease(Key::KeyS));

send(&EventType::MouseMove { x: 0.0, y: 0.0 });
send(&EventType::MouseMove { x: 400.0, y: 400.0 });
send(&EventType::ButtonPress(Button::Left));
send(&EventType::ButtonRelease(Button::Right));
send(&EventType::Wheel {
    delta_x: 0,
    delta_y: 1,
});

Main structs

Event

In order to detect what a user types, we need to plug to the OS level management
of keyboard state (modifiers like shift, ctrl, but also dead keys if they exist).

EventType corresponds to a physical event, corresponding to QWERTY layout
Event corresponds to an actual event that was received and Event.name reflects
what key was interpreted by the OS at that time, it will respect the layout.

rust
/// When events arrive from the system we can add some information
/// time is when the event was received.
#[derive(Debug)]
pub struct Event {
    pub time: SystemTime,
    pub name: Option<String>,
    pub event_type: EventType,
}

Be careful, Event::name, might be None, but also String::from(""), and might contain
not displayable unicode characters. We send exactly what the OS sends us so do some sanity checking
before using it.
Caveat: Dead keys don't function yet on Linux

EventType

In order to manage different OS, the current EventType choices is a mix&match
to account for all possible events.
There is a safe mechanism to detect events no matter what, which are the
Unknown() variant of the enum which will contain some OS specific value.
Also not that not all keys are mapped to an OS code, so simulate might fail if you
try to send an unmapped key. Sending Unknown() variants will always work (the OS might
still reject it).

rust
/// In order to manage different OS, the current EventType choices is a mix&match
/// to account for all possible events.
#[derive(Debug)]
pub enum EventType {
    /// The keys correspond to a standard qwerty layout, they don't correspond
    /// To the actual letter a user would use, that requires some layout logic to be added.
    KeyPress(Key),
    KeyRelease(Key),
    /// Some mouse will have more than 3 buttons, these are not defined, and different OS will
    /// give different Unknown code.
    ButtonPress(Button),
    ButtonRelease(Button),
    /// Values in pixels
    MouseMove {
        x: f64,
        y: f64,
    },
    /// Note: On Linux, there is no actual delta the actual values are ignored for delta_x
    /// and we only look at the sign of delta_y to simulate wheelup or wheeldown.
    Wheel {
        delta_x: i64,
        delta_y: i64,
    },
}

Getting the main screen size

rust
use rdev::{display_size};

let (w, h) = display_size().unwrap();
assert!(w > 0);
assert!(h > 0);

Keyboard state

We can define a dummy Keyboard, that we will use to detect
what kind of EventType trigger some String. We get the currently used
layout for now !
Caveat : This is layout dependent. If your app needs to support
layout switching don't use this !
Caveat: On Linux, the dead keys mechanism is not implemented.
Caveat: Only shift and dead keys are implemented, Alt+unicode code on windows
won't work.

rust
use rdev::{Keyboard, EventType, Key, KeyboardState};

let mut keyboard = Keyboard::new().unwrap();
let string = keyboard.add(&EventType::KeyPress(Key::KeyS));
// string == Some("s")

Grabbing global events. (Requires `unstable_grab` feature)

Installing this library with the unstable_grab feature adds the grab function
which hooks into the global input device event stream.
by suppling this function with a callback, you can intercept
all keyboard and mouse events before they are delivered to applications / window managers.
In the callback, returning None ignores the event and returning the event let's it pass.
There is no modification of the event possible here (yet).

Note: the use of the word unstable here refers specifically to the fact that the grab API is unstable and subject to change

rust
#[cfg(feature = "unstable_grab")]
use rdev::{grab, Event, EventType, Key};

#[cfg(feature = "unstable_grab")]
let callback = |event: Event| -> Option<Event> {
    if let EventType::KeyPress(Key::CapsLock) = event.event_type {
        println!("Consuming and cancelling CapsLock");
        None  // CapsLock is now effectively disabled
    }
    else { Some(event) }
};
// This will block.
#[cfg(feature = "unstable_grab")]
if let Err(error) = grab(callback) {
    println!("Error: {:?}", error)
}

OS Caveats:

When using the listen and/or grab functions, the following caveats apply:

Mac OS

The process running the blocking grab function (loop) needs to be the parent process (no fork before).
The process needs to be granted access to the Accessibility API (ie. if you're running your process
inside Terminal.app, then Terminal.app needs to be added in
System Preferences > Security & Privacy > Privacy > Accessibility)
If the process is not granted access to the Accessibility API, the grab call will fail with an
EventTapError (at least in MacOS 10.15, possibly other versions as well)

Linux

The grab function use the evdev library to intercept events, so they will work with both X11 and Wayland
In order for this to work, the process runnign the listen or grab loop needs to either run as root (not recommended),
or run as a user who's a member of the input group (recommended)
Note: on some distros, the group name for evdev access is called plugdev, and on some systems, both groups can exist.
When in doubt, add your user to both groups if they exist.

Serialization

Event data returned by the listen and grab functions can be serialized and de-serialized with
Serde if you install this library with the serialize feature.