// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package atspi import ( "math" "git.stormux.org/storm/cthulhu/internal/platform/xorg" ) // Epoch changes with the foreground client, including departure to an empty // workspace. Moving/resizing the same client does not reset reading ownership. type ForegroundState struct { Client xorg.Foreground Epoch uint64 } func foregroundWindowMatches(client xorg.Foreground, pid int, bounds Rectangle) bool { if client.PID > 0 && pid > 0 && client.PID != pid { return false } if !bounds.Valid() || client.Width <= 0 || client.Height <= 0 { return false } inner := Rectangle{X: int32(client.X), Y: int32(client.Y), Width: int32(client.Width), Height: int32(client.Height)} outer := Rectangle{X: int32(client.OuterX), Y: int32(client.OuterY), Width: int32(client.OuterWidth), Height: int32(client.OuterHeight)} return bounds == inner || bounds == outer || scaledWindowMatches(bounds, inner) || scaledWindowMatches(bounds, outer) } // Some Chromium windows report AT-SPI extents in logical pixels while X11 // reports physical pixels. Require the same origin and proportional dimensions // so a smaller dialog or unrelated sibling cannot claim the foreground client. func scaledWindowMatches(accessible, xWindow Rectangle) bool { if !accessible.Valid() || !xWindow.Valid() || accessible.X != xWindow.X || accessible.Y != xWindow.Y { return false } scaleX := float64(xWindow.Width) / float64(accessible.Width) scaleY := float64(xWindow.Height) / float64(accessible.Height) if scaleX < 0.5 || scaleX > 3 || scaleY < 0.5 || scaleY > 3 { return false } return math.Abs(scaleX-scaleY) <= 0.01*math.Max(scaleX, scaleY) } // A menu popup remains within the foreground application's AT-SPI bus even // when its own accessible frame does not match the X11 foreground client's // bounds. The caller must confirm an active menu window or a focused menu item. func foregroundMenuPopupMatches(client xorg.Foreground, foregroundWindow, popupWindow ObjectID, pid int, menuContext bool) bool { return menuContext && client.Window != 0 && foregroundWindow.Valid() && popupWindow.Valid() && foregroundWindow != popupWindow && foregroundWindow.BusName == popupWindow.BusName && (client.PID <= 0 || pid <= 0 || client.PID == pid) }