diff --git a/applets/cosmic-applet-workspaces/Cargo.toml b/applets/cosmic-applet-workspaces/Cargo.toml
new file mode 100644
index 00000000..3f9ea555
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "cosmic-applet-workspaces"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[dependencies]
+cosmic-panel-config = { git="https://github.com/pop-os/cosmic-panel/", features = ["gtk4"] }
+cascade = "1.0.0"
+gtk4 = { version = "0.4.5", features = ["v4_4"] }
+once_cell = "1.9.0"
+pretty_env_logger = "0.4"
+anyhow = "1.0.50"
+i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] }
+i18n-embed-fl = "0.6.4"
+rust-embed = "6.3.0"
+tokio = { version = "1.16.1", features = ["sync"] }
+
+
+[build-dependencies]
+gio = "0.15.10"
diff --git a/applets/cosmic-applet-workspaces/build.rs b/applets/cosmic-applet-workspaces/build.rs
new file mode 100644
index 00000000..7c42fb5e
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/build.rs
@@ -0,0 +1,7 @@
+fn main() {
+ gio::compile_resources(
+ "data/resources",
+ "data/resources/resources.gresource.xml",
+ "compiled.gresource",
+ );
+}
diff --git a/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop b/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop
new file mode 100644
index 00000000..8119b2b1
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/data/com.system76.CosmicAppletWorkspaces.desktop
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Name=Cosmic Applet Power
+Comment=Write a GTK + Rust application
+Type=Application
+Exec=cosmic-applet-power
+Terminal=false
+Categories=GNOME;GTK;
+Keywords=Gnome;GTK;
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=com.system76.CosmicAppletPower.svg
+StartupNotify=true
+NoDisplay=true
diff --git a/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg b/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg
new file mode 100644
index 00000000..c2bd5b1b
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/data/icons/com.system76.CosmicAppletWorkspaces.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml b/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml
new file mode 100644
index 00000000..2cf0970f
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/data/resources/resources.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/applets/cosmic-applet-workspaces/data/resources/style.css b/applets/cosmic-applet-workspaces/data/resources/style.css
new file mode 100644
index 00000000..e69de29b
diff --git a/applets/cosmic-applet-workspaces/i18n.toml b/applets/cosmic-applet-workspaces/i18n.toml
new file mode 100644
index 00000000..05c50ba2
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/i18n.toml
@@ -0,0 +1,4 @@
+fallback_language = "en"
+
+[fluent]
+assets_dir = "i18n"
\ No newline at end of file
diff --git a/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl b/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl
new file mode 100644
index 00000000..2d5ea1e4
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/i18n/en/cosmic_applet_workspaces.ftl
@@ -0,0 +1 @@
+cosmic-app-list = Cosmic Dock App List
diff --git a/applets/cosmic-applet-workspaces/src/localize.rs b/applets/cosmic-applet-workspaces/src/localize.rs
new file mode 100644
index 00000000..efb92aa4
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/localize.rs
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use i18n_embed::{
+ fluent::{fluent_language_loader, FluentLanguageLoader},
+ DefaultLocalizer, LanguageLoader, Localizer,
+};
+use once_cell::sync::Lazy;
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "i18n/"]
+struct Localizations;
+
+pub static LANGUAGE_LOADER: Lazy = Lazy::new(|| {
+ let loader: FluentLanguageLoader = fluent_language_loader!();
+
+ loader
+ .load_fallback_language(&Localizations)
+ .expect("Error while loading fallback language");
+
+ loader
+});
+
+#[macro_export]
+macro_rules! fl {
+ ($message_id:literal) => {{
+ i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
+ }};
+
+ ($message_id:literal, $($args:expr),*) => {{
+ i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *)
+ }};
+}
+
+// Get the `Localizer` to be used for localizing this library.
+pub fn localizer() -> Box {
+ Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
+}
diff --git a/applets/cosmic-applet-workspaces/src/main.rs b/applets/cosmic-applet-workspaces/src/main.rs
new file mode 100644
index 00000000..df05758b
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/main.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use gtk4::{gdk::Display, gio::{self, ApplicationFlags}, glib, prelude::*, CssProvider, StyleContext};
+use once_cell::sync::OnceCell;
+use window::CosmicWorkspacesWindow;
+use std::sync::{Arc, Mutex};
+use tokio::sync::mpsc;
+use utils::{Event, BoxedWorkspaceList};
+
+mod window;
+mod workspace_button;
+mod workspace_list;
+mod wayland;
+mod localize;
+mod utils;
+mod workspace_object;
+
+const ID: &str = "com.system76.CosmicAppletWorkspaces";
+static TX: OnceCell> = OnceCell::new();
+
+pub fn localize() {
+ let localizer = crate::localize::localizer();
+ let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
+
+ if let Err(error) = localizer.select(&requested_languages) {
+ eprintln!("Error while loading language for App List {}", error);
+ }
+}
+
+fn load_css() {
+ let provider = CssProvider::new();
+ provider.load_from_resource("/com.system76.CosmicAppletWorkspaces/style.css");
+
+ StyleContext::add_provider_for_display(
+ &Display::default().unwrap(),
+ &provider,
+ gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
+ );
+}
+
+fn main() {
+ // Initialize logger
+ pretty_env_logger::init();
+ glib::set_application_name(ID);
+
+ localize();
+ gio::resources_register_include!("compiled.gresource").unwrap();
+
+ let app = gtk4::Application::new(Some(ID), ApplicationFlags::default());
+
+ app.connect_activate(|app| {
+ load_css();
+ let (tx, mut rx) = mpsc::channel(100);
+
+ let window = CosmicWorkspacesWindow::new(app, tx.clone());
+
+ let workspace_list = Arc::new(Mutex::new(Vec::::new()));
+
+ TX.set(tx.clone()).unwrap();
+
+ let _ = glib::MainContext::default().spawn_local(async move {
+ while let Some(event) = rx.recv().await {
+ match event {
+ Event::Activate(_) => {
+ // TODO activate the selected workspace
+ }
+ Event::WorkspaceList => {
+ // TODO update the model with the new workspace list
+ }
+ }
+ }
+ });
+ window.show();
+ });
+ app.run();
+}
diff --git a/applets/cosmic-applet-workspaces/src/utils.rs b/applets/cosmic-applet-workspaces/src/utils.rs
new file mode 100644
index 00000000..4d27e5e9
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/utils.rs
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use std::path::PathBuf;
+
+use gtk4::glib;
+use std::future::Future;
+
+#[derive(Debug)]
+pub enum Event {
+ WorkspaceList,
+ Activate(u32),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Workspace {
+ pub(crate) id: u32,
+ pub(crate) active: bool,
+}
+
+#[derive(Clone, Debug, Default, glib::Boxed)]
+#[boxed_type(name = "BoxedWorkspace")]
+pub struct BoxedWorkspace(pub Option);
+
+#[derive(Clone, Debug, Default, glib::Boxed)]
+#[boxed_type(name = "BoxedWorkspaceList")]
+pub struct BoxedWorkspaceList(pub Vec);
+
+pub fn data_path() -> PathBuf {
+ let mut path = glib::user_data_dir();
+ path.push(crate::ID);
+ std::fs::create_dir_all(&path).expect("Could not create directory.");
+ path.push("data.json");
+ path
+}
+
+pub fn thread_context() -> glib::MainContext {
+ glib::MainContext::thread_default().unwrap_or_else(|| {
+ let ctx = glib::MainContext::new();
+ ctx
+ })
+}
+
+pub fn block_on(future: F) -> F::Output
+where
+ F: Future,
+{
+ let ctx = thread_context();
+ ctx.with_thread_default(|| ctx.block_on(future)).unwrap()
+}
diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/applets/cosmic-applet-workspaces/src/window/imp.rs b/applets/cosmic-applet-workspaces/src/window/imp.rs
new file mode 100644
index 00000000..d614237c
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/window/imp.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use gtk4::{glib, subclass::prelude::*};
+use once_cell::sync::OnceCell;
+use crate::{workspace_list::WorkspaceList};
+
+// Object holding the state
+#[derive(Default)]
+pub struct CosmicWorkspacesWindow {
+ pub(super) inner: OnceCell,
+}
+
+// The central trait for subclassing a GObject
+#[glib::object_subclass]
+impl ObjectSubclass for CosmicWorkspacesWindow {
+ // `NAME` needs to match `class` attribute of template
+ const NAME: &'static str = "CosmicWorkspacesWindow";
+ type Type = super::CosmicWorkspacesWindow;
+ type ParentType = gtk4::ApplicationWindow;
+}
+
+// Trait shared by all GObjects
+impl ObjectImpl for CosmicWorkspacesWindow {}
+
+// Trait shared by all widgets
+impl WidgetImpl for CosmicWorkspacesWindow {}
+
+// Trait shared by all windows
+impl WindowImpl for CosmicWorkspacesWindow {}
+
+// Trait shared by all application
+impl ApplicationWindowImpl for CosmicWorkspacesWindow {}
diff --git a/applets/cosmic-applet-workspaces/src/window/mod.rs b/applets/cosmic-applet-workspaces/src/window/mod.rs
new file mode 100644
index 00000000..585a1e87
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/window/mod.rs
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use crate::{fl, utils::Event, workspace_list::WorkspaceList};
+use cascade::cascade;
+use cosmic_panel_config::config::CosmicPanelConfig;
+use gtk4::{
+ gio,
+ glib::{self, Object},
+ prelude::*,
+ subclass::prelude::*,
+};
+use tokio::sync::mpsc;
+
+mod imp;
+
+glib::wrapper! {
+ pub struct CosmicWorkspacesWindow(ObjectSubclass)
+ @extends gtk4::ApplicationWindow, gtk4::Window, gtk4::Widget,
+ @implements gio::ActionGroup, gio::ActionMap, gtk4::Accessible, gtk4::Buildable,
+ gtk4::ConstraintTarget, gtk4::Native, gtk4::Root, gtk4::ShortcutManager;
+}
+
+impl CosmicWorkspacesWindow {
+ pub fn new(app: >k4::Application, tx: mpsc::Sender) -> Self {
+ let self_: Self = Object::new(&[("application", app)])
+ .expect("Failed to create `CosmicWorkspacesWindow`.");
+ let imp = imp::CosmicWorkspacesWindow::from_instance(&self_);
+
+ cascade! {
+ &self_;
+ ..set_width_request(1);
+ ..set_height_request(1);
+ ..set_decorated(false);
+ ..set_resizable(false);
+ ..set_title(Some(&fl!("cosmic-app-list")));
+ ..add_css_class("transparent");
+ };
+ let config = CosmicPanelConfig::load_from_env().unwrap_or_default();
+
+ let app_list = WorkspaceList::new(tx, config);
+ self_.set_child(Some(&app_list));
+ imp.inner.set(app_list).unwrap();
+
+ self_
+ }
+}
diff --git a/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs
new file mode 100644
index 00000000..254713df
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_button/imp.rs
@@ -0,0 +1,29 @@
+use std::{rc::Rc, cell::RefCell};
+use gtk4::{Label, ToggleButton, glib, subclass::prelude::*};
+use tokio::sync::mpsc;
+use once_cell::sync::OnceCell;
+use crate::Event;
+
+// Object holding the state
+#[derive(Default)]
+pub struct WorkspaceButton {
+ pub tx: Rc>>,
+ pub button: Rc>,
+}
+
+// The central trait for subclassing a GObject
+#[glib::object_subclass]
+impl ObjectSubclass for WorkspaceButton {
+ const NAME: &'static str = "WorkspaceButton";
+ type Type = super::WorkspaceButton;
+ type ParentType = gtk4::Box;
+}
+
+// Trait shared by all GObjects
+impl ObjectImpl for WorkspaceButton {}
+
+// Trait shared by all widgets
+impl WidgetImpl for WorkspaceButton {}
+
+// Trait shared by all buttons
+impl BoxImpl for WorkspaceButton {}
\ No newline at end of file
diff --git a/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs
new file mode 100644
index 00000000..bf5a4ab8
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_button/mod.rs
@@ -0,0 +1,43 @@
+mod imp;
+
+use glib::Object;
+use gtk4::{glib, prelude::*, subclass::prelude::*, ToggleButton};
+use tokio::sync::mpsc;
+use crate::{Event, workspace_object::WorkspaceObject};
+
+glib::wrapper! {
+ pub struct WorkspaceButton(ObjectSubclass)
+ @extends gtk4::Box, gtk4::Widget,
+ @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable;
+}
+
+impl WorkspaceButton {
+ pub fn new(tx: mpsc::Sender) -> Self {
+ let self_ = Object::new(&[]).expect("Failed to create `WorkspaceButton`.");
+ let imp = imp::WorkspaceButton::from_instance(&self_);
+ imp.tx.set(tx).unwrap();
+
+ let tb = ToggleButton::with_label("");
+ self_.append(&tb);
+
+ imp.button.replace(tb);
+
+ self_
+ }
+
+ pub fn set_workspace_object(&self, obj: &WorkspaceObject) {
+ let imp = imp::WorkspaceButton::from_instance(&self);
+ let old_button = imp.button.take();
+ self.remove(&old_button);
+
+ let id = obj.id();
+ let new_button = ToggleButton::with_label(&format!("{}", id));
+ new_button.set_active(obj.active());
+ self.append(&new_button);
+ new_button.connect_clicked(glib::clone!(@weak imp.tx as tx => move |_| {
+ let _ = tx.get().unwrap().send(Event::Activate(id));
+ }));
+
+ imp.button.replace(new_button);
+ }
+}
diff --git a/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs
new file mode 100644
index 00000000..ee855b1d
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_list/imp.rs
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use cosmic_panel_config::config::{Anchor, CosmicPanelConfig};
+use glib::SignalHandlerId;
+use gtk4::subclass::prelude::*;
+use gtk4::{gio, glib};
+use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView};
+use once_cell::sync::OnceCell;
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+use tokio::sync::mpsc;
+
+use crate::utils::Event;
+
+#[derive(Debug, Default)]
+pub struct WorkspaceList {
+ pub list_view: OnceCell,
+ pub model: OnceCell,
+ pub click_controller: OnceCell,
+ pub tx: OnceCell>,
+ pub config: OnceCell
+}
+
+#[glib::object_subclass]
+impl ObjectSubclass for WorkspaceList {
+ const NAME: &'static str = "WorkspaceList";
+ type Type = super::WorkspaceList;
+ type ParentType = Box;
+}
+
+impl ObjectImpl for WorkspaceList {}
+
+impl WidgetImpl for WorkspaceList {}
+
+impl BoxImpl for WorkspaceList {}
diff --git a/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs
new file mode 100644
index 00000000..5cd6fe35
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_list/mod.rs
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use crate::utils::{Event, BoxedWorkspace};
+use crate::workspace_button::WorkspaceButton;
+use crate::workspace_object::WorkspaceObject;
+use cascade::cascade;
+use cosmic_panel_config::config::{CosmicPanelConfig};
+use gtk4::{glib, gio, prelude::*, subclass::prelude::*};
+use gtk4::ListView;
+use gtk4::Orientation;
+use gtk4::SignalListItemFactory;
+use tokio::sync::mpsc::Sender;
+
+mod imp;
+
+glib::wrapper! {
+ pub struct WorkspaceList(ObjectSubclass)
+ @extends gtk4::Widget, gtk4::Box,
+ @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable;
+}
+
+impl WorkspaceList {
+ pub fn new(tx: Sender, config: CosmicPanelConfig) -> Self {
+ let self_: WorkspaceList = glib::Object::new(&[]).expect("Failed to create WorkspaceList");
+ let imp = imp::WorkspaceList::from_instance(&self_);
+ imp.tx.set(tx).unwrap();
+ imp.config.set(config).unwrap();
+ self_.layout();
+ //dnd behavior is different for each type, as well as the data in the model
+ self_.setup_model();
+ self_.setup_factory();
+ self_
+ }
+
+ pub fn model(&self) -> &gio::ListStore {
+ // Get state
+ let imp = imp::WorkspaceList::from_instance(self);
+ imp.model.get().expect("Could not get model")
+ }
+
+ fn layout(&self) {
+ let imp = imp::WorkspaceList::from_instance(self);
+ let list_view = cascade! {
+ ListView::default();
+ ..set_orientation(Orientation::Horizontal);
+ ..add_css_class("transparent");
+ };
+ self.append(&list_view);
+ imp.list_view.set(list_view).unwrap();
+ }
+
+ fn setup_model(&self) {
+ let imp = imp::WorkspaceList::from_instance(self);
+ let model = gio::ListStore::new(BoxedWorkspace::static_type());
+
+ let selection_model = gtk4::NoSelection::new(Some(&model));
+
+ // Wrap model with selection and pass it to the list view
+ let list_view = imp.list_view.get().unwrap();
+ list_view.set_model(Some(&selection_model));
+ imp.model.set(model).expect("Could not set model");
+ }
+
+ fn setup_factory(&self) {
+ let imp = imp::WorkspaceList::from_instance(self);
+ let factory = SignalListItemFactory::new();
+ let model = imp.model.get().expect("Failed to get saved app model.");
+ let tx = imp.tx.get().unwrap().clone();
+ let icon_size = imp.config.get().unwrap().get_applet_icon_size();
+ factory.connect_setup(
+ glib::clone!(@weak model => move |_, list_item| {
+ let workspace_button = WorkspaceButton::new(tx.clone());
+ list_item.set_child(Some(&workspace_button));
+ }),
+ );
+ factory.connect_bind(|_, list_item| {
+ let workspace_object = list_item
+ .item()
+ .expect("The item has to exist.")
+ .downcast::()
+ .expect("The item has to be a `DockObject`");
+ let workspace_button = list_item
+ .child()
+ .expect("The list item child needs to exist.")
+ .downcast::()
+ .expect("The list item type needs to be `DockItem`");
+ workspace_button.set_workspace_object(&workspace_object);
+ },
+ );
+ // Set the factory of the list view
+ imp.list_view.get().unwrap().set_factory(Some(&factory));
+ }
+}
diff --git a/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs b/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs
new file mode 100644
index 00000000..90972991
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_object/imp.rs
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use std::cell::Cell;
+use std::cell::RefCell;
+
+use glib::{ParamFlags, ParamSpec, Value};
+use gtk4::gdk::glib::ParamSpecBoolean;
+use gtk4::glib;
+use gtk4::glib::ParamSpecUInt;
+use gtk4::prelude::*;
+use gtk4::subclass::prelude::*;
+use once_cell::sync::Lazy;
+
+// Object holding the state
+#[derive(Default)]
+pub struct WorkspaceObject {
+ pub(crate) id: Cell,
+ pub(crate) active: Cell,
+}
+
+// The central trait for subclassing a GObject
+#[glib::object_subclass]
+impl ObjectSubclass for WorkspaceObject {
+ const NAME: &'static str = "WorkspaceObject";
+ type Type = super::WorkspaceObject;
+ type ParentType = glib::Object;
+}
+
+// Trait shared by all GObjects
+impl ObjectImpl for WorkspaceObject {
+ fn properties() -> &'static [ParamSpec] {
+ static PROPERTIES: Lazy> = Lazy::new(|| {
+ vec![
+ ParamSpecUInt::new(
+ // Name
+ "id",
+ // Nickname
+ "id",
+ // Short description
+ "id",
+ // Minimum value
+ u32::MIN,
+ // Maximum value
+ u32::MAX,
+ // Default value
+ 0,
+ // The property can be read and written to
+ ParamFlags::READWRITE,
+ ),
+ ParamSpecBoolean::new(
+ "active",
+ "active",
+ "Indicates whether workspace is active",
+ false,
+ ParamFlags::READWRITE,
+ ),
+
+ ]
+ });
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) {
+ match pspec.name() {
+ "active" => {
+ self.active
+ .replace(value.get().expect("Value needs to be a boolean"));
+ }
+ "id" => {
+ self.id
+ .replace(value.get().expect("Value needs to be a boolean"));
+ }
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value {
+ match pspec.name() {
+ "id" => self.id.get().to_value(),
+ "active" => self.active.get().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+}
diff --git a/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs b/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs
new file mode 100644
index 00000000..5fa2f740
--- /dev/null
+++ b/applets/cosmic-applet-workspaces/src/workspace_object/mod.rs
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MPL-2.0-only
+
+use gtk4::{glib, subclass::prelude::*};
+
+mod imp;
+
+glib::wrapper! {
+ pub struct WorkspaceObject(ObjectSubclass);
+}
+
+impl WorkspaceObject {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).unwrap()
+ }
+
+ pub fn from_id_active(id: u32, active: bool) -> Self {
+ glib::Object::new(&[("id", &id), ("active", &active)]).unwrap()
+ }
+
+ pub fn id(&self) -> u32 {
+ imp::WorkspaceObject::from_instance(&self).id.get()
+ }
+
+ pub fn active(&self) -> bool {
+ imp::WorkspaceObject::from_instance(&self).active.get()
+ }
+}
+
+#[derive(Clone, Debug, Default, glib::Boxed)]
+#[boxed_type(name = "BoxedWorkspaceObject")]
+pub struct BoxedWorkspaceObject(pub Option);