Compare commits

...

2 Commits

Author SHA1 Message Date
c9f8325da9 Add unit tests for core components
Some checks failed
Makefile CI / Release - Linux-x86_64 (push) Has been cancelled
Makefile CI / Release - Windows-x86_64 (push) Has been cancelled
2025-04-10 13:44:53 -04:00
2bb5ce0327 Rename udev rules file and update documentation
- Renamed udev rules file from 99-vpc.rules to 70-vpc.rules for better priority
- Updated all references to the file in Makefile and documentation
- Added instructions for manually creating udev rules when using precompiled binaries
- Improved installation instructions for both Windows and Linux platforms
2025-04-10 13:44:52 -04:00
11 changed files with 860 additions and 33 deletions

2
.gitignore vendored
View File

@@ -4,7 +4,7 @@
### C++ template
# Prerequisites
*.d
#*.d
# Compiled Object files
*.slo

135
CONTRIBUTING.md Normal file
View 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
View File

@@ -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",
]

View File

@@ -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
View 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

View File

@@ -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
View File

@@ -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
View 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

78
src/lib.rs Normal file
View 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
}
}
}

197
tests/basic_tests.rs Normal file
View 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)"
);
}