Compare commits
6 Commits
644cfa4128
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
c9f8325da9
|
|||
|
2bb5ce0327
|
|||
|
9b43f600b8
|
|||
|
75230a5cc7
|
|||
|
c313131a88
|
|||
|
8a889aef95
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
### C++ template
|
||||
# Prerequisites
|
||||
*.d
|
||||
#*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
|
||||
135
CONTRIBUTING.md
Normal file
135
CONTRIBUTING.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Contributing to OpenVPC Shift Tool
|
||||
|
||||
Thank you for your interest in contributing to the OpenVPC Shift Tool! This document provides guidelines and instructions for contributing to the project.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please be respectful and considerate of others when contributing to this project. We aim to foster an inclusive and welcoming community.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository on GitHub
|
||||
2. Clone your fork locally:
|
||||
```
|
||||
git clone https://github.com/YOUR-USERNAME/vpc-shift-tool.git
|
||||
cd vpc-shift-tool
|
||||
```
|
||||
3. Add the original repository as an upstream remote:
|
||||
```
|
||||
git remote add upstream https://github.com/RavenX8/vpc-shift-tool.git
|
||||
```
|
||||
4. Create a new branch for your changes:
|
||||
```
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
1. Install the Rust toolchain from [rustup.rs](https://rustup.rs/)
|
||||
2. Install dependencies:
|
||||
- Windows: No additional dependencies required
|
||||
- Linux: `sudo apt install libudev-dev pkg-config` (Ubuntu/Debian)
|
||||
|
||||
3. Build the project:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
|
||||
4. Run the application:
|
||||
```
|
||||
cargo run
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
|
||||
1. Make your changes to the codebase
|
||||
2. Write or update tests as necessary
|
||||
3. Ensure all tests pass:
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
4. Format your code:
|
||||
```
|
||||
cargo fmt
|
||||
```
|
||||
5. Run the linter:
|
||||
```
|
||||
cargo clippy
|
||||
```
|
||||
|
||||
## Commit Guidelines
|
||||
|
||||
- Use clear and descriptive commit messages
|
||||
- Reference issue numbers in your commit messages when applicable
|
||||
- Keep commits focused on a single change
|
||||
- Use the present tense ("Add feature" not "Added feature")
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Update your fork with the latest changes from upstream:
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/main
|
||||
```
|
||||
2. Push your changes to your fork:
|
||||
```
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
3. Create a pull request through the GitHub interface
|
||||
4. Ensure your PR description clearly describes the changes and their purpose
|
||||
5. Link any related issues in the PR description
|
||||
|
||||
## Code Style
|
||||
|
||||
- Follow the Rust style guidelines
|
||||
- Use meaningful variable and function names
|
||||
- Add comments for complex logic
|
||||
- Document public functions and types
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/main.rs`: Application entry point and main structure
|
||||
- `src/about.rs`: About screen information
|
||||
- `src/config.rs`: Configuration handling
|
||||
- `src/device.rs`: Device representation and management
|
||||
- `src/hid_worker.rs`: HID communication worker thread
|
||||
- `src/state.rs`: Application state management
|
||||
- `src/ui.rs`: User interface components
|
||||
- `src/util.rs`: Utility functions and constants
|
||||
|
||||
## Adding Support for New Devices
|
||||
|
||||
If you're adding support for new device types:
|
||||
|
||||
1. Update the device detection logic in `device.rs`
|
||||
2. Add any necessary report format definitions in `util.rs`
|
||||
3. Test with the actual hardware if possible
|
||||
4. Document the new device support in your PR
|
||||
|
||||
## Testing
|
||||
|
||||
- Write unit tests for new functionality
|
||||
- Test on both Windows and Linux if possible
|
||||
- Test with actual VirPil hardware if available
|
||||
|
||||
## Documentation
|
||||
|
||||
- Update the README.md with any new features or changes
|
||||
- Document new functions and types with rustdoc comments
|
||||
- Update TECHNICAL.md for significant architectural changes
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you find a bug or have a suggestion for improvement:
|
||||
|
||||
1. Check if the issue already exists in the [GitHub Issues](https://github.com/RavenX8/vpc-shift-tool/issues)
|
||||
2. If not, create a new issue with:
|
||||
- A clear title and description
|
||||
- Steps to reproduce the issue
|
||||
- Expected and actual behavior
|
||||
- System information (OS, Rust version, etc.)
|
||||
- Screenshots if applicable
|
||||
|
||||
## License
|
||||
|
||||
By contributing to this project, you agree that your contributions will be licensed under the project's [GNU General Public License v3.0](LICENSE).
|
||||
92
Cargo.lock
generated
92
Cargo.lock
generated
@@ -377,7 +377,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -412,7 +412,7 @@ checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -565,7 +565,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -701,7 +701,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -889,7 +889,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1073,7 +1073,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1228,7 +1228,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1279,7 +1279,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1682,7 +1682,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1937,6 +1937,26 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mock-it"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c79f6245a4564f117ae4e640a829a7b425e641ca3e6ea279d74a9caf05d2daf"
|
||||
dependencies = [
|
||||
"mock-it_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mock-it_codegen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a887ee7c909093b773c59ee57412f0fd29d2f262905eeea721cfc31a38e18f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "23.1.0"
|
||||
@@ -2043,7 +2063,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2370,7 +2390,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2401,7 +2421,7 @@ checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2704,7 +2724,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2727,7 +2747,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2764,6 +2784,7 @@ dependencies = [
|
||||
"fast_config",
|
||||
"hidapi",
|
||||
"log",
|
||||
"mock-it",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -2890,6 +2911,17 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.96"
|
||||
@@ -2909,7 +2941,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2961,7 +2993,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2972,7 +3004,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3046,7 +3078,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3193,7 +3225,7 @@ dependencies = [
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3228,7 +3260,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3560,7 +3592,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3571,7 +3603,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3979,7 +4011,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4039,7 +4071,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"zbus-lockstep",
|
||||
"zbus_xml",
|
||||
"zvariant",
|
||||
@@ -4054,7 +4086,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
@@ -4100,7 +4132,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4120,7 +4152,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4143,7 +4175,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4168,7 +4200,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
@@ -4180,5 +4212,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -5,6 +5,14 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[[bin]]
|
||||
name = "shift_tool"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "vpc_shift_tool"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.4", features = ["derive"] }
|
||||
eframe = "0.30.0"
|
||||
@@ -21,3 +29,6 @@ chrono = "0.4.40"
|
||||
logging = []
|
||||
|
||||
default = ["logging"]
|
||||
|
||||
[dev-dependencies]
|
||||
mock-it = "0.9.0"
|
||||
|
||||
150
INSTALL.md
Normal file
150
INSTALL.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Installation Guide for OpenVPC Shift Tool
|
||||
|
||||
This guide provides detailed instructions for installing and setting up the OpenVPC Shift Tool on different operating systems.
|
||||
|
||||
## Windows Installation
|
||||
|
||||
### Using Pre-built Binary
|
||||
|
||||
1. Download the Windows build artifact (`Windows-x86_64_build`) from the [GitHub Actions](https://github.com/RavenX8/vpc-shift-tool/actions) page by selecting the most recent successful workflow run
|
||||
2. The downloaded artifact is the executable binary itself (shift_tool.exe)
|
||||
3. Place it in a location of your choice and run it
|
||||
|
||||
### Building from Source on Windows
|
||||
|
||||
1. Install the Rust toolchain from [rustup.rs](https://rustup.rs/)
|
||||
2. Open Command Prompt or PowerShell
|
||||
3. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/RavenX8/vpc-shift-tool.git
|
||||
cd vpc-shift-tool
|
||||
```
|
||||
4. Build the release version:
|
||||
```
|
||||
cargo build --release
|
||||
```
|
||||
5. The executable will be available at `target\release\shift_tool.exe`
|
||||
|
||||
## Linux Installation
|
||||
|
||||
### Using Pre-built Binary
|
||||
|
||||
1. Download the Linux build artifact (`Linux-x86_64_build`) from the [GitHub Actions](https://github.com/RavenX8/vpc-shift-tool/actions) page by selecting the most recent successful workflow run
|
||||
2. The downloaded artifact is the executable binary itself, no extraction needed. Just make it executable:
|
||||
```
|
||||
chmod +x Linux-x86_64_build
|
||||
# Optionally rename it to something more convenient
|
||||
mv Linux-x86_64_build shift_tool
|
||||
```
|
||||
3. Create and install the udev rules for device access:
|
||||
```
|
||||
sudo mkdir -p /etc/udev/rules.d/
|
||||
# Create the udev rule file
|
||||
echo -e '# Virpil Control devices\nSUBSYSTEM=="usb", ATTRS{idVendor}=="3344", TAG+="uaccess", GROUP:="input"' | sudo tee /etc/udev/rules.d/70-vpc.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
4. Make the binary executable and run it:
|
||||
```
|
||||
chmod +x shift_tool
|
||||
./shift_tool
|
||||
```
|
||||
|
||||
### Building from Source on Linux
|
||||
|
||||
1. Install dependencies:
|
||||
```
|
||||
# Ubuntu/Debian
|
||||
sudo apt install build-essential libudev-dev pkg-config
|
||||
|
||||
# Fedora
|
||||
sudo dnf install gcc libudev-devel pkgconfig
|
||||
|
||||
# Arch Linux
|
||||
sudo pacman -S base-devel
|
||||
```
|
||||
|
||||
2. Install the Rust toolchain from [rustup.rs](https://rustup.rs/)
|
||||
|
||||
3. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/RavenX8/vpc-shift-tool.git
|
||||
cd vpc-shift-tool
|
||||
```
|
||||
|
||||
4. Build and install using the Makefile:
|
||||
```
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Or manually:
|
||||
```
|
||||
cargo build --release
|
||||
sudo cp target/release/shift_tool /usr/local/bin/
|
||||
sudo mkdir -p /etc/udev/rules.d/
|
||||
sudo cp udev/rules.d/70-vpc.rules /etc/udev/rules.d/
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
## Verifying Installation
|
||||
|
||||
After installation, you can verify that the application can detect your VirPil devices:
|
||||
|
||||
1. Connect your VirPil device(s) to your computer
|
||||
2. Launch the OpenVPC Shift Tool
|
||||
3. Click the "Refresh Devices" button
|
||||
4. Your devices should appear in the dropdown menus
|
||||
|
||||
If devices are not detected:
|
||||
|
||||
- On Windows, ensure you have the correct drivers installed
|
||||
- On Linux, verify the udev rules are installed correctly and you've reloaded the rules
|
||||
- Check that your devices are properly connected and powered on
|
||||
|
||||
## Configuration Location
|
||||
|
||||
The application stores its configuration in:
|
||||
|
||||
- Windows: `%APPDATA%\shift_tool.json`
|
||||
- Linux: `~/.config/shift_tool.json`
|
||||
|
||||
## Uninstallation
|
||||
|
||||
### Windows
|
||||
|
||||
Simply delete the application files.
|
||||
|
||||
### Linux
|
||||
|
||||
If installed using the Makefile:
|
||||
```
|
||||
sudo rm /usr/local/bin/shift_tool
|
||||
sudo rm /etc/udev/rules.d/70-vpc.rules
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Linux Permission Issues
|
||||
|
||||
If you encounter permission issues accessing the devices on Linux:
|
||||
|
||||
1. Ensure the udev rules are installed correctly
|
||||
2. Add your user to the input group:
|
||||
```
|
||||
sudo usermod -a -G input $USER
|
||||
```
|
||||
3. Log out and log back in, or reboot your system
|
||||
4. Verify your user is in the input group:
|
||||
```
|
||||
groups $USER
|
||||
```
|
||||
|
||||
### Windows Device Access Issues
|
||||
|
||||
If the application cannot access devices on Windows:
|
||||
|
||||
1. Ensure no other application (like the official VPC software) is currently using the devices
|
||||
2. Try running the application as Administrator (right-click, "Run as Administrator")
|
||||
3. Check Device Manager to ensure the devices are properly recognized by Windows
|
||||
2
Makefile
2
Makefile
@@ -25,7 +25,7 @@ install:
|
||||
install -d $(DESTDIR)$(PREFIX)/bin
|
||||
install -d $(DESTDIR)/etc/udev/rules.d
|
||||
install -m 0755 target/$(target)/$(prog)$(extension) $(DESTDIR)$(PREFIX)/bin
|
||||
install -m 0644 udev/rules.d/99-vpc.rules $(DESTDIR)/etc/udev/rules.d
|
||||
install -m 0644 udev/rules.d/70-vpc.rules $(DESTDIR)/etc/udev/rules.d
|
||||
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
110
README.md
110
README.md
@@ -1,2 +1,110 @@
|
||||
# vpc-shift-tool
|
||||
# OpenVPC - Shift Tool
|
||||
|
||||
A free and open-source alternative to the VPC Shift Tool bundled with the VirPil control software package.
|
||||
|
||||
## Overview
|
||||
|
||||
OpenVPC Shift Tool is a utility designed for VirPil flight simulation controllers. It allows you to combine button inputs from multiple VirPil devices using logical operations (OR, AND, XOR), creating a "shift state" that can be sent to receiver devices. This enables more complex control schemes and button combinations for flight simulators.
|
||||
|
||||
## Features
|
||||
|
||||
- Connect to multiple VirPil devices simultaneously
|
||||
- Configure source devices that provide button inputs
|
||||
- Set up receiver devices that receive the combined shift state
|
||||
- Choose between different logical operations (OR, AND, XOR) for each bit
|
||||
- Automatic device detection for VirPil hardware
|
||||
- Configuration saving and loading
|
||||
- Cross-platform support (Windows and Linux)
|
||||
|
||||
## Installation
|
||||
|
||||
### Pre-built Binaries
|
||||
|
||||
Pre-built binaries for Windows and Linux are available in the GitHub Actions artifacts for each commit. You can find them by:
|
||||
|
||||
1. Going to the [Actions tab](https://github.com/RavenX8/vpc-shift-tool/actions)
|
||||
2. Selecting the most recent successful workflow run
|
||||
3. Downloading the appropriate artifact for your platform (Linux-x86_64_build or Windows-x86_64_build)
|
||||
|
||||
### Building from Source
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
- Rust toolchain (install from [rustup.rs](https://rustup.rs/))
|
||||
- For Linux: libudev-dev package
|
||||
|
||||
#### Build Steps
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/RavenX8/vpc-shift-tool.git
|
||||
cd vpc-shift-tool
|
||||
|
||||
# Build the release version
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The compiled binary will be available in `target/release/`.
|
||||
|
||||
### Linux Installation
|
||||
|
||||
On Linux, you need to install udev rules to access VirPil devices without root privileges:
|
||||
|
||||
```bash
|
||||
# Using the Makefile
|
||||
sudo make install
|
||||
|
||||
# Or manually
|
||||
sudo cp udev/rules.d/70-vpc.rules /etc/udev/rules.d/
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Launch the application
|
||||
2. The main interface shows three sections:
|
||||
- **Sources**: Devices that provide button inputs
|
||||
- **Rules**: Logical operations to apply to each bit
|
||||
- **Receivers**: Devices that receive the combined shift state
|
||||
|
||||
3. Add source devices by selecting them from the dropdown menu
|
||||
4. Configure which bits are active for each source
|
||||
5. Set the logical operation (OR, AND, XOR) for each bit in the Rules section
|
||||
6. Add receiver devices that will receive the combined shift state
|
||||
7. Click "Start" to begin the shift operation
|
||||
|
||||
## Configuration
|
||||
|
||||
The application automatically saves your configuration to:
|
||||
- Windows: `%APPDATA%\shift_tool.json`
|
||||
- Linux: `~/.config/shift_tool.json`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Device Not Detected
|
||||
|
||||
- Ensure your VirPil devices are properly connected
|
||||
- On Linux, verify udev rules are installed correctly
|
||||
- Try refreshing the device list with the "Refresh Devices" button
|
||||
|
||||
### Permission Issues on Linux
|
||||
|
||||
If you encounter permission issues accessing the devices on Linux:
|
||||
|
||||
1. Ensure the udev rules are installed correctly
|
||||
2. Log out and log back in, or reboot your system
|
||||
3. Verify your user is in the "input" group: `groups $USER`
|
||||
|
||||
## License
|
||||
|
||||
GNU General Public License v3.0
|
||||
|
||||
## Author
|
||||
|
||||
RavenX8
|
||||
|
||||
## Links
|
||||
|
||||
- [GitHub Repository](https://github.com/RavenX8/vpc-shift-tool)
|
||||
- [Gitea Repository](https://gitea.azgstudio.com/Raven/vpc-shift-tool)
|
||||
|
||||
116
TECHNICAL.md
Normal file
116
TECHNICAL.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# OpenVPC Shift Tool - Technical Documentation
|
||||
|
||||
This document provides technical details about the OpenVPC Shift Tool application, its architecture, and how it works internally.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The application is built using Rust with the following main components:
|
||||
|
||||
1. **GUI Layer**: Uses the `eframe` crate (egui) for cross-platform GUI
|
||||
2. **HID Communication**: Uses the `hidapi` crate to communicate with VirPil devices
|
||||
3. **Configuration Management**: Uses the `fast_config` crate for saving/loading settings
|
||||
4. **Worker Thread**: Background thread that handles device communication
|
||||
|
||||
## Core Components
|
||||
|
||||
### Main Application Structure
|
||||
|
||||
The main application is represented by the `ShiftTool` struct in `src/main.rs`, which contains:
|
||||
|
||||
- State management
|
||||
- Device list
|
||||
- Shared state between UI and worker thread
|
||||
- Configuration data
|
||||
|
||||
### Modules
|
||||
|
||||
- **about.rs**: Contains application information and about screen text
|
||||
- **config.rs**: Configuration data structures and serialization
|
||||
- **device.rs**: Device representation and management
|
||||
- **hid_worker.rs**: Background worker thread for HID communication
|
||||
- **state.rs**: Application state enum
|
||||
- **ui.rs**: User interface drawing and event handling
|
||||
- **util.rs**: Utility functions and constants
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. The application scans for VirPil devices (vendor ID 0x3344)
|
||||
2. User selects source and receiver devices in the UI
|
||||
3. When "Start" is clicked, a worker thread is spawned
|
||||
4. The worker thread:
|
||||
- Opens connections to all configured devices
|
||||
- Reads input from source devices
|
||||
- Applies logical operations based on configuration
|
||||
- Writes the resulting shift state to receiver devices
|
||||
5. Shared state (protected by mutexes) is used to communicate between the UI and worker thread
|
||||
|
||||
## Device Communication
|
||||
|
||||
### Device Detection
|
||||
|
||||
Devices are detected using the HID API, filtering for VirPil's vendor ID (0x3344). The application creates `VpcDevice` objects for each detected device, which include:
|
||||
|
||||
- Vendor ID and Product ID
|
||||
- Device name and firmware version
|
||||
- Serial number
|
||||
- Usage page/ID
|
||||
|
||||
### HID Protocol
|
||||
|
||||
The application supports different report formats based on device firmware versions. The worker thread:
|
||||
|
||||
1. Reads HID reports from source devices
|
||||
2. Extracts button states from the reports
|
||||
3. Applies logical operations (OR, AND, XOR) to combine states
|
||||
4. Formats the combined state into HID reports
|
||||
5. Sends the reports to receiver devices
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is stored in JSON format using the `fast_config` crate. The configuration includes:
|
||||
|
||||
- Source devices (vendor ID, product ID, serial number, enabled bits)
|
||||
- Receiver devices (vendor ID, product ID, serial number, enabled bits)
|
||||
- Shift modifiers (logical operations for each bit)
|
||||
|
||||
## Threading Model
|
||||
|
||||
The application uses a main UI thread and a separate worker thread:
|
||||
|
||||
1. **Main Thread**: Handles UI rendering and user input
|
||||
2. **Worker Thread**: Performs HID communication in the background
|
||||
|
||||
Thread synchronization is achieved using:
|
||||
- `Arc<Mutex<T>>` for shared state
|
||||
- `Arc<(Mutex<bool>, Condvar)>` for signaling thread termination
|
||||
|
||||
## Linux-Specific Features
|
||||
|
||||
On Linux, the application requires udev rules to access HID devices without root privileges. The rule is installed to `/etc/udev/rules.d/70-vpc.rules` and contains:
|
||||
|
||||
```
|
||||
# Virpil Control devices
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="3344", TAG+="uaccess", GROUP:="input"
|
||||
```
|
||||
|
||||
## Building and Deployment
|
||||
|
||||
The application can be built using Cargo:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
A Makefile is provided for easier installation on Linux, which:
|
||||
1. Builds the application
|
||||
2. Installs the binary to `/usr/local/bin`
|
||||
3. Installs udev rules to `/etc/udev/rules.d/`
|
||||
|
||||
## Future Development
|
||||
|
||||
Potential areas for enhancement:
|
||||
- Support for additional device types
|
||||
- More complex logical operations
|
||||
- Custom button mapping
|
||||
- Profile management
|
||||
- Integration with game APIs
|
||||
@@ -1,7 +1,7 @@
|
||||
use hidapi::{DeviceInfo, HidApi, HidError};
|
||||
use log::{error, info, warn, debug, trace}; // Use log crate
|
||||
use hidapi::{DeviceInfo, HidApi};
|
||||
use log::{error, warn, debug, trace}; // Use log crate
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::rc::Rc; // Keep Rc for potential sharing within UI if needed
|
||||
use std::rc::Rc;
|
||||
|
||||
// Represents a discovered VPC device
|
||||
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Clone)]
|
||||
@@ -73,7 +73,6 @@ impl Default for SavedDevice {
|
||||
|
||||
/// Finds the index in the `device_list` corresponding to the saved device data.
|
||||
/// Returns 0 (default "No Connection") if not found or if saved_device is invalid.
|
||||
// Make this function standalone or static, not requiring &self
|
||||
pub(crate) fn find_device_index_for_saved(
|
||||
device_list: &[VpcDevice], // Pass device list explicitly
|
||||
saved_device: &SavedDevice,
|
||||
@@ -94,7 +93,6 @@ pub(crate) fn find_device_index_for_saved(
|
||||
|
||||
// --- Device Management Functions ---
|
||||
|
||||
// Now part of ShiftTool impl block
|
||||
impl crate::ShiftTool {
|
||||
/// Refreshes the internal list of available HID devices.
|
||||
pub(crate) fn refresh_devices(&mut self) {
|
||||
@@ -168,20 +166,6 @@ impl crate::ShiftTool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the index in the `device_list` corresponding to the saved receiver config.
|
||||
pub(crate) fn find_receiver_device_index(&self, receiver_config_index: usize) -> usize {
|
||||
self.find_device_index_for_saved(
|
||||
&self.config.data.receivers[receiver_config_index]
|
||||
)
|
||||
}
|
||||
|
||||
/// Finds the index in the `device_list` corresponding to the saved source config.
|
||||
pub(crate) fn find_source_device_index(&self, source_config_index: usize) -> usize {
|
||||
self.find_device_index_for_saved(
|
||||
&self.config.data.sources[source_config_index]
|
||||
)
|
||||
}
|
||||
|
||||
/// Generic helper to find a device index based on SavedDevice data.
|
||||
fn find_device_index_for_saved(&self, saved_device: &SavedDevice) -> usize {
|
||||
if saved_device.vendor_id == 0 && saved_device.product_id == 0 {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::config::{ModifiersArray};
|
||||
use crate::device::SavedDevice;
|
||||
use crate::{SharedDeviceState, SharedStateFlag}; // Import shared types
|
||||
use crate::util::{self, merge_u8_into_u16, read_bit, set_bit, ReportFormat, MAX_REPORT_SIZE};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use hidapi::{HidApi, HidDevice, HidError};
|
||||
use crate::util::{self, ReportFormat, MAX_REPORT_SIZE};
|
||||
use log::{error, info, trace, warn};
|
||||
use hidapi::{HidApi, HidDevice};
|
||||
use std::{
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -259,14 +258,14 @@ fn run_hid_worker_loop(hidapi: HidApi, data: WorkerData) {
|
||||
let mut read_buffer = [0u8; MAX_REPORT_SIZE];
|
||||
let mut write_buffer = [0u8; MAX_REPORT_SIZE]; // Buffer for calculated output
|
||||
|
||||
let &(ref run_lock, ref run_cvar) = &*data.run_state;
|
||||
let &(ref run_lock, ref _run_cvar) = &*data.run_state;
|
||||
|
||||
loop {
|
||||
// --- Check Run State ---
|
||||
let should_run = { // Scope for mutex guard
|
||||
match run_lock.lock() {
|
||||
Ok(guard) => *guard,
|
||||
Err(poisoned) => {
|
||||
Err(_poisoned) => {
|
||||
error!("Run state mutex poisoned in worker loop!");
|
||||
false
|
||||
}
|
||||
@@ -404,21 +403,6 @@ fn run_hid_worker_loop(hidapi: HidApi, data: WorkerData) {
|
||||
// If send fails later, reopen will be attempted then.
|
||||
}
|
||||
}
|
||||
|
||||
// --- Read current state (Optional Merge) ---
|
||||
read_buffer[0] = receiver_format.report_id; // Use receiver format ID
|
||||
let mut receiver_current_state: u16 = 0;
|
||||
log::trace!("Worker: Reading current state from receiver[{}] before merge using format '{}'.", i, receiver_format.name);
|
||||
match device.get_feature_report(&mut read_buffer) {
|
||||
Ok(bytes_read) => {
|
||||
// --- Use correct unpack_state function name ---
|
||||
if let Some(current_state) = receiver_format.unpack_state(&read_buffer[0..bytes_read]) {
|
||||
// --- End change ---
|
||||
receiver_current_state = current_state;
|
||||
} else { /* warn unpack failed */ }
|
||||
}
|
||||
Err(e_read) => { /* warn read failed */ }
|
||||
}
|
||||
state_to_send |= receiver_current_state; // Merge
|
||||
// --- End Read current state ---
|
||||
|
||||
@@ -538,7 +522,7 @@ fn run_hid_worker_loop(hidapi: HidApi, data: WorkerData) {
|
||||
if let Ok(mut guard) = shared_state.lock() { *guard = 0; }
|
||||
}
|
||||
}
|
||||
Err(e_actual) => {
|
||||
Err(_e_actual) => {
|
||||
if let Some(shared_state) = data.receiver_states_shared.get(i) {
|
||||
if let Ok(mut guard) = shared_state.lock() { *guard = 0; }
|
||||
}
|
||||
|
||||
78
src/lib.rs
Normal file
78
src/lib.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
// Export modules for testing
|
||||
pub mod about;
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod hid_worker;
|
||||
pub mod state;
|
||||
pub mod ui;
|
||||
pub mod util;
|
||||
|
||||
// Re-export main struct and types for testing
|
||||
pub use crate::config::ConfigData;
|
||||
pub use crate::device::VpcDevice;
|
||||
pub use crate::state::State;
|
||||
|
||||
// Constants
|
||||
pub const PROGRAM_TITLE: &str = "OpenVPC - Shift Tool";
|
||||
pub const INITIAL_WIDTH: f32 = 740.0;
|
||||
pub const INITIAL_HEIGHT: f32 = 260.0;
|
||||
|
||||
// Type aliases for shared state
|
||||
pub use std::sync::{Arc, Condvar, Mutex};
|
||||
pub type SharedStateFlag = Arc<(Mutex<bool>, Condvar)>;
|
||||
pub type SharedDeviceState = Arc<Mutex<u16>>;
|
||||
|
||||
// Args struct for command line parsing
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[arg(short, long, default_value_t = false)]
|
||||
pub skip_firmware: bool,
|
||||
}
|
||||
|
||||
// Wrapper for ConfigData to match the actual structure
|
||||
pub use fast_config::Config;
|
||||
|
||||
// The main application struct
|
||||
pub struct ShiftTool {
|
||||
// State
|
||||
pub state: State,
|
||||
pub thread_state: SharedStateFlag, // Is the worker thread running?
|
||||
|
||||
// Device Data
|
||||
pub device_list: Vec<VpcDevice>, // List of discovered compatible devices
|
||||
|
||||
// Shared state between UI and Worker Thread
|
||||
pub shift_state: SharedDeviceState, // Current shift state
|
||||
pub source_states: Vec<SharedDeviceState>, // Current state of each source device
|
||||
pub receiver_states: Vec<SharedDeviceState>, // Current state of each receiver device
|
||||
|
||||
// Configuration
|
||||
pub config: Config<ConfigData>,
|
||||
pub selected_source: usize,
|
||||
pub selected_receiver: usize,
|
||||
}
|
||||
|
||||
// Implementations for ShiftTool
|
||||
impl ShiftTool {
|
||||
// Add a new source state tracking object
|
||||
pub fn add_source_state(&mut self) {
|
||||
self.source_states.push(Arc::new(Mutex::new(0)));
|
||||
}
|
||||
|
||||
// Add a new receiver state tracking object
|
||||
pub fn add_receiver_state(&mut self) {
|
||||
self.receiver_states.push(Arc::new(Mutex::new(0)));
|
||||
}
|
||||
|
||||
// Get the current thread status
|
||||
pub fn get_thread_status(&self) -> bool {
|
||||
let &(ref lock, _) = &*self.thread_state;
|
||||
match lock.lock() {
|
||||
Ok(guard) => *guard,
|
||||
Err(_) => false, // Return false if the mutex is poisoned
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main.rs
11
src/main.rs
@@ -19,7 +19,7 @@ use clap::Parser;
|
||||
|
||||
// Internal Module Imports
|
||||
use config::{ConfigData}; // Import specific items
|
||||
use device::{VpcDevice, SavedDevice};
|
||||
use device::{VpcDevice};
|
||||
use state::State; // Import the State enum
|
||||
|
||||
// Constants
|
||||
@@ -82,7 +82,7 @@ impl Default for ShiftTool {
|
||||
device_list: vec![],
|
||||
source_states: vec![],
|
||||
receiver_states: vec![],
|
||||
shift_state: Arc::new((Mutex::new(0))), // Keep Condvar if needed for shift_state?
|
||||
shift_state: Arc::new(Mutex::new(0)), // Keep Condvar if needed for shift_state?
|
||||
thread_state: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
config,
|
||||
}
|
||||
@@ -115,13 +115,13 @@ impl ShiftTool {
|
||||
// Helper to add state tracking for a new source
|
||||
fn add_source_state(&mut self) {
|
||||
self.source_states
|
||||
.push(Arc::new((Mutex::new(0))));
|
||||
.push(Arc::new(Mutex::new(0)));
|
||||
}
|
||||
|
||||
// Helper to add state tracking for a new receiver
|
||||
fn add_receiver_state(&mut self) {
|
||||
self.receiver_states
|
||||
.push(Arc::new((Mutex::new(0))));
|
||||
.push(Arc::new(Mutex::new(0)));
|
||||
}
|
||||
|
||||
// Helper to get thread status (could be in ui.rs or main.rs)
|
||||
@@ -172,10 +172,9 @@ impl eframe::App for ShiftTool {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
log::debug!("Update Called.");
|
||||
// Request repaint ensures GUI updates even if worker is slow
|
||||
ctx.request_repaint_after(Duration::from_millis(50)); // e.g., 10 FPS target
|
||||
ctx.request_repaint_after(Duration::from_millis(50));
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// Use show_inside_add to handle resize correctly
|
||||
egui::Resize::default()
|
||||
.default_width(INITIAL_WIDTH)
|
||||
.default_height(INITIAL_HEIGHT)
|
||||
|
||||
34
src/ui.rs
34
src/ui.rs
@@ -1,7 +1,7 @@
|
||||
use crate::about;
|
||||
use crate::config::{ShiftModifiers};
|
||||
use crate::device::VpcDevice; // Assuming VpcDevice has Display impl
|
||||
use crate::{ShiftTool, INITIAL_HEIGHT, INITIAL_WIDTH, PROGRAM_TITLE}; // Import main struct
|
||||
use crate::{ShiftTool, INITIAL_WIDTH, PROGRAM_TITLE}; // Import main struct
|
||||
use crate::state::State;
|
||||
use crate::util::read_bit; // Import utility
|
||||
use eframe::egui::{self, Color32, Context, ScrollArea, Ui};
|
||||
@@ -117,6 +117,7 @@ pub(crate) fn draw_running_state(
|
||||
}
|
||||
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].set_width(612 as f32);
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false, false])
|
||||
.show(&mut columns[0], |ui| {
|
||||
@@ -130,6 +131,7 @@ pub(crate) fn draw_running_state(
|
||||
});
|
||||
});
|
||||
|
||||
columns[1].set_width(128 as f32);
|
||||
columns[1].vertical(|ui| {
|
||||
draw_control_buttons(app, ui, ctx, thread_running);
|
||||
});
|
||||
@@ -348,7 +350,7 @@ fn device_selector_combo(
|
||||
};
|
||||
|
||||
ui.add_enabled_ui(!disabled, |ui| {
|
||||
egui::ComboBox::from_id_source(id_source)
|
||||
egui::ComboBox::from_id_salt(id_source)
|
||||
.width(300.0) // Adjust width as needed
|
||||
.selected_text(selected_text)
|
||||
.show_ui(ui, |ui| {
|
||||
@@ -462,34 +464,6 @@ fn draw_status_bits(
|
||||
});
|
||||
}
|
||||
|
||||
/// Draws the ONLINE/OFFLINE status indicator.
|
||||
fn draw_online_status(
|
||||
ui: &mut Ui,
|
||||
saved_device_config: &crate::device::SavedDevice, // Pass the config for this slot
|
||||
thread_running: bool,
|
||||
) {
|
||||
// Infer status: Online if thread is running AND device is configured (VID/PID != 0)
|
||||
let is_configured = saved_device_config.vendor_id != 0 && saved_device_config.product_id != 0;
|
||||
|
||||
// Determine status text and color
|
||||
let (text, color) = if thread_running && is_configured {
|
||||
// We assume the worker *tries* to talk to configured devices.
|
||||
// A more advanced check could involve reading another shared state
|
||||
// updated by the worker indicating recent success/failure for this device.
|
||||
("ONLINE", Color32::GREEN)
|
||||
} else if !is_configured {
|
||||
("UNCONFIGURED", Color32::YELLOW) // Show if slot is empty
|
||||
} else { // Thread not running or device not configured
|
||||
("OFFLINE", Color32::GRAY)
|
||||
};
|
||||
|
||||
// Use selectable_label for consistent look, but make it non-interactive
|
||||
// Set 'selected' argument to false as it's just a status display
|
||||
ui.selectable_label(false, egui::RichText::new(text).color(color));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Draws the control buttons in the right column.
|
||||
fn draw_control_buttons(
|
||||
app: &mut ShiftTool,
|
||||
|
||||
23
src/util.rs
23
src/util.rs
@@ -1,6 +1,6 @@
|
||||
use clap::Parser;
|
||||
use chrono::NaiveDate;
|
||||
use log::{error, info, trace, warn};
|
||||
use log::{error, trace, warn};
|
||||
|
||||
pub(crate) const FEATURE_REPORT_ID_SHIFT: u8 = 4;
|
||||
|
||||
@@ -150,7 +150,7 @@ struct FormatRule {
|
||||
const FORMAT_RULES: &[FormatRule] = &[
|
||||
// Rule 1: Check for Original format based on date
|
||||
FormatRule {
|
||||
matches: |name, fw| {
|
||||
matches: |_name, fw| {
|
||||
const THRESHOLD: &str = "2024-12-26";
|
||||
let date_str = fw.split_whitespace().last().unwrap_or("");
|
||||
if date_str.len() == 8 {
|
||||
@@ -203,25 +203,6 @@ pub(crate) fn read_bit(value: u16, position: u8) -> bool {
|
||||
(value & (1 << position)) != 0
|
||||
}
|
||||
|
||||
/// Sets or clears a specific bit in a u8 value.
|
||||
/// `bit_position` is 0-indexed (0-7).
|
||||
/// Returns the modified u8 value.
|
||||
pub(crate) fn set_bit(value: u8, bit_position: u8, bit_value: bool) -> u8 {
|
||||
if bit_position > 7 {
|
||||
warn!("set_bit called with invalid position: {}", bit_position);
|
||||
return value; // Return original value on error
|
||||
}
|
||||
if bit_value {
|
||||
value | (1 << bit_position) // Set the bit to 1
|
||||
} else {
|
||||
value & !(1 << bit_position) // Set the bit to 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines high and low bytes into a u16 value.
|
||||
pub(crate) fn merge_u8_into_u16(high_byte: u8, low_byte: u8) -> u16 {
|
||||
(high_byte as u16) << 8 | (low_byte as u16)
|
||||
}
|
||||
|
||||
/// Checks if a device firmware string is supported.
|
||||
/// TODO: Implement actual firmware checking logic if needed.
|
||||
|
||||
197
tests/basic_tests.rs
Normal file
197
tests/basic_tests.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use vpc_shift_tool::config::{ConfigData, ShiftModifiers, ModifiersArray};
|
||||
use vpc_shift_tool::device::{SavedDevice, VpcDevice};
|
||||
use vpc_shift_tool::state::State;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn test_config_data_default() {
|
||||
// Test that the default ConfigData is created correctly
|
||||
let config = ConfigData::default();
|
||||
|
||||
// Check that sources and receivers are empty
|
||||
assert_eq!(config.sources.len(), 0);
|
||||
assert_eq!(config.receivers.len(), 0);
|
||||
|
||||
// Check that shift_modifiers has the default value (all OR)
|
||||
for i in 0..8 {
|
||||
assert_eq!(config.shift_modifiers[i], ShiftModifiers::OR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shift_modifiers_display() {
|
||||
// Test the Display implementation for ShiftModifiers
|
||||
assert_eq!(format!("{}", ShiftModifiers::OR), "OR");
|
||||
assert_eq!(format!("{}", ShiftModifiers::AND), "AND");
|
||||
assert_eq!(format!("{}", ShiftModifiers::XOR), "XOR");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_saved_device_default() {
|
||||
// Test that the default SavedDevice is created correctly
|
||||
let device = SavedDevice::default();
|
||||
|
||||
assert_eq!(device.vendor_id, 0);
|
||||
assert_eq!(device.product_id, 0);
|
||||
assert_eq!(device.serial_number, "");
|
||||
assert_eq!(device.state_enabled, [true; 8]); // All bits enabled by default
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_enum() {
|
||||
// Test that the State enum has the expected variants
|
||||
let initializing = State::Initialising;
|
||||
let about = State::About;
|
||||
let running = State::Running;
|
||||
|
||||
// Test that the variants are different
|
||||
assert_ne!(initializing, about);
|
||||
assert_ne!(initializing, running);
|
||||
assert_ne!(about, running);
|
||||
|
||||
// Test equality with same variant
|
||||
assert_eq!(initializing, State::Initialising);
|
||||
assert_eq!(about, State::About);
|
||||
assert_eq!(running, State::Running);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_with_devices() {
|
||||
// Test creating a ConfigData with sources and receivers
|
||||
let mut config = ConfigData::default();
|
||||
|
||||
// Create some test devices
|
||||
let device1 = SavedDevice {
|
||||
vendor_id: 0x3344,
|
||||
product_id: 0x0001,
|
||||
serial_number: "123456".to_string(),
|
||||
state_enabled: [true, false, true, false, true, false, true, false],
|
||||
};
|
||||
|
||||
let device2 = SavedDevice {
|
||||
vendor_id: 0x3344,
|
||||
product_id: 0x0002,
|
||||
serial_number: "654321".to_string(),
|
||||
state_enabled: [false, true, false, true, false, true, false, true],
|
||||
};
|
||||
|
||||
// Add devices to sources and receivers
|
||||
config.sources.push(device1.clone());
|
||||
config.receivers.push(device2.clone());
|
||||
|
||||
// Check that the devices were added correctly
|
||||
assert_eq!(config.sources.len(), 1);
|
||||
assert_eq!(config.receivers.len(), 1);
|
||||
|
||||
assert_eq!(config.sources[0].vendor_id, 0x3344);
|
||||
assert_eq!(config.sources[0].product_id, 0x0001);
|
||||
assert_eq!(config.sources[0].serial_number, "123456");
|
||||
assert_eq!(config.sources[0].state_enabled, [true, false, true, false, true, false, true, false]);
|
||||
|
||||
assert_eq!(config.receivers[0].vendor_id, 0x3344);
|
||||
assert_eq!(config.receivers[0].product_id, 0x0002);
|
||||
assert_eq!(config.receivers[0].serial_number, "654321");
|
||||
assert_eq!(config.receivers[0].state_enabled, [false, true, false, true, false, true, false, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modifiers_array() {
|
||||
// Test the ModifiersArray implementation
|
||||
let mut modifiers = ModifiersArray::default();
|
||||
|
||||
// Check default values
|
||||
for i in 0..8 {
|
||||
assert_eq!(modifiers[i], ShiftModifiers::OR);
|
||||
}
|
||||
|
||||
// Test setting values
|
||||
modifiers[0] = ShiftModifiers::AND;
|
||||
modifiers[4] = ShiftModifiers::XOR;
|
||||
|
||||
// Check the modified values
|
||||
assert_eq!(modifiers[0], ShiftModifiers::AND);
|
||||
assert_eq!(modifiers[4], ShiftModifiers::XOR);
|
||||
|
||||
// Check that other values remain unchanged
|
||||
for i in 1..4 {
|
||||
assert_eq!(modifiers[i], ShiftModifiers::OR);
|
||||
}
|
||||
for i in 5..8 {
|
||||
assert_eq!(modifiers[i], ShiftModifiers::OR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vpc_device_default() {
|
||||
// Test the default VpcDevice implementation
|
||||
let device = VpcDevice::default();
|
||||
|
||||
assert_eq!(device.full_name, "");
|
||||
assert_eq!(*device.name, "-NO CONNECTION (Select device from list)-");
|
||||
assert_eq!(*device.firmware, "");
|
||||
assert_eq!(device.vendor_id, 0);
|
||||
assert_eq!(device.product_id, 0);
|
||||
assert_eq!(device.serial_number, "");
|
||||
assert_eq!(device.usage, 0);
|
||||
assert_eq!(device.active, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vpc_device_display() {
|
||||
// Test the Display implementation for VpcDevice
|
||||
|
||||
// Test default device
|
||||
let device = VpcDevice::default();
|
||||
assert_eq!(format!("{}", device), "-NO CONNECTION (Select device from list)-");
|
||||
|
||||
// Test a real device
|
||||
let device = VpcDevice {
|
||||
full_name: "3344:0001:123456".to_string(),
|
||||
name: Rc::new("VPC MongoosT-50CM3".to_string()),
|
||||
firmware: Rc::new("VIRPIL Controls 20240101".to_string()),
|
||||
vendor_id: 0x3344,
|
||||
product_id: 0x0001,
|
||||
serial_number: "123456".to_string(),
|
||||
usage: 0,
|
||||
active: false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", device),
|
||||
"VID:3344 PID:0001 VPC MongoosT-50CM3 (SN:123456 FW:VIRPIL Controls 20240101)"
|
||||
);
|
||||
|
||||
// Test a device with empty serial number
|
||||
let device = VpcDevice {
|
||||
full_name: "3344:0001:no_sn".to_string(),
|
||||
name: Rc::new("VPC MongoosT-50CM3".to_string()),
|
||||
firmware: Rc::new("VIRPIL Controls 20240101".to_string()),
|
||||
vendor_id: 0x3344,
|
||||
product_id: 0x0001,
|
||||
serial_number: "".to_string(),
|
||||
usage: 0,
|
||||
active: false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", device),
|
||||
"VID:3344 PID:0001 VPC MongoosT-50CM3 (SN:N/A FW:VIRPIL Controls 20240101)"
|
||||
);
|
||||
|
||||
// Test a device with empty firmware
|
||||
let device = VpcDevice {
|
||||
full_name: "3344:0001:123456".to_string(),
|
||||
name: Rc::new("VPC MongoosT-50CM3".to_string()),
|
||||
firmware: Rc::new("".to_string()),
|
||||
vendor_id: 0x3344,
|
||||
product_id: 0x0001,
|
||||
serial_number: "123456".to_string(),
|
||||
usage: 0,
|
||||
active: false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", device),
|
||||
"VID:3344 PID:0001 VPC MongoosT-50CM3 (SN:123456 FW:N/A)"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user