155 lines
5.9 KiB
Rust
155 lines
5.9 KiB
Rust
|
#![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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|