Advertisement
Guest User

Popper.py with LCD support!

a guest
Feb 26th, 2013
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 51.97 KB | None | 0 0
  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.     ser = serial.Serial(SERIAL_PORT, 115200, timeout=1)
  552.  
  553.     try:
  554.         print "Line 1: {0}".format(line1)
  555.         print "Line 2: {0}".format(line2)
  556.         if backlight:
  557.             serial_write_command(BACKLIGHT_ON)
  558.         else:
  559.             serial_write_command(BACKLIGHT_OFF)
  560.         print "After a serial write"
  561.         serial_write_command(BLOCK_CURSOR_OFF)
  562.         serial_write_command(CLEAR)
  563.         serial_write(line1)
  564.         serial_write_command(MOVE_SECOND_LINE)
  565.         serial_write(line2)
  566.  
  567.         ser.close()
  568.     except Exception:
  569.         print "Could not write to {0}".format(SERIAL_PORT)
  570.         raise
  571.  
  572. def serial_write(data):
  573.     global ser
  574.     ser.write(data)
  575.     time.sleep(0.05)
  576.  
  577. def serial_write_command(data):
  578.     serial_write(MATRIX_PREFIX + data)
  579.  
  580. def commandExecutor(command, context_menu_command=None):
  581.     if context_menu_command != None:                                    # check origin of command
  582.         command = context_menu_command
  583.  
  584.     if command == 'clear':                                              # clear indicator list immediatelly
  585.         indicator.clear()
  586.         if indicator.desktop_display != None:
  587.             indicator.desktop_display.destroy()
  588.     elif command == 'exit':                                             # exit popper immediatelly
  589.         delete_pid()
  590.         exit(0)
  591.     elif command == 'check':                                            # check immediatelly for new emails
  592.         indicator.timeout()
  593.     elif command == 'list':                                             # open summary window
  594.         rows = []
  595.         for mail in indicator.mail_list:
  596.             rows.append([mail.provider, mail.sender, \
  597.                 mail.subject, mail.datetime])
  598.         ProviderEmailList(rows, self.sender)                            # show email list for all providers
  599.     else:
  600.         command_list = command.split(' ')                               # create list of arguments
  601.         pid.append(subprocess.Popen(command_list))                      # execute 'command'
  602.  
  603.  
  604. # Indicator ============================================================
  605. class Indicator:
  606.     def __init__(self):
  607.         self.limit = 7                                                  # max. indicator menu entries
  608.         self.mails = Mails()                                            # create Mails object
  609.         self.messages = []                                              # empty message list
  610.         self.reminder = Reminder()                                      # create Reminder object
  611.         desktop_file = os.path.join(user_path, "popper.desktop")        # path of the desktop file
  612.         self.server = indicate.indicate_server_ref_default()            # create indicator server
  613.         self.server.set_type("message.mail")
  614.         self.server.set_desktop_file(desktop_file)
  615.         self.server.connect("server-display", self.headline_clicked)    # if clicked on headline
  616.         self.server.show()
  617.         pynotify.init("icon-summary-body")                              # initialize Notification
  618.         self.link = cfg.get('indicate', 'start_on_click')               # get link address
  619.         self.desktop_display = None                                     # the Window object for Desktop_Display
  620.  
  621.  
  622.     def timeout(self):
  623.         print 'Checking email accounts at:', time.asctime()             # debug
  624.         pid.kill()                                                      # kill all Zombies
  625.         new_mails = 0                                                   # reset number of new mails
  626.         sort_by = cfg.get('indicate', 'sort_by')                        # 1 = date  0 = provider
  627.         show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  628.         menu_count = self.number_of_menu_entries()                      # nr of menu entries
  629.         if firstcheck and autostarted:
  630.             self.reminder.load()
  631.             self.add_menu_entries('asc')                                # add menu entries to indicator menu
  632.             self.sort_order = 'asc'                                     # set sort order for mail_list and summary window
  633.             self.mail_list = self.mails.get_mail(self.sort_order)       # get all mails from all inboxes
  634.             if sort_by == '1':                                          # sort by date
  635.                 max = self.limit - menu_count                           # calculate boundaries
  636.                 if max > len(self.mail_list):
  637.                     max = len(self.mail_list)                           # calculate boundaries
  638.                 for i in range(0, max):
  639.                     if not show_only_new or \
  640.                     self.reminder.unseen(self.mail_list[i].id):
  641.                         self.messages.append(Message(self.mail_list[i]))# add new mail to messages
  642.             else:                                                       # sort by provider
  643.                 self.add_account_summaries()                            # add providers to indicator menu
  644.         else:
  645.             if firstcheck:                                              # Manual firststart
  646.                 self.reminder.load()
  647.             self.sort_order = 'asc'                                     # set sort order for mail_list and summary window
  648.             self.mail_list = self.mails.get_mail(self.sort_order)       # get all mails from all inboxes
  649.             if sort_by == '1':                                          # sort by date
  650.                 for message in self.messages:                           # clear indicator menu
  651.                     message.set_property("draw-attention", "false")     # white envelope in panel
  652.                     message.hide()                                      # hide message in indicator menu
  653.                 self.messages = []                                      # clear message list
  654.  
  655.                 max = len(self.mail_list)                               # calculate boundaries
  656.                 low = max - self.limit + menu_count                     # calculate boundaries
  657.                 if low < 0:
  658.                     low = 0                                             # calculate boundaries
  659.                 for i in range(low, max):
  660.                     if not show_only_new or \
  661.                     self.reminder.unseen(self.mail_list[i].id):
  662.                         self.messages.append(Message(self.mail_list[i]))# add new mail to messages
  663.             else:                                                       # sort by provider
  664.                 self.add_account_summaries()                            # add providers to indicator menu
  665.             self.add_menu_entries('desc')                               # add menu entries to indicator menu
  666.  
  667.         sender = ''
  668.         subject = ''
  669.         for mail in self.mail_list:                                     # get number of new mails
  670.             if not self.reminder.contains(mail.id):
  671.                 new_mails += 1
  672.                 sender = mail.sender                                    # get sender for "you have one new mail" notification
  673.                 subject = mail.subject                                  # same for subject
  674.  
  675.         # Notify =======================================================
  676.         if new_mails > 0:                                               # new mails?
  677.             if new_mails > 1:                                           # multiple new emails
  678.                 notify_text = cfg.get('notify', 'text_multi') % str(new_mails)
  679.             else:
  680.                 notify_text = cfg.get('notify', 'text_one')             # only one new email
  681.                 notify_sender = bool(int(cfg.get('notify', 'notify_sender')))   # show sender?
  682.                 notify_subject = bool(int(cfg.get('notify', 'notify_subject'))) # show subject?
  683.                 if notify_sender and notify_subject:
  684.                     notify_text += "\n" + sender + "\n" + subject       # sender and subject
  685.                 elif notify_sender and not notify_subject:
  686.                     notify_text += "\n" + sender                        # sender
  687.                 elif not notify_sender and notify_subject:
  688.                     notify_text += "\n" + subject                       # subject
  689.  
  690.             if cfg.get('notify', 'playsound') == '1':                   # play sound?
  691.                 soundcommand = ['aplay', '-q', cfg.get('notify', 'soundfile')]
  692.                 pid.append(subprocess.Popen(soundcommand))
  693.  
  694.             if cfg.get('notify', 'notify') == '1':                      # show bubble?
  695.                 headline = cfg.get('indicate', 'headline')
  696.                 notification = pynotify.Notification(headline, \
  697.                     notify_text, "notification-message-email")
  698.                 try: notification.show()
  699.                 except: print "Exception in notification.show()", notify_text   # debug
  700.  
  701.             if cfg.get('notify', 'speak') == '1':                       # speak?
  702.                 self.speak(notify_text)
  703.  
  704.         # Desktop Display ==============================================
  705.         if cfg.get('dd', 'on') == '1' and new_mails > 0:                # show Desktop Display
  706.             content = []
  707.             for mail in self.mail_list:
  708.                 if not show_only_new or self.reminder.unseen(mail.id):
  709.                     content.append([mail.sender + ' - ' + mail.datetime, mail.subject])
  710.             content.reverse()                                           # turn around
  711.             if self.desktop_display != None:
  712.                 self.desktop_display.destroy()                          # destroy old window
  713.             self.desktop_display = DesktopDisplay(content)
  714.             self.desktop_display.show()
  715.  
  716.         # Misc =========================================================
  717.         user_scripts("on_email_arrival", new_mails)                     # process user scripts
  718.         new_mail_script(self.mail_list)                                 # send all emails to script
  719.  
  720.         self.reminder.save(self.mail_list)
  721.         sys.stdout.flush()                                              # write stdout to log file
  722.         return True
  723.  
  724.  
  725.     def add_account_summaries(self):                                    # add entries per provider in indicator menu
  726.         if firstcheck:                                                  # firstcheck = True -> add entries
  727.             for acc in accounts.account:                                # loop all email accounts
  728.                 self.messages.append(Message(Mail(4.0, acc.name, \
  729.                     acc.name, None, 'id_4', 'acc_entry')))
  730.         else:                                                           # firstcheck = False -> update count
  731.             show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  732.             for message in self.messages:
  733.                 if message.provider == 'acc_entry':
  734.                     old_count = message.get_property("count")           # get last count
  735.                     if show_only_new:
  736.                         count = self.get_new_count(message.subject)     # get count of emails that are not in reminder
  737.                     else:
  738.                         count = accounts.get_count(message.subject)     # get number of mails per account
  739.                     message.set_property("count", count)                # update count
  740.                     if int(count) > int(old_count):                     # new emails arrived
  741.                         message.set_property("draw-attention", "true")  # green envelope in panel
  742.                     elif int(count) == 0:
  743.                         message.set_property("draw-attention", "false") # white envelope in panel
  744.  
  745.  
  746.     def add_menu_entries(self, sort_order):                             # add menu entries to messages
  747.         if sort_order == 'asc':
  748.             show_menu_1 = cfg.get('indicate', 'show_menu_1')
  749.             if show_menu_1 == '1' and 'id_1' not in \
  750.             [message.id for message in self.messages]:
  751.                 name_menu_1 = cfg.get('indicate', 'name_menu_1')
  752.                 cmd_menu_1 = cfg.get('indicate', 'cmd_menu_1')
  753.                 if name_menu_1 != '' and cmd_menu_1 != '':
  754.                     self.messages.append(Message(Mail(1.0, name_menu_1, \
  755.                         cmd_menu_1, None, 'id_1', 'menu_entry')))
  756.  
  757.             show_menu_2 = cfg.get('indicate', 'show_menu_2')
  758.             if show_menu_2 == '1' and 'id_2' not in \
  759.             [message.id for message in self.messages]:
  760.                 name_menu_2 = cfg.get('indicate', 'name_menu_2')
  761.                 cmd_menu_2 = cfg.get('indicate', 'cmd_menu_2')
  762.                 if name_menu_2 != '' and cmd_menu_2 != '':
  763.                     self.messages.append(Message(Mail(2.0, name_menu_2, \
  764.                         cmd_menu_2, None, 'id_2', 'menu_entry')))
  765.  
  766.             show_menu_3 = cfg.get('indicate', 'show_menu_3')
  767.             if show_menu_3 == '1' and 'id_3' not in \
  768.             [message.id for message in self.messages]:
  769.                 name_menu_3 = cfg.get('indicate', 'name_menu_3')
  770.                 cmd_menu_3 = cfg.get('indicate', 'cmd_menu_3')
  771.                 if name_menu_3 != '' and cmd_menu_3 != '':
  772.                     self.messages.append(Message(Mail(3.0, name_menu_3, \
  773.                         cmd_menu_3, None, 'id_3', 'menu_entry')))
  774.         else:
  775.             show_menu_3 = cfg.get('indicate', 'show_menu_3')
  776.             if show_menu_3 == '1' and 'id_3' not in \
  777.             [message.id for message in self.messages]:
  778.                 name_menu_3 = cfg.get('indicate', 'name_menu_3')
  779.                 cmd_menu_3 = cfg.get('indicate', 'cmd_menu_3')
  780.                 if name_menu_3 != '' and cmd_menu_3 != '':
  781.                     self.messages.append(Message(Mail(3.0, name_menu_3, \
  782.                         cmd_menu_3, None, 'id_3', 'menu_entry')))
  783.  
  784.             show_menu_2 = cfg.get('indicate', 'show_menu_2')
  785.             if show_menu_2 == '1' and 'id_2' not in \
  786.             [message.id for message in self.messages]:
  787.                 name_menu_2 = cfg.get('indicate', 'name_menu_2')
  788.                 cmd_menu_2 = cfg.get('indicate', 'cmd_menu_2')
  789.                 if name_menu_2 != '' and cmd_menu_2 != '':
  790.                     self.messages.append(Message(Mail(2.0, name_menu_2, \
  791.                         cmd_menu_2, None, 'id_2', 'menu_entry')))
  792.  
  793.             show_menu_1 = cfg.get('indicate', 'show_menu_1')
  794.             if show_menu_1 == '1' and 'id_1' not in \
  795.             [message.id for message in self.messages]:
  796.                 name_menu_1 = cfg.get('indicate', 'name_menu_1')
  797.                 cmd_menu_1 = cfg.get('indicate', 'cmd_menu_1')
  798.                 if name_menu_1 != '' and cmd_menu_1 != '':
  799.                     self.messages.append(Message(Mail(1.0, name_menu_1, \
  800.                         cmd_menu_1, None, 'id_1', 'menu_entry')))
  801.  
  802.  
  803.     def number_of_menu_entries(self):                                   # return number of active menu entries
  804.         count = 0
  805.         if cfg.get('indicate', 'show_menu_1') == '1':
  806.             count += 1
  807.         if cfg.get('indicate', 'show_menu_2') == '1':
  808.             count += 1
  809.         if cfg.get('indicate', 'show_menu_3') == '1':
  810.             count += 1
  811.         return count
  812.  
  813.  
  814.     def headline_clicked(self, server, dummy):                          # click on headline
  815.         if self.desktop_display != None:
  816.             self.desktop_display.destroy()
  817.         clear_on_click = cfg.get('indicate', 'clear_on_click')
  818.         if clear_on_click == '1':
  819.             self.clear()                                                # clear messages list
  820.         emailclient = self.link.split(' ')                              # create list of command arguments
  821.         pid.append(subprocess.Popen(emailclient))                       # start link (Email Client)
  822.  
  823.  
  824.     def clear(self):                                                    # clear the messages list (not the menu entries)
  825.         show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  826.         remove_list = []
  827.         for message in self.messages:                                   # for all messages
  828.             message.set_property("draw-attention", "false")             # white envelope in panel
  829.             if message.provider not in ['menu_entry','acc_entry']:      # email entry
  830.                 message.hide()                                          # remove from indicator menu
  831.                 remove_list.append(message)                             # add to removal list
  832.             elif message.provider == 'acc_entry':                       # account entry
  833.                 message.set_property("count", "0")                      # reset count to account menu entry
  834.             if show_only_new:
  835.                 for mail in self.mail_list:                             # loop mails in mail list
  836.                     self.reminder.set_to_seen(mail.id)                  # set seen flag for this email to True
  837.         for message in remove_list:
  838.             self.messages.remove(message)                               # remove all email entries from messages list
  839.         if show_only_new:
  840.             self.reminder.save(self.mail_list)                          # save to popper.dat
  841.         else:                                                           # keep 'list' filled
  842.             self.mail_list = []                                         # clear mail list
  843.  
  844.  
  845.     def get_new_count(self, provider):                                  # get count of emails not in reminder for one provider
  846.         count = 0                                                       # default counter
  847.         for mail in self.mail_list:                                     # loop all emails
  848.             if mail.provider == provider and self.reminder.unseen(mail.id): # this provider and unflaged
  849.                 count += 1
  850.         return str(count)
  851.  
  852.  
  853.     def speak(self, text):                                              # speak the text
  854.         lang = locale.getdefaultlocale()[0].lower()                     # get language from operating system
  855.         if 'de' in lang:
  856.             lang = 'de'
  857.         else:
  858.             lang = 'en'
  859.         if cfg.get('notify', 'playsound') == '1':                       # if sound should be played
  860.             time.sleep(1)                                               # wait until sound is played halfway
  861.  
  862.         pathfile = user_path + 'popper_speak.sh'                        # path to script file
  863.         if os.path.exists(pathfile):
  864.             pid.append(subprocess.Popen([pathfile, lang, text]))        # speak text via shell script
  865.         else:
  866.             print 'Warning: cannot execute script:', pathfile
  867.  
  868.  
  869. # Message ==============================================================
  870. class Message(indicate.Indicator):
  871.     def __init__(self, mail):
  872.         indicate.Indicator.__init__(self)
  873.  
  874.         self.seconds = mail.seconds
  875.         self.subject = mail.subject
  876.         self.sender = mail.sender
  877.         self.datetime = mail.datetime
  878.         self.id = mail.id
  879.         self.provider = mail.provider
  880.  
  881.         self.connect("user-display", self.clicked)
  882.  
  883.         if self.provider not in ['menu_entry','acc_entry']:             # it is a normal message
  884.             self.set_property("draw-attention", "true")                 # green envelope in panel
  885.             subject_short = self.get_short_subject(self.subject)        # cut subject text
  886.             self.set_property("subtype", "mail")
  887.  
  888.             show_sender = cfg.get('indicate', 'show_sender')
  889.             show_subject = cfg.get('indicate', 'show_subject')
  890.  
  891.             if show_sender == '0' and show_subject == '0':
  892.                 message_text = self.datetime                            # datetime
  893.             elif show_sender == '1' and show_subject == '0':
  894.                 message_text = self.sender                              # sender
  895.             elif show_sender == '0' and show_subject == '1':
  896.                 message_text = subject_short                            # subject
  897.             else:
  898.                 message_format = cfg.get('indicate', 'message_format')
  899.                 if message_format == '0':
  900.                     message_text = "%s - %s" % (subject_short, self.sender)
  901.                 else:
  902.                     message_text = "%s - %s" % (self.sender, subject_short)
  903.  
  904.             show_provider = cfg.get('indicate', 'show_provider')
  905.             if show_provider == '1':
  906.                 message_text = "%s - %s" % (self.provider, message_text)    # provider
  907.                 self.set_property("name", message_text)
  908.             else:
  909.                 self.set_property("name", message_text)
  910.             self.set_property("count", self.get_time_delta())           # put age in counter spot
  911.  
  912.         else:                                                           # it is a menue or account entry
  913.             show_only_new = bool(int(cfg.get('indicate', 'show_only_new'))) # get show_only_new flag
  914.             self.set_property("name", self.subject)
  915.             if self.provider == 'acc_entry':                            # it is an account entry
  916.                 if firstcheck:
  917.                     old_count = '0'                                     # default last count
  918.                 else: old_count = self.get_property("count")            # get last count
  919.                 if show_only_new:
  920.                     count = indicator.get_new_count(self.subject)       # get count of emails that are not in reminder
  921.                 else:
  922.                     count = accounts.get_count(self.subject)            # get number of mails per account
  923.                 self.set_property("count", count)                       # add count to provider menu entry
  924.                 if int(count) > int(old_count):                         # new emails arrived
  925.                     self.set_property("draw-attention", "true")         # green envelope in panel
  926.         self.show()
  927.  
  928.  
  929.     def clicked(self, MessageObject, MessageNumber):                    # click on message
  930.         if self.provider not in ['menu_entry','acc_entry']:             # it is a normal message
  931.             if cfg.get('indicate', 'remove_single_email') == '1':
  932.                 self.hide()                                             # remove from indicator menu
  933.                 show_only_new = bool(int(cfg.get('indicate', 'show_only_new')))
  934.                 if show_only_new:
  935.                     indicator.reminder.set_to_seen(self.id)             # don't show it again
  936.             else:                                                       # show OSD
  937.                 notify_text = self.sender + '\n' + self.datetime + \
  938.                     '\n' + self.subject
  939.                 notification = pynotify.Notification(self.provider, \
  940.                     notify_text, "notification-message-email")
  941.                 notification.show()                                     # show details of one message
  942.  
  943.             user_scripts("on_email_clicked", \
  944.                 [self.sender, self.datetime, self.subject, self.provider])  # process 'user_scripts'
  945.         elif self.provider == 'menu_entry':                             # it is a menu entry
  946.             command = self.sender
  947.             commandExecutor(command)
  948.         else:   # account entry
  949.             rows = []
  950.             for mail in indicator.mail_list:
  951.                 if mail.provider == self.sender:                        # self.sender holds the account name
  952.                     rows.append([mail.provider, mail.sender, \
  953.                         mail.subject, mail.datetime])
  954.             ProviderEmailList(rows, self.sender)                        # show email list for this provider
  955.             user_scripts("on_account_clicked", \
  956.                 [self.sender, len(rows), [row[2] for row in rows], \
  957.                 [row[3] for row in rows]])                              # process 'user_scripts'
  958.  
  959.  
  960.     def get_short_subject(self, subject):                               # shorten long mail subject text
  961.         subject_length = int(cfg.get('indicate', 'subject_length'))     # format subject
  962.         if len(subject) > subject_length:
  963.             dot_filler = '...'                                          # set dots if longer than n
  964.         else:
  965.             dot_filler = ''
  966.         subject_short = subject[:subject_length] + dot_filler           # shorten subject
  967.         return subject_short
  968.  
  969.  
  970.     def get_time_delta(self):                                           # return delta hours
  971.         delta = time.time() - self.seconds                              # calculate delta seconds
  972.         if delta < 0:
  973.             return "?"                                                  # negative age
  974.         else:
  975.             unit = " s"
  976.             if delta > 60:
  977.                 delta /= 60                                             # convert to minutes
  978.                 unit = " m"
  979.                 if delta > 60:
  980.                     delta /= 60                                         # convert to hours
  981.                     unit = " h"
  982.                     if delta > 24:
  983.                         delta /= 24                                     # convert to days
  984.                         unit = " d"
  985.                         if delta > 7:
  986.                             delta /= 7                                  # convert to weeks
  987.                             unit = " w"
  988.                             if delta > 30:
  989.                                 delta /= 30                             # convert to months
  990.                                 unit = " M"
  991.                                 if delta > 12:
  992.                                     delta /= 12                         # convert to years
  993.                                     unit = " Y"
  994.         delta_string = str(int(round(delta))) + unit                    # make string
  995.         return delta_string
  996.  
  997.  
  998. # Provider Email List Dialog ===========================================
  999. class ProviderEmailList:
  1000.     def __init__(self, rows, title):
  1001.         if len(rows) == 0:
  1002.             return                                                      # nothing to show
  1003.         builder = gtk.Builder()                                         # build GUI from Glade file
  1004.         builder.set_translation_domain('popper_list')
  1005.         builder.add_from_file("popper_list.glade")
  1006.         builder.connect_signals({ \
  1007.         "gtk_main_quit" : self.exit, \
  1008.         "on_button_close_clicked" : self.exit})
  1009.  
  1010.         colhead = [_('Provider'), _('From'), _('Subject'), _('Date')]   # column headings
  1011.         self.window = builder.get_object("dialog_popper_list")
  1012.         self.window.set_title('Popper')
  1013.         width, hight = self.get_window_size(rows, colhead)              # calculate window size
  1014.         self.window.set_default_size(width, hight)                      # set the window size
  1015.  
  1016.         treeview = builder.get_object("treeview")                       # get the widgets
  1017.         liststore = builder.get_object("liststore")
  1018.         close_button = builder.get_object("button_close")
  1019.         close_button.set_label(_('Close'))
  1020.  
  1021.         renderer = gtk.CellRendererText()
  1022.         column0 = gtk.TreeViewColumn(colhead[0], renderer, text=0)      # Provider
  1023.         column0.set_sort_column_id(0)                                   # make column sortable
  1024.         column0.set_resizable(True)                                     # column width resizable
  1025.         treeview.append_column(column0)
  1026.  
  1027.         column1 = gtk.TreeViewColumn(colhead[1], renderer, text=1)      # From
  1028.         column1.set_sort_column_id(1)
  1029.         column1.set_resizable(True)
  1030.         treeview.append_column(column1)
  1031.  
  1032.         column2 = gtk.TreeViewColumn(colhead[2], renderer, text=2)      # Subject
  1033.         column2.set_sort_column_id(2)
  1034.         column2.set_resizable(True)
  1035.         treeview.append_column(column2)
  1036.  
  1037.         column3 = gtk.TreeViewColumn(colhead[3], renderer, text=3)      # Date
  1038.         column3.set_sort_column_id(3)
  1039.         column3.set_resizable(True)
  1040.         treeview.append_column(column3)
  1041.  
  1042.         if not autostarted:
  1043.             rows.reverse()                                              # not autostarted
  1044.         elif indicator.sort_order == 'asc':
  1045.             rows.reverse()                                              # autostarted and firstcheck
  1046.  
  1047.         for row in rows:
  1048.             liststore.append(row)                                       # add emails to summary window
  1049.         self.window.show()
  1050.  
  1051.  
  1052.     def get_window_size(self, rows, colhead):
  1053.         max = 0                                                         # default for widest row
  1054.         fix = 50                                                        # fix part of width (frame, lines, etc)
  1055.         charlen = 7                                                     # average width of one character
  1056.         height = 480                                                    # fixed set window height
  1057.         min_width = 320                                                 # lower limit for window width
  1058.         max_width = 1024                                                # upper limit for window width
  1059.         alist = self.transpose(rows)                                    # transpose list
  1060.         for i in range(len(colhead)):
  1061.             alist[i].append(colhead[i] + '  ')                          # add column headings
  1062.         colmax = []                                                     # list with the widest string per column
  1063.         for col in alist:                                               # loop all columns
  1064.             temp_widest = 0                                             # reset temporary widest row value
  1065.             for row in col:                                             # loop all row strings
  1066.                 if len(row) > temp_widest:
  1067.                     temp_widest = len(row)                              # find the widest string in that column
  1068.             colmax.append(temp_widest)                                  # save the widest string in that column
  1069.         for row in colmax:
  1070.             max += row                                                  # add all widest strings
  1071.         width = fix + max * charlen                                     # calculate window width
  1072.         if width < min_width:
  1073.             width = min_width                                           # avoid width underrun
  1074.         if width > max_width:
  1075.             width = max_width                                           # avoid width overrun
  1076.         return width, height
  1077.  
  1078.  
  1079.     def transpose(self, array):                                         # transpose list (switch cols with rows)
  1080.         return map(lambda *row: list(row), *array)
  1081.  
  1082.  
  1083.     def exit(self, widget):                                             # exit
  1084.         self.window.destroy()
  1085.  
  1086.  
  1087. # Reminder =============================================================
  1088. class Reminder(dict):
  1089.  
  1090.     def load(self):                                                     # load last known messages from popper.dat
  1091.         remember = cfg.get('indicate', 'remember')
  1092.         dat_file = user_path + 'popper.dat'
  1093.         we_have_a_file = os.path.exists(dat_file)                       # check if file exists
  1094.         if remember == '1' and we_have_a_file:
  1095.             f = open(dat_file, 'r')                                     # open file again
  1096.             for line in f:
  1097.                 stripedline = line.strip()                              # remove CR at the end
  1098.                 content = stripedline.split(',')                        # get all items from one line in a list: ["mailid", show_only_new flag"]
  1099.                 try:
  1100.                     self[content[0]] = content[1]                       # add to dict [id : flag]
  1101.                 except IndexError:
  1102.                     self[content[0]] = '0'                              # no flags in popper.dat
  1103.             f.close()                                                   # close file
  1104.  
  1105.  
  1106.     def save(self, mail_list):                                          # save mail ids to file
  1107.         dat_file = user_path + 'popper.dat'
  1108.         f = open(dat_file, 'w')                                         # open the file for overwrite
  1109.         for m in mail_list:
  1110.             try:
  1111.                 seen_flag = self[m.id]
  1112.             except KeyError:
  1113.                 seen_flag = '0'                                         # id of a new mail is not yet known to reminder
  1114.             line = m.id + ',' + seen_flag + '\n'                        # construct line: email_id, seen_flag
  1115.             f.write(line)                                               # write line to file
  1116.             self[m.id] = seen_flag                                      # add to dict
  1117.         f.close()                                                       # close the file
  1118.  
  1119.  
  1120.     def contains(self, id):                                             # check if mail id is in reminder list
  1121.         try:
  1122.             self[id]
  1123.             return True
  1124.         except KeyError:
  1125.             return False
  1126.  
  1127.  
  1128.     def set_to_seen(self, id):                                          # set seen flag for this email on True
  1129.         try:
  1130.             self[id] = '1'
  1131.         except KeyError:
  1132.             pass
  1133.  
  1134.  
  1135.     def unseen(self, id):                                               # return True if flag == '0'
  1136.         try:
  1137.             flag = self[id]
  1138.             if flag == '0':
  1139.                 return True
  1140.             else:
  1141.                 return False
  1142.         except KeyError:
  1143.             return True
  1144.  
  1145.  
  1146. # Pid ==================================================================
  1147. class Pid(list):                                                        # List class to manage subprocess PIDs
  1148.  
  1149.     def kill(self):                                                     # kill all zombies
  1150.         removals = []                                                   # list of PIDs to remove from list
  1151.         for p in self:
  1152.             returncode = p.poll()                                       # get returncode of subprocess
  1153.             if returncode == 0:
  1154.                 removals.append(p)                                      # zombie will be removed
  1155.         for p in removals:
  1156.             self.remove(p)                                              # remove non-zombies from list
  1157.  
  1158.  
  1159. # Desktop Display ======================================================
  1160. class DesktopDisplay(gtk.Window):                                       # displays a transparent frameless window on the desktop
  1161.     __gsignals__ = {
  1162.         'expose-event': 'override'
  1163.         }
  1164.  
  1165.     def __init__(self, content):
  1166.         super(DesktopDisplay, self).__init__()
  1167.  
  1168.         self.content = content                                          # array of text lists
  1169.  
  1170.         self.set_title('Popper Desktop Display')
  1171.         self.set_app_paintable(True)                                    # no window border
  1172.         self.set_decorated(False)
  1173.         self.set_position(gtk.WIN_POS_CENTER)
  1174.         pixbuf = gtk.gdk.pixbuf_new_from_file('popper.png')             # get icon from png
  1175.         self.set_icon(pixbuf)                                           # set window icon
  1176.         pos_x = int(cfg.get('dd', 'pos_x'))
  1177.         pos_y = int(cfg.get('dd', 'pos_y'))
  1178.         self.move(pos_x, pos_y)                                         # move window to position x,y
  1179.  
  1180.         self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
  1181.         self.connect("button-press-event", self.event_mouse_clicked)
  1182.  
  1183.         screen = self.get_screen()                                      # see if we can do transparency
  1184.         alphamap = screen.get_rgba_colormap()
  1185.         rgbmap   = screen.get_rgb_colormap()
  1186.         if alphamap is not None:
  1187.             self.set_colormap(alphamap)
  1188.         else:
  1189.             self.set_colormap(rgbmap)
  1190.             print _("Warning: transparency is not supported")
  1191.  
  1192.         width = int(cfg.get('dd','width'))
  1193.         height = int(cfg.get('dd','height'))
  1194.         self.set_size_request(width, height)
  1195.  
  1196.         font = cfg.get('dd','font_name').split(' ')                     # make list ['ubuntu', 12]
  1197.         self.font_name = ' '.join(font[:-1])                            # everything except the last one
  1198.         try:
  1199.             self.font_size = int(font[-1])                              # only the last one
  1200.         except ValueError:                                              # if something wrong, use defaults
  1201.             self.font_name = 'ubuntu'
  1202.             self.font_size = '14'
  1203.  
  1204.  
  1205.     def do_expose_event(self, event):
  1206.         width, height = self.get_size()
  1207.         surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
  1208.         ctx = cairo.Context(surface)
  1209.  
  1210.         # Background
  1211.         red, green, blue = self.get_rgb(str(cfg.get('dd','bg_color')))  # convert hex color to (r, g, b) / 100 values
  1212.         alpha = (100 - int(cfg.get('dd','transparency'))) / 100.0
  1213.         ctx.set_source_rgba(red, green, blue, alpha)                    # background is red, green, blue, alpha
  1214.         ctx.paint()
  1215.  
  1216.         # Text
  1217.         ctx.select_font_face(self.font_name, cairo.FONT_SLANT_NORMAL, \
  1218.             cairo.FONT_WEIGHT_NORMAL)
  1219.         ctx.set_font_size(self.font_size)
  1220.         red, green, blue = self.get_rgb(str(cfg.get('dd','text_color')))
  1221.         ctx.set_source_rgb(red, green, blue)
  1222.         self.show_text(ctx, self.content)                               # write text to surface
  1223.  
  1224.         dest_ctx = self.window.cairo_create()                           # now copy to our window surface
  1225.         dest_ctx.rectangle(event.area.x, event.area.y, \
  1226.             event.area.width, event.area.height)                        # only update what needs to be drawn
  1227.         dest_ctx.clip()
  1228.  
  1229.         dest_ctx.set_operator(cairo.OPERATOR_SOURCE)                    # source operator means replace, don't draw on top of
  1230.         dest_ctx.set_source_surface(surface, 0, 0)
  1231.         dest_ctx.paint()
  1232.  
  1233.  
  1234.     def show_text(self, ctx, content):                                  # write text to surface
  1235.         x = 10
  1236.         y = 20
  1237.         mail_offset = 10
  1238.         line_offset = self.font_size
  1239.         width = int(cfg.get('dd','width'))
  1240.         x_cut = int(width / self.font_size * 1.7)                       # end of line
  1241.         height = int(cfg.get('dd','height'))
  1242.         y_cut = int(height / (2 * line_offset + mail_offset)) - 2       # end of page
  1243.         mail_count = 0
  1244.  
  1245.         for mail in content:                                            # iterate all mails
  1246.             mail_count += 1
  1247.             for line in mail:                                           # iterate lines in mail
  1248.                 ctx.move_to(x, y)
  1249.                 if len(line) > x_cut: continuation = '...'
  1250.                 else: continuation = ''
  1251.                 ctx.show_text(line[:x_cut] + continuation)              # print stripped line
  1252.                 y += line_offset
  1253.             y += mail_offset
  1254.             if mail_count > y_cut:                                      # end of page
  1255.                 if len(content) > mail_count:
  1256.                     ctx.move_to(x, y)
  1257.                     ctx.show_text('...')
  1258.                 break
  1259.  
  1260.  
  1261.     def get_rgb(self, hexcolor):                                        # convert hex color to (r, g, b) / 100 values
  1262.         color = gtk.gdk.color_parse(hexcolor)
  1263.         divisor = 65535.0
  1264.         red = color.red / divisor
  1265.         green = color.green / divisor
  1266.         blue = color.blue / divisor
  1267.         return red, green, blue                                         # exp.: 0.1, 0.5, 0.8
  1268.  
  1269.  
  1270.     def event_mouse_clicked(self, widget, event):                       # mouse button clicked
  1271.         if event.button == 1:                                           # left button?
  1272.             if bool(int(cfg.get('dd', 'click_launch'))):
  1273.                 indicator.headline_clicked(None, None)
  1274.             if bool(int(cfg.get('dd', 'click_close'))):
  1275.                 self.destroy()
  1276.         if event.button == 3:                                           # right button?
  1277.             self.event_mouse_clicked_right(widget, event)
  1278.  
  1279.  
  1280.     def event_mouse_clicked_right(self, widget, event):                 # right mouse button clicked
  1281.         contextMenu = gtk.Menu()
  1282.         for str_i in ('1', '2', '3'):
  1283.             if bool(int(cfg.get('indicate', 'show_menu_' + str_i))):    # if command is enabled append to context menu
  1284.                 menu_item = gtk.MenuItem(cfg.get('indicate', 'name_menu_' + str_i))
  1285.                 menu_item.connect('activate', commandExecutor,
  1286.                     cfg.get('indicate', 'cmd_menu_' + str_i))
  1287.                 contextMenu.append(menu_item)
  1288.         if len(contextMenu) > 0:                                        # any entries at all?
  1289.             contextMenu.show_all()
  1290.             contextMenu.popup(None, None, None, event.button, event.time)
  1291.  
  1292.  
  1293. # Main =================================================================
  1294. def main():
  1295.     global cfg, user_path, accounts, mails, indicator, autostarted, firstcheck, pid, _
  1296.     new_version = '0.31'
  1297.  
  1298.     try:                                                                # Internationalization
  1299.         locale.setlocale(locale.LC_ALL, '')                             # locale language, e.g.: de_CH.utf8
  1300.     except locale.Error:
  1301.         locale.setlocale(locale.LC_ALL, 'en_US.utf8')                   # english for all unsupported locale languages
  1302.     localedir = '../locale'                                             # points to '/usr/share/locale'
  1303.     locale.bindtextdomain('popper', localedir)
  1304.     gettext.bindtextdomain('popper', localedir)
  1305.     gettext.textdomain('popper')
  1306.     _ = gettext.gettext
  1307.  
  1308.     user_path = os.path.expanduser("~/.popper/")                        # set path to: "/home/user/.popper/"
  1309.     autostarted = False                                                 # default setting for command line argument
  1310.     cmdline = sys.argv                                                  # get command line arguments
  1311.     if len(cmdline) > 1:                                                # do we have something in command line?
  1312.         if cmdline[1] == 'autostarted':
  1313.             autostarted = True
  1314.  
  1315.     cfg = read_config(user_path + 'popper.cfg', new_version, 'popper')  # get configuration from file
  1316.     if cfg == False:                                                    # problem with config file
  1317.         print 'error: wrong cfg'
  1318.         exit(1)                                                         # start popper_config.py
  1319.  
  1320.     write_pid()                                                         # write Popper's process id to file
  1321.  
  1322.     accounts = Accounts()                                               # create Accounts object
  1323.     if accounts.keyring_was_locked: firstcheck = False                  # required for correct sortorder in indi menu
  1324.     else: firstcheck = True
  1325.  
  1326.     pid = Pid()                                                         # create Pid object
  1327.     indicator = Indicator()                                             # create Indicator object
  1328.     indicator.timeout()                                                 # immediate check, firstcheck=True
  1329.     firstcheck = False                                                  # firstcheck is over
  1330.     if cfg.get('account', 'check_once') == '0':                         # wanna do more than one email check?
  1331.         frequency = int(cfg.get('account', 'frequency'))                # get email check frequency
  1332.         gobject.timeout_add_seconds(60*frequency, indicator.timeout)    # repetitive check
  1333.         gtk.main()                                                      # start Loop
  1334.     delete_pid()                                                        # delete file 'popper.pid'
  1335.     return 0
  1336.  
  1337.  
  1338. if __name__ == '__main__': main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement