DEV Community

Cover image for Bongo Copy Cat: A Tauri Cat that wants to be involved in everything you do πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’»
Abhijeet Singh
Abhijeet Singh

Posted on

Bongo Copy Cat: A Tauri Cat that wants to be involved in everything you do πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’»

Bongo Copy Cat wants to be involved in everything you do but instead just imitates you hitting your keyboard all day. After all it's just a cat 🐈.

I am new to Rust, and this is my first project built using the awesome programming language.

Check out the project on github. The documentation includes the executables and as well as instructions on building it locally.

GitHub logo abjt14 / bongo-copy-cat

Bongo Copy Cat wants to be involved in everything you do but instead just imitates you hitting your keyboard all day. After all it's just a cat.

Bongo Copy Cat

made-with-rust Build License

Introduction

Bongo Copy Cat wants to be involved in everything you do but instead just imitates you hitting your keyboard all day. After all it's just a cat.

A desktop app built using the Tauri framework. Supported platforms: MacOS and Windows.

This is a practice project for me to learn Rust and Tauri. Read more about the idea on my blog.

Here's the app in action

About the meme

Bongo Cat is a meme created by DitzyFlama (tweet) using StrayRogue's drawing of a cat.

Libraries that made this possible

  • Rdev for listening to global input events on Mac OS.
  • InputBot for listening to global input events on Windows.

Installation Methods

  • Download exectuables for MacOS and Windows from the releases page.
  • Build it yourself.

Building

You can always build the project youself. This will prevent the system from throwing warnings while…

Table of Contents

Dilemma

So here's the story. I decided to learn Rust, read the docs, solved the rustlings challenges. The next step in learning a new programming language is to build something with it. And so began my dilemma.

I can always build a simple todo app, a calculator, or something more challenging. But I am a creative, the urge to build something unique is too strong. And so I started brainstorming ideas.

Inspiration

A few weeks go by, I move on to my art projects while taking time out to find something fun to build with Rust. I know I know, this is not going good.

One night, as I was scrolling through Instagram, watching videos of cats, procrastinating going to sleep, I found my project.

There was a video of a lady explaining how she stopped her cat from sitting on her laptop while she works from home. She claimed that cats want to copy you, be involved in what you do, so how about we give the cat a laptop? She got her cat a mini toy laptop and it worked! While the lady was busy striking the keys on her laptop, the cat played with the toy pretending to work.

And so I thought, what if the Bongo Cat copied me working on my laptop.

Research

First, I made a list of the requirements.

  1. A Rust based global input listener that can listen to the keyboard events even if the app isn't in focus.
  2. A cross-platform Rust framework so I can target multiple platforms.
  3. An app to draw the cat meme.

I assumed that my research to find a Rust based global input listener will lead me to Stack Overflow, instead I found my answers on Reddit.

The two crates I found that worked for my use-case were Rdev and InputBot.

Tauri was the obvious choice for the cross platform application. With 57.3K stars on Github and growing, it is a framework loved by the community. It combines your favourite frontend frameworks with a Rust backend to build "smaller, faster, and more secure desktop applications".

Lastly, I wanted a drawing app for my simple use-case. Since I love open source apps, I chose Krita. It may be an overkill, but I have wanted to learn the application for a while so I can use it with my other projects.

Challenges

Rdev supported Window, MacOS, and Linux, but InputBot only supports Windows and Linux. Since I develop on a Mac OS system, I tested Rdev first.

Both libraries had partial support for linux and I had trouble getting them to work in my Tauri project. So I've skipped building the app for linux.

The next hurdle I ran into was the global event listener library itself. The GitHub documentation of Rdev mentions a few OS specific caveats in addition to the fact that the listener is "blocking".

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 => (),
    }
}
Enter fullscreen mode Exit fullscreen mode

While it was easy to get it working as a standalone Rust program, running the loop blocked Tauri from building the application.

After a lot of research and testing, I found that one way to make it work was to use the loop inside a tauri::command that can be invoked from the frontend once the application has loaded.

Here I define the tauri::command.

#[tauri::command]
fn listener(window: Window) {
    let emit_event = move || {
        if let Err(error) = window.eval("document.querySelector('button').click()") {
            println!("Error: {:?}", error)
        };
    };

    let callback = move |event: Event| {
        match event.name {
            Some(_string) => emit_event(),
            None => (),
        }
    };

    if let Err(error) = listen(callback) {
        println!("Error: {:?}", error)
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we need to use a Closure Function of the following form if we want to capture variables inside an inner function. For more details on why we have to do this, check out this answer on Stack Overflow.

let closure_fn = | args | -> () { do stuff };
Enter fullscreen mode Exit fullscreen mode

In order to communicate with the frontend, I used the window.eval() function to click on an HTML button I have in my index.html file.

Now that my app was running on Mac OS, I decided to test it on Windows. Unfortunately, as soon as the application launched, it crashed. After some testing, I figured out that the listener is blocking Tauri from running the app.

I tried using multithreading to prevent the listener from blocking but it didn't work. Since I couldn't figure out how to fix this, I decided to test the other library called InputBot.

The Github repository for the library has an example code snippet to bind all keys to the listener. Since this function wasn't blocking, it worked seamlessly without any issues.

Separating the dependencies and imports based on the operating system was pretty easy as it is supported by Rust out of the box.

In cargo.toml, I defined the conditional dependencies like this:

[target.'cfg(windows)'.dependencies]
inputbot = { git = "https://github.com/obv-mikhail/InputBot", branch = "develop" }

[target.'cfg(unix)'.dependencies]
rdev = "0.5.2"
Enter fullscreen mode Exit fullscreen mode

And in my main.rs file, I used conditional imports and functions like this:

#[cfg(target_os = "macos")]
use rdev::{listen, Event};

#[cfg(target_os = "windows")]
use inputbot::KeybdKey;

#[cfg(target_os = "macos")]
#[tauri::command]
fn listener(window: Window) {
    // code using "rdev"
}

#[cfg(target_os = "windows")]
#[tauri::command]
fn listener(window: Window) {
    // code using "inputBot"
}
Enter fullscreen mode Exit fullscreen mode

Lastly, the Tauri docs made it really easy to setup a Github Workflow to automate release builds. It's plug and play. Kudos to the contributors.

Wrap Up

Here's the app in action.
The app in action

Rust is awesome. I find "fixing bugs and issues" to be a great way to learn a new language/library/framework. The rust compiler makes you a better developer, allowing you to use good programming habits in other languages as well. Learning Rust means practice, practice, practice. And I'm gonna do just that.

Github Workflows is a lot simpler than I thought. I assumed it to have complicated code and low community support. I couldn't have been more wrong.

Tauri is an solid framework with a bright future. I know I have only scratched the surface here, but I look forward to building more complex applications in future.

For developers new to Rust, please understand that the one thing that makes the language difficult to learn is the approach it takes towards coding. Take your time to understand the system. It makes you a better developer, allowing you to use good programming habits in other languages as well.

I commend the Rust community and all the Rustaceans for creating such an amazing ecosystem.

This is my first blog post on this website. I appreciate all the questions and suggestions. Thank you for reading ❀️

Top comments (0)