using System; using System.IO; using System.Collections; using System.Timers; using System.Threading; using System.Reflection; using System.Resources; using Gtk; //using Gdk; using System.Diagnostics; using Lumpio.Net.Msn; //using Lumpio.Ink; namespace Lumpio.LuMsn { public class LuMsn { MsnConversation convo = null; public MsnClient msn = null; Window connecting; VBox connVBox; Label connLabel; Button cancButton; Window contactList; VBox contVBox; ScrolledWindow sw; TreeView tree; Button imButton; TreeStore contactStore; ArrayList users; public ArrayList convos; public Gdk.Pixbuf iconUserTyping; public Gdk.Pixbuf[] presenceIcons; System.Timers.Timer msnTimer; ThreadNotify msnTimerNotify; public static void Main(string[] args) { //Gtk.Application.Init(); new LuMsn(args); } public LuMsn(string[] args) { Gtk.Application.Init(); Assembly asm = Assembly.GetExecutingAssembly(); iconUserTyping = new Gdk.Pixbuf(asm, "UserTypingIcon"); Gdk.Pixbuf presenceIconStrip = new Gdk.Pixbuf(asm, "PresenceIcons"); presenceIcons = getImageStrip(presenceIconStrip, 16); users = new ArrayList(); convos = new ArrayList(); Debug.Listeners.Add( new TextWriterTraceListener(System.Console.Out)); createGui(); connecting.SetPosition(WindowPosition.Center); connecting.ShowAll(); msn = new MsnClient(); //msn.ClientCapabilities |= MsnClientCapabilities.ReceiveInk | MsnClientCapabilities.ReceiveSendInk; msn.Connected += new MsnConnectEventHandler(connected); msn.Invited += new MsnInviteEventHandler(invite); msn.PresenceChanged += new MsnPresenceEventHandler(presence); Debug.WriteLine("Connecting..."); msnTimerNotify = new ThreadNotify(updateMsn); msnTimer = new System.Timers.Timer(100); msnTimer.Elapsed += new ElapsedEventHandler(msnTimerElapsed); msnTimer.AutoReset = true; msnTimer.Start(); // Put passport and password HERE: msn.Passport = "passport"; msn.Password = "password"; msn.Connect(); /*while (go) { Gtk.Application.RunIteration(false); msn.Run(); Thread.Sleep(10); }*/ Gtk.Application.Run(); while (convos.Count > 0) { ((Conversation)convos[0]).Close(); } msnTimer.Stop(); msn.Signoff(); msn.Run(); } private void msnTimerElapsed(object sender, ElapsedEventArgs args) { msnTimerNotify.WakeupMain(); } private void updateMsn() { msn.Run(); } private void userQuit() { Debug.WriteLine("User quit"); Gtk.Application.Quit(); } private void windowDelete(object sender, DeleteEventArgs args) { userQuit(); } private void cancelClicked(object sender, EventArgs args) { userQuit(); } private void startConvo(TreeIter iter) { foreach (User u in users) { if (u.ti.Equals(iter)) { foreach (Conversation co in convos) { if (!co.isChat && co.firstUser == u.user) { co.PresentWindow(); return; } } Debug.WriteLine("Starting conversation with " + u.user.Passport); MsnConversation convo = msn.StartConversation(); //convo.Invite(u.user); Conversation c = new Conversation(u.user, convo, this); convos.Add(c); } } } private void startConvo(object sender, RowActivatedArgs args) { //Debug.WriteLine("Row activated " + args.Path.ToString()); TreeIter iter; if (contactStore.GetIter(out iter, args.Path)) { startConvo(iter); } } private void startConvo(object sender, EventArgs args) { TreeIter iter; TreeModel model; if (tree.Selection.GetSelected(out model, out iter)) { startConvo(iter); } } private void createGui() { connecting = new Window("LSMN - Connecting..."); connecting.BorderWidth = 20; connVBox = new VBox(); connVBox.Spacing = 20; connLabel = new Label("Connecting to Msn, please wait"); connVBox.PackStart(connLabel); cancButton = new Button(Stock.Cancel); cancButton.Clicked += new EventHandler(cancelClicked); connVBox.PackStart(cancButton); connecting.Add(connVBox); connecting.DeleteEvent += new DeleteEventHandler(windowDelete); contactList = new Window("LMSN - Contact list"); //contactList.BorderWidth = 3; contactList.SetDefaultSize(300, 600); contVBox = new VBox(); contVBox.Spacing = 3; contVBox.Homogeneous = false; contactStore = new TreeStore(typeof(Gdk.Pixbuf), typeof(string)); sw = new ScrolledWindow(); tree = new TreeView(); tree.Model = contactStore; tree.HeadersVisible = false; tree.RowActivated += new RowActivatedHandler(startConvo); TreeViewColumn col = tree.AppendColumn("Status", new CellRendererPixbuf(), "pixbuf", 0); col.Spacing = 0; tree.AppendColumn("Nickname", new CellRendererText(), "markup", 1); contactStore.SetSortFunc(0, new TreeIterCompareFunc(sortCompareFunc), (IntPtr)0, null); contactStore.SetSortColumnId(0, SortType.Ascending); sw.Add(tree); sw.Show(); contVBox.PackStart(sw, true, true, 0); imButton = new Button("IM"); imButton.HeightRequest = 25; imButton.Clicked += new EventHandler(startConvo); contVBox.PackStart(imButton, false, false, 0); contactList.Add(contVBox); contactList.DeleteEvent += new DeleteEventHandler(windowDelete); } private int sortCompareFunc(TreeModel model, TreeIter i1, TreeIter i2) { int sv1 = 0, sv2 = 0; foreach (User u in users) { if (u.ti.Equals(i1)) { sv1 = sortValueFor(u.user.Presence); } if (u.ti.Equals(i2)) { sv2 = sortValueFor(u.user.Presence); } } return Math.Sign(sv2 - sv1); } private int sortValueFor(MsnPresence presence) { switch (presence) { case MsnPresence.Offline: return 7; case MsnPresence.Available: return 8; case MsnPresence.Idle: return 1; case MsnPresence.BeRightBack: return 2; case MsnPresence.Away: return 3; case MsnPresence.OnThePhone: return 4; case MsnPresence.OutToLunch: return 5; case MsnPresence.Busy: return 6; } Debug.WriteLine("This isn't supposed to happen!!!!!!!!!!!!!!!"); return 0; } private void connected(object sender, EventArgs args) { if (sender == msn) { Console.WriteLine("Connected"); foreach (MsnUser user in msn.ContactList) { //User u = new User(user, contactStore.AppendValues(presenceIcons[(int)user.Presence], user.NickName + " <" + user.Passport + ">")); User u = new User(user, contactStore.AppendValues(presenceIcons[(int)user.Presence], user.NickName + " " + user.PersonalMessage + "")); users.Add(u); } connecting.HideAll(); contactList.ShowAll(); } else if (sender == convo) { convo.Invite(msn.ContactList.GetByPassport("lumpio2@hotmail.com")); } else if (sender is MsnConversation) { Debug.WriteLine("Conversation has been synced"); } } /*private string statusString(MsnPresence p) { switch (p) { case MsnPresence.Offline: return "-"; case MsnPresence.Available: return "+"; case MsnPresence.Busy: return "+BSY"; case MsnPresence.Idle: return "+..."; case MsnPresence.BeRightBack: return "+BRB"; case MsnPresence.Away: return "+AWA"; case MsnPresence.OnThePhone: return "+PHN"; case MsnPresence.OutToLunch: return "+LUN"; } return ""; }*/ private void invite(object sender, MsnInviteEventArgs args) { Conversation nc; Debug.WriteLine(args.Inviter.NickName + " invites you to a conversation"); foreach (Conversation c in convos) { if (!c.isChat && c.firstUser == args.Inviter) { c.Convo = args.Conversation; return; } } nc = new Conversation(null, args.Conversation, this); convos.Add(nc); } private void presence(object sender, MsnPresenceEventArgs args) { foreach (User u in users) { if (u.user == args.User) { //contactStore.SetValue(u.ti, 0, statusString(args.User.Presence)); contactStore.SetValue(u.ti, 0, presenceIcons[(int)args.User.Presence]); contactStore.SetValue(u.ti, 1, args.User.NickName + " " + args.User.PersonalMessage + ""); } } } private Gdk.Pixbuf[] getImageStrip(Gdk.Pixbuf buf, int width) { int numframes = buf.Width / width; Gdk.Pixbuf[] bufs = new Gdk.Pixbuf[numframes]; for (int i = 0; i < numframes; ++i) { bufs[i] = new Gdk.Pixbuf(buf, width * i, 0, width, buf.Height); } return bufs; } class Conversation { LuMsn msnc; public MsnUser firstUser = null; //public MsnUserCollection users; public Window win = null; Image typing; Notebook tabs; ScrolledWindow sw; TextView tv; TextView msgtv; // InkCollector inkcoll; Button sendbtn; MenuBar menubar; Queue msgQueue; Queue lastTrIDs; public bool isChat = false; TextTag ownmsgtag, othermsgtag, infomsgtag, useraddtag, errortag; System.Timers.Timer typingTimer; ThreadNotify typingTimerNotify; System.Timers.Timer selfTypingTimer; ThreadNotify selfTypingTimerNotify; MsnConversation convo = null; public MsnConversation Convo { get { return convo; } set { if (convo != null && convo.state != MsnConversation.ConnectionState.Closed) { convo.Close(); } convo = value; convo.UserJoined += new MsnConversation.JoinEventHandler(join); convo.UserLeft += new MsnConversation.LeaveEventHandler(leave); convo.MessageReceived += new MsnConversation.MessageEventHandler(message); convo.Connected += new MsnConnectEventHandler(connect); convo.MessageConfirmed += new MsnConversation.MessageConfirmationEventHandler(messageConfirm); } } bool windowOpened = false; public Conversation(MsnUser invite, MsnConversation convo, LuMsn msnc) { Convo = convo; this.msnc = msnc; this.firstUser = invite; //users = new MsnUserCollection(); msgQueue = new Queue(); lastTrIDs = new Queue(); typingTimerNotify = new ThreadNotify(new ReadyEvent(hideTyping)); typingTimer = new System.Timers.Timer(10000); typingTimer.Elapsed += new ElapsedEventHandler(hideTypingEvent); selfTypingTimerNotify = new ThreadNotify(new ReadyEvent(selfTyping)); selfTypingTimer = new System.Timers.Timer(5000); selfTypingTimer.AutoReset = true; selfTypingTimer.Elapsed += new ElapsedEventHandler(selfTypingEvent); } public void PresentWindow() { if (win != null) { win.Present(); } } private void createWindow(string nick, string passport) { VBox vbox; HBox hbox; VPaned vpaned; Menu menu; MenuItem pmi, mi; win = new Window("Conversation: " + nick + " <" + passport + ">"); win.SetDefaultSize(500, 500); win.DeleteEvent += new DeleteEventHandler(close); vbox = new VBox(); vbox.Spacing = 3; hbox = new HBox(); hbox.Spacing = 3; menubar = new MenuBar(); menu = new Menu(); pmi = new MenuItem("Conversation"); //pmi.Submenu = mi; mi = new MenuItem("Close"); mi.Activated += new EventHandler(close); pmi.Submenu = menu; menu.Append(mi); menubar.Append(pmi); hbox.PackStart(menubar); typing = new Image(msnc.iconUserTyping); hbox.PackStart(typing, false, false, 0); vbox.PackStart(hbox, false, false, 0); vpaned = new VPaned(); sw = new ScrolledWindow(); tv = new TextView(); tv.Editable = false; tv.WrapMode = WrapMode.Word; tv.CursorVisible = false; ownmsgtag = new TextTag(null); tv.Buffer.TagTable.Add(ownmsgtag); ownmsgtag.ForegroundGdk = new Gdk.Color(0x00, 0x00, 0xDD); ownmsgtag.Weight = Pango.Weight.Bold; othermsgtag = new TextTag(null); tv.Buffer.TagTable.Add(othermsgtag); othermsgtag.ForegroundGdk = new Gdk.Color(0xDD, 0x00, 0x00); othermsgtag.Weight = Pango.Weight.Bold; infomsgtag = new TextTag(null); tv.Buffer.TagTable.Add(infomsgtag); infomsgtag.ForegroundGdk = new Gdk.Color(0x80, 0x80, 0x80); infomsgtag.Style = Pango.Style.Italic; useraddtag = new TextTag(null); tv.Buffer.TagTable.Add(useraddtag); useraddtag.ForegroundGdk = new Gdk.Color(0x00, 0xA0, 0xA0); useraddtag.Style = Pango.Style.Italic; errortag = new TextTag(null); tv.Buffer.TagTable.Add(errortag); errortag.ForegroundGdk = new Gdk.Color(0xA0, 0x00, 0x00); errortag.Weight = Pango.Weight.Bold; sw.Add(tv); vpaned.Pack1(sw, true, true); hbox = new HBox(); hbox.Spacing = 3; tabs = new Notebook(); tabs.TabPos = PositionType.Bottom; sw = new ScrolledWindow(); msgtv = new TextView(); msgtv.WrapMode = WrapMode.Word; msgtv.KeyReleaseEvent += new KeyReleaseEventHandler(msgtvKeyUp); msgtv.Buffer.Changed += new EventHandler(textChanged); sw.Add(msgtv); tabs.AppendPage(sw, new Label("Text")); // inkcoll = new InkCollector(); // tabs.AppendPage(inkcoll, new Label("Ink")); hbox.PackStart(tabs); sendbtn = new Button("Send"); sendbtn.Clicked += new EventHandler(send); hbox.PackStart(sendbtn, false, false, 0); vpaned.Pack2(hbox, true, true); vbox.PackStart(vpaned); win.Add(vbox); win.ShowAll(); win.FocusChild = msgtv; typing.Hide(); } private void msgtvKeyUp(object sender, KeyReleaseEventArgs args) { if (args.Event.Key == Gdk.Key.Return) { if ((args.Event.State & Gdk.ModifierType.ControlMask) == 0) { send(null, null); } } } private void textChanged(object sender, EventArgs args) { if (!selfTypingTimer.Enabled) { if (msgtv.Buffer.Text != "") { convo.TypingNotifyMessage(); selfTypingTimer.Stop(); selfTypingTimer.Start(); } else if (msgtv.Buffer.Text == "") { selfTypingTimer.Stop(); } } } private void send(object sender, EventArgs args) { if (tabs.CurrentPage == 0) { string text = msgtv.Buffer.Text.TrimEnd(new char[]{'\r', '\n'}); msgtv.Buffer.Text = ""; if (text == "/debug") { Debug.WriteLine("CONVOS ON LIST: " + msnc.convos.Count); Debug.WriteLine("USERS ON LIST: " + convo.Users.Count); return; } else if (text == "/nudge") { addInfoMsg("Sending a nudge"); convo.NudgeMessage(); return; } else if (text.IndexOf("/ink") == 0) { if (text.Length < 5) { return; } string fn = text.Substring(5); FileStream fs; try { fs = File.OpenRead(fn); } catch (Exception e) { addText("File couldn't be opened: " + e.Message + "\r\n", errortag); return; } byte[] data = new byte[fs.Length]; fs.Read(data, 0, data.Length); convo.GifMessage(data); addText(msnc.msn.NickName + ":\r\n", ownmsgtag); addPixbuf(new Gdk.Pixbuf(new MemoryStream(data))); addText("\r\n", null); return; } addText(msnc.msn.NickName + ": ", ownmsgtag); addText(text + "\r\n", null); if (convo.Users.Count == 0) { msgQueue.Enqueue(text); convo.Invite(firstUser); } else { sendMessage(text); } }/* else { int w, h; Gdk.Pixmap map = inkcoll.Ink.Render(win.GdkWindow.Depth, out w, out h); Gdk.Pixbuf buf = new Gdk.Pixbuf(Gdk.Colorspace.Rgb, false, 8, w, h); buf = buf.GetFromDrawable(map, win.GdkWindow.Colormap, 0, 0, 0, 0, w, h); buf.Savev("temp_ink.gif", "gif", null, null); FileStream fs; try { fs = File.OpenRead("temp_ink.gif"); } catch (Exception e) { addText("File couldn't be opened: " + e.Message + "\r\n", errortag); return; } byte[] data = new byte[fs.Length]; fs.Read(data, 0, data.Length); convo.GifMessage(data); addText(msnc.msn.NickName + ":\r\n", ownmsgtag); addPixbuf(buf); addText("\r\n", null); inkcoll.Ink.Clear(); }*/ } private void sendMessage(string text) { selfTypingTimer.Stop(); UInt32 trid = convo.PlainTextMessage(text); lastTrIDs.Enqueue(trid); if (lastTrIDs.Count > 10) { lastTrIDs.Dequeue(); } } private void addText(string text, TextTag tag) { TextIter iter = tv.Buffer.EndIter; if (tag == null) { tv.Buffer.Insert(ref iter, text); } else { tv.Buffer.InsertWithTags(ref iter, text, new TextTag[]{tag}); } TextMark mark = tv.Buffer.CreateMark(null, tv.Buffer.EndIter, true); tv.ScrollMarkOnscreen(mark); tv.Buffer.DeleteMark(mark); } private void addPixbuf(Gdk.Pixbuf buf) { TextIter iter = tv.Buffer.EndIter; tv.Buffer.InsertPixbuf(ref iter, buf); TextMark mark = tv.Buffer.CreateMark(null, tv.Buffer.EndIter, true); tv.ScrollMarkOnscreen(mark); tv.Buffer.DeleteMark(mark); } private void addInfoMsg(string msg) { addText(msg + "\r\n", infomsgtag); } private void connect(object sender, EventArgs args) { if (convo.Users.Count == 0 && firstUser != null) { Debug.WriteLine("Inviting " + firstUser.NickName); convo.Invite(firstUser); }/* else { Debug.WriteLine("Connected, opening window"); createWindow(convo.Users[0].NickName); }*/ } private void join(object sender, MsnConversation.JoinEventArgs args) { if (firstUser == null) { firstUser = args.User; } if (windowOpened == false) { createWindow(args.User.NickName, args.User.Passport); windowOpened = true; } if (args.User != firstUser) { addText(args.User.ToString() + " has been added to conversation\r\n", useraddtag); } if (convo.Users.Count > 1) { isChat = true; win.Title = "Chat: " + convo.Users.Count + " users"; } //if (!isChat) { while (msgQueue.Count > 0) { sendMessage((string)msgQueue.Dequeue()); } //} //users.Add(args.User); } private void leave(object sender, MsnConversation.LeaveEventArgs args) { //users.Remove(args.User); if (args.Timeout) { addInfoMsg("The conversation timed out"); } else { addInfoMsg(args.User + " has left the conversation"); } if (isChat) { win.Title = "Chat: " + convo.Users.Count + " users"; } } private void message(object sender, MsnConversation.MessageEventArgs args) { if (!windowOpened) { Debug.WriteLine("--- OPENING WINDOW ---"); createWindow(convo.Users[0].NickName, convo.Users[0].Passport); } if (args.Message.MessageType == MsnMessageType.PlainText) { addText(args.Message.From.NickName + ": ", othermsgtag); addText(args.Message.Body + "\r\n", null); hideTyping(); } else if (args.Message.MessageType == MsnMessageType.TypingNotify) { //typing.Text = args.Message.From.NickName + " is typing..."; typing.Show(); typingTimer.Stop(); typingTimer.Start(); } else if (args.Message.MessageType == MsnMessageType.Gif) { Debug.WriteLine("GIF MESSAGE RECEIVED"); byte[] data = args.Message.DecodeBody(); if (data != null) { addText(args.Message.From.NickName + ": \r\n", othermsgtag); addPixbuf(new Gdk.Pixbuf(new MemoryStream(data))); addText("\r\n", null); } } } private void messageConfirm(object sender, MsnConversation.MessageConfirmationEventArgs args) { if (!args.Success) { if (lastTrIDs.Contains(args.TrID)) { addText("Message could not be delivered to all recipients\r\n", errortag); } } } private void selfTypingEvent(object sender, ElapsedEventArgs args) { selfTypingTimerNotify.WakeupMain(); } private void selfTyping() { convo.TypingNotifyMessage(); } private void hideTypingEvent(object sender, ElapsedEventArgs args) { typingTimerNotify.WakeupMain(); } private void hideTyping() { typing.Hide(); //typing.Text = ""; } public void Close() { Debug.WriteLine("closing conversation"); selfTypingTimer.Stop(); typingTimer.Stop(); convo.Close(); win.Destroy(); msnc.convos.Remove(this); } private void close(object sender, EventArgs args) { Close(); } } class User { public TreeIter ti; public MsnUser user = null; public User(MsnUser user, TreeIter ti) { this.user = user; this.ti = ti; } } } }