/* Copyright (C) 2010 David Zeuthen */ #include #include #include /* temporary include */ #include /* see http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and * http://people.freedesktop.org/~david/gdbus-binding-tool-20100902/ */ static gint opt_port = 8080; static gchar *opt_root = NULL; static gchar *opt_name = NULL; static gchar *opt_object_path = NULL; static GDBusProxyManager *proxy_manager = NULL; static GOptionEntry cmd_entries[] = { {"port", 'p', 0, G_OPTION_ARG_INT, &opt_port, "Local port to bind to (8080 if unset)", NULL}, {"root", 'r', 0, G_OPTION_ARG_STRING, &opt_root, "Document root", NULL}, {"name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "D-Bus name to bridge objects from", NULL}, {"object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "D-Bus object path to bridge objects from", NULL}, {NULL} }; static void send_error (GOutputStream *out, gint error_code, const gchar *reason_format, ...) G_GNUC_PRINTF (3, 4); static void send_error (GOutputStream *out, gint error_code, const gchar *reason_format, ...) { gchar *s; va_list var_args; gchar *reason; va_start (var_args, reason_format); reason = g_strdup_vprintf (reason_format, var_args); va_end (var_args); s = g_strdup_printf ("HTTP/1.0 %d %s\r\n" "\r\n" "%d %s" "%s", error_code, reason, error_code, reason, reason); g_output_stream_write_all (out, s, strlen (s), NULL, NULL, NULL); g_free (s); g_debug ("sending error: %d %s", error_code, reason); g_free (reason); } static guint32 val_from_key (const gchar *key) { gint64 sum; guint num_spaces; guint32 ret; guint n; sum = 0; num_spaces = 0; for (n = 0; key[n] != '\0'; n++) { gint c = key[n]; if (g_ascii_isdigit (c)) { sum *= 10; sum += c - '0'; } else if (c == ' ') { num_spaces++; } } if (num_spaces > 0) { ret = sum / num_spaces; } else { g_debug ("key `%s' has no spaces", key); ret = 1; } return ret; } static JsonBuilder * _json_builder_add_gvariant (JsonBuilder *builder, GVariant *value) { g_return_val_if_fail (JSON_IS_BUILDER (builder), builder); g_variant_ref_sink (value); switch (g_variant_classify (value)) { case G_VARIANT_CLASS_BOOLEAN: json_builder_add_boolean_value (builder, g_variant_get_boolean (value)); break; case G_VARIANT_CLASS_BYTE: json_builder_add_int_value (builder, g_variant_get_byte (value)); break; case G_VARIANT_CLASS_INT16: json_builder_add_int_value (builder, g_variant_get_int16 (value)); break; case G_VARIANT_CLASS_UINT16: json_builder_add_int_value (builder, g_variant_get_uint16 (value)); break; case G_VARIANT_CLASS_INT32: json_builder_add_int_value (builder, g_variant_get_int32 (value)); break; case G_VARIANT_CLASS_UINT32: json_builder_add_int_value (builder, g_variant_get_uint32 (value)); break; case G_VARIANT_CLASS_INT64: json_builder_add_int_value (builder, g_variant_get_int64 (value)); break; case G_VARIANT_CLASS_UINT64: json_builder_add_int_value (builder, g_variant_get_uint64 (value)); break; case G_VARIANT_CLASS_HANDLE: json_builder_add_int_value (builder, g_variant_get_handle (value)); break; case G_VARIANT_CLASS_DOUBLE: json_builder_add_double_value (builder, g_variant_get_double (value)); break; case G_VARIANT_CLASS_STRING: /* explicit fall-through */ case G_VARIANT_CLASS_OBJECT_PATH: /* explicit fall-through */ case G_VARIANT_CLASS_SIGNATURE: json_builder_add_string_value (builder, g_variant_get_string (value, NULL)); break; /* TODO: */ case G_VARIANT_CLASS_VARIANT: { GVariant *child; child = g_variant_get_variant (value); _json_builder_add_gvariant (builder, child); g_variant_unref (child); } break; case G_VARIANT_CLASS_MAYBE: g_assert_not_reached (); break; case G_VARIANT_CLASS_ARRAY: if (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING)) { /* TODO: Ensure it is UTF-8 */ json_builder_add_string_value (builder, g_variant_get_bytestring (value)); } else { const GVariantType *type; const GVariantType *element_type; type = g_variant_get_type (value); element_type = g_variant_type_element (type); if (g_variant_type_is_dict_entry (element_type)) { GVariantIter iter; GVariant *child; json_builder_begin_object (builder); g_variant_iter_init (&iter, value); while ((child = g_variant_iter_next_value (&iter)) != NULL) { _json_builder_add_gvariant (builder, child); g_variant_unref (child); } json_builder_end_object (builder); } else { GVariantIter iter; GVariant *child; json_builder_begin_array (builder); g_variant_iter_init (&iter, value); while ((child = g_variant_iter_next_value (&iter)) != NULL) { _json_builder_add_gvariant (builder, child); g_variant_unref (child); } json_builder_end_array (builder); } } break; case G_VARIANT_CLASS_TUPLE: g_assert_not_reached (); break; case G_VARIANT_CLASS_DICT_ENTRY: { GVariant *dict_key; GVariant *dict_value; gchar *dict_key_string; dict_key = g_variant_get_child_value (value, 0); dict_value = g_variant_get_child_value (value, 1); dict_key_string = g_variant_print (dict_key, FALSE); json_builder_set_member_name (builder, dict_key_string); _json_builder_add_gvariant (builder, dict_value); g_free (dict_key_string); g_variant_unref (dict_key); g_variant_unref (dict_value); } break; } g_variant_unref (value); return builder; } static gchar * _json_builder_to_str (JsonBuilder *builder) { JsonGenerator *generator; JsonNode *root; gchar *ret; generator = json_generator_new (); root = json_builder_get_root (builder); json_generator_set_root (generator, root); ret = json_generator_to_data (generator, NULL); json_node_free (root); g_object_unref (generator); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static void add_interface (JsonBuilder *builder, GDBusProxy *interface) { gchar **properties; guint n; json_builder_set_member_name (builder, g_dbus_proxy_get_interface_name (interface)); json_builder_begin_object (builder); properties = g_dbus_proxy_get_cached_property_names (interface); for (n = 0; properties != NULL && properties[n] != NULL; n++) { const gchar *property_name = properties[n]; GVariant *value; value = g_dbus_proxy_get_cached_property (interface, property_name); if (value != NULL) { json_builder_set_member_name (builder, property_name); _json_builder_add_gvariant (builder, value); g_variant_unref (value); } } g_strfreev (properties); if (properties == NULL) { json_builder_set_member_name (builder, "HackEmpty"); json_builder_add_string_value (builder, "HackEmpty"); } json_builder_end_object (builder); } static void add_object (JsonBuilder *builder, GDBusObjectProxy *object) { GList *interfaces; GList *l; json_builder_set_member_name (builder, "path"); json_builder_add_string_value (builder, g_dbus_object_proxy_get_object_path (object)); json_builder_set_member_name (builder, "interfaces"); json_builder_begin_object (builder); interfaces = g_dbus_object_proxy_get_all (object); for (l = interfaces; l != NULL; l = l->next) { GDBusProxy *interface = G_DBUS_PROXY (l->data); add_interface (builder, interface); } g_list_foreach (interfaces, (GFunc) g_object_unref, NULL); g_list_free (interfaces); json_builder_end_object (builder); } static gchar * get_all_objects (void) { GList *l; GList *objects; gchar *ret; JsonBuilder *builder; builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "command"); json_builder_add_string_value (builder, "seed"); json_builder_set_member_name (builder, "data"); json_builder_begin_object (builder); objects = g_dbus_proxy_manager_get_all (proxy_manager); for (l = objects; l != NULL; l = l->next) { GDBusObjectProxy *object = G_DBUS_OBJECT_PROXY (l->data); json_builder_set_member_name (builder, g_dbus_object_proxy_get_object_path (object)); json_builder_begin_object (builder); add_object (builder, object); json_builder_end_object (builder); } g_list_foreach (objects, (GFunc) g_object_unref, NULL); g_list_free (objects); json_builder_end_object (builder); json_builder_end_object (builder); ret = _json_builder_to_str (builder); g_object_unref (builder); return ret; } /* ---------------------------------------------------------------------------------------------------- */ G_LOCK_DEFINE_STATIC (client_list_lock); static GList *client_list = NULL; typedef struct { GMutex *write_lock; GDataOutputStream *out; } WebSocketClient; /* ---------------------------------------------------------------------------------------------------- */ static gboolean client_write (WebSocketClient *client, const gchar *str) { gboolean ret; ret = FALSE; g_mutex_lock (client->write_lock); if (!g_data_output_stream_put_byte (client->out, 0x00, NULL, NULL)) goto out; if (!g_data_output_stream_put_string (client->out, str, NULL, NULL)) goto out; if (!g_data_output_stream_put_byte (client->out, 0xff, NULL, NULL)) goto out; ret = TRUE; out: g_mutex_unlock (client->write_lock); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static void client_write_all (const gchar *str) { GList *l; G_LOCK (client_list_lock); for (l = client_list; l != NULL; l = l->next) { WebSocketClient *client = l->data; client_write (client, str); } G_UNLOCK (client_list_lock); } /* ---------------------------------------------------------------------------------------------------- */ static JsonBuilder * prepare_builder (const gchar *command) { JsonBuilder *builder; builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "command"); json_builder_add_string_value (builder, command); json_builder_set_member_name (builder, "data"); return builder; } static JsonBuilder * write_and_free_builder (JsonBuilder *builder) { gchar *s; json_builder_end_object (builder); s = _json_builder_to_str (builder); client_write_all (s); g_object_unref (builder); g_free (s); return builder; } /* ---------------------------------------------------------------------------------------------------- */ static void on_object_proxy_added (GDBusProxyManager *manager, GDBusObjectProxy *object_proxy, gpointer user_data) { JsonBuilder *builder; builder = prepare_builder ("object-proxy-added"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "object"); json_builder_begin_object (builder); add_object (builder, object_proxy); json_builder_end_object (builder); json_builder_end_object (builder); write_and_free_builder (builder); } static void on_object_proxy_removed (GDBusProxyManager *manager, GDBusObjectProxy *object_proxy, gpointer user_data) { JsonBuilder *builder; builder = prepare_builder ("object-proxy-removed"); json_builder_begin_array (builder); json_builder_add_string_value (builder, g_dbus_object_proxy_get_object_path (object_proxy)); json_builder_end_array (builder); write_and_free_builder (builder); } /* ---------------------------------------------------------------------------------------------------- */ static void on_interface_proxy_added (GDBusProxyManager *manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, gpointer user_data) { JsonBuilder *builder; builder = prepare_builder ("interface-proxy-added"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "path"); json_builder_add_string_value (builder, g_dbus_object_proxy_get_object_path (object_proxy)); json_builder_set_member_name (builder, "iface_name"); json_builder_add_string_value (builder, g_dbus_proxy_get_interface_name (interface_proxy)); json_builder_set_member_name (builder, "iface"); json_builder_begin_object (builder); add_interface (builder, interface_proxy); json_builder_end_object (builder); json_builder_end_object (builder); write_and_free_builder (builder); } static void on_interface_proxy_removed (GDBusProxyManager *manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, gpointer user_data) { JsonBuilder *builder; builder = prepare_builder ("interface-proxy-removed"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "path"); json_builder_add_string_value (builder, g_dbus_object_proxy_get_object_path (object_proxy)); json_builder_set_member_name (builder, "iface_name"); json_builder_add_string_value (builder, g_dbus_proxy_get_interface_name (interface_proxy)); json_builder_end_object (builder); write_and_free_builder (builder); } static void on_interface_proxy_properties_changed (GDBusProxyManager *manager, GDBusObjectProxy *object_proxy, GDBusProxy *interface_proxy, GVariant *changed_properties, const gchar *const *invalidated_properties, gpointer user_data) { JsonBuilder *builder; builder = prepare_builder ("interface-proxy-properties-changed"); json_builder_begin_object (builder); json_builder_set_member_name (builder, "path"); json_builder_add_string_value (builder, g_dbus_object_proxy_get_object_path (object_proxy)); json_builder_set_member_name (builder, "iface_name"); json_builder_add_string_value (builder, g_dbus_proxy_get_interface_name (interface_proxy)); /* It's a bit of a waste to send all properties - would be cheaper to just * send @changed_properties and @invalidated_properties. But this is simpler. */ json_builder_set_member_name (builder, "iface"); json_builder_begin_object (builder); add_interface (builder, interface_proxy); json_builder_end_object (builder); json_builder_end_object (builder); write_and_free_builder (builder); } /* ---------------------------------------------------------------------------------------------------- */ static void websocket_event_loop (const gchar *escaped_resource, GHashTable *headers, GDataInputStream *in, GDataOutputStream *out) { gchar *s; WebSocketClient *client; g_debug ("servicing WebSocket for resource %s", escaped_resource); /* OK, we have an active Web Socket session */ client = g_new0 (WebSocketClient, 1); client->out = out; client->write_lock = g_mutex_new (); G_LOCK (client_list_lock); client_list = g_list_prepend (client_list, client); G_UNLOCK (client_list_lock); /* Start by sending all objects */ s = get_all_objects (); if (!client_write (client, s)) { g_free (s); goto out; } g_free (s); /* Then sit and listen for commands */ do { gint byte; gchar *str; gsize str_len; byte = g_data_input_stream_read_byte (in, NULL, NULL); if (byte != 0x00) { send_error (G_OUTPUT_STREAM (out), 500, "Expected byte 0x00, read 0x%02x instead", byte); goto out; } str = g_data_input_stream_read_upto (in, "\xff", -1, &str_len, NULL, NULL); if (str == NULL) goto out; byte = g_data_input_stream_read_byte (in, NULL, NULL); if (byte != 0xff) { send_error (G_OUTPUT_STREAM (out), 500, "Expected byte 0xff, read 0x%02x instead", byte); goto out; } g_debug ("read `%s'", str); g_free (str); } while (TRUE); out: G_LOCK (client_list_lock); client_list = g_list_remove (client_list, client); G_UNLOCK (client_list_lock); g_mutex_free (client->write_lock); g_free (client); g_debug ("Done servicing WebSocket"); } static void handle_websocket (const gchar *escaped_resource, GHashTable *headers, GDataInputStream *in, GDataOutputStream *out) { const gchar *key1; const gchar *key2; const gchar *protocol; gssize num_read; guchar data[4 + 4 + 8 + 1]; guchar md5[16 + 1]; gsize md5_len; guint32 val1; guint32 val2; GChecksum *checksum; GString *str; memset (data, '\0', sizeof (data)); memset (md5, '\0', sizeof (md5)); key1 = g_hash_table_lookup (headers, "Sec-WebSocket-Key1"); key2 = g_hash_table_lookup (headers, "Sec-WebSocket-Key2"); protocol = g_hash_table_lookup (headers, "Sec-WebSocket-Protocol"); g_debug ("key1=`%s'", key1); g_debug ("key2=`%s'", key2); g_debug ("protocol=`%s'", protocol); num_read = g_input_stream_read (G_INPUT_STREAM (in), data + 4 + 4, 8, NULL, NULL); if (num_read != 8) { send_error (G_OUTPUT_STREAM (out), 500, "Expected 8 bytes following headers, read %" G_GSSIZE_FORMAT " instead", num_read); goto out; } val1 = val_from_key (key1); val2 = val_from_key (key2); g_debug ("val1 = %d", val1); g_debug ("val2 = %d", val2); ((guint32 *) data)[0] = GINT32_TO_BE (val1); ((guint32 *) data)[1] = GINT32_TO_BE (val2); #if 0 g_print ("data: "); for (n = 0; n < 16; n++) g_print ("\\x%02x", data[n]); g_print (" `%s'\n", data); #endif checksum = g_checksum_new (G_CHECKSUM_MD5); g_checksum_update (checksum, data, 16); md5_len = 16; g_checksum_get_digest (checksum, md5, &md5_len); g_assert_cmpint (md5_len, ==, 16); g_checksum_free (checksum); #if 0 g_print ("md5: "); for (n = 0; n < 16; n++) g_print ("\\x%02x", md5[n]); g_print (" `%s'\n", md5); #endif /* TODO: bail if Origin is missing */ const gchar *origin; gchar *origin_host; origin = g_hash_table_lookup (headers, "Origin"); if (origin == NULL) { g_warning ("No origin header"); origin_host = g_strdup ("127.0.0.1:8080"); } if (g_str_has_prefix (origin, "http://")) { origin_host = g_strdup (origin + 7); } else { g_warning ("malformed/unexpected origin %s", origin); origin_host = g_strdup ("127.0.0.1:8080"); } str = g_string_new ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" "Connection: Upgrade\r\n"); g_string_append_printf (str, "Sec-WebSocket-Origin: http://%s\r\n" "Sec-WebSocket-Location: ws://%s/udisks\r\n" "\r\n", origin_host, origin_host); g_free (origin_host); g_string_append_len (str, (const gchar*) md5, 16); if (!g_output_stream_write_all (G_OUTPUT_STREAM (out), str->str, str->len, NULL, NULL, NULL)) { g_string_free (str, TRUE); goto out; } g_string_free (str, TRUE); websocket_event_loop (escaped_resource, headers, in, out); out: ; } /* Runs in dedicated thread - may do blocking IO */ static gboolean on_run (GThreadedSocketService *service, GSocketConnection *connection, GSocketListener *listener, gpointer user_data) { GOutputStream *out; GInputStream *in; GFileInputStream *file_in; GDataInputStream *data; GDataOutputStream *out_data; gchar *line; const gchar *escaped; gchar *tmp; gchar *query; gchar *unescaped; gchar *path; gchar *version; GFile *f; GError *error; GFileInfo *info; GString *str; GHashTable *headers; headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); in = g_io_stream_get_input_stream (G_IO_STREAM (connection)); out = g_io_stream_get_output_stream (G_IO_STREAM (connection)); data = g_data_input_stream_new (in); g_data_input_stream_set_byte_order (data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN); out_data = g_data_output_stream_new (out); g_data_output_stream_set_byte_order (out_data, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN); /* Be tolerant of input */ g_data_input_stream_set_newline_type (data, G_DATA_STREAM_NEWLINE_TYPE_ANY); /* First read the request line */ line = g_data_input_stream_read_line (data, NULL, NULL, NULL); if (line == NULL) { send_error (out, 400, "Invalid request"); goto out; } /* Then each header */ do { gchar *header_line; gchar *key; gchar *value; header_line = g_data_input_stream_read_line (data, NULL, NULL, NULL); if (header_line == NULL) { send_error (out, 400, "Invalid request"); goto out; } if (strlen (header_line) == 0) { g_free (header_line); break; } tmp = strstr (header_line, ": "); if (tmp == NULL) { send_error (out, 400, "Malformed header"); g_free (header_line); goto out; } key = g_strndup (header_line, tmp - header_line); value = g_strdup (tmp + 2); g_strstrip (key); g_debug ("header: %s -> `%s' -> `%s'", header_line, key, value); g_hash_table_insert (headers, key, value); g_free (header_line); } while (TRUE); if (!g_str_has_prefix (line, "GET ")) { send_error (out, 501, "Only GET implemented"); goto out; } escaped = line + 4; /* Skip "GET " */ version = NULL; tmp = strchr (escaped, ' '); if (tmp != NULL) { *tmp = 0; version = tmp + 1; } if (g_strcmp0 (g_hash_table_lookup (headers, "Upgrade"), "WebSocket") == 0 && g_strcmp0 (g_hash_table_lookup (headers, "Connection"), "Upgrade") == 0) { handle_websocket (escaped, headers, data, out_data); goto out; } query = strchr (escaped, '?'); if (query != NULL) *query++ = 0; again: unescaped = g_uri_unescape_string (escaped, NULL); path = g_build_filename (opt_root, unescaped, NULL); g_free (unescaped); f = g_file_new_for_path (path); g_free (path); error = NULL; file_in = g_file_read (f, NULL, &error); if (file_in == NULL) { if (g_strcmp0 (escaped, "/") == 0 && error->domain == G_IO_ERROR && (error->code == G_IO_ERROR_NOT_FOUND || error->code == G_IO_ERROR_IS_DIRECTORY)) { escaped = "/index.html"; goto again; } send_error (out, 404, "%s", error->message); g_error_free (error); goto out; } str = g_string_new ("HTTP/1.0 200 OK\r\n"); info = g_file_input_stream_query_info (file_in, G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, NULL, NULL); if (info != NULL) { const gchar *content_type; if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) { g_string_append_printf (str, "Content-Length: %" G_GINT64_FORMAT "\r\n", g_file_info_get_size (info)); } content_type = g_file_info_get_content_type (info); if (content_type != NULL) { gchar *mime_type; mime_type = g_content_type_get_mime_type (content_type); if (mime_type != NULL) { g_string_append_printf (str, "Content-Type: %s\r\n", mime_type); g_free (mime_type); } } } g_string_append (str, "\r\n"); if (g_output_stream_write_all (out, str->str, str->len, NULL, NULL, NULL)) { g_output_stream_splice (out, G_INPUT_STREAM (file_in), 0, NULL, NULL); } g_string_free (str, TRUE); g_input_stream_close (G_INPUT_STREAM (file_in), NULL, NULL); g_object_unref (file_in); out: g_object_unref (data); g_object_unref (out_data); g_hash_table_unref (headers); return TRUE; } int main (int argc, char *argv[]) { GSocketService *service; GOptionContext *context; GMainLoop *loop; GError *error; gint ret; ret = 1; g_type_init (); g_thread_init (NULL); context = g_option_context_new (NULL); g_option_context_add_main_entries (context, cmd_entries, NULL); error = NULL; if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("%s: Error parsing options: %s (%s, %d)\n", argv[0], error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } if (opt_root == NULL) { g_printerr ("%s: Document root not set (use option --root)\n", argv[0]); goto out; } if (opt_name == NULL) { g_printerr ("%s: D-Bus name not set (use option --name)\n", argv[0]); goto out; } if (opt_object_path == NULL) { g_printerr ("%s: Object path not set (use option --object-path)\n", argv[0]); goto out; } error = NULL; proxy_manager = g_dbus_proxy_manager_new_for_bus_sync (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_MANAGER_FLAGS_NONE, opt_name, opt_object_path, NULL, /* GDBusProxyTypeFunc */ NULL, /* user data for GDBusProxyTypeFunc */ NULL, /* GCancellable */ &error); if (proxy_manager == NULL) { g_printerr ("%s: Error constructing proxy manager: %s (%s, %d)\n", argv[0], error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); goto out; } g_signal_connect (proxy_manager, "object-proxy-added", G_CALLBACK (on_object_proxy_added), NULL); g_signal_connect (proxy_manager, "object-proxy-removed", G_CALLBACK (on_object_proxy_removed), NULL); g_signal_connect (proxy_manager, "interface-proxy-added", G_CALLBACK (on_interface_proxy_added), NULL); g_signal_connect (proxy_manager, "interface-proxy-removed", G_CALLBACK (on_interface_proxy_removed), NULL); g_signal_connect (proxy_manager, "interface-proxy-properties-changed", G_CALLBACK (on_interface_proxy_properties_changed), NULL); #if 0 g_signal_connect (proxy_manager, "interface-proxy-signal", G_CALLBACK (on_interface_proxy_signal), NULL); #endif service = g_threaded_socket_service_new (10); error = NULL; if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), opt_port, NULL, &error)) { g_printerr ("%s: Error binding to port %d: %s\n", argv[0], opt_port, error->message); g_error_free (error); goto out; } g_print ("Server listening on port %d, serving static files from %s\n" "and proxying D-Bus objects from %s owned\n" "by %s\n", opt_port, opt_root, opt_object_path, opt_name); g_signal_connect (service, "run", G_CALLBACK (on_run), NULL); loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); ret = 0; out: return ret; }