import asyncio
from playwright.async_api import async_playwright
class CDPSniper:
def __init__(self, port: int = 9222):
self.cdp_url = f"http://localhost:{port}"
self.playwright = None
self.browser = None
async def connect(self):
"""Avaa pysyvC$n yhteyden selaimeen suorituskyvyn maksimoimiseksi."""
if not self.playwright:
self.playwright = await async_playwright().start()
if not self.browser:
self.browser = await self.playwright.chromium.connect_over_cdp(self
.cdp_url)
async def disconnect(self):
"""Sulkee yhteyden."""
if self.browser:
await self.browser.close()
self.browser = None
if self.playwright:
await self.playwright.stop()
self.playwright = None
async def get_element_at_absolute_pos(self, x: int, y: int):
"""
Muuntaa X11 absoluuttiset koordinaatit selaimen viewport-koordinaateiks
i
ja palauttaa elementin tiedot.
"""
if not self.browser:
await self.connect()
# Oletetaan, ettC$ haluamme aina operoida pC$C$llimmC$isellC$ sivulla
# (Monimutkaisemmissa X11-setupissa voit iteroida browser.contexts)
context = self.browser.contexts[0]
page = context.pages[0]
# Injektoitava JavaScript-koodi
js_snippet = """
([absX, absY]) => {
// 1. Offsetin laskenta: Selaimen sijainti nC$ytC6llC$ +
UI-reunukset
// window.screenY kertoo selainikkunan ylC$reunan absoluuttisen
sijainnin
// window.outerHeight - innerHeight kertoo selainpalkin
(vC$lilehdet, url) koon
const uiTopOffset = window.outerHeight - window.innerHeight;
// Modernissa Chromessa sivureunukset ovat usein 0px tai
minimaalisia
const uiLeftOffset = window.outerWidth - window.innerWidth;
const viewportTopOnScreen = window.screenY + uiTopOffset;
// Jaetaan vasen offset (esim. varjo/reunus) kahdella
const viewportLeftOnScreen = window.screenX + (uiLeftOffset > 0 ?
(uiLeftOffset / 2) : 0);
// 2. Koordinaattimuunnos: Absoluuttinen -> Viewport-relatiivinen
const relX = absX - viewportLeftOnScreen;
const relY = absY - viewportTopOnScreen;
// Tarkistetaan osuuko klikkaus edes sivun sisC$lle
if (relX < 0 || relY < 0 || relX > window.innerWidth || relY >
window.innerHeight) {
return {
status: "miss",
reason: "Koordinaatti on viewportin ulkopuolella",
rel_coords: { x: relX, y: relY }
};
}
// 3. JS Hit-Testing
let el = document.elementFromPoint(relX, relY);
if (!el) {
return { status: "miss", reason: "Ei elementtiC$ tC$ssC$
pisteessC$", rel_coords: { x: relX, y: relY } };
}
// Jos elementti on iframe, elementFromPoint palauttaa iframen, ei
sen sisC$ltC6C$.
const isIframe = el.tagName.toLowerCase() === 'iframe';
// 4. Attribuuttien poiminta
// Puhdistetaan teksti rivinvaihdoista ja turhasta tyhjC$stC$
tilasta
const cleanText = (el.textContent || '').replace(/\\s+/g, '
').trim().substring(0, 150);
return {
status: "hit",
rel_coords: { x: Math.round(relX), y: Math.round(relY) },
element: {
tag: el.tagName.toLowerCase(),
role: el.getAttribute('role'),
test_id: el.getAttribute('data-testid') ||
el.getAttribute('data-som-id'),
aria_label: el.getAttribute('aria-label'),
text: cleanText,
id: el.id,
classes: el.className,
is_iframe: isIframe
}
};
}
"""
try:
# Suoritetaan koko logiikka suoraan selaimen sisC$llC$ yhdellC$
CDP-kutsulla
result = await page.evaluate(js_snippet, [x, y])
return result
except Exception as e:
return {"status": "error", "reason": str(e)}
# === Esimerkki X11-kuuntelijan integroinnista ===
if __name__ == "__main__":
async def main():
sniper = CDPSniper(port=9222)
# Simuloidaan X11-kuuntelijalta tuleva klikkaus
click_x11_x = 835
click_x11_y = 998
print(f"Ammutaan koordinaatteihin X:{click_x11_x}, Y:{click_x11_y}...")
result = await sniper.get_element_at_absolute_pos(click_x11_x,
click_x11_y)
import json
print(json.dumps(result, indent=2, ensure_ascii=False))
await sniper.disconnect()
asyncio.run(main())