use anyhow::Result;
use serde::Serialize;
use crate::session::SUPPORTED_SCHEMA;
pub const MIN_PIXELCOORDS: &str = "0.7.0";
fn parts(version: &str) -> Option<(u32, u32, u32)> {
let mut fields = version.trim().split('.');
let major = fields.next()?.parse().ok()?;
let minor = fields.next()?.parse().ok()?;
let patch = fields.next()?.split(['-', '+']).next()?.parse().ok()?;
if fields.next().is_some() {
return None;
}
Some((major, minor, patch))
}
pub fn meets_minimum(found: &str) -> bool {
let (Some(found), Some(needed)) = (parts(found), parts(MIN_PIXELCOORDS)) else {
return false;
};
found >= needed
}
pub fn require_supported_pixelcoords() -> Result<(), String> {
let status = pixelcoords_status();
if !status.found {
return Err(
"pixelcoords is not on PATH — it is what relocates and verifies regions. \
Install it with `cargo install pixelcoords`"
.to_string(),
);
}
let Some(version) = status.version else {
return Err(
"could not read `pixelcoords --version`, so its version cannot be trusted. \
Reinstall with `cargo install pixelcoords`"
.to_string(),
);
};
if !meets_minimum(&version) {
return Err(format!(
"pixelcoords {version} is too old — this build needs {MIN_PIXELCOORDS} or newer. \
Older captures composite the mouse pointer into the image, which makes \
relocation unreliable in a way that looks like flakiness. \
Upgrade with `cargo install pixelcoords`"
));
}
Ok(())
}
#[derive(Debug, Serialize)]
struct Report {
schema: u32,
platform: &'static str,
supported_platform: bool,
native_space: pixelactions_core::convert::Space,
session_schema_supported: u32,
pixelcoords: PixelcoordsStatus,
capabilities: Capabilities,
accessibility_trusted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
linux: Option<LinuxStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
windows: Option<WindowsStatus>,
probe: Probe,
}
#[derive(Debug, Serialize)]
struct WindowsStatus {
dpi_aware_v2: bool,
virtual_desktop: pixelactions_core::virtualdesk::VirtualDesktop,
#[serde(skip_serializing_if = "Option::is_none")]
elevated: Option<bool>,
}
#[derive(Debug, Serialize)]
struct LinuxStatus {
server: pixelactions_core::display::Server,
rung: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
display: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
connected: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
portal_remote_desktop_version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
portal_screen_cast_version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
portal_device_types: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
cursor_metadata_available: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
grant_remembered: Option<bool>,
}
#[derive(Debug, Serialize)]
struct PixelcoordsStatus {
found: bool,
version: Option<String>,
minimum: &'static str,
}
#[derive(Debug, Serialize)]
struct Capabilities {
resolve: bool,
inject: bool,
verify: bool,
}
#[derive(Debug, Serialize)]
struct Probe {
attempted: bool,
moved: bool,
confirmed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
detail: Option<String>,
}
fn pixelcoords_status() -> PixelcoordsStatus {
let output = std::process::Command::new("pixelcoords")
.arg("--version")
.output();
let Ok(output) = output else {
return PixelcoordsStatus {
found: false,
version: None,
minimum: MIN_PIXELCOORDS,
};
};
let text = String::from_utf8_lossy(&output.stdout);
let version = text.split_whitespace().nth(1).map(str::to_string);
PixelcoordsStatus {
found: true,
version,
minimum: MIN_PIXELCOORDS,
}
}
pub fn run(json: bool, probe: bool) -> Result<i32> {
let probe_result = run_probe(probe);
let can_inject = crate::inject::availability();
let report = Report {
schema: 1,
platform: std::env::consts::OS,
supported_platform: can_inject.is_ok(),
native_space: pixelactions_core::convert::native_space(),
session_schema_supported: SUPPORTED_SCHEMA,
pixelcoords: pixelcoords_status(),
capabilities: Capabilities {
resolve: true,
inject: can_inject.is_ok(),
verify: true,
},
accessibility_trusted: trusted(),
linux: linux_status(),
windows: windows_status(),
probe: probe_result,
};
let refusal = can_inject.err();
if json {
println!("{}", serde_json::to_string_pretty(&report)?);
return Ok(0);
}
println!("platform: {}", report.platform);
println!(
"supported: {}",
if report.supported_platform {
"yes".to_string()
} else {
format!("no — {}", refusal.as_deref().unwrap_or("unsupported"))
}
);
if let Some(linux) = &report.linux {
print_linux(linux);
}
if let Some(windows) = &report.windows {
print_windows(windows);
}
println!("native space: {:?}", report.native_space);
println!(
"session schema: {} and older",
report.session_schema_supported
);
match (&report.pixelcoords.found, &report.pixelcoords.version) {
(true, Some(version)) => {
let verdict = if meets_minimum(version) {
"ok"
} else {
"TOO OLD"
};
println!("pixelcoords: {version} (minimum {MIN_PIXELCOORDS}) — {verdict}");
}
(true, None) => println!("pixelcoords: found, version unreadable"),
(false, _) => println!("pixelcoords: not on PATH — needed to relocate and verify"),
}
println!();
println!("capabilities:");
println!(" resolve a plan yes");
println!(" inject input {}", inject_line(&report));
println!(" verify a step yes — via pixelcoords find");
if report.probe.attempted {
println!();
match (
report.probe.moved,
report.probe.confirmed,
&report.probe.detail,
) {
(true, true, _) => {
println!("probe: the cursor moved, and the OS confirmed where it went");
}
(true, false, detail) => {
println!("probe: input was granted and accepted, NOT confirmed");
if let Some(detail) = detail {
println!(" {detail}");
}
}
(false, _, Some(detail)) => println!("probe: FAILED\n {detail}"),
(false, _, None) => println!("probe: failed, no detail"),
}
}
if report.probe.attempted && !report.probe.moved {
return Ok(3);
}
Ok(0)
}
fn inject_line(report: &Report) -> String {
if !report.capabilities.inject {
return "no".to_string();
}
if let Some(windows) = &report.windows {
return format!("yes — {}", windows_grant_cost(windows));
}
let Some(linux) = &report.linux else {
return "yes — needs macOS Accessibility permission".to_string();
};
if linux.rung == "none" {
return "no — this session has no input path".to_string();
}
format!("yes — via {}, {}", linux.rung, grant_cost(linux))
}
fn grant_cost(linux: &LinuxStatus) -> &'static str {
match (linux.server, linux.grant_remembered) {
(pixelactions_core::display::Server::X11, _) => "which asks nothing of you",
(_, Some(true)) => "using a remembered screen-share grant",
_ => "using a screen-share grant you approve once",
}
}
fn display_line(linux: &LinuxStatus) -> Option<String> {
let verdict = if linux.connected? {
"connected"
} else {
"no answer"
};
let display = linux.display.as_deref().unwrap_or("(unset)");
Some(format!("{display} — {verdict}"))
}
fn portal_line(linux: &LinuxStatus) -> Option<String> {
Some(format!(
"RemoteDesktop v{} · ScreenCast v{} · devices {:#b}",
linux.portal_remote_desktop_version?,
linux.portal_screen_cast_version.unwrap_or(0),
linux.portal_device_types.unwrap_or(0)
))
}
fn grant_line(linux: &LinuxStatus) -> &'static str {
use pixelactions_core::display::Server;
match (linux.server, linux.grant_remembered) {
(Server::X11, _) => {
"none needed — any X client may inject into any other, which is the hole Wayland closes"
}
(_, Some(true)) => "remembered — no dialog expected",
(_, Some(false)) => "not yet given — the first run will ask",
(_, None) => "nothing to grant — this session has no input path",
}
}
fn kill_switch_line(linux: &LinuxStatus) -> &'static str {
use pixelactions_core::display::Server;
match (linux.server, linux.cursor_metadata_available) {
(Server::X11, _) => "armed — X11 reports the pointer position, so the corner check works",
(Server::Wayland, Some(true)) => {
"no eyes on Wayland in this build (the compositor could provide them)"
}
(Server::Wayland, _) => "no eyes on Wayland, and this compositor offers no cursor metadata",
(Server::Unknown, _) => "nothing to watch — no session was found",
}
}
fn windows_grant_cost(windows: &WindowsStatus) -> &'static str {
match windows.elevated {
Some(true) => {
"nothing to grant, and this process is elevated, so only the secure desktop \
(UAC, the login screen) is out of reach"
}
Some(false) => {
"nothing to grant, but UIPI puts elevated windows out of reach — this process \
is not elevated"
}
None => "nothing to grant; UIPI puts elevated windows out of reach",
}
}
fn dpi_line(windows: &WindowsStatus) -> &'static str {
if windows.dpi_aware_v2 {
return "per-monitor v2 — scale factors are read per display, so mixed 100%/150% \
layouts resolve correctly";
}
"NOT per-monitor v2 — something overrode it, and Windows is virtualizing every \
coordinate against the primary monitor's scale. Clicks on a scaled display will be \
wrong. Check for an app-compatibility override on your terminal"
}
fn desktop_line(windows: &WindowsStatus) -> String {
let desk = windows.virtual_desktop;
format!(
"{} × {} from ({}, {}) — every monitor, via MOUSEEVENTF_VIRTUALDESK",
desk.width, desk.height, desk.x, desk.y
)
}
fn uipi_line(windows: &WindowsStatus) -> &'static str {
match windows.elevated {
Some(true) => {
"elevated — this process can drive elevated windows. The UAC dialog and the \
login screen still cannot be reached by anything"
}
Some(false) => {
"not elevated — input to an elevated window will be dropped by Windows, not by \
this tool. Run the target unelevated, or run this elevated too"
}
None => "unknown — this process's token could not be read",
}
}
fn print_windows(windows: &WindowsStatus) {
println!("dpi awareness: {}", dpi_line(windows));
println!("virtual desktop: {}", desktop_line(windows));
println!("elevation: {}", uipi_line(windows));
println!(
"kill switch: armed — Windows reports the pointer position, so the corner check works"
);
}
fn print_linux(linux: &LinuxStatus) {
println!("session: {}", linux.server.name());
println!("input path: {}", linux.rung);
if let Some(display) = display_line(linux) {
println!("display: {display}");
}
if let Some(portal) = portal_line(linux) {
println!("portal: {portal}");
}
println!("grant: {}", grant_line(linux));
println!("kill switch: {}", kill_switch_line(linux));
}
#[cfg(any(target_os = "linux", test))]
fn no_input_path(server: pixelactions_core::display::Server) -> LinuxStatus {
LinuxStatus {
server,
rung: "none",
display: None,
connected: None,
portal_remote_desktop_version: None,
portal_screen_cast_version: None,
portal_device_types: None,
cursor_metadata_available: None,
grant_remembered: None,
}
}
#[cfg(target_os = "linux")]
fn linux_status() -> Option<LinuxStatus> {
use pixelactions_core::display::Server;
let server = crate::inject::session_server();
let status = match server {
Server::X11 => x11_status(),
Server::Wayland => wayland_status(),
Server::Unknown => no_input_path(server),
};
Some(status)
}
#[cfg(target_os = "linux")]
fn x11_status() -> LinuxStatus {
use pixelactions_core::display::Server;
let connected = crate::inject::X11Injector::new().is_ok();
LinuxStatus {
rung: if connected {
"XTEST on the root window"
} else {
"none"
},
display: std::env::var("DISPLAY")
.ok()
.filter(|value| !value.trim().is_empty()),
connected: Some(connected),
..no_input_path(Server::X11)
}
}
#[cfg(target_os = "linux")]
fn wayland_status() -> LinuxStatus {
use pixelactions_core::display::Server;
let Ok(portal) = crate::portal::capabilities() else {
return no_input_path(Server::Wayland);
};
LinuxStatus {
rung: if portal.usable() {
"portal RemoteDesktop + EIS"
} else {
"none"
},
portal_remote_desktop_version: Some(portal.remote_desktop_version),
portal_screen_cast_version: Some(portal.screen_cast_version),
portal_device_types: Some(portal.device_types),
cursor_metadata_available: Some(portal.cursor_metadata()),
grant_remembered: Some(portal.have_stored_token),
..no_input_path(Server::Wayland)
}
}
#[cfg(not(target_os = "linux"))]
fn linux_status() -> Option<LinuxStatus> {
None
}
#[cfg(target_os = "windows")]
fn windows_status() -> Option<WindowsStatus> {
Some(WindowsStatus {
dpi_aware_v2: crate::win::is_per_monitor_aware_v2(),
virtual_desktop: crate::win::virtual_desktop(),
elevated: crate::win::is_elevated(),
})
}
#[cfg(not(target_os = "windows"))]
fn windows_status() -> Option<WindowsStatus> {
None
}
fn trusted() -> Option<bool> {
#[cfg(target_os = "macos")]
{
Some(crate::mac::is_trusted())
}
#[cfg(not(target_os = "macos"))]
{
None
}
}
#[cfg(target_os = "macos")]
fn run_probe(requested: bool) -> Probe {
if !requested {
return Probe {
attempted: false,
moved: false,
confirmed: false,
detail: None,
};
}
if !crate::mac::is_trusted() {
crate::mac::request_trust();
return Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(
"Accessibility is not granted to the application running pixelactions, \
so synthetic events would be discarded silently. A system dialog was \
just requested — approve it, or add the app under System Settings > \
Privacy & Security > Accessibility, then quit and reopen it and run \
this again. The grant attaches to the app you launched from (your \
terminal), not to the pixelactions binary."
.to_string(),
),
};
}
let outcome = crate::inject::RealInjector::new().and_then(|mut injector| {
use crate::inject::Injector;
injector.probe()
});
match outcome {
Ok(()) => Probe {
attempted: true,
moved: true,
confirmed: true,
detail: None,
},
Err(error) => Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(format!("{error:#}")),
},
}
}
#[cfg(target_os = "linux")]
fn run_probe(requested: bool) -> Probe {
use pixelactions_core::display::Server;
if !requested {
return Probe {
attempted: false,
moved: false,
confirmed: false,
detail: None,
};
}
if let Err(reason) = crate::inject::availability() {
return Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(reason),
};
}
match crate::inject::session_server() {
Server::X11 => probe_x11(),
_ => probe_wayland(),
}
}
#[cfg(target_os = "linux")]
fn probe_x11() -> Probe {
let outcome = crate::inject::X11Injector::new().and_then(|mut injector| {
use crate::inject::Injector;
injector.probe()
});
match outcome {
Ok(()) => Probe {
attempted: true,
moved: true,
confirmed: true,
detail: None,
},
Err(error) => Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(format!("{error:#}")),
},
}
}
#[cfg(target_os = "linux")]
fn probe_wayland() -> Probe {
let outcome = crate::inject::WaylandInjector::new(&[]).and_then(|mut injector| {
use crate::inject::Injector;
injector.probe().map(|()| {
let regions = injector.regions().len();
let typing = injector.can_type();
(regions, typing)
})
});
match outcome {
Ok((regions, typing)) => Probe {
attempted: true,
moved: true,
confirmed: false,
detail: Some(format!(
"the compositor granted input and described {regions} region(s); typing is \
{}. Whether the pointer moved cannot be checked — Wayland exposes no way \
to ask where it is, which is also why the corner kill switch has nothing \
to watch here",
if typing {
"available"
} else {
"unavailable (no keymap was sent)"
}
)),
},
Err(error) => Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(format!("{error:#}")),
},
}
}
#[cfg(target_os = "windows")]
fn run_probe(requested: bool) -> Probe {
if !requested {
return Probe {
attempted: false,
moved: false,
confirmed: false,
detail: None,
};
}
let outcome = crate::inject::WindowsInjector::new().and_then(|mut injector| {
use crate::inject::Injector;
injector.probe()
});
match outcome {
Ok(()) => Probe {
attempted: true,
moved: true,
confirmed: true,
detail: None,
},
Err(error) => Probe {
attempted: true,
moved: false,
confirmed: false,
detail: Some(format!("{error:#}")),
},
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
fn run_probe(requested: bool) -> Probe {
Probe {
attempted: requested,
moved: false,
confirmed: false,
detail: requested
.then(|| "input synthesis is not implemented for this platform yet".to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_declared_minimum_is_itself_readable() {
assert!(parts(MIN_PIXELCOORDS).is_some(), "{MIN_PIXELCOORDS}");
}
#[test]
fn newer_and_equal_versions_are_accepted() {
assert!(meets_minimum(MIN_PIXELCOORDS));
assert!(meets_minimum("0.7.1"));
assert!(meets_minimum("0.8.0"));
assert!(meets_minimum("1.0.0"));
assert!(meets_minimum("0.7.0-rc1"));
}
#[test]
fn older_versions_are_refused() {
assert!(!meets_minimum("0.6.0"));
assert!(!meets_minimum("0.5.3"));
assert!(!meets_minimum("0.1.2"));
assert!(!meets_minimum("0.0.9"));
}
#[test]
fn an_unreadable_version_is_refused_rather_than_assumed_good() {
for bad in ["", "0.1", "0.1.2.3", "banana", "v0.1.2", "0.x.2"] {
assert!(!meets_minimum(bad), "should refuse {bad:?}");
}
}
use pixelactions_core::display::Server;
fn x11(connected: bool) -> LinuxStatus {
LinuxStatus {
rung: if connected {
"XTEST on the root window"
} else {
"none"
},
display: Some(":0".to_string()),
connected: Some(connected),
..no_input_path(Server::X11)
}
}
fn windows(dpi_aware_v2: bool, elevated: Option<bool>) -> WindowsStatus {
WindowsStatus {
dpi_aware_v2,
virtual_desktop: pixelactions_core::virtualdesk::VirtualDesktop {
x: -1920,
y: 0,
width: 3840,
height: 1080,
},
elevated,
}
}
fn wayland() -> LinuxStatus {
LinuxStatus {
rung: "portal RemoteDesktop + EIS",
portal_remote_desktop_version: Some(2),
portal_screen_cast_version: Some(5),
portal_device_types: Some(0b111),
cursor_metadata_available: Some(true),
grant_remembered: Some(true),
..no_input_path(Server::Wayland)
}
}
#[test]
fn an_x11_session_is_never_described_as_a_wayland_one() {
let linux = x11(true);
assert_eq!(display_line(&linux).as_deref(), Some(":0 — connected"));
assert!(
portal_line(&linux).is_none(),
"nothing asked the portal on X11, so there is no version to print"
);
let grant = grant_line(&linux);
assert!(grant.contains("none needed"), "{grant}");
let kill = kill_switch_line(&linux);
assert!(kill.starts_with("armed"), "{kill}");
assert!(
!kill.contains("no eyes"),
"X11 can read the pointer: {kill}"
);
}
#[test]
fn the_kill_switch_line_disagrees_between_the_two_servers() {
assert_ne!(
kill_switch_line(&x11(true)),
kill_switch_line(&wayland()),
"the whole point of reporting it is that the answer differs"
);
assert!(kill_switch_line(&wayland()).contains("no eyes"));
assert!(
kill_switch_line(&no_input_path(Server::Unknown)).contains("no session"),
"a session with no path has nothing to watch either"
);
}
#[test]
fn an_x_display_that_did_not_answer_says_so() {
let line = display_line(&x11(false)).expect("X11 names its display");
assert!(line.contains("no answer"), "{line}");
}
#[test]
fn a_wayland_session_still_reports_its_portal_and_grant() {
let linux = wayland();
let portal = portal_line(&linux).expect("the portal answered");
assert!(portal.contains("RemoteDesktop v2"), "{portal}");
assert!(portal.contains("ScreenCast v5"), "{portal}");
assert!(
display_line(&linux).is_none(),
"Wayland has no X display to name"
);
assert!(grant_line(&linux).contains("remembered"));
}
#[test]
fn the_capability_line_names_what_each_path_costs() {
let report = |linux: Option<LinuxStatus>| Report {
schema: 1,
platform: "linux",
supported_platform: true,
native_space: pixelactions_core::convert::Space::Physical,
session_schema_supported: SUPPORTED_SCHEMA,
pixelcoords: pixelcoords_status(),
capabilities: Capabilities {
resolve: true,
inject: true,
verify: true,
},
accessibility_trusted: None,
linux,
windows: None,
probe: Probe {
attempted: false,
moved: false,
confirmed: false,
detail: None,
},
};
let x11_line = inject_line(&report(Some(x11(true))));
assert!(x11_line.contains("XTEST"), "{x11_line}");
assert!(x11_line.contains("asks nothing of you"), "{x11_line}");
let wayland_line = inject_line(&report(Some(wayland())));
assert!(wayland_line.contains("remembered"), "{wayland_line}");
let dead = inject_line(&report(Some(x11(false))));
assert!(!dead.contains("via none"), "{dead}");
assert!(dead.starts_with("no"), "{dead}");
let mac_line = inject_line(&report(None));
assert!(mac_line.contains("Accessibility"), "{mac_line}");
let mut on_windows = report(None);
on_windows.platform = "windows";
on_windows.windows = Some(windows(true, Some(false)));
let line = inject_line(&on_windows);
assert!(line.contains("nothing to grant"), "{line}");
assert!(line.contains("UIPI"), "{line}");
assert!(!line.contains("Accessibility"), "{line}");
}
#[test]
fn a_process_without_per_monitor_awareness_says_the_coordinates_are_wrong() {
let good = dpi_line(&windows(true, Some(false)));
assert!(good.contains("per-monitor v2"), "{good}");
assert!(good.contains("mixed"), "names the case it fixes: {good}");
let bad = dpi_line(&windows(false, Some(false)));
assert!(bad.contains("NOT per-monitor v2"), "{bad}");
assert!(
bad.contains("wrong"),
"an unaware process clicks the wrong place, and must say so: {bad}"
);
}
#[test]
fn the_virtual_desktop_line_shows_a_negative_origin_rather_than_hiding_it() {
let line = desktop_line(&windows(true, None));
assert!(line.contains("3840 × 1080"), "{line}");
assert!(line.contains("(-1920, 0)"), "{line}");
assert!(line.contains("VIRTUALDESK"), "{line}");
}
#[test]
fn the_elevation_line_tells_the_two_states_apart() {
let unelevated = uipi_line(&windows(true, Some(false)));
assert!(unelevated.contains("not elevated"), "{unelevated}");
assert!(
unelevated.contains("dropped by Windows, not by this tool"),
"the refusal is the OS's, and saying so is the point: {unelevated}"
);
let elevated = uipi_line(&windows(true, Some(true)));
assert_ne!(elevated, unelevated);
assert!(
elevated.contains("can drive elevated windows"),
"{elevated}"
);
assert!(
elevated.contains("login screen"),
"even elevated, the secure desktop is out of reach: {elevated}"
);
assert!(uipi_line(&windows(true, None)).contains("unknown"));
}
#[test]
fn windows_serializes_what_it_has_and_nothing_it_does_not() {
let json = serde_json::to_string(&windows(true, Some(false))).expect("serializes");
assert!(json.contains(r#""dpi_aware_v2":true"#), "{json}");
assert!(json.contains(r#""x":-1920"#), "{json}");
assert!(json.contains(r#""elevated":false"#), "{json}");
let json = serde_json::to_string(&windows(true, None)).expect("serializes");
assert!(!json.contains("elevated"), "{json}");
}
#[test]
fn absent_fields_are_omitted_rather_than_zeroed() {
let json = serde_json::to_string(&x11(true)).expect("serializes");
assert!(json.contains(r#""server":"x11""#), "{json}");
assert!(json.contains(r#""connected":true"#), "{json}");
assert!(!json.contains("portal_"), "{json}");
assert!(!json.contains("grant_remembered"), "{json}");
let json = serde_json::to_string(&wayland()).expect("serializes");
assert!(
json.contains(r#""portal_remote_desktop_version":2"#),
"{json}"
);
assert!(!json.contains("connected"), "{json}");
}
}