aprs.py
  1. # -*- coding: UTF-8 -*-
  2. # Copyright (C) 2008, Jack Zielke <[email protected]>
  3. # Copyright (C) 2008, One Laptop Per Child
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18.  
  19. import gtk
  20. import pango
  21. import time
  22. import socket
  23. import random
  24. import gobject
  25. import json
  26.  
  27. from gettext import gettext as _
  28.  
  29. from sugar import profile
  30. from sugar.activity import activity
  31. from sugar.bundle.activitybundle import ActivityBundle
  32. from sugar.graphics.menuitem import MenuItem
  33. from sugar.graphics.toolbutton import ToolButton
  34.  
  35. HOST = 'rotate.aprs2.net'
  36. #HOST = '192.168.50.6'
  37. PORT = 14580
  38. RECV_BUFFER = 4096
  39.  
  40. MAXLINES = 50
  41. MAXRETRIES = 15
  42. MAX_MSG_QUEUE = 25
  43. MAX_PER_CALL_QUEUE = 2
  44.  
  45. FILTER = "m/300"
  46.  
  47. bundle = ActivityBundle(activity.get_bundle_path())
  48. VERSION = bundle.get_activity_version()
  49. del bundle
  50.  
  51. class APRSActivity(activity.Activity):
  52.     def __init__(self, handle):
  53.         activity.Activity.__init__(self, handle)
  54.         self.set_title(_('APRS-XO Activity'))
  55.  
  56.         self.sock = None
  57.         self.location = "home"
  58.         self.site = None
  59.         self.sent_acks = {}
  60.         self.recv_acks = {}
  61.         self.timers = []
  62.         self.bulletins = ["AIR", "DGPS", "QST", "TEL", "ALL", "DRILL", "QTH", "TEST", "AP", "DX", "RTCM", "TLM", "BEACON", "ID", "SKY", "WX", "CQ", "JAVA", "SPACE", "ZIP", "GPS", "MAIL", "SPC", "DF", "MICE", "SYM", "BLN", "NWS", "NTS"]
  63.         self.validating = False
  64.         self.help = True
  65.         self.messagebox = False
  66.         self.sequence = random.randrange(0, 7000)
  67.         self.queue_list = {}
  68.         self.message_list = {}
  69.         self.seen_bulletins = {}
  70.         self.message_marks = {}
  71.         self.input_watch = []
  72.         self.output_watch = []
  73.         self.current_message = {}
  74.  
  75.         titlefont = pango.FontDescription('Sans bold 8')
  76.         mediumfont = pango.FontDescription('Sans 6.5')
  77.         smallfont = pango.FontDescription('Sans 6')
  78.         verysmallfont = pango.FontDescription('Sans 4')
  79.  
  80.         firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
  81.  
  82.         toolbox = activity.ActivityToolbox(self)
  83.         self.set_toolbox(toolbox)
  84.  
  85.         activity_toolbar = toolbox.get_activity_toolbar()
  86.         activity_toolbar.share.props.visible = False
  87. #        activity_toolbar.keep.props.visible = False
  88.  
  89.         toolbox.show()
  90.  
  91.         win = gtk.HBox(False, 10)
  92. #        self.set_canvas(win)
  93.  
  94.         leftwin = gtk.VBox(False, 10)
  95.  
  96.         # Top 'about' box
  97.         aboutbox = gtk.VBox(False, 11)
  98.         aboutbox.set_border_width(10)
  99.  
  100.         topaboutbox = gtk.VBox(False, 0)
  101.  
  102.         titlebox = gtk.HBox(False, 0)
  103.         titlelabel = gtk.Label("APRS-XO:")
  104.         titlelabel.set_alignment(0, 0)
  105.         titlelabel.modify_font(titlefont)
  106.         titlebox.pack_start(titlelabel, False, False, 0)
  107.         titlelabel.show()
  108.         aboutlabel1 = gtk.Label("This amateur radio program will update your")
  109.         aboutlabel1.set_alignment(0, 0.8)
  110. #        aboutlabel1.modify_font(mediumfont)
  111.         titlebox.pack_start(aboutlabel1, False, False, 0)
  112.         aboutlabel1.show()
  113.         topaboutbox.pack_start(titlebox, False, False, 0)
  114.         titlebox.show()
  115.         aboutlabel2 = gtk.Label("positon & status on all of the global APRS web pages once\nevery 10 minutes.")
  116.         aboutlabel2.set_alignment(0, 0)
  117. #        aboutlabel2.modify_font(mediumfont)
  118.         topaboutbox.pack_start(aboutlabel2, False, False, 0)
  119.         aboutlabel2.show()
  120.         aboutbox.pack_start(topaboutbox, False, False, 0)
  121.         topaboutbox.show()
  122.  
  123.         sitebox = gtk.HBox(False, 10)
  124.         sitelabel = gtk.Label("Select an APRS site:")
  125.         sitelabel.set_alignment(0, 0.4)
  126.         sitebox.pack_start(sitelabel, False, False, 0)
  127.         sitelabel.show()
  128.  
  129.         findubutton = gtk.Button()
  130.         findubutton.set_label("FINDU.COM")
  131.         findubutton.connect("clicked", self.set_site, "http://www.findu.com/cgi-bin/symbol.cgi?icon=XA&limit=200")
  132.         sitebox.pack_start(findubutton, False, False, 0)
  133.         findubutton.show()
  134.  
  135.         aprsworldbutton = gtk.Button()
  136.         aprsworldbutton.set_label("APRSworld")
  137.         aprsworldbutton.connect("clicked", self.set_site, "http://aprsworld.net/")
  138.         sitebox.pack_start(aprsworldbutton, False, False, 0)
  139.         aprsworldbutton.show()
  140.  
  141.         otherbutton = gtk.Button()
  142.         otherbutton.set_label("About")
  143.         otherbutton.connect("clicked", self.set_site, "http://aprs.org/")
  144.         sitebox.pack_start(otherbutton, False, False, 0)
  145.         otherbutton.show()
  146.  
  147.         aboutbox.pack_start(sitebox, False, False, 0)
  148.         sitebox.show()
  149.  
  150.         leftwin.pack_start(aboutbox, False, False, 0)
  151.         aboutbox.show()
  152.  
  153.         separator = gtk.HSeparator()
  154.         leftwin.pack_start(separator, False, False, 0)
  155.         separator.show()
  156.  
  157.         # identifiers box
  158.         identbox = gtk.VBox(False, 4)
  159.         identbox.set_border_width(10)
  160.  
  161.         identlabel = gtk.Label("Identifiers")
  162.         identlabel.set_alignment(0, 0)
  163.         identlabel.modify_font(titlefont)
  164.         identbox.pack_start(identlabel, False, False, 0)
  165.         identlabel.show()
  166.  
  167.         bottomidentbox = gtk.HBox(False, 10)
  168.  
  169.         calllabel1 = gtk.Label("Callsign: ")
  170.         calllabel1.set_alignment(1, 0.5)
  171.         bottomidentbox.pack_start(calllabel1, False, False, 0)
  172.         calllabel1.show()
  173.  
  174.         self.calltext = gtk.Entry()
  175.         self.calltext.set_max_length(9)
  176.         self.calltext.set_width_chars(9)
  177.         self.calltext.set_text(self.metadata.get('callsign', ""))
  178.         self.calltext.connect("changed", self.disable_beacon)
  179.         bottomidentbox.pack_start(self.calltext, False, False, 0)
  180.         self.calltext.show()
  181.  
  182.         passlabel1 = gtk.Label("Password: ")
  183.         passlabel1.set_alignment(1, 0.5)
  184.         bottomidentbox.pack_start(passlabel1, False, False, 0)
  185.         passlabel1.show()
  186.  
  187.         self.passtext = gtk.Entry()
  188.         self.passtext.set_max_length(5)
  189.         self.passtext.set_width_chars(5)
  190.         self.passtext.set_invisible_char("x")
  191.         self.passtext.set_visibility(False)
  192.         bottomidentbox.pack_start(self.passtext, False, False, 0)
  193.         self.passtext.show()
  194.  
  195.         identbox.pack_start(bottomidentbox, False, False, 0)
  196.         bottomidentbox.show()
  197.  
  198.         passlabel2 = gtk.Label("optional")
  199.         passlabel2.set_alignment(0.74, 0)
  200.         passlabel2.modify_font(smallfont)
  201.         identbox.pack_start(passlabel2, False, False, 0)
  202.         passlabel2.show()
  203.  
  204.         leftwin.pack_start(identbox, False, False, 0)
  205.         identbox.show()
  206.  
  207.         separator = gtk.HSeparator()
  208.         leftwin.pack_start(separator, False, False, 0)
  209.         separator.show()
  210.  
  211.         # station box
  212.         stationbox = gtk.VBox(False, 11)
  213.         stationbox.set_border_width(10)
  214.  
  215.         stationlabel = gtk.Label("Station Comment")
  216.         stationlabel.set_alignment(0, 0)
  217.         stationlabel.modify_font(titlefont)
  218.         stationbox.pack_start(stationlabel, False, False, 0)
  219.         stationlabel.show()
  220.  
  221.         # so the text box does not fill all horizontal space
  222.         stationtextbox = gtk.HBox()
  223.  
  224.         self.stationtext = gtk.Entry()
  225.         self.stationtext.set_max_length(43)
  226.         self.stationtext.set_width_chars(43)
  227.         self.stationtext.set_text("%s's XO at home." % firstName)
  228.         self.stationtext.connect("changed", self.disable_beacon)
  229.         stationtextbox.pack_start(self.stationtext, False, False, 0)
  230.         self.stationtext.show()
  231.         stationbox.pack_start(stationtextbox, False, False, 0)
  232.         stationtextbox.show()
  233.  
  234.         stationhelp = gtk.Label("Optional description of current position, status,\nor destination.  Enter up to 43 characters, most\nimportant first since some displays can only see\nthe first 20 or 28.")
  235.         stationhelp.set_alignment(0, 0)
  236. #        stationhelp.modify_font(mediumfont)
  237.         stationbox.pack_start(stationhelp, False, False, 0)
  238.         stationhelp.show()
  239.  
  240.         leftwin.pack_start(stationbox, False, False, 0)
  241.         stationbox.show()
  242.  
  243.         separator = gtk.HSeparator()
  244.         leftwin.pack_start(separator, False, False, 0)
  245.         separator.show()
  246.  
  247.         # position box
  248.         positbox = gtk.VBox(False, 11)
  249.         positbox.set_border_width(10)
  250.  
  251.         toppositbox = gtk.HBox(False, 0)
  252.  
  253.         topleftpositbox = gtk.VBox(False, 4)
  254.  
  255.         positlabel1 = gtk.Label("Position Data")
  256.         positlabel1.set_alignment(0, 0)
  257.         positlabel1.modify_font(titlefont)
  258.         topleftpositbox.pack_start(positlabel1, False, False, 0)
  259.         positlabel1.show()
  260.  
  261.         latpositbox = gtk.HBox(False, 4)
  262.         latlabel1 = gtk.Label("Latitude:    ")
  263.         latlabel1.set_alignment(0, 0.5)
  264.         latpositbox.pack_start(latlabel1, False, False, 0)
  265.         latlabel1.show()
  266.  
  267.         self.latDDtext = gtk.Entry()
  268.         self.latDDtext.set_max_length(2)
  269.         self.latDDtext.set_width_chars(4)
  270.         self.latDDtext.set_text("DD")
  271.         self.latDDtext.connect("changed", self.disable_beacon)
  272.         latpositbox.pack_start(self.latDDtext, False, False, 0)
  273.         self.latDDtext.show()
  274.  
  275.         self.latMMtext = gtk.Entry()
  276.         self.latMMtext.set_max_length(2)
  277.         self.latMMtext.set_width_chars(3)
  278.         self.latMMtext.set_text("MM")
  279.         self.latMMtext.connect("changed", self.disable_beacon)
  280.         latpositbox.pack_start(self.latMMtext, False, False, 0)
  281.         self.latMMtext.show()
  282.  
  283.         latlabel2 = gtk.Label(".")
  284.         latlabel2.set_alignment(0, 1)
  285.         latpositbox.pack_start(latlabel2, False, False, 0)
  286.         latlabel2.show()
  287.  
  288.         self.latmmtext = gtk.Entry()
  289.         self.latmmtext.set_max_length(2)
  290.         self.latmmtext.set_width_chars(3)
  291.         self.latmmtext.set_text("mm")
  292.         self.latmmtext.connect("changed", self.disable_beacon)
  293.         latpositbox.pack_start(self.latmmtext, False, False, 0)
  294.         self.latmmtext.show()
  295.  
  296.         self.latcombo = gtk.combo_box_new_text()
  297.         self.latcombo.append_text("N")
  298.         self.latcombo.append_text("S")
  299.         self.latcombo.set_active(0)
  300.         latpositbox.pack_start(self.latcombo, False, False, 0)
  301.         self.latcombo.show()
  302.  
  303.         topleftpositbox.pack_start(latpositbox, False, False, 0)
  304.         latpositbox.show()
  305.  
  306.         lonpositbox = gtk.HBox(False, 4)
  307.         lonlabel1 = gtk.Label("Longitude: ")
  308.         lonlabel1.set_alignment(0, 0.5)
  309.         lonpositbox.pack_start(lonlabel1, False, False, 0)
  310.         lonlabel1.show()
  311.  
  312.         self.lonDDDtext = gtk.Entry()
  313.         self.lonDDDtext.set_max_length(3)
  314.         self.lonDDDtext.set_width_chars(4)
  315.         self.lonDDDtext.set_text("DDD")
  316.         self.lonDDDtext.connect("changed", self.disable_beacon)
  317.         lonpositbox.pack_start(self.lonDDDtext, False, False, 0)
  318.         self.lonDDDtext.show()
  319.  
  320.         self.lonMMtext = gtk.Entry()
  321.         self.lonMMtext.set_max_length(2)
  322.         self.lonMMtext.set_width_chars(3)
  323.         self.lonMMtext.set_text("MM")
  324.         self.lonMMtext.connect("changed", self.disable_beacon)
  325.         lonpositbox.pack_start(self.lonMMtext, False, False, 0)
  326.         self.lonMMtext.show()
  327.  
  328.         lonlabel2 = gtk.Label(".")
  329.         lonlabel2.set_alignment(0, 1)
  330.         lonpositbox.pack_start(lonlabel2, False, False, 0)
  331.         lonlabel2.show()
  332.  
  333.         self.lonmmtext = gtk.Entry()
  334.         self.lonmmtext.set_max_length(2)
  335.         self.lonmmtext.set_width_chars(3)
  336.         self.lonmmtext.set_text("mm")
  337.         self.lonmmtext.connect("changed", self.disable_beacon)
  338.         lonpositbox.pack_start(self.lonmmtext, False, False, 0)
  339.         self.lonmmtext.show()
  340.  
  341.         self.loncombo = gtk.combo_box_new_text()
  342.         self.loncombo.append_text("W")
  343.         self.loncombo.append_text("E")
  344.         self.loncombo.set_active(0)
  345.         lonpositbox.pack_start(self.loncombo, False, False, 0)
  346.         self.loncombo.show()
  347.  
  348.         topleftpositbox.pack_start(lonpositbox, False, False, 0)
  349.         lonpositbox.show()
  350.  
  351.         toppositbox.pack_start(topleftpositbox, False, False, 0)
  352.         topleftpositbox.show()
  353.  
  354.         toprightpositbox = gtk.VBox(False, 4)
  355.         toprightpositbox.set_border_width(10)
  356.  
  357.         loclabel = gtk.Label("-OR- Zip Code:")
  358.         loclabel.set_alignment(0.3, 0)
  359.         toprightpositbox.pack_start(loclabel, False, False, 0)
  360.         loclabel.show()
  361.  
  362.         self.ziptext = gtk.Entry()
  363.         self.ziptext.set_max_length(5)
  364.         self.ziptext.set_width_chars(5)
  365.         self.ziptext.connect("changed", self.disable_beacon)
  366.         toprightpositbox.pack_start(self.ziptext, False, False, 0)
  367.         self.ziptext.show()
  368.  
  369.         toppositbox.pack_start(toprightpositbox, False, False, 0)
  370.         toprightpositbox.show()
  371.  
  372.         positbox.pack_start(toppositbox, False, False, 0)
  373.         toppositbox.show()
  374.  
  375.         positlabel2 = gtk.Label("If you do not know your LAT/LONG then your zip code will\nbe used to place you on the map.")
  376.         positlabel2.set_alignment(0, 0)
  377. #        positlabel2.modify_font(mediumfont)
  378.         positbox.pack_start(positlabel2, False, False, 0)
  379.         positlabel2.show()
  380.  
  381.         leftwin.pack_start(positbox, False, False, 0)
  382.         positbox.show()
  383.  
  384.         separator = gtk.HSeparator()
  385.         leftwin.pack_start(separator, False, False, 0)
  386.         separator.show()
  387.  
  388.         # defined here so clear and connect buttons have access
  389.         self.statusview = gtk.TextView()
  390.         self.statusbuffer = self.statusview.get_buffer()
  391.         self.messageview = gtk.TextView()
  392.         self.messagebuffer = self.messageview.get_buffer()
  393.  
  394.         self.connectbutton = gtk.Button()
  395.         self.connectbutton.set_label("Connect")
  396.         self.connectbutton.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  397.         self.connectbutton.connect("clicked", self.connect_aprs)
  398.         leftwin.pack_start(self.connectbutton, False, False, 12)
  399.         self.connectbutton.show()
  400.  
  401.         win.pack_start(leftwin, False, False, 0)
  402.         leftwin.show()
  403.  
  404.         rightwin = gtk.VBox(False, 4)
  405.         rightwin.set_border_width(4)
  406.  
  407.         rightwintopbox = gtk.HBox(False, 4)
  408.  
  409.         clearbutton = gtk.Button()
  410.         clearbutton.set_label(" Cancel Messages ")
  411.         clearbutton.connect("clicked", self.clear_msg_queue)
  412.         rightwintopbox.pack_start(clearbutton, False, False, 18)
  413.         clearbutton.show()
  414.  
  415.         self.beaconbutton = gtk.CheckButton("Beacon every 10 minutes")
  416.         self.beaconbutton.set_active(True)
  417.         self.beaconbutton.connect("toggled", self.enable_beacon, "beacon")
  418.         rightwintopbox.pack_start(self.beaconbutton, False, False, 18)
  419.         self.beaconbutton.show()
  420.  
  421.         rightwin.pack_start(rightwintopbox, False, False, 0)
  422.         rightwintopbox.show()
  423.  
  424.         self.messageview.set_editable(False)
  425.         self.messageview.set_cursor_visible(True)
  426.         self.messageview.set_wrap_mode(gtk.WRAP_CHAR)
  427.         self.messageview.set_justification(gtk.JUSTIFY_LEFT)
  428.         self.messageview.modify_font(smallfont)
  429.  
  430.         self.messagebuffer.set_text("Welcome to APRS-XO.\n\nThis program sends your position information to a server that\nwill display your location on a webpage.  This program requires an active Internet connection to work.\n\nSelect an APRS Site\nSelecting a button will copy the URI to the clipboard.\n\nIndentifiers\nEnter your callsign and optionally an aprsd password.\n\nStation Comment\nData in the Station Comment field will appear after your\nlocation information on the website.")
  431.  
  432.         # tags for easier reading of messages
  433.         self.messagebold = self.messagebuffer.create_tag("bold", weight=pango.WEIGHT_BOLD)
  434.  
  435.         self.messagewindow = gtk.ScrolledWindow()
  436.         self.messagewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  437. #        self.messagewindow.add_with_viewport(self.messageview)
  438.         self.messagewindow.add(self.messageview)
  439.         self.messageview.show()
  440.         rightwin.pack_start(self.messagewindow, True, True, 0)
  441.         self.messagewindow.show()
  442.  
  443.         messagebox = gtk.HBox(False, 4)
  444.  
  445.         self.messagecombo = gtk.combo_box_entry_new_text()
  446.         self.messagecombo.append_text("ALL")
  447.         self.messagecombo.append_text("BEACON")
  448.         self.messagecombo.append_text("CQ")
  449.         self.messagecombo.append_text("QST")
  450.         self.messagecombo.set_active(-1)
  451.         self.messagedest = self.messagecombo.get_child()
  452.         self.messagedest.set_max_length(9)
  453.         self.messagedest.set_width_chars(5)
  454.         self.messagedest.modify_font(smallfont)
  455.         messagebox.pack_start(self.messagecombo, False, False, 0)
  456.         self.messagecombo.show()
  457.  
  458.         self.messagetext = gtk.Entry()
  459.         self.messagetext.set_max_length(67)
  460.         self.messagetext.set_width_chars(31)
  461.         self.messagetext.modify_font(smallfont)
  462.         self.messagetext.connect("activate", self.send_message, self.messagetext)
  463.         messagebox.pack_start(self.messagetext, False, False, 0)
  464.         self.messagetext.show()
  465.  
  466.         messagebutton = gtk.Button()
  467.         messagebutton.set_label("Send")
  468.         messagebutton.connect("clicked", self.send_message, "message")
  469.         messagebox.pack_start(messagebutton, False, False, 0)
  470.         messagebutton.show()
  471.  
  472.         rightwin.pack_start(messagebox, False, False, 0)
  473.         messagebox.show()
  474.  
  475.         self.statusview.set_editable(False)
  476.         self.statusview.set_cursor_visible(True)
  477.         self.statusview.set_wrap_mode(gtk.WRAP_CHAR)
  478.         self.statusview.set_justification(gtk.JUSTIFY_LEFT)
  479.         self.statusview.modify_font(smallfont)
  480.  
  481.         self.statusbuffer.set_text("Position\nEnter your lat/long.  You may leave the decimal minutes (mm)\nblank for position ambiguity.  If you do not know your lat/long,\nenter your 5 digit zip code instead.\n\nBeacon\nWhen selected, sends location data every 10 minutes.  The\nbeacon will automatically stop when you edit personal\ninformation.  Must be manually reselected after edit\ncompletion.\n\nThis message will self destruct when you press Connect.\n\nAPRS Copyright (c) Bob Bruninga WB4APR\n\n")
  482.  
  483.         self.statuswindow = gtk.ScrolledWindow()
  484.         self.statuswindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  485. #        self.statuswindow.add_with_viewport(self.statusview)
  486.         self.statuswindow.add(self.statusview)
  487.         self.statusview.show()
  488.         rightwin.pack_start(self.statuswindow, True, True, 0)
  489.         self.statuswindow.show()
  490.  
  491.         self.rawbox = gtk.HBox(False, 6)
  492.  
  493.         self.rawtext = gtk.Entry()
  494.         self.rawtext.set_max_length(128)
  495.         self.rawtext.set_width_chars(42)
  496.         self.rawtext.modify_font(smallfont)
  497.         self.rawtext.connect("activate", self.raw_send)
  498.         self.rawbox.pack_start(self.rawtext, False, False, 0)
  499.         self.rawtext.show()
  500.  
  501.         rawbutton = gtk.Button()
  502.         rawbutton.set_label("Send")
  503.         rawbutton.connect("clicked", self.raw_send)
  504.         self.rawbox.pack_start(rawbutton, False, False, 0)
  505.         rawbutton.show()
  506.  
  507.         rightwin.pack_start(self.rawbox, False, False, 0)
  508.         self.rawbox.show()
  509.  
  510.         win.pack_start(rightwin, False, False, 0)
  511.         rightwin.show()
  512.  
  513.         self.set_canvas(win)
  514.         win.show()
  515.         self.calltext.grab_focus()
  516.  
  517.         # Fix window not updating until activity after alt-tab
  518.         self.statusbuffer.create_mark("end", self.statusbuffer.get_end_iter(), False)
  519.         # Do the same for message window, without auto delete it did not have the problem
  520.         self.messagebuffer.create_mark("end", self.messagebuffer.get_end_iter(), False)
  521.  
  522.     def clear_status(self, button=None):
  523.         self.statusbuffer.set_text("")
  524.  
  525.     def clear_message(self, button=None):
  526.         self.messagebuffer.set_text("")
  527.  
  528.     def connect_aprs(self, button):
  529.         if (self.sock == None):
  530.  
  531.             if (self.help):
  532.                 self.clear_status()
  533.                 self.messagebuffer.set_text("Message Window")
  534.                 self.messagebox = True
  535.                 self.help = False
  536.  
  537.             if (self.validate_data() == False):
  538.                 return False
  539.             self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  540.             self.status_write("Connecting ")
  541.             try:
  542.                 iplist = socket.gethostbyname_ex(HOST)[2]
  543.             except socket.error, msg:
  544.                 self.status_write("\n%s\n" % msg[1])
  545.                 self.sock = None
  546.                 return False
  547.             server = random.choice(iplist)
  548.             self.status_write("to %s\n" % server)
  549.             try:
  550.                 self.sock.connect((server, PORT))
  551.             except socket.error, msg:
  552.                 self.status_write("%s\n" % msg[1])
  553.                 self.sock = None
  554.                 return False
  555.  
  556.             self.status_write("Connected\n")
  557.             button.set_label("Disconnect")
  558.             button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#e6000a"))
  559.        
  560.             response = self.sock.recv(RECV_BUFFER)
  561.             self.status_write("%s" % response)
  562.             if (response.find("javAPRSSrvr") == -1):
  563.                 self.status_write("invalid response.\n")
  564.                 self.disconnect_aprs(button)
  565.                 return False
  566.  
  567.             if (response.find("Port Full") != -1):
  568.                 self.status_write("Port Full.\n")
  569.                 self.disconnect_aprs(button)
  570.                 return False
  571.  
  572.             if (self.calltext.get_text() != "" and self.passtext.get_text() != ""):
  573.                 sendme = "user %s pass %s vers aprs_xo %d filter %s\n" % (self.calltext.get_text(), self.passtext.get_text(), VERSION, FILTER)
  574.             else:
  575.                 sendme = "user %s vers aprs_xo %d\n" % (self.calltext.get_text(), VERSION)
  576.             self.sock.sendall(sendme)
  577.             self.status_write("%s" % sendme)
  578.  
  579.             response = self.sock.recv(RECV_BUFFER)
  580.             self.status_write("%s" % response)
  581.             if (response.find("# logresp") == -1):
  582.                 self.status_write("invalid response.\n")
  583.                 self.disconnect_aprs(button)
  584.                 return False
  585.  
  586.             if (response.find("unverified") != -1):
  587.                 sendme = "# filter %s\n" % FILTER
  588.                 self.sock.sendall(sendme)
  589.                 self.status_write("%s\n" % sendme)
  590.  
  591.             self.status_write("\n")
  592.  
  593.             self.input_watch.append(gobject.io_add_watch(self.sock, gobject.IO_IN, self.recv_data))
  594.  
  595.             # send banner
  596.             sendme = "%s>APRS-XO v%s\n" % (self.calltext.get_text(), VERSION)
  597.             self.sock.sendall(sendme)
  598.             self.status_write("%s>\n" % self.calltext.get_text())
  599.             self.status_write("APRS-XO v%s\n\n" % VERSION)
  600.  
  601.             self.send_beacon()
  602.             self.output_watch.append(gobject.timeout_add(10 * 60 * 1000, self.send_beacon))
  603.         else:
  604.             self.disconnect_aprs(button)
  605.  
  606.     def disconnect(self):
  607.         if (self.sock != None):
  608.             # for test server only
  609.             if(HOST == "192.168.50.6"):
  610.                 self.sock.sendall("q")
  611.  
  612.             self.sock.close()
  613.             self.sock = None
  614.  
  615.     def disconnect_aprs(self, button):
  616.         self.disconnect()
  617.         self.clear_msg_queue()
  618.  
  619.         self.status_write("Disconnected\n")
  620.  
  621.         # stop input watcher - just in case
  622.         for source in self.input_watch:
  623.             try:
  624.                 gobject.source_remove(source)
  625.             except:
  626.                 pass
  627.  
  628.         # stop beacon
  629.         for source in self.output_watch:
  630.             try:
  631.                 gobject.source_remove(source)
  632.             except:
  633.                 pass
  634.  
  635.         self.input_watch = []
  636.         self.output_watch = []
  637.  
  638.         button.set_label("Connect")
  639.         button.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#00b20d"))
  640.  
  641.     def recv_data(self, sock, condition):
  642.         if (self.sock == None):
  643.             return False
  644.         while 1:
  645.             try:
  646.                 recv_data = sock.recv(RECV_BUFFER)
  647.             except:
  648.                 self.status_write("Server closed connection.\n")
  649.                 self.sock = None
  650.                 self.disconnect_aprs(self.connectbutton)
  651.                 return False
  652.             if not recv_data:
  653.                 self.status_write("Server closed connection.\n")
  654.                 self.sock = None
  655.                 self.disconnect_aprs(self.connectbutton)
  656.                 return False
  657.             else:
  658.                 # sometimes more than one packet comes in
  659.                 for packet in recv_data.split("\n"):
  660.                     if (len(packet) < 2):
  661.                         break
  662.                     # find uri's http or www
  663.                     webaddy = packet.lower().find("http")
  664.                     if (webaddy != -1):
  665.                         packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  666.                     else:
  667.                         webaddy = packet.lower().find("www")
  668.                         if (webaddy != -1):
  669.                             packet = ("%s\n%s" % (packet[:webaddy], packet[webaddy:]))
  670.                     if (packet[0] == "#"):
  671.                         self.status_write("%s\n\n" % packet)
  672.                     else:
  673.                         cuthere = packet.find(":")
  674.                         self.status_write("%s\n" % packet[:cuthere+1])
  675.                         self.status_write("%s\n\n" % packet[cuthere+1:])
  676.                         self.msg_check(packet)
  677.                 return True
  678.  
  679.     def send_beacon(self):
  680.         if (self.sock == None):
  681.             return False
  682.         if (self.beaconbutton.get_active()):
  683.             beacon = "=%s%s.%s%sX%s%s.%s%sA%s" % (self.latDDtext.get_text(), self.latMMtext.get_text(), self.latmmtext.get_text(), self.latcombo.get_active_text(), self.lonDDDtext.get_text(), self.lonMMtext.get_text(), self.lonmmtext.get_text(), self.loncombo.get_active_text(), self.stationtext.get_text())
  684.             if (self.send_data(beacon)):
  685.                 return True
  686.             else:
  687.                 self.status_write("\nProblem sending beacon - STOPPED")
  688.                 return False
  689.  
  690.     def send_data(self, msg):
  691.         if (self.sock == None):
  692.             return False
  693.         path = "%s>APOLPC:" % self.calltext.get_text()
  694.         try:
  695.             self.sock.sendall("%s%s\n" % (path, msg))
  696.         except:
  697.             self.status_write("Problem sending packet\n")
  698.             self.sock = None
  699.             self.disconnect_aprs(self.connectbutton)
  700.             return False
  701.         self.status_write("%s\n" % path)
  702.         self.status_write("%s\n\n" % msg)
  703.         return True
  704.  
  705.     def send_ack(self, tocall, sequence):
  706.         # should check for duplicate messages in the next 30 seconds and drop them
  707.         # aprsd does this for us - but should do it here as well
  708.         if (tocall in self.sent_acks):
  709.             currentack = self.sent_acks[tocall]
  710.             if (currentack == sequence):
  711.                 ackmessage = ":%s:ack%s" % (tocall.ljust(9), sequence)
  712.                 self.send_data(ackmessage)
  713.         return False
  714.  
  715.     def status_write(self, text):
  716.         self.statusbuffer.insert(self.statusbuffer.get_end_iter(), text)
  717.         statuslines = self.statusbuffer.get_line_count()
  718.         if (statuslines > MAXLINES):
  719.             deletehere = self.statusbuffer.get_iter_at_line(statuslines - MAXLINES)
  720.             self.statusbuffer.delete(self.statusbuffer.get_start_iter(), deletehere)
  721.         if (not self.statusview.is_focus()):
  722.             self.statusbuffer.move_mark_by_name("end", self.statusbuffer.get_end_iter())
  723.             self.statusview.scroll_mark_onscreen(self.statusbuffer.get_mark("end"))
  724.  
  725.     def message_write(self, text, bold=None):
  726.         if (self.messagebox):
  727.             self.clear_message()
  728.             self.messagebox = False
  729.         iter = self.messagebuffer.get_end_iter()
  730.         self.messagebuffer.insert(iter, text)
  731.         if (bold):
  732.             bold_end = iter.copy()
  733.             bold_start = iter.copy()
  734.             bold_end.forward_to_line_end()
  735.             bold_start = bold_end.backward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  736.             bold_start[0].forward_chars(2)
  737.             self.messagebuffer.apply_tag(self.messagebold, bold_start[0], bold_end)
  738.  
  739.         if (not self.messageview.is_focus()):
  740.             self.messagebuffer.move_mark_by_name("end", self.messagebuffer.get_end_iter())
  741.             self.messageview.scroll_mark_onscreen(self.messagebuffer.get_mark("end"))
  742.  
  743.     def set_site(self, widget, data=None):
  744.         self.site = data
  745.         self.clipboard()
  746.  
  747.     def validate_data(self):
  748.         stop_here = False
  749.         self.validating = True
  750.         beaconchecked = self.beaconbutton.get_active()
  751.  
  752.         if (self.calltext.get_text() == "" and self.ziptext.get_text() == ""):
  753.             self.status_write("A callsign or zip code must be provided.\n")
  754.             return False
  755.  
  756.         # convert zip to position
  757.         if (self.ziptext.get_text() != ""):
  758.             self.status_write("Attempting to set location from zip code\n")
  759.             self.ziptext.set_text(self.ziptext.get_text().zfill(5))
  760.             try:
  761.                 zipsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  762.             except socket.error, msg:
  763.                 self.status_write("[ERROR] %s\n" % msg[1])
  764.                 self.validating = False
  765.                 return False
  766.             try:
  767.                 zipsock.connect(('geocoder.us', 80))
  768.             except socket.error, msg:
  769.                 self.status_write("[ERROR] %s\n" % msg[1])
  770.                 self.validating = False
  771.                 return False
  772.             try:
  773.                 zipsock.sendall("GET /service/csv/geocode?zip=%s\n" % self.ziptext.get_text())
  774.             except socket.error, msg:
  775.                 self.status_write("[ERROR] %s\n" % msg[1])
  776.                 self.validating = False
  777.                 return False
  778.             zipsock.shutdown(1)
  779.             try:
  780.                 response = zipsock.recv(RECV_BUFFER)
  781.             except socket.error, msg:
  782.                 self.status_write("[ERROR] %s\n" % msg[1])
  783.                 self.validating = False
  784.                 return False
  785.             A = random.randrange(0, 9)
  786.             O = random.randrange(0, 9)
  787.             if (self.calltext.get_text() == ""):
  788.                 self.calltext.set_text("X%s-%d%d" % (self.ziptext.get_text(), A, O))
  789.             try:
  790.                 (lat, lon, junk) = response.split(',', 2)
  791.             except:
  792.                 self.status_write("%s\n" % response)
  793.                 lat = "0"
  794.                 lon = "0"
  795.             lat = float(lat.strip())
  796.             lon = float(lon.strip())
  797.             if (lat < 0):
  798.                 self.latcombo.set_active(1)     # S
  799.             else:
  800.                 self.latcombo.set_active(0)     # N
  801.             lat = abs(lat)
  802.             if (lon < 0):
  803.                 self.loncombo.set_active(0)     # W
  804.             else:
  805.                 self.loncombo.set_active(1)     # E
  806.             lon = abs(lon)
  807.             self.latDDtext.set_text("%02d" % int(lat))
  808.             self.latMMtext.set_text("%02d" % int((lat - int(lat)) * 60 + 0.5))
  809.             self.latmmtext.set_text("%d " % A)
  810.             self.lonDDDtext.set_text("%03d" % int(lon))
  811.             self.lonMMtext.set_text("%02d" % int((lon - int(lon)) * 60 + 0.5))
  812.             self.lonmmtext.set_text("%d " % O)
  813.             self.ziptext.set_text("")
  814.         # end set loc by zip code
  815.  
  816.         if (self.latDDtext.get_text() == "DD" or self.latDDtext.get_text() == ""):
  817.             self.status_write("Latitude Degrees are required.\n")
  818.             stop_here = True
  819.         if (self.latMMtext.get_text() == "MM" or self.latMMtext.get_text() == ""):
  820.             self.status_write("Latitude Minutes are required.\n")
  821.             stop_here = True
  822.         if (self.lonDDDtext.get_text() == "DDD" or self.lonDDDtext.get_text() == ""):
  823.             self.status_write("Longitude Degrees are required.\n")
  824.             stop_here = True
  825.         if (self.lonMMtext.get_text() == "MM" or self.lonMMtext.get_text() == ""):
  826.             self.status_write("Longitude Minutes are required.\n")
  827.             stop_here = True
  828.  
  829.         if (stop_here):
  830.             self.status_write("Latitude and Longitude must be complete.\nFor position ambiguity omit decimal Minutes (mm).\nIf you do not know your lat/long, leave the letters\n(DD, MM, etc) and provide your zip code.\n")
  831.             self.validating = False
  832.             return False
  833.  
  834.         if (not self.latDDtext.get_text().isdigit()):
  835.             self.status_write("Latitude Degrees must be a number.\n")
  836.             stop_here = True
  837.         if (not self.latMMtext.get_text().isdigit()):
  838.             self.status_write("Latitude Minutes must be a number.\n")
  839.             stop_here = True
  840.         if (not self.lonDDDtext.get_text().isdigit()):
  841.             self.status_write("Longitude Degrees must be a number.\n")
  842.             stop_here = True
  843.         if (not self.lonMMtext.get_text().isdigit()):
  844.             self.status_write("Longitude Minutes must be a number.\n")
  845.             stop_here = True
  846.  
  847.         if (stop_here):
  848.             self.status_write("Invalid Position.\n")
  849.             self.validating = False
  850.             return False
  851.  
  852.         if (int(self.latDDtext.get_text()) < 0  or int(self.latDDtext.get_text()) > 90):
  853.             self.status_write("Latitude Degrees must be between 0 and 90.\n")
  854.             stop_here = True
  855.         if (int(self.latMMtext.get_text()) < 0  or int(self.latMMtext.get_text()) > 60):
  856.             self.status_write("Latitude Minutes must be between 0 and 60.\n")
  857.             stop_here = True
  858.         if (int(self.lonDDDtext.get_text()) < 0  or int(self.lonDDDtext.get_text()) > 180):
  859.             self.status_write("Longitude Degrees must be between 0 and 180.\n")
  860.             stop_here = True
  861.         if (int(self.lonMMtext.get_text()) < 0  or int(self.lonMMtext.get_text()) > 60):
  862.             self.status_write("Longitude Minutes must be between 0 and 60.\n")
  863.             stop_here = True
  864.  
  865.         if (stop_here):
  866.             self.status_write("Invalid Position.\n")
  867.             self.validating = False
  868.             return False
  869.  
  870.         # clean up entries
  871.         self.latDDtext.set_text(self.latDDtext.get_text().zfill(2))
  872.         self.latMMtext.set_text(self.latMMtext.get_text().zfill(2))
  873.         self.lonDDDtext.set_text(self.lonDDDtext.get_text().zfill(3))
  874.         self.lonMMtext.set_text(self.lonMMtext.get_text().zfill(2))
  875.         self.latmmtext.set_text(self.latmmtext.get_text().ljust(2))
  876.         self.lonmmtext.set_text(self.lonmmtext.get_text().ljust(2))
  877.         if (self.latmmtext.get_text() == "mm"):
  878.             self.latmmtext.set_text("  ")
  879.         if (self.lonmmtext.get_text() == "mm"):
  880.             self.lonmmtext.set_text("  ")
  881.         self.calltext.set_text(self.calltext.get_text().upper())
  882.         self.beaconbutton.set_active(beaconchecked)
  883.         self.validating = False
  884.  
  885.     def can_close( self ):
  886.         self.hide()
  887.         if (self.sock != None):
  888.             self.disconnect()
  889.         return True
  890.  
  891.     def write_file(self, file_path):
  892.         try:
  893.             self.metadata['mime_type'] = 'text/plain'
  894.  
  895.             self.metadata['callsign'] = self.calltext.get_text()
  896.             self.metadata['password'] = self.passtext.get_text()
  897.             self.metadata['latDD'] = self.latDDtext.get_text()
  898.             self.metadata['latMM'] = self.latMMtext.get_text()
  899.             self.metadata['latmm'] = self.latmmtext.get_text()
  900.             self.metadata['lat'] = self.latcombo.get_active_text()
  901.             self.metadata['lonDDD'] = self.lonDDDtext.get_text()
  902.             self.metadata['lonMM'] = self.lonMMtext.get_text()
  903.             self.metadata['lonmm'] = self.lonmmtext.get_text()
  904.             self.metadata['lon'] = self.loncombo.get_active_text()
  905.             self.metadata['stationtext'] = self.stationtext.get_text()
  906.             if (self.beaconbutton.get_active()):
  907.                 self.metadata['beacon'] = 'True'
  908.             else:
  909.                 self.metadata['beacon'] = 'False'
  910.  
  911.             callsignlist = []
  912.             model = self.messagecombo.get_model()
  913.             iter = model.get_iter_first()
  914.             while iter:
  915.                 callsignlist.append(model.get(iter, 0)[0])
  916.                 iter = model.iter_next(iter)
  917.  
  918.             JournalData = {}
  919.             JournalData['callsignlist'] = callsignlist
  920.             if (self.help):
  921.                 JournalData['messages'] = "Message Window"
  922.             else:
  923.                 JournalData['messages'] = self.messagebuffer.get_text(self.messagebuffer.get_start_iter(), self.messagebuffer.get_end_iter())
  924.             data = json.write(JournalData)
  925.  
  926.             f = open(file_path, 'w')
  927.             try:
  928.                 f.write(data)
  929.             finally:
  930.                 f.close()
  931.  
  932.         except Exception, e:
  933.             self.status_write("write_file(): %s\n" % e)
  934.  
  935.     def read_file(self, file_path):
  936.         self.statusbuffer.set_text("Status Window\n\n")
  937.         self.calltext.set_text(self.metadata.get('callsign', ""))
  938.         self.passtext.set_text(self.metadata.get('password', ""))
  939.         self.latDDtext.set_text(self.metadata.get('latDD', "DD"))
  940.         self.latMMtext.set_text(self.metadata.get('latMM', "MM"))
  941.         self.latmmtext.set_text(self.metadata.get('latmm', "mm"))
  942.         if (self.metadata.get('lat', "N") == "N"):
  943.             self.latcombo.set_active(0)
  944.         else:
  945.             self.latcombo.set_active(1)
  946.         self.lonDDDtext.set_text(self.metadata.get('lonDDD', "DDD"))
  947.         self.lonMMtext.set_text(self.metadata.get('lonMM', "MM"))
  948.         self.lonmmtext.set_text(self.metadata.get('lonmm', "mm"))
  949.         if (self.metadata.get('lon', "W") == "W"):
  950.             self.loncombo.set_active(0)
  951.         else:
  952.             self.loncombo.set_active(1)
  953.         stationtext = self.metadata.get('stationtext', "")
  954.         if (stationtext == ""):
  955.             firstName = profile.get_nick_name().split(None, 1)[0].capitalize()
  956.             self.stationtext.set_text("%s's XO at home." % firstName)
  957.         else:
  958.             self.stationtext.set_text(stationtext)
  959.         if (self.metadata.get('beacon', "True") == "True"):
  960.             self.beaconbutton.set_active(True)
  961.         else:
  962.             self.beaconbutton.set_active(False)
  963.  
  964.         JournalData = {}
  965.         callsignlist = ["ALL", "BEACON", "CQ", "QST"]
  966.         messages = "Message Window"
  967.         try:
  968.             f = open(file_path, 'r')
  969.             JournalData = json.read(f.read())
  970.             if JournalData.has_key('callsignlist'):
  971.                 callsignlist = JournalData['callsignlist']
  972.             if JournalData.has_key('messages'):
  973.                 messages = JournalData['messages']
  974.         except:
  975.             pass
  976.         finally:
  977.             f.close()
  978.         self.messagebuffer.set_text(messages)
  979.         self.help = False
  980.         if (messages == "Message Window"):
  981.             self.messagebox = True
  982.         else:
  983.             self.messagebox = False
  984.         for currentcall in callsignlist:
  985.             self.add_callsign(currentcall, False)
  986.  
  987.     def msg_check(self, data):
  988.         firstcheck = data.find("::")
  989.         secondcheck = data[firstcheck+11:firstcheck+12]
  990.         thirdcheck = data.find("{")
  991.         if (firstcheck != -1 and secondcheck == ":"):
  992.             tocall = data[firstcheck+2:firstcheck+11].upper()
  993.             strippedtocall = tocall.strip()
  994.             fromcall = data[:data.find(">")].upper()
  995.             isbulletin = self.bulletin_check(strippedtocall)
  996.             if (isbulletin):
  997.                 message = data[firstcheck+12:]
  998.                 self.add_callsign(fromcall, False)
  999.                 bln_id = "%s-%s" % (fromcall, strippedtocall)
  1000.                 if (not bln_id in self.seen_bulletins):
  1001.                     self.seen_bulletins[bln_id] = 1
  1002.                     self.message_write("%s %s:%s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, strippedtocall, message), True)
  1003.             else:
  1004.                 if (strippedtocall == self.calltext.get_text()):
  1005.                     self.add_callsign(fromcall, False)
  1006.                     if (thirdcheck != -1):
  1007.                         sequence_end = data.find("}")
  1008.                         if (sequence_end == -1):
  1009.                             sequence = data[thirdcheck + 1:]
  1010.                         else:
  1011.                             sequence = data[thirdcheck + 1:sequence_end]
  1012.                             replyack = data[sequence_end + 1:]
  1013.                             if (len(replyack) > 1):
  1014.                                 ackid = "%s-%s" % (fromcall, replyack)
  1015.                                 if (ackid in self.recv_acks):
  1016.                                     if (self.recv_acks[ackid] == 0):
  1017.  
  1018.                                         count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1019.                                         count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  1020.                                         self.messagebuffer.delete(count_start, count_end[0])
  1021.  
  1022.                                         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1023.                                         queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1024.                                         line_end.forward_to_line_end()
  1025.                                         queue_start.forward_to_line_end()
  1026.                                         queue_start.backward_chars(12)
  1027.                                         self.messagebuffer.delete(queue_start, line_end)
  1028.  
  1029.                                         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1030.                                         line_end.forward_to_line_end()
  1031.                                         self.messagebuffer.insert(line_end, " <*ACKED*>")
  1032.  
  1033.                                         self.send_msg_queue(fromcall)
  1034.                                     self.recv_acks[ackid] = 1
  1035.                         message = data[firstcheck+12:thirdcheck]
  1036.                         ackmessage = ":%s:ack%s" % (fromcall.ljust(9), data[thirdcheck + 1:])
  1037.                         self.send_data(ackmessage)
  1038.                         id = "%s-%s" % (fromcall, sequence)
  1039.                         if (id in self.sent_acks):
  1040.                             self.timers.append(gobject.timeout_add(30 * 1000, self.send_ack, tocall, sequence))
  1041.                             self.timers.append(gobject.timeout_add(60 * 1000, self.send_ack, tocall, sequence))
  1042.                             self.timers.append(gobject.timeout_add(120 * 1000, self.send_ack, tocall, sequence))
  1043.                         else:
  1044.                             # TODO beep?
  1045.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message), True)
  1046.                         self.sent_acks[id] = time.time() # to help a cleanup thread later
  1047.                         self.sent_acks[fromcall] = sequence # to help with reply acks later
  1048.                     else:
  1049.                         message = data[firstcheck+12:]
  1050.                         if (message[0:3] == "ack"):
  1051.                             sequence_end = message.find("}")
  1052.                             if (sequence_end == -1):
  1053.                                 sequence = message[3:]
  1054.                             else:
  1055.                                 sequence = message[3:sequence_end]
  1056.                             ackid = "%s-%s" % (fromcall, sequence)
  1057.                             if (ackid in self.recv_acks):
  1058.                                 if (self.recv_acks[ackid] == 0):
  1059.  
  1060.                                     count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1061.                                     count_end = count_start.forward_search(">", gtk.TEXT_SEARCH_TEXT_ONLY)
  1062.                                     self.messagebuffer.delete(count_start, count_end[0])
  1063.  
  1064.                                     line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1065.                                     queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1066.                                     line_end.forward_to_line_end()
  1067.                                     queue_start.forward_to_line_end()
  1068.                                     queue_start.backward_chars(12)
  1069.                                     self.messagebuffer.delete(queue_start, line_end)
  1070.  
  1071.                                     line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[ackid])
  1072.                                     line_end.forward_to_line_end()
  1073.                                     self.messagebuffer.insert(line_end, " <*ACKED*>")
  1074.  
  1075.                                     self.send_msg_queue(fromcall)
  1076.                             self.recv_acks[ackid] = 1
  1077.                         else:
  1078.                             # TODO beep?
  1079.                             self.message_write("%s %s> %s\n" % (time.strftime("%m/%d %H:%M", time.localtime()), fromcall, message), True)
  1080.  
  1081.     def disable_beacon(self, widget, data=None):
  1082.         if (self.sock != None):
  1083.             self.beaconbutton.set_active(False)
  1084.  
  1085.     def enable_beacon(self, widget, data=None):
  1086.         if (not self.validating and widget.get_active()):
  1087.             self.validate_data()
  1088.  
  1089.     def raw_send(self, widget, data=None):
  1090.         msg = "%s\n" % self.rawtext.get_text()
  1091.         try:
  1092.             self.sock.sendall(msg)
  1093.         except:
  1094.             self.status_write("Problem sending message\n")
  1095.             self.sock = None
  1096.             self.disconnect_aprs(self.connectbutton)
  1097.             return False
  1098.         self.status_write("%s\n" % msg)
  1099.         self.rawtext.set_text("")
  1100.         return True
  1101.  
  1102.     def send_message(self, widget, data=None):
  1103.         sendnow = False
  1104.  
  1105.         tocall = self.messagedest.get_text().upper()
  1106.         message = self.messagetext.get_text()
  1107.  
  1108.         if (message == ""):
  1109.             return False
  1110.  
  1111.         isbulletin = self.bulletin_check(tocall)
  1112.  
  1113.         # check for illegal characters before clearing message
  1114.         if (isbulletin):
  1115.             if (message.find("|") != -1 or message.find("~") != -1):
  1116.                 self.message_write("Bulletins can not contain the following characters: | ~")
  1117.                 return False
  1118.         else:
  1119.             if (message.find("|") != -1 or message.find("~") != -1 or message.find("{") != -1):
  1120.                 self.message_write("Messages can not contain the following characters: | ~ {")
  1121.                 return False
  1122.  
  1123.         # add callsign to list if just entered
  1124.         if (self.messagecombo.get_active() == -1):
  1125.             self.add_callsign(tocall, True)
  1126.  
  1127.         # get a sequence number
  1128.         if (isbulletin):
  1129.             # don't waste a number on a bulletin
  1130.             sequence = ""
  1131.         else:
  1132.             sequence = "%s" % self.b90()
  1133.  
  1134.         # TODO
  1135.         # cancel message option - menu popup
  1136.  
  1137.         # is there a queue for this callsign?
  1138.         if (tocall not in self.queue_list):
  1139.             sendnow = True
  1140.  
  1141.         # add the message to the queue
  1142.         if (not self.msg_queue(tocall, sequence, message)):
  1143.             if (self.sequence > 0):
  1144.                 self.sequence -= 1
  1145.             return False
  1146.  
  1147.         # clear the message text box
  1148.         self.messagetext.set_text("")
  1149.  
  1150.         if (sendnow):
  1151.             self.send_msg_queue(tocall)
  1152.  
  1153.     def b90(self):
  1154.         if (self.sequence > 8099):
  1155.             self.sequence = 0
  1156.         first = ((self.sequence / 90) % 90) + 33
  1157.         second = (self.sequence % 90) + 33
  1158.         output = "%c%c" % (first, second)
  1159.         self.sequence += 1
  1160.         return output
  1161.  
  1162.     def msg_timer(self, tocall, message, sequence, count, delay):
  1163.         id = "%s-%s" % (tocall, sequence)
  1164.         if (self.recv_acks[id] == 1):
  1165.             return False
  1166.         else:
  1167.             replyack = self.replyack(tocall)
  1168.             self.send_data(":%s:%s{%s}%s" % (tocall.ljust(9), message, sequence, replyack))
  1169.  
  1170.             count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1171.             count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1172.             count_end.forward_chars(len(str(count - 1)) + 1)
  1173.             self.messagebuffer.delete(count_start, count_end)
  1174.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1175.             self.messagebuffer.insert(iter, " %i" % count)
  1176.  
  1177.             # start the next timer
  1178.             count += 1
  1179.             if (count > MAXRETRIES):
  1180.                 self.recv_acks[id] = 1
  1181.  
  1182.                 count_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1183.                 count_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1184.                 count_end.forward_chars(len(str(count - 1)) + len(str(MAXRETRIES)) + 2)
  1185.                 self.messagebuffer.delete(count_start, count_end)
  1186.  
  1187.                 line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1188.                 queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1189.                 line_end.forward_to_line_end()
  1190.                 queue_start.forward_to_line_end()
  1191.                 queue_start.backward_chars(12)
  1192.                 self.messagebuffer.delete(queue_start, line_end)
  1193.  
  1194.                 line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1195.                 line_end.forward_to_line_end()
  1196.                 self.messagebuffer.insert(line_end, " <-timeout->")
  1197.  
  1198.                 self.send_msg_queue(tocall)
  1199.                 return False
  1200.             delay *=  2
  1201.             if (delay > 600):
  1202.                 delay = 600
  1203.             gobject.timeout_add(delay * 1000, self.msg_timer, tocall, message, sequence, count, delay)
  1204.  
  1205.             # and stop this timer
  1206.             return False
  1207.  
  1208.     def clipboard(self):
  1209.         clipboard = gtk.clipboard_get()
  1210.         target = [("text/uri-list", 0, 0)]
  1211.         clipboard.set_with_data(target, self.clipboard_get, self.clipboard_clear, (self.site))
  1212.  
  1213.     def clipboard_get(self, clipboard, selection, info, data):
  1214.         selection.set_uris([data])
  1215.  
  1216.     def clipboard_clear(self, clipboard, data):
  1217.         pass
  1218.  
  1219.     def add_callsign(self, callsign, activate):
  1220.         model = self.messagecombo.get_model()
  1221.         notfound = True
  1222.         iter = model.get_iter_first()
  1223.         while iter:
  1224.             currentcall = model.get(iter, 0)[0]
  1225.             if (currentcall == callsign):
  1226.                 notfound = False
  1227.                 break
  1228.             iter = model.iter_next(iter)
  1229.         if (notfound):
  1230.             self.messagecombo.prepend_text(callsign)
  1231.             if (activate):
  1232.                 self.messagecombo.set_active(0)
  1233.  
  1234.     def bulletin_check(self, callsign):
  1235.         for currentcall in self.bulletins:
  1236.             length = len(currentcall)
  1237.             if (currentcall == callsign[:length]):
  1238.                 return True
  1239.         return False
  1240.  
  1241.     def replyack(self, tocall):
  1242.         replyack = ""
  1243.         if (tocall in self.sent_acks):
  1244.             id = "%s-%s" % (tocall, self.sent_acks[tocall])
  1245.             if (time.time() - self.sent_acks[id] < 5400):
  1246.                 # less than 90 minutes ago
  1247.                 replyack = self.sent_acks[tocall]
  1248.         return replyack
  1249.  
  1250.     def msg_queue(self, call, sequence, message):
  1251.         if (len(self.message_list) >= MAX_MSG_QUEUE):
  1252.             self.message_write("Too many messages in queue.\n")
  1253.             return False
  1254.         id = "%s-%s" % (call, sequence)
  1255.         if (call in self.queue_list):
  1256.             if (len(self.queue_list[call]) >= MAX_PER_CALL_QUEUE):
  1257.                 self.message_write("Too many messages to %s in queue.\n" % call)
  1258.                 return False
  1259.             self.queue_list[call].append(sequence)
  1260.             self.message_list[id] = message
  1261.         else:
  1262.             self.queue_list[call] = []
  1263.             self.queue_list[call].append(sequence)
  1264.             self.message_list[id] = message
  1265.         self.message_write("%s To:%s> %s <-queued->\n" % (time.strftime("%m/%d %H:%M", time.localtime()), call, message))
  1266.         # if a message is received before the next lines are run there will be problems...
  1267.         iter = self.messagebuffer.get_iter_at_line(self.messagebuffer.get_line_count() - 2)
  1268.         iter.forward_chars(15 + len(call))
  1269.         self.message_marks[id] = self.messagebuffer.create_mark(None, iter, True)
  1270. #        self.message_marks[id].set_visible(True)
  1271.  
  1272.         bold_end = iter.copy()
  1273.         bold_start = iter.copy()
  1274.         bold_end.forward_to_line_end()
  1275.         bold_end = bold_end.backward_search("<", gtk.TEXT_SEARCH_TEXT_ONLY)
  1276.         bold_start.forward_chars(2)
  1277.         self.messagebuffer.apply_tag(self.messagebold, bold_start, bold_end[0])
  1278.  
  1279.         return True
  1280.  
  1281.     def send_msg_queue(self, call):
  1282.         if (call in self.queue_list):
  1283.             if (self.queue_list[call] == []):
  1284.                 del self.queue_list[call]
  1285.                 return False
  1286.  
  1287.             sequence = self.queue_list[call].pop(0)
  1288.             id = "%s-%s" % (call, sequence)
  1289.             message = self.message_list[id]
  1290.             del self.message_list[id]
  1291.  
  1292.             # record this so cancel can work on current messages
  1293.             self.current_message[call] = sequence
  1294.  
  1295.             # send message
  1296.             isbulletin = self.bulletin_check(call)
  1297.             if (isbulletin):
  1298.                 self.send_data(":%s:%s" % (call.ljust(9), message))
  1299.                 gobject.timeout_add(600 * 1000, self.msg_timer, call, message, "", 2, 600)
  1300.             else:
  1301.                 replyack = self.replyack(call)
  1302.                 self.send_data(":%s:%s{%s}%s" % (call.ljust(9), message, sequence, replyack))
  1303.                 self.recv_acks["%s-%s" % (call, sequence)] = 0
  1304.                 gobject.timeout_add(7 * 1000, self.msg_timer, call, message, sequence, 2, 7)
  1305.  
  1306.             # reset the timestamp
  1307.             line_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1308.             date_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1309.             length = line_start.get_line_offset()
  1310.             line_start.backward_chars(length)
  1311.             date_end.backward_chars(length)
  1312.             date_end.forward_chars(11)
  1313.             self.messagebuffer.delete(line_start, date_end)
  1314.  
  1315.             line_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1316.             length = line_start.get_line_offset()
  1317.             line_start.backward_chars(length)
  1318.             self.messagebuffer.insert(line_start, time.strftime("%m/%d %H:%M", time.localtime()))
  1319.  
  1320.             # clear "<-queued->"
  1321.             line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1322.             queue_start = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1323.             line_end.forward_to_line_end()
  1324.             queue_start.forward_to_line_end()
  1325.             queue_start.backward_chars(11)
  1326.             self.messagebuffer.delete(queue_start, line_end)
  1327.  
  1328.             # add <-sending->
  1329.             line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1330.             line_end.forward_to_line_end()
  1331.             self.messagebuffer.insert(line_end, " <-sending->")
  1332.  
  1333.             # add the counter
  1334.             iter = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1335.             self.messagebuffer.insert(iter, " 1/%s" % MAXRETRIES)
  1336.  
  1337.     def cancel_message(self, id):
  1338.         if (self.recv_acks[id] == 1):
  1339.             return
  1340.  
  1341.         self.recv_acks[id] = 1
  1342.  
  1343.         # remove status
  1344.         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1345.         line_end.forward_to_line_end()
  1346.         status_start = line_end.backward_search("<", gtk.TEXT_SEARCH_TEXT_ONLY)
  1347.         self.messagebuffer.delete(status_start[0], line_end)
  1348.  
  1349.         # add <-cancelled->
  1350.         line_end = self.messagebuffer.get_iter_at_mark(self.message_marks[id])
  1351.         line_end.forward_to_line_end()
  1352.         self.messagebuffer.insert(line_end, " <-cancelled->")
  1353.  
  1354.         # leave counter and timestamp alone
  1355.  
  1356.     def clear_msg_queue(self, button=None):
  1357.         if (self.queue_list != {}):
  1358.             for call in self.queue_list:
  1359.                 for sequence in self.queue_list[call]:
  1360.                     id = "%s-%s" % (call, sequence)
  1361.                     del self.message_list[id]
  1362.                     self.cancel_message(id)
  1363.             self.queue_list = {}
  1364.  
  1365.         if (self.current_message != {}):
  1366.             for call in self.current_message:
  1367.                 sequence = self.current_message[call]
  1368.                 id = "%s-%s" % (call, sequence)
  1369.                 self.cancel_message(id)
  1370.             self.current_message = {}
  1371.