Extending my GRUB theme swapper

In November, I created a Rust application that would allow me to swap between my grub themes easily and quickly, requiring only 2 clicks. Getting the paths to swap out the themes was easy, as it really just required opening up a directory and reading the directory name, then updating the grub config file with this directory and calling the update-grub function. In the past few weeks, I decided to expand this project by using HTML/CSS/JS to create a much nicer looking frontend. While the original Rust app was perfectly functional, it looked terrible: eGui screenshot In my rewrite of the app, the number 1 priority was to get a thumbnail image for each theme to work. I managed to achieve this and I’m happy enough with the results. Tauri screenshot # Adding Images The most important part of the rewrite was adding thumbnails to each button so that I know what each theme looks like before I install it. At the time of downloading new themes, I do know how they look but months onward I certainly won’t remember.

I thought this would be extremely easy by getting the path and plugging it into an html image tag, but this was not possible using javascript. I believe this is because Tauri uses a web view for its frontend which inherits the security traits found in browsers. Instead, I had to read the bytes using Rust and encode them manually before passing them over to javascript using a data URI scheme, which was something new I learned about when building this.

fn load_image(path: String) -> tauri::Result<String> {
    use std::fs::read;
    use base64::encode;

    let bytes = read(path)?;
    let encoded = encode(&bytes);
    Ok(format!("data:image/png;base64,{}", encoded))
}

Thankfully I was able to take the output of this function and input them into an img tag which worked just fine.

In order to actually get the image file paths so that I could load up an image using the function above, I first had to find the image that would be used as a thumbnail. I did this with this Rust function:

    pub fn largest_image_in_dir(dir: String) -> String {
        let path = Path::new(&dir);
        let mut largest_size: u64 = 0;
        let mut largest_file = String::new();

        fs::read_dir(path).unwrap().filter_map(Result::ok).filter(|entry| {
            let path = entry.path();
            let ext = path.extension().and_then(|os_str| os_str.to_str()).unwrap_or("");
            path.is_file() && (ext == "jpg" || ext == "jpeg" || ext == "png")
        })
            .for_each(|entry| {
                let size = entry.metadata().unwrap().len();
                if size > largest_size {
                    largest_size = size;
                    largest_file = entry.path().to_str().unwrap().to_string();
                }
            });

        return largest_file;
    }

The assumption I made with thumbnails was that the largest image file in a theme directory would be the main background image used in the theme, so the rust function above finds the largest image in the theme directory and returns its path, which is then handed to the load_image function. The function above looks more complex than it really is. Heres what it does: 1. read the directory path 2. if the directory path is valid, go to the filter_map which will return valid file paths within the directory 3. Among the files, filter out files that end in jpg, jpeg or png. Each successful image is then piped into the for_each loop, which will look at how large the file is and only keep the largest file by size. This way, I could get the thumbnails to each theme from the users file system.

Plugging it into JS

The primary reason I used Tauri is because it allowed me to use familiar front end languages with Rust which excited me. In this project, I had to pass the file paths of themes and their backgrounds to the front end to be displayed. To accomplish this, I first used a struct but that turned out to be a non-starter because it wasn’t so simple to access the struct in javascript. Although I used a struct on the Rust side to get the file path and thumbnail path, I had to do away with the struct by putting all the paths into one array. I kept it organized by having a theme path be followed by its thumbnail path. This way, in javascript I would only have to display the even-indexed theme paths while the odd-indexed thumbnail paths would be used to load an image.

Result

I’m happy with the app in its current state as it does what I need it to do. While I could continue adding in more features like wallpapers or changing the grub tone, I’ve decided it’s best to use my time making another project to learn a different framework, deepen my knowledge of rust and/or tauri, or learn something new entirely (like shaders) instead of the tedium of adding more.

I’ve uploaded the project to github here. Installation instructions are also there.

The code used for finding a users filepaths (to themes and to grub config) is located in /src-tauri/paths.rs

The rust bindings for javascript are found in /src-tauri/main.rs

javascript code dictating buttons and banners are in /src/main.js

HTML/SCSS is in /src/index.html and /src/styles.scss (compiled to css)