commit 0f02e9cd765ce7ede5f7fdd0f44d9d4516cc3a87 Author: psyc://psyced.org/~lynX <@> Date: Wed Feb 24 09:50:45 2010 +0100 last state we had in cvs diff --git a/AGENDA.txt b/AGENDA.txt new file mode 100644 index 0000000..337a16d --- /dev/null +++ b/AGENDA.txt @@ -0,0 +1,70 @@ +Meaning of states: +X: done +-: started or halfway done +?: I have no clue what this is about but it sounds important + : nothing done yet but may have a clue how to do it + (note: then make a sketch) + +VERSION 0.1 +=========== + [X] connect to a main psyc UNI via TCP + [X] make it working again automatically, write a package + [X] configfile parsing + [X] implement psyctext parser + [X] actually it is a mmptext-parser as well + [X] parse MMP and PSYCpackets + [ ] implement augment und diminish, but atm muve does not support it + [X] create state objects / machines + [X] handler architecture for psyc functions (requires state objects) + [X] interface definition (taken from bitkoenig's jaPSYC) + [X] basic layout for the classes and objects + [X] support for multiple GUI toolkits by design + [X] make gui main objects differ from application objects + [X] draw a communicative sketch of how it works + [X] debug GUI + +VERSION 0.2 +=========== + [ ] no fullstate sending + [ ] UDP support for some things + [?] what things? listen and wait what comes... + [ ] TCP server listening on port 4404 + [?] authorization checking -> psycauth + [ ] UDP server listening on port 4404 + [ ] same as for tcp + [X] implement graphical room interfaces + [X] use user input + [X] implement graphical user interfaces + [-] implement graphical friendlist + [X] think about command syntax (like: "/" for direct commands, "#" for uni commands, "!" for sending rooms a request_execute + +VERSION 0.3 +=========== + [ ] connect to friend unl's if you want to talk to them and they + are using a psyc interface (ask lynx for remote buddies) + [-] connect to distant rooms (how does it work? ask lynx) + is it even done via a server or is it the clients job? + +VERSION 0.4 +=========== + [ ] parse all messages from lynx's psyced meaningfully + +VERSION 0.5 +=========== + [-] find psyc protocol gagdets and errors and fix them + +VERSION 0.6 +=========== + [ ] make a reference implementation testsuite (tests are good) + that identifies severe and not-so-important lacks in + clients and/or servers. all real psyc clients should support a + minimum set of commands + + +VERSION 1.0 +=========== + [-] "plug-in" gui support (Tkinter, pyGTK, pyQT, wxPython, ...) + Note: basically you only need to implement some classes... + Tkinter GUI may serve as an example for more powerful clients + [-] user-defined handler architecture for "scripting" + ("users" have to write packages anyway) diff --git a/CONTRIBUTE.txt b/CONTRIBUTE.txt new file mode 100644 index 0000000..c204fc8 --- /dev/null +++ b/CONTRIBUTE.txt @@ -0,0 +1,21 @@ + Should you wish to contribute to this program, you can receive a + write access to the software repository from symlynX. By checking + code in, you grant us permission to license your contribution under + both the GNU General Public License and under other licenses. + + With the act of checking into the software repository, you guarantee + that you are either the author and copyright owner of the contribution + you are making, or that you have a license to use said contribution in + a way compatible with our terms of use. If you fail to do so, you + remain the sole responsible for your acting; no part of the + responsibility will be transferred to symlynX. + + This means, for example, that you cannot contribute code elements + which stem from an other GPL project, as this would not comply with + the dual licensing requirement described above. + + + +... if you don't like these terms, feel free to start your own repository ... + or maybe just talk with us about it? + diff --git a/COPYLEFT.txt b/COPYLEFT.txt new file mode 100644 index 0000000..960fe74 --- /dev/null +++ b/COPYLEFT.txt @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/FLOW.txt b/FLOW.txt new file mode 100644 index 0000000..841cf98 --- /dev/null +++ b/FLOW.txt @@ -0,0 +1,49 @@ + psyc://user@host psyc://host2/@place psyc://host/ + user unl some nice place my uni-host + ^ ^ ^ + | | | + | (connections) | + | | | +----------------------------------------------------------------- +| | +| MMP | +| | +----------------------------------------------------------------- + | ^ + packetReceived() | + (MMP, data) (packet generation) + | (state keeping) + v | +----------------------------------------------------------------- +| | +| PSYC | +| | +----------------------------------------------------------------- + | ^ + packetReceived() | + (MMP, PSYC) | + | | + v | +----------------------------------------------------------------- +| | +| PSYCMessagecenter | +| | +----------------------------------------------------------------- + ^ | ^ | + | received() | received() + register() | register() | + | v | v +--------------------------------- ------------------------------ +| | | | +| PSYCPackage Interface | | GUI | +| | | implementing PSYCPackage | +--------------------------------- ------------------------------ + | + update() + | + v +--------------------------------- +| | +| GUI | +| | +--------------------------------- diff --git a/GUI/Abstract/Gui.py b/GUI/Abstract/Gui.py new file mode 100644 index 0000000..77a49ad --- /dev/null +++ b/GUI/Abstract/Gui.py @@ -0,0 +1,29 @@ +"""Abstract Gui Module - serves as interface reference""" +import sys + +class UserGui: + def __init__(self): + if self.__class__ == UserGui: + raise "class UserGui is abstract!" + def set_model(self, model): self.model = model + + +class RoomGui: + def __init___(self): + if self.__class__ == RoomGui: + raise "class RoomGui is abstract!" + def set_model(self, model): self.model = model + + +class MainGui: + def __init__(self, argv): + if self.__class__ == MainGui: + raise "class MainGui is abstract!" + self.center = None + def run(self): + pass + def quit(self): + self.center.quit() + sys.exit(0) + def connect(self): + pass diff --git a/GUI/Abstract/Gui.pyc b/GUI/Abstract/Gui.pyc new file mode 100644 index 0000000..d4c933f Binary files /dev/null and b/GUI/Abstract/Gui.pyc differ diff --git a/GUI/Abstract/__init__.py b/GUI/Abstract/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/GUI/Abstract/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/GUI/Abstract/__init__.pyc b/GUI/Abstract/__init__.pyc new file mode 100644 index 0000000..0873385 Binary files /dev/null and b/GUI/Abstract/__init__.pyc differ diff --git a/GUI/Tkinter/Gui.py b/GUI/Tkinter/Gui.py new file mode 100644 index 0000000..8c4fa20 --- /dev/null +++ b/GUI/Tkinter/Gui.py @@ -0,0 +1,448 @@ +import GUI.Abstract.Gui as AbstractGui +import Tkinter, asyncore, sys + +from pypsyc.PSYC.PSYCRoom import PSYCPackage +class FriendList(Tkinter.Listbox): + def __init__(self): + self.packagename = "Friends gui" + + root = Tkinter.Toplevel(height=480, width=150) + root.title("friends online:") + + self.mapping = {} + self.model = None + + Tkinter.Listbox.__init__(self, root) + self.grid(row = 0, column = 0, sticky=Tkinter.E + Tkinter.W + Tkinter.N + Tkinter.S) + + self.bind("", self.on_leftclick) + self.bind("", self.on_rightclick) + + bframe = Tkinter.Frame(root) + bframe.grid(row=1, column = 0, sticky = Tkinter.W + Tkinter.E) + self.linklabel = Tkinter.Label(bframe, text="unlinked") + self.linklabel.grid(row=0, column = 0, sticky = Tkinter.W) + #u = Tkinter.Button(bframe, text="update", command=self.update_friends) + #u.grid(row=0, column = 1, sticky=Tkinter.E) + + def set_model(self, model): self.model = model + + def set_status(self, status): + if status == "_notice_link": + self.linklabel["text"] = "link" + elif status == "_notice_unlink": + self.linklabel["text"] = "unlink" + + def on_leftclick(self, event): + if self.selection_get(): + self.model.set_target(self.selection_get()) + self.model.set_mc("_internal_message_private_window_popup") + self.model.castmsg() + + def on_rightclick(self, event): + if self.selection_get(): + #print "we should pop up a context menu for", self.selection_get() + self.model.set_target(self.selection_get()) + self.model.set_mc("_request_examine") + self.model.send() + def received(self, source, mc, mmp, psyc): + # das print-zeugs is doch nicht ganz so nutzlos! + # muss es aber werden, oder? + if mc == "_notice_friend_present": + self.present(mmp.get_source()) + elif mc == "_notice_friend_absent": + self.absent(mmp.get_source()) + elif mc == "_print_notice_friend_present": + #print "friend present", psyc._query("_friends") + pass + elif mc == "_print_notice_friend_absent": + #print "friend absent", psyc._query("_friends") + pass + elif mc == "_print_list_friends_present": + #print "friends present", psyc._query("_friends") + #print psyc.get_text() + for friend in psyc._query("_friends").split(" "): + print friend + print "friends:", psyc.get_text() + elif mc == "_notice_link" or mc == "_notice_unlink": + self.set_status(mc) + + def present(self, nick): + if not self.mapping.has_key(nick): + self.mapping[nick] = self.size() # where we insert it + self.insert(self.size(), nick) + + def absent(self, nick): + if self.mapping.has_key(nick): + self.delete(self.mapping[nick]) + + +from pypsyc.PSYC import parsetext, get_user +class UserGui(AbstractGui.UserGui): + def __init__(self): + AbstractGui.UserGui.__init__(self) + self.model = None + self.windows = {} + + def makewindow(self, source): + # better check it again... + if self.windows.has_key(source): + return + + win = Tkinter.Toplevel() + win.title("Dialog with " + source) + win.protocol("WM_DELETE_WINDOW", lambda: self.deletewindow(source)) + dframe = Tkinter.Frame(win) + displayfield = Tkinter.Text(dframe, width=50, height=15, + state=Tkinter.DISABLED, + wrap=Tkinter.WORD) + displayfield.grid(row=0, column=0) + scrollbar = Tkinter.Scrollbar(dframe, + command=displayfield.yview) + scrollbar.grid(row=0, column=1, sticky = Tkinter.N + Tkinter.S) + displayfield.config(yscrollcommand=scrollbar.set) + dframe.pack() + + entryfield = Tkinter.Text(win, width=54, height=5) + entryfield.pack() + frame = Tkinter.Frame(win) + entrybutton = Tkinter.Button(frame, text="send", + command=lambda: self.say(source)) + + entrybutton.grid(row = 0, column = 0) + frame.pack() + self.windows[source] = {"window" : win, + "displayfield" : displayfield, + "entryfield" : entryfield, + "button" : entrybutton} + + def deletewindow(self, source): + self.windows[source]["window"].destroy() + del self.windows[source] + + def append_text(self, displayfield, text, encoding="iso-8859-1"): + displayfield["state"] = Tkinter.NORMAL + displayfield.insert(Tkinter.END, + text.strip().decode(encoding) + '\n') + displayfield["state"] = Tkinter.DISABLED + displayfield.yview(Tkinter.END) + + def get_input(self, entryfield, encoding="iso-8859-1"): + text = entryfield.get(0.0, Tkinter.END).encode(encoding) + entryfield.delete(0.0, Tkinter.END) + return text + + def say(self, source): + self.model.set_target(source) + self.model.set_mc("_message_private") + + self.model.psyc._assign("_nick", get_user(self.model.center.ME())) + + text = self.get_input(self.windows[source]["entryfield"]) + self.model.set_text(text.strip()) + + self.append_text(self.windows[source]["displayfield"], "Du sagst: " + text) + self.model.send() + + def received(self, source, mc, mmp, psyc): + if mc == "_message_private": + source = mmp.get_source() + if not self.windows.has_key(source): + self.makewindow(source) + self.append_text(self.windows[source]["displayfield"], + get_user(source)+":"+parsetext(mmp, psyc)) + elif mc == "_internal_message_private_window_popup": + target = mmp._query("_target") + print "target:", target + if not self.windows.has_key(target): + self.makewindow(target) + + +from pypsyc.PSYC import parsetext +class RoomGui(AbstractGui.RoomGui, Tkinter.Toplevel): + def __init__(self): + Tkinter.Toplevel.__init__(self) + self.model = None + + self.buffers = {} + self.topics = {} + +## self.menu = Tkinter.Menu(self) +## self.config(menu=self.menu) + +## options = Tkinter.Menu(self.menu) +## options.add_command(label="show nicklist", +## command=self.show_nicklist) +## options.add_command(label="hide nicklist", +## command=self.hide_nicklist) +## self.menu.add_cascade(label ="Options", menu=options) + + self.topiclabel = Tkinter.Label(self, text="dummy topic") + + mainframe = Tkinter.Frame(self) + + self.textfield = Tkinter.Text(mainframe, + wrap=Tkinter.WORD) + self.textfield.config(state=Tkinter.DISABLED) + self.textfield.grid(row=0, column=0, + sticky=Tkinter.W + Tkinter.N + Tkinter.S) + + scrollbar = Tkinter.Scrollbar(mainframe, + command = self.textfield.yview) + scrollbar.grid(row=0, column=1, sticky = Tkinter.N + Tkinter.S) + + self.textfield.config(yscrollcommand=scrollbar.set) + + self.nicklist = Tkinter.Listbox(mainframe) + + entryframe = Tkinter.Frame(self) + self.entryfield = Tkinter.Entry(entryframe) + self.entryfield.grid(sticky = Tkinter.E + Tkinter.W) + + self.bleiste = Tkinter.Frame(self) + self.placebuttons = {} + + l = Tkinter.Label(self.bleiste, + text="|") + l.grid(row = 0, column = 0, sticky = Tkinter.W) + + self.topiclabel.grid(row=0, sticky = Tkinter.W) + mainframe.grid(row=1, sticky = Tkinter.W) + entryframe.grid(row=2, sticky = Tkinter.W) + self.bleiste.grid(row=3, sticky = Tkinter.W) + + self.bind("", self.say) + + def append_text(self, text, encoding="iso-8859-1"): + self.textfield["state"] = Tkinter.NORMAL + self.textfield.insert(Tkinter.END, + text.decode(encoding) + '\n') + self.textfield["state"] = Tkinter.DISABLED + self.textfield.yview(Tkinter.END) + + def received(self, source, mc, mmp, psyc): + ## evil + try: + context = mmp._query("_context").lower() +## print context + if mc.startswith("_notice_place_enter"): + ## if _source == ME eigentlich... + if not self.placebuttons.has_key(context): + self.add_room(context) + if not self.buffers.has_key(context): + self.buffers[context] = "" + self.buffers[context] += parsetext(mmp, psyc) + '\n' + if self.model.get_context() == context: + self.append_text(parsetext(mmp, psyc)) + elif mc.startswith("_notice_place_leave"): + self.buffers[context] += parsetext(mmp, psyc) + '\n' + if self.model.get_context() == context: + self.append_text(parsetext(mmp, psyc)) + elif mc == '_message_public': + text = psyc._query("_nick") + if psyc._query("_action") != "": + text += " " + psyc._query("_action") + text += ": " + parsetext(mmp, psyc) + if self.model.get_context() == context: + self.append_text(text) + self.buffers[context] += text + '\n' + elif mc == "_status_place_topic": + self.topics[mmp.get_source().lower()] = parsetext(mmp, psyc) + ## evil + except KeyError: + print "KeyError:", context + + def get_input(self, encoding="iso-8859-1"): + text = self.entryfield.get().encode(encoding) + self.entryfield.delete(0, Tkinter.END) + return text + + def say(self, event): + text = self.get_input() + if text and text[0] == '/': + # we have a command + # if we know the command, we set the appropiate mc + # else we do _request_execute + if text.startswith("/join") and text.__len__() > 16: + # 16 == len(/join psyc://h/@r) + self.model.set_mc("_request_enter") + self.model.set_target(text.split(" ")[1]) + self.model.send() + elif text.startswith("/part"): + # wie waers mit /part_logout, part_home, part_type, ... + self.model.set_mc("_request_leave") + self.model.set_target(self.model.get_context()) + self.model.send() + + elif text.startswith("/quit"): + self.model.set_mc("_request_execute") + self.model.set_target(self.model.center.ME()) + self.model.set_text("/quit") + self.model.send() + elif text.startswith("/connect"): + foo = len(text.split(" ")) + if foo == 2: + self.model.center.connect(text.split(" ")[1]) + elif foo == 3: + self.model.center.connect(text.split(" ")[1], text.split(" ")[2]) + else: + self.model.set_target(self.model.center.ME()) + self.model.set_mc("_request_execute") + self.model.set_text(text) + self.model.send() + elif text and text[0] == "#": + self.model.set_target(self.model.center.ME()) + self.model.set_mc("_request_execute") + self.model.set_text("/" + text[1:]) + self.model.send() + elif text and text[0] == "!": + self.model.set_target(self.model.get_context()) + self.model.set_mc("_request_execute") + self.model.set_text(text) + self.model.send() + elif text: +## print "msg to", self.model.get_context() + self.model.set_target(self.model.get_context()) + self.model.set_mc("_message_public") + self.model.set_text(text) + self.model.send() + + def get_topic(self, context = None): + if not context: context = self.model.get_context() + if self.topics.has_key(context): + return self.topics[context] + else: return "" + + def change_room(self, room): + # gucken ob wir nicklist hatten, u.u. loeschen + + # dieses ding sollte eigentlich ne eigene Klasse haben... + a = self.model.get_context() + if a and self.placebuttons.has_key(a): + self.placebuttons[a]["relief"] = Tkinter.RAISED + self.placebuttons[room]["relief"] = Tkinter.SUNKEN + self.model.set_context(room) + self.title("on " + room) + short = room[room.rfind("@"):] # kurzform fuer raum... nicht ideal? + self.topiclabel["text"] = short + ":" + self.get_topic() + + self.textfield.config(state=Tkinter.NORMAL) + self.textfield.delete(1.0, Tkinter.END) + self.textfield.insert(Tkinter.END, self.buffers[room]) + self.textfield.config(state=Tkinter.DISABLED) + self.textfield.yview(Tkinter.END) + + def add_room(self, room): + short = room[room.rfind("@"):] + button = Tkinter.Button(self.bleiste, text=short, + command=lambda: self.change_room(room)) + self.placebuttons[room] = button + button.grid(row=0, column = len(self.placebuttons) - 1, + sticky = Tkinter.W) + + def delete_room(self, roomname): + # delete the button? + pass + +## def hide_nicklist(self): +## self.nicklist.grid_forget() + +## def show_nicklist(self): +## self.nicklist.grid(row=0, column=2, +## sticky = Tkinter.E + Tkinter.N + Tkinter.S) + + + +from pypsyc.MMP.MMPState import MMPState +from pypsyc.PSYC.PSYCState import PSYCState +from pypsyc.MMP import _isModifier +class MainWindow(Tkinter.Tk): + def __init__(self, center = None): + Tkinter.Tk.__init__(self) + self.center = center + + self.title("pyPSYCgui - simple psyc client - see http://www.psyc.eu") + + self.menu = Tkinter.Menu(self) + self.config(menu=self.menu) + + filemenu = Tkinter.Menu(self.menu) + filemenu.add_command(label="Quit", command=self.quit) + self.menu.add_cascade(label ="File", menu=filemenu) + + connectionmenu = Tkinter.Menu(self.menu) + connectionmenu.add_command(label="connect", command=self.connect) + self.menu.add_cascade(label="Connections", menu=connectionmenu) + frame = Tkinter.Frame(self) + self.displayField = Tkinter.Text(frame, height=20, width=60, state=Tkinter.DISABLED) + self.displayField.grid(row=0, column=0) + scrollbar = Tkinter.Scrollbar + scrollbar = Tkinter.Scrollbar(frame, + command = self.displayField.yview) + scrollbar.grid(row=0, column=1, sticky = Tkinter.N + Tkinter.S) + self.displayField.config(yscrollcommand=scrollbar.set) + frame.pack() + # funzt eh net +## self.scrollbar = Tkinter.Scrollbar(self) +## self.scrollbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) +## self.scrollbar.config(command=self.displayField.yview) + self.inputField = Tkinter.Text(self, height=5, width=60) + self.inputField.pack() + Tkinter.Button(self, text="send", command=self.input).pack( + side=Tkinter.LEFT) + + def connect(self, host = ''): + self.center.connect(host) + + def write(self, string): + self.displayField.config(state=Tkinter.NORMAL) + self.displayField.insert(Tkinter.END, string) + self.displayField.config(state = Tkinter.DISABLED) + + def input(self): +## print "TkinterGui::MainWindow::input" + cmd = self.inputField.get(1.0, Tkinter.END) + self.inputField.delete(1.0, Tkinter.END) + state = 'mmp' + mmp = MMPState() + psyc = PSYCState() + for line in cmd.split('\n'): + if line == ".": + #end of packet + break + if line == "" or (not _isModifier(line) + and state == 'mmp'): + state = 'psyc' + + if state == 'mmp': + mmp.set_state(line) + continue + if state == 'psyc': + if _isModifier(line): + psyc.set_state(line) + elif line.__len__() and line[0] == '_': + psyc.set_mc(line) + else: + psyc.append_text(line) + + self.center.send(mmp, psyc) + + +class Application(AbstractGui.MainGui): + def __init__(self, argv, center): + AbstractGui.MainGui.__init__(self, argv) + + self.mainWindow = MainWindow(center) + + ## nah... das is eigentlich auch evil ;) + sys.stdout = self.mainWindow + + def socket_check(self): + asyncore.poll(timeout=0.0) + # das hier is auch noch doof... + self.mainWindow.after(100, self.socket_check) + def run(self): + self.socket_check() + Tkinter.mainloop() + + diff --git a/GUI/Tkinter/__init__.py b/GUI/Tkinter/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/GUI/Tkinter/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/GUI/__init__.py b/GUI/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/GUI/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/GUI/__init__.pyc b/GUI/__init__.pyc new file mode 100644 index 0000000..2b8ddcc Binary files /dev/null and b/GUI/__init__.pyc differ diff --git a/GUI/wx/Gui.py b/GUI/wx/Gui.py new file mode 100644 index 0000000..7dbb5f6 --- /dev/null +++ b/GUI/wx/Gui.py @@ -0,0 +1,332 @@ +# ask fippo if we need all this +#import pypsyc.GUI.Abstract.Gui as AbstractGui +import sys +#from pypsyc.PSYC.PSYCRoom import PSYCPackage + +# we need this +from wxPython.wx import * +from wxPython.lib.rcsizer import RowColSizer +from wxPython.lib.grids import wxFlexGridSizer +#from wxPython.lib.mixins.listctrl import wxColumnSorterMixin, wxListCtrlAutoWidthMixin +# have to read about Create_Id() so we don't have to bother with the numbers +ID_ABOUT = 1002 +ID_CONNECT = 1001 +ID_DISCONNECT = 10011 +ID_STATUS = 9901 +ID_MENU = 9902 +ID_BUDDY_LIST = 9903 +ID_BUDDY_LIST_DK = 990301 +ID_EXIT = 1099 +ID_CLOSECHATNOTEBOOK = 2099 +ID_CLOSECHATNOTE = 2098 + + +class Friend: + """ a user object """ + def __init__(self, uni, unl='255.255.255.255'): + self.uni = str(uni) + self.unl = str(unl) + self.chatting = 0 + self.tab = None + + def chat(self, inst): + # das hier sollte zu dem schon bestehenden fenster extrawin(was direkt beim + # starten des cients erzeugt wird und beim connecten sichtbar wird + # ein tab hinzufügen + if self.chatting == 1: + inst.notebook.Show(True) + # focus the window where the chat is taking place + # still a bit buggy + # if you close a tab self.tab is wrong + inst.notebook.SetSelection(self.tab) + + else: + panel = wxPanel(inst.notebook, -1) + + button = wxButton(panel, ID_CLOSECHATNOTE, "close chat") + button2 = wxButton(panel, ID_STATUS, "online") + nick_box = wxTextCtrl(panel, -1, style=wxTE_READONLY) + box = wxBoxSizer(wxHORIZONTAL) + box.Add(nick_box, 1,wxEXPAND) + box.Add(button2,0,wxEXPAND) + box.Add(button,0,wxEXPAND) + + text_box = wxTextCtrl(panel, -1, style=wxTE_MULTILINE|wxTE_READONLY) + entry_box = wxTextCtrl(panel, -1, style=wxTE_MULTILINE, size=wxDefaultSize) + + sizer = RowColSizer() + #sizer.Add(button, pos=(1,1)) + #sizer.Add(nick_box, pos=(1,2), flag=wxEXPAND) + #sizer.Add(button2, pos=(1,3)) + sizer.Add(box, pos=(1,1), colspan=3,flag=wxEXPAND) + sizer.Add(text_box, pos=(2,1),flag=wxEXPAND, colspan=3) + sizer.Add(entry_box, pos=(3,1),flag=wxEXPAND, colspan=2) + sizer.AddGrowableCol(1) + #sizer.AddGrowableCol(2) + sizer.AddGrowableRow(2) + panel.SetSizer(sizer) + inst.notebook.AddPage(panel, self.uni, select=1) + self.tab = inst.notebook.GetSelection() + self.chatting = 1 + + def getStatus(self): + return 'On' + + def stop_chat(self): + self.chatting = 0 + self.tab = None + # do whatever has to be done + + + + +class TabBook(wxFrame): + """ do the actual displaying """ + def __init__(self, parent, ID, title, pos=wxDefaultPosition, size=wxDefaultSize, style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + menu = wxMenu() + menu.Append(ID_DISCONNECT, "&Disconnect", "bye-bye") + menu.Append(ID_ABOUT, "&About", "tritratrullala") + menu.AppendSeparator() + menu.Append(ID_EXIT, "&Exit", "leave us") + menuBar = wxMenuBar() + menuBar.Append(menu, "&File"); + self.SetMenuBar(menuBar) + self.notebook = wxNotebook(self, -1) + + status_panel = wxPanel(self.notebook, -1) + button = wxButton(status_panel, ID_CLOSECHATNOTEBOOK, "close status win") + self.notebook.AddPage(status_panel, 'status') + + # event handling + EVT_BUTTON(self, ID_CLOSECHATNOTEBOOK, self.OnCloseMe) + #EVT_BUTTON(self, ID_CLOSECHATNOTE, self.OnCloseChat) + EVT_CLOSE(self, self.OnCloseWindow) + + def newChat(self, who): + #todo: check if there is already a 'chat' with that who, make a nice panel, nicklist option? + panel = wxPanel(self.notebook, -1) + button = wxButton(panel, ID_CLOSECHATNOTE, "close chat") + self.notebook.AddPage(panel, who, select =1) + + # event methods + def OnCloseChat(self, event): + where = self.notebook.GetSelection() + t = self.notebook.GetPageText(where) + self.notebook.DeletePage(where) + self.notebook.Show(True) + event.Skip() + return t + + def OnCloseMe(self, event): + self.Show(False) + #self.Close(true) + event.Skip() + + def OnCloseWindow(self, event): + self.Show(False) + #self.Destroy() + event.Skip() + + + + +class UserGui(wxFrame): + """ handle the "querys" / chats with a single user """ + def __init__(self, parent, ID, title, pos=wxDefaultPosition, size=wxDefaultSize, style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos, size, style) + menu = wxMenu() + menu.Append(ID_DISCONNECT, "&Disconnect", "bye-bye") + menu.Append(ID_ABOUT, "&About", "tritratrullala") + menu.AppendSeparator() + menu.Append(ID_EXIT, "&Exit", "leave us") + menuBar = wxMenuBar() + menuBar.Append(menu, "&File"); + self.SetMenuBar(menuBar) + self.notebook = wxNotebook(self, -1) + + status_panel = wxPanel(self.notebook, -1) + button = wxButton(status_panel, ID_CLOSECHATNOTEBOOK, "close status win") + self.notebook.AddPage(status_panel, 'status') + + # event handling + EVT_BUTTON(self, ID_CLOSECHATNOTEBOOK, self.OnCloseMe) + #EVT_BUTTON(self, ID_CLOSECHATNOTE, self.OnCloseChat) + EVT_CLOSE(self, self.OnCloseWindow) + + def newChat(self, who): + #todo: check if there is already a 'chat' with that who, make a nice panel, nicklist option? + panel = wxPanel(self.notebook, -1) + button = wxButton(panel, ID_CLOSECHATNOTE, "close chat") + self.notebook.AddPage(panel, who, select =1) + + # event methods + def OnCloseChat(self, event): + where = self.notebook.GetSelection() + t = self.notebook.GetPageText(where) + self.notebook.DeletePage(where) + self.notebook.Show(True) + event.Skip() + return t + + def OnCloseMe(self, event): + self.Show(False) + #self.Close(true) + event.Skip() + + def OnCloseWindow(self, event): + self.Show(False) + #self.Destroy() + event.Skip() + + +class FriendList(wxFrame): + """ buddy list and other stuff """ + def __init__(self, parent, ID, title, pos=wxDefaultPosition, size=wxSize(190, 400), style=wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, parent, ID, title, pos , size, style) + + # menubar, statusbar et al + self.CreateStatusBar() + self.SetStatusText("welcome to pyPSYC") + menu = wxMenu() + menu.Append(ID_CONNECT, "&Connect", "connect to the net") + menu.Append(ID_ABOUT, "&About", "tritratrullala") + menu.AppendSeparator() + menu.Append(ID_EXIT, "&Exit", "leave us") + menuBar = wxMenuBar() + menuBar.Append(menu, "&File"); + self.SetMenuBar(menuBar) + + + ##'buddy' list, perhaps ;]] + self.SampleList= [] + self.buddylist_dict ={} + #self.BuddyList = wxListBox(self , ID_BUDDY_LIST,style=wxLB_NEEDED_SB|wxLB_SINGLE, choices=self.SampleList) + self.BuddyList = wxListCtrl(self, ID_BUDDY_LIST, style=wxLC_REPORT|wxLC_SINGLE_SEL|wxSUNKEN_BORDER) + self.BuddyList.InsertColumn(0, "ST") + self.BuddyList.InsertColumn(1, "Nick")# , wxLIST_FORMAT_RIGHT) + self.BuddyList.SetColumnWidth(0, 20) + ##end buddy list + + # define the buttons and so on at the bottom of the window + self.status = wxComboBox(self, ID_STATUS, "", choices=["", "This", "is a", "Place", "to put commands"], size=(150,-1), style=wxCB_DROPDOWN) + self.menu_button = wxButton( self, ID_MENU, 'pyPSYC') + self.exit_button = wxButton( self, ID_EXIT, 'exit') + self.con_menu = wxBoxSizer(wxHORIZONTAL) + self.con_menu.Add(self.menu_button, 1, wxALIGN_BOTTOM) + self.con_menu.Add(self.exit_button, 1, wxALIGN_BOTTOM) + + sizer = wxFlexGridSizer(3, 0 , 0,0) + sizer.Add(self.BuddyList, 1, wxGROW) + sizer.Add(self.con_menu, 1,wxGROW) + sizer.Add(self.status, 1,wxGROW) + sizer.AddGrowableRow(0) + sizer.AddGrowableCol(0) + # do somethign so that the buttons don't vanish in a too small window + # this is h4x0r-style but does the job at the moment + sizer.SetItemMinSize(self.BuddyList, 30, 10) + sizer.SetMinSize(wxSize(200,280)) + self.SetSizer(sizer) + self.SetAutoLayout(true) + ##dunno where to put it at the moment,but believe it shouldn't be here + # wir machen uns ne instanz von dem fenster wo alle chats rein kommen sollen + self.extrawin = TabBook(self, -1, "blah blubb", size=(800, 400), style = wxDEFAULT_FRAME_STYLE) + # für buddy auswahl + self.currentBuddy = 0 + + # event handling + EVT_BUTTON( self, ID_EXIT, self.onExit ) + EVT_BUTTON(self, ID_CONNECT, self.doConnect) + EVT_BUTTON(self, ID_CLOSECHATNOTE, self.closeChat) + EVT_MENU( self, ID_EXIT, self.onExit) + EVT_MENU(self, ID_CONNECT, self.doConnect) + ##EVT_LEFT_DCLICK(self.BuddyList, self.onFriend) + EVT_LIST_ITEM_SELECTED(self, ID_BUDDY_LIST, self.OnBuddySelected) + # ect_activated muß weg sosnt wird zweimal nen chat geöffnet ;]] + EVT_LIST_ITEM_ACTIVATED(self, ID_BUDDY_LIST, self.onFriend) + + def PopulateBuddyList(self): + items = self.buddylist_dict.items() + for x in range(len(items)): + nick, obj = items[x] + self.BuddyList.InsertStringItem(x ,obj.getStatus()) + self.BuddyList.SetStringItem(x, 1,str(nick)) + + self.BuddyList.SetColumnWidth(0, wxLIST_AUTOSIZE) + self.BuddyList.SetColumnWidth(1, wxLIST_AUTOSIZE) + + def getColumnText(self, index, col): + item = self.BuddyList.GetItem(index, col) + return item.GetText() + + + def onExit(self, event): + """do magic stuff before closing""" + #disconnect() oder sowas + self.Close(TRUE) + + def doConnect(self , event): + #todo: all the socket stuff, way to much + """do even more fippo(psyc-proto) magic""" + # this list is created by some network magic + self.current_buddys = ['tim', 'tom', 'foo', 'neo', 'fippo', 'garrit', 'marder', 'troete', 'bar', '23', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen'] + + for user in self.current_buddys: + tt = Friend(user) + self.buddylist_dict[user] = tt + + #print self.buddylist_dict + #print '----\n' + ##self.extrawin = TabBook(self, -1, "blah blubb", size=(800, 400), style = wxDEFAULT_FRAME_STYLE) + ##self.otherWin = self.extrawin + self.PopulateBuddyList() + event.Skip() + + def OnBuddySelected(self, event): + self.currentBuddy = event.m_itemIndex + #print str(self.currentBuddy) +'--' + + def onFriend(self, event): + #todo: catch the case that there is no extrawin(we aren't connected) + """open a new tab in the usergui""" + tt = str(self.getColumnText(self.currentBuddy, 1)) + t = self.buddylist_dict[tt] + t.chat(self.extrawin) + self.extrawin.Show(True) + #print self.getColumnText(self.currentBuddy, 1) + event.Skip() + + def closeChat(self, event): + t = self.extrawin.OnCloseChat(event) + buddy = self.buddylist_dict[t] + buddy.stop_chat() + event.Skip() + + +class Application(wxApp): + def OnInit(self): + frame = FriendList(NULL, -1, "pyPSYC 0.0.0.0.0.1") + frame.Show(true) + self.SetTopWindow(frame) + #self.timer = wxTimer() + #self.timer.SetOwner(self.socket_check, 6666) + #self.timer.Start(5000) # alle 100 ms / 5 secs + #self.timer = wxPyTimer(self.socket_check) + #self.timer.Start(500) # alle 500 ms + return true + + def run(self): + # blah mainloop + from twisted.internet import wxsupport, reactor + wxsupport.install(self) + print "running reactor..." + reactor.run() + +## self.MainLoop() + + +## this has to change i guess +#app = PsycApp(0) +#app.MainLoop() + +##EOF diff --git a/GUI/wx/__init__.py b/GUI/wx/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/GUI/wx/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/GUI/wx/__init__.pyc b/GUI/wx/__init__.pyc new file mode 100644 index 0000000..f3a2724 Binary files /dev/null and b/GUI/wx/__init__.pyc differ diff --git a/GUI/wx/devGui.py b/GUI/wx/devGui.py new file mode 100644 index 0000000..4e46bcf --- /dev/null +++ b/GUI/wx/devGui.py @@ -0,0 +1,696 @@ +# be warned this is the _dev_ version of the wx gui +# perhaps it will work, perhaps it's all borked up, +# this is a plazground shouldn't be used on a day to +# day basis +import GUI.Abstract.Gui as AbstractGui +import asyncore, sys, os +import ConfigParser +from string import split, rfind, strip + +from pypsyc.PSYC.PSYCRoom import PSYCPackage +from pypsyc.PSYC.PSYCRoom import Friends as FriendsPackage +from pypsyc.PSYC import parsetext, get_user + +from wxPython.wx import * +from wxPython.stc import * + + +from wxPython.lib.rcsizer import RowColSizer +from wxPython.lib.grids import wxFlexGridSizer + +VERSION = 'pyPSYC-wx 0.0.1' + +# for linux/posix this should work +CONFIG_FILE = os.getenv("HOME") + "/.pypsyc/wx-config" + +# windows users should uncomment the next line and comment out the line above +# CONFIG_FILE = 'wx-config' + +ID_ABOUT = 1002 +ID_CONNECT = 1001 +ID_DISCONNECT = 10011 +ID_SAY = 3300 +ID_NOTEBOOK = 3301 +ID_STATUS = 9901 +ID_MENU = 9902 +ID_BUDDY_LIST = 9903 +ID_BUDDY_LIST_DK = 990301 +ID_EXIT = 1099 +ID_CLOSECHATNOTEBOOK = 2099 +ID_CLOSECHATNOTE = 2098 + +def opj(path): + """Convert paths to the platform-specific separator""" + return apply(os.path.join, tuple(path.split('/'))) + +class Tab(wxPanel): + def __init__(self, context, notebook): + self.notebook = notebook + wxPanel.__init__(self, self.notebook, -1) + #panel = wxPanel(self.notebook, -1) + button = wxButton(self, ID_CLOSECHATNOTE, "close chat") + #button2 = wxButton(self, ID_STATUS, "NOOP") + self.nick_box = wxTextCtrl(self, -1, style=wxTE_READONLY) + box = wxBoxSizer(wxHORIZONTAL) + box.Add(self.nick_box, 1,wxEXPAND) + #box.Add(button2,0,wxEXPAND) + box.Add(button,0,wxEXPAND) + + #scroller = wxScrolledWindow(self, -1, size=(-1,-1)) + self.text_box = wxTextCtrl(self, -1, style=wxTE_MULTILINE|wxTE_RICH2|wxTE_READONLY|wxTE_AUTO_URL, size=wxDefaultSize) + self.entry_box = wxTextCtrl(self, ID_SAY, style=wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB, size=wxDefaultSize) + + sizer = RowColSizer() + sizer.Add(box, pos=(1,1), colspan=3,flag=wxEXPAND) + sizer.Add(self.text_box, pos=(2,1),flag=wxEXPAND, colspan=3) + #sizer.Add(scroller, pos=(2,1),flag=wxEXPAND, colspan=3) + sizer.Add(self.entry_box, pos=(3,1),flag=wxEXPAND, colspan=2) + sizer.AddGrowableCol(1) + sizer.AddGrowableRow(2) + self.SetSizer(sizer) + + def append_text(self, text): + #text = text.decode('iso-8859-1') + if os.name == 'posix': + #self.text_box.SetEditable(True) + #ende = self.text_box.GetInsertionPoint() + #self.text_box.ShowPosition(ende+len(text)) + self.text_box.AppendText(text) + #self.text_box.SetInsertionPointEnd() + #ende = self.text_box.GetInsertionPoint() + #self.text_box.ShowPosition(ende+len(text)) + self.text_box.ScrollLines(2) + #self.text_box.SetEditable(False) + else: + self.text_box.WriteText(text) + self.text_box.ScrollLines(2) + + def clear_entry(self): + self.entry_box.Clear() + self.entry_box.SetInsertionPoint(0) + + def append_entry(self, text): + self.entry_box.AppendText(text) + + def set_topic(self, text): + self.nick_box.Clear() + self.nick_box.AppendText(text) + + def get_text(self): + return self.entry_box.GetValue() + +class FriendList(wxFrame, PSYCPackage): + def __init__(self, parent=None, ID=0, title='pyPSYC', center=None, config=None, pos=wxDefaultPosition, size=wxSize(190, 400), style=wxDEFAULT_FRAME_STYLE): + # bei mir is friendlist, firendlist und maingui in einem + # zumindestens sollte es das sein ob es so klappt weiss ich noch nicht + self.global_config = config + self.config = ConfigParser.ConfigParser() + self.config.read(CONFIG_FILE) + + if self.config: + pos = split(self.config.get('gui', 'wx_friend_pos'), ',') + size = split(self.config.get('gui', 'wx_friend_size'), ',') + + wxFrame.__init__(self, parent, ID, title, wxPoint(int(pos[0]), int(pos[1])) , wxSize(int(size[0]), int(size[1])), style) + PSYCPackage.__init__(self) + self.currentBuddy = None + self.center = center + self.model = None + self.packagename = "Friends gui" + # we have to register by ourselves + # perhaps it's not the way to do it but it works +# no.. it's not working.. --lynX +# self.center.register(FriendsPackage(self)) + + # menubar, statusbar et al + self.CreateStatusBar() + self.SetStatusText("welcome to pyPSYC") + menu = wxMenu() + menu.Append(ID_CONNECT, "&Connect", "connect to the net") + menu.Append(ID_ABOUT, "&About", "tritratrullala") + menu.AppendSeparator() + menu.Append(ID_EXIT, "&Exit", "leave us") + menuBar = wxMenuBar() + menuBar.Append(menu, "&File"); + self.SetMenuBar(menuBar) + + ##'buddy' list, perhaps ;]] + self.BuddyList = wxListCtrl(self, ID_BUDDY_LIST, style=wxLC_REPORT|wxLC_SINGLE_SEL|wxSUNKEN_BORDER) + self.BuddyList.InsertColumn(0, "ST") + self.BuddyList.InsertColumn(1, "Nick")# , wxLIST_FORMAT_RIGHT) + self.BuddyList.SetColumnWidth(0, 20) + ##end buddy list + + # define the buttons and so on at the bottom of the window + self.status = wxComboBox(self, ID_STATUS, "", choices=["", "This", "is a", "Place", "to put commands"], size=(150,-1), style=wxCB_DROPDOWN) + self.menu_button = wxButton( self, ID_MENU, 'pyPSYC') + self.exit_button = wxButton( self, ID_EXIT, 'exit') + self.con_menu = wxBoxSizer(wxHORIZONTAL) + self.con_menu.Add(self.menu_button, 1, wxALIGN_BOTTOM) + self.con_menu.Add(self.exit_button, 1, wxALIGN_BOTTOM) + + sizer = wxFlexGridSizer(3, 0 , 0,0) + sizer.Add(self.BuddyList, 1, wxGROW) + sizer.Add(self.con_menu, 1,wxGROW) + sizer.Add(self.status, 1,wxGROW) + sizer.AddGrowableRow(0) + sizer.AddGrowableCol(0) + # do something so that the buttons don't vanish in a too small window + # this is h4x0r-style but does the job at the moment + sizer.SetItemMinSize(self.BuddyList, 30, 10) + sizer.SetMinSize(wxSize(200,280)) + self.SetSizer(sizer) + self.SetAutoLayout(true) + + # event handling + EVT_BUTTON( self, ID_EXIT, self.onExit ) + EVT_BUTTON( self, ID_MENU, self.doConnect ) + #EVT_BUTTON(self, ID_CONNECT, self.doConnect) + #EVT_BUTTON(self, ID_CLOSECHATNOTE, self.closeChat) + EVT_MENU( self, ID_EXIT, self.onExit) + EVT_MENU(self, ID_CONNECT, self.doConnect) + #EVT_LEFT_DCLICK(self.BuddyList, self.onFriend) + #EVT_LIST_ITEM_SELECTED(self, ID_BUDDY_LIST, self.OnBuddySelected) + # ect_activated muss weg sonst wird zweimal nen chat geoeffnet ;]] + ##EVT_LIST_ITEM_ACTIVATED(self, ID_BUDDY_LIST, self.onFriend) + + def received(self, source, mc, mmp, psyc): + # muss ueberall sein, warum weiss ich noch nicht so genau + # .._present .._absent versteh ich also is es da;] + if mc == "_notice_friend_present": + self.present(mmp.get_source()) + elif mc == "_notice_friend_absent": + self.absent(mmp.get_source()) + elif mc == "_notice_link" or mc == "_notice_unlink" or mc == "_status_linked": + self.set_status(mc) + + def set_model(self, model): + self.model = model + + def present(self, nick): + """ do what has to be done if a friend turns up """ + # at the moment: + pass + #fuer das x muesste man wissen wie lang die buddy list bisher ist + #sprich vllt doch wieder nen dict dafuer bauen or so + #self.BuddyList.InsertStringItem(x ,'on') + #self.BuddyList.SetStringItem(x, 1, nick) + + def absent(self, nick): + """ do what has to be done if a friend leaves us """ + # at the moment also: + pass + + def set_status(self, status): + """ we should tell the user if the status changes """ + #print 'link status: ' + str(status) + + def OnBuddySelected(self, event): + self.currentBuddy = event.m_itemIndex + event.Skip() + + def doConnect(self, event, host = ''): + self.center.connect(host) + #print 'connecting...' + event.Skip() + + def onExit(self, event): + """ do stuff we have to do before exiting """ + #print 'exiting...' + pos = str(self.GetPositionTuple()[0]) + ', ' + str(self.GetPositionTuple()[1]) + size = str(self.GetSizeTuple()[0]) + ', ' + str(self.GetSizeTuple()[1]) + self.config.set('gui', 'wx_friend_pos', pos) + self.config.set('gui', 'wx_friend_size', size) + config_file = file(CONFIG_FILE, 'w') + self.config.write(config_file) + config_file.close() + + self.model.set_mc("_request_execute") + self.model.set_target(self.model.center.ME()) + self.model.set_text("/quit") + self.model.send() + self.Close(TRUE) + #sys.exit(0) + event.Skip() + +class UeberGui(wxFrame): + def __init__(self, parent=NULL, ID=-1, title='uebergui teil', status_tab = 1, pos=wxDefaultPosition, size=wxDefaultSize, style=wxDEFAULT_FRAME_STYLE): + self.config = ConfigParser.ConfigParser() + self.config.read(CONFIG_FILE) + + if self.config: + pos = split(self.config.get('gui', 'wx_room_pos'), ',') + size = split(self.config.get('gui', 'wx_room_size'), ',') + wxFrame.__init__(self, parent, ID, title, wxPoint(int(pos[0]), int(pos[1])) , wxSize(int(size[0]), int(size[1])), style) + #PSYCPackage.__init__(self) + self.notebook = wxNotebook(self, ID_NOTEBOOK) + + self.tabs = [] # ein nummer zu context mapping + self.querys = [] + self.buffers = {} # context : "chatter going on in this context" + self.tab_inst = {} # context : tab instanz + self.query_inst = {} # context : tab instanz + self.members = {} # dictionary a la { 'PSYC' : ['fippo', 'lethe', ...]} + + if status_tab == 1: + self.addTab('status') + EVT_TEXT_ENTER(self, ID_SAY, self.say) + EVT_NOTEBOOK_PAGE_CHANGED(self, ID_NOTEBOOK, self.OnTabChange) + EVT_CLOSE(self, self.onClose ) + EVT_TEXT(self, ID_SAY, self.EvtText) + + def EvtText(self, event): + #print 'EvtText: %s' % event.GetString() + event.Skip() + + def find_nick(self, wiesel): + positve_members= [] + members = self.members[self.model.get_context()] + for member in members: + member = strip(member) + if member.startswith(wiesel): + positve_members.append(member) + return positve_members + + def EvtChar(self, event): + #print 'EvtChar: ' + str(event.GetKeyCode()) + if event.GetKeyCode() == 9: + # hier muessen wir nick raten und so weiter + wiesel = self.tab_inst[self.model.get_context()].get_text() + wiesel2 = wiesel + wiesel = split(wiesel, ' ') + marder = self.find_nick(wiesel[-1]) + if len(marder) > 1: + print 't1' + self.tab_inst[self.model.get_context()].append_text(str(marder) + '\n') + elif len(marder) == 1: + print 't2' + nick = marder[0] + text = wiesel2 + pos = - len(wiesel[-1]) + text = text[:pos] + nick + self.tab_inst[self.model.get_context()].clear_entry() + self.tab_inst[self.model.get_context()].append_entry(text) + else: + print 't3' + else: + # hier passen wir einfach und reset'en den coutner + self.dep_count = 0 + event.Skip() + + def onClose(self,event): + print 'UeberGui.onCLose() worked' + + def say(self, event): + """ wird gerufen wenn jmd enter drückt """ + text = event.GetString() + #text = text.encode('iso-8859-1') + if text != '' and text[0] == '/': + # we have a command + # if we know the command, we set the appropiate mc + # else we do _request_execute + if text.startswith("/join") and text.__len__() > 16: + # 16 == len(/join psyc://h/@r) + self.model.set_mc("_request_enter") + self.model.set_target(text.split(" ")[1]) + self.model.send() + elif text.startswith("/part"): + # wie waers mit /part_logout, part_home, part_type, ... + self.model.set_mc("_request_leave") + self.model.set_target(self.model.get_context()) + self.model.send() + + elif text.startswith("/quit"): + self.model.set_mc("_request_execute") + self.model.set_target(self.model.center.ME()) + self.model.set_text("/quit") + self.model.send() + + elif text.startswith('/me') and text[3] == ' ': + self.model.set_target(self.model.get_context()) + self.model.set_mc('_message_public') + self.model.set_psycvar('_action', text[4:]) + self.model.set_text('') + self.model.send() + + elif text.startswith('/names'): + self.model.set_target(self.model.get_context()) + self.model.set_mc('_request_members') + self.model.set_text('') + self.model.send() + + #elif text.startswith('/lnames'): + # context = self.model.get_context() + # self.tab_inst[context].append_text(self.members[context][0] + '\n') + + elif text.startswith("/connect"): + foo = len(text.split(" ")) + if foo == 2: + self.model.center.connect(text.split(" ")[1]) + elif foo == 3: + self.model.center.connect(text.split(" ")[1], text.split(" ")[2]) + else: + self.model.set_target(self.model.center.ME()) + self.model.set_mc("_request_execute") + self.model.set_text(text) + self.model.send() + elif text != '' and text[0] == "#": + self.model.set_target(self.model.center.ME()) + self.model.set_mc("_request_execute") + self.model.set_text("/" + text[1:]) + self.model.send() + elif text != '' and text[0] == "!": + self.model.set_target(self.model.get_context()) + self.model.set_mc("_request_execute") + self.model.set_text(text) + self.model.send() + elif text != '' and text[-1] == '?': + self.model.set_target(self.model.get_context()) + self.model.set_mc("_message_public") + self.model.set_text(text) + self.model.send() + elif text != '': + #print "msg to", self.model.get_context() + self.model.set_target(self.model.get_context()) + self.model.set_mc("_message_public") + self.model.set_text(text) + self.model.set_psycvar('_action', 'testet') + self.model.send() + self.tab_inst[self.model.context].clear_entry() + event.Skip() + + def addTab(self, title): + panel = Tab(title, self.notebook) + if title != 'status': + short_title= title[rfind(title, '@'):] + self.model.set_context(title) + panel.append_text('heading over to ' + title + '\n') + self.model.set_target(self.model.get_context()) + self.model.set_mc("_request_members") + self.model.set_text('') + self.model.send() + + else: + short_title = title + self.notebook.AddPage(panel, short_title, select = 1) + + self.tab_inst[title] = panel + self.SetTitle(title) + EVT_CHAR(panel.entry_box, self.EvtChar) + + if title == 'status': + motd = 'Welcome to ' + VERSION + '\n for Q&A go to psyc://ve.symlynx.com/@PSYC \n or contact tim@trash-media.de for GUI problems \n for all the other problems go and talk to fippo!' + panel.append_text(motd) + + self.tabs.append(title) + #self.buffers[title] = '' + + def addQuery(self, title): + panel = Tab(title, self.notebook) + if title != 'status': + short_title= title[rfind(title, '~'):] + #self.model.set_context(title) + print dir(self.model) + panel.append_text('talking to ' + short_title + '\n') + #self.model.set_target(self.model.get_context()) + #self.model.set_mc("_request_members") + #self.model.set_text('') + #self.model.send() + else: + short_title = title + self.notebook.AddPage(panel, short_title, select = 1) + + self.query_inst[title] = panel + self.SetTitle(title) + if title == 'status': + motd = 'Welcome to ' + VERSION + '\n for Q&A go to psyc://ve.symlynx.com/@PSYC \n or contact tim@trash-media.de for GUI problems \n for all the other problems go and talk to fippo!' + panel.append_text(motd) + + self.querys.append(title) + self.source = self.querys[-1] + #print (self.querys) + #print '<<<<<<<<<<<<<<' + #self.buffers[title] = '' + + def OnTabChange(self, event): + old = event.GetOldSelection() + new = event.GetSelection() + self.SetTitle(self.tabs[new]) + self.model.set_context(self.tabs[new]) + #if self.buffers[self.model.context] != '': + # self.tab_inst[self.model.context].append_text(self.buffers[self.model.context]) + # self.buffers[self.model.context] = '' + event.Skip() + + def msg(self, text): + """ mehr text! """ + +class UserGui(UeberGui, AbstractGui.UserGui, PSYCPackage): + def __init__(self): + """ das hier is fuer querys """ + PSYCPackage.__init__(self) + UeberGui.__init__(self, status_tab = 0) + self.model = None + self.source = '' + #pass + + def say(self, event): + text = event.GetString() + #source self.tabs[] + source = self.source + self.model.set_target(source) + self.model.set_mc("_message_private") + self.model.psyc._assign("_nick", get_user(self.model.center.ME())) + + #text = self.get_input(self.windows[source]["entryfield"]) + self.model.set_text(text.strip()) + #self.append_text(self.windows[source]["displayfield"], "Du sagst: " + text) + self.model.send() + self.query_inst[source].append_text('Du> ' + text + '\n') + self.query_inst[source].clear_entry() + event.Skip() + + + def received(self, source, mc, mmp, psyc): + context = 'status' + if mmp._query("_source") != None: + context = mmp._query("_source").lower() + else: + context = source.lower() + + if mc == "_internal_message_private_window_popup": + # open a new tab + self.Show(True) + self.addQuery('test') + + elif mc == "_message_echo_private": + self.Show(True) + text = '' + text += 'Du>' + if context not in self.querys: + self.addQuery(context) + text += ' ' + parsetext(mmp, psyc) + self.query_inst[context].append_text(text + '\n') + + elif mc =='_message_private': + self.Show(True) + text = '' + text += 'Anderer_Nick>' + if context not in self.querys: + self.addQuery(context) + text += ' ' + parsetext(mmp, psyc) + self.query_inst[context].append_text(text + '\n') + + def set_model(self, model): + self.model = model + + def OnTabChange(self, event): + old = event.GetOldSelection() + new = event.GetSelection() + self.SetTitle(self.querys[new]) + self.source = self.querys[new] + #self.model.set_context(self.tabs[new]) + #if self.buffers[self.model.context] != '': + # self.tab_inst[self.model.context].append_text(self.buffers[self.model.context]) + # self.buffers[self.model.context] = '' + event.Skip() + + + +class RoomGui(UeberGui, AbstractGui.RoomGui, PSYCPackage): + def __init___(self): + """ das hier is fuer raeume """ + PSYCPackage.__init__(self) + UeberGui.__init__(self,pos=pos, size=size) + self.model = None + + EVT_CLOSE(self, self.onClose) + + def onClose(self,event): + #print 'RoomGui.onClose() worked' + pos = str(self.GetPositionTuple()[0]) + ', ' + str(self.GetPositionTuple()[1]) + size = str(self.GetSizeTuple()[0]) + ', ' + str(self.GetSizeTuple()[1]) + self.config.set('gui', 'wx_room_pos', pos) + self.config.set('gui', 'wx_room_size', size) + config_file = file(CONFIG_FILE, 'w') + self.config.write(config_file) + config_file.close() + #self.Show(False) + event.Skip() + + def set_model(self, model): + self.model = model + + def received(self, source, mc, mmp, psyc): + context = 'status' + if mmp._query("_context") != None: + context = mmp._query("_context").lower() + else: + context = source.lower() + #if psyc._query('_nick_place') != None: + # context = 'psyc://ve.symlynx.com/@' + psyc._query('_nick_place').lower() + #context = source.lower() + if mc.startswith('_notice_place_leave'): + if context not in self.tabs: + self.addTab(context) + text = parsetext(mmp, psyc) + self.tab_inst[context].append_text(text + '\n') + if self.members.has_key(context): + nick = psyc._query('_nick') + self.members[context].remove(nick) + + if mc.startswith('_notice_place_enter'): + self.Show(true) + if context not in self.tabs: + self.addTab(context) + text = parsetext(mmp, psyc) + self.tab_inst[context].append_text(text + '\n') + if self.members.has_key(context): + nick = psyc._query('_nick') + if nick not in self.members[context]: + self.members[context].append(nick) + + elif mc == '_message_public_question': + text = '' + text += str(psyc._query("_nick")) + if context not in self.tabs: + self.addTab(context) + text += ' fragt ' + parsetext(mmp, psyc) + self.tab_inst[context].append_text(text + '\n') + + elif mc.startswith('_status_place_topic') or mc.startswith('_notice_place_topic'): + topic = psyc._query('_topic') + context = source.lower() + if context not in self.tabs: + self.addTab(context) + self.tab_inst[context].set_topic(topic) + + elif mc == '_message_public': + text = '' + text += str(psyc._query("_nick")) + if context not in self.tabs: + self.addTab(context) + if psyc._query("_action"): + ptext = parsetext(mmp, psyc) + if ptext == '': + text += " " + psyc._query("_action") + else: + text += " " + psyc._query("_action") + '> ' + ptext + else: + text += "> " + parsetext(mmp, psyc) + #text = text.encode('iso-8859-1') + self.tab_inst[context].append_text(text + '\n') + #print '!!' + context + ': ' + text + + elif mc == '_status_place_members': + text = parsetext(mmp, psyc) + if context not in self.tabs: + self.addTab(context) + members = split(psyc._query('_members'), ', ') + self.members[context] = members + self.tab_inst[context].append_text(text + '\n') + + else: + # everything we don't know goes into the status tab + # hopefully parsetext doesn't crash with' bogus' pakets + text = source.lower() + ' >>> ' + text += parsetext(mmp, psyc) + self.tab_inst['status'].append_text(text + '\n') + + + +class MainWindow(PSYCPackage): + # eigentlich brauch das kein schwein oder??? + def __init__(self, argv): + """ was ich hiermit mache weiss ich noch net genau, eigentlich brauch + ich es net im moment lebt es für den devel handler""" + self.center = None + PSYCPackage.__init__(self) + + def title(self, text): + pass + + def run(self): + pass + + def quit(self): + self.center.quit() + sys.exit(0) + + def connect(self): + pass + + def write(self, text): + #print text + pass + +class MySplashScreen(wxSplashScreen): + def __init__(self, argv, center, config): + self.center = center + self.argv = argv + self.config = config + bmp = wxImage(opj("GUI/wx/psych2o.jpg")).ConvertToBitmap() + wxSplashScreen.__init__(self, bmp, + wxSPLASH_CENTRE_ON_SCREEN|wxSPLASH_TIMEOUT, + 4000, None, -1, + style = wxFRAME_NO_TASKBAR|wxSTAY_ON_TOP) + EVT_CLOSE(self, self.OnClose) + + def OnClose(self, evt): + frame = FriendList(NULL, -1, "pyPSYC 0.0.0.0.0.1", center=self.center, config=self.config) + frame.Show() + evt.Skip() + +class Application(wxApp): + def __init__(self, argv, center, config): + self.center = center + self.argv = argv + self.config = config + wxApp.__init__(self,0) + #print 'tt' + + def OnInit(self): + #self.frame = FriendList(NULL, -1, "pyPSYC 0.0.0.0.0.1", center=self.center, config=self.config) + #self.frame.Show(true) + #sys.stdout = self.frame + wxInitAllImageHandlers() + splash = MySplashScreen(self.argv, self.center, self.config) + self.SetTopWindow(splash) + splash.Show() + return True +## +## self.timer = wxPyTimer(self.socket_check) +## self.timer.Start(100) # alle 100 ms +## #print 'ttt' +## +## def socket_check(self): +## asyncore.poll(timeout=0.0) +## + def run(self): + # blah mainloop + #print 'tttt' + + from twisted.internet import wxsupport, reactor + wxsupport.install(self) + print "running reactor..." + reactor.run() diff --git a/GUI/wx/devGui.pyc b/GUI/wx/devGui.pyc new file mode 100644 index 0000000..cbb9d4b Binary files /dev/null and b/GUI/wx/devGui.pyc differ diff --git a/GUI/wx/psych2o.jpg b/GUI/wx/psych2o.jpg new file mode 100644 index 0000000..4814971 Binary files /dev/null and b/GUI/wx/psych2o.jpg differ diff --git a/GUI/wx/test.py b/GUI/wx/test.py new file mode 100644 index 0000000..6f08766 --- /dev/null +++ b/GUI/wx/test.py @@ -0,0 +1,7 @@ +## some sort of testing thingie +## damit man es sieht wies geht udn die Gui.py auch sonst importieren kann + +import Gui + +app = Gui.Application(0) +app.MainLoop() diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..5daab9f --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,24 @@ +pyPSYC depends on python (tested with 2.2.1) and tkinter +(note: if you want you can write qt, gtk, wxpython gui's etc) +also, this version depends on twisted python from twistedmatrix.com + +prior to starting you need to edit the config-example file and move it +to $HOME/.pypsyc/config + +on windows keep it in the same directory as chat_client.py and change +line 22 + config.read(os.getenv("HOME") + "/.pypsyc/config") +to + config.read("config") +This will soon be automated somehow. +start pyPSYC with `python chat_client.py` + +there are two command styles... local command which start with "/" +currently there are two of them, "/join psyc://host/@room" +(atm this works only if you are connected to host) +and "/part" which parts the current room. +/quit is also supported, although atm it is semantically an alias for #quit + +the other command style is commands starting with "#" +they are handled by your UNI with a _request_execute + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..08041c7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,26 @@ + + pyPSYC, a multiplexing PSYC client written in Python + Copyright (C) 2002, 2003 by Philipp "fippo" Hancke + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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. + + Taken from lynx license for the psyced: + + SPECIAL ENCOURAGEMENTS FOR PSYC DEVELOPERS. + + 1. Whenever you make derivates of this library publically available, be it + in source or binary form, please keep all PSYC network transactions + according to the current specification of the PSYC protocol as available + from http://www.psyc.eu. This will help keeping PSYC implementations + interoperable. + + 2. Whenever you release a derivative work of this software, please inform + the PSYC community of the existence of your new software by promoting it + on http://about.psyc.eu. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bcaaec8 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +# toplevel makefile +clean : + find . -name "*~" | xargs rm + find . -name "*.pyc" | xargs rm diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..65d6e4c --- /dev/null +++ b/README.txt @@ -0,0 +1,6 @@ +Obtain the newest version by git... + + git clone git://git.psyced.org/pypsyc + +See also http://about.psyc.eu/pypsyc + diff --git a/README.wx b/README.wx new file mode 100644 index 0000000..e75915d --- /dev/null +++ b/README.wx @@ -0,0 +1,55 @@ +WELCOME TO pyPSYC(with a funky wxWindows GUI) + +bugs & black-mail to tim@rash-media.de + +-> HOWTO TURN THE BEAST ALIVE + + * install wxPython from www.wxpython.org + + * extract the tar + + * edit config-example and put it into ~/.pypsyc/config + + * edit wx-config-example and put it into ~/.pypsyc/wx-config only copying it + there should be enough and you shouldn't edit it afterwards, it's only a + palce for the gui to remeber things like size, position and so on + + * windows users should only rename the two files, instead of moving them: + + config-example -> config + wx-config-example -> wx-config + + * windows users have to edit chat_client.py to make sure it looks for the + config file in the right place and you have to edit the GUI/wx/devGui.py + file also. + + * you now should be setup and ready to explore the world of psyc + + +-> OFFICIAL NOTES + + * you need wxPython 2.4.1 and Python 2.2 other versions might also work but + who knows + + +-> INOFFICIAL NOTES + + * versions of wxPython <2.4.1 have a nasty bug that prevents the msg window + from scrolling so you should upgrade + + * we have a nick completer now!!! shout out loud: "Hurraayy!" + + * bribe marder if you want a shiny-one-click windows installer + + * if you don't like the default speak action look into devGui.yp and change + if, shoudl be quite easy + + +-> STATUS OF THE BEAST + + * there are reports that pyPSYC crashes with win2k, we are looking into that + + * querys don't work + + * you can't close the tabs ;]] + diff --git a/config-example b/config-example new file mode 100644 index 0000000..d2cf8e7 --- /dev/null +++ b/config-example @@ -0,0 +1,15 @@ +# should be changed to support http://www.psyc.eu/storage + +[main] +uni: psyc://host/~user +password: geheim + +[packages] +friends: enabled +conferencing: disabled +biff: disabled +user: disabled +devel: enabled + +[gui] +type: wx diff --git a/console-client.py b/console-client.py new file mode 100755 index 0000000..b6d8156 --- /dev/null +++ b/console-client.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# +# broken - fix this some +# +import asyncore +import ConfigParser +import os +import sys +# here we start importing our own modules +from pypsyc.PSYC.PSYCMessagecenter import PSYCMessagecenter +from pypsyc.PSYC import parsetext +# import packages +from pypsyc.PSYC.PSYCRoom import Authentication as AuthenticationPackage +#import Listener + +debug=0 + +class stdin_channel (asyncore.file_dispatcher): + def __init__(self): + asyncore.file_dispatcher.__init__(self, 0) + self.buffer = "" + def handle_read(self): + data = self.recv(512) + self.buffer += data + lines = self.buffer.split("\n") + for line in lines[:-1]: + self.lineReceived(line) + self.buffer = lines[-1] + def handle_close(self): + try: + self.close() + except: + pass + def lineReceived(self, line): + print "line:", line # overridden + def writable(self): + return 0 + def log(self, *ignore): + pass + +from pypsyc.PSYC.PSYCRoom import PSYCPackage +class stdin_client(stdin_channel, PSYCPackage): + def __init__(self): + stdin_channel.__init__(self) + PSYCPackage.__init__(self) + self.methods = ["devel"] + self.packagename = "pyPSYC console client" + def received(self, source, mc, mmp, psyc): + if debug: + #print self.packagename, "handling", mc, "from", source + print "" + print "mc:", mc + for (key, val) in mmp.thisState.items(): + print "MMP:", key, "=>", val + for (key, val) in psyc.thisState.items(): + print "PSYC:", key, "=>", val + print parsetext(mmp, psyc) + if mc == "_echo_logoff" or mc == "_status_unlinked": + print "Bye bye." + # eh? shouldn't this clean up and exit? --lynX + sys.exit() + + def set_mc(self, mc): self.psyc.set_mc(mc) + def set_target(self, target): self.mmp._assign("_target", target) + + def set_text(self, text): + self.psyc.reset_text() + self.psyc.append_text(text) + + def send(self): + self.center.send(self.mmp, self.psyc) + # this shouldn't need to be done here + self.mmp.reset_state() + self.psyc.reset_state() + # after each send the temporary vars must be reset + # to the permanent var state automatically. -lynX + def lineReceived(self, line): + self.mmp._set("_source_identification", self.center.ME()) + self.set_target(self.center.ME()) + self.set_mc("_request_input") + self.set_text(line) + self.send() + + +config = ConfigParser.ConfigParser() +config.read(os.getenv("HOME") + "/.pypsyc/config") + +console = stdin_client() + +center = PSYCMessagecenter(config) + +center.register(AuthenticationPackage(config)) +center.register(console) + +center.connect() + +asyncore.loop() + diff --git a/fippos-twisted/README b/fippos-twisted/README new file mode 100644 index 0000000..e214c2f --- /dev/null +++ b/fippos-twisted/README @@ -0,0 +1,24 @@ +pyPSYC - asyncore based library for PSYC + +The application programming interface (API) of this library is stable, +says fippo. But he left it unmaintained since 2005. + + +News at http://about.psyc.eu/pypsyc +Download by anonCVS from the same server as psyced. + :pserver:anonymous@cvs.psyced.org:/CVS/anoCVS + + +QUESTIONS? +/tell psyc://psyced.org/~fippo or come to psyc://psyced.org/@welcome + +AUTHORS + Philipp Hancke + + + Tim Head + + + Andreas Neue + + diff --git a/fippos-twisted/contrib/ircd.py b/fippos-twisted/contrib/ircd.py new file mode 100644 index 0000000..3916b68 --- /dev/null +++ b/fippos-twisted/contrib/ircd.py @@ -0,0 +1,230 @@ +# usage: twistd -noy ircd.py + +import sys +from twisted.application import service, internet +from pypsyc.center import ServerCenter +from pypsyc.net import PSYCServerFactory +from pypsyc.objects import PSYCReceiver +from pypsyc.objects.server import Person, Place, GroupSlave +from pypsyc import parseURL + +from twisted.protocols import irc +from twisted.internet.protocol import ServerFactory + + +""" +watch out, the main purpose of this thing is to debug the context slave +system +""" + +class IRCD(irc.IRC, PSYCReceiver): + center = None + password = '' + nick = '' + url = '' + source = None + def __init__(self, center, hostname): + self.center = center + self.hostname = hostname + def connectionMade(self): + peer = self.transport.getPeer() + self.source = 'object:%s:%d'%(peer.host, peer.port) + self.center.register_object(self.source, self) + def connectionLost(self, reason): + if self.url: + self.irc_QUIT(None, None) + elif self.nick is '': + print 'closing unknown connection' + def sendNumeric(self, numeric, msg): + self.sendLine(':%s %s %s :%s'%(self.hostname, numeric, self.nick, msg)) +# def sendLine(self, line): +# irc.IRC.sendLine(self, line.encode('iso-8859-1')) + def sendMessage(self, source, target, cmd, data = None): + s = ':%s %s %s'%(self.url2prefix(source), target, cmd) + if data: + s += ' :%s'%data + self.sendLine(s) + def sNotice(self, data): + self.sendLine(':%s NOTICE %s :%s'%(self.hostname, self.nick, data)) + """helper functions""" + def expandUrl(self, target): + """quick and dirty is_uniform check""" + if target.find(':') is -1: + if target[0] == '#': + return self.hostname + '/@%s'%target[1:] + return self.hostname + '/~%s'%target + if target[0] == '#': + return target[1:] + return target + def minimizeUrl(self, source): + if source.startswith(self.hostname): + # remark: +2 to skip trailing /@ + return source[len(self.hostname) + 2:] + return source + def url2prefix(self, url): + if url.find(':') != -1: + u = parseURL(url) + if u['resource'][0] == '~': + ident = u['resource'][1:] + else: + ident = '*' + host = u['host'] + else: + ident = url # its a local nick + host = 'localuser' + return '%s!%s@%s'%(url, ident, host) + + """irc_ command hooks""" + def irc_USER(self, prefix, params): + pass + def irc_PASS(self, prefix, params): + self.password = params[0] + def irc_NICK(self, prefix, params): + self.nick = params[0] + self.center.msg({'_source' : self.source, + '_target' : self.expandUrl(self.nick), + '_password' : self.password}, + '_request_link', '') + def irc_PRIVMSG(self, prefix, params): + target = params[0] + mc = '_message_private' + if target[0] == '#': + mc = '_message_public' + self.center.msg({ '_source' : self.source, + '_target' : self.expandUrl(target), + '_nick' : self.nick}, + mc, params[-1]) + def irc_JOIN(self, prefix, params): + chan = params[0] + self.center.msg({ '_source' : self.source, + '_target' : self.expandUrl(chan), + '_nick' : self.nick}, + '_request_enter', '') + def irc_PART(self, prefix, params): + chan = params[0] + self.center.msg({ '_source' : self.source, + '_target' : self.expandUrl(chan), + '_nick' : self.nick}, + '_request_leave', '') + def irc_QUIT(self, prefix, params): + self.center.msg({ '_source' : self.source, + '_target' : self.url }, + '_request_unlink', '') + def irc_unknown(self, prefix, command, params): + if command == 'ROSTER': + self.center.msg({ '_source' : self.source, + '_target' : self.url }, + '_request_roster', '') + elif command == 'FRIEND': + self.center.msg({ '_source' : self.source, + '_target' : self.expandUrl(params[0]) }, + '_request_friendship', 'Lass uns Freunde sein') + else: + print 'unknown irc cmd %s'%command + """pypsyc msg API""" + def msgUnknownMethod(self, vars, mc, data): + print 'unsupported %s from %s'%(mc, vars['_source']) + def msg_notice_link(self, vars, mc, data): + self.url = vars['_source'] + self.sendNumeric(irc.RPL_WELCOME, 'Hello, %s'%self.nick) + self.sendNumeric(irc.RPL_YOURHOST, 'Welcome to %s'%self.hostname) + self.sendNumeric(irc.RPL_MYINFO, '%s is a pyPSYC daemon IRC interface'%self.hostname) + def msg_message_private(self, vars, mc, data): + if vars['_target'] is self.source: + t = self.nick + else: # should not happen + raise + # TODO: might be appropriate to use self.privmsg() + # self.privmsg(vars['_source'], self.nick, None, data) + s = self.minimizeUrl(vars['_source']) + self.sendMessage(s, 'PRIVMSG', t, data) + def msg_message_echo_private(self, vars, mc, data): + pass # echo is not common in irc + def msg_message_public(self, vars, mc, data): + if vars['_source'] != self.url: # skip echo + if vars.has_key('_context'): + t = '#' + self.minimizeUrl(vars['_context']) + else: + t = '#' + self.minimizeUrl(vars['_source']) + s = self.minimizeUrl(vars['_source']) + # TODO: might be appropriate to use self.privmsg() + # self.privmsg(vars['_source'], None, t, data) + self.sendMessage(s, 'PRIVMSG', t, data) + else: + pass # echo is not common in IRC + def msg_echo_place_enter(self, vars, mc, data): + t = '#' + self.minimizeUrl(vars['_source']) + self.sendMessage(self.nick, 'JOIN', t) + def msg_echo_place_leave(self, vars, mc, data): + t = '#' + self.minimizeUrl(vars['_source']) + self.sendMessage(self.nick, 'PART', t) + def msg_status_place_members(self, vars, mc, data): + t = '#' + self.minimizeUrl(vars['_source']) + self.names(self.nick, t, map(self.minimizeUrl, vars['_list_members'])) + def msg_notice_unlink(self, vars, mc, data): + if vars['_source'] == self.url: + self.url = None + self.transport.loseConnection() + def msg_notice_place_enter(self, vars, mc, data): + s = vars['_source'] + c = '#' + self.minimizeUrl(vars['_context']) + if s == self.url: + return # we dont like being joined via notice! + self.join(self.url2prefix(self.minimizeUrl(s)), c) + def msg_notice_place_leave(self, vars, mc, data): + s = vars['_source'] + c = '#' + self.minimizeUrl(vars['_context']) + self.part(self.url2prefix(self.minimizeUrl(s)), c) + def msg_notice_roster(self, vars, mc, data): + friends = vars['_list_friends'] + places = vars['_list_places'] + if friends: + self.sNotice('Friends') + for friend in vars['_list_friends']: + self.sNotice('~ %s'%friend) + if places: + self.sNotice('Places') + for place in places: + self.sNotice('@ %s'%place) + def msg_request_friendship(self, vars, mc, data): + sni = self.minimizeUrl(vars['_source']) + self.notice(sni, self.nick, + '%s wants to be your friend'%(sni)) + def msg_notice_friendship_established(self, vars, mc, data): + sni = self.minimizeUrl(vars['_source']) + self.notice(sni, self.nick, + '%s is now your friend'%(sni)) + +class IRCDFactory(ServerFactory): + center = None + def __init__(self, center, location): + self.center = center + self.location = location + def buildProtocol(self, addr): + p = IRCD(self.center, self.location) + p.factory = self + return p + +class MyServerCenter(ServerCenter): + def create_user(self, netname): + return Person(netname, self) + def create_place(self, netname): + return Place(netname, self) + def create_context(self, netname): + return GroupSlave(netname, self) + + +root = 'psyc://ente' # TODO: this does belong into a config file! +application = service.Application('psycserver') + +center = MyServerCenter(root) + +factory = PSYCServerFactory(center, None, root) +psycServer = internet.TCPServer(4404, factory) + +ircfactory = IRCDFactory(center, root) +ircServer = internet.TCPServer(6667, ircfactory) + +myService = service.IServiceCollection(application) +psycServer.setServiceParent(myService) +ircServer.setServiceParent(myService) diff --git a/fippos-twisted/contrib/rss/README b/fippos-twisted/contrib/rss/README new file mode 100644 index 0000000..a3f0093 --- /dev/null +++ b/fippos-twisted/contrib/rss/README @@ -0,0 +1,9 @@ +Purpose: +this is designed to be a news distributing server only. It fetches RSS feeds + +Running: + twistd -noy rss_server.py +This code depends on +- rss.py from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277099 +- feedparser.py from http://diveintomark.org/projects/feed_parser/ +- twisted python (python is less fun without) diff --git a/fippos-twisted/contrib/rss/rss_server.py b/fippos-twisted/contrib/rss/rss_server.py new file mode 100644 index 0000000..116d69e --- /dev/null +++ b/fippos-twisted/contrib/rss/rss_server.py @@ -0,0 +1,131 @@ +# usage: twistd -noy rss_server.py +from twisted.application import service, internet +from twisted.internet import reactor +from pypsyc.center import ServerCenter +from pypsyc.net import PSYCServerFactory +from pypsyc.objects.server import Place + +from pypsyc import parseUNL + +try: + from rss import FeederFactory +except ImportError: + print 'error while importing rss.py' + print 'make sure you have rss.py from ', + print 'from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/277099' + print 'and feedparser.py from ', + print 'http://diveintomark.org/projects/feed_parser/' + + +class PlaceFeeder(FeederFactory): + def getFeeds(self): + return [] + + +class Feedplace(Place): + channel = None + silent = True + def __init__(self, netname, center, feed): + Place.__init__(self, netname, center) + self.feed = feed + self.error = 0 # number of times that we didnt succeed + self.fetched = 0 # number of times we fetched sucessfully + self.fetched_items = 0 # the average number of new items per fetch + + self.fetch_interval = 15 * 60 # initial feed interval + + self.feeder = PlaceFeeder(False) + self.news = [] + reactor.callLater(5, self.fetchFeed) + def fetchFeed(self): + d = self.feeder.start([(self.feed, '')]) + d.addCallback(self.gotFeed) + d.addErrback(self.gotError) + def gotError(self, error): + self.error += 1 + # TODO: react on feeds that are temp/perm unreachable + reactor.callLater(self.fetch_interval, self.fetchFeed) + print 'looks as if feed %s is unreachable'%self.feed + print error + def gotFeed(self, data): + self.fetched += 1 + new = [] + items = {} + if self.channel is None: + self.channel = data['channel'] + self.castmsg({ '_nick' : self.netname, + '_topic' : self.showTopic()}, + '_status_place_topic', + 'Topic by [_nick]: [_topic]') + for item in data['items']: + # diff by url + href = item['link'] + new.append(href) + items[href] = item + + diff = filter(lambda x: x not in self.news, new) + for href in diff: + item = items[href] + v = {'_news_headline' : item['title_detail']['value'], + '_page_news' : href, + '_channel_title' : data['channel']['title'] } + self.castmsg(v, '_notice_news_headline_rss', + '([_channel_title]) [_news_headline]\n[_page_news]') + + self.news = new + + # feeds whose average number of new items is < x + # can be polled with less frequency + self.fetched_items += len(diff) + avg = float(self.fetched_items) / self.fetched + print 'avg no of new items per fetch for %s is %f'%(self.feed, avg) + if avg < 1.5: # x + # lower frequency + self.fetch_interval *= avg + elif avg > 4.5 and self.fetched > 10: # y + # increase frequenzy + self.fetch_interval /= 2 + print 'callLater in %d'%(self.fetch_interval) + reactor.callLater(self.fetch_interval, self.fetchFeed) + def showMembers(self): + return [] + def showTopic(self): + if self.channel is not None: + return 'feed \'%s\' available from %s'%(self.channel['title'], + self.feed) + else: + return 'stand by while fetching feed %s'%self.feed + def msg_message_public(self, vars, mc, data): + pass # they're not for talking + + +class MyServerCenter(ServerCenter): + feeds = {} + def create_user(self, netname): + return False + def create_place(self, netname): + u = parseUNL(netname) + res = u['resource'][1:] + if self.feeds.has_key(res): + return Feedplace(netname, center, self.feeds[res]) + return False + def create_context(self, netname): + return False + def setFeeds(self, feeds): + self.feeds = feeds + def getFeeds(self): + return self.feeds + + +root = 'psyc://ente' +application = service.Application('psyc news distributor') + +center = MyServerCenter(root) +factory = PSYCServerFactory(center, None, root) +psycServer = internet.TCPServer(4404, factory) + +center.setFeeds({ 'heise' : 'http://www.heise.de/newsticker/heise.rdf' }) + +myService = service.IServiceCollection(application) +psycServer.setServiceParent(myService) + diff --git a/fippos-twisted/contrib/whitepaper.py b/fippos-twisted/contrib/whitepaper.py new file mode 100644 index 0000000..1b6b17a --- /dev/null +++ b/fippos-twisted/contrib/whitepaper.py @@ -0,0 +1,28 @@ +"""p2p manager and client as described in the old PSYC whitepaper at +http://psyc.pages.de/whitepaper/ +probably broken currently""" +from pypsyc.center import ServerCenter, ClientCenter +from pypsyc.objects.PSYCObject import PSYCClient +from pypsyc.objects.Advanced import AdvancedManager, AdvancedPlace +import sys +import asyncore + +location = 'psyc://adamantine.aquarium' + +type = sys.argv[1] +if type == 'manager': + center = ServerCenter([location + ':4405/', location + ':4406', + location + ':4407', location + ':4408']) + center2 = ServerCenter([location]) + AdvancedManager(location + '/@advanced', center2) +if type == 'client': + center = ClientCenter() + me = PSYCClient(location + '/~fippo', center) + me.online() + AdvancedPlace(location + "/@advanced", center) + me.sendmsg({'_target' : location + '/@advanced', + '_source' : location + '/~fippo'}, + '_request_enter', '') + +while center: + asyncore.poll(timeout=0.5) diff --git a/fippos-twisted/doc/DESIGN b/fippos-twisted/doc/DESIGN new file mode 100644 index 0000000..56fd265 --- /dev/null +++ b/fippos-twisted/doc/DESIGN @@ -0,0 +1,10 @@ +Design of the pyPSYC library + +Asynchronus programming + +The role of the center object + +Writing a client +- GUI event loop Integration + +Writing a server diff --git a/fippos-twisted/doc/FEATURES b/fippos-twisted/doc/FEATURES new file mode 100644 index 0000000..32bf06e --- /dev/null +++ b/fippos-twisted/doc/FEATURES @@ -0,0 +1 @@ +onesided zlib compression diff --git a/fippos-twisted/pypsyc/PSYC.py b/fippos-twisted/pypsyc/PSYC.py new file mode 100644 index 0000000..8c0e04c --- /dev/null +++ b/fippos-twisted/pypsyc/PSYC.py @@ -0,0 +1,70 @@ +from pypsyc.State import State + +from twisted.protocols.basic import LineReceiver + + +class PSYCProtocol(LineReceiver): + statemachine = None + state = 'vars' + mc = None + text = None + delimiter = '\n' + initialized = False + def connectionMade(self): + self.statemachine = State() + self.reset() + self.transport.write('.\n') + def msg(self, vars, mc, text): + """serialize a packet and send to the other side + + @type vars: C{dict} + @param vars: Dictionary of variables to be serialized. + Variables should be strings or lists, variable names start + with an underscore + + @type mc: C{str} + @param mc: Methodname of the packet, starts with an underscore + + @type text: C{str} + @param text: Data part of the packet""" + packet = self.statemachine.serialize(vars) # this has a newline already! + packet += mc + '\n' + packet += text + '\n' + packet += '.\n' + self.transport.write(packet.encode('iso-8859-1')) + def reset(self): + self.statemachine.reset() + self.state = 'vars' + self.mc = '' + self.text = '' + def lineReceived(self, line): + """this does not yet handle binary mode and fragments""" + line = line.strip() + if self.initialized is False: + if line != '.': + self.msg({}, '_error_syntax_protocol_initialization', + 'The protocol begins with a dot on a line of by itself') + self.transport.loseConnection() + return + else: + self.initialized = True + return + if line == '.': + self.packetReceived() + elif self.state is 'vars' and line.startswith('_'): + self.statemachine.eat(None) + self.mc = line + self.state = 'text' + elif self.state == 'vars': + self.statemachine.eat(line) + else: + self.text += line + '\n' + def packetReceived(self): + vars = self.statemachine.copy() + peer = self.transport.getPeer() + if not vars.has_key('_source'): + vars['_source'] = 'psyc://%s:-%d'%(peer.host, peer.port) + mc = self.mc[:] + data = self.text[:].strip().decode('iso-8859-1') + self.reset() + self.factory.packetReceived(vars, mc, data, self, peer) diff --git a/fippos-twisted/pypsyc/State.py b/fippos-twisted/pypsyc/State.py new file mode 100644 index 0000000..c687165 --- /dev/null +++ b/fippos-twisted/pypsyc/State.py @@ -0,0 +1,156 @@ +# TODO: write tests +from pypsyc import MMPVARS + +from copy import deepcopy + +class SenderState: + def __init__(self): + self.laststate = {} + self.persistent_out = {} + def serializeList(self, varname, value): + t = [] + old = self.persistent_out.get(varname) + # that is far to experimental + if True: # not old: # no previous list sent + self.persistent_out[varname] = value + t.append(':%s\t%s\n'%(varname, value[0].replace('\n', '\n\t'))) + for item in value[1:]: + t.append(':\t%s\n'%(item.replace('\n', '\n\t'))) + + return '\n'.join(t) + if False: + augmented = filter(lambda x: x not in old, value) + if augmented: + packet += '+%s\t%s\n'%(varname, augmented[0].replace('\n', + '\n\t')) + for item in augmented[1:]: + packet += '+\t%s\n'%(item.replace('\n', '\n\t')) + diminished = filter(lambda x: x not in value, old) + if diminished: + packet += '-%s\t%s\n'%(varname, diminished[0].replace('\n', + '\n\t')) + for item in diminished[1:]: + packet += '-\t%s\n'%(item.replace('\n', '\n\t')) + self.persistent_out[varname] = value + def serialize(self, state): + """ + serializes a set of variables into a string + + @type state: C{dict} + @param state: Dictionary of variables to be serialized + """ + L = [] + # beware of the lambdas! + L.append(self.varencode(filter(lambda x: x[0] in MMPVARS and x[1], + state.items()))) + if L != []: + L.append('\n') + L.append(self.varencode(filter(lambda x: x[0] not in MMPVARS and x[1], + state.items()))) + self.laststate = state + bytes = '\n'.join(L) + return bytes + def varencode(self, v): + """ + encodes a set of variables, setting the state persistent according to + some strategy + + @type v: C{dict} + @param v: Dictionary of variables to be serialized + """ + t = [] + for (varname, value) in v: + if self.persistent_out.get(varname) == value: + pass + elif varname.startswith('_list') or type(value) == type([]): + t.append(self.serializeList(varname, value)) + elif self.laststate.get(varname) == value and varname != '_context': + self.persistent_out[varname] = value + t.append('=%s\t%s\n'%(varname, value.replace('\n', '\n\t'))) + else: + t.append(':%s\t%s\n'%(varname, value.replace('\n', '\n\t'))) + return '\n'.join(t) + + +class ReceiverState: + glyph = '' + varname = '' + listFlag = False + value = '' + def __init__(self): + self.state = {} + self.persistent = {} + def reset(self): + self.state = {} + self.glyph = '' + self.varname = '' + self.listFlag= False + self.value = '' + def copy(self): + # do we actually need those deep copys? TODO + t = deepcopy(self.persistent) + t.update(deepcopy(self.state)) + return t + def eat(self, line): + """ + this one is tricky... first it handles the previous line, + and then it prepares the current line. + This is needed to implement multiline-continuations in lists + + @type line: C{str} or C{None} + @param line: line to be parsed, a None signals that variable + parsing for current packet is finished + """ + if line: line = line.decode('iso-8859-1') # we use unicode internally + if line and (line[0] == ' ' or line[0] == '\t'): # multiline support + self.value += '\n' + line[1:] + return + + # glyph handling + if self.glyph == ':': + if self.listFlag: + if not type(self.state[self.varname]) == list: + self.state[self.varname] = [self.state[self.varname]] + self.state[self.varname].append(self.value) + else: + if self.varname.startswith('_list'): + self.value = [self.value] + self.state[self.varname] = self.value + elif self.glyph == '=': + if self.listFlag: + if not type(self.persistent[self.varname]) == list: + self.persistent[self.varname] = [self.self.persistent[self.varname]] + self.persistent[self.varname].append(self.value) + else: + self.persistent[self.varname] = self.value + elif self.glyph == '+': + self.persistent.get(self.varname, []).append(self.value) + elif self.glyph == '-': + raise NotImplementedError + elif self.glyph == '?': + raise NotImplementedError + + if not line: # feeding done + return + + # here we parse the current line + self.glyph = line[0] + if line[1] == '\t': + # lastvarname-optimization: varname remains the same + self.listFlag = True + self.value = line[2:] + else: + self.listFlag = False + if line.find('\t') == -1: + self.varname = line[1:] + self.value = '' + else: + self.varname, self.value = line[1:].split('\t', 1) + self.value = self.value + + +class State(SenderState, ReceiverState): + """combination of sender and receiver state""" + def __init__(self): + SenderState.__init__(self) + ReceiverState.__init__(self) diff --git a/fippos-twisted/pypsyc/__init__.py b/fippos-twisted/pypsyc/__init__.py new file mode 100644 index 0000000..bfe806d --- /dev/null +++ b/fippos-twisted/pypsyc/__init__.py @@ -0,0 +1,101 @@ +"""common methods and constants for pyPSYC""" + +# URL parsing functions modelled after psycMUVE parseURL +def parseURL(url): + u = { 'scheme' : '', + 'user' : '', + 'pass' : '', + 'host' : '', + 'port' : '4404', + 'transport' : '', + 'string' : url, + 'body' : '', + 'userAtHost' : '', + 'hostPort' : '', + 'root' : '', + 'circuit' : '', + 'size' : '' + } + if url.find(':') == -1: return u + u['scheme'], t = url.split(':', 1) + if t[0:2] == '//': t = t[2:] + u['body'] = t[:] + if t.find('/') != -1: + t, u['resource'] = t.split('/', 1) + else: + u['resource'] = '' + if u.has_key('resource') and u['resource'].find('#') != -1: + u['resource'], u['fragment'] = u['resource'].split('#', 1) + u['userAtHost'] = t[:] + if t.find('@') != -1: + s, t = t.split('@', 1) + if s.find(':') != -1: + u['user'], u['pass'] = s.split(':', 1) + else: + u['user'] = s + u['hostPort'] = t[:] + u['root'] = u['scheme'] + '://' + u['hostPort'] + if t.find(':') != -1: + t, s = t.split(':', 1) + # TODO: split s in Port (numeric), Transport + if s and s[-1] in ['c', 'd', 'm']: + u['transport'] = s[-1] + u['port'] = s[:-1] or '4404' + else: + u['port'] = s or '4404' + u['host'] = t[:] +# print "parseurl(%s)"%url, u + return u + + +def parseUNL(unl): return parseURL(unl) # alias + +def UNL2Location(unl): + # if we did not have the user@host syntax this would + # reduce to a simple splitting in front of # + u = parseUNL(unl) + short = u['scheme'] + '://' + u['host'] + if u['port'] != '4404': + short += ':' + u['port'] + if u['resource']: + return short + '/' + u['resource'] + return short + + +def netLocation(unl): + u = parseURL(unl) + return u['root'] + +def parsetext(vars, mc, data, caller=None): + pstring = data + #print '---' + #print type(data) + #print '---' + try: + for (varname, value) in vars.items(): + if type(value) == list: + no_list = u'' + for x in value: + no_list += x + ', ' + pstring = pstring.replace(u'[' + varname + u']', no_list[:-2]) + else: + pstring = pstring.replace(u'[' + varname + u']', value) + except: + print 'Error in parsetext() for vars' + return pstring + +# debugging helper +def dump_packet(banner, vars, mc, data): + print banner + ' ', + for key in vars.keys(): + try: + print key + '=' + vars[key] + ' ', + except: + pass + print mc, + print '[' + parsetext(vars, mc, data) + ']' + + +# constants +GLYPHS = [':', '=', '+', '-', '?', ' ', '\t' ] +MMPVARS = ["_source", "_target", "_context"] diff --git a/fippos-twisted/pypsyc/center.py b/fippos-twisted/pypsyc/center.py new file mode 100644 index 0000000..43b40f0 --- /dev/null +++ b/fippos-twisted/pypsyc/center.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +from pypsyc import parseUNL, UNL2Location, netLocation + +from twisted.internet import reactor, defer +from pypsyc.net import PSYCClientConnector, PSYCActiveConnector + +from pypsyc import dump_packet + + +class Center: + object_handlers = {} + remote_connections = {} + def msg(self, vars, mc, data): + raise NotImplementedError + + def sendmsg(self, vars, mc, data, target = None): + raise NotImplementedError + + def register_object(self, netname, object): + # multiple handlers are not possible + # we only want lower case netnames + self.object_handlers[netname.lower()] = object + + def find_object(self, netname): + """find a local object that corresponds to netname""" + # try "real/local" object" + # we only have lower case netnames + return self.object_handlers.get(netname.lower()) + + def find_remote(self, netname): + """find a remote location where netname may be located""" + address = netLocation(netname) + if self.remote_connections.has_key(address): + return self.remote_connections[address] + return None + def register_remote(self, obj, where): + self.remote_connections[where] = obj + def unregister_remote(self, obj, where): + if self.remote_connections.get(where) == obj: + self.remote_connections.pop(where) + def connect(self, location): + raise NotImplementedError + def create_user(self, netname): + raise NotImplementedError + def create_place(self, netname): + raise NotImplementedError + + +class ClientCenter(Center): + # connection to the homeserver, default return for find_remote + default_connection = None + options = {} + def get_option(self, option): + return self.options.get(option) + def connect(self, location): + # already connected or connecting? + a = self.find_remote(location) + if a: + return a + a = PSYCClientConnector(self, location) + return a.get_queue() + def find_remote(self, netname): + """clients will send most things via their homeserver + p2p connections are an exception to that, but they will be + created by user objects""" + return Center.find_remote(self, netname) or Center.find_remote(self, self.default_connection) + def msg(self, vars, mc, data): + if not mc: + #print "warning: minor bug in pypsyc: msg() without mc" + return + if self.get_option('debug'): + dump_packet(">>>", vars, mc, data) + source = vars.get('_context') or vars.get('_source') + if not source: return # ignore empty packets? + obj = self.find_object(source) + if obj: + obj.msg(vars, mc, data) + return + print 'unhandled packet from %s'%source + # do something about it... pop up a window, etc + u = parseUNL(source) + if u['resource'] and u['resource'].startswith('~'): + # create a user object + obj = self.create_user(source) + obj.msg(vars, mc, data) + elif u['resource'] and u['resource'].startswith('@'): + # create a place object + obj = self.create_place(source) + obj.msg(vars, mc, data) + else: + print 'no handler for %s object'%(source) + def sendmsg(self, vars, mc, data): + target = vars.get('_target') + if not target: return + obj = self.find_remote(target) + if obj: + obj.msg(vars, mc, data) + else: + raise + # this should not happen! + + +# TODO this does not belong here +import sha +class Authenticator: + def __init__(self, center, uni, password): + self.center = center + self.uni = uni + self.password = password + def startLink(self): + print 'start link' + self.sendmsg({'_target' : uni }, '_request_link', '') + def msg_query_password(self, vars, mc, data): + if vars['_nonce'] and self.center.get_option('auth_sha'): + digest = sha.sha(vars['_nonce'] + self.password).hexdigest() + self.center.sendmsg({ '_method' : 'sha1', + '_password' : digest }, + '_set_password', '') + elif self.center.get_option('auth_plain'): + self.sendmsg({ '_password' : self.password }, + '_set_password', '') + else: + print 'no authorization method available!' + return + + +class Client(ClientCenter): + default_uni = '' + nick = '' + online_callback = None + def __init__(self, config): + self.config = config + + self.default_uni = config.get('main', 'uni') + + u = parseUNL(self.default_uni) + self.nick = u['resource'][1:] + self.default_connection = netLocation(self.default_uni) + if self.config.has_section('library'): + for option in self.config.options('library'): + self.options[option] = self.config.getboolean('library', option) + def online(self): + self.connect(self.default_connection) + # experimental API using the cool Deferred's + self.online_callback = defer.Deferred() + return self.online_callback + def gotOnline(self): + if self.online_callback: + self.online_callback.callback(None) + print 'connected to host of default location' + def sendmsg(self, vars, mc, data): + if vars.get('_nick') == '': + vars['_nick'] = self.nick + # TODO: I am not sure, it this is correct + if vars.get('_source') == '': + vars['_source'] = self.default_uni + ClientCenter.sendmsg(self, vars, mc, data) + + +class ServerCenter(Center): + unl2uni = {} + remote_contexts = {} + def __init__(self, location): + self.location = location + def msg(self, vars, mc, data): + if not mc: + return # ignore empty packets + source = vars['_source'] + # TODO: auth check 'global' or per-object + if vars.has_key('_identification'): + print 'Identification of %s is %s'%(source, vars['_identification']) + context = vars.get('_context') + if context and not self.is_local_object(context): + target = context + else: + target = vars['_target'] + if self.unl2uni.has_key(source): + """local client who is using us as a proxy""" + self.unl2uni[source].sendmsg(vars, mc, data) + elif target: + obj = self.find_object(target) + if obj: + return obj.msg(vars, mc, data) + # probably object has to be created + if self.is_local_object(target): + u = parseUNL(target) + if (u['resource'] and u['resource'].startswith('@')): + obj = self.create_place(target) + if obj: + return obj.msg(vars, mc, data) + else: + vars['_target'], vars['_source'] = vars['_source'], vars['_target'] + self.sendmsg(vars, '_error_unknown_name_place', + 'No such place: [_source]') + elif (u['resource'] and u['resource'].startswith('~')): + obj = self.create_user(target) + if obj: + return obj.msg(vars, mc, data) + else: + vars['_target'], vars['_source'] = vars['_source'], vars['_target'] + self.sendmsg(vars, '_error_unknown_name_user', + 'No such user: [_source]') + elif u['resource']: + vars['_target'] = source + vars['_source'] = target + self.sendmsg(vars, '_error_unknown_name', + 'No such object: [_source]') + else: # rootmsg + pass + elif context is not None and self.remote_contexts.has_key(context): + self.remote_contexts[context].castmsg(vars, mc, data) + else: # nonlocal target + print 'rejected relay %s from %s to %s'%(mc, source, target) + return # skip it for now + self.sendmsg({ '_target' : source, '_source' : self.location, + '_destination' : target }, + '_error_rejected_relay', + 'You are not allowed to send messages to [_destination]') + else: + # no target??? + print 'no target in packet???' + + def sendmsg(self, vars, mc, data): + target = vars['_target'] + if self.is_local_object(target): + self.msg(vars, mc, data) + else: + u = parseUNL(target) + target = (self.find_object(target) or + self.find_remote(target) or + self.find_remote(target)) + if not target and u['scheme'] == 'psyc': + q = self.connect(u['root']) + q.msg(vars, mc, data) + elif target: + target.msg(vars, mc, data) + else: + raise 'can not find %s'%(target) # programming error? + def register_object(self, netname, object): + self.object_handlers[netname.lower()] = object + def link_unl(self, unl, uni): + self.unl2uni[unl] = uni + def unlink_unl(self, unl, uni): + if self.unl2uni.has_key(unl) and self.unl2uni[unl] == uni: + self.unl2uni.pop(unl) + def is_local_object(self, location): + return netLocation(location) == netLocation(self.location) + def connect(self, location): + a = PSYCActiveConnector(self, location, self.location) + self.remote_connections[location] = a.get_queue() + return a.get_queue() + def find_remote(self, netname): + address = netLocation(netname) + return self.remote_connections.get(address, None) + def join_context(self, context, obj): + if self.is_local_object(context): + print 'skipping join to local context' + if not self.remote_contexts.has_key(context): + self.remote_contexts[context] = self.create_context(context) + self.remote_contexts[context].join(obj) + def leave_context(self, context, obj): + if self.is_local_object(context): + print 'skipping part of local context' + if self.remote_contexts.has_key(context): + self.remote_contexts[context].leave(obj) + # TODO possibly clean_up that context + def create_context(self, netname): + """this is how we create context slaves""" + raise NotImplementedError diff --git a/fippos-twisted/pypsyc/net.py b/fippos-twisted/pypsyc/net.py new file mode 100644 index 0000000..a42352e --- /dev/null +++ b/fippos-twisted/pypsyc/net.py @@ -0,0 +1,186 @@ +"""network code for pyPSYC""" + +from pypsyc import GLYPHS + +from pypsyc.PSYC import PSYCProtocol +from pypsyc import parseUNL, UNL2Location + +from twisted.names import client +from twisted.internet import reactor +from twisted.internet.protocol import ClientFactory, ServerFactory + + +class HostChecker: + """checks validity of _source/_context in a packet + this could well implement policies like trusted from localhost""" + def __init__(self, center, target, myname): + self.target = target + self.myname = myname + self.center = center + def check(self, vars, mc, data, protocol, peer): + source = vars.get('_context') or vars.get('_source') + if (source and not + source.startswith('psyc://%s'%(peer.host))): + u = parseUNL(source) + d = client.lookupAddress(u['host']) + d.addCallback(self.resolved, peer.host, vars, mc, data, protocol) + elif source: + # numeric address + self.handle(vars, mc, data, protocol) + else: + protocol.msg({ '_source' : self.myname }, + '_error_syntax_protocol_missing_source', + 'Your implementation is broken') + def handle(self, vars, mc, data, protocol): + method = getattr(self, 'recv%s'%mc, None) + if method is not None: + method(vars, mc, data, protocol) + else: + u = parseUNL(vars['_source']) + self.center.register_remote(protocol, u['root']) + self.center.msg(vars, mc, data) + def resolved(self, record, shouldbe, vars, mc, data, protocol): + v = { '_source' : self.myname, + '_target' : vars['_source'] } + if not record[0]: + print 'name not resolvable' + protocol.msg(v, '_error_rejected_relay', + '[_source] is not resolvable. Goodbye') + protocol.transport.loseConnection() + elif record[0][0].type == 5: + payload = record[0][0].payload + d = client.lookupAddress(payload.name.name) + d.addCallback(self.resolved, shouldbe, vars, mc, data, protocol) + else: + payload = record[0][0].payload + if payload.dottedQuad() == shouldbe: + self.handle(vars, mc, data, protocol) + else: + print 'rejected relay' + protocol.msg(v, '_error_rejected_relay', + '[_source] does not resolve to your ip.') + protocol.transport.loseConnection() + + +class PSYCConnector: + hostname = '' + port = 4404 + factory = None + factory_type = None + real_hostname = '' + def __init__(self, center, target, myname = None): + # TODO: dont try to resolve IP addresses ;) + # must subclass + if not self.factory_type: raise NotImplementedError + try: + self.factory = self.factory_type(center, target, myname) + except: + print self.factory_type + raise + u = parseUNL(target) + self.host = u['host'] + d = client.lookupService('_psyc._tcp.' + self.host) + d.addCallback(self.srvResolved) + def get_queue(self): + return self.factory + def resolved(self, record): + if not record[0]: + print 'resolution failed...' + else: + payload = record[0][0].payload + if record[0][0].type == 5: # evil cname + d = client.lookupAddress(payload.name.name) + d.addCallback(self.resolved) + else: + reactor.connectTCP(payload.dottedQuad(), self.port, self.factory) + return True + def srvResolved(self, record): + if not record[0]: + d = client.lookupAddress(self.host) + d.addCallback(self.resolved) + else: + payload = record[0][0].payload + self.port = payload.port + d = client.lookupAddress(payload.target.name) + d.addCallback(self.resolved) + return True + + +class PSYCClientFactory(ClientFactory): + """a factory for a client which does not have to check + for validity of host names""" + class PSYCClient(PSYCProtocol): + def connectionMade(self): + PSYCProtocol.connectionMade(self) + self.factory.connectionMade(self) + + protocol = PSYCClient + center = None + location = None + def __init__(self, center, location, myname): + self.center = center + self.location = location + def connectionMade(self, proto): + self.center.register_remote(proto, self.location) + self.center.gotOnline() + def packetReceived(self, vars, mc, data, protocol, peer): + self.center.msg(vars, mc, data) + + +class PSYCClientConnector(PSYCConnector): + factory_type = PSYCClientFactory + + +class PSYCActiveFactory(ClientFactory, HostChecker): + """a factory for a client which does hostname checks + maybe this will also become a Q + probably we want to override connectionMade?""" + protocol = PSYCProtocol + queue = [] + connected = None + def packetReceived(self, vars, mc, data, protocol, peer): + self.check(vars, mc, data, protocol, peer) + def msg(self, vars, mc, data): + # watch out, if we dont use dict-vars we may need deepcopy + if self.connected is not None: + self.connected.msg(vars, mc, data) + else: + self.queue.append((vars, mc, data)) + def run_queue(self, target): + for (vars, mc, data) in self.queue: + target.msg(vars, mc, data) + self.queue = [] + self.connected = target + def recv_notice_circuit_established(self, vars, mc, data, protocol): + print 'got _notice_circuit_established' + protocol.msg({'_source' : self.myname, + '_target' : self.target or vars['_source'] }, + '_notice_circuit_established', + 'hi there') + # TODO: what do we register here? source oder our definition of + # what the source should be (self.target) + self.center.register_remote(protocol, self.target) + self.run_queue(protocol) + + +class PSYCActiveConnector(PSYCConnector): + factory_type = PSYCActiveFactory + + +class PSYCServerFactory(ServerFactory, HostChecker): + """funny question... do we deal all the stuff about + modules, compression, tls etc here?""" + class PSYCServerProtocol(PSYCProtocol): + def connectionMade(self): + peer = self.transport.getPeer() + PSYCProtocol.connectionMade(self) + self.msg({ '_source' : self.factory.myname, + '_target' : 'psyc://%s:-%d'%(peer.host, peer.port)}, + '_notice_circuit_established', + 'Connection to [_source] established') + + protocol = PSYCServerProtocol + def packetReceived(self, vars, mc, data, protocol, peer): + self.check(vars, mc, data, protocol, peer) + def recv_notice_circuit_established(self, vars, mc, data, protocol): + self.center.register_remote(protocol, vars['_source']) diff --git a/fippos-twisted/pypsyc/objects/__init__.py b/fippos-twisted/pypsyc/objects/__init__.py new file mode 100644 index 0000000..42973b6 --- /dev/null +++ b/fippos-twisted/pypsyc/objects/__init__.py @@ -0,0 +1,49 @@ +class PSYCReceiver: + def msg(self, vars, mc, data): + 'called when a message is received' + # method inheritance + if mc.count('_') > 10: + # considered abusive + return + + l = len(mc) + while l > 0: + method = getattr(self, 'msg%s'%(mc[:l]), None) + if method is not None: + # could this method return the next methodname + # to be called? freaky! + # actually, this a much more flexible fallthrough + # than switch/case provides. + # yet it is more expensive to evaluate + method(vars, mc, data) + break + l = mc.rfind('_', 0, l) + else: + self.msgUnknownMethod(vars, mc, data) + def msgUnknownMethod(self, vars, mc, data): + print 'unknown %s'%mc + + +class PSYCObject(PSYCReceiver): + """generic PSYC object""" + def __init__(self, netname, center): + self.center = center + self.netname = netname.lower() + self.center.register_object(netname, self) + def url(self): + return self.netname + def str(self): + return self.netname + def sendmsg(self, vars, mc, data): + 'called to send a message' + l = len(mc) + while l > 0: + method = getattr(self, 'sendmsg%s'%(mc[:l]), None) + if method is not None: + method(vars, mc, data) + break + l = mc.rfind('_', 0, l) + else: + self.center.sendmsg(vars, mc, data) + def castmsg(self, vars, mc, data): + 'called to send a message to a group' diff --git a/fippos-twisted/pypsyc/objects/client.py b/fippos-twisted/pypsyc/objects/client.py new file mode 100644 index 0000000..0177a3d --- /dev/null +++ b/fippos-twisted/pypsyc/objects/client.py @@ -0,0 +1,14 @@ +"""Client API for PSYC objects""" + +class IPSYCClientObject: + def msg(self, vars, mc, data): + """receive a message""" + def sendmsg(self, vars, mc, data): + """send a message to a single person or a group manager + use this make requests to the other side that should not be + distributed in an unchangend fashion""" + def castmsg(self, vars, mc, data): + """send a message that is destined to be delivered to a group + Note that you should use this for all communication with the + group, as it enables transparent distribution for both centralistic + and peer2peer scenarios""" diff --git a/fippos-twisted/pypsyc/objects/server.py b/fippos-twisted/pypsyc/objects/server.py new file mode 100644 index 0000000..f10a848 --- /dev/null +++ b/fippos-twisted/pypsyc/objects/server.py @@ -0,0 +1,319 @@ +from pypsyc.objects import PSYCObject +from pypsyc import parseUNL + +from twisted.internet import defer + +class GroupMaster(PSYCObject): + """ + @type members: C{dict} + @ivar members: list of members of the group + """ + def __init__(self, netname, center): + PSYCObject.__init__(self, netname, center) + self.members = {} + self.flat = {} + def sizeof(self): + return len(self.flat) + def remove(self, whom, origin = None): + self.flat.pop(whom, None) + if origin is not None: + self.members.get(origin, {}).pop(whom, None) + if not self.members.get(origin, None): + self.members.pop(origin, None) + else: + self.members.pop(whom, None) + def insert(self, whom, origin = None, data = None): + if not data: data = True + self.flat[whom] = data + if origin and not self.netname.startswith(origin): + if not self.members.get(origin): + self.members[origin] = {} + self.members[origin][whom] = True + else: + self.members[whom] = True + def getData(self, whose): + return self.flat[whose] + def setData(self, whose, data): + self.flat[whose] = data + def member(self, who): + return who in self.flat + def castmsg(self, vars, mc, data): + vars['_nick_place'] = 'muh' # IMHO _nick_place is superflous + vars['_context'] = self.netname + for (route, list) in self.members.items(): + # TODO: deepcopy needed? + vars['_target'] = route + self.center.sendmsg(vars, mc, data) + + +class GroupSlave: + def __init__(self, netname, center): + self.center = center + self.context = netname + self.members = {} + def join(self, obj): + url = obj.url() + self.members[url] = obj + # TODO: install a watcher on memberobj? + def leave(self, obj): + url = obj.url() + if url in self.members: + self.members.pop(url) + def castmsg(self, vars, mc, data): + for (target, obj) in self.members.items(): + vars['_target'] = target + obj.msg(vars, mc, data) + + +class Place(GroupMaster): + silent = False + def __init__(self, netname, center): + self.idcallbacks = {} + self.identifications = {} + self.reverseidentifications = {} + GroupMaster.__init__(self, netname, center) + def sendmsg(self, vars, mc, data): + # things like setting vars['_nick_place'] + vars['_source'] = self.netname + t = self.reverseidentifications.get(vars['_target']) + if t: + print 'setting target from %s to %s'%(vars['_target'], t) + vars['_target'] = t + GroupMaster.sendmsg(self, vars, mc, data) + def showMembers(self): + return self.flat.keys() + def showTopic(self): + return '' + def msg(self, vars, mc, data): + if '_context' in vars: + print '%s got %s with context %s from %s, bad' % (self.netname, mc, + vars['_context'], + vars['_source']) + return + ident = vars.get('_identification') + source = vars['_source'] + if ident and self.identifications.get(source) == ident: + print 'ident of %s is %s'%(source, + self.identifications[vars['_source']]) + vars['_source'] = ident + vars.pop('_identification') + GroupMaster.msg(self, vars, mc, data) + def msg_error_invalid_authentication(self, vars, mc, data): + print 'invalid auth' + d = self.idcallbacks.pop((vars['_location'], vars['_source']), None) + if d: + d.errback(1) + def msg_notice_authentication(self, vars, mc, data): + print 'valid auth' + d = self.idcallbacks.pop((vars['_location'], vars['_source']), None) + if d: + self.identifications[vars['_location']] = vars['_source'] + self.reverseidentifications[vars['_source']] = vars['_location'] + d.callback(1) + def helper(self, res, vars, mc, data): + self.msg(vars, mc, data) + def msg_request_enter(self, vars, mc, data): + source = vars['_source'] + if '_identification' in vars: + ident = vars.get('_identification') + print 'looking up identification %s'%(ident,) + self.sendmsg({ '_target' : ident, + '_location' : source }, + '_request_authenticate', 'Is that really you?') + d = defer.Deferred() + d.addCallback(self.helper, vars, mc, data) + d.addErrback(self.helper, vars, mc, data) + self.idcallbacks[(source, ident)] = d + return + # TODO: while it is not the final plan to make this via parseUNL + # it is acceptable for now + origin = parseUNL(source)['root'] + v = { '_target' : source, + '_source' : self.netname } + if '_tag' in vars: + v['_tag'] = vars['_tag'] + else: + pass + if '_nick' in vars: + v['_nick'] = vars['_nick'] + else: + pass + if self.silent is True: + v['_control'] = '_silent' + self.sendmsg(v, '_echo_place_enter', + '[_nick] enters') + v.pop('_control', None) + v.pop('_tag', None) + + v['_list_members'] = self.showMembers() + [ source ] + self.sendmsg(v, '_status_place_members', '...') + + v.pop('_list_members') + if not self.member(source): + v['_source'] = source + self.castmsg(v, '_notice_place_enter', '[_nick] enters') + self.insert(vars['_source'], origin) + def msg_request_leave(self, vars, mc, data): + source = vars['_source'] + # TODO again + origin = parseUNL(source)['root'] + v = { '_source' : source } + if self.member(source): + self.castmsg({ '_source' : source }, '_notice_place_leave', + '[_nick] leaves') + self.remove(source) + else: # not a member for whatever reason + self.sendmsg({ '_target' : source}, '_echo_place_leave', + 'You are not even a member') + pass + if self.sizeof() == 0: + # empty + pass + def msg_message_public(self, vars, mc, data): + if self.silent is False and self.member(vars['_source']): + self.castmsg(vars, mc, data) + + +class Person(GroupMaster): + def __init__(self, netname, center): + GroupMaster.__init__(self, netname, center) + self.location = None + self.forward = False + self.places = {} + self.tags = {} + def disconnected(self, prot, loc): + """client has closed connection""" + self.location = None + print 'should leave', places.keys() + self.center.disconnected(prot, loc) + print self.center.remote_connections + def msg(self, vars, mc, data): + if '_context' in vars and not vars['_context'] in self.places: + # context faking, should be impossible as of now + print 'catching fake context!' + return + # dont forward messages from our location to it... + self.forward = vars['_source'] is not self.location + GroupMaster.msg(self, vars, mc, data) + if self.forward is True and self.location is not None: + vars['_target'] = self.location + self.sendmsg(vars, mc, data) + self.forward = False + # user is offline + def sendmsg(self, vars, mc, data): + # TODO: things like tag-creation for joins belong here + # as well as setting vars['_nick'] + if not '_source' in vars or vars['_source'] == self.location: + vars['_source'] = self.netname + if vars['_target'] == self.location: + self.center.sendmsg(vars, mc, data) + else: + GroupMaster.sendmsg(self, vars, mc, data) + def sendmsg_request_enter(self, vars, mc, data): + import random + t = str(random.randint(0, 10000)) + self.tags[vars['_target']] = t + vars['_tag'] = t + # tobi says, tags become invalid after a certain amount of time + self.center.sendmsg(vars, mc, data) + def sendmsg_request_leave(self, vars, mc, data): + # prepare to leave context after a reasonable amount of time + self.center.sendmsg(vars, mc, data) + def msgUnknownMethod(self, vars, mc, data): + if not self.location: + print 'person:unknown %s for offline user'%mc + def msg_request_link(self, vars, mc, data): + if vars['_password']: + self.msg_set_password(vars, mc, data) + else: + self.sendmsg({ '_target' : vars['_source'] }, + '_query_password', 'Is that really you?') + + self.forward = False + def msg_set_password(self, vars, mc, data): + """note that we have NO authorization""" + # TODO: here we have to use origin + if self.location is not None: + self.sendmsg({ '_target' : self.location}, + '_notice_unlink', + 'You have just lost the magic feeling.') + self.location = vars['_source'] + # TODO: we may need to deregister this thing... + self.center.link_unl(self.location, self) + self.sendmsg({ '_target' : self.location}, + '_notice_link', 'You are now connected') + self.forward = False + def msg_request_unlink(self, vars, mc, data): + if vars['_source'] == self.netname: # heh, this one is nifty! + self.sendmsg({'_source' : self.netname, + '_target' : self.location }, + '_notice_unlink', + 'You have just lost the magic feeling.') + self.location = None + self.center.unlink_unl(self.location, self.netname) + for place in self.places.copy(): + self.sendmsg({'_source' : self.netname, + '_target' : place }, + '_request_leave_logout', + 'l8er') + self.forward = False + def msg_echo_place_enter(self, vars, mc, data): + source = vars['_source'] + tag = vars.get('_tag') + if not source in self.tags: + self.forward = False + elif self.tags[source] != tag: + self.tags.pop(source) # wrong tag, you get invalid + self.forward = False + else: + self.places[vars['_source']] = 1 + self.center.join_context(source, self) + def msg_notice_place_leave(self, vars, mc, data): + if vars['_source'] == self.netname: + mc = '_echo' + mc[7:] + vars['_source'] = vars['_context'] + self.msg(vars, mc, data) + def msg_echo_place_leave(self, vars, mc, data): + place = vars.get('_context') or vars['_source'] + if place in self.places: + self.places.pop(place) + self.forward = True + def msg_request_roster(self, vars, mc, data): + print 'Roster: %s'%(self.flat) + self.sendmsg({'_source' : self.netname, + '_target' : self.location, + '_list_places' : self.places, + '_list_friends' : self.flat.keys()}, + '_notice_roster', + 'Your friends are [_friends], you have entered in [_list_places].') + self.forward = False + def msg_request_friendship(self, vars, mc, data): + """ + Friendship states: + known -> 0 + asked from -> 1 + offered to -> 2 + dual accepted -> 3 + """ + source = vars['_source'] + if self.member(source) and self.getData(source) == 2: + self.setData(source, 3) + mc = '_notice_friendship_establshed' + # TODO + else: + self.insert(source, None, 1) + self.forward = True + def sendmsg_request_friendship(self, vars, mc, data): + target = vars['_target'] + if self.member(target) and self.getData(target) == 1: + self.setData(target, 3) + mc = '_notice_friendship_established' + # TODO: echo_notice_friendship_established + else: + self.insert(target, None, 2) + self.center.sendmsg(vars, mc, data) + self.forward = True + def msg_notice_friendship_established(self, vars, mc, data): + source = vars['_source'] + if self.member(source) and self.getData(source) == 2: + self.setData(source, 3) diff --git a/fippos-twisted/test.py b/fippos-twisted/test.py new file mode 100644 index 0000000..1358429 --- /dev/null +++ b/fippos-twisted/test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +from pypsyc import parseUNL, UNL2Location, netLocation + +from pypsyc.objects.PSYCObject import PSYCQueueObject, PSYCObject, PSYCUNI, PSYCPlace, PSYCClient, ClientUser, ClientPlace, AdvancedManager, AdvancedPlace + +from pypsyc.net import PSYCUDPSender + +from pypsyc.center import ServerCenter, ClientCenter + +import asyncore + +if __name__ == '__main__': + import sys + type = sys.argv[1] + location = 'psyc://adamantine.aquarium' + center = None + if type == "server": + # tcp only server + center = ServerCenter([location + ':c']) + PSYCPlace(location + '/@place', center) + PSYCUNI(location + '/~fippo', center) + + if type == "server2": + # tcp and udp server on non-standard port + center = ServerCenter([location + ':4405']) + center.connect(location) + if type == "udpserver": + center = ServerCenter([location + ':4405d']) + PSYCUNI(location + ':4405/~fool', center) + if type == "udpclient": + # this should better be done via a Center which can parse + # URLs and them handle according to their transport + # but for a quick udp sender this is okay... + q = PSYCUDPSender(location + ':4405d') + q.msg({'_target' : 'psyc://adamantine.aquarium:d/~fippo'}, + {'_nick' : 'udpclient'}, + '_message_private', + 'hallo udp welt') + if type == "client": + center = ClientCenter() + PSYCObject('psyc://adamantine.aquarium', center) + # maybe add config information here? + # and let this thing connect as well? + me = PSYCClient('psyc://adamantine.aquarium/~fippo', center) + me.online() + # but thats the fast way to do it + me.sendmsg({'_target' : 'psyc://adamantine.aquarium/~fippo'}, + {'_password' : 'xfippox'}, + '_request_link', + '') + + + while center: + asyncore.poll(timeout=0.5) diff --git a/gtkpypsyc/client.py b/gtkpypsyc/client.py new file mode 100644 index 0000000..2662ecc --- /dev/null +++ b/gtkpypsyc/client.py @@ -0,0 +1,66 @@ +import gtk +import ConfigParser +import os, sys +import encodings.iso8859_1, encodings.ascii, encodings.cp1250, encodings.utf_8, encodings.utf_16 + +from pypsyc.center import Client + +from tabs.place import PlaceTab +from tabs.user import UserTab +from tabs.rss import RSSTab + + +class GTKClient(Client): + def __init__(self, config): + Client.__init__(self, config) + self.places_window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.places_window.connect('delete_event', + lambda w, e: gtk.main_quit()) + self.notebook = gtk.Notebook() + self.notebook.set_tab_pos(gtk.POS_BOTTOM) + self.places_window.add(self.notebook) + #self.places_window.show_all() + def show(self): + self.places_window.show_all() + return gtk.FALSE + def create_place(self, netname): + place = PlaceTab(netname, self) + self.notebook.append_page(place, gtk.Label(netname)) + self.notebook.show_all() + return place + +def poll(): + import asyncore + asyncore.poll(timeout = 0.0) + gtk.timeout_add(250, poll) # 0.25 secs + + +if __name__ == '__main__': + import sys + config = ConfigParser.ConfigParser() + if sys.platform == 'win32': + os.environ['PATH'] += ';lib;' + config.read('.pypsycrc') + else: + config.read(os.getenv('HOME') + '/.pypsycrc') + center = GTKClient(config) + + splash = gtk.Window() + splash.set_decorated(gtk.FALSE) + img = gtk.Image() + img.set_from_file(config.get('interface', + 'splashscreen')) + splash.add(img) + img.show() + splash.show() + gtk.timeout_add(3500, splash.hide) + gtk.timeout_add(4000, center.show) + + rsstab = RSSTab('psyc://adamantine.aquarium/@heise', center) + center.notebook.append_page(rsstab, gtk.Label('heise rss')) + usertab = UserTab('psyc://adamantine.aquarium/~fool', center) + center.notebook.append_page(usertab, gtk.Label('user dialog')) + + center.online() + poll() + gtk.main() diff --git a/gtkpypsyc/demo.py b/gtkpypsyc/demo.py new file mode 100644 index 0000000..64fb46e --- /dev/null +++ b/gtkpypsyc/demo.py @@ -0,0 +1,67 @@ +#!/usr/bin/envv python + +import gtk +import gobject + +class UI: + def __init__(self): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.netobjects = {} + + self.model = gtk.TreeStore(gtk.gdk.Pixbuf, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + self.view = gtk.TreeView(self.model) + self.view.set_headers_visible(gtk.FALSE) + + + pxrenderer = gtk.CellRendererPixbuf() + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn() + column.pack_start(pxrenderer, gtk.FALSE) + column.pack_end(renderer, gtk.TRUE) + column.set_attributes(pxrenderer, pixbuf=0, visible=2) + column.set_attributes(renderer, text=1) + self.view.append_column(column) + + self.view.show() + + vbox = gtk.VBox() + vbox.show() + vbox.pack_start(self.view) + + combo = gtk.Combo() + # TODO: disable editing of current entry, even cursor + combo.set_popdown_strings(['Offline', 'Online']) + combo.entry.set_editable(gtk.FALSE) + combo.show() + vbox.pack_end(combo, gtk.FALSE) + + self.friendstree = self.model.insert(None, 0, [None, 'Friends', False]) + self.placetree = self.model.insert(None, 1, [None, 'Places', False]) + + self.append_person('fippo') + self.append_place('PSYC') + + gtk.timeout_add(1000, self.change_icon, 1) + gtk.timeout_add(3000, self.change_icon, 2) + gtk.timeout_add(5000, self.change_icon, 3) + + self.view.expand_all() + + self.window.add(vbox) + self.window.show() + self.window.connect('delete_event', lambda e, w: gtk.main_quit()) + def append_person(self, name): + self.model.insert(self.friendstree, -1, [None, name, True]) + def append_place(self, name): + image = gtk.Image() + image.set_from_file('./pix/place/icon_f01.png') + pxbuf = image.get_pixbuf() + self.netobjects[name] = self.model.insert(self.placetree, -1, [pxbuf, name, True]) + def change_icon(self, image): + print 'change', image + return gtk.FALSE # call only once + +u = UI() +gtk.mainloop() diff --git a/gtkpypsyc/list.py b/gtkpypsyc/list.py new file mode 100644 index 0000000..49360a0 --- /dev/null +++ b/gtkpypsyc/list.py @@ -0,0 +1,71 @@ +#!/usr/bin/envv python + +import gtk +import gobject + + +class ListWindow: + def __init__(self): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.netobjects = {} + + self.model = gtk.TreeStore(gtk.gdk.Pixbuf, + str, # _nick_place + str, # _source/target/context + gobject.TYPE_BOOLEAN) + self.view = gtk.TreeView(self.model) + self.view.set_headers_visible(gtk.FALSE) + + pxrenderer = gtk.CellRendererPixbuf() + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn() + column.pack_start(pxrenderer, gtk.FALSE) + column.pack_end(renderer, gtk.TRUE) + column.set_attributes(pxrenderer, pixbuf=0, visible=3) + column.set_attributes(renderer, text=1) + self.view.append_column(column) + + self.view.show() + + vbox = gtk.VBox() + vbox.show() + vbox.pack_start(self.view) + + combo = gtk.Combo() + # TODO: disable editing of current entry, even cursor + # combobox may be false + combo.set_popdown_strings(['Offline', 'Online']) + combo.entry.set_editable(gtk.FALSE) + combo.show() + vbox.pack_end(combo, gtk.FALSE) + + self.friendstree = self.model.insert(None, 0, [None, 'Friends', '', False]) + self.placetree = self.model.insert(None, 1, [None, 'Places', '', False]) + + #gtk.timeout_add(1000, self.change_icon, 1) + + self.view.expand_all() + + self.window.add(vbox) + self.window.show() + #self.window.connect('delete_event', lambda e, w: gtk.main_quit()) +# def change_icon(self, image): +# return gtk.FALSE # call only once + def msg(self, vars, mc, data, caller): + if mc.startswith('_notice_place_enter'): + # only if source == self! + image = gtk.Image() + image.set_from_file('./pix/place/icon_f01.png') + self.netobjects[vars['_context']] = self.model.insert(self.placetree, -1, + [image.get_pixbuf(), + vars['_nick_place'], + vars['_context'], True]) + return + if mc.startswith('_notice_friend_present'): + image = gtk.Image() + image.set_from_file('./pix/friend/present.png') + self.netobjects[vars['_source']] = self.model.insert(self.friendstree, -1, + [image.get_pixbuf(), + vars['_nick'], + vars['_source'], True]) + return diff --git a/gtkpypsyc/rcfile b/gtkpypsyc/rcfile new file mode 100644 index 0000000..3f09dbf --- /dev/null +++ b/gtkpypsyc/rcfile @@ -0,0 +1,6 @@ +[main] +uni: psyc://your.host/~yournick +password: yourpass + +[interface] +splashscreen: pix/symlynX.gif diff --git a/gtkpypsyc/tabs/__init__.py b/gtkpypsyc/tabs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gtkpypsyc/tabs/place.py b/gtkpypsyc/tabs/place.py new file mode 100644 index 0000000..608aef7 --- /dev/null +++ b/gtkpypsyc/tabs/place.py @@ -0,0 +1,92 @@ +import gtk +from pypsyc.objects import PSYCObject + +class PlaceTab(gtk.Frame, PSYCObject): + def __init__(self, netname, center): + gtk.Frame.__init__(self, label = netname) + PSYCObject.__init__(self, netname, center) + + vbox = gtk.VBox() + self.add(vbox) + + self.textview = gtk.TextView() + self.textview.set_cursor_visible(gtk.FALSE) + self.textview.set_wrap_mode(gtk.WRAP_WORD) + self.textview.set_editable(gtk.FALSE) + self.textbuf = self.textview.get_buffer() + + hbox = gtk.HPaned() + hbox.pack1(self.textview) + vbox.pack_start(hbox) + + # uni, nick + self.nicklist = gtk.ListStore(str, str) + self.nicklist.set_sort_column_id(1, gtk.SORT_ASCENDING) + nickview = gtk.TreeView() + nickview.set_model(self.nicklist) + nickview.set_headers_visible(gtk.FALSE) + + col = gtk.TreeViewColumn('Nick', gtk.CellRendererText(), text = 1) + col.set_resizable(gtk.FALSE) + nickview.append_column(col) + + hbox.pack2(nickview, shrink=gtk.FALSE, resize=gtk.FALSE) + + self.entry = gtk.Entry() + self.entry.set_text('') + self.entry.connect('activate', self.onEntry) + vbox.pack_end(self.entry, gtk.FALSE) + self.show_all() + def append_text(self, text): + iter = self.textbuf.get_end_iter() + text = text.encode('utf-8') + '\n' + self.textbuf.insert(iter, text) + iter = self.textbuf.get_end_iter() + self.textbuf.place_cursor(iter) + mark = self.textbuf.get_insert() + self.textview.scroll_mark_onscreen(mark) + def onEntry(self, widget): + text = self.entry.get_text() + #print self.netname, '>>', text + self.entry.set_text('') + # handle a command... yes this should not be done that way + if text and text[0] == '/': + cmd = text[1:].upper() + if cmd == 'PART': + self.sendmsg({'_target' : self.netname}, + {}, + '_request_leave', + '') + return + self.sendmsg({'_target' : self.netname}, + {}, + '_message_public', + text) + def msg(self, vars, mc, data, caller): + if mc == '_message_public': + self.append_text('%s: %s'%(vars['_nick'], data)) + return + if mc == '_message_public_question': + self.append_text('%s %s: %s'%(vars['_nick'], + 'fragt', + data)) + return + if mc == '_message_public_text_action': + self.append_text('%s %s: %s'%(vars['_nick'], + vars['_action'], + data)) + return + if mc == '_status_place_members': + # _list_members, _list_members_nicks + for i in range(0, len(vars['_list_members'])): + self.nicklist.append((vars['_list_members'][i], + vars['_list_members_nicks'][i])) + return + if mc.startswith('_notice_place_leave'): + return + if mc.startswith('_notice_place_enter'): + self.nicklist.append((vars['_source'], vars['_nick'])) + return + PSYCObject.msg(self, vars, mc, data, caller) + + diff --git a/gtkpypsyc/tabs/rss.py b/gtkpypsyc/tabs/rss.py new file mode 100644 index 0000000..a1db525 --- /dev/null +++ b/gtkpypsyc/tabs/rss.py @@ -0,0 +1,65 @@ +import gtk +from pypsyc.objects import PSYCObject + +class RSSTab(gtk.Frame, PSYCObject): + def __init__(self, netname, center): + import gobject + gtk.Frame.__init__(self, label = netname) + PSYCObject.__init__(self, netname, center) + + vbox = gtk.VPaned() + self.add(vbox) + + self.label = gtk.Label() + self.model = gtk.ListStore(str, str, str) + self.tree = gtk.TreeView(self.model) + + # demos + self.model.append(['Handy-Flatrate von DoCoMo in Japan'.encode('utf-8'), + 'http://heise.de/newsticker/meldung/44883', + 'Ab Sommer 2004 will auch DoCoMo den Japanern für umgerechnet 31 Euro im Monat eine mobile Flatrate anbieten. mehr...' ]) + + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("Headline", renderer, text=0) + self.tree.append_column(column) + + self.tree.connect('row-activated', self.preview) + + s1 = gtk.ScrolledWindow() + s1.add(self.tree) + s1.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + vbox.pack1(s1, gtk.TRUE) + + self.textview = gtk.TextView() + self.textview.set_cursor_visible(gtk.FALSE) + self.textview.set_wrap_mode(gtk.WRAP_WORD) + self.textview.set_editable(gtk.FALSE) + self.textbuf = self.textview.get_buffer() + + s2 = gtk.ScrolledWindow() + s2.add(self.textview) + s2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + vbox.pack2(s2) + + #vbox.pack_end(self.textview, gtk.FALSE) + self.show_all() + def msg(self, vars, mc, data, caller): + if mc == '_status_place_description_news_rss': + self.label.set_label(data) + self.show_all() + return + if mc == '_notice_news_headline_rss': + renderer = gtk.CellRendererText() + text = vars.get('_news_headline').encode('utf-8') + column = gtk.TreeViewColumn(vars.get('_news_headline'), renderer, text=1) + self.tree.append_column(column) + self.show_all() + return + PSYCObject.msg(self, vars, mc, data, caller) + def preview(self, widget, path, column): + model, iter = self.tree.get_selection().get_selected() + self.set_text(model.get_value(iter, 1) + '\n' + model.get_value(iter, 2)) + def set_text(self, text): + text = text.encode('utf-8') + self.textbuf.set_text(text) diff --git a/gtkpypsyc/tabs/user.py b/gtkpypsyc/tabs/user.py new file mode 100644 index 0000000..0678e9d --- /dev/null +++ b/gtkpypsyc/tabs/user.py @@ -0,0 +1,30 @@ +import gtk +from pypsyc.objects import PSYCObject + +class UserTab(gtk.Frame): + def __init__(self, netname, center): + gtk.Frame.__init__(self, label = netname) + self.textview = gtk.TextView() + self.textview.set_cursor_visible(gtk.FALSE) + self.textview.set_wrap_mode(gtk.WRAP_WORD) + self.textview.set_editable(gtk.FALSE) + self.textbuf = self.textview.get_buffer() + s = gtk.ScrolledWindow() + s.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + s.add(self.textview) + + pane = gtk.VPaned() + #pane.pack1(s) + + h = gtk.VBox() + pane.pack2(h, shrink=gtk.FALSE, resize = gtk.FALSE) + pane.pack1(s) + box = gtk.HBox() + box.pack_start(gtk.Button(label='spacing'), expand=gtk.FALSE) + h.pack_start(box, expand=gtk.FALSE) + + self.entryfield = gtk.Entry() + h.pack_start(self.entryfield, expand=gtk.FALSE) + + self.add(pane) + self.show_all() diff --git a/gui-client.py b/gui-client.py new file mode 100755 index 0000000..989be5a --- /dev/null +++ b/gui-client.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# get stuff above at twistedmatrix.com +import ConfigParser +import os +import sys +# here we start importing our own modules +from pypsyc.PSYC.PSYCMessagecenter import PSYCMessagecenter +# import packages +from pypsyc.PSYC.PSYCRoom import Conferencing as ConferencingPackage +from pypsyc.PSYC.PSYCRoom import Friends as FriendsPackage +from pypsyc.PSYC.PSYCRoom import User as UserPackage +from pypsyc.PSYC.PSYCRoom import Authentication as AuthenticationPackage +from pypsyc.PSYC.PSYCRoom import Devel as DevelPackage +#import Listener + +# for linux/posix this should work +CONFIG_FILE = os.getenv("HOME") + "/.pypsyc/config" + +# windows users should uncomment the next line and comment the one above +# CONFIG_FILE = 'config' + +config = ConfigParser.ConfigParser() +config.read(CONFIG_FILE) + +center = PSYCMessagecenter(config) + +gui = None +Gui = None + +try: + guitype = config.get("gui", "type") + if guitype == "Tkinter": + import GUI.Tkinter.Gui as Gui + elif guitype == "Qt": + import GUI.Qt.Gui as Gui + elif guitype == 'wx': + import GUI.wx.devGui as Gui + + if Gui: + if guitype == 'wx': + gui = Gui.Application(sys.argv, center, config) + else: + gui = Gui.Application(sys.argv, center) + ## hier muss man besser entscheiden, was ein Toplevel() und was ein Tk() ist! + if config.get("packages", "conferencing") == "enabled": + conferencing_gui = Gui.RoomGui() + center.register(ConferencingPackage(conferencing_gui)) + + if config.get("packages", "friends") == "enabled": + friendlist = Gui.FriendList() + center.register(FriendsPackage(friendlist)) + + if config.get("packages", "user") == "enabled": + usergui = Gui.UserGui() + center.register(UserPackage(usergui)) + + if config.get("packages", "devel") == "enabled": + debuggui = Gui.MainWindow(center) + debuggui.title("debug window") + center.register(DevelPackage(debuggui)) + + ## hier was in der Art von setMainWindow() +except ConfigParser.NoSectionError: + print "Error reading config file" + +center.register(AuthenticationPackage(config)) + +gui.run() + diff --git a/in-silico/gui/__init__.py b/in-silico/gui/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/in-silico/gui/__init__.py @@ -0,0 +1 @@ + diff --git a/in-silico/gui/displays.py b/in-silico/gui/displays.py new file mode 100644 index 0000000..bb5af01 --- /dev/null +++ b/in-silico/gui/displays.py @@ -0,0 +1,248 @@ +# -*- coding: latin-1 -*- +#from wxPython.wx import * +import wx +# XXX Please feel free to modify and vershclimmbesser this piece of code +# especially the bits marked with XXX +##from pypsyc.objects.PSYCObject import +from pypsyc.objects.client import ClientUser, ClientPlace, PSYCClient +from pypsyc.objects import PSYCObject +from pypsyc.center import ClientCenter + +from pypsyc import netLocation, parsetext +#from psycObjects import PUser, PPlace, PClient, PServer + +import extras +import asyncore, sys, os + +ID_ABOUT = 1002 +ID_CONNECT = 1001 +ID_DISCONNECT = 10011 +ID_SAY = 3300 +ID_NOTEBOOK = 3301 +ID_STATUS = 9901 +ID_MENU = 9902 +ID_BUDDY_LIST = 9903 +ID_BUDDY_LIST_DK = 990301 +ID_EXIT = 1099 +ID_CLOSECHATNOTEBOOK = 2099 +ID_CLOSECHATNOTE = 2098 + + +class wxObject: + def __init__(self, parent, psyc_parent = None): + """ basic display object """ + if not psyc_parent: print 'WARNING: no psyc_parent set, this could be a problem' + self.netname = self.psyc_parent.netname + def append1(self, text): + """ use this to append multi/single line text""" + pass + + +class wxPTab(wx.Panel): + def __init__(self, parent, psyc_parent = None): + """ all das ausehen usw """ + wx.Panel.__init__(self, parent, -1, style=wx.NO_BORDER) + # we use logic_parent to call the functions pypsyc provides + # gui stuff in the wx.Blah objects, psyc stuff in the PBlah objects + if not psyc_parent: print 'WARNING: no psyc_parent set, this could be a problem' + self.psyc_parent = psyc_parent + config = self.psyc_parent.center.config + self.prompt = config['prompt'] + self.lock = 0 + self.counter = 0 + self.buffer = '' + self.text_box = wx.TextCtrl(self, -1, style=wx.NO_BORDER|wx.TE_MULTILINE|wx.TE_RICH2|wx.TE_READONLY, size=wx.DefaultSize) + self.entry_box = wx.TextCtrl(self, ID_SAY, style=wx.NO_BORDER|wx.TE_PROCESS_ENTER|wx.TE_RICH2|wx.TE_PROCESS_TAB, size=wx.DefaultSize) + + fontcolour = wx.Colour(config['fontcolour'][0], config['fontcolour'][1], config['fontcolour'][2]) + bgcolour = wx.Colour(config['bgcolour'][0], config['bgcolour'][1], config['bgcolour'][2]) + points = self.text_box.GetFont().GetPointSize() # get the current size + f = wx.Font(points, wx.MODERN, wx.NORMAL, wx.BOLD, False) + if os.name == 'nt': + self.text_box.SetDefaultStyle(wx.TextAttr(fontcolour, bgcolour, f)) + self.entry_box.SetDefaultStyle(wx.TextAttr(fontcolour, bgcolour, f)) + self.entry_box.SetBackgroundColour(bgcolour) + self.text_box.SetBackgroundColour(bgcolour) + self.SetBackgroundColour(bgcolour) + if os.name == 'posix': + self.text_box.SetDefaultStyle(wx.TextAttr(wx.NullColour, wx.NullColour, f)) + self.entry_box.SetDefaultStyle(wx.TextAttr(wx.NullColour, wx.NullColour, f)) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.text_box, 1, wx.EXPAND) + sizer.Add(self.entry_box, 0, wx.EXPAND) + self.SetSizer(sizer) + + #print 'wx.PTab: ' + str(dir(self)) + wx.EVT_TEXT_ENTER(self, ID_SAY, self.input) + + def input(self, event): + text = event.GetString() + text = text.lstrip() # only strip whites from the beginning + mc = '' + vars = {} + data = '' + vars['_target'] = self.psyc_parent.netname # eigentlich is target immer netname + if text != '' and text[0] == '/': + # houston we have a command + if text.startswith("/join"): # and text.__len__() > 16: + mc = '_request_enter' + vars['_target'] = text[6:] + #elif text.startswith("/part"): + # mc = '_request_leave' + # vars['_target'] = text[6:] + elif text.startswith('/password'): + mc = '_set_password' + t = text.split('/password') + if t[1] != '' and t[1] != ' ': + vars['_password'] = t[1] + vars['_target'] = self.psyc_parent.center.config['uni'] + else: + self.append1('Usage: /password ') + return + elif text.startswith('/connect'): + self.psyc_parent.center.connect(text[9:]) + self.append1('connecting to: ' + text[9:]) + self.entry_box.SetValue('') + return + elif text.startswith('/retrieve'): + mc = '_request_retrieve' + vars['_target'] = self.psyc_parent.center.config['uni'] + elif text.startswith('/store'): + mc = '_request_store' + vars['_target'] = self.psyc_parent.center.config['uni'] + vars['_storic'] = text[7:] + data = text[7:] + else: + vars['_target'] = self.psyc_parent.center.config['uni'] + #vars['_source'] + mc = '_request_execute' + data = text + + elif text!= '' and text.startswith('##debug'): + self.entry_box.SetValue('') + self.append1(str(getattr(self.psyc_parent.center, text[8:]))) + return + + elif text != '' and text[0] == '#': + self.append1(text) + self.entry_box.SetValue('') + return + + else: + mc = '_message_public' + try: + text2 = text.decode('iso-8859-15') + except: + text2 = text + print 'unicode' + data1 = text2.encode('iso-8859-1') + data = data1 + # am ende einfach wegschicken das fertige paket + self.entry_box.SetValue('') + #print_psyc(mmp, psyc, mc, data, 'place sending') + self.psyc_parent.sendmsg(vars, mc, data) + + def append1(self, line): + """ use this to append multi/single line text""" + if type(line) == type(u'öäü'): + try: + line = line.encode('iso-8859-1') + except: + print 'waahh' + if os.name == 'posix': + if self.lock == 0: + self.lock = 1 + if self.buffer != '': + self.text_box.AppendText('buffered: ' + self.buffer) + self.buffer = '' + for linex in line.split('\n'): + self.text_box.AppendText(self.prompt.encode('iso-8859-15') + linex) + self.text_box.AppendText('\n') + self.psyc_parent.center.Yield() + self.lock = 0 + else: + self.buffer += line + '\n' + elif os.name == 'nt': + # AppendText() doesn't seem to do the right thing in windows + for linex in line.split('\n'): + self.text_box.WriteText(self.prompt.encode('iso-8859-15') + linex) + self.text_box.WriteText('\n') + # we should find out how many 'lines' we write and scroll the right + # amount instead of scrolling 10000 at once XXX + self.text_box.ScrollLines(10000) + self.psyc_parent.center.Yield() + + def append(self, text): + """ use this for more then one line USE append1() this is obsolete!! """ + # is this broken? do we have to do the same as in append1()? + lines = text.split('\n') + for line in lines: + self.text_box.AppendText(self.prompt + line + '\n') + #print self.netname + ': ' + str(text) + + +class wxPFrame(wx.Frame): + def __init__(self, parent = None, psyc_parent = None, title = 'pypsyc-frame', pos = wx.DefaultPosition, size = wx.Size(920, 570), style = wx.DEFAULT_FRAME_STYLE): + wx.Frame.__init__(self, None, -1, title, size=size) + # do we need it here? wx.Frame is only supposed to be a container + # and shouldn't do that much -> only here to be flexible + # we use logic_parent to call the functions pypsyc provides + # gui stuff in the wx.Blah objects, psyc stuff in the PBlah objects + if not psyc_parent: print 'WARNING: no psyc_parent set, this could be a problem' + self.psyc_parent = psyc_parent + self.notebook = wx.Notebook(self, -1, style=0) + self.CreateStatusBar() + self.SetStatusText("welcome to pypsyc") + self.Show() + + def addTab(self, tab): + tab.create_display(parent=self.notebook) + self.notebook.AddPage(tab.display['default'], str(tab.netname), 1) + + +class wxPPlace(wxPTab): + def __init__(self, parent, psyc_parent = None): + wxPTab.__init__(self, parent=parent, psyc_parent=psyc_parent) + self.netname = self.psyc_parent.netname + + +class wxPUser(wxPTab): + def __init__(self, parent, psyc_parent = None): + wxPTab.__init__(self, parent=parent, psyc_parent=psyc_parent) + self.netname = self.psyc_parent.netname + + +class wxPClient(wx.Frame): + def __init__(self, parent = None, title = 'pypsyc', psyc_parent = None, pos = wx.DefaultPosition, size = wx.Size(100, 400), style = wx.DEFAULT_FRAME_STYLE): + wx.Frame.__init__(self, None, -1, title, size=size) + if not psyc_parent: print 'WARNING: no psyc_parent set, this could be a problem' + self.psyc_parent = psyc_parent + + self.CreateStatusBar() + self.SetStatusText("welcome to pypsyc") + self.BuddyList = wx.ListCtrl(self, 2222, style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.SUNKEN_BORDER) + self.BuddyList.InsertColumn(0, "ST") + self.BuddyList.InsertColumn(1, "Nick")# , wx.LIST_FORMAT_RIGHT) + self.BuddyList.SetColumnWidth(0, 20) + + self.status = wx.ComboBox(self, ID_STATUS, "", choices=["Offline", "Online", "Away"], size=(150,-1), style=wx.CB_DROPDOWN) + self.menu_button = wx.Button( self, ID_MENU, 'pypsyc') + self.exit_button = wx.Button( self, ID_EXIT, 'exit') + self.con_menu = wx.BoxSizer(wx.HORIZONTAL) + self.con_menu.Add(self.menu_button, 1, wx.ALIGN_BOTTOM) + self.con_menu.Add(self.exit_button, 1, wx.ALIGN_BOTTOM) + + sizer = wx.FlexGridSizer(3, 0 , 0,0) + sizer.Add(self.BuddyList, 1, wx.GROW) + sizer.Add(self.con_menu, 1,wx.GROW) + sizer.Add(self.status, 1,wx.GROW) + sizer.AddGrowableRow(0) + sizer.AddGrowableCol(0) + # do something so that the buttons don't vanish in a too small window + # this is h4x0r-style but does the job at the moment + sizer.SetItemMinSize(self.BuddyList, 30, 10) + sizer.SetMinSize(wx.Size(200,280)) + self.SetSizer(sizer) + self.SetAutoLayout(True) + self.Show() diff --git a/in-silico/gui/extras.py b/in-silico/gui/extras.py new file mode 100644 index 0000000..b823456 --- /dev/null +++ b/in-silico/gui/extras.py @@ -0,0 +1,66 @@ +# -*- coding: latin-1 -*- + +def print_psyc(vars, mc, data, caller = ''): + print '------ ' + str(caller) + print '-- mc:' + print mc + print '-- vars:' + print str(vars.items()) + print '-- data:' + print str([data]) + print '------' + + +class Context: + class __impl: + #from extras import Config + #def __init__(self): + # self.config = Config() + # self.hust = 'hallo garrit' + hust = 'hallo garrit' + def spam(self): + return id(self) + __instance = __impl() + + def __getattr__(self, attr): + return getattr(self.__instance, attr) + + def __setattr__(self, attr, value): + return setattr(self.__instance, attr, value) + + +class DevNull: + def __init__(self): + pass + def write(self, text): + pass + +class Config(dict): + def __init__(self, file = None): + self[u'uni'] = u'psyc://ve.symlynx.com/~betatim' + self[u'password'] = u'tim0914' + self[u'action'] = u'brabbel' + self[u'bgcolour'] = (255, 236, 191) + self[u'fontcolour'] = (34, 63, 92) + self[u'fontsize'] = 8 + self[u'prompt'] = u'* ' + + +class Display(dict): + """ this dict like object organises multiple displays """ + def __init__(self, display = None): + if display: + self['default'] = display + + def append1(self, text): + self['default'].append1(text) + + t = """def __setitem__(key = None, item = None): + if key and item: + dict.__setitem__(key, item) + elif item: + if self['default']: + print 'Overwrite default display explicitly' + else: + self['default'] = item""" + diff --git a/in-silico/gui/psycObjects.py b/in-silico/gui/psycObjects.py new file mode 100644 index 0000000..9929160 --- /dev/null +++ b/in-silico/gui/psycObjects.py @@ -0,0 +1,297 @@ +# -*- coding: latin-1 -*- + +#from wxPython.wx import * +import wx +# XXX Please feel free to modify and vershclimmbesser this piece of code +# especially the bits marked with XXX +##from pypsyc.objects.PSYCObject import +from pypsyc.objects.client import ClientUser, ClientPlace, PSYCClient +from pypsyc.objects import PSYCObject +from pypsyc.center import ClientCenter + +from pypsyc import netLocation, parsetext, UNL2Location +from displays import wxPFrame, wxPTab, wxPPlace, wxPUser, wxPClient +import asyncore, sys, os +import extras + +class PObject(PSYCObject): + def __init___(self, netname, center): + PSYCObject.__init__(self, netname, center) + self.context = extras.Context() + self.display = extras.Display() + self.queue = [] # a place to store pakets + + def create_display(self, display=None, parent=None, name = 'default'): + """ create a new display for this object """ + # display := display object + # parent := if we don't have a display object we need to create one + # and perhaps need a parent + # name := the display needs a name so we can access it later + if name == 'default': + if self.display['default']: + print 'there is already a default display, \ + if you want a new display give it a unique name' + return + else: + if display: + # a display has to have some basic functionality + self.display[name] = display + else: + # we're a bit confused now because we can't know what sort of + # display to create, but perhas someone comes up with a idea later + print 'HELP!' + + def msg(self, vars, mc, data, caller): + PSYCObject.msg(self, vars, mc, data, caller) + # store things until we know what do with them + f = (vars, mc, data, caller) + self.queue.append(f) + + +class wxCenter(wx.App, ClientCenter): + def __init__(self): #vllt noch cmd line args uebergeben or so + wx.App.__init__(self, 0) + #self.run() + + def OnInit(self): + ClientCenter.__init__(self) + self.context = extras.Context() + self.context.config = extras.Config() + self.config = self.context.config + + self.frame = wxPFrame(psyc_parent=self) # nen frame um die tabs aufzuheben + self.timer = wx.PyTimer(self.socket_check) + self.timer.Start(100) # alle 100 ms + return True + + def socket_check(self): + asyncore.poll(timeout=0.0) # das sollte vllt klappen + + def run(self): + puni = UNL2Location(self.config['uni']) + self.client = PClient(puni, self) + self.create_server_place(netLocation(self.config['uni'])) + # XXX extremly buggy here!! + #self.create_server_place('psyc://adamantine.fippo.int') + + self.default_connection = netLocation(self.config['uni']) + self.client.online() + self.client.create_display(self) + self.SetTopWindow(self.client.display['default']) + self.client.display['default'].Show() + + self.MainLoop() + + def sendmsg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, 'center sending') + ClientCenter.sendmsg(self, vars, mc, data, caller) + + def set_default_connection(self, uni): + #netLocation(self.uni) + print 'set_default_connect: ' + netLocation(uni) + self.default_connection = netLocation(uni) + + def msg(self, vars, mc, data, caller): + source = vars['_context'] or vars['_source'] + if not ClientCenter.msg(self, vars, mc, data, caller): + # create a psyc object of unknown type + #self.create_psyc_object(source) + #print '\n\n !!WE ARE IN TROUBLE!!\n\n\n' + print '' + + def create_psyc_object(self, netname): + # this could do some better guessing about the type + self.create_server_place(netname) + # XXX + + def create_user(self, netname): + t = PUser(netname, self) + #print 'i am a USER and my name is: ' + netname + ' / ' + netLocation(netname) + self.frame.addTab(t) + return t + + def create_place(self, netname): + t = PPlace(netname, self) + #print 'i am a PLACE and my name is: ' + netname + ' / ' + netLocation(netname) + self.frame.addTab(t) + return t + + def create_server_place(self, netname): + t = PServer(netname, self) + #print 'i am a SERVER and my name is: ' + netname + ' / ' + netLocation(netname) + self.frame.addTab(t) + self.client.create_display(name='server', display=t.display['default']) + #sys.stdout = extras.DevNull() + return t + +class PPlace(ClientPlace): + def __init__(self, netname, center): + ClientPlace.__init__(self, netname, center) + self.display = extras.Display() + print 'i am a PLACE and my name is: ' + self.netname + ' / ' + netLocation(self.netname) + + def create_display(self, parent = None, name = 'default', display = None): + if name == 'default': + self.display['default'] = wxPPlace(parent=parent, psyc_parent=self) + else: + self.display[name] = display + + def msg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, 'place') + #print 'ddd' + str(type(data)) + parsedtext = parsetext(vars, mc, data) + #print dir(parsedtext) , type(parsedtext) + #parsedtext = parsedtext.encode('iso-8859-15') + #print type(parsedtext) + if mc == '_message_public': + line = u'' + if vars['_nick']: + line += vars['_nick'] + else: + line += vars['_source'] + + if vars['_action']: line += ' ' + vars['_action'] + '>' + else: line += '>' + try: + line += ' ' + parsedtext.decode('iso-8859-15') + except: + line += ' ' + parsedtext + self.display.append1(line) + elif mc == '_message_public_question': + line = u'' + line += vars['_nick'] + if vars['_action']: line += ' ' + vars['_action'] + '>' + else: line += ' fragt>' + try: + line += ' ' + parsedtext.decode('iso-8859-15') + except: + line += ' ' + parsedtext + self.display.append1(line) + elif mc.startswith('_status_place_topic'): + self.display.append1('TOPIC: ' + parsedtext) + else: + self.display.append1(parsedtext) + + +class PUser(ClientUser): + def __init__(self, netname, center): + ClientUser.__init__(self, netname, center) + self.display = extras.Display() + print 'i am a USER and my name is: ' + self.netname + ' / ' + netLocation(self.netname) + + def create_display(self, parent = None, name = 'default', display = None): + if name == 'default': + self.display['default'] = wxPUser(parent=parent, psyc_parent=self) + else: + self.display[name] = display + + def msg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, caller) + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + if mc == '_message_private': + self.display.append1(parsedtext) + else: + self.display.append1(parsedtext) + + +class PServer(PSYCObject): + def __init__(self, netname, center): + PSYCObject.__init__(self, netname, center) + print 'registered server' + #print 'PServer: ' + str(dir(self)) + self.display = extras.Display() + print 'i am a SERVER and my name is: ' + self.netname + ' / ' + netLocation(self.netname) + + def create_display(self, parent = None, name = 'default', display = None): + if name == 'default': + self.display['default'] = wxPPlace(parent=parent, psyc_parent=self) + else: + self.display[name] = display + + def msg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, 'server') + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + #self.center.Yield() + if mc == '_notice_circuit_established' and vars['_source'] == netLocation(self.center.config['uni']): + mc = '_request_link' + vars = {} + data ='' + vars['_target'] = self.center.config['uni'] + self.sendmsg(vars, mc, data) + self.display.append1(parsedtext) + else: + self.display.append1(parsedtext) + + def write(self, text): + """ redirected stdout """ + # we have problems if text contains \n and with print adding an extra \n + lines = text.split('\n') + for line in lines: + if line == '': + return + else: + self.display.append1('printed: ' + line) + + def sendmsg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, 'server sending...') + PSYCObject.sendmsg(self, vars, mc, data, caller) + + +class PClient(PSYCClient): + def __init__(self, netname, center): + PSYCClient.__init__(self, netname, center) + self.display = extras.Display() + #self.extra_display = {} # XXX + #self.create_display(parent=None) + print 'default_conmnect is --> ' + self.center.default_connection + print 'i am a CLIENT and my name is: ' + self.netname + ' / ' + netLocation(self.netname) + + def create_display(self, parent = None, name = 'default', display = None): + if name == 'default': + self.display[name] = wxPClient(parent=parent, psyc_parent=self) + else: + if self.display.has_key(name): + # XXX tempory hack for multi server connect + print 'HACKING GOING ON!' + return + else: + self.display[name] = display + + def set_display(self, which, display): + self.display[which] = display + + def msg(self, vars, mc, data, caller = None): + extras.print_psyc(vars, mc, data, 'client') + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + if mc == '_query_password': + if self.center.config['password'] != '': + mc = '_set_password' + vars = {'_password' : self.center.config['password']} + vars['_target'] = self.netname + data ='' + self.sendmsg(vars, mc, data) + else: + self.display['server'].append1("Please enter your password or choose a different nick if you don't know the password") + self.display['server'].entry_box.SetValue('/password ') + elif mc == '_status_friends': + self.display['server'].append1(parsedtext) + + elif mc == '_error_invalid_password': + self.display['server'].append1(parsedtext) + self.display['server'].append1("Please enter your password or choose a different nick if you don't know the password") + self.display['server'].entry_box.SetValue('/password ') + else: + self.display['server'].append1(parsedtext) + + def online(self): + self.center.connect(self.center.default_connection) + #mc = '_request_link' + #mmp = {} + #psyc = {} + #data ='' + #mmp['_target'] = self.netname + + #self.sendmsg(mmp, psyc, mc, data) diff --git a/in-silico/gui/wx.py b/in-silico/gui/wx.py new file mode 100644 index 0000000..679dfa8 --- /dev/null +++ b/in-silico/gui/wx.py @@ -0,0 +1,450 @@ +# -*- coding: latin-1 -*- +from wxPython.wx import * + +# XXX Please feel free to modify and vershclimmbesser this piece of code +# especially the bits marked with XXX +##from pypsyc.objects.PSYCObject import +from pypsyc.objects.PSYCObject import ClientUser, ClientPlace, PSYCClient, PSYCObject +from pypsyc.center import ClientCenter + +from pypsyc import netLocation, parsetext + +import asyncore, sys, os + +# ich glaub ich weiss jetzt wofuer die hier toll sind +# !!! +# this list isn't up-to-date anymore, stolen from the old gui code +# !!! +ID_ABOUT = 1002 +ID_CONNECT = 1001 +ID_DISCONNECT = 10011 +ID_SAY = 3300 +ID_NOTEBOOK = 3301 +ID_STATUS = 9901 +ID_MENU = 9902 +ID_BUDDY_LIST = 9903 +ID_BUDDY_LIST_DK = 990301 +ID_EXIT = 1099 +ID_CLOSECHATNOTEBOOK = 2099 +ID_CLOSECHATNOTE = 2098 + +def opj(path): + """Convert paths to the platform-specific separator""" + return apply(os.path.join, tuple(path.split('/'))) + +def print_psyc(vars, mc, data, caller = ''): + print '------ ' + str(caller) + print '-- mc:' + print mc + print '-- vars:' + print str(vars.items()) + print '-- data:' + print str([data]) + print '------' + + +class DevNull: + def write(self, text): + return + +class wxCenter(wxApp, ClientCenter): + def __init__(self): #vllt noch cmd line args uebergeben or so + wxApp.__init__(self, 0) + self.run() + + def OnInit(self): + ClientCenter.__init__(self) + self.config = {} # = { 'uin' : 'psyc://blah/~user', 'foo': 'bar'} + self.config[u'uni'] = u'psyc://ve.symlynx.com/~betatim3' + self.config[u'password'] = u'tim' + self.config[u'action'] = u'brabbel' + self.config[u'bgcolour'] = (255, 236, 191) + self.config[u'fontcolour'] = (34, 63, 92) + self.config[u'fontsize'] = 8 + self.config[u'prompt'] = u'* ' + print sys.getdefaultencoding() + print os.name + + #print self.config + self.frame = wxPFrame(logic_parent=self) # nen frame um die tabs aufzuheben + self.client = PClient(self.config['uni'], self) + self.create_server_place(netLocation(self.config['uni'])) # eignetlich is es kein place aber naja + + self.default_connection = netLocation(self.config['uni']) + self.client.online() + self.client.create_display(self) + self.SetTopWindow(self.client.display) + self.client.display.Show() + + self.timer = wxPyTimer(self.socket_check) + self.timer.Start(100) # alle 100 ms + return True + + def input(self, event): + print event.GetString() + event.Skip() + + def socket_check(self): + asyncore.poll(timeout=0.0) # das sollte vllt klappen + + def run(self): + self.MainLoop() + + def sendmsg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, 'center sending') + ClientCenter.sendmsg(self, vars, mc, data, caller) + + def set_default_connection(self, uni): + #netLocation(self.uni) + print 'set_default_connect: ' + netLocation(uni) + self.default_connection = netLocation(uni) + + def create_user(self, netname): + #print "this should create a user" + t = PUser(netname, self) + # nicht tab.display damit wir netname haben ,]] + # auch wenn es extrem strange ist wir uebergeben + self.frame.addTab(t) + return t + + def create_place(self, netname): + #print "this should create a room" + t = PPlace(netname, self) + self.frame.addTab(t) # same here + return t + + def create_server_place(self, netname): + t = PServer(netname, self) + self.frame.addTab(t, extra_display='server') # same here + sys.stdout = t + return t + + +class wxPTab(wxPanel): + def __init__(self, parent, logic_parent = None): + """ all das ausehen usw """ + wxPanel.__init__(self, parent, -1, style=0) + # we use logic_parent to call the functions pypsyc provides + # gui stuff in the wxBlah objects, psyc stuff in the PBlah objects + if not logic_parent: print 'WARNING: no logic_parent set, this could be a problem' + self.logic_parent = logic_parent + config = self.logic_parent.center.config + self.prompt = config['prompt'] + self.lock = 0 + self.counter = 0 + self.buffer = '' + self.text_box = wxTextCtrl(self, -1, style=wxTE_MULTILINE|wxTE_RICH2|wxTE_READONLY, size=wxDefaultSize) + self.entry_box = wxTextCtrl(self, ID_SAY, style=wxTE_PROCESS_ENTER|wxTE_RICH2|wxTE_PROCESS_TAB, size=wxDefaultSize) + + fontcolour = wxColour(config['fontcolour'][0], config['fontcolour'][1], config['fontcolour'][2]) + bgcolour = wxColour(config['bgcolour'][0], config['bgcolour'][1], config['bgcolour'][2]) + self.entry_box.SetBackgroundColour(bgcolour) + self.text_box.SetBackgroundColour(bgcolour) + points = self.text_box.GetFont().GetPointSize() # get the current size + f = wxFont(points, wxMODERN, wxNORMAL, wxBOLD, False) + self.text_box.SetDefaultStyle(wxTextAttr(fontcolour, bgcolour, f)) + self.entry_box.SetDefaultStyle(wxTextAttr(fontcolour, bgcolour, f)) + self.SetBackgroundColour(bgcolour) + + sizer = wxBoxSizer(wxVERTICAL) + sizer.Add(self.text_box, 1, wxEXPAND) + sizer.Add(self.entry_box, 0, wxEXPAND) + self.SetSizer(sizer) + + #print 'wxPTab: ' + str(dir(self)) + EVT_TEXT_ENTER(self, ID_SAY, self.input) + + def input(self, event): + text = event.GetString() + text = text.lstrip() # only strip whites from the beginning + mc = '' + vars = {} + data = '' + vars['_target'] = self.logic_parent.netname # eigentlich is target immer netname + if text != '' and text[0] == '/': + # houston we have a command + if text.startswith("/join"): # and text.__len__() > 16: + mc = '_request_enter' + vars['_target'] = text[6:] + elif text.startswith("/part"): + mc = '_request_leave' + vars['_target'] = text[6:] + elif text.startswith('/password'): + mc = '_set_password' + t = text.split('/password') + if t[1] != '' and t[1] != ' ': + vars['_password'] = t[1] + vars['_target'] = self.logic_parent.center.config['uni'] + else: + self.append1('Usage: /password ') + return + elif text.startswith('/retrieve'): + mc = '_request_retrieve' + vars['_target'] = self.logic_parent.center.config['uni'] + else: + mc = '_request_execute' + data = text + + elif text != '' and text[0] == '#': + self.append1(text) + self.entry_box.SetValue('') + return + + else: + mc = '_message_public' + try: + text2 = text.decode('iso-8859-15') + except: + text2 = text + print 'unicode' + data1 = text2.encode('iso-8859-1') + data = data1 + # am ende einfach wegschicken das fertige paket + self.entry_box.SetValue('') + #print_psyc(mmp, psyc, mc, data, 'place sending') + self.logic_parent.sendmsg(vars, mc, data) + + def append1(self, line): + """ use this to append multi/single line text""" + if type(line) == type(u'öäü'): + line = line.encode('iso-8859-15') + if os.name == 'posix': + if self.lock == 0: + self.lock = 1 + if self.buffer != '': + self.text_box.AppendText('buffered: ' + self.buffer) + self.buffer = '' + for linex in line.split('\n'): + self.text_box.AppendText(self.prompt.encode('iso-8859-15') + linex) + self.text_box.AppendText('\n') + self.logic_parent.center.Yield() + self.lock = 0 + else: + self.buffer += line + '\n' + elif os.name == 'nt': + # AppendText() doesn't seem to do the right thing in windows + for linex in line.split('\n'): + self.text_box.WriteText(self.prompt.encode('iso-8859-15') + linex) + self.text_box.WriteText('\n') + # we should find out how many 'lines' we write and scroll the right + # amount instead of scrolling 10000 at once XXX + self.text_box.ScrollLines(10000) + self.logic_parent.center.Yield() + + def append(self, text): + """ use this for more then one line USE append1() this is obsolete!! """ + # is this broken? do we have to do the same as in append1()? + lines = text.split('\n') + for line in lines: + self.text_box.AppendText(self.prompt + line + '\n') + #print self.netname + ': ' + str(text) + + +class wxPFrame(wxFrame): + def __init__(self, parent = None, logic_parent = None, title = 'pypsyc-frame', pos = wxDefaultPosition, size = wxSize(920, 570), style = wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, None, -1, title, size=size) + # do we need it here? wxFrame is only supposed to be a container + # and shouldn't do that much -> only here to be flexible + # we use logic_parent to call the functions pypsyc provides + # gui stuff in the wxBlah objects, psyc stuff in the PBlah objects + if not logic_parent: print 'WARNING: no logic_parent set, this could be a problem' + self.logic_parent = logic_parent + self.notebook = wxNotebook(self, -1, style=0) + self.CreateStatusBar() + self.SetStatusText("welcome to pypsyc") + self.Show() + + def addTab(self, tab,extra_display = None): + tab.create_display(self.notebook) + if extra_display: + self.logic_parent.client.set_display(extra_display, tab.display) + self.notebook.AddPage(tab.display, str(tab.netname), 1) + + +class wxPPlace(wxPTab): + def __init__(self, parent, logic_parent = None): + wxPTab.__init__(self, parent=parent, logic_parent=logic_parent) + self.netname = self.logic_parent.netname + + +class wxPUser(wxPTab): + def __init__(self, parent, logic_parent = None): + wxPTab.__init__(self, parent=parent, logic_parent=logic_parent) + self.netname = self.logic_parent.netname + + +class wxPClient(wxFrame): + def __init__(self, parent = None, title = 'pypsyc', logic_parent = None, pos = wxDefaultPosition, size = wxSize(100, 400), style = wxDEFAULT_FRAME_STYLE): + wxFrame.__init__(self, None, -1, title, size=size) + # we use logic_parent to call the functions pypsyc provides + # gui stuff in the wxBlah objects, psyc stuff in the PBlah objects + if not logic_parent: print 'WARNING: no logic_parent set, this could be a problem' + self.logic_parent = logic_parent + + self.CreateStatusBar() + self.SetStatusText("welcome to pypsyc") + self.BuddyList = wxListCtrl(self, 2222, style=wxLC_REPORT|wxLC_SINGLE_SEL|wxSUNKEN_BORDER) + self.BuddyList.InsertColumn(0, "ST") + self.BuddyList.InsertColumn(1, "Nick")# , wxLIST_FORMAT_RIGHT) + self.BuddyList.SetColumnWidth(0, 20) + + self.status = wxComboBox(self, ID_STATUS, "", choices=["Offline", "Online", "Away"], size=(150,-1), style=wxCB_DROPDOWN) + self.menu_button = wxButton( self, ID_MENU, 'pypsyc') + self.exit_button = wxButton( self, ID_EXIT, 'exit') + self.con_menu = wxBoxSizer(wxHORIZONTAL) + self.con_menu.Add(self.menu_button, 1, wxALIGN_BOTTOM) + self.con_menu.Add(self.exit_button, 1, wxALIGN_BOTTOM) + + sizer = wxFlexGridSizer(3, 0 , 0,0) + sizer.Add(self.BuddyList, 1, wxGROW) + sizer.Add(self.con_menu, 1,wxGROW) + sizer.Add(self.status, 1,wxGROW) + sizer.AddGrowableRow(0) + sizer.AddGrowableCol(0) + # do something so that the buttons don't vanish in a too small window + # this is h4x0r-style but does the job at the moment + sizer.SetItemMinSize(self.BuddyList, 30, 10) + sizer.SetMinSize(wxSize(200,280)) + self.SetSizer(sizer) + self.SetAutoLayout(true) + self.Show() + + +class PPlace(ClientPlace): + def __init__(self, netname, center): + ClientPlace.__init__(self, netname, center) + self.display = None + + def create_display(self, parent): + self.display = wxPPlace(parent=parent, logic_parent=self) + + def msg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, 'place') + #print 'ddd' + str(type(data)) + parsedtext = parsetext(vars, mc, data) + #print dir(parsedtext) , type(parsedtext) + #parsedtext = parsedtext.encode('iso-8859-15') + #print type(parsedtext) + if mc == '_message_public': + line = u'' + line += vars['_nick'] + if vars['_action']: line += ' ' + vars['_action'] + '>' + else: line += '>' + try: + line += ' ' + parsedtext.decode('iso-8859-15') + except: + line += ' ' + parsedtext + self.display.append1(line) + elif mc.startswith('_status_place_topic'): + self.display.append1('TOPIC: ' + parsedtext) + else: + self.display.append1(parsedtext) + + +class PUser(ClientUser): + def __init__(self, netname, center): + ClientUser.__init__(self, netname, center) + self.display = None + + def create_display(self, parent): + self.display = wxPUser(parent=parent, logic_parent=self) + + def msg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, caller) + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + if mc == '_message_public': + self.display.append1(parsedtext) + else: + self.display.append1(parsedtext) + + +class PServer(PSYCObject): + def __init__(self, netname, center): + PSYCObject.__init__(self, netname, center) + print 'registered server' + #print 'PServer: ' + str(dir(self)) + self.display = None + + def create_display(self, parent): + self.display = wxPPlace(parent=parent, logic_parent=self) + + def msg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, 'server') + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + #self.center.Yield() + if mc == '_notice_circuit_established': + mc = '_request_link' + vars = {} + data ='' + vars['_target'] = self.center.config['uni'] + self.sendmsg(vars, mc, data) + self.display.append1(parsedtext) + else: + self.display.append1(parsedtext) + + def write(self, text): + """ redirected stdout """ + # we have problems if text contains \n and with print adding an extra \n + lines = text.split('\n') + for line in lines: + if line == '': + return + else: + self.display.append1('printed: ' + line) + + def sendmsg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, 'server sending...') + PSYCObject.sendmsg(self, vars, mc, data, caller) + + +class PClient(PSYCClient): + def __init__(self, netname, center): + PSYCClient.__init__(self, netname, center) + self.display = None + self.extra_display = {} # XXX + #self.create_display(parent=None) + print 'default_conmnect is --> ' + self.center.default_connection + + def create_display(self, parent): + self.display = wxPClient(parent=parent) + def set_display(self, which, display): + self.extra_display[which] = display + + + def msg(self, vars, mc, data, caller = None): + print_psyc(vars, mc, data, 'client') + parsedtext = parsetext(vars, mc,data) + #parsedtext = parsedtext.encode('iso-8859-15') + if mc == '_query_password': + if self.center.config['password'] != '': + mc = '_set_password' + vars = {'_password' : self.center.config['password']} + vars['_target'] = self.netname + data ='' + self.sendmsg(vars, mc, data) + else: + self.extra_display['server'].append1("Please enter your password or choose a different nick if you don't know the password") + self.extra_display['server'].entry_box.SetValue('/password ') + elif mc == '_status_friends': + self.extra_display['server'].append1(parsedtext) + + elif mc == '_error_invalid_password': + self.extra_display['server'].append1(parsedtext) + self.extra_display['server'].append1("Please enter your password or choose a different nick if you don't know the password") + self.extra_display['server'].entry_box.SetValue('/password ') + else: + self.extra_display['server'].append1(parsedtext) + + def online(self): + self.center.connect(self.center.default_connection) + #mc = '_request_link' + #mmp = {} + #psyc = {} + #data ='' + #mmp['_target'] = self.netname + + #self.sendmsg(mmp, psyc, mc, data) + + diff --git a/in-silico/test.py b/in-silico/test.py new file mode 100644 index 0000000..3247f00 --- /dev/null +++ b/in-silico/test.py @@ -0,0 +1,8 @@ +from gui.psycObjects import wxCenter + + +if __name__ == '__main__': + center = wxCenter() + center.run() + #center = wx.wxCenter() + #center.heartbeat() diff --git a/mjacob/TODO.txt b/mjacob/TODO.txt new file mode 100644 index 0000000..f38a9e2 --- /dev/null +++ b/mjacob/TODO.txt @@ -0,0 +1,8 @@ +-"AuthModul" muss erweitert (Passwort) und aus twisted_client.py verschoben +werden +-Ereignisse (bzw. alles außer Nachrichten) werden noch nicht angezeigt +-private messages und in andere Räume joinen funktionieren noch nicht +-Einrichtungsassistent oder etwas in die Richtung wäre nicht schlecht +-Schließen funktioniert nicht (man muss im Moment /quit in den Client, Ctrl-C +im Konsolenfenster eingeben und Fenster schließen) +-noch viel mehr... diff --git a/mjacob/UI/__init__.py b/mjacob/UI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/UI/__init__.pyc b/mjacob/UI/__init__.pyc new file mode 100644 index 0000000..054c29d Binary files /dev/null and b/mjacob/UI/__init__.pyc differ diff --git a/mjacob/UI/wx_.py b/mjacob/UI/wx_.py new file mode 100755 index 0000000..10a4e4a --- /dev/null +++ b/mjacob/UI/wx_.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import wx + +from base import Module +from lib import psyctext +from twisted_client import install_wx + + +class Tab(wx.Panel): + def __init__(self, parent, name): + self.name = name + wx.Panel.__init__(self, parent, -1) + self.textctrl = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE|wx.TE_READONLY) + self.users = [] + + def OnResize(event): + self.textctrl.SetSize(event.GetSize()) + self.Bind(wx.EVT_SIZE, OnResize, self) + + +class MainWindow(Module, wx.Frame): + methods = ['_message*'] + def __init__(self, ui): + wx.Frame.__init__(self, None, -1, size = (800, 600)) + + self.Bind(wx.EVT_CLOSE, self.OnClose, self) + + self.ui = ui + + self.tabs = {} + splitter = wx.SplitterWindow(self, -1) + + self.pl = wx.Panel(splitter, -1) + sizer = wx.GridBagSizer(0, 0) + + self.nb = wx.Notebook(self.pl, -1, style = wx.NB_TOP) + sizer.Add(self.nb, (0, 0), (1, 1), wx.EXPAND) + sizer.AddGrowableCol(0) + sizer.AddGrowableRow(0) + + self.input = wx.TextCtrl(self.pl, -1) + sizer.Add(self.input, (1, 0), (1, 1), wx.EXPAND) + self.pl.SetSizerAndFit(sizer) + self.add_tab("psyc://%s" % self.ui.base.config.host) + + self.pr = wx.Panel(splitter, -1) # right panel + self.userlist = wx.ListBox(self.pr, -1, (0, 5), style = wx.SUNKEN_BORDER) + + self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnChangeTab, self.nb) + + def OnResize(event): + size = event.GetSize() + self.userlist.SetSize((size[0], size[1] - 10)) + self.pr.Bind(wx.EVT_SIZE, OnResize, self.pr) + + splitter.SplitVertically(self.pl, self.pr, self.GetSize()[0] * 0.7) + splitter.SetSashGravity(1) + + mainmenu = wx.Menu() + + menuitem = mainmenu.Append(-1, '&Connect', 'Connect to the server') + self.Bind(wx.EVT_MENU, self.OnConnect, menuitem) + + menuitem = mainmenu.Append(-1, '&Debug window', + 'Open or close debug window') + self.Bind(wx.EVT_MENU, self.OnOpenDebugWindow, menuitem) + + mainmenu.AppendSeparator() + + menuitem = mainmenu.Append(-1, '&Exit', 'Exit pyPSYC') + self.Bind(wx.EVT_MENU, self.OnClose, menuitem) + + menubar = wx.MenuBar() + menubar.Append(mainmenu, '&pyPSYC') + + self.SetMenuBar(menubar) + + self.input.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + self.Show(True) + + def OnKeyDown(self, event): + if event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + pagename = self.nb.GetCurrentPage().name + value = self.input.GetValue() + if value: + self.center.input(pagename, value) + self.input.SetValue('') + + event.Skip() + + def OnChangeTab(self, event): + self.userlist.Set(self.nb.GetCurrentPage().users) + + def OnConnect(self, event): + self.center.connect(self.center.config.host) + self.ui.connected = True + + def OnOpenDebugWindow(self, event): + # open or close window: + if not self.ui.debugwindow.Show(True): self.ui.debugwindow.Show(False) + + def OnClose(self, event): + pass + + def add_tab(self, name): + self.tabs[name] = Tab(self.nb, name) + self.nb.AddPage(self.tabs[name], name) + + def handle_message_public(self, packet, physsource): + self._print(packet.mmpvars['_context'], "%s: %s" % (packet.psycvars['_nick'], psyctext(packet))) + + handle_message_echo_public = handle_message_public + + def _print(self, window, text): + if window not in self.tabs: + self.add_tab(window) + self.tabs[window].textctrl.AppendText(text + '\n') + + + +class UI: + def __init__(self, base): + self.base = base + self.gui = wx.PySimpleApp() + self.mainwindow = MainWindow(self) + self.base.register_module(self.mainwindow) + install_wx(self.gui) diff --git a/mjacob/UI/wx_.pyc b/mjacob/UI/wx_.pyc new file mode 100644 index 0000000..818e9cb Binary files /dev/null and b/mjacob/UI/wx_.pyc differ diff --git a/mjacob/base.py b/mjacob/base.py new file mode 100755 index 0000000..6a1cd30 --- /dev/null +++ b/mjacob/base.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## base.py + + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + + +from lib.parse import MMPParser, PSYCParser +from lib.render import MMPRenderer, PSYCRenderer +from lib.packets import MMPPacket, PSYCPacket + + +debug = False + + +class Module: + methods = [] + + def register_center(self, center): + self.center = center + + def received(self, packet, physsource): + func = getattr(self, 'handle_%s' % packet.mc[1:], None) + + if func: + func(packet, physsource) + + else: + self.handle(packet, physsource) + + +class MMPCircuit: + def __init__(self, center): + self.center = center + self.mmp_parser = MMPParser() + self.mmp_parser.recv_packet = self.recv_packet + self.mmp_renderer = MMPRenderer() + + def init(self): + self.send_mmp_packet(MMPPacket()) + + def recv_packet(self, packet): + self.center.recv_mmp_packet(packet, self) + + def send_mmp_packet(self, packet): + self._send(self.mmp_renderer.render(packet)) + + +class PSYCCircuit(MMPCircuit): + def __init__(self, center): + MMPCircuit.__init__(self, center) + self.psyc_parser = PSYCParser() + self.psyc_renderer = PSYCRenderer() + + def send_psyc_packet(self, packet): + self.send_mmp_packet(self.psyc_renderer.render(packet)) + + +class Plugin: + def __init__(self, name): + self.name = name + self.p = __import__('plugins.' + self.name, (), (), [self.name]) + self.needs = self.p.needs + + def load(self): + self.instance = self.p.Module() + + +class Config: + def __init__(self, app): + self.app = app + + self.settings_folder = self.get_settings_folder() + self.configfile = self.open_configfile() + + +class BaseCenter: + type = 'base' + registered_modules = [] + handlers = {} + plugins = {} + connections = {} + + def load_plugin(self, name): + p = Plugin(name) + + for load in p.needs: + self.load_plugin(load) + + p.load() + self.register_module(p.instance) + self.plugins[name] = p + + def register_module(self, module): + for i in module.methods: + if i in self.handlers: + self.handlers[i].append(module) + + else: + self.handlers[i] = [module] + + module.register_center(self) + self.registered_modules.append(module) + + def connected(self, connection, host): + self.connections[host] = connection + self._connected(connection, host) + + def _connected(self, connection, host): + pass + + def recv_mmp_packet(self, packet, physsource): # called by circuit + raise NotImplementedError + + def recv_psyc_packet(self, packet, physsource): # called by subclass + handlers = [] + + for key, value in self.handlers.items(): + if key == packet.mc or \ + (key[-1] == '*' and packet.mc.startswith(key[:-1])): + handlers.extend(value) + + for i in handlers: + i.received(packet, physsource) + + def send_mmp_packet(self, packet, physdest): + raise NotImplemented + + def _send_psyc_packet(self, packet, physdest): + if debug: # WARNING: Do not show all packets + print """packet to %s:: +mmpvars: %s +psycvars: %s +mc: %s +text: %s +""" % (physdest, packet.mmpvars, packet.psycvars, packet.mc, packet.data) + + physdest.send_psyc_packet(packet) diff --git a/mjacob/base.pyc b/mjacob/base.pyc new file mode 100644 index 0000000..75c1c07 Binary files /dev/null and b/mjacob/base.pyc differ diff --git a/mjacob/client.py b/mjacob/client.py new file mode 100755 index 0000000..d8594de --- /dev/null +++ b/mjacob/client.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +import os + +from base import Config, BaseCenter +from lib.packets import PSYCPacket, MMPPacket +from lib import get_host, get_user + +class ClientConfig(Config): + def __init__(self, app): + Config.__init__(self, app) + self.uni_file = os.path.join(self.settings_folder, 'me') + self.clientconfig_file = os.path.join(self.settings_folder, 'pypsyc', 'config') + + #self.uni = self.get_uni() + import random + self.uni = 'psyc://beta.ve.symlynX.com/~pypsyctest' + ''.join([random.Random().choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789') for i in [0]*10]) + #self.uni = raw_input('uni?: ') + self.host = get_host(self.uni) + self.username = get_user(self.uni) + + self.ui = 'wx' + + def get_settings_folder(self): + # resolve settings folder + if os.name == 'posix': + folder = os.path.join(os.environ.get('HOME'), ".psyc") + elif os.name == 'nt': + folder = os.path.join(os.environ.get('APPDATA'), 'psyc') + else: + folder = os.path.join(os.getcwd(), 'psyc') + + # create settings folder if necessary + try: + os.mkdir(folder) + except OSError: + pass + + return folder + + def get_uni(self): + f = file(self.uni_file) + uni = f.read().strip() + f.close() + return uni + + def open_configfile(self): + return None + + +class ClientCenter(BaseCenter): + type = 'client' + + def __init__(self): + self.config = ClientConfig(self) + + def _connected(self, connection, host): + pass + + def recv_mmp_packet(self, packet, physsource): + if not packet.vars and not packet.body: + return + psyc_packet = physsource.psyc_parser.parse(packet) + self.recv_psyc_packet(psyc_packet, physsource) + + def send_psyc_packet(self, packet): + self._send_psyc_packet(packet, self.connections[self.config.host]) + + def input(self, pagename, text): + if '@' in pagename: + self.send_psyc_packet(PSYCPacket(mmpvars = {'_target': pagename}, mc = '_message_public', data = text)) + + if text.startswith('/'): + self.command(pagename, text) + + def command(self, pagename, text): + if text == '/quit' or '/bye': + self.send_psyc_packet(PSYCPacket(mmpvars = {'_target': self.config.uni}, mc = '_request_execute', data = text)) + + else: + self.send_psyc_packet(PSYCPacket(psycvars = {'_focus': pagename}, mc = '_request_input', data = text)) diff --git a/mjacob/client.pyc b/mjacob/client.pyc new file mode 100644 index 0000000..ef6dffd Binary files /dev/null and b/mjacob/client.pyc differ diff --git a/mjacob/lib/__init__.py b/mjacob/lib/__init__.py new file mode 100755 index 0000000..6fcab5d --- /dev/null +++ b/mjacob/lib/__init__.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## __init__.py + + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + + +import re + +# the following two functions are from the 'original' pypsyc +# (and slightly modified) + +def get_host(uni): + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/(.+)?\/\@(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/(.+)$", uni) + if m: return m.group(1) + + raise "invalid uni" + +def get_user(uni): + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(1) + + raise "invalid uni" + +def psyctext(packet): + text = packet.data + for key, value in packet.vars.items(): + text = text.replace('[%s]' % key, str(value)) + return text + + +class Vars(dict): + def __init__(self, vars, existing = {}): + dict.__init__(self, existing) + self.vars = vars + self.vars.update(existing) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.vars.__setitem__(key, value) + + +all_mmpvars = ( +'_source', '_source_identification', '_source_location', '_source_relay', +'_target', '_context', '_counter', '_length', '_initialize', '_fragment', +'_encoding', '_amount_fragments', '_list_using_modules', +'_list_require_modules', '_list_understand_modules', '_list_using_encoding', +'_list_require_encoding', '_list_understand_encoding', +'_list_using_protocols', '_list_require_protocols', +'_list_understand_protocols', '_trace', '_tag', '_tag_relay', '_relay') diff --git a/mjacob/lib/packets.py b/mjacob/lib/packets.py new file mode 100755 index 0000000..c9e19b7 --- /dev/null +++ b/mjacob/lib/packets.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## packets.py + + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +from __init__ import Vars + +class MMPPacket: + def __init__(self, mmpvars = {}, body = ''): + self.vars = {} + self.mmpvars = Vars(self.vars, mmpvars) + self.body = body + + +class PSYCPacket(MMPPacket): + def __init__(self, mmpvars = {}, psycvars = {}, mc = None, data = ''): + MMPPacket.__init__(self, mmpvars) + self.psycvars = Vars(self.vars, psycvars) + self.mc = mc + self.data = data diff --git a/mjacob/lib/parse.py b/mjacob/lib/parse.py new file mode 100755 index 0000000..568b6d6 --- /dev/null +++ b/mjacob/lib/parse.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## parse.py + + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + + +from packets import MMPPacket, PSYCPacket + +def parse_vars(lines): + cvars = {} + pvars = {} + + _list = None + + for i in lines: + tmp = i.split('\t') + if tmp[0] == ':': + vars[_list].append(tmp[1]) + continue + + elif tmp[0].startswith(':'): + vars = cvars + + elif tmp[0].startswith('='): + vars = pvars + + if tmp[0][1:6] == '_list': + _list = tmp[0][1:] + vars[_list] = [tmp[1]] + + else: + _list = None + vars[tmp[0][1:]] = tmp[1] + + return cvars, pvars + + +class LineBased: + def __init__(self): + self.__buffer = '' + self.linemode = True + + def data(self, data): + self.__buffer += data + while self.linemode and self.__buffer: + try: + line, self.__buffer = self.__buffer.split('\n', 1) + self.line(line) + except ValueError: + return + + else: + if not self.linemode: + __buffer = self.__buffer + self.__buffer = '' + self.raw(__buffer) + + +class MMPParser(LineBased): # callback-based + pvars = {} + def __init__(self): + LineBased.__init__(self) + self.reset() + + def reset(self): + self.mode = 'vars' + self.varlines = [] + + self.cvars = {} + self.body = '' + + def line(self, line): + if line == '.': + self.recv() + elif self.mode == 'vars': + if line: + self.varlines.append(line) + else: + self.cvars, pvars = parse_vars(self.varlines) + + for key, value in pvars.items(): + if value: + self.pvars[key] = value + else: + if key in self.pvars: + del self.pvars[key] + + self.mode = 'body' + + else: + self.body += line + '\n' + + def recv(self): + vars = self.pvars.copy() + vars.update(self.cvars) + if self.body and self.body[-1] == '\n': + self.body = self.body[:-1] + packet = MMPPacket(vars, self.body) + packet.not_to_render = True + self.recv_packet(packet) + self.reset() + + def recv_packet(self, packet): + raise NotImplementedError + + +class PSYCParser: + pvars = {} + + def parse(self, packet): + lines = packet.body.split('\n') + _is_text = False + cvars = {} + varlines = [] + textlines = [] + mc = '' + + for i in lines: + if not i: + continue + + tmp = i.split('\t') + + if _is_text: + textlines.append(i) + + elif i[0] in (':', '='): + varlines.append(i) + + elif i[0] == '_': + cvars, pvars = parse_vars(varlines) + + for key, value in pvars.items(): + if value: + self.pvars[key] = value + else: + if key in self.pvars: + del self.pvars[key] + + mc = i + _is_text = True + + text = '\n'.join(textlines) + vars = self.pvars.copy() + vars.update(cvars) + packet = PSYCPacket(packet.mmpvars, vars, mc, text) + packet.not_to_render = True + return packet + + +if __name__ == '__main__': + MMPParser().data(':_context\ti\n=_foo\tbar\n\n_message_public\nHello\n.\n') + MMPParser().data(':_context\ti\n\n_message_public\nHello\n.\n') diff --git a/mjacob/lib/render.py b/mjacob/lib/render.py new file mode 100755 index 0000000..1df188d --- /dev/null +++ b/mjacob/lib/render.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## render.py + + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +from packets import MMPPacket + +def to_vars(cvars = {}, pvars = {}, other = {}): + data = '' + for key, value in cvars.items(): + data += ":%s\t%s\n" % (key, value) + for key, value in pvars.items(): + data += "=%s\t%s\n" % (key, value) + for key, value in other.items(): + data += "%s\t%s\n" % (key, value) + return data + + +class MMPRenderer: + pvars = {} + new_pvars = {} + def render(self, packet): + data = '' + if packet.mmpvars: + data += to_vars(packet.mmpvars, self.new_pvars) + '\n' + self.new_pvars = {} + + data += '%s\n.\n' % packet.body + + return data.encode('utf-8') + + def set_pvar(self, key, value): + self.new_pvars[key] = value + self.pvars[key] = value + + def del_pvar(self, key): + self.new_pvars[key] = '' + del self.pvars[key] + + +class PSYCRenderer: + pvars = {} + new_pvars = {} + def render(self, packet): + data = '' + if packet.psycvars: + data += to_vars(packet.psycvars, self.new_pvars) + self.new_pvars = {} + + if packet.mc: + data += packet.mc + '\n' + + if packet.data: + data += packet.data + + return MMPPacket(packet.mmpvars, data) + + def set_pvar(self, key, value): + self.new_pvars[key] = value + self.pvars[key] = value + + def del_pvar(self, key): + self.new_pvars[key] = '' + del self.pvars[key] diff --git a/mjacob/old/MMP/MMPProtocol.py b/mjacob/old/MMP/MMPProtocol.py new file mode 100755 index 0000000..70e75d4 --- /dev/null +++ b/mjacob/old/MMP/MMPProtocol.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +from twisted.protocols import basic + + +def convert_lines(lines): + """convert list of lines to vars, method (mc) and text""" + _is_text = False + dict = {} + mc = None + textlines = [] + for i in lines: + tmp = i.split('\t') + + if _is_text: + textlines.append(i) + + elif tmp[0] == ':': # second or later item in the list + dict[_list].append(tmp[1]) + + elif i[0] in (':', '='): + if tmp[0][1:6] == '_list': + _list = tmp[0][1:] + dict[_list] = [tmp[1]] + else: dict[tmp[0][1:]] = tmp[1] + + elif i[0] == '_': + mc = i + _is_text = True + + text = '\n'.join(textlines) + return dict, mc, text + + +class MMPProtocol(basic.LineReceiver): + def __init__(self, callback): + self.msg = callback + self.delimiter = '\n' + self.initialized = False + self.charset = 'UTF-8' + self.reset() + + def reset(self): + self.lines = [] + self._reset() + + def _reset(self): + raise NotImplementedError('Implement in Subclass.') + + def lineReceived(self, line): + if not self.initialized: + assert line == '.', 'first line is not ".", line is "%s"' % line + self.initialized = True + return + + if line == '.': # packet ends + self.recv_packet() + else: + self.lines.append(line) + + def recv_packet(self): + h2b = self.lines.index('') # head to body delimiter + mmp = self.lines[:h2b] + data = self.lines[h2b+1:] + mmpvars = convert_lines(mmp)[0] + if '_length' in mmpvars: + bodylength = mmpvars['_length'] + else: + bodylength = len(data) + + if len(data) == bodylength: + self._recv_packet(mmpvars, data) + self.reset() + + def _recv_packet(self, mmpvars, data): + raise NotImplementedError('Implement in Subclass.') + + def _send_packet(self, mmpvars, data): + mmpstring = '' + if mmpvars: + for key, value in mmpvars.items(): + mmpstring += "=%s\t%s\n" % (key, value) + + if not data: + data = '' + + send = "%s%s.\n" % (mmpstring, data) + #print ('send', send.encode(self.charset)) + self.transport.write(send.encode(self.charset)) diff --git a/mjacob/old/MMP/__init__.py b/mjacob/old/MMP/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/old/PSYC/PSYCPacket.py b/mjacob/old/PSYC/PSYCPacket.py new file mode 100755 index 0000000..f8202df --- /dev/null +++ b/mjacob/old/PSYC/PSYCPacket.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class PSYCPacket: + def __init__(self, mmpvars = {}, psycvars = {}, mc = None, text = ''): + self.mmpvars = mmpvars + self.psycvars = psycvars + self.vars = mmpvars.copy() + self.vars.update(psycvars) + self.mc = mc + self.text = text + + def __repr__(self): + return 'vars: %s, mc: %s, text: %s' % (self.vars, self.mc, self.text) diff --git a/mjacob/old/PSYC/PSYCProtocol.py b/mjacob/old/PSYC/PSYCProtocol.py new file mode 100755 index 0000000..7f2858d --- /dev/null +++ b/mjacob/old/PSYC/PSYCProtocol.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + + +from MMP.MMPProtocol import MMPProtocol, convert_lines +from PSYC.PSYCPacket import PSYCPacket + +class PSYCProtocol(MMPProtocol): + mmpvars = ( +'_source', '_source_identification', '_source_location', '_source_relay', +'_target', '_context', '_counter', '_length', '_initialize', '_fragment', +'_encoding', '_amount_fragments', '_list_using_modules', +'_list_require_modules', '_list_understand_modules', '_list_using_encoding', +'_list_require_encoding', '_list_understand_encoding', +'_list_using_protocols', '_list_require_protocols', +'_list_understand_protocols', '_trace', '_tag', '_tag_relay', '_relay') + + def _reset(self): + pass + + def __init__(self, callback): + MMPProtocol.__init__(self, callback) + + def _recv_packet(self, mmpvars, data): + #print 'recv' + psycvars, mc, text = convert_lines(data) + packet = PSYCPacket(mmpvars, psycvars, mc, text) + + func = getattr(self, "handle_%s" % packet.mc[1:], None) + + not_call_msg = False + + if func: + not_call_msg = func(packet) + + if not not_call_msg: + self.msg(packet) + + def handle_status_circuit(self, packet): + self._send_packet(None, None) # send an empty mmp packet (single dot) + self.send_packet(PSYCPacket(mmpvars = + {'_target': self.factory.config.uni}, mc = '_request_link')) + + self.charset = packet.vars['_using_characters'] + + def send_packet(self, packet): + self.factory.ui.outpacketcount += 1 + self.factory.ui.pre_send(packet) + data = '' + + if packet.psycvars: + data += '\n' + for key, value in packet.psycvars.items(): + data += ":%s\t%s\n" % (key, value) + + if packet.mc: + data += packet.mc + '\n' + + if packet.text: + data += packet.text + '\n' + + self._send_packet(packet.mmpvars, data) + + ################################ + # implementation of the PSYC API + ################################ + + def msg(self, packet): + raise NotImplementedError + + def sendmsg(self, target, mc, data, vars): + mmpvars = {'_target': target} + psycvars = {} + for key, value in vars.items(): + if i in self.mmpvars: + mmpvars[key] = value + else: + psycvars[key] = value + + packet = PSYCPacket(mmpvars, psycvars, mc, data) + self.send_packet(packet) + + def castmsg(self, source, mc, data, vars): + mmpvars = {'_context': source} + psycvars = {} + for key, value in vars.items(): + if i in self.mmpvars: + mmpvars[key] = value + else: + psycvars[key] = value + + packet = PSYCPacket(mmpvars, psycvars, mc, data) + self.send_packet(packet) diff --git a/mjacob/old/PSYC/__init__.py b/mjacob/old/PSYC/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/old/UI/__init__.py b/mjacob/old/UI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/old/UI/base.py b/mjacob/old/UI/base.py new file mode 100755 index 0000000..faa361f --- /dev/null +++ b/mjacob/old/UI/base.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +import re + +from twisted.internet import reactor + +from PSYC.PSYCPacket import PSYCPacket + +re_var = re.compile('\[_.[^\[]+\]') + +class BaseUI: + ignored_mcs = [] + ignored_users = [] + mc_prefixes = ('_notice', '_error', '_info', '_status') + inpacketcount = 0 + outpacketcount = 0 + connected = False + + def recv(self, packet): + self.inpacketcount += 1 + if packet.mc in self.ignored_mcs: + return + + #if packet.vars['_source'] in self.ignored_users: + # return + + self.pre_print(packet) + self._insert_vars(packet) + self.print_(packet) + + def _insert_vars(self, packet): + while True: + x = re_var.search(packet.text) + if not x: + break + tmp = x.group() + if tmp[1:-1] in packet.vars: + packet.text = packet.text.replace(tmp, packet.vars[tmp[1:-1]]) + + def print_(self, packet): + func = None + for i in self.mc_prefixes: + if packet.mc.startswith(i): + func = getattr(self, 'handle_%s_' % i[1:], None) + + tmp = getattr(self, 'handle_%s' % packet.mc[1:], None) + + if tmp: + func = tmp + + if func: + func(packet) + + else: + self._print('Server', 'unhandled packetmc (Packet %s)' + % self.inpacketcount) + + def handle_notice_(self, packet): + self._print('Server', "%s: %s" % (packet.mc, packet.text)) + + def handle_error_(self, packet): + self._print('Server', "%s: %s" % (packet.mc, packet.text)) + + def handle_info_(self, packet): + self._print('Server', "%s: %s" % (packet.mc, packet.text)) + + def handle_status_(self, packet): + self._print('Server', "%s: %s" % (packet.mc, packet.text)) + + def handle_message_public(self, packet): + self._print(packet.vars['_context'], '%s says: %s' + % (packet.vars['_nick'], packet.text)) + + def handle_status_place_topic_official(self, packet): + pass + + def handle_echo_logoff(self, packet): + reactor.stop() + + def server_command(self, command, target): + if target == 'Server': + target = 'psyc://%s/' % self.factory.config.host + + if command.startswith('/go'): + self.factory.sc.send_packet(PSYCPacket(mmpvars = {'_target': command[4:]}, psycvars = {'_nick': self.factory.config.username}, mc = '_request_enter')) + + elif command.startswith('/join'): + self.factory.sc.send_packet(PSYCPacket(mmpvars = {'_target': command[6:]}, psycvars = {'_nick': self.factory.config.username}, mc = '_request_enter')) + + elif command.startswith('/quit'): + self.factory.sc.send_packet(PSYCPacket(mmpvars = {'_target': self.factory.config.uni, '_source_identification': self.factory.config.uni}, mc = '_request_execute', text = 'bye')) + + + else: + self.factory.sc.send_packet(PSYCPacket(mmpvars = {'_target': self.factory.config.uni, '_source_identification': self.factory.config.uni}, mc = '_request_input', text = command)) + + #if command.startswith('/quit'): + # reactor.stop() + + #if command.startwith('/part'): + # self.factory.sc.send_packet(PSYCPacket()) diff --git a/mjacob/old/UI/text/Interface.py b/mjacob/old/UI/text/Interface.py new file mode 100755 index 0000000..aef78c2 --- /dev/null +++ b/mjacob/old/UI/text/Interface.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +from UI import base + +class Interface(base.BaseUI): + def __init__(self, factory): + self.factory = factory + self.factory.connect() + #BaseUI.__init__(self) + + def _print(self, window, text): + print '%s: %s' % (window, text) diff --git a/mjacob/old/UI/text/__init__.py b/mjacob/old/UI/text/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/old/UI/wx/Interface.py b/mjacob/old/UI/wx/Interface.py new file mode 100755 index 0000000..b49d88b --- /dev/null +++ b/mjacob/old/UI/wx/Interface.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +import wx + +from twisted.internet import reactor + +from UI import base +from PSYC.PSYCPacket import PSYCPacket + +class Tab(wx.Panel): + def __init__(self, parent, name): + self.name = name + wx.Panel.__init__(self, parent, -1) + self.textctrl = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE|wx.TE_READONLY) + self.users = [] + + def OnResize(event): + self.textctrl.SetSize(event.GetSize()) + self.Bind(wx.EVT_SIZE, OnResize, self) + + +class MainWindow(wx.Frame): + def __init__(self, ui): + wx.Frame.__init__(self, None, -1, size = (800, 600)) + + self.Bind(wx.EVT_CLOSE, self.OnClose, self) + + self.ui = ui + self.factory = self.ui.factory + + self.tabs = {} + splitter = wx.SplitterWindow(self, -1) + + self.pl = wx.Panel(splitter, -1) + sizer = wx.GridBagSizer(0, 0) + + self.nb = wx.Notebook(self.pl, -1, style = wx.NB_TOP) + sizer.Add(self.nb, (0, 0), (1, 1), wx.EXPAND) + sizer.AddGrowableCol(0) + sizer.AddGrowableRow(0) + + self.input = wx.TextCtrl(self.pl, -1) + sizer.Add(self.input, (1, 0), (1, 1), wx.EXPAND) + self.pl.SetSizerAndFit(sizer) + self.add_tab('Server') + + self.pr = wx.Panel(splitter, -1) # right panel + self.userlist = wx.ListBox(self.pr, -1, (0, 5), style = wx.SUNKEN_BORDER) + + self.nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnChangeTab, self.nb) + + def OnResize(event): + size = event.GetSize() + self.userlist.SetSize((size[0], size[1] - 10)) + self.pr.Bind(wx.EVT_SIZE, OnResize, self.pr) + + splitter.SplitVertically(self.pl, self.pr, self.GetSize()[0] * 0.7) + splitter.SetSashGravity(1) + + mainmenu = wx.Menu() + + menuitem = mainmenu.Append(-1, '&Connect', 'Connect to the server') + self.Bind(wx.EVT_MENU, self.OnConnect, menuitem) + + menuitem = mainmenu.Append(-1, '&Debug window', + 'Open or close debug window') + self.Bind(wx.EVT_MENU, self.OnOpenDebugWindow, menuitem) + + mainmenu.AppendSeparator() + + menuitem = mainmenu.Append(-1, '&Exit', 'Exit pyPSYC') + self.Bind(wx.EVT_MENU, self.OnClose, menuitem) + + menubar = wx.MenuBar() + menubar.Append(mainmenu, '&pyPSYC') + + self.SetMenuBar(menubar) + + self.input.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + self.Show(True) + + def OnKeyDown(self, event): + if event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): + pagename = self.nb.GetCurrentPage().name + value = self.input.GetValue() + if value: + if pagename == 'Server' or value.startswith('/'): + self.ui.server_command(value, self.nb.GetCurrentPage().name) + + elif '@' in pagename: + self.factory.sc.castmsg(pagename, '_message_public', + value, {}) + + elif '~' in pagename: + self.factory.sc.sendmsg(pagename, '_message_private', + value, {}) + + self.input.SetValue('') + + event.Skip() + + def OnChangeTab(self, event): + self.userlist.Set(self.nb.GetCurrentPage().users) + + def OnConnect(self, event): + self.factory.connect() + self.ui.connected = True + + def OnOpenDebugWindow(self, event): + # open or close window: + if not self.ui.debugwindow.Show(True): self.ui.debugwindow.Show(False) + + def OnClose(self, event): + if self.ui.connected: + self.ui.server_command('/quit', None) + else: + reactor.stop() + + def add_tab(self, name): + self.tabs[name] = Tab(self.nb, name) + self.nb.AddPage(self.tabs[name], name) + + +class DebugWindow(wx.Frame): + def __init__(self, factory): + wx.Frame.__init__(self, parent = None, id = -1, + title = 'pyPSYC debug window', size = (800, 600), + style = wx.DEFAULT_FRAME_STYLE| + wx.NO_FULL_REPAINT_ON_RESIZE) + self.Bind(wx.EVT_CLOSE, self.OnClose, self) + + self.tree = wx.TreeCtrl(self, -1, wx.DefaultPosition, (-1,-1), + wx.TR_HIDE_ROOT|wx.TR_HAS_BUTTONS) + self.treeroot = self.tree.AddRoot('Programmer') + + def OnClose(self, event): + self.Show(False) + + +class Interface(base.BaseUI): + def __init__(self, factory): + self.factory = factory + self.gui = wx.PySimpleApp() + self.mainwindow = MainWindow(self) + self.debugwindow = DebugWindow(self) + + def _print(self, window, text): + if window not in self.mainwindow.tabs: + self.mainwindow.add_tab(window) + self.mainwindow.tabs[window].textctrl.AppendText(text + '\n') + + def pre_print(self, packet): + print ('recv', packet) + tmp = self.debugwindow.tree.AppendItem(self.debugwindow.treeroot, + 'Incoming Packet %s' + % self.inpacketcount) + self.debugwindow.tree.AppendItem(tmp, "MMPVars: %s" % packet.mmpvars) + self.debugwindow.tree.AppendItem(tmp, "PSYCVars: %s" % packet.psycvars) + self.debugwindow.tree.AppendItem(tmp, "Method: %s" % packet.mc) + self.debugwindow.tree.AppendItem(tmp, "Text: %s" % packet.text) + + def pre_send(self, packet): + print ('send', packet) + tmp = self.debugwindow.tree.AppendItem(self.debugwindow.treeroot, + 'Outgoing Packet %s' + % self.outpacketcount) + self.debugwindow.tree.AppendItem(tmp, "MMPVars: %s" % packet.mmpvars) + self.debugwindow.tree.AppendItem(tmp, "PSYCVars: %s" % packet.psycvars) + self.debugwindow.tree.AppendItem(tmp, "Method: %s" % packet.mc) + self.debugwindow.tree.AppendItem(tmp, "Text: %s" % packet.text) + + def handle_echo_place_enter_login(self, packet): + self._print(packet.vars['_source_relay'], packet.text) + self.mainwindow.tabs[packet.vars['_source_relay']].users = packet.vars['_list_members'] + + def handle_echo_place_enter(self, packet): + self._print(packet.vars['_nick_place'], packet.text) + self.mainwindow.tabs[packet.vars['_nick_place']].users = packet.vars['_list_members'] + + +if __name__ == '__main__': + app = wx.PySimpleApp() + MainWindow() + app.MainLoop() + +##class Tab(wx.Panel): +## def __init__(self, name, main): +## self.name = name +## self.main = main +## self.ui = self.main.ui +## self.factory = self.ui.factory +## wx.Panel.__init__(self, main.nb, -1) +## self.textbox = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE| +## wx.TE_READONLY,) +## self.input = wx.TextCtrl(self, -1) +## #self.input.SetFocus() +## self.input.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) +## +## def OnKeyDown(self, event): +## if event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER): +## if self.input.GetValue(): +## self.factory.sc.castmsg(self.name, '_message_public', +## self.input.GetValue(), {}) +## self.input.SetValue('') +## +## event.Skip() +# +# +#class Tab(wx.Panel): +# def __init__(self, name, main): +# self.name = name +# self.main = main +# self.ui = self.main.ui +# if hasattr(self.ui, 'factory'): +# self.factory = self.ui.factory +# wx.Panel.__init__(self, main.nb, -1, size = wx.DefaultSize) +# #wx.Frame.__init__(self, None, -1, size = wx.DefaultSize) +# +# #splitter = wx.SplitterWindow(self, -1) +# #pl = wx.Panel(splitter, -1, size = wx.DefaultSize) # panel left +# ##pls = wx.BoxSizer(wx.VERTICAL) +# #wx.TextCtrl(pl, -1, style = wx.TE_MULTILINE|wx.TE_READONLY, size = wx.DefaultSize) +# +# #pr = wx.Panel(splitter, -1, size = wx.DefaultSize) # panel right +# #wx.ListCtrl(pr, -1, size = wx.DefaultSize) +# +# #splitter.SplitVertically(pl, pr) +# +# splitter = wx.SplitterWindow(self, -1) +# panel1 = wx.Panel(splitter, -1) +# wx.StaticText(panel1, -1, +# "Whether you think that you can, or that you can't, you are usually right." +# "\n\n Henry Ford", +# (100,100), style=wx.ALIGN_CENTRE) +# panel1.SetBackgroundColour(wx.LIGHT_GREY) +# panel2 = wx.Panel(splitter, -1) +# panel2.SetBackgroundColour(wx.WHITE) +# splitter.SplitVertically(panel1, panel2) +# self.Centre() +# +# +#class MainWindow(wx.Frame): +# def __init__(self, ui): +# wx.Frame.__init__(self, parent = None, id = -1, title = 'pyPSYC', +# size = (800, 600), +# style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE) +# +# self.Bind(wx.EVT_CLOSE, self.OnClose, self) +# +# self.ui = ui +# if hasattr(self.ui, 'factory'): +# self.factory = self.ui.factory +# +# self.CreateStatusBar() +# +# # Set up and create the menu +# mainmenu = wx.Menu() +# +# menuitem = mainmenu.Append(-1, '&Connect', 'Connect to the server') +# self.Bind(wx.EVT_MENU, self.OnConnect, menuitem) +# +# menuitem = mainmenu.Append(-1, '&Debug window', +# 'Open or close debug window') +# self.Bind(wx.EVT_MENU, self.OnOpenDebugWindow, menuitem) +# +# mainmenu.AppendSeparator() +# +# menuitem = mainmenu.Append(-1, '&Exit', 'Exit pyPSYC') +# self.Bind(wx.EVT_MENU, self.OnClose, menuitem) +# +# menubar = wx.MenuBar() +# menubar.Append(mainmenu, '&pyPSYC') +# self.SetMenuBar(menubar) +# +# self.nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM) +# +# self.tabs = {} +# +# self.Show(True) +# +# def OnConnect(self, event): +# self.factory.connect() +# +# def OnOpenDebugWindow(self, event): +# # open or close window: +# if not self.ui.debugwindow.Show(True): self.ui.debugwindow.Show(False) +# +# def OnClose(self, event): +# reactor.stop() +# +# +#class DebugWindow(wx.Frame): +# def __init__(self, factory): +# wx.Frame.__init__(self, parent = None, id = -1, +# title = 'pyPSYC debug window', size = (800, 600), +# style = wx.DEFAULT_FRAME_STYLE| +# wx.NO_FULL_REPAINT_ON_RESIZE) +# self.Bind(wx.EVT_CLOSE, self.OnClose, self) +# +# self.tree = wx.TreeCtrl(self, -1, wx.DefaultPosition, (-1,-1), +# wx.TR_HIDE_ROOT|wx.TR_HAS_BUTTONS) +# self.treeroot = self.tree.AddRoot('Programmer') +# +# def OnClose(self, event): +# self.Show(False) +# +# +#class Interface:#(base.BaseUI): +# def __init__(self, factory): +# self.gui = wx.PySimpleApp() +# if factory: +# self.factory = factory +# reactor.registerWxApp(self.gui) +# self.mainwindow = MainWindow(self) +# self.debugwindow = DebugWindow(self) +# window = 'Server' +# self.mainwindow.tabs[window] = Tab(window, self.mainwindow) +# self.mainwindow.nb.AddPage(self.mainwindow.tabs[window], window) +# +# def _print(self, window, text): +# if window not in self.mainwindow.tabs: +# self.mainwindow.tabs[window] = Tab(window, self.mainwindow) +# +# #def OnOvrSize(event, tab=self.mainwindow.tabs[window]): +# # size = event.GetSize() +# # textboxsize = (size[0], size[1] - 24) +# # inputpos = (0, size[1] - 24) +# # tab.textbox.SetSize(textboxsize) +# # tab.input.SetPosition(inputpos) +# # tab.input.SetSize((size[0], 21)) +# +# #wx.EVT_SIZE(self.mainwindow.tabs[window], OnOvrSize) +# +# self.mainwindow.nb.AddPage(self.mainwindow.tabs[window], window) +# +# self.mainwindow.tabs[window].textbox.AppendText(text) +# +# +# def pre_print(self, packet): +# tmp = self.debugwindow.tree.AppendItem(self.debugwindow.treeroot, +# 'Incoming Packet %s' +# % self.inpacketcount) +# self.debugwindow.tree.AppendItem(tmp, "MMPVars: %s" % packet.mmpvars) +# self.debugwindow.tree.AppendItem(tmp, "PSYCVars: %s" % packet.psycvars) +# self.debugwindow.tree.AppendItem(tmp, "Method: %s" % packet.mc) +# self.debugwindow.tree.AppendItem(tmp, "Text: %s" % packet.text) +# +# def pre_send(self, packet): +# tmp = self.debugwindow.tree.AppendItem(self.debugwindow.treeroot, +# 'Outgoing Packet %s' +# % self.outpacketcount) +# self.debugwindow.tree.AppendItem(tmp, "MMPVars: %s" % packet.mmpvars) +# self.debugwindow.tree.AppendItem(tmp, "PSYCVars: %s" % packet.psycvars) +# self.debugwindow.tree.AppendItem(tmp, "Method: %s" % packet.mc) +# self.debugwindow.tree.AppendItem(tmp, "Text: %s" % packet.text) +# +#if __name__ == '__main__': +# interface = Interface(None) +# interface.gui.MainLoop() diff --git a/mjacob/old/UI/wx/__init__.py b/mjacob/old/UI/wx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/old/__init__.py b/mjacob/old/__init__.py new file mode 100755 index 0000000..f4c8100 --- /dev/null +++ b/mjacob/old/__init__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +import re + +# the following two functions are from the 'original' pypsyc +# (and slightly modified) + +def get_host(uni): + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/(.+)?\/\@(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/(.+)$", uni) + if m: return m.group(1) + + raise "invalid uni" + +def get_user(uni): + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(1) + + raise "invalid uni" diff --git a/mjacob/old/pypsyc-twisted.py b/mjacob/old/pypsyc-twisted.py new file mode 100755 index 0000000..e08dbe3 --- /dev/null +++ b/mjacob/old/pypsyc-twisted.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the GPLv2 license +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + +# Copyright 2007, Manuel Jacob + +import os + +from __init__ import get_host, get_user +from PSYC.PSYCProtocol import PSYCProtocol +from PSYC.PSYCPacket import PSYCPacket + +try: + from twisted.internet import wxreactor + wxreactor.install() +except ImportError: + pass + +from twisted.internet import reactor, protocol + +class Config: + def __init__(self, factory): + self.factory = factory + self.settings_folder = self.get_settings_folder() + + self.uni_file = os.path.join(self.settings_folder, 'me') + self.clientconfig_file = os.path.join(self.settings_folder, 'pypsyc', 'config') + + self.uni = self.get_uni() + #self.uni = 'psyc://beta.ve.symlynX.com/~pypsyctest' + #self.uni = raw_input('uni?: ') + self.host = get_host(self.uni) + self.username = get_user(self.uni) + + self.configfile = self.open_configfile() + + self.ui = 'wx' + + def get_settings_folder(self): + # resolve settings folder + if os.name == 'posix': + folder = os.path.join(os.environ.get('HOME'), ".psyc") + elif os.name == 'nt': + folder = os.path.join(os.environ.get('APPDATA'), 'psyc') + else: + folder = os.path.join(os.getcwd(), 'psyc') + + # create settings folder if necessary + try: + os.mkdir(folder) + except OSError: + pass + + return folder + + def get_uni(self): + f = file(self.uni_file) + uni = f.read().strip() + f.close() + return uni + + def open_configfile(self): + return None + + +class App(protocol.ClientFactory): + def __init__(self): + self.config = Config(self) + self.protocol = PSYCProtocol + self.connections = {} + + #Interface = __import__('UI.%s.Interface' % self.config.ui, [], [], ['Interface']) + exec("from UI.%s.Interface import Interface" % self.config.ui) + self.ui = Interface(self) + + self.callback_on_recv = self.ui.recv + + def connect(self): + reactor.connectTCP(self.config.host, 4404, self, timeout = 20) + + def buildProtocol(self, addr): + p = self.protocol(self.callback_on_recv) + p.factory = self + self.callback_on_recv + self.connections[addr.host] = p + if len(self.connections) == 1: + self.sc = p # sc = server connection + return p + + +factory = App() +reactor.run() diff --git a/mjacob/old/pypsyc.conf b/mjacob/old/pypsyc.conf new file mode 100644 index 0000000..d205a09 --- /dev/null +++ b/mjacob/old/pypsyc.conf @@ -0,0 +1,4 @@ +[server1] +host = beta.ve.symlynX.com +port = 4404 +username = l diff --git a/mjacob/server.py b/mjacob/server.py new file mode 100755 index 0000000..e5fca0f --- /dev/null +++ b/mjacob/server.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Licensed under the MIT license +# http://opensource.org/licenses/mit-license.php + +# Copyright 2007, Manuel Jacob + +import os + +from base import Config, BaseCenter, MMPCircuit + + +### TODO: This don't works yet ### + + +class ToClientCircuit(MMPCircuit): + def __init__(self, center): + MMPCircuit.__init__(self, center) + + def init(self): + MMPCircuit.init(self) + #self. + + +class ServerConfig(Config): + def __init__(self, app): + Config.__init__(self, app) + self.serverconfig_file = os.path.join(self.settings_folder, 'config') + + def get_settings_folder(self): + # resolve settings folder + if os.name == 'posix': + folder = os.path.join(os.environ.get('HOME'), ".pypsyced") + elif os.name == 'nt': + folder = os.path.join(os.environ.get('APPDATA'), 'pypsyced') + else: + folder = os.path.join(os.getcwd(), 'pypsyced') + + # create settings folder if necessary + try: + os.mkdir(folder) + except OSError: + pass + + return folder + + def open_configfile(self): + return None + + +class ServerCenter(BaseCenter): + def __init__(self): + self.config = ServerConfig(self) + + def recv_mmp_packet(self, packet, physsource): + if not packet.vars and not packet.body: + return + + if physsource.linked_uni == None or packet.mmpvars['_target']: + psyc_packet = physsource.psyc_parser.parse(packet) + self.recv_psyc_packet(psyc_packet, physsource) diff --git a/mjacob/server.pyc b/mjacob/server.pyc new file mode 100644 index 0000000..3ca05c8 Binary files /dev/null and b/mjacob/server.pyc differ diff --git a/mjacob/socket_/__init__.py b/mjacob/socket_/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mjacob/socket_/__init__.pyc b/mjacob/socket_/__init__.pyc new file mode 100644 index 0000000..6033bb7 Binary files /dev/null and b/mjacob/socket_/__init__.pyc differ diff --git a/mjacob/socket_/twisted_.py b/mjacob/socket_/twisted_.py new file mode 100755 index 0000000..acfa3ed --- /dev/null +++ b/mjacob/socket_/twisted_.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from twisted.internet import protocol + +from base import MMPCircuit, PSYCCircuit +from client import ClientCenter +from server import ServerCenter + + +class CircuitBaseProtocol(protocol.Protocol): + def connectionMade(self): + self.init() + + def dataReceived(self, data): + self.mmp_parser.data(data) + + def _send(self, data): + print (data,) + self.transport.write(data) + + +class MMPCircuitProtocol(CircuitBaseProtocol, MMPCircuit): pass + + +class PSYCCircuitProtocol(CircuitBaseProtocol, PSYCCircuit): pass + + +class TwistedClientCenter(ClientCenter, protocol.ClientFactory): + def buildProtocol(self, addr): + p = PSYCCircuitProtocol(self) + self.connected(p, addr.host) + return p + + def connect(self, host, port = 4404): + self.reactor.connectTCP(host, port, self) + + +#class TwistedServerCenter(ClientCenter, protocol.ClientFactory): +# def buildProtocol(self, addr): +# p = CircuitProtocol(self) +# self.connected(p, addr.host) +# return p +# +# def connect(self, host, port = 4404): +# self.reactor.connectTCP(host, port, self) diff --git a/mjacob/socket_/twisted_.pyc b/mjacob/socket_/twisted_.pyc new file mode 100644 index 0000000..ee7dfa1 Binary files /dev/null and b/mjacob/socket_/twisted_.pyc differ diff --git a/mjacob/twisted_client.py b/mjacob/twisted_client.py new file mode 100755 index 0000000..da1b7a0 --- /dev/null +++ b/mjacob/twisted_client.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from lib import psyctext +from lib.packets import MMPPacket, PSYCPacket +from socket_.twisted_ import TwistedClientCenter#, reactor + +factory = TwistedClientCenter() +#factory.connect(factory.config.host) + +debug = True + +import base +base.debug = debug + +def install_wx(app): # not tested + #del sys.modules['twisted.internet.reactor'] + from twisted.internet import wxreactor + wxreactor.install() + from twisted.internet import reactor + reactor.registerWxApp(app) + +def install_tk(app): # not tested + from twisted.internet import tksupport + tksupport.install(app) + +def install_gtk(app): # not tested + del sys.modules['twisted.internet.reactor'] + from twisted.internet import gtkreactor + gtkreactor.install() + from twisted.internet import reactor + +def install_gtk2(app): # not tested + del sys.modules['twisted.internet.reactor'] + from twisted.internet import gtk2reactor + gtk2reactor.install() + from twisted.internet import reactor + +if debug and __name__ == '__main__': # You can test modules here + from base import Module + + class DebugModule(Module): + methods = ['*'] + def received(self, packet, physsource): + print """packet from %s:: +mmpvars: %s +psycvars: %s +mc: %s +text: %s +""" % (physsource, packet.mmpvars, packet.psycvars, packet.mc, psyctext(packet)) + + factory.register_module(DebugModule()) + + + class AuthModule(Module): + methods = ['_notice_circuit_established'] + + def handle_notice_circuit_established(self, packet, physsource): + physsource.send_psyc_packet(PSYCPacket(mmpvars = {'_target': factory.config.uni}, mc = '_request_link')) + physsource.mmp_renderer.set_pvar('_source_identification', factory.config.uni) + + factory.register_module(AuthModule()) + + +if __name__ == '__main__': + from UI.wx_ import UI + + app = UI(factory) + + from twisted.internet import reactor + factory.reactor = reactor + + print 'start reactor' + reactor.run() diff --git a/mjacob/twisted_client.pyc b/mjacob/twisted_client.pyc new file mode 100644 index 0000000..5ec8c6f Binary files /dev/null and b/mjacob/twisted_client.pyc differ diff --git a/pypsyc/GUI/Gtk/__init__.py b/pypsyc/GUI/Gtk/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/pypsyc/GUI/Gtk/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/pypsyc/GUI/Gtk2/__init__.py b/pypsyc/GUI/Gtk2/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/pypsyc/GUI/Gtk2/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/pypsyc/GUI/Qt/Gui.py b/pypsyc/GUI/Qt/Gui.py new file mode 100644 index 0000000..8bc1700 --- /dev/null +++ b/pypsyc/GUI/Qt/Gui.py @@ -0,0 +1,167 @@ +import sys +import qt +from qt import SIGNAL, SLOT +import asyncore + +import pypsyc.GUI.Abstract.Gui as AbstractGui + +class MainDebugWidget(qt.QWidget): + def __init__(self, parent = None, name = None, fl=0): + qt.QWidget.__init__(self,parent,name,fl) + + self.textField = qt.QTextBrowser(self) + self.textField.setGeometry(10, 5, 580, 350) + + self.inputField = qt.QMultiLineEdit(self) + self.inputField.setGeometry(10, 360, 580 , 100) + + qt.QObject.connect(self.inputField, SIGNAL('returnPressed()'), + self.inputText) + + self.resize(qt.QSize(600,480).expandedTo(self.minimumSizeHint())) + sys.stdout = self + + def inputText(self): + if self.inputField.text().findRev('\n.\n') - self.inputField.text().length() == -3: + data = self.inputField.text() + print "packet entered" + self.inputField.setText('') + def write(self, string): + self.textField.append(string) + + +class RoomWidget(qt.QWidget): + def __init__(self, parent = None, name = None, fl = 0): + qt.QWidget.__init__(self, parent, name, fl) + self.name = name + + self.textField = qt.QTextBrowser(self) + self.textField.setGeometry(qt.QRect(10, 35, 470, 350)) + + self.topicField = qt.QLineEdit(self) + self.topicField.setGeometry(qt.QRect(10, 5, 580, 25)) + + self.nicklist = qt.QListBox(self) + self.nicklist.setGeometry(qt.QRect(490, 35, 100, 350)) + + self.inputField = qt.QLineEdit(self) + self.inputField.setGeometry(qt.QRect(10, 390, 470, 20)) + + qt.QObject.connect(self.inputField, SIGNAL('returnPressed()'), + self.inputText) + qt.QObject.connect(self.topicField, SIGNAL('returnPressed()'), + self.inputTopic) + + self.resize(qt.QSize(600,550).expandedTo(self.minimumSizeHint())) + + def inputText(self): + print self.name + " input: " + self.inputField.text().stripWhiteSpace().latin1() + self.inputField.setText('') + + def inputTopic(self): + print self.name + " topic: " + self.topicField.text().stripWhiteSpace().latin1() + + def append(self, text): self.textField.append(text.strip() + '\n') + def set_topic(self, text): self.topicField.setText(text) + def inputNickCurrent(self): + print self.name + " current nick:" + self.nickField.text().stripWhiteSpace().latin1() + + +from pypsyc.PSYC import parsetext +class RoomGui(qt.QMainWindow, AbstractGui.RoomGui): + """qt frontend for pyPSYC""" + # better inherit from widget made with designer? + def __init__(self): + qt.QMainWindow.__init__(self) + #AbstractGui.RoomGui.__init__(self) # attribute error? + self.model = None + + self.rooms = {} + self.topics = {} + self.setCaption("Qt pyPSYC Frontend") + self.menuBar = qt.QMenuBar(self) + + self.workspace = qt.QVBox(self) + + self.roombar = qt.QTabWidget(self.workspace) + + # only in debug mode + self.roombar.addTab(MainDebugWidget(self.workspace), 'main') + + self.workspace.show() + self.setCentralWidget(self.workspace) + self.resize(qt.QSize(600, 500)) # fixed size is bad + + + fileMenu = [("Exit the program", "Quit", 0, "quit") + ] + connectionMenu = [("Link to your UNL", "Connect", 0, "connect") + ] + self.menus = [("&File", fileMenu), + ("&Connection", connectionMenu)] + self.menuActions = {} + for menu in self.menus: + popupMenu = qt.QPopupMenu(self) + for entry in menu[1]: + if entry: + helptxt, menutxt, accel, callbackName = entry + action = qt.QAction(helptxt, qt.QIconSet(qt.QPixmap("./green.gif")), + menutxt, accel, self) + action.addTo(popupMenu) + self.menuActions[callbackName] = action + callback = getattr(self, callbackName, None) + if callback: + qt.QObject.connect(action, SIGNAL("activated()"), + callback) + else: + popupMenu.insertSeparator() + self.menuBar.insertItem(menu[0], popupMenu) + + self.show() + + def update(self, mc, mmp, psyc): + context = mmp._query("_context") + if mc.startswith("_notice_place_enter"): + ## if _source == ME eigentlich... + if not self.rooms.has_key(context): + self.add_room(context) + self.rooms[context].append(parsetext(mmp, psyc)) + +# elif mc == "_status_place_topic": +# self.topics[context] = parsetext(mmp, psyc) + elif mc == '_message_public': + self.rooms[context].append(psyc._query("_nick") + ": " + + parsetext(mmp, psyc)) + + def add_room(self, room): + if not self.rooms.has_key(room): + self.rooms[room] = RoomWidget(self.workspace, room) + short = room[room.rfind("@"):] # kurzform für raum... nicht ideal? + self.roombar.addTab(self.rooms[room], short) + + def quit(self): + self.app.quit() + + def connect(self, host = ''): + self.model.center.connect(host) + +class UserGui(qt.QMainWindow, AbstractGui.UserGui): + pass + +class MainGui(qt.QApplication, AbstractGui.MainGui): + def __init__(self, argv, center): + qt.QApplication.__init__(self, argv) + AbstractGui.MainGui.__init__(self, argv) + + qt.QObject.connect(self, SIGNAL('lastWindowClosed()'), + self, SLOT('quit()')) + self.timer = qt.QTimer() + qt.QObject.connect(self.timer, SIGNAL("timeout()"), + self.socket_check) + + def socket_check(self): + asyncore.poll(timeout=0.0) + + def run(self): + self.timer.start(100) + self.exec_loop() diff --git a/pypsyc/GUI/Qt/__init__.py b/pypsyc/GUI/Qt/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/pypsyc/GUI/Qt/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/pypsyc/GUI/__init__.py b/pypsyc/GUI/__init__.py new file mode 100644 index 0000000..5d4eecb --- /dev/null +++ b/pypsyc/GUI/__init__.py @@ -0,0 +1 @@ +# different user interfaces for pyPSYC diff --git a/pypsyc/MMP/MMPPacket.py b/pypsyc/MMP/MMPPacket.py new file mode 100644 index 0000000..458dbee --- /dev/null +++ b/pypsyc/MMP/MMPPacket.py @@ -0,0 +1,28 @@ +from pypsyc.MMP import Glyphs + +#obsolete? +class MMPPacket: + """encapsulates a set of MMP state variables and data within a + MMP packet. See http://www.psyc.eu/mmp.html""" + # do subclassing for binary data packets and fragmented packets + + delimiter = '.\n' # end of packet delimiter + def generate(self, MMPModifiers, data): + """Expects a (emtpy) dictionary of MMP Modifiers and + a (empty) string of data. Does nothing with the data itself""" + packet = "" + packet += MMPModifiers.plain() + data = data.strip() + + # take a look data the first character of data. If it is no glyph + # we may leave out the newline before data + if data.__len__() and _isModifier(data[0]): + packet += '\n' + else: + # keys may be empty (so we either have an empty packet or we + # set default variables for communication + pass + + + # append data and EOP delimiter + return packet + data + '\n' + MMPPacket.delimiter diff --git a/pypsyc/MMP/MMPProtocol.py b/pypsyc/MMP/MMPProtocol.py new file mode 100644 index 0000000..41a3d79 --- /dev/null +++ b/pypsyc/MMP/MMPProtocol.py @@ -0,0 +1,84 @@ +import asynchat, asyncore, socket +from pypsyc.MMP import Glyphs, _isModifier +from pypsyc.MMP.MMPState import MMPState + +debug=0 + +class MMPProtocol(asynchat.async_chat): + """modular message protocol + see http://www.psyc.eu/mmp.html for details.""" + def __init__(self, callback, connection): + asynchat.async_chat.__init__(self, connection) + self.set_terminator('\n') + self.buffer = '' + self.mmp = MMPState() + self.state = 'mmp' + self.data = [] + + self.callback = callback + + def handle_connect(self): + # dead code neuerdings, eh? + print "connected... but this is dead code" + + def handle_close(self): + s = MMPState() + if self.addr[1] != 4404: + s._set('_source', "psyc://%s:%d/"%self.addr) + else: + s._set('_source', "psyc://%s/"%self.addr[0]) + self.packetReceived(s, "_notice_circuit_broken\nYour TCP connection to [_source] was broken by the bull from the china shop\n.\n") + self.close() + + def collect_incoming_data(self, data): + self.buffer += data + + def found_terminator(self): + # hierfuer siehe asyncchat.py... das setzt + # terminator auf 0 zurueck wenns nen int als terminator + # hat und den findet (line 111) + if self.get_terminator() == 0: + self.set_terminator("\n") + line = self.buffer + self.buffer = '' + self.lineReceived(line) + + def lineReceived(self, line): + # kind of state machine + line = line.strip() + if line == ".": +## print "<>" + self.packetReceived(self.mmp, self.data) + # reset state, clear temporary variables, etc + self.mmp.reset_state() + self.data = [] + self.state = 'mmp' + return + + if (line == '' or not _isModifier(line)) and self.state == 'mmp': + # if we have _length for data then + # binary support + if self.mmp._query('_length'): + self.set_terminator(self.mmp._query('_length')) + self.state = "data" +## print "<<" + self.state + ">> " + line ## comment out + if self.state == 'mmp': + # maybe else with the above could also work... + self.mmp.set_state(line) + return + elif line != '': + self.data += [line] + + def packetReceived(self, mmp, data): +## print "MMPProtocol::packetReceived" +## print "=>MMP :", mmp.get_state() +## print "=>data:", data + self.callback.packetReceived(mmp, data) + + def sendPacket(self, mmp, data): +## print "calling MMPProtocol::sendPacket()" + packet = mmp.plain() + packet += '\n'+ data +'\n.\n' + if debug: + print ">>send>>\n", packet, "<>>", target +# print "mmp :", mmp.get_state() +# print "psyc:", psyc.get_state() +# print "mc :", psyc.get_mc() +# print "text:", psyc.get_text() +# #print "get_host(" + unl + "):", get_host(unl) +# print "---" + + host = gethostbyname(get_host(target)) + if not self.clients.has_key(host): +## print "Error: we are not yet connected to ", host + self.connect(host) + # put the current packet in a queue, + # connect and auth before sending + else: + # wait... we have to register before sending? + # print "sending..." + # print "client is:", self.clients[hostpart] + self.clients[host].sendPacket(mmp, psyc) + + def quit(self): + # "unlink" waer der bessere name? + print "unlinking world..." + for host in self.connections.keys(): + # auch nicht grade grateful... request unlink + # waer schoener + del self.clients[host] + + def register(self, psycPackage, methods = []): + """see bitkoenigs psycpackage interface... that's the way to + do it""" + if methods == []: + methods = psycPackage.getMethods() + for method in methods: + # jetzt auch support fuer mehrere packages pro mc + if not self.handlers.has_key(method): + self.handlers[method] = [ psycPackage ] + else: + self.handlers[method] += [ psycPackage ] + psycPackage.registerCenter(self) + + def castmsg(self, mmp, psyc): + # ob das noch/ueberhaupt noetig ist... + """for internal packets""" + self.handle(mmp, psyc) + + def handle(self, mmp, psyc): + context = mmp._query("_context") + if context: + if not context.startswith("psyc://") and context.startswith("@"): + context = "psyc://" + self.DEFAULT_HOST()+"/"+context + mmp._set("_context", context) + + #if self.handlers.has_key(psyc.get_mc()): + # for package in self.handlers[psyc.get_mc()]: + # package.received(mmp.get_source(), + # psyc.get_mc(), + # mmp, psyc) + + method = psyc.get_mc() + source = mmp.get_source() + found = 0 + for handler in self.handlers.keys(): + if handler.endswith("*"): + tmp = handler.replace("*", "") + if method.startswith(tmp): + found = 1 + for package in self.handlers[handler]: + package.received(source, method, mmp, psyc) + else: + if handler == method: + found = 1 + for package in self.handlers[method]: + package.received(source, method, mmp, psyc) + + if not found: + if self.handlers.has_key("devel"): #(an) was elif + for package in self.handlers["devel"]: + package.received(source, method, mmp, psyc) + + + +class PSYCServercenter: + def __init__(self): + self.clients = {} + + def handle(self, mmp, psyc): + # das problem ist, dass wir die Klasse von der + # das kommt auch kennen muessen + print mmp.get_target() + diff --git a/pypsyc/PSYC/PSYCMessagecenter.pyc b/pypsyc/PSYC/PSYCMessagecenter.pyc new file mode 100644 index 0000000..9c716d4 Binary files /dev/null and b/pypsyc/PSYC/PSYCMessagecenter.pyc differ diff --git a/pypsyc/PSYC/PSYCProtocol.py b/pypsyc/PSYC/PSYCProtocol.py new file mode 100644 index 0000000..e696fde --- /dev/null +++ b/pypsyc/PSYC/PSYCProtocol.py @@ -0,0 +1,47 @@ +from PSYCState import PSYCState + +from pypsyc.MMP import _isModifier +from pypsyc.MMP.MMPProtocol import MMPProtocol + +class PSYCProtocol: + def __init__(self, callback, socket): + self.callback = callback + self.state = PSYCState() + self.mmp = MMPProtocol(self, socket) + + def sendPacket(self, mmp, psyc): +## print "PSYCProtocol::sendPacket()" + data = '' + full = psyc.get_state() + for var in full: + data += ":" + var + '\t' + full[var] + '\n' + data += psyc.get_mc() + '\n' + data += psyc.get_text() + self.mmp.sendPacket(mmp, data) + + def packetReceived(self, mmp, data): + self.state.reset_state() + state = 'psyc' + for line in data: + if _isModifier(line) and state == 'psyc': + self.state.set_state(line) + + continue + elif line.__len__() and line[0] == '_' and state != 'text': +## print "mc:", line +## print "source:", mmp.get_source() + self.state.set_mc(line) + state = 'text' + continue + elif state == 'text': + self.state.append_text(line) + else: + print "unknown psyc stuff:", line + +## print "PSYCProtocol::packetReceived from", mmp.get_source() +## print "=>PSYC :", self.state.get_state() +## print "=>mc :", self.state.get_mc() +## print "=>text :", self.state.get_text() +## print "----" + self.callback.handle(mmp, self.state) + diff --git a/pypsyc/PSYC/PSYCProtocol.pyc b/pypsyc/PSYC/PSYCProtocol.pyc new file mode 100644 index 0000000..251b787 Binary files /dev/null and b/pypsyc/PSYC/PSYCProtocol.pyc differ diff --git a/pypsyc/PSYC/PSYCRoom.py b/pypsyc/PSYC/PSYCRoom.py new file mode 100644 index 0000000..d90aaca --- /dev/null +++ b/pypsyc/PSYC/PSYCRoom.py @@ -0,0 +1,199 @@ +from pypsyc.PSYC import parsetext + +from pypsyc.MMP.MMPState import MMPState +from pypsyc.PSYC.PSYCState import PSYCState + +class PSYCPackage: + """see bitkoenig's java interface specification""" + def __init__(self): + self.packagename = "Abstract psycPackage interface" + self.methods = [] + + self.center = None + self.psyc = PSYCState() + self.mmp = MMPState() + + def registerCenter(self, center): + self.center = center + + def getMethods(self): + return self.methods + + def received(self, source, mc, mmp, psyc): + print "<<<", source, "[", self.packagename, "]" + print "mmp:", mmp.packetstate + print "psyc:", psyc.packetstate + print "mc:", mc + print "text:", parsetext(mmp, psyc) + print "----" + + def set_mc(self, mc): self.psyc.set_mc(mc) + def set_target(self, target): self.mmp._assign("_target", target) + + def set_text(self, text): + self.psyc.reset_text() + self.psyc.append_text(text) + + def set_psycvar(self, var, value): + self.psyc._assign(var, value) + + def send(self): + self.center.send(self.mmp, self.psyc) + self.mmp.reset_state() + self.psyc.reset_state() + + def castmsg(self): + self.center.castmsg(self.mmp, self.psyc) + self.mmp.reset_state() + self.psyc.reset_state() + + +class Devel(PSYCPackage): + def __init__(self, view): + PSYCPackage.__init__(self) + self.packagename = "Debug package... dump out stuff" + self.methods = ["devel"] # all unhandled methods + self.view = view + + def received(self, source, mc, mmp, psyc): + print >> self.view, "devel handler:" + print >> self.view, "source=>", source + print >> self.view, "target=>", mmp.get_target() + print >> self.view, "mmp =>", mmp.get_state() + print >> self.view, "psyc =>", psyc.get_state() + print >> self.view, "mc =>", mc + print >> self.view, "text =>", parsetext(mmp, psyc) + print >> self.view, "----\n" + + +class Conferencing(PSYCPackage): + # model for rooms. has to know its controller and view + # the view is an instance of RoomGui (singleton in case of Qt GUI) + def __init__(self, view): + PSYCPackage.__init__(self) + self.packagename = "package for rooms" + self.view = view + self.model = self + self.view.set_model(self.model) + self.controller = self + + self.context = '' + + # ideal hier waere es wenn man auf _notice_place_enter* registern + # koennte, oder? + self.methods = ["_notice_place*", + "_notice_person_present_netburp", + "_notice_person_absent_netburp", + "_message*", + "_status_place*", + "_status_person_present_netburp", + "_print_status_place*"] + + def get_context(self): return self.context + def set_context(self, context): self.context = context + + def received(self, source, mc, mmp, psyc): + #if mc.startswith("_message_public"): + # self.view.received(source, mc, mmp, psyc) + #elif mc == "_status_place_topic": + # self.view.received(source, mc, mmp, psyc) + #elif mc.startswith("_notice_place_enter"): + # self.view.received(source, mc, mmp, psyc) + #elif mc.startswith("_notice_place_leave"): + # self.view.received(source, mc, mmp, psyc) + #elif mc == "_print_status_place_members": + # print parsetext(mmp, psyc) + #else: + # PSYCPackage.received(self, source, mc, mmp, psyc) + self.view.received(source, mc, mmp, psyc) + PSYCPackage.received(self, source, mc, mmp, psyc) + +class Friends(PSYCPackage): + def __init__(self, view): + PSYCPackage.__init__(self) + self.packagename = "package for friends" + self.view = view + self.model = self + self.view.set_model(self.model) + self.controller = self + self.methods = ["_notice_friend_present", + "_notice_friend_absent", + "_list_friends_present", # create this! +# "_print_notice_friend_present", # _print family should be obsolete now +# "_print_notice_friend_absent", # _print family should be obsolete now + "_print_list_friends_present", # obsolete this! + "_notice_link", + "_notice_unlink", + "_status_linked"] # added by tim] + + def received(self, source, mc, mmp, psyc): + self.view.received(source, mc, mmp, psyc) + PSYCPackage.received(self, source, mc, mmp, psyc) + + +class User(PSYCPackage): + def __init__(self, view): + PSYCPackage.__init__(self) + self.packagename = "package for user dialogues" + self.view = view + self.model = self + + self.view.set_model(self.model) + + self.controller = self + + self.methods = ["_internal_message_private_window_popup", + "_message*", + "_notice_query*"] + + + def received(self, source, mc, mmp, psyc): + self.view.received(source, mc, mmp, psyc) + PSYCPackage.received(self, source, mc, mmp, psyc) + +class Peer(User): + # is das ueberhaupt sinnvoll _hier_? + def __init__(self, view): + User.__init__(self, view) + self.packagename = "package for p2p dialogues" + + +from pypsyc.MMP.MMPState import MMPState +from pypsyc.PSYC.PSYCState import PSYCState +class Authentication(PSYCPackage): + # das hier sollte noch das setzen von _identification regeln oder? + def __init__(self, config): + PSYCPackage.__init__(self) + self.config = config + self.methods = ["_query_password", + "_notice_circuit_established", + "_error_illegal_name", + "_error_illegal_password", + "_error_invalid_password"] + + def auth(self): + psyc = PSYCState() + mmp = MMPState() + if self.center: + # das hier ist sicher weils nur an die eigene uni geht + mmp.set_state(":_target\t" + self.config.get("main", "uni")) + psyc.set_state(":_password\t" + self.config.get("main", + "password")) + psyc.set_mc("_set_password") + + self.center.send(mmp, psyc) + + def register(self): + psyc = PSYCState() + mmp = MMPState() + if self.center: + mmp.set_state(":_target\t" + self.config.get("main", "uni")) + psyc.set_mc("_request_link") + self.center.send(mmp, psyc) + + def received(self, source, mc, mmp, psyc): + if mc == "_query_password": + self.auth() + elif mc == "_notice_circuit_established": + self.register() + else: PSYCPackage.received(self, source, mc, mmp, psyc) diff --git a/pypsyc/PSYC/PSYCRoom.pyc b/pypsyc/PSYC/PSYCRoom.pyc new file mode 100644 index 0000000..2ce6d24 Binary files /dev/null and b/pypsyc/PSYC/PSYCRoom.pyc differ diff --git a/pypsyc/PSYC/PSYCState.py b/pypsyc/PSYC/PSYCState.py new file mode 100644 index 0000000..6bee923 --- /dev/null +++ b/pypsyc/PSYC/PSYCState.py @@ -0,0 +1,29 @@ +from pypsyc.MMP.MMPState import MMPState + +class PSYCState(MMPState): + def __init__(self): + self.mc = '' + self.text = '' + MMPState.__init__(self) + def set_mc(self, mc): + self.mc = mc + def get_mc(self): + return self.mc + def reset_mc(self): + self.mc = '' + def append_text(self, text): + if self.text != '': + self.text += '\n' + self.text += text + def get_text(self): + return self.text + def reset_text(self): + self.text = '' + def reset_state(self): + self.mc = '' + self.text = '' + MMPState.reset_state(self) + def get_source(self): raise "Meep. No source in PSYC Layer" + def get_target(self): raise "Meep. No target in PSYC Layer" + def get_context(self): raise "Meep. No context in PSYC Layer" + def get_length(self): raise "Meep. No length in PSYC Layer" diff --git a/pypsyc/PSYC/PSYCState.pyc b/pypsyc/PSYC/PSYCState.pyc new file mode 100644 index 0000000..40b1021 Binary files /dev/null and b/pypsyc/PSYC/PSYCState.pyc differ diff --git a/pypsyc/PSYC/__init__.py b/pypsyc/PSYC/__init__.py new file mode 100644 index 0000000..c7b2a8d --- /dev/null +++ b/pypsyc/PSYC/__init__.py @@ -0,0 +1,38 @@ +def parsetext(mmp, psyc): + """string with variables of the form '[_varname]' to be substituted + by the value dictVariables['_varname']""" + pstring = psyc.get_text() + for (varname, value) in mmp.get_state().items(): + pstring = pstring.replace('[' + varname + ']', value) + for (varname, value) in psyc.get_state().items(): + pstring = pstring.replace('[' + varname + ']', value) + return pstring + +def get_host(uni): + import re + + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/(.+)?\/\@(.+)?\/?$", uni) + if m: return m.group(1) + + m = re.match("^psyc:\/\/(.+)$", uni) + if m: return m.group(1) + +def get_place(uni): + import re + m = re.match("^psyc:\/\/(.+)?\/\@(.+)?\/?$", uni) + if m: return m.group(2) + +def get_user(uni): + import re + m = re.match("^psyc:\/\/(.+)?\/~(.+)?\/?$", uni) + if m: return m.group(2) + + m = re.match("^psyc:\/\/([^\/@]+)\@(.+?)\/?$", uni) + if m: return m.group(1) + diff --git a/pypsyc/PSYC/__init__.pyc b/pypsyc/PSYC/__init__.pyc new file mode 100644 index 0000000..91c0522 Binary files /dev/null and b/pypsyc/PSYC/__init__.pyc differ diff --git a/pypsyc/__init__.py b/pypsyc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pypsyc/__init__.pyc b/pypsyc/__init__.pyc new file mode 100644 index 0000000..86da0c3 Binary files /dev/null and b/pypsyc/__init__.pyc differ diff --git a/therapy/COPYLEFT b/therapy/COPYLEFT new file mode 100644 index 0000000..960fe74 --- /dev/null +++ b/therapy/COPYLEFT @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/therapy/INSTALL b/therapy/INSTALL new file mode 100644 index 0000000..be2fe8a --- /dev/null +++ b/therapy/INSTALL @@ -0,0 +1,9 @@ +$Id: INSTALL,v 1.3 2003/09/22 18:57:09 an Exp $ + +therapy via CVS ziehen +pypsyc via CVS ziehen +pypsyc nach therapy/pypsyc kopieren +therapyrc-dist nach ~/.therapyrc kopieren und entsprechend anpassen +therapy starten und freut sich drauf :) + +Ihr braucht pygtk und gtk+ 2.0 (siehe README) diff --git a/therapy/LICENSE b/therapy/LICENSE new file mode 100644 index 0000000..c701313 --- /dev/null +++ b/therapy/LICENSE @@ -0,0 +1,27 @@ + + theraPY, a PSYC client written in Python + Copyright (C) 2003 by Andreas "an" Neue + theraPY is based on pyPSYC by Philipp "fippo" Hancke + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + 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. + + Taken from lynx license for the psycMUVE: + + SPECIAL ENCOURAGEMENTS FOR PSYC DEVELOPERS. + + 1. Whenever you make derivates of this library publically available, be it + in source or binary form, please keep all PSYC network transactions + according to the current specification of the PSYC protocol as available + from http://psyc.pages.de. This will help keeping PSYC implementations + interoperable. + + 2. Whenever you release a derivative work of this software, please inform + the PSYC community of the existence of your new software by filling out + the registration form at http://psyc.pages.de. diff --git a/therapy/Makefile b/therapy/Makefile new file mode 100644 index 0000000..148c5ae --- /dev/null +++ b/therapy/Makefile @@ -0,0 +1,16 @@ +fix: pypsyc therapy + expand therapy >therapy-big-big-big + -mv -f therapy /tmp + -mv -f therapy-big-big-big therapy + python -tt therapy + +# paranoid check of syntax +jack: pypsyc therapy + python -tt therapy + +pypsyc: + @echo I will presume you have also checked out pypsyc. + ln -s ../pypsyc/pypsyc . + +it: jack + diff --git a/therapy/README b/therapy/README new file mode 100644 index 0000000..426e702 --- /dev/null +++ b/therapy/README @@ -0,0 +1,10 @@ +$Id: README,v 1.2 2003/09/22 18:54:51 an Exp $ + +pypsyc was slightly changed for therapy. Hopefully, i'll get that clean one +day. + +To run therapy, you need Python (tested with 2.3), pypsyc (provided in +this Package), PyGTK 1.99 and GTK+ >2.0. You can obtain PyGTK from +http://www.daa.com.au/~james/software/pygtk/. + +Have fun. diff --git a/therapy/TODO b/therapy/TODO new file mode 100644 index 0000000..72b2038 --- /dev/null +++ b/therapy/TODO @@ -0,0 +1,31 @@ +BUGS: + +fippo hat alles wieder zerschossen... ;) + error: uncaptured python exception, closing channel (exceptions.TypeError:not enough arguments for format string [/usr/lib/python2.3/asyncore.py|read|69] [/usr/lib/python2.3/asyncore.py|handle_read_event|390] [/usr/lib/python2.3/asynchat.py|handle_read|105] [/V/dev/therapy/pypsyc/MixedReceiver.py|collect_incoming_data|44] [/V/dev/therapy/pypsyc/PSYC.py|lineReceived|42] [/V/dev/therapy/pypsyc/PSYC.py|packetReceived|66] [/V/dev/therapy/pypsyc/center.py|msg|207] [./therapy|msg|404]) + say( hullo ) + error: uncaptured python exception, closing channel (socket.error:(9, 'Bad file descriptor') [/usr/lib/python2.3/asynchat.py|initiate_send|218] [/usr/lib/python2.3/asyncore.py|send|337]) + TypeError: main_quit() takes no arguments (2 given) + +_message_invite + +/me fixen + +Tab-Leiste im GTK-Interface: Scroller, wenn Tabs breiter als Fenster sind + +/all in GoodAdvice tuts nicht: _target mitgeben + + +FEATURES: + +farben via config einstellbar +fonts via config einstellbar + +zusaetzliche UIs (wx, curses etc.) + +Userliste in Raeumen + +Friendlist + +Floating Windows + +anclickbare links diff --git a/therapy/therapy b/therapy/therapy new file mode 100755 index 0000000..70f4d4f --- /dev/null +++ b/therapy/therapy @@ -0,0 +1,629 @@ +#!/usr/bin/python +# -*- coding: iso-8859-1 -*- +# +# theraPY +# A simple Chat Software for PSYC +# +# Copyright (c) 2003,2004 by Andreas Neue +# +# $Id: therapy,v 1.43 2005/02/10 21:28:23 fippo Exp $ + +try: + from twisted.internet import gtk2reactor + gtk2reactor.install() + from twisted.internet import reactor, task + twisting = True +except ImportError: + import asyncore + twisting = False + +import gc + +import os, sys, time, string, socket +import gtk, pango +import ConfigParser + +from pypsyc.objects import PSYCObject +from pypsyc.center import Center +from pypsyc.center import Client, Authenticator +from pypsyc import parsetext, parseURL + +conn = None + +nl = '\n' + +release = "050119" +infotext = "theraPY/" + release + """ +(c) 2003/04 by Andreas "an" Neue +Based on pyPSYC by Philipp "fippo" Hancke +Thanks to mju for conceiving the right name at the right time + +This software is licensed under the terms of the GPL. +""" + +banner = '* ' + +config = None + +main_window = None +status_window = None +authenticator = None + +tab_windows = [] + + +class TabWindow(PSYCObject): + + def __init__(self, netname, label, config, center): + global tab_windows + + PSYCObject.__init__(self, netname, center) + + self.netname = netname + + self.vbox = gtk.VBox(gtk.FALSE, 0) + self.vbox.show() + + self.hbox = gtk.HBox(gtk.FALSE, 0) + self.hbox.show() + + self.vbox.pack_start(self.hbox, gtk.FALSE, gtk.FALSE, 0) + + self.infobar = gtk.Button(netname) + self.hbox.pack_start(self.infobar, gtk.TRUE, gtk.TRUE, 0) + self.infobar.show() + + self.closer = gtk.Button('X') + self.hbox.pack_start(self.closer, gtk.FALSE, gtk.FALSE, 0) + self.closer.show() + self.closer.connect('clicked', self.delete) + + self.scrollwin = gtk.ScrolledWindow() + self.scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) + + self.textview = gtk.TextView() + self.textview.set_cursor_visible(gtk.FALSE) + self.textview.set_wrap_mode(gtk.WRAP_WORD) + self.textview.set_editable(gtk.FALSE) + self.textbuf = self.textview.get_buffer() + + self.scrollwin.add(self.textview) + self.scrollwin.show() + + font = pango.FontDescription(config.get('ui', 'font')) + self.textview.modify_font(font) + self.textview.show() + + self.label = label + if netname.find('@') != -1: + self.label_text= netname[netname.rfind('@'):] + elif netname.find('~') != -1: + self.label_text = netname[netname.rfind('~')+1:] + else: + self.label_text = netname + + self.label.set_text(self.label_text + ' *') + + self.vbox.pack_start(self.scrollwin, gtk.TRUE, gtk.TRUE, 0) + + tab_windows.append(self) + def get_frame(self): + return self.vbox + def __del__(self): + pass + + def delete(self, foo): + global tab_windows + + num = main_window.notebook.page_num(self.vbox) + main_window.notebook.remove_page(num) + tab_windows.remove(self) + + def append_text(self, text): + #iter = self.textbuf.get_end_iter() + #self.textbuf.insert(iter, text) + #iter = self.textbuf.get_end_iter() + #self.textbuf.place_cursor(iter) + #mark = self.textbuf.get_insert() + ##mark = self.textbuf.get_mark_at_iter(iter) + #self.textview.scroll_mark_onscreen(mark) + + iter = self.textbuf.get_end_iter() + text = text.encode('utf-8') + '\n' + self.textbuf.insert(iter, text) + iter = self.textbuf.get_end_iter() + self.textbuf.place_cursor(iter) + mark = self.textbuf.get_insert() + self.textview.scroll_mark_onscreen(mark) + + cur_page = main_window.notebook.get_current_page() + win = None + for w in tab_windows: + if main_window.notebook.page_num(w.get_frame()) == cur_page: + win = w + if win != self: + #self.label.modify_fg(gtk.STATE_NORMAL, + # self.label.get_colormap().alloc_color('darkred')) + self.label.set_text(self.label_text + ' *') + + +class MainWindow(Client): + + def __init__(self, config, entry_handler): + global main_window + Client.__init__(self, config) + + self.config = config + + self.entry_handler = entry_handler + + # create a new window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_size_request(350, 400) + self.window.set_title('theraPY') + self.window.connect('delete_event', lambda e, w: gtk.main_quit()) + + self.vbox = gtk.VBox(gtk.FALSE, 0) + self.window.add(self.vbox) + self.vbox.show() + + self.notebook = gtk.Notebook() + self.notebook.set_tab_pos(gtk.POS_BOTTOM) + # window.add(notebook) + self.vbox.pack_start(self.notebook, gtk.TRUE, gtk.TRUE, 0) + self.notebook.show() + + self.entry = gtk.Entry() + self.entry.set_max_length(256) + self.entry.connect('activate', self.enter_callback, self.entry) + self.entry.set_text('') + self.vbox.pack_start(self.entry, gtk.FALSE, gtk.FALSE, 0) + self.font = pango.FontDescription(config.get('ui', 'font')) + self.entry.modify_font(self.font) + self.entry.show() + + self.statusbar = gtk.Statusbar() + self.vbox.pack_start(self.statusbar, gtk.FALSE, gtk.FALSE, 0) + self.statusbar.show() + + self.window.connect('key_press_event', self.key_press_callback) + self.window.show() + + main_window = self + + def create_user(self, netname): + label = gtk.Label() + win = QueryWindow(netname, label, self.config, self) + self.notebook.append_page(win.get_frame(), label) + return win + + def create_place(self, netname): + label = gtk.Label() + win = PlaceWindow(netname, label, self.config, self) + self.notebook.append_page(win.get_frame(), label) + return win + + def key_press_callback(self, widget, event, *args): + key = event.keyval + if event.state & gtk.gdk.MOD1_MASK: + if key > 47 and key < 58: + if key == 48: + key += 10 + key -= 49 + try: + self.notebook.set_current_page(key) + except: + pass + return 1 + elif key == 110: + self.notebook.next_page() + return 1 + elif key == 112: + self.notebook.prev_page() + return 0 + + def enter_callback(self, widget, entry): + global tab_windows + + text = entry.get_text() + + cur_page = main_window.notebook.get_current_page() + netname = None + for w in tab_windows: + if main_window.notebook.page_num(w.vbox) == cur_page: + netname = w.netname + win = w + + self.entry_handler(win, netname, text) + + entry.set_text('') + + + +def update_notebook_tabs(): + cur_page = main_window.notebook.get_current_page() + win = None + for w in tab_windows: + if main_window.notebook.page_num(w.vbox) == cur_page: + win = w + if not win: + return + + #win.label.modify_fg(gtk.STATE_NORMAL, + # win.label.get_colormap().alloc_color('black')) + win.label.set_text(win.label_text) + + if not main_window.entry.flags() & gtk.HAS_FOCUS: + i1 = win.textbuf.get_iter_at_mark(win.textbuf.get_insert()) + i2 = win.textbuf.get_iter_at_mark(win.textbuf.get_selection_bound()) + if not i1.compare(i2): + main_window.entry.grab_focus() + p = main_window.entry.get_text().__len__() + main_window.entry.select_region(p, p) + + +class StatusWindow(TabWindow): + + def __init__(self, netname, label, config, center): + self.conn = conn + TabWindow.__init__(self, netname, label, config, center) + + def msg(self, vars, mc, data, caller): + if mc.startswith('_query_password'): + authenticator.authenticate(vars, mc, data, caller) + return + if mc.startswith('_notice'): + self.append_text(banner + parsetext(vars, mc, data)) + return + if mc.startswith('_status'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_info'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_warning'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_failure'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + PSYCObject.msg(self, vars, mc, data, caller) + + def say(self, text): + #text = text.encode(self.config.get('main', 'encoding')) + print "input(", text, ")" + self.sendmsg({'_target': self.netname, + '_identification': config.get('main', 'uni'), + }, '_request_input', text) + + + +class QueryWindow(TabWindow): + + def __init__(self, netname, label, config, center): + TabWindow.__init__(self, netname, label, config, center) + self.netname = netname + self.config = config + + def msg(self, vars, mc, data, caller): + nickaction = vars['_nick'] + try: + nickaction = nickaction + " " + vars['_action'] + except: + pass + + #template = self.config.get('ui', 'msgtemplate') + + if mc == '_message_public': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_private': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_echo_private': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_private_question': + nickaction += " fragt" + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_private_ask': + nickaction += " fragt" + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_private_text_action': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_status_place_members': + # _list_members, _list_members_nicks + for i in range(0, len(vars['_list_members'])): + self.nicklist.append((vars['_list_members'][i], + vars['_list_members_nicks'][i])) + return + + if mc.startswith('_notice_place_leave'): + return + + if mc.startswith('_notice_place_enter'): + self.nicklist.append((vars['_source'], vars['_nick'])) + return + + if mc.startswith('_status'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_info'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_notice'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_failure'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_error'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_warning'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + PSYCObject.msg(self, vars, mc, data, caller) + + def say(self, text): + me = config.get('main', 'uni') + text = text.encode(self.config.get('main', 'encoding')) + print "talk(", text, ")" + self.sendmsg({'_target': self.netname, + '_identification': config.get('main', 'uni'), + '_nick': config.get('main', 'nick')}, + '_message_private', text) + + + +class PlaceWindow(TabWindow): + + def __init__(self, netname, label, config, center): + TabWindow.__init__(self, netname, label, config, center) + self.nicklist = [] + self.netname = netname + self.config = config + + def msg(self, vars, mc, data, caller): + #print "got PlaceWindow.msg(", mc, ") from", caller + nickaction = vars['_nick'] + try: + nickaction = nickaction + " " + vars['_action'] + except: + pass + + #template = self.config.get('ui', 'msgtemplate') + + if mc == '_message_public': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_private': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_echo_public': + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_public_question': + nickaction += " fragt" + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_public_ask': + nickaction += " fragt" + self.append_text('%s: %s' % (nickaction, data)) + return + + if mc == '_message_public_text_action': + self.append_text(template % (nickaction, data)) + return + + if mc == '_status_place_members': + # _list_members, _list_members_nicks + for i in range(0, len(vars['_list_members'])): + self.nicklist.append((vars['_list_members'][i], + vars['_list_members_nicks'][i])) + return + + if mc.startswith('_notice_place_leave'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_notice_place_enter'): + #self.nicklist.append((vars['_source'], vars['_nick'])) + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_echo_place_enter'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_echo_place_leave'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_status'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_info'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_notice'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_failure'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_error'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + if mc.startswith('_warning'): + self.append_text(banner + parsetext(vars, mc, data)) + return + + PSYCObject.msg(self, vars, mc, data, caller) + + def say(self, text): + text = text.encode(self.config.get('main', 'encoding')) + print "say(", text, ")" + self.sendmsg({'_target': self.netname, + '_identification': config.get('main', 'uni'), + '_nick': config.get('main', 'nick')}, + '_message_public', text) + + + + +def poll(): + asyncore.poll(timeout = 0.0) + gtk.timeout_add(250, poll) + update_notebook_tabs() + +def cmd(win, text): + #text = text.encode(win.config.get('main', 'encoding')) + print "cmd(", text, ")" + win.sendmsg({'_target': win.netname, + '_identification': config.get('main', 'uni'), + }, '_request_execute', text) + +def entry_handler(win, netname, text): + global config + """ + if text and text[0] == "/": + win.sendmsg({'_target': config.get('main', 'uni')}, + '_request_execute', text) + else: + win.sendmsg({'_target': config.get('main', 'uni')}, + '_request_execute', + '/msg ' + win.netname + ' ' + text) + """ + if text and text[0] == '/': + if text.startswith('/q ') or \ + text.startswith('/query '): + user = text.split(' ')[1] + u = parseURL(config.get('main', 'uni')) + if user.find('~') == -1: + user = 'psyc://' + u['host'] + '/~' + user + elif user[0] == '~': + user = 'psyc://' + u['host'] + '/' + user + QueryWindow(user, None, config, main_window) + + elif text.startswith('/j ') or \ + text.startswith('/join '): + place = text.split(' ')[1] + u = parseURL(config.get('main', 'uni')) + if place.find('@') == -1: + place = 'psyc://' + u['host'] + '/@' + place + elif place[0] == '@': + place = 'psyc://' + u['host'] + '/' + place + print 'join', place + win.sendmsg({'_target': place, + '_nick': config.get('main', 'nick')}, + '_request_enter', '') + + elif text == '/pa' or \ + text == '/part': + try: + win.sendmsg({'_target' : win.netname}, + '_request_leave', '') + except: + print "failed to leave current place" + + elif text.startswith('/pa ') or \ + text.startswith('/part '): + try: + win.sendmsg({'_target' : text.split(' ')[1]}, + '_request_leave', '') + except: + print "failed to leave that place" + +# elif text.startswith('/quit'): +# pass +# +# elif text.startswith('/m '): +# pass +# +# elif text.startswith('/me '): +# pass +# + else: + try: + # should use the text[1..] really! TODO + cmd(win, text) + except: + pass + + elif text: + #print "calling say(", text, ") in", win + win.say(text) + + + + +if __name__ == '__main__': + #gc.set_debug(gc.DEBUG_LEAK) + + # pypsyc should one day implement http://psyc.pages.de/storage + # but that's something we can look into on some much later day. + # in the meantime we could provide for defaults if therapyrc + # is missing. what about.. psyc://beta.ve.symlynx.com/~$USER + config = ConfigParser.ConfigParser() + if len(sys.argv) == 2: + config_file = sys.argv[1] + else: + config_file = os.getenv('HOME') + '/.psyc/therapyrc' + config.read(config_file) + + main_window = MainWindow(config, entry_handler) + + authenticator = Authenticator(main_window, + config.get('main', 'uni'), + config.get('main', 'password')) + + status_label = gtk.Label('Status') + status_window = StatusWindow(config.get('main', 'uni'), status_label, + config, main_window) + main_window.notebook.append_page(status_window.get_frame(), status_label) + status_window.append_text(infotext) + + main_window.online() + + if not twisting: + poll() + gtk.main() + else: + l = task.LoopingCall(update_notebook_tabs) + l.start(0.25) # 250msec + reactor.run() + + print "cleaning up" + cmd(status_window, "QUIT") + + +# vim:expandtab diff --git a/therapy/therapyrc-dist b/therapy/therapyrc-dist new file mode 100644 index 0000000..59eb740 --- /dev/null +++ b/therapy/therapyrc-dist @@ -0,0 +1,21 @@ +# ~/.therapyrc +# Use this file as base for your configuration +# +# $Id: therapyrc-dist,v 1.8 2005/12/07 10:26:24 lynx Exp $ + +[main] +uni: psyc://psyced.org/~an +port: 4404 +password: supergeheim +nick: an +encoding: iso-8859-1 + +[ui] +font: Courier 9 +msgtemplate: [nick]: [text] + +[library] +compression: off +auth_sha: on +auth_plain: on +debug: off diff --git a/therapy/ui/__init__.py b/therapy/ui/__init__.py new file mode 100644 index 0000000..0d2dd8d --- /dev/null +++ b/therapy/ui/__init__.py @@ -0,0 +1 @@ +# different user interfaces for therapy diff --git a/therapy/ui/gtk/__init__.py b/therapy/ui/gtk/__init__.py new file mode 100644 index 0000000..0d2dd8d --- /dev/null +++ b/therapy/ui/gtk/__init__.py @@ -0,0 +1 @@ +# different user interfaces for therapy diff --git a/therapy/ui/gtk/ui.py b/therapy/ui/gtk/ui.py new file mode 100644 index 0000000..b30a7b8 --- /dev/null +++ b/therapy/ui/gtk/ui.py @@ -0,0 +1,237 @@ +# $Id: ui.py,v 1.7 2003/09/23 18:04:27 an Exp $ +# $Revision + +import gc + +import os, sys, time, string +import asyncore +import gtk, pango +import ConfigParser + +main_window = None +tab_windows = [] + +nl = "\r\n" + +class Window: + + vbox = None + hbox = None + scrollwin = None + textview = None + textbuf = None + label = None + infobar = None + target = "" + view = None + + def __init__(self, target, label): + global tab_windows + + self.vbox = gtk.VBox(gtk.FALSE, 0) + self.vbox.show() + + self.hbox = gtk.HBox(gtk.FALSE, 0) + self.hbox.show() + + self.vbox.pack_start(self.hbox, gtk.FALSE, gtk.FALSE, 0) + + self.infobar = gtk.Button(target) + self.hbox.pack_start(self.infobar, gtk.TRUE, gtk.TRUE, 0) + self.infobar.show() + + self.closer = gtk.Button("close") + self.hbox.pack_start(self.closer, gtk.FALSE, gtk.FALSE, 0) + self.closer.show() + self.closer.connect("clicked", self.delete) + + self.scrollwin = gtk.ScrolledWindow() + self.scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) + + self.textview = gtk.TextView() + self.textview.set_cursor_visible(gtk.FALSE) + self.textview.set_wrap_mode(gtk.WRAP_WORD) + self.textview.set_editable(gtk.FALSE) + self.textbuf = self.textview.get_buffer() + + self.scrollwin.add(self.textview) + self.scrollwin.show() + + font = pango.FontDescription('courier Medium 12') + self.textview.modify_font(font) + self.textview.show() + + self.target = target + + if label == None: + if target.find("@") != -1: + label = target[target.rfind("@"):] + elif target.find("~") != -1: + label = target[target.rfind("~")+1:] + else: + label = target + + self.label = gtk.Label(label) + + self.vbox.pack_start(self.scrollwin, gtk.TRUE, gtk.TRUE, 0) + + main_window.notebook.append_page(self.vbox, self.label) + + tab_windows.append(self) + + def __del__(self): + pass + + def set_view(self, view): + self.view = view + + def delete(self, foo): + global tab_windows + + num = main_window.notebook.page_num(self.vbox) + main_window.notebook.remove_page(num) + self.view.delete(self.target) + tab_windows.remove(self) + + #gc.collect() + + def append_text(self, text): + try: + iter = self.textbuf.get_end_iter() + # sieht krank aus, funktioniert aber + text = text.decode("iso-8859-1").encode("utf-8") + text += nl + self.textbuf.insert(iter, text) + iter = self.textbuf.get_end_iter() + self.textbuf.place_cursor(iter) + mark = self.textbuf.get_insert() + #mark = self.textbuf.get_mark_at_iter(iter) + self.textview.scroll_mark_onscreen(mark) + + cur_page = main_window.notebook.get_current_page() + win = None + for w in tab_windows: + if main_window.notebook.page_num(w.vbox) == cur_page: + win = w + if win != self: + self.label.modify_fg(gtk.STATE_NORMAL, + self.label.get_colormap().alloc_color('darkred')) + except: + pass + +class MainWindow: + + window = None + vbox = None + notebook = None + entry = None + statusbar = None + font = None + entry_handler = None + + def __init__(self, config, entry_handler): + global main_window + + self.entry_handler = entry_handler + + # create a new window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_size_request(350, 400) + self.window.set_title("theraPY") + self.window.connect("delete_event", gtk.mainquit) + + self.vbox = gtk.VBox(gtk.FALSE, 0) + self.window.add(self.vbox) + self.vbox.show() + + self.notebook = gtk.Notebook() + self.notebook.set_tab_pos(gtk.POS_BOTTOM) + # window.add(notebook) + self.vbox.pack_start(self.notebook, gtk.TRUE, gtk.TRUE, 0) + self.notebook.show() + + self.entry = gtk.Entry() + self.entry.set_max_length(256) + #self.entry.set_flags(gtk.HAS_DEFAULT) + self.entry.connect("activate", self.enter_callback, self.entry) + self.entry.set_text("") + # entry.insert_text(" world", len(entry.get_text())) + # entry.select_region(0, len(entry.get_text())) + self.vbox.pack_start(self.entry, gtk.FALSE, gtk.FALSE, 0) + self.font = pango.FontDescription('courier Medium 12') + self.entry.modify_font(self.font) + self.entry.show() + + self.statusbar = gtk.Statusbar() + self.vbox.pack_start(self.statusbar, gtk.FALSE, gtk.FALSE, 0) + self.statusbar.show() + + self.window.connect('key_press_event', self.key_press_callback) + self.window.show() + + main_window = self + + def key_press_callback(self, widget, event, *args): + key = event.keyval + if event.state & gtk.gdk.MOD1_MASK: + if key > 47 and key < 58: + if key == 48: + key += 10 + key -= 49 + try: + self.notebook.set_current_page(key) + except: + pass + return 1 + elif key == 110: + self.notebook.next_page() + return 1 + elif key == 112: + self.notebook.prev_page() + return 0 + + def enter_callback(self, widget, entry): + global tab_windows + + text = entry.get_text() + + cur_page = main_window.notebook.get_current_page() + target = None + for w in tab_windows: + if main_window.notebook.page_num(w.vbox) == cur_page: + target = w.target + win = w + + self.entry_handler(win, target, text) + + entry.set_text("") + +def update_notebook_tabs(): + cur_page = main_window.notebook.get_current_page() + win = None + for w in tab_windows: + if main_window.notebook.page_num(w.vbox) == cur_page: + win = w + if not win: + return + + win.label.modify_fg(gtk.STATE_NORMAL, + win.label.get_colormap().alloc_color('black')) + + if not main_window.entry.flags() & gtk.HAS_FOCUS: + i1 = win.textbuf.get_iter_at_mark(win.textbuf.get_insert()) + i2 = win.textbuf.get_iter_at_mark(win.textbuf.get_selection_bound()) + if not i1.compare(i2): + main_window.entry.grab_focus() + p = main_window.entry.get_text().__len__() + main_window.entry.select_region(p, p) + + +def poll(): + asyncore.poll(timeout = 0.0) + gtk.timeout_add(250, poll) + update_notebook_tabs() + +def mainloop(): + poll() + gtk.main() diff --git a/wx-config-example b/wx-config-example new file mode 100644 index 0000000..41f5fed --- /dev/null +++ b/wx-config-example @@ -0,0 +1,8 @@ +# this goes to ~/.pypsyc/wx-config +# or where ever you told pyPSYC it is +[gui] +wx_friend_size = 145, 291 +wx_room_size = 431, 522 +wx_friend_pos = 713, 814 +wx_room_pos = 712, 258 +