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
8class Plugin(object):
9 """
10 Base class for :class:`pi3bar.app.Pi3Bar` plugins.
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)
25 Examples:
27 .. code-block:: python
29 # Run ``network-manager`` on left-click:
30 Plugin(on_click={1:'network-manager'})
32 # Refresh data every 60 seconds:
33 Plugin(ticks=60)
34 """
36 #: (pi3bar protocol)
37 #: :class:`str` - full length text to show
38 full_text = 'Plugin'
40 #: (pi3bar protocol)
41 #: :class:`str` - show this when not enough space
42 short_text = None
44 #: (pi3bar protocol)
45 #: :class:`str` - html/hex starting with "#"
46 color = None
48 #: (pi3bar protocol)
49 #: :class:`str` - html/hex starting with "#"
50 #:
51 #: **new in version:** *0.2*
52 background = None
54 #: (pi3bar protocol)
55 #: :class:`int`, :class:`str` - minimum width to prevent shifting in pixels or a string to determine
56 min_width = None
58 #: (pi3bar protocol)
59 #: :class:`str` - where to align if min_width adds spaces: 'center', 'right' or 'left' (default)
60 align = None
62 #: (pi3bar protocol)
63 #: :class:`str` - instance
64 instance = None
66 #: (pi3bar protocol)
67 #: :class:`bool` - whether the current value is urgent
68 urgent = None
70 #: (pi3bar protocol)
71 #: :class:`bool` - show a separator
72 separator = None
74 #: (pi3bar protocol)
75 #: :class:`int` - separator padding in pixels (default: 9)
76 #: use an odd number!
77 separator_block_width = None
79 #: :class:`int` - seconds between each :meth:`cycle` call
80 ticks = 60
82 #: :class:`dict` - mapping of mouse button numbers and shell commands to
83 #: execute when they are clicked.
84 click_commands = {}
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')
109 if kwargs:
110 # left kwargs are unknown! raise
111 raise AttributeError('Got invalid kwarg(s): ' + str(kwargs))
113 self.logger = None # will be set by Pi3Bar instance
114 self.timer = None
116 def __eq__(self, other):
117 return isinstance(other, self.__class__) and repr(self) == repr(other)
119 def __ne__(self, other):
120 return not self.__eq__(other)
122 def __repr__(self):
123 """
124 Instance identifier
125 """
126 return '<%s: %s>' % (self.name, self.instance)
128 @property
129 def name(self):
130 """
131 Plugin identifier
132 """
133 return self.__class__.__name__
135 def cycle(self):
136 """
137 Overwrite and refresh attributes here
138 """
139 pass
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
149 self.timer = threading.Timer(self.ticks, self.run)
150 self.timer.start()
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()
160 duration = now - start
161 hours, minutes, seconds = split_seconds(duration.seconds)
163 msg = 'cycle took %02d:%02d:%02d:%06d.' % (hours, minutes, seconds, duration.microseconds)
164 self.debug(msg)
166 self.timer = None
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()
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.
182 Called by :meth:`pi3bar.app.Pi3Bar.on_plugin_click`.
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)
194 def get_output(self):
195 """
196 Build and return the dictionary ready for i3bar.
197 """
198 d = dict()
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
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
219 def _format_log(self, args):
220 """
221 Convert ``args`` to strings, join them and return a formatted string
222 ready for logging.
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)
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)
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)
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)
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)