Hide keyboard shortcuts

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 

12 

13 

14class PasswordDialog(threading.Thread): 

15 """ 

16 A Thread to run a *xterm* instance with a whiptail passwordbox in it. 

17 

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 

25 

26 def run(self): 

27 """ 

28 Run ``whiptail --passwordbox`` in a new *xterm*. 

29 Wait for its output and pass it to ``on_finish``. 

30 

31 :return: :class:`None` 

32 """ 

33 width = len(self.title) + 8 

34 if width < 21: # minimum value 

35 width = 21 

36 

37 tmp_file = tempfile.NamedTemporaryFile() 

38 

39 cmd = [ 

40 'xterm', 

41 '-e', 

42 'whiptail --passwordbox "%s" 8 %s %s' % (self.title, str(width), '2>%s' % tmp_file.name), 

43 ] 

44 

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() 

52 

53 self.on_finish(value) 

54 

55 

56class IMAPUnread(Plugin): 

57 """ 

58 :class:`pi3bar.app.Pi3Bar` plugin to show unread emails. 

59 

60 The password is not stored on disk! 

61 

62 Right-click shows a password dialog using `whiptail`. 

63 

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) 

67 

68 Examples: 

69 

70 .. code-block:: python 

71 

72 IMAPUnread(host, username) 

73 

74 # change imap port (default is 993) 

75 IMAPUnread(host, username, port=9993) 

76 """ 

77 

78 #: Refresh every 5 minutes 

79 ticks = 300 

80 

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) 

88 

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' 

97 

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' 

105 

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?' 

113 

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) 

120 

121 def set_waiting(self): 

122 """ 

123 Show message '...' 

124 """ 

125 self.color = '#ffff00' 

126 self.full_text = '...' 

127 self.short_text = '...' 

128 

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 

141 

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 

151 

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 

158 

159 obj.select() 

160 unread = len(obj.search(None, 'UnSeen')[1][0].split()) 

161 self.debug('%d unread' % unread) 

162 self.set_unread(unread) 

163 

164 def open_password_dialog(self): 

165 """ 

166 Create and run a :class:`PasswordDialog` thread instance. 

167 

168 :return: :class:`None` 

169 """ 

170 self.set_waiting() 

171 

172 def done(pw): 

173 if pw: 

174 self.password = pw 

175 self.rerun() 

176 else: 

177 self.set_login_failed() 

178 

179 title = 'Password for %s@%s' % (self.username, self.server) 

180 thread = PasswordDialog(title, done) 

181 thread.start() 

182 

183 def on_click(self, button, x, y): 

184 """ 

185 Call :func:`open_password_dialog` on right-click. 

186 

187 Called by :meth:`pi3bar.io.I3BarEventThread.run`. 

188 

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