Some progress on the overlay state machine

merge-notes
isark 2 years ago
parent 1936d33407
commit 9c514830bc

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/<executable file>",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

1119
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -13,7 +13,7 @@ edition = "2021"
tauri-build = { version = "1.2", features = [] }
[dependencies]
tauri = { version = "1.2", features = ["shell-open"] }
tauri = { version = "1.2", features = [ "api-all"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Underlayer = { git = "https://git.isark.me/isark/Underlay.git" }
@ -23,6 +23,8 @@ simple_logger = "4.2.0"
x11rb = "0.12.0"
raw-window-handle = "0.5.2"
statig = "0.3.0"
crossbeam = "0.8.2"
[features]

@ -3,9 +3,12 @@
use std::{sync::Mutex, time::Duration};
use crossbeam::channel::Sender;
use overlay::{EnforceableEvent, Overlay};
use log::Level;
use overlay::{Overlay, Manager};
use tauri::{LogicalSize, Manager, PhysicalPosition, PhysicalSize, Window};
use simple_logger::SimpleLogger;
// use overlay::{Overlay, Manager};
use tauri::{LogicalSize, Manager, PhysicalPosition, PhysicalSize, Window, StateManager};
use underlayer::Bounds;
use x11rb::protocol::xproto::ConnectionExt;
@ -17,139 +20,132 @@ fn greet(name: &str) -> String {
mod overlay;
fn show(window: &Window) {
window.set_always_on_top(true);
window.show();
window.set_resizable(true);
if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
if state.bounds.height == 0 || state.bounds.width == 0 {
return;
}
window
.set_position(PhysicalPosition {
x: state.bounds.x,
y: state.bounds.y,
})
.expect("msg");
window
.set_size(PhysicalSize {
width: state.bounds.width,
height: state.bounds.height,
})
.expect("msg");
// fn show(window: &Window) {
// window.set_always_on_top(true);
// window.show();
// window.set_resizable(true);
// if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
// if state.bounds.height == 0 || state.bounds.width == 0 {
// return;
// }
// window
// .set_position(PhysicalPosition {
// x: state.bounds.x,
// y: state.bounds.y,
// })
// .expect("msg");
// window
// .set_size(PhysicalSize {
// width: state.bounds.width,
// height: state.bounds.height,
// })
// .expect("msg");
// }
// std::thread::spawn(|| {
// std::thread::sleep(Duration::from_millis(50));
// underlayer::focus_target();
// });
// }
// fn hide(window: &Window) {
// window.hide();
// window.set_resizable(false);
// }
// type RuntimeWrapper = Mutex<Overlay>;
// remember to call `.manage(MyState::default())`
#[tauri::command]
fn set_interactable(interactable: bool, state: tauri::State<Sender<EnforceableEvent>>) {
log::info!("set_interactable: {interactable:?}");
if interactable {
state.send(EnforceableEvent::Force(overlay::StateEvent::Interactable));
} else {
state.send(EnforceableEvent::Return);
}
std::thread::spawn(|| {
std::thread::sleep(Duration::from_millis(50));
underlayer::focus_target();
});
}
fn hide(window: &Window) {
window.hide();
window.set_resizable(false);
#[tauri::command]
fn set_auto_hide(auto_hide: bool, state: tauri::State<Sender<EnforceableEvent>>) {
log::info!("set_auto_hide: {auto_hide:?}");
state.send(EnforceableEvent::AutoHide(auto_hide));
}
type RuntimeWrapper = Mutex<Overlay>;
fn main() {
simple_logger::init_with_level(Level::Info).expect("Could not initialize simple_logger");
SimpleLogger::new().with_module_level("underlayer", log::LevelFilter::Info).with_level(log::LevelFilter::Trace).init().expect("Could not init logger");
tauri::Builder::default()
.manage(Mutex::new(Overlay {
should_take_focus: false,
bounds: Bounds {
x: 0,
y: 0,
width: 0,
height: 0,
},
}))
.setup(|app| {
let window = app.get_window("Overlay").unwrap();
window.set_min_size::<LogicalSize<i32>>(None);
let window2 = window.clone();
window.on_window_event(move |e| match e {
tauri::WindowEvent::Focused(focus) => {
if *focus {
if let Ok(runtime) = window2.state::<RuntimeWrapper>().lock() {
if !runtime.should_take_focus {
window2.set_ignore_cursor_events(true);
}
}
}
}
_ => {}
});
Manager::init(
let tx = Overlay::new(
app.get_window("Overlay")
.expect("Could not get main overlay window"),
"Untitled 1 - Mousepad",
);
std::thread::spawn(move || {
log::info!("Running event thread");
let mut target_is_focused = false;
let underlay: std::sync::mpsc::Receiver<underlayer::UnderlayEvent> =
underlayer::register("Untitled 1 - Mousepad");
while let Ok(event) = underlay.recv() {
log::info!("Got event: {event:?}");
match event {
underlayer::UnderlayEvent::Attach {
bounds,
has_access,
is_fullscreen,
} => {
if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
state.bounds = bounds;
}
}
underlayer::UnderlayEvent::Detach => {
hide(&window);
}
underlayer::UnderlayEvent::MoveResize(bounds) => {
if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
state.bounds = bounds;
if bounds.height == 0 || bounds.width == 0 {
return;
}
window
.set_position(PhysicalPosition {
x: bounds.x,
y: bounds.y,
})
.expect("msg");
window
.set_size(PhysicalSize {
width: bounds.width + 10,
height: bounds.height,
})
.expect("msg");
}
}
underlayer::UnderlayEvent::Focus => {
target_is_focused = true;
show(&window);
}
underlayer::UnderlayEvent::Blur => {
target_is_focused = false;
if !window.is_focused().unwrap() {
hide(&window);
}
}
underlayer::UnderlayEvent::X11FullscreenEvent { is_fullscreen } => {}
}
}
});
app.manage(tx);
// std::thread::spawn(move || {
// log::info!("Running event thread");
// let mut target_is_focused = false;
// let underlay: std::sync::mpsc::Receiver<underlayer::UnderlayEvent> =
// underlayer::register("Untitled 1 - Mousepad");
// while let Ok(event) = underlay.recv() {
// log::info!("Got event: {event:?}");
// match event {
// underlayer::UnderlayEvent::Attach {
// bounds,
// has_access,
// is_fullscreen,
// } => {
// if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
// state.bounds = bounds;
// }
// }
// underlayer::UnderlayEvent::Detach => {
// hide(&window);
// }
// underlayer::UnderlayEvent::MoveResize(bounds) => {
// if let Ok(mut state) = window.state::<RuntimeWrapper>().lock() {
// state.bounds = bounds;
// if bounds.height == 0 || bounds.width == 0 {
// return;
// }
// window
// .set_position(PhysicalPosition {
// x: bounds.x,
// y: bounds.y,
// })
// .expect("msg");
// window
// .set_size(PhysicalSize {
// width: bounds.width + 10,
// height: bounds.height,
// })
// .expect("msg");
// }
// }
// underlayer::UnderlayEvent::Focus => {
// target_is_focused = true;
// show(&window);
// }
// underlayer::UnderlayEvent::Blur => {
// target_is_focused = false;
// if !window.is_focused().unwrap() {
// hide(&window);
// }
// }
// underlayer::UnderlayEvent::X11FullscreenEvent { is_fullscreen } => {}
// }
// }
// });
Ok(())
})
.invoke_handler(tauri::generate_handler![greet])
.invoke_handler(tauri::generate_handler![set_interactable, set_auto_hide])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

@ -1,65 +1,342 @@
use std::{sync::{Arc, Mutex}, marker::PhantomData};
#![allow(unused)]
use tauri::{Manager as TManager, Window};
use std::{
borrow::BorrowMut,
cell::RefCell,
ops::DerefMut,
sync::{mpsc::Receiver as MpscReceiver, Arc, Mutex},
time::Duration,
};
use crossbeam::{
channel::{Receiver, Sender},
select,
};
// The prelude module re-exports the most common used items from statig.
use statig::prelude::*;
use tauri::{PhysicalPosition, PhysicalSize, Window};
use underlayer::{Bounds, UnderlayEvent};
use crate::overlay;
pub enum StateEvent {
Visible,
Hidden,
Interactable,
}
pub enum EnforceableEvent {
Force(StateEvent),
Request(StateEvent),
Relax,
Bounds(Bounds),
AutoHide(bool),
Return
}
#[derive(Clone)]
pub struct Overlay {
should_take_focus: bool,
bounds: Bounds,
window: Window,
auto_hide: bool,
previous: State,
}
pub struct Manager<State> {
window: Window,
should_take_focus: bool,
bounds: Bounds,
state: PhantomData<State>
fn wrap_underlay_rx(rx: MpscReceiver<UnderlayEvent>) -> Receiver<UnderlayEvent> {
let (cb_underlay_tx, cb_underlay_rx) = crossbeam::channel::unbounded();
std::thread::spawn(move || {
while let Ok(event) = rx.recv() {
cb_underlay_tx.send(event).ok();
}
});
cb_underlay_rx
}
pub type ManagerData = Arc<Mutex<Manager>>;
impl<S> Manager<S> {
pub fn init(window: Window, target_label: &str) {
std::thread::spawn(move || {
let manager = Arc::new(Mutex::new(Manager {
window,
should_take_focus: false,
bounds: Default::default(),
state: PhantomData,
}));
let app = window.app_handle();
let underlay_rx = underlayer::register(target_label);
app.manage(manager.clone());
while let Ok(event) = underlay_rx.recv() {
app.state::<ManagerData>().lock().and_then(|mut manager| {
manager.on_event(event);
Ok(())
});
impl Overlay {
pub fn new(window: Window, target_title: &str) -> Sender<EnforceableEvent> {
log::warn!("New overlay object");
let mut overlay = Self {
window: window.clone(),
bounds: Bounds::default(),
auto_hide: true,
previous: State::hidden(),
};
let mut fsm = Overlay::uninitialized_state_machine(overlay).init();
let underlay_rx = wrap_underlay_rx(underlayer::register(target_title));
let (ee_tx, ee_rx) = crossbeam::channel::unbounded();
let (window_event_tx, window_event_rx) = crossbeam::channel::unbounded();
window.on_window_event(move |event| match event {
tauri::WindowEvent::Focused(focus) => {
window_event_tx.send(*focus).ok();
}
_ => {}
});
std::thread::spawn(move || loop {
select! {
recv(underlay_rx) -> msg => {
match msg {
Ok(event) => {
Self::handle_underlay_event(&mut fsm, event);
},
Err(_) => break,
}
},
recv(ee_rx) -> msg => {
match msg {
Ok(event) => {
Self::handle_enforceable_event(&mut fsm, event);
},
Err(_) => break,
}
},
recv(window_event_rx) -> msg => {
match msg {
Ok(focus) => Self::handle_overlay_focused(&mut fsm, focus),
Err(_) => break,
}
}
}
});
ee_tx
}
fn on_event(&mut self, event: UnderlayEvent) {
fn handle_overlay_focused(fsm: &mut InitializedStateMachine<Overlay>, focus: bool) {
match fsm.state() {
State::Visible {} | State::EnforcedVisible {} => {
if focus {
fsm.window.set_ignore_cursor_events(true);
underlayer::focus_target();
}
}
_ => {}
}
}
fn handle_underlay_event(fsm: &mut InitializedStateMachine<Overlay>, event: UnderlayEvent) {
log::info!("EVENT: {event:?}");
match event {
UnderlayEvent::Attach {
bounds,
has_access,
is_fullscreen,
} => {
fsm.handle(&EnforceableEvent::Bounds(bounds));
}
UnderlayEvent::MoveResize(bounds) => fsm.handle(&EnforceableEvent::Bounds(bounds)),
UnderlayEvent::Detach => fsm.handle(&EnforceableEvent::Request(StateEvent::Hidden)),
UnderlayEvent::Focus => fsm.handle(&EnforceableEvent::Request(StateEvent::Visible)),
UnderlayEvent::Blur => {
if !fsm.window.is_focused().unwrap() && fsm.auto_hide {
fsm.handle(&EnforceableEvent::Request(StateEvent::Hidden))
}
}
UnderlayEvent::X11FullscreenEvent { is_fullscreen } => {}
}
}
fn handle_enforceable_event(
fsm: &mut InitializedStateMachine<Overlay>,
event: EnforceableEvent,
) {
fsm.handle(&event);
}
}
// let mut fsm = fsm::Overlay::uninitialized_state_machine(Default::default()).init();
// fsm.handle(&EnforceableEvent::Force(fsm::StateEvent::Visible));
// fsm.handle(&EnforceableEvent::Relax);
// fsm.handle(&EnforceableEvent::Request(fsm::StateEvent::Interactable));
// fsm.handle(&EnforceableEvent::Request(fsm::StateEvent::Hidden));
/// The `state_machine` procedural macro generates the `State` and `Superstate`
/// enums by parsing the function signatures with a `state`, `superstate` or
/// `action` attribute. It also implements the `statig::State` and
/// `statig::Superstate` traits. We also pass an argument that will add the
/// derive macro with the Debug trait to the `State` enum.
#[state_machine(
initial = "State::hidden()",
state(derive(Debug, Clone, PartialEq, Eq)),
superstate(derive(Debug)),
on_transition = "Self::on_transition"
)]
impl Overlay {
#[action]
fn on_enter_visible(&mut self) {
self.window.set_always_on_top(true);
self.window.show();
self.window.set_resizable(true);
if self.bounds.width > 0 && self.bounds.height > 0 {
self.window
.set_position(PhysicalPosition::new(self.bounds.x, self.bounds.y));
self.window
.set_size(PhysicalSize::new(self.bounds.width, self.bounds.height));
}
struct Visible;
struct Interactable;
struct Hidden;
log::info!("on_enter_visible");
}
/*
#[action]
fn on_enter_interactable(&mut self) {
log::info!("on_enter_interactable");
self.window.set_ignore_cursor_events(false);
}
should_take_focus
#[action]
fn on_enter_hidden(&mut self) {
log::info!("on_enter_hidden");
self.window.hide();
self.window.set_resizable(false);
}
#[action]
fn on_exit_interactable(&mut self) {
log::info!("on_exit_interactable");
self.window.set_ignore_cursor_events(true);
}
Overlay visible
#[state(superstate = "super_visible")]
fn enforced_visible(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(StateEvent::Visible) => Super,
EnforceableEvent::Force(event) => Self::match_forced(event),
EnforceableEvent::Request(_) => Handled,
EnforceableEvent::Relax => Transition(State::visible()),
_ => Super,
}
}
Overlay interactable
#[state(superstate = "super_visible")]
fn visible(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(underlying) => Self::match_forced(underlying),
EnforceableEvent::Request(underlying) => match underlying {
StateEvent::Visible => Super,
StateEvent::Hidden => Transition(State::hidden()),
StateEvent::Interactable => Transition(State::interactable()),
},
EnforceableEvent::Relax => Super,
_ => Super,
}
}
Overlay hidden
#[state(superstate = "super_hidden")]
fn enforced_hidden(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(StateEvent::Hidden) => Super,
EnforceableEvent::Force(event) => Self::match_forced(event),
EnforceableEvent::Request(_) => Handled,
EnforceableEvent::Relax => Transition(State::hidden()),
_ => Super,
}
}
*/
#[state(superstate = "super_hidden")]
fn hidden(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(underlying) => Self::match_forced(underlying),
EnforceableEvent::Request(underlying) => match underlying {
StateEvent::Visible => Transition(State::visible()),
StateEvent::Hidden => Super,
StateEvent::Interactable => Transition(State::interactable()),
},
EnforceableEvent::Relax => Super,
_ => Super,
}
}
#[state(superstate = "super_interactable")]
fn enforced_interactable(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(StateEvent::Interactable) => Super,
EnforceableEvent::Force(event) => Self::match_forced(event),
EnforceableEvent::Request(_) => Handled,
EnforceableEvent::Relax => Transition(State::interactable()),
_ => Super,
}
}
#[state(superstate = "super_interactable")]
fn interactable(&mut self, event: &EnforceableEvent) -> Response<State> {
match event {
EnforceableEvent::Force(underlying) => Self::match_forced(underlying),
EnforceableEvent::Request(underlying) => match underlying {
StateEvent::Visible => Transition(State::visible()),
StateEvent::Hidden => Transition(State::hidden()),
StateEvent::Interactable => Super,
},
EnforceableEvent::Relax => Super,
_ => Super,
}
}
fn match_forced(event: &StateEvent) -> Response<State> {
match event {
StateEvent::Hidden => Transition(State::enforced_hidden()),
StateEvent::Interactable => Transition(State::enforced_interactable()),
StateEvent::Visible => Transition(State::enforced_visible()),
}
}
#[superstate(superstate = "common", entry_action = "on_enter_visible")]
fn super_visible(&mut self, event: &EnforceableEvent) -> Response<State> {
log::info!("in super_visible");
Super
}
#[superstate(
superstate = "super_visible",
entry_action = "on_enter_interactable",
exit_action = "on_exit_interactable"
)]
fn super_interactable(&mut self, event: &EnforceableEvent) -> Response<State> {
log::info!("in super_interactable");
Super
}
#[superstate(
superstate = "common",
entry_action = "on_visible",
entry_action = "on_enter_hidden"
)]
fn super_hidden(&mut self, event: &EnforceableEvent) -> Response<State> {
log::info!("in super_hidden");
Super
}
#[superstate]
fn common(&mut self, event: &EnforceableEvent) -> Response<State> {
log::info!("in common");
match event {
EnforceableEvent::Bounds(bounds) => {
self.bounds = *bounds;
if bounds.width > 0 && bounds.height > 0 {
self.window
.set_position(PhysicalPosition::new(bounds.x, bounds.y));
self.window
.set_size(PhysicalSize::new(bounds.width, bounds.height));
}
Super
}
EnforceableEvent::Relax => Super,
EnforceableEvent::AutoHide(auto_hide) => {
self.auto_hide = *auto_hide;
Super
}
EnforceableEvent::Return => {
log::info!("Returning to: {:?}", self.previous);
Transition(self.previous.clone())
}
_ => Super,
}
}
fn on_transition(&mut self, src: &State, dest: &State) {
log::info!("from {src:?} to {dest:?}");
self.previous = src.clone();
}
}

@ -12,7 +12,7 @@
},
"tauri": {
"allowlist": {
"all": false,
"all": true,
"shell": {
"all": false,
"open": true

@ -10,10 +10,7 @@
</div>
<p>Click on the logos to learn more about the frameworks</p>
<div class="row">
<form (submit)="greet($event, greetInput.value)">
<input #greetInput id="greet-input" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
</div>
<p>{{ greetingMessage }}</p>
<input type="checkbox" [(ngModel)]="auto_hide" (change)="toggleAutoHide()">
</div>

@ -1,20 +1,29 @@
import { Component } from "@angular/core";
import { AfterViewInit, Component, OnInit } from "@angular/core";
import { app } from "@tauri-apps/api";
import { invoke } from "@tauri-apps/api/tauri";
import { register } from "@tauri-apps/api/globalShortcut";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
greetingMessage = "";
export class AppComponent implements OnInit {
auto_hide: boolean = true;
interactable: boolean = false;
ngOnInit(): void {
register('F6', () => {
this.interactable = !this.interactable;
invoke("set_interactable", {interactable: this.interactable}).then();
}).then();
greet(event: SubmitEvent, name: string): void {
event.preventDefault();
invoke("set_auto_hide", {auto_hide: this.auto_hide}).then();
}
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
invoke<string>("greet", { name }).then((text) => {
this.greetingMessage = text;
});
toggleAutoHide() {
console.log("toggle auto hide!!");
invoke("set_auto_hide", {autoHide: this.auto_hide}).then();
}
}

@ -2,10 +2,11 @@ import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { AppComponent } from "./app.component";
import { FormsModule } from "@angular/forms";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
imports: [BrowserModule, FormsModule],
providers: [],
bootstrap: [AppComponent],
})

Loading…
Cancel
Save