Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb27334f57
|
||
|
|
1e13905885
|
||
|
|
8fc73b2992
|
||
|
|
43b72995dd
|
||
|
|
d3473d46e3
|
||
|
|
1363f5b125
|
||
|
|
d15b8070ec | ||
|
|
efccd6ed50 | ||
|
|
5c73ee7bd5 | ||
|
|
445b63fa4c | ||
|
|
941868dcc8 | ||
|
|
762a3b9b26 |
@@ -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
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
A toolset of convenient things for GTA V Online.
|
A toolset of convenient things for GTA V Online.
|
||||||
|
|
||||||
 
|
<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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,23 +62,23 @@ 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 {
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
|
|||||||
Reference in New Issue
Block a user