Powoli zaczynam rozumieć, jak działa Rust.
Chcę napisać swoją pierwszą aplikację do obsługi myszy Razer Trinity w systemie Linux. Aplikacja musi umożliwiać: wykrywanie zmian panelu bocznego myszy, tworzenie profili dla konkretnych paneli, przypisywanie profili do programów oraz aktywację profilu podczas pracy z przypisanym do niego programem. Próbowałem zrobić to w Pythonie, ale utknąłem w martwym punkcie.
Teraz postanowiłem spróbować sił w języku Rust.
Muszę zaznaczyć, że jestem w tym języku zupełnie początkujący, ale postawiłem sobie to wyzwanie, by się go nauczyć. Na razie dysponuję kodem zamieszczonym poniżej; nie komunikuje się on jeszcze z właściwym sterownikiem myszy (rolę „sterownika” pełni na razie zwykły plik tekstowy), ale pętla działa poprawnie. Jeśli chodzi o OpenRazer, to niestety nie wykrywa on zmian panelu bocznego w tym modelu myszy, więc zamierzam spróbować obsłużyć to bezpośrednio, odczytując dane z `hidraw`.
Korzystam z pomocy sztucznej inteligencji przy tworzeniu aplikacji, ale nie zlecam jej napisania całego kodu; używam jej raczej do zrozumienia struktury języka i uzyskiwania sugestii dotyczących rozwiązań.
Oto dotychczasowe efekty – to mój pierwszy dzień programowania, choć spędziłem już około 10 godzin na analizie materiałów o języku Rust dostępnych w sieci. Dajcie znać, co o tym sądzicie i czy macie jakieś pomysły.
Z góry dziękuję za pomoc.
// Importujemy potrzebne narzędzia z biblioteki standardowej Rusta (std)
use std::fs; // fs - do pracy z plikami (zapis/odczyt)
use std::thread::sleep; // sleep - do wstrzymywania działania programu
use std::time::Duration; // Duration - do określania czasu (np. sekundy)
/// 1. DEFINICJE TYPÓW DANYCH (Poza funkcją main)
// Enum (typ wyliczeniowy) reprezentuje zamkniętą listę możliwości.
// Makro #[derive(...)] automatycznie generuje dodatkowe funkcjonalności:
// - Debug: pozwala na wypisywanie enuma w konsoli za pomocą {:?}
// - PartialEq: pozwala na porównywanie wartości za pomocą "=="
// - Clone: umożliwia tworzenie kopii tego obiektu w pamięci
#[derive(Debug, PartialEq, Clone)]
enum TypPanelu {
DwaPrzyciski,
SiedemPrzyciskow,
DwanasciePrzyciskow,
}
// Struktura to kontener łączący różne powiązane ze sobą dane.
#[derive(Debug, Clone)]
struct Profil {
nazwa: String, // Ludzka nazwa profilu
program: String, // Nazwa procesu/programu (np. "csgo")
typ_panelu: TypPanelu, // Jaki panel jest wymagany dla tego profilu (używamy enuma powyżej)
}
/// 2. GŁÓWNA FUNKCJA PROGRAMU
fn main() {
// Tworzymy przykładowy profil.
// String::from() jest potrzebne, bo tekst w cudzysłowie to tzw. &str (niezmienny wycinek),
// a nasza struktura wymaga pełnoprawnego, modyfikowalnego obiektu typu String.
let profil_do_gier = Profil {
nazwa: String::from("Profil do CS-a"),
program: String::from("csgo"),
typ_panelu: TypPanelu::SiedemPrzyciskow,
};
// Wypisujemy informacje startowe w konsoli
println!("Wczytano profil: {} (Wymaga panelu: {:?})", profil_do_gier.nazwa, profil_do_gier.typ_panelu);
println!("--- Uruchamiam monitorowanie myszki ---");
// Zmienna mutowalna (mut), która służy jako "pamięć" programu.
// Przechowuje stan panelu z poprzedniej sekundy, dzięki czemu program
// nie wyświetla komunikatów cały czas, a reaguje TYLKO na zmiany.
let mut poprzedni_panel = String::new();
// Uruchamiamy nieskończoną pętlę (tzw. demon lub usługa w tle)
loop {
// Próba odczytania pliku tekstowego symulującego sterownik.
// fs::read_to_string zwraca typ Result (sukces lub błąd), dlatego musimy użyć 'match'.
let wynik_czytania = fs::read_to_string("C:\\Users\\Adam Graszk\\zgadywanka\\razer_panel.txt");
match wynik_czytania {
// PRZYPADEK 1: Plik udało się odczytać, a jego zawartość trafia do zmiennej `zawartosc`
Ok(zawartosc) => {
// .trim() usuwa białe znaki z początku i końca (np. spacja lub znak nowej linii \n)
let oczyszczony_tekst = zawartosc.trim();
// Sprawdzamy, czy obecna zawartość pliku różni się od tego, co zapamiętaliśmy sekundę temu
if oczyszczony_tekst != poprzedni_panel {
// Drugi match: sprawdzamy co dokładnie znajduje się w pliku
match oczyszczony_tekst {
"2" => println!("Wykryto panel z 2 przyciskami!"),
"7" => println!("Wykryto panel z 7 przyciskami!"),
"12" => println!("Wykryto panel z 12 przyciskami!"),
// Gwiazdka/podkreślenie (_) to tzw. catch-all. Obsługuje każdy inny tekst
_ => println!("Nieznany panel lub brak panelu!"),
}
// Aktualizujemy naszą "pamięć" o nowy stan panelu, konwertując &str na String
poprzedni_panel = oczyszczony_tekst.to_string();
}
}
// PRZYPADEK 2: Nie udało się odczytać pliku (np. został usunięty lub brak uprawnień)
// Podkreślenie przed '_blad' mówi kompilatorowi, że wiemy o istnieniu błędu, ale go ignorujemy.
Err(_blad) => {
println!("Ostrzeżenie: Nie można odczytać sterownika. Próba za sekundę...");
// Resetujemy pamięć, aby po naprawieniu błędu program na nowo wykrył panel
poprzedni_panel = String::new();
}
}
// Wstrzymujemy działanie pętli na 1 sekundę, aby program nie zużywał 100% mocy procesora
sleep(Duration::from_secs(1));
}
}