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

1import datetime 

2import shlex 

3import subprocess 

4import threading 

5from pi3bar.utils import split_seconds 

6 

7 

8class Plugin(object): 

9 """ 

10 Base class for :class:`pi3bar.app.Pi3Bar` plugins. 

11 

12 :param on_click: :class:`dict` - Mapping of mouse button numbers and 

13 commands to execute when they`re clicked. (1=left, 2=middle, 3 = right) 

14 :param ticks: :class:`int` - tick rate in seconds 

15 :param full_text: :class:`str` - (default: 'Plugin') 

16 :param short_text: :class:`str` - (default: None) 

17 :param color: :class:`str` - (default: None) 

18 :param background: :class:`str` - (default: None) 

19 :param min_width: :class:`int` - (default: None) 

20 :param align: :class:`str` - (default: None) 

21 :param urgent: :class:`bool` - (default: None) 

22 :param separator: :class:`bool` - (default: None) 

23 :param separator_block_width: :class:`int` (default: None) 

24 

25 Examples: 

26 

27 .. code-block:: python 

28 

29 # Run ``network-manager`` on left-click: 

30 Plugin(on_click={1:'network-manager'}) 

31 

32 # Refresh data every 60 seconds: 

33 Plugin(ticks=60) 

34 """ 

35 

36 #: (pi3bar protocol) 

37 #: :class:`str` - full length text to show 

38 full_text = 'Plugin' 

39 

40 #: (pi3bar protocol) 

41 #: :class:`str` - show this when not enough space 

42 short_text = None 

43 

44 #: (pi3bar protocol) 

45 #: :class:`str` - html/hex starting with "#" 

46 color = None 

47 

48 #: (pi3bar protocol) 

49 #: :class:`str` - html/hex starting with "#" 

50 #: 

51 #: **new in version:** *0.2* 

52 background = None 

53 

54 #: (pi3bar protocol) 

55 #: :class:`int`, :class:`str` - minimum width to prevent shifting in pixels or a string to determine 

56 min_width = None 

57 

58 #: (pi3bar protocol) 

59 #: :class:`str` - where to align if min_width adds spaces: 'center', 'right' or 'left' (default) 

60 align = None 

61 

62 #: (pi3bar protocol) 

63 #: :class:`str` - instance 

64 instance = None 

65 

66 #: (pi3bar protocol) 

67 #: :class:`bool` - whether the current value is urgent 

68 urgent = None 

69 

70 #: (pi3bar protocol) 

71 #: :class:`bool` - show a separator 

72 separator = None 

73 

74 #: (pi3bar protocol) 

75 #: :class:`int` - separator padding in pixels (default: 9) 

76 #: use an odd number! 

77 separator_block_width = None 

78 

79 #: :class:`int` - seconds between each :meth:`cycle` call 

80 ticks = 60 

81 

82 #: :class:`dict` - mapping of mouse button numbers and shell commands to 

83 #: execute when they are clicked. 

84 click_commands = {} 

85 

86 def __init__(self, **kwargs): 

87 def assign(name, alt_name=None): 

88 """Assign attribute ``name`` if passed as kwarg. 

89 If given with alternative name ``alt_name``.""" 

90 try: 

91 v = kwargs[name] 

92 except KeyError: 

93 return # attribute not passed 

94 del kwargs[name] # should be empty at the end 

95 setattr(self, alt_name or name, v) 

96 assign('ticks') 

97 assign('on_click', 'click_commands') 

98 assign('full_text') 

99 assign('short_text') 

100 assign('color') 

101 assign('background') 

102 assign('min_width') 

103 assign('align') 

104 assign('instance') 

105 assign('urgent') 

106 assign('separator') 

107 assign('separator_block_width') 

108 

109 if kwargs: 

110 # left kwargs are unknown! raise 

111 raise AttributeError('Got invalid kwarg(s): ' + str(kwargs)) 

112 

113 self.logger = None # will be set by Pi3Bar instance 

114 self.timer = None 

115 

116 def __eq__(self, other): 

117 return isinstance(other, self.__class__) and repr(self) == repr(other) 

118 

119 def __ne__(self, other): 

120 return not self.__eq__(other) 

121 

122 def __repr__(self): 

123 """ 

124 Instance identifier 

125 """ 

126 return '<%s: %s>' % (self.name, self.instance) 

127 

128 @property 

129 def name(self): 

130 """ 

131 Plugin identifier 

132 """ 

133 return self.__class__.__name__ 

134 

135 def cycle(self): 

136 """ 

137 Overwrite and refresh attributes here 

138 """ 

139 pass 

140 

141 def run(self): 

142 """ 

143 Run a timer to call :meth:`cycle` every :attr:`ticks` seconds. 

144 """ 

145 if self.timer: 

146 self.error('cycle took longer than ticks. Cancelled.') 

147 return 

148 

149 self.timer = threading.Timer(self.ticks, self.run) 

150 self.timer.start() 

151 

152 start = datetime.datetime.now() 

153 try: 

154 self.cycle() 

155 except: 

156 self.error('Exception in cycle:', exc_info=1) 

157 finally: 

158 now = datetime.datetime.now() 

159 

160 duration = now - start 

161 hours, minutes, seconds = split_seconds(duration.seconds) 

162 

163 msg = 'cycle took %02d:%02d:%02d:%06d.' % (hours, minutes, seconds, duration.microseconds) 

164 self.debug(msg) 

165 

166 self.timer = None 

167 

168 def rerun(self): 

169 """ 

170 Cancel current timer and call `run`. 

171 """ 

172 if self.timer: 

173 self.timer.cancel() 

174 self.timer = None 

175 self.run() 

176 

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

178 """ 

179 Get the `command` for the clicked button from 

180 :attr:`click_commands` and execute it. 

181 

182 Called by :meth:`pi3bar.app.Pi3Bar.on_plugin_click`. 

183 

184 *Overwrite this in your plugin if you need dynamic commands.* 

185 """ 

186 if button in self.click_commands: 

187 command = self.click_commands[button] 

188 # run independent process 

189 subprocess.Popen(shlex.split(command), 

190 stdout=subprocess.PIPE, 

191 stdin=subprocess.PIPE, 

192 stderr=subprocess.PIPE) 

193 

194 def get_output(self): 

195 """ 

196 Build and return the dictionary ready for i3bar. 

197 """ 

198 d = dict() 

199 

200 def assign(name): 

201 """Only set if not None. Raise if required and None.""" 

202 attr = getattr(self, name) 

203 if attr is not None: 

204 d[name] = attr 

205 

206 assign('full_text') 

207 assign('short_text') 

208 assign('color') 

209 assign('background') 

210 assign('min_width') 

211 assign('align') 

212 assign('name') 

213 assign('instance') 

214 assign('urgent') 

215 assign('separator') 

216 assign('separator_block_width') 

217 return d 

218 

219 def _format_log(self, args): 

220 """ 

221 Convert ``args`` to strings, join them and return a formatted string 

222 ready for logging. 

223 

224 E.g. ``'[name] [instance] args'`` 

225 """ 

226 s = '[%s] ' % self.name 

227 if self.instance: 

228 s += '[%s] ' % self.instance 

229 return s + ' '.join(str(a) for a in args) 

230 

231 def info(self, *args, **kwargs): 

232 """ 

233 Call :meth:`_format_log` with the given ``args`` and log result string as *info*. 

234 Pass ``kwargs`` to the logger call. 

235 """ 

236 self.logger.info(self._format_log(args), **kwargs) 

237 

238 def debug(self, *args, **kwargs): 

239 """ 

240 Call :meth:`_format_log` with the given ``args`` and log result string as *debug*. 

241 Pass ``kwargs`` to the logger call. 

242 """ 

243 self.logger.debug(self._format_log(args), **kwargs) 

244 

245 def error(self, *args, **kwargs): 

246 """ 

247 Call :meth:`_format_log` with the given ``args`` and log result string as *error*. 

248 Pass ``kwargs`` to the logger call. 

249 """ 

250 self.logger.error(self._format_log(args), **kwargs) 

251 

252 def warning(self, *args, **kwargs): 

253 """ 

254 Call :meth:`_format_log` with the given ``args`` and log result string as *warning*. 

255 Pass ``kwargs`` to the logger call. 

256 """ 

257 self.logger.warning(self._format_log(args), **kwargs)