Initiating a call from a hardware IP phone running Windows. Getting to Know RUST

I once noticed that when I clicked links like «tel:,» Windows prompted me to call via Skype. But I don’t use Skype! I do, however, use a physical IP phone. And as sometimes happens, you lazily dial a number from some website, and even manage to mistype it… How cool would it be to make a call with just one click on a link? And as it turns out, this is entirely possible, since devices like Fanvil support HTTP request emulation. All that’s left is to write a small program that detects calls to certain links and transmits the dialed number to the device.

Choosing a programming language

What should I implement the program in? I was immediately advised to use Python. But damn it, why would I install an interpreter in Windows for such a trivial matter? I fundamentally wanted to write the program as an .exe file that wouldn’t require any special dependencies. And then I remembered that I’d long wanted to get acquainted with a young and promising competitor to C++, called Rust.
The occasion was simply brilliant, because the desired program is quite simple and does not require deep immersion, which is just right for a first combat experience, instead of a banal «hello world.» =)

Preparing the environment

And here, I think I won’t repeat a bunch of other instructions, but I’ll just give a link to the one that worked for me. In fact, it also includes a course on learning the language itself.

Actually, the code.

What does a sysadmin do when they absolutely need to write a program in an unfamiliar language? That’s right! Copy-pasting is everything. :) But in this case, ChatGPT was a huge help, and on about the 20th try, I was able to coax practically working code out of it. It was only in parts, of course, but I didn’t add much of my own. I only fixed some parts that the compiler was clearly complaining about.

The result was this:

 <pre class="wp-block-syntaxhighlighter-code">#![windows_subsystem = "windows"] // Объявляем, что мы на винде. В ином случае получится консольное приложение.

use std::env;
use reqwest;
use regex::Regex;
extern crate toml;
use std::fs;
use toml::Value;
use native_dialog::MessageType;
use native_dialog::MessageDialog;
use std::error::Error;

fn show_error(error_message: &str) { // Функция для отображения ошибок
    let result = MessageDialog::new()
        .set_type(MessageType::Error)
        .set_title("Ошибка")
        .set_text(error_message)
        .show_alert();
    if let Err(err) = result {
        eprintln!("Ошибка при отображении диалогового окна: {:?}", err);
    }
}

#[tokio::main] // тут что-то про асинхронность. :) 
async fn main() ->  Result<(), Box<dyn Error>> {
    if let Ok(current_exe) = env::current_exe() { // тут мы проверяем, где находится исполнимый файл
        if let Some(parent_dir) = current_exe.parent() { // для того, чтобы....
            let path = format!("{}\\config.toml", parent_dir.to_string_lossy()); // Подхватить лежащий рядом конфиг.
            let config_contents = fs::read_to_string(path)?; // Читаем файл
            let config: Value = toml::from_str(&config_contents)?; // Парсим
            let url2 = config["url"].to_string(); // Используем то, что там написано.
            let args: Vec<String> = env::args().collect();
            if args.len() > 1 {
                let re = Regex::new(r"\D+").unwrap();
                let phone_number = re.replace_all(&args[1], "");
                let url = format!("{}{}",url2.trim_matches('\"'), phone_number);
                let response = reqwest::get(&url).await?; // Отправляем номер методом GET
                let response_text = response.text().await?;
            } else {
                show_error("Нет номера телефона для обработки!"); // Внезапно, прогу запустили без параметров.
            }

        }
    }   
    Ok(()) // Всем спасибо, все свободны.
}</pre> 

Next: Cargo.toml. This contains some information about our program, as well as version dependencies.

[package]
name = "caller"
version = "0.1.0"
edition = "2021"
metadata = "icon.ico"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = "0.11"
tokio = { version = "1", features = ["full"] }
log = "0.4"
regex = "1.4"
toml = "0.8.0"
native-dialog = "0.6.4"

[build-dependencies]
winres = "0.1"

[build]
target = "x86_64-pc-windows-gnu"

And in theory, everything should build fine, but if something goes wrong, you can download the source code archive here . And if you’re too lazy to build it yourself, the ready-made software archive is here .

We link the program to links like tel: and sip:

Here, oddly enough, I encountered the most confusing part of my plan. I simply had no idea how to tell Windows to open links with my custom program. But since I had software on my computer that already did this (I think it was microsip), I decided to dig through the registry and look for entries that could make this happen. The result was this:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\caller]
@="C:\\Program Files\\Fanvil\\"

[HKEY_CURRENT_USER\Software\caller\Capabilities]
"ApplicationDescription"="Phone Adaptor"
"ApplicationName"="caller"

[HKEY_CURRENT_USER\Software\caller\Capabilities\UrlAssociations]
"tel"="caller"
"callto"="caller"
"sip"="caller"

[HKEY_CURRENT_USER\Software\RegisteredApplications]
"caller"="SOFTWARE\\caller\\Capabilities"

[HKEY_CURRENT_USER\Software\Classes\caller]
@="Internet Call Protocol"

[HKEY_CURRENT_USER\Software\Classes\caller\DefaultIcon]
@="C:\\Program Files\\Fanvil\\caller.exe,0"

[HKEY_CURRENT_USER\Software\Classes\caller\shell]

[HKEY_CURRENT_USER\Software\Classes\caller\shell\open]

[HKEY_CURRENT_USER\Software\Classes\caller\shell\open\command]
@="\"C:\\Program Files\\Fanvil\\caller.exe\" \"%1\""

Based on the paths listed above, you can understand where I placed the program itself.

Next to the program, there should be a config.toml file, which contains the URL for transferring the number. In my case, its contents look something like this:

# config.toml
url = "http://admin:пароль@192.168.0.10/cgi-bin/ConfigManApp.com?key="

Where 192.168.0.10 is the phone’s IP address, and the password and login are already clear… The phone must have the dial-via-URL function enabled.