summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG17
-rw-r--r--README.rst5
-rw-r--r--[-rwxr-xr-x]examples/empty_class.py0
-rw-r--r--examples/i3bar_click_events.py127
-rw-r--r--examples/whoami.py26
-rwxr-xr-xpy3status/__init__.py41
-rwxr-xr-xsetup.py3
7 files changed, 215 insertions, 4 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 0dea4ce..0554746 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,20 @@
+version 1.1 (2013-12-15)
+* new generic click event handler using the special module file named i3bar_click_events.py which will be forwarded any orphan click event for action. this allows you to take action on clicks made on your i3status modules !
+* add the filename as the module_name property to the Module class
+* drop useless modes on examples
+* new example whoami displaying the currently logged in user
+* be more concrete in the i3status.conf example
+* add IRC information, join us on #py3status on freenode to share your ideas or ask for help
+* redirect stdout and stderr to null to suppress modules outputs, this prevents i3bar from frezzing when a user module prints something to stdout or stderr wrt issue #20
+* use base stdout for the output on i3bar
+* fix first event opening line wrt issue #19 thx to @lathan
+* fix delay on py3status start waiting for i3status, this caused a useless first refresh delay of py3status of i3status interval seconds, thx to @Edholm on issue #18
+* fix i3status time adjustment when format does not contain the necessary items to get an exact datetime wrt issue #18
+* fix respect user's locale for time transformation, thx to @Edholm on issue #18
+* new example module displaying Yahoo Weather forcast
+* Merge pull request #15 from alethiophile/master
+* Float update-time option, by Tom Hunt
+
version 1.0 (2013-08-30)
* total rewrite (yes, again)
* support for i3bar click_events, they're dispatched to user-written py3status classes based on their name/instance
diff --git a/README.rst b/README.rst
index a21dd3a..2c678a1 100644
--- a/README.rst
+++ b/README.rst
@@ -18,11 +18,16 @@ See the wiki for up to date documentation:
Learn how to write your own modules:
https://github.com/ultrabug/py3status/wiki/Write-your-own-modules
+Get help or share your ideas on IRC:
+ channel **#py3status** on **FreeNode**
+
Requirements
============
You **must** set the `output_format` to `i3bar` in the general section of your i3status.conf:
::
general {
+ colors = true
+ interval = 5
output_format = "i3bar"
}
diff --git a/examples/empty_class.py b/examples/empty_class.py
index 077269c..077269c 100755..100644
--- a/examples/empty_class.py
+++ b/examples/empty_class.py
diff --git a/examples/i3bar_click_events.py b/examples/i3bar_click_events.py
new file mode 100644
index 0000000..792965c
--- /dev/null
+++ b/examples/i3bar_click_events.py
@@ -0,0 +1,127 @@
+from subprocess import Popen
+from time import time
+
+
+class Py3status:
+ """
+ This module allows you to take actions based on click events made on
+ the i3status modules. For example, thanks to this module you could
+ launch the wicd GUI when clicking on the ethernet or wireless module
+ of your i3status output !
+
+ IMPORTANT:
+ This module file name is reserved and should NOT be changed if you
+ want py3status to handle your i3status modules click events !
+
+ The behavior described above will only work if this file is named
+ 'i3bar_click_events.py' !
+ """
+ def __init__(self):
+ """
+ This is where you setup your actions based on your i3status config.
+
+ Configuration:
+ --------------
+ self.actions = {
+ "<module name and instance>": {
+ <button number>: [<function to run>, <arg1>, <arg2>],
+ }
+ }
+
+ Variables:
+ ----------
+ <button number> is an integer from 1 to 3:
+ 1 : left click
+ 2 : middle click
+ 3 : right click
+
+ <function to run> is a python function written in this module.
+ The 'external_command' function is provided for convenience if
+ you want to call an external program from this module.
+ You can of course write your own functions and have them executed
+ on a click event at will with possible arguments <arg1>, <arg2>...
+
+ <module name and instance> is a string made from the module
+ attribute 'name' and 'instance' using a space as separator:
+ For i3status modules, it's simply the name of the module as it
+ appears in the 'order' instruction of your i3status.conf.
+ Example:
+ in i3status.conf -> order += "wireless wlan0"
+ self.actions key -> "wireless wlan0"
+
+ Usage example:
+ --------------
+ - Open the wicd-gtk GUI when we LEFT click on the ethernet
+ module of i3status.
+ - Open emelfm2 to /home when we LEFT click on
+ the /home instance of disk_info
+ - Open emelfm2 to / when we LEFT click on
+ the / instance of disk_info
+
+ The related i3status.conf looks like:
+ order += "disk /home"
+ order += "disk /"
+ order += "ethernet eth0"
+
+ The resulting self.actions should be:
+ self.actions = {
+ "ethernet eth0": {
+ 1: [external_command, 'wicd-gtk', '-n'],
+ },
+ "disk_info /home": {
+ 1: [external_command, 'emelfm2', '-1', '~'],
+ },
+ "disk_info /": {
+ 1: [external_command, 'emelfm2', '-1', '/'],
+ },
+ }
+ """
+ # CONFIGURE ME PLEASE, LOVE YOU BIG TIME !
+ self.actions = {
+ }
+
+ def on_click(self, i3status_output_json, i3status_config, event):
+ """
+ If an action is configured for the given i3status module 'name' and
+ 'instance' (if present), then we'll execute the given function with
+ its arguments (if present).
+
+ Usually you SHOULD NOT modify this part of the code.
+ """
+ button = event['button']
+ key_name = '{} {}'.format(
+ event['name'],
+ event.get('instance', '')
+ ).strip()
+ if key_name in self.actions and button in self.actions[key_name]:
+ # get the function to run
+ func = self.actions[key_name][button][0]
+ assert hasattr(func, '__call__'), \
+ 'first element of the action list must be a function'
+
+ # run the function with the possibly given arguments
+ func(*self.actions[key_name][button][1:])
+
+ def i3bar_click_events(self, i3status_output_json, i3status_config):
+ """
+ Cached empty output, this module doesn't show anything.
+ """
+ response = {'full_text': '', 'name': 'i3bar_click_events'}
+ response['cached_until'] = time() + 3600
+ return (-1, response)
+
+
+def external_command(*cmd):
+ """
+ This convenience function lets you call an external program at will.
+
+ NOTE:
+ The stdout and stderr MUST be suppressed as shown here to avoid any
+ output from being caught by the i3bar (this would freeze it).
+ See issue #20 for more info.
+ """
+ Popen(
+ cmd,
+ stdout=open('/dev/null', 'w'),
+ stderr=open('/dev/null', 'w')
+ )
diff --git a/examples/whoami.py b/examples/whoami.py
new file mode 100644
index 0000000..f930991
--- /dev/null
+++ b/examples/whoami.py
@@ -0,0 +1,26 @@
+from getpass import getuser
+from time import time
+
+
+class Py3status:
+ """
+ Simply output the currently logged in user in i3bar.
+
+ Inspired by i3 FAQ:
+ https://faq.i3wm.org/question/1618/add-user-name-to-status-bar/
+ """
+ def whoami(self, i3status_output_json, i3status_config):
+ """
+ We use the getpass module to get the current user.
+ """
+ # the current user doesnt change so much, cache it good
+ CACHE_TIMEOUT = 600
+
+ # here you can change the format of the output
+ # default is just to show the username
+ username = '{}'.format(getuser())
+
+ # set, cache and return the output
+ response = {'full_text': username, 'name': 'whoami'}
+ response['cached_until'] = time() + CACHE_TIMEOUT
+ return (0, response)
diff --git a/py3status/__init__.py b/py3status/__init__.py
index bd8072a..cb24d39 100755
--- a/py3status/__init__.py
+++ b/py3status/__init__.py
@@ -34,8 +34,8 @@ def print_line(line):
"""
Print given line to stdout (i3bar).
"""
- sys.stdout.write('{}\n'.format(line))
- sys.stdout.flush()
+ sys.__stdout__.write('{}\n'.format(line))
+ sys.__stdout__.flush()
class IOPoller:
@@ -252,6 +252,22 @@ class Events(Thread):
if self.config['debug']:
syslog(LOG_INFO, 'dispatching default event {}'.format(event))
+ def i3bar_click_events_module(self):
+ """
+ Detect the presence of the special i3bar_click_events.py module.
+
+ When py3status detects a module named 'i3bar_click_events.py',
+ it will dispatch i3status click events to this module so you can catch
+ them and trigger any function call based on the event.
+ """
+ for module in self.modules:
+ if not module.click_events:
+ continue
+ if module.module_name == 'i3bar_click_events.py':
+ return module
+ else:
+ return False
+
def run(self):
"""
Wait for an i3bar JSON event, then find the right module to dispatch
@@ -276,6 +292,7 @@ class Events(Thread):
# setup default action on button 2 press
default_event = False
+ dispatched = False
if 'button' in event and event['button'] == 2:
default_event = True
@@ -291,10 +308,23 @@ class Events(Thread):
if 'instance' in event:
if event['instance'] == obj['instance']:
self.dispatch(module, obj, event)
+ dispatched = True
break
else:
self.dispatch(module, obj, event)
+ dispatched = True
break
+
+ # fall back to i3bar_click_events.py module if present
+ if not dispatched:
+ module = self.i3bar_click_events_module()
+ if module:
+ if self.config['debug']:
+ syslog(
+ LOG_INFO,
+ 'dispatching event to i3bar_click_events'
+ )
+ self.dispatch(module, obj, event)
except Exception:
err = sys.exc_info()[1]
syslog(LOG_WARNING, 'event failed ({})'.format(err))
@@ -320,6 +350,7 @@ class Module(Thread):
self.lock = lock
self.methods = {}
self.module_class = None
+ self.module_name = f_name
#
self.load_methods(include_path, f_name)
@@ -389,7 +420,7 @@ class Module(Thread):
syslog(
LOG_INFO,
'module {} click_events={} has_kill={} methods={}'.format(
- f_name,
+ self.module_name,
self.click_events,
self.has_kill,
self.methods.keys()
@@ -601,6 +632,10 @@ class Py3statusWrapper():
if self.config['debug']:
syslog(LOG_INFO, 'events thread started')
+ # suppress modules' ouput wrt issue #20
+ sys.stdout = open('/dev/null', 'w')
+ sys.stderr = open('/dev/null', 'w')
+
# load and spawn modules threads
for include_path, f_name in self.list_modules():
try:
diff --git a/setup.py b/setup.py
index 9fdf349..3e12e85 100755
--- a/setup.py
+++ b/setup.py
@@ -5,6 +5,7 @@ py3status
import os
from setuptools import find_packages, setup
+
# Utility function to read the README file.
# Used for the long_description. It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
@@ -14,7 +15,7 @@ def read(fname):
setup(
name='py3status',
- version='1.0',
+ version='1.1',
author='Ultrabug',
author_email='ultrabug@ultrabug.net',
description='py3status is an extensible i3status wrapper written in python',