pidgin/pidgin

f8b477a1b0b6
Add an InfoPane to the Contacts widget and move search to it

Testing Done:
Consulted with the turtles.
Opened the contact list window and searched in many ways.

Reviewed at https://reviews.imfreedom.org/r/2918/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include "purpleircv3capabilities.h"
#include "purpleircv3connection.h"
#include "purpleircv3core.h"
#include "purpleircv3sasl.h"
enum {
PROP_0,
PROP_CONNECTION,
N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };
/* Windows is does something weird with signal handling that includes defining
* SIG_ACK. We don't care about that here, so we undef it if we find it.
* See https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants?view=msvc-170
*/
#ifdef SIG_ACK
# undef SIG_ACK
#endif /* SIG_ACK */
enum {
SIG_READY,
SIG_ACK,
SIG_NAK,
SIG_DONE,
SIG_NEW,
SIG_DEL,
N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };
struct _PurpleIRCv3Capabilities {
GObject parent;
PurpleIRCv3Connection *connection;
GHashTable *caps;
GPtrArray *requests;
gatomicrefcount wait_counters;
};
/******************************************************************************
* Helpers
*****************************************************************************/
static void
purple_ircv3_capabilities_set_connection(PurpleIRCv3Capabilities *capabilities,
PurpleIRCv3Connection *connection)
{
g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
if(g_set_object(&capabilities->connection, connection)) {
g_object_notify_by_pspec(G_OBJECT(capabilities),
properties[PROP_CONNECTION]);
}
}
static void
purple_ircv3_capabilities_finish(PurpleIRCv3Capabilities *capabilities) {
purple_ircv3_connection_writef(capabilities->connection,
"CAP END");
g_signal_emit(capabilities, signals[SIG_DONE], 0);
}
static void
purple_ircv3_capabilities_add(PurpleIRCv3Capabilities *capabilities,
const char *capability)
{
char *equals = g_strstr_len(capability, -1, "=");
if(equals != NULL) {
char *key = g_strndup(capability, equals - capability);
char *value = g_strdup(equals + 1);
g_hash_table_insert(capabilities->caps, key, value);
} else {
g_hash_table_insert(capabilities->caps, g_strdup(capability), NULL);
}
}
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
ircv3_capabilities_message_tags_ack_cb(PurpleIRCv3Capabilities *capabilities,
G_GNUC_UNUSED const char *capability,
G_GNUC_UNUSED gpointer data)
{
/* We have message tags so add the stuff we support that depends on it. */
purple_ircv3_capabilities_lookup_and_request(capabilities, "msgid");
}
/******************************************************************************
* PurpleIRCv3Capabilities Implementation
*****************************************************************************/
static void
purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities)
{
PurpleAccount *account = NULL;
PurpleConnection *purple_connection = NULL;
purple_connection = PURPLE_CONNECTION(capabilities->connection);
account = purple_connection_get_account(purple_connection);
/* Don't request the sasl capability unless the user has selected the
* require-password option.
*/
if(purple_account_get_require_password(account)) {
gboolean found = FALSE;
purple_ircv3_capabilities_lookup(capabilities, "sasl", &found);
if(found) {
purple_ircv3_sasl_request(capabilities);
}
}
/* cap-notify is implied when we use CAP LS 302, so this is really just to
* make sure it's requested.
*/
purple_ircv3_capabilities_lookup_and_request(capabilities, "cap-notify");
/* message-tags is used for a lot of stuff so we need to tell everyone we
* do in fact support it.
*/
if(purple_ircv3_capabilities_lookup_and_request(capabilities,
"message-tags"))
{
g_signal_connect(capabilities, "ack::message-tags",
G_CALLBACK(ircv3_capabilities_message_tags_ack_cb),
NULL);
}
/* The server-time capability just tells the server to send a tag to
* messages, so we just need to request it and then handle the tag when
* we're processing messages if it exists.
*/
purple_ircv3_capabilities_lookup_and_request(capabilities,
PURPLE_IRCV3_CAPABILITY_SERVER_TIME);
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Capabilities,
purple_ircv3_capabilities, G_TYPE_OBJECT,
G_TYPE_FLAG_FINAL, {})
static void
purple_ircv3_capabilities_get_property(GObject *obj, guint param_id,
GValue *value, GParamSpec *pspec)
{
PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
switch(param_id) {
case PROP_CONNECTION:
g_value_set_object(value,
purple_ircv3_capabilities_get_connection(capabilities));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_ircv3_capabilities_set_property(GObject *obj, guint param_id,
const GValue *value, GParamSpec *pspec)
{
PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
switch(param_id) {
case PROP_CONNECTION:
purple_ircv3_capabilities_set_connection(capabilities,
g_value_get_object(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
break;
}
}
static void
purple_ircv3_capabilities_dispose(GObject *obj) {
PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
g_clear_object(&capabilities->connection);
G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->dispose(obj);
}
static void
purple_ircv3_capabilities_finalize(GObject *obj) {
PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
g_hash_table_destroy(capabilities->caps);
g_ptr_array_free(capabilities->requests, TRUE);
G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->finalize(obj);
}
static void
purple_ircv3_capabilities_init(PurpleIRCv3Capabilities *capabilities) {
capabilities->caps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
g_free);
capabilities->requests = g_ptr_array_new_full(0, g_free);
g_atomic_ref_count_init(&capabilities->wait_counters);
}
static void
purple_ircv3_capabilities_class_finalize(G_GNUC_UNUSED PurpleIRCv3CapabilitiesClass *klass) {
}
static void
purple_ircv3_capabilities_class_init(PurpleIRCv3CapabilitiesClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->dispose = purple_ircv3_capabilities_dispose;
obj_class->finalize = purple_ircv3_capabilities_finalize;
obj_class->get_property = purple_ircv3_capabilities_get_property;
obj_class->set_property = purple_ircv3_capabilities_set_property;
/**
* PurpleIRCv3Capabilities:connection:
*
* The PurpleIRCv3Connection object that this capabilities was created
* with.
*
* Since: 3.0.0
*/
properties[PROP_CONNECTION] = g_param_spec_object(
"connection", "connection",
"The connection this capabilities was created for.",
PURPLE_IRCV3_TYPE_CONNECTION,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
/**
* PurpleIRCv3Capabilities::ready:
* @capabilities: The instance.
*
* Emitted when @capabilities has finished receiving the list of
* capabilities from the server at startup.
*
* For dynamically added capabilities see the `added` and `removed`
* signals.
*
* Since: 3.0.0
*/
signals[SIG_READY] = g_signal_new_class_handler(
"ready",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
G_CALLBACK(purple_ircv3_capabilities_default_ready_cb),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
/**
* PurpleIRCv3Capabilities::ack:
* @capabilities: The instance.
* @capability: The capability string.
*
* Emitted when the server has acknowledged a `CAP REQ` call from
* purple_ircv3_capabilities_request.
*
* The value of @capability will be the same as the one that was requested.
*
* Since: 3.0.0
*/
signals[SIG_ACK] = g_signal_new_class_handler(
"ack",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_STRING);
/**
* PurpleIRCv3Capabilities::nak:
* @capabilities: The instance.
* @capability: The capability string.
*
* Emitted when the server has nacked a `CAP REQ` call from
* purple_ircv3_capabilities_request.
*
* The value of @capability will be the same as the one that was requested.
*
* Since: 3.0.0
*/
signals[SIG_NAK] = g_signal_new_class_handler(
"nak",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_STRING);
/**
* PurpleIRCv3Capabilities::done:
* @capabilities: The instance.
*
* Emitted when all of the requested capabilities have been either ack'd or
* nak'd by the server.
*
* Since: 3.0.0
*/
signals[SIG_DONE] = g_signal_new_class_handler(
"done",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
/**
* PurpleIRCv3Capabilities::new:
* @capabilities: The instance.
* @added: The newly added capabilities.
*
* Emitted when the server sends the `CAP NEW` command. @added is a
* [type@GLib.Strv] of the new capabilities the server added.
*
* There are two approaches to how you can use this signal. You can check
* each item in @added for the values you need and parsing their values, or
* you can call #purple_ircv3_capabilities_lookup to see if the
* capabilities you're interested in have been added.
*
* Since: 3.0.0
*/
signals[SIG_NEW] = g_signal_new_class_handler(
"new",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_STRV);
/**
* PurpleIRCv3Capabilities::del:
* @capabilities: The instance.
* @removed: The capabilities that were removed.
*
* Emitted when the server sends the `CAP DEL` command. @removed is a
* [type@GLib.Strv] of the capabilities that the server removed.
*
* There are two approaches to how you can use this signal. You can check
* each item in @removed for the values you care about, or you can call
* #purple_ircv3_capabilities_lookup to see if the capabilities you're
* interested in have been removed.
*
* Since: 3.0.0
*/
signals[SIG_DEL] = g_signal_new_class_handler(
"del",
G_OBJECT_CLASS_TYPE(klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_STRV);
}
/******************************************************************************
* Command handlers
*****************************************************************************/
static gboolean
purple_ircv3_capabilities_handle_list(PurpleIRCv3Capabilities *capabilities,
guint n_params,
GStrv params,
G_GNUC_UNUSED GError **error)
{
gboolean done = TRUE;
gchar **parts = NULL;
/* Check if we have more messages coming. */
if(n_params > 1 && purple_strequal(params[0], "*")) {
parts = g_strsplit(params[1], " ", -1);
done = FALSE;
} else {
parts = g_strsplit(params[0], " ", -1);
}
/* Add each capability to our hash table, splitting the keys and values. */
for(int i = 0; parts[i] != NULL; i++) {
purple_ircv3_capabilities_add(capabilities, parts[i]);
}
g_strfreev(parts);
if(done) {
g_signal_emit(capabilities, signals[SIG_READY], 0, signals[SIG_READY]);
/* If no capabilities were requested after we emitted the ready signal
* we're done with capability negotiation.
*/
if(capabilities->requests->len == 0) {
purple_ircv3_capabilities_remove_wait(capabilities);
}
}
return TRUE;
}
static gboolean
purple_ircv3_capabilities_handle_ack_nak(PurpleIRCv3Capabilities *capabilities,
GStrv params,
guint sig,
const char *method,
GError **error)
{
char *caps = g_strjoinv(" ", params);
guint index = 0;
gboolean found = FALSE;
gboolean ret = TRUE;
g_signal_emit(capabilities, sig, g_quark_from_string(caps), caps);
found = g_ptr_array_find_with_equal_func(capabilities->requests, caps,
g_str_equal, &index);
if(found) {
g_ptr_array_remove_index(capabilities->requests, index);
} else {
g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
"received CAP %s for unknown capability %s", method, caps);
ret = FALSE;
}
g_free(caps);
if(capabilities->requests->len == 0) {
purple_ircv3_capabilities_remove_wait(capabilities);
}
return ret;
}
static gboolean
purple_ircv3_capabilities_handle_new(PurpleIRCv3Capabilities *capabilities,
guint n_params,
GStrv params,
G_GNUC_UNUSED GError **error)
{
for(guint i = 0; i < n_params; i++) {
purple_ircv3_capabilities_add(capabilities, params[i]);
}
g_signal_emit(capabilities, signals[SIG_NEW], 0, params);
return TRUE;
}
static gboolean
purple_ircv3_capabilities_handle_del(PurpleIRCv3Capabilities *capabilities,
guint n_params,
GStrv params,
G_GNUC_UNUSED GError **error)
{
for(guint i = 0; i < n_params; i++) {
g_hash_table_remove(capabilities->caps, params[i]);
}
g_signal_emit(capabilities, signals[SIG_DEL], 0, params);
return TRUE;
}
/******************************************************************************
* Internal API
*****************************************************************************/
void
purple_ircv3_capabilities_register(GPluginNativePlugin *plugin) {
purple_ircv3_capabilities_register_type(G_TYPE_MODULE(plugin));
}
PurpleIRCv3Capabilities *
purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection) {
return g_object_new(
PURPLE_IRCV3_TYPE_CAPABILITIES,
"connection", connection,
NULL);
}
void
purple_ircv3_capabilities_start(PurpleIRCv3Capabilities *capabilities) {
purple_ircv3_connection_writef(capabilities->connection, "CAP LS %s",
PURPLE_IRCV3_CAPABILITY_CAP_LS_VERSION);
}
gboolean
purple_ircv3_capabilities_message_handler(PurpleIRCv3Message *message,
GError **error,
gpointer data)
{
PurpleIRCv3Connection *connection = data;
PurpleIRCv3Capabilities *capabilities = NULL;
GStrv params = NULL;
GStrv subparams = NULL;
const char *subcommand = NULL;
guint n_params = 0;
guint n_subparams = 0;
params = purple_ircv3_message_get_params(message);
if(params != NULL) {
n_params = g_strv_length(params);
}
if(n_params < 2) {
return FALSE;
}
capabilities = purple_ircv3_connection_get_capabilities(connection);
/* Initialize some variables to make it easier to call our sub command
* handlers.
*
* params[0] is the nick or * if it hasn't been negotiated yet, we don't
* have a need for this, so we ignore it.
*
* params[1] is the CAP subcommand sent from the server. We use it here
* purely for dispatching to our subcommand handlers.
*
* params[2] and higher are the parameters to the subcommand. To make the
* code a bit easier all around, we subtract 2 from n_params to remove
* references to the nick and subcommand name. Like wise, we add 2 to the
* params GStrv which will now point to the second item in the array again
* ignoring the nick and subcommand.
*/
subcommand = params[1];
n_subparams = n_params - 2;
subparams = params + 2;
/* Dispatch the subcommand. */
if(purple_strequal(subcommand, "LS") ||
purple_strequal(subcommand, "LIST"))
{
return purple_ircv3_capabilities_handle_list(capabilities, n_subparams,
subparams, error);
} else if(purple_strequal(subcommand, "ACK")) {
return purple_ircv3_capabilities_handle_ack_nak(capabilities,
subparams,
signals[SIG_ACK],
"ACK",
error);
} else if(purple_strequal(subcommand, "NAK")) {
return purple_ircv3_capabilities_handle_ack_nak(capabilities,
subparams,
signals[SIG_NAK],
"NAK",
error);
} else if(purple_strequal(subcommand, "NEW")) {
return purple_ircv3_capabilities_handle_new(capabilities, n_subparams,
subparams, error);
} else if(purple_strequal(subcommand, "DEL")) {
return purple_ircv3_capabilities_handle_del(capabilities, n_subparams,
subparams, error);
}
g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
"No handler for CAP subcommand %s", subcommand);
return FALSE;
}
/******************************************************************************
* Public API
*****************************************************************************/
PurpleIRCv3Connection *
purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities)
{
g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL);
return capabilities->connection;
}
void
purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities,
const char *capability)
{
g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
g_return_if_fail(capability != NULL);
g_ptr_array_add(capabilities->requests, g_strdup(capability));
purple_ircv3_connection_writef(capabilities->connection, "CAP REQ :%s",
capability);
}
const char *
purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities,
const char *name, gboolean *found)
{
gpointer value = NULL;
gboolean real_found = FALSE;
g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL);
g_return_val_if_fail(name != NULL, NULL);
real_found = g_hash_table_lookup_extended(capabilities->caps, name, NULL,
&value);
if(found != NULL) {
*found = real_found;
}
return value;
}
gboolean
purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities,
const char *name)
{
gboolean found = FALSE;
g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), FALSE);
g_return_val_if_fail(name != NULL, FALSE);
purple_ircv3_capabilities_lookup(capabilities, name, &found);
if(found) {
purple_ircv3_capabilities_request(capabilities, name);
}
return found;
}
void
purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities) {
g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
g_atomic_ref_count_inc(&capabilities->wait_counters);
}
void
purple_ircv3_capabilities_remove_wait(PurpleIRCv3Capabilities *capabilities) {
g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
if(g_atomic_ref_count_dec(&capabilities->wait_counters)) {
purple_ircv3_capabilities_finish(capabilities);
}
}