2 * Music messaging plugin for Purple
4 * Copyright (C) 2005 Christian Muise.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 /* NOTICE: This plugin is currently broken as the libpurple DBus bindings
29 #include "conversation.h"
31 #include "gtk3compat.h"
33 #include "gtkplugin.h"
40 #define DBUS_API_SUBJECT_TO_CHANGE
41 #include <dbus/dbus.h>
42 #include "dbus-maybe.h"
43 #include "dbus-bindings.h"
44 #include "dbus-server.h"
45 #include "dbus-purple.h"
47 #define MUSICMESSAGING_PLUGIN_ID "gtk-hazure-musicmessaging"
48 #define MUSICMESSAGING_PREFIX "##MM##"
49 #define MUSICMESSAGING_START_MSG _("A music messaging session has been requested. Please click the MM icon to accept.")
50 #define MUSICMESSAGING_CONFIRM_MSG _("Music messaging session confirmed.")
53 PurpleConversation
*conv
; /* pointer to the conversation */
54 GtkWidget
*seperator
; /* seperator in the conversation */
55 GtkWidget
*button
; /* button in the conversation */
56 GPid pid
; /* the pid of the score editor */
58 gboolean started
; /* session has started and editor run */
59 gboolean originator
; /* started the mm session */
60 gboolean requested
; /* received a request to start a session */
64 static gboolean
start_session(MMConversation
*mmconv
);
65 static void run_editor(MMConversation
*mmconv
);
66 static void kill_editor(MMConversation
*mmconv
);
67 static void add_button (MMConversation
*mmconv
);
68 static void remove_widget (GtkWidget
*button
);
69 static void init_conversation (PurpleConversation
*conv
);
70 static void conv_destroyed(PurpleConversation
*conv
);
71 static gboolean
intercept_sent(PurpleAccount
*account
, PurpleMessage
*msg
, void* pData
);
72 static gboolean
intercept_received(PurpleAccount
*account
, char **sender
, char **message
, PurpleConversation
*conv
, int *flags
);
73 static gboolean
send_change_request (const int session
, const char *id
, const char *command
, const char *parameters
);
74 static gboolean
send_change_confirmed (const int session
, const char *command
, const char *parameters
);
75 static void session_end (MMConversation
*mmconv
);
78 /* List of sessions */
79 static GList
*conversations
;
81 /* Pointer to this plugin */
82 static PurplePlugin
*plugin_pointer
;
84 /* Define types needed for DBus */
85 DBusGConnection
*connection
;
87 #define DBUS_SERVICE_GSCORE "org.gscore.GScoreService"
88 #define DBUS_PATH_GSCORE "/org/gscore/GScoreObject"
89 #define DBUS_INTERFACE_GSCORE "org.gscore.GScoreInterface"
91 /* Define the functions to export for use with DBus */
93 music_messaging_change_request(const int session
, const char *command
,
94 const char *parameters
);
97 music_messaging_change_confirmed(const int session
, const char *command
,
98 const char *parameters
);
101 music_messaging_change_failed(const int session
, const char *id
,
102 const char *command
, const char *parameters
);
105 music_messaging_done_session(const int session
);
107 /* This file has been generated by the #dbus-analize-functions.py
108 script. It contains dbus wrappers for the four functions declared
110 #include "music-messaging-bindings.ch"
112 /* Exported functions */
114 music_messaging_change_request(const int session
, const char *command
,
115 const char *parameters
)
118 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
122 if (mmconv
->originator
)
124 const char *name
= purple_conversation_get_name(mmconv
->conv
);
125 send_change_request (session
, name
, command
, parameters
);
128 GString
*to_send
= g_string_new("");
129 g_string_append_printf(to_send
, "##MM## request %s %s##MM##", command
, parameters
);
131 purple_conversation_send(mmconv
->conv
, to_send
->str
);
133 purple_debug_misc("musicmessaging", "Sent request: %s\n", to_send
->str
);
140 music_messaging_change_confirmed(const int session
, const char *command
,
141 const char *parameters
)
144 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
148 if (mmconv
->originator
)
150 GString
*to_send
= g_string_new("");
151 g_string_append_printf(to_send
, "##MM## confirm %s %s##MM##", command
, parameters
);
153 purple_conversation_send(mmconv
->conv
, to_send
->str
);
156 /* Do nothing. If they aren't the originator, then they can't confirm. */
163 music_messaging_change_failed(const int session
, const char *id
,
164 const char *command
, const char *parameters
)
166 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
168 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_INFO
, command
,
169 parameters
, NULL
, NULL
, NULL
, NULL
);
173 if (mmconv
->originator
)
175 GString
*to_send
= g_string_new("");
176 g_string_append_printf(to_send
, "##MM## failed %s %s %s##MM##", id
, command
, parameters
);
178 purple_conversation_send(mmconv
->conv
, to_send
->str
);
181 /* Do nothing. If they aren't the originator, then they can't confirm. */
187 music_messaging_done_session(const int session
)
189 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
191 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_INFO
, "Session",
192 "Session Complete", NULL
, NULL
, NULL
, NULL
);
198 /* DBus commands that can be sent to the editor */
200 DBusConnection
*purple_dbus_get_connection(void);
203 static gboolean
send_change_request (const int session
, const char *id
, const char *command
, const char *parameters
)
205 DBusMessage
*message
;
207 /* Create the signal we need */
208 message
= dbus_message_new_signal (PURPLE_DBUS_PATH
, PURPLE_DBUS_INTERFACE
, "GscoreChangeRequest");
210 /* Append the string "Ping!" to the signal */
211 dbus_message_append_args (message
,
212 DBUS_TYPE_INT32
, &session
,
213 DBUS_TYPE_STRING
, &id
,
214 DBUS_TYPE_STRING
, &command
,
215 DBUS_TYPE_STRING
, ¶meters
,
218 /* Send the signal */
219 dbus_connection_send (purple_dbus_get_connection(), message
, NULL
);
221 /* Free the signal now we have finished with it */
222 dbus_message_unref (message
);
224 /* Tell the user we sent a signal */
225 g_printerr("Sent change request signal: %d %s %s %s\n", session
, id
, command
, parameters
);
230 static gboolean
send_change_confirmed (const int session
, const char *command
, const char *parameters
)
232 DBusMessage
*message
;
234 /* Create the signal we need */
235 message
= dbus_message_new_signal (PURPLE_DBUS_PATH
, PURPLE_DBUS_INTERFACE
, "GscoreChangeConfirmed");
237 /* Append the string "Ping!" to the signal */
238 dbus_message_append_args (message
,
239 DBUS_TYPE_INT32
, &session
,
240 DBUS_TYPE_STRING
, &command
,
241 DBUS_TYPE_STRING
, ¶meters
,
244 /* Send the signal */
245 dbus_connection_send (purple_dbus_get_connection(), message
, NULL
);
247 /* Free the signal now we have finished with it */
248 dbus_message_unref (message
);
250 /* Tell the user we sent a signal */
251 g_printerr("Sent change confirmed signal.\n");
258 mmconv_from_conv_loc(PurpleConversation
*conv
)
261 MMConversation
*mmconv_current
= NULL
;
265 for (l
= conversations
; l
!= NULL
; l
= l
->next
)
267 mmconv_current
= l
->data
;
268 if (conv
== mmconv_current
->conv
)
277 static MMConversation
*
278 mmconv_from_conv(PurpleConversation
*conv
)
280 return (MMConversation
*)g_list_nth_data(conversations
, mmconv_from_conv_loc(conv
));
284 intercept_sent(PurpleAccount
*account
, PurpleMessage
*msg
, void* pData
)
286 const gchar
*cont
= purple_message_get_contents(msg
);
288 if (purple_message_is_empty(msg
))
291 if (0 == strncmp(cont
, MUSICMESSAGING_PREFIX
, strlen(MUSICMESSAGING_PREFIX
)))
293 purple_debug_misc("purple-musicmessaging", "Sent MM Message: %s\n", cont
);
295 else if (0 == strncmp(cont
, MUSICMESSAGING_START_MSG
, strlen(MUSICMESSAGING_START_MSG
)))
297 purple_debug_misc("purple-musicmessaging", "Sent MM request.\n");
300 else if (0 == strncmp(cont
, MUSICMESSAGING_CONFIRM_MSG
, strlen(MUSICMESSAGING_CONFIRM_MSG
)))
302 purple_debug_misc("purple-musicmessaging", "Sent MM confirm.\n");
305 else if (0 == strncmp(cont
, "test1", strlen("test1")))
307 purple_debug_misc("purple-musicmessaging", "\n\nTEST 1\n\n");
308 send_change_request(0, "test-id", "test-command", "test-parameters");
311 else if (0 == strncmp(cont
, "test2", strlen("test2")))
313 purple_debug_misc("purple-musicmessaging", "\n\nTEST 2\n\n");
314 send_change_confirmed(1, "test-command", "test-parameters");
320 /* Do nothing...procceed as normal */
326 intercept_received(PurpleAccount
*account
, char **sender
, char **message
, PurpleConversation
*conv
, int *flags
)
328 MMConversation
*mmconv
;
331 /* XXX: This is just to avoid a crash (#2726).
332 * We may want to create the conversation instead of returning from here
337 mmconv
= mmconv_from_conv(conv
);
339 purple_debug_misc("purple-musicmessaging", "Intercepted: %s\n", *message
);
340 if (strstr(*message
, MUSICMESSAGING_PREFIX
))
342 char *parsed_message
= strtok(strstr(*message
, MUSICMESSAGING_PREFIX
), "<");
343 purple_debug_misc("purple-musicmessaging", "Received an MM Message: %s\n", parsed_message
);
347 if (strstr(parsed_message
, "request"))
349 if (mmconv
->originator
)
351 int session
= mmconv_from_conv_loc(conv
);
352 const char *id
= purple_conversation_get_name(mmconv
->conv
);
356 purple_debug_misc("purple-musicmessaging", "Sending request to gscore.\n");
358 /* Get past the first two terms - '##MM##' and 'request' */
359 strtok(parsed_message
, " "); /* '##MM##' */
360 strtok(NULL
, " "); /* 'request' */
362 command
= strtok(NULL
, " ");
363 parameters
= strtok(NULL
, "#");
365 send_change_request (session
, id
, command
, parameters
);
368 } else if (strstr(parsed_message
, "confirm"))
370 if (!mmconv
->originator
)
372 int session
= mmconv_from_conv_loc(conv
);
376 purple_debug_misc("purple-musicmessaging", "Sending confirmation to gscore.\n");
378 /* Get past the first two terms - '##MM##' and 'confirm' */
379 strtok(parsed_message
, " "); /* '##MM##' */
380 strtok(NULL
, " "); /* 'confirm' */
382 command
= strtok(NULL
, " ");
383 parameters
= strtok(NULL
, "#");
385 send_change_confirmed (session
, command
, parameters
);
387 } else if (strstr(parsed_message
, "failed"))
392 /* Get past the first two terms - '##MM##' and 'confirm' */
393 strtok(parsed_message
, " "); /* '##MM##' */
394 strtok(NULL
, " "); /* 'failed' */
396 id
= strtok(NULL
, " ");
397 command
= strtok(NULL
, " ");
398 /* char *parameters = strtok(NULL, "#"); DONT NEED PARAMETERS */
400 // TODO: Shouldn't this be strcmp() ?
401 if (purple_conversation_get_name(mmconv
->conv
) == id
)
403 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_ERROR
,
404 _("Music Messaging"),
405 _("There was a conflict in running the command:"), command
, NULL
, NULL
, NULL
);
412 else if (strstr(*message
, MUSICMESSAGING_START_MSG
))
414 purple_debug_misc("purple-musicmessaging", "Received MM request.\n");
415 if (!(mmconv
->originator
))
417 mmconv
->requested
= TRUE
;
422 else if (strstr(*message
, MUSICMESSAGING_CONFIRM_MSG
))
424 purple_debug_misc("purple-musicmessagin", "Received MM confirm.\n");
426 if (mmconv
->originator
)
428 start_session(mmconv
);
440 static void send_request(MMConversation
*mmconv
)
442 PurpleConnection
*connection
= purple_conversation_get_connection(mmconv
->conv
);
443 const char *convName
= purple_conversation_get_name(mmconv
->conv
);
444 purple_serv_send_im(connection
, purple_message_new_outgoing(
445 convName
, MUSICMESSAGING_START_MSG
, 0));
448 static void send_request_confirmed(MMConversation
*mmconv
)
450 PurpleConnection
*connection
= purple_conversation_get_connection(mmconv
->conv
);
451 const char *convName
= purple_conversation_get_name(mmconv
->conv
);
452 purple_serv_send_im(connection
, purple_message_new_outgoing(
453 convName
, MUSICMESSAGING_CONFIRM_MSG
, 0));
458 start_session(MMConversation
*mmconv
)
464 static void session_end (MMConversation
*mmconv
)
466 mmconv
->started
= FALSE
;
467 mmconv
->originator
= FALSE
;
468 mmconv
->requested
= FALSE
;
472 static void music_button_toggled (GtkWidget
*widget
, gpointer data
)
474 MMConversation
*mmconv
= mmconv_from_conv(((MMConversation
*) data
)->conv
);
475 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget
)))
477 if (((MMConversation
*) data
)->requested
)
479 start_session(mmconv
);
480 send_request_confirmed(mmconv
);
484 ((MMConversation
*) data
)->originator
= TRUE
;
485 send_request((MMConversation
*) data
);
488 session_end((MMConversation
*)data
);
492 static void set_editor_path (GtkWidget
*button
, GtkWidget
*text_field
)
494 const char * path
= gtk_entry_get_text((GtkEntry
*)text_field
);
495 purple_prefs_set_string("/plugins/gtk/musicmessaging/editor_path", path
);
499 static void run_editor (MMConversation
*mmconv
)
501 GError
*spawn_error
= NULL
;
504 args
[0] = (gchar
*)purple_prefs_get_string("/plugins/gtk/musicmessaging/editor_path");
506 args
[1] = "-session_id";
507 session_id
= g_string_new("");
508 g_string_append_printf(session_id
, "%d", mmconv_from_conv_loc(mmconv
->conv
));
509 args
[2] = session_id
->str
;
513 if (!(g_spawn_async (".", args
, NULL
, 4, NULL
, NULL
, &(mmconv
->pid
), &spawn_error
)))
515 purple_notify_error(plugin_pointer
, _("Error Running Editor"),
516 _("The following error has occurred:"), spawn_error
->message
, NULL
);
517 mmconv
->started
= FALSE
;
521 mmconv
->started
= TRUE
;
525 static void kill_editor (MMConversation
*mmconv
)
529 kill(mmconv
->pid
, SIGINT
);
534 static void init_conversation (PurpleConversation
*conv
)
536 MMConversation
*mmconv
;
537 mmconv
= g_new0(MMConversation
, 1);
540 mmconv
->started
= FALSE
;
541 mmconv
->originator
= FALSE
;
542 mmconv
->requested
= FALSE
;
546 conversations
= g_list_append(conversations
, mmconv
);
549 static void conv_destroyed (PurpleConversation
*conv
)
551 MMConversation
*mmconv
= mmconv_from_conv(conv
);
553 remove_widget(mmconv
->button
);
554 remove_widget(mmconv
->seperator
);
559 conversations
= g_list_remove(conversations
, mmconv
);
562 static void add_button (MMConversation
*mmconv
)
565 PurpleConversation
*conv
= mmconv
->conv
;
568 GtkWidget
*button
, *image
, *sep
;
571 button
= gtk_toggle_button_new();
572 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
574 g_signal_connect(G_OBJECT(button
), "toggled", G_CALLBACK(music_button_toggled
), mmconv
);
576 file_path
= g_build_filename(PURPLE_DATADIR
,
577 "pixmaps", "purple", "buttons", "music.png", NULL
);
578 image
= gtk_image_new_from_file(file_path
);
581 gtk_container_add((GtkContainer
*)button
, image
);
583 sep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
585 mmconv
->seperator
= sep
;
586 mmconv
->button
= button
;
588 gtk_widget_show(sep
);
589 gtk_widget_show(image
);
590 gtk_widget_show(button
);
593 gtk_box_pack_start(GTK_BOX(PIDGIN_CONVERSATION(conv
)->toolbar
), sep
, FALSE
, FALSE
, 0);
594 gtk_box_pack_start(GTK_BOX(PIDGIN_CONVERSATION(conv
)->toolbar
), button
, FALSE
, FALSE
, 0);
598 static void remove_widget (GtkWidget
*button
)
600 gtk_widget_hide(button
);
601 gtk_widget_destroy(button
);
605 get_config_frame(PurplePlugin
*plugin
)
610 GtkWidget
*editor_path
;
611 GtkWidget
*editor_path_label
;
612 GtkWidget
*editor_path_button
;
614 /* Outside container */
615 ret
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 18);
616 gtk_container_set_border_width(GTK_CONTAINER(ret
), 10);
618 /* Configuration frame */
619 vbox
= pidgin_make_frame(ret
, _("Music Messaging Configuration"));
621 /* Path to the score editor */
622 editor_path
= gtk_entry_new();
623 editor_path_label
= gtk_label_new(_("Score Editor Path"));
624 editor_path_button
= gtk_button_new_with_mnemonic(_("_Apply"));
626 gtk_entry_set_text((GtkEntry
*)editor_path
, "/usr/local/bin/gscore");
628 g_signal_connect(G_OBJECT(editor_path_button
), "clicked",
629 G_CALLBACK(set_editor_path
), editor_path
);
631 gtk_box_pack_start(GTK_BOX(vbox
), editor_path_label
, FALSE
, FALSE
, 0);
632 gtk_box_pack_start(GTK_BOX(vbox
), editor_path
, FALSE
, FALSE
, 0);
633 gtk_box_pack_start(GTK_BOX(vbox
), editor_path_button
, FALSE
, FALSE
, 0);
635 gtk_widget_show_all(ret
);
640 static PidginPluginInfo
*
641 plugin_query(GError
**error
) {
642 const gchar
* const authors
[] = {
643 "Christian Muise <christian.muise@gmail.com>",
647 return pidgin_plugin_info_new(
648 "id", MUSICMESSAGING_PLUGIN_ID
,
649 "name", N_("Music Messaging"),
650 "version", DISPLAY_VERSION
,
651 "category", N_("Music"),
652 "summary", N_("Music Messaging Plugin for "
653 "collaborative composition."),
654 "description", N_("The Music Messaging Plugin allows a "
655 "number of users to simultaneously work "
656 "on a piece of music by editing a common "
657 "score in real-time."),
659 "website", PURPLE_WEBSITE
,
660 "abi-version", PURPLE_ABI_VERSION
,
661 "gtk-config-frame-cb", get_config_frame
,
667 plugin_load(PurplePlugin
*plugin
, GError
**error
) {
668 void *conv_list_handle
;
671 PURPLE_DBUS_RETURN_FALSE_IF_DISABLED(plugin
);
673 purple_prefs_add_none("/plugins/gtk/musicmessaging");
674 purple_prefs_add_string("/plugins/gtk/musicmessaging/editor_path", "/usr/bin/gscore");
676 /* First, we have to register our four exported functions with the
677 main purple dbus loop. Without this statement, the purple dbus
678 code wouldn't know about our functions. */
679 PURPLE_DBUS_REGISTER_BINDINGS(plugin
);
681 /* Keep the plugin for reference (needed for notify's) */
682 plugin_pointer
= plugin
;
684 /* Add the button to all the current conversations */
685 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
686 init_conversation((PurpleConversation
*)l
->data
);
688 /* Listen for any new conversations */
689 conv_list_handle
= purple_conversations_get_handle();
691 purple_signal_connect(conv_list_handle
, "conversation-created",
692 plugin
, PURPLE_CALLBACK(init_conversation
), NULL
);
694 /* Listen for conversations that are ending */
695 purple_signal_connect(conv_list_handle
, "deleting-conversation",
696 plugin
, PURPLE_CALLBACK(conv_destroyed
), NULL
);
698 /* Listen for sending/receiving messages to replace tags */
699 purple_signal_connect(conv_list_handle
, "sending-im-msg",
700 plugin
, PURPLE_CALLBACK(intercept_sent
), NULL
);
701 purple_signal_connect(conv_list_handle
, "receiving-im-msg",
702 plugin
, PURPLE_CALLBACK(intercept_received
), NULL
);
708 plugin_unload(PurplePlugin
*plugin
, GError
**error
) {
709 MMConversation
*mmconv
= NULL
;
711 while (conversations
!= NULL
)
713 mmconv
= conversations
->data
;
714 conv_destroyed(mmconv
->conv
);
719 PURPLE_PLUGIN_INIT(musicmessaging
, plugin_query
, plugin_load
, plugin_unload
);