Skip to content
Merged
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
10 changes: 8 additions & 2 deletions docs/point-scan.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Point scan ports the Android line-only and grid-then-line techniques to Switchif

Open **Settings → Switches** to add named keyboard switches and assign normal and hold actions. There is no on/off control: scanning is armed whenever the saved switches cover the current mode (Select for automatic scanning; Select, Next and Previous for manual) and the environment allows it. The runtime re-arms after a save, after key learning, after Escape, when a mobile session ends, and retries a failed key reservation every two seconds. Fresh installs have no assignments; old point-scan keys migrate once. See [switch assignments](switches.md). Scanning arms at startup, so assigned keys are reserved from launch.

Focus the intended application and press Select to start. Line mode chooses X, then Y, and opens an action menu. Grid mode chooses a row, then a cell, before the same line sequence. Selecting happens on switch release; holding any switch freezes the position and repeat keydowns do not select again. The scan resets after a menu click or completed drag and waits for the next Select, unless After a selection is set to Keep scanning for point scanning; the next scan then starts by itself. See [After a selection](scanner-architecture.md#after-a-selection). Movement wraps at the selected region's edges. Automatic movement stops after three full passes of the current phase without a selection; the scan resets and waits for the next Select. Manual steps never trigger this limit, and each Select starts a fresh count for the next phase. Android's five speeds are 45, 75, 120, 180, and 270 logical units per second, with delayed ticks capped at 250 ms.
Press Select to open Home and choose Point (see [Home](#home)). Line mode chooses X, then Y, and opens an action menu. Grid mode chooses a row, then a cell, before the same line sequence. Selecting happens on switch release; holding any switch freezes the position and repeat keydowns do not select again. The scan resets after a menu click or completed drag and waits for the next Select, unless After a selection is set to Keep scanning for point scanning; the next scan then starts by itself. See [After a selection](scanner-architecture.md#after-a-selection). Movement wraps at the selected region's edges. Automatic movement stops after three full passes of the current phase without a selection; the scan resets and waits for the next Select. Manual steps never trigger this limit, and each Select starts a fresh count for the next phase. Android's five speeds are 45, 75, 120, 180, and 270 logical units per second, with delayed ticks capped at 250 ms.

The scan uses the monitor under the pointer when it starts. Windows uses native physical coordinates and display scaling; macOS uses Core Graphics display units and converts overlay rectangles to AppKit coordinates. A monitor geometry change cancels scanning. Native overlay strips are topmost, click-through, and nonactivating. The pointer moves only when a menu action executes.

Expand Down Expand Up @@ -60,4 +60,10 @@ Start forwarding, then press Select to begin. Local switch keys are inactive dur

Cancelled edges, missing edges and sync mismatches never select. Held-state mismatches cancel the gesture and require neutral input. Sessions expire after five seconds without an authenticated edge or sync. Disconnects and safety stops discard the point and release drag buttons; start explicitly again. Ordinary forwarding and remote scanning cannot own input simultaneously. Local scanning resumes after Remote disconnects.

Every scanning-menu page ends with a navigation row containing Close menu. Subpages retain Back, the main page retains New point, and drag confirmation retains Cancel drag. Close menu ends the current scan and clears its points, pending selection and parent-page history without issuing desktop input. Select starts a fresh scan afterward. Pages use up to three columns and four rows; native layout scales the entire menu to fit the display. Pause/resume and emergency-stop behavior are unchanged.
## Home

Select opens a scanned Home menu, centred on the display under the pointer, when scanning starts afresh: the first Select, or the next one after Close menu, Stop scanning, Escape, or a scan that reached its pass limit without a selection. After a click, completed drag or other action ends a scan, the next Select carries on in the same mode instead, so routine clicking costs no extra steps. The "Select starts" setting can choose Last mode used, which never opens Home on Select.

Home offers Point, Mouse, Keyboard, Apps and windows, Editing, Browser, Media, Switchify and Close menu. Every Home action works without a chosen point, so Home ignores which window is in front: Apps and windows can bring another app forward with switches, and nobody needs to focus an app before scanning. A command chosen in Home, or in a page opened from it, returns to Home. The keyboard opened from Home returns there when closed. Switchify shows the settings window and ends the scan. A point chosen after leaving Home keeps the usual foreground check. Home is also a tile in the Point action menu and Mouse Actions.

Every scanning-menu page ends with a navigation row containing Close menu. Subpages retain Back, the main page retains New point, drag confirmation retains Cancel drag, and Home, which has nothing to go back to, ends with Close menu. Close menu ends the current scan and clears its points, pending selection and parent-page history without issuing desktop input. Select starts a fresh scan afterward. Pages use up to three columns and four rows; native layout scales the entire menu to fit the display. Pause/resume and emergency-stop behavior are unchanged.
2 changes: 1 addition & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl PendingNavigation {
}
}

fn show_main_window(app: &AppHandle, destination: Option<&str>) {
pub(crate) fn show_main_window(app: &AppHandle, destination: Option<&str>) {
if let Some(window) = app.get_webview_window("main") {
let _ = window.show();
let _ = window.unminimize();
Expand Down
29 changes: 29 additions & 0 deletions src-tauri/src/point_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ pub enum ControlMode {
Point,
Mouse,
}
/// What Select opens when scanning starts afresh rather than continuing after an action.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum StartWith {
#[default]
Home,
LastMode,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum KeyboardLayout {
Expand Down Expand Up @@ -47,6 +55,7 @@ impl MouseRepeatStopEdge {
#[serde(rename_all = "camelCase", default)]
pub struct Config {
pub control_mode: ControlMode,
pub start_with: StartWith,
pub mouse_repeat_stop_edge: MouseRepeatStopEdge,
#[serde(deserialize_with = "crate::scan_preferences::deserialize_preferences")]
pub scan_preferences: crate::scan_preferences::Preferences,
Expand Down Expand Up @@ -75,6 +84,7 @@ impl Default for Config {
fn default() -> Self {
Self {
control_mode: ControlMode::Point,
start_with: StartWith::Home,
mouse_repeat_stop_edge: MouseRepeatStopEdge::Release,
scan_preferences: Default::default(),
word_prediction: true,
Expand Down Expand Up @@ -146,6 +156,7 @@ impl Config {
pub fn point(&self) -> PointSettings {
PointSettings {
control_mode: self.control_mode,
start_with: self.start_with,
mouse_repeat_stop_edge: self.mouse_repeat_stop_edge,
scan: self.resolved(crate::scan_preferences::Area::Point),
menu_scan: self.resolved(crate::scan_preferences::Area::Menu),
Expand Down Expand Up @@ -173,6 +184,7 @@ impl Config {
#[derive(Clone)]
pub struct PointSettings {
pub control_mode: ControlMode,
pub start_with: StartWith,
pub mouse_repeat_stop_edge: MouseRepeatStopEdge,
pub scan: crate::scan_preferences::Resolved,
pub menu_scan: crate::scan_preferences::Resolved,
Expand Down Expand Up @@ -1141,6 +1153,22 @@ mod tests {
assert!(e.technique.x > -1000.0);
}
#[test]
fn saved_settings_without_a_start_choice_open_home_and_the_choice_round_trips() {
let config: Config =
serde_json::from_value(serde_json::json!({"controlMode":"mouse"})).unwrap();
assert_eq!(config.start_with, StartWith::Home);
assert_eq!(config.control_mode, ControlMode::Mouse);
let config: Config =
serde_json::from_value(serde_json::json!({"startWith":"lastMode"})).unwrap();
assert_eq!(config.start_with, StartWith::LastMode);
assert_eq!(config.point().start_with, StartWith::LastMode);
assert_eq!(
serde_json::to_value(&config).unwrap()["startWith"],
serde_json::json!("lastMode")
);
assert!(serde_json::from_value::<Config>(serde_json::json!({"startWith":"menu"})).is_err());
}
#[test]
fn existing_flat_settings_gain_safe_auto_select_defaults() {
let mut json = serde_json::json!({"mode":"grid","automatic":false,"speed":4,"gridSize":7,"blockIntervalMs":1500,"selectKey":"F1","nextKey":"F2","backKey":"F3","pauseKey":"F4"});
let config: Config = serde_json::from_value(json.clone()).unwrap();
Expand All @@ -1153,6 +1181,7 @@ mod tests {
json["autoSelectDelayMs"] = serde_json::json!(1000);
json["scannerColor"] = serde_json::json!("blue");
json["controlMode"] = serde_json::json!("point");
json["startWith"] = serde_json::json!("home");
json["mouseRepeatStopEdge"] = serde_json::json!("release");
json["wordPrediction"] = serde_json::json!(true);
json["keyboardLayout"] = serde_json::json!("qwerty");
Expand Down
9 changes: 9 additions & 0 deletions src-tauri/src/point_scan_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ impl Adapter for PointScan {
config.control_mode = next;
true
}
fn carry_over(previous: &Workflow, next: &mut Workflow) {
next.resume_from(previous);
}
fn keep_runtime_config(next: &mut Config, current: &Config) {
next.control_mode = current.control_mode;
// Layout changes can also come from the scanned keyboard. Settings
Expand Down Expand Up @@ -187,6 +190,12 @@ impl Adapter for PointScan {
crate::prediction::close();
crate::scan_executor::activate(request)
}
Request::OpenSwitchify => {
crate::prediction::close();
crate::scan_executor::activate(request)?;
crate::show_main_window(app, None);
return Ok(None);
}
Request::MouseDrag => {
let (position, _) = display_navigation::displays(app).map_err(|e| e.message)?;
return crate::scan_executor::toggle_mouse_drag((
Expand Down
Loading
Loading