forked from qarmin/czkawka
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Windows taskbar progress support (qarmin#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)
- Loading branch information
Showing
10 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
} | ||
} |