pax_global_header00006660000000000000000000000064137500521410014510gustar00rootroot0000000000000052 comment=567a8c84a74134d832f031641c11269b2d65ecb7 python-i3ipc-2.2.1/000077500000000000000000000000001375005214100140405ustar00rootroot00000000000000python-i3ipc-2.2.1/.dockerignore000066400000000000000000000000141375005214100165070ustar00rootroot00000000000000__pycache__ python-i3ipc-2.2.1/.flake8000066400000000000000000000001501375005214100152070ustar00rootroot00000000000000[flake8] ignore= E501 E126 E402 F722 W503 per-file-ignores= */__init__.py:F401 python-i3ipc-2.2.1/.github/000077500000000000000000000000001375005214100154005ustar00rootroot00000000000000python-i3ipc-2.2.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001375005214100175635ustar00rootroot00000000000000python-i3ipc-2.2.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000001571375005214100222600ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: acrisci --- python-i3ipc-2.2.1/.gitignore000066400000000000000000000013411375005214100160270ustar00rootroot00000000000000# editor *.swp # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # Visual studio code .vscode/ .pytest_cache python-i3ipc-2.2.1/.style.yapf000066400000000000000000000000331375005214100161330ustar00rootroot00000000000000[style] column_limit = 100 python-i3ipc-2.2.1/.travis.yml000066400000000000000000000002311375005214100161450ustar00rootroot00000000000000language: minimal dist: xenial services: - docker before_install: - docker build -t i3ipc-python-test . script: - docker run -it i3ipc-python-test python-i3ipc-2.2.1/CHANGELOG.md000066400000000000000000000143721375005214100156600ustar00rootroot00000000000000# Changelog ## Version 2.2.1 Version 2.2.1 includes sway comatibility enhancements (#98) and other bugfixes. * Make the sway INPUT event subscribable (#127). * Remove the enum-compat dependency (#128). * Add examples and docs to MANIFEST.in (#132). * Add sway-specific fields to OutputReply (#137). * Add `find_by_pid()` method for sway (#142). * Add side properties to Gaps object (#143). * Add `ipc_data` attribute to objects (8947b9f). * Add all known sway properties (e6c7f1b). * Fix scratchpad for sway (f11e729). * Bug: fix crash after reload then restart (#148). ## Version 2.1.1 Version 2.1.1 includes bugfixes and new features. * Regression: emit detailed events correctly (#126) * Regression: null values in replies should be python None (#123) * Add the sway input event (#122) * Raise handler exceptions from Connection.main() (the old behavior was just to exit silently which is wrong) (#125) ## Version 2.0.1 Version 2.0.1 is a major release which adds breaking changes and major new features. i3ipc-python is now Python 3 only. This release adds the asyncio connection class. New code for Python 3.6 or greater is recommended to use this class over the old blocking connection. * (breaking) Remove python2 support (#110) * (breaking) Use the `Rect` class for `OutputReply` and `WorkspaceReply` rect attributes (cec29f9). * (breaking) Make socket-related members of the `Connection` private (4936704) - `cmd_socket`, `cmd_lock`, `sub_socket`, `sub_lock`, `MAGIC`, `_event_socket_setup()`, `_event_socket_teardown()`, `_event_socket_poll()` * (breaking) Make event-related members of the `Connection` private (8424811) - `subscribe()`, `EventType` * (breaking) Remove `GenericEvent` in favor of specific events `OutputEvent`, `ModeEvent`, and `ShutdownEvent`. * (breaking) Remove the `PropsObject` (6ddbc22) * Add asyncio support with the new `aio.Connection` class. * Add `Event` class for event subscription by enum (#59) * Add the `app_id` attribute to the `Con` class (sway only) (#113). * Get the socket path from the root window with python-xlib (#116). * Add a commands to get inputs and seats (sway only) (#115). * Add `event_state_mask` and `symbols` (sway only) to `BindingInfo` (262246d). * Add version info in `__version__.py` (ee779b). * Use real X windows in tests (4e9746c). * Run tests in a docker container (97d0455). * Add type annotations for all public members. * Completely rewrite the documentation. ## Version 1.7.1 Version 1.7.1 adds some bugfixes and features. * Add support to get socketpath from the `sway` binary (93a8f0c). * Return empty list on commands that don't return a result (cf55812). * Implement the `SHUTDOWN` event (d338889). * Implement the autoreconnect feature (fa3a813). * Make sending commands thread safe (e9fcefa). * Add `title` attribute to `Con` class (34ea24e). * Add `pid` attribute to `Con` class for sway (bd0224e). ## Version 1.6.0 Version 1.6.0 adds the following bugfixes and features: * Properly tear down subscription socket (#83) * Implement send_tick message and tick event * Add a timeout parameter to the main function * Implement GET_BINDING_MODES * Implement GET_CONFIG * Implement GET_MARKS * Fix pickling of types by fixing a _ReplyType exception (#89) * Add the sticky property ## Version 1.5.1 Version 1.5.1 adds the following bugfixes and features: * add the Connection::off() function to stop listening to events * add a timeout parameter to the main loop to terminate after some time * use SHUT_RDWR on the socket to fix some bugs with main_quit() ## Version 1.5.0 Version 1.5.0 adds the following bugfixes and features: * fix bug where floating nodes are not in the tree * add support for SWAYSOCK and other fixes for sway ## Version 1.4.0 Version 1.4.0 adds the following bugfixes and features: * Add container property 'floating' * Add container property 'focus' (the focus stack) * Add container info for window gaps * Use native byte order everywhere * Add descendents iterator to Con * Add `Con.find_instanced()` * Add documentation and tests * List descendents BFS * Allow usage from external event loops * bug: return command result in `Con.command()` ## Version 1.3.0 Version 1.3.0 adds the following bugfixes and features: - Remove python-xlib dependency by getting the socket path from i3 binary. - The `Con::command_children()` method should work properly. - Make `socket.recv()` robust against interruptions. - Change `Con::mark` to `Con::marks` for the new ipc api (might be breaking). - Add `Con::window_rect` and `Con::deco_rect` properties. - Fix encoding problems in reading README. - `Con::workspace()` returns self if it is a workspace instead of None. - Fix the ipc-shutdown event. - The library is now installed as a directory instead of a single file. - Make the main loop work in multi-threaded environments. - Add Travis CI. - Add a test suite. - Add robustness against UTF-8 errors by replacing bad UTF-8. ## Version 1.2.0 Version 1.2.0 adds the following features: - Obey I3SOCK environment variable - Add Con::find_fullscreen() - Added properties: `scratchpad_state`, `window_role` - Con::find_marked() - make pattern optional And the following bugfixes: - Fix crash on `barconfig_update` event - Use underscores to subscribe to `barconfig_update` event - Correctly put floating nodes in the `floating_nodes` list of the Con ## Version 1.1.6 Version 1.1.6 adds the following bug fixes - Use enum-compat instead of enum34 - Safely set window class and instance (fixes crashes for windows with no class or instance) ## Version 1.1.4 Version 1.1.4 includes the following enhancements and bug fixes: - Convert README to rst - fix con::command() formatting - fix searches to not crash when windows don't have the searched-for property - always set class properties - get_bar_config() defaults to the first bar id - add get_bar_config_list() ## Version 1.1.1 This version includes the following improvements: - Python 2 support - Support the `window_instance` container property - Pep8 compliance code cleanup ## Version 0.1.1 This version contains the following feature enhancements: - Bump required i3ipc-GLib to version 0.1.1 - Connection `main` method quits the main loop when the ipc shuts down ## Version 0.0.1 This is the initial release of i3ipc-python, an improved Python library to control [i3wm](http://i3wm.org). python-i3ipc-2.2.1/Dockerfile000066400000000000000000000020151375005214100160300ustar00rootroot00000000000000FROM ubuntu:19.10 WORKDIR /app RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential git automake autotools-dev libev-dev libxcb1-dev \ libxcb-util-dev ca-certificates libxkbcommon-dev libxkbcommon-x11-dev \ libyajl-dev libstartup-notification0-dev libxcb-xinerama0-dev \ libxcb-randr0-dev libxcb-shape0-dev libxcb-cursor-dev libxcb-keysyms1-dev \ libxcb-icccm4-dev libxcb-xrm-dev libpcre3-dev libpango1.0-dev \ libpangocairo-1.0-0 xvfb python3-pip && \ rm -rf /var/lib/apt/lists/* RUN pip3 install python-xlib pytest pytest-asyncio pytest-timeout RUN git clone https://github.com/i3/i3 && \ cd ./i3 && \ git checkout cf505ea && \ autoreconf -fi && \ ./configure --prefix=/usr && \ cd ./x86_64-pc-linux-gnu && \ make -j8 && \ make install ADD . /app CMD ["./run-tests.py"] python-i3ipc-2.2.1/LICENSE000066400000000000000000000027121375005214100150470ustar00rootroot00000000000000Copyright (c) 2015, Tony Crisci All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-i3ipc-2.2.1/MANIFEST.in000066400000000000000000000004161375005214100155770ustar00rootroot00000000000000include README.rst LICENSE CHANGELOG.md pytest.ini requirements.txt .flake8 .style.yapf include Makefile include run-tests.py recursive-include test *.py include test/i3.config recursive-include examples *.md recursive-include examples *.py recursive-include docs *.rst python-i3ipc-2.2.1/Makefile000066400000000000000000000012441375005214100155010ustar00rootroot00000000000000.PHONY: test format lint all clean publish docs coverage docker-test .DEFAULT_GOAL := all source_dirs = i3ipc test examples lint: flake8 $(source_dirs) format: yapf -rip $(source_dirs) test: ./run-tests.py docker-test: docker build -t i3ipc-python-test . docker run -it i3ipc-python-test clean: rm -rf dist i3ipc.egg-info build docs/_build rm -rf `find -type d -name __pycache__` publish: python3 setup.py sdist bdist_wheel python3 -m twine upload --repository-url https://upload.pypi.org/legacy/ dist/* docs: sphinx-build docs docs/_build/html livedocs: sphinx-autobuild docs docs/_build/html --watch i3ipc -i '*swp' -i '*~' all: format lint docker-test python-i3ipc-2.2.1/README.rst000066400000000000000000000076551375005214100155440ustar00rootroot00000000000000i3ipc-python ============ An improved Python library to control `i3wm `__ and `sway `__. About ----- i3's interprocess communication (or `ipc `__) is the interface i3wm uses to receive `commands `__ from client applications such as ``i3-msg``. It also features a publish/subscribe mechanism for notifying interested parties of window manager events. i3ipc-python is a Python library for controlling the window manager. This project is intended to be useful for general scripting, and for applications that interact with the window manager like status line generators, notification daemons, and window pagers. If you have an idea for a script to extend i3wm, you can add your script to the `examples folder `__. For details on how to use the library, see the `reference documentation `__. Installation ------------ i3ipc is on `PyPI `__. ``pip3 install i3ipc`` Example ------- .. code:: python3 from i3ipc import Connection, Event # Create the Connection object that can be used to send commands and subscribe # to events. i3 = Connection() # Print the name of the focused window focused = i3.get_tree().find_focused() print('Focused window %s is on workspace %s' % (focused.name, focused.workspace().name)) # Query the ipc for outputs. The result is a list that represents the parsed # reply of a command like `i3-msg -t get_outputs`. outputs = i3.get_outputs() print('Active outputs:') for output in filter(lambda o: o.active, outputs): print(output.name) # Send a command to be executed synchronously. i3.command('focus left') # Take all fullscreen windows out of fullscreen for container in i3.get_tree().find_fullscreen(): container.command('fullscreen') # Print the names of all the containers in the tree root = i3.get_tree() print(root.name) for con in root: print(con.name) # Define a callback to be called when you switch workspaces. def on_workspace_focus(self, e): # The first parameter is the connection to the ipc and the second is an object # with the data of the event sent from i3. if e.current: print('Windows on this workspace:') for w in e.current.leaves(): print(w.name) # Dynamically name your workspaces after the current window class def on_window_focus(i3, e): focused = i3.get_tree().find_focused() ws_name = "%s:%s" % (focused.workspace().num, focused.window_class) i3.command('rename workspace to "%s"' % ws_name) # Subscribe to events i3.on(Event.WORKSPACE_FOCUS, on_workspace_focus) i3.on(Event.WINDOW_FOCUS, on_window_focus) # Start the main loop and wait for events to come in. i3.main() Asyncio Support --------------- Support for asyncio is included in the ``i3ipc.aio`` package. The interface is similar to the blocking interface but the methods that interact with the socket are coroutines. .. code:: python3 from i3ipc.aio import Connection from i3ipc import Event import asyncio async def main(): def on_window(self, e): print(e) c = await Connection(auto_reconnect=True).connect() workspaces = await c.get_workspaces() c.on(Event.WINDOW, on_window) await c.main() asyncio.get_event_loop().run_until_complete(main()) Contributing ------------ Development happens on `Github `__. Please feel free to report bugs, request features or add examples by submitting a pull request. License ------- This work is available under a BSD-3-Clause license (see LICENSE). Copyright © 2015, Tony Crisci All rights reserved. python-i3ipc-2.2.1/docs/000077500000000000000000000000001375005214100147705ustar00rootroot00000000000000python-i3ipc-2.2.1/docs/.gitignore000066400000000000000000000007651375005214100167700ustar00rootroot00000000000000# sphinx build folder _build # Compiled source # ################### *.com *.class *.dll *.exe *.o *.so # Packages # ############ # it's better to unpack these files and commit the raw source # git has its own built in compression methods *.7z *.dmg *.gz *.iso *.jar *.rar *.tar *.zip # Logs and databases # ###################### *.log *.sql *.sqlite # OS generated files # ###################### .DS_Store? ehthumbs.db Icon? Thumbs.db # Editor backup files # ####################### *~ _build python-i3ipc-2.2.1/docs/Makefile000066400000000000000000000172071375005214100164370ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " serve to make HTML files and serve them, and rebuild on file changes" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/i3ipc-python.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/i3ipc-python.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/i3ipc-python" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/i3ipc-python" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." .PHONY: serve serve: sphinx-autobuild -b html $(ALLSPINXOPTS) --watch ../i3ipc . $(BUILDDIR)/html python-i3ipc-2.2.1/docs/_static/000077500000000000000000000000001375005214100164165ustar00rootroot00000000000000python-i3ipc-2.2.1/docs/_static/.gitignore000066400000000000000000000000001375005214100203740ustar00rootroot00000000000000python-i3ipc-2.2.1/docs/aio-con.rst000066400000000000000000000001451375005214100170470ustar00rootroot00000000000000aio.Con ======= .. autoclass:: i3ipc.aio.Con :members: :undoc-members: :inherited-members: python-i3ipc-2.2.1/docs/aio-connection.rst000066400000000000000000000001431375005214100204250ustar00rootroot00000000000000aio.Connection ============== .. autoclass:: i3ipc.aio.Connection :members: :undoc-members: python-i3ipc-2.2.1/docs/con.rst000066400000000000000000000002701375005214100163000ustar00rootroot00000000000000Con === .. autoclass:: i3ipc.Con :members: :undoc-members: .. autoclass:: i3ipc.Rect :members: :undoc-members: .. autoclass:: i3ipc.Gaps :members: :undoc-members: python-i3ipc-2.2.1/docs/conf.py000066400000000000000000000233551375005214100162770ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # i3ipc-python documentation build configuration file, created by # sphinx-quickstart on Mon Sep 5 16:46:27 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..')) import i3ipc # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc', 'sphinxcontrib.asyncio', 'sphinxcontrib.fulltoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'i3ipc-python' copyright = i3ipc.__copyright__ author = i3ipc.__author__ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = i3ipc.__version__ # The full version, including alpha/beta/rc tags. release = i3ipc.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = 'i3ipc-python v1.2.0' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'i3ipc-pythondoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'i3ipc-python.tex', 'i3ipc-python Documentation', 'Tony Crisci', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'i3ipc-python', 'i3ipc-python Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'i3ipc-python', 'i3ipc-python Documentation', author, 'i3ipc-python', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False python-i3ipc-2.2.1/docs/connection.rst000066400000000000000000000001271375005214100176610ustar00rootroot00000000000000Connection ========== .. autoclass:: i3ipc.Connection :members: :undoc-members: python-i3ipc-2.2.1/docs/events.rst000066400000000000000000000013501375005214100170250ustar00rootroot00000000000000Events ====== .. autoclass:: i3ipc.Event :members: :undoc-members: .. autoclass:: i3ipc.WorkspaceEvent :members: :undoc-members: .. autoclass:: i3ipc.WindowEvent :members: :undoc-members: .. autoclass:: i3ipc.BarconfigUpdateEvent :members: :undoc-members: .. autoclass:: i3ipc.BindingInfo :members: :undoc-members: .. autoclass:: i3ipc.BindingEvent :members: :undoc-members: .. autoclass:: i3ipc.TickEvent :members: :undoc-members: .. autoclass:: i3ipc.ModeEvent :members: :undoc-members: .. autoclass:: i3ipc.OutputEvent :members: :undoc-members: .. autoclass:: i3ipc.ShutdownEvent :members: :undoc-members: .. autoclass:: i3ipc.InputEvent :members: :undoc-members: python-i3ipc-2.2.1/docs/index.rst000066400000000000000000000075131375005214100166370ustar00rootroot00000000000000i3ipc-python Documentation ========================== .. module:: i3ipc .. toctree:: :maxdepth: 2 :caption: Reference: connection con aio-connection aio-con events replies .. codeauthor:: acrisci Overview ++++++++ i3ipc-python is a library for controlling `i3 window manager `_ and `sway `_. i3 users can use this library to create their own plugin scripts to customize their desktop or integrate i3 into other applications. With this library, you can query the state of the window manager, listen to events, and send commands to i3 to perform window manager actions such as focusing or closing windows. The main entry point into the features of the library is the :class:`Connection ` class. This class manages a Unix socket connection to the ipc interface exposed by the window manager. By default, the ``Connection`` will attempt to connect to the running instance of i3 by using information present in the environment or the running X11 display. .. code-block:: python3 from i3ipc.aio import Connection i3 = await Connection().connect() You can use the ``Connection`` to query window manager state such as the names of the workspaces and outputs. .. code-block:: python3 workspaces = await i3.get_workspaces() outputs = await i3.get_outputs() for workspace in workspaces: print(f'workspace: {workspace.name}') for output in outputs: print(f'output: {output.name}') You can use it to send commands to i3 to control the window manager in an automated fashion with the same command syntax as the i3 config or ``i3-msg``. .. code-block:: python3 await i3.command('workspace 5') await i3.command('focus left') await i3.command('kill') You can use it to query the windows to find specific applications, get information about their windows, and send them window manager commands. The i3 layout tree is represented by the :class:`Con ` class. .. code-block:: python3 # get_tree() returns the root container tree = await i3.get_tree() # get some information about the focused window focused = tree.find_focused() print(f'Focused window: {focused.name}') workspace = focused.workspace() print(f'Focused workspace: {workspace.name}') # focus firefox and set it to fullscreen mode ff = tree.find_classed('Firefox')[0] await ff.command('focus') await ff.command('fullscreen') # iterate through all the container windows (or use tree.leaves() for just # application windows) for container in workspace: print(f'On the focused workspace: {container.name}') And you can use it to subscribe to window manager events and call a handler when they occur. .. code-block:: python3 from i3ipc import Event def on_new_window(i3, e): print(f'a new window opened: {e.container.name}') def on_workspace_focus(i3, e): print(f'workspace just got focus: {e.current.name}') i3.on(Event.WINDOW_NEW, on_new_window) i3.on(Event.WORKSPACE_FOCUS, on_workspace_focus) await i3.main() For more examples, see the `examples `_ folder in the repository for useful scripts people have contributed. Installation ++++++++++++ This library is available on PyPi as `i3ipc `_. .. code-block:: bash pip3 install i3ipc Contributing ++++++++++++ Development for this library happens on `Github `_. Report bugs or request features there. Contributions are welcome. License ++++++++ This library is available under a `BSD-3-Clause License `_. © 2015, Tony Crisci Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-i3ipc-2.2.1/docs/replies.rst000066400000000000000000000011451375005214100171660ustar00rootroot00000000000000Replies ======= .. autoclass:: i3ipc.CommandReply :members: :undoc-members: .. autoclass:: i3ipc.VersionReply :members: :undoc-members: .. autoclass:: i3ipc.BarConfigReply :members: :undoc-members: .. autoclass:: i3ipc.OutputReply :members: :undoc-members: .. autoclass:: i3ipc.InputReply :members: :undoc-members: .. autoclass:: i3ipc.SeatReply :members: :undoc-members: .. autoclass:: i3ipc.WorkspaceReply :members: :undoc-members: .. autoclass:: i3ipc.TickReply :members: :undoc-members: .. autoclass:: i3ipc.ConfigReply :members: :undoc-members: python-i3ipc-2.2.1/docs/serve.sh000077500000000000000000000001371375005214100164540ustar00rootroot00000000000000#!/usr/bin/env bash set -e make clean html cd _build/html python3 -m http.server -b 127.0.0.1 python-i3ipc-2.2.1/examples/000077500000000000000000000000001375005214100156565ustar00rootroot00000000000000python-i3ipc-2.2.1/examples/README.md000066400000000000000000000004661375005214100171430ustar00rootroot00000000000000# i3ipc-python Examples You can contribute examples by adding your python scripts to this folder. If your script is useful, someone might help you improve and maintain it. You can also request a new script by [posting a script request](https://github.com/altdesktop/i3ipc-python/issues) on the issue tracker. python-i3ipc-2.2.1/examples/app-on-ws-init.py000077500000000000000000000021251375005214100210150ustar00rootroot00000000000000#!/usr/bin/env python3 # https://faq.i3wm.org/question/3699/how-can-i-open-an-application-when-i-open-a-certain-workspace-for-the-first-time/ from argparse import ArgumentParser import i3ipc i3 = i3ipc.Connection() parser = ArgumentParser(description="""Open the given application each time the given workspace is created. For instance, running 'app-on-ws-init.py 6 i3-sensible-terminal' should open your terminal as soon as you create the workspace 6. """) parser.add_argument('--workspace', metavar='WS_NAME', nargs='+', required=True, help='The name of the workspaces to run the command on') parser.add_argument('--command', metavar='CMD', required=True, help='The command to run on the newly initted workspace') args = parser.parse_args() def on_workspace(i3, e): if e.current.name in args.workspace and not len(e.current.leaves()): i3.command('exec {}'.format(args.command)) i3.on('workspace::focus', on_workspace) i3.main() python-i3ipc-2.2.1/examples/asyncio-i3status-wrapper.py000077500000000000000000000117371375005214100231440ustar00rootroot00000000000000#!/usr/bin/env python3 # This module is an example of how to use i3ipc with asyncio event loop. It # implements an i3status wrapper that handles a special keybinding to switch # keyboard layout, while also displaying current layout in i3bar. # # The keyboard layout switcher can be activated by adding something like this # to i3 config: # # bindsym KEYS nop switch_layout import asyncio import collections import json import subprocess import sys import tempfile import i3ipc configure_i3_status = False try: # Unfortunately i3status does not have a simple way to set the # output_format outside of its configuration file. If not set, it will # guess the output format in a very hacky way by looking at the parent # process name which is horrible for embedders. So, we try to "fool" it # into using i3bar output format by changing the process title with # setproctitle module (install with pip3 install --user setproctitle). # Of course, this is not needed if output_format is set explicitly in the # config file. This is only done for demonstration purposes. import setproctitle setproctitle.setproctitle('i3bar') except ImportError: # Configure i3status by explicitly setting "i3bar" as output_format configure_i3_status = True I3STATUS_CFG = ''' general { output_format = "i3bar" colors = true interval = 5 } order += "disk /" order += "load" order += "tztime local" tztime local { format = "%Y-%m-%d %H:%M:%S" } load { format = "%1min" } disk "/" { format = "%avail" } ''' class Status(object): def __init__(self): self.current_status = collections.OrderedDict() # the first write does not contain a leading newline since it # represents the first item in a json array. self.first_write = True self.layouts = ['us', 'us intl'] self.current_layout = -1 self.command_handlers = {'switch_layout': lambda: self.switch_layout()} # perform a switch now, which will force the keyboard layout to be # shown before other data self.switch_layout() def switch_layout(self): self.current_layout = (self.current_layout + 1) % len(self.layouts) new_layout = self.layouts[self.current_layout] subprocess.call('setxkbmap {}'.format(new_layout), shell=True) self.update([{'name': 'keyboard_layout', 'markup': 'none', 'full_text': new_layout}]) def dispatch_command(self, command): c = command.split(' ') if (len(c) < 2 or c[0] != 'nop' or c[1] not in self.command_handlers): return self.command_handlers[c[1]]() self.repaint() def merge(self, status_update): for item in status_update: self.current_status[item['name']] = item def update(self, new_status): self.merge(new_status) def repaint(self): template = '{}' if self.first_write else ',{}' self.first_write = False sys.stdout.write( template.format( json.dumps([item for item in self.current_status.values() if item], separators=(',', ':')))) sys.stdout.write('\n') sys.stdout.flush() @asyncio.coroutine def i3status_reader(self): def handle_i3status_payload(line): self.update(json.loads(line)) self.repaint() if configure_i3_status: # use a custom i3 status configuration to ensure we get json output cfg_file = tempfile.NamedTemporaryFile(mode='w+b') cfg_file.write(I3STATUS_CFG.encode('utf8')) cfg_file.flush() create = asyncio.create_subprocess_exec('i3status', '-c', cfg_file.name, stdout=asyncio.subprocess.PIPE) else: create = asyncio.create_subprocess_exec('i3status', stdout=asyncio.subprocess.PIPE) i3status = yield from create # forward first line, version information sys.stdout.write((yield from i3status.stdout.readline()).decode('utf8')) # forward second line, an opening list bracket (no idea why this # exists) sys.stdout.write((yield from i3status.stdout.readline()).decode('utf8')) # third line is a json payload handle_i3status_payload((yield from i3status.stdout.readline()).decode('utf8')) while True: # all subsequent lines are json payload with a leading comma handle_i3status_payload((yield from i3status.stdout.readline()).decode('utf8')[1:]) status = Status() i3 = i3ipc.Connection() i3.on('binding::run', lambda i3, e: status.dispatch_command(e.binding.command)) i3.event_socket_setup() loop = asyncio.get_event_loop() loop.add_reader(i3.sub_socket, lambda: i3.event_socket_poll()) loop.run_until_complete(status.i3status_reader()) python-i3ipc-2.2.1/examples/command-on-exit.py000066400000000000000000000005631375005214100212330ustar00rootroot00000000000000#!/usr/bin/env python3 # This example shows how to run a command when i3 exits # # https://faq.i3wm.org/question/3468/run-a-command-when-i3-exits/ # This is the command to run COMMAND = ['echo', 'hello, world'] from subprocess import Popen import i3ipc def on_shutdown(i3): Popen(COMMAND) i3 = i3ipc.Connection() i3.on('ipc_shutdown', on_shutdown) i3.main() python-i3ipc-2.2.1/examples/disable-standby-fs.py000066400000000000000000000021411375005214100217010ustar00rootroot00000000000000#!/usr/bin/env python3 from argparse import ArgumentParser from subprocess import call import i3ipc i3 = i3ipc.Connection() parser = ArgumentParser(prog='disable-standby-fs', description=''' Disable standby (dpms) and screensaver when a window becomes fullscreen or exits fullscreen-mode. Requires `xorg-xset`. ''') args = parser.parse_args() def find_fullscreen(con): # XXX remove me when this method is available on the con in a release return [c for c in con.descendents() if c.type == 'con' and c.fullscreen_mode] def set_dpms(state): if state: print('setting dpms on') call(['xset', 's', 'on']) call(['xset', '+dpms']) else: print('setting dpms off') call(['xset', 's', 'off']) call(['xset', '-dpms']) def on_fullscreen_mode(i3, e): set_dpms(not len(find_fullscreen(i3.get_tree()))) def on_window_close(i3, e): if not len(find_fullscreen(i3.get_tree())): set_dpms(True) i3.on('window::fullscreen_mode', on_fullscreen_mode) i3.on('window::close', on_window_close) i3.main() python-i3ipc-2.2.1/examples/floating-mode.py000066400000000000000000000011031375005214100207500ustar00rootroot00000000000000#!/usr/bin/env python3 # budRich@budlabs - 2019 # # this will make all new windows floating # due to the way i3 handles for_window rules # setting a "global rule" to make all new # windows floating, may have undesired side effects. # # https://github.com/i3/i3/issues/3628 # https://github.com/i3/i3/pull/3188 # https://old.reddit.com/r/i3wm/comments/85ctji/when_windows_are_floating_by_default_how_do_i/ from i3ipc import Connection i3 = Connection() def set_floating(i3, event): event.container.command('floating enable') i3.on('window::new', set_floating) i3.main() python-i3ipc-2.2.1/examples/focus-last.py000077500000000000000000000070061375005214100203160ustar00rootroot00000000000000#!/usr/bin/env python3 import os import socket import selectors import tempfile import threading from argparse import ArgumentParser import i3ipc SOCKET_DIR = '{}/i3_focus_last.{}{}'.format(tempfile.gettempdir(), os.geteuid(), os.getenv("DISPLAY")) SOCKET_FILE = '{}/socket'.format(SOCKET_DIR) MAX_WIN_HISTORY = 15 class FocusWatcher: def __init__(self): self.i3 = i3ipc.Connection() self.i3.on('window::focus', self.on_window_focus) # Make a directory with permissions that restrict access to # the user only. os.makedirs(SOCKET_DIR, mode=0o700, exist_ok=True) self.listening_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if os.path.exists(SOCKET_FILE): os.remove(SOCKET_FILE) self.listening_socket.bind(SOCKET_FILE) self.listening_socket.listen(1) self.window_list = [] self.window_list_lock = threading.RLock() def on_window_focus(self, i3conn, event): with self.window_list_lock: window_id = event.container.id if window_id in self.window_list: self.window_list.remove(window_id) self.window_list.insert(0, window_id) if len(self.window_list) > MAX_WIN_HISTORY: del self.window_list[MAX_WIN_HISTORY:] def launch_i3(self): self.i3.main() def launch_server(self): selector = selectors.DefaultSelector() def accept(sock): conn, addr = sock.accept() selector.register(conn, selectors.EVENT_READ, read) def read(conn): data = conn.recv(1024) if data == b'switch': with self.window_list_lock: tree = self.i3.get_tree() windows = set(w.id for w in tree.leaves()) for window_id in self.window_list[1:]: if window_id not in windows: self.window_list.remove(window_id) else: self.i3.command('[con_id=%s] focus' % window_id) break elif not data: selector.unregister(conn) conn.close() selector.register(self.listening_socket, selectors.EVENT_READ, accept) while True: for key, event in selector.select(): callback = key.data callback(key.fileobj) def run(self): t_i3 = threading.Thread(target=self.launch_i3) t_server = threading.Thread(target=self.launch_server) for t in (t_i3, t_server): t.start() if __name__ == '__main__': parser = ArgumentParser(prog='focus-last.py', description=''' Focus last focused window. This script should be launch from the .xsessionrc without argument. Then you can bind this script with the `--switch` option to one of your i3 keybinding. ''') parser.add_argument('--switch', dest='switch', action='store_true', help='Switch to the previous window', default=False) args = parser.parse_args() if not args.switch: focus_watcher = FocusWatcher() focus_watcher.run() else: client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client_socket.connect(SOCKET_FILE) client_socket.send(b'switch') client_socket.close() python-i3ipc-2.2.1/examples/focus-next-visible.py000066400000000000000000000030651375005214100217620ustar00rootroot00000000000000#!/usr/bin/env python3 """ focus-next-visible.py - cycles input focus between visible windows on workspace - requires the `xprop` utility Usage: # focus next visible window bindsym $mod+n exec --no-startup-id focus-next-visible.py # focus previous visible window bindsym $mod+Shift+n exec --no-startup-id focus-next-visible.py reverse https://faq.i3wm.org/question/6937/move-focus-from-tabbed-container-to-win... """ from sys import argv from itertools import cycle from subprocess import check_output import i3ipc def get_windows_on_ws(conn): return filter(lambda x: x.window, conn.get_tree().find_focused().workspace().descendents()) def find_visible_windows(windows_on_workspace): visible_windows = [] for w in windows_on_workspace: try: xprop = check_output(['xprop', '-id', str(w.window)]).decode() except FileNotFoundError: raise SystemExit("The `xprop` utility is not found!" " Please install it and retry.") if '_NET_WM_STATE_HIDDEN' not in xprop: visible_windows.append(w) return visible_windows if __name__ == '__main__': conn = i3ipc.Connection() visible_windows = find_visible_windows(get_windows_on_ws(conn)) if len(argv) > 1 and argv[1] == "reverse": cycle_windows = cycle(reversed(visible_windows)) else: cycle_windows = cycle(visible_windows) for window in cycle_windows: if window.focused: focus_to = next(cycle_windows) conn.command('[id="%d"] focus' % focus_to.window) break python-i3ipc-2.2.1/examples/focused-windows.py000066400000000000000000000014261375005214100213530ustar00rootroot00000000000000#!/usr/bin/env python3 from argparse import ArgumentParser import i3ipc i3 = i3ipc.Connection() def focused_windows(): tree = i3.get_tree() workspaces = tree.workspaces() for workspace in workspaces: container = workspace while container: if not hasattr(container, 'focus') or not container.focus: break container_id = container.focus[0] container = container.find_by_id(container_id) if container: coname = container.name wsname = workspace.name print('WS', wsname + ':', coname) if __name__ == '__main__': parser = ArgumentParser(description='Print the names of the focused window of each workspace.') parser.parse_args() focused_windows() python-i3ipc-2.2.1/examples/i3-cmd.py000077500000000000000000000034721375005214100173150ustar00rootroot00000000000000#!/usr/bin/python3 import i3ipc from argparse import ArgumentParser from subprocess import check_output, Popen, CalledProcessError from sys import exit from os.path import basename history = [] parser = ArgumentParser(prog='i3-cmd', description=''' i3-cmd is a dmenu-based script that sends the given command to i3. ''', epilog=''' Additional arguments after "--" will be passed to the menu command. ''') parser.add_argument('--menu', default='dmenu', help='The menu command to run (ex: --menu=rofi)') try: with open('/tmp/i3-cmd-history') as f: history = f.read().split('\n') except FileNotFoundError: pass i3 = i3ipc.Connection() (args, menu_args) = parser.parse_known_args() if len(menu_args) and menu_args[0] == '--': menu_args = menu_args[1:] # set default menu args for supported menus if basename(args.menu) == 'dmenu': menu_args += ['-i', '-f'] elif basename(args.menu) == 'rofi': menu_args += ['-show', '-dmenu', '-p', 'i3-cmd: '] cmd = '' try: cmd = check_output([args.menu] + menu_args, input=bytes('\n'.join(history), 'UTF-8')).decode('UTF-8').strip() except CalledProcessError as e: exit(e.returncode) if not cmd: # nothing to do exit(0) result = i3.command(cmd) cmd_success = True for r in result: if not r.success: cmd_success = False Popen(['notify-send', 'i3-cmd error', r.error]) if cmd_success: with open('/tmp/i3-cmd-history', 'w') as f: try: history.remove(cmd) except ValueError: pass history.insert(0, cmd) f.write('\n'.join(history)) python-i3ipc-2.2.1/examples/i3-container-commander.py000066400000000000000000000076721375005214100225020ustar00rootroot00000000000000#!/usr/bin/env python3 # This example shows how to implement a simple, but highly configurable window # switcher (like a much improved "alt-tab") with iterative dmenu calls. This # script works well for most use cases with no arguments. # # https://faq.i3wm.org/question/228/how-do-i-find-an-app-buried-in-some-workspace-by-its-title/ from argparse import ArgumentParser from subprocess import check_output from os.path import basename import i3ipc i3 = i3ipc.Connection() parser = ArgumentParser(prog='i3-container-commander.py', description=''' i3-container-commander.py is a simple but highly configurable dmenu-based script for creating dynamic context-based commands for controlling top-level windows. With no arguments, it is an efficient and ergonomical window switcher. ''', epilog=''' Additional arguments found after "--" will be passed to dmenu. ''') parser.add_argument('--group-by', metavar='PROPERTY', default='window_class', help='''A container property to initially group windows for selection or "none" to skip the grouping step. This works best for properties of type string. See for a list of properties. (default: "window_class")''') parser.add_argument('--command', metavar='COMMAND', default='focus', help='''The command to execute on the container that you end up selecting. The command should be a single command or comma-separated list such as what is passed to i3-msg. The command will only affect the selected container (it will be selected by criteria). (default: "focus")''') parser.add_argument('--item-format', metavar='FORMAT_STRING', default='{workspace.name}: {container.name}', help='''A Python format string to use to display the menu items. The format string will have the container and workspace available as template variables. (default: '{workspace.name}: {container.name}') ''') parser.add_argument('--menu', default='dmenu', help='The menu command to run (ex: --menu=rofi)') (args, menu_args) = parser.parse_known_args() if len(menu_args) and menu_args[0] == '--': menu_args = menu_args[1:] # set default menu args for supported menus if basename(args.menu) == 'dmenu': menu_args += ['-i', '-f'] elif basename(args.menu) == 'rofi': menu_args += ['-show', '-dmenu'] def find_group(container): return str(getattr(container, args.group_by)) if args.group_by != 'none' else '' def show_menu(items, prompt): menu_input = bytes(str.join('\n', items), 'UTF-8') menu_cmd = [args.menu] + ['-l', str(len(items)), '-p', prompt] + menu_args menu_result = check_output(menu_cmd, input=menu_input) return menu_result.decode('UTF-8').strip() def show_container_menu(containers): def do_format(c): return args.item_format.format(workspace=c.workspace(), container=c) items = [do_format(c) for c in containers] items.sort() menu_result = show_menu(items, args.command) for c in containers: if do_format(c) == menu_result: return c containers = i3.get_tree().leaves() if args.group_by: groups = dict() for c in containers: g = find_group(c) if g: groups[g] = groups[g] + 1 if g in groups else 1 if len(groups) > 1: chosen_group = show_menu(['{} ({})'.format(k, v) for k, v in groups.items()], args.group_by) chosen_group = chosen_group[:chosen_group.rindex(' ')] containers = list(filter(lambda c: find_group(c) == chosen_group, containers)) if len(containers): chosen_container = containers[0] if len(containers) == 1 else show_container_menu(containers) if chosen_container: chosen_container.command(args.command) python-i3ipc-2.2.1/examples/i3-cycle-focus.py000066400000000000000000000161621375005214100207630ustar00rootroot00000000000000#!/usr/bin/env python3 # # provides alt+tab functionality between windows, switching # between n windows; example i3 conf to use: # exec_always --no-startup-id i3-cycle-focus.py --history 2 # bindsym $mod1+Tab exec --no-startup-id i3-cycle-focus.py --switch import os import socket import selectors import threading from argparse import ArgumentParser import i3ipc SOCKET_FILE = '/tmp/.i3-cycle-focus.sock' MAX_WIN_HISTORY = 16 UPDATE_DELAY = 2.0 def on_shutdown(i3_conn, e): os._exit(0) class FocusWatcher: def __init__(self): self.i3 = i3ipc.Connection() self.i3.on('window::focus', self.on_window_focus) self.i3.on('shutdown', on_shutdown) self.listening_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if os.path.exists(SOCKET_FILE): os.remove(SOCKET_FILE) self.listening_socket.bind(SOCKET_FILE) self.listening_socket.listen(1) self.window_list = [] self.window_list_lock = threading.RLock() self.focus_timer = None self.window_index = 1 def update_windowlist(self, window_id): with self.window_list_lock: if window_id in self.window_list: self.window_list.remove(window_id) self.window_list.insert(0, window_id) if len(self.window_list) > MAX_WIN_HISTORY: del self.window_list[MAX_WIN_HISTORY:] self.window_index = 1 def get_valid_windows(self): tree = self.i3.get_tree() if args.active_workspace: return set(w.id for w in tree.find_focused().workspace().leaves()) elif args.visible_workspaces: ws_list = [] w_set = set() for item in self.i3.get_outputs(): ws_list.append(item.current_workspace) for ws in tree.workspaces(): if str(ws.num) in ws_list: for w in ws.leaves(): w_set.add(w.id) return w_set else: return set(w.id for w in tree.leaves()) def on_window_focus(self, i3conn, event): if args.ignore_float and (event.container.floating == "user_on" or event.container.floating == "auto_on"): return if UPDATE_DELAY != 0.0: if self.focus_timer is not None: self.focus_timer.cancel() self.focus_timer = threading.Timer(UPDATE_DELAY, self.update_windowlist, [event.container.id]) self.focus_timer.start() else: self.update_windowlist(event.container.id) def launch_i3(self): self.i3.main() def launch_server(self): selector = selectors.DefaultSelector() def accept(sock): conn, addr = sock.accept() selector.register(conn, selectors.EVENT_READ, read) def read(conn): data = conn.recv(1024) if data == b'switch': with self.window_list_lock: windows = self.get_valid_windows() for window_id in self.window_list[self.window_index:]: if window_id not in windows: self.window_list.remove(window_id) else: if self.window_index < (len(self.window_list) - 1): self.window_index += 1 else: self.window_index = 0 self.i3.command('[con_id=%s] focus' % window_id) break elif not data: selector.unregister(conn) conn.close() selector.register(self.listening_socket, selectors.EVENT_READ, accept) while True: for key, event in selector.select(): callback = key.data callback(key.fileobj) def run(self): t_i3 = threading.Thread(target=self.launch_i3) t_server = threading.Thread(target=self.launch_server) for t in (t_i3, t_server): t.start() if __name__ == '__main__': parser = ArgumentParser(prog='i3-cycle-focus.py', description=""" Cycle backwards through the history of focused windows (aka Alt-Tab). This script should be launched from ~/.xsession or ~/.xinitrc. Use the `--history` option to set the maximum number of windows to be stored in the focus history (Default 16 windows). Use the `--delay` option to set the delay between focusing the selected window and updating the focus history (Default 2.0 seconds). Use a value of 0.0 seconds to toggle focus only between the current and the previously focused window. Use the `--ignore-floating` option to exclude all floating windows when cycling and updating the focus history. Use the `--visible-workspaces` option to include windows on visible workspaces only when cycling the focus history. Use the `--active-workspace` option to include windows on the active workspace only when cycling the focus history. To trigger focus switching, execute the script from a keybinding with the `--switch` option.""") parser.add_argument('--history', dest='history', help='Maximum number of windows in the focus history', type=int) parser.add_argument('--delay', dest='delay', help='Delay before updating focus history', type=float) parser.add_argument('--ignore-floating', dest='ignore_float', action='store_true', help='Ignore floating windows ' 'when cycling and updating the focus history') parser.add_argument('--visible-workspaces', dest='visible_workspaces', action='store_true', help='Include windows on visible ' 'workspaces only when cycling the focus history') parser.add_argument('--active-workspace', dest='active_workspace', action='store_true', help='Include windows on the ' 'active workspace only when cycling the focus history') parser.add_argument('--switch', dest='switch', action='store_true', help='Switch to the previous window', default=False) args = parser.parse_args() if args.history: MAX_WIN_HISTORY = args.history if args.delay: UPDATE_DELAY = args.delay else: if args.delay == 0.0: UPDATE_DELAY = args.delay if not args.switch: focus_watcher = FocusWatcher() focus_watcher.run() else: client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client_socket.connect(SOCKET_FILE) client_socket.send(b'switch') client_socket.close() python-i3ipc-2.2.1/examples/i3-debug-console.py000077500000000000000000000026071375005214100212770ustar00rootroot00000000000000#!/usr/bin/env python3 import i3ipc from curses import wrapper def con_type_to_text(con): if con.type != 'con': return con.type if len(con.nodes): return 'container' else: return 'view' def layout_txt(con): if con.layout == 'splith': return 'HORIZ' elif con.layout == 'splitv': return 'VERT' else: return '' def container_to_text(con, indent): t = con_type_to_text(con) txt = (' ' * indent) + '(' txt += t + ' ' + layout_txt(con) if con.focused: txt += ' focus' has_children = len(con.nodes) for c in con.nodes: txt += '\n' txt += container_to_text(c, indent + 4) if has_children: txt += '\n' + (' ' * indent) txt += ')' return txt last_txt = '' def main(stdscr): ipc = i3ipc.Connection() def on_event(ipc, e): txt = '' for ws in ipc.get_tree().workspaces(): txt += container_to_text(ws, 0) + '\n' global last_txt if txt == last_txt: return stdscr.clear() for l in txt: try: stdscr.addstr(l) except Exception: break stdscr.refresh() last_txt = txt on_event(ipc, None) ipc.on('window', on_event) ipc.on('binding', on_event) ipc.on('workspace', on_event) ipc.main() wrapper(main) python-i3ipc-2.2.1/examples/i3-focus/000077500000000000000000000000001375005214100173065ustar00rootroot00000000000000python-i3ipc-2.2.1/examples/i3-focus/focus-app.py000077500000000000000000000017141375005214100215630ustar00rootroot00000000000000#!/usr/bin/env python3 import re from argparse import ArgumentParser from functools import reduce import i3ipc from tools import App, Lists, Menu, Sockets parser = ArgumentParser(prog='i3-app-focus.py', description=''' i3-app-focus.py is dmenu-based script for creating dynamic app switcher. ''', epilog=''' Additional arguments found after "--" will be passed to dmenu. ''') parser.add_argument('--menu', default='dmenu', help='The menu command to run (ex: --menu=rofi)') parser.add_argument('--socket-file', default='/tmp/i3-app-focus.socket', help='Socket file path') (args, menu_args) = parser.parse_known_args() sockets = Sockets(args.socket_file) containers_info = sockets.get_containers_history() apps = list(map(App, containers_info)) apps_uniq = reduce(Lists.accum_uniq_apps, apps, []) i3 = i3ipc.Connection() menu = Menu(i3, args.menu, menu_args) menu.show_menu_app(apps_uniq) python-i3ipc-2.2.1/examples/i3-focus/focus-current-app-window.py000077500000000000000000000017411375005214100245500ustar00rootroot00000000000000#!/usr/bin/env python3 import re from argparse import ArgumentParser from functools import reduce import i3ipc from tools import App, Lists, Menu, Sockets parser = ArgumentParser(prog='i3-app-focus.py', description=''' i3-app-focus.py is dmenu-based script for creating dynamic app switcher. ''', epilog=''' Additional arguments found after "--" will be passed to dmenu. ''') parser.add_argument('--menu', default='dmenu', help='The menu command to run (ex: --menu=rofi)') parser.add_argument('--socket-file', default='/tmp/i3-app-focus.socket', help='Socket file path') (args, menu_args) = parser.parse_known_args() sockets = Sockets(args.socket_file) containers_info = sockets.get_containers_history() containers_info_by_focused_app = Lists.find_all_by_focused_app(containers_info) i3 = i3ipc.Connection() menu = Menu(i3, args.menu, menu_args) menu.show_menu_container_info(containers_info_by_focused_app) python-i3ipc-2.2.1/examples/i3-focus/focus-window.py000077500000000000000000000016011375005214100223050ustar00rootroot00000000000000#!/usr/bin/env python3 import re from argparse import ArgumentParser from functools import reduce import i3ipc from tools import App, Lists, Menu, Sockets parser = ArgumentParser(prog='i3-app-focus.py', description=''' i3-app-focus.py is dmenu-based script for creating dynamic app switcher. ''', epilog=''' Additional arguments found after "--" will be passed to dmenu. ''') parser.add_argument('--menu', default='dmenu', help='The menu command to run (ex: --menu=rofi)') parser.add_argument('--socket-file', default='/tmp/i3-app-focus.socket', help='Socket file path') (args, menu_args) = parser.parse_known_args() sockets = Sockets(args.socket_file) containers_info = sockets.get_containers_history() i3 = i3ipc.Connection() menu = Menu(i3, args.menu, menu_args) menu.show_menu_container_info(containers_info) python-i3ipc-2.2.1/examples/i3-focus/history-server.py000077500000000000000000000055751375005214100227040ustar00rootroot00000000000000#!/usr/bin/env python3 import os import socket import selectors import threading import json from argparse import ArgumentParser import i3ipc MAX_WIN_HISTORY = 15 parser = ArgumentParser(prog='i3-app-focus.py', description='''''', epilog='''''') parser.add_argument('--socket-file', default='/tmp/i3-app-focus.socket', help='Socket file path') (args, other) = parser.parse_known_args() class FocusWatcher: def __init__(self): self.i3 = i3ipc.Connection() self.i3.on('window::focus', self._on_window_focus) self.listening_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if os.path.exists(args.socket_file): os.remove(args.socket_file) self.listening_socket.bind(args.socket_file) self.listening_socket.listen(2) self.window_list = [] self.window_list_lock = threading.RLock() def run(self): t_i3 = threading.Thread(target=self._launch_i3) t_server = threading.Thread(target=self._launch_server) for t in (t_i3, t_server): t.start() def _on_window_focus(self, i3conn, event): window_id = event.container.id con = self.i3.get_tree().find_by_id(window_id) if not self._is_window(con): return with self.window_list_lock: if window_id in self.window_list: self.window_list.remove(window_id) self.window_list.insert(0, window_id) if len(self.window_list) > MAX_WIN_HISTORY: del self.window_list[MAX_WIN_HISTORY:] def _launch_i3(self): self.i3.main() def _launch_server(self): selector = selectors.DefaultSelector() def accept(sock): conn, addr = sock.accept() tree = self.i3.get_tree() info = [] with self.window_list_lock: for window_id in self.window_list: con = tree.find_by_id(window_id) if con: info.append({ "id": con.id, "window": con.window, "window_title": con.window_title, "window_class": con.window_class, "window_role": con.window_role, "focused": con.focused }) conn.send(json.dumps(info).encode()) conn.close() selector.register(self.listening_socket, selectors.EVENT_READ, accept) while True: for key, event in selector.select(): callback = key.data callback(key.fileobj) @staticmethod def _is_window(con): return not con.nodes and con.type == "con" and (con.parent and con.parent.type != "dockarea" or True) focus_watcher = FocusWatcher() focus_watcher.run() python-i3ipc-2.2.1/examples/i3-focus/tools/000077500000000000000000000000001375005214100204465ustar00rootroot00000000000000python-i3ipc-2.2.1/examples/i3-focus/tools/__init__.py000066400000000000000000000001421375005214100225540ustar00rootroot00000000000000from .app import App from .lists import Lists from .menu import Menu from .sockets import Sockets python-i3ipc-2.2.1/examples/i3-focus/tools/app.py000066400000000000000000000025101375005214100215760ustar00rootroot00000000000000import re import i3ipc class App: def __init__(self, container_info): self._container_info = container_info def get_con_id(self): return self._container_info["id"] def get_window_class(self): return self._container_info["window_class"] def get_title(self): window_class = self._container_info["window_class"] method_name = '_get_title_' + window_class.replace('-', '_').lower() method = getattr(self, method_name, self._get_title_based_on_class) return method() def _get_title_based_on_class(self): return self._container_info["window_class"].replace('-', ' ').title() def _get_title_based_on_title(self): return re.match(r"^.*?\s*(?P[^-—]+)$", self._container_info["window_title"]).group("title") # App specific functions def _get_title_google_chrome(self): is_browser_in_app_mode = self._container_info["window_role"] == "pop-up" if is_browser_in_app_mode: return self._get_title_based_on_title() + ' (Chrome)' return self._get_title_based_on_class() def _get_title_st_256color(self): title = self._get_title_based_on_title() if self._container_info["window_title"] != "Simple Terminal": return title + ' (ST)' return title ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/i3-focus/tools/lists.py�������������������������������������������������0000664�0000000�0000000�00000001440�13750052141�0022155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from . import App class Lists: @staticmethod def accum_uniq_apps(result, app): exists = False for a in result: if a.get_title() == app.get_title(): exists = True if not exists: result.append(app) return result @staticmethod def find_all_by_focused_app(infos): for i in infos: if i["focused"]: focused_info = i focused_app = App(focused_info) focused_app_windows_by_class = list( filter(lambda i: App(i).get_title() == focused_app.get_title(), infos)) return focused_app_windows_by_class @staticmethod def find_app_by_title(title, apps): for a in apps: if a.get_title() == title: return a ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/i3-focus/tools/menu.py��������������������������������������������������0000664�0000000�0000000�00000003513�13750052141�0021766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from collections import deque from subprocess import check_output from . import Lists, App class Menu: def __init__(self, i3, menu, menu_args): self._i3 = i3 self._menu = menu self._menu_args = menu_args def show_menu(self, items): menu_input = bytes(str.join('\n', items), 'UTF-8') menu_cmd = [self._menu] + ['-l', str(len(items))] + self._menu_args menu_result = check_output(menu_cmd, input=menu_input) return menu_result.decode().strip() def show_menu_app(self, apps): titles = list(map(lambda a: a.get_title(), apps)) selected_title = self.show_menu(titles) selected_app = Lists.find_app_by_title(selected_title, apps) tree = self._i3.get_tree() con = tree.find_by_id(selected_app.get_con_id()) con.command('focus') def show_menu_container_info(self, containers_info): titles = self._get_titles_with_app_prefix(containers_info) titles_with_suffix = self._add_uniqu_suffix(titles) infos_by_title = dict(zip(titles_with_suffix, containers_info)) selected_title = self.show_menu(titles_with_suffix) selected_info = infos_by_title[selected_title] tree = self._i3.get_tree() con = tree.find_by_id(selected_info["id"]) con.command('focus') def _get_titles_with_app_prefix(self, containers_info): return list(map(lambda i: App(i).get_title() + ': ' + i["window_title"], containers_info)) def _add_uniqu_suffix(self, titles): counters = dict() titles_with_suffix = [] for title in titles: counters[title] = counters[title] + 1 if title in counters else 1 if counters[title] > 1: title = f'{title} ({counters[title]})' titles_with_suffix.append(title) return titles_with_suffix �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/i3-focus/tools/sockets.py�����������������������������������������������0000664�0000000�0000000�00000000625�13750052141�0022476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import socket import json class Sockets: def __init__(self, socket_file): self._socket_file = socket_file self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) def get_containers_history(self): self._client.connect(self._socket_file) history_json = self._client.recv(4096).decode() self._client.close() return json.loads(history_json) �����������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/ipc-trace.py������������������������������������������������������������0000664�0000000�0000000�00000001535�13750052141�0020103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 import i3ipc from time import strftime, gmtime i3 = i3ipc.Connection() def print_separator(): print('-----') def print_time(): print(strftime(strftime("%Y-%m-%d %H:%M:%S", gmtime()))) def print_con_info(con): if con: print('Id: %s' % con.id) print('Name: %s' % con.name) else: print('(none)') def on_window(i3, e): print_separator() print('Got window event:') print_time() print('Change: %s' % e.change) print_con_info(e.container) def on_workspace(i3, e): print_separator() print('Got workspace event:') print_time() print('Change: %s' % e.change) print('Current:') print_con_info(e.current) print('Old:') print_con_info(e.old) # TODO subscribe to all events i3.on('window', on_window) i3.on('workspace', on_workspace) i3.main() �������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/nth_window_in_workspace.py����������������������������������������������0000664�0000000�0000000�00000005336�13750052141�0023163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 from subprocess import check_output import i3ipc def get_windows_on_ws(conn): return filter(lambda x: x.window, conn.get_tree().find_focused().workspace().descendents()) def workspace_by_name(conn, workspace): return next(filter(lambda ws: ws.name == workspace, conn.get_tree().workspaces()), None) def window_is_visible(w): try: xprop = check_output(['xprop', '-id', str(w.window)]).decode() except FileNotFoundError: raise SystemExit("The `xprop` utility is not found!" " Please install it and retry.") return '_NET_WM_STATE_HIDDEN' not in xprop def pick_from_list(lst, n, alt=None): cnt = len(lst) return lst[max(0, min(n, cnt - 1))] if cnt > 0 else alt def main(args): conn = i3ipc.Connection() workspace = workspace_by_name(conn, args.workspace) # Find workspace. if workspace is None: print("Workspace not found, making it.") conn.command("workspace " + args.workspace) else: windows = workspace.leaves() # Find windows in there. if args.filter == 'visible': windows = filter(window_is_visible, windows) elif args.filter != 'none': print("WARN: currently only support `visible` as window filter.") window = pick_from_list(list(windows), args.nth) # Pick correct window from there. if window is not None: print("Focussing %d" % window.window) conn.command('[id="%d"] focus' % window.window) else: print("Did not find window(%d) going to workspace anyway." % args.nth) conn.command("workspace " + args.workspace) if args.mode != 'no': conn.command("mode " + args.mode) if __name__ == '__main__': from argparse import ArgumentParser parser = ArgumentParser( prog='nth_window_in_workspace.py', description="Program using i3ipc to select the nth window from a workspace.") parser.add_argument('workspace', help="Name of workspace to go to.") parser.add_argument('nth', type=int, default=0, help="""Nth window in that workspace. If integer too high it will go to the last one, if no windows in there, goes to the workspace.""") parser.add_argument("--filter", default='none', help="filters to apply, i.e. `visible` or `none`(default)") parser.add_argument("--mode", default='default', help="""Convenience feature; what to change the i3-mode to afterwards. So you can exit the mode after you're done. Defaultly it goes back to `default`, can set it to `no` to not change mode at all.""") main(parser.parse_args()) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/open-floating-on-ws.py��������������������������������������������������0000664�0000000�0000000�00000001166�13750052141�0022037�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 # This example shows how to make any window that opens on a workspace floating # All workspaces that start with a string in this list will have their windows # open floating FLOATING_WORKSPACES = ['3'] def is_ws_floating(name): for floating_ws in FLOATING_WORKSPACES: if name.startswith(floating_ws): return True return False import i3ipc i3 = i3ipc.Connection() def on_window_open(i3, e): ws = i3.get_tree().find_focused().workspace() if is_ws_floating(ws.name): e.container.command('floating toggle') i3.on('window::new', on_window_open) i3.main() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/stop-application-on-unfocus.py������������������������������������������0000664�0000000�0000000�00000004351�13750052141�0023613�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 """ Stop an application when unfocused using SIGSTOP Restart it when focused again using SIGCONT Useful to save battery / reduce CPU load when running browsers. Warning: if more than one process with the same name are being run, they will all be stopped/restarted Federico Ceratto <federico@firelet.net> License: GPLv3 """ import atexit import i3ipc import psutil from argparse import ArgumentParser class FocusMonitor(object): def __init__(self, args): self.had_focus = False self.class_name = args.class_name self.process_name = args.process_name self.debug = args.debug self.conn = i3ipc.Connection() self.conn.on('window::focus', self.focus_change) atexit.register(self.continue_at_exit) def stop_cont(self, cont=True): """Send SIGSTOP/SIGCONT to processes called <name> """ for proc in psutil.process_iter(): if proc.name() == self.process_name: sig = psutil.signal.SIGCONT if cont else psutil.signal.SIGSTOP proc.send_signal(sig) if self.debug: sig = 'CONT' if cont else 'STOP' print("Sent SIG%s to process %d" % (sig, proc.pid)) def focus_change(self, i3conn, event): """Detect focus change on a process with class class_name. On change, stop/continue the process called process_name """ has_focus_now = (event.container.window_class == self.class_name) if self.had_focus ^ has_focus_now: # The monitored application changed focus state self.had_focus = has_focus_now self.stop_cont(has_focus_now) def continue_at_exit(self): """Send SIGCONT on script termination""" self.stop_cont(True) def run(self): try: self.conn.main() except KeyboardInterrupt: print('Exiting on keyboard interrupt') def parse_args(): ap = ArgumentParser() ap.add_argument('class_name') ap.add_argument('process_name') ap.add_argument('-d', '--debug', action='store_true') return ap.parse_args() def main(): args = parse_args() fm = FocusMonitor(args) fm.run() if __name__ == '__main__': main() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/tiling-indicator.py�����������������������������������������������������0000664�0000000�0000000�00000001073�13750052141�0021471�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 import i3ipc i3 = i3ipc.Connection() splitv_text = 'V' splith_text = 'H' last = '' def on_event(self, _): global last layout = i3.get_tree().find_focused().parent.layout if layout == 'splitv' and not layout == last: print(splitv_text) elif layout == 'splith' and not layout == last: print(splith_text) elif layout != last: print(' ') last = layout # Subscribe to events i3.on("window::focus", on_event) i3.on("binding", on_event) # Start the main loop and wait for events to come in. i3.main() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/workspace-new.py��������������������������������������������������������0000664�0000000�0000000�00000001356�13750052141�0021022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 import i3ipc import re from argparse import ArgumentParser def main(): parser = ArgumentParser(description=''' Simple script to go to a new workspace. It will switch to a workspace with the lowest available number. ''') parser.parse_args() i3 = i3ipc.Connection() workspaces = i3.get_workspaces() numbered_workspaces = filter(lambda w: w.name[0].isdigit(), workspaces) numbers = list(map(lambda w: int(re.search(r'^([0-9]+)', w.name).group(0)), numbered_workspaces)) new = 0 for i in range(1, max(numbers) + 2): if i not in numbers: new = i break i3.command("workspace %s" % new) if __name__ == '__main__': main() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/examples/workspace_renumber.py���������������������������������������������������0000775�0000000�0000000�00000003426�13750052141�0022135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 import i3ipc # make connection to i3 ipc i3 = i3ipc.Connection() # check if workspaces are all in order def workspaces_ordered(i3conn): last_workspace = 0 for i in sorted(i3conn.get_workspaces(), key=lambda x: x.num): number = int(i.num) if number != last_workspace + 1: return False last_workspace += 1 return True # find all the workspaces that are out of order and # the least possible valid workspace number that is unassigned def find_disordered(i3conn): disordered = [] least_number = None workspaces = sorted(i3conn.get_workspaces(), key=lambda x: x.num) occupied_workspaces = [int(x.num) for x in workspaces] last_workspace = 0 for i in workspaces: number = int(i.num) if number != last_workspace + 1: disordered.append(number) if least_number is None and last_workspace + 1 not in occupied_workspaces: least_number = last_workspace + 1 last_workspace += 1 return (disordered, least_number) # renumber all the workspaces that appear out of order from the others def fix_ordering(i3conn): if workspaces_ordered(i3conn): return else: workspaces = i3conn.get_tree().workspaces() disordered_workspaces, least_number = find_disordered(i3conn) containers = list(filter(lambda x: x.num in disordered_workspaces, workspaces)) for c in containers: for i in c.leaves(): i.command("move container to workspace %s" % least_number) least_number += 1 return # callback for when workspace focus changes def on_workspace_focus(i3conn, e): fix_ordering(i3conn) if __name__ == '__main__': i3.on('workspace::focus', on_workspace_focus) i3.main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13750052141�0015047�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/__init__.py����������������������������������������������������������������0000664�0000000�0000000�00000001130�13750052141�0017153�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .__version__ import (__title__, __description__, __url__, __version__, __author__, __author_email__, __license__, __copyright__) from .replies import (BarConfigReply, CommandReply, ConfigReply, OutputReply, TickReply, VersionReply, WorkspaceReply, SeatReply, InputReply) from .events import (BarconfigUpdateEvent, BindingEvent, BindingInfo, OutputEvent, ShutdownEvent, WindowEvent, TickEvent, ModeEvent, WorkspaceEvent, InputEvent, Event) from .con import Con from .model import Rect, Gaps from .connection import Connection ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/__version__.py�������������������������������������������������������������0000664�0000000�0000000�00000000470�13750052141�0017703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������__title__ = 'i3ipc' __description__ = 'An improved Python library to control i3wm and sway' __url__ = 'https://github.com/altdesktop/i3ipc-python' __version__ = '2.2.1' __author__ = 'Tony Crisci' __author_email__ = 'tony@dubstepdish.com' __license__ = 'BSD-3-Clause' __copyright__ = 'Copyright 2015 Tony Crisci' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/_private/������������������������������������������������������������������0000775�0000000�0000000�00000000000�13750052141�0016660�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/_private/__init__.py�������������������������������������������������������0000664�0000000�0000000�00000000157�13750052141�0020774�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .pubsub import PubSub from .types import MessageType, ReplyType, EventType from .sync import Synchronizer �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/_private/pubsub.py���������������������������������������������������������0000664�0000000�0000000�00000001731�13750052141�0020534�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������class PubSub(object): def __init__(self, conn): self.conn = conn self._subscriptions = [] def subscribe(self, detailed_event, handler): event = detailed_event.replace('-', '_') detail = '' if detailed_event.count('::') > 0: [event, detail] = detailed_event.split('::') self._subscriptions.append({'event': event, 'detail': detail, 'handler': handler}) def unsubscribe(self, handler): self._subscriptions = list(filter(lambda s: s['handler'] != handler, self._subscriptions)) def emit(self, event, data): detail = '' if data and hasattr(data, 'change'): detail = data.change for s in self._subscriptions: if s['event'] == event: if not s['detail'] or s['detail'] == detail: if data: s['handler'](self.conn, data) else: s['handler'](self.conn) ���������������������������������������python-i3ipc-2.2.1/i3ipc/_private/sync.py�����������������������������������������������������������0000664�0000000�0000000�00000002220�13750052141�0020202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from Xlib import display from Xlib.protocol import event from Xlib import X import random class Synchronizer: def __init__(self): self.display = display.Display() self.screen = self.display.screen() self.root = self.screen.root self.sync_atom = self.display.intern_atom('I3_SYNC') self.send_window = self.root.create_window(-10, -10, 10, 10, 0, self.screen.root_depth) def sync(self): rnd = random.randint(0, 2147483647) message = event.ClientMessage(window=self.root, data=([32, [self.send_window.id, rnd, 0, 0, 0]]), message_type=self.sync_atom, client_type=self.sync_atom, sequence_number=0) self.display.send_event(self.root, message, X.SubstructureRedirectMask) while True: e = self.display.next_event() if e.type == X.ClientMessage and e.client_type == self.sync_atom: fmt, data = e.data if data[0] == self.send_window.id and data[1] == rnd: break ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/_private/types.py����������������������������������������������������������0000664�0000000�0000000�00000004145�13750052141�0020402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from enum import Enum class MessageType(Enum): COMMAND = 0 GET_WORKSPACES = 1 SUBSCRIBE = 2 GET_OUTPUTS = 3 GET_TREE = 4 GET_MARKS = 5 GET_BAR_CONFIG = 6 GET_VERSION = 7 GET_BINDING_MODES = 8 GET_CONFIG = 9 SEND_TICK = 10 # sway-specific command types GET_INPUTS = 100 GET_SEATS = 101 class ReplyType(Enum): COMMAND = 0 WORKSPACES = 1 SUBSCRIBE = 2 OUTPUTS = 3 TREE = 4 MARKS = 5 BAR_CONFIG = 6 VERSION = 7 BINDING_MODES = 8 GET_CONFIG = 9 TICK = 10 class EventType(Enum): WORKSPACE = (1 << 0) OUTPUT = (1 << 1) MODE = (1 << 2) WINDOW = (1 << 3) BARCONFIG_UPDATE = (1 << 4) BINDING = (1 << 5) SHUTDOWN = (1 << 6) TICK = (1 << 7) INPUT = (1 << 21) def to_string(self): return str.lower(self.name) @staticmethod def from_string(val): match = [e for e in EventType if e.to_string() == val] if not match: raise ValueError('event not implemented: ' + val) return match[0] def to_list(self): events_list = [] if self.value & EventType.WORKSPACE.value: events_list.append(EventType.WORKSPACE.to_string()) if self.value & EventType.OUTPUT.value: events_list.append(EventType.OUTPUT.to_string()) if self.value & EventType.MODE.value: events_list.append(EventType.MODE.to_string()) if self.value & EventType.WINDOW.value: events_list.append(EventType.WINDOW.to_string()) if self.value & EventType.BARCONFIG_UPDATE.value: events_list.append(EventType.BARCONFIG_UPDATE.to_string()) if self.value & EventType.BINDING.value: events_list.append(EventType.BINDING.to_string()) if self.value & EventType.SHUTDOWN.value: events_list.append(EventType.SHUTDOWN.to_string()) if self.value & EventType.TICK.value: events_list.append(EventType.TICK.to_string()) if self.value & EventType.INPUT.value: events_list.append(EventType.INPUT.to_string()) return events_list ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/aio/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�13750052141�0015617�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/aio/__init__.py������������������������������������������������������������0000664�0000000�0000000�00000000050�13750052141�0017723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .connection import Connection, Con ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/aio/connection.py����������������������������������������������������������0000664�0000000�0000000�00000052664�13750052141�0020345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .._private import PubSub, MessageType, EventType, Synchronizer from ..replies import (BarConfigReply, CommandReply, ConfigReply, OutputReply, TickReply, VersionReply, WorkspaceReply, SeatReply, InputReply) from ..events import (IpcBaseEvent, BarconfigUpdateEvent, BindingEvent, OutputEvent, ShutdownEvent, WindowEvent, TickEvent, ModeEvent, WorkspaceEvent, InputEvent, Event) from .. import con import os import json from typing import Optional, List, Tuple, Callable, Union from Xlib import display, X from Xlib.error import DisplayError import struct import socket import asyncio from asyncio.subprocess import PIPE from asyncio import Future _MAGIC = b'i3-ipc' # safety string for i3-ipc _chunk_size = 1024 # in bytes _timeout = 0.5 # in seconds _struct_header = f'={len(_MAGIC)}sII' _struct_header_size = struct.calcsize(_struct_header) class _AIOPubSub(PubSub): def queue_handler(self, handler, data=None): conn = self.conn async def handler_coroutine(): try: if data: if asyncio.iscoroutinefunction(handler): await handler(conn, data) else: handler(conn, data) else: if asyncio.iscoroutinefunction(handler): await handler(conn) else: handler(conn) except Exception as e: conn.main_quit(_error=e) asyncio.ensure_future(handler_coroutine()) def emit(self, event, data): detail = '' if data and hasattr(data, 'change'): detail = data.change for s in self._subscriptions: if s['event'] == event: if not s['detail'] or s['detail'] == detail: self.queue_handler(s['handler'], data) class Con(con.Con): """A container of a window and child containers gotten from :func:`i3ipc.Connection.get_tree()` or events. .. seealso:: https://i3wm.org/docs/ipc.html#_tree_reply :ivar border: :vartype border: str :ivar current_border_width: :vartype current_border_with: int :ivar floating: :vartype floating: bool :ivar focus: The focus stack for this container as a list of container ids. The "focused inactive" is at the top of the list which is the container that would be focused if this container recieves focus. :vartype focus: list(int) :ivar focused: :vartype focused: bool :ivar fullscreen_mode: :vartype fullscreen_mode: int :ivar ~.id: :vartype ~.id: int :ivar layout: :vartype layout: str :ivar marks: :vartype marks: list(str) :ivar name: :vartype name: str :ivar num: :vartype num: int :ivar orientation: :vartype orientation: str :ivar percent: :vartype percent: float :ivar scratchpad_state: :vartype scratchpad_state: str :ivar sticky: :vartype sticky: bool :ivar type: :vartype type: str :ivar urgent: :vartype urgent: bool :ivar window: :vartype window: int :ivar nodes: :vartype nodes: list(:class:`Con <i3ipc.Con>`) :ivar floating_nodes: :vartype floating_nodes: list(:class:`Con <i3ipc.Con>`) :ivar window_class: :vartype window_class: str :ivar window_instance: :vartype window_instance: str :ivar window_role: :vartype window_role: str :ivar window_title: :vartype window_title: str :ivar rect: :vartype rect: :class:`Rect <i3ipc.Rect>` :ivar window_rect: :vartype window_rect: :class:`Rect <i3ipc.Rect>` :ivar deco_rect: :vartype deco_rect: :class:`Rect <i3ipc.Rect>` :ivar app_id: (sway only) :vartype app_id: str :ivar pid: (sway only) :vartype pid: int :ivar gaps: (gaps only) :vartype gaps: :class:`Gaps <i3ipc.Gaps>` :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ async def command(self, command: str) -> List[CommandReply]: """Runs a command on this container. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :returns: A list of replies for each command in the given command string. :rtype: list(CommandReply) """ return await self._conn.command('[con_id="{}"] {}'.format(self.id, command)) async def command_children(self, command: str) -> List[CommandReply]: """Runs a command on the immediate children of the currently selected container. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :returns: A list of replies for each command that was executed. :rtype: list(CommandReply) """ if not len(self.nodes): return [] commands = [] for c in self.nodes: commands.append('[con_id="{}"] {};'.format(c.id, command)) return await self._conn.command(' '.join(commands)) def _pack(msg_type: MessageType, payload: str) -> bytes: pb = payload.encode() s = struct.pack('=II', len(pb), msg_type.value) return b''.join((_MAGIC, s, pb)) def _unpack_header(data: bytes) -> Tuple[bytes, int, int]: return struct.unpack(_struct_header, data[:_struct_header_size]) async def _find_socket_path() -> Optional[str]: socket_path = None def exists(path): if not path: return False return os.path.exists(path) # first try environment variables socket_path = os.environ.get('I3SOCK') if exists(socket_path): return socket_path socket_path = os.environ.get('SWAYSOCK') if exists(socket_path): return socket_path # next try the root window property try: d = display.Display() atom = d.get_atom('I3_SOCKET_PATH') root = d.screen().root prop = root.get_full_property(atom, X.AnyPropertyType) if prop and prop.value: socket_path = prop.value.decode() except DisplayError: pass if exists(socket_path): return socket_path # finally try the binaries for binary in ('i3', 'sway'): try: process = await asyncio.create_subprocess_exec(binary, '--get-socketpath', stdout=PIPE, stderr=PIPE) except Exception: continue stdout, stderr = await process.communicate() if process.returncode == 0 and stdout: socket_path = stdout.decode().strip() if exists(socket_path): return socket_path # could not find the socket path return None class Connection: """A connection to the i3 ipc used for querying window manager state and listening to events. The ``Connection`` class is the entry point into all features of the library. You must call :func:`connect() <i3ipc.aio.Connection.connect>` before using this ``Connection``. :Example: .. code-block:: python3 i3 = await Connection().connect() workspaces = await i3.get_workspaces() await i3.command('focus left') :param socket_path: A path to the i3 ipc socket path to connect to. If not given, find the socket path through the default search path. :type socket_path: str :param auto_reconnect: Whether to attempt to reconnect if the connection to the socket is broken when i3 restarts. :type auto_reconnect: bool :raises Exception: If the connection to i3 cannot be established. """ def __init__(self, socket_path: Optional[str] = None, auto_reconnect: bool = False): self._socket_path = socket_path self._auto_reconnect = auto_reconnect self._pubsub = _AIOPubSub(self) self._subscriptions = set() self._main_future = None self._reconnect_future = None self._synchronizer = None def _sync(self): if self._synchronizer is None: self._synchronizer = Synchronizer() self._synchronizer.sync() @property def socket_path(self) -> str: """The path of the socket this ``Connection`` is connected to. :rtype: str """ return self._socket_path @property def auto_reconect(self) -> bool: """Whether this ``Connection`` will attempt to reconnect when the connection to the socket is broken. :rtype: bool """ return self._auto_reconnect async def _ipc_recv(self, sock): pass def _message_reader(self): try: self._read_message() except Exception as e: self.main_quit(_error=e) def _read_message(self): error = None buf = b'' try: buf = self._sub_socket.recv(_struct_header_size) except ConnectionError as e: error = e if not buf or error is not None: self._loop.remove_reader(self._sub_fd) if self._auto_reconnect: asyncio.ensure_future(self._reconnect()) else: if error is not None: raise error else: raise EOFError() return magic, message_length, event_type = _unpack_header(buf) assert magic == _MAGIC message = json.loads(self._sub_socket.recv(message_length)) # events have the highest bit set if not event_type & (1 << 31): # a reply return event_type = EventType(1 << (event_type & 0x7f)) if event_type == EventType.WORKSPACE: event = WorkspaceEvent(message, self, _Con=Con) elif event_type == EventType.OUTPUT: event = OutputEvent(message) elif event_type == EventType.MODE: event = ModeEvent(message) elif event_type == EventType.WINDOW: event = WindowEvent(message, self, _Con=Con) elif event_type == EventType.BARCONFIG_UPDATE: event = BarconfigUpdateEvent(message) elif event_type == EventType.BINDING: event = BindingEvent(message) elif event_type == EventType.SHUTDOWN: event = ShutdownEvent(message) elif event_type == EventType.TICK: event = TickEvent(message) elif event_type == EventType.INPUT: event = InputEvent(message) else: # we have not implemented this event return self._pubsub.emit(event_type.to_string(), event) async def connect(self) -> 'Connection': """Connects to the i3 ipc socket. You must await this method to use this Connection. :returns: The ``Connection``. :rtype: :class:`~.Connection` """ if not self._socket_path: self._socket_path = await _find_socket_path() if not self.socket_path: raise Exception('Failed to retrieve the i3 or sway IPC socket path') self._cmd_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._cmd_socket.connect(self.socket_path) self._sub_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._sub_socket.connect(self.socket_path) self._loop = asyncio.get_event_loop() self._sub_fd = self._sub_socket.fileno() self._loop.add_reader(self._sub_fd, self._message_reader) await self.subscribe(list(self._subscriptions), force=True) return self def _reconnect(self) -> Future: if self._reconnect_future is not None: return self._reconnect_future self._reconnect_future = self._loop.create_future() async def do_reconnect(): error = None for tries in range(0, 1000): try: await self.connect() error = None break except Exception as e: error = e await asyncio.sleep(0.001) if error: self._reconnect_future.set_exception(error) else: self._reconnect_future.set_result(None) self._reconnect_future = None asyncio.ensure_future(do_reconnect()) return self._reconnect_future async def _message(self, message_type: MessageType, payload: str = '') -> bytes: if message_type is MessageType.SUBSCRIBE: raise Exception('cannot subscribe on the command socket') for tries in range(0, 5): try: await self._loop.sock_sendall(self._cmd_socket, _pack(message_type, payload)) buf = await self._loop.sock_recv(self._cmd_socket, _struct_header_size) break except ConnectionError as e: if not self._auto_reconnect: raise e await self._reconnect() if not buf: return b'' magic, message_length, reply_type = _unpack_header(buf) assert reply_type == message_type.value assert magic == _MAGIC try: message = await self._loop.sock_recv(self._cmd_socket, message_length) except ConnectionError as e: if self._auto_reconnect: asyncio.ensure_future(self._reconnect()) raise e return message async def subscribe(self, events: Union[List[Event], List[str]], force: bool = False): """Send a ``SUBSCRIBE`` command to the ipc subscription connection and await the result. To attach event handlers, use :func:`Connection.on() <i3ipc.aio.Connection.on()>`. Calling this is only needed if you want to be notified when events will start coming in. :ivar events: A list of events to subscribe to. Currently you cannot subscribe to detailed events. :vartype events: list(:class:`Event <i3ipc.Event>`) or list(str) :ivar force: If ``False``, the message will not be sent if this connection is already subscribed to the event. :vartype force: bool """ if not events: return if type(events) is not list: raise TypeError('events must be a list of events') subscriptions = set() for e in events: e = Event(e) if e not in Event._subscribable_events: correct_event = str.split(e.value, '::')[0].upper() raise ValueError( f'only nondetailed events are subscribable (use Event.{correct_event})') subscriptions.add(e) if not force: subscriptions = subscriptions.difference(self._subscriptions) if not subscriptions: return self._subscriptions.update(subscriptions) payload = json.dumps([s.value for s in subscriptions]) await self._loop.sock_sendall(self._sub_socket, _pack(MessageType.SUBSCRIBE, payload)) def on(self, event: Union[Event, str], handler: Callable[['Connection', IpcBaseEvent], None]): """Subscribe to the event and call the handler when it is emitted by the i3 ipc. :param event: The event to subscribe to. :type event: :class:`Event <i3ipc.Event>` or str :param handler: The event handler to call. :type handler: :class:`Callable` """ if type(event) is Event: event = event.value event = event.replace('-', '_') if event.count('::') > 0: [base_event, __] = event.split('::') else: base_event = event self._pubsub.subscribe(event, handler) asyncio.ensure_future(self.subscribe([base_event])) def off(self, handler: Callable[['Connection', IpcBaseEvent], None]): """Unsubscribe the handler from being called on ipc events. :param handler: The handler that was previously attached with :func:`on()`. :type handler: :class:`Callable` """ self._pubsub.unsubscribe(handler) async def command(self, cmd: str) -> List[CommandReply]: """Sends a command to i3. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :param cmd: The command to send to i3. :type cmd: str :returns: A list of replies that contain info for the result of each command given. :rtype: list(:class:`CommandReply <i3ipc.CommandReply>`) """ data = await self._message(MessageType.COMMAND, cmd) if data: data = json.loads(data) return CommandReply._parse_list(data) else: return [] async def get_version(self) -> VersionReply: """Gets the i3 version. :returns: The i3 version. :rtype: :class:`i3ipc.VersionReply` """ data = await self._message(MessageType.GET_VERSION) data = json.loads(data) return VersionReply(data) async def get_bar_config_list(self) -> List[str]: """Gets the names of all bar configurations. :returns: A list of all bar configurations. :rtype: list(str) """ data = await self._message(MessageType.GET_BAR_CONFIG) return json.loads(data) async def get_bar_config(self, bar_id=None) -> Optional[BarConfigReply]: """Gets the bar configuration specified by the id. :param bar_id: The bar id to get the configuration for. If not given, get the configuration for the first bar id. :type bar_id: str :returns: The bar configuration for the bar id. :rtype: :class:`BarConfigReply <i3ipc.BarConfigReply>` or :class:`None` if no bar configuration is found. """ if not bar_id: bar_config_list = await self.get_bar_config_list() if not bar_config_list: return None bar_id = bar_config_list[0] data = await self._message(MessageType.GET_BAR_CONFIG, bar_id) data = json.loads(data) return BarConfigReply(data) async def get_outputs(self) -> List[OutputReply]: """Gets the list of current outputs. :returns: A list of current outputs. :rtype: list(:class:`i3ipc.OutputReply`) """ data = await self._message(MessageType.GET_OUTPUTS) data = json.loads(data) return OutputReply._parse_list(data) async def get_workspaces(self) -> List[WorkspaceReply]: """Gets the list of current workspaces. :returns: A list of current workspaces :rtype: list(:class:`i3ipc.WorkspaceReply`) """ data = await self._message(MessageType.GET_WORKSPACES) data = json.loads(data) return WorkspaceReply._parse_list(data) async def get_tree(self) -> Con: """Gets the root container of the i3 layout tree. :returns: The root container of the i3 layout tree. :rtype: :class:`i3ipc.Con` """ data = await self._message(MessageType.GET_TREE) return Con(json.loads(data), None, self) async def get_marks(self) -> List[str]: """Gets the names of all currently set marks. :returns: A list of currently set marks. :rtype: list(str) """ data = await self._message(MessageType.GET_MARKS) return json.loads(data) async def get_binding_modes(self) -> List[str]: """Gets the names of all currently configured binding modes :returns: A list of binding modes :rtype: list(str) """ data = await self._message(MessageType.GET_BINDING_MODES) return json.loads(data) async def get_config(self) -> ConfigReply: """Returns the last loaded i3 config. :returns: A class containing the config. :rtype: :class:`i3ipc.ConfigReply` """ data = await self._message(MessageType.GET_CONFIG) data = json.loads(data) return ConfigReply(data) async def send_tick(self, payload: str = "") -> TickReply: """Sends a tick with the specified payload. :returns: The reply to the tick command :rtype: :class:`i3ipc.TickReply` """ data = await self._message(MessageType.SEND_TICK, payload) data = json.loads(data) return TickReply(data) async def get_inputs(self) -> List[InputReply]: """(sway only) Gets the inputs connected to the compositor. :returns: The reply to the inputs command :rtype: list(:class:`i3ipc.InputReply`) """ data = await self._message(MessageType.GET_INPUTS) data = json.loads(data) return InputReply._parse_list(data) async def get_seats(self) -> List[SeatReply]: """(sway only) Gets the seats configured on the compositor :returns: The reply to the seats command :rtype: list(:class:`i3ipc.SeatReply`) """ data = await self._message(MessageType.GET_SEATS) data = json.loads(data) return SeatReply._parse_list(data) def main_quit(self, _error=None): """Quits the running main loop for this connection.""" if self._main_future is not None: if _error: self._main_future.set_exception(_error) else: self._main_future.set_result(None) self._main_future = None async def main(self): """Starts the main loop for this connection to start handling events.""" if self._main_future is not None: raise Exception('the main loop is already running') self._main_future = self._loop.create_future() await self._main_future ����������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/con.py���������������������������������������������������������������������0000664�0000000�0000000�00000033232�13750052141�0016203�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import re import sys from .model import Rect, Gaps from . import replies from collections import deque from typing import List, Optional class Con: """A container of a window and child containers gotten from :func:`i3ipc.Connection.get_tree()` or events. .. seealso:: https://i3wm.org/docs/ipc.html#_tree_reply :ivar border: :vartype border: str :ivar current_border_width: :vartype current_border_with: int :ivar floating: :vartype floating: bool :ivar focus: The focus stack for this container as a list of container ids. The "focused inactive" is at the top of the list which is the container that would be focused if this container recieves focus. :vartype focus: list(int) :ivar focused: :vartype focused: bool :ivar fullscreen_mode: :vartype fullscreen_mode: int :ivar ~.id: :vartype ~.id: int :ivar layout: :vartype layout: str :ivar marks: :vartype marks: list(str) :ivar name: :vartype name: str :ivar num: :vartype num: int :ivar orientation: :vartype orientation: str :ivar percent: :vartype percent: float :ivar scratchpad_state: :vartype scratchpad_state: str :ivar sticky: :vartype sticky: bool :ivar type: :vartype type: str :ivar urgent: :vartype urgent: bool :ivar window: :vartype window: int :ivar nodes: :vartype nodes: list(:class:`Con <i3ipc.Con>`) :ivar floating_nodes: :vartype floating_nodes: list(:class:`Con <i3ipc.Con>`) :ivar window_class: :vartype window_class: str :ivar window_instance: :vartype window_instance: str :ivar window_role: :vartype window_role: str :ivar window_title: :vartype window_title: str :ivar rect: :vartype rect: :class:`Rect <i3ipc.Rect>` :ivar window_rect: :vartype window_rect: :class:`Rect <i3ipc.Rect>` :ivar deco_rect: :vartype deco_rect: :class:`Rect <i3ipc.Rect>` :ivar geometry: :vartype geometry: :class:`Rect <i3ipc.Rect>` :ivar app_id: (sway only) :vartype app_id: str :ivar pid: (sway only) :vartype pid: int :ivar gaps: (gaps only) :vartype gaps: :class:`Gaps <i3ipc.Gaps>` :ivar representation: (sway only) :vartype representation: str :ivar visible: (sway only) :vartype visible: bool :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data, parent, conn): self.ipc_data = data self._conn = conn self.parent = parent # set simple properties ipc_properties = [ 'border', 'current_border_width', 'floating', 'focus', 'focused', 'fullscreen_mode', 'id', 'layout', 'marks', 'name', 'num', 'orientation', 'percent', 'scratchpad_state', 'sticky', 'type', 'urgent', 'window', 'pid', 'app_id', 'representation' ] for attr in ipc_properties: if attr in data: setattr(self, attr, data[attr]) else: setattr(self, attr, None) # XXX in 4.12, marks is an array (old property was a string "mark") if self.marks is None: self.marks = [] if 'mark' in data and data['mark']: self.marks.append(data['mark']) # XXX this is for compatability with 4.8 if isinstance(self.type, int): if self.type == 0: self.type = "root" elif self.type == 1: self.type = "output" elif self.type == 2 or self.type == 3: self.type = "con" elif self.type == 4: self.type = "workspace" elif self.type == 5: self.type = "dockarea" # set complex properties self.nodes = [] if 'nodes' in data: for n in data['nodes']: self.nodes.append(self.__class__(n, self, conn)) self.floating_nodes = [] if 'floating_nodes' in data: for n in data['floating_nodes']: self.floating_nodes.append(self.__class__(n, self, conn)) self.window_class = None self.window_instance = None self.window_role = None self.window_title = None if 'window_properties' in data: if 'class' in data['window_properties']: self.window_class = data['window_properties']['class'] if 'instance' in data['window_properties']: self.window_instance = data['window_properties']['instance'] if 'window_role' in data['window_properties']: self.window_role = data['window_properties']['window_role'] if 'title' in data['window_properties']: self.window_title = data['window_properties']['title'] self.rect = Rect(data['rect']) if 'window_rect' in data: self.window_rect = Rect(data['window_rect']) self.deco_rect = None if 'deco_rect' in data: self.deco_rect = Rect(data['deco_rect']) self.geometry = None if 'geometry' in data: self.geometry = Rect(data['geometry']) self.gaps = None if 'gaps' in data: self.gaps = Gaps(data['gaps']) def __iter__(self): """Iterate through the descendents of this node (breadth-first tree traversal) """ queue = deque(self.nodes) queue.extend(self.floating_nodes) while queue: con = queue.popleft() yield con queue.extend(con.nodes) queue.extend(con.floating_nodes) def root(self) -> 'Con': """Gets the root container. :returns: The root container. :rtype: :class:`Con` """ if not self.parent: return self con = self.parent while con.parent: con = con.parent return con def descendants(self) -> List['Con']: """Gets a list of all child containers for the container in breadth-first order. :returns: A list of descendants. :rtype: list(:class:`Con`) """ return [c for c in self] def descendents(self) -> List['Con']: """Gets a list of all child containers for the container in breadth-first order. .. deprecated:: 2.0.1 Use :func:`descendants` instead. :returns: A list of descendants. :rtype: list(:class:`Con`) """ print('WARNING: descendents is deprecated. Use `descendants()` instead.', file=sys.stderr) return self.descendants() def leaves(self) -> List['Con']: """Gets a list of leaf child containers for this container in breadth-first order. Leaf containers normally contain application windows. :returns: A list of leaf descendants. :rtype: list(:class:`Con`) """ leaves = [] for c in self: if not c.nodes and c.type == "con" and c.parent.type != "dockarea": leaves.append(c) return leaves def command(self, command: str) -> List[replies.CommandReply]: """Runs a command on this container. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :returns: A list of replies for each command in the given command string. :rtype: list(:class:`CommandReply <i3ipc.CommandReply>`) """ return self._conn.command('[con_id="{}"] {}'.format(self.id, command)) def command_children(self, command: str) -> List[replies.CommandReply]: """Runs a command on the immediate children of the currently selected container. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :returns: A list of replies for each command that was executed. :rtype: list(:class:`CommandReply <i3ipc.CommandReply>`) """ if not len(self.nodes): return commands = [] for c in self.nodes: commands.append('[con_id="{}"] {};'.format(c.id, command)) self._conn.command(' '.join(commands)) def workspaces(self) -> List['Con']: """Gets a list of workspace containers for this tree. :returns: A list of workspace containers. :rtype: list(:class:`Con`) """ workspaces = [] def collect_workspaces(con): if con.type == "workspace" and not con.name.startswith('__'): workspaces.append(con) return for c in con.nodes: collect_workspaces(c) collect_workspaces(self.root()) return workspaces def find_focused(self) -> Optional['Con']: """Finds the focused container under this container if it exists. :returns: The focused container if it exists. :rtype: :class:`Con` or :class:`None` if the focused container is not under this container """ try: return next(c for c in self if c.focused) except StopIteration: return None def find_by_id(self, id: int) -> Optional['Con']: """Finds a container with the given container id under this node. :returns: The container with this container id if it exists. :rtype: :class:`Con` or :class:`None` if there is no container with this container id. """ try: return next(c for c in self if c.id == id) except StopIteration: return None def find_by_pid(self, pid: int) -> List['Con']: """Finds all the containers under this node with this pid. :returns: A list of containers with this pid. :rtype: list(:class:`Con`) """ return [c for c in self if c.pid == pid] def find_by_window(self, window: int) -> Optional['Con']: """Finds a container with the given window id under this node. :returns: The container with this window id if it exists. :rtype: :class:`Con` or :class:`None` if there is no container with this window id. """ try: return next(c for c in self if c.window == window) except StopIteration: return None def find_by_role(self, pattern: str) -> List['Con']: """Finds all the containers under this node with a window role that matches the given regex pattern. :returns: A list of containers that have a window role that matches the pattern. :rtype: list(:class:`Con`) """ return [c for c in self if c.window_role and re.search(pattern, c.window_role)] def find_named(self, pattern: str) -> List['Con']: """Finds all the containers under this node with a name that matches the given regex pattern. :returns: A list of containers that have a name that matches the pattern. :rtype: list(:class:`Con`) """ return [c for c in self if c.name and re.search(pattern, c.name)] def find_titled(self, pattern: str) -> List['Con']: """Finds all the containers under this node with a window title that matches the given regex pattern. :returns: A list of containers that have a window title that matches the pattern. :rtype: list(:class:`Con`) """ return [c for c in self if c.window_title and re.search(pattern, c.window_title)] def find_classed(self, pattern: str) -> List['Con']: """Finds all the containers under this node with a window class that matches the given regex pattern. :returns: A list of containers that have a window class that matches the pattern. :rtype: list(:class:`Con`) """ return [c for c in self if c.window_class and re.search(pattern, c.window_class)] def find_instanced(self, pattern: str) -> List['Con']: """Finds all the containers under this node with a window instance that matches the given regex pattern. :returns: A list of containers that have a window instance that matches the pattern. :rtype: list(:class:`Con`) """ return [c for c in self if c.window_instance and re.search(pattern, c.window_instance)] def find_marked(self, pattern: str = ".*") -> List['Con']: """Finds all the containers under this node with a mark that matches the given regex pattern. :returns: A list of containers that have a mark that matches the pattern. :rtype: list(:class:`Con`) """ pattern = re.compile(pattern) return [c for c in self if any(pattern.search(mark) for mark in c.marks)] def find_fullscreen(self) -> List['Con']: """Finds all the containers under this node that are in fullscreen mode. :returns: A list of fullscreen containers. :rtype: list(:class:`Con`) """ return [c for c in self if c.type == 'con' and c.fullscreen_mode] def workspace(self) -> Optional['Con']: """Finds the workspace container for this node if this container is at or below the workspace level. :returns: The workspace container if it exists. :rtype: :class:`Con` or :class:`None` if this container is above the workspace level. """ if self.type == 'workspace': return self ret = self.parent while ret: if ret.type == 'workspace': break ret = ret.parent return ret def scratchpad(self) -> 'Con': """Finds the scratchpad container. :returns: The scratchpad container. :rtype: class:`Con` """ for con in self.root(): if con.type == 'workspace' and con.name == "__i3_scratch": return con return None ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/connection.py��������������������������������������������������������������0000664�0000000�0000000�00000042123�13750052141�0017562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 from .con import Con from .replies import (BarConfigReply, CommandReply, ConfigReply, OutputReply, TickReply, VersionReply, WorkspaceReply, SeatReply, InputReply) from .events import (IpcBaseEvent, BarconfigUpdateEvent, BindingEvent, OutputEvent, ShutdownEvent, WindowEvent, TickEvent, ModeEvent, WorkspaceEvent, InputEvent, Event) from ._private import PubSub, MessageType, EventType, Synchronizer from typing import List, Optional, Union, Callable import struct import json import socket import os from threading import Timer, Lock import time import Xlib import Xlib.display class Connection: """A connection to the i3 ipc used for querying window manager state and listening to events. The ``Connection`` class is the entry point into all features of the library. :Example: .. code-block:: python3 i3 = Connection() workspaces = i3.get_workspaces() i3.command('focus left') :param socket_path: A path to the i3 ipc socket path to connect to. If not given, find the socket path through the default search path. :type socket_path: str :param auto_reconnect: Whether to attempt to reconnect if the connection to the socket is broken when i3 restarts. :type auto_reconnect: bool :raises Exception: If the connection to i3 cannot be established. """ _MAGIC = 'i3-ipc' # safety string for i3-ipc _chunk_size = 1024 # in bytes _timeout = 0.5 # in seconds _struct_header = '=%dsII' % len(_MAGIC.encode('utf-8')) _struct_header_size = struct.calcsize(_struct_header) def __init__(self, socket_path=None, auto_reconnect=False): if not socket_path: socket_path = os.environ.get("I3SOCK") if not socket_path: socket_path = os.environ.get("SWAYSOCK") if not socket_path: try: disp = Xlib.display.Display() root = disp.screen().root i3atom = disp.intern_atom("I3_SOCKET_PATH") socket_path = root.get_full_property(i3atom, Xlib.X.AnyPropertyType).value.decode() except Exception: pass if not socket_path: raise Exception('Failed to retrieve the i3 or sway IPC socket path') self.subscriptions = 0 self._pubsub = PubSub(self) self._socket_path = socket_path self._cmd_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._cmd_socket.connect(self._socket_path) self._cmd_lock = Lock() self._sub_socket = None self._sub_lock = Lock() self._auto_reconnect = auto_reconnect self._quitting = False self._synchronizer = None def _sync(self): if self._synchronizer is None: self._synchronizer = Synchronizer() self._synchronizer.sync() @property def socket_path(self) -> str: """The path of the socket this ``Connection`` is connected to. :rtype: str """ return self._socket_path @property def auto_reconnect(self) -> bool: """Whether this ``Connection`` will attempt to reconnect when the connection to the socket is broken. :rtype: bool """ return self._auto_reconnect def _pack(self, msg_type, payload): """Packs the given message type and payload. Turns the resulting message into a byte string. """ pb = payload.encode('utf-8') s = struct.pack('=II', len(pb), msg_type.value) return self._MAGIC.encode('utf-8') + s + pb def _unpack(self, data): """Unpacks the given byte string and parses the result from JSON. Returns None on failure and saves data into "self.buffer". """ msg_magic, msg_length, msg_type = self._unpack_header(data) msg_size = self._struct_header_size + msg_length # XXX: Message shouldn't be any longer than the data payload = data[self._struct_header_size:msg_size] return payload.decode('utf-8', 'replace') def _unpack_header(self, data): """Unpacks the header of given byte string. """ return struct.unpack(self._struct_header, data[:self._struct_header_size]) def _ipc_recv(self, sock): data = sock.recv(14) if len(data) == 0: # EOF return '', 0 msg_magic, msg_length, msg_type = self._unpack_header(data) msg_size = self._struct_header_size + msg_length while len(data) < msg_size: data += sock.recv(msg_length) return self._unpack(data), msg_type def _ipc_send(self, sock, message_type, payload): """Send and receive a message from the ipc. NOTE: this is not thread safe """ sock.sendall(self._pack(message_type, payload)) data, msg_type = self._ipc_recv(sock) return data def _wait_for_socket(self): # for the auto_reconnect feature only socket_path_exists = False for tries in range(0, 500): socket_path_exists = os.path.exists(self._socket_path) if socket_path_exists: break time.sleep(0.001) return socket_path_exists def _message(self, message_type, payload): try: self._cmd_lock.acquire() return self._ipc_send(self._cmd_socket, message_type, payload) except ConnectionError as e: if not self.auto_reconnect: raise e # XXX: can the socket path change between restarts? if not self._wait_for_socket(): raise e self._cmd_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._cmd_socket.connect(self._socket_path) return self._ipc_send(self._cmd_socket, message_type, payload) finally: self._cmd_lock.release() def command(self, payload: str) -> List[CommandReply]: """Sends a command to i3. .. seealso:: https://i3wm.org/docs/userguide.html#list_of_commands :param cmd: The command to send to i3. :type cmd: str :returns: A list of replies that contain info for the result of each command given. :rtype: list(:class:`CommandReply <i3ipc.CommandReply>`) """ data = self._message(MessageType.COMMAND, payload) if data: data = json.loads(data) return CommandReply._parse_list(data) else: return [] def get_version(self) -> VersionReply: """Gets the i3 version. :returns: The i3 version. :rtype: :class:`i3ipc.VersionReply` """ data = self._message(MessageType.GET_VERSION, '') data = json.loads(data) return VersionReply(data) def get_bar_config(self, bar_id: str = None) -> Optional[BarConfigReply]: """Gets the bar configuration specified by the id. :param bar_id: The bar id to get the configuration for. If not given, get the configuration for the first bar id. :type bar_id: str :returns: The bar configuration for the bar id. :rtype: :class:`BarConfigReply <i3ipc.BarConfigReply>` or :class:`None` if no bar configuration is found. """ if not bar_id: bar_config_list = self.get_bar_config_list() if not bar_config_list: return None bar_id = bar_config_list[0] data = self._message(MessageType.GET_BAR_CONFIG, bar_id) data = json.loads(data) return BarConfigReply(data) def get_bar_config_list(self) -> List[str]: """Gets the names of all bar configurations. :returns: A list of all bar configurations. :rtype: list(str) """ data = self._message(MessageType.GET_BAR_CONFIG, '') return json.loads(data) def get_outputs(self) -> List[OutputReply]: """Gets the list of current outputs. :returns: A list of current outputs. :rtype: list(:class:`i3ipc.OutputReply`) """ data = self._message(MessageType.GET_OUTPUTS, '') data = json.loads(data) return OutputReply._parse_list(data) def get_inputs(self) -> List[InputReply]: """(sway only) Gets the inputs connected to the compositor. :returns: The reply to the inputs command :rtype: list(:class:`i3ipc.InputReply`) """ data = self._message(MessageType.GET_INPUTS, '') data = json.loads(data) return InputReply._parse_list(data) def get_seats(self) -> List[SeatReply]: """(sway only) Gets the seats configured on the compositor :returns: The reply to the seats command :rtype: list(:class:`i3ipc.SeatReply`) """ data = self._message(MessageType.GET_SEATS, '') data = json.loads(data) return SeatReply._parse_list(data) def get_workspaces(self) -> List[WorkspaceReply]: """Gets the list of current workspaces. :returns: A list of current workspaces :rtype: list(:class:`i3ipc.WorkspaceReply`) """ data = self._message(MessageType.GET_WORKSPACES, '') data = json.loads(data) return WorkspaceReply._parse_list(data) def get_tree(self) -> Con: """Gets the root container of the i3 layout tree. :returns: The root container of the i3 layout tree. :rtype: :class:`i3ipc.Con` """ data = self._message(MessageType.GET_TREE, '') return Con(json.loads(data), None, self) def get_marks(self) -> List[str]: """Gets the names of all currently set marks. :returns: A list of currently set marks. :rtype: list(str) """ data = self._message(MessageType.GET_MARKS, '') return json.loads(data) def get_binding_modes(self) -> List[str]: """Gets the names of all currently configured binding modes :returns: A list of binding modes :rtype: list(str) """ data = self._message(MessageType.GET_BINDING_MODES, '') return json.loads(data) def get_config(self) -> ConfigReply: """Returns the last loaded i3 config. :returns: A class containing the config. :rtype: :class:`i3ipc.ConfigReply` """ data = self._message(MessageType.GET_CONFIG, '') data = json.loads(data) return ConfigReply(data) def send_tick(self, payload: str = "") -> TickReply: """Sends a tick with the specified payload. :returns: The reply to the tick command :rtype: :class:`i3ipc.TickReply` """ data = self._message(MessageType.SEND_TICK, payload) data = json.loads(data) return TickReply(data) def _subscribe(self, events): events_obj = [] if events & EventType.WORKSPACE.value: events_obj.append("workspace") if events & EventType.OUTPUT.value: events_obj.append("output") if events & EventType.MODE.value: events_obj.append("mode") if events & EventType.WINDOW.value: events_obj.append("window") if events & EventType.BARCONFIG_UPDATE.value: events_obj.append("barconfig_update") if events & EventType.BINDING.value: events_obj.append("binding") if events & EventType.SHUTDOWN.value: events_obj.append("shutdown") if events & EventType.TICK.value: events_obj.append("tick") if events & EventType.INPUT.value: events_obj.append("input") try: self._sub_lock.acquire() data = self._ipc_send(self._sub_socket, MessageType.SUBSCRIBE, json.dumps(events_obj)) finally: self._sub_lock.release() data = json.loads(data) result = CommandReply(data) self.subscriptions |= events return result def off(self, handler: Callable[['Connection', IpcBaseEvent], None]): """Unsubscribe the handler from being called on ipc events. :param handler: The handler that was previously attached with :func:`on()`. :type handler: :class:`Callable` """ self._pubsub.unsubscribe(handler) def on(self, event: Union[Event, str], handler: Callable[['Connection', IpcBaseEvent], None]): """Subscribe to the event and call the handler when it is emitted by the i3 ipc. :param event: The event to subscribe to. :type event: :class:`Event <i3ipc.Event>` or str :param handler: The event handler to call. :type handler: :class:`Callable` """ if type(event) is Event: event = event.value event = event.replace('-', '_') if event.count('::') > 0: [base_event, __] = event.split('::') else: base_event = event # special case: ipc-shutdown is not in the protocol if event == 'ipc_shutdown': # TODO deprecate this self._pubsub.subscribe(event, handler) return event_type = 0 if base_event == 'workspace': event_type = EventType.WORKSPACE elif base_event == 'output': event_type = EventType.OUTPUT elif base_event == 'mode': event_type = EventType.MODE elif base_event == 'window': event_type = EventType.WINDOW elif base_event == 'barconfig_update': event_type = EventType.BARCONFIG_UPDATE elif base_event == 'binding': event_type = EventType.BINDING elif base_event == 'shutdown': event_type = EventType.SHUTDOWN elif base_event == 'tick': event_type = EventType.TICK elif base_event == 'input': event_type = EventType.INPUT if not event_type: raise Exception('event not implemented') self.subscriptions |= event_type.value self._pubsub.subscribe(event, handler) def _event_socket_setup(self): self._sub_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._sub_socket.connect(self._socket_path) self._subscribe(self.subscriptions) def _event_socket_teardown(self): if self._sub_socket: self._sub_socket.shutdown(socket.SHUT_RDWR) self._sub_socket = None def _event_socket_poll(self): if self._sub_socket is None: return True data, msg_type = self._ipc_recv(self._sub_socket) if len(data) == 0: # EOF self._pubsub.emit('ipc_shutdown', None) return True data = json.loads(data) msg_type = 1 << (msg_type & 0x7f) event_name = '' event = None if msg_type == EventType.WORKSPACE.value: event_name = 'workspace' event = WorkspaceEvent(data, self) elif msg_type == EventType.OUTPUT.value: event_name = 'output' event = OutputEvent(data) elif msg_type == EventType.MODE.value: event_name = 'mode' event = ModeEvent(data) elif msg_type == EventType.WINDOW.value: event_name = 'window' event = WindowEvent(data, self) elif msg_type == EventType.BARCONFIG_UPDATE.value: event_name = 'barconfig_update' event = BarconfigUpdateEvent(data) elif msg_type == EventType.BINDING.value: event_name = 'binding' event = BindingEvent(data) elif msg_type == EventType.SHUTDOWN.value: event_name = 'shutdown' event = ShutdownEvent(data) elif msg_type == EventType.TICK.value: event_name = 'tick' event = TickEvent(data) elif msg_type == EventType.INPUT.value: event_name = 'input' event = InputEvent(data) else: # we have not implemented this event return try: self._pubsub.emit(event_name, event) except Exception as e: print(e) raise e def main(self, timeout: float = 0.0): """Starts the main loop for this connection to start handling events. :param timeout: If given, quit the main loop after ``timeout`` seconds. :type timeout: float """ loop_exception = None self._quitting = False timer = None while True: try: self._event_socket_setup() if timeout: timer = Timer(timeout, self.main_quit) timer.start() while not self._event_socket_poll(): pass except Exception as e: loop_exception = e finally: if timer: timer.cancel() self._event_socket_teardown() if self._quitting or not self.auto_reconnect: break if not self._wait_for_socket(): break if loop_exception: raise loop_exception def main_quit(self): """Quits the running main loop for this connection.""" self._quitting = True self._event_socket_teardown() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/events.py������������������������������������������������������������������0000664�0000000�0000000�00000022311�13750052141�0016724�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from . import con from .replies import BarConfigReply, InputReply from enum import Enum class IpcBaseEvent: """An abstract base event that all events inherit from. """ pass class Event(Enum): """An enumeration of events that can be subscribed to with :func:`Connection.on()`. """ WORKSPACE = 'workspace' OUTPUT = 'output' MODE = 'mode' WINDOW = 'window' BARCONFIG_UPDATE = 'barconfig_update' BINDING = 'binding' SHUTDOWN = 'shutdown' TICK = 'tick' INPUT = 'input' WORKSPACE_FOCUS = 'workspace::focus' WORKSPACE_INIT = 'workspace::init' WORKSPACE_EMPTY = 'workspace::empty' WORKSPACE_URGENT = 'workspace::urgent' WORKSPACE_RELOAD = 'workspace::reload' WORKSPACE_RENAME = 'workspace::rename' WORKSPACE_RESTORED = 'workspace::restored' WORKSPACE_MOVE = 'workspace::move' WINDOW_NEW = 'window::new' WINDOW_CLOSE = 'window::close' WINDOW_FOCUS = 'window::focus' WINDOW_TITLE = 'window::title' WINDOW_FULLSCREEN_MODE = 'window::fullscreen_mode' WINDOW_MOVE = 'window::move' WINDOW_FLOATING = 'window::floating' WINDOW_URGENT = 'window::urgent' WINDOW_MARK = 'window::mark' SHUTDOWN_RESTART = 'shutdown::restart' SHUTDOWN_EXIT = 'shutdown::exit' INPUT_ADDED = 'input::added' INPUT_REMOVED = 'input::removed' Event._subscribable_events = [e for e in Event if '::' not in e.value] class WorkspaceEvent(IpcBaseEvent): """Sent when the user switches to a different workspace, when a new workspace is initialized or when a workspace is removed (because the last client vanished). .. seealso:: https://i3wm.org/docs/ipc.html#_workspace_event :ivar change: The type of change. :vartype change: str :ivar current: The affected workspace. :vartype current: :class:`Con` :ivar old: When the change is "focus", an old (object) property will be present with the previous workspace if it exists. :vartype old: :class:`Con` or :class:`None` :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data, conn, _Con=con.Con): self.ipc_data = data self.change = data['change'] self.current = None self.old = None if 'current' in data and data['current']: self.current = _Con(data['current'], None, conn) if 'old' in data and data['old']: self.old = _Con(data['old'], None, conn) class OutputEvent(IpcBaseEvent): """Sent when RandR issues a change notification (of either screens, outputs, CRTCs or output properties). .. seealso:: https://i3wm.org/docs/ipc.html#_output_event :ivar change: The type of change (currently only "unspecified"). :vartype change: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.change = data['change'] class ModeEvent(IpcBaseEvent): """Sent whenever i3 changes its binding mode. .. seealso:: https://i3wm.org/docs/ipc.html#_mode_event :ivar change: The name of the current mode in use. :vartype change: str :ivar pango_markup: Whether pango markup should be used for displaying this mode. :vartype pango_markup: bool :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.change = data['change'] self.pango_markup = data.get('pango_markup', False) class WindowEvent(IpcBaseEvent): """Sent when a client’s window is successfully reparented (that is when i3 has finished fitting it into a container), when a window received input focus or when certain properties of the window have changed. .. seealso:: https://i3wm.org/docs/ipc.html#_window_event :ivar change: The type of change. :vartype change: str :ivar container: The window's parent container. :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data, conn, _Con=con.Con): self.ipc_data = data self.change = data['change'] self.container = _Con(data['container'], None, conn) class BarconfigUpdateEvent(IpcBaseEvent, BarConfigReply): """Sent when the hidden_state or mode field in the barconfig of any bar instance was updated and when the config is reloaded. .. seealso:: https://i3wm.org/docs/ipc.html#_barconfig_update_event :ivar id: The ID for this bar. :vartype id: str :ivar mode: Either dock (the bar sets the dock window type) or hide (the bar does not show unless a specific key is pressed). :vartype mode: str :ivar position: Either bottom or top at the moment. :vartype position: str :ivar status_command: Command which will be run to generate a statusline. :vartype status_command: str :ivar font: The font to use for text on the bar. :vartype font: str :ivar workspace_buttons: Display workspace buttons or not. :vartype workspace_buttons: bool :ivar binding_mode_indicator: Display the mode indicator or not. :vartype binding_mode_indicator: bool :ivar verbose: Should the bar enable verbose output for debugging. :vartype verbose: bool :ivar colors: Contains key/value pairs of colors. Each value is a color code in hex, formatted #rrggbb (like in HTML). :vartype colors: dict :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ pass class BindingInfo: """Info about a binding associated with a :class:`BindingEvent`. :ivar ~.command: The i3 command that is configured to run for this binding. :vartype ~.command: str :ivar event_state_mask: The group and modifier keys that were configured with this binding. :vartype event_state_mask: list(str) :ivar input_code: If the binding was configured with bindcode, this will be the key code that was given for the binding. :vartype input_code: int :ivar symbol: If this is a keyboard binding that was configured with bindsym, this field will contain the given symbol. :vartype symbol: str or :class:`None` if this binding was not configured with a symbol. :ivar input_type: This will be "keyboard" or "mouse" depending on whether or not this was a keyboard or a mouse binding. :vartype input_type: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.command = data['command'] self.event_state_mask = data.get('event_state_mask', []) self.input_code = data['input_code'] self.symbol = data.get('symbol', None) self.input_type = data['input_type'] # sway only self.symbols = data.get('symbols', []) # not included in sway self.mods = data.get('mods', []) class BindingEvent(IpcBaseEvent): """Sent when a configured command binding is triggered with the keyboard or mouse. .. seealso:: https://i3wm.org/docs/ipc.html#_binding_event :ivar change: The type of change. :vartype change: str :ivar binding: Contains details about the binding that was run. :vartype binding: :class:`BindingInfo <i3ipc.BindingInfo>` :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.change = data['change'] self.binding = BindingInfo(data['binding']) class ShutdownEvent(IpcBaseEvent): """Sent when the ipc shuts down because of a restart or exit by user command. .. seealso:: https://i3wm.org/docs/ipc.html#_shutdown_event :ivar change: The type of change. :vartype change: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.change = data['change'] class TickEvent(IpcBaseEvent): """Sent when the ipc client subscribes to the tick event (with "first": true) or when any ipc client sends a SEND_TICK message (with "first": false). .. seealso:: https://i3wm.org/docs/ipc.html#_tick_event :ivar first: True when the ipc first subscribes to the tick event. :vartype first: bool or :class:`None` if not supported by this version of i3 (<=4.15). :ivar payload: The payload that was sent with the tick. :vartype payload: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data # i3 didn't include the 'first' field in 4.15. See i3/i3#3271. self.first = data.get('first', None) self.payload = data['payload'] class InputEvent(IpcBaseEvent): """(sway only) Sent when something related to the input devices changes. :ivar change: The type of change ("added" or "removed") :vartype change: str :ivar input: Information about the input that changed. :vartype input: :class:`InputReply <i3ipc.InputReply>` :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ def __init__(self, data): self.ipc_data = data self.change = data['change'] self.input = InputReply(data['input']) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/model.py�������������������������������������������������������������������0000664�0000000�0000000�00000004115�13750052141�0016522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Rect: """Used by other classes to represent rectangular position and dimensions. :ivar x: The x coordinate. :vartype x: int :ivar y: The y coordinate. :vartype y: int :ivar height: The height of the rectangle. :vartype height: int :ivar width: The width of the rectangle. :vartype width: int """ def __init__(self, data): self.x = data['x'] self.y = data['y'] self.height = data['height'] self.width = data['width'] class OutputMode: """(sway only) A mode for an output :ivar width: The width of the output in this mode. :vartype width: int :ivar height: The height of the output in this mode. :vartype height: int :vartype refresh: The refresh rate of the output in this mode. :vartype refresh: int """ def __init__(self, data): self.width = data['width'] self.height = data['height'] self.refresh = data['refresh'] def __getitem__(self, item): # for backwards compatability because this used to be a dict if not hasattr(self, item): raise KeyError(item) return getattr(self, item) @classmethod def _parse_list(cls, data): return [cls(d) for d in data] class Gaps: """For forks that have useless gaps, the dimension of the gaps. :ivar inner: The inner gaps. :vartype inner: int :ivar outer: The outer gaps. :vartype outer: int :ivar left: The left outer gaps. :vartype left: int or :class:`None` if not supported. :ivar right: The right outer gaps. :vartype right: int or :class:`None` if not supported. :ivar top: The top outer gaps. :vartype top: int or :class:`None` if not supported. :ivar bottom: The bottom outer gaps. :vartype bottom: int or :class:`None` if not supported. """ def __init__(self, data): self.inner = data['inner'] self.outer = data['outer'] self.left = data.get('left', None) self.right = data.get('right', None) self.top = data.get('top', None) self.bottom = data.get('bottom', None) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/i3ipc/replies.py�����������������������������������������������������������������0000664�0000000�0000000�00000026342�13750052141�0017073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .model import Rect, OutputMode class _BaseReply: def __init__(self, data): self.ipc_data = data for member in self.__class__._members: value = data.get(member[0], None) if value is not None: setattr(self, member[0], member[1](value)) else: setattr(self, member[0], None) @classmethod def _parse_list(cls, data): return [cls(d) for d in data] class CommandReply(_BaseReply): """A reply to the ``RUN_COMMAND`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_command_reply :ivar success: Whether the command succeeded. :vartype success: bool :ivar error: A human-readable error message. :vartype error: str or :class:`None` if no error message was set. :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('success', bool), ('error', str), ] class WorkspaceReply(_BaseReply): """A reply to the ``GET_WORKSPACES`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_workspaces_reply :ivar num: The logical number of the workspace. Corresponds to the command to switch to this workspace. For named workspaces, this will be -1. :vartype num: int :ivar name: The name of this workspace (by default num+1), as changed by the user. :vartype name: str :ivar visible: Whether this workspace is currently visible on an output (multiple workspaces can be visible at the same time). :vartype visible: bool :ivar focused: Whether this workspace currently has the focus (only one workspace can have the focus at the same time). :vartype focused: bool :ivar urgent: Whether a window on this workspace has the "urgent" flag set. :vartype urgent: bool :ivar rect: The rectangle of this workspace (equals the rect of the output it is on) :vartype rect: :class:`Rect` :ivar output: The video output this workspace is on (LVDS1, VGA1, ...). :vartype output: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('num', int), ('name', str), ('visible', bool), ('focused', bool), ('urgent', bool), ('rect', Rect), ('output', str), ] class OutputReply(_BaseReply): """A reply to the ``GET_OUTPUTS`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_outputs_reply :ivar name: The name of this output (as seen in xrandr(1)). :vartype name: str :ivar active: Whether this output is currently active (has a valid mode). :vartype active: bool :ivar primary: Whether this output is currently the primary output. :vartype primary: bool :ivar current_workspace: The name of the current workspace that is visible on this output. :class:`None` if the output is not active. :vartype current_workspace: str or :class:`None` if the output is not active. :ivar rect: The rectangle of this output (equals the rect of the output it is on). :vartype rect: :class:`Rect` :ivar make: (sway only) :vartype make: str :ivar model: (sway only) :vartype model: str :ivar serial: (sway only) :vartype serial: str :ivar scale: (sway only) :vartype scale: float :ivar transform: (sway only) :vartype transform: str :ivar max_render_time: (sway only) :vartype max_render_time: int :ivar focused: (sway only) :vartype focused: bool :ivar dpms: (sway only) :vartype dpms: bool :ivar subpixel_hinting: (sway only) :vartype subpixel_hinting: str :ivar modes: (sway only) :vartype modes: list(:class:`OutputMode`) :ivar current_mode: (sway only) :vartype current_mode: :class:`OutputMode` :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('name', str), ('active', bool), ('primary', bool), ('current_workspace', str), ('rect', Rect), # Sway only output fields: ('make', str), ('model', str), ('serial', str), ('scale', float), ('transform', str), ('max_render_time', int), ('focused', bool), ('dpms', bool), ('subpixel_hinting', str), ('modes', OutputMode._parse_list), ('current_mode', OutputMode), ] class BarConfigGaps: """(sway only) The useless gaps for the bar. :ivar left: The gap to the left. :vartype left: int :ivar right: The gap to the right. :vartype right: int :ivar top: The gap on the top. :vartype top: int :ivar bottom: The gap on the bottom. :vartype bottom: int """ def __init__(self, data): self.left = data['left'] self.right = data['right'] self.top = data['top'] self.bottom = data['bottom'] class BarConfigReply(_BaseReply): """A reply to the ``GET_BAR_CONFIG`` message with a specified bar id. .. seealso:: https://i3wm.org/docs/ipc.html#_bar_config_reply :ivar id: The ID for this bar. :vartype id: str :ivar mode: Either dock (the bar sets the dock window type) or hide (the bar does not show unless a specific key is pressed). :vartype mode: str :ivar position: Either bottom or top at the moment. :vartype position: str :ivar status_command: Command which will be run to generate a statusline. :vartype status_command: str :ivar font: The font to use for text on the bar. :vartype font: str :ivar workspace_buttons: Display workspace buttons or not. :vartype workspace_buttons: bool :ivar binding_mode_indicator: Display the mode indicator or not. :vartype binding_mode_indicator: bool :ivar verbose: Should the bar enable verbose output for debugging. :vartype verbose: bool :ivar colors: Contains key/value pairs of colors. Each value is a color code in hex, formatted #rrggbb (like in HTML). :vartype colors: dict :ivar tray_padding: :vartype tray_padding: int :ivar hidden_state: :vartype hidden_state: str :ivar modifier: :vartype modifier: int :ivar workspace_min_width: :vartype workspace_min_width: int :ivar strip_workspace_numbers: :vartype strip_workspace_numbers: bool :ivar strip_workspace_name: :vartype strip_workspace_name: bool :ivar gaps: (sway only) :vartype gaps: :class:`BarConfigGaps` :ivar bar_height: (sway only) :vartype bar_height: int :ivar status_padding: (sway only) :vartype status_padding: int :ivar status_edge_padding: (sway only) :vartype status_edge_padding: int :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('id', str), ('tray_padding', int), ('hidden_state', str), ('mode', str), ('modifier', int), ('position', str), ('status_command', str), ('font', str), ('workspace_buttons', bool), ('workspace_min_width', int), ('strip_workspace_numbers', bool), ('strip_workspace_name', bool), ('binding_mode_indicator', bool), ('verbose', bool), ('colors', dict), ('gaps', BarConfigGaps), ('bar_height', int), ('status_padding', int), ('status_edge_padding', int), ] class VersionReply(_BaseReply): """A reply to the ``GET_VERSION`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_version_reply :ivar major: The major version of i3. :vartype major: int :ivar minor: The minor version of i3. :vartype minor: int :ivar patch: The patch version of i3. :vartype patch: int :ivar human_readable: A human-readable version of i3 containing the precise git version, build date and branch name. :vartype human_readable: str :ivar loaded_config_file_name: The current config path. :vartype loaded_config_file_name: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('major', int), ('minor', int), ('patch', int), ('human_readable', str), ('loaded_config_file_name', str), ] class ConfigReply(_BaseReply): """A reply to the ``GET_CONFIG`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_config_reply :ivar config: A string containing the config file as loaded by i3 most recently. :vartype config: str :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('config', str), ] class TickReply(_BaseReply): """A reply to the ``SEND_TICK`` message. .. seealso:: https://i3wm.org/docs/ipc.html#_tick_reply :ivar success: Whether the tick succeeded. :vartype success: bool :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('success', bool), ] class InputReply(_BaseReply): """(sway only) A reply to ``GET_INPUTS`` message. .. seealso:: https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd :ivar identifier: The identifier for the input device. :vartype identifier: str :ivar name: The human readable name for the device :vartype name: str :ivar vendor: The vendor code for the input device :vartype vendor: int :ivar product: The product code for the input device :vartype product: int :ivar type: The device type. Currently this can be keyboard, pointer, touch, tablet_tool, tablet_pad, or switch :vartype type: str :ivar xkb_active_layout_name: (Only keyboards) The name of the active keyboard layout in use :vartype xkb_active_layout_name: str :ivar xkb_layout_names: (Only keyboards) A list a layout names configured for the keyboard :vartype xkb_layout_names: list(str) :ivar xkb_active_layout_index: (Only keyboards) The index of the active keyboard layout in use :vartype xkb_active_layout_index: int :ivar libinput: (Only libinput devices) An object describing the current device settings. :vartype libinput: dict :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [ ('identifier', str), ('name', str), ('vendor', int), ('product', int), ('type', str), ('xkb_active_layout_name', str), ('xkb_layout_names', list), ('xkb_active_layout_index', int), ('libinput', dict), ] class SeatReply(_BaseReply): """(sway only) A reply to the ``GET_SEATS`` message. .. seealso:: https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd :ivar name: The unique name for the seat. :vartype name: str :ivar capabilities: The number of capabilities the seat has. :vartype capabilities: int :ivar focus: The id of the node currently focused by the seat or _0_ when the seat is not currently focused by a node (i.e. a surface layer or xwayland unmanaged has focus) :vartype focus: int :ivar devices: An array of input devices that are attached to the seat. :vartype devices: list(:class:`InputReply`) :ivar ipc_data: The raw data from the i3 ipc. :vartype ipc_data: dict """ _members = [('name', str), ('capabilities', int), ('focus', int), ('devices', InputReply._parse_list)] ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/pytest.ini�����������������������������������������������������������������������0000664�0000000�0000000�00000000025�13750052141�0016066�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[pytest] timeout = 5 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/requirements.txt�����������������������������������������������������������������0000664�0000000�0000000�00000000162�13750052141�0017323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-xlib pytest pytest-asyncio pytest-timeout yapf flake8 sphinx sphinxcontrib-asyncio sphinxcontrib-fulltoc ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/run-tests.py���������������������������������������������������������������������0000775�0000000�0000000�00000005035�13750052141�0016364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 import subprocess from subprocess import Popen, call import os from os import listdir, path from os.path import isfile, join import sys import re import time import random from shutil import which here = os.path.abspath(os.path.dirname(__file__)) XVFB = 'Xvfb' I3_BINARY = 'i3' SOCKETDIR = '/tmp/.X11-unix' def check_dependencies(): if not which(XVFB): # TODO make this optional print('Xvfb is required to run tests') print('Command "%s" not found in PATH' % XVFB) sys.exit(127) if not which(I3_BINARY): print('i3 binary is required to run tests') print('Command "%s" not found in PATH' % I3_BINARY) sys.exit(127) def get_open_display(): if not os.path.isdir(SOCKETDIR): sys.stderr.write( 'warning: could not find the X11 socket directory at {}. Using display 0.\n' .format(SOCKETDIR)) sys.stderr.flush() return 0 socket_re = re.compile(r'^X([0-9]+)$') socket_files = [f for f in listdir(SOCKETDIR) if socket_re.match(f)] displays = [int(socket_re.search(f).group(1)) for f in socket_files] open_display = min( [i for i in range(0, max(displays or [0]) + 2) if i not in displays]) return open_display def start_server(display): xvfb = Popen([XVFB, ':%d' % display]) # wait for the socket to make sure the server is running socketfile = path.join(SOCKETDIR, 'X%d' % display) tries = 0 while True: if path.exists(socketfile): break else: tries += 1 if tries > 100: print('could not start x server') xvfb.kill() sys.exit(1) time.sleep(0.1) return xvfb def run_pytest(display): version_info = sys.version_info if version_info[0] < 3: raise NotImplementedError('tests are not implemented for python < 3') cmd = ['python3', '-m', 'pytest', '-s'] if version_info[1] < 6: cmd += ['--ignore', 'test/aio'] env = os.environ.copy() env['DISPLAY'] = ':%d' % display env['PYTHONPATH'] = here env['I3SOCK'] = '/tmp/i3ipc-test-sock-{display}'.format(display=display) return subprocess.run(cmd + sys.argv[1:], env=env) def main(): check_dependencies() call([I3_BINARY, '--version']) display = get_open_display() with start_server(display) as server: result = run_pytest(display) server.terminate() sys.exit(result.returncode) if __name__ == '__main__': main() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/setup.py�������������������������������������������������������������������������0000664�0000000�0000000�00000002717�13750052141�0015561�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 # -*- coding: utf-8 -*- import io import os from setuptools import setup, find_packages REQUIRES_PYTHON = '>=3.4.0' REQUIRED = ['python-xlib'] EXTRAS = {} here = os.path.abspath(os.path.dirname(__file__)) with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = '\n' + f.read() # Load the package's __version__.py module as a dictionary. about = {} with open(os.path.join(here, 'i3ipc', '__version__.py')) as f: exec(f.read(), about) setup( name=about['__title__'], version=about['__version__'], description=about['__description__'], long_description=long_description, author=about['__author__'], author_email=about['__author_email__'], python_requires=REQUIRES_PYTHON, url=about['__url__'], packages=find_packages(exclude=['test', '*.test', '*.test.*', 'test.*']), install_requires=REQUIRED, extras_require=EXTRAS, include_package_data=True, license='BSD', keywords='i3 i3wm extensions add-ons', classifiers=[ # Trove classifiers # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ] ) �������������������������������������������������python-i3ipc-2.2.1/test/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13750052141�0015017�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�13750052141�0015567�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/__init__.py�������������������������������������������������������������0000664�0000000�0000000�00000000000�13750052141�0017666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/ipctest.py��������������������������������������������������������������0000664�0000000�0000000�00000003702�13750052141�0017616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from subprocess import Popen import pytest from i3ipc.aio import Connection from i3ipc import CommandReply import math from random import random import asyncio from .window import Window class IpcTest: timeout_thread = None i3_conn = None @pytest.fixture(scope='class') def event_loop(self): return asyncio.get_event_loop() @pytest.fixture(scope='class') async def i3(self): process = Popen(['i3', '-c', 'test/i3.config']) # wait for i3 to start up tries = 0 while True: try: IpcTest.i3_conn = await Connection().connect() break except Exception: tries += 1 if tries > 1000: raise Exception('could not start i3') await asyncio.sleep(0.001) yield IpcTest.i3_conn try: tree = await IpcTest.i3_conn.get_tree() for l in tree.leaves(): await l.command('kill') await IpcTest.i3_conn.command('exit') except OSError: pass process.kill() process.wait() IpcTest.i3_conn = None async def command_checked(self, cmd): i3 = IpcTest.i3_conn assert i3 result = await i3.command(cmd) assert type(result) is list for r in result: assert type(r) is CommandReply assert r.success is True return result def open_window(self): window = Window() window.run() IpcTest.i3_conn._sync() return window.window.id async def fresh_workspace(self): i3 = IpcTest.i3_conn assert i3 workspaces = await i3.get_workspaces() while True: new_name = str(math.floor(random() * 100000)) if not any(w for w in workspaces if w.name == new_name): await i3.command('workspace %s' % new_name) return new_name ��������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_event_exceptions.py������������������������������������������������0000664�0000000�0000000�00000000733�13750052141�0022565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest import asyncio class HandlerException(Exception): pass class TestEventExceptions(IpcTest): def exception_throwing_handler(self, i3, e): raise HandlerException() @pytest.mark.asyncio async def test_event_exceptions(self, i3): i3.on('tick', self.exception_throwing_handler) asyncio.ensure_future(i3.send_tick()) with pytest.raises(HandlerException): await i3.main() �������������������������������������python-i3ipc-2.2.1/test/aio/test_get_bindings_modes.py����������������������������������������������0000664�0000000�0000000�00000000562�13750052141�0023026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestBindingModes(IpcTest): @pytest.mark.asyncio async def test_binding_modes(self, i3): binding_modes = await i3.get_binding_modes() assert isinstance(binding_modes, list) assert len(binding_modes) == 2 assert 'default' in binding_modes assert 'resize' in binding_modes ����������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_get_config.py������������������������������������������������������0000664�0000000�0000000�00000000562�13750052141�0021307�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import i3ipc import io import pytest class TestGetConfig(IpcTest): @pytest.mark.asyncio async def test_get_config(self, i3): config = await i3.get_config() assert isinstance(config, i3ipc.ConfigReply) with io.open('test/i3.config', 'r', encoding='utf-8') as f: assert config.config == f.read() ����������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_get_marks.py�������������������������������������������������������0000664�0000000�0000000�00000001113�13750052141�0021150�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestGetMarks(IpcTest): @pytest.mark.asyncio async def test_get_marks(self, i3): self.open_window() await self.command_checked('mark a') await self.command_checked('mark --add b') self.open_window() await self.command_checked('mark "(╯°□°)╯︵ ┻━┻"') marks = await i3.get_marks() assert isinstance(marks, list) assert len(marks) == 3 assert 'a' in marks assert 'b' in marks assert '(╯°□°)╯︵ ┻━┻' in marks �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_leaves.py����������������������������������������������������������0000664�0000000�0000000�00000000753�13750052141�0020464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestLeaves(IpcTest): @pytest.mark.asyncio async def test_workspace_leaves(self, i3): ws_name = await self.fresh_workspace() con1 = self.open_window() await self.command_checked(f'[id={con1}] floating enable') self.open_window() self.open_window() tree = await i3.get_tree() ws = [w for w in tree.workspaces() if w.name == ws_name][0] assert (len(ws.leaves()) == 3) ���������������������python-i3ipc-2.2.1/test/aio/test_output.py����������������������������������������������������������0000664�0000000�0000000�00000001021�13750052141�0020532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestOutput(IpcTest): @pytest.mark.asyncio async def test_output(self, i3): await i3.command('workspace 12') outputs = await i3.get_outputs() xroot = next(filter(lambda o: o.name == 'xroot-0', outputs)) screen = next(filter(lambda o: o.name == 'screen', outputs)) assert screen.current_workspace == '12' assert screen.primary is False assert xroot.current_workspace is None assert xroot.primary is False ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_requests.py��������������������������������������������������������0000664�0000000�0000000�00000002313�13750052141�0021052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest from i3ipc import (VersionReply, BarConfigReply, OutputReply, WorkspaceReply, ConfigReply, TickReply) from i3ipc.aio import Con import pytest class TestResquests(IpcTest): @pytest.mark.asyncio async def test_requests(self, i3): resp = await i3.get_version() assert type(resp) is VersionReply resp = await i3.get_bar_config_list() assert type(resp) is list assert 'bar-0' in resp resp = await i3.get_bar_config('bar-0') assert type(resp) is BarConfigReply resp = await i3.get_outputs() assert type(resp) is list assert resp assert type(resp[0]) is OutputReply resp = await i3.get_workspaces() assert type(resp) is list assert resp assert type(resp[0]) is WorkspaceReply resp = await i3.get_tree() assert type(resp) is Con resp = await i3.get_marks() assert type(resp) is list resp = await i3.get_binding_modes() assert type(resp) is list resp = await i3.get_config() assert type(resp) is ConfigReply resp = await i3.send_tick() assert type(resp) is TickReply ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_restart.py���������������������������������������������������������0000664�0000000�0000000�00000000375�13750052141�0020671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestRestart(IpcTest): @pytest.mark.asyncio async def test_auto_reconnect(self, i3): i3._auto_reconnect = True await i3.command('restart') assert await i3.command('nop') �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_scratchpad.py������������������������������������������������������0000664�0000000�0000000�00000001115�13750052141�0021312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest class TestScratchpad(IpcTest): @pytest.mark.asyncio async def test_scratchpad(self, i3): scratchpad = (await i3.get_tree()).scratchpad() assert scratchpad is not None assert scratchpad.name == '__i3_scratch' assert scratchpad.type == 'workspace' assert not scratchpad.floating_nodes win = self.open_window() await i3.command('move scratchpad') scratchpad = (await i3.get_tree()).scratchpad() assert scratchpad is not None assert scratchpad.floating_nodes ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_shutdown_event.py��������������������������������������������������0000664�0000000�0000000�00000001332�13750052141�0022253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest import asyncio class TestShutdownEvent(IpcTest): events = [] def restart_func(self, i3): asyncio.ensure_future(i3.command('restart')) def on_shutdown(self, i3, e): self.events.append(e) if len(self.events) == 1: i3._loop.call_later(0.1, self.restart_func, i3) elif len(self.events) == 2: i3.main_quit() @pytest.mark.asyncio async def test_shutdown_event_reconnect(self, i3): i3._auto_reconnect = True self.events = [] i3.on('shutdown::restart', self.on_shutdown) i3._loop.call_later(0.1, self.restart_func, i3) await i3.main() assert len(self.events) == 2 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_ticks.py�����������������������������������������������������������0000664�0000000�0000000�00000001512�13750052141�0020314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest import asyncio class TestTicks(IpcTest): events = [] async def on_tick(self, i3, e): self.events.append(e) if len(self.events) == 3: i3.main_quit() @pytest.mark.asyncio async def test_tick_event(self, i3): i3.on('tick', self.on_tick) def send_ticks(): asyncio.ensure_future(i3.send_tick()) asyncio.ensure_future(i3.send_tick('hello world')) i3._loop.call_later(0.1, send_ticks) await i3.main() assert len(self.events) == 3 assert self.events[0].first assert self.events[0].payload == '' assert not self.events[1].first assert self.events[1].payload == '' assert not self.events[2].first assert self.events[2].payload == 'hello world' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_window.py����������������������������������������������������������0000664�0000000�0000000�00000003267�13750052141�0020517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest from i3ipc import Event import pytest import asyncio class TestWindow(IpcTest): @pytest.mark.asyncio async def test_window_event(self, i3): event = None def on_window(i3, e): nonlocal event event = e i3.main_quit() await i3.subscribe([Event.WINDOW]) i3.on(Event.WINDOW, on_window) self.open_window() await i3.main() assert event i3.off(on_window) @pytest.mark.asyncio async def test_detailed_window_event(self, i3): events = [] def on_window(i3, e): events.append(e) async def generate_events(): win1 = self.open_window() win2 = self.open_window() await i3.command(f'[id={win1}] kill; [id={win2}] kill') # TODO sync protocol await asyncio.sleep(0.01) i3.main_quit() await i3.subscribe([Event.WINDOW]) i3.on(Event.WINDOW_NEW, on_window) asyncio.ensure_future(generate_events()) await i3.main() i3.off(on_window) assert len(events) for e in events: assert e.change == 'new' events.clear() i3.on(Event.WINDOW_FOCUS, on_window) asyncio.ensure_future(generate_events()) await i3.main() i3.off(on_window) assert len(events) for e in events: assert e.change == 'focus' @pytest.mark.asyncio async def test_marks(self, i3): await self.fresh_workspace() self.open_window() await i3.command('mark foo') tree = await i3.get_tree() assert 'foo' in tree.find_focused().marks �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/aio/test_workspace.py�������������������������������������������������������0000664�0000000�0000000�00000001717�13750052141�0021204�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from .ipctest import IpcTest import pytest import asyncio from i3ipc import Event, TickEvent events = asyncio.Queue() class TestWorkspace(IpcTest): async def on_workspace(self, i3, e): await events.put(e) async def on_tick(self, i3, e): await events.put(e) @pytest.mark.asyncio async def test_workspace(self, i3): await i3.command('workspace 0') await i3.subscribe([Event.WORKSPACE, Event.TICK]) i3.on(Event.WORKSPACE_FOCUS, self.on_workspace) i3.on(Event.TICK, self.on_tick) await i3.send_tick() assert isinstance(await events.get(), TickEvent) assert isinstance(await events.get(), TickEvent) await i3.command('workspace 12') e = await events.get() workspaces = await i3.get_workspaces() assert len(workspaces) == 1 ws = workspaces[0] assert ws.name == '12' assert e is not None assert e.current.name == '12' �������������������������������������������������python-i3ipc-2.2.1/test/aio/window.py���������������������������������������������������������������0000664�0000000�0000000�00000005635�13750052141�0017461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from Xlib import X, Xutil from Xlib.display import Display from threading import Thread class Window(object): def __init__(self, display=None): if display is None: display = Display() self.d = display self.screen = self.d.screen() bgsize = 20 bgpm = self.screen.root.create_pixmap(bgsize, bgsize, self.screen.root_depth) bggc = self.screen.root.create_gc(foreground=self.screen.black_pixel, background=self.screen.black_pixel) bgpm.fill_rectangle(bggc, 0, 0, bgsize, bgsize) bggc.change(foreground=self.screen.white_pixel) bgpm.arc(bggc, -bgsize // 2, 0, bgsize, bgsize, 0, 360 * 64) bgpm.arc(bggc, bgsize // 2, 0, bgsize, bgsize, 0, 360 * 64) bgpm.arc(bggc, 0, -bgsize // 2, bgsize, bgsize, 0, 360 * 64) bgpm.arc(bggc, 0, bgsize // 2, bgsize, bgsize, 0, 360 * 64) self.window = self.screen.root.create_window(100, 100, 400, 300, 0, self.screen.root_depth, X.InputOutput, X.CopyFromParent, background_pixmap=bgpm, event_mask=(X.StructureNotifyMask | X.ButtonReleaseMask), colormap=X.CopyFromParent) self.WM_DELETE_WINDOW = self.d.intern_atom('WM_DELETE_WINDOW') self.WM_PROTOCOLS = self.d.intern_atom('WM_PROTOCOLS') self.window.set_wm_name('i3 test window') self.window.set_wm_class('i3win', 'i3win') self.window.set_wm_protocols([self.WM_DELETE_WINDOW]) self.window.set_wm_hints(flags=Xutil.StateHint, initial_state=Xutil.NormalState) self.window.set_wm_normal_hints(flags=(Xutil.PPosition | Xutil.PSize | Xutil.PMinSize), min_width=50, min_height=50) self.window.map() display.flush() def run(self): def loop(): while True: e = self.d.next_event() if e.type == X.DestroyNotify: break elif e.type == X.ClientMessage: if e.client_type == self.WM_PROTOCOLS: fmt, data = e.data if fmt == 32 and data[0] == self.WM_DELETE_WINDOW: self.window.destroy() self.d.flush() break Thread(target=loop).start() ���������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/i3.config�������������������������������������������������������������������0000664�0000000�0000000�00000013152�13750052141�0016523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# i3 config file (v4) # # Please see http://i3wm.org/docs/userguide.html for a complete reference! # # This config file uses keycodes (bindsym) and was written for the QWERTY # layout. # # To get a config file with the same key positions, but for your current # layout, use the i3-config-wizard # # Font for window titles. Will also be used by the bar unless a different font # is used in the bar {} block below. font pango:monospace 8 # This font is widely installed, provides lots of unicode glyphs, right-to-left # text rendering and scalability on retina/hidpi displays (thanks to pango). #font pango:DejaVu Sans Mono 8 # Before i3 v4.8, we used to recommend this one as the default: # font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # The font above is very space-efficient, that is, it looks good, sharp and # clear in small sizes. However, its unicode glyph coverage is limited, the old # X core fonts rendering does not support right-to-left and this being a bitmap # font, it doesn’t scale on retina/hidpi displays. # use these keys for focus, movement, and resize directions when reaching for # the arrows is not convenient set $up l set $down k set $left j set $right semicolon # use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 # start a terminal bindsym Mod1+Return exec i3-sensible-terminal # kill focused window bindsym Mod1+Shift+q kill # start dmenu (a program launcher) bindsym Mod1+d exec dmenu_run # There also is the (new) i3-dmenu-desktop which only displays applications # shipping a .desktop file. It is a wrapper around dmenu, so you need that # installed. # bindsym Mod1+d exec --no-startup-id i3-dmenu-desktop # change focus bindsym Mod1+$left focus left bindsym Mod1+$down focus down bindsym Mod1+$up focus up bindsym Mod1+$right focus right # alternatively, you can use the cursor keys: bindsym Mod1+Left focus left bindsym Mod1+Down focus down bindsym Mod1+Up focus up bindsym Mod1+Right focus right # move focused window bindsym Mod1+Shift+$left move left bindsym Mod1+Shift+$down move down bindsym Mod1+Shift+$up move up bindsym Mod1+Shift+$right move right # alternatively, you can use the cursor keys: bindsym Mod1+Shift+Left move left bindsym Mod1+Shift+Down move down bindsym Mod1+Shift+Up move up bindsym Mod1+Shift+Right move right # split in horizontal orientation bindsym Mod1+h split h # split in vertical orientation bindsym Mod1+v split v # enter fullscreen mode for the focused container bindsym Mod1+f fullscreen toggle # change container layout (stacked, tabbed, toggle split) bindsym Mod1+s layout stacking bindsym Mod1+w layout tabbed bindsym Mod1+e layout toggle split # toggle tiling / floating bindsym Mod1+Shift+space floating toggle # change focus between tiling / floating windows bindsym Mod1+space focus mode_toggle # focus the parent container bindsym Mod1+a focus parent # focus the child container #bindsym Mod1+d focus child # move the currently focused window to the scratchpad bindsym Mod1+Shift+minus move scratchpad # Show the next scratchpad window or hide the focused scratchpad window. # If there are multiple scratchpad windows, this command cycles through them. bindsym Mod1+minus scratchpad show # switch to workspace bindsym Mod1+1 workspace 1 bindsym Mod1+2 workspace 2 bindsym Mod1+3 workspace 3 bindsym Mod1+4 workspace 4 bindsym Mod1+5 workspace 5 bindsym Mod1+6 workspace 6 bindsym Mod1+7 workspace 7 bindsym Mod1+8 workspace 8 bindsym Mod1+9 workspace 9 bindsym Mod1+0 workspace 10 # move focused container to workspace bindsym Mod1+Shift+1 move container to workspace 1 bindsym Mod1+Shift+2 move container to workspace 2 bindsym Mod1+Shift+3 move container to workspace 3 bindsym Mod1+Shift+4 move container to workspace 4 bindsym Mod1+Shift+5 move container to workspace 5 bindsym Mod1+Shift+6 move container to workspace 6 bindsym Mod1+Shift+7 move container to workspace 7 bindsym Mod1+Shift+8 move container to workspace 8 bindsym Mod1+Shift+9 move container to workspace 9 bindsym Mod1+Shift+0 move container to workspace 10 # reload the configuration file bindsym Mod1+Shift+c reload # restart i3 inplace (preserves your layout/session, can be used to upgrade i3) bindsym Mod1+Shift+r restart # exit i3 (logs you out of your X session) bindsym Mod1+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'" # resize window (you can also use the mouse for that) mode "resize" { # These bindings trigger as soon as you enter the resize mode # Pressing left will shrink the window’s width. # Pressing right will grow the window’s width. # Pressing up will shrink the window’s height. # Pressing down will grow the window’s height. bindsym $left resize shrink width 10 px or 10 ppt bindsym $down resize grow height 10 px or 10 ppt bindsym $up resize shrink height 10 px or 10 ppt bindsym $right resize grow width 10 px or 10 ppt # same bindings, but for the arrow keys bindsym Left resize shrink width 10 px or 10 ppt bindsym Down resize grow height 10 px or 10 ppt bindsym Up resize shrink height 10 px or 10 ppt bindsym Right resize grow width 10 px or 10 ppt # back to normal: Enter or Escape bindsym Return mode "default" bindsym Escape mode "default" } bindsym Mod1+r mode "resize" # Start i3bar to display a workspace bar (plus the system information i3status # finds out, if available) bar { #status_command i3status i3bar_command /bin/true } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/ipctest.py������������������������������������������������������������������0000664�0000000�0000000�00000002647�13750052141�0017055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from subprocess import Popen import pytest import i3ipc import math from random import random import time from aio.window import Window class IpcTest: i3_conn = None @pytest.fixture(scope='class') def i3(self): process = Popen(['i3', '-c', 'test/i3.config']) # wait for i3 to start up tries = 0 while True: try: IpcTest.i3_conn = i3ipc.Connection() break except Exception: tries += 1 if tries > 1000: raise Exception('could not start i3') time.sleep(0.01) yield IpcTest.i3_conn try: tree = IpcTest.i3_conn.get_tree() for l in tree.leaves(): l.command('kill') IpcTest.i3_conn.command('exit') except OSError: pass process.kill() process.wait() IpcTest.i3_conn = None def open_window(self): window = Window() window.run() IpcTest.i3_conn._sync() return window.window.id def fresh_workspace(self): i3 = IpcTest.i3_conn assert i3 workspaces = i3.get_workspaces() while True: new_name = str(math.floor(random() * 100000)) if not any(w for w in workspaces if w.name == new_name): i3.command('workspace %s' % new_name) return new_name �����������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_event_exceptions.py����������������������������������������������������0000664�0000000�0000000�00000000677�13750052141�0022024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest from threading import Timer import pytest class HandlerException(Exception): pass class TestEventExceptions(IpcTest): def exception_throwing_handler(self, i3, e): raise HandlerException() def test_event_exceptions(self, i3): i3.on('tick', self.exception_throwing_handler) Timer(0.001, i3.send_tick).start() with pytest.raises(HandlerException): i3.main() �����������������������������������������������������������������python-i3ipc-2.2.1/test/test_get_bindings_modes.py��������������������������������������������������0000664�0000000�0000000�00000000475�13750052141�0022261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest class TestBindingModes(IpcTest): def test_binding_modes(self, i3): binding_modes = i3.get_binding_modes() assert isinstance(binding_modes, list) assert len(binding_modes) == 2 assert 'default' in binding_modes assert 'resize' in binding_modes ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_get_config.py����������������������������������������������������������0000664�0000000�0000000�00000000475�13750052141�0020542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest import i3ipc import io class TestGetConfig(IpcTest): def test_get_config(self, i3): config = i3.get_config() assert isinstance(config, i3ipc.ConfigReply) with io.open('test/i3.config', 'r', encoding='utf-8') as f: assert config.config == f.read() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_get_marks.py�����������������������������������������������������������0000664�0000000�0000000�00000001036�13750052141�0020404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# coding=utf-8 from __future__ import unicode_literals from ipctest import IpcTest class TestGetMarks(IpcTest): def test_get_marks(self, i3): self.open_window() i3.command('mark a') i3.command('mark --add b') self.open_window() i3.command('mark "(╯°□°)╯︵ ┻━┻"') marks = i3.get_marks() assert isinstance(marks, list) assert len(marks) == 3 assert 'a' in marks assert 'b' in marks assert '(╯°□°)╯︵ ┻━┻' in marks ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_leaves.py��������������������������������������������������������������0000664�0000000�0000000�00000000616�13750052141�0017712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest class TestLeaves(IpcTest): def test_workspace_leaves(self, i3): ws_name = self.fresh_workspace() con1 = self.open_window() i3.command('[id=%s] floating enable' % con1) self.open_window() self.open_window() ws = [w for w in i3.get_tree().workspaces() if w.name == ws_name][0] assert (len(ws.leaves()) == 3) ������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_restart.py�������������������������������������������������������������0000664�0000000�0000000�00000000302�13750052141�0020107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest class TestRestart(IpcTest): def test_auto_reconnect(self, i3): i3._auto_reconnect = True i3.command('restart') assert i3.command('nop') ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_shutdown_event.py������������������������������������������������������0000664�0000000�0000000�00000001316�13750052141�0021505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from threading import Timer from ipctest import IpcTest class TestShutdownEvent(IpcTest): events = [] def restart_func(self, i3): i3.command('restart') def on_shutdown(self, i3, e): self.events.append(e) assert i3._wait_for_socket() if len(self.events) == 1: Timer(0.1, self.restart_func, args=(i3, )).start() elif len(self.events) == 2: i3.main_quit() def test_shutdown_event_reconnect(self, i3): i3._auto_reconnect = True self.events = [] i3.on('shutdown::restart', self.on_shutdown) Timer(0.2, self.restart_func, args=(i3, )).start() i3.main(timeout=1) assert len(self.events) == 2 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_ticks.py���������������������������������������������������������������0000664�0000000�0000000�00000001357�13750052141�0017553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ipctest import IpcTest class TestTicks(IpcTest): events = [] def on_tick(self, i3, e): self.events.append(e) if len(self.events) == 3: i3.main_quit() def test_tick_event(self, i3): i3.on('tick', self.on_tick) i3._event_socket_setup() i3.send_tick() i3.send_tick('hello world') while not i3._event_socket_poll(): pass i3._event_socket_teardown() assert len(self.events) == 3 assert self.events[0].first assert self.events[0].payload == '' assert not self.events[1].first assert self.events[1].payload == '' assert not self.events[2].first assert self.events[2].payload == 'hello world' ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������python-i3ipc-2.2.1/test/test_window.py��������������������������������������������������������������0000664�0000000�0000000�00000002754�13750052141�0017747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from i3ipc import Event import time from ipctest import IpcTest from threading import Timer class TestWindow(IpcTest): def test_window_event(self, i3): event = None def on_window(i3, e): nonlocal event event = e i3.main_quit() i3.on('window', on_window) Timer(0.001, self.open_window).start() i3.main(timeout=2) assert event is not None i3.off(on_window) def test_marks(self, i3): self.fresh_workspace() self.open_window() i3.command('mark foo') assert 'foo' in i3.get_tree().find_focused().marks def test_detailed_window_event(self, i3): events = [] def generate_events(): win1 = self.open_window() win2 = self.open_window() i3.command(f'[id={win1}] kill; [id={win2}] kill') # TODO sync protocol time.sleep(0.01) i3.main_quit() def on_window(i3, e): nonlocal events events.append(e) i3.on(Event.WINDOW_NEW, on_window) Timer(0.01, generate_events).start() i3.main(timeout=2) assert len(events) for e in events: assert e.change == 'new' events.clear() i3.off(on_window) i3.on(Event.WINDOW_FOCUS, on_window) Timer(0.01, generate_events).start() i3.main(timeout=2) assert len(events) for e in events: assert e.change == 'focus' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������