├── .gitignore ├── LICENSE ├── README.md ├── autohidewibox.conf └── autohidewibox.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Auto-hide the awesome-wibox/taskbar 2 | If you ever wanted to squeeze out that last bit of screen real estate in awesome and 3 | only show the wibox when needed (i.e when pressing the ModKey), this is for you. 4 | 5 | Since awesome doesn't allow easy access to the states of the Super/Mod-Key itself in 6 | *rc.lua*, one cannot simply show the wibox while the ModKey is pressed and hide it again 7 | on release. 8 | This little python daemon will sit in the background and do just that. 9 | 10 | Extending away from what the name suggests it can also execute any custom lua code on 11 | hide or show, specified in the config file. 12 | 13 | ### Installation 14 | Install the `xinput` binary. The package is named `xinput` in Debian/Ubuntu and 15 | `xorg-xinput` in Arch. (Some other popular distros don't seem to readily provide this 16 | package, according to [pkgs.org](https://pkgs.org/search/?q=xinput)). 17 | 18 | Download [the python script]( 19 | https://raw.githubusercontent.com/grandchild/autohidewibox/master/autohidewibox.py) 20 | directly and put it somewhere nice–`~/.config/awesome/` seems fitting–and make it 21 | executable. 22 | 23 | Arch Linux users can also install the AUR package [autohidewibox]( 24 | https://aur.archlinux.org/packages/autohidewibox/). 25 | 26 | ### Usage 27 | ``` 28 | autohidewibox.py [configfile.conf] 29 | ``` 30 | Config files will be tried in the order 31 | * commandline parameter 32 | * `~/.config/awesome/autohidewibox.conf` 33 | * `~/.config/autohidewibox.conf` 34 | * `/etc/autohidewibox.conf` 35 | 36 | Otherwise settings default to *SuperL* and *SuperR* toggling *mywibox*. 37 | 38 | You can simply add `autohidewibox.py` to your autostart list in rc.lua. It doesn't 39 | require special permissions to run. 40 | 41 | To terminate the script, simply `killall xinput` and the script will restore the wibox 42 | and shut down. 43 | 44 | Note that if, for autostarting programs, you use that little `run_once`-script that 45 | floats around, the safeguard doesn't work and you should therefore add `killall xinput` 46 | to the autostart list before `autohidewibox.py` so the old script instance will shutdown 47 | before awesome *re*starts. 48 | 49 | ### Dependencies 50 | * xorg-xinput 51 | 52 | #### TODO 53 | * independence from xinput 54 | * leave wibox visible on run/lua_exec etc. 55 | * show wibox when mouse hits bottom of the screen 56 | * ??? 57 | 58 | #### Credits 59 | Inspired by the first idea in http://stackoverflow.com/a/21837280 . Thanks :) 60 | 61 | ### License 62 | 63 | [![License](https://img.shields.io/github/license/grandchild/autohidewibox.svg)]( 64 | https://creativecommons.org/publicdomain/zero/1.0/) 65 | 66 | You may use this code without attribution, that is without mentioning where it's from or 67 | who wrote it. I would actually prefer if you didn't mention me. You may even claim it's 68 | your own. 69 | -------------------------------------------------------------------------------- /autohidewibox.conf: -------------------------------------------------------------------------------- 1 | [autohidewibox] 2 | 3 | # Select your awesome version. 4 | # Possible values: 3, 4 5 | awesome_version=4 6 | 7 | # A comma-separated list of keys. 8 | # Some suggestions: 9 | # 133 - Meta-L 10 | # 134 - Meta-R 11 | # 37 - Ctrl-L 12 | # 105 - Ctrl-R 13 | # 66 - CapsLock 14 | super_keys=133,134 15 | 16 | # The show/hide behavior. Possible values: 17 | # transient: The wibox is only shown while a super key is pressed. 18 | # toggle: Pressing and releasing a super key (press and release) toggles 19 | # the wibox visibility. 20 | # An invalid config value here will not throw an error, the script will 21 | # simply not work. 22 | # Default = transient. 23 | mode=transient 24 | 25 | # The name of one or more (comma separated) wiboxes which to autohide. 26 | wiboxname=mywibox 27 | 28 | # Delay execution in ms. 29 | delay_show=0 30 | delay_hide=0 31 | 32 | # Custom commands to send to awesome. 33 | # Use this to call custom-defined event functions in your awesome config 34 | # (Note: You can leave 'wiboxname' above empty, or remove it completely) 35 | #custom_hide=myonhidefunc() 36 | #custom_show=myonshowfunc() 37 | 38 | # Used for debug/development purposes. Prints status information to stdout. 39 | # Possible values: 0, 1 40 | debug=0 41 | -------------------------------------------------------------------------------- /autohidewibox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import configparser 3 | import os.path as path 4 | import re 5 | import subprocess 6 | import sys 7 | import threading 8 | 9 | MODE_TRANSIENT = "transient" 10 | MODE_TOGGLE = "toggle" 11 | 12 | config = configparser.ConfigParser() 13 | try: 14 | user_awesome_conf = path.join( 15 | path.expanduser("~"), ".config/awesome/autohidewibox.conf" 16 | ) 17 | user_conf = path.join(path.expanduser("~"), ".config/autohidewibox.conf") 18 | system_conf = "/etc/autohidewibox.conf" 19 | if len(sys.argv) > 1 and path.isfile(sys.argv[1]): 20 | config.read(sys.argv[1]) 21 | elif path.isfile(user_awesome_conf): 22 | config.read(user_awesome_conf) 23 | elif path.isfile(user_conf): 24 | config.read(user_conf) 25 | else: 26 | config.read(system_conf) 27 | except configparser.MissingSectionHeaderError: 28 | pass 29 | 30 | 31 | awesome_version = config.get("autohidewibox", "awesome_version", fallback=4) 32 | super_keys = config.get("autohidewibox", "super_keys", fallback="133,134").split(",") 33 | wiboxes = config.get("autohidewibox", "wiboxname", fallback="mywibox").split(",") 34 | custom_hide = config.get("autohidewibox", "custom_hide", fallback=None) 35 | custom_show = config.get("autohidewibox", "custom_show", fallback=None) 36 | delay_show = config.getfloat("autohidewibox", "delay_show", fallback=0) 37 | delay_hide = config.getfloat("autohidewibox", "delay_hide", fallback=0) 38 | mode = config.get("autohidewibox", "mode", fallback=MODE_TRANSIENT) 39 | debug = config.getboolean("autohidewibox", "debug", fallback=False) 40 | 41 | # (remove the following line if your wibox variables have strange characters) 42 | wiboxes = [w for w in wiboxes if re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", w)] 43 | ### python>=3.4: 44 | # wiboxes = [ w for w in wiboxes if re.fullmatch("[a-zA-Z_][a-zA-Z0-9_]*", w) ] 45 | 46 | delay = {True: delay_show, False: delay_hide} 47 | delay_thread = None 48 | wibox_is_currently_visible = False 49 | waiting_for = False 50 | non_super_key_was_pressed = False 51 | cancel = threading.Event() 52 | 53 | sh_path = "" 54 | sh_potential_paths = ["/usr/bin/sh", "/bin/sh"] 55 | for p in sh_potential_paths: 56 | if path.exists(p): 57 | sh_path = p 58 | break 59 | if sh_path == "": 60 | print("Can't find sh in any of: " + ",".join(sh_potential_paths), file=sys.stderr) 61 | sys.exit(1) 62 | 63 | hide_command_v3 = "for k,v in pairs({wibox}) do v.visible = {state} end" 64 | hide_command_v4 = "for s in screen do s.{wibox}.visible = {state} end" 65 | try: 66 | hide_command = hide_command_v4 if int(awesome_version) >= 4 else hide_command_v3 67 | except ValueError: 68 | hide_command = hide_command_v4 69 | 70 | 71 | def _debug(*args): 72 | if debug: 73 | print(*args) 74 | 75 | 76 | def set_wibox_state(state=True, immediate=False): 77 | global delay_thread, waiting_for, cancel, wibox_is_currently_visible 78 | wibox_is_currently_visible = state 79 | dbg_pstate = "show" if state else "hide" 80 | if delay[not state] > 0: 81 | _debug(dbg_pstate, "delay other") 82 | if type(delay_thread) == threading.Thread and delay_thread.is_alive(): 83 | # two consecutive opposing events cancel out. second event should not be 84 | # called 85 | _debug(dbg_pstate, "delay other, thread alive -> cancel") 86 | cancel.set() 87 | return 88 | if delay[state] > 0 and not immediate: 89 | _debug(dbg_pstate + " delay same") 90 | if not (type(delay_thread) == threading.Thread and delay_thread.is_alive()): 91 | _debug(dbg_pstate, "delay same, thread dead -> start wait") 92 | waiting_for = state 93 | cancel.clear() 94 | delay_thread = threading.Thread( 95 | group=None, target=wait_delay, kwargs={"state": state} 96 | ) 97 | delay_thread.daemon = True 98 | delay_thread.start() 99 | # a second event setting the same state is silently discarded 100 | return 101 | _debug("state:", dbg_pstate) 102 | for wibox in wiboxes: 103 | subprocess.call( 104 | sh_path 105 | + " " 106 | + "-c \"echo '" 107 | + hide_command.format(wibox=wibox, state="true" if state else "false") 108 | + "' | awesome-client\"", 109 | shell=True, 110 | ) 111 | 112 | customcmd = custom_show if state else custom_hide 113 | if customcmd: 114 | subprocess.call( 115 | sh_path + " " + "-c \"echo '" + customcmd + "' | awesome-client\"", 116 | shell=True, 117 | ) 118 | 119 | 120 | def wait_delay(state=True): 121 | if not cancel.wait(delay[state] / 1000): 122 | set_wibox_state(state=state, immediate=True) 123 | 124 | 125 | try: 126 | set_wibox_state(False) 127 | 128 | proc = subprocess.Popen( 129 | ["xinput", "--test-xi2", "--root", "3"], stdout=subprocess.PIPE 130 | ) 131 | 132 | field = None 133 | key_state = None 134 | 135 | for line in proc.stdout: 136 | l = line.decode("utf-8").strip() 137 | event_match = re.match("EVENT type (\\d+) \\(.+\\)", l) 138 | detail_match = re.match("detail: (\\d+)", l) 139 | 140 | if event_match: 141 | _debug(event_match) 142 | try: 143 | field = "event" 144 | key_state = event_match.group(1) 145 | _debug("found event, waiting for detail...") 146 | except IndexError: 147 | field = None 148 | key_state = None 149 | 150 | if (field == "event") and detail_match: 151 | _debug(detail_match) 152 | try: 153 | if detail_match.group(1) in super_keys: 154 | _debug("is a super key") 155 | if key_state == "13": # press 156 | non_super_key_was_pressed = False 157 | if mode == MODE_TRANSIENT: 158 | _debug("showing wibox") 159 | set_wibox_state(True) 160 | if key_state == "14": # release 161 | if mode == MODE_TRANSIENT: 162 | _debug("hiding wibox") 163 | set_wibox_state(False) 164 | # Avoid toggling the wibox when a super key is used in 165 | # conjunction with another key. 166 | elif mode == MODE_TOGGLE and not non_super_key_was_pressed: 167 | _debug("toggling wibox") 168 | set_wibox_state(not wibox_is_currently_visible) 169 | non_super_key_was_pressed = False 170 | else: 171 | non_super_key_was_pressed = True 172 | except IndexError: 173 | _debug("Couldn't parse key_state number.") 174 | pass 175 | finally: 176 | field = None 177 | key_state = None 178 | except KeyboardInterrupt: 179 | pass 180 | finally: 181 | set_wibox_state(True, True) 182 | _debug("Shutting down") 183 | --------------------------------------------------------------------------------