-
-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
external_script: show notification with full output on click, refresh only on button_refresh #1439
Changes from all commits
3281667
e007e27
1dcd7cf
1698c70
af22971
6d4dc12
3405dca
6db3b07
d2ab7b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -15,21 +15,68 @@ | |||||||||
format: see placeholders below (default '{output}') | ||||||||||
localize: should script output be localized (if available) | ||||||||||
(default True) | ||||||||||
notifications: specify a nested dict to send a notification if matched | ||||||||||
against the keys, see ``Notifications`` section for more information | ||||||||||
(default {}) | ||||||||||
script_path: script you want to show output of (compulsory) | ||||||||||
(default None) | ||||||||||
strip_output: shall we strip leading and trailing spaces from output | ||||||||||
(default False) | ||||||||||
|
||||||||||
Format placeholders: | ||||||||||
{output} output of script given by "script_path" | ||||||||||
{line} number of lines in the output | ||||||||||
{output} first line of the output of script given by "script_path" | ||||||||||
{output_full} full output of script given by "script_path" | ||||||||||
|
||||||||||
i3status.conf example: | ||||||||||
Notifications: | ||||||||||
Specify a nested dictionary of notification states and options to use. | ||||||||||
|
||||||||||
Notification states: | ||||||||||
'changed': display a notification only if it is changed | ||||||||||
'click': display a notification of last output on click | ||||||||||
'current': display a current notification regardless | ||||||||||
|
||||||||||
Notification options: | ||||||||||
'title': notification title | ||||||||||
'msg': notification message | ||||||||||
'level': must be 'info', 'error' or 'warning'. | ||||||||||
'rate_limit': time period in seconds not to be repeated | ||||||||||
'icon': must be an icon path or icon name. | ||||||||||
|
||||||||||
You can add `format` placeholders in `msg` and `title`. | ||||||||||
The `msg` and `title` options will also be formatted. | ||||||||||
|
||||||||||
Examples: | ||||||||||
``` | ||||||||||
# add a script | ||||||||||
external_script { | ||||||||||
format = "my name is {output}" | ||||||||||
script_path = "/usr/bin/whoami" | ||||||||||
} | ||||||||||
|
||||||||||
# display changed notifications, same output means no notification | ||||||||||
external_script { | ||||||||||
notifications = {'changed': {'msg': '{output}'}} | ||||||||||
script_path = "~/my_script.py" | ||||||||||
} | ||||||||||
|
||||||||||
# display current notifications, no output means no notification | ||||||||||
external_script { | ||||||||||
notifications = {'current': {'msg': '{output}'}} | ||||||||||
script_path = "~/my_script.py" | ||||||||||
} | ||||||||||
|
||||||||||
# display current notification only if output have more than 20 lines | ||||||||||
external_script { | ||||||||||
notifications = {'current': {'msg': '\?if=line>20 {output}'}} | ||||||||||
script_path = "~/my_script.py" | ||||||||||
} | ||||||||||
|
||||||||||
# display a notification of last full output on click | ||||||||||
external_script { | ||||||||||
notifications = {'click': {'msg': '{output_full}'}} | ||||||||||
script_path = "~/my_script.py" | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
@author frimdo [email protected] | ||||||||||
|
@@ -52,20 +99,55 @@ class Py3status: | |||||||||
cache_timeout = 15 | ||||||||||
format = '{output}' | ||||||||||
localize = True | ||||||||||
notifications = {} | ||||||||||
script_path = None | ||||||||||
strip_output = False | ||||||||||
|
||||||||||
def post_config_hook(self): | ||||||||||
if not self.script_path: | ||||||||||
raise Exception(STRING_ERROR) | ||||||||||
|
||||||||||
self.button_refresh = 2 | ||||||||||
self.notification = {'normal': [], 'click': False} | ||||||||||
if self.notifications: | ||||||||||
for x in ['changed', 'click', 'current']: | ||||||||||
self.notification[x] = self.notifications.get(x, False) | ||||||||||
if self.notification[x]: | ||||||||||
if x in ['changed', 'current']: | ||||||||||
self.notification['normal'].append(x) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still cannot grasp this logic, if notification is added for "changed", we add it to "normal"? What is normal? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With only two, it would be just I add It's a bit different here because of notifications so we check both Normal is both |
||||||||||
self.last_changed = self.py3.storage_get('changed') | ||||||||||
|
||||||||||
def _get_notification(self, state): | ||||||||||
temporary = self.notification[state].copy() | ||||||||||
for x in ['title', 'msg']: | ||||||||||
if x in temporary: | ||||||||||
temporary[x] = self.py3.get_composite_string( | ||||||||||
self.py3.safe_format(temporary[x], self.script_data) | ||||||||||
) | ||||||||||
return temporary | ||||||||||
|
||||||||||
def _notify_user(self, state=None): | ||||||||||
if state is None: | ||||||||||
if self.notification['changed']: | ||||||||||
changed = self._get_notification('changed') | ||||||||||
if changed != self.last_changed: | ||||||||||
self.last_changed = changed | ||||||||||
self.py3.storage_set('changed', changed) | ||||||||||
self.py3.notify_user(**changed) | ||||||||||
if self.notification['current']: | ||||||||||
self.py3.notify_user(**self._get_notification('current')) | ||||||||||
elif state == 'click': | ||||||||||
self.py3.notify_user(**self._get_notification('click')) | ||||||||||
|
||||||||||
def external_script(self): | ||||||||||
output_lines = None | ||||||||||
response = {} | ||||||||||
response['cached_until'] = self.py3.time_in(self.cache_timeout) | ||||||||||
try: | ||||||||||
output = self.py3.command_output(self.script_path, shell=True, localized=self.localize) | ||||||||||
output_lines = output.splitlines() | ||||||||||
output_full = self.py3.command_output( | ||||||||||
self.script_path, shell=True, localized=self.localize | ||||||||||
) | ||||||||||
output_lines = output_full.splitlines() | ||||||||||
if len(output_lines) > 1: | ||||||||||
output_color = output_lines[1] | ||||||||||
if re.search(r'^#[0-9a-fA-F]{6}$', output_color): | ||||||||||
|
@@ -96,10 +178,28 @@ def external_script(self): | |||||||||
else: | ||||||||||
output = '' | ||||||||||
|
||||||||||
self.script_data = { | ||||||||||
'line': len(output_lines), | ||||||||||
'output': output, | ||||||||||
'output_full': ' '.join(output_full.splitlines()) | ||||||||||
} | ||||||||||
response['full_text'] = self.py3.safe_format( | ||||||||||
self.format, {'output': output}) | ||||||||||
self.format, self.script_data | ||||||||||
) | ||||||||||
|
||||||||||
self.script_data['output_full'] = output_full | ||||||||||
if self.notification['normal']: | ||||||||||
self._notify_user() | ||||||||||
|
||||||||||
return response | ||||||||||
|
||||||||||
def on_click(self, event): | ||||||||||
button = event["button"] | ||||||||||
if button != self.button_refresh: | ||||||||||
if self.notification['click']: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a reminder for future self: this throws exception if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What configs? I'm not able to reproduce. The py3status/py3status/modules/external_script.py Lines 111 to 114 in c6a1c21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is my exact config:
Add this, then restart i3 (important), then click on the module. I get an error, and in
|
||||||||||
self._notify_user('click') | ||||||||||
self.py3.prevent_refresh() | ||||||||||
|
||||||||||
|
||||||||||
if __name__ == "__main__": | ||||||||||
""" | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lasers could you describe what is happening in this
if
block?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
post_config_hook
checking to find out which notifications to use... so we use a dict+list of booelans instead of repeatedly testing the conditions on each interval.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking at this right now and I think it's a bit over-engineered, both external_script and battery_level (and potentially updates) only need notifications on click (battery has extra special case of showing notification on certain threshold), is there any module that currently shows notifications always or when changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC I think battery's implementation of showing notification on clicks is a bit silly. We likely could make it show up on the bar instead. Maybe with a toggle to show/hide what we want.
Current and changed is similar. I'm not even sure if it makes sense to make a
changed
state... because we technically could makechanged
placeholder so you do\?if=changed {test}
in current state. Maybe better not to. Fleshing out a nice notification solution is hard though.wwan
on changed.github
on always (always failing). Could be inpost_config_hook
instead.battery_level
is both click and changed.pomodoro
is changed when timer reached zero. Not sure how we would add into this as it's notchanged
.Things to take from this is that we are only making notification states to allow different kind of notifications to happen in a module. I guess we could use
changed
here.graphite
seems to be always.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wwan
on changed - 👍github
is not always, it's a weird way of showing error, completely custom use of notifications and should not be generalizedbattery_level
is click and on certain thresholds, this is again custom and is not "changed" (i.e. it will not show notification when changing from 81% to 80%).pomodoro
yes, it's again not changed but some custom logic.I'm not sure how much we will be able to generalize if every module has some special use of notifications...