Advertisement
Guest User

Popper.py with LCD support! V2

a guest
Feb 26th, 2013
559
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # popper.py
  5. #
  6. # Copyright 2012 Ralf Hersel <ralf.hersel@gmx.net>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  21. # MA 02110-1301, USA.
  22. #
  23. #=======================================================================
  24. # POPPER : An indicator and notifier for new emails
  25. #
  26. # Author : Ralf Hersel, ralf.hersel@gmx.net
  27. # Version: 0.31
  28. # Date   : Apr 15, 2012
  29. # Licence: GPL 3.0
  30. #
  31. # Libraries ============================================================
  32.  
  33. import poplib
  34. import imaplib
  35. import urllib2
  36. import ConfigParser
  37. import os
  38. import subprocess
  39. import pynotify
  40. import indicate
  41. import gobject
  42. import gtk
  43. import time
  44. import email
  45. from email.header import decode_header
  46. import sys
  47. import locale
  48. import gettext
  49. from popper_config import Keyring, read_config, set_default_config
  50. import cairo
  51. import serial
  52.  
  53. MATRIX_PREFIX = "\xFE"
  54. BACKLIGHT_ON = "\x42\x00"
  55. BACKLIGHT_OFF = "\x46"
  56. CLEAR = "\x58"
  57. HOME = "\x48"
  58. MOVE_SECOND_LINE = "\x47\x01\x02"
  59. MOVE_FIRST_LINE = "\x47\x01\x01"
  60. UNDERLINE_CURSOR_ON = "\x4A"
  61. UNDERLINE_CURSOR_OFF = "\x4B"
  62.  
  63. BLOCK_CURSOR_OFF = "\x54"
  64.  
  65. SERIAL_PORT = '/dev/infodisplay-001'
  66.  
  67. ser = None
  68.  
  69.  
  70. # Accounts and Account =================================================
  71. class Account:
  72.     def __init__(self, name, server, user, password, imap, folder, port, ssl):
  73.         self.name = name
  74.         self.server = server
  75.         self.user = user
  76.         self.password = password
  77.         self.imap = imap                                                # int
  78.         self.folder = folder
  79.         self.port = port
  80.         self.ssl = ssl
  81.         self.mail_count = 0
  82.  
  83.  
  84.     def get_connection(self):                                           # get email server connection
  85.         if self.imap:                                                   # IMAP
  86.             try:
  87.                 try:
  88.                     if self.port == '':
  89.                         srv = imaplib.IMAP4_SSL(self.server)            # SSL
  90.                     else:
  91.                         srv = imaplib.IMAP4_SSL(self.server, self.port)
  92.                 except:
  93.                     if not bool(self.ssl):                              # only if 'force SSL' is off
  94.                         if self.port == '':
  95.                             srv = imaplib.IMAP4(self.server)            # non SSL
  96.                         else:
  97.                             srv = imaplib.IMAP4(self.server, self.port)
  98.                 srv.login(self.user, self.password)
  99.             except:
  100.                 print "  Warning: Cannot connect to IMAP account: %s. " \
  101.                     "Next try in 30 seconds." % self.server
  102.                 time.sleep(30)                                          # wait 30 seconds
  103.                 try:
  104.                     try:
  105.                         if self.port == '':
  106.                             srv = imaplib.IMAP4_SSL(self.server)        # SSL
  107.                         else:
  108.                             srv = imaplib.IMAP4_SSL(self.server, self.port)
  109.                     except:
  110.                         if not bool(self.ssl):                          # only if 'force SSL' is off
  111.                             if self.port == '':
  112.                                 srv = imaplib.IMAP4(self.server)        # non SSL
  113.                             else:
  114.                                 srv = imaplib.IMAP4(self.server, self.port)
  115.                     srv.login(self.user, self.password)
  116.                 except:
  117.                     frequency = cfg.get('account', 'frequency')         # get email check frequency
  118.                     print "  Error: Cannot connect to IMAP account: %s. " \
  119.                         "Next try in %s minutes." % (self.server, frequency)
  120.                     srv = False
  121.         else:                                                           # POP
  122.             try:
  123.                 try:
  124.                     if self.port == '':
  125.                         srv = poplib.POP3_SSL(self.server)              # connect to Email-Server via SSL
  126.                     else:
  127.                         srv = poplib.POP3_SSL(self.server, self.port)
  128.                 except:
  129.                     if not bool(self.ssl):                              # only if 'force SSL' is off
  130.                         if self.port == '':
  131.                             srv = poplib.POP3(self.server)              # non SSL
  132.                         else:
  133.                             srv = poplib.POP3(self.server, self.port)
  134.                 srv.getwelcome()
  135.                 srv.user(self.user)
  136.                 srv.pass_(self.password)
  137.             except:
  138.                 print "  Warning: Cannot connect to POP account: %s. " \
  139.                     "Next try in 30 seconds." % self.server
  140.                 time.sleep(30)                                          # wait 30 seconds
  141.                 try:
  142.                     try:
  143.                         if self.port == '':
  144.                             srv = poplib.POP3_SSL(self.server)          # try it again
  145.                         else:
  146.                             srv = poplib.POP3_SSL(self.server, self.port)
  147.                     except:
  148.                         if not bool(self.ssl):                          # only if 'force SSL' is off
  149.                             if self.port == '':
  150.                                 srv = poplib.POP3(self.server)          # non SSL
  151.                             else:
  152.                                 srv = poplib.POP3(self.server, self.port)
  153.                     srv.getwelcome()
  154.                     srv.user(self.user)
  155.                     srv.pass_(self.password)
  156.                 except:
  157.                     frequency = cfg.get('account', 'frequency')         # get email check frequency
  158.                     print "  Error: Cannot connect to POP account: %s. " \
  159.                         "Next try in %s minutes." % (self.server, frequency)
  160.                     srv = False
  161.  
  162.         return srv                                                      # server object
  163.  
  164.  
  165. class Accounts:
  166.     def __init__(self):
  167.         self.account = []
  168.         keyring = Keyring()
  169.         self.keyring_was_locked = keyring.was_locked
  170.  
  171.         on = cfg.get('account', 'on')
  172.         name = cfg.get('account', 'name')
  173.         server = cfg.get('account', 'server')
  174.         user = cfg.get('account', 'user')
  175.         imap = cfg.get('account', 'imap')
  176.         folder = cfg.get('account', 'folder')
  177.         port = cfg.get('account', 'port')
  178.         ssl = cfg.get('account', 'ssl')
  179.  
  180.         separator = '|'
  181.         on_list = on.split(separator)
  182.         name_list = name.split(separator)
  183.         server_list = server.split(separator)
  184.         user_list = user.split(separator)
  185.         imap_list = imap.split(separator)
  186.         folder_list = folder.split(separator)
  187.         port_list = port.split(separator)
  188.         ssl_list = ssl.split(separator)
  189.  
  190.         for i in range(len(name_list)):                                 # iterate 0 to nr of elements in name_list
  191.             on = int(on_list[i])
  192.             name = name_list[i]
  193.             if not on or name == '': continue                           # ignore accounts that are off or have no name
  194.             server = server_list[i]
  195.             user = user_list[i]
  196.             imap = int(imap_list[i])
  197.             folder = folder_list[i]
  198.             port = port_list[i]
  199.             ssl = int(ssl_list[i])
  200.             if imap: protocol = 'imap'
  201.             else: protocol = 'pop'
  202.             password = keyring.get(protocol, user, server)
  203.             self.account.append(Account(name, server, user, password, imap, folder, port, ssl))
  204.  
  205.  
  206.     def get_count(self, name):                                          # get number of emails for this provider
  207.         count = 'error'
  208.         for acc in self.account:
  209.             if acc.name == name:
  210.                 count = str(acc.mail_count)
  211.                 break
  212.         if count == 'error':
  213.             print 'Cannot find account (get_count)'
  214.         return count
  215.  
  216.  
  217. # Mail =================================================================
  218. class Mail:
  219.     def __init__(self, seconds, subject, sender, datetime, id, provider):
  220.         self.seconds = seconds
  221.         self.subject = subject
  222.         self.sender = sender
  223.         self.datetime = datetime
  224.         self.id = id
  225.         self.provider = provider
  226.  
  227.  
  228. # Mails ================================================================
  229. class Mails:
  230.     def get_mail(self, sort_order):
  231.         mail_list = []                                                  # initialize list of mails
  232.         mail_ids = []                                                   # initialize list of mail ids
  233.  
  234.         while not self.is_online():
  235.             time.sleep(10)                                              # wait for internet connection (10 sec.)
  236.  
  237.         filter_on = int(cfg.get('filter', 'filter_on'))                 # get filter switch
  238.  
  239.         for acc in accounts.account:                                    # loop all email accounts
  240.             srv = acc.get_connection()                                  # get server connection for this account
  241.             if srv == False:
  242.                 continue                                                # continue with next account if server is empty
  243.             elif acc.imap:                                              # IMAP
  244.                 folder_list = acc.folder.split(',')                     # make a list of folders
  245.                 mail_count = 0                                          # reset email counter
  246.                 if folder_list[0] == '':
  247.                     folder_list = ['INBOX']
  248.                 for folder in folder_list:
  249.                     srv.select(folder.strip(), readonly=True)           # select IMAP folder
  250.                     status, data = srv.search(None, 'UNSEEN')           # ALL or UNSEEN
  251.                     if status != 'OK' or None in [d for d in data]:
  252.                         print "Folder", folder, "in status", status, "| Data:", data, "\n"
  253.                         continue                                        # Bugfix LP-735071
  254.                     for num in data[0].split():
  255.                         typ, msg_data = srv.fetch(num, '(BODY.PEEK[HEADER])')   # header only (without setting READ flag)
  256.                         for response_part in msg_data:
  257.                             if isinstance(response_part, tuple):
  258.                                 try:
  259.                                     msg = email.message_from_string(response_part[1])
  260.                                 except:
  261.                                     print "Could not get IMAP message:", response_part      # debug
  262.                                     continue
  263.                                 try:
  264.                                     try:
  265.                                         sender = self.format_header('sender', msg['From'])  # get sender and format it
  266.                                     except KeyError:
  267.                                         print "KeyError exception for key 'From' in message:", msg  # debug
  268.                                         sender = self.format_header('sender', msg['from'])
  269.                                 except:
  270.                                     print "Could not get sender from IMAP message:", msg    # debug
  271.                                     sender = "Error in sender"
  272.                                 try:
  273.                                     try:
  274.                                         datetime, seconds = self.format_header('date', msg['Date']) # get date and format it
  275.                                     except KeyError:
  276.                                         print "KeyError exception for key 'Date' in message:", msg  # debug
  277.                                         datetime, seconds = self.format_header('date', msg['date'])
  278.                                 except:
  279.                                     print "Could not get date from IMAP message:", msg      # debug
  280.                                     datetime = time.strftime('%Y.%m.%d %X')                 # take current time as "2010.12.31 13:57:04"
  281.                                     seconds = time.time()                                   # take current time as seconds
  282.                                 try:
  283.                                     try:
  284.                                         subject = self.format_header('subject', msg['Subject']) # get subject and format it
  285.                                     except KeyError:
  286.                                         print "KeyError exception for key 'Subject' in message:", msg   # debug
  287.                                         subject = self.format_header('subject', msg['subject'])
  288.                                 except:
  289.                                     print "Could not get subject from IMAP message:", msg   # debug
  290.                                     subject = 'Error in subject'
  291.                                 try:
  292.                                     id = msg['Message-Id']
  293.                                 except:
  294.                                     print "Could not get id from IMAP message:", msg        # debug
  295.                                     id = None                           # prepare emergency id
  296.                                 if id == None or id == '':
  297.                                     id = str(hash(subject))             # create emergency id
  298.                         if id not in mail_ids:                          # prevent duplicates caused by Gmail labels
  299.                             if not (filter_on and self.in_filter(sender + subject)):        # check filter
  300.                                 mail_list.append(Mail(seconds, subject, \
  301.                                     sender, datetime, id, acc.name))
  302.                                 mail_count += 1                         # increment mail counter for this IMAP folder
  303.                                 mail_ids.append(id)                     # add id to list
  304.                 acc.mail_count = mail_count                             # store number of emails per account
  305.                 srv.close()
  306.                 srv.logout()
  307.             else:                                                       # POP
  308.                 try:
  309.                     mail_total = len(srv.list()[1])                     # number of mails on the server
  310.                 except:                                                 # trap: 'exceeded login limit'
  311.                     continue
  312.                 mail_count = 0                                          # reset number of relevant mails
  313.                 for i in range(1, mail_total+1):                        # for each mail
  314.                     try:
  315.                         message = srv.top(i, 0)[1]                      # header plus first 0 lines from body
  316.                     except:
  317.                         print "Could not get POP message"               # debug
  318.                         continue
  319.                     message_string = '\n'.join(message)                 # convert list to string
  320.                     try:
  321.                         msg = dict(email.message_from_string(message_string))   # put message into email object and make a dictionary
  322.                     except:
  323.                         print "Could not get msg from POP message:", message_string # debug
  324.                         continue
  325.                     try:
  326.                         try:
  327.                             sender = self.format_header('sender', msg['From'])  # get sender and format it
  328.                         except KeyError:
  329.                             print "KeyError exception for key 'From' in message:", msg  # debug
  330.                             sender = self.format_header('sender', msg['from'])
  331.                     except:
  332.                         print "Could not get sender from POP message:", msg # debug
  333.                         sender = "Error in sender"
  334.                     try:
  335.                         try:
  336.                             datetime, seconds = self.format_header('date', msg['Date']) # get date and format it
  337.                         except KeyError:
  338.                             print "KeyError exception for key 'Date' in message:", msg  # debug
  339.                             datetime, seconds = self.format_header('date', msg['date'])
  340.                     except:
  341.                         print "Could not get date from POP message:", msg   # debug
  342.                         datetime = time.strftime('%Y.%m.%d %X')         # take current time as "2010.12.31 13:57:04"
  343.                         seconds = time.time()                           # take current time as seconds
  344.                     try:
  345.                         try:
  346.                             subject = self.format_header('subject', msg['Subject']) # get subject and format it
  347.                         except KeyError:
  348.                             print "KeyError exception for key 'Subject' in message:", msg   # debug
  349.                             subject = self.format_header('subject', msg['subject'])
  350.                     except:
  351.                         print "Could not get subject from POP message:", msg
  352.                         subject = 'Error in subject'
  353.                     try:
  354.                         uidl = srv.uidl(i)                              # get id
  355.                     except:
  356.                         print "Could not get id from POP message:", message # debug
  357.                         uidl = None                                     # prepare emergency id
  358.                     if uidl == None or uidl == '':
  359.                         uidl = str(hash(subject))                       # create emergency id
  360.                     id = acc.user + uidl.split(' ')[2]                  # create unique id
  361.                     if not (filter_on and self.in_filter(sender + subject)):    # check filter
  362.                         mail_list.append(Mail(seconds, subject, sender, \
  363.                             datetime, id, acc.name))
  364.                         mail_count += 1                                 # increment mail counter for this IMAP folder
  365.                     acc.mail_count = mail_count                         # store number of emails per account
  366.                 srv.quit()                                              # disconnect from Email-Server
  367.         mail_list = self.sort_mails(mail_list, sort_order)              # sort mails
  368.         sys.stdout.flush()                                              # write stdout to log file
  369.         return mail_list
  370.  
  371.  
  372.     def is_online(self):                                                # check for internet connection
  373.         try:
  374.             urllib2.urlopen("http://www.google.com/")
  375.             return True
  376.         except:
  377.             return False
  378.  
  379.  
  380.     def in_filter(self, sendersubject):                                 # check if filter appears in sendersubject
  381.         status = False
  382.         filter_text = cfg.get('filter', 'filter_text')
  383.         filter_list = filter_text.split(',')                            # convert text to list
  384.         for filter_item in filter_list:
  385.             filter_stripped_item = filter_item.strip()                  # remove CR and white space
  386.             if filter_stripped_item.lower() in sendersubject.lower():
  387.                 status = True                                           # subject contains filter item
  388.                 break
  389.         return status
  390.  
  391.  
  392.     def sort_mails(self, mail_list, sort_order):                        # sort mail list
  393.         sort_list = []
  394.         for mail in mail_list:
  395.             sort_list.append([mail.seconds, mail])                      # extract seconds from mail instance
  396.         sort_list.sort()                                                # sort asc
  397.         if sort_order == 'desc':
  398.             sort_list.reverse()                                         # sort desc
  399.         mail_list = []                                                  # sort by field 'seconds'
  400.         for mail in sort_list:
  401.             mail_list.append(mail[1])                                   # recreate mail_list
  402.         return mail_list
  403.  
  404.  
  405.     def format_header(self, field, content):                            # format sender, date, subject etc.
  406.         if field == 'sender':
  407.             try:
  408.                 sender_real, sender_addr = email.utils.parseaddr(content) # get the two parts of the sender
  409.                 sender_real = self.convert(sender_real)
  410.                 sender_addr = self.convert(sender_addr)
  411.                 sender = (sender_real, sender_addr)                     # create decoded tupel
  412.             except:
  413.                 sender = ('','Error: cannot format sender')
  414.  
  415.             sender_format = cfg.get('indicate', 'sender_format')
  416.             if sender_format == '1' and sender[0] != '':                # real sender name if not empty
  417.                 sender = sender_real
  418.             else:
  419.                 sender = sender_addr
  420.             return sender
  421.  
  422.         if field == 'date':
  423.             try:
  424.                 parsed_date = email.utils.parsedate_tz(content)         # make a 10-tupel (UTC)
  425.                 seconds = email.utils.mktime_tz(parsed_date)            # convert 10-tupel to seconds incl. timezone shift
  426.                 tupel = time.localtime(seconds)                         # convert seconds to tupel
  427.                 datetime = time.strftime('%Y.%m.%d %X', tupel)          # convert tupel to string
  428.             except:
  429.                 print 'Error: cannot format date:', content
  430.                 datetime = time.strftime('%Y.%m.%d %X')                 # take current time as "2010.12.31 13:57:04"
  431.                 seconds = time.time()                                   # take current time as seconds
  432.             return datetime, seconds
  433.  
  434.         if field == 'subject':
  435.             try:
  436.                 subject = self.convert(content)
  437.             except:
  438.                 subject = 'Error: cannot format subject'
  439.             return subject
  440.  
  441.  
  442.     def convert(self, raw_content):                                     # decode and concatenate multi-coded header parts
  443.         content = raw_content.replace('\n',' ')                         # replace newline by space
  444.         content = content.replace('?==?','?= =?')                       # fix bug in email.header.decode_header()
  445.         tupels = decode_header(content)                                 # list of (text_part, charset) tupels
  446.         content_list = []
  447.         for text, charset in tupels:                                    # iterate parts
  448.             if charset == None: charset = 'latin-1'                     # set default charset for decoding
  449.             content_list.append(text.decode(charset, 'ignore'))         # replace non-decodable chars with 'nothing'
  450.         decoded_content = ' '.join(content_list)                        # insert blanks between parts
  451.         decoded_content = decoded_content.strip()                       # get rid of whitespace
  452.  
  453.         #~ print "  raw    :", raw_content                                  # debug
  454.         #~ print "  tupels :", tupels                                       # debug
  455.         #~ print "  decoded:", decoded_content                              # debug
  456.  
  457.         return decoded_content
  458.  
  459.  
  460. # Misc =================================================================
  461. def write_pid():                                                        # write Popper's process id to file
  462.     pid_file = user_path + 'popper.pid'
  463.     f = open(pid_file, 'w')
  464.     f.write(str(os.getpid()))                                           # get PID and write to file
  465.     f.close()
  466.  
  467.  
  468. def delete_pid():                                                       # delete file popper.pid
  469.     pid_file = user_path + 'popper.pid'
  470.     if os.path.exists(pid_file):
  471.         os.popen("rm " + pid_file)                                      # delete it
  472.  
  473.  
  474. def user_scripts(event, data):                                          # process user scripts
  475.     if event == "on_email_arrival":
  476.         if cfg.get('script', 'script0_on') == '1' and data > 0:         # on new emails
  477.             pathfile = cfg.get('script', 'script0_file')                # get script pathfile
  478.             if pathfile != '' and os.path.exists(pathfile):             # not empty and existing
  479.                 pid.append(subprocess.Popen([pathfile, str(data)]))     # execute script with 'number of new mails'
  480.             else:
  481.                 print 'Warning: cannot find script:', pathfile
  482.  
  483.         if cfg.get('script', 'script1_on') == '1' and data == 0:        # on no new emails
  484.             pathfile = cfg.get('script', 'script1_file')
  485.             if pathfile != '' and os.path.exists(pathfile):
  486.                 pid.append(subprocess.Popen(pathfile))                  # execute script
  487.             else:
  488.                 print 'Warning: cannot find script:', pathfile
  489.  
  490.     elif cfg.get('script', 'script2_on') == '1' and event == "on_email_clicked":
  491.         pathfile = cfg.get('script', 'script2_file')
  492.         if pathfile != '' and os.path.exists(pathfile):
  493.             pid.append(subprocess.Popen([pathfile, \
  494.             data[0], str(data[1]), data[2], data[3]]))
  495.             # execute script with 'sender, datetime, subject, provider'
  496.         else:
  497.             print 'Warning: cannot find script:', pathfile
  498.  
  499.     elif cfg.get('script', 'script3_on') == '1' and event == "on_account_clicked":
  500.         pathfile = cfg.get('script', 'script3_file')
  501.         if pathfile != '' and os.path.exists(pathfile):
  502.             pid.append(subprocess.Popen([pathfile, \
  503.             data[0], str(data[1]), str(data[2])[1:-1], str(data[3])[1:-1]]))
  504.             # execute script with 'account name, number of emails,
  505.             # comma separated subjects, comma seperated datetimes'
  506.         else:
  507.             print 'Warning: cannot find script:', pathfile
  508.  
  509.     else:
  510.         return False
  511.  
  512. def new_mail_script(mails):                                             # process user scripts
  513.     line2 = ""
  514.     if len(mails) > 0:
  515.         if len(mails) == 1:
  516.             mail = mails[0]
  517.            
  518.             line1 = mail.provider[0] + ":" + mail.sender[:14]
  519.             line2 = "S:" + mail.subject[:16]
  520.             write_lcd(line1, line2, True)
  521.         else:
  522.             providers = {}
  523.             for mail in mails:
  524.                 if mail.provider not in providers:
  525.                     providers[mail.provider] = 1
  526.                 else:
  527.                     providers[mail.provider] += 1
  528.                
  529.                 print "These are the mails:"
  530.                 print "- seconds: {0}".format(mail.seconds)
  531.                 print "- subject: {0}".format(mail.subject)
  532.                 print "- sender: {0}".format(mail.sender)
  533.                 print "- datetime: {0}".format(mail.datetime)
  534.                 print "- id: {0}".format(mail.id)
  535.                 print "- provider: {0}".format(mail.provider)
  536.            
  537.             line1 = ""
  538.             for provider in providers:
  539.                 line1 += provider[0] + ": " + str(providers[provider]) + ", "
  540.             line1 = line1[:len(line1)-2]
  541.             write_lcd(line1, line2, True)
  542.     else:
  543.         line1 = "No new mails"
  544.         print "No new mails"
  545.         write_lcd(line1, line2, False)
  546.  
  547. def write_lcd(line1, line2, backlight):
  548.     global ser
  549.  
  550.     #connect to display
  551.     try:
  552.         ser = serial.Serial(SERIAL_PORT, 115200, timeout=1)
  553.  
  554.         try:
  555.             print "Line 1: {0}".format(line1)
  556.             print "Line 2: {0}".format(line2)
  557.             if backlight:
  558.                 serial_write_command(BACKLIGHT_ON)
  559.             else:
  560.                 serial_write_command(BACKLIGHT_OFF)
  561.             print "After a serial write"
  562.             serial_write_command(BLOCK_CURSOR_OFF)
  563.             serial_write_command(CLEAR)
  564.             serial_write(line1)
  565.             serial_write_command(MOVE_SECOND_LINE)
  566.             serial_write(line2)
  567.  
  568.             ser.close()
  569.         except Exception:
  570.             print "Could not write to {0}".format(SERIAL_PORT)
  571.             pass
  572.     except Exception:
  573.         print "Could not open {0}".format(SERIAL_PORT)
  574.         pass
  575.  
  576. def serial_write(data):
  577.     global ser
  578.     ser.write(data)
  579.     time.sleep(0.05)
  580.  
  581. def serial_write_command(data):
  582.     serial_write(MATRIX_PREFIX + data)
  583.  
  584. def commandExecutor(command, context_menu_command=None):
  585.     if context_menu_command != None:                                    # check origin of command
  586.         command = context_menu_command
  587.  
  588.     if command == 'clear':                                              # clear indicator list immediatelly
  589.         indicator.clear()
  590.         if indicator.desktop_display != None:
  591.             indicator.desktop_display.destroy()
  592.     elif command == 'exit':                                             # exit popper immediatelly
  593.         delete_pid()
  594.         exit(0)
  595.     elif command == 'check':                                            # check immediatelly for new emails
  596.         indicator.timeout()
  597.     elif command == 'list':                                             # open summary window
  598.         rows = []
  599.         for mail in indicator.mail_list:
  600.             rows.append([mail.provider, mail.sender, \
  601.                 mail.subject, mail.datetime])
  602.         ProviderEmailList(rows, self.sender)                            # show email list for all providers
  603.     else:
  604.         command_list = command.split(' ')                               # create list of arguments
  605.         pid.append(subprocess.Popen(command_list))                      # execute 'command'
  606.  
  607.  
  608. # Indicator ============================================================
  609. class Indicator:
  610.     def __init__(self):
  611.         self.limit = 7                                                  # max. indicator menu entries
  612.         self.mails = Mails()                                            # create Mails object
  613.         self.messages = []                                              # empty message list
  614.         self.reminder = Reminder()                                      # create Reminder object
  615.         desktop_file = os.path.join(user_path, "popper.desktop")        # path of the desktop file
  616.         self.server = indicate.indicate_server_ref_default()            # create indicator server
  617.         self.server.set_type("message.mail")
  618.         self.server.set_desktop_file(desktop_file)
  619.         self.server.connect("server-display", self.headline_clicked)    # if clicked on headline
  620.         self.server.show()
  621.         pynotify.init("icon-summary-body")                              # initialize Notification
  622.         self.link = cfg.get('indicate', 'start_on_click')               # get link address
  623.         self.desktop_display = None                                     # the Window object for Desktop_Display
  624.  
  625.  
  626.     def timeout(self):
  627.         print 'Checking email accounts at:', time.asctime()             # debug
  628.         pid.kill()                                                      # kill all Zombies
  629.         new_mails = 0                                                   # reset number of new mails
  630.         sort_by = cfg.get('indicate', 'sort_by')                        # 1 = date  0 = provider
  631.         show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  632.         menu_count = self.number_of_menu_entries()                      # nr of menu entries
  633.         if firstcheck and autostarted:
  634.             self.reminder.load()
  635.             self.add_menu_entries('asc')                                # add menu entries to indicator menu
  636.             self.sort_order = 'asc'                                     # set sort order for mail_list and summary window
  637.             self.mail_list = self.mails.get_mail(self.sort_order)       # get all mails from all inboxes
  638.             if sort_by == '1':                                          # sort by date
  639.                 max = self.limit - menu_count                           # calculate boundaries
  640.                 if max > len(self.mail_list):
  641.                     max = len(self.mail_list)                           # calculate boundaries
  642.                 for i in range(0, max):
  643.                     if not show_only_new or \
  644.                     self.reminder.unseen(self.mail_list[i].id):
  645.                         self.messages.append(Message(self.mail_list[i]))# add new mail to messages
  646.             else:                                                       # sort by provider
  647.                 self.add_account_summaries()                            # add providers to indicator menu
  648.         else:
  649.             if firstcheck:                                              # Manual firststart
  650.                 self.reminder.load()
  651.             self.sort_order = 'asc'                                     # set sort order for mail_list and summary window
  652.             self.mail_list = self.mails.get_mail(self.sort_order)       # get all mails from all inboxes
  653.             if sort_by == '1':                                          # sort by date
  654.                 for message in self.messages:                           # clear indicator menu
  655.                     message.set_property("draw-attention", "false")     # white envelope in panel
  656.                     message.hide()                                      # hide message in indicator menu
  657.                 self.messages = []                                      # clear message list
  658.  
  659.                 max = len(self.mail_list)                               # calculate boundaries
  660.                 low = max - self.limit + menu_count                     # calculate boundaries
  661.                 if low < 0:
  662.                     low = 0                                             # calculate boundaries
  663.                 for i in range(low, max):
  664.                     if not show_only_new or \
  665.                     self.reminder.unseen(self.mail_list[i].id):
  666.                         self.messages.append(Message(self.mail_list[i]))# add new mail to messages
  667.             else:                                                       # sort by provider
  668.                 self.add_account_summaries()                            # add providers to indicator menu
  669.             self.add_menu_entries('desc')                               # add menu entries to indicator menu
  670.  
  671.         sender = ''
  672.         subject = ''
  673.         for mail in self.mail_list:                                     # get number of new mails
  674.             if not self.reminder.contains(mail.id):
  675.                 new_mails += 1
  676.                 sender = mail.sender                                    # get sender for "you have one new mail" notification
  677.                 subject = mail.subject                                  # same for subject
  678.  
  679.         # Notify =======================================================
  680.         if new_mails > 0:                                               # new mails?
  681.             if new_mails > 1:                                           # multiple new emails
  682.                 notify_text = cfg.get('notify', 'text_multi') % str(new_mails)
  683.             else:
  684.                 notify_text = cfg.get('notify', 'text_one')             # only one new email
  685.                 notify_sender = bool(int(cfg.get('notify', 'notify_sender')))   # show sender?
  686.                 notify_subject = bool(int(cfg.get('notify', 'notify_subject'))) # show subject?
  687.                 if notify_sender and notify_subject:
  688.                     notify_text += "\n" + sender + "\n" + subject       # sender and subject
  689.                 elif notify_sender and not notify_subject:
  690.                     notify_text += "\n" + sender                        # sender
  691.                 elif not notify_sender and notify_subject:
  692.                     notify_text += "\n" + subject                       # subject
  693.  
  694.             if cfg.get('notify', 'playsound') == '1':                   # play sound?
  695.                 soundcommand = ['aplay', '-q', cfg.get('notify', 'soundfile')]
  696.                 pid.append(subprocess.Popen(soundcommand))
  697.  
  698.             if cfg.get('notify', 'notify') == '1':                      # show bubble?
  699.                 headline = cfg.get('indicate', 'headline')
  700.                 notification = pynotify.Notification(headline, \
  701.                     notify_text, "notification-message-email")
  702.                 try: notification.show()
  703.                 except: print "Exception in notification.show()", notify_text   # debug
  704.  
  705.             if cfg.get('notify', 'speak') == '1':                       # speak?
  706.                 self.speak(notify_text)
  707.  
  708.         # Desktop Display ==============================================
  709.         if cfg.get('dd', 'on') == '1' and new_mails > 0:                # show Desktop Display
  710.             content = []
  711.             for mail in self.mail_list:
  712.                 if not show_only_new or self.reminder.unseen(mail.id):
  713.                     content.append([mail.sender + ' - ' + mail.datetime, mail.subject])
  714.             content.reverse()                                           # turn around
  715.             if self.desktop_display != None:
  716.                 self.desktop_display.destroy()                          # destroy old window
  717.             self.desktop_display = DesktopDisplay(content)
  718.             self.desktop_display.show()
  719.  
  720.         # Misc =========================================================
  721.         user_scripts("on_email_arrival", new_mails)                     # process user scripts
  722.         new_mail_script(self.mail_list)                                 # send all emails to script
  723.  
  724.         self.reminder.save(self.mail_list)
  725.         sys.stdout.flush()                                              # write stdout to log file
  726.         return True
  727.  
  728.  
  729.     def add_account_summaries(self):                                    # add entries per provider in indicator menu
  730.         if firstcheck:                                                  # firstcheck = True -> add entries
  731.             for acc in accounts.account:                                # loop all email accounts
  732.                 self.messages.append(Message(Mail(4.0, acc.name, \
  733.                     acc.name, None, 'id_4', 'acc_entry')))
  734.         else:                                                           # firstcheck = False -> update count
  735.             show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  736.             for message in self.messages:
  737.                 if message.provider == 'acc_entry':
  738.                     old_count = message.get_property("count")           # get last count
  739.                     if show_only_new:
  740.                         count = self.get_new_count(message.subject)     # get count of emails that are not in reminder
  741.                     else:
  742.                         count = accounts.get_count(message.subject)     # get number of mails per account
  743.                     message.set_property("count", count)                # update count
  744.                     if int(count) > int(old_count):                     # new emails arrived
  745.                         message.set_property("draw-attention", "true")  # green envelope in panel
  746.                     elif int(count) == 0:
  747.                         message.set_property("draw-attention", "false") # white envelope in panel
  748.  
  749.  
  750.     def add_menu_entries(self, sort_order):                             # add menu entries to messages
  751.         if sort_order == 'asc':
  752.             show_menu_1 = cfg.get('indicate', 'show_menu_1')
  753.             if show_menu_1 == '1' and 'id_1' not in \
  754.             [message.id for message in self.messages]:
  755.                 name_menu_1 = cfg.get('indicate', 'name_menu_1')
  756.                 cmd_menu_1 = cfg.get('indicate', 'cmd_menu_1')
  757.                 if name_menu_1 != '' and cmd_menu_1 != '':
  758.                     self.messages.append(Message(Mail(1.0, name_menu_1, \
  759.                         cmd_menu_1, None, 'id_1', 'menu_entry')))
  760.  
  761.             show_menu_2 = cfg.get('indicate', 'show_menu_2')
  762.             if show_menu_2 == '1' and 'id_2' not in \
  763.             [message.id for message in self.messages]:
  764.                 name_menu_2 = cfg.get('indicate', 'name_menu_2')
  765.                 cmd_menu_2 = cfg.get('indicate', 'cmd_menu_2')
  766.                 if name_menu_2 != '' and cmd_menu_2 != '':
  767.                     self.messages.append(Message(Mail(2.0, name_menu_2, \
  768.                         cmd_menu_2, None, 'id_2', 'menu_entry')))
  769.  
  770.             show_menu_3 = cfg.get('indicate', 'show_menu_3')
  771.             if show_menu_3 == '1' and 'id_3' not in \
  772.             [message.id for message in self.messages]:
  773.                 name_menu_3 = cfg.get('indicate', 'name_menu_3')
  774.                 cmd_menu_3 = cfg.get('indicate', 'cmd_menu_3')
  775.                 if name_menu_3 != '' and cmd_menu_3 != '':
  776.                     self.messages.append(Message(Mail(3.0, name_menu_3, \
  777.                         cmd_menu_3, None, 'id_3', 'menu_entry')))
  778.         else:
  779.             show_menu_3 = cfg.get('indicate', 'show_menu_3')
  780.             if show_menu_3 == '1' and 'id_3' not in \
  781.             [message.id for message in self.messages]:
  782.                 name_menu_3 = cfg.get('indicate', 'name_menu_3')
  783.                 cmd_menu_3 = cfg.get('indicate', 'cmd_menu_3')
  784.                 if name_menu_3 != '' and cmd_menu_3 != '':
  785.                     self.messages.append(Message(Mail(3.0, name_menu_3, \
  786.                         cmd_menu_3, None, 'id_3', 'menu_entry')))
  787.  
  788.             show_menu_2 = cfg.get('indicate', 'show_menu_2')
  789.             if show_menu_2 == '1' and 'id_2' not in \
  790.             [message.id for message in self.messages]:
  791.                 name_menu_2 = cfg.get('indicate', 'name_menu_2')
  792.                 cmd_menu_2 = cfg.get('indicate', 'cmd_menu_2')
  793.                 if name_menu_2 != '' and cmd_menu_2 != '':
  794.                     self.messages.append(Message(Mail(2.0, name_menu_2, \
  795.                         cmd_menu_2, None, 'id_2', 'menu_entry')))
  796.  
  797.             show_menu_1 = cfg.get('indicate', 'show_menu_1')
  798.             if show_menu_1 == '1' and 'id_1' not in \
  799.             [message.id for message in self.messages]:
  800.                 name_menu_1 = cfg.get('indicate', 'name_menu_1')
  801.                 cmd_menu_1 = cfg.get('indicate', 'cmd_menu_1')
  802.                 if name_menu_1 != '' and cmd_menu_1 != '':
  803.                     self.messages.append(Message(Mail(1.0, name_menu_1, \
  804.                         cmd_menu_1, None, 'id_1', 'menu_entry')))
  805.  
  806.  
  807.     def number_of_menu_entries(self):                                   # return number of active menu entries
  808.         count = 0
  809.         if cfg.get('indicate', 'show_menu_1') == '1':
  810.             count += 1
  811.         if cfg.get('indicate', 'show_menu_2') == '1':
  812.             count += 1
  813.         if cfg.get('indicate', 'show_menu_3') == '1':
  814.             count += 1
  815.         return count
  816.  
  817.  
  818.     def headline_clicked(self, server, dummy):                          # click on headline
  819.         if self.desktop_display != None:
  820.             self.desktop_display.destroy()
  821.         clear_on_click = cfg.get('indicate', 'clear_on_click')
  822.         if clear_on_click == '1':
  823.             self.clear()                                                # clear messages list
  824.         emailclient = self.link.split(' ')                              # create list of command arguments
  825.         pid.append(subprocess.Popen(emailclient))                       # start link (Email Client)
  826.  
  827.  
  828.     def clear(self):                                                    # clear the messages list (not the menu entries)
  829.         show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  830.         remove_list = []
  831.         for message in self.messages:                                   # for all messages
  832.             message.set_property("draw-attention", "false")             # white envelope in panel
  833.             if message.provider not in ['menu_entry','acc_entry']:      # email entry
  834.                 message.hide()                                          # remove from indicator menu
  835.                 remove_list.append(message)                             # add to removal list
  836.             elif message.provider == 'acc_entry':                       # account entry
  837.                 message.set_property("count", "0")                      # reset count to account menu entry
  838.             if show_only_new:
  839.                 for mail in self.mail_list:                             # loop mails in mail list
  840.                     self.reminder.set_to_seen(mail.id)                  # set seen flag for this email to True
  841.         for message in remove_list:
  842.             self.messages.remove(message)                               # remove all email entries from messages list
  843.         if show_only_new:
  844.             self.reminder.save(self.mail_list)                          # save to popper.dat
  845.         else:                                                           # keep 'list' filled
  846.             self.mail_list = []                                         # clear mail list
  847.  
  848.  
  849.     def get_new_count(self, provider):                                  # get count of emails not in reminder for one provider
  850.         count = 0                                                       # default counter
  851.         for mail in self.mail_list:                                     # loop all emails
  852.             if mail.provider == provider and self.reminder.unseen(mail.id): # this provider and unflaged
  853.                 count += 1
  854.         return str(count)
  855.  
  856.  
  857.     def speak(self, text):                                              # speak the text
  858.         lang = locale.getdefaultlocale()[0].lower()                     # get language from operating system
  859.         if 'de' in lang:
  860.             lang = 'de'
  861.         else:
  862.             lang = 'en'
  863.         if cfg.get('notify', 'playsound') == '1':                       # if sound should be played
  864.             time.sleep(1)                                               # wait until sound is played halfway
  865.  
  866.         pathfile = user_path + 'popper_speak.sh'                        # path to script file
  867.         if os.path.exists(pathfile):
  868.             pid.append(subprocess.Popen([pathfile, lang, text]))        # speak text via shell script
  869.         else:
  870.             print 'Warning: cannot execute script:', pathfile
  871.  
  872.  
  873. # Message ==============================================================
  874. class Message(indicate.Indicator):
  875.     def __init__(self, mail):
  876.         indicate.Indicator.__init__(self)
  877.  
  878.         self.seconds = mail.seconds
  879.         self.subject = mail.subject
  880.         self.sender = mail.sender
  881.         self.datetime = mail.datetime
  882.         self.id = mail.id
  883.         self.provider = mail.provider
  884.  
  885.         self.connect("user-display", self.clicked)
  886.  
  887.         if self.provider not in ['menu_entry','acc_entry']:             # it is a normal message
  888.             self.set_property("draw-attention", "true")                 # green envelope in panel
  889.             subject_short = self.get_short_subject(self.subject)        # cut subject text
  890.             self.set_property("subtype", "mail")
  891.  
  892.             show_sender = cfg.get('indicate', 'show_sender')
  893.             show_subject = cfg.get('indicate', 'show_subject')
  894.  
  895.             if show_sender == '0' and show_subject == '0':
  896.                 message_text = self.datetime                            # datetime
  897.             elif show_sender == '1' and show_subject == '0':
  898.                 message_text = self.sender                              # sender
  899.             elif show_sender == '0' and show_subject == '1':
  900.                 message_text = subject_short                            # subject
  901.             else:
  902.                 message_format = cfg.get('indicate', 'message_format')
  903.                 if message_format == '0':
  904.                     message_text = "%s - %s" % (subject_short, self.sender)
  905.                 else:
  906.                     message_text = "%s - %s" % (self.sender, subject_short)
  907.  
  908.             show_provider = cfg.get('indicate', 'show_provider')
  909.             if show_provider == '1':
  910.                 message_text = "%s - %s" % (self.provider, message_text)    # provider
  911.                 self.set_property("name", message_text)
  912.             else:
  913.                 self.set_property("name", message_text)
  914.             self.set_property("count", self.get_time_delta())           # put age in counter spot
  915.  
  916.         else:                                                           # it is a menue or account entry
  917.             show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  918.             self.set_property("name", self.subject)
  919.             if self.provider == 'acc_entry':                            # it is an account entry
  920.                 if firstcheck:
  921.                     old_count = '0'                                     # default last count
  922.                 else: old_count = self.get_property("count")            # get last count
  923.                 if show_only_new:
  924.                     count = indicator.get_new_count(self.subject)       # get count of emails that are not in reminder
  925.                 else:
  926.                     count = accounts.get_count(self.subject)            # get number of mails per account
  927.                 self.set_property("count", count)                       # add count to provider menu entry
  928.                 if int(count) > int(old_count):                         # new emails arrived
  929.                     self.set_property("draw-attention", "true")         # green envelope in panel
  930.         self.show()
  931.  
  932.  
  933.     def clicked(self, MessageObject, MessageNumber):                    # click on message
  934.         if self.provider not in ['menu_entry','acc_entry']:             # it is a normal message
  935.             if cfg.get('indicate', 'remove_single_email') == '1':
  936.                 self.hide()                                             # remove from indicator menu
  937.                 show_only_new = bool(int(cfg.get('indicate', 'show_only_new')))
  938.                 if show_only_new:
  939.                     indicator.reminder.set_to_seen(self.id)             # don't show it again
  940.             else:                                                       # show OSD
  941.                 notify_text = self.sender + '\n' + self.datetime + \
  942.                     '\n' + self.subject
  943.                 notification = pynotify.Notification(self.provider, \
  944.                     notify_text, "notification-message-email")
  945.                 notification.show()                                     # show details of one message
  946.  
  947.             user_scripts("on_email_clicked", \
  948.                 [self.sender, self.datetime, self.subject, self.provider])  # process 'user_scripts'
  949.         elif self.provider == 'menu_entry':                             # it is a menu entry
  950.             command = self.sender
  951.             commandExecutor(command)
  952.         else:   # account entry
  953.             rows = []
  954.             for mail in indicator.mail_list:
  955.                 if mail.provider == self.sender:                        # self.sender holds the account name
  956.                     rows.append([mail.provider, mail.sender, \
  957.                         mail.subject, mail.datetime])
  958.             ProviderEmailList(rows, self.sender)                        # show email list for this provider
  959.             user_scripts("on_account_clicked", \
  960.                 [self.sender, len(rows), [row[2] for row in rows], \
  961.                 [row[3] for row in rows]])                              # process 'user_scripts'
  962.  
  963.  
  964.     def get_short_subject(self, subject):                               # shorten long mail subject text
  965.         subject_length = int(cfg.get('indicate', 'subject_length'))     # format subject
  966.         if len(subject) > subject_length:
  967.             dot_filler = '...'                                          # set dots if longer than n
  968.         else:
  969.             dot_filler = ''
  970.         subject_short = subject[:subject_length] + dot_filler           # shorten subject
  971.         return subject_short
  972.  
  973.  
  974.     def get_time_delta(self):                                           # return delta hours
  975.         delta = time.time() - self.seconds                              # calculate delta seconds
  976.         if delta < 0:
  977.             return "?"                                                  # negative age
  978.         else:
  979.             unit = " s"
  980.             if delta > 60:
  981.                 delta /= 60                                             # convert to minutes
  982.                 unit = " m"
  983.                 if delta > 60:
  984.                     delta /= 60                                         # convert to hours
  985.                     unit = " h"
  986.                     if delta > 24:
  987.                         delta /= 24                                     # convert to days
  988.                         unit = " d"
  989.                         if delta > 7:
  990.                             delta /= 7                                  # convert to weeks
  991.                             unit = " w"
  992.                             if delta > 30:
  993.                                 delta /= 30                             # convert to months
  994.                                 unit = " M"
  995.                                 if delta > 12:
  996.                                     delta /= 12                         # convert to years
  997.                                     unit = " Y"
  998.         delta_string = str(int(round(delta))) + unit                    # make string
  999.         return delta_string
  1000.  
  1001.  
  1002. # Provider Email List Dialog ===========================================
  1003. class ProviderEmailList:
  1004.     def __init__(self, rows, title):
  1005.         if len(rows) == 0:
  1006.             return                                                      # nothing to show
  1007.         builder = gtk.Builder()                                         # build GUI from Glade file
  1008.         builder.set_translation_domain('popper_list')
  1009.         builder.add_from_file("popper_list.glade")
  1010.         builder.connect_signals({ \
  1011.         "gtk_main_quit" : self.exit, \
  1012.         "on_button_close_clicked" : self.exit})
  1013.  
  1014.         colhead = [_('Provider'), _('From'), _('Subject'), _('Date')]   # column headings
  1015.         self.window = builder.get_object("dialog_popper_list")
  1016.         self.window.set_title('Popper')
  1017.         width, hight = self.get_window_size(rows, colhead)              # calculate window size
  1018.         self.window.set_default_size(width, hight)                      # set the window size
  1019.  
  1020.         treeview = builder.get_object("treeview")                       # get the widgets
  1021.         liststore = builder.get_object("liststore")
  1022.         close_button = builder.get_object("button_close")
  1023.         close_button.set_label(_('Close'))
  1024.  
  1025.         renderer = gtk.CellRendererText()
  1026.         column0 = gtk.TreeViewColumn(colhead[0], renderer, text=0)      # Provider
  1027.         column0.set_sort_column_id(0)                                   # make column sortable
  1028.         column0.set_resizable(True)                                     # column width resizable
  1029.         treeview.append_column(column0)
  1030.  
  1031.         column1 = gtk.TreeViewColumn(colhead[1], renderer, text=1)      # From
  1032.         column1.set_sort_column_id(1)
  1033.         column1.set_resizable(True)
  1034.         treeview.append_column(column1)
  1035.  
  1036.         column2 = gtk.TreeViewColumn(colhead[2], renderer, text=2)      # Subject
  1037.         column2.set_sort_column_id(2)
  1038.         column2.set_resizable(True)
  1039.         treeview.append_column(column2)
  1040.  
  1041.         column3 = gtk.TreeViewColumn(colhead[3], renderer, text=3)      # Date
  1042.         column3.set_sort_column_id(3)
  1043.         column3.set_resizable(True)
  1044.         treeview.append_column(column3)
  1045.  
  1046.         if not autostarted:
  1047.             rows.reverse()                                              # not autostarted
  1048.         elif indicator.sort_order == 'asc':
  1049.             rows.reverse()                                              # autostarted and firstcheck
  1050.  
  1051.         for row in rows:
  1052.             liststore.append(row)                                       # add emails to summary window
  1053.         self.window.show()
  1054.  
  1055.  
  1056.     def get_window_size(self, rows, colhead):
  1057.         max = 0                                                         # default for widest row
  1058.         fix = 50                                                        # fix part of width (frame, lines, etc)
  1059.         charlen = 7                                                     # average width of one character
  1060.         height = 480                                                    # fixed set window height
  1061.         min_width = 320                                                 # lower limit for window width
  1062.         max_width = 1024                                                # upper limit for window width
  1063.         alist = self.transpose(rows)                                    # transpose list
  1064.         for i in range(len(colhead)):
  1065.             alist[i].append(colhead[i] + '  ')                          # add column headings
  1066.         colmax = []                                                     # list with the widest string per column
  1067.         for col in alist:                                               # loop all columns
  1068.             temp_widest = 0                                             # reset temporary widest row value
  1069.             for row in col:                                             # loop all row strings
  1070.                 if len(row) > temp_widest:
  1071.                     temp_widest = len(row)                              # find the widest string in that column
  1072.             colmax.append(temp_widest)                                  # save the widest string in that column
  1073.         for row in colmax:
  1074.             max += row                                                  # add all widest strings
  1075.         width = fix + max * charlen                                     # calculate window width
  1076.         if width < min_width:
  1077.             width = min_width                                           # avoid width underrun
  1078.         if width > max_width:
  1079.             width = max_width                                           # avoid width overrun
  1080.         return width, height
  1081.  
  1082.  
  1083.     def transpose(self, array):                                         # transpose list (switch cols with rows)
  1084.         return map(lambda *row: list(row), *array)
  1085.  
  1086.  
  1087.     def exit(self, widget):                                             # exit
  1088.         self.window.destroy()
  1089.  
  1090.  
  1091. # Reminder =============================================================
  1092. class Reminder(dict):
  1093.  
  1094.     def load(self):                                                     # load last known messages from popper.dat
  1095.         remember = cfg.get('indicate', 'remember')
  1096.         dat_file = user_path + 'popper.dat'
  1097.         we_have_a_file = os.path.exists(dat_file)                       # check if file exists
  1098.         if remember == '1' and we_have_a_file:
  1099.             f = open(dat_file, 'r')                                     # open file again
  1100.             for line in f:
  1101.                 stripedline = line.strip()                              # remove CR at the end
  1102.                 content = stripedline.split(',')                        # get all items from one line in a list: ["mailid", show_only_new flag"]
  1103.                 try:
  1104.                     self[content[0]] = content[1]                       # add to dict [id : flag]
  1105.                 except IndexError:
  1106.                     self[content[0]] = '0'                              # no flags in popper.dat
  1107.             f.close()                                                   # close file
  1108.  
  1109.  
  1110.     def save(self, mail_list):                                          # save mail ids to file
  1111.         dat_file = user_path + 'popper.dat'
  1112.         f = open(dat_file, 'w')                                         # open the file for overwrite
  1113.         for m in mail_list:
  1114.             try:
  1115.                 seen_flag = self[m.id]
  1116.             except KeyError:
  1117.                 seen_flag = '0'                                         # id of a new mail is not yet known to reminder
  1118.             line = m.id + ',' + seen_flag + '\n'                        # construct line: email_id, seen_flag
  1119.             f.write(line)                                               # write line to file
  1120.             self[m.id] = seen_flag                                      # add to dict
  1121.         f.close()                                                       # close the file
  1122.  
  1123.  
  1124.     def contains(self, id):                                             # check if mail id is in reminder list
  1125.         try:
  1126.             self[id]
  1127.             return True
  1128.         except KeyError:
  1129.             return False
  1130.  
  1131.  
  1132.     def set_to_seen(self, id):                                          # set seen flag for this email on True
  1133.         try:
  1134.             self[id] = '1'
  1135.         except KeyError:
  1136.             pass
  1137.  
  1138.  
  1139.     def unseen(self, id):                                               # return True if flag == '0'
  1140.         try:
  1141.             flag = self[id]
  1142.             if flag == '0':
  1143.                 return True
  1144.             else:
  1145.                 return False
  1146.         except KeyError:
  1147.             return True
  1148.  
  1149.  
  1150. # Pid ==================================================================
  1151. class Pid(list):                                                        # List class to manage subprocess PIDs
  1152.  
  1153.     def kill(self):                                                     # kill all zombies
  1154.         removals = []                                                   # list of PIDs to remove from list
  1155.         for p in self:
  1156.             returncode = p.poll()                                       # get returncode of subprocess
  1157.             if returncode == 0:
  1158.                 removals.append(p)                                      # zombie will be removed
  1159.         for p in removals:
  1160.             self.remove(p)                                              # remove non-zombies from list
  1161.  
  1162.  
  1163. # Desktop Display ======================================================
  1164. class DesktopDisplay(gtk.Window):                                       # displays a transparent frameless window on the desktop
  1165.     __gsignals__ = {
  1166.         'expose-event': 'override'
  1167.         }
  1168.  
  1169.     def __init__(self, content):
  1170.         super(DesktopDisplay, self).__init__()
  1171.  
  1172.         self.content = content                                          # array of text lists
  1173.  
  1174.         self.set_title('Popper Desktop Display')
  1175.         self.set_app_paintable(True)                                    # no window border
  1176.         self.set_decorated(False)
  1177.         self.set_position(gtk.WIN_POS_CENTER)
  1178.         pixbuf = gtk.gdk.pixbuf_new_from_file('popper.png')             # get icon from png
  1179.         self.set_icon(pixbuf)                                           # set window icon
  1180.         pos_x = int(cfg.get('dd', 'pos_x'))
  1181.         pos_y = int(cfg.get('dd', 'pos_y'))
  1182.         self.move(pos_x, pos_y)                                         # move window to position x,y
  1183.  
  1184.         self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
  1185.         self.connect("button-press-event", self.event_mouse_clicked)
  1186.  
  1187.         screen = self.get_screen()                                      # see if we can do transparency
  1188.         alphamap = screen.get_rgba_colormap()
  1189.         rgbmap   = screen.get_rgb_colormap()
  1190.         if alphamap is not None:
  1191.             self.set_colormap(alphamap)
  1192.         else:
  1193.             self.set_colormap(rgbmap)
  1194.             print _("Warning: transparency is not supported")
  1195.  
  1196.         width = int(cfg.get('dd','width'))
  1197.         height = int(cfg.get('dd','height'))
  1198.         self.set_size_request(width, height)
  1199.  
  1200.         font = cfg.get('dd','font_name').split(' ')                     # make list ['ubuntu', 12]
  1201.         self.font_name = ' '.join(font[:-1])                            # everything except the last one
  1202.         try:
  1203.             self.font_size = int(font[-1])                              # only the last one
  1204.         except ValueError:                                              # if something wrong, use defaults
  1205.             self.font_name = 'ubuntu'
  1206.             self.font_size = '14'
  1207.  
  1208.  
  1209.     def do_expose_event(self, event):
  1210.         width, height = self.get_size()
  1211.         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
  1212.         ctx = cairo.Context(surface)
  1213.  
  1214.         # Background
  1215.         red, green, blue = self.get_rgb(str(cfg.get('dd','bg_color')))  # convert hex color to (r, g, b) / 100 values
  1216.         alpha = (100 - int(cfg.get('dd','transparency'))) / 100.0
  1217.         ctx.set_source_rgba(red, green, blue, alpha)                    # background is red, green, blue, alpha
  1218.         ctx.paint()
  1219.  
  1220.         # Text
  1221.         ctx.select_font_face(self.font_name, cairo.FONT_SLANT_NORMAL, \
  1222.             cairo.FONT_WEIGHT_NORMAL)
  1223.         ctx.set_font_size(self.font_size)
  1224.         red, green, blue = self.get_rgb(str(cfg.get('dd','text_color')))
  1225.         ctx.set_source_rgb(red, green, blue)
  1226.         self.show_text(ctx, self.content)                               # write text to surface
  1227.  
  1228.         dest_ctx = self.window.cairo_create()                           # now copy to our window surface
  1229.         dest_ctx.rectangle(event.area.x, event.area.y, \
  1230.             event.area.width, event.area.height)                        # only update what needs to be drawn
  1231.         dest_ctx.clip()
  1232.  
  1233.         dest_ctx.set_operator(cairo.OPERATOR_SOURCE)                    # source operator means replace, don't draw on top of
  1234.         dest_ctx.set_source_surface(surface, 0, 0)
  1235.         dest_ctx.paint()
  1236.  
  1237.  
  1238.     def show_text(self, ctx, content):                                  # write text to surface
  1239.         x = 10
  1240.         y = 20
  1241.         mail_offset = 10
  1242.         line_offset = self.font_size
  1243.         width = int(cfg.get('dd','width'))
  1244.         x_cut = int(width / self.font_size * 1.7)                       # end of line
  1245.         height = int(cfg.get('dd','height'))
  1246.         y_cut = int(height / (2 * line_offset + mail_offset)) - 2       # end of page
  1247.         mail_count = 0
  1248.  
  1249.         for mail in content:                                            # iterate all mails
  1250.             mail_count += 1
  1251.             for line in mail:                                           # iterate lines in mail
  1252.                 ctx.move_to(x, y)
  1253.                 if len(line) > x_cut: continuation = '...'
  1254.                 else: continuation = ''
  1255.                 ctx.show_text(line[:x_cut] + continuation)              # print stripped line
  1256.                 y += line_offset
  1257.             y += mail_offset
  1258.             if mail_count > y_cut:                                      # end of page
  1259.                 if len(content) > mail_count:
  1260.                     ctx.move_to(x, y)
  1261.                     ctx.show_text('...')
  1262.                 break
  1263.  
  1264.  
  1265.     def get_rgb(self, hexcolor):                                        # convert hex color to (r, g, b) / 100 values
  1266.         color = gtk.gdk.color_parse(hexcolor)
  1267.         divisor = 65535.0
  1268.         red = color.red / divisor
  1269.         green = color.green / divisor
  1270.         blue = color.blue / divisor
  1271.         return red, green, blue                                         # exp.: 0.1, 0.5, 0.8
  1272.  
  1273.  
  1274.     def event_mouse_clicked(self, widget, event):                       # mouse button clicked
  1275.         if event.button == 1:                                           # left button?
  1276.             if bool(int(cfg.get('dd', 'click_launch'))):
  1277.                 indicator.headline_clicked(None, None)
  1278.             if bool(int(cfg.get('dd', 'click_close'))):
  1279.                 self.destroy()
  1280.         if event.button == 3:                                           # right button?
  1281.             self.event_mouse_clicked_right(widget, event)
  1282.  
  1283.  
  1284.     def event_mouse_clicked_right(self, widget, event):                 # right mouse button clicked
  1285.         contextMenu = gtk.Menu()
  1286.         for str_i in ('1', '2', '3'):
  1287.             if bool(int(cfg.get('indicate', 'show_menu_' + str_i))):    # if command is enabled append to context menu
  1288.                 menu_item = gtk.MenuItem(cfg.get('indicate', 'name_menu_' + str_i))
  1289.                 menu_item.connect('activate', commandExecutor,
  1290.                     cfg.get('indicate', 'cmd_menu_' + str_i))
  1291.                 contextMenu.append(menu_item)
  1292.         if len(contextMenu) > 0:                                        # any entries at all?
  1293.             contextMenu.show_all()
  1294.             contextMenu.popup(None, None, None, event.button, event.time)
  1295.  
  1296.  
  1297. # Main =================================================================
  1298. def main():
  1299.     global cfg, user_path, accounts, mails, indicator, autostarted, firstcheck, pid, _
  1300.     new_version = '0.31'
  1301.  
  1302.     try:                                                                # Internationalization
  1303.         locale.setlocale(locale.LC_ALL, '')                             # locale language, e.g.: de_CH.utf8
  1304.     except locale.Error:
  1305.         locale.setlocale(locale.LC_ALL, 'en_US.utf8')                   # english for all unsupported locale languages
  1306.     localedir = '../locale'                                             # points to '/usr/share/locale'
  1307.     locale.bindtextdomain('popper', localedir)
  1308.     gettext.bindtextdomain('popper', localedir)
  1309.     gettext.textdomain('popper')
  1310.     _ = gettext.gettext
  1311.  
  1312.     user_path = os.path.expanduser("~/.popper/")                        # set path to: "/home/user/.popper/"
  1313.     autostarted = False                                                 # default setting for command line argument
  1314.     cmdline = sys.argv                                                  # get command line arguments
  1315.     if len(cmdline) > 1:                                                # do we have something in command line?
  1316.         if cmdline[1] == 'autostarted':
  1317.             autostarted = True
  1318.  
  1319.     cfg = read_config(user_path + 'popper.cfg', new_version, 'popper')  # get configuration from file
  1320.     if cfg == False:                                                    # problem with config file
  1321.         print 'error: wrong cfg'
  1322.         exit(1)                                                         # start popper_config.py
  1323.  
  1324.     write_pid()                                                         # write Popper's process id to file
  1325.  
  1326.     accounts = Accounts()                                               # create Accounts object
  1327.     if accounts.keyring_was_locked: firstcheck = False                  # required for correct sortorder in indi menu
  1328.     else: firstcheck = True
  1329.  
  1330.     pid = Pid()                                                         # create Pid object
  1331.     indicator = Indicator()                                             # create Indicator object
  1332.     indicator.timeout()                                                 # immediate check, firstcheck=True
  1333.     firstcheck = False                                                  # firstcheck is over
  1334.     if cfg.get('account', 'check_once') == '0':                         # wanna do more than one email check?
  1335.         frequency = int(cfg.get('account', 'frequency'))                # get email check frequency
  1336.         gobject.timeout_add_seconds(60*frequency, indicator.timeout)    # repetitive check
  1337.         gtk.main()                                                      # start Loop
  1338.     delete_pid()                                                        # delete file 'popper.pid'
  1339.     return 0
  1340.  
  1341.  
  1342. if __name__ == '__main__': main()
Advertisement
RAW Paste Data Copied
Advertisement