diff options
author | Jamie Murphy <hello@itsjamie.dev> | 2023-01-16 19:55:35 -0800 |
---|---|---|
committer | Marge Bot <marge-bot@gnome.org> | 2023-02-11 18:16:33 +0000 |
commit | 19499b9a3fd6c8b0c32600309456bc35dbc29f52 (patch) | |
tree | a0a55c8868e2ab7ceee82d13c4fa0b5f1268074f /src | |
parent | e3e18dc1d518e26e8a746dc34162682e6addc78f (diff) | |
download | epiphany-19499b9a3fd6c8b0c32600309456bc35dbc29f52.tar.gz |
Use popovers for passwords and permissions
Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1265>
Diffstat (limited to 'src')
-rw-r--r-- | src/ephy-location-entry.c | 139 | ||||
-rw-r--r-- | src/ephy-location-entry.h | 14 | ||||
-rw-r--r-- | src/ephy-password-popover.c | 206 | ||||
-rw-r--r-- | src/ephy-password-popover.h | 36 | ||||
-rw-r--r-- | src/ephy-permission-popover.c | 310 | ||||
-rw-r--r-- | src/ephy-permission-popover.h | 49 | ||||
-rw-r--r-- | src/ephy-window.c | 331 | ||||
-rw-r--r-- | src/meson.build | 2 | ||||
-rw-r--r-- | src/resources/ephy-permission-camera-symbolic.svg | 4 | ||||
-rw-r--r-- | src/resources/ephy-permission-generic-symbolic.svg | 8 | ||||
-rw-r--r-- | src/resources/ephy-permission-location-symbolic.svg | 4 | ||||
-rw-r--r-- | src/resources/ephy-permission-microphone-symbolic.svg | 4 | ||||
-rw-r--r-- | src/resources/ephy-permission-notifications-symbolic.svg | 4 | ||||
-rw-r--r-- | src/resources/epiphany.gresource.xml | 7 | ||||
-rw-r--r-- | src/resources/gtk/location-entry.ui | 12 | ||||
-rw-r--r-- | src/resources/gtk/password-popover.ui | 101 | ||||
-rw-r--r-- | src/resources/gtk/permission-popover.ui | 88 | ||||
-rw-r--r-- | src/resources/style-hc.css | 6 | ||||
-rw-r--r-- | src/resources/style.css | 33 |
19 files changed, 1357 insertions, 1 deletions
diff --git a/src/ephy-location-entry.c b/src/ephy-location-entry.c index 402034cf2..1cb278899 100644 --- a/src/ephy-location-entry.c +++ b/src/ephy-location-entry.c @@ -57,9 +57,11 @@ struct _EphyLocationEntry { GtkWidget *text; GtkWidget *progress; GtkWidget *security_button; + GtkWidget *password_button; GtkWidget *bookmark_button; GtkWidget *reader_mode_button; GList *page_actions; + GList *permission_buttons; GtkWidget *suggestions_popover; GtkWidget *scrolled_window; @@ -867,6 +869,15 @@ root_notify_is_active_cb (EphyLocationEntry *entry) } static void +on_permission_popover_response (EphyPermissionPopover *popover, + GtkMenuButton *button) +{ + EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (gtk_widget_get_parent (GTK_WIDGET (button))); + gtk_widget_unparent (GTK_WIDGET (button)); + entry->permission_buttons = g_list_remove (entry->permission_buttons, button); +} + +static void update_reader_icon (EphyLocationEntry *entry) { GdkDisplay *display; @@ -981,6 +992,15 @@ ephy_location_entry_measure (GtkWidget *widget, gtk_widget_measure (entry->text, orientation, for_size, &min, &nat, NULL, NULL); /* Any other icons need to be similarly added to the text width */ + for (l = entry->permission_buttons; l; l = l->next) { + if (gtk_widget_should_layout (GTK_WIDGET (l->data))) { + gtk_widget_measure (GTK_WIDGET (l->data), orientation, for_size, + &child_min, &child_nat, NULL, NULL); + min += child_min; + nat += child_nat; + } + } + if (gtk_widget_should_layout (entry->security_button)) { gtk_widget_measure (entry->security_button, orientation, for_size, &child_min, &child_nat, NULL, NULL); @@ -988,6 +1008,13 @@ ephy_location_entry_measure (GtkWidget *widget, nat += child_nat; } + if (gtk_widget_should_layout (entry->password_button)) { + gtk_widget_measure (entry->password_button, orientation, for_size, + &child_min, &child_nat, NULL, NULL); + min += child_min; + nat += child_nat; + } + if (gtk_widget_should_layout (entry->bookmark_button)) { gtk_widget_measure (entry->bookmark_button, orientation, for_size, &child_min, &child_nat, NULL, NULL); @@ -1070,8 +1097,14 @@ ephy_location_entry_size_allocate (GtkWidget *widget, GskTransform *transform; GList *l; + for (l = entry->permission_buttons; l; l = l->next) { + allocate_icon (widget, height, baseline, l->data, + GTK_PACK_START, &icon_left_pos, &icon_right_pos); + } allocate_icon (widget, height, baseline, entry->security_button, GTK_PACK_START, &icon_left_pos, &icon_right_pos); + allocate_icon (widget, height, baseline, entry->password_button, + GTK_PACK_END, &icon_left_pos, &icon_right_pos); allocate_icon (widget, height, baseline, entry->bookmark_button, GTK_PACK_END, &icon_left_pos, &icon_right_pos); allocate_icon (widget, height, baseline, entry->reader_mode_button, @@ -1227,6 +1260,7 @@ static void ephy_location_entry_dispose (GObject *object) { EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (object); + GList *l; g_clear_handle_id (&entry->progress_timeout, g_source_remove); @@ -1235,10 +1269,14 @@ ephy_location_entry_dispose (GObject *object) ephy_location_entry_page_action_clear (entry); + for (l = entry->permission_buttons; l; l = l->next) + gtk_widget_unparent (GTK_WIDGET (l->data)); + gtk_widget_unparent (entry->context_menu); gtk_widget_unparent (entry->text); gtk_widget_unparent (entry->progress); gtk_widget_unparent (entry->security_button); + gtk_widget_unparent (entry->password_button); gtk_widget_unparent (entry->bookmark_button); gtk_widget_unparent (entry->reader_mode_button); gtk_widget_unparent (entry->suggestions_popover); @@ -1394,6 +1432,7 @@ ephy_location_entry_class_init (EphyLocationEntryClass *klass) gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, text); gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, progress); gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, security_button); + gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, password_button); gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, bookmark_button); gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, reader_mode_button); gtk_widget_class_bind_template_child (widget_class, EphyLocationEntry, suggestions_popover); @@ -1773,6 +1812,106 @@ ephy_location_entry_set_lock_tooltip (EphyLocationEntry *entry, } void +ephy_location_entry_add_permission_popover (EphyLocationEntry *entry, + EphyPermissionPopover *popover) +{ + GtkMenuButton *menu_button; + + g_assert (EPHY_IS_LOCATION_ENTRY (entry)); + g_assert (EPHY_IS_PERMISSION_POPOVER (popover)); + + menu_button = GTK_MENU_BUTTON (gtk_menu_button_new ()); + + switch (ephy_permission_popover_get_permission_type (popover)) { + case EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-notifications-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Notification Request")); + break; + case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-camera-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Camera Request")); + break; + case EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-microphone-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Microphone Request")); + break; + case EPHY_PERMISSION_TYPE_ACCESS_LOCATION: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-location-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Location Request")); + break; + case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-generic-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Webcam and Microphone Request")); + break; + default: + gtk_menu_button_set_icon_name (menu_button, "ephy-permission-generic-symbolic"); + gtk_widget_set_tooltip_text (GTK_WIDGET (menu_button), _("Permission Request")); + } + + gtk_widget_set_valign (GTK_WIDGET (menu_button), GTK_ALIGN_CENTER); + gtk_menu_button_set_popover (menu_button, GTK_WIDGET (popover)); + + gtk_widget_add_css_class (GTK_WIDGET (menu_button), "entry-icon"); + gtk_widget_add_css_class (GTK_WIDGET (menu_button), "start"); + + gtk_widget_set_parent (GTK_WIDGET (menu_button), GTK_WIDGET (entry)); + entry->permission_buttons = g_list_prepend (entry->permission_buttons, menu_button); + g_signal_connect (popover, "allow", G_CALLBACK (on_permission_popover_response), menu_button); + g_signal_connect (popover, "deny", G_CALLBACK (on_permission_popover_response), menu_button); +} + +void +ephy_location_entry_show_best_permission_popover (EphyLocationEntry *entry) +{ + g_assert (EPHY_IS_LOCATION_ENTRY (entry)); + + if (entry->permission_buttons) { + GtkWidget *menu_button = g_list_last (entry->permission_buttons)->data; + + gtk_menu_button_popup (GTK_MENU_BUTTON (menu_button)); + } +} + +void +ephy_location_entry_clear_permission_buttons (EphyLocationEntry *entry) +{ + GList *l; + + g_assert (EPHY_IS_LOCATION_ENTRY (entry)); + + for (l = entry->permission_buttons; l; l = l->next) { + GtkMenuButton *button = l->data; + GtkPopover *popover = gtk_menu_button_get_popover (button); + + g_signal_handlers_disconnect_by_func (popover, G_CALLBACK (on_permission_popover_response), button); + + gtk_widget_unparent (GTK_WIDGET (button)); + } + + g_clear_pointer (&entry->permission_buttons, g_list_free); +} + +void +ephy_location_entry_set_password_popover (EphyLocationEntry *entry, + EphyPasswordPopover *popover) +{ + g_assert (EPHY_IS_LOCATION_ENTRY (entry)); + g_assert (popover == NULL || EPHY_IS_PASSWORD_POPOVER (popover)); + + gtk_menu_button_set_popover (GTK_MENU_BUTTON (entry->password_button), + GTK_WIDGET (popover)); + gtk_widget_set_visible (entry->password_button, popover != NULL); +} + +void +ephy_location_entry_show_password_popover (EphyLocationEntry *entry) +{ + g_assert (EPHY_IS_LOCATION_ENTRY (entry)); + + gtk_menu_button_popup (GTK_MENU_BUTTON (entry->password_button)); +} + +void ephy_location_entry_set_add_bookmark_popover (EphyLocationEntry *entry, GtkPopover *popover) { diff --git a/src/ephy-location-entry.h b/src/ephy-location-entry.h index 40ce38925..0071569f0 100644 --- a/src/ephy-location-entry.h +++ b/src/ephy-location-entry.h @@ -27,6 +27,8 @@ #include "ephy-adaptive-mode.h" #include "ephy-bookmark-states.h" +#include "ephy-password-popover.h" +#include "ephy-permission-popover.h" #include "ephy-security-levels.h" G_BEGIN_DECLS @@ -54,6 +56,18 @@ void ephy_location_entry_set_bookmark_icon_state (EphyLocationEntr void ephy_location_entry_set_lock_tooltip (EphyLocationEntry *entry, const char *tooltip); +void ephy_location_entry_set_password_popover (EphyLocationEntry *entry, + EphyPasswordPopover *popover); + +void ephy_location_entry_show_password_popover (EphyLocationEntry *entry); + +void ephy_location_entry_add_permission_popover (EphyLocationEntry *entry, + EphyPermissionPopover *popover); + +void ephy_location_entry_show_best_permission_popover (EphyLocationEntry *entry); + +void ephy_location_entry_clear_permission_buttons (EphyLocationEntry *entry); + void ephy_location_entry_set_add_bookmark_popover (EphyLocationEntry *entry, GtkPopover *popover); diff --git a/src/ephy-password-popover.c b/src/ephy-password-popover.c new file mode 100644 index 000000000..acd79a43a --- /dev/null +++ b/src/ephy-password-popover.c @@ -0,0 +1,206 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2023 Jamie Murphy <hello@itsjamie.dev> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "ephy-password-popover.h" + +#include "ephy-window.h" + +struct _EphyPasswordPopover { + GtkPopover parent_instance; + + GtkWidget *username_entry; + GtkWidget *password_entry; + + EphyPasswordRequestData *request_data; +}; + +G_DEFINE_FINAL_TYPE (EphyPasswordPopover, ephy_password_popover, GTK_TYPE_POPOVER) + +enum { + PROP_0, + PROP_REQUEST_DATA, + LAST_PROP +}; + +static GParamSpec *obj_properties[LAST_PROP]; + +enum { + RESPONSE, + LAST_SIGNAL +}; + +static gint signals[LAST_SIGNAL] = { 0 }; + +static void +on_entry_changed (EphyPasswordPopover *self, + GtkEditable *entry) +{ + const char *text = gtk_editable_get_text (entry); + + if (entry == GTK_EDITABLE (self->username_entry)) + self->request_data->username = g_strdup (text); + + if (entry == GTK_EDITABLE (self->password_entry)) + self->request_data->password = g_strdup (text); +} + +static void +on_password_never (EphyPasswordPopover *self, + GtkButton *button) +{ + EphyEmbedShell *shell = EPHY_EMBED_SHELL (ephy_embed_shell_get_default ()); + EphyPermissionsManager *permissions_manager = ephy_embed_shell_get_permissions_manager (shell); + + ephy_permissions_manager_set_permission (permissions_manager, + EPHY_PERMISSION_TYPE_SAVE_PASSWORD, + self->request_data->origin, + EPHY_PERMISSION_DENY); + + gtk_popover_popdown (GTK_POPOVER (self)); + g_signal_emit (self, signals[RESPONSE], 0); +} + +static void +on_password_save (EphyPasswordPopover *self, + GtkButton *button) +{ + EphyEmbedShell *shell = EPHY_EMBED_SHELL (ephy_embed_shell_get_default ()); + EphyPasswordManager *password_manager = ephy_embed_shell_get_password_manager (shell); + + ephy_password_manager_save (password_manager, self->request_data->origin, + self->request_data->target_origin, self->request_data->username, + self->request_data->password, self->request_data->usernameField, + self->request_data->passwordField, self->request_data->isNew); + + gtk_popover_popdown (GTK_POPOVER (self)); + g_signal_emit (self, signals[RESPONSE], 0); +} + +static void +ephy_password_popover_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyPasswordPopover *self = EPHY_PASSWORD_POPOVER (object); + + switch (prop_id) { + case PROP_REQUEST_DATA: + g_value_set_pointer (value, self->request_data); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_password_popover_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyPasswordPopover *self = EPHY_PASSWORD_POPOVER (object); + + switch (prop_id) { + case PROP_REQUEST_DATA: + self->request_data = g_value_get_pointer (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_password_popover_constructed (GObject *object) +{ + EphyPasswordPopover *self = EPHY_PASSWORD_POPOVER (object); + + G_OBJECT_CLASS (ephy_password_popover_parent_class)->constructed (object); + + gtk_editable_set_text (GTK_EDITABLE (self->username_entry), + self->request_data->username); + gtk_editable_set_text (GTK_EDITABLE (self->password_entry), + self->request_data->password); +} + +static void +ephy_password_popover_finalize (GObject *object) +{ + EphyPasswordPopover *popover = EPHY_PASSWORD_POPOVER (object); + + ephy_password_request_data_free (popover->request_data); + + G_OBJECT_CLASS (ephy_password_popover_parent_class)->finalize (object); +} + +static void +ephy_password_popover_class_init (EphyPasswordPopoverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = ephy_password_popover_get_property; + object_class->set_property = ephy_password_popover_set_property; + object_class->constructed = ephy_password_popover_constructed; + object_class->finalize = ephy_password_popover_finalize; + + obj_properties[PROP_REQUEST_DATA] = + g_param_spec_pointer ("request-data", "", "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, obj_properties); + + /** + * EphyPasswordPopover::response: + * @popover: the object on which the signal is emitted + * + * Emitted when the user responses to the popover prompt + * + */ + signals[RESPONSE] = g_signal_new ("response", G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/password-popover.ui"); + gtk_widget_class_bind_template_child (widget_class, EphyPasswordPopover, username_entry); + gtk_widget_class_bind_template_child (widget_class, EphyPasswordPopover, password_entry); + + gtk_widget_class_bind_template_callback (widget_class, on_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, on_password_save); + gtk_widget_class_bind_template_callback (widget_class, on_password_never); +} + +static void +ephy_password_popover_init (EphyPasswordPopover *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +EphyPasswordPopover * +ephy_password_popover_new (EphyPasswordRequestData *request_data) +{ + return g_object_new (EPHY_TYPE_PASSWORD_POPOVER, + "request-data", request_data, + NULL); +} diff --git a/src/ephy-password-popover.h b/src/ephy-password-popover.h new file mode 100644 index 000000000..de30dd99d --- /dev/null +++ b/src/ephy-password-popover.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2023 Jamie Murphy <hello@itsjamie.dev> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> +#include <gtk/gtk.h> + +#include "ephy-password-manager.h" + +G_BEGIN_DECLS + +#define EPHY_TYPE_PASSWORD_POPOVER (ephy_password_popover_get_type()) + +G_DECLARE_FINAL_TYPE (EphyPasswordPopover, ephy_password_popover, EPHY, PASSWORD_POPOVER, GtkPopover) + +EphyPasswordPopover *ephy_password_popover_new (EphyPasswordRequestData *request_data); + +G_END_DECLS diff --git a/src/ephy-permission-popover.c b/src/ephy-permission-popover.c new file mode 100644 index 000000000..a6e3f3ba9 --- /dev/null +++ b/src/ephy-permission-popover.c @@ -0,0 +1,310 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2023 Jamie Murphy <hello@itsjamie.dev> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "ephy-embed-shell.h" +#include "ephy-lib-type-builtins.h" +#include "ephy-permission-popover.h" + +#include <glib/gi18n.h> + +struct _EphyPermissionPopover { + GtkPopover parent_instance; + + GtkLabel *permission_title; + GtkLabel *permission_description; + + EphyPermissionType permission_type; + WebKitPermissionRequest *permission_request; + char *origin; +}; + +G_DEFINE_FINAL_TYPE (EphyPermissionPopover, ephy_permission_popover, GTK_TYPE_POPOVER) + +enum { + PROP_0, + PROP_PERMISSION_TYPE, + PROP_PERMISSION_REQUEST, + PROP_ORIGIN, + LAST_PROP +}; + +static GParamSpec *obj_properties[LAST_PROP]; + +enum { + ALLOW, + DENY, + LAST_SIGNAL +}; + +static gint signals[LAST_SIGNAL] = { 0 }; + +static void +on_permission_deny (EphyPermissionPopover *self) +{ + gtk_popover_popdown (GTK_POPOVER (self)); + g_signal_emit (self, signals[DENY], 0); +} + +static void +on_permission_allow (EphyPermissionPopover *self) +{ + gtk_popover_popdown (GTK_POPOVER (self)); + g_signal_emit (self, signals[ALLOW], 0); +} + +static void +ephy_permission_popover_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphyPermissionPopover *self = EPHY_PERMISSION_POPOVER (object); + + switch (prop_id) { + case PROP_PERMISSION_TYPE: + g_value_set_enum (value, self->permission_type); + break; + case PROP_PERMISSION_REQUEST: + g_value_set_object (value, self->permission_request); + break; + case PROP_ORIGIN: + g_value_set_string (value, self->origin); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_permission_popover_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphyPermissionPopover *self = EPHY_PERMISSION_POPOVER (object); + + switch (prop_id) { + case PROP_PERMISSION_TYPE: + self->permission_type = g_value_get_enum (value); + break; + case PROP_PERMISSION_REQUEST: + self->permission_request = g_object_ref (g_value_get_object (value)); + break; + case PROP_ORIGIN: + self->origin = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_permission_popover_constructed (GObject *object) +{ + EphyPermissionPopover *self = EPHY_PERMISSION_POPOVER (object); + g_autofree char *title = NULL; + g_autofree char *message = NULL; + + ephy_permission_popover_get_text (self, &title, &message); + + gtk_label_set_label (self->permission_title, title); + gtk_label_set_label (self->permission_description, message); +} + +static void +ephy_permission_popover_dispose (GObject *object) +{ + EphyPermissionPopover *self = EPHY_PERMISSION_POPOVER (object); + + g_clear_object (&self->permission_request); + + G_OBJECT_CLASS (ephy_permission_popover_parent_class)->dispose (object); +} + +static void +ephy_permission_popover_finalize (GObject *object) +{ + EphyPermissionPopover *self = EPHY_PERMISSION_POPOVER (object); + + g_free (self->origin); + + G_OBJECT_CLASS (ephy_permission_popover_parent_class)->finalize (object); +} + +static void +ephy_permission_popover_class_init (EphyPermissionPopoverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = ephy_permission_popover_get_property; + object_class->set_property = ephy_permission_popover_set_property; + object_class->constructed = ephy_permission_popover_constructed; + object_class->dispose = ephy_permission_popover_dispose; + object_class->finalize = ephy_permission_popover_finalize; + + obj_properties[PROP_PERMISSION_TYPE] = + g_param_spec_enum ("permission-type", "", "", EPHY_TYPE_PERMISSION_TYPE, + EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_PERMISSION_REQUEST] = + g_param_spec_object ("permission-request", "", "", WEBKIT_TYPE_PERMISSION_REQUEST, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + obj_properties[PROP_ORIGIN] = + g_param_spec_string ("origin", "", "", "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, obj_properties); + + /** + * EphyPermissionPopover::allow: + * @popover: the object on which the signal is emitted + * + * Emitted when the user presses "Allow" on the popover prompt + * + */ + signals[ALLOW] = g_signal_new ("allow", G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + /** + * EphyPermissionPopover::deny: + * @popover: the object on which the signal is emitted + * + * Emitted when the user presses "Deny" on the popover prompt + * + */ + signals[DENY] = g_signal_new ("deny", G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/permission-popover.ui"); + gtk_widget_class_bind_template_child (widget_class, EphyPermissionPopover, permission_title); + gtk_widget_class_bind_template_child (widget_class, EphyPermissionPopover, permission_description); + + gtk_widget_class_bind_template_callback (widget_class, on_permission_deny); + gtk_widget_class_bind_template_callback (widget_class, on_permission_allow); +} + +static void +ephy_permission_popover_init (EphyPermissionPopover *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +EphyPermissionPopover * +ephy_permission_popover_new (EphyPermissionType permission_type, + WebKitPermissionRequest *permission_request, + const char *origin) +{ + return g_object_new (EPHY_TYPE_PERMISSION_POPOVER, + "permission-type", permission_type, + "permission-request", permission_request, + "origin", origin, + NULL); +} + +EphyPermissionType +ephy_permission_popover_get_permission_type (EphyPermissionPopover *self) +{ + g_assert (EPHY_IS_PERMISSION_POPOVER (self)); + + return self->permission_type; +} + +WebKitPermissionRequest * +ephy_permission_popover_get_permission_request (EphyPermissionPopover *self) +{ + g_assert (EPHY_IS_PERMISSION_POPOVER (self)); + + return self->permission_request; +} + +const char * +ephy_permission_popover_get_origin (EphyPermissionPopover *self) +{ + g_assert (EPHY_IS_PERMISSION_POPOVER (self)); + + return g_strdup (self->origin); +} + +void +ephy_permission_popover_get_text (EphyPermissionPopover *self, + char **title, + char **message) +{ + const char *requesting_domain = NULL; + const char *current_domain = NULL; + g_autofree char *bold_origin = NULL; + + g_assert (EPHY_IS_PERMISSION_POPOVER (self)); + + bold_origin = g_markup_printf_escaped ("<b>%s</b>", self->origin); + switch (self->permission_type) { + case EPHY_PERMISSION_TYPE_SHOW_NOTIFICATIONS: + /* Translators: Notification policy for a specific site. */ + *title = g_strdup (_("Notification Request")); + *message = g_strdup_printf (_("The page at “%s” would like to send you notifications"), + bold_origin); + break; + case EPHY_PERMISSION_TYPE_ACCESS_LOCATION: + /* Translators: Geolocation policy for a specific site. */ + *title = g_strdup (_("Location Access Request")); + *message = g_strdup_printf (_("The page at “%s” would like to know your location"), + bold_origin); + break; + case EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE: + /* Translators: Microphone policy for a specific site. */ + *title = g_strdup (_("Microphone Access Request")); + *message = g_strdup_printf (_("The page at “%s” would like to use your microphone"), + bold_origin); + break; + case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM: + /* Translators: Webcam policy for a specific site. */ + *title = g_strdup (_("Webcam Access Request")); + *message = g_strdup_printf (_("The page at “%s” would like to use your webcam"), + bold_origin); + break; + case EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE: + /* Translators: Webcam and microphone policy for a specific site. */ + *title = g_strdup (_("Webcam and Microphone Access Request")); + *message = g_strdup_printf (_("The page at “%s” would like to use your webcam and microphone"), + bold_origin); + break; + case EPHY_PERMISSION_TYPE_COOKIES: + requesting_domain = webkit_website_data_access_permission_request_get_requesting_domain (WEBKIT_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST (self->permission_request)); + current_domain = webkit_website_data_access_permission_request_get_current_domain (WEBKIT_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST (self->permission_request)); + /* Translators: Cookie policy for a specific site. */ + *title = g_strdup (_("Third-party Cookies Request")); + *message = g_strdup_printf (_("The page at “%s” would like to use cookies while browsing “%s”. This will allow “%s” to track your activity."), + requesting_domain, current_domain, requesting_domain); + break; + default: + g_assert_not_reached (); + } +} diff --git a/src/ephy-permission-popover.h b/src/ephy-permission-popover.h new file mode 100644 index 000000000..8f67a2c1b --- /dev/null +++ b/src/ephy-permission-popover.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2023 Jamie Murphy <hello@itsjamie.dev> + * + * This file is part of Epiphany. + * + * Epiphany is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Epiphany is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Epiphany. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "ephy-permissions-manager.h" + +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <webkit/webkit.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_PERMISSION_POPOVER (ephy_permission_popover_get_type()) + +G_DECLARE_FINAL_TYPE (EphyPermissionPopover, ephy_permission_popover, EPHY, PERMISSION_POPOVER, GtkPopover) + +EphyPermissionPopover *ephy_permission_popover_new (EphyPermissionType permission_type, + WebKitPermissionRequest *permission_request, + const char *origin); + +EphyPermissionType ephy_permission_popover_get_permission_type (EphyPermissionPopover *self); + +WebKitPermissionRequest *ephy_permission_popover_get_permission_request (EphyPermissionPopover *self); + +const char *ephy_permission_popover_get_origin (EphyPermissionPopover *self); + +void ephy_permission_popover_get_text (EphyPermissionPopover *self, + char **title, + char **message); + +G_END_DECLS diff --git a/src/ephy-window.c b/src/ephy-window.c index ae5c7a7ec..d815a1dc7 100644 --- a/src/ephy-window.c +++ b/src/ephy-window.c @@ -43,12 +43,15 @@ #include "ephy-fullscreen-box.h" #include "ephy-gsb-utils.h" #include "ephy-header-bar.h" +#include "ephy-lib-type-builtins.h" #include "ephy-link.h" #include "ephy-location-entry.h" #include "ephy-mouse-gesture-controller.h" #include "ephy-pages-popover.h" #include "ephy-pages-view.h" +#include "ephy-password-popover.h" #include "ephy-permissions-manager.h" +#include "ephy-permission-popover.h" #include "ephy-prefs.h" #include "ephy-security-popover.h" #include "ephy-session.h" @@ -177,6 +180,7 @@ struct _EphyWindow { GtkWidget *toast_overlay; GtkWidget *switch_to_tab; AdwToast *switch_toast; + GHashTable *active_permission_requests; GList *pending_decisions; gulong filters_initialized_id; @@ -2646,6 +2650,187 @@ reader_mode_cb (EphyWebView *view, } static void +load_all_available_popovers (EphyWindow *window, + EphyWebView *view) +{ + GList *popover_list = g_hash_table_lookup (window->active_permission_requests, view); + EphyTitleWidget *title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar)); + EphyLocationEntry *lentry; + GList *l; + + if (!EPHY_IS_LOCATION_ENTRY (title_widget)) + return; + + lentry = EPHY_LOCATION_ENTRY (title_widget); + + ephy_location_entry_set_password_popover (lentry, NULL); + ephy_location_entry_clear_permission_buttons (lentry); + + for (l = popover_list; l; l = l->next) { + if (EPHY_IS_PASSWORD_POPOVER (l->data)) + ephy_location_entry_set_password_popover (lentry, EPHY_PASSWORD_POPOVER (l->data)); + else if (EPHY_IS_PERMISSION_POPOVER (l->data)) + ephy_location_entry_add_permission_popover (lentry, EPHY_PERMISSION_POPOVER (l->data)); + } +} + +static void +popover_response_cb (EphyWindow *window, + gpointer popover) +{ + EphyTitleWidget *title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar)); + EphyLocationEntry *lentry; + EphyWebView *view; + GList *popover_list; + + if (!EPHY_IS_LOCATION_ENTRY (title_widget)) + return; + + lentry = EPHY_LOCATION_ENTRY (title_widget); + + if (EPHY_IS_PASSWORD_POPOVER (popover)) + ephy_location_entry_set_password_popover (lentry, NULL); + + view = ephy_shell_get_active_web_view (ephy_shell_get_default ()); + popover_list = g_hash_table_lookup (window->active_permission_requests, view); + popover_list = g_list_remove (popover_list, popover); + + if (!popover_list) + g_hash_table_steal (window->active_permission_requests, view); + else + g_hash_table_replace (window->active_permission_requests, view, popover_list); + + g_object_unref (popover); +} + +static void +set_permission (EphyPermissionPopover *popover, + gboolean response) +{ + EphyEmbedShell *shell = ephy_embed_shell_get_default (); + EphyPermissionsManager *permissions_manager = ephy_embed_shell_get_permissions_manager (shell); + EphyPermissionType permission_type = ephy_permission_popover_get_permission_type (popover); + const char *origin = ephy_permission_popover_get_origin (popover); + + if (permission_type != (EPHY_PERMISSION_TYPE_ACCESS_WEBCAM_AND_MICROPHONE | EPHY_PERMISSION_TYPE_COOKIES)) { + ephy_permissions_manager_set_permission (permissions_manager, + permission_type, origin, + response ? EPHY_PERMISSION_PERMIT + : EPHY_PERMISSION_DENY); + } else { + ephy_permissions_manager_set_permission (permissions_manager, + EPHY_PERMISSION_TYPE_ACCESS_WEBCAM, + origin, + response ? EPHY_PERMISSION_PERMIT + : EPHY_PERMISSION_DENY); + ephy_permissions_manager_set_permission (permissions_manager, + EPHY_PERMISSION_TYPE_ACCESS_MICROPHONE, + origin, + response ? EPHY_PERMISSION_PERMIT + : EPHY_PERMISSION_DENY); + } + + gtk_widget_unparent (GTK_WIDGET (popover)); +} + +static void +on_permission_allow (AdwMessageDialog *self, + char *response, + EphyPermissionPopover *popover) +{ + webkit_permission_request_allow (ephy_permission_popover_get_permission_request (popover)); + set_permission (popover, TRUE); +} + +static void +on_permission_deny (AdwMessageDialog *self, + char *response, + EphyPermissionPopover *popover) +{ + webkit_permission_request_deny (ephy_permission_popover_get_permission_request (popover)); + set_permission (popover, FALSE); +} + +static void +popover_allow_cb (EphyPermissionPopover *popover, + EphyWindow *window) +{ + webkit_permission_request_allow (ephy_permission_popover_get_permission_request (popover)); + set_permission (popover, TRUE); + + popover_response_cb (window, popover); +} + +static void +popover_deny_cb (EphyPermissionPopover *popover, + EphyWindow *window) +{ + webkit_permission_request_deny (ephy_permission_popover_get_permission_request (popover)); + set_permission (popover, FALSE); + + popover_response_cb (window, popover); +} + +static void +permission_requested_cb (EphyWebView *view, + EphyPermissionType permission_type, + WebKitPermissionRequest *request, + const char *origin, + EphyWindow *window) +{ + EphyPermissionPopover *popover; + + if (!gtk_widget_is_visible (GTK_WIDGET (window))) + return; + + popover = ephy_permission_popover_new (permission_type, request, origin); + + if ((ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) == EPHY_EMBED_SHELL_MODE_APPLICATION) || + (window->adaptive_mode == EPHY_ADAPTIVE_MODE_NARROW)) { + AdwMessageDialog *dialog; + g_autofree char *title = NULL; + g_autofree char *message = NULL; + + ephy_permission_popover_get_text (popover, &title, &message); + dialog = ADW_MESSAGE_DIALOG (adw_message_dialog_new (GTK_WINDOW (window), title, message)); + + adw_message_dialog_add_responses (dialog, + "close", _("_Ask Later"), + "deny", _("_Deny"), + "allow", _("_Allow"), + NULL); + + adw_message_dialog_set_body_use_markup (dialog, TRUE); + adw_message_dialog_set_response_appearance (dialog, "deny", ADW_RESPONSE_DESTRUCTIVE); + adw_message_dialog_set_response_appearance (dialog, "allow", ADW_RESPONSE_SUGGESTED); + adw_message_dialog_set_default_response (dialog, "close"); + adw_message_dialog_set_close_response (dialog, "close"); + + g_signal_connect (dialog, "response::allow", G_CALLBACK (on_permission_allow), popover); + g_signal_connect (dialog, "response::deny", G_CALLBACK (on_permission_deny), popover); + + gtk_window_present (GTK_WINDOW (dialog)); + } else { + EphyTitleWidget *title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar)); + EphyLocationEntry *lentry; + GList *list = g_hash_table_lookup (EPHY_WINDOW (window)->active_permission_requests, view); + + g_assert (EPHY_IS_LOCATION_ENTRY (title_widget)); + + g_object_ref_sink (popover); + + lentry = EPHY_LOCATION_ENTRY (title_widget); + ephy_location_entry_add_permission_popover (lentry, popover); + ephy_location_entry_show_best_permission_popover (lentry); + list = g_list_append (list, popover); + g_hash_table_replace (EPHY_WINDOW (window)->active_permission_requests, view, list); + + g_signal_connect (popover, "allow", G_CALLBACK (popover_allow_cb), window); + g_signal_connect (popover, "deny", G_CALLBACK (popover_deny_cb), window); + } +} + +static void tab_view_page_attached_cb (AdwTabView *tab_view, AdwTabPage *page, gint position, @@ -2663,6 +2848,9 @@ tab_view_page_attached_cb (AdwTabView *tab_view, g_signal_connect_object (ephy_embed_get_web_view (embed), "download-only-load", G_CALLBACK (download_only_load_cb), window, G_CONNECT_AFTER); + g_signal_connect_object (ephy_embed_get_web_view (embed), "permission-requested", + G_CALLBACK (permission_requested_cb), window, G_CONNECT_AFTER); + g_signal_connect_object (ephy_embed_get_web_view (embed), "notify::reader-mode", G_CALLBACK (reader_mode_cb), window, G_CONNECT_AFTER); @@ -2689,6 +2877,8 @@ tab_view_page_detached_cb (AdwTabView *tab_view, g_signal_handlers_disconnect_by_func (ephy_embed_get_web_view (EPHY_EMBED (content)), G_CALLBACK (download_only_load_cb), window); + g_signal_handlers_disconnect_by_func + (ephy_embed_get_web_view (EPHY_EMBED (content)), G_CALLBACK (permission_requested_cb), window); } static void @@ -2956,6 +3146,8 @@ tab_view_notify_selected_page_cb (EphyWindow *window) ephy_window_set_active_tab (window, embed); update_reader_mode (window, view); + + load_all_available_popovers (window, view); } static EphyTabView * @@ -3086,6 +3278,13 @@ is_browser_default (void) } static void +free_permission_requests (gpointer key, + GList *value) +{ + g_list_free_full (value, g_object_unref); +} + +static void ephy_window_dispose (GObject *object) { EphyWindow *window = EPHY_WINDOW (object); @@ -3105,6 +3304,11 @@ ephy_window_dispose (GObject *object) g_clear_handle_id (&window->modified_forms_timeout_id, g_source_remove); g_hash_table_unref (window->action_labels); + + g_hash_table_foreach (window->active_permission_requests, + (GHFunc)free_permission_requests, NULL); + + g_hash_table_unref (window->active_permission_requests); } G_OBJECT_CLASS (ephy_window_parent_class)->dispose (object); @@ -3488,6 +3692,125 @@ download_completed_cb (EphyDownload *download, } static void +on_username_entry_changed (GtkEditable *entry, + EphyPasswordRequestData *request_data) +{ + const char *text = gtk_editable_get_text (entry); + g_free (request_data->username); + request_data->username = g_strdup (text); +} + +static void +on_password_entry_changed (GtkEditable *entry, + EphyPasswordRequestData *request_data) +{ + const char *text = gtk_editable_get_text (entry); + g_free (request_data->password); + request_data->password = g_strdup (text); +} + +static void +on_password_never (AdwMessageDialog *dialog, + const char *response, + EphyPasswordRequestData *request_data) +{ + EphyEmbedShell *shell = EPHY_EMBED_SHELL (ephy_embed_shell_get_default ()); + EphyPermissionsManager *permissions_manager = ephy_embed_shell_get_permissions_manager (shell); + + ephy_permissions_manager_set_permission (permissions_manager, + EPHY_PERMISSION_TYPE_SAVE_PASSWORD, + request_data->origin, + EPHY_PERMISSION_DENY); +} + +static void +on_password_save (AdwMessageDialog *dialog, + const char *response, + EphyPasswordRequestData *request_data) +{ + EphyEmbedShell *shell = EPHY_EMBED_SHELL (ephy_embed_shell_get_default ()); + EphyPasswordManager *password_manager = ephy_embed_shell_get_password_manager (shell); + + ephy_password_manager_save (password_manager, request_data->origin, + request_data->target_origin, request_data->username, + request_data->password, request_data->usernameField, + request_data->passwordField, request_data->isNew); +} + +static void +save_password_cb (EphyEmbedShell *shell, + EphyPasswordRequestData *request_data) +{ + GtkWindow *window; + GtkWidget *title_widget; + EphyLocationEntry *lentry; + + /* Sanity checks would have already occurred before this point */ + window = gtk_application_get_active_window (GTK_APPLICATION (ephy_shell_get_default ())); + if (!gtk_widget_is_visible (GTK_WIDGET (window))) + return; + + if ((ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) == EPHY_EMBED_SHELL_MODE_APPLICATION) || + (EPHY_WINDOW (window)->adaptive_mode == EPHY_ADAPTIVE_MODE_NARROW)) { + AdwMessageDialog *dialog; + GtkBox *entry_box; + GtkWidget *username_entry; + GtkWidget *password_entry; + + dialog = ADW_MESSAGE_DIALOG (adw_message_dialog_new (GTK_WINDOW (window), _("Save login?"), + _("Passwords are saved only on your device and can be removed at any time in Preferences"))); + entry_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6)); + username_entry = gtk_entry_new (); + password_entry = gtk_password_entry_new (); + + adw_message_dialog_add_responses (dialog, + "close", _("Not Now"), + "never", _("Never Save"), + "save", _("Save"), + NULL); + + adw_message_dialog_set_response_appearance (dialog, "never", ADW_RESPONSE_DESTRUCTIVE); + adw_message_dialog_set_response_appearance (dialog, "save", ADW_RESPONSE_SUGGESTED); + adw_message_dialog_set_default_response (dialog, "close"); + adw_message_dialog_set_close_response (dialog, "close"); + + gtk_editable_set_text (GTK_EDITABLE (username_entry), request_data->username); + gtk_editable_set_text (GTK_EDITABLE (password_entry), request_data->password); + gtk_password_entry_set_show_peek_icon (GTK_PASSWORD_ENTRY (password_entry), TRUE); + + adw_message_dialog_set_extra_child (dialog, GTK_WIDGET (entry_box)); + gtk_box_append (entry_box, username_entry); + gtk_box_append (entry_box, password_entry); + + g_signal_connect (dialog, "response::save", G_CALLBACK (on_password_save), request_data); + g_signal_connect (dialog, "response::never", G_CALLBACK (on_password_never), request_data); + g_signal_connect (username_entry, "changed", G_CALLBACK (on_username_entry_changed), request_data); + g_signal_connect (password_entry, "changed", G_CALLBACK (on_password_entry_changed), request_data); + + gtk_window_present (GTK_WINDOW (dialog)); + } else { + EphyPasswordPopover *popover = ephy_password_popover_new (request_data); + EphyWebView *view = ephy_shell_get_active_web_view (EPHY_SHELL (shell)); + GList *list = g_hash_table_lookup (EPHY_WINDOW (window)->active_permission_requests, view); + + title_widget = GTK_WIDGET (ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (ephy_window_get_header_bar (EPHY_WINDOW (window))))); + + g_assert (EPHY_IS_LOCATION_ENTRY (title_widget)); + + lentry = EPHY_LOCATION_ENTRY (title_widget); + + g_object_ref_sink (popover); + + ephy_location_entry_set_password_popover (lentry, popover); + ephy_location_entry_show_password_popover (lentry); + list = g_list_append (list, popover); + g_hash_table_replace (EPHY_WINDOW (window)->active_permission_requests, view, list); + + g_signal_connect_swapped (popover, "response", G_CALLBACK (popover_response_cb), window); + } +} + +static void notify_leaflet_child_cb (EphyWindow *window) { GActionGroup *action_group; @@ -3581,6 +3904,9 @@ ephy_window_constructed (GObject *object) g_strdup (action_label[i].label)); } + window->active_permission_requests = g_hash_table_new (g_direct_hash, + g_direct_equal); + /* Set accels for actions */ app = g_application_get_default (); for (i = 0; i < G_N_ELEMENTS (accels); i++) { @@ -3733,6 +4059,7 @@ ephy_window_class_init (EphyWindowClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkWindowClass *window_class = GTK_WINDOW_CLASS (klass); + EphyShell *shell; EphyDownloadsManager *manager; object_class->constructed = ephy_window_constructed; @@ -3766,8 +4093,10 @@ ephy_window_class_init (EphyWindowClass *klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - manager = ephy_embed_shell_get_downloads_manager (EPHY_EMBED_SHELL (ephy_shell_get_default ())); + shell = ephy_shell_get_default (); + manager = ephy_embed_shell_get_downloads_manager (EPHY_EMBED_SHELL (shell)); g_signal_connect (manager, "download-completed", G_CALLBACK (download_completed_cb), NULL); + g_signal_connect (shell, "password-form-submitted", G_CALLBACK (save_password_cb), NULL); } static void diff --git a/src/meson.build b/src/meson.build index 3e48be2d8..4edcccd1d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,6 +44,8 @@ libephymain_sources = [ 'ephy-page-row.c', 'ephy-pages-popover.c', 'ephy-pages-view.c', + 'ephy-permission-popover.c', + 'ephy-password-popover.c', 'ephy-security-popover.c', 'ephy-session.c', 'ephy-shell.c', diff --git a/src/resources/ephy-permission-camera-symbolic.svg b/src/resources/ephy-permission-camera-symbolic.svg new file mode 100644 index 000000000..f3f6564cd --- /dev/null +++ b/src/resources/ephy-permission-camera-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 7.023438 3.003906 c -1.644532 0 -3 1.355469 -3 3 v 1.425782 l -3.316407 -3.429688 h -0.6835935 v 8 h 0.6445315 l 3.355469 -3.496094 v 1.5 c 0 1.644532 1.355468 3 3 3 h 5 c 1.644531 0 3 -1.355468 3 -3 v -4 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 5 c 0.570312 0 1 0.429688 1 1 v 4 c 0 0.574219 -0.429688 1 -1 1 h -5 c -0.570313 0 -1 -0.425781 -1 -1 v -4 c 0 -0.570312 0.429687 -1 1 -1 z m 0 0" fill="#222222"/> +</svg> diff --git a/src/resources/ephy-permission-generic-symbolic.svg b/src/resources/ephy-permission-generic-symbolic.svg new file mode 100644 index 000000000..89b383792 --- /dev/null +++ b/src/resources/ephy-permission-generic-symbolic.svg @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <g fill="#222222"> + <path d="m 11.5 1 c -1.921875 0 -3.5 1.578125 -3.5 3.5 s 1.578125 3.5 3.5 3.5 s 3.5 -1.578125 3.5 -3.5 s -1.578125 -3.5 -3.5 -3.5 z m 0 2 c 0.839844 0 1.5 0.660156 1.5 1.5 s -0.660156 1.5 -1.5 1.5 s -1.5 -0.660156 -1.5 -1.5 s 0.660156 -1.5 1.5 -1.5 z m 0 0"/> + <path d="m 4.5 8 c -1.921875 0 -3.5 1.578125 -3.5 3.5 s 1.578125 3.5 3.5 3.5 c 1.386719 0 2.59375 -0.820312 3.15625 -2 h 5.84375 c 0.832031 0 1.5 -0.667969 1.5 -1.5 s -0.667969 -1.5 -1.5 -1.5 h -5.84375 c -0.5625 -1.179688 -1.769531 -2 -3.15625 -2 z m 0 2 c 0.839844 0 1.5 0.660156 1.5 1.5 s -0.660156 1.5 -1.5 1.5 s -1.5 -0.660156 -1.5 -1.5 s 0.660156 -1.5 1.5 -1.5 z m 0 0"/> + <path d="m 2.5 3 c -0.832031 0 -1.5 0.667969 -1.5 1.5 s 0.667969 1.5 1.5 1.5 h 4.769531 c -0.175781 -0.480469 -0.265625 -0.988281 -0.269531 -1.5 c 0 -0.511719 0.09375 -1.019531 0.269531 -1.5 z m 0 0" fill-opacity="0.34902"/> + </g> +</svg> diff --git a/src/resources/ephy-permission-location-symbolic.svg b/src/resources/ephy-permission-location-symbolic.svg new file mode 100644 index 000000000..3e672d0bb --- /dev/null +++ b/src/resources/ephy-permission-location-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 8 2 c -2.761719 0 -5 2.238281 -5 5 c 0.003906 0.589844 0.113281 1.175781 0.320312 1.730469 h -0.011718 c 0.53125 1.652343 3.152344 4.039062 4.691406 5.578125 v -0.003906 c 1.257812 -1.253907 3.230469 -3.074219 4.203125 -4.601563 c 0.222656 -0.34375 0.390625 -0.671875 0.488281 -0.972656 h -0.015625 c 0.210938 -0.554688 0.320313 -1.140625 0.324219 -1.730469 c 0 -2.761719 -2.238281 -5 -5 -5 z m 0 3.078125 c 1.0625 0 1.921875 0.859375 1.921875 1.921875 c -0.007813 0.988281 -0.757813 1.808594 -1.742187 1.902344 l -0.53125 -0.019532 c -0.90625 -0.167968 -1.566407 -0.957031 -1.570313 -1.882812 c 0 -1.0625 0.859375 -1.921875 1.921875 -1.921875 z m 0 0" fill="#222222"/> +</svg> diff --git a/src/resources/ephy-permission-microphone-symbolic.svg b/src/resources/ephy-permission-microphone-symbolic.svg new file mode 100644 index 000000000..ac1467110 --- /dev/null +++ b/src/resources/ephy-permission-microphone-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 8 0 c -1.660156 0 -3 1.339844 -3 3 v 5 c 0 1.660156 1.339844 3 3 3 s 3 -1.339844 3 -3 v -5 c 0 -1.660156 -1.339844 -3 -3 -3 z m 0 2 c 0.554688 0 1 0.445312 1 1 v 5 c 0 0.554688 -0.445312 1 -1 1 s -1 -0.445312 -1 -1 v -5 c 0 -0.554688 0.445312 -1 1 -1 z m -6 5 v 1.011719 c 0 2.964843 2.164062 5.429687 5 5.90625 v 2.082031 h 2 v -2.082031 c 2.835938 -0.476563 5 -2.941407 5 -5.90625 v -1.011719 h -1.5 v 1.011719 c 0 2.5 -1.992188 4.488281 -4.5 4.488281 s -4.5 -1.988281 -4.5 -4.488281 v -1.011719 z m 0 0" fill="#222222"/> +</svg> diff --git a/src/resources/ephy-permission-notifications-symbolic.svg b/src/resources/ephy-permission-notifications-symbolic.svg new file mode 100644 index 000000000..e4e4429b2 --- /dev/null +++ b/src/resources/ephy-permission-notifications-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 8 1 c -0.710938 0.003906 -1.316406 0.523438 -1.4375 1.238281 c -1.542969 0.597657 -2.558594 2.085938 -2.5625 3.742188 c 0 0.007812 0 0.011719 0.003906 0.019531 h -0.003906 v 4 h -0.5 c -0.554688 0 -1 0.445312 -1 1 s 0.445312 1 1 1 h 9 c 0.554688 0 1 -0.445312 1 -1 s -0.445312 -1 -1 -1 h -0.5 v -4 c 0 -0.007812 0 -0.011719 0 -0.019531 c 0 -1.660157 -1.015625 -3.152344 -2.558594 -3.75 c -0.128906 -0.710938 -0.734375 -1.230469 -1.441406 -1.230469 z m -2 12 c 0 0.714844 0.382812 1.375 1 1.730469 c 0.617188 0.359375 1.382812 0.359375 2 0 c 0.617188 -0.355469 1 -1.015625 1 -1.730469 z m 0 0" fill="#222222"/> +</svg> diff --git a/src/resources/epiphany.gresource.xml b/src/resources/epiphany.gresource.xml index 70f6cea6e..16458e790 100644 --- a/src/resources/epiphany.gresource.xml +++ b/src/resources/epiphany.gresource.xml @@ -23,7 +23,9 @@ <file preprocess="xml-stripblanks" compressed="true">gtk/page-row.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/pages-popover.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/pages-view.ui</file> + <file preprocess="xml-stripblanks" compressed="true">gtk/password-popover.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/passwords-view.ui</file> + <file preprocess="xml-stripblanks" compressed="true">gtk/permission-popover.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/prefs-appearance-page.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/prefs-dialog.ui</file> <file preprocess="xml-stripblanks" compressed="true">gtk/prefs-extensions-page.ui</file> @@ -56,6 +58,11 @@ <file compressed="true" alias="scalable/actions/ephy-download-done-symbolic.svg">ephy-download-done-symbolic.svg</file> <file compressed="true" alias="scalable/actions/ephy-bookmark-tag-symbolic.svg">ephy-bookmark-tag-symbolic.svg</file> <file compressed="true" alias="scalable/actions/ephy-reader-mode-symbolic.svg">ephy-reader-mode-symbolic.svg</file> + <file compressed="true" alias="scalable/actions/ephy-permission-camera-symbolic.svg">ephy-permission-camera-symbolic.svg</file> + <file compressed="true" alias="scalable/actions/ephy-permission-generic-symbolic.svg">ephy-permission-generic-symbolic.svg</file> + <file compressed="true" alias="scalable/actions/ephy-permission-location-symbolic.svg">ephy-permission-location-symbolic.svg</file> + <file compressed="true" alias="scalable/actions/ephy-permission-microphone-symbolic.svg">ephy-permission-microphone-symbolic.svg</file> + <file compressed="true" alias="scalable/actions/ephy-permission-notifications-symbolic.svg">ephy-permission-notifications-symbolic.svg</file> <file compressed="true" alias="scalable/status/ephy-audio-muted-symbolic.svg">ephy-audio-muted-symbolic.svg</file> <file compressed="true" alias="scalable/status/ephy-audio-playing-symbolic.svg">ephy-audio-playing-symbolic.svg</file> <file compressed="true" alias="scalable/status/ephy-non-starred-symbolic.svg">ephy-non-starred-symbolic.svg</file> diff --git a/src/resources/gtk/location-entry.ui b/src/resources/gtk/location-entry.ui index 779772a35..aa92c4ea9 100644 --- a/src/resources/gtk/location-entry.ui +++ b/src/resources/gtk/location-entry.ui @@ -92,6 +92,18 @@ </object> </child> <child> + <object class="GtkMenuButton" id="password_button"> + <property name="tooltip-text" translatable="yes">View available passwords</property> + <property name="valign">center</property> + <property name="visible">False</property> + <property name="icon-name">dialog-password-symbolic</property> + <style> + <class name="entry-icon"/> + <class name="end"/> + </style> + </object> + </child> + <child> <object class="GtkPopover" id="suggestions_popover"> <property name="has-arrow">False</property> <property name="autohide">False</property> diff --git a/src/resources/gtk/password-popover.ui b/src/resources/gtk/password-popover.ui new file mode 100644 index 000000000..4b2d6f3b4 --- /dev/null +++ b/src/resources/gtk/password-popover.ui @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.16 --> + <template class="EphyPasswordPopover" parent="GtkPopover"> + <style> + <class name="message-popover"/> + </style> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="margin-top">18</property> + <property name="margin-bottom">12</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="xalign">0</property> + <property name="label">Save login?</property> + <property name="margin-bottom">6</property> + <style> + <class name="heading"/> + </style> + </object> + </child> + <child> + <object class="GtkEntry" id="username_entry"> + <signal name="changed" handler="on_entry_changed" object="EphyPasswordPopover" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkPasswordEntry" id="password_entry"> + <signal name="changed" handler="on_entry_changed" object="EphyPasswordPopover" swapped="yes"/> + <property name="show-peek-icon">True</property> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="label">Passwords are saved only on your device and can be removed at any time in Preferences</property> + <property name="wrap">True</property> + <property name="margin-top">6</property> + <property name="max-width-chars">40</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSeparator"> + </object> + </child> + <child> + <object class="GtkBox"> + <style> + <class name="response-area"/> + </style> + <child> + <object class="GtkButton" id="close_button"> + <property name="label">_Never Save</property> + <property name="use-underline">True</property> + <property name="hexpand">True</property> + <signal name="clicked" handler="on_password_never" object="EphyPasswordPopover" swapped="yes"/> + <style> + <class name="flat"/> + </style> + </object> + </child> + <child> + <object class="GtkSeparator"> + </object> + </child> + <child> + <object class="GtkButton" id="save_button"> + <property name="label">_Save</property> + <property name="use-underline">True</property> + <property name="hexpand">True</property> + <signal name="clicked" handler="on_password_save" object="EphyPasswordPopover" swapped="yes"/> + <style> + <class name="flat"/> + <class name="suggested"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="close_button"/> + <widget name="save_button"/> + </widgets> + </object> +</interface> diff --git a/src/resources/gtk/permission-popover.ui b/src/resources/gtk/permission-popover.ui new file mode 100644 index 000000000..4b41e0882 --- /dev/null +++ b/src/resources/gtk/permission-popover.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 3.16 --> + <template class="EphyPermissionPopover" parent="GtkPopover"> + <style> + <class name="message-popover"/> + </style> + <child> + <object class="GtkBox"> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="margin-top">18</property> + <property name="margin-bottom">12</property> + <property name="margin-start">18</property> + <property name="margin-end">18</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="permission_title"> + <property name="xalign">0</property> + <property name="margin-bottom">6</property> + <style> + <class name="heading"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="permission_description"> + <property name="wrap">True</property> + <property name="max-width-chars">40</property> + <property name="use-markup">True</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkSeparator"> + </object> + </child> + <child> + <object class="GtkBox"> + <style> + <class name="response-area"/> + </style> + <child> + <object class="GtkButton" id="deny_button"> + <property name="label">_Deny</property> + <property name="use-underline">True</property> + <property name="hexpand">True</property> + <signal name="clicked" handler="on_permission_deny" object="EphyPermissionPopover" swapped="yes"/> + <style> + <class name="flat"/> + </style> + </object> + </child> + <child> + <object class="GtkSeparator"> + </object> + </child> + <child> + <object class="GtkButton" id="allow_button"> + <property name="label">_Allow</property> + <property name="use-underline">True</property> + <property name="hexpand">True</property> + <signal name="clicked" handler="on_permission_allow" object="EphyPermissionPopover" swapped="yes"/> + <style> + <class name="flat"/> + <class name="suggested"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkSizeGroup"> + <property name="mode">horizontal</property> + <widgets> + <widget name="deny_button"/> + <widget name="allow_button"/> + </widgets> + </object> +</interface> diff --git a/src/resources/style-hc.css b/src/resources/style-hc.css index e8d7f412e..f1973ffd6 100644 --- a/src/resources/style-hc.css +++ b/src/resources/style-hc.css @@ -13,3 +13,9 @@ popover.suggestions listview > row:selected { box-shadow: inset 0 0 0 1px @borders; } + +.response-area > button:hover, +.response-area > button:active, +.response-area > button:checked { + box-shadow: none; +} diff --git a/src/resources/style.css b/src/resources/style.css index d682d4538..2d0ba1197 100644 --- a/src/resources/style.css +++ b/src/resources/style.css @@ -231,3 +231,36 @@ dnd > .boxed-list { background-color: @window_bg_color; background-image: image(@card_bg_color); } + +.message-popover > contents { + padding: 0; + margin: 0; +} + +.message-popover .response-area > button { + padding: 10px 14px; + border-radius: 0; + margin-top: -1px; + margin-right: -1px; + margin-left: -1px; +} + +.message-popover .response-area > button.suggested { + color: @accent_color; +} + +.message-popover .response-area > button.destructive { + color: @destructive_color; +} + +.message-popover .response-area > button:first-child:dir(ltr), +.message-popover .response-area > button:last-child:dir(rtl) { + border-bottom-left-radius: 12px; + margin-left: 0; +} + +.message-popover .response-area > button:last-child:dir(ltr), +.message-popover .response-area > button:first-child:dir(rtl) { + border-bottom-right-radius: 12px; + margin-right: 0; +} |