Как-то раз я заметил, что при клике на ссылки типа tel: винда предлагает мне сделать вызов через skype. Но я не пользуюсь скайпом! А вот физическим IP телефоном очень даже пользуюсь. И как порой бывает, ты лениво набираешь на нём номер с какого-то сайта, а ещё умудряешься набрать его с ошибкой… Как было бы круто отправлять вызов одним кликом на ссылку? И как оказалось, это вполне реально, ведь такие аппараты, как Fanvil, поддерживают эмуляцию ввода через HTTP запрос. Осталось только написать небольшую программку, которая ловит вызов определённых ссылок и передаёт вызываемый номер аппарату.
Выбор языка программирования
На чём реализовать программу? Мне сразу же посоветовали python. Но чёрт побери, зачем мне ради такой мелочи ставить в винду интерпретатор? Я принципиально захотел запилить прогу в виде exe’шника, которому не нужны какие-то особенные зависимости. И тут я вспомнил, что уже давно хотел познакомиться с молодым и перспективным конкурентом C++, под названием Rust.
Повод был просто шикарный, потому как желаемая программа весьма проста и не требует глубокого погружения, что как раз подходит для первого боевого опыта, вместо банального “hello world”. =)
Подготовка среды
И вот тут я, пожалуй, не стану повторять кучу других инструкций, а просто дам ссылочку на ту, с которой у меня всё получилось. Собственно, там же и курс по изучению самого языка.
Собственно, код.
Что делает сисадмин, когда очень нужно написать программу на незнакомом языке? Правильно! Копипастинг – наше всё. :) Но в данном случае, мне круто помог ChatGPT и где-то с 20-й попытки получилось выудить у него практически рабочий код. По частям конечно, но тем не менее, от себя я практически ничего не добавлял. Разве что исправлял фрагменты, на которые явно жаловался компилятор.
В результате получилось вот это:
#![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(()) // Всем спасибо, все свободны.
}
Далее: Cargo.toml. Тут указывается некоторая инфа о нашей программе, а так-же зависимости с версиями.
[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"
И тут по идее всё должно собраться, но если что-то пошло не так – архив с исходниками можно качнуть тут. Ну а если собирать самостоятельно лень, то тут лежит архив с готовой софтиной.
Привязываем программу к ссылкам типа tel: и sip:
Тут, как ни странно, я столкнулся с самой непонятной частью моей затеи. Я просто не имел никакого понятия, как сообщить винде, что нужно открывать ссылки моей кастомной программой. Но имея на компе софт, который уже это делает (кажется это был microsip), я решил порыться в реестре и поискать записи, при помощи которых это может работать. В результате получилось следующее:
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\""
Исходя из указанных выше путей, можно понять, куда я разместил саму программу.
Рядом с программой должен лежать файл config.toml, в котором хранится URL для передачи номера. Его содержимое в моём случае выглядит примерно так:
# config.toml
url = "http://admin:пароль@192.168.0.10/cgi-bin/ConfigManApp.com?key="
Где 192.168.0.10 – это ip адрес телефона, ну а с паролем и логином и так всё понятно… В телефоне должна быть включена функция набора через URL.