From aa7b7d6204ed908f716fad12d5cf713af34a37d0 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 22 Jun 2016 20:00:39 +0300 Subject: [PATCH] First release --- .gitignore | 2 + Cargo.toml | 9 ++ src/aimpremote.rs | 366 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 + 4 files changed, 383 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/aimpremote.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cbf194c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "aimpremote" +version = "0.1.0" +authors = ["aNNiMON "] + +[dependencies] +winapi="*" +user32-sys="*" +kernel32-sys="*" \ No newline at end of file diff --git a/src/aimpremote.rs b/src/aimpremote.rs new file mode 100644 index 0000000..2baa485 --- /dev/null +++ b/src/aimpremote.rs @@ -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 { + get_property(AIMP_RA_PROPERTY_VERSION).map(|v| v as u32) +} + +/// Returns player version as string +pub fn version_str() -> Option { + 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 { + 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 { + get_property(AIMP_RA_PROPERTY_PLAYER_DURATION) +} + +/// Returns current player state +pub fn get_state() -> Option { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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::() 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 { + 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() + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e9fffcf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +extern crate winapi; +extern crate user32; +extern crate kernel32; + +pub mod aimpremote; +pub use aimpremote::*; \ No newline at end of file