diff --git a/debian/control b/debian/control index af6d8a7a46e25b8f1476999903b8c4572586bcb8..344dcc3c210a673eadd67133a093559dd2715f09 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,7 @@ Priority: optional Maintainer: Guido Günther <agx@sigxcpu.org> Build-Depends: debhelper (>= 10), + libgcr-3-dev, libgnome-desktop-3-dev, libgtk-3-dev, libhandy-0.0-dev (>= 0.0.1), diff --git a/src/meson.build b/src/meson.build index 1e0a8fdec7b78a4c20cc63016d50cc59c36b73aa..e54800f4be075d8dfaaaa265fa7f089aa1f0aea1 100644 --- a/src/meson.build +++ b/src/meson.build @@ -55,6 +55,10 @@ phosh_sources = [ 'lockshield.h', 'monitor-manager.c', 'monitor-manager.h', + 'system-prompt.c', + 'system-prompt.h', + 'system-prompter.c', + 'system-prompter.h', 'panel.c', 'panel.h', 'phosh.c', @@ -73,6 +77,7 @@ phosh_sources = [ ] phosh_deps = [ + dependency('gcr-3', version: '>= 3.7.5'), dependency('gnome-desktop-3.0', version: '>=3.26'), dependency('gtk+-3.0', version: '>=3.22'), dependency('gtk+-wayland-3.0', version: '>=3.22'), diff --git a/src/phosh.c b/src/phosh.c index 0eb703200cf99954c154c53ac7c06e5e146e2f5e..b4cb85feea6af9b20958100a054df736527c86c7 100644 --- a/src/phosh.c +++ b/src/phosh.c @@ -31,6 +31,7 @@ #include "home.h" #include "favorites.h" #include "settings.h" +#include "system-prompter.h" enum { @@ -467,6 +468,8 @@ phosh_shell_dispose (GObject *object) g_clear_pointer (&priv->panel, gtk_widget_destroy); g_clear_object (&priv->lockscreen_manager); g_clear_object (&priv->monitor_manager); + phosh_system_prompter_unregister (); + G_OBJECT_CLASS (phosh_shell_parent_class)->dispose (object); } @@ -498,6 +501,7 @@ phosh_shell_constructed (GObject *object) /* Create background after panel since it needs the panel's size */ background_create (self); priv->lockscreen_manager = phosh_lockscreen_manager_new (); + phosh_system_prompter_register (); } diff --git a/src/phosh.gresources.xml b/src/phosh.gresources.xml index f450527f400fc818961c6eaf74c4a762c3a315d6..4cc7ea7d40f7f8992355dfa2ee2d23dff38f1fc8 100644 --- a/src/phosh.gresources.xml +++ b/src/phosh.gresources.xml @@ -4,6 +4,7 @@ <file preprocess="xml-stripblanks">ui/home.ui</file> <file preprocess="xml-stripblanks">ui/lockscreen.ui</file> <file preprocess="xml-stripblanks">ui/settings-menu.ui</file> + <file preprocess="xml-stripblanks">ui/system-prompt.ui</file> <file preprocess="xml-stripblanks">ui/top-panel.ui</file> <file compressed="true">style.css</file> </gresource> diff --git a/src/system-prompt.c b/src/system-prompt.c new file mode 100644 index 0000000000000000000000000000000000000000..1f6ddd134ccf0caf3aa4a636a112c73425034b1a --- /dev/null +++ b/src/system-prompt.c @@ -0,0 +1,737 @@ +/* + * Copyright (C) 2018 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + * Author: Guido Günther <agx@sigxcpu.org> + * + * Based on gnome-shell's shell-keyring-prompt.c + * Author: Stef Walter <stefw@gnome.org> + */ + +#define G_LOG_DOMAIN "phosh-system-prompt" + +#include "config.h" + +#include "system-prompt.h" + +#define GCR_API_SUBJECT_TO_CHANGE +#include <gcr/gcr.h> + +#include <glib/gi18n.h> + +/** + * SECTION:phosh-system-prompt + * @short_description: A modal system prompt + * @Title: PhoshSystemPrompt + * + * The #PhoshSystemPrompt is used to ask for PINs and passwords + */ + +enum { + PROP_0, + + /* GcrPromptIface */ + PROP_TITLE, + PROP_MESSAGE, + PROP_DESCRIPTION, + PROP_WARNING, + PROP_CHOICE_LABEL, + PROP_CHOICE_CHOSEN, + PROP_PASSWORD_NEW, + PROP_PASSWORD_STRENGTH, + PROP_CALLER_WINDOW, + PROP_CONTINUE_LABEL, + PROP_CANCEL_LABEL, + + /* our own */ + PROP_PASSWORD_VISIBLE, + PROP_CONFIRM_VISIBLE, + PROP_WARNING_VISIBLE, + PROP_CHOICE_VISIBLE, + PROP_LAST_PROP, +}; + +typedef enum +{ + PROMPTING_NONE, + PROMPTING_FOR_CONFIRM, + PROMPTING_FOR_PASSWORD +} PromptingMode; + + +typedef struct +{ + gchar *title; + gchar *message; + gchar *description; + gchar *warning; + gchar *choice_label; + gboolean choice_chosen; + gboolean password_new; + guint password_strength; + gchar *continue_label; + gchar *cancel_label; + + GtkWidget *grid; + GtkWidget *btn_continue; + GtkEntryBuffer *password_buffer; + GtkEntryBuffer *confirm_buffer; + + GTask *task; + GcrPromptReply last_reply; + PromptingMode mode; + gboolean shown; +} PhoshSystemPromptPrivate; + + +struct _PhoshSystemPrompt +{ + PhoshLayerSurface parent; +}; + + +static void phosh_system_prompt_iface_init (GcrPromptIface *iface); +G_DEFINE_TYPE_WITH_CODE(PhoshSystemPrompt, phosh_system_prompt, PHOSH_TYPE_LAYER_SURFACE, + G_IMPLEMENT_INTERFACE (GCR_TYPE_PROMPT, + phosh_system_prompt_iface_init) + G_ADD_PRIVATE (PhoshSystemPrompt)); + + +static void +phosh_system_prompt_set_property (GObject *obj, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (obj); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + + switch (prop_id) { + case PROP_TITLE: + g_free (priv->title); + priv->title = g_value_dup_string (value); + g_object_notify (obj, "title"); + break; + case PROP_MESSAGE: + g_free (priv->message); + priv->message = g_value_dup_string (value); + g_object_notify (obj, "message"); + break; + case PROP_DESCRIPTION: + g_free (priv->description); + priv->description = g_value_dup_string (value); + g_object_notify (obj, "description"); + break; + case PROP_WARNING: + g_free (priv->warning); + priv->warning = g_value_dup_string (value); + g_object_notify (obj, "warning"); + g_object_notify (obj, "warning-visible"); + break; + case PROP_CHOICE_LABEL: + g_free (priv->choice_label); + priv->choice_label = g_value_dup_string (value); + g_object_notify (obj, "choice-label"); + g_object_notify (obj, "choice-visible"); + break; + case PROP_CHOICE_CHOSEN: + priv->choice_chosen = g_value_get_boolean (value); + g_object_notify (obj, "choice-chosen"); + break; + case PROP_PASSWORD_NEW: + priv->password_new = g_value_get_boolean (value); + g_object_notify (obj, "password-new"); + g_object_notify (obj, "confirm-visible"); + break; + case PROP_CALLER_WINDOW: + /* ignored */ + break; + case PROP_CONTINUE_LABEL: + g_free (priv->continue_label); + priv->continue_label = g_value_dup_string (value); + g_object_notify (obj, "continue-label"); + break; + case PROP_CANCEL_LABEL: + g_free (priv->cancel_label); + priv->cancel_label = g_value_dup_string (value); + g_object_notify (obj, "cancel-label"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; + } +} + + +static void +phosh_system_prompt_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (obj); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + + switch (prop_id) { + case PROP_TITLE: + g_value_set_string (value, priv->title ? priv->title : ""); + break; + case PROP_MESSAGE: + g_value_set_string (value, priv->message ? priv->message : ""); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, priv->description ? priv->description : ""); + break; + case PROP_WARNING: + g_value_set_string (value, priv->warning ? priv->warning : ""); + break; + case PROP_CHOICE_LABEL: + g_value_set_string (value, priv->choice_label ? priv->choice_label : ""); + break; + case PROP_CHOICE_CHOSEN: + g_value_set_boolean (value, priv->choice_chosen); + break; + case PROP_PASSWORD_NEW: + g_value_set_boolean (value, priv->password_new); + break; + case PROP_PASSWORD_STRENGTH: + g_value_set_int (value, priv->password_strength); + break; + case PROP_CALLER_WINDOW: + g_value_set_string (value, ""); + break; + case PROP_CONTINUE_LABEL: + g_value_set_string (value, priv->continue_label); + break; + case PROP_CANCEL_LABEL: + g_value_set_string (value, priv->cancel_label); + break; + case PROP_PASSWORD_VISIBLE: + g_value_set_boolean (value, priv->mode == PROMPTING_FOR_PASSWORD); + break; + case PROP_CONFIRM_VISIBLE: + g_value_set_boolean (value, priv->password_new && + priv->mode == PROMPTING_FOR_CONFIRM); + break; + case PROP_WARNING_VISIBLE: + g_value_set_boolean (value, priv->warning && priv->warning[0]); + break; + case PROP_CHOICE_VISIBLE: + g_value_set_boolean (value, priv->choice_label && priv->choice_label[0]); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); + break; + } +} + + + +static void +phosh_system_prompt_password_async (GcrPrompt *prompt, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (prompt); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + GObject *obj; + + g_debug ("Starting system password prompt: %s", __func__); + if (priv->task != NULL) { + g_warning ("this prompt can only show one prompt at a time"); + return; + } + priv->mode = PROMPTING_FOR_PASSWORD; + priv->task = g_task_new (self, NULL, callback, user_data); + g_task_set_source_tag (priv->task, phosh_system_prompt_password_async); + + gtk_widget_set_sensitive (priv->btn_continue, TRUE); + gtk_widget_set_sensitive (priv->grid, TRUE); + + obj = G_OBJECT (self); + g_object_notify (obj, "password-visible"); + g_object_notify (obj, "confirm-visible"); + g_object_notify (obj, "warning-visible"); + g_object_notify (obj, "choice-visible"); + priv->shown = TRUE; +} + + +static const gchar * +phosh_system_prompt_password_finish (GcrPrompt *prompt, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_get_source_object (G_TASK (result)) == prompt, NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, + phosh_system_prompt_password_async), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + + +static void +phosh_system_prompt_close (GcrPrompt *prompt) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (prompt); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + + if (!priv->shown) + return; + + priv->shown = FALSE; + gtk_widget_destroy (GTK_WIDGET (self)); +} + + +static void +phosh_system_prompt_confirm_async (GcrPrompt *prompt, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (prompt); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + GObject *obj; + + g_debug ("Starting system confirmation prompt: %s", __func__); + if (priv->task != NULL) { + g_warning ("this prompt can only show one prompt at a time"); + return; + } + priv->mode = PROMPTING_FOR_CONFIRM; + priv->task = g_task_new (self, NULL, callback, user_data); + g_task_set_source_tag (priv->task, phosh_system_prompt_confirm_async); + + gtk_widget_set_sensitive (priv->btn_continue, TRUE); + gtk_widget_set_sensitive (priv->grid, TRUE); + + obj = G_OBJECT (self); + g_object_notify (obj, "password-visible"); + g_object_notify (obj, "confirm-visible"); + g_object_notify (obj, "warning-visible"); + g_object_notify (obj, "choice-visible"); + priv->shown = TRUE; +} + + +static GcrPromptReply +phosh_system_prompt_confirm_finish (GcrPrompt *prompt, + GAsyncResult *result, + GError **error) +{ + GTask *task = G_TASK (result); + gssize res; + + g_debug ("Finishing system confirmation prompt: %s", __func__); + g_return_val_if_fail (g_task_get_source_object (task) == prompt, + GCR_PROMPT_REPLY_CANCEL); + g_return_val_if_fail (g_async_result_is_tagged (result, + phosh_system_prompt_confirm_async), GCR_PROMPT_REPLY_CANCEL); + + res = g_task_propagate_int (task, error); + return res == -1 ? GCR_PROMPT_REPLY_CANCEL : (GcrPromptReply)res; +} + + +static gboolean +prompt_complete (PhoshSystemPrompt *self) +{ + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + GTask *res; + PromptingMode mode; + const gchar *password; + const gchar *confirm; + const gchar *env; + + g_return_val_if_fail (PHOSH_IS_SYSTEM_PROMPT (self), FALSE); + g_return_val_if_fail (priv->mode != PROMPTING_NONE, FALSE); + g_return_val_if_fail (priv->task != NULL, FALSE); + + password = gtk_entry_buffer_get_text (priv->password_buffer); + + if (priv->mode == PROMPTING_FOR_CONFIRM) { + /* Is it a new password? */ + if (priv->password_new) { + confirm = gtk_entry_buffer_get_text (priv->confirm_buffer); + /* Do the passwords match? */ + if (!g_str_equal (password, confirm)) { + gcr_prompt_set_warning (GCR_PROMPT (self), _("Passwords do not match.")); + return FALSE; + } + + /* Don't allow blank passwords if in paranoid mode */ + env = g_getenv ("GNOME_KEYRING_PARANOID"); + if (env && *env) { + gcr_prompt_set_warning (GCR_PROMPT (self), _("Password cannot be blank")); + return FALSE; + } + } + } + + res = priv->task; + mode = priv->mode; + priv->task = NULL; + priv->mode = PROMPTING_NONE; + + if (mode == PROMPTING_FOR_CONFIRM) + g_task_return_int (res, (gssize)GCR_PROMPT_REPLY_CONTINUE); + else + g_task_return_pointer (res, (gpointer)password, NULL); + g_object_unref (res); + + gtk_widget_set_sensitive (priv->btn_continue, FALSE); + gtk_widget_set_sensitive (priv->grid, FALSE); + + return TRUE; +} + + +static void +prompt_cancel (PhoshSystemPrompt *self) +{ + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + GTask *res; + PromptingMode mode; + + g_debug ("Canceling system password prompt: %s", __func__); + g_return_if_fail (PHOSH_IS_SYSTEM_PROMPT (self)); + /* + * If cancelled while not prompting, we should just close the prompt, + * the user wants it to go away. + */ + if (priv->mode == PROMPTING_NONE) { + if (priv->shown) + gcr_prompt_close (GCR_PROMPT (self)); + return; + } + + g_return_if_fail (priv->task != NULL); + + res = priv->task; + mode = priv->mode; + priv->task = NULL; + priv->mode = PROMPTING_NONE; + + if (mode == PROMPTING_FOR_CONFIRM) + g_task_return_int (res, (gssize) GCR_PROMPT_REPLY_CANCEL); + else + g_task_return_pointer (res, NULL, NULL); + g_object_unref (res); +} + + +static void +on_password_changed (GtkEditable *editable, + gpointer user_data) +{ + int upper, lower, digit, misc; + const char *password; + gdouble pwstrength; + int length, i; + + password = gtk_entry_get_text (GTK_ENTRY (editable)); + + /* + * This code is based on the Master Password dialog in Firefox + * (pref-masterpass.js) + * Original code triple-licensed under the MPL, GPL, and LGPL + * so is license-compatible with this file + */ + + length = strlen (password); + upper = 0; + lower = 0; + digit = 0; + misc = 0; + + for ( i = 0; i < length ; i++) { + if (g_ascii_isdigit (password[i])) + digit++; + else if (g_ascii_islower (password[i])) + lower++; + else if (g_ascii_isupper (password[i])) + upper++; + else + misc++; + } + + if (length > 5) + length = 5; + if (digit > 3) + digit = 3; + if (upper > 3) + upper = 3; + if (misc > 3) + misc = 3; + + pwstrength = ((length * 0.1) - 0.2) + + (digit * 0.1) + + (misc * 0.15) + + (upper * 0.1); + + if (pwstrength < 0.0) + pwstrength = 0.0; + if (pwstrength > 1.0) + pwstrength = 1.0; + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (user_data), pwstrength); +} + + +static void +btn_continue_clicked_cb (PhoshSystemPrompt *self, GtkButton *btn) +{ + prompt_complete (self); +} + + +static void +btn_cancel_clicked_cb (PhoshSystemPrompt *self, GtkButton *btn) +{ + prompt_cancel (self); +} + +static void +phosh_system_prompt_iface_init (GcrPromptIface *iface) +{ + iface->prompt_confirm_async = phosh_system_prompt_confirm_async; + iface->prompt_confirm_finish = phosh_system_prompt_confirm_finish; + iface->prompt_password_async = phosh_system_prompt_password_async; + iface->prompt_password_finish = phosh_system_prompt_password_finish; + iface->prompt_close = phosh_system_prompt_close; +} + +static gboolean +draw_cb (GtkWidget *widget, cairo_t *cr, gpointer unused) +{ + GtkStyleContext *context = gtk_widget_get_style_context (widget); + GdkRGBA c; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gtk_style_context_get_background_color (context, GTK_STATE_FLAG_NORMAL, &c); + G_GNUC_END_IGNORE_DEPRECATIONS + cairo_set_source_rgba (cr, c.red, c.green, c.blue, 0.7); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint (cr); + return FALSE; +} + + +static void +phosh_system_prompt_dispose (GObject *obj) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (obj); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + + if (priv->shown) + gcr_prompt_close (GCR_PROMPT (self)); + + if (priv->task) + prompt_cancel (self); + + g_assert (priv->task == NULL); + G_OBJECT_CLASS (phosh_system_prompt_parent_class)->dispose (obj); +} + + +static void +phosh_system_prompt_finalize (GObject *obj) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (obj); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + + g_free (priv->title); + g_free (priv->message); + g_free (priv->description); + g_free (priv->warning); + g_free (priv->choice_label); + g_free (priv->continue_label); + g_free (priv->cancel_label); + + G_OBJECT_CLASS (phosh_system_prompt_parent_class)->finalize (obj); +} + + +static void +phosh_system_prompt_constructed (GObject *object) +{ + PhoshSystemPrompt *self = PHOSH_SYSTEM_PROMPT (object); + PhoshSystemPromptPrivate *priv = phosh_system_prompt_get_instance_private (self); + GtkBuilder *builder; + GtkWidget *widget; + GtkEntry *entry; + + G_OBJECT_CLASS (phosh_system_prompt_parent_class)->constructed (object); + + /* + * We use gtk_buidler_* instead of the nicer + * gtk_widget_class_set_template_from_resource since + * PhoshLayerSuface isn't supported as type in glade yet. + */ + builder = gtk_builder_new_from_resource ("/sm/puri/phosh/ui/system-prompt.ui"); + widget = GTK_WIDGET (gtk_builder_get_object (builder, "grid_system_prompt")); + gtk_container_add (GTK_CONTAINER (self), widget); + gtk_widget_set_valign (widget, GTK_ALIGN_CENTER); + gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); + priv->grid = widget; + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "lbl_message")); + g_object_bind_property (self, "message", widget, "label", G_BINDING_DEFAULT); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "lbl_description")); + g_object_bind_property (self, "description", widget, "label", G_BINDING_DEFAULT); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "lbl_password")); + g_object_bind_property (self, "password-visible", widget, "visible", G_BINDING_DEFAULT); + + priv->password_buffer = gcr_secure_entry_buffer_new (); + entry = + GTK_ENTRY (gtk_builder_get_object (builder, "entry_password")); + gtk_entry_set_buffer (GTK_ENTRY (entry), GTK_ENTRY_BUFFER (priv->password_buffer)); + g_object_bind_property (self, "password-visible", entry, "visible", G_BINDING_DEFAULT); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "lbl_confirm")); + g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT); + + priv->confirm_buffer = gcr_secure_entry_buffer_new (); + widget = + GTK_WIDGET (gtk_builder_get_object (builder, "entry_confirm")); + gtk_entry_set_buffer (GTK_ENTRY (widget), GTK_ENTRY_BUFFER (priv->confirm_buffer)); + g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "pbar_quality")); + g_object_bind_property (self, "confirm-visible", widget, "visible", G_BINDING_DEFAULT); + g_signal_connect (entry, "changed", G_CALLBACK (on_password_changed), widget); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "lbl_warning")); + g_object_bind_property (self, "warning", widget, "label", G_BINDING_DEFAULT); + g_object_bind_property (self, "warning-visible", widget, "visible", G_BINDING_DEFAULT); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "checkbtn_choice")); + g_object_bind_property (self, "choice-label", widget, "label", G_BINDING_DEFAULT); + g_object_bind_property (self, "choice-visible", widget, "visible", G_BINDING_DEFAULT); + g_object_bind_property (self, "choice-chosen", widget, "active", G_BINDING_BIDIRECTIONAL); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "btn_cancel")); + g_object_bind_property (self, "cancel-label", widget, "label", G_BINDING_DEFAULT); + g_signal_connect_object (widget, + "clicked", + G_CALLBACK (btn_cancel_clicked_cb), + self, + G_CONNECT_SWAPPED); + + widget = GTK_WIDGET (gtk_builder_get_object (builder, "btn_continue")); + g_object_bind_property (self, "continue-label", widget, "label", G_BINDING_DEFAULT); + g_signal_connect_object (widget, + "clicked", + G_CALLBACK (btn_continue_clicked_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_grab_default (widget); + priv->btn_continue = widget; + + gtk_style_context_add_class ( + gtk_widget_get_style_context (GTK_WIDGET (self)), + "phosh-system-prompt"); + + gtk_widget_set_app_paintable(GTK_WIDGET (self), TRUE); + g_signal_connect (G_OBJECT(self), + "draw", + G_CALLBACK(draw_cb), + NULL); +} + + +static void +phosh_system_prompt_class_init (PhoshSystemPromptClass *klass) +{ + GObjectClass *object_class = (GObjectClass *)klass; + + object_class->get_property = phosh_system_prompt_get_property; + object_class->set_property = phosh_system_prompt_set_property; + object_class->constructed = phosh_system_prompt_constructed; + object_class->dispose = phosh_system_prompt_dispose; + object_class->finalize = phosh_system_prompt_finalize; + + g_object_class_override_property (object_class, PROP_TITLE, "title"); + g_object_class_override_property (object_class, PROP_MESSAGE, "message"); + g_object_class_override_property (object_class, PROP_DESCRIPTION, "description"); + g_object_class_override_property (object_class, PROP_WARNING, "warning"); + g_object_class_override_property (object_class, PROP_PASSWORD_NEW, "password-new"); + g_object_class_override_property (object_class, PROP_PASSWORD_STRENGTH, "password-strength"); + g_object_class_override_property (object_class, PROP_CHOICE_LABEL, "choice-label"); + g_object_class_override_property (object_class, PROP_CHOICE_CHOSEN, "choice-chosen"); + g_object_class_override_property (object_class, PROP_CALLER_WINDOW, "caller-window"); + g_object_class_override_property (object_class, PROP_CONTINUE_LABEL, "continue-label"); + g_object_class_override_property (object_class, PROP_CANCEL_LABEL, "cancel-label"); + + /** + * GcrPromptDialog:password-visible: + * + * Whether the password entry is visible or not. + */ + g_object_class_install_property (object_class, PROP_PASSWORD_VISIBLE, + g_param_spec_boolean ("password-visible", "Password visible", "Password field is visible", + FALSE, G_PARAM_READABLE)); + + /** + * GcrPromptDialog:confirm-visible: + * + * Whether the password confirm entry is visible or not. + */ + g_object_class_install_property (object_class, PROP_CONFIRM_VISIBLE, + g_param_spec_boolean ("confirm-visible", "Confirm visible", "Confirm field is visible", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * PhoshSystemPrompt:warning-visible: + * + * Whether the warning label is visible or not. + */ + g_object_class_install_property (object_class, PROP_WARNING_VISIBLE, + g_param_spec_boolean ("warning-visible", "Warning visible", "Warning is visible", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * PhoshSystemPrompt:choice-visible: + * + * Whether the choice check box is visible or not. + */ + g_object_class_install_property (object_class, PROP_CHOICE_VISIBLE, + g_param_spec_boolean ("choice-visible", "Choice visible", "Choice is visible", + FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + + +static void +phosh_system_prompt_init (PhoshSystemPrompt *self) +{ + /* + * This is a stupid hack to help the window act like a normal object + * with regards to reference counting and unref. (see gcr's + * gcr_prompt_dialog_init ui/gcr-prompt-dialog.c as of + * e0a506eeb29bc6be01a96e805e0244a03428ebf5. + * Otherwise it gets clean up too early. + */ + gtk_window_set_has_user_ref_count (GTK_WINDOW (self), FALSE); +} + + +GtkWidget * +phosh_system_prompt_new (gpointer layer_shell, + gpointer wl_output) +{ + return g_object_new (PHOSH_TYPE_SYSTEM_PROMPT, + "layer-shell", layer_shell, + "wl-output", wl_output, + "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + "layer", ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + "kbd-interactivity", TRUE, + "exclusive-zone", -1, + "namespace", "phosh prompter", + NULL); +} diff --git a/src/system-prompt.h b/src/system-prompt.h new file mode 100644 index 0000000000000000000000000000000000000000..a9b486d506fe7a2edec978c625d37c0de921270f --- /dev/null +++ b/src/system-prompt.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * SPDX-License-Identifier: GPL-3.0+ + */ +#pragma once + +#include <gtk/gtk.h> +#include "layersurface.h" + +#define PHOSH_TYPE_SYSTEM_PROMPT (phosh_system_prompt_get_type()) + +G_DECLARE_FINAL_TYPE (PhoshSystemPrompt, phosh_system_prompt, PHOSH, SYSTEM_PROMPT, PhoshLayerSurface) + +GtkWidget *phosh_system_prompt_new (gpointer layer_shell, + gpointer wl_output); diff --git a/src/system-prompter.c b/src/system-prompter.c new file mode 100644 index 0000000000000000000000000000000000000000..81d71c94c706773cb993c545ffa15e230b8f7adb --- /dev/null +++ b/src/system-prompter.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + * Author: Guido Günther <agx@sigxcpu.org> + */ + +#define G_LOG_DOMAIN "phosh-system-prompter" + +#include "config.h" + +#include "system-prompt.h" +#include "system-prompter.h" +#include "phosh.h" +#include "phosh-wayland.h" + +/** + * SECTION:phosh-system_prompter + * @short_description: Manages system prompter registration + * @Title: PhoshSystemPrompter + * + * The #PhoshSystemPrompter is responsible for displaying system + * wide modal #PhoshSystemPrompt dialogs + */ +static GcrSystemPrompter *_prompter; +static ulong owner_id; +static gboolean registered_prompter; +static gboolean acquired_prompter; + + +static GcrPrompt * +new_prompt_cb (GcrSystemPrompter *prompter, + gpointer user_data) +{ + PhoshWayland *wl = phosh_wayland_get_default (); + PhoshShell *shell = phosh_shell_get_default (); + + g_debug ("Building new system prompt"); + g_return_val_if_fail (GCR_IS_SYSTEM_PROMPTER (prompter), NULL); + return GCR_PROMPT (phosh_system_prompt_new (phosh_wayland_get_zwlr_layer_shell_v1 (wl), + phosh_shell_get_primary_monitor (shell))); +} + + +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("bus acquired for %s", name); + + if (!registered_prompter) { + gcr_system_prompter_register (_prompter, connection); + g_debug ("registered prompter"); + } + registered_prompter = TRUE; +} + + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("lost name: %s", name); + + /* Called like so when no connection can be made */ + if (connection == NULL) { + g_warning ("couldn't connect to session bus"); + phosh_system_prompter_unregister (); + } +} + + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + g_debug ("acquired name: %s", name); + acquired_prompter = TRUE; +} + + +GcrSystemPrompter * +phosh_system_prompter_register () +{ + _prompter = gcr_system_prompter_new (GCR_SYSTEM_PROMPTER_SINGLE, 0); + + g_signal_connect (_prompter, "new-prompt", + G_CALLBACK (new_prompt_cb), NULL); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.gnome.keyring.SystemPrompter", + G_BUS_NAME_OWNER_FLAGS_REPLACE, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + return _prompter; +} + + +void +phosh_system_prompter_unregister() +{ + if (_prompter) { + gcr_system_prompter_unregister (_prompter, TRUE); + _prompter = NULL; + } + + if (acquired_prompter) { + g_bus_unown_name (owner_id); + owner_id = 0; + acquired_prompter = FALSE; + } +} diff --git a/src/system-prompter.h b/src/system-prompter.h new file mode 100644 index 0000000000000000000000000000000000000000..99e7e15ff35bfebfb5d454fec6ea19a9d4b459e3 --- /dev/null +++ b/src/system-prompter.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2018 Purism SPC + * + * SPDX-License-Identifier: GPL-3.0+ + */ +#pragma once + +#define GCR_API_SUBJECT_TO_CHANGE +#include <gcr/gcr-base.h> + +GcrSystemPrompter *phosh_system_prompter_register(); +void phosh_system_prompter_unregister(); diff --git a/src/ui/system-prompt.ui b/src/ui/system-prompt.ui new file mode 100644 index 0000000000000000000000000000000000000000..fd8da1f46b014f6b2a82d1fff3404e1990f4b46c --- /dev/null +++ b/src/ui/system-prompt.ui @@ -0,0 +1,196 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkGrid" id="grid_system_prompt"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_left">20</property> + <property name="margin_right">20</property> + <property name="margin_top">20</property> + <property name="margin_bottom">20</property> + <property name="row_spacing">12</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="lbl_description"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_bottom">4</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lbl_password"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Password:</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="entry_password"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">â—</property> + <property name="activates_default">True</property> + <property name="primary_icon_sensitive">False</property> + <property name="secondary_icon_sensitive">False</property> + <property name="input_purpose">password</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lbl_confirm"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Confirm:</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="entry_confirm"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="valign">center</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">â—</property> + <property name="activates_default">True</property> + <property name="primary_icon_sensitive">False</property> + <property name="secondary_icon_sensitive">False</property> + <property name="input_purpose">password</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="pbar_quality"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lbl_warning"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <attributes> + <attribute name="style" value="italic"/> + </attributes> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">5</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="checkbtn_choice"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">6</property> + <property name="width">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="btn_continue"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="is_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="valign">center</property> + <property name="use_stock">True</property> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">7</property> + </packing> + </child> + <child> + <object class="GtkButton" id="btn_cancel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">7</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="lbl_message"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="margin_bottom">8</property> + <property name="hexpand">True</property> + <property name="wrap">True</property> + <attributes> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="icon_name">dialog-password-symbolic</property> + <property name="icon_size">6</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> +</interface>