12 Commits
14 changed files with 182 additions and 110 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Create release for pushed tag
on:
push:
tags:
- "*"
jobs:
release-on-tag:
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Create release
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
echo "A release already exists for $TAG, skipping"
else
cargo build --release
prev_tag=$(gh release list --limit 100 --json tagName --jq '.[0].tagName')
notes="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${prev_tag}...${TAG}"
gh release create "$TAG" target/release/gta-tools.exe \
--title "$TAG" \
--notes "$notes"
fi
@@ -0,0 +1,43 @@
name: Create releases for existing tags
on:
workflow_dispatch:
jobs:
release-retroactively:
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Get tags and create release for each
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mapfile -t tags < <(git tag -l)
echo "${#tags[@]} tags found."
prev_tag=""
for tag in "${tags[@]}"; do
if gh release view "$tag" >/dev/null 2>&1; then
echo "A release already exists for $tag, skipping"
else
echo "Proceeding with tag $tag"
git checkout "$tag"
cargo build --release
if [ -n "$prev_tag" ]; then
notes="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${prev_tag}...${tag}"
else
notes="**Full Changelog**: https://github.com/${{ github.repository }}/commits/${tag}"
fi
gh release create "$tag" target/release/gta-tools.exe \
--title "$tag" \
--notes "$notes"
fi
prev_tag="$tag"
done
Generated
+1 -1
View File
@@ -825,7 +825,7 @@ dependencies = [
[[package]] [[package]]
name = "gta-tools" name = "gta-tools"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"catppuccin-egui", "catppuccin-egui",
"chrono", "chrono",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gta-tools" name = "gta-tools"
version = "0.8.0" version = "0.9.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
+7 -4
View File
@@ -2,12 +2,15 @@
A toolset of convenient things for GTA V Online. A toolset of convenient things for GTA V Online.
![](https://i.vgy.me/9j6ZSO.png) ![](https://i.vgy.me/xJgskM.png) <picture>
<source srcset="https://i.vgy.me/M4sOHh.png" media="(prefers-color-scheme: dark)">
<img src="https://i.vgy.me/mpO9uc.png">
</picture>
## Installing ## Installing
**Option 1** — <ins>Download</ins> **Option 1** — <ins>Download</ins>
Download the latest release [here](https://codeberg.org/futile/gta-tools/releases/download/latest/gta-tools.exe) and place it somewhere convenient for you, such as Documents. You could then make a shortcut titled "GTA Tools", and pin it to taskbar or Start. Download the latest release [here](https://github.com/elituf/gta-tools/releases/latest/download/gta-tools.exe) and place it somewhere convenient for you, such as Documents. You could then make a shortcut titled "GTA Tools", and pin it to taskbar or Start.
**Option 2** — <ins>Build from source</ins> **Option 2** — <ins>Build from source</ins>
You will need the Rust toolchain, which can be obtained [here](https://rustup.rs). Follow the instructions of its installer. Once you have Rust installed, clone this repo and navigate to it. At this point, you should probably `git checkout x.x.x`, where `x.x.x` is the latest tag. You can then run `cargo build --release`. Once you do that, you can use the binary located at `.\target\release\gta-tools.exe` in the same way as **Option 1**. You will need the Rust toolchain, which can be obtained [here](https://rustup.rs). Follow the instructions of its installer. Once you have Rust installed, clone this repo and navigate to it. At this point, you should probably `git checkout x.x.x`, where `x.x.x` is the latest tag. You can then run `cargo build --release`. Once you do that, you can use the binary located at `.\target\release\gta-tools.exe` in the same way as **Option 1**.
@@ -16,7 +19,7 @@ You will need the Rust toolchain, which can be obtained [here](https://rustup.rs
Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin. Every feature of GTA Tools is Legacy/Enhanced-agnostic. Some functionality of GTA Tools requires administrator access. If necessary, GTA Tools can either be started as admin manually, or, the user can simply use the <kbd>Elevate</kbd> button to relaunch GTA Tools as admin.
It is recommended to always use an up-to-date version of GTA Tools from [releases](https://codeberg.org/futile/gta-tools/releases). You can also easily access the current latest release by going to the <kbd>About</kbd> page of GTA Tools and clicking the <kbd>↓</kbd> button near the version number. It is recommended to always use an up-to-date version of GTA Tools from [releases](https://github.com/elituf/gta-tools/releases). You can also easily access the current latest release by going to the <kbd>About</kbd> page of GTA Tools and clicking the <kbd>↓</kbd> button near the version number.
#### Game #### Game
@@ -24,7 +27,7 @@ This section is quite simple.
There is a <kbd>Launch</kbd> feature, which will start your game on the chosen launcher and game version. There is a <kbd>Launch</kbd> feature, which will start your game on the chosen launcher and game version.
There is also a <kbd>Force close game</kbd> feature, which simply kills all game processes. This *does not* touch Rockstar Games Launcher or any other processes, only ones named `GTA5_Enhanced.exe` or `GTA5.exe`. There is also a <kbd>Force close game</kbd> feature, which simply kills all game processes. This button requires a second press after the first one for confirmation. This *does not* touch Rockstar Games Launcher or any other processes, only ones named `GTA5_Enhanced.exe` or `GTA5.exe`.
#### Session #### Session
+9
View File
@@ -4,3 +4,12 @@ install:
cargo build --release cargo build --release
cp .\target\release\gta-tools.exe ~\.cargo\bin cp .\target\release\gta-tools.exe ~\.cargo\bin
cp .\target\release\gta-tools.exe ~\Documents cp .\target\release\gta-tools.exe ~\Documents
lint:
cargo clippy -- -W clippy::pedantic -W clippy::nursery -A clippy::cast_sign_loss -A clippy::cast_possible_truncation -A clippy::cast_possible_wrap
lint-full:
cargo clippy -- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used
lint-unwraps:
cargo clippy -- -W clippy::unwrap_used
+1 -1
View File
@@ -42,7 +42,7 @@ impl AntiAfk {
fn send(vk_codes: &[VIRTUAL_KEY]) { fn send(vk_codes: &[VIRTUAL_KEY]) {
let mut inputs = Vec::new(); let mut inputs = Vec::new();
for &vk_code in vk_codes { for &vk_code in vk_codes {
let scan_code = unsafe { MapVirtualKeyW(u32::from(vk_code.0), MAPVK_VK_TO_VSC) as u16 }; let scan_code = unsafe { MapVirtualKeyW(u32::from(vk_code.0), MAPVK_VK_TO_VSC) } as u16;
for event in [KEYBD_EVENT_FLAGS(0), KEYEVENTF_KEYUP] { for event in [KEYBD_EVENT_FLAGS(0), KEYEVENTF_KEYUP] {
let mut input = INPUT { let mut input = INPUT {
r#type: INPUT_KEYBOARD, r#type: INPUT_KEYBOARD,
+4 -8
View File
@@ -62,8 +62,7 @@ pub fn activate(game_handle: &mut HANDLE, system_info: &mut SystemInfo) -> Resul
let Some(pid) = get_gta_pid(system_info) else { let Some(pid) = get_gta_pid(system_info) else {
return Err(()); return Err(());
}; };
unsafe { match unsafe { OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) } {
match OpenProcess(PROCESS_SUSPEND_RESUME, false, pid) {
Ok(handle) => *game_handle = handle, Ok(handle) => *game_handle = handle,
Err(why) => { Err(why) => {
let message = format!("failed to suspend game for empty session:\n{why}"); let message = format!("failed to suspend game for empty session:\n{why}");
@@ -71,16 +70,13 @@ pub fn activate(game_handle: &mut HANDLE, system_info: &mut SystemInfo) -> Resul
return Err(()); return Err(());
} }
} }
let _ = NtSuspendProcess(*game_handle); let _ = unsafe { NtSuspendProcess(*game_handle) };
}
Ok(()) Ok(())
} }
pub fn deactivate(game_handle: &mut HANDLE) { pub fn deactivate(game_handle: &mut HANDLE) {
unsafe {
if !game_handle.is_invalid() { if !game_handle.is_invalid() {
let _ = NtResumeProcess(*game_handle); let _ = unsafe { NtResumeProcess(*game_handle) };
let _ = CloseHandle(*game_handle); let _ = unsafe { CloseHandle(*game_handle) };
}
} }
} }
+19 -23
View File
@@ -64,10 +64,8 @@ impl Default for GameNetworking {
impl Drop for GameNetworking { impl Drop for GameNetworking {
fn drop(&mut self) { fn drop(&mut self) {
unsafe {
if self.com_initialized { if self.com_initialized {
CoUninitialize(); unsafe { CoUninitialize() };
}
} }
} }
} }
@@ -79,44 +77,42 @@ impl GameNetworking {
return; return;
}; };
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().unwrap() }; let rules = unsafe { policy.Rules() }.unwrap();
let exe_path = BSTR::from(exe_path.to_string_lossy().to_string()); let exe_path = BSTR::from(exe_path.to_string_lossy().to_string());
for filter in [ for filter in [
(FILTER_NAME_IN, NET_FW_RULE_DIR_IN), (FILTER_NAME_IN, NET_FW_RULE_DIR_IN),
(FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT), (FILTER_NAME_OUT, NET_FW_RULE_DIR_OUT),
] { ] {
let _ = unsafe { rules.Remove(&BSTR::from(filter.0)) }; let _ = unsafe { rules.Remove(&BSTR::from(filter.0)) };
unsafe {
let rule: INetFwRule = let rule: INetFwRule =
CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER).unwrap(); unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }.unwrap();
rule.SetName(&BSTR::from(filter.0)).unwrap(); unsafe { rule.SetName(&BSTR::from(filter.0)) }.unwrap();
rule.SetApplicationName(&exe_path).unwrap(); unsafe { rule.SetApplicationName(&exe_path) }.unwrap();
rule.SetDirection(filter.1).unwrap(); unsafe { rule.SetDirection(filter.1) }.unwrap();
rule.SetEnabled(true.into()).unwrap(); unsafe { rule.SetEnabled(true.into()) }.unwrap();
rule.SetAction(NET_FW_ACTION_BLOCK).unwrap(); unsafe { rule.SetAction(NET_FW_ACTION_BLOCK) }.unwrap();
rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0).unwrap(); unsafe { rule.SetProtocol(NET_FW_IP_PROTOCOL_ANY.0) }.unwrap();
rules.Add(&rule).unwrap(); unsafe { rules.Add(&rule) }.unwrap();
}
} }
self.blocked_status = Self::is_blocked().into(); self.blocked_status = Self::is_blocked().into();
} }
pub fn unblock_all(&mut self) { pub fn unblock_all(&mut self) {
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().unwrap() }; let rules = unsafe { policy.Rules() }.unwrap();
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)).unwrap() }; unsafe { rules.Remove(&BSTR::from(FILTER_NAME_IN)) }.unwrap();
unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)).unwrap() }; unsafe { rules.Remove(&BSTR::from(FILTER_NAME_OUT)) }.unwrap();
self.blocked_status = Self::is_blocked().into(); self.blocked_status = Self::is_blocked().into();
} }
fn is_blocked() -> bool { fn is_blocked() -> bool {
let policy: INetFwPolicy2 = let policy: INetFwPolicy2 =
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER).unwrap() }; unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }.unwrap();
let rules = unsafe { policy.Rules().unwrap() }; let rules = unsafe { policy.Rules() }.unwrap();
let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)).is_ok() }; let in_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_IN)) }.is_ok();
let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)).is_ok() }; let out_rule_exists = unsafe { rules.Item(&BSTR::from(FILTER_NAME_OUT)) }.is_ok();
in_rule_exists || out_rule_exists in_rule_exists || out_rule_exists
} }
+11 -10
View File
@@ -22,19 +22,17 @@ pub struct Launch {
pub fn launch(platform: &Platform, version: &LaunchVersion) { pub fn launch(platform: &Platform, version: &LaunchVersion) {
match platform { match platform {
Platform::Steam => { Platform::Steam => {
let steam_url = if *version == LaunchVersion::Enhanced { let steam_url = match version {
"steam://run/3240220" LaunchVersion::Enhanced => "steam://run/3240220",
} else { LaunchVersion::Legacy => "steam://run/271590",
"steam://run/271590"
}; };
let _ = open::that_detached(steam_url); let _ = open::that_detached(steam_url);
} }
Platform::Rockstar => { Platform::Rockstar => {
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
let rockstar_url = if *version == LaunchVersion::Enhanced { let rockstar_url = match version {
r"SOFTWARE\WOW6432Node\Rockstar Games\GTAV Enhanced" LaunchVersion::Enhanced => r"SOFTWARE\WOW6432Node\Rockstar Games\GTAV Enhanced",
} else { LaunchVersion::Legacy => r"SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V",
r"SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V"
}; };
let Ok(gta_v_enhanced) = hklm.open_subkey(rockstar_url) else { let Ok(gta_v_enhanced) = hklm.open_subkey(rockstar_url) else {
return; return;
@@ -49,10 +47,13 @@ pub fn launch(platform: &Platform, version: &LaunchVersion) {
let _ = Command::new(play_gtav_path).spawn(); let _ = Command::new(play_gtav_path).spawn();
} }
Platform::Epic => { Platform::Epic => {
let epic_url = if *version == LaunchVersion::Enhanced { let epic_url = match version {
LaunchVersion::Enhanced => {
"com.epicgames.launcher://apps/8769e24080ea413b8ebca3f1b8c50951?action=launch&silent=true" "com.epicgames.launcher://apps/8769e24080ea413b8ebca3f1b8c50951?action=launch&silent=true"
} else { }
LaunchVersion::Legacy => {
"com.epicgames.launcher://apps/9d2d0eb64d5c44529cece33fe2a46482?action=launch&silent=true" "com.epicgames.launcher://apps/9d2d0eb64d5c44529cece33fe2a46482?action=launch&silent=true"
}
}; };
let _ = open::that_detached(epic_url); let _ = open::that_detached(epic_url);
} }
+2 -2
View File
@@ -227,7 +227,7 @@ impl App {
ui.label("with "); ui.label("with ");
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().visuals.hyperlink_color = colours::RED; ui.style_mut().visuals.hyperlink_color = colours::RED;
ui.hyperlink_to("", "https://codeberg.org/futile/gta-tools"); ui.hyperlink_to("", "https://github.com/elituf/gta-tools");
}); });
ui.label(" from "); ui.label(" from ");
ui.hyperlink_to("futile", "https://futile.eu"); ui.hyperlink_to("futile", "https://futile.eu");
@@ -244,7 +244,7 @@ impl App {
.on_hover_text("Go to current latest version."); .on_hover_text("Go to current latest version.");
if button.clicked() { if button.clicked() {
let _ = let _ =
open::that("https://codeberg.org/futile/gta-tools/releases/latest"); open::that("https://github.com/elituf/gta-tools/releases/latest");
} }
}); });
}); });
+3 -3
View File
@@ -21,10 +21,10 @@ impl App {
use windows::Win32::UI::WindowsAndMessaging::{ use windows::Win32::UI::WindowsAndMessaging::{
GetForegroundWindow, GetWindowTextW, GetForegroundWindow, GetWindowTextW,
}; };
let current_title = {
let mut buffer = [0; 512]; let mut buffer = [0; 512];
let current_title = unsafe { let hwnd = unsafe { GetForegroundWindow() };
let hwnd = GetForegroundWindow(); let length = unsafe { GetWindowTextW(hwnd, &mut buffer) };
let length = GetWindowTextW(hwnd, &mut buffer);
String::from_utf16_lossy(&buffer[..length as usize]) String::from_utf16_lossy(&buffer[..length as usize])
}; };
ui.label(format!("focused: \"{current_title}\"")); ui.label(format!("focused: \"{current_title}\""));
+21 -24
View File
@@ -1,12 +1,13 @@
use std::{ use std::{
os::windows::process::CommandExt, ffi::{OsStr, OsString},
os::windows::{ffi::OsStringExt, process::CommandExt},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
}; };
use windows::{ use windows::{
Win32::System::{ Win32::System::{
Diagnostics::ToolHelp::{ Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, PROCESSENTRY32, Process32First, Process32Next, CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW,
TH32CS_SNAPPROCESS, TH32CS_SNAPPROCESS,
}, },
Threading::{ Threading::{
@@ -20,7 +21,7 @@ use windows::{
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Process { pub struct Process {
pid: u32, pid: u32,
name: String, name: OsString,
exe: Option<PathBuf>, exe: Option<PathBuf>,
} }
@@ -29,7 +30,7 @@ impl Process {
self.pid self.pid
} }
pub fn name(&self) -> &str { pub fn name(&self) -> &OsStr {
&self.name &self.name
} }
@@ -56,23 +57,23 @@ pub struct SystemInfo {
impl SystemInfo { impl SystemInfo {
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
let mut processes = Vec::new(); let mut processes = Vec::new();
let snapshot_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0).unwrap() }; let snapshot_handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }.unwrap();
let mut process_entry = PROCESSENTRY32 { let mut process_entry = PROCESSENTRY32W {
dwSize: size_of::<PROCESSENTRY32>() as u32, dwSize: size_of::<PROCESSENTRY32W>() as u32,
..Default::default() ..Default::default()
}; };
unsafe { Process32First(snapshot_handle, &raw mut process_entry).unwrap() }; unsafe { Process32FirstW(snapshot_handle, &raw mut process_entry) }.unwrap();
let exe_full_path = get_exe_full_path(&process_entry); let exe_full_path = get_exe_full_path(&process_entry);
processes.push(Process { processes.push(Process {
pid: process_entry.th32ProcessID, pid: process_entry.th32ProcessID,
name: c_char_arr_to_string(&process_entry.szExeFile), name: wide_array_to_os_string(&process_entry.szExeFile),
exe: exe_full_path, exe: exe_full_path,
}); });
while unsafe { Process32Next(snapshot_handle, &raw mut process_entry) }.is_ok() { while unsafe { Process32NextW(snapshot_handle, &raw mut process_entry) }.is_ok() {
let exe_full_path = get_exe_full_path(&process_entry); let exe_full_path = get_exe_full_path(&process_entry);
processes.push(Process { processes.push(Process {
pid: process_entry.th32ProcessID, pid: process_entry.th32ProcessID,
name: c_char_arr_to_string(&process_entry.szExeFile), name: wide_array_to_os_string(&process_entry.szExeFile),
exe: exe_full_path, exe: exe_full_path,
}); });
} }
@@ -84,7 +85,7 @@ impl SystemInfo {
} }
} }
fn get_exe_full_path(process_entry: &PROCESSENTRY32) -> Option<PathBuf> { fn get_exe_full_path(process_entry: &PROCESSENTRY32W) -> Option<PathBuf> {
let process_handle_result = unsafe { let process_handle_result = unsafe {
OpenProcess( OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION,
@@ -92,29 +93,25 @@ fn get_exe_full_path(process_entry: &PROCESSENTRY32) -> Option<PathBuf> {
process_entry.th32ProcessID, process_entry.th32ProcessID,
) )
}; };
let mut exename = [0u16; 260];
let mut dwsize = exename.len() as u32;
process_handle_result.map_or(None, |process_handle| { process_handle_result.map_or(None, |process_handle| {
let mut exe_name = [0u16; 260];
let mut dw_size = exe_name.len() as u32;
let image_name_result = unsafe { let image_name_result = unsafe {
QueryFullProcessImageNameW( QueryFullProcessImageNameW(
process_handle, process_handle,
PROCESS_NAME_WIN32, PROCESS_NAME_WIN32,
PWSTR(exename.as_mut_ptr()), PWSTR(exe_name.as_mut_ptr()),
&raw mut dwsize, &raw mut dw_size,
) )
}; };
match image_name_result { match image_name_result {
Ok(()) => Some(PathBuf::from( Ok(()) => Some(PathBuf::from(wide_array_to_os_string(&exe_name))),
unsafe { PWSTR(exename.as_mut_ptr()).to_string() }.unwrap(),
)),
Err(_) => None, Err(_) => None,
} }
}) })
} }
fn c_char_arr_to_string(arr: &[i8]) -> String { fn wide_array_to_os_string(wide: &[u16]) -> OsString {
arr.iter() let null_pos = wide.iter().position(|&x| x == 0).unwrap_or(wide.len());
.take_while(|&&b| b != 0) OsString::from_wide(&wide[..null_pos])
.map(|&b| b as u8 as char)
.collect()
} }
+10 -14
View File
@@ -25,25 +25,21 @@ pub fn is_cursor_visible() -> bool {
cbSize: u32::try_from(std::mem::size_of::<CURSORINFO>()).unwrap(), cbSize: u32::try_from(std::mem::size_of::<CURSORINFO>()).unwrap(),
..Default::default() ..Default::default()
}; };
unsafe { unsafe { GetCursorInfo(&raw mut ci) }.unwrap();
GetCursorInfo(&raw mut ci).unwrap();
}
ci.flags == CURSOR_SHOWING ci.flags == CURSOR_SHOWING
} }
pub fn is_window_focused(target_title: &str) -> bool { pub fn is_window_focused(target_title: &str) -> bool {
let mut buffer = [0; 512]; let mut buffer = [0; 512];
unsafe { let hwnd = unsafe { GetForegroundWindow() };
let hwnd = GetForegroundWindow(); let length = unsafe { GetWindowTextW(hwnd, &mut buffer) };
let length = GetWindowTextW(hwnd, &mut buffer);
let current_title = String::from_utf16_lossy(&buffer[..length as usize]); let current_title = String::from_utf16_lossy(&buffer[..length as usize]);
current_title == target_title current_title == target_title
}
} }
pub fn is_any_key_pressed(keys: &[VIRTUAL_KEY]) -> bool { pub fn is_any_key_pressed(keys: &[VIRTUAL_KEY]) -> bool {
keys.iter() keys.iter()
.any(|&key| unsafe { (GetAsyncKeyState(i32::from(key.0)) & i16::MIN) != 0 }) .any(|&key| unsafe { GetAsyncKeyState(i32::from(key.0)) } & i16::MIN != 0)
} }
pub fn elevate(closing: ElevationExitMethod) { pub fn elevate(closing: ElevationExitMethod) {
@@ -66,22 +62,22 @@ pub fn elevate(closing: ElevationExitMethod) {
pub fn is_elevated() -> bool { pub fn is_elevated() -> bool {
let mut token: HANDLE = HANDLE::default(); let mut token: HANDLE = HANDLE::default();
unsafe { if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token) }.is_err() {
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token).is_err() {
return false; return false;
} }
let mut elevation = TOKEN_ELEVATION::default(); let mut elevation = TOKEN_ELEVATION::default();
let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap(); let mut size = u32::try_from(std::mem::size_of::<TOKEN_ELEVATION>()).unwrap();
let result = GetTokenInformation( let result = unsafe {
GetTokenInformation(
token, token,
TokenElevation, TokenElevation,
Some((&raw mut elevation).cast()), Some((&raw mut elevation).cast()),
size, size,
&raw mut size, &raw mut size,
); )
CloseHandle(token).unwrap(); };
unsafe { CloseHandle(token) }.unwrap();
result.is_ok() && elevation.TokenIsElevated != 0 result.is_ok() && elevation.TokenIsElevated != 0
}
} }
pub fn is_system_theme_dark() -> bool { pub fn is_system_theme_dark() -> bool {