Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2.. todo:: Other PasswordDialogs
3"""
4import imaplib
5import socket
6import ssl
7import subprocess
8import tempfile
9import threading
10import time
11from pi3bar.plugins.base import Plugin
14class PasswordDialog(threading.Thread):
15 """
16 A Thread to run a *xterm* instance with a whiptail passwordbox in it.
18 :param title: :class:`str` - Dialog title
19 :param on_finish: :class:`lambda` - Function to process the password
20 """
21 def __init__(self, title, on_finish):
22 threading.Thread.__init__(self)
23 self.title = title
24 self.on_finish = on_finish
26 def run(self):
27 """
28 Run ``whiptail --passwordbox`` in a new *xterm*.
29 Wait for its output and pass it to ``on_finish``.
31 :return: :class:`None`
32 """
33 width = len(self.title) + 8
34 if width < 21: # minimum value
35 width = 21
37 tmp_file = tempfile.NamedTemporaryFile()
39 cmd = [
40 'xterm',
41 '-e',
42 'whiptail --passwordbox "%s" 8 %s %s' % (self.title, str(width), '2>%s' % tmp_file.name),
43 ]
45 try:
46 p = subprocess.Popen(cmd, stderr=subprocess.PIPE)
47 while p.poll() is None:
48 time.sleep(0.1)
49 value = tmp_file.read()
50 finally:
51 tmp_file.close()
53 self.on_finish(value)
56class IMAPUnread(Plugin):
57 """
58 :class:`pi3bar.app.Pi3Bar` plugin to show unread emails.
60 The password is not stored on disk!
62 Right-click shows a password dialog using `whiptail`.
64 :param server: :class:`str` - Your imap server address
65 :param username: :class:`str` - Your imap login username
66 :param port: :class:`int` - (default: 993)
68 Examples:
70 .. code-block:: python
72 IMAPUnread(host, username)
74 # change imap port (default is 993)
75 IMAPUnread(host, username, port=9993)
76 """
78 #: Refresh every 5 minutes
79 ticks = 300
81 def __init__(self, server, username, **kwargs):
82 self.password = None
83 self.server = server
84 self.username = username
85 self.port = kwargs.pop('port', 993)
86 self.instance = '%s@%s' % (self.username, self.server)
87 super(IMAPUnread, self).__init__(**kwargs)
89 def set_no_link(self):
90 """
91 Show error 'No Link'
92 """
93 self.warning('No Link')
94 self.color = '#ff0000'
95 self.full_text = 'NoLink'
96 self.short_text = 'Link'
98 def set_login_failed(self):
99 """
100 Show error 'Login failed'
101 """
102 self.color = '#ff0000'
103 self.full_text = 'Login failed'
104 self.short_text = 'login'
106 def set_no_password(self):
107 """
108 Show message 'Password?'
109 """
110 self.color = '#ffff00'
111 self.full_text = 'Password?'
112 self.short_text = 'PW?'
114 def set_unread(self, count):
115 """
116 Show username and number of unread messages
117 """
118 self.color = count and '#00ff00' or None
119 self.full_text = '%s %d' % (self.username, count)
121 def set_waiting(self):
122 """
123 Show message '...'
124 """
125 self.color = '#ffff00'
126 self.full_text = '...'
127 self.short_text = '...'
129 def cycle(self):
130 """
131 1. if no password is given: :func:`set_no_password`
132 2. Connect to server
133 3. if connection fails: :func:`set_no_link`
134 4. Login
135 5. if login fails: :func:`set_login_failed`
136 6. get number of unread messages and pass it to :func:`set_unread`
137 """
138 if self.password is None:
139 self.set_no_password()
140 return
142 socket.setdefaulttimeout(5)
143 try:
144 obj = imaplib.IMAP4_SSL(self.server, str(self.port))
145 except socket.error:
146 self.set_no_link()
147 return
148 except Exception:
149 self.error(exc_info=1)
150 return
152 try:
153 obj.login(self.username, self.password)
154 except ssl.SSLError:
155 self.warning('Login failed')
156 self.set_login_failed()
157 return
159 obj.select()
160 unread = len(obj.search(None, 'UnSeen')[1][0].split())
161 self.debug('%d unread' % unread)
162 self.set_unread(unread)
164 def open_password_dialog(self):
165 """
166 Create and run a :class:`PasswordDialog` thread instance.
168 :return: :class:`None`
169 """
170 self.set_waiting()
172 def done(pw):
173 if pw:
174 self.password = pw
175 self.rerun()
176 else:
177 self.set_login_failed()
179 title = 'Password for %s@%s' % (self.username, self.server)
180 thread = PasswordDialog(title, done)
181 thread.start()
183 def on_click(self, button, x, y):
184 """
185 Call :func:`open_password_dialog` on right-click.
187 Called by :meth:`pi3bar.io.I3BarEventThread.run`.
189 :param button: :class:`int` - 1=left, 2=middle, 3=right mouse button
190 :param x: :class:`int` - cursor position in pixel
191 :param y: :class:`int` - cursor position in pixel
192 :return: :class:`None`
193 """
194 if button == 3:
195 return self.open_password_dialog()
196 super(IMAPUnread, self).on_click(button, x, y) # allow commands on other buttons