diff --git a/gnome-control-center.doap b/gnome-control-center.doap
index 600fd66c8..7986d9150 100644
--- a/gnome-control-center.doap
+++ b/gnome-control-center.doap
@@ -13,7 +13,7 @@
C
-
+
Georges Basile Stavracas Neto
diff --git a/panels/meson.build b/panels/meson.build
index 2f4fdc5e3..1318904ae 100644
--- a/panels/meson.build
+++ b/panels/meson.build
@@ -15,6 +15,7 @@ panels = [
'lock',
'microphone',
'mouse',
+ 'multitasking',
'notifications',
'online-accounts',
'power',
diff --git a/panels/multitasking/assets/active-screen-edges.svg b/panels/multitasking/assets/active-screen-edges.svg
new file mode 100644
index 000000000..34dc67b4e
--- /dev/null
+++ b/panels/multitasking/assets/active-screen-edges.svg
@@ -0,0 +1,118 @@
+
+
+
+
diff --git a/panels/multitasking/assets/hot-corner.svg b/panels/multitasking/assets/hot-corner.svg
new file mode 100644
index 000000000..0a1732c0e
--- /dev/null
+++ b/panels/multitasking/assets/hot-corner.svg
@@ -0,0 +1,124 @@
+
+
+
+
diff --git a/panels/multitasking/assets/workspaces-primary-display.svg b/panels/multitasking/assets/workspaces-primary-display.svg
new file mode 100644
index 000000000..56e05c4bb
--- /dev/null
+++ b/panels/multitasking/assets/workspaces-primary-display.svg
@@ -0,0 +1,130 @@
+
+
+
+
diff --git a/panels/multitasking/assets/workspaces-span-displays.svg b/panels/multitasking/assets/workspaces-span-displays.svg
new file mode 100644
index 000000000..7b3655df9
--- /dev/null
+++ b/panels/multitasking/assets/workspaces-span-displays.svg
@@ -0,0 +1,178 @@
+
+
+
+
diff --git a/panels/multitasking/cc-multitasking-panel.c b/panels/multitasking/cc-multitasking-panel.c
new file mode 100644
index 000000000..6a3177fe9
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-panel.c
@@ -0,0 +1,150 @@
+/* cc-multitasking-panel.h
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include "cc-multitasking-panel.h"
+
+#include "cc-multitasking-resources.h"
+#include "cc-multitasking-row.h"
+#include "list-box-helper.h"
+
+struct _CcMultitaskingPanel
+{
+ CcPanel parent_instance;
+
+ GSettings *interface_settings;
+ GSettings *mutter_settings;
+ GSettings *overrides_settings;
+ GSettings *shell_settings;
+ GSettings *wm_settings;
+
+ GtkSwitch *active_screen_edges_switch;
+ GtkToggleButton *current_workspace_radio;
+ GtkToggleButton *dynamic_workspaces_radio;
+ GtkToggleButton *fixed_workspaces_radio;
+ GtkSwitch *hot_corner_switch;
+ GtkSpinButton *number_of_workspaces_spin;
+ GtkToggleButton *workspaces_primary_display_radio;
+ GtkToggleButton *workspaces_span_displays_radio;
+};
+
+CC_PANEL_REGISTER (CcMultitaskingPanel, cc_multitasking_panel)
+
+/* GObject overrides */
+
+static void
+cc_multitasking_panel_finalize (GObject *object)
+{
+ CcMultitaskingPanel *self = (CcMultitaskingPanel *)object;
+
+ g_clear_object (&self->interface_settings);
+ g_clear_object (&self->mutter_settings);
+ g_clear_object (&self->overrides_settings);
+ g_clear_object (&self->shell_settings);
+ g_clear_object (&self->wm_settings);
+
+ G_OBJECT_CLASS (cc_multitasking_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_multitasking_panel_class_init (CcMultitaskingPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_ensure (CC_TYPE_MULTITASKING_ROW);
+
+ object_class->finalize = cc_multitasking_panel_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/multitasking/cc-multitasking-panel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, active_screen_edges_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, current_workspace_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, dynamic_workspaces_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, fixed_workspaces_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, hot_corner_switch);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, number_of_workspaces_spin);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, workspaces_primary_display_radio);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingPanel, workspaces_span_displays_radio);
+}
+
+static void
+cc_multitasking_panel_init (CcMultitaskingPanel *self)
+{
+ g_resources_register (cc_multitasking_get_resource ());
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->interface_settings = g_settings_new ("org.gnome.desktop.interface");
+ g_settings_bind (self->interface_settings,
+ "enable-hot-corners",
+ self->hot_corner_switch,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ self->mutter_settings = g_settings_new ("org.gnome.mutter");
+
+ if (g_settings_get_boolean (self->mutter_settings, "workspaces-only-on-primary"))
+ gtk_toggle_button_set_active (self->workspaces_primary_display_radio, TRUE);
+ else
+ gtk_toggle_button_set_active (self->workspaces_span_displays_radio, TRUE);
+
+ g_settings_bind (self->mutter_settings,
+ "workspaces-only-on-primary",
+ self->workspaces_primary_display_radio,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (self->mutter_settings,
+ "edge-tiling",
+ self->active_screen_edges_switch,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ self->overrides_settings = g_settings_new ("org.gnome.shell.overrides");
+
+ if (g_settings_get_boolean (self->overrides_settings, "dynamic-workspaces"))
+ gtk_toggle_button_set_active (self->dynamic_workspaces_radio, TRUE);
+ else
+ gtk_toggle_button_set_active (self->fixed_workspaces_radio, TRUE);
+
+ g_settings_bind (self->overrides_settings,
+ "dynamic-workspaces",
+ self->dynamic_workspaces_radio,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ self->wm_settings = g_settings_new ("org.gnome.desktop.wm.preferences");
+ g_settings_bind (self->wm_settings,
+ "num-workspaces",
+ self->number_of_workspaces_spin,
+ "value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ self->shell_settings = g_settings_new ("org.gnome.shell.app-switcher");
+
+ if (g_settings_get_boolean (self->shell_settings, "current-workspace-only"))
+ gtk_toggle_button_set_active (self->current_workspace_radio, TRUE);
+
+ g_settings_bind (self->shell_settings,
+ "current-workspace-only",
+ self->current_workspace_radio,
+ "active",
+ G_SETTINGS_BIND_DEFAULT);
+}
diff --git a/panels/multitasking/cc-multitasking-panel.h b/panels/multitasking/cc-multitasking-panel.h
new file mode 100644
index 000000000..81e78f071
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-panel.h
@@ -0,0 +1,30 @@
+/* cc-multitasking-panel.h
+ *
+ * Copyright 2020 Georges Basile Stavracas Neto
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_MULTITASKING_PANEL (cc_multitasking_panel_get_type())
+G_DECLARE_FINAL_TYPE (CcMultitaskingPanel, cc_multitasking_panel, CC, MULTITASKING_PANEL, CcPanel)
+
+G_END_DECLS
diff --git a/panels/multitasking/cc-multitasking-panel.ui b/panels/multitasking/cc-multitasking-panel.ui
new file mode 100644
index 000000000..5b5725038
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-panel.ui
@@ -0,0 +1,357 @@
+
+
+
+ True
+
+
+
+
+
+
+ 1.0
+ 1.0
+ 4.0
+
+ 36.0
+
+
diff --git a/panels/multitasking/cc-multitasking-row.c b/panels/multitasking/cc-multitasking-row.c
new file mode 100644
index 000000000..608e4c48f
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-row.c
@@ -0,0 +1,684 @@
+/* cc-multitasking-row.c
+ *
+ * Copyright 2018 Purism SPC
+ * 2021 Georges Basile Stavracas Neto
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "cc-multitasking-row.h"
+
+struct _CcMultitaskingRow
+{
+ HdyPreferencesRow parent;
+
+ GtkBox *artwork_box;
+ GtkBox *header;
+ GtkImage *image;
+ GtkBox *prefixes;
+ GtkLabel *subtitle;
+ GtkBox *suffixes;
+ GtkLabel *title;
+ GtkBox *title_box;
+
+ GtkWidget *previous_parent;
+
+ gboolean use_underline;
+ gint title_lines;
+ gint subtitle_lines;
+ GtkWidget *activatable_widget;
+};
+
+static void cc_multitasking_row_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CcMultitaskingRow, cc_multitasking_row, HDY_TYPE_PREFERENCES_ROW,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, cc_multitasking_row_buildable_init))
+
+enum
+{
+ PROP_0,
+ PROP_ICON_NAME,
+ PROP_ACTIVATABLE_WIDGET,
+ PROP_SUBTITLE,
+ PROP_USE_UNDERLINE,
+ PROP_TITLE_LINES,
+ PROP_SUBTITLE_LINES,
+ N_PROPS,
+};
+
+enum
+{
+ SIGNAL_ACTIVATED,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static GParamSpec *props[N_PROPS] = { NULL, };
+static guint signals[SIGNAL_LAST_SIGNAL] = { 0, };
+
+static void
+row_activated_cb (CcMultitaskingRow *self,
+ GtkListBoxRow *row)
+{
+ /* No need to use GTK_LIST_BOX_ROW() for a pointer comparison. */
+ if ((GtkListBoxRow *) self == row)
+ cc_multitasking_row_activate (self);
+}
+
+static void
+parent_cb (CcMultitaskingRow *self)
+{
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+ if (self->previous_parent != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (self->previous_parent,
+ G_CALLBACK (row_activated_cb),
+ self);
+ self->previous_parent = NULL;
+ }
+
+ if (parent == NULL || !GTK_IS_LIST_BOX (parent))
+ return;
+
+ self->previous_parent = parent;
+ g_signal_connect_swapped (parent, "row-activated", G_CALLBACK (row_activated_cb), self);
+}
+
+static void
+update_subtitle_visibility (CcMultitaskingRow *self)
+{
+ gtk_widget_set_visible (GTK_WIDGET (self->subtitle),
+ gtk_label_get_text (self->subtitle) != NULL &&
+ g_strcmp0 (gtk_label_get_text (self->subtitle), "") != 0);
+}
+
+static void
+cc_multitasking_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON_NAME:
+ g_value_set_string (value, cc_multitasking_row_get_icon_name (self));
+ break;
+ case PROP_ACTIVATABLE_WIDGET:
+ g_value_set_object (value, (GObject *) cc_multitasking_row_get_activatable_widget (self));
+ break;
+ case PROP_SUBTITLE:
+ g_value_set_string (value, cc_multitasking_row_get_subtitle (self));
+ break;
+ case PROP_SUBTITLE_LINES:
+ g_value_set_int (value, cc_multitasking_row_get_subtitle_lines (self));
+ break;
+ case PROP_TITLE_LINES:
+ g_value_set_int (value, cc_multitasking_row_get_title_lines (self));
+ break;
+ case PROP_USE_UNDERLINE:
+ g_value_set_boolean (value, cc_multitasking_row_get_use_underline (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_multitasking_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_ICON_NAME:
+ cc_multitasking_row_set_icon_name (self, g_value_get_string (value));
+ break;
+ case PROP_ACTIVATABLE_WIDGET:
+ cc_multitasking_row_set_activatable_widget (self, (GtkWidget*) g_value_get_object (value));
+ break;
+ case PROP_SUBTITLE:
+ cc_multitasking_row_set_subtitle (self, g_value_get_string (value));
+ break;
+ case PROP_SUBTITLE_LINES:
+ cc_multitasking_row_set_subtitle_lines (self, g_value_get_int (value));
+ break;
+ case PROP_TITLE_LINES:
+ cc_multitasking_row_set_title_lines (self, g_value_get_int (value));
+ break;
+ case PROP_USE_UNDERLINE:
+ cc_multitasking_row_set_use_underline (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_multitasking_row_dispose (GObject *object)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (object);
+
+ if (self->previous_parent != NULL) {
+ g_signal_handlers_disconnect_by_func (self->previous_parent, G_CALLBACK (row_activated_cb), self);
+ self->previous_parent = NULL;
+ }
+
+ G_OBJECT_CLASS (cc_multitasking_row_parent_class)->dispose (object);
+}
+
+static void
+cc_multitasking_row_show_all (GtkWidget *widget)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (widget);
+
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+
+ gtk_container_foreach (GTK_CONTAINER (self->prefixes),
+ (GtkCallback) gtk_widget_show_all,
+ NULL);
+
+ gtk_container_foreach (GTK_CONTAINER (self->suffixes),
+ (GtkCallback) gtk_widget_show_all,
+ NULL);
+
+ GTK_WIDGET_CLASS (cc_multitasking_row_parent_class)->show_all (widget);
+}
+
+static void
+cc_multitasking_row_destroy (GtkWidget *widget)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (widget);
+
+ if (self->header)
+ {
+ gtk_widget_destroy (GTK_WIDGET (self->header));
+ self->header = NULL;
+ }
+
+ cc_multitasking_row_set_activatable_widget (self, NULL);
+
+ self->prefixes = NULL;
+ self->suffixes = NULL;
+
+ GTK_WIDGET_CLASS (cc_multitasking_row_parent_class)->destroy (widget);
+}
+
+static void
+cc_multitasking_row_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (container);
+
+ /* When constructing the widget, we want the box to be added as the child of
+ * the GtkListBoxRow, as an implementation detail.
+ */
+ if (!self->header)
+ {
+ GTK_CONTAINER_CLASS (cc_multitasking_row_parent_class)->add (container, child);
+ }
+ else
+ {
+ gtk_container_add (GTK_CONTAINER (self->suffixes), child);
+ gtk_widget_show (GTK_WIDGET (self->suffixes));
+ }
+}
+
+static void
+cc_multitasking_row_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (container);
+
+ if (child == GTK_WIDGET (self->header))
+ GTK_CONTAINER_CLASS (cc_multitasking_row_parent_class)->remove (container, child);
+ else if (gtk_widget_get_parent (child) == GTK_WIDGET (self->prefixes))
+ gtk_container_remove (GTK_CONTAINER (self->prefixes), child);
+ else
+ gtk_container_remove (GTK_CONTAINER (self->suffixes), child);
+}
+
+typedef struct {
+ CcMultitaskingRow *row;
+ GtkCallback callback;
+ gpointer callback_data;
+} ForallData;
+
+static void
+for_non_internal_child (GtkWidget *widget,
+ gpointer callback_data)
+{
+ ForallData *data = callback_data;
+ CcMultitaskingRow *self = data->row;
+
+ if (widget != (GtkWidget *) self->image &&
+ widget != (GtkWidget *) self->prefixes &&
+ widget != (GtkWidget *) self->suffixes &&
+ widget != (GtkWidget *) self->title_box)
+ {
+ data->callback (widget, data->callback_data);
+ }
+}
+
+static void
+cc_multitasking_row_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (container);
+ ForallData data;
+
+ if (include_internals)
+ {
+ GTK_CONTAINER_CLASS (cc_multitasking_row_parent_class)->forall (GTK_CONTAINER (self),
+ include_internals,
+ callback,
+ callback_data);
+ return;
+ }
+
+ data.row = self;
+ data.callback = callback;
+ data.callback_data = callback_data;
+
+ if (self->prefixes)
+ {
+ GTK_CONTAINER_GET_CLASS (self->prefixes)->forall (GTK_CONTAINER (self->prefixes),
+ include_internals,
+ for_non_internal_child,
+ &data);
+ }
+ if (self->suffixes)
+ {
+ GTK_CONTAINER_GET_CLASS (self->suffixes)->forall (GTK_CONTAINER (self->suffixes),
+ include_internals,
+ for_non_internal_child,
+ &data);
+ }
+ if (self->header)
+ {
+ GTK_CONTAINER_GET_CLASS (self->header)->forall (GTK_CONTAINER (self->header),
+ include_internals,
+ for_non_internal_child,
+ &data);
+ }
+}
+
+static void
+cc_multitasking_row_class_init (CcMultitaskingRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->get_property = cc_multitasking_row_get_property;
+ object_class->set_property = cc_multitasking_row_set_property;
+ object_class->dispose = cc_multitasking_row_dispose;
+
+ widget_class->destroy = cc_multitasking_row_destroy;
+ widget_class->show_all = cc_multitasking_row_show_all;
+
+ container_class->add = cc_multitasking_row_add;
+ container_class->remove = cc_multitasking_row_remove;
+ container_class->forall = cc_multitasking_row_forall;
+
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "Icon name",
+ "",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_ACTIVATABLE_WIDGET] =
+ g_param_spec_object ("activatable-widget",
+ "Activatable widget",
+ "The widget to be activated when the row is activated",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE);
+
+ props[PROP_SUBTITLE] =
+ g_param_spec_string ("subtitle",
+ "Subtitle",
+ "Subtitle",
+ "",
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_USE_UNDERLINE] =
+ g_param_spec_boolean ("use-underline",
+ "Use underline",
+ "If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_TITLE_LINES] =
+ g_param_spec_int ("title-lines",
+ "Number of title lines",
+ "The desired number of title lines",
+ 0, G_MAXINT,
+ 1,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_SUBTITLE_LINES] =
+ g_param_spec_int ("subtitle-lines",
+ "Number of subtitle lines",
+ "The desired number of subtitle lines",
+ 0, G_MAXINT,
+ 1,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ signals[SIGNAL_ACTIVATED] =
+ g_signal_new ("activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/multitasking/cc-multitasking-row.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, artwork_box);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, header);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, image);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, prefixes);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, subtitle);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, suffixes);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, title);
+ gtk_widget_class_bind_template_child (widget_class, CcMultitaskingRow, title_box);
+}
+
+static gboolean
+string_is_not_empty (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ const gchar *string = g_value_get_string (from_value);
+
+ g_value_set_boolean (to_value, string != NULL && g_strcmp0 (string, "") != 0);
+
+ return TRUE;
+}
+
+static void
+cc_multitasking_row_init (CcMultitaskingRow *self)
+{
+ self->title_lines = 1;
+ self->subtitle_lines = 1;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_object_bind_property_full (self, "title",
+ self->title, "visible",
+ G_BINDING_SYNC_CREATE,
+ string_is_not_empty,
+ NULL, NULL, NULL);
+
+ update_subtitle_visibility (self);
+
+ g_signal_connect (self, "notify::parent", G_CALLBACK (parent_cb), NULL);
+}
+
+static void
+cc_multitasking_row_buildable_add_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *type)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (buildable);
+
+ if (self->header == NULL || !type)
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
+ else if (type && strcmp (type, "prefix") == 0)
+ cc_multitasking_row_add_prefix (self, GTK_WIDGET (child));
+ else if (type && strcmp (type, "artwork") == 0)
+ cc_multitasking_row_add_artwork(self, GTK_WIDGET (child));
+ else
+ GTK_BUILDER_WARN_INVALID_CHILD_TYPE (self, type);
+}
+
+static void
+cc_multitasking_row_buildable_init (GtkBuildableIface *iface)
+{
+ iface->add_child = cc_multitasking_row_buildable_add_child;
+}
+
+const gchar *
+cc_multitasking_row_get_subtitle (CcMultitaskingRow *self)
+{
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), NULL);
+
+ return gtk_label_get_text (self->subtitle);
+}
+
+void
+cc_multitasking_row_set_subtitle (CcMultitaskingRow *self,
+ const gchar *subtitle)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+
+ if (g_strcmp0 (gtk_label_get_text (self->subtitle), subtitle) == 0)
+ return;
+
+ gtk_label_set_text (self->subtitle, subtitle);
+ gtk_widget_set_visible (GTK_WIDGET (self->subtitle),
+ subtitle != NULL && g_strcmp0 (subtitle, "") != 0);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]);
+}
+
+const gchar *
+cc_multitasking_row_get_icon_name (CcMultitaskingRow *self)
+{
+ const gchar *icon_name;
+
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), NULL);
+
+ gtk_image_get_icon_name (self->image, &icon_name, NULL);
+
+ return icon_name;
+}
+
+void
+cc_multitasking_row_set_icon_name (CcMultitaskingRow *self,
+ const gchar *icon_name)
+{
+ const gchar *old_icon_name;
+
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+
+ gtk_image_get_icon_name (self->image, &old_icon_name, NULL);
+ if (g_strcmp0 (old_icon_name, icon_name) == 0)
+ return;
+
+ gtk_image_set_from_icon_name (self->image, icon_name, GTK_ICON_SIZE_INVALID);
+ gtk_widget_set_visible (GTK_WIDGET (self->image),
+ icon_name != NULL && g_strcmp0 (icon_name, "") != 0);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
+}
+
+GtkWidget *
+cc_multitasking_row_get_activatable_widget (CcMultitaskingRow *self)
+{
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), NULL);
+
+ return self->activatable_widget;
+}
+
+static void
+activatable_widget_weak_notify (gpointer data,
+ GObject *where_the_object_was)
+{
+ CcMultitaskingRow *self = CC_MULTITASKING_ROW (data);
+
+ self->activatable_widget = NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]);
+}
+
+void
+cc_multitasking_row_set_activatable_widget (CcMultitaskingRow *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+ g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
+
+ if (self->activatable_widget == widget)
+ return;
+
+ if (self->activatable_widget)
+ g_object_weak_unref (G_OBJECT (self->activatable_widget),
+ activatable_widget_weak_notify,
+ self);
+
+ self->activatable_widget = widget;
+
+ if (self->activatable_widget != NULL) {
+ g_object_weak_ref (G_OBJECT (self->activatable_widget),
+ activatable_widget_weak_notify,
+ self);
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), TRUE);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVATABLE_WIDGET]);
+}
+
+gboolean
+cc_multitasking_row_get_use_underline (CcMultitaskingRow *self)
+{
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), FALSE);
+
+ return self->use_underline;
+}
+
+void
+cc_multitasking_row_set_use_underline (CcMultitaskingRow *self,
+ gboolean use_underline)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+
+ use_underline = !!use_underline;
+
+ if (self->use_underline == use_underline)
+ return;
+
+ self->use_underline = use_underline;
+ hdy_preferences_row_set_use_underline (HDY_PREFERENCES_ROW (self), self->use_underline);
+ gtk_label_set_use_underline (self->title, self->use_underline);
+ gtk_label_set_use_underline (self->subtitle, self->use_underline);
+ gtk_label_set_mnemonic_widget (self->title, GTK_WIDGET (self));
+ gtk_label_set_mnemonic_widget (self->subtitle, GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_UNDERLINE]);
+}
+
+gint
+cc_multitasking_row_get_title_lines (CcMultitaskingRow *self)
+{
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), 0);
+
+ return self->title_lines;
+}
+
+void
+cc_multitasking_row_set_title_lines (CcMultitaskingRow *self,
+ gint title_lines)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+ g_return_if_fail (title_lines >= 0);
+
+ if (self->title_lines == title_lines)
+ return;
+
+ self->title_lines = title_lines;
+
+ gtk_label_set_lines (self->title, title_lines);
+ gtk_label_set_ellipsize (self->title, title_lines == 0 ? PANGO_ELLIPSIZE_NONE : PANGO_ELLIPSIZE_END);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE_LINES]);
+}
+
+gint
+cc_multitasking_row_get_subtitle_lines (CcMultitaskingRow *self)
+{
+ g_return_val_if_fail (CC_IS_MULTITASKING_ROW (self), 0);
+
+ return self->subtitle_lines;
+}
+
+void
+cc_multitasking_row_set_subtitle_lines (CcMultitaskingRow *self,
+ gint subtitle_lines)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+ g_return_if_fail (subtitle_lines >= 0);
+
+ if (self->subtitle_lines == subtitle_lines)
+ return;
+
+ self->subtitle_lines = subtitle_lines;
+
+ gtk_label_set_lines (self->subtitle, subtitle_lines);
+ gtk_label_set_ellipsize (self->subtitle, subtitle_lines == 0 ? PANGO_ELLIPSIZE_NONE : PANGO_ELLIPSIZE_END);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE_LINES]);
+}
+
+void
+cc_multitasking_row_add_prefix (CcMultitaskingRow *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+ g_return_if_fail (GTK_IS_WIDGET (self));
+
+ gtk_box_pack_start (self->prefixes, widget, FALSE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (self->prefixes));
+}
+
+void
+cc_multitasking_row_add_artwork (CcMultitaskingRow *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+ g_return_if_fail (GTK_IS_WIDGET (self));
+
+ /* HACK: the artwork box pushes the title too much to the top, so we
+ * need to compensate this here.
+ */
+ gtk_widget_set_margin_top (GTK_WIDGET (self->header), 12);
+
+ gtk_box_pack_start (self->artwork_box, widget, FALSE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (self->artwork_box));
+}
+
+void
+cc_multitasking_row_activate (CcMultitaskingRow *self)
+{
+ g_return_if_fail (CC_IS_MULTITASKING_ROW (self));
+
+ if (self->activatable_widget)
+ gtk_widget_mnemonic_activate (self->activatable_widget, FALSE);
+
+ g_signal_emit (self, signals[SIGNAL_ACTIVATED], 0);
+}
diff --git a/panels/multitasking/cc-multitasking-row.h b/panels/multitasking/cc-multitasking-row.h
new file mode 100644
index 000000000..c35d9d688
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-row.h
@@ -0,0 +1,63 @@
+/* cc-multitasking-row.h
+ *
+ * Copyright 2018 Purism SPC
+ * 2021 Georges Basile Stavracas Neto
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_MULTITASKING_ROW (cc_multitasking_row_get_type())
+G_DECLARE_FINAL_TYPE (CcMultitaskingRow, cc_multitasking_row, CC, MULTITASKING_ROW, HdyPreferencesRow)
+
+const gchar *cc_multitasking_row_get_subtitle (CcMultitaskingRow *self);
+void cc_multitasking_row_set_subtitle (CcMultitaskingRow *self,
+ const gchar *subtitle);
+
+const gchar *cc_multitasking_row_get_icon_name (CcMultitaskingRow *self);
+void cc_multitasking_row_set_icon_name (CcMultitaskingRow *self,
+ const gchar *icon_name);
+
+GtkWidget *cc_multitasking_row_get_activatable_widget (CcMultitaskingRow *self);
+void cc_multitasking_row_set_activatable_widget (CcMultitaskingRow *self,
+ GtkWidget *widget);
+
+gboolean cc_multitasking_row_get_use_underline (CcMultitaskingRow *self);
+void cc_multitasking_row_set_use_underline (CcMultitaskingRow *self,
+ gboolean use_underline);
+
+gint cc_multitasking_row_get_title_lines (CcMultitaskingRow *self);
+void cc_multitasking_row_set_title_lines (CcMultitaskingRow *self,
+ gint title_lines);
+
+gint cc_multitasking_row_get_subtitle_lines (CcMultitaskingRow *self);
+void cc_multitasking_row_set_subtitle_lines (CcMultitaskingRow *self,
+ gint subtitle_lines);
+
+void cc_multitasking_row_add_prefix (CcMultitaskingRow *self,
+ GtkWidget *widget);
+
+void cc_multitasking_row_add_artwork (CcMultitaskingRow *self,
+ GtkWidget *widget);
+
+void cc_multitasking_row_activate (CcMultitaskingRow *self);
+
+G_END_DECLS
diff --git a/panels/multitasking/cc-multitasking-row.ui b/panels/multitasking/cc-multitasking-row.ui
new file mode 100644
index 000000000..a3377229f
--- /dev/null
+++ b/panels/multitasking/cc-multitasking-row.ui
@@ -0,0 +1,122 @@
+
+
+
+ False
+
+
+
+
+
+
+
+
diff --git a/panels/multitasking/gnome-multitasking-panel.desktop.in.in b/panels/multitasking/gnome-multitasking-panel.desktop.in.in
new file mode 100644
index 000000000..675879bc2
--- /dev/null
+++ b/panels/multitasking/gnome-multitasking-panel.desktop.in.in
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Name=Multitasking
+Comment=Manage preferences for productivity and multitasking
+Exec=gnome-control-center multitasking
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings-multitasking-symbolic
+Terminal=false
+Type=Application
+NoDisplay=true
+StartupNotify=true
+Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-PersonalizationSettings;
+OnlyShowIn=GNOME;
+# Translators: Search terms to find the Search panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Multitasking;Multitask;Productivity;Customize;Desktop;
diff --git a/panels/multitasking/icons/meson.build b/panels/multitasking/icons/meson.build
new file mode 100644
index 000000000..b274dd102
--- /dev/null
+++ b/panels/multitasking/icons/meson.build
@@ -0,0 +1,4 @@
+install_data(
+ 'scalable/org.gnome.Settings-multitasking-symbolic.svg',
+ install_dir: join_paths(control_center_icondir, 'hicolor', 'scalable', 'apps')
+)
diff --git a/panels/multitasking/icons/scalable/org.gnome.Settings-multitasking-symbolic.svg b/panels/multitasking/icons/scalable/org.gnome.Settings-multitasking-symbolic.svg
new file mode 100644
index 000000000..0e09bbcd5
--- /dev/null
+++ b/panels/multitasking/icons/scalable/org.gnome.Settings-multitasking-symbolic.svg
@@ -0,0 +1,106 @@
+
+
diff --git a/panels/multitasking/meson.build b/panels/multitasking/meson.build
new file mode 100644
index 000000000..772b63813
--- /dev/null
+++ b/panels/multitasking/meson.build
@@ -0,0 +1,49 @@
+panels_list += cappletname
+desktop = 'gnome-@0@-panel.desktop'.format(cappletname)
+
+desktop_in = configure_file(
+ input: desktop + '.in.in',
+ output: desktop + '.in',
+ configuration: desktop_conf
+)
+
+i18n.merge_file(
+ desktop,
+ type: 'desktop',
+ input: desktop_in,
+ output: desktop,
+ po_dir: po_dir,
+ install: true,
+ install_dir: control_center_desktopdir
+)
+
+sources = files(
+ 'cc-multitasking-panel.c',
+ 'cc-multitasking-row.c',
+)
+
+resource_data = files(
+ 'cc-multitasking-panel.ui',
+)
+
+sources += gnome.compile_resources(
+ 'cc-' + cappletname + '-resources',
+ cappletname + '.gresource.xml',
+ c_name: 'cc_' + cappletname,
+ dependencies: resource_data,
+ export: true
+)
+
+cflags += [
+ '-DDATADIR="@0@"'.format(control_center_datadir)
+]
+
+panels_libs += static_library(
+ cappletname,
+ sources: sources,
+ include_directories: [ top_inc, common_inc ],
+ dependencies: common_deps,
+ c_args: cflags
+)
+
+subdir('icons')
diff --git a/panels/multitasking/multitasking.gresource.xml b/panels/multitasking/multitasking.gresource.xml
new file mode 100644
index 000000000..db8365f68
--- /dev/null
+++ b/panels/multitasking/multitasking.gresource.xml
@@ -0,0 +1,13 @@
+
+
+
+ cc-multitasking-panel.ui
+ cc-multitasking-row.ui
+
+
+ assets/active-screen-edges.svg
+ assets/hot-corner.svg
+ assets/workspaces-primary-display.svg
+ assets/workspaces-span-displays.svg
+
+
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
index a8eb279d7..fe536dbd9 100644
--- a/shell/cc-panel-list.c
+++ b/shell/cc-panel-list.c
@@ -386,6 +386,7 @@ static const gchar * const panel_order[] = {
"background",
"notifications",
"search",
+ "multitasking",
"applications",
"privacy",
"online-accounts",
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
index f20384394..fcbf398ca 100644
--- a/shell/cc-panel-loader.c
+++ b/shell/cc-panel-loader.c
@@ -43,6 +43,7 @@ extern GType cc_display_panel_get_type (void);
extern GType cc_info_overview_panel_get_type (void);
extern GType cc_keyboard_panel_get_type (void);
extern GType cc_mouse_panel_get_type (void);
+extern GType cc_multitasking_panel_get_type (void);
#ifdef BUILD_NETWORK
extern GType cc_network_panel_get_type (void);
extern GType cc_wifi_panel_get_type (void);
@@ -107,6 +108,7 @@ static CcPanelLoaderVtable default_panels[] =
PANEL_TYPE("lock", cc_lock_panel_get_type, NULL),
PANEL_TYPE("microphone", cc_microphone_panel_get_type, NULL),
PANEL_TYPE("mouse", cc_mouse_panel_get_type, NULL),
+ PANEL_TYPE("multitasking", cc_multitasking_panel_get_type, NULL),
#ifdef BUILD_NETWORK
PANEL_TYPE("network", cc_network_panel_get_type, NULL),
PANEL_TYPE("wifi", cc_wifi_panel_get_type, cc_wifi_panel_static_init_func),
diff --git a/shell/gnome-control-center.gresource.xml b/shell/gnome-control-center.gresource.xml
index 9be077ec9..5550440e1 100644
--- a/shell/gnome-control-center.gresource.xml
+++ b/shell/gnome-control-center.gresource.xml
@@ -6,4 +6,10 @@
help-overlay.ui
style.css
+
+
+
+ icons/multitasking-symbolic.svg
+ style.css
+
diff --git a/shell/icons/multitasking-symbolic.svg b/shell/icons/multitasking-symbolic.svg
new file mode 100644
index 000000000..7959ddbfc
--- /dev/null
+++ b/shell/icons/multitasking-symbolic.svg
@@ -0,0 +1,98 @@
+
+