1
\$\begingroup\$

The program is supposed to be set as the default browser in Windows and allows me to select which browser profile I want to use when I open a link from another program. I have quite some experience in PHP, Java, C, C++, Pascal, but this is my first venture into Rust. Before I continue adding features I'd like to know how good my understanding of the language is and what could be solved better than what I found.

#![windows_subsystem = "windows"]

use nwg::Button;
use nwg::WindowFlags;
use std::process::Command;
use serde::Deserialize;
use std::env;
use std::fs;
use std::rc::Rc;
use std::sync::Arc;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Settings {
    browsers: Vec<Browser>
}

#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Browser {
    name: String,
    command_line: String,
    arguments: Vec<String>,
    icon: String,
}

fn run_browser(browser: &Browser, url: String) {
    let mut arguments: Vec<String> = Vec::new();
    for argument in &browser.arguments {
        arguments.push(argument.clone());
    }
    arguments.push(url);
    
    let _process = match Command::new(&browser.command_line)
        .args(arguments)
        .spawn() {
            Ok(process) => process,
            Err(err)    => panic!("Running process error: {}", err),
        };

    nwg::stop_thread_dispatch();
    std::process::exit(0);
}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");

    let mut window = Default::default();
    let mut url_edit = Default::default();
    let layout = Default::default();

    let appdata = env::var("APPDATA").unwrap();
    let settings_filepath = format!("{}/CrossRoad/settings.json", appdata);
    let settings_file = fs::read_to_string(settings_filepath).expect("Unable to read settings.json");
    let parsed_settings: Settings = serde_json::from_str(&settings_file).expect("Error reading settings.json");
    let settings = Arc::new(parsed_settings);
    
    let mut buttons: Vec<Button> = Vec::new(); 

    let args: Vec<String> = env::args().collect();
    let mut url="";
    if args.len() > 1 {
        url = &args[1];
    }

    nwg::Window::builder()
        .center(true)
        .size((600, 250))
        .flags(WindowFlags::WINDOW | WindowFlags::POPUP)
        .title("CrossRoad")
        .build(&mut window)
        .unwrap();

    nwg::TextInput::builder()
        .text(url)
        .focus(true)
        .parent(&window)
        .build(&mut url_edit)
        .unwrap();

    let mut buttons_layout = nwg::GridLayout::builder()
        .parent(&window)
        .spacing(1)
        .child_item(nwg::GridLayoutItem::new(&url_edit, 0, 0, settings.browsers.len() as u32, 1));

    let mut button_count = 0;
    for browser in &settings.browsers {
        let mut button = Default::default();
        nwg::Button::builder()
        .text(&browser.name)
        .parent(&window)
        .build(&mut button)
        .unwrap();
        buttons_layout = buttons_layout.child_item(nwg::GridLayoutItem::new(&button, button_count, 1, 1, 1));
        button_count += 1;
        buttons.push(button);
    };
    
    buttons_layout.build(&layout)
        .unwrap();

    let window = Rc::new(window);
    window.set_visible(true);
    let events_window = window.clone();

    let handler = nwg::full_bind_event_handler(&window.handle, move |evt, _evt_data, handle| {
        use nwg::Event as E;

        match evt {
            E::OnWindowClose => 
                if &handle == &events_window as &nwg::Window {
                    nwg::stop_thread_dispatch();
                },
            E::OnButtonClick => 
                for button in &buttons {
                    if &handle == &button.handle {
                        for browser in &settings.browsers {
                            if &browser.name == &button.text() {
                                run_browser(&browser, url_edit.text());
                            }
                        }
                    }
                },
            _ => {}
        }
    });

    nwg::dispatch_thread_events();
    nwg::unbind_event_handler(&handler);
}

I'm aware that there already are some programs that do that, but most of them are very outdated and limited, as they only allow to predefine URL lists that are used to decide which browser to use for which URL. I only found a single program that allows an interactive selection and that was bloated up to 50MB, which indicates that it was written using a framework like Electron. I prefer optimized programs that are written for the intended platform. I wanted to dive into Rust for some time now and decided to use this as an opportunity to learn it.

Here is the complete project on GitHub.

What feels "dirty" to me:

  • building the arguments array in the function run_browser. It seems superfluous to create a new vector of strings and iterate over an existing vector of strings and add each string one by one, but I was unable to find a way to just use, clone or cast the existing vector.
  • finding the correct browser after a button click by comparing the button text. I'd rather store a reference to the button in the Browser object, but I couldn't find how to ignore this during the serialization/deserialization.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.