From 4c205ce098d2ca2f9e7ef38ffc0c2f8ed2b2be4f Mon Sep 17 00:00:00 2001 From: krzysdz Date: Sat, 20 Feb 2021 12:24:36 +0100 Subject: [PATCH] Windows taskbar progress support (#264) * Initial Windows taskbar progress support * Changes to COM (un)init It turns out winapi exposes IIDs through a `uuidof()` function of interfaces, so the copied one can be removed. * Don't return error codes Now the `TaskbarProgress` functions fail silently. The `TaskbarProgress` struct now will always be created (even in case of errors in initialisation), but it won't do anything. * Fix builds for other systems * Formatted code * Fix progress shown after the operation finished A progress update was received after the stop event. Also `as_ref()` was removed in many places (I don't even know why it was there). * Remove redundant call to hide It's already called by the `glib_stop_receiver` receiver. * Release the ITaskbarList3 and call CoUninitialize at exit Because objects moved to closures used as fallbacks in GTK have [static lifetimes](https://gtk-rs.org/docs-src/tutorial/closures#closures), the `TaskbarProgress` will never be dropped. To workaround this problem a `release` function is called when the main window is closed. This function behaves like `drop`, but sets the struct in a valid "empty" state, so that calling `release`/`drop` again won't cause problems. * Don't set the NORMAL state manually Because only NOPROGRESS and INDETERMINATE states are used, there is no need to set the NORMAL state when changing the progress value. Now `set_progress_value` will also change the `TaskbarProgress::current_state` if such situation occurs. > Unless [SetProgressState](https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate) > has set a blocking state (TBPF_ERROR or TBPF_PAUSED) for the window, a call to **SetProgressValue** assumes the TBPF_NORMAL > state even if it is not explicitly set. A call to **SetProgressValue** overrides and clears the TBPF_INDETERMINATE state. See the [SetProgressValue documentation](https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressvalue#how-the-taskbar-button-chooses-the-progress-indicator-for-a-group) --- Cargo.lock | 1 + czkawka_gui/Cargo.toml | 3 + czkawka_gui/src/connect_button_search.rs | 5 + czkawka_gui/src/connect_compute_results.rs | 3 + czkawka_gui/src/connect_progress_window.rs | 43 ++++++ czkawka_gui/src/gui_data.rs | 8 ++ czkawka_gui/src/main.rs | 7 + czkawka_gui/src/taskbar_progress.rs | 5 + czkawka_gui/src/taskbar_progress_dummy.rs | 46 ++++++ czkawka_gui/src/taskbar_progress_win.rs | 154 +++++++++++++++++++++ 10 files changed, 275 insertions(+) create mode 100644 czkawka_gui/src/taskbar_progress.rs create mode 100644 czkawka_gui/src/taskbar_progress_dummy.rs create mode 100644 czkawka_gui/src/taskbar_progress_win.rs diff --git a/Cargo.lock b/Cargo.lock index d0d1220..d6b607f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -600,6 +600,7 @@ dependencies = [ "humansize", "image", "open", + "winapi", ] [[package]] diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 385bd77..b0ec2bb 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -31,6 +31,9 @@ open = "1.4.0" # To get image preview image = "0.23.12" +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.9", features = ["combaseapi", "objbase", "shobjidl_core", "windef", "winerror", "wtypesbase", "winuser"] } + [dependencies.gtk] version = "0.9.2" default-features = false # just in case diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index b8a72dd..f004a21 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -21,6 +21,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; +use crate::taskbar_progress::tbp_flags::TBPF_NOPROGRESS; + #[allow(clippy::too_many_arguments)] pub fn connect_button_search( gui_data: &GuiData, @@ -83,6 +85,7 @@ pub fn connect_button_search( let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone(); let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let image_preview_similar_images = gui_data.main_notebook.image_preview_similar_images.clone(); let radio_button_hash_type_blake3 = gui_data.main_notebook.radio_button_hash_type_blake3.clone(); let radio_button_hash_type_crc32 = gui_data.main_notebook.radio_button_hash_type_crc32.clone(); @@ -405,6 +408,8 @@ pub fn connect_button_search( // Show progress dialog if show_dialog.load(Ordering::Relaxed) { window_progress.show(); + taskbar_state.borrow().show(); + taskbar_state.borrow().set_progress_state(TBPF_NOPROGRESS); } }); } diff --git a/czkawka_gui/src/connect_compute_results.rs b/czkawka_gui/src/connect_compute_results.rs index 696ecf5..9f58cd8 100644 --- a/czkawka_gui/src/connect_compute_results.rs +++ b/czkawka_gui/src/connect_compute_results.rs @@ -39,12 +39,15 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let shared_same_music_state = gui_data.shared_same_music_state.clone(); let buttons_names = gui_data.bottom_buttons.buttons_names.clone(); let window_progress = gui_data.progress_window.window_progress.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); glib_stop_receiver.attach(None, move |msg| { buttons_search.show(); window_progress.hide(); + taskbar_state.borrow().hide(); + // Restore clickability to main notebook notebook_main.set_sensitive(true); diff --git a/czkawka_gui/src/connect_progress_window.rs b/czkawka_gui/src/connect_progress_window.rs index 47618d8..8d48dcf 100644 --- a/czkawka_gui/src/connect_progress_window.rs +++ b/czkawka_gui/src/connect_progress_window.rs @@ -1,4 +1,5 @@ use crate::gui_data::GuiData; +use crate::taskbar_progress::tbp_flags::TBPF_INDETERMINATE; use czkawka_core::{big_file, broken_files, duplicate, empty_files, empty_folder, invalid_symlinks, same_music, similar_images, temporary, zeroed}; @@ -27,6 +28,7 @@ pub fn connect_progress_window( let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_duplicate_files.next().await { match item.checking_method { @@ -39,6 +41,7 @@ pub fn connect_progress_window( // progress_bar_all_stages.hide(); progress_bar_all_stages.set_fraction(0 as f64); label_stage.set_text(format!("Scanned size of {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } // Hash - first 1KB file 1 => { @@ -47,9 +50,11 @@ pub fn connect_progress_window( if item.files_to_check != 0 { progress_bar_all_stages.set_fraction((1f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + taskbar_state.borrow().set_progress_value((item.files_to_check + item.files_checked) as u64, item.files_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(1, 1 + item.max_stage as u64); } label_stage.set_text(format!("Analyzed partial hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); } @@ -58,9 +63,13 @@ pub fn connect_progress_window( if item.files_to_check != 0 { progress_bar_all_stages.set_fraction((2f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + taskbar_state + .borrow() + .set_progress_value((2 * item.files_to_check + item.files_checked) as u64, item.files_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((2f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(2, 1 + item.max_stage as u64); } if item.checking_method == duplicate::CheckingMethod::Hash { @@ -79,12 +88,14 @@ pub fn connect_progress_window( grid_progress_stages.hide(); label_stage.set_text(format!("Scanned name of {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } duplicate::CheckingMethod::Size => { label_stage.show(); grid_progress_stages.hide(); label_stage.set_text(format!("Scanned size {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } duplicate::CheckingMethod::None => { panic!(); @@ -97,9 +108,11 @@ pub fn connect_progress_window( { // Empty Files let label_stage = gui_data.progress_window.label_stage.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_empty_files.next().await { label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; main_context.spawn_local(future); @@ -107,9 +120,11 @@ pub fn connect_progress_window( { // Empty Folder let label_stage = gui_data.progress_window.label_stage.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_empty_folder.next().await { label_stage.set_text(format!("Scanned {} folders", item.folders_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; main_context.spawn_local(future); @@ -117,9 +132,11 @@ pub fn connect_progress_window( { // Big Files let label_stage = gui_data.progress_window.label_stage.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_big_files.next().await { label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; main_context.spawn_local(future); @@ -129,21 +146,25 @@ pub fn connect_progress_window( let label_stage = gui_data.progress_window.label_stage.clone(); let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_same_music.next().await { match item.current_stage { 0 => { progress_bar_current_stage.hide(); label_stage.set_text(format!("Scanned {} files", item.music_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); if item.music_to_check != 0 { progress_bar_all_stages.set_fraction((1f64 + (item.music_checked) as f64 / item.music_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.music_checked) as f64 / item.music_to_check as f64); + taskbar_state.borrow().set_progress_value((item.music_to_check + item.music_checked) as u64, item.music_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } label_stage.set_text(format!("Reading tags of {}/{} music files", item.music_checked, item.music_to_check).as_str()); } @@ -151,9 +172,13 @@ pub fn connect_progress_window( if item.music_to_check != 0 { progress_bar_all_stages.set_fraction((2f64 + (item.music_checked) as f64 / item.music_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.music_checked) as f64 / item.music_to_check as f64); + taskbar_state + .borrow() + .set_progress_value((2 * item.music_to_check + item.music_checked) as u64, item.music_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((2f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(2, (item.max_stage + 1) as u64); } label_stage.set_text(format!("Checking for duplicates of {}/{} music files", item.music_checked, item.music_to_check).as_str()); } @@ -170,21 +195,27 @@ pub fn connect_progress_window( let label_stage = gui_data.progress_window.label_stage.clone(); let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_similar_images.next().await { match item.current_stage { 0 => { progress_bar_current_stage.hide(); label_stage.set_text(format!("Scanned {} files", item.images_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); if item.images_to_check != 0 { progress_bar_all_stages.set_fraction((1f64 + (item.images_checked) as f64 / item.images_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.images_checked) as f64 / item.images_to_check as f64); + taskbar_state + .borrow() + .set_progress_value((item.images_to_check + item.images_checked) as u64, item.images_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } label_stage.set_text(format!("Hashing {}/{} image", item.images_checked, item.images_to_check).as_str()); } @@ -199,9 +230,11 @@ pub fn connect_progress_window( { // Temporary let label_stage = gui_data.progress_window.label_stage.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_temporary.next().await { label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; main_context.spawn_local(future); @@ -211,21 +244,25 @@ pub fn connect_progress_window( let label_stage = gui_data.progress_window.label_stage.clone(); let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_zeroed.next().await { match item.current_stage { 0 => { progress_bar_current_stage.hide(); label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); if item.files_to_check != 0 { progress_bar_all_stages.set_fraction((1f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + taskbar_state.borrow().set_progress_value((item.files_to_check + item.files_checked) as u64, item.files_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } label_stage.set_text(format!("Checking {}/{} file", item.files_checked, item.files_to_check).as_str()); } @@ -240,9 +277,11 @@ pub fn connect_progress_window( { // Invalid Symlinks let label_stage = gui_data.progress_window.label_stage.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_invalid_symlinks.next().await { label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; main_context.spawn_local(future); @@ -252,21 +291,25 @@ pub fn connect_progress_window( let label_stage = gui_data.progress_window.label_stage.clone(); let progress_bar_current_stage = gui_data.progress_window.progress_bar_current_stage.clone(); let progress_bar_all_stages = gui_data.progress_window.progress_bar_all_stages.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_broken_files.next().await { match item.current_stage { 0 => { progress_bar_current_stage.hide(); label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); if item.files_to_check != 0 { progress_bar_all_stages.set_fraction((1f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + taskbar_state.borrow().set_progress_value((item.files_to_check + item.files_checked) as u64, item.files_to_check as u64 * (item.max_stage + 1) as u64); } else { progress_bar_all_stages.set_fraction((1f64) / (item.max_stage + 1) as f64); progress_bar_current_stage.set_fraction(0f64); + taskbar_state.borrow().set_progress_value(1, (item.max_stage + 1) as u64); } label_stage.set_text(format!("Checking {}/{} files", item.files_checked, item.files_to_check).as_str()); } diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index bdfe7d3..8fe3e2f 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -8,6 +8,7 @@ use crate::gui_popovers::GUIPopovers; use crate::gui_progress_dialog::GUIProgressDialog; use crate::gui_upper_notepad::GUIUpperNotebook; use crate::notebook_enums::*; +use crate::taskbar_progress::TaskbarProgress; use crossbeam_channel::unbounded; use czkawka_core::big_file::BigFile; use czkawka_core::broken_files::BrokenFiles; @@ -43,6 +44,9 @@ pub struct GuiData { pub options: GUIOptions, pub header: GUIHeader, + // Taskbar state + pub taskbar_state: Rc>, + // Buttons state pub shared_buttons: Rc>>>, @@ -95,6 +99,9 @@ impl GuiData { //////////////////////////////////////////////////////////////////////////////////////////////// + // Taskbar state + let taskbar_state = Rc::new(RefCell::new(TaskbarProgress::new())); + // Buttons State - to remember existence of different buttons on pages let shared_buttons: Rc> = Rc::new(RefCell::new(HashMap::>::new())); @@ -160,6 +167,7 @@ impl GuiData { about, options, header, + taskbar_state, shared_buttons, shared_upper_notebooks, shared_duplication_state, diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index f1a3081..9e6ddc5 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -31,6 +31,11 @@ mod help_functions; mod initialize_gui; mod notebook_enums; mod saving_loading; +mod taskbar_progress; +#[cfg(not(target_os = "windows"))] +mod taskbar_progress_dummy; +#[cfg(target_os = "windows")] +mod taskbar_progress_win; use czkawka_core::*; @@ -140,9 +145,11 @@ fn main() { // Quit the program when X in main window was clicked { let window_main = gui_data.window_main.clone(); + let taskbar_state = gui_data.taskbar_state.clone(); window_main.connect_delete_event(move |_, _| { save_configuration(&gui_data, false); // Save configuration at exit gtk::main_quit(); + taskbar_state.borrow_mut().release(); Inhibit(false) }); } diff --git a/czkawka_gui/src/taskbar_progress.rs b/czkawka_gui/src/taskbar_progress.rs new file mode 100644 index 0000000..1d9033a --- /dev/null +++ b/czkawka_gui/src/taskbar_progress.rs @@ -0,0 +1,5 @@ +#[cfg(target_os = "windows")] +pub use crate::taskbar_progress_win::{tbp_flags, TaskbarProgress}; + +#[cfg(not(target_os = "windows"))] +pub use crate::taskbar_progress_dummy::{tbp_flags, TaskbarProgress}; diff --git a/czkawka_gui/src/taskbar_progress_dummy.rs b/czkawka_gui/src/taskbar_progress_dummy.rs new file mode 100644 index 0000000..790a79c --- /dev/null +++ b/czkawka_gui/src/taskbar_progress_dummy.rs @@ -0,0 +1,46 @@ +#![cfg(not(target_os = "windows"))] +use std::convert::From; + +enum HWND__ {} +type HWND = *mut HWND__; + +#[allow(non_camel_case_types, dead_code)] +pub enum TBPFLAG { + TBPF_NOPROGRESS = 0, + TBPF_INDETERMINATE = 0x1, + TBPF_NORMAL = 0x2, + TBPF_ERROR = 0x4, + TBPF_PAUSED = 0x8, +} + +pub mod tbp_flags { + pub use super::TBPFLAG::*; +} + +pub struct TaskbarProgress {} + +impl TaskbarProgress { + pub fn new() -> TaskbarProgress { + TaskbarProgress {} + } + + pub fn set_progress_state(&self, _tbp_flags: TBPFLAG) {} + + pub fn set_progress_value(&self, _completed: u64, _total: u64) {} + + pub fn hide(&self) {} + + pub fn show(&self) {} + + pub fn release(&mut self) {} +} + +impl From for TaskbarProgress { + fn from(_hwnd: HWND) -> Self { + TaskbarProgress {} + } +} + +impl Drop for TaskbarProgress { + fn drop(&mut self) {} +} diff --git a/czkawka_gui/src/taskbar_progress_win.rs b/czkawka_gui/src/taskbar_progress_win.rs new file mode 100644 index 0000000..2d43094 --- /dev/null +++ b/czkawka_gui/src/taskbar_progress_win.rs @@ -0,0 +1,154 @@ +#![cfg(target_os = "windows")] +extern crate winapi; +use std::cell::RefCell; +use std::convert::From; +use std::ptr; +use winapi::ctypes::c_void; +use winapi::shared::windef::HWND; +use winapi::shared::winerror::{E_POINTER, S_OK}; +use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; +use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList3, TBPFLAG}; +use winapi::um::{combaseapi, objbase, winuser}; +use winapi::Interface; + +pub mod tbp_flags { + pub use winapi::um::shobjidl_core::{TBPF_ERROR, TBPF_INDETERMINATE, TBPF_NOPROGRESS, TBPF_NORMAL, TBPF_PAUSED}; +} + +pub struct TaskbarProgress { + hwnd: HWND, + taskbar_list: *mut ITaskbarList3, + current_state: RefCell, + current_progress: RefCell<(u64, u64)>, + must_uninit_com: bool, + is_active: RefCell, +} + +impl TaskbarProgress { + pub fn new() -> TaskbarProgress { + let hwnd = unsafe { winuser::GetActiveWindow() }; + TaskbarProgress::from(hwnd) + } + + pub fn set_progress_state(&self, tbp_flags: TBPFLAG) { + if tbp_flags == *self.current_state.borrow() || !*self.is_active.borrow() { + return (); + } + let result = unsafe { + if let Some(list) = self.taskbar_list.as_ref() { + list.SetProgressState(self.hwnd, tbp_flags) + } else { + E_POINTER + } + }; + if result == S_OK { + self.current_state.replace(tbp_flags); + } + } + + pub fn set_progress_value(&self, completed: u64, total: u64) { + // Don't change the value if the is_active flag is false or the value has not changed. + // If is_active is true and the value has not changed, but the progress indicator was in NOPROGRESS or INDETERMINATE state, set the value (and NORMAL state). + if ((completed, total) == *self.current_progress.borrow() && *self.current_state.borrow() != tbp_flags::TBPF_NOPROGRESS && *self.current_state.borrow() != tbp_flags::TBPF_INDETERMINATE) || !*self.is_active.borrow() { + return (); + } + let result = unsafe { + if let Some(list) = self.taskbar_list.as_ref() { + list.SetProgressValue(self.hwnd, completed, total) + } else { + E_POINTER + } + }; + if result == S_OK { + self.current_progress.replace((completed, total)); + if *self.current_state.borrow() == tbp_flags::TBPF_NOPROGRESS || *self.current_state.borrow() == tbp_flags::TBPF_INDETERMINATE { + self.current_state.replace(tbp_flags::TBPF_NORMAL); + } + } + } + + pub fn hide(&self) { + self.set_progress_state(tbp_flags::TBPF_NOPROGRESS); + *self.is_active.borrow_mut() = false; + } + + pub fn show(&self) { + *self.is_active.borrow_mut() = true; + } + + /// Releases the ITaskbarList3 pointer, uninitialises the COM API and sets the struct to a valid "empty" state. + /// It's required for proper use of the COM API, because `drop` is never called (objects moved to GTK closures have `static` lifetime). + pub fn release(&mut self) { + unsafe { + if let Some(list) = self.taskbar_list.as_ref() { + list.Release(); + self.taskbar_list = ptr::null_mut(); + self.hwnd = ptr::null_mut(); + } + // A thread must call CoUninitialize once for each successful call it has made to + // the CoInitialize or CoInitializeEx function, including any call that returns S_FALSE. + if self.must_uninit_com { + combaseapi::CoUninitialize(); + self.must_uninit_com = false; + } + } + } +} + +impl From for TaskbarProgress { + fn from(hwnd: HWND) -> Self { + if hwnd.is_null() { + return TaskbarProgress { + hwnd, + taskbar_list: ptr::null_mut(), + current_state: RefCell::new(tbp_flags::TBPF_NOPROGRESS), + current_progress: RefCell::new((0, 0)), + must_uninit_com: false, + is_active: RefCell::new(false), + }; + } + + let init_result = unsafe { combaseapi::CoInitializeEx(ptr::null_mut(), objbase::COINIT_APARTMENTTHREADED) }; + // S_FALSE means that COM library is already initialised for this thread + // Success codes are not negative, RPC_E_CHANGED_MODE should not be possible and is treated as an error + if init_result < 0 { + return TaskbarProgress { + hwnd: ptr::null_mut(), + taskbar_list: ptr::null_mut(), + current_state: RefCell::new(tbp_flags::TBPF_NOPROGRESS), + current_progress: RefCell::new((0, 0)), + must_uninit_com: false, + is_active: RefCell::new(false), + }; + } + + let mut taskbar_list: *mut ITaskbarList3 = ptr::null_mut(); + let taskbar_list_ptr: *mut *mut ITaskbarList3 = &mut taskbar_list; + + unsafe { combaseapi::CoCreateInstance(&CLSID_TaskbarList, ptr::null_mut(), CLSCTX_INPROC_SERVER, &ITaskbarList3::uuidof(), taskbar_list_ptr as *mut *mut c_void) }; + + TaskbarProgress { + hwnd: if taskbar_list.is_null() { ptr::null_mut() } else { hwnd }, + taskbar_list, + current_state: RefCell::new(tbp_flags::TBPF_NOPROGRESS), // Assume no progress + current_progress: RefCell::new((0, 0)), + must_uninit_com: true, + is_active: RefCell::new(false), + } + } +} + +impl Drop for TaskbarProgress { + fn drop(&mut self) { + unsafe { + if let Some(list) = self.taskbar_list.as_ref() { + list.Release(); + } + // A thread must call CoUninitialize once for each successful call it has made to + // the CoInitialize or CoInitializeEx function, including any call that returns S_FALSE. + if self.must_uninit_com { + combaseapi::CoUninitialize(); + } + } + } +}