1
0
Fork 0
mirror of synced 2024-06-20 19:30:47 +12:00

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)
This commit is contained in:
krzysdz 2021-02-20 12:24:36 +01:00 committed by GitHub
parent db255a87d4
commit 4c205ce098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 275 additions and 0 deletions

1
Cargo.lock generated
View file

@ -600,6 +600,7 @@ dependencies = [
"humansize",
"image",
"open",
"winapi",
]
[[package]]

View file

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

View file

@ -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);
}
});
}

View file

@ -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);

View file

@ -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());
}

View file

@ -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<RefCell<TaskbarProgress>>,
// Buttons state
pub shared_buttons: Rc<RefCell<HashMap<NotebookMainEnum, HashMap<String, bool>>>>,
@ -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<RefCell<_>> = Rc::new(RefCell::new(HashMap::<NotebookMainEnum, HashMap<String, bool>>::new()));
@ -160,6 +167,7 @@ impl GuiData {
about,
options,
header,
taskbar_state,
shared_buttons,
shared_upper_notebooks,
shared_duplication_state,

View file

@ -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)
});
}

View file

@ -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};

View file

@ -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<HWND> for TaskbarProgress {
fn from(_hwnd: HWND) -> Self {
TaskbarProgress {}
}
}
impl Drop for TaskbarProgress {
fn drop(&mut self) {}
}

View file

@ -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<TBPFLAG>,
current_progress: RefCell<(u64, u64)>,
must_uninit_com: bool,
is_active: RefCell<bool>,
}
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<HWND> 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();
}
}
}
}