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
25 #include "conversation.h"
27 #include "gtk3compat.h"
29 #include "gtkplugin.h"
36 #define DBUS_API_SUBJECT_TO_CHANGE
37 #include <dbus/dbus.h>
38 #include "dbus-maybe.h"
39 #include "dbus-bindings.h"
40 #include "dbus-server.h"
41 #include "dbus-purple.h"
43 #define MUSICMESSAGING_PLUGIN_ID "gtk-hazure-musicmessaging"
44 #define MUSICMESSAGING_PREFIX "##MM##"
45 #define MUSICMESSAGING_START_MSG _("A music messaging session has been requested. Please click the MM icon to accept.")
46 #define MUSICMESSAGING_CONFIRM_MSG _("Music messaging session confirmed.")
49 PurpleConversation
*conv
; /* pointer to the conversation */
50 GtkWidget
*seperator
; /* seperator in the conversation */
51 GtkWidget
*button
; /* button in the conversation */
52 GPid pid
; /* the pid of the score editor */
54 gboolean started
; /* session has started and editor run */
55 gboolean originator
; /* started the mm session */
56 gboolean requested
; /* received a request to start a session */
60 static gboolean
start_session(MMConversation
*mmconv
);
61 static void run_editor(MMConversation
*mmconv
);
62 static void kill_editor(MMConversation
*mmconv
);
63 static void add_button (MMConversation
*mmconv
);
64 static void remove_widget (GtkWidget
*button
);
65 static void init_conversation (PurpleConversation
*conv
);
66 static void conv_destroyed(PurpleConversation
*conv
);
67 static gboolean
intercept_sent(PurpleAccount
*account
, PurpleMessage
*msg
, void* pData
);
68 static gboolean
intercept_received(PurpleAccount
*account
, char **sender
, char **message
, PurpleConversation
*conv
, int *flags
);
69 static gboolean
send_change_request (const int session
, const char *id
, const char *command
, const char *parameters
);
70 static gboolean
send_change_confirmed (const int session
, const char *command
, const char *parameters
);
71 static void session_end (MMConversation
*mmconv
);
74 /* List of sessions */
75 static GList
*conversations
;
77 /* Pointer to this plugin */
78 static PurplePlugin
*plugin_pointer
;
80 /* Define types needed for DBus */
81 DBusGConnection
*connection
;
83 #define DBUS_SERVICE_GSCORE "org.gscore.GScoreService"
84 #define DBUS_PATH_GSCORE "/org/gscore/GScoreObject"
85 #define DBUS_INTERFACE_GSCORE "org.gscore.GScoreInterface"
87 /* Define the functions to export for use with DBus */
89 music_messaging_change_request(const int session
, const char *command
,
90 const char *parameters
);
93 music_messaging_change_confirmed(const int session
, const char *command
,
94 const char *parameters
);
97 music_messaging_change_failed(const int session
, const char *id
,
98 const char *command
, const char *parameters
);
101 music_messaging_done_session(const int session
);
103 /* This file has been generated by the #dbus-analize-functions.py
104 script. It contains dbus wrappers for the four functions declared
106 #include "music-messaging-bindings.c"
108 /* Exported functions */
110 music_messaging_change_request(const int session
, const char *command
,
111 const char *parameters
)
114 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
118 if (mmconv
->originator
)
120 const char *name
= purple_conversation_get_name(mmconv
->conv
);
121 send_change_request (session
, name
, command
, parameters
);
124 GString
*to_send
= g_string_new("");
125 g_string_append_printf(to_send
, "##MM## request %s %s##MM##", command
, parameters
);
127 purple_conversation_send(mmconv
->conv
, to_send
->str
);
129 purple_debug_misc("musicmessaging", "Sent request: %s\n", to_send
->str
);
136 music_messaging_change_confirmed(const int session
, const char *command
,
137 const char *parameters
)
140 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
144 if (mmconv
->originator
)
146 GString
*to_send
= g_string_new("");
147 g_string_append_printf(to_send
, "##MM## confirm %s %s##MM##", command
, parameters
);
149 purple_conversation_send(mmconv
->conv
, to_send
->str
);
152 /* Do nothing. If they aren't the originator, then they can't confirm. */
159 music_messaging_change_failed(const int session
, const char *id
,
160 const char *command
, const char *parameters
)
162 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
164 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_INFO
, command
,
165 parameters
, NULL
, NULL
, NULL
, NULL
);
169 if (mmconv
->originator
)
171 GString
*to_send
= g_string_new("");
172 g_string_append_printf(to_send
, "##MM## failed %s %s %s##MM##", id
, command
, parameters
);
174 purple_conversation_send(mmconv
->conv
, to_send
->str
);
177 /* Do nothing. If they aren't the originator, then they can't confirm. */
183 music_messaging_done_session(const int session
)
185 MMConversation
*mmconv
= (MMConversation
*)g_list_nth_data(conversations
, session
);
187 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_INFO
, "Session",
188 "Session Complete", NULL
, NULL
, NULL
, NULL
);
194 /* DBus commands that can be sent to the editor */
196 DBusConnection
*purple_dbus_get_connection(void);
199 static gboolean
send_change_request (const int session
, const char *id
, const char *command
, const char *parameters
)
201 DBusMessage
*message
;
203 /* Create the signal we need */
204 message
= dbus_message_new_signal (PURPLE_DBUS_PATH
, PURPLE_DBUS_INTERFACE
, "GscoreChangeRequest");
206 /* Append the string "Ping!" to the signal */
207 dbus_message_append_args (message
,
208 DBUS_TYPE_INT32
, &session
,
209 DBUS_TYPE_STRING
, &id
,
210 DBUS_TYPE_STRING
, &command
,
211 DBUS_TYPE_STRING
, ¶meters
,
214 /* Send the signal */
215 dbus_connection_send (purple_dbus_get_connection(), message
, NULL
);
217 /* Free the signal now we have finished with it */
218 dbus_message_unref (message
);
220 /* Tell the user we sent a signal */
221 g_printerr("Sent change request signal: %d %s %s %s\n", session
, id
, command
, parameters
);
226 static gboolean
send_change_confirmed (const int session
, const char *command
, const char *parameters
)
228 DBusMessage
*message
;
230 /* Create the signal we need */
231 message
= dbus_message_new_signal (PURPLE_DBUS_PATH
, PURPLE_DBUS_INTERFACE
, "GscoreChangeConfirmed");
233 /* Append the string "Ping!" to the signal */
234 dbus_message_append_args (message
,
235 DBUS_TYPE_INT32
, &session
,
236 DBUS_TYPE_STRING
, &command
,
237 DBUS_TYPE_STRING
, ¶meters
,
240 /* Send the signal */
241 dbus_connection_send (purple_dbus_get_connection(), message
, NULL
);
243 /* Free the signal now we have finished with it */
244 dbus_message_unref (message
);
246 /* Tell the user we sent a signal */
247 g_printerr("Sent change confirmed signal.\n");
254 mmconv_from_conv_loc(PurpleConversation
*conv
)
257 MMConversation
*mmconv_current
= NULL
;
261 for (l
= conversations
; l
!= NULL
; l
= l
->next
)
263 mmconv_current
= l
->data
;
264 if (conv
== mmconv_current
->conv
)
273 static MMConversation
*
274 mmconv_from_conv(PurpleConversation
*conv
)
276 return (MMConversation
*)g_list_nth_data(conversations
, mmconv_from_conv_loc(conv
));
280 intercept_sent(PurpleAccount
*account
, PurpleMessage
*msg
, void* pData
)
282 const gchar
*cont
= purple_message_get_contents(msg
);
284 if (purple_message_is_empty(msg
))
287 if (0 == strncmp(cont
, MUSICMESSAGING_PREFIX
, strlen(MUSICMESSAGING_PREFIX
)))
289 purple_debug_misc("purple-musicmessaging", "Sent MM Message: %s\n", cont
);
291 else if (0 == strncmp(cont
, MUSICMESSAGING_START_MSG
, strlen(MUSICMESSAGING_START_MSG
)))
293 purple_debug_misc("purple-musicmessaging", "Sent MM request.\n");
296 else if (0 == strncmp(cont
, MUSICMESSAGING_CONFIRM_MSG
, strlen(MUSICMESSAGING_CONFIRM_MSG
)))
298 purple_debug_misc("purple-musicmessaging", "Sent MM confirm.\n");
301 else if (0 == strncmp(cont
, "test1", strlen("test1")))
303 purple_debug_misc("purple-musicmessaging", "\n\nTEST 1\n\n");
304 send_change_request(0, "test-id", "test-command", "test-parameters");
307 else if (0 == strncmp(cont
, "test2", strlen("test2")))
309 purple_debug_misc("purple-musicmessaging", "\n\nTEST 2\n\n");
310 send_change_confirmed(1, "test-command", "test-parameters");
316 /* Do nothing...procceed as normal */
322 intercept_received(PurpleAccount
*account
, char **sender
, char **message
, PurpleConversation
*conv
, int *flags
)
324 MMConversation
*mmconv
;
327 /* XXX: This is just to avoid a crash (#2726).
328 * We may want to create the conversation instead of returning from here
333 mmconv
= mmconv_from_conv(conv
);
335 purple_debug_misc("purple-musicmessaging", "Intercepted: %s\n", *message
);
336 if (strstr(*message
, MUSICMESSAGING_PREFIX
))
338 char *parsed_message
= strtok(strstr(*message
, MUSICMESSAGING_PREFIX
), "<");
339 purple_debug_misc("purple-musicmessaging", "Received an MM Message: %s\n", parsed_message
);
343 if (strstr(parsed_message
, "request"))
345 if (mmconv
->originator
)
347 int session
= mmconv_from_conv_loc(conv
);
348 const char *id
= purple_conversation_get_name(mmconv
->conv
);
352 purple_debug_misc("purple-musicmessaging", "Sending request to gscore.\n");
354 /* Get past the first two terms - '##MM##' and 'request' */
355 strtok(parsed_message
, " "); /* '##MM##' */
356 strtok(NULL
, " "); /* 'request' */
358 command
= strtok(NULL
, " ");
359 parameters
= strtok(NULL
, "#");
361 send_change_request (session
, id
, command
, parameters
);
364 } else if (strstr(parsed_message
, "confirm"))
366 if (!mmconv
->originator
)
368 int session
= mmconv_from_conv_loc(conv
);
372 purple_debug_misc("purple-musicmessaging", "Sending confirmation to gscore.\n");
374 /* Get past the first two terms - '##MM##' and 'confirm' */
375 strtok(parsed_message
, " "); /* '##MM##' */
376 strtok(NULL
, " "); /* 'confirm' */
378 command
= strtok(NULL
, " ");
379 parameters
= strtok(NULL
, "#");
381 send_change_confirmed (session
, command
, parameters
);
383 } else if (strstr(parsed_message
, "failed"))
388 /* Get past the first two terms - '##MM##' and 'confirm' */
389 strtok(parsed_message
, " "); /* '##MM##' */
390 strtok(NULL
, " "); /* 'failed' */
392 id
= strtok(NULL
, " ");
393 command
= strtok(NULL
, " ");
394 /* char *parameters = strtok(NULL, "#"); DONT NEED PARAMETERS */
396 // TODO: Shouldn't this be strcmp() ?
397 if (purple_conversation_get_name(mmconv
->conv
) == id
)
399 purple_notify_message(plugin_pointer
, PURPLE_NOTIFY_MSG_ERROR
,
400 _("Music Messaging"),
401 _("There was a conflict in running the command:"), command
, NULL
, NULL
, NULL
);
408 else if (strstr(*message
, MUSICMESSAGING_START_MSG
))
410 purple_debug_misc("purple-musicmessaging", "Received MM request.\n");
411 if (!(mmconv
->originator
))
413 mmconv
->requested
= TRUE
;
418 else if (strstr(*message
, MUSICMESSAGING_CONFIRM_MSG
))
420 purple_debug_misc("purple-musicmessagin", "Received MM confirm.\n");
422 if (mmconv
->originator
)
424 start_session(mmconv
);
436 static void send_request(MMConversation
*mmconv
)
438 PurpleConnection
*connection
= purple_conversation_get_connection(mmconv
->conv
);
439 const char *convName
= purple_conversation_get_name(mmconv
->conv
);
440 purple_serv_send_im(connection
, purple_message_new_outgoing(
441 convName
, MUSICMESSAGING_START_MSG
, 0));
444 static void send_request_confirmed(MMConversation
*mmconv
)
446 PurpleConnection
*connection
= purple_conversation_get_connection(mmconv
->conv
);
447 const char *convName
= purple_conversation_get_name(mmconv
->conv
);
448 purple_serv_send_im(connection
, purple_message_new_outgoing(
449 convName
, MUSICMESSAGING_CONFIRM_MSG
, 0));
454 start_session(MMConversation
*mmconv
)
460 static void session_end (MMConversation
*mmconv
)
462 mmconv
->started
= FALSE
;
463 mmconv
->originator
= FALSE
;
464 mmconv
->requested
= FALSE
;
468 static void music_button_toggled (GtkWidget
*widget
, gpointer data
)
470 MMConversation
*mmconv
= mmconv_from_conv(((MMConversation
*) data
)->conv
);
471 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget
)))
473 if (((MMConversation
*) data
)->requested
)
475 start_session(mmconv
);
476 send_request_confirmed(mmconv
);
480 ((MMConversation
*) data
)->originator
= TRUE
;
481 send_request((MMConversation
*) data
);
484 session_end((MMConversation
*)data
);
488 static void set_editor_path (GtkWidget
*button
, GtkWidget
*text_field
)
490 const char * path
= gtk_entry_get_text((GtkEntry
*)text_field
);
491 purple_prefs_set_string("/plugins/gtk/musicmessaging/editor_path", path
);
495 static void run_editor (MMConversation
*mmconv
)
497 GError
*spawn_error
= NULL
;
500 args
[0] = (gchar
*)purple_prefs_get_string("/plugins/gtk/musicmessaging/editor_path");
502 args
[1] = "-session_id";
503 session_id
= g_string_new("");
504 g_string_append_printf(session_id
, "%d", mmconv_from_conv_loc(mmconv
->conv
));
505 args
[2] = session_id
->str
;
509 if (!(g_spawn_async (".", args
, NULL
, 4, NULL
, NULL
, &(mmconv
->pid
), &spawn_error
)))
511 purple_notify_error(plugin_pointer
, _("Error Running Editor"),
512 _("The following error has occurred:"), spawn_error
->message
, NULL
);
513 mmconv
->started
= FALSE
;
517 mmconv
->started
= TRUE
;
521 static void kill_editor (MMConversation
*mmconv
)
525 kill(mmconv
->pid
, SIGINT
);
530 static void init_conversation (PurpleConversation
*conv
)
532 MMConversation
*mmconv
;
533 mmconv
= g_malloc(sizeof(MMConversation
));
536 mmconv
->started
= FALSE
;
537 mmconv
->originator
= FALSE
;
538 mmconv
->requested
= FALSE
;
542 conversations
= g_list_append(conversations
, mmconv
);
545 static void conv_destroyed (PurpleConversation
*conv
)
547 MMConversation
*mmconv
= mmconv_from_conv(conv
);
549 remove_widget(mmconv
->button
);
550 remove_widget(mmconv
->seperator
);
555 conversations
= g_list_remove(conversations
, mmconv
);
558 static void add_button (MMConversation
*mmconv
)
561 PurpleConversation
*conv
= mmconv
->conv
;
564 GtkWidget
*button
, *image
, *sep
;
567 button
= gtk_toggle_button_new();
568 gtk_button_set_relief(GTK_BUTTON(button
), GTK_RELIEF_NONE
);
570 g_signal_connect(G_OBJECT(button
), "toggled", G_CALLBACK(music_button_toggled
), mmconv
);
572 file_path
= g_build_filename(PURPLE_DATADIR
,
573 "pixmaps", "purple", "buttons", "music.png", NULL
);
574 image
= gtk_image_new_from_file(file_path
);
577 gtk_container_add((GtkContainer
*)button
, image
);
579 sep
= gtk_separator_new(GTK_ORIENTATION_VERTICAL
);
581 mmconv
->seperator
= sep
;
582 mmconv
->button
= button
;
584 gtk_widget_show(sep
);
585 gtk_widget_show(image
);
586 gtk_widget_show(button
);
589 gtk_box_pack_start(GTK_BOX(PIDGIN_CONVERSATION(conv
)->toolbar
), sep
, FALSE
, FALSE
, 0);
590 gtk_box_pack_start(GTK_BOX(PIDGIN_CONVERSATION(conv
)->toolbar
), button
, FALSE
, FALSE
, 0);
594 static void remove_widget (GtkWidget
*button
)
596 gtk_widget_hide(button
);
597 gtk_widget_destroy(button
);
601 get_config_frame(PurplePlugin
*plugin
)
606 GtkWidget
*editor_path
;
607 GtkWidget
*editor_path_label
;
608 GtkWidget
*editor_path_button
;
610 /* Outside container */
611 ret
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 18);
612 gtk_container_set_border_width(GTK_CONTAINER(ret
), 10);
614 /* Configuration frame */
615 vbox
= pidgin_make_frame(ret
, _("Music Messaging Configuration"));
617 /* Path to the score editor */
618 editor_path
= gtk_entry_new();
619 editor_path_label
= gtk_label_new(_("Score Editor Path"));
620 editor_path_button
= gtk_button_new_with_mnemonic(_("_Apply"));
622 gtk_entry_set_text((GtkEntry
*)editor_path
, "/usr/local/bin/gscore");
624 g_signal_connect(G_OBJECT(editor_path_button
), "clicked",
625 G_CALLBACK(set_editor_path
), editor_path
);
627 gtk_box_pack_start(GTK_BOX(vbox
), editor_path_label
, FALSE
, FALSE
, 0);
628 gtk_box_pack_start(GTK_BOX(vbox
), editor_path
, FALSE
, FALSE
, 0);
629 gtk_box_pack_start(GTK_BOX(vbox
), editor_path_button
, FALSE
, FALSE
, 0);
631 gtk_widget_show_all(ret
);
636 static PidginPluginInfo
*
637 plugin_query(GError
**error
) {
638 const gchar
* const authors
[] = {
639 "Christian Muise <christian.muise@gmail.com>",
643 return pidgin_plugin_info_new(
644 "id", MUSICMESSAGING_PLUGIN_ID
,
645 "name", N_("Music Messaging"),
646 "version", DISPLAY_VERSION
,
647 "category", N_("Music"),
648 "summary", N_("Music Messaging Plugin for "
649 "collaborative composition."),
650 "description", N_("The Music Messaging Plugin allows a "
651 "number of users to simultaneously work "
652 "on a piece of music by editing a common "
653 "score in real-time."),
655 "website", PURPLE_WEBSITE
,
656 "abi-version", PURPLE_ABI_VERSION
,
657 "gtk-config-frame-cb", get_config_frame
,
663 plugin_load(PurplePlugin
*plugin
, GError
**error
) {
664 void *conv_list_handle
;
667 PURPLE_DBUS_RETURN_FALSE_IF_DISABLED(plugin
);
669 purple_prefs_add_none("/plugins/gtk/musicmessaging");
670 purple_prefs_add_string("/plugins/gtk/musicmessaging/editor_path", "/usr/bin/gscore");
672 /* First, we have to register our four exported functions with the
673 main purple dbus loop. Without this statement, the purple dbus
674 code wouldn't know about our functions. */
675 PURPLE_DBUS_REGISTER_BINDINGS(plugin
);
677 /* Keep the plugin for reference (needed for notify's) */
678 plugin_pointer
= plugin
;
680 /* Add the button to all the current conversations */
681 for (l
= purple_conversations_get_all(); l
!= NULL
; l
= l
->next
)
682 init_conversation((PurpleConversation
*)l
->data
);
684 /* Listen for any new conversations */
685 conv_list_handle
= purple_conversations_get_handle();
687 purple_signal_connect(conv_list_handle
, "conversation-created",
688 plugin
, PURPLE_CALLBACK(init_conversation
), NULL
);
690 /* Listen for conversations that are ending */
691 purple_signal_connect(conv_list_handle
, "deleting-conversation",
692 plugin
, PURPLE_CALLBACK(conv_destroyed
), NULL
);
694 /* Listen for sending/receiving messages to replace tags */
695 purple_signal_connect(conv_list_handle
, "sending-im-msg",
696 plugin
, PURPLE_CALLBACK(intercept_sent
), NULL
);
697 purple_signal_connect(conv_list_handle
, "receiving-im-msg",
698 plugin
, PURPLE_CALLBACK(intercept_received
), NULL
);
704 plugin_unload(PurplePlugin
*plugin
, GError
**error
) {
705 MMConversation
*mmconv
= NULL
;
707 while (conversations
!= NULL
)
709 mmconv
= conversations
->data
;
710 conv_destroyed(mmconv
->conv
);
715 PURPLE_PLUGIN_INIT(musicmessaging
, plugin_query
, plugin_load
, plugin_unload
);