wip; sending window close events back to elixir is pretty slick.

This commit is contained in:
James Harton 2024-05-15 21:26:44 +12:00
parent 244dedfb9b
commit fc46908151
Signed by: james
GPG key ID: 90E82DAA13F624F4
7 changed files with 294 additions and 16 deletions

View file

@ -114,4 +114,39 @@ defmodule Scenic.Driver.Renderling.Window do
def terminate(_, driver) do
Nif.terminate(driver.assigns.server)
end
@doc false
@impl GenServer
def handle_info(:shutdown, driver) do
case driver.assigns.config.on_close do
:restart ->
{:stop, :normal, driver}
:stop_driver ->
ViewPort.stop_driver(driver.viewport, self())
{:stop, :normal, driver}
:stop_viewport ->
ViewPort.stop(driver.viewport)
{:stop, :normal, driver}
:stop_system ->
System.stop(:shutdown)
{:stop, :normal, driver}
:halt_system ->
System.halt(:shutdown)
{:stop, :normal, driver}
{module, _fun, 1} ->
module.fun(:shutdown)
{:stop, :normal, driver}
end
end
def handle_info(msg, driver) do
dbg(msg)
{:noreply, driver}
end
end

View file

@ -1,3 +1,4 @@
use crate::message;
use rustler::{NifStruct, NifTaggedEnum, NifTuple, NifUnitEnum, NifUntaggedEnum, Term};
/// Structures received from BEAM.
@ -405,3 +406,111 @@ pub enum InputClass {
Led,
Switch,
}
#[derive(NifTuple, Debug, Clone)]
pub struct Point(u32, u32);
impl From<message::Point> for Point {
fn from(point: message::Point) -> Self {
Point(point.x, point.y)
}
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum ModifierKey {
Meta,
Alt,
Ctrl,
Shift,
CapsLock,
NumLock,
ScrollLock,
}
impl From<message::ModifierKey> for ModifierKey {
fn from(key: message::ModifierKey) -> Self {
match key {
message::ModifierKey::Meta => ModifierKey::Meta,
message::ModifierKey::Alt => ModifierKey::Alt,
message::ModifierKey::Ctrl => ModifierKey::Ctrl,
message::ModifierKey::Shift => ModifierKey::Shift,
message::ModifierKey::CapsLock => ModifierKey::CapsLock,
message::ModifierKey::NumLock => ModifierKey::NumLock,
message::ModifierKey::ScrollLock => ModifierKey::ScrollLock,
}
}
}
#[derive(NifUnitEnum, Debug, Clone)]
pub enum ViewPortEvent {
Enter,
Exit,
Reshape,
}
impl From<message::ViewPortEvent> for ViewPortEvent {
fn from(event: message::ViewPortEvent) -> Self {
match event {
message::ViewPortEvent::Enter => ViewPortEvent::Enter,
message::ViewPortEvent::Exit => ViewPortEvent::Exit,
message::ViewPortEvent::Reshape => ViewPortEvent::Reshape,
}
}
}
#[derive(NifTaggedEnum, Debug, Clone)]
pub enum InputEvent {
CodePoint(String, Vec<ModifierKey>),
Key(String, u8, Vec<ModifierKey>),
CursorButton(String, u8, Vec<ModifierKey>, Point),
CursorScroll(Point, Point),
CursorPos(Point),
ViewPort(ViewPortEvent, Point),
Relative(Point),
Led(String, u32),
Switch(String, u32),
}
impl From<message::InputEvent> for InputEvent {
fn from(event: message::InputEvent) -> Self {
match event {
message::InputEvent::CodePoint {
codepoint,
modifiers,
} => InputEvent::CodePoint(
codepoint,
modifiers.iter().map(|x| x.clone().into()).collect(),
),
message::InputEvent::Key {
key,
value,
modifiers,
} => InputEvent::Key(
key,
value,
modifiers.iter().map(|x| x.clone().into()).collect(),
),
message::InputEvent::CursorButton {
button,
value,
modifiers,
position,
} => InputEvent::CursorButton(
button,
value,
modifiers.iter().map(|x| x.clone().into()).collect(),
position.into(),
),
message::InputEvent::CursorScroll { offset, position } => {
InputEvent::CursorScroll(offset.into(), position.into())
}
message::InputEvent::CursorPos { position } => InputEvent::CursorPos(position.into()),
message::InputEvent::ViewPort { event, position } => {
InputEvent::ViewPort(event.into(), position.into())
}
message::InputEvent::Relative { position } => InputEvent::Relative(position.into()),
message::InputEvent::Led { id, value } => InputEvent::Led(id, value),
message::InputEvent::Switch { id, value } => InputEvent::Switch(id, value),
}
}
}

View file

@ -1,24 +1,22 @@
mod elixir_types;
mod message;
use crate::elixir_types::{Color, DriverConfig, InputClass, ScriptItem};
use crate::elixir_types::{Color, DriverConfig, InputClass, InputEvent, ScriptItem};
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
use rustler::{Env, NifResult, ResourceArc, Term};
use rustler::{Encoder, Env, LocalPid, NifResult, OwnedEnv, ResourceArc, Term};
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use std::thread::sleep;
struct IpcResourceInner {
ipc_channel_name: String,
sender: IpcSender<message::Message>,
receiver: IpcReceiver<message::Message>,
handle: Child,
}
struct IpcResource(Mutex<IpcResourceInner>);
#[rustler::nif]
fn init(cfg: DriverConfig) -> ResourceArc<IpcResource> {
fn init(env: Env<'_>, cfg: DriverConfig) -> ResourceArc<IpcResource> {
let (ipc, ipc_channel_name) = IpcOneShotServer::<message::IpcExchange>::new().unwrap();
let handle = Command::new(cfg.server_path.clone())
@ -32,12 +30,28 @@ fn init(cfg: DriverConfig) -> ResourceArc<IpcResource> {
sender.send(cfg.into()).unwrap();
ResourceArc::new(IpcResource(Mutex::new(IpcResourceInner {
ipc_channel_name,
handle,
sender,
receiver,
})))
let pid = env.pid();
std::thread::spawn(move || loop {
let event = receiver.recv().unwrap();
let mut msg_env = OwnedEnv::new();
match event {
message::Message::InputEvent(input_event) => {
let _ = msg_env.send_and_clear(&pid, |env| {
let input_event: InputEvent = input_event.into();
input_event.encode(env)
});
}
message::Message::Shutdown => {
let _ = msg_env.send_and_clear(&pid, |env| {
rustler::Atom::from_str(env, "shutdown").unwrap()
});
}
_ => unimplemented!(),
}
});
ResourceArc::new(IpcResource(Mutex::new(IpcResourceInner { handle, sender })))
}
#[rustler::nif]

View file

@ -611,6 +611,71 @@ impl From<elixir_types::ScriptItem> for ScriptItem {
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ModifierKey {
Meta,
Alt,
Ctrl,
Shift,
CapsLock,
NumLock,
ScrollLock,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Point {
pub x: u32,
pub y: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ViewPortEvent {
Enter,
Exit,
Reshape,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InputEvent {
CodePoint {
codepoint: String,
modifiers: Vec<ModifierKey>,
},
Key {
key: String,
value: u8,
modifiers: Vec<ModifierKey>,
},
CursorButton {
button: String,
value: u8,
modifiers: Vec<ModifierKey>,
position: Point,
},
CursorScroll {
offset: Point,
position: Point,
},
CursorPos {
position: Point,
},
ViewPort {
event: ViewPortEvent,
position: Point,
},
Relative {
position: Point,
},
Led {
id: String,
value: u32,
},
Switch {
id: String,
value: u32,
},
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Message {
Shutdown,
@ -632,6 +697,7 @@ pub enum Message {
DeleteScripts(Vec<String>),
RequestInput(Vec<InputClass>),
UpdateScene(String, Vec<ScriptItem>),
InputEvent(InputEvent),
}
impl From<elixir_types::DriverConfig> for Message {

View file

@ -8,7 +8,7 @@ use clap::Parser;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use message::{OnClose, Orientation};
use message::{InputClass, OnClose, Orientation};
use winit::application::ApplicationHandler;
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::event::WindowEvent;
@ -38,7 +38,7 @@ fn main() -> anyhow::Result<()> {
let sender = IpcSender::connect(args.ipc_channel)?;
let (c2s_send, c2s_recv) = ipc::channel()?;
let (_s2c_send, s2c_recv) = ipc::channel()?;
let (s2c_send, s2c_recv) = ipc::channel()?;
sender.send(IpcExchange {
sender: c2s_send,
receiver: s2c_recv,
@ -60,7 +60,7 @@ fn main() -> anyhow::Result<()> {
};
});
let mut state = Application::new(msg);
let mut state = Application::new(msg, s2c_send);
event_loop.run_app(&mut state).map_err(Into::into)
} else {
panic!("Server: Invalid message received {:?}", msg);
@ -80,10 +80,12 @@ struct Application {
height: u32,
on_close: OnClose,
window: Option<Window>,
input_classes: Vec<InputClass>,
sender: IpcSender<Message>,
}
impl Application {
fn new(event: Message) -> Self {
fn new(event: Message, sender: IpcSender<Message>) -> Self {
match event {
Message::Init {
opacity,
@ -110,6 +112,18 @@ impl Application {
height,
on_close,
window: None,
input_classes: vec![
InputClass::CursorButton,
InputClass::CursorScroll,
InputClass::CursorPos,
InputClass::Codepoint,
InputClass::Key,
InputClass::Viewport,
InputClass::Relative,
InputClass::Led,
InputClass::Switch,
],
sender,
},
_ => unreachable!(),
}
@ -127,7 +141,47 @@ impl ApplicationHandler<Message> for Application {
_window_id: WindowId,
event: WindowEvent,
) {
println!("window event {:?}", event)
match event {
WindowEvent::KeyboardInput { event, .. } => {
// From cairo driver:
//
// int action = (event->type == GDK_KEY_PRESS) ? 1 : 0;
// uint32_t unicode = gdk_keyval_to_unicode(event->keyval);
// send_key(KEYMAP_GDK, event->keyval, event->hardware_keycode, action, event->state);
// if (!(event->keyval & 0xF000) && event->type == GDK_KEY_PRESS) {
// send_codepoint(KEYMAP_GDK, unicode, event->state);
// }
// return TRUE;
//
// Basically, always send a Key event and sometimes a Codepoint event.
}
WindowEvent::CloseRequested => {
self.sender.send(Message::Shutdown).unwrap();
}
WindowEvent::Destroyed => {
self.sender.send(Message::Shutdown).unwrap();
}
WindowEvent::CursorMoved { position, .. } => {
// Send an CursorPos event.
}
WindowEvent::MouseInput { state, button, .. } => {
// Send a CursorButton event.
}
WindowEvent::AxisMotion { axis, value, .. } => {
// Send a CursorScroll event.
}
WindowEvent::CursorEntered { device_id } => {
// Send a ViewPort { event: ViewPortEvent::Enter, position: ?? }
}
WindowEvent::CursorLeft { device_id } => {
// Send a ViewPort { event: ViewPortEvent::Exit, position: ?? }
}
WindowEvent::Resized(size) => {
// Send a ViewPort { event: ViewPortEvent::Reshape, position: Point(size.x, size.y) }
}
_ => println!("unhandled window event {:?}", event),
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {

Binary file not shown.

Binary file not shown.