[vlc-commits] [Git][videolan/vlc][master] 19 commits: rust: add vlcrs-macros crate for module!{} macro

Steve Lhomme (@robUx4) gitlab at videolan.org
Sat Jun 29 10:59:16 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
a1036731 by Loïc Branstett at 2024-06-29T10:44:26+00:00
rust: add vlcrs-macros crate for module!{} macro

This macro tries to do the same things as the one in C but in a Rust-ty
way.

Signed-off-by: Alexandre Janniaux <ajanni at videolabs.io>

Picked from main rust MR as-is, so it won't be compiling correctly.
Follow-up changes will add the crates to the workspace members after
adapting to the new tree.

The crate will provide the module!{} macro to define the plugin manifest
in a declarative and type-safe way, bringing the language part to create
new modules in Rust. Only the buildsystem part will be missing after
this crate is enabled.

- - - - -
053f4c38 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: Cargo.toml: use keys from Workspace

- - - - -
a054d5d8 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: fix for-loops used as if

cargo clippy was complaining that the loop would trigger only once.
Indeed, the for-loop is used to trigger iter() and return immediately
with an error at the first element.

Using if-let deconstructuring construct on the first() element achieve
the same feature while providing more semantics on what it is checking.

- - - - -
863f1891 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: remove superfluous format!()

The format!() call has no formatting argument, so returning an str slice
is enough.

- - - - -
d38c1070 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: remove superfluous reference

- - - - -
9a2d8a88 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: rename crate

- - - - -
6f7b912f by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
rust: add vlcrs-plugin crate

The vlcrs-plugin crate exports structures matching vlc_plugin.h, which
is ough to be included by modules directly or indirectly (through
vlcrs-macros) to define every parts needed to setup the module manifest.

The current state exports a minimal set required for the current
vlcrs-macro code to work correctly.

- - - - -
7a626bfc by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
modules: Cargo.toml: expose vlcrs-plugin

- - - - -
3afe5bec by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: adapt to vlcrs-plugin changes

vlcrs_core doesn't exist anymore since we've splitted the crates for
each matching header in vlccore, and some of the defines have been
renamed since we can provide a better name through context as opposed
to C code.

Some of the types don't exist, namely vlcrs-plugin::ModuleArgs, because
they depends on vlc_variable. This commit still changes those locations
so that we can remove the correct version while keeping the revert
possible on future commits.

- - - - -
213ba900 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-plugin: add Debug and TryFrom traits for ModuleProperties

Those will be used from the tests to display nicer names when parsing
the module manifest.

- - - - -
54981398 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macro: module: fix clippy warning

`param` was already a reference so there's no need to re-enforce a
reference here.

- - - - -
75c0955c by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: remove parameter assignment

This will require vlc_variable support, which is not merged yet. The
patch will be reverted as soon as vlc_variable can be used.

- - - - -
183d214e by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-plugin: add ModuleProtocol trait

The trait defines a protocol to be implemented by any "Loader" for the
"CapabilityTrait". Both terms will be defined in follow-up commits.

In particular, this trait enforce the module loaders to provide the
activation function and deactivation function, as well as providing
default values for the deactivation functions.

More in-depth experiments were done to extend this approach to the
capability trait, so that traits defining a capability and traits
defining the ModuleLoader interface are separated, but automatically
implementing a capability trait for module structures that would use
the capability interface requires the following kind of declaration:

    impl<T : SpecificCapabilityTrait> CapabilityProtocol for T
        where T : SpecificCapabilityTrait
    {
        type Activate = vlc_activate;
        type Deactivate = vlc_deactivate;
        type Loader = ModuleLoader<Self>;
    }

In this case, the implementation of the trait is done in crates exposing
the capability, for instance vlcrs-interface or an external crate
exposing an out-of-tree module, and the CapabilityProtocol is exposed by
the vlcrs-plugin crate, so it is considered as a generic implementation
of a foreign type, which is not allowed and would end up with the
following error:

    error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
      --> vlcrs-macros/tests/module.rs:52:6
       |
    52 | impl<T : SpecificCapabilityModule> CapabilityProtocol for T
       |      ^ type parameter `T` must be used as the type parameter for some local type
       |
       = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
       = note: only traits defined in the current crate can be implemented for a type parameter

    Some errors have detailed explanations: E0210, E0576.
    For more information about an error, try `rustc --explain E0210`.

- - - - -
fc186db0 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: add capability trait in module!{}

The capability trait will embed the type system which brings the
activation and deactivation function for the module, as well as some
future changes regarding capability checking at compile time.

Forcing to specify the capability trait enables a struct to be
implemented by multiple capabilities, and enable the module manifest
macro to know which capability trait is actually associated with which
capability specification, and will enable static checks to be performed
at compile time.

- - - - -
190ba5a5 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: use capability trait to load modules

The previous implementation from Loïc Branstett was using public
activation and deactivation functions that would be fetched from the
capability name. But it meant that capabilities could not have spaces in
them, removing the possibility to use "video filter" or requiring to
patch the name beforehand, and it was fit to a model with a single crate
so as to know where to find the activation functions.

Since the introduction of the binding generator in commit
bd1aaaf4ed5dc89761b0b65b24a31316341c830c, our crates are separated and
sorted to match each include file from vlccore.

In addition, it meant that it would not have been possible to write
out-of-tree capabilities while enjoying the type-safe features brought
by the bindings, as opposed to the current C API. Finally, it was also
not compatible with custom loading function type.

This commit brings a new indirection by allowing capability traits to
define `Loader`, `Activate` and `Deactivate` types in their definitions,
in an effort to keep a type-safe interface when __writing__ modules, as
well as being able to easily describe new module interfaces without
compromising on the current features of the module infrastructure.

       Module Implementor                      Core support

    - Implement capability              - Provides capability traits 
      traits                            - Provides module loader
                                          functions

The types `Activate` and `Deactivate` would allow to define custom
activation function type, such as those we find when using
vlc_module_load or manually probing using vlc_module_match, as opposed
to the common vlc_object_t-based module_need() activation function.

The type `Loader` allows to define a default associated type that would
provide the activation functions to the module manifest writer, so that
the correct interface is used. It effectively allows capability traits
to define custom activation function types, without having the module
implementor explicitely provide those functions. The module implementor
is still able to provide their own activation functions by providing
their own loader.

- - - - -
c76dff98 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: adapt documentation to changes on traits

The module!{} macro documentation should now expose an existing
dedicated capability trait and specify the loader. We don't want that to
be visible from the module!{} macro, so everything is hidden, but we
might want to replace the specific capability by an exisiting capability
trait when some will exist in the future, so that the documentation is
simplified and the example matches real life usage.

Otherwise, the doctest could be turned into `ignore` instead of
`no_build` but it means that the documentation could become out-of-date
with what the users are supposed to do.

- - - - -
2da6855c by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
rust: Cargo.toml: enable vlcrs-macros

After this commit, vlcrs-macros must pass the tests and doctests.

- - - - -
15d9c3b7 by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
vlcrs-macros: add test for module!{}

The tests are checking:
 - [x] whether the module manifest has wanted opcode generated
 - [ ] whether the module manifest is 100% correct
 - [ ] whether the arguments for the module manifest opcode are correct
 - [x] whether we can create modules with usual activation functions
 - [x] whether we can create modules with custom activation functions
 - [x] whether we can omit the deactivation function
 - [x] whether the activation function is correctly called
 - [x] whether the deactivation function is correctly called
 - [x] whether the infrastructure works for submodules

The test module.rs provides a simple test with normal interface to check
the module manifest with open and close functions.

The test module_multiple.rs is important to ensure the macro doesn't
generate code with duplicated identifiers, that would trigger errors
only when multiple modules are declared from a single manifest.

The test module_default.rs checks the minimal definition required for
core module loading implementation to define a new capability, using
the default functions as much as possible.

The test module_specific checks that we can use other signatures and
uses this feature to check that the activation and deactivation
functions are correctly called when using the module API.

- - - - -
0b3d497a by Alexandre Janniaux at 2024-06-29T10:44:26+00:00
modules: Cargo.toml: expose vlcrs-macros

- - - - -


13 changed files:

- modules/Cargo.toml
- src/rust/Cargo.toml
- src/rust/Makefile.am
- + src/rust/vlcrs-macros/Cargo.toml
- + src/rust/vlcrs-macros/src/lib.rs
- + src/rust/vlcrs-macros/src/module.rs
- + src/rust/vlcrs-macros/tests/module.rs
- + src/rust/vlcrs-macros/tests/module_default.rs
- + src/rust/vlcrs-macros/tests/module_multiple.rs
- + src/rust/vlcrs-macros/tests/module_specific.rs
- + src/rust/vlcrs-plugin/Cargo.toml
- + src/rust/vlcrs-plugin/src/lib.rs
- + src/rust/vlcrs-plugin/src/sys.rs


Changes:

=====================================
modules/Cargo.toml
=====================================
@@ -3,4 +3,6 @@ resolver = "2"
 members = []
 
 [workspace.dependencies]
+vlcrs-macros = { path = "../src/rust/vlcrs-macros" }
 vlcrs-messages = { path = "../src/rust/vlcrs-messages" }
+vlcrs-plugin = { path = "../src/rust/vlcrs-plugin" }


=====================================
src/rust/Cargo.toml
=====================================
@@ -1,6 +1,8 @@
 [workspace]
 members = [
+    "vlcrs-macros",
     "vlcrs-messages",
+    "vlcrs-plugin",
     "vlcrs-sys-generator"
 ]
 resolver = "2"


=====================================
src/rust/Makefile.am
=====================================
@@ -3,13 +3,18 @@ CARGO_LOG_DRIVER = env top_builddir="${abs_top_builddir}" \
                    $(abs_top_srcdir)/buildsystem/cargo-test.py \
                    --working-directory="${abs_top_srcdir}/src/rust/"
 
+vlcrs-macros.cargo:
 vlcrs-messages.cargo:
+vlcrs-plugin.cargo:
 vlcrs-utils.cargo:
 	cd $(top_srcdir)/src/rust/$(@:.cargo=) && env \
 		top_builddir="${abs_top_builddir}" \
 		cargo build
+
 if HAVE_RUST
 TESTS += \
+	vlcrs-macros.cargo \
 	vlcrs-messages.cargo \
+	vlcrs-plugin.cargo \
 	vlcrs-utils.cargo
 endif


=====================================
src/rust/vlcrs-macros/Cargo.toml
=====================================
@@ -0,0 +1,14 @@
+[package]
+name = "vlcrs-macros"
+edition = "2021"
+version.workspace = true
+license.workspace = true
+
+[lib]
+proc-macro = true
+
+[dependencies]
+vlcrs-plugin = { path = "../vlcrs-plugin" }
+quote = "1.0"
+syn = { version = "1.0", features = ["full"] }
+proc-macro2 = "1.0"


=====================================
src/rust/vlcrs-macros/src/lib.rs
=====================================
@@ -0,0 +1,143 @@
+//! vlcrs-core macros
+
+use proc_macro::TokenStream;
+
+mod module;
+
+/// Module macro
+///
+/// ```no_run
+/// # #![feature(associated_type_defaults)]
+/// # use vlcrs_macros::module;
+/// # use vlcrs_plugin::ModuleProtocol;
+/// # use std::ffi::{c_int, c_void};
+/// # type ActivateFunction = unsafe extern "C" fn() -> c_int;
+/// # type DeactivateFunction = unsafe extern "C" fn() -> c_void;
+/// # pub trait CapabilityTrait {
+/// #     type Activate = ActivateFunction;
+/// #     type Deactivate = DeactivateFunction;
+/// #     type Loader = CapabilityTraitLoader<Self>;
+/// # }
+/// # extern "C" fn activate() -> c_int{ 0 }
+/// # pub struct CapabilityTraitLoader<T: ?Sized> {
+/// #     _phantom : std::marker::PhantomData<T>
+/// # }
+/// # impl<T: ?Sized + CapabilityTrait> ModuleProtocol<
+/// #     T,
+/// #     ActivateFunction,
+/// #     DeactivateFunction>
+/// # for CapabilityTraitLoader<T> {
+/// #     fn activate_function() -> ActivateFunction { activate }
+/// #     fn deactivate_function() -> Option<DeactivateFunction> { None }
+/// # }
+/// struct MyModule {}
+/// impl CapabilityTrait for MyModule {}
+/// module! {
+///     type: MyModule (CapabilityTrait),
+///     shortname: "infrs",
+///     shortcuts: ["mp4", "MP4A"],
+///     description: "This a Rust Module - inflate-rs",
+///     help: "This is a dummy help text",
+///     category: INPUT_STREAM_FILTER,
+///     capability: "stream_filter" @ 330,
+///     #[prefix = "infrs"]
+///     params: {
+///         #[deprecated]
+///         my_bool: i64 {
+///             default: 5,
+///             range: 0..=6,
+///             text: "My bool",
+///             long_text: "Explain the purpose!",
+///         },
+///     }
+/// }
+/// ```
+///
+/// ## Parameters attribute
+///
+/// - `#[rgb]`
+/// - `#[font]`
+/// - `#[savefile]`
+/// - `#[loadfile]`
+/// - `#[password]`
+/// - `#[directory]`
+/// - `#[deprecated]`
+///
+/// There is also `section` attribute:
+/// `#![section(name = "My Section", description = "The description")]`
+///
+/// ## Complete example
+///
+/// ```no_run
+/// # #![feature(associated_type_defaults)]
+/// # use vlcrs_macros::module;
+/// # use vlcrs_plugin::ModuleProtocol;
+/// # use std::ffi::{c_int, c_void};
+/// # type ActivateFunction = unsafe extern "C" fn() -> c_int;
+/// # type DeactivateFunction = unsafe extern "C" fn() -> c_void;
+/// # pub trait CapabilityTrait {
+/// #     type Activate = ActivateFunction;
+/// #     type Deactivate = DeactivateFunction;
+/// #     type Loader = CapabilityTraitLoader<Self>;
+/// # }
+/// # extern "C" fn activate() -> c_int{ 0 }
+/// # pub struct CapabilityTraitLoader<T: ?Sized> {
+/// #     _phantom : std::marker::PhantomData<T>
+/// # }
+/// # impl<T: ?Sized + CapabilityTrait> ModuleProtocol<
+/// #     T,
+/// #     ActivateFunction,
+/// #     DeactivateFunction>
+/// # for CapabilityTraitLoader<T> {
+/// #     fn activate_function() -> ActivateFunction { activate }
+/// #     fn deactivate_function() -> Option<DeactivateFunction> { None }
+/// # }
+/// struct Inflate {}
+/// impl CapabilityTrait for Inflate {}
+/// module! {
+///     type: Inflate (CapabilityTrait),
+///     shortcuts: ["mp4", "MP4A"],
+///     shortname: "infrs",
+///     description: "This a Rust Module - inflate-rs",
+///     help: "This is a dummy help text",
+///     category: INPUT_STREAM_FILTER,
+///     capability: "stream_filter" @ 330,
+///     #[prefix = "infrs"]
+///     params: {
+///         #![section(name = "", description = "kk")]
+///         #[deprecated]
+///         my_bool: bool {
+///             default: false,
+///             text: "",
+///             long_text: "",
+///         },
+///         my_i32: i64 {
+///             default: 5,
+///             range: -2..=2,
+///             text: "",
+///             long_text: "",
+///         },
+///         my_f32: f32 {
+///             default: 5.,
+///             range: -2.0..=2.0,
+///             text: "",
+///             long_text: "",
+///         },
+///         my_str: str {
+///             default: "aaa",
+///             text: "",
+///             long_text: "",
+///         },
+///         #[loadfile]
+///         my_loadfile: str {
+///             default: "aaa",
+///             text: "",
+///             long_text: "",
+///         },
+///     }
+/// }
+/// ```
+#[proc_macro]
+pub fn module(input: TokenStream) -> TokenStream {
+    module::module(input)
+}


=====================================
src/rust/vlcrs-macros/src/module.rs
=====================================
@@ -0,0 +1,1119 @@
+//! Module macros implementation
+
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, quote_spanned, ToTokens};
+use syn::{
+    braced, bracketed, parenthesized, parse::Parse, parse_macro_input,
+    punctuated::Punctuated, spanned::Spanned, Attribute, Error, ExprRange,
+    Ident, Lit, LitByteStr, LitInt, LitStr, MetaNameValue, RangeLimits, Token
+};
+
+struct SectionInfo {
+    name: LitStr,
+    description: Option<LitStr>,
+}
+
+struct PrefixInfo {
+    prefix: LitStr,
+}
+
+struct CapabilityInfo {
+    capability: LitStr,
+    score: LitInt,
+}
+
+struct ParameterInfo {
+    name: Ident,
+    type_: Ident,
+    range: Option<ExprRange>,
+    default_: Lit,
+    text: LitStr,
+    long_text: LitStr,
+    prefix: Option<PrefixInfo>,
+    section: Option<SectionInfo>,
+    local_attrs: Vec<Attribute>,
+}
+
+struct ParametersInfo {
+    params: Punctuated<ParameterInfo, Token![,]>,
+}
+
+struct SubmoduleInfo {
+    submodule: ModuleInfo,
+}
+
+struct ModuleInfo {
+    type_: Ident,
+    trait_: Ident,
+    category: Ident,
+    capability: CapabilityInfo,
+    description: LitStr,
+    help: Option<LitStr>,
+    shortname: Option<LitStr>,
+    prefix: Option<PrefixInfo>,
+    params: Option<ParametersInfo>,
+    shortcuts: Option<Punctuated<LitStr, Token![,]>>,
+    submodules: Option<Punctuated<SubmoduleInfo, Token![,]>>,
+}
+
+impl Parse for ModuleInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let mut type_ = None;
+        let mut trait_ = None;
+        let mut category = None;
+        let mut capability = None;
+        let mut description = None;
+        let mut help = None;
+        let mut shortcuts = None;
+        let mut shortname = None;
+        let mut params = None;
+        let mut prefix = None;
+        let mut submodules = None;
+
+        while !input.is_empty() {
+            let global_attrs: Vec<Attribute> = input.call(Attribute::parse_inner)?;
+            let local_attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
+
+            if let Some(global_attr) = global_attrs.first() {
+                return Err(Error::new_spanned(
+                    global_attr,
+                    "no global arguments are expected here",
+                ));
+            }
+
+            // "type" is special because it's a keyword
+            if input.peek(Token![type]) {
+                input.parse::<Token![type]>()?;
+                input.parse::<Token![:]>()?;
+                type_ = Some(input.parse()?);
+                let parenthesis_content;
+                parenthesized!(parenthesis_content in input);
+                trait_ = Some(parenthesis_content.parse()?);
+                input.parse::<Token![,]>()?;
+                continue;
+            }
+
+            let key: Ident = input.parse()?;
+            let key_name = key.to_string();
+            let mut use_local_args = false;
+
+            match key_name.as_str() {
+                "capability" => {
+                    input.parse::<Token![:]>()?;
+                    capability = Some(input.parse()?);
+                }
+                "shortcuts" => {
+                    input.parse::<Token![:]>()?;
+
+                    let inner;
+                    bracketed!(inner in input);
+                    shortcuts = Some(inner.parse_terminated(<LitStr as syn::parse::Parse>::parse)?);
+                }
+                "shortname" => {
+                    input.parse::<Token![:]>()?;
+                    shortname = Some(input.parse()?);
+                }
+                "description" => {
+                    input.parse::<Token![:]>()?;
+                    description = Some(input.parse()?);
+                }
+                "help" => {
+                    input.parse::<Token![:]>()?;
+                    help = Some(input.parse()?);
+                }
+                "category" => {
+                    input.parse::<Token![:]>()?;
+                    category = Some(input.parse()?);
+                }
+                "params" => {
+                    use_local_args = true;
+
+                    if let [attr] = &local_attrs[..] {
+                        match attr.path.get_ident() {
+                            Some(ident) if ident == "prefix" => {
+                                prefix = Some(attr.parse_meta()?.try_into()?);
+                            }
+                            Some(ident) => {
+                                return Err(Error::new_spanned(
+                                    attr,
+                                    format!(
+                                        "`#[{ident}]` was not expected here, only `#[prefix = \"...\"]` is"
+                                    ),
+                                ));
+                            }
+                            _ => {
+                                return Err(Error::new_spanned(
+                                    attr,
+                                    "unexcepted, try `#[prefix = \"...\"]`",
+                                ));
+                            }
+                        }
+                    } else {
+                        return Err(Error::new_spanned(
+                            key,
+                            "one (and only one) attribute was expect here, try `#[prefix = \"...\"]`",
+                        ));
+                    }
+
+                    input.parse::<Token![:]>()?;
+                    params = Some(input.parse()?);
+                }
+                "submodules" => {
+                    input.parse::<Token![:]>()?;
+                    
+                    let inner;
+                    bracketed!(inner in input);
+                    let parsed_submodules = inner.parse_terminated(SubmoduleInfo::parse)?;
+
+                    for submodule in &parsed_submodules {
+                        if submodule.submodule.submodules.is_some() {
+                            return Err(Error::new_spanned(
+                                key,
+                                "nested submodules are not allowed",
+                            ));
+                        }
+                    }
+
+                    submodules = Some(parsed_submodules);
+                }
+                _ => {
+                    return Err(Error::new_spanned(key, format!("unknow {key_name} key")));
+                }
+            }
+
+            if !use_local_args {
+                if let Some(local_attr) = local_attrs.first() {
+                    return Err(Error::new_spanned(
+                        local_attr,
+                        "no local arguments are expected here",
+                    ));
+                }
+            }
+
+            if !input.is_empty() {
+                input.parse::<Token![,]>()?;
+            }
+        }
+
+        let Some(type_) = type_ else {
+            return Err(input.error("missing `type` key"));
+        };
+
+        let Some(trait_) = trait_ else {
+            return Err(input.error("invalid `type` key, missing module trait"));
+        };
+
+        let Some(capability) = capability else {
+            return Err(input.error("missing `capability` key"));
+        };
+
+        let Some(category) = category else {
+            return Err(input.error("missing `category` key"));
+        };
+
+        let Some(description) = description else {
+            return Err(input.error("missing `description` key"));
+        };
+
+        if params.is_some() && prefix.is_none() {
+            return Err(input.error("missing `#[prefix = \"...\"]` on `params`"));
+        }
+
+        Ok(ModuleInfo {
+            type_,
+            trait_,
+            category,
+            capability,
+            description,
+            help,
+            shortcuts,
+            shortname,
+            params,
+            prefix,
+            submodules,
+        })
+    }
+}
+
+impl Parse for ParametersInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let content;
+        braced!(content in input);
+        Ok(ParametersInfo {
+            params: content.parse_terminated(ParameterInfo::parse)?,
+        })
+    }
+}
+
+impl Parse for SubmoduleInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let content;
+        braced!(content in input);
+        Ok(SubmoduleInfo {
+            submodule: content.parse()?,
+        })
+    }
+}
+
+impl ParameterInfo {
+    fn has_local_attr(&self, name: &str) -> bool {
+        for attr in &self.local_attrs {
+            if attr.path.is_ident(name) {
+                return true;
+            }
+        }
+        false
+    }
+}
+
+impl TryFrom<syn::Meta> for PrefixInfo {
+    type Error = syn::Error;
+
+    fn try_from(value: syn::Meta) -> Result<Self, Self::Error> {
+        let name_value: MetaNameValue = match value {
+            syn::Meta::Path(_) | syn::Meta::List(_) => {
+                return Err(Error::new_spanned(value, "expected name-value arguments"))
+            }
+            syn::Meta::NameValue(nv) => nv,
+        };
+
+        let lit_str = match name_value.lit {
+            Lit::Str(lit_str) => lit_str,
+            Lit::ByteStr(_)
+            | Lit::Byte(_)
+            | Lit::Char(_)
+            | Lit::Int(_)
+            | Lit::Float(_)
+            | Lit::Bool(_)
+            | Lit::Verbatim(_) => return Err(Error::new_spanned(name_value.lit, "expected a str")),
+        };
+
+        Ok(PrefixInfo { prefix: lit_str })
+    }
+}
+
+impl Parse for SectionInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let mut name = None;
+        let mut description = None;
+
+        while !input.is_empty() {
+            let key: Ident = input.parse()?;
+
+            if key == "name" {
+                input.parse::<Token![=]>()?;
+                name = Some(input.parse()?);
+            } else if key == "description" {
+                input.parse::<Token![=]>()?;
+                description = Some(input.parse()?);
+            } else {
+                let key_name = key.to_string();
+                return Err(Error::new_spanned(key, format!("unknow {key_name} key")));
+            }
+
+            if !input.is_empty() {
+                input.parse::<Token![,]>()?;
+            }
+        }
+
+        let Some(name) = name else {
+            return Err(input.error("missing `name` key"));
+        };
+
+        Ok(SectionInfo { name, description })
+    }
+}
+
+impl Parse for CapabilityInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let capability = input.parse()?;
+        input.parse::<Token![@]>()?;
+        let score = input.parse()?;
+
+        Ok(CapabilityInfo { capability, score })
+    }
+}
+
+impl Parse for ParameterInfo {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        const LOCAL_ACCEPTED: [&str; 7] = [
+            "deprecated",
+            "rgb",
+            "font",
+            "savefile",
+            "loadfile",
+            "password",
+            "directory",
+        ];
+
+        let global_attrs: Vec<Attribute> = input.call(Attribute::parse_inner)?;
+        let local_attrs: Vec<Attribute> = input.call(Attribute::parse_outer)?;
+
+        let mut section = None;
+        let mut prefix = None;
+
+        for attr in &global_attrs {
+            // Replace with let_chains when stable
+            if attr.path.is_ident("section") {
+                section = Some(attr.parse_args::<SectionInfo>()?);
+            } else {
+                return Err(Error::new_spanned(
+                    attr,
+                    "only `#[section(name=\"\", description=\"\")]` is allowed here",
+                ));
+            }
+        }
+
+        for attr in &local_attrs {
+            // Replace with let_chains when stable
+            match attr.path.get_ident() {
+                Some(ident) if LOCAL_ACCEPTED.contains(&ident.to_string().as_str()) => {
+                    if ident == "prefix" {
+                        prefix = Some(attr.parse_meta()?.try_into()?);
+                    } else if ident != "deprecated" && !attr.tokens.is_empty() {
+                        return Err(Error::new_spanned(
+                            attr,
+                            format!("`#[{ident}]` doesn't take any argument"),
+                        ));
+                    }
+                }
+                _ => {
+                    return Err(Error::new_spanned(
+                        attr,
+                        "only `#[font]`, `#[savefile]`, `#[loadfile]`, `#[password]` \
+                        and `#[deprecated = ...]` are supported",
+                    ));
+                }
+            }
+        }
+
+        let name: Ident = input.parse()?;
+        input.parse::<Token![:]>()?;
+        let type_: Ident = input.parse()?;
+
+        if type_ != "i64" && type_ != "f32" && type_ != "bool" && type_ != "str" {
+            return Err(Error::new_spanned(
+                type_,
+                "only `i64`, `f32`, `bool` and `str` are supported",
+            ));
+        }
+
+        let inner;
+        braced!(inner in input);
+
+        let mut default_ = None;
+        let mut range = None;
+        let mut text = None;
+        let mut long_text = None;
+
+        while !inner.is_empty() {
+            // default is a kayword so we nned to special case it here
+            if inner.peek(Token![default]) {
+                inner.parse::<Token![default]>()?;
+                inner.parse::<Token![:]>()?;
+                default_ = Some(inner.parse()?);
+                inner.parse::<Token![,]>()?;
+                continue;
+            }
+
+            let key: Ident = inner.parse()?;
+            let key_name = key.to_string();
+
+            match key_name.as_str() {
+                "range" => {
+                    inner.parse::<Token![:]>()?;
+                    range = Some({
+                        let range = inner.parse::<ExprRange>()?;
+                        if !range.attrs.is_empty() {
+                            return Err(Error::new_spanned(range, "no attrs are supported here"));
+                        }
+                        if !matches!(range.limits, RangeLimits::Closed(_)) {
+                            return Err(Error::new_spanned(
+                                range,
+                                "only half-open ranges (ie. 5..=10) are supported here",
+                            ));
+                        }
+                        if range.from.is_none() || range.to.is_none() {
+                            return Err(Error::new_spanned(
+                                range,
+                                "only half-open ranges (ie. -200..=100) are supported here",
+                            ));
+                        }
+                        range
+                    });
+                }
+                "text" => {
+                    inner.parse::<Token![:]>()?;
+                    text = Some(inner.parse()?);
+                }
+                "long_text" => {
+                    inner.parse::<Token![:]>()?;
+                    long_text = Some(inner.parse()?);
+                }
+                _ => {
+                    return Err(Error::new_spanned(key, format!("unknow {key_name} key")));
+                }
+            }
+            if !inner.is_empty() {
+                inner.parse::<Token![,]>()?;
+            }
+        }
+
+        let Some(default_) = default_ else {
+            return Err(input.error("missing `default` key"));
+        };
+
+        let Some(text) = text else {
+            return Err(input.error("missing `text` key"));
+        };
+
+        let Some(long_text) = long_text else {
+            return Err(input.error("missing `long_text` key"));
+        };
+
+        Ok(ParameterInfo {
+            name,
+            type_,
+            range,
+            default_,
+            text,
+            long_text,
+            prefix,
+            section,
+            local_attrs,
+        })
+    }
+}
+
+macro_rules! tt_c_str {
+    ($span:expr => $value:expr) => {{
+        LitByteStr::new(&format!("{}\0", $value).into_bytes(), $span).into_token_stream()
+    }};
+}
+
+fn vlc_param_name(module_info: &ModuleInfo, param: &ParameterInfo) -> String {
+    let mut name = if let Some(prefix) = &param.prefix {
+        prefix.prefix.value()
+    } else if let Some(prefix) = &module_info.prefix {
+        prefix.prefix.value()
+    } else {
+        unreachable!();
+    };
+
+    if !name.ends_with("_") && !name.ends_with("-") {
+        name.push('-');
+    }
+
+    name.push_str(&param.name.to_string().replace("_", "-"));
+
+    name
+}
+
+#[allow(unused_variables)]
+fn generate_module_code(module_info: &ModuleInfo) -> TokenStream2 {
+    let ModuleInfo {
+        type_,
+        trait_,
+        category,
+        description,
+        help,
+        shortcuts,
+        shortname,
+        prefix,
+        params,
+        capability: CapabilityInfo { capability, score },
+        submodules,
+    } = module_info;
+
+    // TODO: Improve this with some kind environment variable passed by the build system
+    // like what is done for the C side.
+    let name = format!("{}-rs", type_.to_string().to_lowercase());
+
+    let description_with_nul = tt_c_str!(description.span() => description.value());
+    let name_with_nul = tt_c_str!(type_.span() => name);
+
+    let module = Ident::new(&capability.value(), capability.span());
+    let capability_with_nul = tt_c_str!(capability.span() =>
+                                        capability.value());
+
+    let module_entry_help = help.as_ref().map(|help| {
+        let help_with_nul = tt_c_str!(help.span() => help.value());
+        quote! {
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_HELP as _,
+                    #help_with_nul,
+                )
+            } != 0
+            {
+                return -1;
+            }
+        }
+    });
+
+    let module_entry_shortname = shortname.as_ref().map(|shortname| {
+        let shortname_with_nul = tt_c_str!(shortname.span() => shortname.value());
+        quote! {
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_SHORTNAME as _,
+                    #shortname_with_nul,
+                )
+            } != 0
+            {
+                return -1;
+            }
+        }
+    });
+
+    let module_entry_shortcuts = shortcuts.as_ref().map(|shortcuts| {
+        let shortcuts_with_nul: Vec<_> = shortcuts
+            .into_iter()
+            .map(|shortcut| tt_c_str!(shortcut.span() => shortcut.value()))
+            .collect();
+
+        let shortcuts_with_nul_len = shortcuts_with_nul.len();
+        quote! {
+            {
+                const SHORCUTS: [*const [u8]; #shortcuts_with_nul_len] = [#(#shortcuts_with_nul),*];
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        module as _,
+                        ::vlcrs_plugin::ModuleProperties::MODULE_SHORTCUT as _,
+                        #shortcuts_with_nul_len,
+                        SHORCUTS.as_ptr(),
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+            }
+        }
+    });
+
+    let vlc_entry_config_subcategory = {
+        quote! {
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    ::std::ptr::null_mut(),
+                    ::vlcrs_plugin::ModuleProperties::CONFIG_CREATE as _,
+                    ::vlcrs_plugin::ConfigModule::SUBCATEGORY as i64,
+                    &mut config as *mut *mut ::vlcrs_plugin::vlc_param,
+                )
+            } != 0
+            {
+                return -1;
+            }
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    config as _,
+                    ::vlcrs_plugin::ModuleProperties::CONFIG_VALUE as _,
+                    ::vlcrs_plugin::ConfigSubcategory::#category as i64,
+                )
+            } != 0
+            {
+                return -1;
+            }
+        }
+    };
+
+    let vlc_entry_config_params = params.as_ref().map(|params| {
+        let params = params.params.iter().map(|param| {
+            let name = vlc_param_name(module_info, param);
+            let name_with_nul = tt_c_str!(param.name.span() => name);
+            let text_with_nul = tt_c_str!(param.text.span() => param.text.value());
+            let long_text_with_nul = tt_c_str!(param.long_text.span() => param.long_text.value());
+            let (
+                has_rgb,
+                has_font,
+                has_savefile,
+                has_loadfile,
+                has_password,
+                has_directory,
+                has_deprecated,
+            ) = (
+                param.has_local_attr("rgb"),
+                param.has_local_attr("font"),
+                param.has_local_attr("savefile"),
+                param.has_local_attr("loadfile"),
+                param.has_local_attr("password"),
+                param.has_local_attr("directory"),
+                param.has_local_attr("deprecated"),
+            );
+
+            let section = if let Some(section) = &param.section {
+                let name_with_nul = tt_c_str!(section.name.span() => section.name.value());
+                let description_with_nul = if let Some(description) = &section.description {
+                    tt_c_str!(description.span() => description.value())
+                } else {
+                    quote! { ::std::ptr::null_mut() }
+                };
+
+                Some(quote! {
+                    if unsafe {
+                        vlc_set(
+                            opaque,
+                            ::std::ptr::null_mut(),
+                            ::vlcrs_plugin::ModuleProperties::CONFIG_CREATE as _,
+                            ::vlcrs_plugin::ConfigModule::SECTION as i64,
+                            &mut config as *mut *mut ::vlcrs_plugin::vlc_param,
+                        )
+                    } != 0
+                    {
+                        return -1;
+                    }
+                    if unsafe {
+                        vlc_set(
+                            opaque,
+                            config.cast(),
+                            ::vlcrs_plugin::ModuleProperties::CONFIG_DESC as _,
+                            #name_with_nul,
+                            #description_with_nul,
+                        )
+                    } != 0
+                    {
+                        return -1;
+                    }
+                })
+            } else {
+                None
+            };
+
+            let (value, item_type, range_type) = if param.type_ == "i64" {
+                let value = {
+                    let default_ = &param.default_;
+                    quote_spanned! {default_.span()=>
+                        ::std::convert::Into::<i64>::into(#default_)
+                    }
+                };
+
+                let item_type = if has_rgb {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_RGB }
+                } else {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_INTEGER }
+                };
+                let range_type = Some(quote! { i64 });
+
+                (value, item_type, range_type)
+            } else if param.type_ == "f32" {
+                let value = {
+                    let default_ = &param.default_;
+                    quote_spanned! {default_.span()=>
+                        ::std::convert::Into::<::std::ffi::c_double>::into(#default_)
+                    }
+                };
+                let item_type =
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_FLOAT };
+                let range_type = Some(quote! { ::std::ffi::c_double });
+
+                (value, item_type, range_type)
+            } else if param.type_ == "bool" {
+                let value = {
+                    let default_ = &param.default_;
+                    quote_spanned! {default_.span()=>
+                        ::std::convert::Into::<i64>::into(#default_)
+                    }
+                };
+                let item_type =
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_BOOL };
+
+                (value, item_type, None)
+            } else if param.type_ == "str" {
+                let value = match param.default_ {
+                    Lit::Str(ref default_) => tt_c_str!(default_.span() => default_.value()),
+                    Lit::ByteStr(_)
+                    | Lit::Byte(_)
+                    | Lit::Char(_)
+                    | Lit::Int(_)
+                    | Lit::Float(_)
+                    | Lit::Bool(_)
+                    | Lit::Verbatim(_) => unreachable!(),
+                };
+
+                let item_type = if has_font {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_FONT }
+                } else if has_savefile {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_SAVEFILE }
+                } else if has_loadfile {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_LOADFILE }
+                } else if has_password {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_PASSWORD }
+                } else if has_directory {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_DIRECTORY }
+                } else {
+                    quote! { ::vlcrs_plugin::ConfigModule::ITEM_STRING }
+                };
+
+                (value, item_type, None)
+            } else {
+                unreachable!();
+            };
+
+            let setup = quote! {
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        ::std::ptr::null_mut(),
+                        ::vlcrs_plugin::ModuleProperties::CONFIG_CREATE as _,
+                        #item_type as i64,
+                        &mut config as *mut *mut ::vlcrs_plugin::vlc_param,
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        config.cast(),
+                        ::vlcrs_plugin::ModuleProperties::CONFIG_DESC as _,
+                        #text_with_nul,
+                        #long_text_with_nul,
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        config.cast(),
+                        ::vlcrs_plugin::ModuleProperties::CONFIG_NAME as _,
+                        #name_with_nul,
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        config.cast(),
+                        ::vlcrs_plugin::ModuleProperties::CONFIG_VALUE as _,
+                        #value,
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+            };
+
+            let deprecated = if has_deprecated {
+                Some(quote! {
+                    if unsafe {
+                        vlc_set(
+                            opaque,
+                            config.cast(),
+                            ::vlcrs_plugin::ModuleProperties::CONFIG_REMOVED as _,
+                        )
+                    } != 0
+                    {
+                        return -1;
+                    }
+                })
+            } else {
+                None
+            };
+
+            let range = if has_rgb {
+                // TODO: check no other range specified
+                Some(quote! {
+                    if unsafe {
+                        vlc_set(
+                            opaque,
+                            config.cast(),
+                            ::vlcrs_plugin::ModuleProperties::CONFIG_RANGE as _,
+                            0,
+                            0xFFFFFF
+                        )
+                    } != 0
+                    {
+                        return -1;
+                    }
+                })
+            } else if let Some(range) = &param.range {
+                if let Some(range_type) = range_type {
+                    let from = match &range.from {
+                        Some(from) => quote_spanned! {from.span()=>
+                            ::std::convert::Into::<#range_type>::into(#from)
+                        },
+                        None => unreachable!(),
+                    };
+                    let to = match &range.to {
+                        Some(to) => quote_spanned! {to.span()=>
+                            ::std::convert::Into::<#range_type>::into(#to)
+                        },
+                        None => unreachable!(),
+                    };
+                    Some(quote! {
+                        if unsafe {
+                            vlc_set(
+                                opaque,
+                                config.cast(),
+                                ::vlcrs_plugin::ModuleProperties::CONFIG_RANGE as _,
+                                #from,
+                                #to
+                            )
+                        } != 0
+                        {
+                            return -1;
+                        }
+                    })
+                } else {
+                    unreachable!("this type doesn't support ranges")
+                }
+            } else {
+                None
+            };
+
+            quote! {
+                #section
+                #setup
+                #deprecated
+                #range
+            }
+        });
+
+        quote! {
+            #(#params)*
+        }
+    });
+
+    // Ensure each submodule has uniquely named open/close functions
+    // to prevent any naming conflicts.
+
+    let module_open = format!("{}-open", type_);
+    let module_close = format!("{}-close", type_);
+
+    let module_open_with_nul = tt_c_str!(type_.span() => module_open);
+    let module_close_with_nul = tt_c_str!(type_.span() => module_close);
+
+    quote! {
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_CAPABILITY as _,
+                    #capability_with_nul,
+                )
+            } != 0 {
+                return -1;
+            }
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_SCORE as _,
+                    ::std::convert::Into::<i32>::into(#score),
+                )
+            } != 0 {
+                return -1;
+            }
+
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_DESCRIPTION as _,
+                    #description_with_nul,
+                )
+            } != 0
+            {
+                return -1;
+            }
+            #module_entry_help
+            #module_entry_shortname
+            #module_entry_shortcuts
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_CB_OPEN as _,
+                    #module_open_with_nul,
+                    unsafe {
+                        std::mem::transmute::<
+                            <#type_ as #trait_>::Activate,
+                            *mut std::ffi::c_void
+                        >(<#type_ as #trait_>::Loader::activate_function())
+                    }
+                )
+            } != 0
+            {
+                return -1;
+            }
+
+            if <#type_ as #trait_>::Loader::deactivate_function() != None {
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        module as _,
+                        ::vlcrs_plugin::ModuleProperties::MODULE_CB_CLOSE as _,
+                        #module_close_with_nul,
+                        unsafe {
+                            std::mem::transmute::<
+                                <#type_ as #trait_>::Deactivate,
+                                *mut std::ffi::c_void
+                            >(<#type_ as #trait_>::Loader::deactivate_function().unwrap())
+                        }
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+            }
+
+            #vlc_entry_config_subcategory
+            #vlc_entry_config_params
+        }
+}
+
+pub fn module(input: TokenStream) -> TokenStream {
+    let module_info = parse_macro_input!(input as ModuleInfo);
+
+    // TODO: Improve this with some kind environment variable passed by the build system
+    // like what is done for the C side.
+    let name = format!("{}-rs", module_info.type_.to_string().to_lowercase());
+    let name_len = name.len() + 1;
+
+    let name_with_nul = tt_c_str!(module_info.type_.span() => name);
+
+    let module_name = quote! {
+        #[used]
+        #[no_mangle]
+        #[doc(hidden)]
+        pub static vlc_module_name: &[u8; #name_len] = #name_with_nul;
+    };
+
+    // Copied from #define VLC_API_VERSION_STRING in include/vlc_plugin.h
+    let entry_api_version = quote! {
+        #[no_mangle]
+        #[doc(hidden)]
+        extern "C" fn vlc_entry_api_version() -> *const u8 {
+            b"4.0.6\0".as_ptr()
+        }
+    };
+
+    let entry_copyright = quote! {
+        #[no_mangle]
+        #[doc(hidden)]
+        extern "C" fn vlc_entry_copyright() -> *const u8 {
+            ::vlcrs_plugin::VLC_COPYRIGHT_VIDEOLAN.as_ptr()
+        }
+    };
+
+    let entry_license = quote! {
+        #[no_mangle]
+        #[doc(hidden)]
+        extern "C" fn vlc_entry_license() -> *const u8 {
+            ::vlcrs_plugin::VLC_LICENSE_LGPL_2_1_PLUS.as_ptr()
+        }
+    };
+
+    let type_params = module_info.params.as_ref().map(|params| {
+        let struct_name = Ident::new(&format!("{}Args", module_info.type_), module_info.type_.span());
+
+        let params_def = params.params.iter().map(|param| {
+            let rust_name = &param.name;
+            let ident_string = Ident::new("String", param.type_.span());
+            let rust_type = if param.type_ == "str" {
+                &ident_string
+            } else {
+                &param.type_
+            };
+
+            quote! {
+                #rust_name: #rust_type,
+            }
+        });
+
+        quote! {
+            #[derive(Debug, PartialEq)]
+            struct #struct_name {
+                #(#params_def)*
+            }
+        }
+    });
+
+    let module_entry_configs = generate_module_code(&module_info);
+
+    let submodules_entry = module_info.submodules.as_ref().map(|submodule_info| {
+        let submodules = submodule_info.iter().map(|submodule_info| {
+            let submodule_entry_configs = generate_module_code(&submodule_info.submodule);
+
+            quote! {
+                if unsafe {
+                    vlc_set(
+                        opaque,
+                        module as _,
+                        ::vlcrs_plugin::ModuleProperties::MODULE_CREATE as _,
+                        &mut module as *mut *mut ::vlcrs_plugin::module_t,
+                    )
+                } != 0
+                {
+                    return -1;
+                }
+                #submodule_entry_configs
+            }
+        });
+
+        quote! {
+            #(#submodules)*
+        }
+    });
+
+    let module_entry = quote! {
+        #[no_mangle]
+        #[doc(hidden)]
+        extern "C" fn vlc_entry(
+            vlc_set: ::vlcrs_plugin::sys::vlc_set_cb,
+            opaque: *mut ::std::ffi::c_void,
+        ) -> i32 {
+            let mut module: *mut ::vlcrs_plugin::module_t = ::std::ptr::null_mut();
+            let mut config: *mut ::vlcrs_plugin::vlc_param = ::std::ptr::null_mut();
+
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    ::std::ptr::null_mut(),
+                    ::vlcrs_plugin::ModuleProperties::MODULE_CREATE as _,
+                    &mut module as *mut *mut ::vlcrs_plugin::module_t,
+                )
+            } != 0
+            {
+                return -1;
+            }
+            if unsafe {
+                vlc_set(
+                    opaque,
+                    module as _,
+                    ::vlcrs_plugin::ModuleProperties::MODULE_NAME as _,
+                    #name_with_nul,
+                )
+            } != 0
+            {
+                return -1;
+            }
+            #module_entry_configs
+            #submodules_entry
+            0
+        }
+    };
+
+    let expanded = quote! {
+        #type_params
+        #entry_api_version
+        #entry_license
+        #entry_copyright
+        #module_name
+        #module_entry
+    };
+    TokenStream::from(expanded)
+}


=====================================
src/rust/vlcrs-macros/tests/module.rs
=====================================
@@ -0,0 +1,104 @@
+//
+// Copyright (C) 2024      Alexandre Janniaux <ajanni at videolabs.io>
+//
+
+#![feature(c_variadic)]
+#![feature(associated_type_defaults)]
+#![feature(extern_types)]
+#![feature(fn_ptr_trait)]
+
+mod test_common;
+use crate::test_common::TestContext;
+
+use vlcrs_macros::module;
+
+use std::ffi::c_int;
+use std::marker::PhantomData;
+
+use vlcrs_plugin::{vlc_activate, vlc_deactivate};
+
+unsafe extern "C"
+fn activate_test<T: SpecificCapabilityModule>(_obj: *mut vlcrs_plugin::vlc_object_t) -> c_int
+{
+    0
+}
+
+unsafe extern "C"
+fn deactivate_test<T: SpecificCapabilityModule>(_obj: *mut vlcrs_plugin::vlc_object_t)
+{}
+
+use vlcrs_plugin::ModuleProtocol;
+
+pub struct ModuleLoader<T> { _phantom: PhantomData<T> }
+impl<T> ModuleProtocol<T, vlc_activate, vlc_deactivate> for ModuleLoader<T>
+    where T: SpecificCapabilityModule
+{
+    fn activate_function() -> vlc_activate
+    {
+        activate_test::<T>
+    }
+
+    fn deactivate_function() -> Option<vlc_deactivate>
+    {
+        Some(deactivate_test::<T>)
+    }
+}
+
+/* Implement dummy module */
+pub trait SpecificCapabilityModule : Sized {
+    type Activate = vlc_activate;
+    type Deactivate = vlc_deactivate;
+
+    type Loader = ModuleLoader<Self>;
+
+    fn open();
+}
+pub struct TestModule;
+impl SpecificCapabilityModule for TestModule {
+    fn open() {
+        todo!()
+    }
+}
+
+module! {
+    type: TestModule (SpecificCapabilityModule),
+    capability: "video_filter" @ 0,
+    category: VIDEO_VFILTER,
+    description: "A new module",
+    shortname: "mynewmodule",
+    shortcuts: ["mynewmodule_filter"],
+}
+
+#[test]
+fn test_module_load_common_activate()
+{
+    use vlcrs_plugin::ModuleProperties;
+
+    let mut context = TestContext::<vlc_activate> {
+        command_cursor: 0,
+        commands: vec![
+            ModuleProperties::MODULE_CREATE,
+            ModuleProperties::MODULE_NAME,
+            ModuleProperties::MODULE_CAPABILITY,
+            ModuleProperties::MODULE_SCORE,
+            ModuleProperties::MODULE_DESCRIPTION,
+            ModuleProperties::MODULE_SHORTNAME,
+            ModuleProperties::MODULE_SHORTCUT,
+            ModuleProperties::MODULE_CB_OPEN,
+            ModuleProperties::MODULE_CB_CLOSE,
+            ModuleProperties::CONFIG_CREATE,
+            ModuleProperties::CONFIG_VALUE,
+        ],
+        open_cb: None,
+        close_cb: None,
+    };
+    let ret = test_common::load_manifest(&mut context, vlc_entry);
+    assert_eq!(ret, 0);
+    assert_ne!(context.open_cb, None);
+    assert_ne!(context.close_cb, None);
+
+    unsafe {
+        context.open_cb.unwrap()(std::ptr::null_mut());
+        context.close_cb.unwrap()(std::ptr::null_mut());
+    }
+}


=====================================
src/rust/vlcrs-macros/tests/module_default.rs
=====================================
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2024      Alexandre Janniaux <ajanni at videolabs.io>
+//
+
+#![feature(c_variadic)]
+#![feature(associated_type_defaults)]
+#![feature(extern_types)]
+#![feature(fn_ptr_trait)]
+
+mod test_common;
+
+use vlcrs_macros::module;
+
+use std::ffi::c_int;
+use std::marker::PhantomData;
+use vlcrs_plugin::{ModuleProtocol,vlc_activate};
+
+unsafe extern "C"
+fn activate_filter(_obj: *mut vlcrs_plugin::vlc_object_t) -> c_int
+{
+    0
+}
+
+use crate::test_common::TestContext;
+
+//
+// Create an implementation loader for the TestFilterCapability
+//
+pub struct FilterModuleLoader<T> {
+    _phantom: PhantomData<T>
+}
+
+///
+/// Signal the core that we can load modules with this loader
+///
+impl<T> ModuleProtocol<T, vlc_activate> for FilterModuleLoader<T>
+    where T: TestNoDeactivateCapability
+{
+    fn activate_function() -> vlc_activate
+    {
+        activate_filter
+    }
+}
+
+/* Implement dummy module capability */
+pub trait TestNoDeactivateCapability : Sized {
+    type Activate = vlc_activate;
+    type Deactivate = *mut ();
+
+    type Loader = FilterModuleLoader<Self>;
+}
+
+///
+/// Create a dummy module using this capability
+///
+pub struct TestModule;
+impl TestNoDeactivateCapability for TestModule {}
+
+//
+// Define a module manifest using this module capability
+// and this module.
+//
+module! {
+    type: TestModule (TestNoDeactivateCapability),
+    capability: "video_filter" @ 0,
+    category: VIDEO_VFILTER,
+    description: "A new module",
+    shortname: "mynewmodule",
+    shortcuts: ["mynewmodule_filter"],
+}
+
+//
+// This test uses the defined capability and module from above
+// and tries to load the manifest and open an instance of the
+// module.
+//
+#[test]
+fn test_module_load_default_deactivate()
+{
+    use vlcrs_plugin::ModuleProperties;
+    let mut context = TestContext::<vlc_activate> {
+        command_cursor: 0,
+        commands: vec![
+            ModuleProperties::MODULE_CREATE,
+            ModuleProperties::MODULE_NAME,
+            ModuleProperties::MODULE_CAPABILITY,
+            ModuleProperties::MODULE_SCORE,
+            ModuleProperties::MODULE_DESCRIPTION,
+            ModuleProperties::MODULE_SHORTNAME,
+            ModuleProperties::MODULE_SHORTCUT,
+            ModuleProperties::MODULE_CB_OPEN,
+            ModuleProperties::CONFIG_CREATE,
+            ModuleProperties::CONFIG_VALUE,
+        ],
+        open_cb: None,
+        close_cb: None,
+    };
+    let ret = test_common::load_manifest(&mut context, vlc_entry);
+    assert_eq!(ret, 0);
+    assert_ne!(context.open_cb, None);
+    assert_eq!(context.close_cb, None);
+}


=====================================
src/rust/vlcrs-macros/tests/module_multiple.rs
=====================================
@@ -0,0 +1,160 @@
+//
+// Copyright (C) 2024      Alexandre Janniaux <ajanni at videolabs.io>
+//
+
+#![feature(c_variadic)]
+#![feature(associated_type_defaults)]
+#![feature(extern_types)]
+#![feature(fn_ptr_trait)]
+
+mod test_common;
+
+use vlcrs_macros::module;
+
+use std::ffi::c_int;
+use std::marker::PhantomData;
+use vlcrs_plugin::ModuleProtocol;
+
+extern {
+    // Create a dummy different type to change the activation function.
+    #[allow(non_camel_case_types)]
+    pub type vlc_filter_t;
+}
+
+#[allow(non_camel_case_types)]
+type vlc_filter_activate = unsafe extern "C" fn (_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int;
+
+unsafe extern "C"
+fn activate_filter<T: TestFilterCapability>(_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int
+{
+    T::open(_obj, valid);
+    0
+}
+
+use crate::test_common::TestContext;
+
+//
+// Create an implementation loader for the TestFilterCapability
+//
+pub struct FilterModuleLoader<T> {
+    _phantom: PhantomData<T>
+}
+
+///
+/// Signal the core that we can load modules with this loader
+///
+impl<T> ModuleProtocol<T, vlc_filter_activate> for FilterModuleLoader<T>
+    where T: TestFilterCapability
+{
+    fn activate_function() -> vlc_filter_activate
+    {
+        activate_filter::<T>
+    }
+}
+
+/* Implement dummy module capability */
+pub trait TestFilterCapability : Sized {
+    type Activate = vlc_filter_activate;
+    type Deactivate = *mut ();
+
+    type Loader = FilterModuleLoader<Self>;
+
+    fn open(obj: *mut vlc_filter_t, bool: &mut bool);
+}
+
+///
+/// Create a dummy module using this capability
+///
+pub struct TestModuleFilter;
+impl TestFilterCapability for TestModuleFilter {
+    fn open(_obj: *mut vlc_filter_t, valid: &mut bool) {
+        *valid = true;
+    }
+}
+
+/* Implement dummy module capability */
+pub trait TestOtherCapability : Sized {
+    type Activate = vlc_filter_activate;
+    type Deactivate = *mut ();
+    type Loader = FilterModuleLoader<Self>;
+
+    fn open(obj: *mut vlc_filter_t, bool: &mut bool);
+}
+
+///
+/// Create a dummy module using this capability
+///
+impl TestOtherCapability for TestModuleFilter {
+    fn open(_obj: *mut vlc_filter_t, valid: &mut bool) {
+        *valid = true;
+    }
+}
+
+
+//
+// Define a module manifest using this module capability
+// and this module.
+//
+module! {
+    type: TestModuleFilter (TestFilterCapability),
+    capability: "video_filter" @ 0,
+    category: VIDEO_VFILTER,
+    description: "A new module",
+    shortname: "mynewmodule",
+    shortcuts: ["mynewmodule_filter"],
+    submodules: [
+        {
+            type: TestModuleFilter (TestOtherCapability),
+            capability: "other_capability" @ 0,
+            category: VIDEO_VFILTER,
+            description: "Another module",
+            shortname: "othermodule"
+        }
+    ]
+}
+
+//
+// This test uses the defined capability and module from above
+// and tries to load the manifest and open an instance of the
+// module.
+//
+#[test]
+fn test_module_manifest_multiple_capabilities()
+{
+    use vlcrs_plugin::ModuleProperties;
+    let mut context = TestContext::<vlc_filter_activate> {
+        command_cursor: 0,
+        commands: vec![
+            ModuleProperties::MODULE_CREATE,
+            ModuleProperties::MODULE_NAME,
+            ModuleProperties::MODULE_CAPABILITY,
+            ModuleProperties::MODULE_SCORE,
+            ModuleProperties::MODULE_DESCRIPTION,
+            ModuleProperties::MODULE_SHORTNAME,
+            ModuleProperties::MODULE_SHORTCUT,
+            ModuleProperties::MODULE_CB_OPEN,
+            ModuleProperties::CONFIG_CREATE,
+            ModuleProperties::CONFIG_VALUE,
+            ModuleProperties::MODULE_CREATE,
+            ModuleProperties::MODULE_CAPABILITY,
+            ModuleProperties::MODULE_SCORE,
+            ModuleProperties::MODULE_DESCRIPTION,
+            ModuleProperties::MODULE_SHORTNAME,
+            ModuleProperties::MODULE_CB_OPEN,
+            ModuleProperties::CONFIG_CREATE,
+            ModuleProperties::CONFIG_VALUE,
+        ],
+        open_cb: None,
+        close_cb: None,
+    };
+    let ret = test_common::load_manifest(&mut context, vlc_entry);
+    assert_eq!(ret, 0);
+    assert_ne!(context.open_cb, None);
+
+    let mut valid = false;
+    unsafe {
+        context.open_cb.unwrap()(std::ptr::null_mut(), &mut valid);
+    }
+
+    assert!(valid, "The open from the module must have been called");
+}


=====================================
src/rust/vlcrs-macros/tests/module_specific.rs
=====================================
@@ -0,0 +1,150 @@
+//
+// Copyright (C) 2024      Alexandre Janniaux <ajanni at videolabs.io>
+//
+
+#![feature(c_variadic)]
+#![feature(associated_type_defaults)]
+#![feature(extern_types)]
+#![feature(fn_ptr_trait)]
+
+mod test_common;
+
+use vlcrs_macros::module;
+
+use std::ffi::c_int;
+use std::marker::PhantomData;
+use vlcrs_plugin::ModuleProtocol;
+
+extern {
+    // Create a dummy different type to change the activation function.
+    #[allow(non_camel_case_types)]
+    pub type vlc_filter_t;
+}
+
+#[allow(non_camel_case_types)]
+type vlc_filter_activate = unsafe extern "C" fn (_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int;
+
+#[allow(non_camel_case_types)]
+type vlc_filter_deactivate = unsafe extern "C" fn (_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int;
+
+unsafe extern "C"
+fn activate_filter<T: TestFilterCapability>(_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int
+{
+    T::open(_obj, valid);
+    0
+}
+
+unsafe extern "C"
+fn deactivate_filter<T: TestFilterCapability>(_obj: *mut vlc_filter_t, valid: &mut bool) -> c_int
+{
+    T::close(_obj, valid);
+    0
+}
+
+use crate::test_common::TestContext;
+
+//
+// Create an implementation loader for the TestFilterCapability
+//
+pub struct FilterModuleLoader<T> {
+    _phantom: PhantomData<T>
+}
+
+///
+/// Signal the core that we can load modules with this loader
+///
+impl<T> ModuleProtocol<T, vlc_filter_activate, vlc_filter_deactivate> for FilterModuleLoader<T>
+    where T: TestFilterCapability
+{
+    fn activate_function() -> vlc_filter_activate
+    {
+        activate_filter::<T>
+    }
+
+    fn deactivate_function() -> Option<vlc_filter_deactivate> {
+        Some(deactivate_filter::<T>)
+    }
+}
+
+/* Implement dummy module capability */
+pub trait TestFilterCapability : Sized {
+    type Activate = vlc_filter_activate;
+    type Deactivate = vlc_filter_deactivate;
+
+    type Loader = FilterModuleLoader<Self>;
+
+    fn open(obj: *mut vlc_filter_t, bool: &mut bool);
+    fn close(obj: *mut vlc_filter_t, valid: &mut bool);
+}
+
+///
+/// Create a dummy module using this capability
+///
+pub struct TestModuleFilter;
+impl TestFilterCapability for TestModuleFilter {
+    fn open(_obj: *mut vlc_filter_t, valid: &mut bool) {
+        *valid = true;
+    }
+
+    fn close(_obj: *mut vlc_filter_t, valid: &mut bool) {
+        *valid = true;
+    }
+}
+
+//
+// Define a module manifest using this module capability
+// and this module.
+//
+module! {
+    type: TestModuleFilter (TestFilterCapability),
+    capability: "video_filter" @ 0,
+    category: VIDEO_VFILTER,
+    description: "A new module",
+    shortname: "mynewmodule",
+    shortcuts: ["mynewmodule_filter"],
+}
+
+//
+// This test uses the defined capability and module from above
+// and tries to load the manifest and open an instance of the
+// module.
+//
+#[test]
+fn test_module_load_specific_open()
+{
+    use vlcrs_plugin::ModuleProperties;
+    let mut context = TestContext::<vlc_filter_activate, vlc_filter_deactivate> {
+        command_cursor: 0,
+        commands: vec![
+            ModuleProperties::MODULE_CREATE,
+            ModuleProperties::MODULE_NAME,
+            ModuleProperties::MODULE_CAPABILITY,
+            ModuleProperties::MODULE_SCORE,
+            ModuleProperties::MODULE_DESCRIPTION,
+            ModuleProperties::MODULE_SHORTNAME,
+            ModuleProperties::MODULE_SHORTCUT,
+            ModuleProperties::MODULE_CB_OPEN,
+            ModuleProperties::MODULE_CB_CLOSE,
+            ModuleProperties::CONFIG_CREATE,
+            ModuleProperties::CONFIG_VALUE,
+        ],
+        open_cb: None,
+        close_cb: None,
+    };
+    let ret = test_common::load_manifest(&mut context, vlc_entry);
+    assert_eq!(ret, 0);
+    assert_ne!(context.open_cb, None);
+    assert_ne!(context.close_cb, None);
+
+    let mut valid = false;
+    unsafe {
+        context.open_cb.unwrap()(std::ptr::null_mut(), &mut valid);
+    }
+    assert!(valid, "The open from the module must have been called");
+
+    valid = false;
+    unsafe {
+        context.close_cb.unwrap()(std::ptr::null_mut(), &mut valid);
+    }
+    assert!(valid, "The close from the module must have been called");
+}


=====================================
src/rust/vlcrs-plugin/Cargo.toml
=====================================
@@ -0,0 +1,7 @@
+[package]
+name = "vlcrs-plugin"
+edition = "2021"
+version.workspace = true
+license.workspace = true
+
+[dependencies]


=====================================
src/rust/vlcrs-plugin/src/lib.rs
=====================================
@@ -0,0 +1,255 @@
+#![feature(extern_types)]
+
+pub mod sys;
+
+pub const VLC_COPYRIGHT_VIDEOLAN : &str = r#"
+\x43\x6f\x70\x79\x72\x69\x67\x68\x74\x20\x28\x43\x29\x20\x74\x68
+\x65\x20\x56\x69\x64\x65\x6f\x4c\x41\x4e\x20\x56\x4c\x43\x20\x6d
+\x65\x64\x69\x61\x20\x70\x6c\x61\x79\x65\x72\x20\x64\x65\x76\x65
+\x6c\x6f\x70\x65\x72\x73"#;
+
+pub const VLC_LICENSE_LGPL_2_1_PLUS : &str = r#"
+\x4c\x69\x63\x65\x6e\x73\x65\x64\x20\x75\x6e\x64\x65\x72\x20\x74
+\x68\x65\x20\x74\x65\x72\x6d\x73\x20\x6f\x66\x20\x74\x68\x65\x20
+\x47\x4e\x55\x20\x4c\x65\x73\x73\x65\x72\x20\x47\x65\x6e\x65\x72
+\x61\x6c\x20\x50\x75\x62\x6c\x69\x63\x20\x4c\x69\x63\x65\x6e\x73
+\x65\x2c\x20\x76\x65\x72\x73\x69\x6f\x6e\x20\x32\x2e\x31\x20\x6f"#;
+
+#[allow(non_camel_case_types)]
+#[allow(unused)]
+extern {
+    pub type vlc_object_t;
+    pub type module_t;
+    pub type vlc_param;
+}
+
+#[allow(non_camel_case_types)]
+#[repr(u32)]
+pub enum ConfigModule {
+    HINT_CATEGORY        = 0x02,
+    SUBCATEGORY          = 0x07,
+    SECTION              = 0x08,
+    ITEM_FLOAT           = 1 << 5,
+    ITEM_INTEGER         = 2 << 5,
+    ITEM_RGB             = ConfigModule::ITEM_INTEGER as u32 | 0x01,
+    ITEM_BOOL            = 3 << 5,
+    ITEM_STRING          = 4 << 5,
+    ITEM_PASSWORD        = ConfigModule::ITEM_STRING  as u32 | 0x01,
+    ITEM_KEY             = ConfigModule::ITEM_STRING  as u32 | 0x02,
+    ITEM_MODULE          = ConfigModule::ITEM_STRING  as u32 | 0x04,
+    ITEM_MODULE_CAT      = ConfigModule::ITEM_STRING  as u32 | 0x05,
+    ITEM_MODULE_LIST     = ConfigModule::ITEM_STRING  as u32 | 0x06,
+    ITEM_MODULE_LIST_CAT = ConfigModule::ITEM_STRING  as u32 | 0x07,
+    ITEM_LOADFILE        = ConfigModule::ITEM_STRING  as u32 | 0x0C,
+    ITEM_SAVEFILE        = ConfigModule::ITEM_STRING  as u32 | 0x0D,
+    ITEM_DIRECTORY       = ConfigModule::ITEM_STRING  as u32 | 0x0E,
+    ITEM_FONT            = ConfigModule::ITEM_STRING  as u32 | 0x0F,
+}
+
+#[allow(non_camel_case_types)]
+#[repr(i32)]
+pub enum ConfigCategory {
+    HIDDEN    = -1,
+    UNKNOWN   = 0,
+    INTERFACE = 1,
+    AUDIO     = 2,
+    VIDEO     = 3,
+    INPUT     = 4,
+    SOUT      = 5,
+    ADVANCED  = 6,
+    PLAYLIST  = 7,
+}
+
+#[allow(non_camel_case_types)]
+#[repr(i32)]
+pub enum ConfigSubcategory {
+    HIDDEN    = -1,
+    UNKNOWN   = 0,
+
+    INTERFACE_GENERAL = 101,
+    INTERFACE_MAIN    = 102,
+    INTERFACE_CONTROL = 103,
+    INTERFACE_HOTKEYS = 104,
+
+    AUDIO_GENERAL   = 201,
+    AUDIO_AOUT      = 202,
+    AUDIO_AFILTER   = 203,
+    AUDIO_VISUAL    = 204,
+    AUDIO_RESAMPLER = 206,
+
+    VIDEO_GENERAL  = 301,
+    VIDEO_VOUT     = 302,
+    VIDEO_VFILTER  = 303,
+    VIDEO_SUBPIC   = 305,
+    VIDEO_SPLITTER = 306,
+
+    INPUT_GENERAL       = 401,
+    INPUT_ACCESS        = 402,
+    INPUT_DEMUX         = 403,
+    INPUT_VCODEC        = 404,
+    INPUT_ACODEC        = 405,
+    INPUT_SCODEC        = 406,
+    INPUT_STREAM_FILTER = 407,
+
+    SOUT_GENERAL    = 501,
+    SOUT_STREAM     = 502,
+    SOUT_MUX        = 503,
+    SOUT_ACO        = 504,
+    SOUT_PACKETIZER = 505,
+    SOUT_VOD        = 507,
+    SOUT_RENDERER   = 508,
+
+    ADVANCED_MISC    = 602,
+    ADVANCED_NETWORK = 603,
+
+    PLAYLIST_GENERAL = 701,
+    PLAYLIST_SD      = 702,
+    PLAYLIST_EXPORT  = 703,
+}
+
+
+#[derive(Debug, PartialEq, PartialOrd)]
+#[allow(non_camel_case_types)]
+#[repr(i32)]
+pub enum ModuleProperties {
+    MODULE_CREATE,
+    CONFIG_CREATE,
+
+    MODULE_CPU_REQUIREMENT      = 0x100,
+    MODULE_SHORTCUT,
+    MODULE_CAPABILITY,
+    MODULE_SCORE,
+    MODULE_CB_OPEN,
+    MODULE_CB_CLOSE,
+    MODULE_NO_UNLOAD,
+    MODULE_NAME,
+    MODULE_SHORTNAME,
+    MODULE_DESCRIPTION,
+    MODULE_HELP,
+    MODULE_TEXTDOMAIN,
+    MODULE_HELP_HTML,
+
+    CONFIG_NAME                 = 0x1000,
+    CONFIG_VALUE,
+    CONFIG_RANGE,
+    CONFIG_ADVANCED_RESERVED,
+    CONFIG_VOLATILE,
+    CONFIG_PERSISTENT_OBSOLETE,
+    CONFIG_PRIVATE,
+    CONFIG_REMOVED,
+    CONFIG_CAPABILITY,
+    CONFIG_SHORTCUT,
+    CONFIG_OLDNAME_OBSOLETE,
+    CONFIG_SAFE,
+    CONFIG_DESC,
+    CONFIG_LIST_OBSOLETE,
+    CONFIG_ADD_ACTION_OBSOLETE,
+    CONFIG_LIST,
+    CONFIG_LIST_CB_OBSOLETE,
+}
+
+impl TryFrom<i32> for ModuleProperties {
+    type Error = ();
+
+    fn try_from(opcode: i32) -> Result<Self, Self::Error> {
+        let prop = match opcode {
+            x if x == ModuleProperties::MODULE_CREATE as i32 => ModuleProperties::MODULE_CREATE,
+            x if x == ModuleProperties::CONFIG_CREATE as i32 => ModuleProperties::CONFIG_CREATE,
+
+            x if x == ModuleProperties::MODULE_CPU_REQUIREMENT as i32 => ModuleProperties::MODULE_CPU_REQUIREMENT,
+            x if x == ModuleProperties::MODULE_SHORTCUT as i32 => ModuleProperties::MODULE_SHORTCUT,
+            x if x == ModuleProperties::MODULE_CAPABILITY as i32 => ModuleProperties::MODULE_CAPABILITY,
+            x if x == ModuleProperties::MODULE_SCORE as i32 => ModuleProperties::MODULE_SCORE,
+            x if x == ModuleProperties::MODULE_CB_OPEN as i32 => ModuleProperties::MODULE_CB_OPEN,
+            x if x == ModuleProperties::MODULE_CB_CLOSE as i32 => ModuleProperties::MODULE_CB_CLOSE,
+            x if x == ModuleProperties::MODULE_NO_UNLOAD as i32 => ModuleProperties::MODULE_NO_UNLOAD,
+            x if x == ModuleProperties::MODULE_NAME as i32 => ModuleProperties::MODULE_NAME,
+            x if x == ModuleProperties::MODULE_SHORTNAME as i32 => ModuleProperties::MODULE_SHORTNAME,
+            x if x == ModuleProperties::MODULE_DESCRIPTION as i32 => ModuleProperties::MODULE_DESCRIPTION,
+            x if x == ModuleProperties::MODULE_HELP as i32 => ModuleProperties::MODULE_HELP,
+            x if x == ModuleProperties::MODULE_TEXTDOMAIN as i32 => ModuleProperties::MODULE_TEXTDOMAIN,
+            x if x == ModuleProperties::MODULE_HELP_HTML as i32 => ModuleProperties::MODULE_HELP_HTML,
+
+            x if x == ModuleProperties::CONFIG_NAME as i32 => ModuleProperties::CONFIG_NAME,
+            x if x == ModuleProperties::CONFIG_VALUE as i32 => ModuleProperties::CONFIG_VALUE,
+            x if x == ModuleProperties::CONFIG_RANGE as i32 => ModuleProperties::CONFIG_RANGE,
+            x if x == ModuleProperties::CONFIG_ADVANCED_RESERVED as i32 => ModuleProperties::CONFIG_ADVANCED_RESERVED,
+            x if x == ModuleProperties::CONFIG_VOLATILE as i32 => ModuleProperties::CONFIG_VOLATILE,
+            x if x == ModuleProperties::CONFIG_PERSISTENT_OBSOLETE as i32 => ModuleProperties::CONFIG_PERSISTENT_OBSOLETE,
+            x if x == ModuleProperties::CONFIG_PRIVATE as i32 => ModuleProperties::CONFIG_PRIVATE,
+            x if x == ModuleProperties::CONFIG_REMOVED as i32 => ModuleProperties::CONFIG_REMOVED,
+            x if x == ModuleProperties::CONFIG_CAPABILITY as i32 => ModuleProperties::CONFIG_CAPABILITY,
+            x if x == ModuleProperties::CONFIG_SHORTCUT as i32 => ModuleProperties::CONFIG_SHORTCUT,
+            x if x == ModuleProperties::CONFIG_OLDNAME_OBSOLETE as i32 => ModuleProperties::CONFIG_OLDNAME_OBSOLETE,
+            x if x == ModuleProperties::CONFIG_SAFE as i32 => ModuleProperties::CONFIG_SAFE,
+            x if x == ModuleProperties::CONFIG_DESC as i32 => ModuleProperties::CONFIG_DESC,
+            x if x == ModuleProperties::CONFIG_LIST_OBSOLETE as i32 => ModuleProperties::CONFIG_LIST_OBSOLETE,
+            x if x == ModuleProperties::CONFIG_ADD_ACTION_OBSOLETE as i32 => ModuleProperties::CONFIG_ADD_ACTION_OBSOLETE,
+            x if x == ModuleProperties::CONFIG_LIST as i32 => ModuleProperties::CONFIG_LIST,
+            x if x == ModuleProperties::CONFIG_LIST_CB_OBSOLETE as i32 => ModuleProperties::CONFIG_LIST_CB_OBSOLETE,
+            _ => return Err(())
+        };
+        Ok(prop)
+    }
+}
+
+use std::ffi::c_int;
+#[allow(non_camel_case_types)]
+pub type vlc_activate = unsafe extern "C" fn(*mut vlc_object_t) -> c_int;
+#[allow(non_camel_case_types)]
+pub type vlc_deactivate = unsafe extern "C" fn(*mut vlc_object_t);
+
+///
+/// Exposes the activation and deactivation functions for modules.
+///
+/// Capabilities for modules are traits implementing a Loader type
+/// implementing the ModuleProtocol trait to provide the activation
+/// and deactivation functions for the module.
+///
+/// * `T`: Type implementing the final capability trait
+/// * `Activate`: Type for the activation function
+/// * `Deactivate`: Type for the deactivation function
+///
+/// ```no_run
+/// #![feature(associated_type_defaults)]
+/// use std::marker::PhantomData;
+/// use vlcrs_plugin::ModuleProtocol;
+///
+/// /* New trait bringing support for new capabilities in modules. */
+///
+/// type ActivateFunction = unsafe extern "C" fn();
+/// type DeactivateFunction = unsafe extern "C" fn();
+/// trait NewCapabilityTrait {
+///     /* Mandatory type definition for capability traits */
+///     type Activate = ActivateFunction;
+///     type Deactivate = DeactivateFunction;
+///     type Loader = NewCapabilityLoader<Self>;
+///
+///     /* Example capability interface, can be anything and can add
+///      * as many function as required. */
+///     fn open(&self);
+/// }
+///
+/// /* Implement a function to load modules implementing NewCapabilityTrait. */
+/// unsafe extern "C" fn activate_new_capability<ModuleType: ?Sized + NewCapabilityTrait>() {
+///     /* Do anything needed to initialize the module, for instance initializing
+///      * a ModuleType object and call the open() function on it. */
+/// }
+///
+/// /* Implement a module loader for this new capability trait. */
+/// struct NewCapabilityLoader<ModuleType: ?Sized> { _phantom : PhantomData<ModuleType> }
+/// impl<ModuleType> ModuleProtocol<
+///     ModuleType,
+///     ActivateFunction,
+///     DeactivateFunction>
+/// for NewCapabilityLoader<ModuleType>
+/// where ModuleType : ?Sized + NewCapabilityTrait {
+///     fn activate_function() -> ActivateFunction { activate_new_capability::<ModuleType> }
+///     fn deactivate_function() -> Option<DeactivateFunction> { None }
+/// }
+/// ```
+pub trait ModuleProtocol<ModuleType: ?Sized, Activate, Deactivate=*mut ()>
+{
+    fn activate_function() -> Activate;
+    fn deactivate_function() -> Option<Deactivate> { None }
+}


=====================================
src/rust/vlcrs-plugin/src/sys.rs
=====================================
@@ -0,0 +1,4 @@
+use std::ffi::{c_int, c_void};
+
+#[allow(non_camel_case_types)]
+pub type vlc_set_cb = unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, ...) -> c_int;



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e9dd462661ef14ac5d3c237e97a4bd5f6a8653fc...0b3d497afd768addc1ba56743bc9fd89136ff8e5

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/e9dd462661ef14ac5d3c237e97a4bd5f6a8653fc...0b3d497afd768addc1ba56743bc9fd89136ff8e5
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list