From 066dd78ccdf29de2add371dc146cbebe51dea9c4 Mon Sep 17 00:00:00 2001 From: Andrey Kutejko Date: Wed, 29 Jan 2025 20:04:36 +0100 Subject: [PATCH] Allow implementing a GAction interface --- examples/Cargo.toml | 4 + examples/gio_action_impl/action.rs | 89 ++++++ examples/gio_action_impl/main.rs | 22 ++ gio/src/subclass/action.rs | 417 +++++++++++++++++++++++++++++ gio/src/subclass/mod.rs | 2 + 5 files changed, 534 insertions(+) create mode 100644 examples/gio_action_impl/action.rs create mode 100644 examples/gio_action_impl/main.rs create mode 100644 gio/src/subclass/action.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c703b6bf524f..3126f24439c6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -51,3 +51,7 @@ path = "object_subclass/main.rs" [[bin]] name = "virtual_methods" path = "virtual_methods/main.rs" + +[[bin]] +name = "gio_action_impl" +path = "gio_action_impl/main.rs" diff --git a/examples/gio_action_impl/action.rs b/examples/gio_action_impl/action.rs new file mode 100644 index 000000000000..7862c5bffc94 --- /dev/null +++ b/examples/gio_action_impl/action.rs @@ -0,0 +1,89 @@ +use gio::{prelude::*, subclass::prelude::*}; + +mod imp { + use super::*; + use std::cell::OnceCell; + + #[derive(glib::Properties, Default)] + #[properties(wrapper_type = super::RenamedAction)] + pub struct RenamedAction { + #[property(get, construct_only)] + pub new_name: OnceCell, + + #[property(get, construct_only)] + pub action: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for RenamedAction { + const NAME: &'static str = "ExampleRenamedAction"; + type Type = super::RenamedAction; + type Interfaces = (gio::Action,); + } + + #[glib::derived_properties] + impl ObjectImpl for RenamedAction { + fn properties() -> &'static [glib::ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + if !self.delegate_set_property(id, value, pspec) { + self.derived_set_property(id, value, pspec); + } + } + + fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value { + self.delegate_get_property(id, pspec) + .unwrap_or_else(|| self.derived_property(id, pspec)) + } + } + + impl ActionImpl for RenamedAction { + fn name(&self) -> glib::GString { + self.obj().new_name() + } + + fn parameter_type(&self) -> Option { + self.obj().action().parameter_type() + } + + fn state_type(&self) -> Option { + self.obj().action().state_type() + } + + fn state_hint(&self) -> Option { + self.obj().action().state_hint() + } + + fn is_enabled(&self) -> bool { + self.obj().action().is_enabled() + } + + fn state(&self) -> Option { + self.obj().action().state() + } + + fn change_state(&self, value: glib::Variant) { + self.obj().action().change_state(&value); + } + + fn activate(&self, parameter: Option) { + self.obj().action().activate(parameter.as_ref()); + } + } +} + +glib::wrapper! { + pub struct RenamedAction(ObjectSubclass) + @implements gio::Action; +} + +impl RenamedAction { + pub fn new(name: &str, action: &impl IsA) -> Self { + glib::Object::builder() + .property("new-name", name) + .property("action", action) + .build() + } +} diff --git a/examples/gio_action_impl/main.rs b/examples/gio_action_impl/main.rs new file mode 100644 index 000000000000..ad9b72f95c65 --- /dev/null +++ b/examples/gio_action_impl/main.rs @@ -0,0 +1,22 @@ +mod action; + +use gio::prelude::*; + +fn main() { + let action = gio::SimpleAction::new("bark", Some(glib::VariantTy::STRING)); + action.connect_activate(|_, p| { + let target = p.unwrap().str().unwrap(); + println!("Woof, {}!", target); + }); + + let renamed_action = action::RenamedAction::new("meow", &action); + + let group = gio::SimpleActionGroup::new(); + group.add_action(&action); + group.add_action(&renamed_action); + + println!("actions = {:?}", group.list_actions()); + + group.activate_action("bark", Some(&"postman".to_variant())); + group.activate_action("meow", Some(&"milkman".to_variant())); +} diff --git a/gio/src/subclass/action.rs b/gio/src/subclass/action.rs new file mode 100644 index 000000000000..3a0c10c0bc37 --- /dev/null +++ b/gio/src/subclass/action.rs @@ -0,0 +1,417 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::sync::LazyLock; + +use glib::{ + ffi::{gboolean, GVariant, GVariantType}, + prelude::*, + subclass::prelude::*, + translate::*, +}; + +use crate::{ffi, Action}; + +pub trait ActionImpl: ObjectImpl + ObjectSubclass> { + fn name(&self) -> glib::GString { + self.parent_name() + } + + fn parameter_type(&self) -> Option { + self.parent_parameter_type() + } + + fn state_type(&self) -> Option { + self.parent_state_type() + } + + fn state_hint(&self) -> Option { + self.parent_state_hint() + } + + fn is_enabled(&self) -> bool { + self.parent_enabled() + } + + fn state(&self) -> Option { + self.parent_state() + } + + fn change_state(&self, value: glib::Variant) { + self.parent_change_state(value); + } + + fn activate(&self, parameter: Option) { + self.parent_activate(parameter); + } +} + +pub trait ActionImplExt: ActionImpl { + fn parent_name(&self) -> glib::GString { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_name + .expect("no parent \"get_name\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_parameter_type(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_parameter_type + .expect("no parent \"get_parameter_type\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_state_type(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state_type + .expect("no parent \"get_state_type\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_state_hint(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state_hint + .expect("no parent \"get_state_hint\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_enabled(&self) -> bool { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_enabled + .expect("no parent \"get_enabled\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + ret != 0 + } + } + + fn parent_state(&self) -> Option { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .get_state + .expect("no parent \"get_state\" implementation"); + let ret = func(self.obj().unsafe_cast_ref::().to_glib_none().0); + from_glib_none(ret) + } + } + + fn parent_change_state(&self, value: glib::Variant) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .change_state + .expect("no parent \"change_state\" implementation"); + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + value.to_glib_none().0, + ); + } + } + + fn parent_activate(&self, parameter: Option) { + unsafe { + let type_data = Self::type_data(); + let parent_iface = + type_data.as_ref().parent_interface::() as *const ffi::GActionInterface; + + let func = (*parent_iface) + .activate + .expect("no parent \"activate\" implementation"); + func( + self.obj().unsafe_cast_ref::().to_glib_none().0, + parameter.to_glib_none().0, + ); + } + } + + fn delegate_get_property( + &self, + prop_id: usize, + _pspec: &glib::ParamSpec, + ) -> Option { + let type_: glib::Type = self.obj().type_(); + let property = ActionProperty::from_type(type_, prop_id)?; + Some(match property { + ActionProperty::Name => self.name().to_value(), + ActionProperty::ParameterType => self.parameter_type().to_value(), + ActionProperty::StateType => self.state_type().to_value(), + ActionProperty::Enabled => self.is_enabled().to_value(), + ActionProperty::State => self.state().to_value(), + }) + } + + fn delegate_set_property( + &self, + _prop_id: usize, + _value: &glib::Value, + _pspec: &glib::ParamSpec, + ) -> bool { + false + } +} + +impl ActionImplExt for T {} + +static FIRST_PROPERTY_ID_QUARK: LazyLock = + LazyLock::new(|| glib::Quark::from_str("gtk-rs-subclass-action-first-prop")); + +fn set_first_prop(type_: glib::Type, first_prop: usize) { + let data = Box::new(first_prop); + unsafe { + glib::gobject_ffi::g_type_set_qdata( + type_.into_glib(), + FIRST_PROPERTY_ID_QUARK.into_glib(), + Box::into_raw(data) as *mut _, + ); + } +} + +fn get_first_prop(type_: glib::Type) -> Option { + unsafe { + let ptr = glib::gobject_ffi::g_type_get_qdata( + type_.into_glib(), + FIRST_PROPERTY_ID_QUARK.into_glib(), + ) as *mut usize; + if ptr.is_null() { + None + } else { + Some(*ptr) + } + } +} + +#[repr(C)] +enum ActionProperty { + Name = 0, + ParameterType, + StateType, + Enabled, + State, +} + +impl ActionProperty { + fn from_id(first_prop: usize, id: usize) -> Option { + match id.checked_sub(first_prop)? { + 0 => Some(Self::Name), + 1 => Some(Self::ParameterType), + 2 => Some(Self::StateType), + 3 => Some(Self::Enabled), + 4 => Some(Self::State), + _ => None, + } + } + + fn from_type(mut type_: glib::Type, id: usize) -> Option { + loop { + if let Some(first_prop) = get_first_prop(type_) { + break ActionProperty::from_id(first_prop, id); + } + type_ = type_.parent()?; + } + } +} + +unsafe impl IsImplementable for Action { + fn interface_init(iface: &mut glib::Interface) { + let instance_type = iface.instance_type(); + let iface = iface.as_mut(); + + iface.get_name = Some(action_get_name::); + iface.get_parameter_type = Some(action_get_parameter_type::); + iface.get_state_type = Some(action_get_state_type::); + iface.get_state_hint = Some(action_get_state_hint::); + iface.get_enabled = Some(action_get_enabled::); + iface.get_state = Some(action_get_state::); + iface.change_state = Some(action_change_state::); + iface.activate = Some(action_activate::); + + unsafe { + let class_ref = glib::object::Class::::from_type(instance_type).unwrap(); + let object_class = + class_ref.as_ref() as *const _ as *mut glib::gobject_ffi::GObjectClass; + + let mut first_prop = std::mem::MaybeUninit::uninit(); + let properties = glib::gobject_ffi::g_object_class_list_properties( + object_class, + first_prop.as_mut_ptr(), + ); + glib::ffi::g_free(properties as *mut _); + let first_prop = first_prop.assume_init() + 1; + + set_first_prop(instance_type, first_prop as usize); + + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::Name as u32, + c"name".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::ParameterType as u32, + c"parameter-type".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::StateType as u32, + c"state-type".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::Enabled as u32, + c"enabled".as_ptr() as *const _, + ); + glib::gobject_ffi::g_object_class_override_property( + object_class, + first_prop + ActionProperty::State as u32, + c"state".as_ptr() as *const _, + ); + } + } +} + +unsafe extern "C" fn action_get_name( + actionptr: *mut ffi::GAction, +) -> *const libc::c_char { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + let instance = imp.obj(); + static QUARK: LazyLock = + LazyLock::new(|| glib::Quark::from_str("gtk-rs-subclass-action-get-name")); + + if let Some(old_name_ptr) = instance.qdata::(*QUARK) { + old_name_ptr.as_ref().as_ptr() + } else { + instance.set_qdata(*QUARK, imp.name()); + instance + .qdata::(*QUARK) + .unwrap() + .as_ref() + .as_ptr() + } +} + +unsafe extern "C" fn action_get_parameter_type( + actionptr: *mut ffi::GAction, +) -> *const GVariantType { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + let instance = imp.obj(); + static QUARK: LazyLock = + LazyLock::new(|| glib::Quark::from_str("gtk-rs-subclass-action-get-parameter-type")); + + if let Some(prev) = instance.qdata::>(*QUARK) { + prev.as_ref().to_glib_none().0 + } else { + let parameter_type = imp.parameter_type(); + let parameter_type_ptr = parameter_type.to_glib_none().0; + instance.set_qdata(*QUARK, parameter_type); + parameter_type_ptr + } +} + +unsafe extern "C" fn action_get_state_type( + actionptr: *mut ffi::GAction, +) -> *const GVariantType { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + let instance = imp.obj(); + static QUARK: LazyLock = + LazyLock::new(|| glib::Quark::from_str("gtk-rs-subclass-action-get-state-type")); + + if let Some(prev) = instance.qdata::>(*QUARK) { + prev.as_ref().to_glib_none().0 + } else { + let state_type = imp.state_type(); + let state_type_ptr = state_type.to_glib_none().0; + instance.set_qdata(*QUARK, state_type); + state_type_ptr + } +} + +unsafe extern "C" fn action_get_state_hint( + actionptr: *mut ffi::GAction, +) -> *mut GVariant { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.state_hint().to_glib_full() +} + +unsafe extern "C" fn action_get_enabled(actionptr: *mut ffi::GAction) -> gboolean { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.is_enabled() as gboolean +} + +unsafe extern "C" fn action_get_state( + actionptr: *mut ffi::GAction, +) -> *mut GVariant { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + + imp.state().to_glib_full() +} + +unsafe extern "C" fn action_change_state( + actionptr: *mut ffi::GAction, + value: *mut GVariant, +) { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + let value: glib::Variant = from_glib_none(value); + + imp.change_state(value); +} + +unsafe extern "C" fn action_activate( + actionptr: *mut ffi::GAction, + parameterptr: *mut GVariant, +) { + let instance = &*(actionptr as *mut T::Instance); + let imp = instance.imp(); + let parameter: Option = from_glib_none(parameterptr); + + imp.activate(parameter); +} diff --git a/gio/src/subclass/mod.rs b/gio/src/subclass/mod.rs index 791d1752a8c8..a532bc74ca14 100644 --- a/gio/src/subclass/mod.rs +++ b/gio/src/subclass/mod.rs @@ -1,5 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. +mod action; mod action_group; mod action_map; mod application; @@ -19,6 +20,7 @@ pub mod prelude { pub use glib::subclass::prelude::*; pub use super::{ + action::{ActionImpl, ActionImplExt}, action_group::{ActionGroupImpl, ActionGroupImplExt}, action_map::{ActionMapImpl, ActionMapImplExt}, application::{ApplicationImpl, ApplicationImplExt},