├── .gitignore ├── bindings ├── src │ └── lib.rs ├── Cargo.toml └── build.rs ├── src ├── lyrics │ ├── mod.rs │ └── query.rs ├── lyrics_window │ ├── mod.rs │ ├── state.rs │ ├── renderer.rs │ └── window.rs ├── types │ ├── mod.rs │ ├── size.rs │ └── rect.rs ├── ui │ ├── mod.rs │ ├── message.rs │ ├── window.rs │ └── utils.rs ├── player │ ├── mod.rs │ └── itunes │ │ └── mod.rs ├── main.rs └── initialize.rs ├── preview.png ├── ilyrics-manifest.rc ├── .cargo └── config.toml ├── .vscode └── settings.json ├── Cargo.toml ├── .github └── workflows │ └── main.yml ├── ilyrics.exe.manifest ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /bindings/src/lib.rs: -------------------------------------------------------------------------------- 1 | windows::include_bindings!(); 2 | -------------------------------------------------------------------------------- /src/lyrics/mod.rs: -------------------------------------------------------------------------------- 1 | mod query; 2 | 3 | pub use query::Query; 4 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lujjjh/iLyrics/HEAD/preview.png -------------------------------------------------------------------------------- /src/lyrics_window/mod.rs: -------------------------------------------------------------------------------- 1 | mod window; 2 | 3 | pub use window::LyricsWindow; 4 | -------------------------------------------------------------------------------- /ilyrics-manifest.rc: -------------------------------------------------------------------------------- 1 | #define RT_MANIFEST 24 2 | 1 RT_MANIFEST "ilyrics.exe.manifest" 3 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod rect; 2 | mod size; 3 | 4 | pub use rect::Rect; 5 | pub use size::Size; 6 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-pc-windows-msvc] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | -------------------------------------------------------------------------------- /src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | mod message; 2 | pub mod utils; 3 | mod window; 4 | 5 | pub use message::*; 6 | pub use window::*; 7 | -------------------------------------------------------------------------------- /src/types/size.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq)] 2 | pub struct Size { 3 | pub width: f32, 4 | pub height: f32, 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "rust-analyzer.assist.importEnforceGranularity": true, 4 | "rust-analyzer.assist.importGranularity": "item" 5 | } 6 | -------------------------------------------------------------------------------- /bindings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bindings" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | windows = "0.19.0" 8 | 9 | [build-dependencies] 10 | windows = "0.19.0" 11 | -------------------------------------------------------------------------------- /src/player/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub mod itunes; 4 | 5 | pub trait Player { 6 | fn get_player_state(&mut self) -> Option; 7 | } 8 | 9 | #[derive(Debug)] 10 | pub struct PlayerState { 11 | pub song_name: String, 12 | pub song_artist: String, 13 | pub player_position: Duration, 14 | } 15 | -------------------------------------------------------------------------------- /src/ui/message.rs: -------------------------------------------------------------------------------- 1 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 2 | 3 | pub fn run_message_loop() { 4 | unsafe { 5 | let mut msg = MSG::default(); 6 | while GetMessageW(&mut msg, None, 0, 0).as_bool() { 7 | TranslateMessage(&msg); 8 | DispatchMessageW(&msg); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ilyrics" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | bindings = { path = "bindings" } 8 | windows = "0.19.0" 9 | utf16_lit = "2.0.2" 10 | reqwest = { version = "0.11", features = ["blocking"] } 11 | tokio = { version = "1.10.0", features = ["full"] } 12 | lrc = "0.1.6" 13 | html-escape = "0.2.9" 14 | once_cell = "1.8.0" 15 | flexi_logger = "0.18" 16 | log = "0.4" 17 | anyhow = "1.0" 18 | regex = "1.5" 19 | 20 | [build-dependencies] 21 | embed-resource = "1.6" 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [pull_request, push] 3 | 4 | jobs: 5 | build-windows: 6 | runs-on: windows-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - run: rustup update --no-self-update nightly && rustup default nightly 10 | - run: rustup target add x86_64-pc-windows-msvc 11 | - name: Release build 12 | run: .\build.ps1 13 | - name: Upload 14 | uses: actions/upload-artifact@v2 15 | with: 16 | name: ilyrics.exe 17 | path: target/x86_64-pc-windows-msvc/release/ilyrics.exe 18 | retention-days: 5 19 | -------------------------------------------------------------------------------- /ilyrics.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A third-party iTunes addon to show the lyrics on the desktop. 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | mod initialize; 4 | mod lyrics; 5 | mod lyrics_window; 6 | mod player; 7 | mod types; 8 | mod ui; 9 | 10 | use anyhow::Result; 11 | use log::error; 12 | use log::info; 13 | 14 | use initialize::initialize; 15 | use lyrics_window::LyricsWindow; 16 | use ui::run_message_loop; 17 | 18 | fn main() -> Result<()> { 19 | let run = || -> Result<()> { 20 | initialize()?; 21 | info!("Initialized"); 22 | let lyrics_window = &mut LyricsWindow::new()?; 23 | lyrics_window.show()?; 24 | run_message_loop(); 25 | Ok(()) 26 | }; 27 | let result = run(); 28 | if let Err(e) = result.as_ref() { 29 | error!("Unexcepted error: {:?}", e); 30 | } 31 | result 32 | } 33 | -------------------------------------------------------------------------------- /bindings/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | windows::build! { 3 | Windows::Win32::Foundation::*, 4 | Windows::Win32::Graphics::Dxgi::*, 5 | Windows::Win32::Graphics::Direct2D::*, 6 | Windows::Win32::Graphics::Direct3D11::*, 7 | Windows::Win32::Graphics::DirectWrite::*, 8 | Windows::Win32::Graphics::DirectComposition::*, 9 | Windows::Win32::System::Com::*, 10 | Windows::Win32::System::Diagnostics::Debug::*, 11 | Windows::Win32::System::OleAutomation::*, 12 | Windows::Win32::System::SystemServices::*, 13 | Windows::Win32::System::LibraryLoader::*, 14 | Windows::Win32::System::Threading::*, 15 | Windows::Win32::UI::Animation::*, 16 | Windows::Win32::UI::Shell::*, 17 | Windows::Win32::UI::WindowsAndMessaging::*, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iLyrics 2 | 3 | A third-party iTunes addon to show the lyrics on the desktop. 4 | 5 | ## Preview 6 | 7 | iLyrics 8 | 9 | ## Prerequisites 10 | 11 | - [iTunes](https://www.microsoft.com/en-us/p/itunes/9pb2mz1zmb1s) 12 | 13 | ## Usage 14 | 15 | 1. [Download the binary](https://github.com/lujjjh/itunes-desktop-lyrics-windows/releases/latest) or [build from source](#build). 16 | 2. Run `ilyrics.exe`. 17 | 3. Listen to the music in iTunes. 18 | 4. Close iTunes and the addon program will be closed automatically. 19 | 20 | ## Subscribe for updates 21 | 22 | Although there is no auto updater, you can subscribe for updates by watching this repository: 23 | 24 | 1. Click “Watch”. 25 | 2. Choose “Custom”. 26 | 3. Select “Releases” and click “Apply”. 27 | 28 | ## Build 29 | 30 | ```powershell 31 | .\build.ps1 32 | ``` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jiahao Lu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/types/rect.rs: -------------------------------------------------------------------------------- 1 | use bindings::Windows::Win32::Graphics::Direct2D::D2D_RECT_F; 2 | 3 | use super::Size; 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub struct Rect { 7 | pub left: f32, 8 | pub top: f32, 9 | pub right: f32, 10 | pub bottom: f32, 11 | } 12 | 13 | impl Rect { 14 | pub fn width(&self) -> f32 { 15 | self.right - self.left 16 | } 17 | 18 | pub fn height(&self) -> f32 { 19 | self.bottom - self.top 20 | } 21 | 22 | pub fn size(&self) -> Size { 23 | Size { 24 | width: self.width(), 25 | height: self.height(), 26 | } 27 | } 28 | 29 | pub fn inset(&self, left: f32, top: f32, right: f32, bottom: f32) -> Self { 30 | Self { 31 | left: self.left + left, 32 | top: self.top + top, 33 | right: self.right - right, 34 | bottom: self.bottom - bottom, 35 | } 36 | } 37 | 38 | pub fn inset_dx_dy(&self, dx: f32, dy: f32) -> Self { 39 | self.inset(dx, dy, dx, dy) 40 | } 41 | 42 | pub fn inset_all(&self, value: f32) -> Self { 43 | self.inset_dx_dy(value, value) 44 | } 45 | } 46 | 47 | impl Into for Rect { 48 | fn into(self) -> D2D_RECT_F { 49 | let Self { 50 | left, 51 | top, 52 | right, 53 | bottom, 54 | } = self; 55 | D2D_RECT_F { 56 | left, 57 | top, 58 | right, 59 | bottom, 60 | } 61 | } 62 | } 63 | 64 | impl Into for D2D_RECT_F { 65 | fn into(self) -> Rect { 66 | let Self { 67 | left, 68 | top, 69 | right, 70 | bottom, 71 | } = self; 72 | Rect { 73 | left, 74 | top, 75 | right, 76 | bottom, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lyrics_window/state.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use bindings::Windows::Win32::Graphics::Direct2D::*; 4 | use lrc::Lyrics; 5 | use windows::*; 6 | 7 | use crate::lyrics_window::element::Rectangle; 8 | use crate::types::Rect; 9 | 10 | use super::element::Render; 11 | 12 | pub(crate) struct LyricsWindowState<'a> { 13 | lyrics: Option<&'a Lyrics>, 14 | position: Option, 15 | elements: Vec>, 16 | invalidated: bool, 17 | } 18 | 19 | impl<'a> LyricsWindowState<'a> { 20 | pub(crate) fn new() -> Self { 21 | Self { 22 | lyrics: None, 23 | position: None, 24 | elements: vec![], 25 | invalidated: true, 26 | } 27 | } 28 | 29 | pub(crate) fn set_lyrics(&mut self, lyrics: Option<&'a Lyrics>) { 30 | self.lyrics = lyrics; 31 | self.position = None; 32 | self.invalidated = true; 33 | } 34 | 35 | pub(crate) fn set_position(&mut self, position: Option) { 36 | if self.position != position { 37 | self.position = position; 38 | self.invalidated = true; 39 | } 40 | } 41 | 42 | pub(crate) fn get_elements(&mut self) -> &mut [Box] { 43 | if self.invalidated { 44 | let mut rect = Box::new(Rectangle::new(Rect { 45 | left: 0., 46 | top: 0., 47 | right: 100., 48 | bottom: 100., 49 | })); 50 | rect.set_fill(Some(D2D1_COLOR_F { 51 | r: 0., 52 | g: 0., 53 | b: 0., 54 | a: 0.5, 55 | })); 56 | self.elements = vec![rect]; 57 | self.invalidated = false; 58 | } 59 | &mut self.elements 60 | } 61 | } 62 | 63 | impl Render for LyricsWindowState<'_> { 64 | fn render(&mut self, dc: &ID2D1DeviceContext) -> Result<()> { 65 | for element in self.get_elements().iter_mut() { 66 | element.render(dc)?; 67 | } 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/initialize.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::exit; 3 | use std::ptr::null_mut; 4 | 5 | use bindings::Windows::Win32::System::Com::*; 6 | use bindings::Windows::Win32::System::Diagnostics::Debug::*; 7 | use bindings::Windows::Win32::System::Threading::*; 8 | use bindings::Windows::Win32::UI::Shell::*; 9 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 10 | use flexi_logger::detailed_format; 11 | use flexi_logger::Cleanup; 12 | use flexi_logger::Criterion; 13 | use flexi_logger::FileSpec; 14 | use flexi_logger::Logger; 15 | use flexi_logger::Naming; 16 | use windows::*; 17 | 18 | #[cfg(debug_assertions)] 19 | const UUID: &str = "c727e9a0-c71a-4001-ad0b-be20fd8e7971"; 20 | 21 | #[cfg(not(debug_assertions))] 22 | const UUID: &str = "0a0e643d-e484-486c-a4d6-1eef4cf6f499"; 23 | 24 | pub fn initialize() -> Result<()> { 25 | unsafe { 26 | SetProcessDPIAware(); 27 | CoInitialize(null_mut())?; 28 | 29 | let mutex_handle = CreateMutexW(null_mut(), true, UUID); 30 | if mutex_handle.is_null() || GetLastError() == ERROR_ALREADY_EXISTS { 31 | MessageBoxW( 32 | None, 33 | "iLyrics is already running.", 34 | "iLyrics", 35 | MB_ICONINFORMATION | MB_OK, 36 | ); 37 | exit(1); 38 | } 39 | 40 | let roaming_app_data_path = known_folder_path(&FOLDERID_RoamingAppData)?; 41 | let log_directory = Path::new(&roaming_app_data_path) 42 | .join("iLyrics") 43 | .join("logs"); 44 | Logger::try_with_str("info") 45 | .unwrap() 46 | .log_to_file( 47 | FileSpec::default() 48 | .directory(log_directory) 49 | .basename("iLyrics"), 50 | ) 51 | .format(detailed_format) 52 | .rotate( 53 | Criterion::Size(5 * 1_024 * 1_024), 54 | Naming::Timestamps, 55 | Cleanup::KeepLogFiles(0), 56 | ) 57 | .start() 58 | .unwrap(); 59 | 60 | Ok(()) 61 | } 62 | } 63 | 64 | fn known_folder_path(id: &windows::Guid) -> Result { 65 | unsafe { 66 | let path = SHGetKnownFolderPath(id, 0, None)?; 67 | if path.0.is_null() { 68 | return Ok(String::new()); 69 | } 70 | let mut end = path.0; 71 | while *end != 0 { 72 | end = end.add(1); 73 | } 74 | let result = String::from_utf16_lossy(std::slice::from_raw_parts( 75 | path.0, 76 | end.offset_from(path.0) as _, 77 | )); 78 | CoTaskMemFree(path.0 as _); 79 | Ok(result) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lyrics/query.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use log::error; 4 | use log::info; 5 | use log::warn; 6 | use lrc::Lyrics; 7 | use regex::Regex; 8 | use reqwest::blocking::Client; 9 | use std::error::Error; 10 | 11 | pub struct Query { 12 | client: Client, 13 | last_query: String, 14 | } 15 | 16 | fn convert_lyrics(lyrics: String) -> Result> { 17 | let re = Regex::new(r"\[(\d{2}:\d{2}\.\d{2})\d\]")?; 18 | let result = re.replace_all(&lyrics, "[$1]"); 19 | Ok(result.to_string()) 20 | } 21 | 22 | impl Query { 23 | pub fn new() -> Self { 24 | let client = Client::builder() 25 | .timeout(Duration::from_secs(10)) 26 | .build() 27 | .unwrap(); 28 | Self { 29 | client, 30 | last_query: String::from(""), 31 | } 32 | } 33 | 34 | // TODO: async 35 | // TODO: refactor 36 | pub fn get_lyrics( 37 | &mut self, 38 | name: &str, 39 | artist: &str, 40 | lyrics: &mut Option, 41 | ) -> Result> { 42 | let query = format!("{} {}", name, artist); 43 | if self.last_query != query { 44 | self.last_query = query; 45 | if name.is_empty() || artist.is_empty() { 46 | *lyrics = None; 47 | return Ok(true); 48 | } 49 | info!("{}", &self.last_query); 50 | *lyrics = None; 51 | let response = self 52 | .client 53 | .get("https://lyrics-api.lujjjh.com/") 54 | .query(&[("name", name), ("artist", artist)]) 55 | .send() 56 | .map_err(|e| { 57 | error!("Network error: {:?}", e); 58 | e 59 | })? 60 | .error_for_status() 61 | .map_err(|e| { 62 | warn!("Bad status: {:?}", e); 63 | e 64 | })?; 65 | let mut body = response.text().map_err(|e| { 66 | error!("Failed to read response body: {:?}", e); 67 | e 68 | })?; 69 | 70 | body = convert_lyrics(body)?; 71 | 72 | let downloaded_lyrics = Lyrics::from_str(body).map_err(|e| { 73 | error!("Failed to parse lyrics: {:?}", e); 74 | e 75 | })?; 76 | info!("OK"); 77 | let mut new_lyrics = Lyrics::new(); 78 | let timed_lines = downloaded_lyrics.get_timed_lines(); 79 | for (i, (time_tag, line)) in timed_lines.iter().enumerate() { 80 | let line = html_escape::decode_html_entities(&line); 81 | let text = line.trim(); 82 | // Skip empty lines that last no longer than 3s. 83 | if text.is_empty() { 84 | if i < timed_lines.len() - 1 { 85 | let duration = 86 | timed_lines[i + 1].0.get_timestamp() - time_tag.get_timestamp(); 87 | if duration <= 3000 { 88 | continue; 89 | } 90 | } 91 | } 92 | new_lyrics.add_timed_line(time_tag.clone(), line)?; 93 | } 94 | *lyrics = Some(new_lyrics); 95 | Ok(true) 96 | } else { 97 | Ok(false) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ui/window.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null_mut; 2 | 3 | use bindings::Windows::Win32::Foundation::*; 4 | use bindings::Windows::Win32::System::LibraryLoader::*; 5 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 6 | use once_cell::sync::OnceCell; 7 | use windows::*; 8 | 9 | fn get_instance() -> HINSTANCE { 10 | static INSTANCE: OnceCell = OnceCell::new(); 11 | *INSTANCE.get_or_init(|| unsafe { GetModuleHandleW(None) }) 12 | } 13 | 14 | pub trait Window: Sized { 15 | const CLASS_NAME: &'static str; 16 | const STYLE: WINDOW_STYLE; 17 | const EX_STYLE: WINDOW_EX_STYLE; 18 | 19 | fn get_hwnd(&self) -> HWND; 20 | fn window_proc(&mut self, hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT; 21 | 22 | fn register_class() -> Result<()> { 23 | static RESULT: OnceCell<()> = OnceCell::new(); 24 | RESULT 25 | .get_or_try_init(|| unsafe { 26 | let instance = get_instance(); 27 | let class_name = HSTRING::from(Self::CLASS_NAME); 28 | let wc = WNDCLASSW { 29 | style: CS_HREDRAW | CS_VREDRAW, 30 | hCursor: LoadCursorW(None, IDI_APPLICATION), 31 | hInstance: instance, 32 | lpfnWndProc: Some(window_proc::), 33 | lpszClassName: PWSTR(class_name.as_wide().as_ptr() as *mut _), 34 | ..Default::default() 35 | }; 36 | let atom = RegisterClassW(&wc); 37 | if atom == 0 { 38 | Err(HRESULT::from_thread().into()) 39 | } else { 40 | Ok(()) 41 | } 42 | }) 43 | .map(|()| ()) 44 | } 45 | 46 | fn create_window(title: &str, rect: &RECT, parent: Option) -> Result { 47 | Self::register_class()?; 48 | let hwnd = unsafe { 49 | CreateWindowExW( 50 | Self::EX_STYLE, 51 | Self::CLASS_NAME, 52 | title, 53 | Self::STYLE, 54 | rect.left, 55 | rect.top, 56 | rect.right - rect.left, 57 | rect.bottom - rect.top, 58 | parent, 59 | None, 60 | get_instance(), 61 | null_mut(), 62 | ) 63 | }; 64 | if hwnd == HWND(0) { 65 | Err(HRESULT::from_thread().into()) 66 | } else { 67 | Ok(hwnd) 68 | } 69 | } 70 | 71 | fn show(&self, cmd: SHOW_WINDOW_CMD) -> Result<()> { 72 | unsafe { 73 | SetWindowLongPtrW(self.get_hwnd(), GWLP_USERDATA, self as *const _ as _); 74 | ShowWindow(self.get_hwnd(), cmd); 75 | } 76 | Ok(()) 77 | } 78 | } 79 | 80 | pub unsafe fn get_window_instance<'a, T: Window>(hwnd: HWND) -> Option<&'a mut T> { 81 | let window_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA); 82 | if window_ptr == 0 { 83 | None 84 | } else { 85 | Some(&mut *(window_ptr as *mut T)) 86 | } 87 | } 88 | 89 | unsafe extern "system" fn window_proc( 90 | hwnd: HWND, 91 | msg: u32, 92 | wparam: WPARAM, 93 | lparam: LPARAM, 94 | ) -> LRESULT { 95 | let window: Option<&mut T> = get_window_instance(hwnd); 96 | match window { 97 | Some(window) => window.window_proc(hwnd, msg, wparam, lparam), 98 | None => DefWindowProcW(hwnd, msg, wparam, lparam), 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ui/utils.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null; 2 | use std::ptr::null_mut; 3 | 4 | use bindings::Windows::Win32::Foundation::*; 5 | use bindings::Windows::Win32::Graphics::Direct2D::*; 6 | use bindings::Windows::Win32::Graphics::Direct3D11::*; 7 | use bindings::Windows::Win32::Graphics::DirectWrite::*; 8 | use bindings::Windows::Win32::Graphics::Dxgi::*; 9 | use bindings::Windows::Win32::System::Com::*; 10 | use bindings::Windows::Win32::UI::Animation::*; 11 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 12 | use windows::*; 13 | 14 | pub fn get_desktop_dpi() -> Result<(f32, f32)> { 15 | let d2d_factory = create_d2d_factory()?; 16 | let (mut dpi_x, mut dpi_y) = (0., 0.); 17 | unsafe { d2d_factory.GetDesktopDpi(&mut dpi_x, &mut dpi_y) }; 18 | Ok((dpi_x, dpi_y)) 19 | } 20 | 21 | pub fn get_scale_factor() -> Result<(f32, f32)> { 22 | let (dpi_x, dpi_y) = get_desktop_dpi()?; 23 | Ok((dpi_x / 96., dpi_y / 96.)) 24 | } 25 | 26 | fn check_result(result: BOOL) -> Result<()> { 27 | if result.as_bool() { 28 | Ok(()) 29 | } else { 30 | Err(HRESULT::from_thread().into()) 31 | } 32 | } 33 | 34 | pub fn get_workarea_rect() -> Result { 35 | let mut rect: RECT = Default::default(); 36 | check_result(unsafe { 37 | SystemParametersInfoW( 38 | SPI_GETWORKAREA, 39 | 0, 40 | &mut rect as *mut _ as _, 41 | SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), 42 | ) 43 | })?; 44 | Ok(rect) 45 | } 46 | 47 | pub fn get_client_rect(hwnd: HWND) -> Result { 48 | let mut rect = RECT::default(); 49 | check_result(unsafe { GetClientRect(hwnd, &mut rect) })?; 50 | Ok(rect) 51 | } 52 | 53 | pub fn create_dxgi_device() -> Result { 54 | unsafe { 55 | let mut direct3d_device = None; 56 | D3D11CreateDevice( 57 | None, 58 | D3D_DRIVER_TYPE_HARDWARE, 59 | None, 60 | D3D11_CREATE_DEVICE_BGRA_SUPPORT, 61 | null(), 62 | 0, 63 | D3D11_SDK_VERSION, 64 | &mut direct3d_device, 65 | null_mut(), 66 | null_mut(), 67 | )?; 68 | let direct3d_device = direct3d_device.unwrap(); 69 | let dxgi_device = direct3d_device.cast::()?; 70 | Ok(dxgi_device) 71 | } 72 | } 73 | 74 | pub fn create_d2d_factory() -> Result { 75 | let mut d2d_factory: Option = None; 76 | unsafe { 77 | D2D1CreateFactory( 78 | D2D1_FACTORY_TYPE_SINGLE_THREADED, 79 | &ID2D1Factory::IID, 80 | &D2D1_FACTORY_OPTIONS { 81 | debugLevel: D2D1_DEBUG_LEVEL(0), 82 | }, 83 | d2d_factory.set_abi(), 84 | )?; 85 | } 86 | Ok(d2d_factory.unwrap()) 87 | } 88 | 89 | pub fn create_device_context( 90 | dxgi_device: &IDXGIDevice, 91 | factory: &ID2D1Factory2, 92 | dpi_x: f32, 93 | dpi_y: f32, 94 | ) -> Result { 95 | unsafe { 96 | let d2d_device = factory.CreateDevice(dxgi_device)?; 97 | let dc = d2d_device.CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE)?; 98 | dc.SetDpi(dpi_x, dpi_y); 99 | dc.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); 100 | Ok(dc) 101 | } 102 | } 103 | 104 | pub fn create_swap_chain(hwnd: HWND, device: &IDXGIDevice) -> Result { 105 | let dxgi_factory = unsafe { CreateDXGIFactory2::(0) }?; 106 | let rect = get_client_rect(hwnd)?; 107 | let swap_chain_desc = DXGI_SWAP_CHAIN_DESC1 { 108 | Width: (rect.right - rect.left) as u32, 109 | Height: (rect.bottom - rect.top) as u32, 110 | Format: DXGI_FORMAT_B8G8R8A8_UNORM, 111 | Stereo: BOOL(0), 112 | SampleDesc: DXGI_SAMPLE_DESC { 113 | Count: 1, 114 | Quality: 0, 115 | }, 116 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, 117 | BufferCount: 2, 118 | Scaling: DXGI_SCALING_STRETCH, 119 | SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 120 | AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, 121 | Flags: 0, 122 | }; 123 | unsafe { dxgi_factory.CreateSwapChainForComposition(device, &swap_chain_desc, None) } 124 | } 125 | 126 | pub fn create_bitmap_from_swap_chain<'a>( 127 | dc: &ID2D1DeviceContext, 128 | swap_chain: &IDXGISwapChain1, 129 | dpi_x: f32, 130 | dpi_y: f32, 131 | ) -> Result<()> { 132 | let dxgi_buffer = unsafe { swap_chain.GetBuffer::(0) }?; 133 | let properties = D2D1_BITMAP_PROPERTIES1 { 134 | pixelFormat: D2D1_PIXEL_FORMAT { 135 | format: DXGI_FORMAT_B8G8R8A8_UNORM, 136 | alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED, 137 | }, 138 | bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, 139 | dpiX: dpi_x, 140 | dpiY: dpi_y, 141 | ..Default::default() 142 | }; 143 | let bitmap = unsafe { dc.CreateBitmapFromDxgiSurface(&dxgi_buffer, &properties) }?; 144 | unsafe { dc.SetTarget(&bitmap) }; 145 | Ok(()) 146 | } 147 | 148 | pub fn create_dwrite_factory() -> Result { 149 | unsafe { 150 | DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IDWriteFactory2::IID) 151 | .unwrap() 152 | .cast::() 153 | } 154 | } 155 | 156 | pub fn create_animation_manager() -> Result { 157 | unsafe { CoCreateInstance(&UIAnimationManager, None, CLSCTX_INPROC_SERVER) } 158 | } 159 | 160 | pub fn create_animation_timer() -> Result { 161 | unsafe { CoCreateInstance(&UIAnimationTimer, None, CLSCTX_INPROC_SERVER) } 162 | } 163 | 164 | pub fn create_animation_transition_library() -> Result { 165 | unsafe { CoCreateInstance(&UIAnimationTransitionLibrary, None, CLSCTX_INPROC_SERVER) } 166 | } 167 | -------------------------------------------------------------------------------- /src/lyrics_window/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::mem::zeroed; 2 | use std::ptr::null; 3 | use std::ptr::null_mut; 4 | 5 | use bindings::Windows::Win32::Foundation::*; 6 | use bindings::Windows::Win32::Graphics::Direct2D::*; 7 | use bindings::Windows::Win32::Graphics::Direct3D11::*; 8 | use bindings::Windows::Win32::Graphics::DirectComposition::*; 9 | use bindings::Windows::Win32::Graphics::DirectWrite::*; 10 | use bindings::Windows::Win32::Graphics::Dxgi::*; 11 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 12 | use windows::*; 13 | 14 | use crate::types::Rect; 15 | use crate::types::Size; 16 | 17 | use super::element::Render; 18 | use super::state::LyricsWindowState; 19 | 20 | const MARGIN_HORIZONTAL: f32 = 10.; 21 | const MARGIN_VERTICAL: f32 = 20.; 22 | const PADDING_HORIZONTAL: f32 = 10.; 23 | const PADDING_VERTICAL: f32 = 5.; 24 | 25 | #[derive(Debug)] 26 | pub(crate) struct Renderer { 27 | d2d_factory: ID2D1Factory2, 28 | dwrite_factory: IDWriteFactory2, 29 | context: ID2D1DeviceContext, 30 | swap_chain: IDXGISwapChain1, 31 | dcomp_device: IDCompositionDevice, 32 | _target: IDCompositionTarget, 33 | text_format: IDWriteTextFormat1, 34 | brush: ID2D1SolidColorBrush, 35 | } 36 | 37 | fn rgba(r: f32, g: f32, b: f32, a: f32) -> D2D1_COLOR_F { 38 | D2D1_COLOR_F { r, g, b, a } 39 | } 40 | 41 | impl Renderer { 42 | pub(crate) fn new(hwnd: HWND) -> Result { 43 | unsafe { 44 | let device = create_dxgi_device()?; 45 | let d2d_factory = create_d2d_factory()?; 46 | let context = create_device_context(&device, &d2d_factory)?; 47 | let swap_chain = create_swap_chain(hwnd, &device)?; 48 | create_bitmap(&d2d_factory, &context, &swap_chain)?; 49 | let dcomp_device = create_dcomp_device(&device)?; 50 | let _target = create_composition(hwnd, &dcomp_device, &swap_chain)?; 51 | let dwrite_factory = create_dwrite_factory()?; 52 | let text_format = create_text_format(&dwrite_factory)?; 53 | let brush = context.CreateSolidColorBrush(&Default::default(), null())?; 54 | Ok(Self { 55 | d2d_factory, 56 | dwrite_factory, 57 | context, 58 | swap_chain, 59 | dcomp_device, 60 | _target, 61 | text_format, 62 | brush, 63 | }) 64 | } 65 | } 66 | 67 | pub(crate) fn render(&mut self, state: &mut LyricsWindowState) -> Result<()> { 68 | let dc = &self.context; 69 | let swap_chain = &self.swap_chain; 70 | unsafe { 71 | dc.BeginDraw(); 72 | dc.Clear(null_mut()); 73 | let animation = self.dcomp_device.CreateAnimation()?; 74 | animation.End(1., 1.)?; 75 | state.render(dc)?; 76 | dc.EndDraw(null_mut(), null_mut())?; 77 | swap_chain.Present(0, 0)?; 78 | } 79 | Ok(()) 80 | } 81 | 82 | fn get_text_rect(&self, text: &str) -> Result { 83 | let client_size = unsafe { self.context.GetSize() }; 84 | let text_rect_size = self.measure_text(text)?; 85 | let left = (client_size.width - text_rect_size.width) / 2.; 86 | let top = client_size.height - (MARGIN_VERTICAL + PADDING_VERTICAL) - text_rect_size.height; 87 | Ok(Rect { 88 | left, 89 | top, 90 | right: left + text_rect_size.width, 91 | bottom: top + text_rect_size.height, 92 | }) 93 | } 94 | 95 | fn create_text_layout( 96 | &self, 97 | text: &str, 98 | max_width: f32, 99 | max_height: f32, 100 | ) -> Result { 101 | let dwrite_factory = &self.dwrite_factory; 102 | let text = HSTRING::from(text); 103 | unsafe { 104 | dwrite_factory.CreateTextLayout( 105 | PWSTR(text.as_wide().as_ptr() as *mut _), 106 | text.len() as u32, 107 | &self.text_format, 108 | max_width, 109 | max_height, 110 | ) 111 | } 112 | } 113 | 114 | pub(crate) fn measure_text(&self, text: &str) -> Result { 115 | unsafe { 116 | let dc = &self.context; 117 | let D2D_SIZE_F { width, height } = dc.GetSize(); 118 | let max_width = width - 2. * (MARGIN_HORIZONTAL + PADDING_HORIZONTAL); 119 | let max_height = height - 2. * (MARGIN_VERTICAL + PADDING_VERTICAL); 120 | let DWRITE_TEXT_METRICS { width, height, .. } = self 121 | .create_text_layout(text, max_width, max_height)? 122 | .GetMetrics()?; 123 | Ok(Size { width, height }) 124 | } 125 | } 126 | 127 | fn draw_text(&self, text: &str, rect: &Rect, brush: ID2D1Brush) -> Result<()> { 128 | unsafe { 129 | let dc = &self.context; 130 | let text_layout = self.create_text_layout(text, rect.width(), rect.height())?; 131 | dc.DrawTextLayout( 132 | D2D_POINT_2F { 133 | x: rect.left, 134 | y: rect.top, 135 | }, 136 | text_layout, 137 | brush, 138 | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, 139 | ); 140 | Ok(()) 141 | } 142 | } 143 | 144 | pub(crate) fn resize(&self, width: u32, height: u32) -> Result<()> { 145 | unsafe { 146 | self.context.SetTarget(None); 147 | self.swap_chain 148 | .ResizeBuffers(2, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, 0)?; 149 | create_bitmap(&self.d2d_factory, &self.context, &self.swap_chain)?; 150 | Ok(()) 151 | } 152 | } 153 | } 154 | 155 | pub(crate) fn get_desktop_dpi() -> Result<(f32, f32)> { 156 | unsafe { 157 | let d2d_factory = create_d2d_factory()?; 158 | let (mut dpi_x, mut dpi_y) = (0., 0.); 159 | d2d_factory.GetDesktopDpi(&mut dpi_x, &mut dpi_y); 160 | Ok((dpi_x, dpi_y)) 161 | } 162 | } 163 | 164 | fn create_dxgi_device() -> Result { 165 | unsafe { 166 | let mut direct3d_device = None; 167 | D3D11CreateDevice( 168 | None, 169 | D3D_DRIVER_TYPE_HARDWARE, 170 | None, 171 | D3D11_CREATE_DEVICE_BGRA_SUPPORT, 172 | null(), 173 | 0, 174 | D3D11_SDK_VERSION, 175 | &mut direct3d_device, 176 | null_mut(), 177 | null_mut(), 178 | )?; 179 | let direct3d_device = direct3d_device.unwrap(); 180 | let dxgi_device = direct3d_device.cast::()?; 181 | Ok(dxgi_device) 182 | } 183 | } 184 | 185 | fn create_d2d_factory() -> Result { 186 | unsafe { 187 | let mut d2d_factory: Option = None; 188 | D2D1CreateFactory( 189 | D2D1_FACTORY_TYPE_SINGLE_THREADED, 190 | &ID2D1Factory::IID, 191 | &D2D1_FACTORY_OPTIONS { 192 | debugLevel: D2D1_DEBUG_LEVEL(0), 193 | }, 194 | d2d_factory.set_abi(), 195 | )?; 196 | Ok(d2d_factory.unwrap()) 197 | } 198 | } 199 | 200 | fn create_device_context( 201 | dxgi_device: &IDXGIDevice, 202 | factory: &ID2D1Factory2, 203 | ) -> Result { 204 | unsafe { 205 | let d2d_device = factory.CreateDevice(dxgi_device)?; 206 | let dc = d2d_device.CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE)?; 207 | let mut dpi_x = 0.; 208 | let mut dpi_y = 0.; 209 | factory.GetDesktopDpi(&mut dpi_x, &mut dpi_y); 210 | dc.SetDpi(dpi_x, dpi_y); 211 | dc.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); 212 | Ok(dc) 213 | } 214 | } 215 | 216 | fn create_swap_chain(hwnd: HWND, device: &IDXGIDevice) -> Result { 217 | unsafe { 218 | let dxgi_factory = CreateDXGIFactory2::(0)?; 219 | let mut rect = zeroed(); 220 | GetClientRect(hwnd, &mut rect); 221 | 222 | let swap_chain_desc = DXGI_SWAP_CHAIN_DESC1 { 223 | Width: (rect.right - rect.left) as u32, 224 | Height: (rect.bottom - rect.top) as u32, 225 | Format: DXGI_FORMAT_B8G8R8A8_UNORM, 226 | Stereo: BOOL(0), 227 | SampleDesc: DXGI_SAMPLE_DESC { 228 | Count: 1, 229 | Quality: 0, 230 | }, 231 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, 232 | BufferCount: 2, 233 | Scaling: DXGI_SCALING_STRETCH, 234 | SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 235 | AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, 236 | Flags: 0, 237 | }; 238 | 239 | dxgi_factory.CreateSwapChainForComposition(device, &swap_chain_desc, None) 240 | } 241 | } 242 | 243 | fn create_bitmap( 244 | factory: &ID2D1Factory2, 245 | dc: &ID2D1DeviceContext, 246 | swap_chain: &IDXGISwapChain1, 247 | ) -> Result { 248 | unsafe { 249 | let dxgi_buffer = swap_chain.GetBuffer::(0)?; 250 | 251 | let properties = D2D1_BITMAP_PROPERTIES1 { 252 | pixelFormat: D2D1_PIXEL_FORMAT { 253 | format: DXGI_FORMAT_B8G8R8A8_UNORM, 254 | alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED, 255 | }, 256 | dpiX: 0., 257 | dpiY: 0., 258 | bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, 259 | colorContext: None, 260 | }; 261 | 262 | let bitmap = dc.CreateBitmapFromDxgiSurface(&dxgi_buffer, &properties)?; 263 | 264 | dc.SetTarget(&bitmap); 265 | 266 | Ok(bitmap) 267 | } 268 | } 269 | 270 | fn create_dcomp_device(device: &IDXGIDevice) -> Result { 271 | unsafe { DCompositionCreateDevice(device) } 272 | } 273 | 274 | fn create_composition( 275 | hwnd: HWND, 276 | dcomp_device: &IDCompositionDevice, 277 | swap_chain: &IDXGISwapChain1, 278 | ) -> Result { 279 | unsafe { 280 | let target = dcomp_device.CreateTargetForHwnd(hwnd, BOOL(1))?; 281 | let visual = dcomp_device.CreateVisual()?; 282 | visual.SetContent(swap_chain)?; 283 | target.SetRoot(&visual)?; 284 | dcomp_device.Commit()?; 285 | Ok(target) 286 | } 287 | } 288 | 289 | fn create_dwrite_factory() -> Result { 290 | unsafe { 291 | DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IDWriteFactory2::IID) 292 | .unwrap() 293 | .cast::() 294 | } 295 | } 296 | 297 | fn create_text_format(dwrite_factory: &IDWriteFactory2) -> Result { 298 | unsafe { 299 | let text_format: IDWriteTextFormat1 = dwrite_factory 300 | .CreateTextFormat( 301 | "Segoe UI", 302 | None, 303 | DWRITE_FONT_WEIGHT_NORMAL, 304 | DWRITE_FONT_STYLE_NORMAL, 305 | DWRITE_FONT_STRETCH_NORMAL, 306 | 24., 307 | "", 308 | )? 309 | .cast()?; 310 | text_format.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)?; 311 | text_format.SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)?; 312 | 313 | let font_fallback_builder = dwrite_factory.CreateFontFallbackBuilder()?; 314 | let ranges = DWRITE_UNICODE_RANGE { 315 | first: 0x0, 316 | last: 0xffffffff, 317 | }; 318 | let fallback_family_names = [ 319 | "Segoe UI Emoji", 320 | "Segoe UI Symbol", 321 | "Helvetica", 322 | "Microsoft Yahei", 323 | ]; 324 | // The two map-collect cannot be merged since the `name`s must live long enough. 325 | let fallback_family_names = fallback_family_names 326 | .iter() 327 | .map(|&name| name.encode_utf16().chain([0]).collect::>()) 328 | .collect::>>(); 329 | let fallback_family_names = fallback_family_names 330 | .iter() 331 | .map(|name| name.as_ptr()) 332 | .collect::>(); 333 | font_fallback_builder.AddMapping( 334 | &ranges, 335 | 1, 336 | fallback_family_names.as_ptr(), 337 | fallback_family_names.len() as u32, 338 | None, 339 | None, 340 | None, 341 | 1., 342 | )?; 343 | font_fallback_builder.AddMappings(dwrite_factory.GetSystemFontFallback()?)?; 344 | let font_fallback = font_fallback_builder.CreateFontFallback()?; 345 | text_format.SetFontFallback(font_fallback)?; 346 | 347 | Ok(text_format) 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/player/itunes/mod.rs: -------------------------------------------------------------------------------- 1 | use std::intrinsics::transmute; 2 | use std::mem; 3 | use std::mem::transmute_copy; 4 | use std::ptr::null_mut; 5 | use std::ptr::NonNull; 6 | use std::time::Duration; 7 | use std::time::SystemTime; 8 | 9 | use anyhow::Context; 10 | use anyhow::Result; 11 | use bindings::Windows::Win32::Foundation::*; 12 | use bindings::Windows::Win32::System::Com::*; 13 | use bindings::Windows::Win32::System::OleAutomation::*; 14 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 15 | use windows::*; 16 | 17 | use super::Player; 18 | use super::PlayerState; 19 | 20 | const ITUNES_CLSID: Guid = Guid::from_values( 21 | 0xDC0C2640, 22 | 0x1415, 23 | 0x4644, 24 | [0x87, 0x5C, 0x6F, 0x4D, 0x76, 0x98, 0x39, 0xBA], 25 | ); 26 | 27 | pub struct ITunes { 28 | instance: IiTunes, 29 | 30 | // last_player_position interpolation. 31 | last_player_position: Option, 32 | last_player_position_updated_at: SystemTime, 33 | } 34 | 35 | impl Player for ITunes { 36 | fn get_player_state(&mut self) -> Option { 37 | if !self.is_playing() { 38 | return None; 39 | } 40 | let current_track_info = self.get_current_track_info(); 41 | let player_position = self.get_player_position(); 42 | current_track_info.and_then(|track_info| { 43 | player_position.map(|player_position| PlayerState { 44 | song_name: track_info.name, 45 | song_artist: track_info.artist, 46 | player_position, 47 | }) 48 | }) 49 | } 50 | } 51 | 52 | impl ITunes { 53 | pub fn new() -> Result { 54 | unsafe { 55 | CoInitialize(null_mut())?; 56 | let instance: IiTunes = CoCreateInstance(&ITUNES_CLSID, None, CLSCTX_LOCAL_SERVER) 57 | .context("Failed to create IiTunes instance")?; 58 | let connection_point_container = instance.cast::()?; 59 | let connection_point = 60 | connection_point_container.FindConnectionPoint(&I_ITUNES_EVENTS_IID)?; 61 | let itunes_events = ITunesImplementation::new(); 62 | connection_point.Advise(itunes_events)?; 63 | Ok(Self { 64 | instance, 65 | last_player_position: None, 66 | last_player_position_updated_at: SystemTime::now(), 67 | }) 68 | } 69 | } 70 | 71 | fn get_instance(&self) -> &IiTunes { 72 | &self.instance 73 | } 74 | 75 | pub fn is_playing(&self) -> bool { 76 | unsafe { self.get_instance().GetPlayerState() } 77 | .map(|state| state == 1) 78 | .unwrap_or(false) 79 | } 80 | 81 | pub fn get_current_track_info(&self) -> Option { 82 | unsafe { 83 | self.get_instance() 84 | .GetCurrentTrack() 85 | .unwrap_or(None) 86 | .and_then(|track| { 87 | (|| -> Result { 88 | let name = track.GetName()?.to_string(); 89 | let artist = track.GetArtist()?.to_string(); 90 | Ok(TrackInfo { name, artist }) 91 | })() 92 | .map(Some) 93 | .unwrap_or(None) 94 | }) 95 | } 96 | } 97 | 98 | pub fn get_player_position(&mut self) -> Option { 99 | let player_position = unsafe { 100 | self.get_instance() 101 | .GetPlayerPositionMS() 102 | // There appears to be a delay in iTunes' PlayerPositionMS. 103 | .map(|ms| Some(Duration::from_millis(ms as u64) + Duration::from_millis(350))) 104 | .unwrap_or(None) 105 | }; 106 | // iTunes' PlayerPositionMS is not continuous. We still have to do the interpolation. 107 | if player_position != self.last_player_position { 108 | self.last_player_position = player_position; 109 | self.last_player_position_updated_at = SystemTime::now(); 110 | } 111 | player_position.map(|value| { 112 | value 113 | + self 114 | .last_player_position_updated_at 115 | .elapsed() 116 | .unwrap() 117 | .min(Duration::from_secs(1)) 118 | }) 119 | } 120 | } 121 | 122 | impl Drop for ITunes { 123 | fn drop(&mut self) { 124 | unsafe { 125 | CoUninitialize(); 126 | } 127 | } 128 | } 129 | 130 | #[derive(Debug)] 131 | pub struct TrackInfo { 132 | pub name: String, 133 | pub artist: String, 134 | } 135 | 136 | #[repr(transparent)] 137 | #[derive(Clone, PartialEq, Eq)] 138 | struct IiTunes(IUnknown); 139 | 140 | #[repr(C)] 141 | #[allow(non_camel_case_types)] 142 | struct IiTunes_abi( 143 | // IUnknown 144 | pub unsafe extern "system" fn(this: RawPtr, iid: *const Guid, interface: *mut RawPtr) -> HRESULT, 145 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 146 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 147 | // IDispatch (TODO) 148 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 149 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 150 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 151 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 152 | // IiTunes 153 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 154 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 155 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 156 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 157 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 158 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 159 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 160 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 161 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 162 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 163 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 164 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 165 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 166 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 167 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 168 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 169 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 170 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 171 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 172 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 173 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 174 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 175 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 176 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 177 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 178 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 179 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 180 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 181 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 182 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 183 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 184 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 185 | pub unsafe extern "system" fn(this: RawPtr, value: *mut i32) -> HRESULT, 186 | pub unsafe extern "system" fn(this: RawPtr, value: *mut i64) -> HRESULT, 187 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 188 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 189 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 190 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 191 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 192 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 193 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 194 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 195 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 196 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 197 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 198 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 199 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 200 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 201 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 202 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 203 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 204 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 205 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 206 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 207 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 208 | pub unsafe extern "system" fn(this: RawPtr, track: *mut RawPtr) -> HRESULT, 209 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 210 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 211 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 212 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 213 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 214 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 215 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 216 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 217 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 218 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 219 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 220 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 221 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 222 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 223 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 224 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 225 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 226 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 227 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 228 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 229 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 230 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 231 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 232 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 233 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 234 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 235 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 236 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 237 | pub unsafe extern "system" fn(this: RawPtr, value: *mut i64) -> HRESULT, 238 | ); 239 | 240 | unsafe impl Interface for IiTunes { 241 | type Vtable = IiTunes_abi; 242 | 243 | const IID: Guid = Guid::from_values( 244 | 0x9DD6_680B, 245 | 0x3EDC, 246 | 0x40db, 247 | [0xA7, 0x71, 0xE6, 0xFE, 0x48, 0x32, 0xE3, 0x4A], 248 | ); 249 | } 250 | 251 | #[allow(non_snake_case)] 252 | impl IiTunes { 253 | pub unsafe fn GetPlayerState(&self) -> Result { 254 | let mut value: i32 = 0; 255 | (Interface::vtable(self).39)(Abi::abi(self), &mut value).ok()?; 256 | Ok(value) 257 | } 258 | 259 | pub unsafe fn GetPlayerPositionMS(&self) -> Result { 260 | let mut value: i64 = 0; 261 | (Interface::vtable(self).91)(Abi::abi(self), &mut value).ok()?; 262 | Ok(value) 263 | } 264 | 265 | pub unsafe fn GetCurrentTrack(&self) -> Result> { 266 | let mut abi = null_mut(); 267 | (Interface::vtable(self).62)(Abi::abi(self), &mut abi).ok()?; 268 | if abi.is_null() { 269 | Ok(None) 270 | } else { 271 | Ok(transmute(abi)) 272 | } 273 | } 274 | } 275 | 276 | #[repr(transparent)] 277 | #[derive(Clone, PartialEq, Eq)] 278 | struct IITTrack(IUnknown); 279 | 280 | #[repr(C)] 281 | #[allow(non_camel_case_types)] 282 | struct IITTrack_abi( 283 | // IUnknown 284 | pub unsafe extern "system" fn(this: RawPtr, iid: *const Guid, interface: *mut RawPtr) -> HRESULT, 285 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 286 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 287 | // IDispatch (TODO) 288 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 289 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 290 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 291 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 292 | // IITObject 293 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 294 | pub unsafe extern "system" fn(this: RawPtr, value: *mut *mut u16) -> HRESULT, 295 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 296 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 297 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 298 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 299 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 300 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 301 | // IITTrack 302 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 303 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 304 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 305 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 306 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 307 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 308 | pub unsafe extern "system" fn(this: RawPtr) -> HRESULT, 309 | pub unsafe extern "system" fn(this: RawPtr, value: *mut *mut u16) -> HRESULT, 310 | ); 311 | 312 | unsafe impl Interface for IITTrack { 313 | type Vtable = IITTrack_abi; 314 | 315 | const IID: Guid = Guid::from_values( 316 | 0x4cb0_915d, 317 | 0x1e54, 318 | 0x4727, 319 | [0xba, 0xf3, 0xce, 0x6c, 0xc9, 0xa2, 0x25, 0xa1], 320 | ); 321 | } 322 | 323 | #[allow(non_snake_case)] 324 | impl IITTrack { 325 | pub unsafe fn GetName(&self) -> Result { 326 | let mut abi: ::Abi = mem::zeroed(); 327 | (Interface::vtable(self).8)(Abi::abi(self), &mut abi) 328 | .from_abi(abi) 329 | .context("Failed to GetName") 330 | } 331 | 332 | pub unsafe fn GetArtist(&self) -> Result { 333 | let mut abi: ::Abi = mem::zeroed(); 334 | (Interface::vtable(self).22)(Abi::abi(self), &mut abi) 335 | .from_abi(abi) 336 | .context("Failed to GetArtist") 337 | } 338 | } 339 | 340 | // TODO: Rewrite with windows::implement once it is ready. 341 | 342 | pub enum ITEvent { 343 | AboutToPromptUserToQuitEvent = 9, 344 | } 345 | 346 | pub const I_ITUNES_EVENTS_IID: Guid = Guid::from_values( 347 | 0x5846_EB78, 348 | 0x317E, 349 | 0x4B6F, 350 | [0xB0, 0xC3, 0x11, 0xEE, 0x8C, 0x8F, 0xEE, 0xF2], 351 | ); 352 | 353 | #[repr(C)] 354 | pub struct ITunesEvents { 355 | vtable: *const ITunesEvents_abi, 356 | implementation: ITunesImplementation, 357 | count: WeakRefCount, 358 | } 359 | 360 | impl ITunesEvents { 361 | pub fn new(implementation: ITunesImplementation) -> Self { 362 | Self { 363 | vtable: &Self::VTABLE, 364 | implementation, 365 | count: WeakRefCount::new(), 366 | } 367 | } 368 | 369 | const VTABLE: ITunesEvents_abi = ITunesEvents_abi( 370 | Self::query_interface, 371 | Self::add_ref, 372 | Self::release, 373 | Self::get_type_info_count, 374 | Self::get_type_info, 375 | Self::get_ids_of_names, 376 | Self::invoke, 377 | ); 378 | 379 | pub unsafe extern "system" fn query_interface( 380 | this: RawPtr, 381 | iid: &Guid, 382 | interface: *mut RawPtr, 383 | ) -> HRESULT { 384 | *interface = match iid { 385 | &IUnknown::IID | &IDispatch::IID | &I_ITUNES_EVENTS_IID => this, 386 | _ => null_mut(), 387 | }; 388 | if (*interface).is_null() { 389 | HRESULT(0x8000_4002) // E_NOINTERFACE 390 | } else { 391 | let this = &mut *((this as *mut ::windows::RawPtr) as *mut Self); 392 | this.count.add_ref(); 393 | HRESULT(0) 394 | } 395 | } 396 | 397 | pub unsafe extern "system" fn add_ref(this: RawPtr) -> u32 { 398 | let this = &mut *((this as *mut ::windows::RawPtr) as *mut Self); 399 | this.count.add_ref() 400 | } 401 | 402 | pub unsafe extern "system" fn release(this: RawPtr) -> u32 { 403 | let this = &mut *((this as *mut ::windows::RawPtr) as *mut Self); 404 | let remaining = this.count.release(); 405 | if remaining == 0 { 406 | Box::from_raw(this); 407 | } 408 | remaining 409 | } 410 | 411 | pub unsafe extern "system" fn get_type_info_count( 412 | _this: RawPtr, 413 | _pctinfo: *mut u32, 414 | ) -> HRESULT { 415 | E_NOTIMPL 416 | } 417 | 418 | pub unsafe extern "system" fn get_type_info( 419 | _this: RawPtr, 420 | _itinfo: u32, 421 | _lcid: u64, 422 | _pptinfo: *mut *mut RawPtr, 423 | ) -> HRESULT { 424 | E_NOTIMPL 425 | } 426 | 427 | pub unsafe extern "system" fn get_ids_of_names( 428 | _this: RawPtr, 429 | _riid: &Guid, 430 | _rgsz_names: *mut *mut u8, 431 | _c_names: u32, 432 | _lcid: u64, 433 | _rgdispid: *mut i64, 434 | ) -> HRESULT { 435 | E_NOTIMPL 436 | } 437 | 438 | pub unsafe extern "system" fn invoke( 439 | this: RawPtr, 440 | dispid_member: i64, 441 | _riid: &Guid, 442 | _lcid: u64, 443 | _flags: u16, 444 | _pdispparams: RawPtr, 445 | _pvar_result: *mut RawPtr, 446 | _pexcepinfo: *mut RawPtr, 447 | _pu_arg_err: *mut u32, 448 | ) -> HRESULT { 449 | let this = &mut *((this as *mut ::windows::RawPtr) as *mut Self); 450 | if dispid_member == ITEvent::AboutToPromptUserToQuitEvent as i64 { 451 | this.implementation.on_obout_to_prompt_user_to_quit(); 452 | }; 453 | HRESULT(0) 454 | } 455 | } 456 | 457 | pub struct ITunesImplementation; 458 | 459 | impl ITunesImplementation { 460 | pub fn new() -> Self { 461 | Self 462 | } 463 | 464 | pub fn on_obout_to_prompt_user_to_quit(&self) { 465 | unsafe { PostQuitMessage(0) }; 466 | } 467 | } 468 | 469 | impl From for IUnknown { 470 | fn from(implementation: ITunesImplementation) -> Self { 471 | let com = ITunesEvents::new(implementation); 472 | unsafe { 473 | let ptr = Box::into_raw(Box::new(com)); 474 | transmute_copy(&NonNull::new_unchecked(&mut (*ptr).vtable as *mut _ as _)) 475 | } 476 | } 477 | } 478 | 479 | impl<'a> IntoParam<'a, IUnknown> for ITunesImplementation { 480 | fn into_param(self) -> windows::Param<'a, IUnknown> { 481 | Param::Owned(Into::::into(self)) 482 | } 483 | } 484 | 485 | #[repr(C)] 486 | #[allow(non_camel_case_types)] 487 | struct ITunesEvents_abi( 488 | // IUnknown 489 | pub unsafe extern "system" fn(this: RawPtr, iid: &Guid, interface: *mut RawPtr) -> HRESULT, 490 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 491 | pub unsafe extern "system" fn(this: RawPtr) -> u32, 492 | // IDispatch 493 | pub unsafe extern "system" fn(this: RawPtr, pctinfo: *mut u32) -> HRESULT, 494 | pub unsafe extern "system" fn( 495 | this: RawPtr, 496 | itinfo: u32, 497 | lcid: u64, 498 | pptinfo: *mut *mut RawPtr, 499 | ) -> HRESULT, 500 | pub unsafe extern "system" fn( 501 | this: RawPtr, 502 | riid: &Guid, 503 | rgszNames: *mut *mut u8, 504 | cNames: u32, 505 | lcid: u64, 506 | rgdispid: *mut i64, 507 | ) -> HRESULT, 508 | pub unsafe extern "system" fn( 509 | this: RawPtr, 510 | dispidMember: i64, 511 | riid: &Guid, 512 | lcid: u64, 513 | wFlags: u16, 514 | pdispparams: RawPtr, 515 | pvarResult: *mut RawPtr, 516 | pexcepinfo: *mut RawPtr, 517 | puArgErr: *mut u32, 518 | ) -> HRESULT, 519 | ); 520 | -------------------------------------------------------------------------------- /src/lyrics_window/window.rs: -------------------------------------------------------------------------------- 1 | use std::ptr::null; 2 | use std::ptr::null_mut; 3 | use std::time::Duration; 4 | 5 | use anyhow::Result; 6 | use bindings::Windows; 7 | use bindings::Windows::Foundation::Numerics::*; 8 | use bindings::Windows::Win32::Foundation::*; 9 | use bindings::Windows::Win32::Graphics::Direct2D::*; 10 | use bindings::Windows::Win32::Graphics::DirectComposition::*; 11 | use bindings::Windows::Win32::Graphics::DirectWrite::*; 12 | use bindings::Windows::Win32::Graphics::Dxgi::*; 13 | use bindings::Windows::Win32::UI::Animation::*; 14 | use bindings::Windows::Win32::UI::WindowsAndMessaging::*; 15 | use lrc::Lyrics; 16 | use once_cell::sync::OnceCell; 17 | use windows::*; 18 | 19 | use crate::lyrics::Query; 20 | use crate::player::itunes::ITunes; 21 | use crate::player::Player; 22 | use crate::player::PlayerState; 23 | use crate::ui::get_window_instance; 24 | use crate::ui::utils::*; 25 | use crate::ui::Window; 26 | 27 | const WINDOW_HEIGHT: i32 = 80; 28 | const PADDING_HORIZONTAL: f64 = 10.; 29 | const PADDING_VERTICAL: f64 = 5.; 30 | 31 | const DURATION_FADE_IN: Duration = Duration::from_millis(100); 32 | const DURATION_FADE_OUT: Duration = Duration::from_millis(800); 33 | const DURATION_SIZING: Duration = Duration::from_millis(200); 34 | const DURATION_SCROLLING: Duration = Duration::from_millis(350); 35 | 36 | struct Resources { 37 | d2d_factory: ID2D1Factory2, 38 | dc: ID2D1DeviceContext, 39 | _dcomp_device: IDCompositionDevice, 40 | _target: IDCompositionTarget, 41 | swap_chain: IDXGISwapChain1, 42 | dwrite_factory: IDWriteFactory2, 43 | brush: ID2D1SolidColorBrush, 44 | text_format: IDWriteTextFormat1, 45 | animation_manager: IUIAnimationManager, 46 | animation_timer: IUIAnimationTimer, 47 | animation_transition_library: IUIAnimationTransitionLibrary, 48 | opacity: IUIAnimationVariable, 49 | bg_width: IUIAnimationVariable, 50 | bg_height: IUIAnimationVariable, 51 | line_current_offset: IUIAnimationVariable, 52 | line_next_offset: IUIAnimationVariable, 53 | line_next_opacity: IUIAnimationVariable, 54 | } 55 | 56 | pub struct LyricsWindow { 57 | hwnd: HWND, 58 | resources: OnceCell, 59 | player: ITunes, 60 | query: Query, 61 | lyrics: Option, 62 | player_position: Option, 63 | line_current: Option, 64 | line_next: Option, 65 | line_next_non_empty: Option, 66 | } 67 | 68 | impl Window for LyricsWindow { 69 | const CLASS_NAME: &'static str = "iLyrics"; 70 | const STYLE: WINDOW_STYLE = WINDOW_STYLE(WS_CLIPSIBLINGS.0 | WS_CLIPCHILDREN.0 | WS_POPUP.0); 71 | const EX_STYLE: WINDOW_EX_STYLE = WINDOW_EX_STYLE( 72 | WS_EX_NOREDIRECTIONBITMAP.0 73 | | WS_EX_LAYERED.0 74 | | WS_EX_TRANSPARENT.0 75 | | WS_EX_TOPMOST.0 76 | | WS_EX_TOOLWINDOW.0, 77 | ); 78 | 79 | fn get_hwnd(&self) -> HWND { 80 | self.hwnd 81 | } 82 | 83 | fn window_proc(&mut self, hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { 84 | match msg { 85 | WM_TIMER => self.on_timer(hwnd, msg, wparam, lparam), 86 | WM_DESTROY => self.on_destroy(hwnd, msg, wparam, lparam), 87 | _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }, 88 | } 89 | } 90 | } 91 | 92 | impl LyricsWindow { 93 | pub fn new() -> Result { 94 | let (_scale_x, scale_y) = get_scale_factor()?; 95 | let mut rect = get_workarea_rect()?; 96 | rect.top = rect.bottom - (WINDOW_HEIGHT as f32 * scale_y).round() as i32; 97 | let hwnd = Self::create_window("iLyrics", &rect, None)?; 98 | let player = ITunes::new()?; 99 | let query = Query::new(); 100 | Ok(Self { 101 | hwnd, 102 | resources: OnceCell::new(), 103 | player, 104 | query, 105 | lyrics: None, 106 | player_position: None, 107 | line_current: None, 108 | line_next: None, 109 | line_next_non_empty: None, 110 | }) 111 | } 112 | 113 | pub fn show(&mut self) -> Result<()> { 114 | unsafe { SetLayeredWindowAttributes(self.hwnd, 0, 255, LWA_ALPHA) }; 115 | Window::show(self, SW_SHOWNOACTIVATE)?; 116 | self.draw()?; 117 | self.set_lyrics_timer()?; 118 | Ok(()) 119 | } 120 | 121 | fn set_lyrics_timer(&self) -> Result<()> { 122 | if unsafe { SetTimer(self.hwnd, 1, 100, None) } > 0 { 123 | Ok(()) 124 | } else { 125 | let windows_error: windows::Error = HRESULT::from_thread().into(); 126 | Err(windows_error.into()) 127 | } 128 | } 129 | 130 | fn on_timer(&mut self, _hwnd: HWND, _msg: u32, wparam: WPARAM, _lparam: LPARAM) -> LRESULT { 131 | match wparam { 132 | WPARAM(1) => { 133 | let player_state = self.player.get_player_state(); 134 | if let Some(PlayerState { 135 | song_name, 136 | song_artist, 137 | .. 138 | }) = player_state.as_ref() 139 | { 140 | let mut lyrics = None; 141 | let changed = self 142 | .query 143 | .get_lyrics(song_name, song_artist, &mut lyrics) 144 | .unwrap_or(true); 145 | if changed { 146 | self.set_lyrics(lyrics).unwrap(); 147 | } 148 | }; 149 | let player_position = player_state.map(|player_state| player_state.player_position); 150 | self.set_player_position(player_position).unwrap(); 151 | LRESULT(1) 152 | } 153 | _ => LRESULT(0), 154 | } 155 | } 156 | 157 | fn set_lyrics(&mut self, lyrics: Option) -> Result<()> { 158 | self.lyrics = lyrics; 159 | self.set_player_position(None) 160 | } 161 | 162 | fn set_player_position(&mut self, player_position: Option) -> Result<()> { 163 | self.player_position = player_position; 164 | self.update_lines() 165 | } 166 | 167 | fn update_lines(&mut self) -> Result<()> { 168 | if let Some(lyrics) = self.lyrics.as_ref() { 169 | if let Some(player_position) = self.player_position { 170 | let line_current = lyrics 171 | .find_timed_line_index(player_position.as_millis() as i64) 172 | .map(|index| lyrics.get_timed_lines()[index].1.to_string()); 173 | let line_next = 174 | lyrics 175 | .find_timed_line_index( 176 | (player_position + DURATION_SCROLLING).as_millis() as i64 177 | ) 178 | .map(|index| lyrics.get_timed_lines()[index].1.to_string()); 179 | if self.line_current != line_current { 180 | self.line_current = line_current; 181 | } 182 | // If the next line is empty, we'd like to delay the animation 183 | // until the next line becomes the current line. 184 | let line_next = match line_next.as_ref() { 185 | Some(line_next) if !line_next.is_empty() => Some(line_next), 186 | _ => self.line_current.as_ref(), 187 | }; 188 | if self.line_next.as_ref() != line_next { 189 | self.line_next = line_next.map(|s| s.clone()); 190 | if !self 191 | .line_next 192 | .as_ref() 193 | .map(|s| s.is_empty()) 194 | .unwrap_or_default() 195 | { 196 | self.line_next_non_empty = self.line_next.clone(); 197 | } 198 | self.schedule_transitions(self.line_next.as_ref())?; 199 | } 200 | return Ok(()); 201 | } 202 | } 203 | if self.line_next != None { 204 | self.line_next = None; 205 | self.schedule_transitions(None)?; 206 | } 207 | Ok(()) 208 | } 209 | 210 | fn schedule_transitions(&self, line_next: Option<&String>) -> Result<()> { 211 | let Resources { 212 | dc, 213 | animation_manager, 214 | animation_timer, 215 | animation_transition_library, 216 | opacity, 217 | bg_width, 218 | bg_height, 219 | line_current_offset, 220 | line_next_offset, 221 | line_next_opacity, 222 | .. 223 | } = self.get_or_init_resources()?; 224 | let time_now = unsafe { animation_timer.GetTime() }?; 225 | let _do_transition = |variable: &IUIAnimationVariable, 226 | duration: Duration, 227 | initial_value: Option, 228 | final_value: f64, 229 | skip_if_hidden: bool, 230 | acceleration_ratio: f64, 231 | deceleration_ratio: f64| 232 | -> Result<()> { 233 | if initial_value.is_none() && unsafe { variable.GetFinalValue() }? == final_value { 234 | return Ok(()); 235 | } 236 | let duration = duration.as_secs_f64(); 237 | unsafe { 238 | let transition = animation_transition_library 239 | .CreateAccelerateDecelerateTransition( 240 | duration, 241 | final_value, 242 | acceleration_ratio, 243 | deceleration_ratio, 244 | )?; 245 | if let Some(initial_value) = initial_value { 246 | transition.SetInitialValue(initial_value)?; 247 | } 248 | if skip_if_hidden && opacity.GetValue()? == 0. { 249 | transition.SetInitialValue(final_value)?; 250 | } 251 | animation_manager.ScheduleTransition(variable, &transition, time_now)?; 252 | } 253 | Ok(()) 254 | }; 255 | let do_transition_ease_out = |variable: &IUIAnimationVariable, 256 | duration: Duration, 257 | initial_value: Option, 258 | final_value: f64, 259 | skip_if_hidden: bool| 260 | -> Result<()> { 261 | _do_transition( 262 | variable, 263 | duration, 264 | initial_value, 265 | final_value, 266 | skip_if_hidden, 267 | 0., 268 | 1., 269 | ) 270 | }; 271 | let do_transition_linear = |variable: &IUIAnimationVariable, 272 | duration: Duration, 273 | initial_value: Option, 274 | final_value: f64, 275 | skip_if_hidden: bool| 276 | -> Result<()> { 277 | _do_transition( 278 | variable, 279 | duration, 280 | initial_value, 281 | final_value, 282 | skip_if_hidden, 283 | 0., 284 | 0., 285 | ) 286 | }; 287 | match line_next { 288 | Some(line_next) if !line_next.is_empty() => { 289 | let size = unsafe { dc.GetSize() }; 290 | do_transition_ease_out(opacity, DURATION_FADE_IN, None, 1., false)?; 291 | 292 | let metrics = self.get_text_metrics(line_next, size.width, size.height)?; 293 | let final_bg_width = metrics.width as f64 + 2. * PADDING_HORIZONTAL; 294 | do_transition_ease_out(bg_width, DURATION_SIZING, None, final_bg_width, true)?; 295 | 296 | let final_bg_height = metrics.height as f64 + 2. * PADDING_VERTICAL; 297 | do_transition_ease_out(bg_height, DURATION_SIZING, None, final_bg_height, true)?; 298 | 299 | let vertical_offset = size.height as f64 / 3.; 300 | do_transition_ease_out( 301 | line_current_offset, 302 | DURATION_SCROLLING, 303 | Some(0.), 304 | -vertical_offset, 305 | true, 306 | )?; 307 | do_transition_ease_out( 308 | line_next_offset, 309 | DURATION_SCROLLING, 310 | Some(vertical_offset), 311 | 0., 312 | true, 313 | )?; 314 | 315 | do_transition_ease_out(line_next_opacity, DURATION_SCROLLING, Some(0.), 1., true)?; 316 | } 317 | _ => { 318 | do_transition_linear(opacity, DURATION_FADE_OUT, None, 0., false)?; 319 | } 320 | } 321 | Ok(()) 322 | } 323 | 324 | fn on_destroy(&self, _hwnd: HWND, _msg: u32, _wparam: WPARAM, _lparam: LPARAM) -> LRESULT { 325 | unsafe { PostQuitMessage(0) }; 326 | LRESULT(1) 327 | } 328 | 329 | fn get_or_init_resources(&self) -> Result<&Resources> { 330 | self.resources.get_or_try_init(|| { 331 | let (dpi_x, dpi_y) = get_desktop_dpi()?; 332 | let d2d_factory = create_d2d_factory()?; 333 | let dxgi_device = create_dxgi_device()?; 334 | let _dcomp_device: IDCompositionDevice = 335 | unsafe { DCompositionCreateDevice(&dxgi_device) }?; 336 | let visual = unsafe { _dcomp_device.CreateVisual() }?; 337 | let dc = create_device_context(&dxgi_device, &d2d_factory, dpi_x, dpi_y)?; 338 | let swap_chain = create_swap_chain(self.hwnd, &dxgi_device)?; 339 | create_bitmap_from_swap_chain(&dc, &swap_chain, dpi_x, dpi_y)?; 340 | unsafe { visual.SetContent(&swap_chain) }?; 341 | let _target = unsafe { _dcomp_device.CreateTargetForHwnd(self.hwnd, BOOL(1)) }?; 342 | unsafe { 343 | _target.SetRoot(&visual)?; 344 | _dcomp_device.Commit()?; 345 | } 346 | let brush = unsafe { dc.CreateSolidColorBrush(&D2D1_COLOR_F::default(), null()) }?; 347 | let dwrite_factory = create_dwrite_factory()?; 348 | let text_format: IDWriteTextFormat1 = unsafe { 349 | dwrite_factory 350 | .CreateTextFormat( 351 | "Segoe UI", 352 | None, 353 | DWRITE_FONT_WEIGHT_NORMAL, 354 | DWRITE_FONT_STYLE_NORMAL, 355 | DWRITE_FONT_STRETCH_NORMAL, 356 | 24., 357 | "", 358 | )? 359 | .cast() 360 | }?; 361 | { 362 | unsafe { 363 | text_format.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)?; 364 | text_format.SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)?; 365 | } 366 | let font_fallback_builder = unsafe { dwrite_factory.CreateFontFallbackBuilder() }?; 367 | let ranges = DWRITE_UNICODE_RANGE { 368 | first: 0x0, 369 | last: 0xffffffff, 370 | }; 371 | let fallback_family_names = [ 372 | HSTRING::from("Segoe UI Emoji"), 373 | HSTRING::from("Segoe UI Symbol"), 374 | HSTRING::from("Helvetica"), 375 | HSTRING::from("Microsoft YaHei UI"), 376 | ]; 377 | let fallback_family_names = fallback_family_names 378 | .iter() 379 | .map(|name| name.as_wide().as_ptr()) 380 | .collect::>(); 381 | unsafe { 382 | font_fallback_builder.AddMapping( 383 | &ranges, 384 | 1, 385 | fallback_family_names.as_ptr(), 386 | fallback_family_names.len() as u32, 387 | None, 388 | None, 389 | None, 390 | 1., 391 | )?; 392 | font_fallback_builder.AddMappings(dwrite_factory.GetSystemFontFallback()?)?; 393 | let font_fallback = font_fallback_builder.CreateFontFallback()?; 394 | text_format.SetFontFallback(font_fallback)?; 395 | } 396 | } 397 | let animation_manager = create_animation_manager()?; 398 | let animation_timer = create_animation_timer()?; 399 | let animation_timer_handler: IUIAnimationTimerEventHandler = 400 | UIAnimationTimerEventHandler::new(self.hwnd).into(); 401 | unsafe { animation_timer.SetTimerEventHandler(animation_timer_handler) }?; 402 | let timer_update_handler: IUIAnimationTimerUpdateHandler = animation_manager.cast()?; 403 | unsafe { 404 | animation_timer.SetTimerUpdateHandler( 405 | &timer_update_handler, 406 | UI_ANIMATION_IDLE_BEHAVIOR_DISABLE, 407 | ) 408 | }?; 409 | let animation_transition_library = create_animation_transition_library()?; 410 | let opacity = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 411 | let bg_width = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 412 | let bg_height = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 413 | let line_current_offset = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 414 | let line_next_offset = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 415 | let line_next_opacity = unsafe { animation_manager.CreateAnimationVariable(0.) }?; 416 | Ok(Resources { 417 | d2d_factory, 418 | dc, 419 | _dcomp_device, 420 | _target, 421 | swap_chain, 422 | brush, 423 | dwrite_factory, 424 | text_format, 425 | animation_manager, 426 | animation_timer, 427 | animation_transition_library, 428 | opacity, 429 | bg_width, 430 | bg_height, 431 | line_current_offset, 432 | line_next_offset, 433 | line_next_opacity, 434 | }) 435 | }) 436 | } 437 | 438 | fn draw(&self) -> Result<()> { 439 | let Resources { 440 | d2d_factory, 441 | dc, 442 | swap_chain, 443 | brush, 444 | opacity, 445 | bg_width, 446 | bg_height, 447 | line_current_offset, 448 | line_next_offset, 449 | line_next_opacity, 450 | .. 451 | } = self.get_or_init_resources()?; 452 | let Self { 453 | line_current, 454 | line_next_non_empty, 455 | .. 456 | } = self; 457 | unsafe { 458 | dc.BeginDraw(); 459 | dc.Clear(null_mut()); 460 | let opacity = opacity.GetValue()? as f32; 461 | dc.PushLayer( 462 | &D2D1_LAYER_PARAMETERS { 463 | contentBounds: D2D_RECT_F { 464 | left: -f32::INFINITY, 465 | top: -f32::INFINITY, 466 | right: f32::INFINITY, 467 | bottom: f32::INFINITY, 468 | }, 469 | geometricMask: None, 470 | maskAntialiasMode: D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, 471 | maskTransform: Matrix3x2::identity(), 472 | opacity, 473 | opacityBrush: None, 474 | layerOptions: D2D1_LAYER_OPTIONS_NONE, 475 | }, 476 | None, 477 | ); 478 | brush.SetColor(&D2D1_COLOR_F { 479 | r: 0., 480 | g: 0., 481 | b: 0., 482 | a: 0.5, 483 | }); 484 | let bg_width = bg_width.GetValue()? as f32; 485 | let bg_height = bg_height.GetValue()? as f32; 486 | let size = dc.GetSize(); 487 | let left = (size.width - bg_width) / 2.; 488 | let top = (size.height - bg_height) / 2.; 489 | let right = left + bg_width; 490 | let bottom = top + bg_height; 491 | let bg_rounded_rect = &D2D1_ROUNDED_RECT { 492 | rect: D2D_RECT_F { 493 | left, 494 | top, 495 | right, 496 | bottom, 497 | }, 498 | radiusX: 4., 499 | radiusY: 4., 500 | }; 501 | let bg_geometry = d2d_factory.CreateRoundedRectangleGeometry(bg_rounded_rect)?; 502 | dc.FillGeometry(&bg_geometry, brush, None); 503 | let line_next_opacity = line_next_opacity.GetValue()? as f32; 504 | let line_current_opacity = 1. - line_next_opacity; 505 | dc.PushLayer( 506 | &D2D1_LAYER_PARAMETERS { 507 | contentBounds: D2D_RECT_F { 508 | left: -f32::INFINITY, 509 | top: -f32::INFINITY, 510 | right: f32::INFINITY, 511 | bottom: f32::INFINITY, 512 | }, 513 | geometricMask: Some((&bg_geometry).into()), 514 | maskAntialiasMode: D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, 515 | maskTransform: Matrix3x2::identity(), 516 | opacity: line_current_opacity, 517 | opacityBrush: None, 518 | layerOptions: D2D1_LAYER_OPTIONS_NONE, 519 | }, 520 | None, 521 | ); 522 | let line_current_offset = line_current_offset.GetValue()? as f32; 523 | let line_next_offset = line_next_offset.GetValue()? as f32; 524 | if let Some(line_current) = line_current { 525 | self.draw_text( 526 | line_current, 527 | &D2D_RECT_F { 528 | left: 0., 529 | top: line_current_offset, 530 | right: size.width, 531 | bottom: line_current_offset + size.height, 532 | }, 533 | )?; 534 | } 535 | dc.PopLayer(); 536 | dc.PushLayer( 537 | &D2D1_LAYER_PARAMETERS { 538 | contentBounds: D2D_RECT_F { 539 | left: -f32::INFINITY, 540 | top: -f32::INFINITY, 541 | right: f32::INFINITY, 542 | bottom: f32::INFINITY, 543 | }, 544 | geometricMask: Some((&bg_geometry).into()), 545 | maskAntialiasMode: D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, 546 | maskTransform: Matrix3x2::identity(), 547 | opacity: line_next_opacity, 548 | opacityBrush: None, 549 | layerOptions: D2D1_LAYER_OPTIONS_NONE, 550 | }, 551 | None, 552 | ); 553 | if let Some(line_next) = line_next_non_empty { 554 | self.draw_text( 555 | line_next, 556 | &D2D_RECT_F { 557 | left: 0., 558 | top: line_next_offset, 559 | right: size.width, 560 | bottom: line_next_offset + size.height, 561 | }, 562 | )?; 563 | } 564 | dc.PopLayer(); 565 | dc.PopLayer(); 566 | dc.EndDraw(null_mut(), null_mut())?; 567 | swap_chain.Present(0, 0)?; 568 | } 569 | Ok(()) 570 | } 571 | 572 | fn create_text_layout( 573 | &self, 574 | text: &str, 575 | max_width: f32, 576 | max_height: f32, 577 | ) -> Result { 578 | let Resources { 579 | dwrite_factory, 580 | text_format, 581 | .. 582 | } = self.get_or_init_resources()?; 583 | let string = HSTRING::from(text); 584 | unsafe { 585 | dwrite_factory 586 | .CreateTextLayout( 587 | PWSTR(string.as_wide().as_ptr() as *mut _), 588 | string.len() as u32, 589 | text_format, 590 | max_width, 591 | max_height, 592 | ) 593 | .map_err(|e| e.into()) 594 | } 595 | } 596 | 597 | fn draw_text(&self, text: &str, rect: &D2D_RECT_F) -> Result<()> { 598 | let Resources { dc, brush, .. } = self.get_or_init_resources()?; 599 | let text_layout = 600 | self.create_text_layout(text, rect.right - rect.left, rect.bottom - rect.top)?; 601 | unsafe { 602 | brush.SetColor(&D2D1_COLOR_F { 603 | r: 1., 604 | g: 1., 605 | b: 1., 606 | a: 1., 607 | }); 608 | dc.DrawTextLayout( 609 | &D2D_POINT_2F { 610 | x: rect.left, 611 | y: rect.top, 612 | }, 613 | &text_layout, 614 | brush, 615 | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, 616 | ); 617 | } 618 | Ok(()) 619 | } 620 | 621 | fn get_text_metrics( 622 | &self, 623 | text: &str, 624 | max_width: f32, 625 | max_height: f32, 626 | ) -> Result { 627 | let text_layout = self.create_text_layout(text, max_width, max_height)?; 628 | unsafe { text_layout.GetMetrics() }.map_err(|e| e.into()) 629 | } 630 | } 631 | 632 | #[implement(Windows::Win32::UI::Animation::IUIAnimationTimerEventHandler)] 633 | struct UIAnimationTimerEventHandler { 634 | hwnd: HWND, 635 | } 636 | 637 | #[allow(non_snake_case)] 638 | impl UIAnimationTimerEventHandler { 639 | fn new(hwnd: HWND) -> Self { 640 | Self { hwnd } 641 | } 642 | 643 | fn OnPreUpdate(&self) -> HRESULT { 644 | S_OK 645 | } 646 | 647 | fn OnPostUpdate(&self) -> HRESULT { 648 | let window: &mut LyricsWindow = unsafe { get_window_instance(self.hwnd) }.unwrap(); 649 | window.draw().unwrap(); 650 | S_OK 651 | } 652 | 653 | fn OnRenderingTooSlow(&self, _fps: u32) -> HRESULT { 654 | S_OK 655 | } 656 | } 657 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anyhow" 46 | version = "1.0.79" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 49 | 50 | [[package]] 51 | name = "atty" 52 | version = "0.2.14" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 55 | dependencies = [ 56 | "hermit-abi 0.1.19", 57 | "libc", 58 | "winapi", 59 | ] 60 | 61 | [[package]] 62 | name = "autocfg" 63 | version = "1.1.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 66 | 67 | [[package]] 68 | name = "backtrace" 69 | version = "0.3.69" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 72 | dependencies = [ 73 | "addr2line", 74 | "cc", 75 | "cfg-if", 76 | "libc", 77 | "miniz_oxide", 78 | "object", 79 | "rustc-demangle", 80 | ] 81 | 82 | [[package]] 83 | name = "base64" 84 | version = "0.21.7" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 87 | 88 | [[package]] 89 | name = "bindings" 90 | version = "0.1.0" 91 | dependencies = [ 92 | "windows", 93 | ] 94 | 95 | [[package]] 96 | name = "bitflags" 97 | version = "1.3.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 100 | 101 | [[package]] 102 | name = "bitflags" 103 | version = "2.4.2" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 106 | 107 | [[package]] 108 | name = "bumpalo" 109 | version = "3.14.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 112 | 113 | [[package]] 114 | name = "bytes" 115 | version = "1.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 118 | 119 | [[package]] 120 | name = "cc" 121 | version = "1.0.83" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 124 | dependencies = [ 125 | "libc", 126 | ] 127 | 128 | [[package]] 129 | name = "cfg-if" 130 | version = "1.0.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 133 | 134 | [[package]] 135 | name = "chrono" 136 | version = "0.4.33" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" 139 | dependencies = [ 140 | "android-tzdata", 141 | "iana-time-zone", 142 | "js-sys", 143 | "num-traits", 144 | "wasm-bindgen", 145 | "windows-targets 0.52.0", 146 | ] 147 | 148 | [[package]] 149 | name = "const-sha1" 150 | version = "0.2.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d" 153 | 154 | [[package]] 155 | name = "core-foundation" 156 | version = "0.9.4" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 159 | dependencies = [ 160 | "core-foundation-sys", 161 | "libc", 162 | ] 163 | 164 | [[package]] 165 | name = "core-foundation-sys" 166 | version = "0.8.6" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 169 | 170 | [[package]] 171 | name = "educe" 172 | version = "0.5.11" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" 175 | dependencies = [ 176 | "enum-ordinalize", 177 | "proc-macro2", 178 | "quote", 179 | "syn 2.0.48", 180 | ] 181 | 182 | [[package]] 183 | name = "embed-resource" 184 | version = "1.8.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "e62abb876c07e4754fae5c14cafa77937841f01740637e17d78dc04352f32a5e" 187 | dependencies = [ 188 | "cc", 189 | "rustc_version", 190 | "toml", 191 | "vswhom", 192 | "winreg 0.10.1", 193 | ] 194 | 195 | [[package]] 196 | name = "encoding_rs" 197 | version = "0.8.33" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 200 | dependencies = [ 201 | "cfg-if", 202 | ] 203 | 204 | [[package]] 205 | name = "enum-ordinalize" 206 | version = "4.3.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" 209 | dependencies = [ 210 | "enum-ordinalize-derive", 211 | ] 212 | 213 | [[package]] 214 | name = "enum-ordinalize-derive" 215 | version = "4.3.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "syn 2.0.48", 222 | ] 223 | 224 | [[package]] 225 | name = "equivalent" 226 | version = "1.0.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 229 | 230 | [[package]] 231 | name = "errno" 232 | version = "0.3.8" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 235 | dependencies = [ 236 | "libc", 237 | "windows-sys 0.52.0", 238 | ] 239 | 240 | [[package]] 241 | name = "fastrand" 242 | version = "2.0.1" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 245 | 246 | [[package]] 247 | name = "flexi_logger" 248 | version = "0.18.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "081e9563bb9600593f0f5d38c24f0e3dc550cd301b4872b986e20f1874104791" 251 | dependencies = [ 252 | "atty", 253 | "chrono", 254 | "glob", 255 | "lazy_static", 256 | "log", 257 | "regex", 258 | "thiserror", 259 | "yansi", 260 | ] 261 | 262 | [[package]] 263 | name = "fnv" 264 | version = "1.0.7" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 267 | 268 | [[package]] 269 | name = "foreign-types" 270 | version = "0.3.2" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 273 | dependencies = [ 274 | "foreign-types-shared", 275 | ] 276 | 277 | [[package]] 278 | name = "foreign-types-shared" 279 | version = "0.1.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 282 | 283 | [[package]] 284 | name = "form_urlencoded" 285 | version = "1.2.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 288 | dependencies = [ 289 | "percent-encoding", 290 | ] 291 | 292 | [[package]] 293 | name = "futures-channel" 294 | version = "0.3.30" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 297 | dependencies = [ 298 | "futures-core", 299 | ] 300 | 301 | [[package]] 302 | name = "futures-core" 303 | version = "0.3.30" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 306 | 307 | [[package]] 308 | name = "futures-io" 309 | version = "0.3.30" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 312 | 313 | [[package]] 314 | name = "futures-sink" 315 | version = "0.3.30" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 318 | 319 | [[package]] 320 | name = "futures-task" 321 | version = "0.3.30" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 324 | 325 | [[package]] 326 | name = "futures-util" 327 | version = "0.3.30" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 330 | dependencies = [ 331 | "futures-core", 332 | "futures-io", 333 | "futures-task", 334 | "memchr", 335 | "pin-project-lite", 336 | "pin-utils", 337 | "slab", 338 | ] 339 | 340 | [[package]] 341 | name = "gimli" 342 | version = "0.28.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 345 | 346 | [[package]] 347 | name = "glob" 348 | version = "0.3.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 351 | 352 | [[package]] 353 | name = "h2" 354 | version = "0.3.24" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" 357 | dependencies = [ 358 | "bytes", 359 | "fnv", 360 | "futures-core", 361 | "futures-sink", 362 | "futures-util", 363 | "http", 364 | "indexmap", 365 | "slab", 366 | "tokio", 367 | "tokio-util", 368 | "tracing", 369 | ] 370 | 371 | [[package]] 372 | name = "hashbrown" 373 | version = "0.14.3" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 376 | 377 | [[package]] 378 | name = "hermit-abi" 379 | version = "0.1.19" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 382 | dependencies = [ 383 | "libc", 384 | ] 385 | 386 | [[package]] 387 | name = "hermit-abi" 388 | version = "0.3.4" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" 391 | 392 | [[package]] 393 | name = "html-escape" 394 | version = "0.2.13" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 397 | dependencies = [ 398 | "utf8-width", 399 | ] 400 | 401 | [[package]] 402 | name = "http" 403 | version = "0.2.11" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 406 | dependencies = [ 407 | "bytes", 408 | "fnv", 409 | "itoa", 410 | ] 411 | 412 | [[package]] 413 | name = "http-body" 414 | version = "0.4.6" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 417 | dependencies = [ 418 | "bytes", 419 | "http", 420 | "pin-project-lite", 421 | ] 422 | 423 | [[package]] 424 | name = "httparse" 425 | version = "1.8.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 428 | 429 | [[package]] 430 | name = "httpdate" 431 | version = "1.0.3" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 434 | 435 | [[package]] 436 | name = "hyper" 437 | version = "0.14.28" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" 440 | dependencies = [ 441 | "bytes", 442 | "futures-channel", 443 | "futures-core", 444 | "futures-util", 445 | "h2", 446 | "http", 447 | "http-body", 448 | "httparse", 449 | "httpdate", 450 | "itoa", 451 | "pin-project-lite", 452 | "socket2", 453 | "tokio", 454 | "tower-service", 455 | "tracing", 456 | "want", 457 | ] 458 | 459 | [[package]] 460 | name = "hyper-tls" 461 | version = "0.5.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 464 | dependencies = [ 465 | "bytes", 466 | "hyper", 467 | "native-tls", 468 | "tokio", 469 | "tokio-native-tls", 470 | ] 471 | 472 | [[package]] 473 | name = "iana-time-zone" 474 | version = "0.1.59" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" 477 | dependencies = [ 478 | "android_system_properties", 479 | "core-foundation-sys", 480 | "iana-time-zone-haiku", 481 | "js-sys", 482 | "wasm-bindgen", 483 | "windows-core", 484 | ] 485 | 486 | [[package]] 487 | name = "iana-time-zone-haiku" 488 | version = "0.1.2" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 491 | dependencies = [ 492 | "cc", 493 | ] 494 | 495 | [[package]] 496 | name = "idna" 497 | version = "0.5.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 500 | dependencies = [ 501 | "unicode-bidi", 502 | "unicode-normalization", 503 | ] 504 | 505 | [[package]] 506 | name = "ilyrics" 507 | version = "0.1.0" 508 | dependencies = [ 509 | "anyhow", 510 | "bindings", 511 | "embed-resource", 512 | "flexi_logger", 513 | "html-escape", 514 | "log", 515 | "lrc", 516 | "once_cell", 517 | "regex", 518 | "reqwest", 519 | "tokio", 520 | "utf16_lit", 521 | "windows", 522 | ] 523 | 524 | [[package]] 525 | name = "indexmap" 526 | version = "2.2.1" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" 529 | dependencies = [ 530 | "equivalent", 531 | "hashbrown", 532 | ] 533 | 534 | [[package]] 535 | name = "ipnet" 536 | version = "2.9.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 539 | 540 | [[package]] 541 | name = "itoa" 542 | version = "1.0.10" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 545 | 546 | [[package]] 547 | name = "js-sys" 548 | version = "0.3.67" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" 551 | dependencies = [ 552 | "wasm-bindgen", 553 | ] 554 | 555 | [[package]] 556 | name = "lazy_static" 557 | version = "1.4.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 560 | 561 | [[package]] 562 | name = "libc" 563 | version = "0.2.152" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" 566 | 567 | [[package]] 568 | name = "linux-raw-sys" 569 | version = "0.4.13" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 572 | 573 | [[package]] 574 | name = "lock_api" 575 | version = "0.4.11" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 578 | dependencies = [ 579 | "autocfg", 580 | "scopeguard", 581 | ] 582 | 583 | [[package]] 584 | name = "log" 585 | version = "0.4.20" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 588 | 589 | [[package]] 590 | name = "lrc" 591 | version = "0.1.8" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "ebdfe9790d2abe68b3cb4673de938bce2627c72ca55934c224e504d08fe4a05c" 594 | dependencies = [ 595 | "educe", 596 | "once_cell", 597 | "regex", 598 | "unicase", 599 | ] 600 | 601 | [[package]] 602 | name = "memchr" 603 | version = "2.7.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 606 | 607 | [[package]] 608 | name = "mime" 609 | version = "0.3.17" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 612 | 613 | [[package]] 614 | name = "miniz_oxide" 615 | version = "0.7.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 618 | dependencies = [ 619 | "adler", 620 | ] 621 | 622 | [[package]] 623 | name = "mio" 624 | version = "0.8.10" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" 627 | dependencies = [ 628 | "libc", 629 | "wasi", 630 | "windows-sys 0.48.0", 631 | ] 632 | 633 | [[package]] 634 | name = "native-tls" 635 | version = "0.2.11" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 638 | dependencies = [ 639 | "lazy_static", 640 | "libc", 641 | "log", 642 | "openssl", 643 | "openssl-probe", 644 | "openssl-sys", 645 | "schannel", 646 | "security-framework", 647 | "security-framework-sys", 648 | "tempfile", 649 | ] 650 | 651 | [[package]] 652 | name = "num-traits" 653 | version = "0.2.17" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 656 | dependencies = [ 657 | "autocfg", 658 | ] 659 | 660 | [[package]] 661 | name = "num_cpus" 662 | version = "1.16.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 665 | dependencies = [ 666 | "hermit-abi 0.3.4", 667 | "libc", 668 | ] 669 | 670 | [[package]] 671 | name = "object" 672 | version = "0.32.2" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 675 | dependencies = [ 676 | "memchr", 677 | ] 678 | 679 | [[package]] 680 | name = "once_cell" 681 | version = "1.19.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 684 | 685 | [[package]] 686 | name = "openssl" 687 | version = "0.10.63" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" 690 | dependencies = [ 691 | "bitflags 2.4.2", 692 | "cfg-if", 693 | "foreign-types", 694 | "libc", 695 | "once_cell", 696 | "openssl-macros", 697 | "openssl-sys", 698 | ] 699 | 700 | [[package]] 701 | name = "openssl-macros" 702 | version = "0.1.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 705 | dependencies = [ 706 | "proc-macro2", 707 | "quote", 708 | "syn 2.0.48", 709 | ] 710 | 711 | [[package]] 712 | name = "openssl-probe" 713 | version = "0.1.5" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 716 | 717 | [[package]] 718 | name = "openssl-sys" 719 | version = "0.9.99" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" 722 | dependencies = [ 723 | "cc", 724 | "libc", 725 | "pkg-config", 726 | "vcpkg", 727 | ] 728 | 729 | [[package]] 730 | name = "parking_lot" 731 | version = "0.12.1" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 734 | dependencies = [ 735 | "lock_api", 736 | "parking_lot_core", 737 | ] 738 | 739 | [[package]] 740 | name = "parking_lot_core" 741 | version = "0.9.9" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 744 | dependencies = [ 745 | "cfg-if", 746 | "libc", 747 | "redox_syscall", 748 | "smallvec", 749 | "windows-targets 0.48.5", 750 | ] 751 | 752 | [[package]] 753 | name = "percent-encoding" 754 | version = "2.3.1" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 757 | 758 | [[package]] 759 | name = "pin-project-lite" 760 | version = "0.2.13" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 763 | 764 | [[package]] 765 | name = "pin-utils" 766 | version = "0.1.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 769 | 770 | [[package]] 771 | name = "pkg-config" 772 | version = "0.3.29" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" 775 | 776 | [[package]] 777 | name = "proc-macro2" 778 | version = "1.0.78" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 781 | dependencies = [ 782 | "unicode-ident", 783 | ] 784 | 785 | [[package]] 786 | name = "quote" 787 | version = "1.0.35" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 790 | dependencies = [ 791 | "proc-macro2", 792 | ] 793 | 794 | [[package]] 795 | name = "redox_syscall" 796 | version = "0.4.1" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 799 | dependencies = [ 800 | "bitflags 1.3.2", 801 | ] 802 | 803 | [[package]] 804 | name = "regex" 805 | version = "1.10.3" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 808 | dependencies = [ 809 | "aho-corasick", 810 | "memchr", 811 | "regex-automata", 812 | "regex-syntax", 813 | ] 814 | 815 | [[package]] 816 | name = "regex-automata" 817 | version = "0.4.5" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 820 | dependencies = [ 821 | "aho-corasick", 822 | "memchr", 823 | "regex-syntax", 824 | ] 825 | 826 | [[package]] 827 | name = "regex-syntax" 828 | version = "0.8.2" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 831 | 832 | [[package]] 833 | name = "reqwest" 834 | version = "0.11.23" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" 837 | dependencies = [ 838 | "base64", 839 | "bytes", 840 | "encoding_rs", 841 | "futures-core", 842 | "futures-util", 843 | "h2", 844 | "http", 845 | "http-body", 846 | "hyper", 847 | "hyper-tls", 848 | "ipnet", 849 | "js-sys", 850 | "log", 851 | "mime", 852 | "native-tls", 853 | "once_cell", 854 | "percent-encoding", 855 | "pin-project-lite", 856 | "serde", 857 | "serde_json", 858 | "serde_urlencoded", 859 | "system-configuration", 860 | "tokio", 861 | "tokio-native-tls", 862 | "tower-service", 863 | "url", 864 | "wasm-bindgen", 865 | "wasm-bindgen-futures", 866 | "web-sys", 867 | "winreg 0.50.0", 868 | ] 869 | 870 | [[package]] 871 | name = "rustc-demangle" 872 | version = "0.1.23" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 875 | 876 | [[package]] 877 | name = "rustc_version" 878 | version = "0.4.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 881 | dependencies = [ 882 | "semver", 883 | ] 884 | 885 | [[package]] 886 | name = "rustix" 887 | version = "0.38.30" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" 890 | dependencies = [ 891 | "bitflags 2.4.2", 892 | "errno", 893 | "libc", 894 | "linux-raw-sys", 895 | "windows-sys 0.52.0", 896 | ] 897 | 898 | [[package]] 899 | name = "ryu" 900 | version = "1.0.16" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 903 | 904 | [[package]] 905 | name = "schannel" 906 | version = "0.1.23" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 909 | dependencies = [ 910 | "windows-sys 0.52.0", 911 | ] 912 | 913 | [[package]] 914 | name = "scopeguard" 915 | version = "1.2.0" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 918 | 919 | [[package]] 920 | name = "security-framework" 921 | version = "2.9.2" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 924 | dependencies = [ 925 | "bitflags 1.3.2", 926 | "core-foundation", 927 | "core-foundation-sys", 928 | "libc", 929 | "security-framework-sys", 930 | ] 931 | 932 | [[package]] 933 | name = "security-framework-sys" 934 | version = "2.9.1" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 937 | dependencies = [ 938 | "core-foundation-sys", 939 | "libc", 940 | ] 941 | 942 | [[package]] 943 | name = "semver" 944 | version = "1.0.21" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" 947 | 948 | [[package]] 949 | name = "serde" 950 | version = "1.0.196" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 953 | dependencies = [ 954 | "serde_derive", 955 | ] 956 | 957 | [[package]] 958 | name = "serde_derive" 959 | version = "1.0.196" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 962 | dependencies = [ 963 | "proc-macro2", 964 | "quote", 965 | "syn 2.0.48", 966 | ] 967 | 968 | [[package]] 969 | name = "serde_json" 970 | version = "1.0.112" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" 973 | dependencies = [ 974 | "itoa", 975 | "ryu", 976 | "serde", 977 | ] 978 | 979 | [[package]] 980 | name = "serde_urlencoded" 981 | version = "0.7.1" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 984 | dependencies = [ 985 | "form_urlencoded", 986 | "itoa", 987 | "ryu", 988 | "serde", 989 | ] 990 | 991 | [[package]] 992 | name = "signal-hook-registry" 993 | version = "1.4.1" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 996 | dependencies = [ 997 | "libc", 998 | ] 999 | 1000 | [[package]] 1001 | name = "slab" 1002 | version = "0.4.9" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1005 | dependencies = [ 1006 | "autocfg", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "smallvec" 1011 | version = "1.13.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 1014 | 1015 | [[package]] 1016 | name = "socket2" 1017 | version = "0.5.5" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 1020 | dependencies = [ 1021 | "libc", 1022 | "windows-sys 0.48.0", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "syn" 1027 | version = "1.0.109" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1030 | dependencies = [ 1031 | "proc-macro2", 1032 | "quote", 1033 | "unicode-ident", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "syn" 1038 | version = "2.0.48" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 1041 | dependencies = [ 1042 | "proc-macro2", 1043 | "quote", 1044 | "unicode-ident", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "system-configuration" 1049 | version = "0.5.1" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1052 | dependencies = [ 1053 | "bitflags 1.3.2", 1054 | "core-foundation", 1055 | "system-configuration-sys", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "system-configuration-sys" 1060 | version = "0.5.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1063 | dependencies = [ 1064 | "core-foundation-sys", 1065 | "libc", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "tempfile" 1070 | version = "3.9.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" 1073 | dependencies = [ 1074 | "cfg-if", 1075 | "fastrand", 1076 | "redox_syscall", 1077 | "rustix", 1078 | "windows-sys 0.52.0", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "thiserror" 1083 | version = "1.0.56" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" 1086 | dependencies = [ 1087 | "thiserror-impl", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "thiserror-impl" 1092 | version = "1.0.56" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" 1095 | dependencies = [ 1096 | "proc-macro2", 1097 | "quote", 1098 | "syn 2.0.48", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "tinyvec" 1103 | version = "1.6.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1106 | dependencies = [ 1107 | "tinyvec_macros", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "tinyvec_macros" 1112 | version = "0.1.1" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1115 | 1116 | [[package]] 1117 | name = "tokio" 1118 | version = "1.35.1" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" 1121 | dependencies = [ 1122 | "backtrace", 1123 | "bytes", 1124 | "libc", 1125 | "mio", 1126 | "num_cpus", 1127 | "parking_lot", 1128 | "pin-project-lite", 1129 | "signal-hook-registry", 1130 | "socket2", 1131 | "tokio-macros", 1132 | "windows-sys 0.48.0", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "tokio-macros" 1137 | version = "2.2.0" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1140 | dependencies = [ 1141 | "proc-macro2", 1142 | "quote", 1143 | "syn 2.0.48", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "tokio-native-tls" 1148 | version = "0.3.1" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1151 | dependencies = [ 1152 | "native-tls", 1153 | "tokio", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "tokio-util" 1158 | version = "0.7.10" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1161 | dependencies = [ 1162 | "bytes", 1163 | "futures-core", 1164 | "futures-sink", 1165 | "pin-project-lite", 1166 | "tokio", 1167 | "tracing", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "toml" 1172 | version = "0.5.11" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1175 | dependencies = [ 1176 | "serde", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "tower-service" 1181 | version = "0.3.2" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1184 | 1185 | [[package]] 1186 | name = "tracing" 1187 | version = "0.1.40" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1190 | dependencies = [ 1191 | "pin-project-lite", 1192 | "tracing-core", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "tracing-core" 1197 | version = "0.1.32" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1200 | dependencies = [ 1201 | "once_cell", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "try-lock" 1206 | version = "0.2.5" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1209 | 1210 | [[package]] 1211 | name = "unicase" 1212 | version = "2.7.0" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1215 | dependencies = [ 1216 | "version_check", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "unicode-bidi" 1221 | version = "0.3.15" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1224 | 1225 | [[package]] 1226 | name = "unicode-ident" 1227 | version = "1.0.12" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1230 | 1231 | [[package]] 1232 | name = "unicode-normalization" 1233 | version = "0.1.22" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1236 | dependencies = [ 1237 | "tinyvec", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "url" 1242 | version = "2.5.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1245 | dependencies = [ 1246 | "form_urlencoded", 1247 | "idna", 1248 | "percent-encoding", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "utf16_lit" 1253 | version = "2.0.2" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "14706d2a800ee8ff38c1d3edb873cd616971ea59eb7c0d046bb44ef59b06a1ae" 1256 | 1257 | [[package]] 1258 | name = "utf8-width" 1259 | version = "0.1.7" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" 1262 | 1263 | [[package]] 1264 | name = "vcpkg" 1265 | version = "0.2.15" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1268 | 1269 | [[package]] 1270 | name = "version_check" 1271 | version = "0.9.4" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1274 | 1275 | [[package]] 1276 | name = "vswhom" 1277 | version = "0.1.0" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" 1280 | dependencies = [ 1281 | "libc", 1282 | "vswhom-sys", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "vswhom-sys" 1287 | version = "0.1.2" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" 1290 | dependencies = [ 1291 | "cc", 1292 | "libc", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "want" 1297 | version = "0.3.1" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1300 | dependencies = [ 1301 | "try-lock", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "wasi" 1306 | version = "0.11.0+wasi-snapshot-preview1" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1309 | 1310 | [[package]] 1311 | name = "wasm-bindgen" 1312 | version = "0.2.90" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" 1315 | dependencies = [ 1316 | "cfg-if", 1317 | "wasm-bindgen-macro", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "wasm-bindgen-backend" 1322 | version = "0.2.90" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" 1325 | dependencies = [ 1326 | "bumpalo", 1327 | "log", 1328 | "once_cell", 1329 | "proc-macro2", 1330 | "quote", 1331 | "syn 2.0.48", 1332 | "wasm-bindgen-shared", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "wasm-bindgen-futures" 1337 | version = "0.4.40" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" 1340 | dependencies = [ 1341 | "cfg-if", 1342 | "js-sys", 1343 | "wasm-bindgen", 1344 | "web-sys", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "wasm-bindgen-macro" 1349 | version = "0.2.90" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" 1352 | dependencies = [ 1353 | "quote", 1354 | "wasm-bindgen-macro-support", 1355 | ] 1356 | 1357 | [[package]] 1358 | name = "wasm-bindgen-macro-support" 1359 | version = "0.2.90" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" 1362 | dependencies = [ 1363 | "proc-macro2", 1364 | "quote", 1365 | "syn 2.0.48", 1366 | "wasm-bindgen-backend", 1367 | "wasm-bindgen-shared", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "wasm-bindgen-shared" 1372 | version = "0.2.90" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" 1375 | 1376 | [[package]] 1377 | name = "web-sys" 1378 | version = "0.3.67" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" 1381 | dependencies = [ 1382 | "js-sys", 1383 | "wasm-bindgen", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "winapi" 1388 | version = "0.3.9" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1391 | dependencies = [ 1392 | "winapi-i686-pc-windows-gnu", 1393 | "winapi-x86_64-pc-windows-gnu", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "winapi-i686-pc-windows-gnu" 1398 | version = "0.4.0" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1401 | 1402 | [[package]] 1403 | name = "winapi-x86_64-pc-windows-gnu" 1404 | version = "0.4.0" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1407 | 1408 | [[package]] 1409 | name = "windows" 1410 | version = "0.19.0" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "ef84dd25f4c69a271b1bba394532bf400523b43169de21dfc715e8f8e491053d" 1413 | dependencies = [ 1414 | "const-sha1", 1415 | "windows_gen", 1416 | "windows_macros", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "windows-core" 1421 | version = "0.52.0" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1424 | dependencies = [ 1425 | "windows-targets 0.52.0", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "windows-sys" 1430 | version = "0.48.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1433 | dependencies = [ 1434 | "windows-targets 0.48.5", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "windows-sys" 1439 | version = "0.52.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1442 | dependencies = [ 1443 | "windows-targets 0.52.0", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "windows-targets" 1448 | version = "0.48.5" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1451 | dependencies = [ 1452 | "windows_aarch64_gnullvm 0.48.5", 1453 | "windows_aarch64_msvc 0.48.5", 1454 | "windows_i686_gnu 0.48.5", 1455 | "windows_i686_msvc 0.48.5", 1456 | "windows_x86_64_gnu 0.48.5", 1457 | "windows_x86_64_gnullvm 0.48.5", 1458 | "windows_x86_64_msvc 0.48.5", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "windows-targets" 1463 | version = "0.52.0" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 1466 | dependencies = [ 1467 | "windows_aarch64_gnullvm 0.52.0", 1468 | "windows_aarch64_msvc 0.52.0", 1469 | "windows_i686_gnu 0.52.0", 1470 | "windows_i686_msvc 0.52.0", 1471 | "windows_x86_64_gnu 0.52.0", 1472 | "windows_x86_64_gnullvm 0.52.0", 1473 | "windows_x86_64_msvc 0.52.0", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "windows_aarch64_gnullvm" 1478 | version = "0.48.5" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1481 | 1482 | [[package]] 1483 | name = "windows_aarch64_gnullvm" 1484 | version = "0.52.0" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 1487 | 1488 | [[package]] 1489 | name = "windows_aarch64_msvc" 1490 | version = "0.48.5" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1493 | 1494 | [[package]] 1495 | name = "windows_aarch64_msvc" 1496 | version = "0.52.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 1499 | 1500 | [[package]] 1501 | name = "windows_gen" 1502 | version = "0.19.0" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "ac7bb21b8ff5e801232b72a6ff554b4cc0cef9ed9238188c3ca78fe3968a7e5d" 1505 | dependencies = [ 1506 | "windows_quote", 1507 | "windows_reader", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "windows_i686_gnu" 1512 | version = "0.48.5" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1515 | 1516 | [[package]] 1517 | name = "windows_i686_gnu" 1518 | version = "0.52.0" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 1521 | 1522 | [[package]] 1523 | name = "windows_i686_msvc" 1524 | version = "0.48.5" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1527 | 1528 | [[package]] 1529 | name = "windows_i686_msvc" 1530 | version = "0.52.0" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 1533 | 1534 | [[package]] 1535 | name = "windows_macros" 1536 | version = "0.19.0" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "5566b8c51118769e4a9094a688bf1233a3f36aacbfc78f3b15817fe0b6e0442f" 1539 | dependencies = [ 1540 | "syn 1.0.109", 1541 | "windows_gen", 1542 | "windows_quote", 1543 | "windows_reader", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "windows_quote" 1548 | version = "0.19.0" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "4af8236a9493c38855f95cdd11b38b342512a5df4ee7473cffa828b5ebb0e39c" 1551 | 1552 | [[package]] 1553 | name = "windows_reader" 1554 | version = "0.19.0" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab" 1557 | 1558 | [[package]] 1559 | name = "windows_x86_64_gnu" 1560 | version = "0.48.5" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1563 | 1564 | [[package]] 1565 | name = "windows_x86_64_gnu" 1566 | version = "0.52.0" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 1569 | 1570 | [[package]] 1571 | name = "windows_x86_64_gnullvm" 1572 | version = "0.48.5" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1575 | 1576 | [[package]] 1577 | name = "windows_x86_64_gnullvm" 1578 | version = "0.52.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 1581 | 1582 | [[package]] 1583 | name = "windows_x86_64_msvc" 1584 | version = "0.48.5" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1587 | 1588 | [[package]] 1589 | name = "windows_x86_64_msvc" 1590 | version = "0.52.0" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1593 | 1594 | [[package]] 1595 | name = "winreg" 1596 | version = "0.10.1" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1599 | dependencies = [ 1600 | "winapi", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "winreg" 1605 | version = "0.50.0" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1608 | dependencies = [ 1609 | "cfg-if", 1610 | "windows-sys 0.48.0", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "yansi" 1615 | version = "0.5.1" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 1618 | --------------------------------------------------------------------------------