SHOW:
|
|
- or go back to the newest paste.
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) |
551 | + | |
552 | ser = serial.Serial(SERIAL_PORT, 115200, timeout=1) | |
553 | ||
554 | - | print "Line 1: {0}".format(line1) |
554 | + | |
555 | - | print "Line 2: {0}".format(line2) |
555 | + | print "Line 1: {0}".format(line1) |
556 | - | if backlight: |
556 | + | print "Line 2: {0}".format(line2) |
557 | - | serial_write_command(BACKLIGHT_ON) |
557 | + | if backlight: |
558 | serial_write_command(BACKLIGHT_ON) | |
559 | - | serial_write_command(BACKLIGHT_OFF) |
559 | + | |
560 | - | print "After a serial write" |
560 | + | serial_write_command(BACKLIGHT_OFF) |
561 | - | serial_write_command(BLOCK_CURSOR_OFF) |
561 | + | print "After a serial write" |
562 | - | serial_write_command(CLEAR) |
562 | + | serial_write_command(BLOCK_CURSOR_OFF) |
563 | - | serial_write(line1) |
563 | + | serial_write_command(CLEAR) |
564 | - | serial_write_command(MOVE_SECOND_LINE) |
564 | + | serial_write(line1) |
565 | - | serial_write(line2) |
565 | + | serial_write_command(MOVE_SECOND_LINE) |
566 | serial_write(line2) | |
567 | - | ser.close() |
567 | + | |
568 | ser.close() | |
569 | - | print "Could not write to {0}".format(SERIAL_PORT) |
569 | + | except Exception: |
570 | - | raise |
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() |