Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/native_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,61 @@ pub fn find_child_window(parent: HWND, class_name: &str) -> Option<HWND> {
}
}

/// Find any tray or clock child window in a taskbar (for primary or secondary taskbars)
pub fn find_taskbar_tray_or_clock_window(taskbar_hwnd: HWND) -> Option<HWND> {
const CANDIDATES: &[&str] = &[
"TrayNotifyWnd",
"Taskbar.SecondaryTrayHostControl",
"TrayClockWClass",
"ClockButton",
];
for &class_name in CANDIDATES {
if let Some(h) = find_child_window(taskbar_hwnd, class_name) {
return Some(h);
}
}
None
}

/// Find the leftmost X coordinate of right-side child controls inside a taskbar
pub fn find_taskbar_right_controls_left(taskbar_hwnd: HWND, taskbar_rect: RECT) -> Option<i32> {
if let Some(tray_hwnd) = find_taskbar_tray_or_clock_window(taskbar_hwnd) {
if let Some(tray_rect) = get_window_rect_safe(tray_hwnd) {
return Some(tray_rect.left);
}
}

unsafe extern "system" fn enum_child_proc(child_hwnd: HWND, lparam: LPARAM) -> BOOL {
let (taskbar_rect, min_left) = &mut *(lparam.0 as *mut (RECT, Option<i32>));
if let Some(rect) = get_window_rect_safe(child_hwnd) {
let taskbar_width = taskbar_rect.right - taskbar_rect.left;
// Only search in the rightmost 25% of the taskbar to avoid taskbar app buttons
let right_zone_x = taskbar_rect.right - (taskbar_width / 4);
if rect.left >= right_zone_x && rect.left < taskbar_rect.right && (rect.right - rect.left) > 0 {
match min_left {
Some(current) => {
if rect.left < *current {
*min_left = Some(rect.left);
}
}
None => *min_left = Some(rect.left),
}
}
}
BOOL(1)
}

let mut data = (taskbar_rect, None);
unsafe {
let _ = EnumChildWindows(
taskbar_hwnd,
Some(enum_child_proc),
LPARAM(&mut data as *mut _ as isize),
);
}
data.1
}

/// Get taskbar position via SHAppBarMessage
pub fn get_taskbar_rect(taskbar_hwnd: HWND) -> Option<RECT> {
unsafe {
Expand Down
117 changes: 90 additions & 27 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const IDM_LANG_SIMPLIFIED_CHINESE: u16 = 51;
const IDM_MODEL_CLAUDE_CODE: u16 = 60;
const IDM_MODEL_CODEX: u16 = 61;
const IDM_MODEL_ANTIGRAVITY: u16 = 62;
const IDM_TASKBAR_BASE: u16 = 70;

const WM_DPICHANGED_MSG: u32 = 0x02E0;
const WM_APP_UPDATE_CHECK_COMPLETE: u32 = WM_APP + 2;
Expand Down Expand Up @@ -524,11 +525,11 @@ fn attach_to_taskbar(hwnd: HWND, requested_index: usize) -> bool {

native_interop::embed_in_taskbar(hwnd, taskbar.hwnd);

let tray_notify = native_interop::find_child_window(taskbar.hwnd, "TrayNotifyWnd");
let tray_notify = native_interop::find_taskbar_tray_or_clock_window(taskbar.hwnd);
if tray_notify.is_some() {
diagnose::log("TrayNotifyWnd found");
diagnose::log("tray/clock control found");
} else {
diagnose::log("TrayNotifyWnd not found");
diagnose::log("tray/clock control not found");
}

let hook = tray_notify.and_then(|tray_hwnd| {
Expand Down Expand Up @@ -565,13 +566,8 @@ fn taskbar_at_point(pt: POINT) -> Option<(usize, native_interop::TaskbarWindow)>
}

fn tray_left_for_taskbar(taskbar_hwnd: HWND, taskbar_rect: RECT) -> i32 {
let mut tray_left = taskbar_rect.right;
if let Some(tray_hwnd) = native_interop::find_child_window(taskbar_hwnd, "TrayNotifyWnd") {
if let Some(tray_rect) = native_interop::get_window_rect_safe(tray_hwnd) {
tray_left = tray_rect.left;
}
}
tray_left
native_interop::find_taskbar_right_controls_left(taskbar_hwnd, taskbar_rect)
.unwrap_or(taskbar_rect.right)
}

fn clamp_offset_for_taskbar(taskbar_hwnd: HWND, taskbar_rect: RECT, offset: i32) -> i32 {
Expand Down Expand Up @@ -2097,16 +2093,10 @@ fn position_at_taskbar() {
};

let taskbar_height = taskbar_rect.bottom - taskbar_rect.top;
let mut tray_left = taskbar_rect.right;
let tray_left = tray_left_for_taskbar(taskbar_hwnd, taskbar_rect);
let anchor_top = taskbar_rect.top;
let anchor_height = taskbar_height;

if let Some(tray_hwnd) = native_interop::find_child_window(taskbar_hwnd, "TrayNotifyWnd") {
if let Some(tray_rect) = native_interop::get_window_rect_safe(tray_hwnd) {
tray_left = tray_rect.left;
}
}

let widget_width = total_widget_width();
let max_offset = (tray_left - taskbar_rect.left - widget_width).max(0);
let tray_offset = tray_offset.clamp(0, max_offset);
Expand Down Expand Up @@ -2368,6 +2358,38 @@ unsafe extern "system" fn wnd_proc(
if is_dragging {
let mut pt = POINT::default();
let _ = GetCursorPos(&mut pt);

let target_switch = {
let state = lock_state();
state.as_ref().and_then(|s| {
if let Some((target_index, _)) = taskbar_at_point(pt) {
if target_index != s.taskbar_index {
Some(target_index)
} else {
None
}
} else {
None
}
})
};

if let Some(target_index) = target_switch {
let mut state = lock_state();
if let Some(s) = state.as_mut() {
s.taskbar_index = target_index;
s.drag_start_mouse_x = pt.x;
s.drag_start_offset = 0;
s.tray_offset = 0;
}
drop(state);
if attach_to_taskbar(hwnd, target_index) {
position_at_taskbar();
render_layered();
}
return LRESULT(0);
}

let move_target = {
let mut state = lock_state();
let s = match state.as_mut() {
Expand All @@ -2391,16 +2413,7 @@ unsafe extern "system" fn wnd_proc(
// Clamp: don't go past left edge of taskbar
if let Some(taskbar_hwnd) = taskbar_hwnd {
if let Some(taskbar_rect) = native_interop::get_taskbar_rect(taskbar_hwnd) {
let mut tray_left = taskbar_rect.right;
if let Some(tray_hwnd) =
native_interop::find_child_window(taskbar_hwnd, "TrayNotifyWnd")
{
if let Some(tray_rect) =
native_interop::get_window_rect_safe(tray_hwnd)
{
tray_left = tray_rect.left;
}
}
let tray_left = tray_left_for_taskbar(taskbar_hwnd, taskbar_rect);
let widget_width = total_widget_width_for_state(s);
let max_offset = (tray_left - taskbar_rect.left - widget_width).max(0);
if new_offset > max_offset {
Expand Down Expand Up @@ -2577,6 +2590,21 @@ unsafe extern "system" fn wnd_proc(
IDM_START_WITH_WINDOWS => {
set_startup_enabled(!is_startup_enabled());
}
id if id >= IDM_TASKBAR_BASE && id < IDM_TASKBAR_BASE + 10 => {
let target_index = (id - IDM_TASKBAR_BASE) as usize;
{
let mut state = lock_state();
if let Some(s) = state.as_mut() {
s.taskbar_index = target_index;
s.tray_offset = 0;
}
}
if attach_to_taskbar(hwnd, target_index) {
position_at_taskbar();
render_layered();
}
save_state_settings();
}
IDM_FREQ_1MIN | IDM_FREQ_5MIN | IDM_FREQ_15MIN | IDM_FREQ_1HOUR => {
let new_interval = match id {
IDM_FREQ_1MIN => POLL_1_MIN,
Expand Down Expand Up @@ -2862,6 +2890,41 @@ fn show_context_menu(hwnd: HWND) {
PCWSTR::from_raw(reset_pos_str.as_ptr()),
);

let taskbars = native_interop::find_taskbars();
if taskbars.len() > 1 {
let screen_menu = CreatePopupMenu().unwrap();
let current_index = {
let state = lock_state();
state.as_ref().map(|s| s.taskbar_index).unwrap_or(0)
};
for (idx, _) in taskbars.iter().enumerate() {
let label = if idx == 0 {
"Screen 1 (Primary)".to_string()
} else {
format!("Screen {}", idx + 1)
};
let label_str = native_interop::wide_str(&label);
let flags = if idx == current_index {
MF_CHECKED
} else {
MENU_ITEM_FLAGS(0)
};
let _ = AppendMenuW(
screen_menu,
flags,
(IDM_TASKBAR_BASE + idx as u16) as usize,
PCWSTR::from_raw(label_str.as_ptr()),
);
}
let screen_label = native_interop::wide_str("Screen / Taskbar");
let _ = AppendMenuW(
settings_menu,
MF_POPUP,
screen_menu.0 as usize,
PCWSTR::from_raw(screen_label.as_ptr()),
);
}

let language_menu = CreatePopupMenu().unwrap();
let system_label = native_interop::wide_str(strings.system_default);
let system_flags = if language_override.is_none() {
Expand Down