summary refs log tree commit diff stats
path: root/src/common/dbus/example.c
blob: 1d072785e25fd221f0b71ea17f1a7210a96fa773 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* example.c - program to demonstrate some D-BUS stuffs.
 * Copyright (C) 2006 Claessens Xavier
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Claessens Xavier
 * xclaesse@gmail.com
 */

#include <config.h>
#include <dbus/dbus-glib.h>
#include <stdlib.h>
#include "marshallers.h"

#define DBUS_SERVICE "org.xchat.service"
#define DBUS_REMOTE "/org/xchat/Remote"
#define DBUS_REMOTE_CONNECTION_INTERFACE "org.xchat.connection"
#define DBUS_REMOTE_PLUGIN_INTERFACE "org.xchat.plugin"

guint command_id;
guint server_id;

static void
write_error (char *message,
	     GError **error)
{
	if (error == NULL || *error == NULL) {
		return;
	}
	g_printerr ("%s: %s\n", message, (*error)->message);
	g_clear_error (error);
}

static void
test_server_cb (DBusGProxy *proxy,
		char *word[],
		char *word_eol[],
		guint hook_id,
		guint context_id,
		gpointer user_data)
{
	if (hook_id == server_id) {
		g_print ("message: %s\n", word_eol[0]);
	}
}

static void
test_command_cb (DBusGProxy *proxy,
		 char *word[],
		 char *word_eol[],
		 guint hook_id,
		 guint context_id,
		 gpointer user_data)
{
	GError *error = NULL;

	if (hook_id == command_id) {
		if (!dbus_g_proxy_call (proxy, "Unhook",
					&error,
					G_TYPE_UINT, hook_id,
					G_TYPE_INVALID, G_TYPE_INVALID)) {
			write_error ("Failed to complete unhook", &error);
		}
		/* Now if you write "/test blah" again in the xchat window
		 * you'll get a "Unknown command" error message */
		g_print ("test command received: %s\n", word_eol[1]);
		if (!dbus_g_proxy_call (proxy, "Print",
					&error,
					G_TYPE_STRING, "test command succeed",
					G_TYPE_INVALID,
					G_TYPE_INVALID)) {
			write_error ("Failed to complete Print", &error);
		}
	}
}

static void
unload_cb (void)
{
	g_print ("Good bye !\n");
	exit (EXIT_SUCCESS);
}

int
main (int argc, char **argv)
{
	DBusGConnection *connection;
	DBusGProxy *remote_object;
	GMainLoop *mainloop;
	gchar *path;
	GError *error = NULL;

	g_type_init ();

	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
	if (connection == NULL) {
		write_error ("Couldn't connect to session bus", &error);
		return EXIT_FAILURE;
	}
  
	remote_object = dbus_g_proxy_new_for_name (connection,
						   DBUS_SERVICE,
						   DBUS_REMOTE,
						   DBUS_REMOTE_CONNECTION_INTERFACE);
	if (!dbus_g_proxy_call (remote_object, "Connect",
				&error,
				G_TYPE_STRING, argv[0],
				G_TYPE_STRING, "example",
				G_TYPE_STRING, "Example of a D-Bus client",
				G_TYPE_STRING, "1.0",
				G_TYPE_INVALID,
				G_TYPE_STRING, &path, G_TYPE_INVALID)) {
		write_error ("Failed to complete Connect", &error);
		return EXIT_FAILURE;
	}
	g_object_unref (remote_object);

	remote_object = dbus_g_proxy_new_for_name (connection,
						   DBUS_SERVICE,
						   path,
						   DBUS_REMOTE_PLUGIN_INTERFACE);
	g_free (path);

	if (!dbus_g_proxy_call (remote_object, "HookCommand",
				&error,
				G_TYPE_STRING, "test",
				G_TYPE_INT, 0,
				G_TYPE_STRING, "Simple D-BUS example",
				G_TYPE_INT, 1, G_TYPE_INVALID,
				G_TYPE_UINT, &command_id, G_TYPE_INVALID)) {
		write_error ("Failed to complete HookCommand", &error);
		return EXIT_FAILURE;
	}
	g_print ("Command hook id=%d\n", command_id);

	if (!dbus_g_proxy_call (remote_object, "HookServer",
				&error,
				G_TYPE_STRING, "RAW LINE",
				G_TYPE_INT, 0,
				G_TYPE_INT, 0, G_TYPE_INVALID,
				G_TYPE_UINT, &server_id, G_TYPE_INVALID)) {
		write_error ("Failed to complete HookServer", &error);
		return EXIT_FAILURE;
	}
	g_print ("Server hook id=%d\n", server_id);

	dbus_g_object_register_marshaller (
		g_cclosure_user_marshal_VOID__POINTER_POINTER_UINT_UINT,
		G_TYPE_NONE,
		G_TYPE_STRV, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_UINT,
		G_TYPE_INVALID);

	dbus_g_object_register_marshaller (
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE,
		G_TYPE_INVALID);

	dbus_g_proxy_add_signal (remote_object, "CommandSignal",
				 G_TYPE_STRV,
				 G_TYPE_STRV,
				 G_TYPE_UINT,
				 G_TYPE_UINT,
				 G_TYPE_INVALID);
	dbus_g_proxy_connect_signal (remote_object, "CommandSignal",
				     G_CALLBACK (test_command_cb),
				     NULL, NULL);

	dbus_g_proxy_add_signal (remote_object, "ServerSignal",
				 G_TYPE_STRV,
				 G_TYPE_STRV,
				 G_TYPE_UINT,
				 G_TYPE_UINT,
				 G_TYPE_INVALID);
	dbus_g_proxy_connect_signal (remote_object, "ServerSignal",
				     G_CALLBACK (test_server_cb),
				     NULL, NULL);

	dbus_g_proxy_add_signal (remote_object, "UnloadSignal",
				 G_TYPE_INVALID);
	dbus_g_proxy_connect_signal (remote_object, "UnloadSignal",
				     G_CALLBACK (unload_cb),
				     NULL, NULL);

	/* Now you can write on the xchat windows: "/test arg1 arg2 ..." */
	mainloop = g_main_loop_new (NULL, FALSE);
	g_main_loop_run (mainloop);

	return EXIT_SUCCESS;
}
span>defined $description; if( $callback ) { $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); } $pkg_info->{shutdown} = $callback; unless( $name && $name =~ /[[:print:]\w]/ ) { $name = "Not supplied"; } unless( $version && $version =~ /\d+(?:\.\d+)?/ ) { $version = "NaN"; } $pkg_info->{gui_entry} = HexChat::Internal::register( $name, $version, $description, $filename ); # keep with old behavior return (); } sub _process_hook_options { my ($options, $keys, $store) = @_; unless( @$keys == @$store ) { die 'Number of keys must match the size of the store'; } my @results; if( ref( $options ) eq 'HASH' ) { for my $index ( 0 .. @$keys - 1 ) { my $key = $keys->[$index]; if( exists( $options->{ $key } ) && defined( $options->{ $key } ) ) { ${$store->[$index]} = $options->{ $key }; } } } } sub hook_server { return undef unless @_ >= 2; my $message = shift; my $callback = shift; my $options = shift; my ($package, $calling_package) = HexChat::Embed::find_pkg(); $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); my ($priority, $data) = ( HexChat::PRI_NORM, undef ); _process_hook_options( $options, [qw(priority data)], [\($priority, $data)], ); my $pkg_info = HexChat::Embed::pkg_info( $package ); my $hook = HexChat::Internal::hook_server( $message, $priority, $callback, $data, $package ); push @{$pkg_info->{hooks}}, $hook if defined $hook; return $hook; } sub hook_command { return undef unless @_ >= 2; my $command = shift; my $callback = shift; my $options = shift; my ($package, $calling_package) = HexChat::Embed::find_pkg(); $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); my ($priority, $help_text, $data) = ( HexChat::PRI_NORM, undef, undef ); _process_hook_options( $options, [qw(priority help_text data)], [\($priority, $help_text, $data)], ); my $pkg_info = HexChat::Embed::pkg_info( $package ); my $hook = HexChat::Internal::hook_command( $command, $priority, $callback, $help_text, $data, $package ); push @{$pkg_info->{hooks}}, $hook if defined $hook; return $hook; } sub hook_print { return undef unless @_ >= 2; my $event = shift; my $callback = shift; my $options = shift; my ($package, $calling_package) = HexChat::Embed::find_pkg(); $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); my ($priority, $run_after, $filter, $data) = ( HexChat::PRI_NORM, 0, 0, undef ); _process_hook_options( $options, [qw(priority run_after_event filter data)], [\($priority, $run_after, $filter, $data)], ); if( $run_after and $filter ) { Carp::carp( "HexChat::hook_print's run_after_event and filter options are mutually exclusive, you can only use of them at a time per hook" ); return; } if( $run_after ) { my $cb = $callback; $callback = sub { my @args = @_; hook_timer( 0, sub { $cb->( @args ); if( ref $run_after eq 'CODE' ) { $run_after->( @args ); } return REMOVE; }); return EAT_NONE; }; } if( $filter ) { my $cb = $callback; $callback = sub { my @args = @{$_[0]}; my $event_data = $_[1]; my $event_name = $event; my $last_arg = @args - 1; my @new = $cb->( \@args, $event_data, $event_name ); # allow changing event by returning the new value if( @new > @args ) { $event_name = pop @new; } # a filter can either return the new results or it can modify # @_ in place. if( @new == @args ) { emit_print( $event_name, @new[ 0 .. $last_arg ] ); return EAT_ALL; } elsif( join( "\0", @{$_[0]} ) ne join( "\0", @args[ 0 .. $last_arg ] ) ) { emit_print( $event_name, @args[ 0 .. $last_arg ] ); return EAT_ALL; } return EAT_NONE; }; } my $pkg_info = HexChat::Embed::pkg_info( $package ); my $hook = HexChat::Internal::hook_print( $event, $priority, $callback, $data, $package ); push @{$pkg_info->{hooks}}, $hook if defined $hook; return $hook; } sub hook_timer { return undef unless @_ >= 2; my ($timeout, $callback, $data) = @_; my ($package, $calling_package) = HexChat::Embed::find_pkg(); $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); if( ref( $data ) eq 'HASH' && exists( $data->{data} ) && defined( $data->{data} ) ) { $data = $data->{data}; } my $pkg_info = HexChat::Embed::pkg_info( $package ); my $hook = HexChat::Internal::hook_timer( $timeout, $callback, $data, $package ); push @{$pkg_info->{hooks}}, $hook if defined $hook; return $hook; } sub hook_fd { return undef unless @_ >= 2; my ($fd, $callback, $options) = @_; return undef unless defined $fd && defined $callback; my $fileno = fileno $fd; return undef unless defined $fileno; # no underlying fd for this handle my ($package, $calling_package) = HexChat::Embed::find_pkg(); $callback = HexChat::Embed::fix_callback( $package, $calling_package, $callback ); my ($flags, $data) = (HexChat::FD_READ, undef); _process_hook_options( $options, [qw(flags data)], [\($flags, $data)], ); my $cb = sub { my $userdata = shift; return $userdata->{CB}->( $userdata->{FD}, $userdata->{FLAGS}, $userdata->{DATA}, ); }; my $pkg_info = HexChat::Embed::pkg_info( $package ); my $hook = HexChat::Internal::hook_fd( $fileno, $cb, $flags, { DATA => $data, FD => $fd, CB => $callback, FLAGS => $flags, }, $package ); push @{$pkg_info->{hooks}}, $hook if defined $hook; return $hook; } sub unhook { my $hook = shift @_; my $package = shift @_; ($package) = caller unless $package; my $pkg_info = HexChat::Embed::pkg_info( $package ); if( defined( $hook ) && $hook =~ /^\d+$/ && grep { $_ == $hook } @{$pkg_info->{hooks}} ) { $pkg_info->{hooks} = [grep { $_ != $hook } @{$pkg_info->{hooks}}]; return HexChat::Internal::unhook( $hook ); } return (); } sub _do_for_each { my ($cb, $channels, $servers) = @_; # not specifying any channels or servers is not the same as specifying # undef for both # - not specifying either results in calling the callback inthe current ctx # - specifying undef for for both results in calling the callback in the # front/currently selected tab if( @_ == 3 && !($channels || $servers) ) { $channels = [ undef ]; $servers = [ undef ]; } elsif( !($channels || $servers) ) { $cb->(); return 1; } $channels = [ $channels ] unless ref( $channels ) eq 'ARRAY'; if( $servers ) { $servers = [ $servers ] unless ref( $servers ) eq 'ARRAY'; } else { $servers = [ undef ]; } my $num_done = 0; my $old_ctx = HexChat::get_context(); for my $server ( @$servers ) { for my $channel ( @$channels ) { if( HexChat::set_context( $channel, $server ) ) { $cb->(); $num_done++ } } } HexChat::set_context( $old_ctx ); return $num_done; } sub print { my $text = shift @_; return "" unless defined $text; if( ref( $text ) eq 'ARRAY' ) { if( $, ) { $text = join $, , @$text; } else { $text = join "", @$text; } } return _do_for_each( sub { HexChat::Internal::print( $text ); }, @_ ); } sub printf { my $format = shift; HexChat::print( sprintf( $format, @_ ) ); } # make HexChat::prnt() and HexChat::prntf() as aliases for HexChat::print() and # HexChat::printf(), mainly useful when these functions are exported sub prnt { goto &HexChat::print; } sub prntf { goto &HexChat::printf; } sub command { my $command = shift; return "" unless defined $command; my @commands; if( ref( $command ) eq 'ARRAY' ) { @commands = @$command; } else { @commands = ($command); } return _do_for_each( sub { HexChat::Internal::command( $_ ) foreach @commands }, @_ ); } sub commandf { my $format = shift; HexChat::command( sprintf( $format, @_ ) ); } sub plugin_pref_set { my $setting = shift // return 0; my $value = shift // return 0; return HexChat::Internal::plugin_pref_set($setting, $value); } sub plugin_pref_get { my $setting = shift // return 0; return HexChat::Internal::plugin_pref_get($setting); } sub plugin_pref_delete { my $setting = shift // return 0; return HexChat::Internal::plugin_pref_delete($setting); } sub plugin_pref_list { my %list = HexChat::Internal::plugin_pref_list(); return \%list; } sub set_context { my $context; if( @_ == 2 ) { my ($channel, $server) = @_; $context = HexChat::find_context( $channel, $server ); } elsif( @_ == 1 ) { if( defined $_[0] && $_[0] =~ /^\d+$/ ) { $context = $_[0]; } else { $context = HexChat::find_context( $_[0] ); } } elsif( @_ == 0 ) { $context = HexChat::find_context(); } return $context ? HexChat::Internal::set_context( $context ) : 0; } sub get_info { my $id = shift; my $info; if( defined( $id ) ) { if( grep { $id eq $_ } qw(state_cursor id) ) { $info = HexChat::get_prefs( $id ); } else { $info = HexChat::Internal::get_info( $id ); } } return $info; } sub user_info { my $nick = HexChat::strip_code(shift @_ || HexChat::get_info( "nick" )); my $user; for (HexChat::get_list( "users" ) ) { if ( HexChat::nickcmp( $_->{nick}, $nick ) == 0 ) { $user = $_; last; } } return $user; } sub context_info { my $ctx = shift @_ || HexChat::get_context; my $old_ctx = HexChat::get_context; my @fields = ( qw(away channel charset host id inputbox libdirfs modes network), qw(nick nickserv server topic version win_ptr win_status), qw(configdir xchatdir xchatdirfs state_cursor), ); if( HexChat::set_context( $ctx ) ) { my %info; for my $field ( @fields ) { $info{$field} = HexChat::get_info( $field ); } my $ctx_info = HexChat::Internal::context_info; @info{keys %$ctx_info} = values %$ctx_info; HexChat::set_context( $old_ctx ); return \%info; } else { return undef; } } sub get_list { unless( grep { $_[0] eq $_ } qw(channels dcc ignore notify users networks) ) { Carp::carp( "'$_[0]' does not appear to be a valid list name" ); } if( $_[0] eq 'networks' ) { return HexChat::List::Network->get(); } else { return HexChat::Internal::get_list( $_[0] ); } } sub strip_code { my $pattern = qr< \cB| #Bold \cC\d{0,2}(?:,\d{1,2})?| #Color \e\[(?:\d{1,2}(?:;\d{1,2})*)?m| # ANSI color code \cG| #Beep \cO| #Reset \cV| #Reverse \c_ #Underline >x; if( defined wantarray ) { my $msg = shift; $msg =~ s/$pattern//g; return $msg; } else { $_[0] =~ s/$pattern//g if defined $_[0]; } } 1