Merge pull request 'Script-to-host messages' (#45) from script-to-host-messages into main

Reviewed-on: #45
This commit is contained in:
mars 2022-11-20 19:06:03 +00:00
commit 80ba46a0d4
13 changed files with 413 additions and 156 deletions

View File

@ -12,6 +12,7 @@ required-features = ["service"]
anyhow = { version = "1", optional = true }
byteorder = "1.4"
canary = { path = "../..", optional = true }
futures-util = { version = "0.3", optional = true, features = ["io"] }
glium = { version = "0.32", optional = true}
mio = { version = "0.8", features = ["net", "os-poll"], optional = true }
mio-signals = { version = "0.2", optional = true }
@ -21,4 +22,5 @@ serde_json = "1"
slab = { version = "0.4", optional = true}
[features]
async = ["dep:futures-util"]
service = ["dep:anyhow", "dep:canary", "dep:glium", "dep:mio", "dep:mio-signals", "dep:parking_lot", "dep:slab"]

View File

@ -1,44 +0,0 @@
// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use serde::Serialize;
use std::os::unix::net::UnixStream;
use std::path::Path;
use crate::protocol::{ClientMessenger, MagpieServerMsg, PanelId, SendMessage, MAGPIE_SOCK};
/// A client to a Magpie server.
pub struct MagpieClient {
pub messenger: ClientMessenger<UnixStream>,
}
impl MagpieClient {
pub fn new() -> std::io::Result<Self> {
let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set");
let sock_dir = Path::new(&sock_dir);
let sock_path = sock_dir.join(MAGPIE_SOCK);
let socket = UnixStream::connect(sock_path)?;
Ok(Self {
messenger: ClientMessenger::new(socket),
})
}
pub fn send(&mut self, msg: &MagpieServerMsg) {
if let Err(err) = self.messenger.send(msg) {
eprintln!("Message send error: {:?}", err);
}
}
pub fn send_json_message<T: Serialize>(&mut self, id: PanelId, msg: &T) {
let msg = serde_json::to_string(msg).unwrap();
eprintln!("Sending message: {}", msg);
let msg = SendMessage {
id,
msg: msg.into_bytes(),
};
self.send(&MagpieServerMsg::SendMessage(msg));
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod client;
pub mod protocol;
#[cfg(feature = "service")]

View File

@ -43,71 +43,58 @@ pub enum MagpieServerMsg {
SendMessage(SendMessage),
}
/// A message sent from a script's panel to a client.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RecvMessage {
pub id: PanelId,
pub msg: Vec<u8>,
}
/// A message sent from the Magpie server to a client.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "kind")]
pub enum MagpieClientMsg {}
pub enum MagpieClientMsg {
RecvMessage(RecvMessage),
}
/// A [Messenger] specialized for Magpie clients.
pub type ClientMessenger<T> = Messenger<T, MagpieClientMsg, MagpieServerMsg>;
impl<T: Write> ClientMessenger<T> {
pub fn send_panel_json<O: Serialize>(&mut self, id: PanelId, msg: &O) {
let msg = serde_json::to_string(msg).unwrap();
eprintln!("Sending message: {:?}", msg);
let _ = self.send(&MagpieServerMsg::SendMessage(SendMessage {
id,
msg: msg.into_bytes(),
}));
}
}
/// A [Messenger] specialized for Magpie servers.
pub type ServerMessenger<T> = Messenger<T, MagpieServerMsg, MagpieClientMsg>;
/// Bidirectional, transport-agnostic Magpie IO wrapper struct.
pub struct Messenger<T, I, O> {
pub transport: T,
/// Piecewise packet assembler for [Messenger].
pub struct MessageQueue<I> {
expected_len: Option<usize>,
received_buf: VecDeque<u8>,
received_queue: VecDeque<I>,
closed: bool,
_output: PhantomData<O>,
}
impl<T: Read + Write, I: DeserializeOwned, O: Serialize> Messenger<T, I, O> {
pub fn new(transport: T) -> Self {
impl<I> Default for MessageQueue<I> {
fn default() -> Self {
Self {
transport,
expected_len: None,
received_buf: Default::default(),
received_queue: Default::default(),
closed: false,
_output: PhantomData,
}
}
}
pub fn is_closed(&self) -> bool {
self.closed
}
pub fn send(&mut self, msg: &O) -> std::io::Result<()> {
use byteorder::{LittleEndian, WriteBytesExt};
let payload = serde_json::to_vec(msg).unwrap();
let len = payload.len() as u32;
self.transport.write_u32::<LittleEndian>(len)?;
self.transport.write_all(&payload)?;
self.transport.flush()?;
Ok(())
}
/// Receives all pending messages and queues them for [recv].
pub fn flush_recv(&mut self) -> std::io::Result<()> {
let mut buf = [0u8; 1024];
loop {
match self.transport.read(&mut buf) {
Ok(0) => {
self.closed = true;
break;
}
Ok(n) => {
self.received_buf.write(&buf[..n])?;
}
Err(ref err) if err.kind() == std::io::ErrorKind::WouldBlock => break,
Err(ref err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
Err(err) => return Err(err),
}
}
impl<I: DeserializeOwned> MessageQueue<I> {
pub fn on_data(&mut self, data: &[u8]) -> std::io::Result<()> {
self.received_buf.write_all(data)?;
loop {
if let Some(expected_len) = self.expected_len {
@ -139,8 +126,137 @@ impl<T: Read + Write, I: DeserializeOwned, O: Serialize> Messenger<T, I, O> {
Ok(())
}
/// Tries to receive a single input packet.
pub fn recv(&mut self) -> Option<I> {
self.received_queue.pop_back()
}
}
/// Bidirectional, transport-agnostic Magpie IO wrapper struct.
pub struct Messenger<T, I, O> {
transport: T,
queue: MessageQueue<I>,
closed: bool,
_output: PhantomData<O>,
}
impl<T, I, O> Messenger<T, I, O> {
pub fn new(transport: T) -> Self {
Self {
transport,
queue: Default::default(),
closed: false,
_output: PhantomData,
}
}
pub fn is_closed(&self) -> bool {
self.closed
}
/// Destroys this messenger and returns the inner transport.
pub fn into_transport(self) -> T {
self.transport
}
}
impl<T: Write, I, O: Serialize> Messenger<T, I, O> {
pub fn send(&mut self, msg: &O) -> std::io::Result<()> {
use byteorder::{LittleEndian, WriteBytesExt};
let payload = serde_json::to_vec(msg).unwrap();
let len = payload.len() as u32;
self.transport.write_u32::<LittleEndian>(len)?;
self.transport.write_all(&payload)?;
self.transport.flush()?;
Ok(())
}
}
impl<T: Read, I: DeserializeOwned, O> Messenger<T, I, O> {
/// Synchronously receives all pending messages and queues them for [recv].
///
/// This function only works if the transport is in non-blocking mode.
/// Otherwise, this may block while waiting for more data, even if the
/// data it receives does not add up to a full message.
pub fn flush_recv(&mut self) -> std::io::Result<()> {
let mut buf = [0u8; 1024];
loop {
match self.transport.read(&mut buf) {
Ok(0) => {
self.closed = true;
break;
}
Err(ref err) if err.kind() == std::io::ErrorKind::ConnectionReset => {
self.closed = true;
break;
}
Ok(n) => {
self.queue.on_data(&buf[..n])?;
}
Err(ref err) if err.kind() == std::io::ErrorKind::WouldBlock => break,
Err(ref err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
Err(err) => return Err(err),
}
}
Ok(())
}
/// Tries to receive a single input packet.
///
/// For messages to be received here, [flush_recv] must be called to
/// continuously read pending data from the transport.
pub fn try_recv(&mut self) -> Option<I> {
self.queue.recv()
}
}
#[cfg(feature = "async")]
mod async_messages {
use super::*;
use futures_util::{AsyncReadExt, AsyncWriteExt};
use std::marker::Unpin;
impl<T: AsyncWriteExt + Unpin> ClientMessenger<T> {
pub async fn send_panel_json_async<O: Serialize>(&mut self, id: PanelId, msg: &O) {
let msg = serde_json::to_string(msg).unwrap();
eprintln!("Sending message: {:?}", msg);
let _ = self
.send_async(&MagpieServerMsg::SendMessage(SendMessage {
id,
msg: msg.into_bytes(),
}))
.await;
}
}
impl<T: AsyncWriteExt + Unpin, I, O: Serialize> Messenger<T, I, O> {
pub async fn send_async(&mut self, msg: &O) -> std::io::Result<()> {
use byteorder::{LittleEndian, WriteBytesExt};
let payload = serde_json::to_vec(msg).unwrap();
let len = payload.len() as u32;
let mut msg = Vec::with_capacity(4 + payload.len());
msg.write_u32::<LittleEndian>(len)?;
msg.extend_from_slice(&payload);
self.transport.write_all(&msg).await?;
self.transport.flush().await?;
Ok(())
}
}
impl<T: AsyncReadExt + Unpin, I: DeserializeOwned, O> Messenger<T, I, O> {
pub async fn recv(&mut self) -> std::io::Result<I> {
let mut buf = [0u8; 1024];
loop {
if let Some(msg) = self.queue.recv() {
return Ok(msg);
}
let num = self.transport.read(&mut buf).await?;
self.queue.on_data(&buf[..num])?;
}
}
}
}

View File

@ -14,18 +14,29 @@ use mio_signals::{Signal, Signals};
use parking_lot::RwLock;
use slab::Slab;
use crate::protocol::{CreatePanel, MagpieServerMsg, SendMessage, ServerMessenger};
use crate::protocol::*;
use crate::service::window::{WindowMessage, WindowMessageSender};
const SOCK_NAME: &str = "magpie.sock";
pub enum IpcMessage {}
#[derive(Debug)]
pub enum IpcMessage {
PanelMessage { window: usize, message: Vec<u8> },
}
#[derive(Clone)]
pub struct IpcMessageSender {
waker: Waker,
waker: Arc<Waker>,
sender: Sender<IpcMessage>,
}
impl IpcMessageSender {
pub fn send(&self, msg: IpcMessage) {
let _ = self.sender.send(msg);
let _ = self.waker.wake();
}
}
/// Wraps [mio::net::UnixListener] with automatic file deletion on drop.
pub struct Listener {
pub uds: UnixListener,
@ -57,6 +68,7 @@ impl DerefMut for Listener {
pub struct IpcData {
poll: Poll,
window_to_client_panel: HashMap<usize, (usize, PanelId)>,
next_window_id: usize,
}
@ -76,27 +88,13 @@ pub struct Client {
id_to_window: HashMap<u32, usize>,
}
impl Drop for Client {
fn drop(&mut self) {
println!("Client #{} disconnected", self.token.0);
let data = self.data.write();
let _ = data
.poll
.registry()
.deregister(&mut self.messenger.transport);
for (_id, window) in self.id_to_window.drain() {
let msg = WindowMessage::CloseWindow { id: window };
let _ = self.window_sender.send_event(msg);
}
}
}
impl Client {
pub fn on_readable(&mut self) -> std::io::Result<bool> {
self.messenger.flush_recv()?;
if let Err(err) = self.messenger.flush_recv() {
eprintln!("flush_recv() error: {:?}", err);
}
while let Some(msg) = self.messenger.recv() {
while let Some(msg) = self.messenger.try_recv() {
println!("Client #{}: {:?}", self.token.0, msg);
match msg {
MagpieServerMsg::CreatePanel(CreatePanel {
@ -104,7 +102,11 @@ impl Client {
protocol,
script,
}) => {
let window = self.data.write().new_window_id();
let mut data = self.data.write();
let window = data.new_window_id();
data.window_to_client_panel
.insert(window, (self.token.0, id));
if let Some(old_id) = self.id_to_window.insert(id, window) {
let msg = WindowMessage::CloseWindow { id: old_id };
@ -129,13 +131,26 @@ impl Client {
Ok(self.messenger.is_closed())
}
pub fn disconnect(mut self) {
println!("Client #{} disconnected", self.token.0);
let mut transport = self.messenger.into_transport();
let mut data = self.data.write();
let _ = data.poll.registry().deregister(&mut transport);
for (_id, window) in self.id_to_window.drain() {
let msg = WindowMessage::CloseWindow { id: window };
let _ = self.window_sender.send_event(msg);
data.window_to_client_panel.remove(&window);
}
}
}
pub struct Ipc {
pub data: Arc<RwLock<IpcData>>,
pub window_sender: WindowMessageSender,
pub message_recv: Receiver<IpcMessage>,
pub events: Events,
pub quit: bool,
pub listener: Listener,
pub signals: Signals,
@ -159,7 +174,6 @@ impl Ipc {
let mut signals = Signals::new(Signal::Interrupt | Signal::Quit)?;
let events = Events::with_capacity(128);
let poll = Poll::new()?;
let listener_token = Token(usize::MAX);
let signals_token = Token(listener_token.0 - 1);
@ -173,12 +187,13 @@ impl Ipc {
let (sender, message_recv) = channel();
let sender = IpcMessageSender {
waker: Waker::new(registry, message_recv_token)?,
waker: Arc::new(Waker::new(registry, message_recv_token)?),
sender,
};
let data = IpcData {
poll,
window_to_client_panel: HashMap::new(),
next_window_id: 0,
};
@ -186,7 +201,6 @@ impl Ipc {
data: Arc::new(RwLock::new(data)),
window_sender,
message_recv,
events,
quit: false,
listener,
signals,
@ -199,10 +213,29 @@ impl Ipc {
Ok((ipc, sender))
}
pub fn poll(&mut self, timeout: Option<Duration>) -> std::io::Result<()> {
self.data.write().poll.poll(&mut self.events, timeout)?;
pub fn on_message(&mut self, msg: IpcMessage) -> std::io::Result<()> {
match msg {
IpcMessage::PanelMessage { window, message } => {
let data = self.data.read();
let (client, panel) = *data.window_to_client_panel.get(&window).unwrap();
let client = self.clients.get_mut(client).unwrap();
let reply = RecvMessage {
id: panel,
msg: message,
};
client
.messenger
.send(&MagpieClientMsg::RecvMessage(reply))?;
}
}
for event in self.events.iter() {
Ok(())
}
pub fn poll(&mut self, events: &mut Events, timeout: Option<Duration>) -> std::io::Result<()> {
self.data.write().poll.poll(events, timeout)?;
for event in events.iter() {
if event.token() == self.listener_token {
loop {
match self.listener.accept() {
@ -238,13 +271,17 @@ impl Ipc {
let _ = self.window_sender.send_event(WindowMessage::Quit);
self.quit = true;
}
} else if event.token() == self.message_recv_token {
while let Ok(received) = self.message_recv.try_recv() {
self.on_message(received)?;
}
} else if let Some(client) = self.clients.get_mut(event.token().0) {
let disconnected = client.on_readable()?;
if disconnected {
self.clients.remove(event.token().0);
self.clients.remove(event.token().0).disconnect();
}
} else {
panic!("Unrecognized event token: {:?}", event);
eprintln!("Unrecognized event token: {:?}", event);
}
}
@ -252,9 +289,10 @@ impl Ipc {
}
pub fn run(mut self) {
let mut events = Events::with_capacity(128);
while !self.quit {
let wait = Duration::from_millis(100);
match self.poll(Some(wait)) {
match self.poll(&mut events, Some(wait)) {
Ok(_) => {}
Err(e) => {
eprintln!("IPC poll error: {:?}", e);

View File

@ -35,6 +35,8 @@ pub enum WindowMessage {
pub type WindowMessageSender = EventLoopProxy<WindowMessage>;
pub struct Window {
pub ipc_sender: IpcMessageSender,
pub ipc_id: usize,
pub graphics: Graphics,
pub panel: Panel,
pub last_update: Instant,
@ -44,6 +46,8 @@ pub struct Window {
impl Window {
pub fn new(
ipc_sender: IpcMessageSender,
ipc_id: usize,
panel: Panel,
event_loop: &EventLoopWindowTarget<WindowMessage>,
) -> Result<Self, DisplayCreationError> {
@ -55,6 +59,8 @@ impl Window {
let graphics = Graphics::new(display);
let last_update = Instant::now();
Ok(Self {
ipc_sender,
ipc_id,
graphics,
panel,
last_update,
@ -71,24 +77,38 @@ impl Window {
self.graphics.display.gl_window().window().request_redraw();
}
/// Receives all messages from the script and forwards them to IPC.
pub fn recv_messages(&mut self) {
for message in self.panel.recv_messages() {
self.ipc_sender.send(IpcMessage::PanelMessage {
window: self.ipc_id,
message,
});
}
}
pub fn update(&mut self) {
let now = Instant::now();
let dt = now.duration_since(self.last_update).as_secs_f32();
self.panel.update(dt);
self.last_update = now;
self.recv_messages();
}
pub fn draw(&mut self) {
let commands = self.panel.draw();
self.graphics.draw(&commands);
self.recv_messages();
}
pub fn send_message(&mut self, msg: Vec<u8>) {
self.panel.on_message(msg);
self.recv_messages();
}
pub fn resize(&mut self, new_size: Vec2) {
self.panel.on_resize(new_size);
self.recv_messages();
}
pub fn on_event(&mut self, event: WindowEvent) {
@ -109,6 +129,7 @@ impl Window {
};
self.panel.on_cursor_event(event, self.cursor_pos);
self.recv_messages();
}
WindowEvent::MouseInput {
state,
@ -127,6 +148,7 @@ impl Window {
};
self.panel.on_cursor_event(event, self.cursor_pos);
self.recv_messages();
}
_ => {}
}
@ -175,7 +197,7 @@ impl WindowStore {
let module = std::fs::read(script)?;
let mut script = self.runtime.load_module(&module)?;
let panel = script.create_panel(&protocol, vec![])?;
let window = Window::new(panel, &event_loop)?;
let window = Window::new(self.ipc_sender.to_owned(), id, panel, &event_loop)?;
let window_id = window.get_id();
self.windows.insert(window_id, window);
self.ipc_to_window.insert(id, window_id);

View File

@ -10,7 +10,7 @@ path = "src/main.rs"
required-features = ["bin"]
[dependencies]
canary-magpie = { path = "../magpie", optional = true }
canary-magpie = { path = "../magpie", optional = true, features = ["async"] }
futures-util = { version = "0.3", optional = true }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View File

@ -1,9 +1,15 @@
// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use canary_magpie::client::MagpieClient;
use canary_magpie::protocol::{CreatePanel, MagpieServerMsg};
use std::path::Path;
use canary_magpie::protocol::{
ClientMessenger, CreatePanel, MagpieClientMsg, MagpieServerMsg, RecvMessage, MAGPIE_SOCK,
};
use canary_music_player::*;
use smol::net::unix::UnixStream;
pub type MagpieClient = ClientMessenger<UnixStream>;
pub mod mpris;
@ -51,30 +57,66 @@ impl<'a> From<MetadataMap<'a>> for Metadata {
}
impl Metadata {
pub fn new(magpie: &mut MagpieClient, metadata: MetadataMap) -> Self {
pub async fn update_new(magpie: &mut MagpieClient, metadata: MetadataMap<'_>) -> Self {
let new: Self = metadata.into();
magpie.send_json_message(0, &InMsg::AlbumChanged(new.album.clone()));
magpie.send_json_message(0, &InMsg::TrackChanged(new.track.clone()));
let msg = InMsg::AlbumChanged(new.album.clone());
magpie.send_panel_json_async(0, &msg).await;
let msg = InMsg::TrackChanged(new.track.clone());
magpie.send_panel_json_async(0, &msg).await;
new
}
pub fn update(&mut self, messenger: &mut MagpieClient, metadata: MetadataMap) {
pub async fn update_diff(&mut self, messenger: &mut MagpieClient, metadata: MetadataMap<'_>) {
let new: Self = metadata.into();
if self.album != new.album {
messenger.send_json_message(0, &InMsg::AlbumChanged(new.album.clone()));
let msg = InMsg::AlbumChanged(new.album.clone());
messenger.send_panel_json_async(0, &msg).await;
}
if self.track != new.track {
messenger.send_json_message(0, &InMsg::TrackChanged(new.track.clone()));
let msg = InMsg::TrackChanged(new.track.clone());
messenger.send_panel_json_async(0, &msg).await;
let progress = ProgressChanged { position: 0.0 };
messenger.send_json_message(0, &InMsg::ProgressChanged(progress));
let msg = InMsg::ProgressChanged(progress);
messenger.send_panel_json_async(0, &msg).await;
}
*self = new;
}
}
async fn on_message(
player: &PlayerProxy<'_>,
magpie: &mut MagpieClient,
message: MagpieClientMsg,
) -> Result<(), Box<dyn std::error::Error>> {
let message = match message {
MagpieClientMsg::RecvMessage(RecvMessage { id: 0, msg }) => msg,
_ => return Ok(()),
};
let message: OutMsg = match serde_json::from_slice(&message) {
Ok(v) => v,
Err(err) => {
eprintln!("Panel message parse error: {:?}", err);
return Ok(());
}
};
match message {
OutMsg::Pause => player.pause().await?,
OutMsg::Play => player.play().await?,
OutMsg::PlayPause => player.play_pause().await?,
OutMsg::Stop => player.stop().await?,
OutMsg::Previous => player.previous().await?,
OutMsg::Next => player.next().await?,
_ => {}
}
Ok(())
}
async fn player_main(
player: &PlayerProxy<'_>,
magpie: &mut MagpieClient,
@ -84,10 +126,17 @@ async fn player_main(
let mut metadata_tracker = player.receive_metadata_changed().await.fuse();
let mut position_tracker = player.receive_position_changed().await.fuse();
let mut metadata = Metadata::new(magpie, player.metadata().await?);
let mut metadata = Metadata::update_new(magpie, player.metadata().await?).await;
use futures_util::FutureExt;
loop {
futures_util::select! {
msg = magpie.recv().fuse() => {
match msg {
Ok(msg) => on_message(player, magpie, msg).await?,
Err(err) => eprintln!("Magpie recv error: {:?}", err),
}
}
// TODO also update volume, shuffle status, and loop status
status = playback_status.next() => {
let status = match status {
@ -104,7 +153,8 @@ async fn player_main(
};
if let Some(status) = status {
magpie.send_json_message(0, &InMsg::PlaybackStatusChanged(status));
let msg = InMsg::PlaybackStatusChanged(status);
magpie.send_panel_json_async(0, &msg).await;
}
}
position = position_tracker.next() => {
@ -116,7 +166,8 @@ async fn player_main(
let position = position.get().await?;
let position = position as f32 / 1_000_000.0; // Microseconds to seconds
let progress = ProgressChanged { position };
magpie.send_json_message(0, &InMsg::ProgressChanged(progress));
let msg = InMsg::ProgressChanged(progress);
magpie.send_panel_json_async(0, &msg).await;
}
new_metadata = metadata_tracker.next() => {
let new_metadata = match new_metadata {
@ -125,7 +176,7 @@ async fn player_main(
};
let new_metadata = new_metadata.get().await?;
metadata.update(magpie, new_metadata);
metadata.update_diff(magpie, new_metadata).await;
}
};
}
@ -140,11 +191,12 @@ fn main() {
.expect("Please pass a path to a Canary script!")
.to_owned();
let mut magpie = MagpieClient::new().unwrap();
smol::block_on(async {
let dbus = zbus::Connection::session().await.unwrap();
let sock_dir = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set");
let sock_dir = Path::new(&sock_dir);
let sock_path = sock_dir.join(MAGPIE_SOCK);
let socket = UnixStream::connect(sock_path).await.unwrap();
let mut magpie = MagpieClient::new(socket);
let protocol = "tebibyte-media.desktop.music-player-controller".to_string();
let script = std::path::PathBuf::from(&module_path);
let msg = CreatePanel {
@ -152,8 +204,11 @@ fn main() {
protocol,
script,
};
let msg = MagpieServerMsg::CreatePanel(msg);
magpie.messenger.send(&msg).unwrap();
magpie.send_async(&msg).await.unwrap();
let dbus = zbus::Connection::session().await.unwrap();
let mut first_loop = true;
let mut connected = false;
@ -169,7 +224,7 @@ fn main() {
if connected {
println!("Disconnected from MPRIS");
let msg = InMsg::Disconnected;
magpie.send_json_message(0, &msg);
magpie.send_panel_json_async(0, &msg).await;
connected = false;
}
@ -193,7 +248,7 @@ fn main() {
player.destination().as_str()
);
connected = true;
magpie.send_json_message(0, &InMsg::Connected);
magpie.send_panel_json_async(0, &InMsg::Connected).await;
match player_main(&player, &mut magpie).await {
Ok(()) => {}

View File

@ -94,6 +94,10 @@ impl Panel {
self.draw_indexed(&vertices, &indices);
}
pub fn send_message(&self, message: &[u8]) {
unsafe { panel_send_message(self.0, message.as_ptr() as u32, message.len() as u32) }
}
}
#[repr(transparent)]
@ -443,4 +447,6 @@ extern "C" {
fn message_get_len(id: u32) -> u32;
fn message_get_data(id: u32, ptr: u32);
fn panel_send_message(id: u32, message_ptr: u32, message_len: u32);
}

View File

@ -1,7 +1,7 @@
use api::*;
use canary_script::*;
use canary_music_player::{AlbumInfo, PlaybackStatus, ProgressChanged, TrackInfo};
use canary_music_player::{AlbumInfo, PlaybackStatus, ProgressChanged, TrackInfo, OutMsg};
use crate::widgets::prelude::*;
use button::{RoundButton, RoundButtonStyle};
@ -57,7 +57,7 @@ impl PanelImpl for MusicPlayerPanel {
use InMsg::*;
match (self.widget.as_mut(), msg) {
(Some(_), Disconnected) => self.widget = None,
(None, Connected) => self.widget = Some(MusicPlayerWidget::new()),
(None, Connected) => self.widget = Some(MusicPlayerWidget::new(self.panel)),
(Some(widget), AlbumChanged(info)) => widget.update_album(info),
(Some(widget), TrackChanged(info)) => widget.update_track(info),
(Some(widget), PlaybackStatusChanged(status)) => widget.update_playback_status(status),
@ -106,6 +106,7 @@ impl Default for MusicPlayerStyle {
}
pub struct MusicPlayerWidget {
panel: Panel,
artist: Offset<Label>,
album: Offset<Label>,
track: Offset<Label>,
@ -146,6 +147,18 @@ impl Container for MusicPlayerWidget {
self.position
.set_text(&Self::format_time(self.position_secs));
}
if self.previous.was_clicked() {
self.send_message(&OutMsg::Previous);
}
if self.play.was_clicked() {
self.send_message(&OutMsg::PlayPause);
}
if self.next.was_clicked() {
self.send_message(&OutMsg::Next);
}
}
fn draw(&mut self, ctx: &DrawContext) {
@ -168,7 +181,7 @@ impl Container for MusicPlayerWidget {
}
impl MusicPlayerWidget {
pub fn new() -> Self {
pub fn new(panel: Panel) -> Self {
let style = MusicPlayerStyle::default();
let display_font = Font::new(crate::DISPLAY_FONT);
let content_font = Font::new(crate::CONTENT_FONT);
@ -245,6 +258,7 @@ impl MusicPlayerWidget {
let next = RoundButton::new(secondary_button, Some(next_text));
Self {
panel,
artist: make_body_label("Artist"),
album: make_body_label("Album"),
track: make_body_label("Track"),
@ -263,6 +277,11 @@ impl MusicPlayerWidget {
}
}
pub fn send_message(&self, msg: &OutMsg) {
let msg = serde_json::to_vec(msg).unwrap();
self.panel.send_message(&msg);
}
pub fn format_time(secs: f32) -> String {
let duration = secs.floor() as usize;
let seconds = duration % 60;

View File

@ -8,6 +8,8 @@
//! implemented, but in the future, [wasm3](https://github.com/wasm3/wasm3)
//! will also be provided.
use std::collections::VecDeque;
use super::*;
pub mod wasmtime;
@ -22,7 +24,7 @@ pub fn make_default_backend() -> anyhow::Result<Box<dyn Backend>> {
/// A WebAssembly runtime backend.
pub trait Backend {
fn load_module(&self, abi: ScriptAbi, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>>;
fn load_module(&self, abi: Arc<ScriptAbi>, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>>;
}
/// An instance of a WebAssembly module.
@ -69,6 +71,7 @@ pub struct ScriptAbi {
loaded_fonts: RwLock<Vec<Arc<text::Font>>>,
text_layouts: RwLock<Slab<text::TextLayout>>,
message_store: RwLock<Slab<Vec<u8>>>,
panels: RwLock<Slab<PanelAbi>>,
}
impl ScriptAbi {
@ -79,6 +82,13 @@ impl ScriptAbi {
}
}
/// Allocates a new ID and host-side storage for a panel.
pub fn create_panel(&self) -> PanelId {
let abi = PanelAbi::default();
let id = self.panels.write().insert(abi);
PanelId(id)
}
pub fn start_draw(&self) {
let mut lock = self.draw_cmds.lock();
lock.clear();
@ -157,4 +167,23 @@ impl ScriptAbi {
let src = store.get(id as usize).unwrap();
dst.copy_from_slice(src);
}
pub fn panel_send_message(&self, id: u32, message: Vec<u8>) {
if let Some(panel) = self.panels.read().get(id as usize) {
panel.outgoing_messages.write().push_back(message);
}
}
pub fn recv_panel_messages(&self, id: PanelId) -> Vec<Vec<u8>> {
if let Some(panel) = self.panels.read().get(id.0) {
panel.outgoing_messages.write().drain(..).collect()
} else {
Vec::new()
}
}
}
#[derive(Default)]
pub struct PanelAbi {
outgoing_messages: RwLock<VecDeque<Vec<u8>>>,
}

View File

@ -9,9 +9,9 @@ use crate::DrawCommand;
use canary_script::{Color, CursorEventKind, Rect, Vec2};
use parking_lot::Mutex;
type Caller<'a> = wasmtime::Caller<'a, ScriptAbi>;
type Store = wasmtime::Store<ScriptAbi>;
type Linker = wasmtime::Linker<ScriptAbi>;
type Caller<'a> = wasmtime::Caller<'a, Arc<ScriptAbi>>;
type Store = wasmtime::Store<Arc<ScriptAbi>>;
type Linker = wasmtime::Linker<Arc<ScriptAbi>>;
pub struct WasmtimeBackend {
engine: wasmtime::Engine,
@ -30,7 +30,7 @@ impl WasmtimeBackend {
}
impl Backend for WasmtimeBackend {
fn load_module(&self, abi: ScriptAbi, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>> {
fn load_module(&self, abi: Arc<ScriptAbi>, module: &[u8]) -> anyhow::Result<Arc<dyn Instance>> {
let module = wasmtime::Module::new(&self.engine, module)?;
let mut store = wasmtime::Store::new(&self.engine, abi);
let mut linker = Linker::new(&self.engine);
@ -147,6 +147,15 @@ impl WasmtimeInstance {
},
)?;
linker.func_wrap(
module,
"panel_send_message",
|mut caller: Caller<'_>, id: u32, ptr: u32, len: u32| {
let message = Self::get_memory_slice_bytes(&mut caller, ptr as usize, len as usize);
caller.data().panel_send_message(id, message.to_vec())
},
)?;
Ok(())
}

View File

@ -29,11 +29,12 @@ impl Runtime {
pub fn load_module(&self, module: &[u8]) -> anyhow::Result<Script> {
let abi = ScriptAbi::new(self.font_store.to_owned());
let instance = self.backend.load_module(abi, module)?;
let abi = Arc::new(abi);
let instance = self.backend.load_module(abi.to_owned(), module)?;
Ok(Script {
instance,
next_panel: 0,
abi,
})
}
}
@ -41,16 +42,16 @@ impl Runtime {
/// A loaded instance of a Canary script.
pub struct Script {
instance: Arc<dyn Instance>,
next_panel: usize,
abi: Arc<ScriptAbi>,
}
impl Script {
pub fn create_panel(&mut self, protocol: &str, msg: Vec<u8>) -> anyhow::Result<Panel> {
let id = PanelId(self.next_panel);
self.next_panel += 1;
let id = self.abi.create_panel();
let userdata = self.instance.bind_panel(id, protocol, msg);
Ok(Panel {
instance: self.instance.clone(),
abi: self.abi.clone(),
id,
userdata,
})
@ -60,6 +61,7 @@ impl Script {
/// A Canary panel.
pub struct Panel {
instance: Arc<dyn Instance>,
abi: Arc<ScriptAbi>,
id: PanelId,
userdata: u32,
}
@ -84,6 +86,10 @@ impl Panel {
pub fn on_message(&self, msg: Vec<u8>) {
self.instance.on_message(self.userdata, msg);
}
pub fn recv_messages(&self) -> Vec<Vec<u8>> {
self.abi.recv_panel_messages(self.id)
}
}
/// Proportion constant between pixels (at 96dpi) to millimeters (Canary's unit measurement).