Blob Blame History Raw
/*
 *
 *  Copyright (C) 2013  Bastien Nocera <hadess@hadess.net>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>

#include <glib/gi18n-lib.h>

#include "bluetooth-pairing-dialog.h"
#include "bluetooth-enums.h"
#include "gnome-bluetooth-enum-types.h"
#include "bluetooth-settings-resources.h"

#define BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE(obj) \
	(bluetooth_pairing_dialog_get_instance_private (obj))

typedef struct _BluetoothPairingDialogPrivate BluetoothPairingDialogPrivate;

struct _BluetoothPairingDialogPrivate {
	GtkWidget            *title;
	GtkWidget            *help_label;
	GtkWidget            *label_pin;
	GtkWidget            *entry_pin;
	GtkWidget            *pin_notebook;
	GtkWidget            *done;
	GtkWidget            *spinner;
	GtkWidget            *cancel;

	BluetoothPairingMode  mode;
	char                 *pin;
};

G_DEFINE_TYPE_WITH_PRIVATE(BluetoothPairingDialog, bluetooth_pairing_dialog, GTK_TYPE_DIALOG)

enum {
	CONFIRMATION_PAGE,
	DISPLAY_PAGE,
	MESSAGE_PAGE
};

void
bluetooth_pairing_dialog_set_mode (BluetoothPairingDialog *self,
				   BluetoothPairingMode    mode,
				   const char             *pin,
				   const char             *device_name)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
	char *title;
	char *help;
	GtkStyleContext *context;

	priv->mode = mode;

	g_clear_pointer (&priv->pin, g_free);
	priv->pin = g_strdup (pin);
	gtk_entry_set_text (GTK_ENTRY (priv->entry_pin), pin ? pin : "");
	gtk_label_set_text (GTK_LABEL (priv->label_pin), pin);

	switch (mode) {
	case BLUETOOTH_PAIRING_MODE_PIN_QUERY:
		gtk_widget_show (priv->done);
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), CONFIRMATION_PAGE);
		title = g_strdup(_("Confirm Bluetooth PIN"));
		help = g_strdup_printf (_("Please confirm the PIN that was entered on “%s”."), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION:
		gtk_widget_show (priv->done);
		gtk_button_set_label (GTK_BUTTON (priv->done), _("Confirm"));
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), CONFIRMATION_PAGE);
		title = g_strdup(_("Confirm Bluetooth PIN"));
		help = g_strdup_printf (_("Confirm the Bluetooth PIN for “%s”. This can usually be found in the device’s manual."), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL:
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD:
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE:
		gtk_widget_hide (priv->done);
		title = g_strdup_printf (_("Pairing “%s”"), device_name);
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), DISPLAY_PAGE);
		break;
	case BLUETOOTH_PAIRING_MODE_PIN_MATCH:
		gtk_button_set_label (GTK_BUTTON (priv->done), _("Confirm"));
		gtk_widget_show (priv->done);
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), DISPLAY_PAGE);
		title = g_strdup(_("Confirm Bluetooth PIN"));
		help = g_strdup_printf (_("Please confirm that the following PIN matches the one displayed on “%s”."), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_YES_NO:
		gtk_widget_show (priv->done);
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), MESSAGE_PAGE);
		title = g_strdup (_("Bluetooth Pairing Request"));
		help = g_strdup_printf (_("“%s” wants to pair with this device. Do you want to allow pairing?"), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_CONFIRM_AUTH:
		gtk_widget_show (priv->done);
		gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->pin_notebook), MESSAGE_PAGE);
		title = g_strdup (_("Confirm Bluetooth Connection"));
		help = g_strdup_printf (_("“%s” wants to connect with this device. Do you want to allow it?"), device_name);
		break;
	default:
		g_assert_not_reached ();
	}

	switch (mode) {
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_NORMAL:
		help = g_strdup_printf (_("Please enter the following PIN on “%s”."), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD:
		help = g_strdup_printf (_("Please enter the following PIN on “%s”. Then press “Return” on the keyboard."), device_name);
		break;
	case BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_ICADE:
		help = g_strdup (_("Please move the joystick of your iCade in the following directions. Then press any of the white buttons."));
		break;
	default:
		g_assert (help);
	}

	if (mode == BLUETOOTH_PAIRING_MODE_YES_NO ||
	    mode == BLUETOOTH_PAIRING_MODE_CONFIRM_AUTH) {
		gtk_button_set_label (GTK_BUTTON (priv->done), _("Allow"));
		context = gtk_widget_get_style_context (priv->done);
		gtk_style_context_remove_class (context, "suggested-action");

		gtk_button_set_label (GTK_BUTTON (priv->cancel), _("Dismiss"));
		context = gtk_widget_get_style_context (priv->cancel);
		gtk_style_context_add_class (context, "destructive-action");

		gtk_widget_hide (priv->pin_notebook);
	} else {
		gtk_button_set_label (GTK_BUTTON (priv->done), _("Confirm"));
		context = gtk_widget_get_style_context (priv->done);
		gtk_style_context_add_class (context, "suggested-action");

		gtk_button_set_label (GTK_BUTTON (priv->cancel), _("Cancel"));
		context = gtk_widget_get_style_context (priv->cancel);
		gtk_style_context_remove_class (context, "destructive-action");

		gtk_widget_show (priv->pin_notebook);
	}

	gtk_label_set_text (GTK_LABEL (priv->title), title);
	gtk_label_set_text (GTK_LABEL (priv->help_label), help);
	g_free (title);
	g_free (help);
}

BluetoothPairingMode
bluetooth_pairing_dialog_get_mode (BluetoothPairingDialog *self)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);

	return priv->mode;
}

char *
bluetooth_pairing_dialog_get_pin (BluetoothPairingDialog *self)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);

	g_assert (priv->mode == BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION ||
		  priv->mode == BLUETOOTH_PAIRING_MODE_PIN_QUERY);
	g_assert (gtk_widget_is_sensitive (GTK_WIDGET (priv->done)));

	return g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry_pin)));
}

void
bluetooth_pairing_dialog_set_pin_entered (BluetoothPairingDialog *self,
					  guint                   entered)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
	char *done;

	g_assert (priv->mode == BLUETOOTH_PAIRING_MODE_PIN_DISPLAY_KEYBOARD);
	g_assert (priv->pin);

	if (entered > 0) {
		gunichar invisible;
		GString *str;
		guint i;

		invisible = gtk_entry_get_invisible_char (GTK_ENTRY (priv->entry_pin));

		str = g_string_new (NULL);
		for (i = 0; i < entered; i++)
			g_string_append_unichar (str, invisible);
		if (entered < strlen (priv->pin))
			g_string_append (str, priv->pin + entered);

		done = g_string_free (str, FALSE);
	} else {
		done = g_strdup (priv->pin);
	}

	gtk_label_set_text (GTK_LABEL (priv->label_pin), done);
	g_free (done);
}

static void
response_cb (GtkWidget *button,
	     gpointer   user_data)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (user_data);
	int response;

	if (button == priv->done)
		response = GTK_RESPONSE_ACCEPT;
	else if (button == priv->cancel)
		response = GTK_RESPONSE_CANCEL;
	else
		g_assert_not_reached ();

	gtk_dialog_response (GTK_DIALOG (user_data), response);
}

static void
text_changed_cb (GObject    *gobject,
		 GParamSpec *pspec,
		 gpointer    user_data)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (user_data);
	const char *text;

	if (priv->mode != BLUETOOTH_PAIRING_MODE_PIN_CONFIRMATION &&
	    priv->mode != BLUETOOTH_PAIRING_MODE_PIN_QUERY)
		return;

	text = gtk_entry_get_text (GTK_ENTRY (priv->entry_pin));
	if (!text || strlen (text) < 4)
		gtk_widget_set_sensitive (GTK_WIDGET (priv->done), FALSE);
	else
		gtk_widget_set_sensitive (GTK_WIDGET (priv->done), TRUE);
}

static void
bluetooth_pairing_dialog_init (BluetoothPairingDialog *self)
{
	GtkCssProvider *provider;

	gtk_widget_init_template (GTK_WIDGET (self));

	gtk_widget_set_size_request (GTK_WIDGET (self), 380, -1);
	gtk_window_set_resizable (GTK_WINDOW (self), FALSE);

	provider = gtk_css_provider_new ();
	gtk_css_provider_load_from_resource (provider, "/org/gnome/bluetooth/bluetooth-settings.css");
	gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
						   GTK_STYLE_PROVIDER (provider),
						   GTK_STYLE_PROVIDER_PRIORITY_USER);
	g_object_unref (provider);

}

static void
bluetooth_pairing_dialog_constructed (GObject *object)
{
	BluetoothPairingDialog *self = BLUETOOTH_PAIRING_DIALOG (object);
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (self);
	GtkWidget *header;
	GtkStyleContext *context;

	/* Header */
	header = gtk_dialog_get_header_bar (GTK_DIALOG (self));
	priv->title = gtk_label_new ("");
	gtk_header_bar_set_custom_title (GTK_HEADER_BAR (header), priv->title);
	gtk_widget_show (priv->title);
	gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), FALSE);

	/* OK button */
	priv->done = gtk_button_new_with_label (_("Accept"));
	gtk_widget_set_no_show_all (priv->done, TRUE);
	gtk_widget_set_can_default (GTK_WIDGET (priv->done), TRUE);
	g_signal_connect (G_OBJECT (priv->done), "clicked",
			  G_CALLBACK (response_cb), self);
	gtk_header_bar_pack_end (GTK_HEADER_BAR (header), priv->done);

	/* Spinner */
	priv->spinner = gtk_spinner_new ();
	gtk_widget_set_margin_end (priv->spinner, 12);
	gtk_widget_set_no_show_all (priv->spinner, TRUE);
	gtk_header_bar_pack_end (GTK_HEADER_BAR (header), priv->spinner);
	g_object_bind_property (priv->spinner, "visible",
				priv->spinner, "active", 0);
	g_object_bind_property (priv->spinner, "visible",
				priv->done, "visible",
				G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN | G_BINDING_BIDIRECTIONAL);

	/* Cancel button */
	priv->cancel = gtk_button_new_with_label (_("Cancel"));
	g_signal_connect (G_OBJECT (priv->cancel), "clicked",
			  G_CALLBACK (response_cb), self);
	gtk_header_bar_pack_start (GTK_HEADER_BAR (header), priv->cancel);
	gtk_widget_show (priv->cancel);
	gtk_widget_grab_default (GTK_WIDGET (priv->done));

	g_signal_connect (G_OBJECT (priv->entry_pin), "notify::text",
			  G_CALLBACK (text_changed_cb), self);

	context = gtk_widget_get_style_context (priv->done);
	gtk_style_context_add_class (context, "suggested-action");
	context = gtk_widget_get_style_context (priv->title);
	gtk_style_context_add_class (context, "title");
}

static void
bluetooth_pairing_dialog_finalize (GObject *object)
{
	BluetoothPairingDialogPrivate *priv = BLUETOOTH_PAIRING_DIALOG_GET_PRIVATE (BLUETOOTH_PAIRING_DIALOG (object));

	g_free (priv->pin);

	G_OBJECT_CLASS(bluetooth_pairing_dialog_parent_class)->finalize(object);
}

static void
bluetooth_pairing_dialog_class_init (BluetoothPairingDialogClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

	object_class->constructed = bluetooth_pairing_dialog_constructed;
	object_class->finalize = bluetooth_pairing_dialog_finalize;

	/* Bind class to template */
	gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/bluetooth/bluetooth-pairing-dialog.ui");
	gtk_widget_class_bind_template_child_private (widget_class, BluetoothPairingDialog, help_label);
	gtk_widget_class_bind_template_child_private (widget_class, BluetoothPairingDialog, pin_notebook);
	gtk_widget_class_bind_template_child_private (widget_class, BluetoothPairingDialog, entry_pin);
	gtk_widget_class_bind_template_child_private (widget_class, BluetoothPairingDialog, label_pin);
}

/**
 * bluetooth_pairing_dialog_new:
 *
 * Returns a new #BluetoothPairingDialog widget.
 *
 * Return value: A #BluetoothPairingDialog widget
 **/
GtkWidget *
bluetooth_pairing_dialog_new (void)
{
	return g_object_new (BLUETOOTH_TYPE_PAIRING_DIALOG,
	                     "use-header-bar", 1,
	                     NULL);
}