use std::rc::Rc;
pub type RawSignalCallback = Box<dyn Fn(&[glib::Value]) -> Option<glib::Value>>;
pub fn route_signal(
obj: &impl glib::object::ObjectExt,
gtk_signal: &str,
actix_signal: &str,
target: impl IntoGenerateRoutingGtkHandler,
) -> Result<glib::SignalHandlerId, crate::Error> {
Ok(target
.into_generate_routing_gtk_handler()
.connect_local(obj, gtk_signal, actix_signal))
}
pub fn route_action(
action: &(impl glib::object::ObjectExt + gio::prelude::ActionExt),
target: impl IntoGenerateRoutingGtkHandler,
) -> Result<glib::SignalHandlerId, crate::Error> {
let signal = if action.state().is_some() {
"change-state"
} else {
"activate"
};
route_signal(action, signal, action.name().as_str(), target)
}
fn panic_if_signal_cannot_be_queued(signal_name: &str, parameters: &[glib::Value]) {
for (i, param) in parameters.iter().enumerate() {
let param_type = param.type_();
if param_type.name().ends_with("Context") {
panic!(
concat!(
"Signal {:?}'s param at position {} is a {}. ",
"Signals with context params cannot be invoked from inside the Actix runtime. ",
"Try running whatever triggered it with `woab::outside()` or `woab::spawn_outside()",
),
signal_name, i, param_type,
);
}
}
}
fn run_signal_routing_future(
future: impl core::future::Future<Output = Result<Result<Option<glib::Propagation>, crate::Error>, actix::MailboxError>> + 'static,
signal_name: &Rc<String>,
parameters: &[glib::Value],
) -> Option<glib::Value> {
match crate::try_block_on(future) {
Ok(result) => {
let result = result.unwrap().unwrap();
if let Some(propagation) = result {
use glib::value::ToValue;
Some(propagation.is_proceed().to_value())
} else {
None
}
}
Err(future) => {
panic_if_signal_cannot_be_queued(signal_name, parameters);
let signal_name = signal_name.clone();
actix::spawn(async move {
let result = future.await.unwrap().unwrap();
if let Some(result) = result {
panic!(
concat!(
"Signal {:?}, was invoked inside the Actix runtime and had to be queued, ",
"but it returned {:?} - which is not supported for queued signals. ",
"Try running whatever triggered it with `woab::outside()` or `woab::spawn_outside()",
),
signal_name.as_str(),
result,
);
}
});
None
}
}
}
#[doc(hidden)]
pub trait GenerateRoutingGtkHandler {
fn connect_local(&self, obj: &impl glib::object::ObjectExt, gtk_signal: &str, actix_signal: &str) -> glib::SignalHandlerId;
fn register_into_builder_rust_scope(&self, scope: >k4::BuilderRustScope, signal_name: &str);
}
fn route_with_tag_generate_impl<T: Clone + 'static>(
signal_name: &str,
tag: T,
recipient: actix::Recipient<crate::Signal<T>>,
) -> impl Fn(&[glib::Value]) -> Option<glib::Value> {
let signal_name = Rc::new(signal_name.to_owned());
move |parameters| {
let signal = crate::Signal::new(signal_name.clone(), parameters.to_owned(), tag.clone());
run_signal_routing_future(recipient.send(signal), &signal_name, parameters)
}
}
impl<T: Clone + 'static> GenerateRoutingGtkHandler for (T, actix::Recipient<crate::Signal<T>>) {
fn register_into_builder_rust_scope(&self, scope: >k4::BuilderRustScope, signal_name: &str) {
let (tag, recipient) = self.clone();
scope.add_callback(signal_name, route_with_tag_generate_impl(signal_name, tag, recipient));
}
fn connect_local(&self, obj: &impl glib::object::ObjectExt, gtk_signal: &str, actix_signal: &str) -> glib::SignalHandlerId {
let (tag, recipient) = self.clone();
obj.connect_local(gtk_signal, false, route_with_tag_generate_impl(actix_signal, tag, recipient))
}
}
#[doc(hidden)]
pub trait IntoGenerateRoutingGtkHandler {
type Generator: GenerateRoutingGtkHandler;
fn into_generate_routing_gtk_handler(self) -> Self::Generator;
}
impl<T: Clone + 'static> IntoGenerateRoutingGtkHandler for (T, actix::Recipient<crate::Signal<T>>) {
type Generator = Self;
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
self
}
}
impl IntoGenerateRoutingGtkHandler for actix::Recipient<crate::Signal> {
type Generator = ((), Self);
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
((), self)
}
}
impl<T: Clone + 'static, A: actix::Actor> IntoGenerateRoutingGtkHandler for (T, actix::Addr<A>)
where
A: actix::Actor,
A: actix::Handler<crate::Signal<T>>,
<A as actix::Actor>::Context: actix::dev::ToEnvelope<A, crate::Signal<T>>,
{
type Generator = (T, actix::Recipient<crate::Signal<T>>);
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
let (tag, actor) = self;
(tag, actor.recipient())
}
}
impl<A: actix::Actor> IntoGenerateRoutingGtkHandler for actix::Addr<A>
where
A: actix::Actor,
A: actix::Handler<crate::Signal>,
<A as actix::Actor>::Context: actix::dev::ToEnvelope<A, crate::Signal>,
{
type Generator = ((), actix::Recipient<crate::Signal>);
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
((), self.recipient())
}
}
#[derive(Default)]
pub struct NamespacedSignalRouter<T> {
targets: hashbrown::HashMap<String, NamespacedSignalRouterTarget<T>>,
}
#[derive(Clone)]
struct NamespacedSignalRouterTarget<T> {
recipient: actix::Recipient<crate::Signal<T>>,
strip_namespace: bool,
}
impl<T> NamespacedSignalRouter<T> {
fn add_target(&mut self, namespace: &str, target: NamespacedSignalRouterTarget<T>) {
match self.targets.entry(namespace.to_owned()) {
hashbrown::hash_map::Entry::Occupied(_) => {
panic!("Namespace {:?} is already routed", namespace);
}
hashbrown::hash_map::Entry::Vacant(entry) => {
entry.insert(target);
}
}
}
pub fn route_ns(mut self, namespace: &str, recipient: actix::Recipient<crate::Signal<T>>) -> Self {
self.add_target(
namespace,
NamespacedSignalRouterTarget {
recipient,
strip_namespace: false,
},
);
self
}
pub fn route_strip_ns(mut self, namespace: &str, recipient: actix::Recipient<crate::Signal<T>>) -> Self {
self.add_target(
namespace,
NamespacedSignalRouterTarget {
recipient,
strip_namespace: true,
},
);
self
}
pub fn route<A>(mut self, actor: actix::Addr<A>) -> Self
where
T: 'static,
A: actix::Actor,
A: actix::Handler<crate::Signal<T>>,
<A as actix::Actor>::Context: actix::dev::ToEnvelope<A, crate::Signal<T>>,
{
let namespace = core::any::type_name::<A>();
let namespace = namespace.split('<').next().unwrap(); let namespace = namespace.split("::").last().unwrap(); let namespace = namespace.split("; ").last().unwrap(); let namespace = namespace.split('&').last().unwrap(); self.add_target(
namespace,
NamespacedSignalRouterTarget {
recipient: actor.recipient(),
strip_namespace: true,
},
);
self
}
}
impl<T: Clone + 'static> NamespacedSignalRouter<T> {
fn generate_impl(&self, signal_name: &str, tag: T) -> impl Fn(&[glib::Value]) -> Option<glib::Value> {
let signal_namespace = {
let mut parts = signal_name.split("::");
if let Some(signal_namespace) = parts.next() {
if parts.next().is_none() {
panic!("Signal {:?} does not have a namespace", signal_name)
} else {
signal_namespace
}
} else {
panic!("Signal is empty")
}
};
let target = if let Some(target) = self.targets.get(signal_namespace) {
target.clone()
} else {
panic!("Unknown namespace {:?}", signal_namespace)
};
let signal_name = Rc::new(
if target.strip_namespace {
let (_, without_namespace) = signal_name.split_at(signal_namespace.len() + 2);
without_namespace
} else {
signal_name
}
.to_owned(),
);
let tag = tag.clone();
move |parameters| {
let signal = crate::Signal::new(signal_name.clone(), parameters.to_owned(), tag.clone());
run_signal_routing_future(target.recipient.send(signal), &signal_name, parameters)
}
}
}
impl<T: Clone + 'static> crate::GenerateRoutingGtkHandler for (T, NamespacedSignalRouter<T>) {
fn register_into_builder_rust_scope(&self, scope: >k4::BuilderRustScope, signal_name: &str) {
let (tag, router) = self;
scope.add_callback(signal_name, router.generate_impl(signal_name, tag.clone()));
}
fn connect_local(&self, obj: &impl glib::object::ObjectExt, gtk_signal: &str, actix_signal: &str) -> glib::SignalHandlerId {
let (tag, router) = self;
obj.connect_local(gtk_signal, false, router.generate_impl(actix_signal, tag.clone()))
}
}
impl<T: Clone + 'static> IntoGenerateRoutingGtkHandler for (T, NamespacedSignalRouter<T>) {
type Generator = Self;
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
self
}
}
impl IntoGenerateRoutingGtkHandler for NamespacedSignalRouter<()> {
type Generator = ((), Self);
fn into_generate_routing_gtk_handler(self) -> Self::Generator {
((), self)
}
}