xtalk-1.3.orig/0040755000076600007650000000000006765256542012765 5ustar garabikatlasxtalk-1.3.orig/ChangeLog0100644000076600007650000001026006765257124014530 0ustar garabikatlasSat Nov 22 16:19:09 1997 Adam P. Jenkins * TalkdInter.py (TalkdInter.__init__): some variables which were supposed to be class variables -- lastAnnounce, localId, remoteId -- had the leading "self." left off. Fixed it. * Talk.py (Talk.makeEdits): Added code to swap the bindings for BackSpace and Delete keys in the text widgets if swapBsDel is true. (main): Added code to get swapBsDel from the XResource database and if it's true, set swapBsDel to 1, if it's false, set it to 0, and if it's not set, just leave swapBsDel set to its default setting. (Talk.setupIO): Added support for swapBsDel; it now decides which character to send as the erase character based on swapBsDel. Wed May 28 12:56:59 1997 Adam P. Jenkins * Talk.py (Talk.announceAgain): Made it also leave an invitation again. Otherwise, the talkd eventually times out and quits, so the original invitation is no longer waiting by the time the remote user responds. Thu May 15 14:22:34 1997 Adam P. Jenkins * Talk.py (Talk.connect): Changed self.error to Talk.error, since error is really a class variable. (Talk.parseAddress): Same thing, s/self.error/Talk.error/ (Talk.__init__): Moved some variables that were previously class variables into __init__. I didn't realize that initializing the variables in class scope made them class variables, ala C++ static member variables. It didn't cause a problem because I only declare one Talk object in the application anyway. Wed May 14 22:11:55 1997 Adam P. Jenkins * Talk.py (Talk.parseAddress): Changed parseAddress to use a regular expression to parse the string instead of writing the code by hand. Now the function is shorter *and* more robust. (Talk.setupIO): Made it ring the bell when a connection is established. (main): Made it set the X Resource class to "XTalk", so you can use .Xdefaults now. Wed Apr 30 09:53:39 1997 Adam P. Jenkins * Talk.py: Added code in several places to catch more exceptions and clean up after them and print a message in the status line, such as if you enter an invalid address, if can't connect to the talk daemon, etc. * TalkdInter.py: Replaced TalkdInter.errorMsgs with TalkdInter._errorMsgs, since this variable is only for internal use. (TalkdInter.transact): replaced a case where I'd written EINTR with errno.EINTR. This was causing an uncaught exception if the socket failed. (TalkdInter.__init__): Added try/except around socket module calls, in __init__. * Talk.py (Talk.connect): Made it so that when you're connected, the connect button is disabled, and is unbound in the address entry. Before, if you clicked on "Connect" while talking to someone, it would start going through the whole connect sequence again. Also just to be complete, I disabled "Disconnect" while not connected, and enable it when connected, though this didn't cause a problem before. (Talk.makeControls): Made Disconnect button start in a DISABLED state. (Talk.cleanup): Added code to complement that in Talk.connect, to reenable the connect handler and disable the disconnect handler. Tue Apr 29 15:08:45 1997 Adam P. Jenkins * Talk.py: modified Talk.cleanup and Talk.setupIO, to add bindings for paste operations in the local text window, so that when the xtalk user pastes text, it gets sent. Also created a new member, Talk.handlePaste, to do the actual work. Thanks to suggestion from Guido van Rossum . (Talk.makeConnection): Corrected spelling mistake of "party", from "pary". Thanks to Vladimir Marangozov for reporting this one. (Talk.__init__): When packing Talk.status, changed expand parameter to FALSE, so that the text windows would expand when resizing vertically. Mon Apr 28 16:28:43 1997 Adam P. Jenkins * Talk.py (Talk.handleRemoteInput): Added see(END) after inserting characters, so the last line is always visible. xtalk-1.3.orig/Makefile0100644000076600007650000000061206765257124014416 0ustar garabikatlasINSTALL_PREFIX=/usr/local LIBDIR = $(INSTALL_PREFIX)/lib/xtalk BINDIR = $(INSTALL_PREFIX)/bin SRCS = Talk.py TalkdInter.py talkd.py xtalk: Talk.py sed 's,@LIBDIR@,$(LIBDIR),g' Talk.py >xtalk chmod +x xtalk install: xtalk install -m 755 -d $(LIBDIR) $(BINDIR) install -m 644 TalkdInter.py talkd.py $(LIBDIR) install -m 755 xtalk $(BINDIR) clean: rm -f *.pyc xtalk xtalk-1.3.orig/README0100644000076600007650000000342006765257124013636 0ustar garabikatlasWhat: X-Windows BSD compatible talk client, written in Python. Author: Adam P. Jenkins Requirements: Python 1.4, Tkinter package, X-Windows. I suppose you could use it in Windows if Tkinter is ported to it, but xtalk also uses Unix socket calls, and I don't know if those are portable. License: GPL Installation: Xtalk comes as three python files. You can try xtalk out by just typing python Talk.py To install it, edit the Makefile to change the install prefix, and type make install This compiles the python files, copies them to /usr/local/lib/xtalk/, an puts the xtalk script in /usr/local/bin, where "/usr/local" is whatever you set INSTALL_PREFIX to in the Makefile. If your backspace doesn't work correctly in XTalk, you can try adding the following line to your .Xdefaults file XTalk.swapBsDel: true which will swap the bindings for the backspace and delete keys. Usage: Either run as "xtalk" or "xtalk user[@address [tty]]". The GUI is quite self-explanatory I think, but I'll explain it just in case. When started, xtalk won't actually try to connect until you click on the "Connect" button in the program. xtalk accepts addresses in the same format as the regular BSD talk program. From in the program you can enter the address in the "Address" entry. At any point during a talk session, clicking on "Disconnect" disconnects the session. The format of a talk address is as follows. To talk to a user on the same host, just use "username" as the address. To talk to a user on another host, use "username@hostname" as the address. If the user is logged in several times, you can specify which tty to "page" them on by saying "username tty" or "username@hostname tty". xtalk-1.3.orig/Talk.py0100755000076600007650000002450006765257124014230 0ustar garabikatlas#!/usr/bin/python ###################################################################### # XTalk - A BSD talk client written in Python. # (C) Adam P. Jenkins # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### import sys sys.path.append('@LIBDIR@') from socket import * SocketError = error from Tkinter import * from ScrolledText import ScrolledText import TalkdInter, string, regex, errno versionMajor = 1 versionMinor = 3 # set to 1 to swap BackSpace and Delete keybindings in edit widgets. swapBsDel = 0 class Talk(Frame): # used to raise exceptions error = 'TalkError' def __init__(self, parent=None, addr): self.sock = None self.afterId = None self.servSock = None self.talkd = None Frame.__init__(self, parent) self.pack(fill=BOTH, expand=TRUE) self.makeControls() self.makeEntry() self.makeEdits() self.entry.var.set(addr) self.status = Label(self, relief=SUNKEN) self.status.pack(side=TOP, fill=X, expand=FALSE) self.entry.address.focus() ##### callback functions def quit(self): self.disconnect() Frame.quit(self) def connect(self, event=None): try: address = self.parseAddress(self.entry.var.get()) except Talk.error, msg: self.status['text'] = msg return # Disable "connect" event handler, enable disconnect self.entry.address.unbind('') self.buttons.connect.config(state=DISABLED) self.buttons.disconnect.config(state=NORMAL) self.status['text'] = "Making connection to '%s@%s %s'" % \ (address[1], address[0], address[2]) self.update_idletasks() apply(self.makeConnection, address) def disconnect(self): self.cleanup() self.status['text'] = "Disconnected" ##### Functions to create the GUI def makeControls(self): self.buttons = Frame(self, relief=RAISED) self.buttons.pack(side=TOP, fill=X) self.buttons.quit = Button(self.buttons, text='Quit', command=self.quit) self.buttons.quit.pack(side=LEFT, padx=2, pady=2) self.buttons.connect = Button(self.buttons, text='Connect', command=self.connect) self.buttons.connect.pack(side=LEFT, padx=2, pady=2) self.buttons.disconnect = Button(self.buttons, text='Disconnect', command=self.disconnect, state=DISABLED) self.buttons.disconnect.pack(side=LEFT, padx=2, pady=2) def makeEntry(self): self.entry = Frame(self, relief=RAISED) self.entry.pack(side=TOP, fill=X) self.entry.lab = Label(self.entry, text='Address') self.entry.lab.pack(side=LEFT) self.entry.var = StringVar() self.entry.address = Entry(self.entry, textvariable=self.entry.var) self.entry.address.pack(side=LEFT, fill=X, expand=TRUE) self.entry.address.bind('', self.connect) def makeEdits(self): self.edit = Frame(self, relief=RAISED) self.edit.pack(side=TOP, fill=BOTH, expand=TRUE) self.edit.local = ScrolledText(self.edit, wrap=WORD, width=80, height=12, state=DISABLED) self.edit.remote = ScrolledText(self.edit, wrap=WORD, width=80, height=12, state=DISABLED) self.edit.local.pack(side=TOP, fill=BOTH, expand=TRUE, pady=2) self.edit.remote.pack(side=TOP, fill=BOTH, expand=TRUE, pady=2) if swapBsDel: bscmd = self.tk.call('bind', 'Text', '') dlcmd = self.tk.call('bind', 'Text', '') self.tk.call('bind', 'Text', '', dlcmd) self.tk.call('bind', 'Text', '', bscmd) ##### Implementation def parseAddress(self, addr): # parses a an address as entered by a user, and returns a # tuple (remote-host, remote-user, remote-tty) or raises a # Talk.error exception if there's an error in address. rx = regex.compile("\(^[^ \t@]+\)\(@\([^ \t@]+\)\)?\([ \t]+\(\w+\)\)?$") if rx.match(string.strip(addr)) > 0: ruser, rhost, rtty = rx.group(1, 3, 5) if not rhost: rhost = 'localhost' if not rtty: rtty = '' return (rhost, ruser, rtty) else: raise Talk.error, "Bad address format given." def cleanup(self): if self.afterId: self.after_cancel(self.afterId) self.afterId = None if self.talkd: try: self.talkd.deleteInvite('mine') self.talkd.deleteInvite('his') except TalkdInter.error, msg: pass self.talkd = None if self.servSock: tkinter.deletefilehandler(self.servSock) self.servSock.close() self.servSock = None if self.sock: tkinter.deletefilehandler(self.sock) self.edit.local.config(state=DISABLED) self.edit.local.unbind('') # unset paste handling in local window for e in ['', '']: self.edit.local.unbind(e) self.sock.close() self.sock = None # enable "connect" event handler, disable disconnect self.entry.address.bind('', self.connect) self.buttons.connect.config(state=NORMAL) self.buttons.disconnect.config(state=DISABLED) def makeConnection(self, rhost, ruser, rtty): try: self.talkd = TalkdInter.TalkdInter(rhost, ruser, rtty) self.status['text'] = "Checking for invite..." self.update_idletasks() try: raddr = self.talkd.lookUp() except TalkdInter.error, msg: raddr = None if raddr: self.status['text'] = "Found invitation. connecting..." self.update_idletasks() self.sock = socket(AF_INET, SOCK_STREAM) self.sock.connect(raddr) self.status['text'] = "Connected to " + `raddr` self.update_idletasks() self.setupIO() return self.servSock = socket(AF_INET, SOCK_STREAM) self.servSock.bind((gethostname(), 0)) myaddr = self.servSock.getsockname() self.servSock.listen(1) self.talkd.leaveInvite(myaddr) self.talkd.announce() except (TalkdInter.error, SocketError), err: self.status['text'] = err self.cleanup() return self.status['text'] = "Ringing remote party..." self.numTries = 1 tkinter.createfilehandler(self.servSock, tkinter.READABLE, self.acceptConnection) # announce again after 30 seconds self.afterId = self.after(30000, self.announceAgain) def announceAgain(self): try: self.talkd.announce() self.talkd.leaveInvite(self.servSock.getsockname()) except TalkdInter.error, msg: self.status['text'] = msg self.cleanup() return self.numTries = self.numTries + 1 self.status['text'] = "Ringing remote party, try " + `self.numTries` self.afterId = self.after(30000, self.announceAgain) def acceptConnection(self, file, mask): acc = self.servSock.accept() self.status['text'] = "Accepted connection from " + `acc[1]` tkinter.deletefilehandler(self.servSock) self.sock = acc[0] self.servSock.close() self.servSock = None try: self.talkd.deleteInvite('mine') self.talkd.deleteInvite('his') except TalkdInter.error, msg: pass self.talkd = None self.setupIO() def setupIO(self): self.bell() if self.afterId: self.after_cancel(self.afterId) self.afterId = None # exchange edit chars. The edit chars are supposed to be # 1) erase, i.e. backspace, Text widget uses \010 # 2) kill, not sure, think it's delete current line # 3) wkill, not sure, think it's delete to beginning of word. if swapBsDel: self.sock.send('\177\0\0') else: self.sock.send('\010\0\0') self.editChars = self.sock.recv(3) tkinter.createfilehandler(self.sock, tkinter.READABLE, self.handleRemoteInput) self.sock.setblocking(0) self.edit.local.config(state=NORMAL) self.edit.remote.config(state=NORMAL) # clear both edit windows self.edit.local.delete('1.0', END) self.edit.remote.delete('1.0', END) self.edit.remote.config(state=DISABLED) self.edit.local.bind('', self.handleLocalInput) # set up paste handling in local window for e in ['', '']: self.edit.local.bind(e, self.handlePaste) self.edit.local.focus() def handleRemoteInput(self, file, mask): try: inp = self.sock.recv(80) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() return if not inp: self.disconnect() return self.edit.remote.config(state=NORMAL) for c in inp: if c == self.editChars[0]: self.edit.remote.delete("end - 2 char") else: self.edit.remote.insert(END, c) self.edit.remote.see(END) self.edit.remote.config(state=DISABLED) def handleLocalInput(self, event): c = event.char if c == '': return if c == '\015': c = '\012' try: self.sock.send(c) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() def handlePaste(self, event): try: p = self.selection_get() except TclError: return if len(p) == 0: return self.edit.local.see(END) try: self.sock.send(p) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() def main(): if len(sys.argv) > 1: if len(sys.argv) >= 3: addr = "%s %s" % (sys.argv[1], sys.argv[2]) else: addr = sys.argv[1] else: addr = '' root = Tk(className='XTalk') opt = string.lower(root.option_get('swapBsDel','')) if opt == 'true': swapBsDel = 1 elif opt == 'false': swapBsDel = 0 elif opt != '': print 'Warning: unrecognized value for XTalk.swapBsDel: %s' % opt talk = Talk(None, addr) talk.winfo_toplevel().title("XTalk %d.%d" % (versionMajor, versionMinor)) talk.mainloop() if __name__ == '__main__': main() xtalk-1.3.orig/TalkdInter.py0100644000076600007650000001634506765257124015403 0ustar garabikatlas###################################################################### # XTalk - A BSD talk client written in Python. # (C) Adam P. Jenkins # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### # talkd interface. A TalkdInter object represents a pair of talk daemons. # TalkdInter has the following member functions. All of them may # raise a TalkdInter.error exception. # # __init__ (remote-host, remote-user, remote-tty='') # # leaveInvite (my-address) # where my-address is a (address, port) tupple of a socket waiting # for a connection # # lookUp () # Does a lookup on the remote host to see if an invititation is # waiting. If so, it returns an (address, port) tuple, otherwise an # exception is thrown # # deleteInvite (which) # which is either 'mine' or 'his'. Deletes the invitation on the # local or remote host. raises an "error" exception if there is no # invite waiting. # # announce () # Announces on the remote users's screen that you want to talk. # raises an exception if the remote user isn't logged in or isn't # accepting connections # # # A talk client wanting to establish a connection would use the # following protocol. # 1) Create a TalkdInter object for the desired (remote-host, # remote-user, tty) that you want to connect with. # 2) use lookUp() to see if there's already an invite waiting. If so, # lookUp() will return a (address, port) that the remote user is # already waiting on. Just connect to this (address, port) and you're # all set. # 3) If lookUp() failed, but not because the user wasn't logged in, # then open a SOCK_STREAM socket, and listen on it for a connection. # Call leaveInvite() with the (address, port) that you're listening # on. # 4) call announce() to announce to the remote user that you're # waiting. Now when they do a lookUp(), they'll find your (address, # port) and connect to you. You can keep sending an announce every so # often. # 5) When they connect to you, call deleteInvite('mine') to delete the # invitation that you left. You may also want to call # deleteInvite('his') just to make sure the other one isn't left there. import socket from socket import * from talkd import * import pwd, os, errno, select error = 'error' _errorMsgs = ("Successful.", "Your party is not logged on", "Target machine is too confused to talk to us", "Target machine does not recognize us", "Your party is refusing messages", "Target machine cannot handle remote talk", "Target machine indicates protocol mismatch", "Target machine indicates protocol botch (addr)", "Target machine indicates protocol botch (ctl_addr)") class TalkdInter: def __init__(self, rhost, ruser, rtty=''): self.lastAnnounce = -2 self.localId = -1 self.remoteId = -1 self.rhost = rhost self.ruser = ruser self.rtty = rtty try: self.luser = pwd.getpwuid(os.getuid())[0] self.raddress = gethostbyname(rhost) self.laddress = gethostbyname(gethostname()) self.talkdPort = getservbyname('ntalk', 'udp') # set up a socket that will be used to communicate with a talk # daemon, self.ctlSock. self.ctlAddr is the corresponding # address. self.ctlSock = socket.socket(AF_INET, SOCK_DGRAM) self.ctlAddr = (self.laddress, 0) self.ctlSock.bind(self.ctlAddr) # find out what port we were assigned self.ctlAddr = self.ctlSock.getsockname() except socket.error, err: raise error, err # and set up the CTL_MSG structure. This initializes the # whole thing except for "addr", which isn't known until a # client wants to open a connection. self.ctlMsg = CTL_MSG() self.ctlMsg.ctlAddr = sockaddr_in(AF_INET, self.ctlAddr[1], self.ctlAddr[0]) self.ctlMsg.addr = sockaddr_in(AF_INET) self.ctlMsg.pid = os.getpid() self.ctlMsg.localName = self.luser self.ctlMsg.remoteName = self.ruser self.ctlMsg.remoteTTY = self.rtty # addr is a (host, port) tuple that the remote talk should respond # to. Leaves an invitation with the local talk daemon. def leaveInvite(self, addr): self.ctlMsg.addr.address = addr[0] self.ctlMsg.addr.port = addr[1] response = self.transact(LEAVE_INVITE, self.laddress) # keep track of local id, so I can delete the invitation later self.localId = response.idNum # does a lookup on the remote talk daemon to see if there's an # invitation waiting. def lookUp(self): response = self.transact(LOOK_UP, self.raddress) if response.answer == SUCCESS: self.remoteId = response.idNum addr = (response.addr.address, response.addr.port) return addr else: raise error, _errorMsgs[response.answer] # deletes an invite. which is either 'mine', or 'his', indicating # whether to delete the local or remote invitation def deleteInvite(self, which): if which == 'mine': addr = self.laddress self.ctlMsg.idNum = self.localId else: addr = self.raddress self.ctlMsg.idNum = self.remoteId response = self.transact(DELETE, addr) if response.answer != SUCCESS: raise error, _errorMsgs[response.answer] def announce(self): self.ctlMsg.idNum = (self.lastAnnounce + 1) response = self.transact(ANNOUNCE, self.raddress) if response.answer != SUCCESS: raise error, _errorMsgs[response.answer] else: self.lastAnnounce = response.idNum def transact(self, type, addr): self.ctlMsg.type = type daemonAddr = (addr, self.talkdPort) sendData = self.ctlMsg.toCStruct() response = CTL_RESPONSE() response.version = -1 while 1: # resend message until a response is obtained while 1: num = self.ctlSock.sendto(sendData, daemonAddr) if num != self.ctlMsg.size: raise error, 'Error on write to talk daemon' try: ready = select.select([self.ctlSock], [], [], 2.0) except select.error, er: if er[0] != errno.EINTR: raise error, er[1] if ready[0]: break # keep reading while there are queued messages while 1: try: readData = self.ctlSock.recv(response.size) except socket.error, err: if err[0] != errno.EINTR: raise error, err[1] response.fromCStruct(readData) ready = select.select([self.ctlSock], [], [], 0) if not ready[0] or (response.version == TALK_VERSION and response.type == type): break if (response.version == TALK_VERSION and response.type == type): break return response xtalk-1.3.orig/copyright0100644000076600007650000000177706765257124014726 0ustar garabikatlas###################################################################### # XTalk - A BSD compatible talk client written in Python. # (C) Adam P. Jenkins # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### xtalk-1.3.orig/finger.py0100755000076600007650000000166006765257124014611 0ustar garabikatlas#!/usr/bin/python import regex, socket, sys from Tkinter import * error = "fingerError" def finger(user, host): fingerPort = socket.getservbyname('finger', 'tcp') sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, fingerPort)) fp = sock.makefile("r+") fp.write(user + "\r\n") fp.flush() return fp.read() def parseQuery(query): rx = regex.compile("^\([^ \t@]+\)?\(@\([^ \t]+\)\)?$") if rx.match(query) > 0: user, host = rx.group(1, 3) if not user: user = "" if not host: host = "localhost" return (user, host) else: raise error, "Bad address format." class Finger(Frame): def __init__ if __name__ == '__main__': if len(sys.argv) > 1: for query in sys.argv[1:]: user, host = parseQuery(query) print "[%s]" % host print finger(user, host) else: print "[localhost]" print finger("", "localhost") xtalk-1.3.orig/talkd.py0100644000076600007650000001264606765257124014441 0ustar garabikatlas###################################################################### # XTalk - A BSD talk client written in Python. # (C) Adam P. Jenkins # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### # Implements the C structures for talking to a talk daemon import struct, regex, regsub, socket, string # This modules defines classes necessary to communicate with a talk # daemon. The talk daemon requires communication via binary data # structures, so this module provides an interface. There are three # classes defined, which correspond to C structures. Each of them has # a method, toCStruct(), which returns a string containing the proper # binary representation of the structure, and a method, # fromCStruct(str), which takes such a binary string, and initializes # itself from the binary string. Each class also has a "size" # attribute which returns its size in bytes. # The classes are: # # sockaddr_in: # family # one of the family constants from socket module # port # a port number # address # an address in cannonical or number-dot format # # CTL_MSG: # # CTL_RESPONSE # The declaration for sockaddr is # struct sockaddr { # unsigned short family; /* AF_* */ # char sa_data[14]; /* the resto of the data */ # }; # # sizeof(sockaddr) == 16 # # What's the rest of the data. Normally it's a sockaddr_in. # struct in_addr { # __u32 s_addr; # }; # # struct sockaddr_in { # short family; # short port; # struct in_addr; # /* plus padding to fill out to the size of a sockaddr. */ # }; # TALK_VERSION = 1 # message types LEAVE_INVITE = 0 LOOK_UP = 1 DELETE = 2 ANNOUNCE = 3 # response types SUCCESS = 0 NOT_HERE = 1 FAILED = 2 MACHINE_UNKNOWN = 3 PERMISSION_DENIED = 4 UNKNOWN_REQUEST = 5 BADVERSION = 6 BADADDR = 7 BADCTLADDR = 8 def htons(n): return struct.pack('bb', n >> 8, n & 0xff) def htonl(n): return struct.pack('4b', n >> 24, n >> 16, n >> 8, n & 0xff) def htonb(n): return struct.pack('b', n) def ntohs(n): hi, low = struct.unpack('bb', n) return ((hi & 0xff) << 8) + (low & 0xff) def ntohl(n): tmp = struct.unpack('4b', n) return (((tmp[0] & 0xff) << 24) + ((tmp[1] & 0xff) << 16) + ((tmp[2] & 0xff) << 8) + (tmp[3] & 0xff)) def ntohb(n): tmp = struct.unpack('b', n) return (tmp[0] & 0xff) class sockaddr_in: size = 16 def __init__(self, family=0, port=0, addr='0.0.0.0'): self.family = family self.port = port self.address = addr # returns the equivalent of a sockaddr_in, already in network byte # order. def toCStruct(self): # make sure address is in numbers-dots notation addr = socket.gethostbyname(self.address) # break it up into components addr = regsub.split(addr, '\.') # and convert them to numbers addr = map(string.atoi, addr) return (htons(self.family) + htons(self.port) + struct.pack('4b8x', addr[0], addr[1], addr[2], addr[3])) def fromCStruct(self, str): self.family = ntohs(str[0:2]) self.port = ntohs(str[2:4]) addr = struct.unpack('bbbb', str[4:8]) addr = map(lambda x: x & 0xff, addr) self.address = '%d.%d.%d.%d' % (addr[0], addr[1], addr[2], addr[3]) class CTL_MSG: size = 84 version = TALK_VERSION type = 0 idNum = 0 addr = sockaddr_in() ctlAddr = sockaddr_in() pid = 0 localName = '' remoteName = '' remoteTTY = '' def toCStruct(self): return (htonb(self.version) + htonb(self.type) + htons(0) + htonl(self.idNum) + self.addr.toCStruct() + self.ctlAddr.toCStruct() + htonl(self.pid) + self.localName + ('\0' * (12 - len(self.localName))) + self.remoteName + ('\0' * (12 - len(self.remoteName))) + self.remoteTTY + ('\0' * (16 - len(self.remoteTTY)))) def fromCStruct(self, str): self.version = ntohb(str[0]) self.type = ntohb(str[1]) self.idNum = ntohl(str[4:8]) self.addr.fromCStruct(str[8:24]) self.ctlAddr.fromCStruct(str[24:40]) self.pid = ntohl(str[40:44]) self.localName = regsub.sub('\0*$', '', str[44:56]) self.remoteName = regsub.sub('\0*$', '', str[56:68]) self.remoteTTY = regsub.sub('\0*$', '', str[68:84]) class CTL_RESPONSE: size = 24 version = TALK_VERSION type = 0 answer = 0 idNum = 0 addr = sockaddr_in() def toCStruct(self): return (struct.pack('bbbx', self.version, self.type, self.answer) + htonl(self.idNum) + self.addr.toCStruct()) def fromCStruct(self, str): self.version = ntohb(str[0]) self.type = ntohb(str[1]) self.answer = ntohb(str[2]) self.idNum = ntohl(str[4:8]) self.addr.fromCStruct(str[8:24]) xtalk-1.3.orig/xtalk0100755000076600007650000002473006765257124014036 0ustar garabikatlas#!/usr/bin/python # -*-python-*- ###################################################################### # XTalk - A BSD talk client written in Python. # (C) Adam P. Jenkins # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### import sys sys.path.append('/usr/local/lib/xtalk') from socket import * SocketError = error from Tkinter import * from ScrolledText import ScrolledText import TalkdInter, string, regex, errno versionMajor = 1 versionMinor = 3 # set to 1 to swap BackSpace and Delete keybindings in edit widgets. swapBsDel = 0 class Talk(Frame): # used to raise exceptions error = 'TalkError' def __init__(self, parent=None, addr): self.sock = None self.afterId = None self.servSock = None self.talkd = None Frame.__init__(self, parent) self.pack(fill=BOTH, expand=TRUE) self.makeControls() self.makeEntry() self.makeEdits() self.entry.var.set(addr) self.status = Label(self, relief=SUNKEN) self.status.pack(side=TOP, fill=X, expand=FALSE) self.entry.address.focus() ##### callback functions def quit(self): self.disconnect() Frame.quit(self) def connect(self, event=None): try: address = self.parseAddress(self.entry.var.get()) except Talk.error, msg: self.status['text'] = msg return # Disable "connect" event handler, enable disconnect self.entry.address.unbind('') self.buttons.connect.config(state=DISABLED) self.buttons.disconnect.config(state=NORMAL) self.status['text'] = "Making connection to '%s@%s %s'" % \ (address[1], address[0], address[2]) self.update_idletasks() apply(self.makeConnection, address) def disconnect(self): self.cleanup() self.status['text'] = "Disconnected" ##### Functions to create the GUI def makeControls(self): self.buttons = Frame(self, relief=RAISED) self.buttons.pack(side=TOP, fill=X) self.buttons.quit = Button(self.buttons, text='Quit', command=self.quit) self.buttons.quit.pack(side=LEFT, padx=2, pady=2) self.buttons.connect = Button(self.buttons, text='Connect', command=self.connect) self.buttons.connect.pack(side=LEFT, padx=2, pady=2) self.buttons.disconnect = Button(self.buttons, text='Disconnect', command=self.disconnect, state=DISABLED) self.buttons.disconnect.pack(side=LEFT, padx=2, pady=2) def makeEntry(self): self.entry = Frame(self, relief=RAISED) self.entry.pack(side=TOP, fill=X) self.entry.lab = Label(self.entry, text='Address') self.entry.lab.pack(side=LEFT) self.entry.var = StringVar() self.entry.address = Entry(self.entry, textvariable=self.entry.var) self.entry.address.pack(side=LEFT, fill=X, expand=TRUE) self.entry.address.bind('', self.connect) def makeEdits(self): self.edit = Frame(self, relief=RAISED) self.edit.pack(side=TOP, fill=BOTH, expand=TRUE) self.edit.local = ScrolledText(self.edit, wrap=WORD, width=80, height=12, state=DISABLED) self.edit.remote = ScrolledText(self.edit, wrap=WORD, width=80, height=12, state=DISABLED) self.edit.local.pack(side=TOP, fill=BOTH, expand=TRUE, pady=2) self.edit.remote.pack(side=TOP, fill=BOTH, expand=TRUE, pady=2) if swapBsDel: bscmd = self.tk.call('bind', 'Text', '') dlcmd = self.tk.call('bind', 'Text', '') self.tk.call('bind', 'Text', '', dlcmd) self.tk.call('bind', 'Text', '', bscmd) ##### Implementation def parseAddress(self, addr): # parses a an address as entered by a user, and returns a # tuple (remote-host, remote-user, remote-tty) or raises a # Talk.error exception if there's an error in address. rx = regex.compile("\(^[^ \t@]+\)\(@\([^ \t@]+\)\)?\([ \t]+\(\w+\)\)?$") if rx.match(string.strip(addr)) > 0: ruser, rhost, rtty = rx.group(1, 3, 5) if not rhost: rhost = 'localhost' if not rtty: rtty = '' return (rhost, ruser, rtty) else: raise Talk.error, "Bad address format given." def cleanup(self): if self.afterId: self.after_cancel(self.afterId) self.afterId = None if self.talkd: try: self.talkd.deleteInvite('mine') self.talkd.deleteInvite('his') except TalkdInter.error, msg: pass self.talkd = None if self.servSock: tkinter.deletefilehandler(self.servSock) self.servSock.close() self.servSock = None if self.sock: tkinter.deletefilehandler(self.sock) self.edit.local.config(state=DISABLED) self.edit.local.unbind('') # unset paste handling in local window for e in ['', '']: self.edit.local.unbind(e) self.sock.close() self.sock = None # enable "connect" event handler, disable disconnect self.entry.address.bind('', self.connect) self.buttons.connect.config(state=NORMAL) self.buttons.disconnect.config(state=DISABLED) def makeConnection(self, rhost, ruser, rtty): try: self.talkd = TalkdInter.TalkdInter(rhost, ruser, rtty) self.status['text'] = "Checking for invite..." self.update_idletasks() try: raddr = self.talkd.lookUp() except TalkdInter.error, msg: raddr = None if raddr: # found an invitation, connect to remote party self.status['text'] = "Found invitation. connecting..." self.update_idletasks() self.sock = socket(AF_INET, SOCK_STREAM) self.sock.connect(raddr) self.status['text'] = "Connected to " + `raddr` self.update_idletasks() self.setupIO() else: # didn't find an invite, invite remote party self.servSock = socket(AF_INET, SOCK_STREAM) self.servSock.bind((gethostname(), 0)) myaddr = self.servSock.getsockname() self.servSock.listen(1) self.talkd.leaveInvite(myaddr) self.talkd.announce() self.status['text'] = "Ringing remote party..." self.numTries = 1 tkinter.createfilehandler(self.servSock, tkinter.READABLE, self.acceptConnection) # announce again after 30 seconds self.afterId = self.after(30000, self.announceAgain) except (TalkdInter.error, SocketError), err: self.status['text'] = err self.cleanup() def announceAgain(self): try: self.talkd.announce() self.talkd.leaveInvite(self.servSock.getsockname()) except TalkdInter.error, msg: self.status['text'] = msg self.cleanup() return self.numTries = self.numTries + 1 self.status['text'] = "Ringing remote party, try " + `self.numTries` self.afterId = self.after(30000, self.announceAgain) def acceptConnection(self, file, mask): acc = self.servSock.accept() self.status['text'] = "Accepted connection from " + `acc[1]` tkinter.deletefilehandler(self.servSock) self.sock = acc[0] self.servSock.close() self.servSock = None try: self.talkd.deleteInvite('mine') self.talkd.deleteInvite('his') except TalkdInter.error, msg: pass self.talkd = None self.setupIO() def setupIO(self): self.bell() if self.afterId: self.after_cancel(self.afterId) self.afterId = None # exchange edit chars. The edit chars are supposed to be # 1) erase, i.e. backspace, Text widget uses \010 # 2) kill, not sure, think it's delete current line # 3) wkill, not sure, think it's delete to beginning of word. if swapBsDel: self.sock.send('\177\0\0') else: self.sock.send('\010\0\0') self.editChars = self.sock.recv(3) tkinter.createfilehandler(self.sock, tkinter.READABLE, self.handleRemoteInput) self.sock.setblocking(0) self.edit.local.config(state=NORMAL) self.edit.remote.config(state=NORMAL) # clear both edit windows self.edit.local.delete('1.0', END) self.edit.remote.delete('1.0', END) self.edit.remote.config(state=DISABLED) self.edit.local.bind('', self.handleLocalInput) # set up paste handling in local window for e in ['', '']: self.edit.local.bind(e, self.handlePaste) self.edit.local.focus() def handleRemoteInput(self, file, mask): try: inp = self.sock.recv(80) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() return if not inp: self.disconnect() return self.edit.remote.config(state=NORMAL) for c in inp: if c == self.editChars[0]: self.edit.remote.delete("end - 2 char") else: self.edit.remote.insert(END, c) self.edit.remote.see(END) self.edit.remote.config(state=DISABLED) def handleLocalInput(self, event): c = event.char if c == '': return elif c == '\015': c = '\012' try: self.sock.send(c) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() def handlePaste(self, event): try: p = self.selection_get() except TclError: return if len(p) == 0: return self.edit.local.see(END) try: self.sock.send(p) except SocketError, err: if err[0] != errno.EWOULDBLOCK: print err[1] self.disconnect() def main(): if len(sys.argv) > 1: if len(sys.argv) >= 3: addr = "%s %s" % (sys.argv[1], sys.argv[2]) else: addr = sys.argv[1] else: addr = '' root = Tk(className='XTalk') opt = string.lower(root.option_get('swapBsDel','')) if opt == 'true': swapBsDel = 1 elif opt == 'false': swapBsDel = 0 elif opt != '': print 'Warning: unrecognized value for XTalk.swapBsDel: %s' % opt talk = Talk(None, addr) talk.winfo_toplevel().title("XTalk %d.%d" % (versionMajor, versionMinor)) talk.mainloop() if __name__ == '__main__': main()