1
0
Fork 0
gdm3/chooser/gdm-host-chooser-widget.c
Daniel Baumann 83b37a3d94
Adding upstream version 48.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 19:45:29 +02:00

877 lines
29 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 1998, 1999, 2000 Martin K, Petersen <mkp@mkp.net>
* Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
*
* This program 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 program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#ifdef ENABLE_X11_SUPPORT
#include <X11/Xmd.h>
#include <X11/Xdmcp.h>
#endif
#include <glib.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include <gtk/gtk.h>
#include "gdm-address.h"
#include "gdm-chooser-host.h"
#include "gdm-host-chooser-widget.h"
struct _GdmHostChooserWidget
{
GtkBox parent;
GtkWidget *treeview;
int kind_mask;
char **hosts;
XdmcpBuffer broadcast_buf;
XdmcpBuffer query_buf;
gboolean have_ipv6;
int socket_fd;
guint io_watch_id;
guint scan_time_id;
guint ping_try_id;
int ping_tries;
GSList *broadcast_addresses;
GSList *query_addresses;
GSList *chooser_hosts;
GdmChooserHost *current_host;
};
enum {
PROP_0,
PROP_KIND_MASK,
};
enum {
HOST_ACTIVATED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0, };
static void gdm_host_chooser_widget_class_init (GdmHostChooserWidgetClass *klass);
static void gdm_host_chooser_widget_init (GdmHostChooserWidget *host_chooser_widget);
G_DEFINE_TYPE (GdmHostChooserWidget, gdm_host_chooser_widget, GTK_TYPE_BOX)
#define GDM_XDMCP_PROTOCOL_VERSION 1001
#define SCAN_TIMEOUT 30
#define PING_TIMEOUT 2
#define PING_TRIES 3
enum {
CHOOSER_LIST_ICON_COLUMN = 0,
CHOOSER_LIST_LABEL_COLUMN,
CHOOSER_LIST_HOST_COLUMN
};
static void
chooser_host_add (GdmHostChooserWidget *widget,
GdmChooserHost *host)
{
widget->chooser_hosts = g_slist_prepend (widget->chooser_hosts, host);
}
#if 0
static void
chooser_host_remove (GdmHostChooserWidget *widget,
GdmChooserHost *host)
{
widget->chooser_hosts = g_slist_remove (widget->chooser_hosts, host);
}
#endif
static gboolean
address_hostnames_equal (GdmAddress *address,
GdmAddress *other_address)
{
char *hostname, *other_hostname;
gboolean are_equal;
if (gdm_address_equal (address, other_address)) {
return TRUE;
}
if (!gdm_address_get_hostname (address, &hostname)) {
gdm_address_get_numeric_info (address, &hostname, NULL);
}
if (!gdm_address_get_hostname (other_address, &other_hostname)) {
gdm_address_get_numeric_info (other_address, &other_hostname, NULL);
}
are_equal = g_strcmp0 (hostname, other_hostname) == 0;
g_free (hostname);
g_free (other_hostname);
return are_equal;
}
static GdmChooserHost *
find_known_host (GdmHostChooserWidget *widget,
GdmAddress *address)
{
GSList *li;
GdmChooserHost *host;
for (li = widget->chooser_hosts; li != NULL; li = li->next) {
GdmAddress *other_address;
host = li->data;
other_address = gdm_chooser_host_get_address (host);
if (address_hostnames_equal (address, other_address)) {
goto out;
}
}
host = NULL;
out:
return host;
}
static void
browser_add_host (GdmHostChooserWidget *widget,
GdmChooserHost *host)
{
char *hostname;
char *name;
char *desc;
char *label;
GtkTreeModel *model;
GtkTreeIter iter;
gboolean res;
GtkTreeSelection *selection;
g_assert (host != NULL);
if (! gdm_chooser_host_get_willing (host)) {
gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE);
return;
}
res = gdm_address_get_hostname (gdm_chooser_host_get_address (host), &hostname);
if (! res) {
gdm_address_get_numeric_info (gdm_chooser_host_get_address (host), &hostname, NULL);
}
name = g_markup_escape_text (hostname, -1);
desc = g_markup_escape_text (gdm_chooser_host_get_description (host), -1);
label = g_strdup_printf ("<b>%s</b>\n%s", name, desc);
g_free (name);
g_free (desc);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->treeview));
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model),
&iter,
CHOOSER_LIST_ICON_COLUMN, NULL,
CHOOSER_LIST_LABEL_COLUMN, label,
CHOOSER_LIST_HOST_COLUMN, host,
-1);
g_free (label);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->treeview));
if (!gtk_tree_selection_get_selected (selection, NULL, NULL)) {
gtk_tree_selection_select_iter (selection, &iter);
}
}
static gboolean
decode_packet (GIOChannel *source,
GIOCondition condition,
GdmHostChooserWidget *widget)
{
struct sockaddr_storage clnt_ss;
GdmAddress *address;
int ss_len;
XdmcpHeader header;
int res;
static XdmcpBuffer buf;
ARRAY8 auth = {0};
ARRAY8 host = {0};
ARRAY8 stat = {0};
char *status;
GdmChooserHost *chooser_host;
status = NULL;
address = NULL;
g_debug ("decode_packet: GIOCondition %d", (int)condition);
if ( ! (condition & G_IO_IN)) {
return TRUE;
}
ss_len = (int) sizeof (clnt_ss);
res = XdmcpFill (widget->socket_fd, &buf, (XdmcpNetaddr)&clnt_ss, &ss_len);
if G_UNLIKELY (! res) {
g_debug (_("XDMCP: Could not create XDMCP buffer!"));
return TRUE;
}
res = XdmcpReadHeader (&buf, &header);
if G_UNLIKELY (! res) {
g_warning (_("XDMCP: Could not read XDMCP header!"));
return TRUE;
}
if G_UNLIKELY (header.version != XDM_PROTOCOL_VERSION &&
header.version != GDM_XDMCP_PROTOCOL_VERSION) {
g_warning (_("XDMCP: Incorrect XDMCP version!"));
return TRUE;
}
address = gdm_address_new_from_sockaddr ((struct sockaddr *) &clnt_ss, ss_len);
if (address == NULL) {
g_warning (_("XDMCP: Unable to parse address"));
return TRUE;
}
gdm_address_debug (address);
if (header.opcode == WILLING) {
if (! XdmcpReadARRAY8 (&buf, &auth)) {
goto done;
}
if (! XdmcpReadARRAY8 (&buf, &host)) {
goto done;
}
if (! XdmcpReadARRAY8 (&buf, &stat)) {
goto done;
}
status = g_strndup ((char *) stat.data, MIN (stat.length, 256));
} else if (header.opcode == UNWILLING) {
/* immaterial, will not be shown */
status = NULL;
} else {
goto done;
}
g_debug ("STATUS: %s", status);
chooser_host = find_known_host (widget, address);
if (chooser_host == NULL) {
chooser_host = g_object_new (GDM_TYPE_CHOOSER_HOST,
"address", address,
"description", status,
"willing", (header.opcode == WILLING),
"kind", GDM_CHOOSER_HOST_KIND_XDMCP,
NULL);
chooser_host_add (widget, chooser_host);
browser_add_host (widget, chooser_host);
} else {
/* server changed it's mind */
if (header.opcode == WILLING
&& ! gdm_chooser_host_get_willing (chooser_host)) {
browser_add_host (widget, chooser_host);
g_object_set (chooser_host, "willing", TRUE, NULL);
}
/* FIXME: handle unwilling? */
}
done:
if (header.opcode == WILLING) {
XdmcpDisposeARRAY8 (&auth);
XdmcpDisposeARRAY8 (&host);
XdmcpDisposeARRAY8 (&stat);
}
g_free (status);
gdm_address_free (address);
return TRUE;
}
static void
do_ping (GdmHostChooserWidget *widget,
gboolean full)
{
GSList *l;
g_debug ("do ping full:%d", full);
for (l = widget->broadcast_addresses; l != NULL; l = l->next) {
GdmAddress *address;
int res;
address = l->data;
gdm_address_debug (address);
errno = 0;
g_debug ("fd:%d", widget->socket_fd);
res = XdmcpFlush (widget->socket_fd,
&widget->broadcast_buf,
(XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address),
(int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address)));
if (! res) {
g_warning ("Unable to flush the XDMCP broadcast packet: %s", g_strerror (errno));
}
}
if (full) {
for (l = widget->query_addresses; l != NULL; l = l->next) {
GdmAddress *address;
int res;
address = l->data;
gdm_address_debug (address);
res = XdmcpFlush (widget->socket_fd,
&widget->query_buf,
(XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address),
(int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address)));
if (! res) {
g_warning ("Unable to flush the XDMCP query packet");
}
}
}
}
static gboolean
ping_try (GdmHostChooserWidget *widget)
{
do_ping (widget, FALSE);
widget->ping_tries --;
if (widget->ping_tries <= 0) {
widget->ping_try_id = 0;
return FALSE;
} else {
return TRUE;
}
}
static void
xdmcp_discover (GdmHostChooserWidget *widget)
{
#if 0
gtk_widget_set_sensitive (GTK_WIDGET (manage), FALSE);
gtk_widget_set_sensitive (GTK_WIDGET (rescan), FALSE);
gtk_list_store_clear (GTK_LIST_STORE (browser_model));
gtk_widget_set_sensitive (GTK_WIDGET (browser), FALSE);
gtk_label_set_label (GTK_LABEL (status_label),
_(scanning_message));
while (hl) {
gdm_chooser_host_dispose ((GdmChooserHost *) hl->data);
hl = hl->next;
}
g_list_free (chooser_hosts);
chooser_hosts = NULL;
#endif
do_ping (widget, TRUE);
#if 0
g_clear_handle_id (&widget->scan_time_id, g_source_remove);
widget->scan_time_id = g_timeout_add_seconds (SCAN_TIMEOUT,
chooser_scan_time_update,
widget);
#endif
/* Note we already used up one try */
widget->ping_tries = PING_TRIES - 1;
g_clear_handle_id (&widget->ping_try_id, g_source_remove);
widget->ping_try_id = g_timeout_add_seconds (PING_TIMEOUT,
(GSourceFunc)ping_try,
widget);
}
/* Find broadcast address for all active, non pointopoint interfaces */
static void
find_broadcast_addresses (GdmHostChooserWidget *widget)
{
int i;
int num;
int sock;
struct ifconf ifc;
char *buf;
struct ifreq *ifr;
g_debug ("Finding broadcast addresses");
sock = socket (AF_INET, SOCK_DGRAM, 0);
#ifdef SIOCGIFNUM
if (ioctl (sock, SIOCGIFNUM, &num) < 0) {
num = 64;
}
#else
num = 64;
#endif
ifc.ifc_len = sizeof (struct ifreq) * num;
ifc.ifc_buf = buf = g_malloc0 (ifc.ifc_len);
if (ioctl (sock, SIOCGIFCONF, &ifc) < 0) {
g_warning ("Could not get local addresses!");
goto out;
}
ifr = ifc.ifc_req;
num = ifc.ifc_len / sizeof (struct ifreq);
for (i = 0 ; i < num ; i++) {
const char *name;
name = ifr[i].ifr_name;
g_debug ("Checking if %s", name);
if (name != NULL && name[0] != '\0') {
struct ifreq ifreq;
GdmAddress *address;
struct sockaddr_in sin;
memset (&ifreq, 0, sizeof (ifreq));
strncpy (ifreq.ifr_name,
ifr[i].ifr_name,
sizeof (ifreq.ifr_name));
/* paranoia */
ifreq.ifr_name[sizeof (ifreq.ifr_name) - 1] = '\0';
if ((ioctl (sock, SIOCGIFFLAGS, &ifreq) < 0) && (errno != ENXIO)) {
g_warning ("Could not get SIOCGIFFLAGS for %s", ifr[i].ifr_name);
}
if ((ifreq.ifr_flags & IFF_UP) == 0 ||
(ifreq.ifr_flags & IFF_BROADCAST) == 0 ||
ioctl (sock, SIOCGIFBRDADDR, &ifreq) < 0) {
g_debug ("Skipping if %s", name);
continue;
}
memcpy (&sin, &ifreq.ifr_broadaddr, sizeof (struct sockaddr_in));
sin.sin_port = htons (XDM_UDP_PORT);
address = gdm_address_new_from_sockaddr ((struct sockaddr *) &sin, sizeof (sin));
if (address != NULL) {
g_debug ("Adding if %s", name);
gdm_address_debug (address);
widget->broadcast_addresses = g_slist_append (widget->broadcast_addresses, address);
}
}
}
out:
g_free (buf);
close (sock);
}
static void
add_hosts (GdmHostChooserWidget *widget)
{
int i;
for (i = 0; widget->hosts != NULL && widget->hosts[i] != NULL; i++) {
struct addrinfo hints;
struct addrinfo *result;
struct addrinfo *ai;
int gaierr;
const char *name;
char serv_buf [NI_MAXSERV];
const char *serv;
name = widget->hosts[i];
if (strcmp (name, "BROADCAST") == 0) {
find_broadcast_addresses (widget);
continue;
}
if (strcmp (name, "MULTICAST") == 0) {
/*gdm_chooser_find_mcaddr ();*/
continue;
}
result = NULL;
memset (&hints, 0, sizeof (hints));
hints.ai_socktype = SOCK_STREAM;
snprintf (serv_buf, sizeof (serv_buf), "%u", XDM_UDP_PORT);
serv = serv_buf;
gaierr = getaddrinfo (name, serv, &hints, &result);
if (gaierr != 0) {
g_warning ("Unable to get address info for name %s: %s", name, gai_strerror (gaierr));
continue;
}
for (ai = result; ai != NULL; ai = ai->ai_next) {
GdmAddress *address;
address = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen);
if (address != NULL) {
widget->query_addresses = g_slist_append (widget->query_addresses, address);
}
}
freeaddrinfo (result);
}
if (widget->broadcast_addresses == NULL && widget->query_addresses == NULL) {
find_broadcast_addresses (widget);
}
}
static void
xdmcp_init (GdmHostChooserWidget *widget)
{
XdmcpHeader header;
int sockopts;
int res;
GIOChannel *ioc;
ARRAYofARRAY8 aanames;
sockopts = 1;
widget->socket_fd = -1;
/* Open socket for communication */
#ifdef ENABLE_IPV6
widget->socket_fd = socket (AF_INET6, SOCK_DGRAM, 0);
if (widget->socket_fd != -1) {
widget->have_ipv6 = TRUE;
#ifdef IPV6_V6ONLY
{
int zero = 0;
if (setsockopt(widget->socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) < 0)
g_warning("setsockopt(IPV6_V6ONLY): %s", g_strerror(errno));
}
#endif
}
#endif
if (! widget->have_ipv6) {
widget->socket_fd = socket (AF_INET, SOCK_DGRAM, 0);
if (widget->socket_fd == -1) {
g_critical ("Could not create socket!");
}
}
res = setsockopt (widget->socket_fd,
SOL_SOCKET,
SO_BROADCAST,
(char *) &sockopts,
sizeof (sockopts));
if (res < 0) {
g_critical ("Could not set socket options!");
}
/* Assemble XDMCP BROADCAST_QUERY packet in static buffer */
memset (&header, 0, sizeof (XdmcpHeader));
header.opcode = (CARD16) BROADCAST_QUERY;
header.length = 1;
header.version = XDM_PROTOCOL_VERSION;
aanames.length = 0;
XdmcpWriteHeader (&widget->broadcast_buf, &header);
XdmcpWriteARRAYofARRAY8 (&widget->broadcast_buf, &aanames);
/* Assemble XDMCP QUERY packet in static buffer */
memset (&header, 0, sizeof (XdmcpHeader));
header.opcode = (CARD16) QUERY;
header.length = 1;
header.version = XDM_PROTOCOL_VERSION;
memset (&widget->query_buf, 0, sizeof (XdmcpBuffer));
XdmcpWriteHeader (&widget->query_buf, &header);
XdmcpWriteARRAYofARRAY8 (&widget->query_buf, &aanames);
add_hosts (widget);
ioc = g_io_channel_unix_new (widget->socket_fd);
g_io_channel_set_encoding (ioc, NULL, NULL);
g_io_channel_set_buffered (ioc, FALSE);
widget->io_watch_id = g_io_add_watch (ioc,
G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
(GIOFunc)decode_packet,
widget);
g_io_channel_unref (ioc);
}
void
gdm_host_chooser_widget_refresh (GdmHostChooserWidget *widget)
{
g_return_if_fail (GDM_IS_HOST_CHOOSER_WIDGET (widget));
xdmcp_discover (widget);
}
GdmChooserHost *
gdm_host_chooser_widget_get_host (GdmHostChooserWidget *widget)
{
GdmChooserHost *host;
g_return_val_if_fail (GDM_IS_HOST_CHOOSER_WIDGET (widget), NULL);
host = NULL;
if (widget->current_host != NULL) {
host = g_object_ref (widget->current_host);
}
return host;
}
static void
_gdm_host_chooser_widget_set_kind_mask (GdmHostChooserWidget *widget,
int kind_mask)
{
if (widget->kind_mask != kind_mask) {
widget->kind_mask = kind_mask;
}
}
static void
gdm_host_chooser_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GdmHostChooserWidget *self;
self = GDM_HOST_CHOOSER_WIDGET (object);
switch (prop_id) {
case PROP_KIND_MASK:
_gdm_host_chooser_widget_set_kind_mask (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gdm_host_chooser_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GObject *
gdm_host_chooser_widget_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GdmHostChooserWidget *widget;
widget = GDM_HOST_CHOOSER_WIDGET (G_OBJECT_CLASS (gdm_host_chooser_widget_parent_class)->constructor (type,
n_construct_properties,
construct_properties));
xdmcp_init (widget);
xdmcp_discover (widget);
return G_OBJECT (widget);
}
static void
gdm_host_chooser_widget_dispose (GObject *object)
{
GdmHostChooserWidget *widget;
widget = GDM_HOST_CHOOSER_WIDGET (object);
g_debug ("Disposing host_chooser_widget");
if (widget->broadcast_addresses != NULL) {
g_slist_foreach (widget->broadcast_addresses,
(GFunc)gdm_address_free,
NULL);
g_slist_free (widget->broadcast_addresses);
widget->broadcast_addresses = NULL;
}
if (widget->query_addresses != NULL) {
g_slist_foreach (widget->query_addresses,
(GFunc)gdm_address_free,
NULL);
g_slist_free (widget->query_addresses);
widget->query_addresses = NULL;
}
if (widget->chooser_hosts != NULL) {
g_slist_foreach (widget->chooser_hosts,
(GFunc)g_object_unref,
NULL);
g_slist_free (widget->chooser_hosts);
widget->chooser_hosts = NULL;
}
widget->current_host = NULL;
G_OBJECT_CLASS (gdm_host_chooser_widget_parent_class)->dispose (object);
}
static void
gdm_host_chooser_widget_class_init (GdmHostChooserWidgetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gdm_host_chooser_widget_get_property;
object_class->set_property = gdm_host_chooser_widget_set_property;
object_class->constructor = gdm_host_chooser_widget_constructor;
object_class->dispose = gdm_host_chooser_widget_dispose;
g_object_class_install_property (object_class,
PROP_KIND_MASK,
g_param_spec_int ("kind-mask",
"kind mask",
"kind mask",
0,
G_MAXINT,
0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
signals [HOST_ACTIVATED] = g_signal_new ("host-activated",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
on_host_selected (GtkTreeSelection *selection,
GdmHostChooserWidget *widget)
{
GtkTreeModel *model = NULL;
GtkTreeIter iter = {0};
GdmChooserHost *curhost;
curhost = NULL;
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
gtk_tree_model_get (model, &iter, CHOOSER_LIST_HOST_COLUMN, &curhost, -1);
}
widget->current_host = curhost;
}
static void
on_row_activated (GtkTreeView *tree_view,
GtkTreePath *tree_path,
GtkTreeViewColumn *tree_column,
GdmHostChooserWidget *widget)
{
g_signal_emit (widget, signals[HOST_ACTIVATED], 0);
}
static void
gdm_host_chooser_widget_init (GdmHostChooserWidget *widget)
{
GtkWidget *scrolled;
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkTreeModel *model;
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
GTK_SHADOW_IN);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start (GTK_BOX (widget), scrolled, TRUE, TRUE, 0);
widget->treeview = gtk_tree_view_new ();
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget->treeview), FALSE);
g_signal_connect (widget->treeview,
"row-activated",
G_CALLBACK (on_row_activated),
widget);
gtk_container_add (GTK_CONTAINER (scrolled), widget->treeview);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->treeview));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
g_signal_connect (selection, "changed",
G_CALLBACK (on_host_selected),
widget);
model = (GtkTreeModel *)gtk_list_store_new (3,
GDK_TYPE_PIXBUF,
G_TYPE_STRING,
G_TYPE_POINTER);
gtk_tree_view_set_model (GTK_TREE_VIEW (widget->treeview), model);
column = gtk_tree_view_column_new_with_attributes ("Icon",
gtk_cell_renderer_pixbuf_new (),
"pixbuf", CHOOSER_LIST_ICON_COLUMN,
NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (widget->treeview), column);
column = gtk_tree_view_column_new_with_attributes ("Hostname",
gtk_cell_renderer_text_new (),
"markup", CHOOSER_LIST_LABEL_COLUMN,
NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (widget->treeview), column);
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
CHOOSER_LIST_LABEL_COLUMN,
GTK_SORT_ASCENDING);
}
GtkWidget *
gdm_host_chooser_widget_new (int kind_mask)
{
GObject *object;
object = g_object_new (GDM_TYPE_HOST_CHOOSER_WIDGET,
"orientation", GTK_ORIENTATION_VERTICAL,
"kind-mask", kind_mask,
NULL);
return GTK_WIDGET (object);
}