From e347793e5094df43ceea147bde33a33943b07524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 24 Jul 2024 10:54:42 +0200 Subject: [PATCH 01/22] docs: Mention `gtk-builder-tool --simplify` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- HACKING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HACKING.md b/HACKING.md index 831d2361e..61cbd1177 100644 --- a/HACKING.md +++ b/HACKING.md @@ -42,6 +42,8 @@ When submitting a merge request consider checking these first: an added [screenshot test](./tests/test-take-screenshots.c), a tool to exercise new DBus API (see e.g. [tools/check-mount-operation](./tools/check-mount-operation). +- [ ] Are property assignments to default values removed from UI files? (See + `gtk-builder-tool simplify file.ui`) If any of the above criteria aren't met yet it's still fine (and encouraged) to open a merge request marked as draft. Please indicate -- GitLab From 3e61125128ca3e1385f18f53861c462ad00e4603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 24 Jul 2024 10:59:41 +0200 Subject: [PATCH 02/22] style: Drop unused osk-button class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- src/stylesheet/common.css | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index fae26dd17..cf16a2674 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -51,13 +51,6 @@ phosh-top-panel .phosh-topbar-date { border-radius: 99px; } -.phosh-osk-button { - border-radius: 50%; - min-width: 0; - min-height: 0; - padding: 6px; -} - /* * Settings menu */ -- GitLab From 66f2297ce1900651a406f7f5802ddb869016ebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 1 Jul 2024 19:59:39 +0200 Subject: [PATCH 03/22] launcher-box: Drop duplicate property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gbp-Dch: Ignore Signed-off-by: Guido Günther Part-of: --- plugins/launcher-box/launcher-box.ui | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/launcher-box/launcher-box.ui b/plugins/launcher-box/launcher-box.ui index bea7540d5..6e14c8848 100644 --- a/plugins/launcher-box/launcher-box.ui +++ b/plugins/launcher-box/launcher-box.ui @@ -30,7 +30,6 @@ True Launchers - True True -- GitLab From 3e72cee67ab6648613c394d75b421bcc806c46f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 18:52:38 +0200 Subject: [PATCH 04/22] wifi-status-page: Wrap text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can easily overflow in translations Signed-off-by: Guido Günther Part-of: --- src/ui/wifi-status-page.ui | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/wifi-status-page.ui b/src/ui/wifi-status-page.ui index 9c77204f4..938567af2 100644 --- a/src/ui/wifi-status-page.ui +++ b/src/ui/wifi-status-page.ui @@ -30,6 +30,8 @@ True No Wi-Fi Device Found + center + True @@ -58,6 +60,8 @@ True Wi-Fi Disabled + center + True -- GitLab From ab15c1a643daf6362e8b0994f6ba808c7382f022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 20:19:06 +0200 Subject: [PATCH 05/22] ci: Ensure build-deps are present when building dist tarball MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Meson checks for available libraries so we need those. Signed-off-by: Guido Günther Part-of: --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eeff37511..05fff65c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -268,6 +268,10 @@ check-dist: stage: test+docs image: ${DEBIAN_IMAGE} <<: *trixie_vars + before_script: + - export DEBIAN_FRONTEND=noninteractive + - apt -y update + - apt -y build-dep . needs: [] script: - meson setup --werror _build-dist -- GitLab From e60309b0fd31e47532265de116f81da65a05b140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 20:13:17 +0200 Subject: [PATCH 06/22] check-license-header: Fail on too short header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We expect it to at least have one prelude, then copyright. Signed-off-by: Guido Günther Part-of: --- tools/check-license-headers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check-license-headers.py b/tools/check-license-headers.py index 7240b3e06..c8d1564a8 100755 --- a/tools/check-license-headers.py +++ b/tools/check-license-headers.py @@ -38,7 +38,7 @@ def handle_file(path: str, filename: str): header.reverse() - if len(header) == 0: + if len(header) < 2: fail(path, "Expected header") line = header.pop() -- GitLab From 9fcd6c836031f22121b3c9726ead95f665da6bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 12:50:48 +0200 Subject: [PATCH 07/22] bt-info: Use g_connect_object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Less code. Signed-off-by: Guido Günther Part-of: --- src/bt-info.c | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/bt-info.c b/src/bt-info.c index cc58fdd16..c2bf11e49 100644 --- a/src/bt-info.c +++ b/src/bt-info.c @@ -157,27 +157,12 @@ phosh_bt_info_constructed (GObject *object) return; } - g_signal_connect_swapped (self->bt, - "notify::icon-name", - G_CALLBACK (update_icon), - self); - - /* TODO: track number of BT devices */ - g_signal_connect_swapped (self->bt, - "notify::enabled", - G_CALLBACK (update_info), - self); - - /* We don't use a binding for self->enabled so we can keep - the property r/o */ - g_signal_connect_swapped (self->bt, - "notify::enabled", - G_CALLBACK (on_bt_enabled), - self); - g_signal_connect_swapped (self->bt, - "notify::present", - G_CALLBACK (on_bt_present), - self); + g_object_connect (self->bt, + "swapped-signal::notify::icon-name", update_icon, self, + "swapped-signal::notify::enabled", update_info, self, + "swapped-signal::notify::enabled", on_bt_enabled, self, + "swapped-signal::notify::present", on_bt_present, self, + NULL); } -- GitLab From d32505a109de11405152c297e20d83b880e50bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 12:51:37 +0200 Subject: [PATCH 08/22] bt-info: Only connect to enabled once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guido Günther Part-of: --- src/bt-info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bt-info.c b/src/bt-info.c index c2bf11e49..d56c77b39 100644 --- a/src/bt-info.c +++ b/src/bt-info.c @@ -110,6 +110,8 @@ on_bt_enabled (PhoshBtInfo *self, GParamSpec *pspec, PhoshBtManager *bt) self->enabled = enabled; g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENABLED]); + + update_info (self); } @@ -159,7 +161,6 @@ phosh_bt_info_constructed (GObject *object) g_object_connect (self->bt, "swapped-signal::notify::icon-name", update_icon, self, - "swapped-signal::notify::enabled", update_info, self, "swapped-signal::notify::enabled", on_bt_enabled, self, "swapped-signal::notify::present", on_bt_present, self, NULL); -- GitLab From 9746005e4338de489cf850d2fd35e9fe211bfadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Jul 2024 13:11:29 +0200 Subject: [PATCH 09/22] settings: Use an action to toggle Bluetooth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows us to use the same code on status page and quick setting and makes it available to plugins. Signed-off-by: Guido Günther Part-of: --- src/settings.c | 6 ++++-- src/ui/settings.ui | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/settings.c b/src/settings.c index dc978500d..5facc842e 100644 --- a/src/settings.c +++ b/src/settings.c @@ -409,9 +409,11 @@ wwan_setting_long_pressed_cb (PhoshSettings *self) open_settings_panel (self, "wwan"); } + static void -bt_setting_clicked_cb (PhoshSettings *self) +on_toggle_bt_activated (GSimpleAction *action, GVariant *param, gpointer data) { + PhoshSettings *self = PHOSH_SETTINGS (data); PhoshShell *shell = phosh_shell_get_default (); PhoshBtManager *manager; gboolean enabled; @@ -851,7 +853,6 @@ phosh_settings_class_init (PhoshSettingsClass *klass) gtk_widget_class_bind_template_child (widget_class, PhoshSettings, scrolled_window); gtk_widget_class_bind_template_callback (widget_class, battery_setting_clicked_cb); - gtk_widget_class_bind_template_callback (widget_class, bt_setting_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, bt_setting_long_pressed_cb); gtk_widget_class_bind_template_callback (widget_class, docked_setting_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, docked_setting_long_pressed_cb); @@ -877,6 +878,7 @@ phosh_settings_class_init (PhoshSettingsClass *klass) static const GActionEntry entries[] = { { .name = "launch-panel", .activate = on_launch_panel_activated, .parameter_type = "s" }, { .name = "close-status-page", .activate = on_close_status_page_activated }, + { .name = "toggle-bt", .activate = on_toggle_bt_activated }, }; diff --git a/src/ui/settings.ui b/src/ui/settings.ui index 47ead5a86..b339ae6c3 100644 --- a/src/ui/settings.ui +++ b/src/ui/settings.ui @@ -116,7 +116,7 @@ True - + settings.toggle-bt -- GitLab From 4eee253d7ba63a2df6979ce109c1e55218a96da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 24 Jul 2024 10:29:48 +0200 Subject: [PATCH 10/22] settings: Only apply margin to toplevel viewport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current CSS would apply a wide margin to all viewports in settings but we only want it for the toplevel one that contains all the widgets but not for others like the ones in status pages. Signed-off-by: Guido Günther Part-of: --- src/stylesheet/common.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index cf16a2674..64597cc3c 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -65,7 +65,7 @@ phosh-top-panel .phosh-topbar-date { border: 2px solid @theme_bg_color; } -.phosh-settings-menu scrolledwindow > viewport { +.phosh-settings-menu > scrolledwindow > viewport { padding: 0 16px; } -- GitLab From 3d14d00be6ea30b0f8cf0706107c58687c89f443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 24 Jul 2024 10:44:36 +0200 Subject: [PATCH 11/22] settings: Use background color for all separators in settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise they're hard to see. Signed-off-by: Guido Günther Part-of: --- src/stylesheet/common.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index 64597cc3c..b4055c908 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -69,6 +69,10 @@ phosh-top-panel .phosh-topbar-date { padding: 0 16px; } +.phosh-settings-menu separator { + background: @phosh_borders_color; +} + #phosh_quick_settings flowboxchild > button { border-radius: 99px; border: 0; @@ -146,10 +150,6 @@ button.phosh-settings-details:focus { background-color: transparent; } -.phosh-settings-list-box separator { - background: @phosh_borders_color; -} - .phosh-settings-list-box list row { border-radius: 8px; } -- GitLab From ab77786f9d7ff0eb059ce034b95636eeb8e2b850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 13:38:43 +0200 Subject: [PATCH 12/22] bt-manager: Modernize property definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gbp-Dch: Ignore Signed-off-by: Guido Günther Part-of: --- src/bt-manager.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/bt-manager.c b/src/bt-manager.c index 7ec1336d7..459014891 100644 --- a/src/bt-manager.c +++ b/src/bt-manager.c @@ -42,9 +42,7 @@ static GParamSpec *props[PROP_LAST_PROP]; struct _PhoshBtManager { PhoshManager manager; - /* Whether bt radio is on */ gboolean enabled; - /* Whether we have a bt device is present */ gboolean present; const char *icon_name; @@ -205,30 +203,35 @@ phosh_bt_manager_class_init (PhoshBtManagerClass *klass) manager_class->idle_init = phosh_bt_manager_idle_init; + /** + * PhoshBtManager::icon-name: + * + * A icon name that indicates the current Bluetooth status. + */ props[PROP_ICON_NAME] = g_param_spec_string ("icon-name", "icon name", "The bt icon name", "bluetooth-disabled-symbolic", - G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); - + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshBtManager::enabled: + * + * Whether a Bluetooth is enabled. + */ props[PROP_ENABLED] = - g_param_spec_boolean ("enabled", - "enabled", - "Whether bluetooth hardware is enabled", + g_param_spec_boolean ("enabled", "", "", FALSE, - G_PARAM_READABLE | - G_PARAM_EXPLICIT_NOTIFY | - G_PARAM_STATIC_STRINGS); - + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshBtManager::present: + * + * Whether a Bluetooth adapter is present + */ props[PROP_PRESENT] = - g_param_spec_boolean ("present", - "Present", - "Whether bluettoh hardware is present", + g_param_spec_boolean ("present", "", "", FALSE, - G_PARAM_READABLE | - G_PARAM_EXPLICIT_NOTIFY | - G_PARAM_STATIC_STRINGS); + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } -- GitLab From 1604d7da356f85a81effc410c7f9b4dd55d5ba21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 2 Jul 2024 08:18:39 +0200 Subject: [PATCH 13/22] status-page: Remove phosh-settings-list-box style class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This breaks the HdyPreferencesRow styles and we only want to use it for the sound list boxes not the status pages in quick settings. Signed-off-by: Guido Günther Part-of: --- src/stylesheet/common.css | 22 +++++++++++++++++++++- src/ui/status-page.ui | 3 +-- src/ui/wifi-status-page.ui | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index b4055c908..2a202e03e 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -138,7 +138,7 @@ button.phosh-settings-details:focus { background: none; } -/* Listboxes in settings (e.g. audio details, quick setting status pages) */ +/* Listboxes in settings (e.g. audio details) */ .phosh-settings-list-box { border-radius: 18px; padding: 12px; @@ -165,6 +165,26 @@ button.phosh-settings-details:focus { -gtk-icon-style: symbolic; } +/* Listboxes in status pages (e.g. audio wifi or bt status pages) */ +.phosh-status-page { + border-radius: 18px; + padding-top: 12px; + padding-bottom: 12px; + border: none; + background-color: @phosh_notification_bg_color; +} + +.phosh-status-page list row:focus { + box-shadow: inset 0 0 0 2px @theme_selected_bg_color; + background: mix(@phosh_button_bg_color, @theme_selected_bg_color, 0.1); + outline-style: none; + transition: none; +} + +.phosh-status-page list row image { + -gtk-icon-style: symbolic; +} + /* * Audio devices listbox */ diff --git a/src/ui/status-page.ui b/src/ui/status-page.ui index d3957d9dd..c434075a0 100644 --- a/src/ui/status-page.ui +++ b/src/ui/status-page.ui @@ -8,8 +8,7 @@ 6 diff --git a/src/ui/wifi-status-page.ui b/src/ui/wifi-status-page.ui index 938567af2..5853f04cd 100644 --- a/src/ui/wifi-status-page.ui +++ b/src/ui/wifi-status-page.ui @@ -193,6 +193,8 @@ True horizontal 6 + 6 + 6 True -- GitLab From 17663a39f6a888e01dbf18cf6c5bbd4d3dd6a38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 1 Jul 2024 12:38:51 +0200 Subject: [PATCH 14/22] bluetooth: Depend on gnome-bluetooth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to add the headers as they're currently not shipped upsteam. We might bundle the library instead. This is from cccaaa7928c2b9195295d8c7dae80665b4b62c4d Signed-off-by: Guido Günther Part-of: --- .gitlab-ci.yml | 2 +- debian/control | 1 + meson.build | 1 + .../gnome-bluetooth/bluetooth-client.h | 48 +++++++ .../gnome-bluetooth/bluetooth-device.h | 17 +++ src/contrib/gnome-bluetooth/bluetooth-enums.h | 132 ++++++++++++++++++ .../gnome-bluetooth-enum-types.h | 28 ++++ src/meson.build | 19 ++- 8 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/contrib/gnome-bluetooth/bluetooth-client.h create mode 100644 src/contrib/gnome-bluetooth/bluetooth-device.h create mode 100644 src/contrib/gnome-bluetooth/bluetooth-enums.h create mode 100644 src/contrib/gnome-bluetooth/gnome-bluetooth-enum-types.h diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05fff65c4..6d58a8b97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,7 +52,7 @@ variables: gtk+3.0-dev libgudev-dev libhandy1-dev gcr-dev libsecret-dev gcovr linux-pam-dev meson musl-dev networkmanager networkmanager-dev ninja polkit-elogind-dev pulseaudio-dev upower-dev wayland-dev wayland-protocols ttf-dejavu evolution-data-server-dev evince-dev - libadwaita-dev json-glib-dev + libadwaita-dev json-glib-dev gnome-bluetooth-dev RUST_BINDINGS_BRANCH: main .trixie_vars: &trixie_vars diff --git a/debian/control b/debian/control index 330044ea3..603d78037 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: libfeedback-dev (>= 0.2.0), libfribidi-dev, libgcr-3-dev, + libgnome-bluetooth-3.0-dev, libgnome-desktop-3-dev, libgtk-3-dev, libgtk-4-dev, diff --git a/meson.build b/meson.build index 3a9537e95..ab1030c62 100644 --- a/meson.build +++ b/meson.build @@ -129,6 +129,7 @@ gcr_dep = dependency('gcr-3', version: '>= 3.7.5') glib_dep = dependency('glib-2.0', version: glib_ver_cmp) gio_dep = dependency('gio-2.0', version: glib_ver_cmp) gio_unix_dep = dependency('gio-unix-2.0', version: glib_ver_cmp) +gnome_bluetooth_dep = dependency ('gnome-bluetooth-3.0', version: '>= 46.0') gmobile_dep = dependency('gmobile', version: '>= 0.1.0', fallback: ['gmobile', 'gmobile_dep'], diff --git a/src/contrib/gnome-bluetooth/bluetooth-client.h b/src/contrib/gnome-bluetooth/bluetooth-client.h new file mode 100644 index 000000000..c79c460dc --- /dev/null +++ b/src/contrib/gnome-bluetooth/bluetooth-client.h @@ -0,0 +1,48 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2008 Marcel Holtmann + * Copyright (C) 2009-2021 Red Hat Inc. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include +#include + +#define BLUETOOTH_TYPE_CLIENT (bluetooth_client_get_type()) +G_DECLARE_FINAL_TYPE (BluetoothClient, bluetooth_client, BLUETOOTH, CLIENT, GObject) + +BluetoothClient *bluetooth_client_new(void); + +GListStore *bluetooth_client_get_devices (BluetoothClient *client); + +void bluetooth_client_connect_service (BluetoothClient *client, + const char *path, + gboolean connect, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean bluetooth_client_connect_service_finish (BluetoothClient *client, + GAsyncResult *res, + GError **error); + +gboolean bluetooth_client_has_connected_input_devices (BluetoothClient *client); diff --git a/src/contrib/gnome-bluetooth/bluetooth-device.h b/src/contrib/gnome-bluetooth/bluetooth-device.h new file mode 100644 index 000000000..82d8dc7f7 --- /dev/null +++ b/src/contrib/gnome-bluetooth/bluetooth-device.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Bastien Nocera + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include +#include + +#define BLUETOOTH_TYPE_DEVICE (bluetooth_device_get_type()) +G_DECLARE_FINAL_TYPE (BluetoothDevice, bluetooth_device, BLUETOOTH, DEVICE, GObject) + +const char *bluetooth_device_get_object_path (BluetoothDevice *device); +void bluetooth_device_dump (BluetoothDevice *device); +char *bluetooth_device_to_string (BluetoothDevice *device); diff --git a/src/contrib/gnome-bluetooth/bluetooth-enums.h b/src/contrib/gnome-bluetooth/bluetooth-enums.h new file mode 100644 index 000000000..3c972d62f --- /dev/null +++ b/src/contrib/gnome-bluetooth/bluetooth-enums.h @@ -0,0 +1,132 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2008 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include + +/** + * SECTION:bluetooth-enums + * @short_description: Bluetooth related enumerations + * @stability: Stable + * @include: bluetooth-enums.h + * + * Enumerations related to Bluetooth. + **/ + +/** + * BluetoothType: + * @BLUETOOTH_TYPE_ANY: any device, or a device of an unknown type + * @BLUETOOTH_TYPE_PHONE: a telephone (usually a cell/mobile phone) + * @BLUETOOTH_TYPE_MODEM: a modem + * @BLUETOOTH_TYPE_COMPUTER: a computer, can be a laptop, a wearable computer, etc. + * @BLUETOOTH_TYPE_NETWORK: a network device, such as a router + * @BLUETOOTH_TYPE_HEADSET: a headset (usually a hands-free device) + * @BLUETOOTH_TYPE_HEADPHONES: headphones (covers two ears) + * @BLUETOOTH_TYPE_OTHER_AUDIO: another type of audio device + * @BLUETOOTH_TYPE_KEYBOARD: a keyboard + * @BLUETOOTH_TYPE_MOUSE: a mouse + * @BLUETOOTH_TYPE_CAMERA: a camera (still or moving) + * @BLUETOOTH_TYPE_PRINTER: a printer + * @BLUETOOTH_TYPE_JOYPAD: a joypad, joystick, or other game controller + * @BLUETOOTH_TYPE_TABLET: a drawing tablet + * @BLUETOOTH_TYPE_VIDEO: a video device, such as a webcam + * @BLUETOOTH_TYPE_REMOTE_CONTROL: a remote control + * @BLUETOOTH_TYPE_SCANNER: a scanner + * @BLUETOOTH_TYPE_DISPLAY: a display + * @BLUETOOTH_TYPE_WEARABLE: a wearable computer + * @BLUETOOTH_TYPE_TOY: a toy or game + * @BLUETOOTH_TYPE_SPEAKERS: audio speaker or speakers + * + * The type of a Bluetooth device. See also %BLUETOOTH_TYPE_INPUT and %BLUETOOTH_TYPE_AUDIO + **/ +typedef enum { + BLUETOOTH_TYPE_ANY = 1 << 0, + BLUETOOTH_TYPE_PHONE = 1 << 1, + BLUETOOTH_TYPE_MODEM = 1 << 2, + BLUETOOTH_TYPE_COMPUTER = 1 << 3, + BLUETOOTH_TYPE_NETWORK = 1 << 4, + BLUETOOTH_TYPE_HEADSET = 1 << 5, + BLUETOOTH_TYPE_HEADPHONES = 1 << 6, + BLUETOOTH_TYPE_OTHER_AUDIO = 1 << 7, + BLUETOOTH_TYPE_KEYBOARD = 1 << 8, + BLUETOOTH_TYPE_MOUSE = 1 << 9, + BLUETOOTH_TYPE_CAMERA = 1 << 10, + BLUETOOTH_TYPE_PRINTER = 1 << 11, + BLUETOOTH_TYPE_JOYPAD = 1 << 12, + BLUETOOTH_TYPE_TABLET = 1 << 13, + BLUETOOTH_TYPE_VIDEO = 1 << 14, + BLUETOOTH_TYPE_REMOTE_CONTROL = 1 << 15, + BLUETOOTH_TYPE_SCANNER = 1 << 16, + BLUETOOTH_TYPE_DISPLAY = 1 << 17, + BLUETOOTH_TYPE_WEARABLE = 1 << 18, + BLUETOOTH_TYPE_TOY = 1 << 19, + BLUETOOTH_TYPE_SPEAKERS = 1 << 20, +} BluetoothType; + +#define _BLUETOOTH_TYPE_NUM_TYPES 21 + +/** + * BLUETOOTH_TYPE_INPUT: + * + * Use this value to select any Bluetooth input device where a #BluetoothType enum is required. + */ +#define BLUETOOTH_TYPE_INPUT (BLUETOOTH_TYPE_KEYBOARD | BLUETOOTH_TYPE_MOUSE | BLUETOOTH_TYPE_TABLET | BLUETOOTH_TYPE_JOYPAD) +/** + * BLUETOOTH_TYPE_AUDIO: + * + * Use this value to select any Bluetooth audio device where a #BluetoothType enum is required. + */ +#define BLUETOOTH_TYPE_AUDIO (BLUETOOTH_TYPE_HEADSET | BLUETOOTH_TYPE_HEADPHONES | BLUETOOTH_TYPE_OTHER_AUDIO | BLUETOOTH_TYPE_SPEAKERS) + +/** + * BluetoothBatteryType: + * @BLUETOOTH_BATTERY_TYPE_NONE: no battery reporting + * @BLUETOOTH_BATTERY_TYPE_PERCENTAGE: battery reported in percentage + * @BLUETOOTH_BATTERY_TYPE_COARSE: battery reported coarsely + * + * The type of battery reporting supported by the device. + **/ +typedef enum { + BLUETOOTH_BATTERY_TYPE_NONE, + BLUETOOTH_BATTERY_TYPE_PERCENTAGE, + BLUETOOTH_BATTERY_TYPE_COARSE +} BluetoothBatteryType; + +/** + * BluetoothAdapterState: + * @BLUETOOTH_ADAPTER_STATE_ABSENT: Bluetooth adapter is missing. + * @BLUETOOTH_ADAPTER_STATE_ON: Bluetooth adapter is on. + * @BLUETOOTH_ADAPTER_STATE_TURNING_ON: Bluetooth adapter is being turned on. + * @BLUETOOTH_ADAPTER_STATE_TURNING_OFF: Bluetooth adapter is being turned off. + * @BLUETOOTH_ADAPTER_STATE_OFF: Bluetooth adapter is off. + * + * A more precise power state for a Bluetooth adapter. + **/ +typedef enum { + BLUETOOTH_ADAPTER_STATE_ABSENT = 0, + BLUETOOTH_ADAPTER_STATE_ON, + BLUETOOTH_ADAPTER_STATE_TURNING_ON, + BLUETOOTH_ADAPTER_STATE_TURNING_OFF, + BLUETOOTH_ADAPTER_STATE_OFF, +} BluetoothAdapterState; diff --git a/src/contrib/gnome-bluetooth/gnome-bluetooth-enum-types.h b/src/contrib/gnome-bluetooth/gnome-bluetooth-enum-types.h new file mode 100644 index 000000000..c0616e07e --- /dev/null +++ b/src/contrib/gnome-bluetooth/gnome-bluetooth-enum-types.h @@ -0,0 +1,28 @@ + +/* This file is generated by glib-mkenums, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */ + +#pragma once + + #include + + + G_BEGIN_DECLS + +/* enumerations from "bluetooth-enums.h" */ + + +GType bluetooth_type_get_type (void); +#define BLUETOOTH_TYPE_TYPE (bluetooth_type_get_type()) + + +GType bluetooth_battery_type_get_type (void); +#define BLUETOOTH_TYPE_BATTERY_TYPE (bluetooth_battery_type_get_type()) + + +GType bluetooth_adapter_state_get_type (void); +#define BLUETOOTH_TYPE_ADAPTER_STATE (bluetooth_adapter_state_get_type()) + +G_END_DECLS + +/* Generated data ends here */ + diff --git a/src/meson.build b/src/meson.build index da7d46d83..b24849d1c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -367,6 +367,20 @@ if libsoup_dep.version().version_compare('< 3.5.1') ) endif + +# Headers are bundled as they're not shipped by gnome-bluetooth +# https://gitlab.gnome.org/GNOME/gnome-bluetooth/-/merge_requests/200 +gnome_bluetooth_headers_dep = declare_dependency( + include_directories: 'contrib/gnome-bluetooth', +) +# We build our own dep to avoid `-Wmissing-include-dirs` as the +# directory in the Cflags of gnome-bluetooth-3.0.pc do not +# necessarily exist +gnome_bluetooth_custom_dep = gnome_bluetooth_dep.partial_dependency( + link_args: true, + links: true, +) + phosh_deps = [ libsoup_dep, fribidi_dep, @@ -376,6 +390,8 @@ phosh_deps = [ glib_dep, gmodule_dep, gmobile_dep, + gnome_bluetooth_custom_dep, + gnome_bluetooth_headers_dep, gnome_desktop_dep, gobject_dep, gsettings_desktop_schemas_dep, @@ -471,7 +487,8 @@ if enable_introspection symbol_prefix : 'phosh', identifier_prefix : 'Phosh', link_with : phosh_lib, - includes : ['Gcr-3', 'Gio-2.0', 'Gtk-3.0', 'GnomeDesktop-3.0', 'Handy-1', 'NM-1.0'], + includes : ['Gcr-3', 'Gio-2.0', 'Gtk-3.0', 'GnomeDesktop-3.0', + 'Handy-1', 'NM-1.0', 'GnomeBluetooth-3.0'], extra_args : phosh_gir_extra_args, dependencies : phosh_static_lib_dep, fatal_warnings : true, -- GitLab From ea06109dde8ca2ab9403b59fc15d2f4774d9ac7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 17:25:01 +0200 Subject: [PATCH 15/22] bt-manager: Track connectable Bluetooth devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We track connectable Bluetooth devices and add some properties based on that like the number of currently connected devices. Signed-off-by: Guido Günther Part-of: --- src/bt-manager.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++- src/bt-manager.h | 15 +++ 2 files changed, 317 insertions(+), 3 deletions(-) diff --git a/src/bt-manager.c b/src/bt-manager.c index 459014891..d8f25d1c0 100644 --- a/src/bt-manager.c +++ b/src/bt-manager.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2020 Purism SPC + * 2024 The Phosh Developers * * SPDX-License-Identifier: GPL-3.0-or-later * @@ -15,10 +16,14 @@ #include "dbus/gsd-rfkill-dbus.h" #include "util.h" +#include "gtk-list-models/gtkfilterlistmodel.h" +#include "gnome-bluetooth-enum-types.h" +#include "bluetooth-client.h" +#include "bluetooth-device.h" + #define BUS_NAME "org.gnome.SettingsDaemon.Rfkill" #define OBJECT_PATH "/org/gnome/SettingsDaemon/Rfkill" - /** * PhoshBtManager: * @@ -33,8 +38,9 @@ enum { PROP_ICON_NAME, PROP_ENABLED, PROP_PRESENT, - /* TODO: keep track of connected devices for quick-settings */ - /* PROP_N_DEVICES */ + PROP_N_DEVICES, + PROP_N_CONNECTED, + PROP_INFO, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; @@ -45,12 +51,48 @@ struct _PhoshBtManager { gboolean enabled; gboolean present; const char *icon_name; + guint n_connected; + guint n_devices; + char *info; + + BluetoothClient *bt_client; + GtkFilterListModel *connectable_devices; PhoshRfkillDBusRfkill *proxy; }; G_DEFINE_TYPE (PhoshBtManager, phosh_bt_manager, PHOSH_TYPE_MANAGER); +static void +on_adapter_setup_mode_changed (PhoshBtManager *self) +{ + gboolean setup_mode; + + g_assert (PHOSH_IS_BT_MANAGER (self)); + g_assert (BLUETOOTH_IS_CLIENT (self->bt_client)); + + g_object_get (self->bt_client, "default-adapter-setup-mode", &setup_mode, NULL); + + g_debug ("Setup-mode: %d", setup_mode); +} + + +static void +on_adapter_state_changed (PhoshBtManager *self) +{ + BluetoothAdapterState state; + g_autofree char *name = NULL; + + g_assert (PHOSH_IS_BT_MANAGER (self)); + g_assert (BLUETOOTH_IS_CLIENT (self->bt_client)); + + g_object_get (self->bt_client, "default-adapter-state", &state, NULL); + name = g_enum_to_string (BLUETOOTH_TYPE_ADAPTER_STATE, state); + + g_debug ("State: %s", name); +} + + static void phosh_bt_manager_get_property (GObject *object, guint property_id, @@ -69,6 +111,15 @@ phosh_bt_manager_get_property (GObject *object, case PROP_PRESENT: g_value_set_boolean (value, self->present); break; + case PROP_N_DEVICES: + g_value_set_uint (value, self->n_devices); + break; + case PROP_N_CONNECTED: + g_value_set_uint (value, self->n_connected); + break; + case PROP_INFO: + g_value_set_string (value, self->info); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -186,6 +237,10 @@ phosh_bt_manager_dispose (GObject *object) PhoshBtManager *self = PHOSH_BT_MANAGER (object); g_clear_object (&self->proxy); + g_clear_object (&self->connectable_devices); + g_clear_object (&self->bt_client); + + g_clear_pointer (&self->info, g_free); G_OBJECT_CLASS (phosh_bt_manager_parent_class)->dispose (object); } @@ -232,15 +287,169 @@ phosh_bt_manager_class_init (PhoshBtManagerClass *klass) g_param_spec_boolean ("present", "", "", FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshBtManager:n-devices: + * + * The number of connectable Bluetooth devices + */ + props[PROP_N_DEVICES] = + g_param_spec_uint ("n-devices", "", "", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshBtManager:n-connected: + * + * The number of currently connected Bluetooth devices + */ + props[PROP_N_CONNECTED] = + g_param_spec_uint ("n-connected", "", "", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshBtManager:info: + * + * If only a single device is connected this gives details about it. + */ + props[PROP_INFO] = + g_param_spec_string ("info", "", "", + NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } +static gboolean +filter_devices (gpointer item, gpointer data) +{ + gboolean connectable; + BluetoothDevice *device = BLUETOOTH_DEVICE (item); + + g_object_get (device, "connectable", &connectable, NULL); + + return connectable; +} + + +static void +refilter_cb (PhoshBtManager *self) +{ + g_assert (PHOSH_IS_BT_MANAGER (self)); + + gtk_filter_list_model_refilter (self->connectable_devices); +} + + +static void +recount_cb (PhoshBtManager *self) +{ + g_autofree char *last_info = NULL; + guint n_devices, n_connected = 0; + + g_assert (PHOSH_IS_BT_MANAGER (self)); + + n_devices = g_list_model_get_n_items (G_LIST_MODEL (self->connectable_devices)); + for (int i = 0; i < n_devices; i++) { + g_autoptr (BluetoothDevice) device = NULL; + g_autofree char *info = NULL; + gboolean connected; + + device = g_list_model_get_item (G_LIST_MODEL (self->connectable_devices), i); + g_object_get (device, "connected", &connected, "alias", &info, NULL); + + if (connected) { + n_connected++; + last_info = g_steal_pointer (&info); + } + } + + if (g_strcmp0 (self->info, last_info)) { + g_debug ("New info: %s", last_info); + g_free (self->info); + self->info = g_steal_pointer (&last_info); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INFO]); + } + + if (self->n_connected != n_connected) { + g_debug ("%d Bluetooth devices connected", n_connected); + self->n_connected = n_connected; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_CONNECTED]); + } + + if (self->n_devices != n_devices) { + g_debug ("%d Bluetooth devices", n_devices); + self->n_devices = n_devices; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_DEVICES]); + } +} + + +static void +on_device_added (PhoshBtManager *self, BluetoothDevice *device) +{ + g_assert (PHOSH_IS_BT_MANAGER (self)); + g_assert (BLUETOOTH_IS_DEVICE (device)); + + g_signal_connect_swapped (device, "notify::connectable", G_CALLBACK (refilter_cb), self); + g_signal_connect_swapped (device, "notify::connected", G_CALLBACK (recount_cb), self); + + refilter_cb (self); + recount_cb (self); +} + + +static void +on_device_removed (PhoshBtManager *self, BluetoothDevice *device) +{ + refilter_cb (self); + recount_cb (self); +} + + +static void +setup_devices (PhoshBtManager *self) +{ + g_autoptr (GListStore) devices = NULL; + guint n_items; + + /* Keep a list of connectable devices */ + devices = bluetooth_client_get_devices (self->bt_client); + self->connectable_devices = gtk_filter_list_model_new (G_LIST_MODEL (devices), + filter_devices, + self, + NULL); + g_object_connect (self->bt_client, + "swapped-object-signal::device-added", on_device_added, self, + "swapped-object-signal::device-removed", on_device_removed, self, + NULL); + + /* cold plug existing devices */ + n_items = g_list_model_get_n_items (G_LIST_MODEL (devices)); + for (int i = 0; i < n_items; i++) { + g_autoptr (BluetoothDevice) device = NULL; + + device = g_list_model_get_item (G_LIST_MODEL (devices), i); + on_device_added (self, device); + } + + recount_cb (self); +} + + static void phosh_bt_manager_init (PhoshBtManager *self) { self->icon_name = "bluetooth-disabled-symbolic"; + + self->bt_client = bluetooth_client_new (); + g_object_connect (self->bt_client, + "swapped-signal::notify::default-adapter-state", + on_adapter_state_changed, self, + "swapped-signal::notify::default-adapter-setup-mode", + on_adapter_setup_mode_changed, self, + NULL); + + setup_devices (self); } @@ -294,3 +503,93 @@ phosh_bt_manager_get_present (PhoshBtManager *self) return self->present; } + +/** + * phosh_bt_manager_get_connectable_devices: + * @self: The Bluetooth manager + * + * Gets the currently connectable devices. + * + * Returns:(transfer none): The connectable devices + */ +GListModel * +phosh_bt_manager_get_connectable_devices (PhoshBtManager *self) +{ + g_return_val_if_fail (PHOSH_IS_BT_MANAGER (self), NULL); + + return G_LIST_MODEL (self->connectable_devices); +} + + +guint +phosh_bt_manager_get_n_connected (PhoshBtManager *self) +{ + g_return_val_if_fail (PHOSH_IS_BT_MANAGER (self), 0); + + return self->n_connected; +} + + +const char * +phosh_bt_manager_get_info (PhoshBtManager *self) +{ + g_return_val_if_fail (PHOSH_IS_BT_MANAGER (self), NULL); + + return self->info; +} + + +static void +on_service_connected (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + GError *err = NULL; + GTask *task = G_TASK (user_data); + gboolean success; + + success = bluetooth_client_connect_service_finish (BLUETOOTH_CLIENT (source_object), res, &err); + if (!success) + g_debug ("Failed to connect: %s", err->message); + + if (!success) { + g_task_return_error (task, err); + return; + } + + g_task_return_boolean (task, success); +} + + +void +phosh_bt_manager_connect_device_async (PhoshBtManager *self, + BluetoothDevice *device, + gboolean connect, + GAsyncReadyCallback callback, + GCancellable *cancellable, + gpointer user_data) +{ + const char *object_path; + GTask *task = g_task_new (self, cancellable, callback, user_data); + + object_path = bluetooth_device_get_object_path (device); + + g_debug ("%s device %s", connect ? "Connecting" : "Disconnecting", object_path); + bluetooth_client_connect_service (self->bt_client, + object_path, + connect, + cancellable, + on_service_connected, + task); +} + + +gboolean +phosh_bt_manager_connect_device_finish (PhoshBtManager *self, + GAsyncResult *result, + GError **error) +{ + g_autoptr (GTask) task = G_TASK (result); + + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + + return g_task_propagate_boolean (task, error); +} diff --git a/src/bt-manager.h b/src/bt-manager.h index 325ce0bb5..b76f4744c 100644 --- a/src/bt-manager.h +++ b/src/bt-manager.h @@ -8,7 +8,10 @@ #include +#include + #include +#include G_BEGIN_DECLS @@ -21,5 +24,17 @@ const char *phosh_bt_manager_get_icon_name (PhoshBtManager *self); gboolean phosh_bt_manager_get_enabled (PhoshBtManager *self); void phosh_bt_manager_set_enabled (PhoshBtManager *self, gboolean enabled); gboolean phosh_bt_manager_get_present (PhoshBtManager *self); +GListModel *phosh_bt_manager_get_connectable_devices (PhoshBtManager *self); +guint phosh_bt_manager_get_n_connected (PhoshBtManager *self); +const char *phosh_bt_manager_get_info (PhoshBtManager *self); +void phosh_bt_manager_connect_device_async (PhoshBtManager *self, + BluetoothDevice *device, + gboolean connect, + GAsyncReadyCallback callback, + GCancellable *cancellable, + gpointer user_data); +gboolean phosh_bt_manager_connect_device_finish (PhoshBtManager *self, + GAsyncResult *result, + GError **error); G_END_DECLS -- GitLab From a9dbb2975513eef35d7b71251eaf49a98eca30cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Wed, 3 Jul 2024 17:26:16 +0200 Subject: [PATCH 16/22] bt-info: Improve available information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If at least two devices are connected we print the number of connected devices. If a single device is connected we print it's info. Signed-off-by: Guido Günther Part-of: --- src/bt-info.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/bt-info.c b/src/bt-info.c index d56c77b39..6fd3b6b2e 100644 --- a/src/bt-info.c +++ b/src/bt-info.c @@ -14,6 +14,8 @@ #include "bt-info.h" #include "bt-manager.h" +#include "gmobile.h" + /** * PhoshBtInfo: * @@ -82,16 +84,43 @@ update_icon (PhoshBtInfo *self, GParamSpec *pspec, PhoshBtManager *bt) static void update_info (PhoshBtInfo *self) { + g_autofree char *msg = NULL; gboolean enabled; + guint n_connected; g_return_if_fail (PHOSH_IS_BT_INFO (self)); - /* TODO: show number of paired devices */ enabled = phosh_bt_manager_get_enabled (self->bt); - if (enabled) - phosh_status_icon_set_info (PHOSH_STATUS_ICON (self), C_("bluetooth:enabled", "On")); - else + if (!enabled) { phosh_status_icon_set_info (PHOSH_STATUS_ICON (self), _("Bluetooth")); + return; + } + + n_connected = phosh_bt_manager_get_n_connected (self->bt); + switch (n_connected) { + case 0: + break; + case 1: { + const char *info = phosh_bt_manager_get_info (self->bt); + if (gm_str_is_null_or_empty (info)) { + /* Translators: One connected Bluetooth device */ + msg = g_strdup_printf ("One device"); + } else { + msg = g_strdup (info); + } + break; + } + default: + /* Translators: The number of currently connected Bluetooth devices */ + msg = g_strdup_printf ("%d devices", n_connected); + } + + if (msg) { + phosh_status_icon_set_info (PHOSH_STATUS_ICON (self), msg); + return; + } + + phosh_status_icon_set_info (PHOSH_STATUS_ICON (self), C_("bluetooth:enabled", "On")); } @@ -163,6 +192,7 @@ phosh_bt_info_constructed (GObject *object) "swapped-signal::notify::icon-name", update_icon, self, "swapped-signal::notify::enabled", on_bt_enabled, self, "swapped-signal::notify::present", on_bt_present, self, + "swapped-signal::notify::n-connected", update_info, self, NULL); } -- GitLab From 0026def0cbcd1e1a17af798cb91cd0607d90a694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 22 Jul 2024 19:25:59 +0200 Subject: [PATCH 17/22] status-page: Add placeholder / empty state widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a placeholder widget for the displaying the empty/disabled state in settings status pages. This is very similar to `HdyStatusPage` but we add a custom widget as we can't scale down the icons in GTK3 via CSS as -gtk-icon-size doesn't exist yet and -gtk-icon-scale scales the icon but doesn't shring the size allocations. This also helps to ease displaying empty states for list boxes as the `placeholder` there doesn't work well with advanced styling. See libhandy#468. This can be replaced with AdwStatusPage once we switch to GTK4. Signed-off-by: Guido Günther Part-of: --- src/meson.build | 2 + src/phosh.gresources.xml | 1 + src/status-page-placeholder.c | 304 ++++++++++++++++++++++++++++++ src/status-page-placeholder.h | 25 +++ src/status-page.c | 2 + src/stylesheet/common.css | 5 + src/ui/status-page-placeholder.ui | 37 ++++ 7 files changed, 376 insertions(+) create mode 100644 src/status-page-placeholder.c create mode 100644 src/status-page-placeholder.h create mode 100644 src/ui/status-page-placeholder.ui diff --git a/src/meson.build b/src/meson.build index b24849d1c..896ea3d0b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -274,6 +274,7 @@ libphosh_headers = files( 'session-manager.h', 'shell.h', 'status-page.h', + 'status-page-placeholder.h', 'system-prompt.h', 'system-prompter.h', 'thumbnail.h', @@ -339,6 +340,7 @@ libphosh_sources = files( 'session-manager.c', 'shell.c', 'status-page.c', + 'status-page-placeholder.c', 'system-prompt.c', 'system-prompter.c', 'thumbnail.c', diff --git a/src/phosh.gresources.xml b/src/phosh.gresources.xml index 718e91c43..67c6a2d88 100644 --- a/src/phosh.gresources.xml +++ b/src/phosh.gresources.xml @@ -32,6 +32,7 @@ ui/settings.ui ui/splash.ui ui/status-page.ui + ui/status-page-placeholder.ui ui/system-modal-dialog.ui ui/system-prompt.ui ui/top-panel.ui diff --git a/src/status-page-placeholder.c b/src/status-page-placeholder.c new file mode 100644 index 000000000..c876cbda7 --- /dev/null +++ b/src/status-page-placeholder.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2024 Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Author: Guido Günther + */ + +#define G_LOG_DOMAIN "phosh-status-page-placeholder" + +#include "phosh-config.h" + +#include "status-page-placeholder.h" + +#include + +/** + * PhoshStatusPagePlaceholder: + * + * A placeholder in a [class@StatusPage]. + * + * The placeholder page has a title and an icon and can have a single + * child which is put below the title. + * + * This widget can be replaced with `AdwStatusPage` and a bit of styling + * once we switch to GTK4. + */ + +enum { + PROP_0, + PROP_TITLE, + PROP_ICON_NAME, + PROP_LAST_PROP +}; +static GParamSpec *props[PROP_LAST_PROP]; + +struct _PhoshStatusPagePlaceholder { + GtkBin parent; + + GtkBox *toplevel_box; + + GtkImage *icon; + char *icon_name; + GtkLabel *title_label; + + GtkWidget *extra_widget; +}; +G_DEFINE_TYPE (PhoshStatusPagePlaceholder, phosh_status_page_placeholder, GTK_TYPE_BIN) + + +static void +update_title_visibility (PhoshStatusPagePlaceholder *self) +{ + const char *text = gtk_label_get_text (self->title_label); + + gtk_widget_set_visible (GTK_WIDGET (self->title_label), !gm_str_is_null_or_empty (text)); +} + + +static void +phosh_status_page_placeholder_destroy (GtkWidget *widget) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (widget); + + if (self->toplevel_box) { + /* Trigger destruction of all contained widgets */ + gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (self->toplevel_box)); + self->toplevel_box = NULL; + self->icon = NULL; + self->title_label = NULL; + self->extra_widget = NULL; + } + + GTK_WIDGET_CLASS (phosh_status_page_placeholder_parent_class)->destroy (widget); +} + + +static void +phosh_status_page_placeholder_add (GtkContainer *container, + GtkWidget *child) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (container); + + if (!self->toplevel_box) { + GTK_CONTAINER_CLASS (phosh_status_page_placeholder_parent_class)->add (container, child); + } else if (!self->extra_widget) { + gtk_container_add (GTK_CONTAINER (self->toplevel_box), child); + self->extra_widget = child; + } else { + g_warning ("Attempting to add a second child to a PhoshStatusPagePlaceholder," + "but a PhoshStatusPagePlaceholder can only have one child"); + } +} + + +static void +phosh_status_page_placeholder_remove (GtkContainer *container, + GtkWidget *child) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (container); + + if (child == GTK_WIDGET (self->toplevel_box)) { + GTK_CONTAINER_CLASS (phosh_status_page_placeholder_parent_class)->remove (container, child); + } else if (child == self->extra_widget) { + gtk_container_remove (GTK_CONTAINER (self->toplevel_box), child); + self->extra_widget = NULL; + } else { + g_return_if_reached (); + } +} + + +static void +phosh_status_page_placeholder_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (container); + + if (include_internals) + GTK_CONTAINER_CLASS (phosh_status_page_placeholder_parent_class)->forall (container, + include_internals, + callback, + callback_data); + else if (self->extra_widget) + callback (self->extra_widget, callback_data); +} + + +static void +phosh_status_page_placeholder_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (object); + + switch (property_id) { + case PROP_TITLE: + phosh_status_page_placeholder_set_title (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + phosh_status_page_placeholder_set_icon_name (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +phosh_status_page_placeholder_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (object); + + switch (property_id) { + case PROP_TITLE: + g_value_set_string (value, phosh_status_page_placeholder_get_title (self)); + break; + case PROP_ICON_NAME: + g_value_set_string (value, phosh_status_page_placeholder_get_icon_name (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +phosh_status_page_placeholder_dispose (GObject *object) +{ + PhoshStatusPagePlaceholder *self = PHOSH_STATUS_PAGE_PLACEHOLDER (object); + + g_clear_pointer (&self->icon_name, g_free); + + G_OBJECT_CLASS (phosh_status_page_placeholder_parent_class)->dispose (object); +} + + +static void +phosh_status_page_placeholder_class_init (PhoshStatusPagePlaceholderClass *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 = phosh_status_page_placeholder_get_property; + object_class->set_property = phosh_status_page_placeholder_set_property; + object_class->dispose = phosh_status_page_placeholder_dispose; + + widget_class->destroy = phosh_status_page_placeholder_destroy; + + container_class->add = phosh_status_page_placeholder_add; + container_class->remove = phosh_status_page_placeholder_remove; + container_class->forall = phosh_status_page_placeholder_forall; + + /** + * PhoshStatusPagePlaceholder:title: + * + * The title of the placeholder page, displayed below the icon. + */ + props[PROP_TITLE] = + g_param_spec_string ("title", "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshStatusPagePlaceholder:icon-name: + * + * The name of the icon on the placeholder page + */ + props[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + + gtk_widget_class_set_template_from_resource (widget_class, + "/sm/puri/phosh/ui/status-page-placeholder.ui"); + + gtk_widget_class_bind_template_child (widget_class, PhoshStatusPagePlaceholder, icon); + gtk_widget_class_bind_template_child (widget_class, PhoshStatusPagePlaceholder, title_label); + gtk_widget_class_bind_template_child (widget_class, PhoshStatusPagePlaceholder, toplevel_box); + + gtk_widget_class_set_css_name (widget_class, "phosh-status-page-placeholder"); +} + + +static void +phosh_status_page_placeholder_init (PhoshStatusPagePlaceholder *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + update_title_visibility (self); +} + + +PhoshStatusPagePlaceholder * +phosh_status_page_placeholder_new (void) +{ + return g_object_new (PHOSH_TYPE_STATUS_PAGE_PLACEHOLDER, NULL); +} + + +void +phosh_status_page_placeholder_set_title (PhoshStatusPagePlaceholder *self, const char *title) +{ + const char *current; + + g_return_if_fail (PHOSH_IS_STATUS_PAGE_PLACEHOLDER (self)); + + current = gtk_label_get_label (self->title_label); + if (g_strcmp0 (current, title) == 0) + return; + + gtk_label_set_label (self->title_label, title); + update_title_visibility (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); +} + + +const char * +phosh_status_page_placeholder_get_title (PhoshStatusPagePlaceholder *self) +{ + g_return_val_if_fail (PHOSH_IS_STATUS_PAGE_PLACEHOLDER (self), NULL); + + return gtk_label_get_label (self->title_label); +} + + +void +phosh_status_page_placeholder_set_icon_name (PhoshStatusPagePlaceholder *self, const char *icon_name) +{ + g_return_if_fail (PHOSH_IS_STATUS_PAGE_PLACEHOLDER (self)); + + if (g_strcmp0 (self->icon_name, icon_name) == 0) + return; + + g_free (self->icon_name); + self->icon_name = g_strdup (icon_name); + + if (!icon_name) + g_object_set (G_OBJECT (self->icon), "icon-name", "image-missing", NULL); + else + g_object_set (G_OBJECT (self->icon), "icon-name", icon_name, NULL); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]); +} + + +const char * +phosh_status_page_placeholder_get_icon_name (PhoshStatusPagePlaceholder *self) +{ + g_return_val_if_fail (PHOSH_IS_STATUS_PAGE_PLACEHOLDER (self), NULL); + + return self->icon_name; +} diff --git a/src/status-page-placeholder.h b/src/status-page-placeholder.h new file mode 100644 index 000000000..8bab316b3 --- /dev/null +++ b/src/status-page-placeholder.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 Phosh Developers + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define PHOSH_TYPE_STATUS_PAGE_PLACEHOLDER (phosh_status_page_placeholder_get_type ()) + +G_DECLARE_FINAL_TYPE (PhoshStatusPagePlaceholder, phosh_status_page_placeholder, PHOSH, STATUS_PAGE_PLACEHOLDER, GtkBin) + +PhoshStatusPagePlaceholder *phosh_status_page_placeholder_new (void); +void phosh_status_page_placeholder_set_title (PhoshStatusPagePlaceholder *self, + const char *title); +const char *phosh_status_page_placeholder_get_title (PhoshStatusPagePlaceholder *self); +void phosh_status_page_placeholder_set_icon_name (PhoshStatusPagePlaceholder *self, + const char *icon_name); +const char *phosh_status_page_placeholder_get_icon_name (PhoshStatusPagePlaceholder *self); + +G_END_DECLS diff --git a/src/status-page.c b/src/status-page.c index 78ac9d7b9..909143499 100644 --- a/src/status-page.c +++ b/src/status-page.c @@ -9,6 +9,7 @@ #define G_LOG_DOMAIN "phosh-status-page" #include "status-page.h" +#include "status-page-placeholder.h" /** * PhoshStatusPage: @@ -90,6 +91,7 @@ phosh_status_page_class_init (PhoshStatusPageClass *klass) g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + g_type_ensure (PHOSH_TYPE_STATUS_PAGE_PLACEHOLDER); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/phosh/ui/status-page.ui"); gtk_widget_class_set_css_name (widget_class, "phosh-status-page"); diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index 2a202e03e..83139c674 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -185,6 +185,11 @@ button.phosh-settings-details:focus { -gtk-icon-style: symbolic; } +phosh-status-page-placeholder > box > label.title { + font-weight: 400; + font-size: 16px; +} + /* * Audio devices listbox */ diff --git a/src/ui/status-page-placeholder.ui b/src/ui/status-page-placeholder.ui new file mode 100644 index 000000000..9778ec87a --- /dev/null +++ b/src/ui/status-page-placeholder.ui @@ -0,0 +1,37 @@ + + + + + -- GitLab From 7fdfe8f19e1e3d2da46923e9fcdbd025c5449b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Tue, 23 Jul 2024 14:46:18 +0200 Subject: [PATCH 18/22] status-page: Introduce page header bar and a footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids leaking parent widget details into the derived classes (like requiring a `packaging` `end`). The optional footer usually holds a button to open Settings. We use a `GtkBox` to place a single child. We'd use `GtkBin` but that is abstract, in GTK4 we'll use `AdwBin`. The header contains the back button and has a user defined title. One also get a user defined child widget. Signed-off-by: Guido Günther Part-of: --- src/status-page.c | 275 +++++++++++++++++++++++++++++++++++--- src/status-page.h | 8 +- src/stylesheet/common.css | 5 +- src/ui/status-page.ui | 78 +++++++++-- 4 files changed, 333 insertions(+), 33 deletions(-) diff --git a/src/status-page.c b/src/status-page.c index 909143499..72ec668b4 100644 --- a/src/status-page.c +++ b/src/status-page.c @@ -1,9 +1,11 @@ /* * Copyright (C) 2023 Tether Operations Limited + * 2024 The Phosh Developers * * SPDX-License-Identifier: GPL-3.0-or-later * - * Author: Arun Mani J + * Authors: Arun Mani J + * Guido Günther */ #define G_LOG_DOMAIN "phosh-status-page" @@ -14,25 +16,40 @@ /** * PhoshStatusPage: * - * A widget to show more details about a status indicator like WiFi, Bluetooth etc. + * Additional status information associated with a [class@QuickSetting]. * - * PhoshStatusPage is used to show more details about a status indicator. It must be subclassed to - * display the required information. PhoshSettings will show this information when the respective - * PhoshQuickSetting is activated. + * This is displayed when the quick setting is long pressed. */ enum { PROP_0, + PROP_TITLE, PROP_HEADER, + PROP_FOOTER, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; typedef struct { - GtkWidget *header; + GtkBox *toplevel_box; + + /* Header */ + GtkLabel *title_label; + GtkBox *header_bin; + GtkWidget *header_widget; + + /* Content */ + GtkBox *content_bin; + GtkWidget *content_widget; + + /* Footer */ + GtkSeparator *footer_separator; + GtkBox *footer_bin; + GtkWidget *footer_widget; } PhoshStatusPagePrivate; -G_DEFINE_TYPE_WITH_PRIVATE (PhoshStatusPage, phosh_status_page, GTK_TYPE_BOX); +G_DEFINE_TYPE_WITH_PRIVATE (PhoshStatusPage, phosh_status_page, GTK_TYPE_BIN); + static void phosh_status_page_set_property (GObject *object, @@ -43,9 +60,15 @@ phosh_status_page_set_property (GObject *object, PhoshStatusPage *self = PHOSH_STATUS_PAGE (object); switch (property_id) { + case PROP_TITLE: + phosh_status_page_set_title (self, g_value_get_string (value)); + break; case PROP_HEADER: phosh_status_page_set_header (self, g_value_get_object (value)); break; + case PROP_FOOTER: + phosh_status_page_set_footer (self, g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -61,74 +84,233 @@ phosh_status_page_get_property (GObject *object, PhoshStatusPage *self = PHOSH_STATUS_PAGE (object); switch (property_id) { + case PROP_TITLE: + g_value_set_string (value, phosh_status_page_get_title (self)); + break; case PROP_HEADER: g_value_set_object (value, phosh_status_page_get_header (self)); break; + case PROP_FOOTER: + g_value_set_object (value, phosh_status_page_get_footer (self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } + +static void +phosh_status_page_destroy (GtkWidget *widget) +{ + PhoshStatusPage *self = PHOSH_STATUS_PAGE (widget); + PhoshStatusPagePrivate *priv = phosh_status_page_get_instance_private (self); + + if (priv->toplevel_box) { + /* Trigger destruction of all contained widgets */ + gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (priv->toplevel_box)); + priv->toplevel_box = NULL; + priv->footer_widget = NULL; + priv->header_widget = NULL; + priv->content_widget = NULL; + priv->footer_bin = NULL; + priv->header_bin = NULL; + priv->content_bin = NULL; + } + + GTK_WIDGET_CLASS (phosh_status_page_parent_class)->destroy (widget); +} + + +static void +phosh_status_page_add (GtkContainer *container, + GtkWidget *child) +{ + PhoshStatusPage *self = PHOSH_STATUS_PAGE (container); + PhoshStatusPagePrivate *priv = phosh_status_page_get_instance_private (self); + + if (!priv->toplevel_box) { + GTK_CONTAINER_CLASS (phosh_status_page_parent_class)->add (container, child); + } else if (!priv->content_widget) { + gtk_container_add (GTK_CONTAINER (priv->content_bin), child); + priv->content_widget = child; + } else { + g_warning ("Attempting to add a second child to a PhoshStatusPage," + "but a PhoshStatusPage can only have one child"); + } +} + + +static void +phosh_status_page_remove (GtkContainer *container, + GtkWidget *child) +{ + PhoshStatusPage *self = PHOSH_STATUS_PAGE (container); + PhoshStatusPagePrivate *priv = phosh_status_page_get_instance_private (self); + + if (child == GTK_WIDGET (priv->toplevel_box)) { + GTK_CONTAINER_CLASS (phosh_status_page_parent_class)->remove (container, child); + } else if (child == priv->content_widget) { + gtk_container_remove (GTK_CONTAINER (priv->content_bin), child); + priv->content_widget = NULL; + } else { + g_return_if_reached (); + } +} + + +static void +phosh_status_page_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + PhoshStatusPage *self = PHOSH_STATUS_PAGE (container); + PhoshStatusPagePrivate *priv = phosh_status_page_get_instance_private (self); + + if (include_internals) { + GTK_CONTAINER_CLASS (phosh_status_page_parent_class)->forall (container, + include_internals, + callback, + callback_data); + } else if (priv->content_widget) { + callback (priv->content_widget, callback_data); + } +} + + static void phosh_status_page_class_init (PhoshStatusPageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); object_class->set_property = phosh_status_page_set_property; object_class->get_property = phosh_status_page_get_property; + widget_class->destroy = phosh_status_page_destroy; + + container_class->add = phosh_status_page_add; + container_class->remove = phosh_status_page_remove; + container_class->forall = phosh_status_page_forall; + + /** + * PhoshStatusPage:title: + * + * The status page title + */ + props[PROP_TITLE] = + g_param_spec_string ("title", "", "", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * PhoshStatusPage:header: * - * The header widget + * An extra widget to add to end of the status page's header */ props[PROP_HEADER] = g_param_spec_object ("header", "", "", GTK_TYPE_WIDGET, - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * PhoshStatusPage:footer: + * + * Widget displayed at the very bottom - usually a button. + */ + props[PROP_FOOTER] = + g_param_spec_object ("footer", "", "", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST_PROP, props); g_type_ensure (PHOSH_TYPE_STATUS_PAGE_PLACEHOLDER); gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/phosh/ui/status-page.ui"); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, content_bin); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, footer_bin); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, footer_separator); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, header_bin); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, title_label); + gtk_widget_class_bind_template_child_private (widget_class, PhoshStatusPage, toplevel_box); + gtk_widget_class_set_css_name (widget_class, "phosh-status-page"); } + static void phosh_status_page_init (PhoshStatusPage *self) { gtk_widget_init_template (GTK_WIDGET (self)); } + PhoshStatusPage * phosh_status_page_new (void) { return g_object_new (PHOSH_TYPE_STATUS_PAGE, NULL); } + void -phosh_status_page_set_header (PhoshStatusPage *self, GtkWidget *header) +phosh_status_page_set_title (PhoshStatusPage *self, const char *title) { PhoshStatusPagePrivate *priv; + const char *current; g_return_if_fail (PHOSH_IS_STATUS_PAGE (self)); - g_return_if_fail (GTK_IS_WIDGET (header)); + priv = phosh_status_page_get_instance_private (self); + + current = gtk_label_get_label (priv->title_label); + if (g_strcmp0 (current, title) == 0) + return; + + gtk_label_set_label (priv->title_label, title); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); +} + + +const char * +phosh_status_page_get_title (PhoshStatusPage *self) +{ + PhoshStatusPagePrivate *priv; + + g_return_val_if_fail (PHOSH_IS_STATUS_PAGE (self), NULL); + priv = phosh_status_page_get_instance_private (self); + + return gtk_label_get_label (priv->title_label); +} + +/** + * phosh_status_page_set_header: + * @self: A quick setting status page + * + * Set the header widget of the status page. See + * [property@StatusPage:header]. + */ +void +phosh_status_page_set_header (PhoshStatusPage *self, GtkWidget *header_widget) +{ + PhoshStatusPagePrivate *priv; + + g_return_if_fail (PHOSH_IS_STATUS_PAGE (self)); + g_return_if_fail (GTK_IS_WIDGET (header_widget)); priv = phosh_status_page_get_instance_private (self); - if (priv->header == header) + if (priv->header_widget == header_widget) return; - if (priv->header) - gtk_container_remove (GTK_CONTAINER (self), priv->header); + if (priv->header_widget) + gtk_container_remove (GTK_CONTAINER (priv->header_bin), priv->header_widget); + + priv->header_widget = header_widget; - priv->header = header; + if (priv->header_widget) + gtk_container_add (GTK_CONTAINER (priv->header_bin), priv->header_widget); - if (priv->header) - gtk_box_pack_start (GTK_BOX (self), priv->header, FALSE, FALSE, 0); + gtk_widget_set_visible (GTK_WIDGET (priv->header_bin), !!header_widget); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HEADER]); } @@ -137,7 +319,7 @@ phosh_status_page_set_header (PhoshStatusPage *self, GtkWidget *header) * phosh_status_page_get_header: * @self: A quick setting status page * - * Get the header of the status page + * Get the header widget of the status page * * Returns:(transfer none): The status page header */ @@ -150,5 +332,58 @@ phosh_status_page_get_header (PhoshStatusPage *self) priv = phosh_status_page_get_instance_private (self); - return priv->header; + return priv->header_widget; +} + +/** + * phosh_status_page_set_footer: + * @self: A quick setting status page + * + * Set the footer widget shown at the bottom of a status page + */ +void +phosh_status_page_set_footer (PhoshStatusPage *self, GtkWidget *footer_widget) +{ + PhoshStatusPagePrivate *priv; + + g_return_if_fail (PHOSH_IS_STATUS_PAGE (self)); + g_return_if_fail (GTK_IS_WIDGET (footer_widget)); + + priv = phosh_status_page_get_instance_private (self); + + if (priv->footer_widget == footer_widget) + return; + + if (priv->footer_widget) + gtk_container_remove (GTK_CONTAINER (priv->footer_bin), priv->footer_widget); + + priv->footer_widget = footer_widget; + + if (priv->footer_widget) + gtk_container_add (GTK_CONTAINER (priv->footer_bin), priv->footer_widget); + + gtk_widget_set_visible (GTK_WIDGET (priv->footer_separator), !!footer_widget); + gtk_widget_set_visible (GTK_WIDGET (priv->footer_bin), !!footer_widget); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOOTER]); +} + +/** + * phosh_status_page_get_footer: + * @self: A quick setting status page + * + * Get the footer of the status page + * + * Returns:(transfer none): The status page footer + */ +GtkWidget * +phosh_status_page_get_footer (PhoshStatusPage *self) +{ + PhoshStatusPagePrivate *priv; + + g_return_val_if_fail (PHOSH_IS_STATUS_PAGE (self), 0); + + priv = phosh_status_page_get_instance_private (self); + + return priv->footer_widget; } diff --git a/src/status-page.h b/src/status-page.h index 35856a2bf..8d053a93c 100644 --- a/src/status-page.h +++ b/src/status-page.h @@ -11,15 +11,19 @@ G_BEGIN_DECLS #define PHOSH_TYPE_STATUS_PAGE phosh_status_page_get_type () -G_DECLARE_DERIVABLE_TYPE (PhoshStatusPage, phosh_status_page, PHOSH, STATUS_PAGE, GtkBox) +G_DECLARE_DERIVABLE_TYPE (PhoshStatusPage, phosh_status_page, PHOSH, STATUS_PAGE, GtkBin) struct _PhoshStatusPageClass { - GtkBoxClass parent_class; + GtkBinClass parent_class; }; PhoshStatusPage *phosh_status_page_new (void); +void phosh_status_page_set_title (PhoshStatusPage *self, const char *title); +const char *phosh_status_page_get_title (PhoshStatusPage *self); void phosh_status_page_set_header (PhoshStatusPage *self, GtkWidget *header); GtkWidget *phosh_status_page_get_header (PhoshStatusPage *self); +void phosh_status_page_set_footer (PhoshStatusPage *self, GtkWidget *footer); +GtkWidget *phosh_status_page_get_footer (PhoshStatusPage *self); G_END_DECLS diff --git a/src/stylesheet/common.css b/src/stylesheet/common.css index 83139c674..68d2e59f1 100644 --- a/src/stylesheet/common.css +++ b/src/stylesheet/common.css @@ -165,11 +165,10 @@ button.phosh-settings-details:focus { -gtk-icon-style: symbolic; } -/* Listboxes in status pages (e.g. audio wifi or bt status pages) */ +/* Status pages of quick settings (e.g. audio wifi or bt status pages) */ .phosh-status-page { border-radius: 18px; - padding-top: 12px; - padding-bottom: 12px; + padding: 12px 12px 6px; border: none; background-color: @phosh_notification_bg_color; } diff --git a/src/ui/status-page.ui b/src/ui/status-page.ui index c434075a0..60d067773 100644 --- a/src/ui/status-page.ui +++ b/src/ui/status-page.ui @@ -1,14 +1,76 @@ -