fix(src): Ensure that the new c api can actually be used

This commit is contained in:
Benedikt Peetz 2024-05-04 15:00:58 +02:00
parent a39a0875a3
commit c233b30a52
10 changed files with 117 additions and 39 deletions

View File

@ -1,2 +1,5 @@
[target.x86_64-unknown-linux-gnu] # [target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"] # rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[build]
rustflags = ["-C", "link-args=-rdynamic -fuse-ld=mold"]

12
Cargo.lock generated
View File

@ -211,9 +211,9 @@ checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]] [[package]]
name = "backoff" name = "backoff"
@ -1696,9 +1696,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.18" version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -2863,9 +2863,9 @@ dependencies = [
[[package]] [[package]]
name = "trixy" name = "trixy"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84becfd882a68c35ec1c2bc23aac6a0b0e10021d74b8675250c048621d633c1" checksum = "73eef2c0a310a228e44c0d5aa8b264381407f221975bb909646edb08d3b722ab"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"libc", "libc",

View File

@ -24,7 +24,7 @@ keymaps = {version = "0.1.1", features = ["crossterm"] }
# c api # c api
libloading = "0.8.3" libloading = "0.8.3"
trixy = {version = "0.1.0"} trixy = {version = "0.1.1"}
# lua stuff # lua stuff
mlua = { version = "0.9.7", features = ["lua54", "async", "send", "serialize"] } mlua = { version = "0.9.7", features = ["lua54", "async", "send", "serialize"] }
@ -43,7 +43,7 @@ directories = "5.0.1"
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
[build-dependencies] [build-dependencies]
trixy = { version = "0.1.0" } trixy = { version = "0.1.1" }
[profile.release] [profile.release]
lto = true lto = true

View File

@ -1,14 +1,58 @@
// Run the `api` bin to see the generated api // Run the `api` bin to see the generated api
use std::{panic::catch_unwind, process, thread};
use cli_log::{debug, info};
use crate::app::{events::Event, COMMAND_TRANSMITTER}; use crate::app::{events::Event, COMMAND_TRANSMITTER};
include!(concat!(env!("OUT_DIR"), "/api.rs")); include!(concat!(env!("OUT_DIR"), "/api.rs"));
pub fn handle_cmd(cmd: Commands) { pub fn handle_cmd(cmd: Commands) {
// Unwinding into the c code implicitly calling this function would be UB. Besides, rust would
// probably just abort the process, giving us a weird shutdown without an error message.
// Thus we catch the panic before the ffi boundary. This could also be moved to trixy.
let error = catch_unwind(|| {
let tx = COMMAND_TRANSMITTER let tx = COMMAND_TRANSMITTER
.get() .get()
.expect("The cell should always be populated, at this point"); .expect("The cell should always be populated, at this point");
info!("Received command: {:#?}", cmd);
// NOTE: The extra future and tokio runtime here is necessary to have a sync api. Which is imho
// better than expecting c to work with a async one. <2024-05-04>
let future = async move {
debug!("Asyncly started to send cmd");
// FIXME: The None here is definitely wrong <2024-05-03> // FIXME: The None here is definitely wrong <2024-05-03>
tx.send(Event::CommandEvent(cmd, None)); tx.send(Event::CommandEvent(cmd, None))
.await
.expect("I hope that this does not fail");
debug!("Done with the async send");
};
debug!("Starting runtime");
let handle = thread::spawn(|| {
// run the task a separate thread to avoid tokio having problems with the currently
// running runtime.
// PERFORMANCE(@soispha): Stating a new thread for each command is very bad. <2024-05-04>
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(future);
});
handle.join().expect("Shall never error");
debug!("Handled command");
});
if let Err(err) = error {
eprintln!("Catched a panic just before the c ffi: {:#?}", err);
// This will leave the terminal in a horrendous shape (as no destructors are run), but what
// can we do at this point beside that?
process::abort();
}
} }

View File

@ -1,4 +1,4 @@
use std::{mem, str::FromStr}; use std::str::FromStr;
use crate::{ use crate::{
app::{ app::{
@ -84,11 +84,12 @@ pub async fn handle(
Trinitrix::Api(api) => match api { Trinitrix::Api(api) => match api {
Api::exit => { Api::exit => {
send_status_output!("Terminating the application.."); send_status_output!("Terminating the application..");
warn!("Terminating the application");
EventStatus::Terminate EventStatus::Terminate
} }
Api::room_message_send { message } => { Api::room_message_send { message } => {
if let Some(room) = app.status.room_mut() { if let Some(room) = app.status.room_mut() {
room.send(message.clone()).await?; room.send(message.to_string()).await?;
send_status_output!("Sent message: `{}`", message); send_status_output!("Sent message: `{}`", message);
} else { } else {
// FIXME(@soispha): This should raise an error within lua, as it would // FIXME(@soispha): This should raise an error within lua, as it would
@ -99,8 +100,6 @@ pub async fn handle(
); );
} }
// NOTE(@soispha): This is temporary, until trixy can do it automatically <2024-05-03>
mem::forget(message);
EventStatus::Ok EventStatus::Ok
} }
Api::Ui(ui) => match ui { Api::Ui(ui) => match ui {
@ -140,9 +139,11 @@ pub async fn handle(
key, key,
callback, callback,
} => { } => {
mode.chars().for_each(|char| { info!("Will add a keymapping for '{}' in mode '{}'", key, mode);
mode.as_str().chars().for_each(|char| {
info!("Setting keymaping ('{}') for mode '{}'", key, char); info!("Setting keymaping ('{}') for mode '{}'", key, char);
let parsed_keys = key let parsed_keys = key
.as_str()
.parse::<Keys>() .parse::<Keys>()
.map_err(|err| { .map_err(|err| {
send_error_output!(err.to_string()); send_error_output!(err.to_string());
@ -172,8 +173,6 @@ pub async fn handle(
}; };
}); });
mem::forget(key);
mem::forget(mode);
EventStatus::Ok EventStatus::Ok
} }
// FIXME(@soispha): It would be nice to have these functions, but well.. // FIXME(@soispha): It would be nice to have these functions, but well..
@ -183,22 +182,20 @@ pub async fn handle(
}, },
Api::Raw(raw) => match raw { Api::Raw(raw) => match raw {
Raw::raise_error { error_message } => { Raw::raise_error { error_message } => {
send_error_output!(error_message); send_error_output!(error_message.to_string());
mem::forget(error_message);
EventStatus::Ok EventStatus::Ok
} }
Raw::display_output { output_message } => { Raw::display_output { output_message } => {
// TODO(@Soispha): This is only used to show the Lua command output to the user. // TODO(@Soispha): This is only used to show the Lua command output to the user.
// Lua commands already receive the output. This should probably be communicated // Lua commands already receive the output. This should probably be communicated
// better, should it? // better, should it?
send_status_output!(output_message); send_status_output!(output_message.to_string());
mem::forget(output_message);
EventStatus::Ok EventStatus::Ok
} }
Raw::send_input_unprocessed { input } => { Raw::send_input_unprocessed { input } => {
let output = match app.status.state() { let output = match app.status.state() {
State::Insert => { State::Insert => {
let key = Key::from_str(&input)?; let key = Key::from_str(input.as_str())?;
let cross_input: Event = key.try_into()?; let cross_input: Event = key.try_into()?;
app.ui app.ui
.message_compose .message_compose
@ -206,7 +203,7 @@ pub async fn handle(
EventStatus::Ok EventStatus::Ok
} }
State::Command => { State::Command => {
let key = Key::from_str(&input)?; let key = Key::from_str(input.as_str())?;
let cross_input: Event = key.try_into()?; let cross_input: Event = key.try_into()?;
app.ui app.ui
.cli .cli
@ -222,7 +219,6 @@ pub async fn handle(
pending_keys: _, pending_keys: _,
} => EventStatus::Ok, } => EventStatus::Ok,
}; };
mem::forget(input);
output output
} }
Raw::Private(private) => { Raw::Private(private) => {

View File

@ -32,7 +32,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
.send(Event::CommandEvent( .send(Event::CommandEvent(
Commands::Trinitrix(Trinitrix::Api(Api::Raw( Commands::Trinitrix(Trinitrix::Api(Api::Raw(
Raw::send_input_unprocessed { Raw::send_input_unprocessed {
input: key.to_string_repr(), input: key.to_string_repr().into(),
}, },
))), ))),
None, None,
@ -45,7 +45,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(
Commands::Trinitrix(Trinitrix::Api(Api::Raw(Raw::send_input_unprocessed { Commands::Trinitrix(Trinitrix::Api(Api::Raw(Raw::send_input_unprocessed {
input: converted_key.to_string_repr(), input: converted_key.to_string_repr().into(),
}))), }))),
None, None,
)) ))

View File

@ -4,7 +4,7 @@ pub mod listeners;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crate::app::{command_interface::Commands, status::State, App}; use crate::app::{command_interface::Commands, status::State, App};
use cli_log::trace; use cli_log::{trace, warn};
use crossterm::event::Event as CrosstermEvent; use crossterm::event::Event as CrosstermEvent;
use handlers::{command, input, lua_command, matrix, setup}; use handlers::{command, input, lua_command, matrix, setup};
@ -29,7 +29,13 @@ impl Event {
.await .await
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)), .with_context(|| format!("Failed to handle command event: `{:#?}`", event)),
Event::LuaCommand(lua_code) => Ok(EventStatus::Terminate), Event::LuaCommand(lua_code) => {
warn!(
"Got lua code to execute, but no exectuter is available:\n{}",
lua_code
);
Ok(EventStatus::Ok)
}
// lua_command::handle(app, lua_code.to_owned()) // lua_command::handle(app, lua_code.to_owned())
// .await // .await
// .with_context(|| format!("Failed to handle lua code: `{}`", lua_code)), // .with_context(|| format!("Failed to handle lua code: `{}`", lua_code)),

View File

@ -5,14 +5,21 @@ pub mod status;
use std::{ use std::{
collections::HashMap, collections::HashMap,
ffi::c_int,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::OnceLock, sync::OnceLock,
}; };
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
use cli_log::{info, warn}; use cli_log::{info, warn};
use crossterm::{
event::DisableMouseCapture,
execute,
terminal::{disable_raw_mode, LeaveAlternateScreen},
};
use directories::ProjectDirs; use directories::ProjectDirs;
use keymaps::trie::Node; use keymaps::trie::Node;
use libloading::{Library, Symbol};
use matrix_sdk::Client; use matrix_sdk::Client;
use tokio::sync::mpsc::{self, Sender}; use tokio::sync::mpsc::{self, Sender};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -86,7 +93,11 @@ impl App<'_> {
}) })
} }
pub async fn run(&mut self, cli_lua_config_file: Option<PathBuf>) -> Result<()> { pub async fn run(
&mut self,
cli_lua_config_file: Option<PathBuf>,
plugin_path: Option<PathBuf>,
) -> Result<()> {
// Spawn input event listener // Spawn input event listener
tokio::task::spawn(events::listeners::input::poll( tokio::task::spawn(events::listeners::input::poll(
self.tx.clone(), self.tx.clone(),
@ -121,6 +132,21 @@ impl App<'_> {
} }
} }
if let Some(plugin) = plugin_path {
info!("Loading plugin_main() from {}", plugin.display());
unsafe {
let lib = Library::new(plugin).context("Failed to load plugin")?;
let func: Symbol<unsafe fn() -> c_int> = lib
.get(b"plugin_main")
.context("Plugin does not have a 'plugin_main' symbol")?;
info!("Starting plugin");
let out = func();
info!("Plugin finished with: {}", out);
}
}
if self.account().is_err() { if self.account().is_err() {
info!("No saved sessions found -> jumping into setup"); info!("No saved sessions found -> jumping into setup");
self.setup().await?; self.setup().await?;

View File

@ -12,9 +12,12 @@ pub struct Args {
/// The subcommand to execute, default is start /// The subcommand to execute, default is start
pub subcommand: Option<Command>, pub subcommand: Option<Command>,
// #[arg(long, short)]
// /// Path to the Lua config file, executed instead of the normal one
// pub lua_config_file: Option<PathBuf>,
#[arg(long, short)] #[arg(long, short)]
/// Path to the Lua config file, executed instead of the normal one /// Path to a plugin to load. It must be a shared-object (.so)
pub lua_config_file: Option<PathBuf>, pub plugin_path: Option<PathBuf>,
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
pub enum Command { pub enum Command {

View File

@ -16,7 +16,9 @@ async fn main() -> anyhow::Result<()> {
match command { match command {
Command::Start {} => { Command::Start {} => {
let mut app = app::App::new()?; let mut app = app::App::new()?;
app.run(args.lua_config_file).await?;
// NOTE(@soispha): The `None` is temporary <2024-05-03>
app.run(None, args.plugin_path).await?;
} }
}; };
@ -24,6 +26,4 @@ async fn main() -> anyhow::Result<()> {
} }
// FIXME(@soispha): Re-exports for trixy, this should be configurable <2024-05-03> // FIXME(@soispha): Re-exports for trixy, this should be configurable <2024-05-03>
pub use crate::app::command_interface::handle_cmd;
pub use crate::app::command_interface::Commands;
pub use crate::app::command_interface::*; pub use crate::app::command_interface::*;