View difference between Paste ID: XLDgTExy and ereUXQ2V
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()