use std::{
cell::RefCell,
rc::{Rc, Weak},
};
use windows::Win32::{
Foundation::{HWND, RECT},
Graphics::Gdi::DeleteObject,
UI::{
Shell::{
Shell_NotifyIconGetRect, Shell_NotifyIconW, NIF_ICON, NIF_MESSAGE, NIF_TIP, NIM_ADD,
NIM_DELETE, NIM_MODIFY, NIM_SETVERSION, NOTIFYICONDATAW, NOTIFYICONDATAW_0,
NOTIFYICONIDENTIFIER, NOTIFYICON_VERSION_4,
},
WindowsAndMessaging::{
CreateIconIndirect, DestroyIcon, SetForegroundWindow, TrackPopupMenuEx, HICON,
ICONINFO, TPM_BOTTOMALIGN, TPM_LEFTALIGN, TPM_RETURNCMD, TPM_RIGHTBUTTON, TPM_VERTICAL,
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE, WM_RBUTTONDOWN, WM_RBUTTONUP,
},
},
};
use crate::{
shell::{
api_model::{ImageData, StatusItemActionType},
platform::util::{HIWORD, LOWORD},
status_item_manager::{StatusItemDelegate, StatusItemHandle},
EngineHandle, IPoint, Point, Rect,
},
Context,
};
use super::{
display::Displays,
error::{PlatformError, PlatformResult},
menu::PlatformMenu,
run_loop::{PlatformRunLoopStatusItemDelegate, WM_STATUS_ITEM},
util::{image_data_to_hbitmap, to_utf16},
};
pub struct PlatformStatusItem {
handle: StatusItemHandle,
delegate: Weak<RefCell<dyn StatusItemDelegate>>,
pub engine: EngineHandle,
context: Context,
image: RefCell<Vec<ImageData>>,
hint: RefCell<String>,
}
impl PlatformStatusItem {
pub fn new(
handle: StatusItemHandle,
delegate: Weak<RefCell<dyn StatusItemDelegate>>,
engine: EngineHandle,
context: Context,
) -> Self {
Self {
handle,
delegate,
engine,
context,
image: RefCell::new(Vec::new()),
hint: RefCell::new(String::new()),
}
}
fn hwnd(&self) -> HWND {
self.context
.get()
.map(|c| c.run_loop.borrow().platform_run_loop.hwnd())
.unwrap_or(HWND(0))
}
pub fn assign_weak_self(&self, _weak: Weak<PlatformStatusItem>) {
let init_data = NOTIFYICONDATAW {
cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as u32,
uID: self.handle.0 as u32,
hWnd: self.hwnd(),
Anonymous: NOTIFYICONDATAW_0 {
uVersion: NOTIFYICON_VERSION_4,
},
..Default::default()
};
unsafe {
Shell_NotifyIconW(NIM_ADD, &init_data as *const _);
Shell_NotifyIconW(NIM_SETVERSION, &init_data as *const _);
}
}
fn update(&self) {
let max_scale = Displays::get_displays()
.displays
.iter()
.map(|d| d.scale)
.reduce(f64::max)
.unwrap_or(1.0);
let ideal_height = (max_scale * 16.0).round() as i32;
let icon = self
.image
.borrow()
.iter()
.min_by(|a, b| {
let d1 = (a.height - ideal_height).abs();
let d2 = (b.height - ideal_height).abs();
d1.cmp(&d2)
})
.map(Self::image_to_icon)
.unwrap_or(HICON(0));
let mut flags = NIF_MESSAGE | NIF_TIP;
if icon.0 != 0 {
flags |= NIF_ICON;
}
let mut data = NOTIFYICONDATAW {
cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as u32,
hWnd: self.hwnd(),
uID: self.handle.0 as u32,
uFlags: flags,
uCallbackMessage: WM_STATUS_ITEM,
hIcon: icon,
Anonymous: NOTIFYICONDATAW_0 {
uVersion: NOTIFYICON_VERSION_4,
},
..Default::default()
};
let tip = to_utf16(&self.hint.borrow());
for (place, data) in data.szTip.iter_mut().zip(tip.iter()) {
*place = *data;
}
unsafe {
Shell_NotifyIconW(NIM_MODIFY, &data as *const _);
if icon.0 != 0 {
DestroyIcon(icon);
}
}
}
fn image_to_icon(image: &ImageData) -> HICON {
let bitmap = image_data_to_hbitmap(image);
let icon_info = ICONINFO {
fIcon: true.into(),
xHotspot: 0,
yHotspot: 0,
hbmMask: bitmap,
hbmColor: bitmap,
};
let res = unsafe { CreateIconIndirect(&icon_info as *const _) };
unsafe {
DeleteObject(bitmap);
};
res
}
pub fn set_image(&self, image: Vec<ImageData>) -> PlatformResult<()> {
self.image.replace(image);
self.update();
Ok(())
}
pub fn set_hint(&self, hint: String) -> PlatformResult<()> {
self.hint.replace(hint);
self.update();
Ok(())
}
pub fn show_menu<F>(&self, menu: Rc<PlatformMenu>, offset: Point, on_done: F)
where
F: FnOnce(PlatformResult<()>) + 'static,
{
match self.get_rect() {
Ok(mut rect) => {
let hwnd = self.hwnd();
let displays = Displays::get_displays();
let display = displays.display_for_physical_point(&IPoint::xy(rect.left, rect.top));
if let Some(display) = display {
rect.left += (offset.x * display.scale).round() as i32;
rect.top += (offset.y * display.scale).round() as i32;
}
if let Some(context) = self.context.get() {
context
.run_loop
.borrow()
.schedule_now(move || {
let res = unsafe {
SetForegroundWindow(hwnd);
let res = TrackPopupMenuEx(
menu.menu,
(TPM_LEFTALIGN
| TPM_BOTTOMALIGN
| TPM_VERTICAL
| TPM_RIGHTBUTTON
| TPM_RETURNCMD)
.0,
rect.left,
rect.top,
hwnd,
std::ptr::null_mut(),
);
res.0
};
if res > 0 {
if let Some(delegate) = menu.delegate.upgrade() {
delegate.borrow().on_menu_action(menu.handle, res as i64);
}
}
on_done(Ok(()));
})
.detach();
}
}
Err(err) => {
on_done(Err(err));
}
}
}
pub fn set_highlighted(&self, _highlighted: bool) -> PlatformResult<()> {
Err(PlatformError::NotAvailable)
}
pub fn get_geometry(&self) -> PlatformResult<Rect> {
let rect = self.get_rect()?;
let displays = Displays::get_displays();
let origin = IPoint::xy(rect.left, rect.top);
let display = displays.display_for_physical_point(&origin);
if let Some(display) = display {
let origin = displays.convert_physical_to_logical(&origin);
if let Some(origin) = origin {
return Ok(Rect::xywh(
origin.x,
origin.y,
(rect.right - rect.left) as f64 / display.scale,
(rect.bottom - rect.top) as f64 / display.scale,
));
}
}
Err(PlatformError::OffsetOutOfScreenBounds)
}
pub fn get_screen_id(&self) -> PlatformResult<i64> {
let rect = self.get_rect()?;
let displays = Displays::get_displays();
let display = displays.display_for_physical_point(&IPoint::xy(rect.left, rect.top));
Ok(display.map(|d| d.id).unwrap_or(0))
}
fn get_rect(&self) -> PlatformResult<RECT> {
unsafe {
let id = NOTIFYICONIDENTIFIER {
cbSize: std::mem::size_of::<NOTIFYICONIDENTIFIER>() as u32,
hWnd: self.hwnd(),
uID: self.handle.0 as u32,
guidItem: Default::default(),
};
Shell_NotifyIconGetRect(&id as *const _)
}
.map_err(PlatformError::from)
}
pub fn on_message(&self, msg: u32, x: u16, y: u16) {
if msg == WM_MOUSEMOVE {
return;
}
if let Ok(rect) = self.get_rect() {
let displays = Displays::get_displays();
let screen = displays.display_for_physical_point(&IPoint::xy(rect.left, rect.top));
if let Some(screen) = screen {
let x = (x as i32 - rect.left) as f64 / screen.scale;
let y = (y as i32 - rect.top) as f64 / screen.scale;
if let Some(action) = match msg {
WM_LBUTTONDOWN => Some(StatusItemActionType::LeftMouseDown),
WM_LBUTTONUP => Some(StatusItemActionType::LeftMouseUp),
WM_RBUTTONDOWN => Some(StatusItemActionType::RightMouseDown),
WM_RBUTTONUP => Some(StatusItemActionType::RightMouseUp),
_ => None,
} {
if let Some(delegate) = self.delegate.upgrade() {
delegate
.borrow()
.on_action(self.handle, action, Point::xy(x, y));
}
}
}
}
}
}
impl Drop for PlatformStatusItem {
fn drop(&mut self) {
let delete_data = NOTIFYICONDATAW {
cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as u32,
uID: self.handle.0 as u32,
hWnd: self.hwnd(),
..Default::default()
};
unsafe {
Shell_NotifyIconW(NIM_DELETE, &delete_data as *const _);
}
}
}
pub struct PlatformStatusItemManager {
context: Context,
items: RefCell<Vec<Rc<PlatformStatusItem>>>,
}
impl PlatformStatusItemManager {
pub fn new(context: Context) -> Self {
Self {
context,
items: RefCell::new(Vec::new()),
}
}
pub fn assign_weak_self(&self, weak: Weak<PlatformStatusItemManager>) {
if let Some(context) = self.context.get() {
context
.run_loop
.borrow()
.platform_run_loop
.set_status_item_delegate(weak);
}
}
pub fn create_status_item(
&self,
handle: StatusItemHandle,
delegate: Weak<RefCell<dyn StatusItemDelegate>>,
engine: EngineHandle,
) -> PlatformResult<Rc<PlatformStatusItem>> {
let res = Rc::new(PlatformStatusItem::new(
handle,
delegate,
engine,
self.context.clone(),
));
self.items.borrow_mut().push(res.clone());
Ok(res)
}
pub fn unregister_status_item(&self, item: &Rc<PlatformStatusItem>) {
self.items.borrow_mut().retain(|i| !Rc::ptr_eq(i, item));
}
}
impl PlatformRunLoopStatusItemDelegate for PlatformStatusItemManager {
fn on_status_item_message(
&self,
w_param: windows::Win32::Foundation::WPARAM,
l_param: windows::Win32::Foundation::LPARAM,
) {
let msg = LOWORD(l_param.0 as u32);
let id = HIWORD(l_param.0 as u32);
let x = LOWORD(w_param.0 as u32);
let y = HIWORD(w_param.0 as u32);
let item = self
.items
.borrow()
.iter()
.find(|i| i.handle.0 == id as i64)
.cloned();
if let Some(item) = item {
item.on_message(msg as u32, x, y);
}
}
}