Skip to main content

Crate spell_framework

Crate spell_framework 

Source
Expand description

§Spell

§Introduction

This crate provides the necessary abstractions for wlr_layer_shell protocols with an implementation of slint platform backend for creating any and every kind of widget in slint. So, by the dark arts of spell, one can program their widgets creation in every kind of way suitable to their specific needs. Internally, spell provides a slint Platform implementation combined with necessary wayland counterparts using Smithay client toolkit’s Wayland bindings in rust. Apart from that, spell also provides convenience functions and method implementations for various common tasks (like Apps search backend, mpris backend etc) according to freedesktop specs standards. Though a lot of them are still partially complete, incomplete or not even started.

The crate is under active development and breaking changes are expected. Base functionalities are complete but more goodies need to be added. I am now receiving PRs and Issues now so feel free to fix something or report something that needs to be fixed.

§Why use Spell and Slint?

It is a necessary question to answer. Spell was created as a personal project to fill a gap, i.e. absence of proper widget making toolkits and frameworks in rust. Moreover, I didn’t want to make yet another abstraction over gtk_layer_shell and call it a day. Slint is simple yet powerful declarative language which provides excellent support for rust as backend. Spell fills this gap, it implements slint’s backend for usage in making widgets. This was rather than bending a verbose language(like rust) to write widgets, people can use slint which was made for it, and still get the excellent support of rust in backend. Another reason was to inspect the inner workings of wayland and how display is managed in linux systems in modern days.

§Panics and Errors

Before starting further, it is important to note that due to nature of rust, some sections become more complicated than they need to be, especially in case of UI related components and libraries. Thus, it is important to read the panic sections (if there is any) of spell objects which you are using, to get a sense of cases in which the code will panic.

§Examples and Basic Usage

Create a new project with cargo new project_name. Let’s start by adding Slint and Spell as dependencies in your project’s Cargo.toml.

[dependencies]
slint = { version = "1.13.1", features = ["renderer-software"] }
spell = "1.0.0"

[build-dependencies]
slint-build = "1.13.1"

[patch.crates-io]
slint = { git = "https://github.com/slint-ui/slint" }
slint-build = { git = "https://github.com/slint-ui/slint" }
i-slint-core = { git = "https://github.com/slint-ui/slint" }
i-slint-renderer-skia = { git = "https://github.com/slint-ui/slint" }

Since, spell uses some of the private APIs of Slint, it is necessary to provide the above mentioned patches. Build deps are required by slint during compilation process. Moving on, add the ui directory (which will store your .slint files) in your project root (via command mkdir ui). Also add build.rs in project root with the following contents for building slint files.

fn main() {
    slint_build::compile("ui/app-window.slint").expect("Slint build failed");
}

Now the main juice, let’s create a counter widget with a button to increment a count which starts from, say 42.

// In path and file name `ui/app-window.slint`
export component AppWindow inherits Window {
    in-out property <int> counter: 42;
    callback request-increase-value();
    VerticalBox {
        Text {
            text: "Counter: \{root.counter}";
        }

        Button {
            text: "Increase value";
            clicked => {
                root.request-increase-value();
            }
        }
    }
}

Now, to increment the data and specify the dimensions of widget add the following to your src/main.rs file.

use std::{
    error::Error,
    sync::mpsc,
    sync::{Arc, RwLock},
};

use slint::ComponentHandle;
use spell::{
    cast_spell,
    layer_properties::{ForeignController, LayerAnchor, LayerType, WindowConf, BoardType},
    wayland_adapter::SpellWin,
    Handle,
};
slint::include_modules!();

fn main() -> Result<(), Box<dyn Error>> {
    // Necessary configurations for the widget like dimensions, layer etc.
    let window_conf = WindowConf::new(
        376,
        576,
        (Some(LayerAnchor::TOP), Some(LayerAnchor::LEFT)),
        (5, 0, 0, 10),
        LayerType::Top,
        BoardType::None,
        false,
    );

    // Getting the window and its event_queue given the properties and a window name.
    let waywindow = SpellWin::invoke_spell("counter-widget", window_conf);

    // Slint specific code. Like initialising the window.
    let ui = AppWindow::new().unwrap();

    // Setting the callback closure value which will be called on when the button is clicked.
    ui.on_request_increase_value({
        let ui_handle = ui.as_weak();
        move || {
            let ui = ui_handle.unwrap();
            ui.set_counter(ui.get_counter() + 1);
        }
    });

    // Calling the event loop function for running the window
    cast_spell(waywindow, None, None)
}

Running this code with cargo will display a widget in your wayland compositor. It is important to mention that if you have defined width and height in both your window and in the rust code,then the renderer will manage the more or less dimensions accordingly, which may lead to undefined behaviour. For details of arguments and use of layer_properties::WindowConf and [cast_spell], head to their respective attached docs. The same frontend code for this example can also be found in slint-rust-template

§Spell CLI

Spell CLI is a utility for managing and handling remotely running windows/widgets. It provides various features like hiding/opening the widget, toggling the widget, remotely setting variables with new/changed values, seeing logs etc. CLI’s commands can be combined with your wayland compositor’s configuration file to make keybind for your windows. Read more about it in its documentation.

§Inspiration

The inspiration for making spell came from various weird places. First and foremost was my inability to understand the workings of GTK (I didn’t spend much time on it), which drifted me away to better alternative,slint. Another reason was to answer the question, “how does wayland actually works?”. Moreover, outfoxxed made quickshell around the same time, which does a similar thing with Qt and C++, this gave me confidence that a non-gtk framework for widget creation is possible.

§Slint part of Spell

This is future work and here is a note to it. As some more base functionalities will be complete I will also create a slint counterpart of “components” which are most commonly used in the process of widget creation.

Re-exports§

pub use paste;
pub use smithay_client_toolkit;
pub use tracing;

Modules§

forge
forge is a mini submodule which provides alternative method to create and run events after a certain duration of time. Obvious approach to tackle such events is to use Timer. Alternatively, if you want a more rust facing interface (where timed events are not managed inside .slint files and rather directly created in rust code), you can use Forge
layer_properties
It contains related enums and struct which are used to manage, define and update various properties of a widget(viz a viz layer). You can import necessary types from this module to implement relevant features. See docs of related objects for their overview.
slint_adapter
This module contains relevent structs for slint side backend configurations. Apart from SpellMultiLayerShell and SpellMultiWinHandler, rest of the structs mentioned are either internal or not used anymore. Still their implementation is public because they had be set by the user of library in intial iterations of spell_framework.
vault
vault contains the necessary utilities/APTs for various common tasks required when creating a custom shell. This includes apps, pipewire, PAM, Mpris etc among other things.
wayland_adapter
It provides various widget types for implementing properties across various functionalities for your shell. The most common widget (or window as called by many) is SpellWin. You can also implement a lock screen with SpellLock.

Macros§

cast_spell
delegate_fractional_scale
delegate_viewporter
generate_widgets

Traits§

ForeignControllerDeprecated
This a boilerplate trait for connection with CLI, it will be replaced by a procedural macro in the future. In the mean time, this function is implemented on a struct you would define in your .slint file. Then state of widgets should be stored as single property of that data type rather than on individual values.
IpcController
SpellAssociated
Internal function for running event loops, implemented by SpellWin and SpellLock.
SpellAssociatedNew

Functions§

cast_spellDeprecated
This is the primary function used for starting the event loop after creating the widgets, setting values and initialising windows. Example of the use can be found here. The function takes in the following function arguments:-
cast_spell_inner
cast_spells_new
enchant_spellsDeprecated
This is the event loop which is to be called when initialising multiple windows through a single main file. It is important to remember that Each value of these vectors corresponds to the number on which a widget is initialised. So, this function will panic if the length of vectors of various types mentioned here are not equal.For more information on checking the arguments, view [cast_spell].

Type Aliases§

StateDeprecated