First release
This commit is contained in:
commit
aa7b7d6204
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "aimpremote"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["aNNiMON <annimon119@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
winapi="*"
|
||||||
|
user32-sys="*"
|
||||||
|
kernel32-sys="*"
|
366
src/aimpremote.rs
Normal file
366
src/aimpremote.rs
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use ::{std, user32, kernel32};
|
||||||
|
use std::mem;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use winapi::winuser::WM_USER;
|
||||||
|
use winapi::basetsd::INT64;
|
||||||
|
use winapi::minwindef::{BOOL, DWORD, UINT};
|
||||||
|
use winapi::memoryapi::FILE_MAP_READ;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::os::windows::ffi::OsStringExt;
|
||||||
|
|
||||||
|
|
||||||
|
const AIMP_REMOTE_ACCESS_MAP_FILE_SIZE : u64 = 2048;
|
||||||
|
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
struct AIMPRemoteFileInfo {
|
||||||
|
Deprecated1: DWORD,
|
||||||
|
Active: BOOL,
|
||||||
|
BitRate: DWORD,
|
||||||
|
Channels: DWORD,
|
||||||
|
Duration: DWORD,
|
||||||
|
FileSize: INT64,
|
||||||
|
FileMark: DWORD,
|
||||||
|
SampleRate: DWORD,
|
||||||
|
TrackNumber: DWORD,
|
||||||
|
AlbumLength: DWORD,
|
||||||
|
ArtistLength: DWORD,
|
||||||
|
DateLength: DWORD,
|
||||||
|
FileNameLength: DWORD,
|
||||||
|
GenreLength: DWORD,
|
||||||
|
TitleLength: DWORD,
|
||||||
|
Deprecated2: [DWORD; 6]
|
||||||
|
}
|
||||||
|
|
||||||
|
const WM_AIMP_COMMAND : UINT = WM_USER + 0x75;
|
||||||
|
const WM_AIMP_NOTIFY : UINT = WM_USER + 0x76;
|
||||||
|
const WM_AIMP_PROPERTY : UINT = WM_USER + 0x77;
|
||||||
|
|
||||||
|
const AIMP_RA_PROPVALUE_GET : u32 = 0;
|
||||||
|
const AIMP_RA_PROPVALUE_SET : u32 = 1;
|
||||||
|
const AIMP_RA_PROPERTY_MASK : u32 = 0xFFFFFFF0;
|
||||||
|
|
||||||
|
const AIMP_RA_PROPERTY_VERSION : u32 = 0x10;
|
||||||
|
const AIMP_RA_PROPERTY_PLAYER_POSITION : u32 = 0x20;
|
||||||
|
const AIMP_RA_PROPERTY_PLAYER_DURATION : u32 = 0x30;
|
||||||
|
const AIMP_RA_PROPERTY_PLAYER_STATE : u32 = 0x40;
|
||||||
|
const AIMP_RA_PROPERTY_VOLUME : u32 = 0x50;
|
||||||
|
const AIMP_RA_PROPERTY_MUTE : u32 = 0x60;
|
||||||
|
const AIMP_RA_PROPERTY_TRACK_REPEAT : u32 = 0x70;
|
||||||
|
const AIMP_RA_PROPERTY_TRACK_SHUFFLE : u32 = 0x80;
|
||||||
|
const AIMP_RA_PROPERTY_RADIOCAP : u32 = 0x90;
|
||||||
|
const AIMP_RA_PROPERTY_VISUAL_FULLSCREEN : u32 = 0xA0;
|
||||||
|
|
||||||
|
const AIMP_RA_CMD_BASE : u32 = 10;
|
||||||
|
const AIMP_RA_CMD_PLAY : u32 = AIMP_RA_CMD_BASE + 3;
|
||||||
|
const AIMP_RA_CMD_PLAYPAUSE : u32 = AIMP_RA_CMD_BASE + 4;
|
||||||
|
const AIMP_RA_CMD_PAUSE : u32 = AIMP_RA_CMD_BASE + 5;
|
||||||
|
const AIMP_RA_CMD_STOP : u32 = AIMP_RA_CMD_BASE + 6;
|
||||||
|
const AIMP_RA_CMD_NEXT : u32 = AIMP_RA_CMD_BASE + 7;
|
||||||
|
const AIMP_RA_CMD_PREV : u32 = AIMP_RA_CMD_BASE + 8;
|
||||||
|
const AIMP_RA_CMD_VISUAL_NEXT : u32 = AIMP_RA_CMD_BASE + 9;
|
||||||
|
const AIMP_RA_CMD_VISUAL_PREV : u32 = AIMP_RA_CMD_BASE + 10;
|
||||||
|
const AIMP_RA_CMD_QUIT : u32 = AIMP_RA_CMD_BASE + 11;
|
||||||
|
const AIMP_RA_CMD_ADD_FILES : u32 = AIMP_RA_CMD_BASE + 12;
|
||||||
|
const AIMP_RA_CMD_ADD_FOLDERS : u32 = AIMP_RA_CMD_BASE + 13;
|
||||||
|
const AIMP_RA_CMD_ADD_PLAYLISTS : u32 = AIMP_RA_CMD_BASE + 14;
|
||||||
|
const AIMP_RA_CMD_ADD_URL : u32 = AIMP_RA_CMD_BASE + 15;
|
||||||
|
const AIMP_RA_CMD_OPEN_FILES : u32 = AIMP_RA_CMD_BASE + 16;
|
||||||
|
const AIMP_RA_CMD_OPEN_FOLDERS : u32 = AIMP_RA_CMD_BASE + 17;
|
||||||
|
const AIMP_RA_CMD_OPEN_PLAYLISTS : u32 = AIMP_RA_CMD_BASE + 18;
|
||||||
|
const AIMP_RA_CMD_VISUAL_START : u32 = AIMP_RA_CMD_BASE + 20;
|
||||||
|
const AIMP_RA_CMD_VISUAL_STOP : u32 = AIMP_RA_CMD_BASE + 21;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrackInfo {
|
||||||
|
pub artist: String,
|
||||||
|
pub title: String,
|
||||||
|
pub album: String,
|
||||||
|
pub genre: String,
|
||||||
|
pub filename: String,
|
||||||
|
pub date: String,
|
||||||
|
pub active: bool,
|
||||||
|
pub bitrate: u32,
|
||||||
|
pub channels: u32,
|
||||||
|
pub duration: u32,
|
||||||
|
pub filesize: i64,
|
||||||
|
pub filemark: u32,
|
||||||
|
pub sample_rate: u32,
|
||||||
|
pub track_number: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum State {
|
||||||
|
Stopped,
|
||||||
|
Paused,
|
||||||
|
Playing
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Dialog {
|
||||||
|
AddFiles = AIMP_RA_CMD_ADD_FILES as isize,
|
||||||
|
AddFolders = AIMP_RA_CMD_ADD_FOLDERS as isize,
|
||||||
|
AddPlaylists = AIMP_RA_CMD_ADD_PLAYLISTS as isize,
|
||||||
|
AddUrl = AIMP_RA_CMD_ADD_URL as isize,
|
||||||
|
OpenFiles = AIMP_RA_CMD_OPEN_FILES as isize,
|
||||||
|
OpenFolders = AIMP_RA_CMD_OPEN_FOLDERS as isize,
|
||||||
|
OpenPlaylists = AIMP_RA_CMD_OPEN_PLAYLISTS as isize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aimp_remote_class() -> CString {
|
||||||
|
CString::new("AIMP2_RemoteInfo").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns player version: `HiWord`: Version ID (for example: 301 -> v3.01), `LoWord`: Build Number
|
||||||
|
pub fn version() -> Option<u32> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_VERSION).map(|v| v as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns player version as string
|
||||||
|
pub fn version_str() -> Option<String> {
|
||||||
|
match version() {
|
||||||
|
Some(version) => {
|
||||||
|
let hi = (version & 0xffff0000) >> 16;
|
||||||
|
let lo = version & 0xffff;
|
||||||
|
Some( format!("v{:.*}, Build {}", 2, (hi as f32) / (100 as f32), lo))
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets current position of now playing track (in msec)
|
||||||
|
pub fn get_position() -> Option<i64> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_PLAYER_POSITION)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets position of now playing track (in msec)
|
||||||
|
pub fn set_position(position: i64) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_PLAYER_POSITION, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets duration of now playing track (in msec)
|
||||||
|
pub fn get_duration() -> Option<i64> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_PLAYER_DURATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns current player state
|
||||||
|
pub fn get_state() -> Option<State> {
|
||||||
|
match get_property(AIMP_RA_PROPERTY_PLAYER_STATE) {
|
||||||
|
Some(v) => match v {
|
||||||
|
0 => Some(State::Stopped),
|
||||||
|
1 => Some(State::Paused),
|
||||||
|
2 => Some(State::Playing),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets player state
|
||||||
|
pub fn set_state(state: State) -> bool {
|
||||||
|
match state {
|
||||||
|
State::Stopped => execute_command(AIMP_RA_CMD_STOP),
|
||||||
|
State::Paused => execute_command(AIMP_RA_CMD_PAUSE),
|
||||||
|
State::Playing => execute_command(AIMP_RA_CMD_PLAY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets current volume [0..100] (%)
|
||||||
|
pub fn get_volume() -> Option<u8> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_VOLUME).map(|v| v as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets volume [0..100] (%)
|
||||||
|
pub fn set_volume(level: u8) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_VOLUME, level as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets current mute state
|
||||||
|
pub fn is_mute() -> Option<bool> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_MUTE).map(|v| v != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets mute state
|
||||||
|
pub fn set_mute(state: bool) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_MUTE, state as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets track repeat state
|
||||||
|
pub fn is_repeat() -> Option<bool> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_TRACK_REPEAT).map(|v| v != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets track repeat state
|
||||||
|
pub fn set_repeat(state: bool) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_TRACK_REPEAT, state as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets shuffle state
|
||||||
|
pub fn is_shuffle() -> Option<bool> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_TRACK_SHUFFLE).map(|v| v != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets shuffle state
|
||||||
|
pub fn set_shuffle(state: bool) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_TRACK_SHUFFLE, state as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets radio capture state
|
||||||
|
pub fn is_radio_capture() -> Option<bool> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_RADIOCAP).map(|v| v != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets radio capture state
|
||||||
|
pub fn set_radio_capture(state: bool) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_RADIOCAP, state as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets fullscreen visualization mode
|
||||||
|
pub fn is_visualization_fullscreen() -> Option<bool> {
|
||||||
|
get_property(AIMP_RA_PROPERTY_VISUAL_FULLSCREEN).map(|v| v != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets fullscreen visualization mode
|
||||||
|
pub fn set_visualization_fullscreen(state: bool) -> bool {
|
||||||
|
set_property(AIMP_RA_PROPERTY_VISUAL_FULLSCREEN, state as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns current playing track info
|
||||||
|
pub fn aimp_track_info() -> Option<TrackInfo> {
|
||||||
|
let haimp = unsafe { kernel32::OpenFileMappingA(FILE_MAP_READ, 0, aimp_remote_class().as_ptr()) };
|
||||||
|
if haimp == std::ptr::null_mut() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let hfilemap = unsafe {
|
||||||
|
kernel32::MapViewOfFile(haimp, FILE_MAP_READ, 0, 0, AIMP_REMOTE_ACCESS_MAP_FILE_SIZE)
|
||||||
|
};
|
||||||
|
let aimp_info = unsafe { (hfilemap as *mut AIMPRemoteFileInfo).as_ref().unwrap() };
|
||||||
|
|
||||||
|
let mut pbuff = hfilemap as *const u16;
|
||||||
|
pbuff = unsafe { pbuff.offset(mem::size_of::<AIMPRemoteFileInfo>() as isize / 2) };
|
||||||
|
|
||||||
|
let album = get_info(pbuff, aimp_info.AlbumLength as usize);
|
||||||
|
pbuff = unsafe { pbuff.offset(aimp_info.AlbumLength as isize) };
|
||||||
|
let artist = get_info(pbuff, aimp_info.ArtistLength as usize);
|
||||||
|
pbuff = unsafe { pbuff.offset(aimp_info.ArtistLength as isize) };
|
||||||
|
let date = get_info(pbuff, aimp_info.DateLength as usize);
|
||||||
|
pbuff = unsafe { pbuff.offset(aimp_info.DateLength as isize) };
|
||||||
|
let filename = get_info(pbuff, aimp_info.FileNameLength as usize);
|
||||||
|
pbuff = unsafe { pbuff.offset(aimp_info.FileNameLength as isize) };
|
||||||
|
let genre = get_info(pbuff, aimp_info.GenreLength as usize);
|
||||||
|
pbuff = unsafe { pbuff.offset(aimp_info.GenreLength as isize) };
|
||||||
|
let title = get_info(pbuff, aimp_info.TitleLength as usize);
|
||||||
|
|
||||||
|
let result = TrackInfo {
|
||||||
|
artist: artist,
|
||||||
|
title: title,
|
||||||
|
album: album,
|
||||||
|
genre: genre,
|
||||||
|
filename: filename,
|
||||||
|
date: date,
|
||||||
|
|
||||||
|
active: aimp_info.Active != 0,
|
||||||
|
bitrate: aimp_info.BitRate,
|
||||||
|
channels: aimp_info.Channels,
|
||||||
|
duration: aimp_info.Duration,
|
||||||
|
filesize: aimp_info.FileSize,
|
||||||
|
filemark: aimp_info.FileMark,
|
||||||
|
sample_rate: aimp_info.SampleRate,
|
||||||
|
track_number: aimp_info.TrackNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { kernel32::UnmapViewOfFile(hfilemap) };
|
||||||
|
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets player state to playing if paused, and paused if playing
|
||||||
|
pub fn play_pause() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_PLAYPAUSE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches to next track
|
||||||
|
pub fn next_track() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_NEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches to previous track
|
||||||
|
pub fn prev_track() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_PREV)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches to next visualization
|
||||||
|
pub fn next_visualization() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_VISUAL_NEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches to previous visualization
|
||||||
|
pub fn prev_visualization() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_VISUAL_PREV)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows dialog
|
||||||
|
pub fn show_dialog(dialog: Dialog) -> bool {
|
||||||
|
execute_command(dialog as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts first visualization
|
||||||
|
pub fn visualization_start() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_VISUAL_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops visualization
|
||||||
|
pub fn visualization_stop() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_VISUAL_STOP)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Closes the program
|
||||||
|
pub fn quit() -> bool {
|
||||||
|
execute_command(AIMP_RA_CMD_QUIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_property(property: u32) -> Option<i64> {
|
||||||
|
let hwnd = unsafe { user32::FindWindowA(aimp_remote_class().as_ptr(), std::ptr::null_mut()) };
|
||||||
|
if hwnd == std::ptr::null_mut() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let result = unsafe {
|
||||||
|
user32::SendMessageA(hwnd, WM_AIMP_PROPERTY, (property | AIMP_RA_PROPVALUE_GET) as u64, 0)
|
||||||
|
};
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(property: u32, value: i64) -> bool {
|
||||||
|
let hwnd = unsafe { user32::FindWindowA(aimp_remote_class().as_ptr(), std::ptr::null_mut()) };
|
||||||
|
if hwnd == std::ptr::null_mut() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
user32::SendMessageA(hwnd, WM_AIMP_PROPERTY, (property | AIMP_RA_PROPVALUE_SET) as u64, value)
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_command(command: u32) -> bool {
|
||||||
|
let hwnd = unsafe { user32::FindWindowA(aimp_remote_class().as_ptr(), std::ptr::null_mut()) };
|
||||||
|
if hwnd == std::ptr::null_mut() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
user32::SendMessageA(hwnd, WM_AIMP_COMMAND, command as u64, 0)
|
||||||
|
};
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_info(ptr: *const u16, length: usize) -> String {
|
||||||
|
unsafe {
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
let slice = std::slice::from_raw_parts(ptr, length);
|
||||||
|
OsString::from_wide(slice).to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
|
}
|
6
src/lib.rs
Normal file
6
src/lib.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
extern crate winapi;
|
||||||
|
extern crate user32;
|
||||||
|
extern crate kernel32;
|
||||||
|
|
||||||
|
pub mod aimpremote;
|
||||||
|
pub use aimpremote::*;
|
Loading…
Reference in New Issue
Block a user