├── .github └── workflows │ ├── prepare-release.yml │ ├── publish-testing.yml │ ├── publish-unstable.yml │ └── test-pr.yml ├── .gitignore ├── COPYING ├── Makefile.am ├── autogen.sh ├── bar.c ├── bar.h ├── block.c ├── block.h ├── config.c ├── config.h ├── configure.ac ├── debian ├── changelog ├── compat ├── control ├── copyright ├── gbp.conf ├── rules └── source │ └── format ├── docs ├── README.adoc ├── Rakefile ├── blocklets-docinfo.html ├── blocklets.adoc ├── blocklets.css ├── blocklets.html ├── blocklets.js ├── i3xrocks.1 ├── i3xrocks.1.adoc ├── i3xrocks.1.html ├── index.html └── pre-commit ├── i3bar.c ├── i3xrocks.conf ├── ini.c ├── ini.h ├── json.c ├── json.h ├── line.c ├── line.h ├── log.h ├── main.c ├── map.c ├── map.h ├── sys.c ├── sys.h └── term.h /.github/workflows/prepare-release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare a Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - debian/changelog 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | release: 17 | runs-on: ubuntu-24.04 18 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Prepare Release 24 | id: prepare 25 | uses: regolith-linux/actions/prepare-release@main 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.ORG_BROADCAST_TOKEN2 }} 28 | with: 29 | name: "${{ github.event.repository.name }}" 30 | repo: "${{ github.server_url }}/${{ github.repository }}.git" 31 | ref: "${{ github.ref_name }}" 32 | 33 | - name: Push Changes to Voulage 34 | uses: stefanzweifel/git-auto-commit-action@v5 35 | if: ${{ steps.prepare.outputs.release-exists == 'false' }} 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.ORG_BROADCAST_TOKEN2 }} 38 | with: 39 | repository: "${{ steps.prepare.outputs.voulage-path }}" 40 | branch: "main" 41 | file_pattern: "stage/testing/**" 42 | commit_message: "chore: bump ${{ github.event.repository.name }} testing to ${{ steps.prepare.outputs.release-version }}" 43 | commit_user_name: regolith-ci-bot 44 | commit_user_email: bot@regolith-desktop.com 45 | commit_author: "regolith-ci-bot " 46 | 47 | - name: Release Package 48 | uses: softprops/action-gh-release@v2 49 | if: ${{ steps.prepare.outputs.release-exists == 'false' }} 50 | with: 51 | name: ${{ steps.prepare.outputs.release-version }} 52 | tag_name: ${{ steps.prepare.outputs.release-version }} 53 | token: ${{ secrets.ORG_BROADCAST_TOKEN2 }} 54 | target_commitish: "${{ github.sha }}" 55 | generate_release_notes: true 56 | -------------------------------------------------------------------------------- /.github/workflows/publish-testing.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Testing 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | matrix-builder: 14 | runs-on: ubuntu-24.04 15 | outputs: 16 | includes: ${{ steps.builder.outputs.includes }} 17 | runners: ${{ steps.builder.outputs.runners }} 18 | steps: 19 | - name: Build Matrix 20 | id: builder 21 | uses: regolith-linux/actions/build-matrix@main 22 | with: 23 | name: "${{ github.event.repository.name }}" 24 | ref: "${{ github.ref_name }}" 25 | arch: "amd64 arm64" 26 | stage: "testing" 27 | 28 | build: 29 | runs-on: ${{ fromJSON(needs.matrix-builder.outputs.runners)[matrix.arch] }} 30 | needs: matrix-builder 31 | container: "ghcr.io/regolith-linux/ci-${{ matrix.distro }}:${{ matrix.codename }}-${{ matrix.arch }}" 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | include: ${{ fromJSON(needs.matrix-builder.outputs.includes) }} 36 | env: 37 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 38 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | 43 | - name: Import GPG Key 44 | uses: regolith-linux/actions/import-gpg@main 45 | with: 46 | gpg-key: "${{ secrets.PACKAGE_PRIVATE_KEY2 }}" 47 | 48 | - name: Build Package 49 | id: build 50 | uses: regolith-linux/actions/build-package@main 51 | with: 52 | name: "${{ github.event.repository.name }}" 53 | distro: "${{ matrix.distro }}" 54 | codename: "${{ matrix.codename }}" 55 | stage: "testing" 56 | suite: "testing" 57 | component: "main" 58 | arch: "${{ matrix.arch }}" 59 | 60 | - name: Setup SSH 61 | uses: regolith-linux/actions/setup-ssh@main 62 | with: 63 | ssh-host: "${{ env.server-address }}" 64 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 65 | 66 | - name: Upload Package 67 | uses: regolith-linux/actions/upload-files@main 68 | with: 69 | upload-to-folder: "${{ github.event.repository.name }}" 70 | 71 | - name: Upload SourceLog 72 | uses: regolith-linux/actions/upload-files@main 73 | with: 74 | upload-from: "${{ steps.build.outputs.buildlog-path }}" 75 | upload-pattern: "SOURCELOG_*.txt" 76 | upload-to-base: "/opt/archives/workspace/" 77 | upload-to-folder: "${{ github.event.repository.name }}" 78 | 79 | sources: 80 | runs-on: ubuntu-24.04 81 | needs: build 82 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 83 | if: ${{ !failure() && !cancelled() }} 84 | env: 85 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 86 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 87 | steps: 88 | - name: Import GPG Key 89 | uses: regolith-linux/actions/import-gpg@main 90 | with: 91 | gpg-key: "${{ secrets.PACKAGE_PRIVATE_KEY2 }}" 92 | 93 | - name: Setup SSH 94 | uses: regolith-linux/actions/setup-ssh@main 95 | with: 96 | ssh-host: "${{ env.server-address }}" 97 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 98 | 99 | - name: Rebuild Sources 100 | uses: regolith-linux/actions/rebuild-sources@main 101 | with: 102 | workspace-subfolder: "${{ github.event.repository.name }}" 103 | only-component: "testing" 104 | only-package: "${{ github.event.repository.name }}" 105 | 106 | publish: 107 | runs-on: ubuntu-24.04 108 | needs: sources 109 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 110 | if: ${{ !failure() && !cancelled() }} 111 | env: 112 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 113 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 114 | steps: 115 | - name: Setup SSH 116 | uses: regolith-linux/actions/setup-ssh@main 117 | with: 118 | ssh-host: "${{ env.server-address }}" 119 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 120 | 121 | - name: Publish Repo 122 | uses: regolith-linux/actions/publish-repo@main 123 | with: 124 | packages-path-subfolder: "${{ github.event.repository.name }}" 125 | only-component: "testing" 126 | 127 | manifests: 128 | runs-on: ubuntu-24.04 129 | needs: [matrix-builder, publish] 130 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 131 | if: ${{ !failure() && !cancelled() }} 132 | steps: 133 | - name: Update Manifests 134 | uses: regolith-linux/actions/update-manifest@main 135 | env: 136 | GITHUB_TOKEN: ${{ secrets.ORG_BROADCAST_TOKEN2 }} 137 | with: 138 | name: "${{ github.event.repository.name }}" 139 | repo: "${{ github.server_url }}/${{ github.repository }}.git" 140 | ref: "${{ github.ref_name }}" 141 | sha: "${{ github.sha }}" 142 | matrix: "${{ needs.matrix-builder.outputs.includes }}" 143 | suite: "testing" 144 | component: "main" 145 | -------------------------------------------------------------------------------- /.github/workflows/publish-unstable.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Unstable 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | matrix-builder: 15 | runs-on: ubuntu-24.04 16 | outputs: 17 | includes: ${{ steps.builder.outputs.includes }} 18 | runners: ${{ steps.builder.outputs.runners }} 19 | steps: 20 | - name: Build Matrix 21 | id: builder 22 | uses: regolith-linux/actions/build-matrix@main 23 | with: 24 | name: "${{ github.event.repository.name }}" 25 | ref: "${{ github.ref_name }}" 26 | arch: "amd64 arm64" 27 | stage: "unstable" 28 | 29 | build: 30 | runs-on: ${{ fromJSON(needs.matrix-builder.outputs.runners)[matrix.arch] }} 31 | needs: matrix-builder 32 | container: "ghcr.io/regolith-linux/ci-${{ matrix.distro }}:${{ matrix.codename }}-${{ matrix.arch }}" 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | include: ${{ fromJSON(needs.matrix-builder.outputs.includes) }} 37 | env: 38 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 39 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | 44 | - name: Import GPG Key 45 | uses: regolith-linux/actions/import-gpg@main 46 | with: 47 | gpg-key: "${{ secrets.PACKAGE_PRIVATE_KEY2 }}" 48 | 49 | - name: Build Package 50 | id: build 51 | uses: regolith-linux/actions/build-package@main 52 | with: 53 | name: "${{ github.event.repository.name }}" 54 | distro: "${{ matrix.distro }}" 55 | codename: "${{ matrix.codename }}" 56 | stage: "unstable" 57 | suite: "unstable" 58 | component: "main" 59 | arch: "${{ matrix.arch }}" 60 | 61 | - name: Setup SSH 62 | uses: regolith-linux/actions/setup-ssh@main 63 | with: 64 | ssh-host: "${{ env.server-address }}" 65 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 66 | 67 | - name: Upload Package 68 | uses: regolith-linux/actions/upload-files@main 69 | with: 70 | upload-to-folder: "${{ github.event.repository.name }}" 71 | 72 | - name: Upload SourceLog 73 | uses: regolith-linux/actions/upload-files@main 74 | with: 75 | upload-from: "${{ steps.build.outputs.buildlog-path }}" 76 | upload-pattern: "SOURCELOG_*.txt" 77 | upload-to-base: "/opt/archives/workspace/" 78 | upload-to-folder: "${{ github.event.repository.name }}" 79 | 80 | sources: 81 | runs-on: ubuntu-24.04 82 | needs: build 83 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 84 | if: ${{ !failure() && !cancelled() }} 85 | env: 86 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 87 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 88 | steps: 89 | - name: Import GPG Key 90 | uses: regolith-linux/actions/import-gpg@main 91 | with: 92 | gpg-key: "${{ secrets.PACKAGE_PRIVATE_KEY2 }}" 93 | 94 | - name: Setup SSH 95 | uses: regolith-linux/actions/setup-ssh@main 96 | with: 97 | ssh-host: "${{ env.server-address }}" 98 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 99 | 100 | - name: Rebuild Sources 101 | uses: regolith-linux/actions/rebuild-sources@main 102 | with: 103 | workspace-subfolder: "${{ github.event.repository.name }}" 104 | only-component: "unstable" 105 | only-package: "${{ github.event.repository.name }}" 106 | 107 | publish: 108 | runs-on: ubuntu-24.04 109 | needs: sources 110 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 111 | if: ${{ !failure() && !cancelled() }} 112 | env: 113 | server-address: "${{ secrets.KAMATERA_HOSTNAME2 }}" 114 | server-username: "${{ secrets.KAMATERA_USERNAME }}" 115 | steps: 116 | - name: Setup SSH 117 | uses: regolith-linux/actions/setup-ssh@main 118 | with: 119 | ssh-host: "${{ env.server-address }}" 120 | ssh-key: "${{ secrets.KAMATERA_SSH_KEY }}" 121 | 122 | - name: Publish Repo 123 | uses: regolith-linux/actions/publish-repo@main 124 | with: 125 | packages-path-subfolder: "${{ github.event.repository.name }}" 126 | only-component: "unstable" 127 | 128 | manifests: 129 | runs-on: ubuntu-24.04 130 | needs: [matrix-builder, publish] 131 | container: "ghcr.io/regolith-linux/ci-ubuntu:noble-amd64" 132 | if: ${{ !failure() && !cancelled() }} 133 | steps: 134 | - name: Update Manifests 135 | uses: regolith-linux/actions/update-manifest@main 136 | env: 137 | GITHUB_TOKEN: ${{ secrets.ORG_BROADCAST_TOKEN2 }} 138 | with: 139 | name: "${{ github.event.repository.name }}" 140 | repo: "${{ github.server_url }}/${{ github.repository }}.git" 141 | ref: "${{ github.ref_name }}" 142 | sha: "${{ github.sha }}" 143 | matrix: "${{ needs.matrix-builder.outputs.includes }}" 144 | suite: "unstable" 145 | component: "main" 146 | -------------------------------------------------------------------------------- /.github/workflows/test-pr.yml: -------------------------------------------------------------------------------- 1 | name: Test Pull Request 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | matrix-builder: 12 | runs-on: ubuntu-24.04 13 | outputs: 14 | includes: ${{ steps.builder.outputs.includes }} 15 | runners: ${{ steps.builder.outputs.runners }} 16 | steps: 17 | - name: Build Matrix 18 | id: builder 19 | uses: regolith-linux/actions/build-matrix@main 20 | with: 21 | name: "${{ github.event.repository.name }}" 22 | ref: "${{ github.base_ref }}" # build for target branch of the pull request 23 | arch: "amd64" # only test on amd64 on pull requests 24 | stage: "unstable" 25 | 26 | build: 27 | runs-on: ${{ fromJSON(needs.matrix-builder.outputs.runners)[matrix.arch] }} 28 | needs: matrix-builder 29 | container: "ghcr.io/regolith-linux/ci-${{ matrix.distro }}:${{ matrix.codename }}-${{ matrix.arch }}" 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | include: ${{ fromJSON(needs.matrix-builder.outputs.includes) }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | 38 | - name: Build Package 39 | uses: regolith-linux/actions/build-package@main 40 | with: 41 | only-build: "true" 42 | name: "${{ github.event.repository.name }}" 43 | distro: "${{ matrix.distro }}" 44 | codename: "${{ matrix.codename }}" 45 | stage: "unstable" 46 | suite: "unstable" 47 | component: "main" 48 | arch: "${{ matrix.arch }}" 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | i3xrocks 3 | .deps/ 4 | Makefile 5 | Makefile.in 6 | aclocal.m4 7 | autom4te.cache/ 8 | build-aux/ 9 | config.log 10 | config.status 11 | configure 12 | i3xrocks-config.h* 13 | stamp-h1 14 | 15 | debian/files 16 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = i3xrocks 2 | i3xrocks_LDADD = -lxcb -lxcb-xrm 3 | i3xrocks_SOURCES = \ 4 | bar.c \ 5 | bar.h \ 6 | block.c \ 7 | block.h \ 8 | config.c \ 9 | config.h \ 10 | i3bar.c \ 11 | ini.c \ 12 | ini.h \ 13 | json.c \ 14 | json.h \ 15 | line.c \ 16 | line.h \ 17 | log.h \ 18 | main.c \ 19 | map.c \ 20 | map.h \ 21 | sys.c \ 22 | sys.h \ 23 | term.h 24 | 25 | dist_man1_MANS = \ 26 | docs/i3xrocks.1 27 | 28 | dist_sysconf_DATA = \ 29 | i3xrocks.conf 30 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | autoreconf -fiv 3 | -------------------------------------------------------------------------------- /bar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bar.c - status line handling functions 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "bar.h" 25 | #include "block.h" 26 | #include "config.h" 27 | #include "json.h" 28 | #include "line.h" 29 | #include "log.h" 30 | #include "map.h" 31 | #include "sched.h" 32 | #include "sys.h" 33 | #include "term.h" 34 | 35 | static void bar_read(struct bar *bar) 36 | { 37 | int err; 38 | 39 | err = i3bar_click(bar); 40 | if (err) 41 | bar_error(bar, "failed to read bar"); 42 | } 43 | 44 | static void bar_print(struct bar *bar) 45 | { 46 | int err; 47 | 48 | err = i3bar_print(bar); 49 | if (err) 50 | fatal("failed to print bar!"); 51 | } 52 | 53 | static int bar_start(struct bar *bar) 54 | { 55 | int err; 56 | 57 | err = i3bar_start(bar); 58 | if (err) 59 | return err; 60 | 61 | debug("bar started"); 62 | 63 | return 0; 64 | } 65 | 66 | static void bar_stop(struct bar *bar) 67 | { 68 | i3bar_stop(bar); 69 | 70 | debug("bar stopped"); 71 | } 72 | 73 | static void bar_poll_timed(struct bar *bar) 74 | { 75 | struct block *block = bar->blocks; 76 | 77 | while (block) { 78 | /* spawn unless it is only meant for click or signal */ 79 | if (block->interval != 0) { 80 | block_spawn(block); 81 | block_touch(block); 82 | } 83 | 84 | block = block->next; 85 | } 86 | } 87 | 88 | static void bar_poll_expired(struct bar *bar) 89 | { 90 | struct block *block = bar->blocks; 91 | 92 | while (block) { 93 | if (block->interval > 0) { 94 | const unsigned long next_update = block->timestamp + block->interval; 95 | unsigned long now; 96 | int err; 97 | 98 | err = sys_gettime(&now); 99 | if (err) 100 | return; 101 | 102 | if (((long) (next_update - now)) <= 0) { 103 | block_debug(block, "expired"); 104 | block_spawn(block); 105 | block_touch(block); 106 | } 107 | } 108 | 109 | block = block->next; 110 | } 111 | } 112 | 113 | static void bar_poll_signaled(struct bar *bar, int sig) 114 | { 115 | struct block *block = bar->blocks; 116 | 117 | while (block) { 118 | if (block->signal == sig) { 119 | block_debug(block, "signaled"); 120 | block_spawn(block); 121 | block_touch(block); 122 | } 123 | 124 | block = block->next; 125 | } 126 | } 127 | 128 | static void bar_poll_exited(struct bar *bar) 129 | { 130 | struct block *block; 131 | pid_t pid; 132 | int err; 133 | 134 | for (;;) { 135 | err = sys_waitid(&pid); 136 | if (err) 137 | break; 138 | 139 | /* Find the dead process */ 140 | block = bar->blocks; 141 | while (block) { 142 | if (block->pid == pid) 143 | break; 144 | 145 | block = block->next; 146 | } 147 | 148 | if (block) { 149 | block_debug(block, "exited"); 150 | block_reap(block); 151 | if (block->interval == INTERVAL_PERSIST) { 152 | block_debug(block, "unexpected exit?"); 153 | } else { 154 | block_update(block); 155 | } 156 | block_close(block); 157 | if (block->interval == INTERVAL_REPEAT) { 158 | block_spawn(block); 159 | block_touch(block); 160 | } 161 | } else { 162 | error("unknown child process %d", pid); 163 | err = sys_waitpid(pid, NULL); 164 | if (err) 165 | break; 166 | } 167 | } 168 | } 169 | 170 | static void bar_poll_readable(struct bar *bar, const int fd) 171 | { 172 | struct block *block = bar->blocks; 173 | 174 | while (block) { 175 | if (block->out[0] == fd) { 176 | block_debug(block, "readable"); 177 | block_update(block); 178 | break; 179 | } 180 | 181 | block = block->next; 182 | } 183 | } 184 | 185 | static int gcd(int a, int b) 186 | { 187 | while (b != 0) 188 | a %= b, a ^= b, b ^= a, a ^= b; 189 | 190 | return a; 191 | } 192 | 193 | static int bar_setup(struct bar *bar) 194 | { 195 | struct block *block = bar->blocks; 196 | sigset_t *set = &bar->sigset; 197 | unsigned long sleeptime = 0; 198 | int sig; 199 | int err; 200 | 201 | while (block) { 202 | err = block_setup(block); 203 | if (err) 204 | return err; 205 | 206 | /* The maximum sleep time is actually the GCD 207 | * between all positive block intervals. 208 | */ 209 | if (block->interval > 0) { 210 | if (sleeptime > 0) 211 | sleeptime = gcd(sleeptime, block->interval); 212 | else 213 | sleeptime = block->interval; 214 | } 215 | 216 | block = block->next; 217 | } 218 | 219 | err = sys_sigemptyset(set); 220 | if (err) 221 | return err; 222 | 223 | /* Control signals */ 224 | err = sys_sigaddset(set, SIGTERM); 225 | if (err) 226 | return err; 227 | 228 | err = sys_sigaddset(set, SIGINT); 229 | if (err) 230 | return err; 231 | 232 | /* Timer signal */ 233 | err = sys_sigaddset(set, SIGALRM); 234 | if (err) 235 | return err; 236 | 237 | /* Block updates (forks) */ 238 | err = sys_sigaddset(set, SIGCHLD); 239 | if (err) 240 | return err; 241 | 242 | /* Deprecated signals */ 243 | err = sys_sigaddset(set, SIGUSR1); 244 | if (err) 245 | return err; 246 | 247 | err = sys_sigaddset(set, SIGUSR2); 248 | if (err) 249 | return err; 250 | 251 | /* Click signal */ 252 | err = sys_sigaddset(set, SIGIO); 253 | if (err) 254 | return err; 255 | 256 | /* I/O Possible signal for persistent blocks */ 257 | err = sys_sigaddset(set, SIGRTMIN); 258 | if (err) 259 | return err; 260 | 261 | /* Real-time signals for blocks */ 262 | for (sig = SIGRTMIN + 1; sig <= SIGRTMAX; sig++) { 263 | err = sys_sigaddset(set, sig); 264 | if (err) 265 | return err; 266 | } 267 | 268 | /* Block signals for which we are interested in waiting */ 269 | err = sys_sigsetmask(set); 270 | if (err) 271 | return err; 272 | 273 | if (sleeptime) { 274 | err = sys_setitimer(sleeptime); 275 | if (err) 276 | return err; 277 | } 278 | 279 | err = sys_cloexec(STDIN_FILENO); 280 | if (err) 281 | return err; 282 | 283 | /* Setup event I/O for stdin (clicks) */ 284 | err = sys_async(STDIN_FILENO, SIGIO); 285 | if (err) 286 | return err; 287 | 288 | debug("bar set up"); 289 | 290 | return 0; 291 | } 292 | 293 | static void bar_teardown(struct bar *bar) 294 | { 295 | struct block *block = bar->blocks; 296 | int err; 297 | 298 | /* Disable event I/O for blocks (persistent) */ 299 | while (block) { 300 | if (block->interval == INTERVAL_PERSIST) { 301 | err = sys_async(block->out[0], 0); 302 | if (err) 303 | block_error(block, "failed to disable event I/O"); 304 | } 305 | 306 | block = block->next; 307 | } 308 | 309 | /* Disable event I/O for stdin (clicks) */ 310 | err = sys_async(STDIN_FILENO, 0); 311 | if (err) 312 | error("failed to disable event I/O on stdin"); 313 | 314 | /* 315 | * Unblock signals (so subsequent syscall can be interrupted) 316 | * and wait for child processes termination. 317 | */ 318 | err = sys_sigunblock(&bar->sigset); 319 | if (err) 320 | error("failed to unblock signals"); 321 | 322 | err = sys_waitanychild(); 323 | if (err) 324 | error("failed to wait for any child"); 325 | 326 | debug("bar tear down"); 327 | } 328 | 329 | static int bar_poll(struct bar *bar) 330 | { 331 | int sig, fd; 332 | int err; 333 | 334 | err = bar_setup(bar); 335 | if (err) 336 | return err; 337 | 338 | /* Initial display (for static blocks and loading labels) */ 339 | bar_print(bar); 340 | 341 | /* First forks (for commands with an interval) */ 342 | bar_poll_timed(bar); 343 | 344 | while (1) { 345 | err = sys_sigwaitinfo(&bar->sigset, &sig, &fd); 346 | if (err) { 347 | /* Hiding the bar may interrupt this system call */ 348 | if (err == -EINTR) 349 | continue; 350 | break; 351 | } 352 | 353 | if (sig == SIGTERM || sig == SIGINT) 354 | break; 355 | 356 | if (sig == SIGALRM) { 357 | bar_poll_expired(bar); 358 | continue; 359 | } 360 | 361 | if (sig == SIGCHLD) { 362 | bar_poll_exited(bar); 363 | bar_print(bar); 364 | continue; 365 | } 366 | 367 | if (sig == SIGIO) { 368 | bar_read(bar); 369 | continue; 370 | } 371 | 372 | if (sig == SIGRTMIN) { 373 | bar_poll_readable(bar, fd); 374 | bar_print(bar); 375 | continue; 376 | } 377 | 378 | if (sig > SIGRTMIN && sig <= SIGRTMAX) { 379 | bar_poll_signaled(bar, sig - SIGRTMIN); 380 | continue; 381 | } 382 | 383 | if (sig == SIGUSR1 || sig == SIGUSR2) { 384 | error("SIGUSR{1,2} are deprecated, ignoring."); 385 | continue; 386 | } 387 | 388 | debug("unhandled signal %d", sig); 389 | } 390 | 391 | bar_teardown(bar); 392 | 393 | return err; 394 | } 395 | 396 | static void bar_destroy(struct bar *bar) 397 | { 398 | struct block *block = bar->blocks; 399 | struct block *next; 400 | 401 | bar_stop(bar); 402 | 403 | while (block) { 404 | next = block->next; 405 | block_destroy(block); 406 | block = next; 407 | } 408 | 409 | free(bar); 410 | } 411 | 412 | static struct bar *bar_create(bool term) 413 | { 414 | struct bar *bar; 415 | int err; 416 | 417 | bar = calloc(1, sizeof(struct bar)); 418 | if (!bar) 419 | return NULL; 420 | 421 | bar->blocks = block_create(bar, NULL); 422 | if (!bar->blocks) { 423 | bar_destroy(bar); 424 | return NULL; 425 | } 426 | 427 | bar->term = term; 428 | 429 | err = bar_start(bar); 430 | if (err) { 431 | bar_destroy(bar); 432 | return NULL; 433 | } 434 | 435 | return bar; 436 | } 437 | 438 | static int bar_config_cb(struct map *map, void *data) 439 | { 440 | struct bar *bar = data; 441 | struct block *block = bar->blocks; 442 | 443 | while (block->next) 444 | block = block->next; 445 | 446 | block->next = block_create(bar, map); 447 | 448 | map_destroy(map); 449 | 450 | if (!block->next) 451 | return -ENOMEM; 452 | 453 | return 0; 454 | } 455 | 456 | static void bar_load(struct bar *bar, const char *path, const char *conf_dir, const char *user_conf_dir) 457 | { 458 | int err; 459 | 460 | if (path) { 461 | debug("Loading config from file %s", path); 462 | err = config_load(path, bar_config_cb, bar); 463 | } else if (conf_dir || user_conf_dir) { 464 | if (user_conf_dir) { 465 | debug("Loading config from user directory %s", user_conf_dir); 466 | err = config_dir_load(user_conf_dir, bar_config_cb, bar, true); 467 | } 468 | 469 | // If attempt at user conf failed and have system conf, or only have system conf: 470 | if ((err && user_conf_dir && conf_dir) || (conf_dir && !user_conf_dir)) { 471 | debug("Loading config from directory %s", conf_dir); 472 | err = config_dir_load(conf_dir, bar_config_cb, bar, false); 473 | } 474 | } 475 | 476 | if (err) 477 | bar_fatal(bar, "Failed to load configuration."); 478 | } 479 | 480 | int bar_init(bool term, const char *path, const char *conf_dir, const char *user_conf_dir) 481 | { 482 | struct bar *bar; 483 | int err; 484 | 485 | bar = bar_create(term); 486 | if (!bar) 487 | return -ENOMEM; 488 | 489 | bar_load(bar, path, conf_dir, user_conf_dir); 490 | 491 | err = bar_poll(bar); 492 | 493 | bar_destroy(bar); 494 | 495 | return err; 496 | } 497 | -------------------------------------------------------------------------------- /bar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bar.h - definition of a status line handling functions 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef BAR_H 20 | #define BAR_H 21 | 22 | #include 23 | 24 | #include "block.h" 25 | #include "sys.h" 26 | 27 | struct bar { 28 | struct block *blocks; 29 | sigset_t sigset; 30 | bool term; 31 | }; 32 | 33 | #define bar_printf(bar, lvl, fmt, ...) \ 34 | block_printf(bar->blocks, lvl, "%s" fmt, bar->term ? "TTY " : "", ##__VA_ARGS__) 35 | 36 | #define bar_fatal(bar, fmt, ...) \ 37 | do { \ 38 | fatal(fmt, ##__VA_ARGS__); \ 39 | bar_printf(bar, LOG_FATAL, "Oops! " fmt ". Increase log level for details.", ##__VA_ARGS__); \ 40 | } while (0) 41 | 42 | #define bar_error(bar, fmt, ...) \ 43 | do { \ 44 | error(fmt, ##__VA_ARGS__); \ 45 | bar_printf(bar, LOG_ERROR, "Error: " fmt, ##__VA_ARGS__); \ 46 | } while (0) 47 | 48 | #define bar_trace(bar, fmt, ...) \ 49 | do { \ 50 | trace(fmt, ##__VA_ARGS__); \ 51 | bar_printf(bar, LOG_TRACE, "Trace: " fmt, ##__VA_ARGS__); \ 52 | } while (0) 53 | 54 | #define bar_debug(bar, fmt, ...) \ 55 | do { \ 56 | debug(fmt, ##__VA_ARGS__); \ 57 | bar_printf(bar, LOG_DEBUG, "Debug: " fmt, ##__VA_ARGS__); \ 58 | } while (0) 59 | 60 | int bar_init(bool term, const char *path, const char *conf_dir, const char *user_conf_dir); 61 | 62 | struct map; 63 | 64 | /* i3bar.c */ 65 | int i3bar_read(int fd, size_t count, struct map *map); 66 | int i3bar_click(struct bar *bar); 67 | int i3bar_print(const struct bar *bar); 68 | int i3bar_printf(struct block *block, int lvl, const char *msg); 69 | int i3bar_setup(struct block *block); 70 | int i3bar_start(struct bar *bar); 71 | void i3bar_stop(struct bar *bar); 72 | 73 | #endif /* BAR_H */ 74 | -------------------------------------------------------------------------------- /block.c: -------------------------------------------------------------------------------- 1 | /* 2 | * block.c - update of a single status line block 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "bar.h" 24 | #include "block.h" 25 | #include "json.h" 26 | #include "line.h" 27 | #include "log.h" 28 | #include "sys.h" 29 | 30 | const char *block_get(const struct block *block, const char *key) 31 | { 32 | return map_get(block->env, key); 33 | } 34 | 35 | int block_set(struct block *block, const char *key, const char *value) 36 | { 37 | return map_set(block->env, key, value); 38 | } 39 | 40 | int block_reset(struct block *block) 41 | { 42 | map_clear(block->env); 43 | 44 | return map_copy(block->env, block->config); 45 | } 46 | 47 | int block_for_each(const struct block *block, 48 | int (*func)(const char *key, const char *value, void *data), 49 | void *data) 50 | { 51 | return map_for_each(block->env, func, data); 52 | } 53 | 54 | static bool block_is_spawned(struct block *block) 55 | { 56 | return block->pid > 0; 57 | } 58 | 59 | static int block_setenv(const char *name, const char *value, void *data) 60 | { 61 | int err; 62 | 63 | if (!value) 64 | value = ""; 65 | 66 | err = sys_setenv(name, value); 67 | if (err) 68 | return err; 69 | 70 | /* Legacy env variables */ 71 | if (strcmp(name, "name") == 0) 72 | return sys_setenv("BLOCK_NAME", value); 73 | if (strcmp(name, "instance") == 0) 74 | return sys_setenv("BLOCK_INSTANCE", value); 75 | if (strcmp(name, "interval") == 0) 76 | return sys_setenv("BLOCK_INTERVAL", value); 77 | if (strcmp(name, "button") == 0) 78 | return sys_setenv("BLOCK_BUTTON", value); 79 | if (strcmp(name, "x") == 0) 80 | return sys_setenv("BLOCK_X", value); 81 | if (strcmp(name, "y") == 0) 82 | return sys_setenv("BLOCK_Y", value); 83 | 84 | return 0; 85 | } 86 | 87 | static int block_child_env(struct block *block) 88 | { 89 | return block_for_each(block, block_setenv, NULL); 90 | } 91 | 92 | static int block_stdout(struct block *block) 93 | { 94 | const char *label, *full_text; 95 | int out = block->out[0]; 96 | char buf[BUFSIZ]; 97 | size_t count; 98 | int err; 99 | 100 | if (block->interval == INTERVAL_PERSIST) 101 | count = 1; 102 | else 103 | count = -1; /* SIZE_MAX */ 104 | 105 | if (block->format == FORMAT_JSON) 106 | err = json_read(out, count, block->env); 107 | else 108 | err = i3bar_read(out, count, block->env); 109 | 110 | if (err && err != -EAGAIN) 111 | return err; 112 | 113 | /* Deprecated label */ 114 | label = block_get(block, "label"); 115 | full_text = block_get(block, "full_text"); 116 | if (label && full_text) { 117 | snprintf(buf, sizeof(buf), "%s%s", label, full_text); 118 | err = block_set(block, "full_text", buf); 119 | if (err) 120 | return err; 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | int block_update(struct block *block) 127 | { 128 | int err; 129 | 130 | /* Reset properties to default before updating from output */ 131 | err = block_reset(block); 132 | if (err) 133 | return err; 134 | 135 | err = block_stdout(block); 136 | if (err) 137 | return err; 138 | 139 | /* Exit code takes precedence over the output */ 140 | if (block->code == EXIT_URGENT) { 141 | err = block_set(block, "urgent", "true"); 142 | if (err) 143 | return err; 144 | } 145 | 146 | block_debug(block, "updated successfully"); 147 | 148 | return 0; 149 | } 150 | 151 | static int block_send_key(const char *key, const char *value, void *data) 152 | { 153 | struct block *block = data; 154 | char buf[BUFSIZ]; 155 | int err; 156 | 157 | if (!json_is_valid(value)) { 158 | err = json_escape(value, buf, sizeof(buf)); 159 | if (err) 160 | return err; 161 | 162 | value = buf; 163 | } 164 | 165 | dprintf(block->in[1], ",\"%s\":%s", key, value); 166 | 167 | return 0; 168 | } 169 | 170 | static int block_send_json(struct block *block) 171 | { 172 | int err; 173 | 174 | dprintf(block->in[1], "{\"\":\"\""); 175 | err = block_for_each(block, block_send_key, block); 176 | dprintf(block->in[1], "}\n"); 177 | 178 | return err; 179 | } 180 | 181 | /* Push data to forked process through the open stdin pipe */ 182 | static int block_send(struct block *block) 183 | { 184 | const char *button = block_get(block, "button"); 185 | 186 | if (!button) { 187 | block_error(block, "no click data to send"); 188 | return -EINVAL; 189 | } 190 | 191 | if (!block_is_spawned(block)) { 192 | block_error(block, "persistent block not spawned"); 193 | return 0; 194 | } 195 | 196 | if (block->format == FORMAT_JSON) 197 | return block_send_json(block); 198 | 199 | dprintf(block->in[1], "%s\n", button); 200 | 201 | return 0; 202 | } 203 | 204 | int block_click(struct block *block) 205 | { 206 | block_debug(block, "clicked"); 207 | 208 | if (block->interval == INTERVAL_PERSIST) 209 | return block_send(block); 210 | 211 | return block_spawn(block); 212 | } 213 | 214 | void block_touch(struct block *block) 215 | { 216 | unsigned long now; 217 | int err; 218 | 219 | err = sys_gettime(&now); 220 | if (err) { 221 | block_error(block, "failed to touch block"); 222 | return; 223 | } 224 | 225 | if (block->timestamp == now) { 226 | block_debug(block, "looping too fast"); 227 | return; 228 | } 229 | 230 | block->timestamp = now; 231 | } 232 | 233 | static int block_child_sig(struct block *block) 234 | { 235 | sigset_t set; 236 | int err; 237 | 238 | /* It'd be safe to assume that all signals are unblocked by default */ 239 | err = sys_sigfillset(&set); 240 | if (err) 241 | return err; 242 | 243 | return sys_sigunblock(&set); 244 | } 245 | 246 | static int block_child_stdin(struct block *block) 247 | { 248 | int err; 249 | 250 | if (block->interval == INTERVAL_PERSIST) { 251 | err = sys_close(block->in[1]); 252 | if (err) 253 | return err; 254 | } else { 255 | err = sys_open("/dev/null", &block->in[0]); 256 | if (err) 257 | return err; 258 | } 259 | 260 | err = sys_dup(block->in[0], STDIN_FILENO); 261 | if (err) 262 | return err; 263 | 264 | return sys_close(block->in[0]); 265 | } 266 | 267 | static int block_child_stdout(struct block *block) 268 | { 269 | int err; 270 | 271 | err = sys_close(block->out[0]); 272 | if (err) 273 | return err; 274 | 275 | err = sys_dup(block->out[1], STDOUT_FILENO); 276 | if (err) 277 | return err; 278 | 279 | return sys_close(block->out[1]); 280 | } 281 | 282 | static int block_child_exec(struct block *block) 283 | { 284 | return sys_execsh(block->command); 285 | } 286 | 287 | static int block_child(struct block *block) 288 | { 289 | int err; 290 | 291 | err = block_child_env(block); 292 | if (err) 293 | return err; 294 | 295 | err = block_child_sig(block); 296 | if (err) 297 | return err; 298 | 299 | err = block_child_stdin(block); 300 | if (err) 301 | return err; 302 | 303 | err = block_child_stdout(block); 304 | if (err) 305 | return err; 306 | 307 | return block_child_exec(block); 308 | } 309 | 310 | static int block_parent_stdin(struct block *block) 311 | { 312 | /* Close read end of stdin pipe */ 313 | if (block->interval == INTERVAL_PERSIST) 314 | return sys_close(block->in[0]); 315 | 316 | return 0; 317 | } 318 | 319 | static int block_parent_stdout(struct block *block) 320 | { 321 | int err; 322 | 323 | /* Close write end of stdout pipe */ 324 | err = sys_close(block->out[1]); 325 | if (err) 326 | return err; 327 | 328 | if (block->interval == INTERVAL_PERSIST) 329 | return sys_async(block->out[0], SIGRTMIN); 330 | 331 | return 0; 332 | } 333 | 334 | static int block_parent(struct block *block) 335 | { 336 | int err; 337 | 338 | err = block_parent_stdin(block); 339 | if (err) 340 | return err; 341 | 342 | err = block_parent_stdout(block); 343 | if (err) 344 | return err; 345 | 346 | block_debug(block, "forked child %d", block->pid); 347 | 348 | return 0; 349 | } 350 | 351 | static int block_fork(struct block *block) 352 | { 353 | int err; 354 | 355 | err = sys_fork(&block->pid); 356 | if (err) 357 | return err; 358 | 359 | if (block->pid == 0) { 360 | err = block_child(block); 361 | if (err) 362 | sys_exit(EXIT_ERR_INTERNAL); 363 | } 364 | 365 | return block_parent(block); 366 | } 367 | 368 | static int block_open(struct block *block) 369 | { 370 | int err; 371 | 372 | err = sys_pipe(block->out); 373 | if (err) 374 | return err; 375 | 376 | if (block->interval == INTERVAL_PERSIST) 377 | return sys_pipe(block->in); 378 | 379 | return 0; 380 | } 381 | 382 | int block_spawn(struct block *block) 383 | { 384 | int err; 385 | 386 | if (!block->command) { 387 | block_debug(block, "no command, skipping"); 388 | return 0; 389 | } 390 | 391 | if (block_is_spawned(block)) { 392 | block_debug(block, "process already spawned"); 393 | return 0; 394 | } 395 | 396 | err = block_open(block); 397 | if (err) 398 | return err; 399 | 400 | return block_fork(block); 401 | } 402 | 403 | static int block_wait(struct block *block) 404 | { 405 | int err; 406 | 407 | if (block->pid <= 0) { 408 | block_debug(block, "not spawned yet"); 409 | return -EAGAIN; 410 | } 411 | 412 | err = sys_waitpid(block->pid, &block->code); 413 | if (err) 414 | return err; 415 | 416 | block_debug(block, "process %d exited with %d", block->pid, block->code); 417 | 418 | /* Process successfully reaped, reset the block PID */ 419 | block->pid = 0; 420 | 421 | if (block->code == EXIT_ERR_INTERNAL) 422 | return -ECHILD; 423 | 424 | return 0; 425 | } 426 | 427 | void block_close(struct block *block) 428 | { 429 | int err; 430 | 431 | /* Invalidate descriptors to avoid misdetection after reassignment */ 432 | if (block->interval == INTERVAL_PERSIST) { 433 | err = sys_close(block->in[1]); 434 | if (err) 435 | block_error(block, "failed to close stdin"); 436 | 437 | block->in[1] = -1; 438 | } 439 | 440 | err = sys_close(block->out[0]); 441 | if (err) 442 | block_error(block, "failed to close stdout"); 443 | 444 | block->out[0] = -1; 445 | } 446 | 447 | int block_reap(struct block *block) 448 | { 449 | int err; 450 | 451 | err = block_wait(block); 452 | if (err) { 453 | if (err == -EAGAIN) 454 | return 0; 455 | 456 | block_error(block, "Internal error"); 457 | return err; 458 | } 459 | 460 | switch (block->code) { 461 | case 0: 462 | case EXIT_URGENT: 463 | break; 464 | case 126: 465 | block_error(block, "Command '%s' not executable", 466 | block->command); 467 | break; 468 | case 127: 469 | block_error(block, "Command '%s' not found or missing dependency", 470 | block->command); 471 | break; 472 | default: 473 | block_error(block, "Command '%s' exited unexpectedly with code %d", 474 | block->command, block->code); 475 | break; 476 | } 477 | 478 | return 0; 479 | } 480 | 481 | static int i3blocks_setup(struct block *block) 482 | { 483 | const char *value; 484 | 485 | value = map_get(block->config, "command"); 486 | if (value && *value != '\0') 487 | block->command = value; 488 | 489 | value = map_get(block->config, "interval"); 490 | if (!value) 491 | block->interval = 0; 492 | else if (strcmp(value, "once") == 0) 493 | block->interval = INTERVAL_ONCE; 494 | else if (strcmp(value, "repeat") == 0) 495 | block->interval = INTERVAL_REPEAT; 496 | else if (strcmp(value, "persist") == 0) 497 | block->interval = INTERVAL_PERSIST; 498 | else 499 | block->interval = atoi(value); 500 | 501 | value = map_get(block->config, "format"); 502 | if (value && strcmp(value, "json") == 0) 503 | block->format = FORMAT_JSON; 504 | else 505 | block->format = FORMAT_RAW; 506 | 507 | value = map_get(block->config, "signal"); 508 | if (!value) 509 | block->signal = 0; 510 | else 511 | block->signal = atoi(value); 512 | 513 | return 0; 514 | } 515 | 516 | int block_setup(struct block *block) 517 | { 518 | int err; 519 | 520 | err = i3bar_setup(block); 521 | if (err) 522 | return err; 523 | 524 | err = i3blocks_setup(block); 525 | if (err) 526 | return err; 527 | 528 | err = block_reset(block); 529 | if (err) 530 | return err; 531 | 532 | block_debug(block, "new block"); 533 | 534 | return 0; 535 | } 536 | 537 | void block_destroy(struct block *block) 538 | { 539 | map_destroy(block->config); 540 | map_destroy(block->env); 541 | free(block->name); 542 | free(block); 543 | } 544 | 545 | struct block *block_create(struct bar *bar, const struct map *config) 546 | { 547 | struct block *block; 548 | int err; 549 | 550 | block = calloc(1, sizeof(struct block)); 551 | if (!block) 552 | return NULL; 553 | 554 | block->bar = bar; 555 | 556 | block->config = map_create(); 557 | if (!block->config) { 558 | block_destroy(block); 559 | return NULL; 560 | } 561 | 562 | if (config) { 563 | err = map_copy(block->config, config); 564 | if (err) { 565 | block_destroy(block); 566 | return NULL; 567 | } 568 | } 569 | 570 | block->env = map_create(); 571 | if (!block->env) { 572 | block_destroy(block); 573 | return NULL; 574 | } 575 | 576 | return block; 577 | } 578 | 579 | void block_printf(struct block *block, int lvl, const char *fmt, ...) 580 | { 581 | char buf[BUFSIZ]; 582 | va_list ap; 583 | int err; 584 | 585 | if (lvl > log_level) 586 | return; 587 | 588 | va_start(ap, fmt); 589 | vsnprintf(buf, sizeof(buf), fmt, ap); 590 | va_end(ap); 591 | 592 | err = i3bar_printf(block, lvl, buf); 593 | if (err) 594 | fatal("failed to format message for block %s: %s", block->name, buf); 595 | } 596 | -------------------------------------------------------------------------------- /block.h: -------------------------------------------------------------------------------- 1 | /* 2 | * block.h - definition of a block 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef BLOCK_H 20 | #define BLOCK_H 21 | 22 | #include 23 | 24 | #include "bar.h" 25 | #include "log.h" 26 | #include "map.h" 27 | 28 | #define INTERVAL_ONCE -1 29 | #define INTERVAL_REPEAT -2 30 | #define INTERVAL_PERSIST -3 31 | 32 | #define FORMAT_RAW 0 33 | #define FORMAT_JSON 1 34 | 35 | /* Block command exit codes */ 36 | #define EXIT_URGENT '!' /* 33 */ 37 | #define EXIT_ERR_INTERNAL 66 38 | 39 | struct block { 40 | const struct bar *bar; 41 | 42 | struct map *config; 43 | struct map *env; 44 | 45 | bool tainted; 46 | 47 | /* Pretty name for log messages */ 48 | char *name; 49 | 50 | /* Shortcuts */ 51 | const char *command; 52 | int interval; 53 | int signal; 54 | unsigned format; 55 | 56 | /* Runtime info */ 57 | unsigned long timestamp; 58 | int in[2]; 59 | int out[2]; 60 | int code; 61 | pid_t pid; 62 | 63 | struct block *next; 64 | }; 65 | 66 | struct block *block_create(struct bar *bar, const struct map *config); 67 | void block_destroy(struct block *block); 68 | 69 | int block_reset(struct block *block); 70 | 71 | const char *block_get(const struct block *block, const char *key); 72 | int block_set(struct block *block, const char *key, const char *value); 73 | 74 | int block_for_each(const struct block *block, 75 | int (*func)(const char *key, const char *value, void *data), 76 | void *data); 77 | 78 | void block_printf(struct block *block, int lvl, const char *fmt, ...); 79 | 80 | #define block_fatal(block, fmt, ...) \ 81 | do { \ 82 | fatal("[%s] " fmt, block->name, ##__VA_ARGS__); \ 83 | block_printf(block, LOG_FATAL, "Oops! " fmt, ##__VA_ARGS__); \ 84 | } while (0) 85 | 86 | #define block_error(block, fmt, ...) \ 87 | do { \ 88 | error("[%s] " fmt, block->name, ##__VA_ARGS__); \ 89 | block_printf(block, LOG_ERROR, "Error: " fmt, ##__VA_ARGS__); \ 90 | } while (0) 91 | 92 | #define block_trace(block, fmt, ...) \ 93 | do { \ 94 | trace("[%s] " fmt, block->name, ##__VA_ARGS__); \ 95 | block_printf(block, LOG_TRACE, fmt, ##__VA_ARGS__); \ 96 | } while (0) 97 | 98 | #define block_debug(block, fmt, ...) \ 99 | do { \ 100 | debug("[%s] " fmt, block->name, ##__VA_ARGS__); \ 101 | block_printf(block, LOG_DEBUG, fmt, ##__VA_ARGS__); \ 102 | } while (0) 103 | 104 | int block_setup(struct block *block); 105 | int block_click(struct block *block); 106 | int block_spawn(struct block *block); 107 | void block_touch(struct block *block); 108 | int block_reap(struct block *block); 109 | int block_update(struct block *block); 110 | void block_close(struct block *block); 111 | 112 | #endif /* BLOCK_H */ 113 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * config.c - parsing of the configuration file 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "config.h" 25 | #include "ini.h" 26 | #include "log.h" 27 | #include "map.h" 28 | #include "sys.h" 29 | 30 | #ifndef SYSCONFDIR 31 | #define SYSCONFDIR "/etc" 32 | #endif 33 | 34 | struct config { 35 | struct map *section; 36 | struct map *global; 37 | config_cb_t *cb; 38 | void *data; 39 | }; 40 | 41 | static int config_finalize(struct config *conf) 42 | { 43 | int err; 44 | 45 | if (conf->section) { 46 | if (conf->cb) { 47 | err = conf->cb(conf->section, conf->data); 48 | if (err) 49 | return err; 50 | } 51 | 52 | conf->section = NULL; 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | static int config_reset(struct config *conf) 59 | { 60 | conf->section = map_create(); 61 | if (!conf->section) 62 | return -ENOMEM; 63 | 64 | if (conf->global) 65 | return map_copy(conf->section, conf->global); 66 | 67 | return 0; 68 | } 69 | 70 | static int config_set(struct config *conf, const char *key, const char *value) 71 | { 72 | struct map *map = conf->section; 73 | 74 | if (!map) { 75 | if (!conf->global) { 76 | conf->global = map_create(); 77 | if (!conf->global) 78 | return -ENOMEM; 79 | } 80 | 81 | map = conf->global; 82 | } 83 | 84 | return map_set(map, key, value); 85 | } 86 | 87 | static int config_ini_section_cb(char *section, void *data) 88 | { 89 | int err; 90 | 91 | err = config_finalize(data); 92 | if (err) 93 | return err; 94 | 95 | err = config_reset(data); 96 | if (err) 97 | return err; 98 | 99 | return config_set(data, "name", section); 100 | } 101 | 102 | static int config_ini_property_cb(char *key, char *value, void *data) 103 | { 104 | return config_set(data, key, value); 105 | } 106 | 107 | static int config_read(struct config *conf, int fd) 108 | { 109 | int err; 110 | 111 | err = ini_read(fd, -1, config_ini_section_cb, config_ini_property_cb, 112 | conf); 113 | if (err && err != -EAGAIN) 114 | return err; 115 | 116 | return config_finalize(conf); 117 | } 118 | 119 | static int config_open(struct config *conf, const char *path, const bool single_mode) 120 | { 121 | size_t plen = strlen(path); 122 | char pname[plen + 1]; 123 | char *dname; 124 | int err; 125 | int fd; 126 | 127 | debug("try file %s", path); 128 | 129 | err = sys_open(path, &fd); 130 | if (err) 131 | return err; 132 | 133 | strcpy(pname, path); 134 | dname = dirname(pname); 135 | err = sys_chdir(dname); 136 | if (err) { 137 | error("failed to change directory to %s", dname); 138 | return err; 139 | } 140 | 141 | debug("changed directory to %s", dname); 142 | 143 | err = config_read(conf, fd); 144 | sys_close(fd); 145 | 146 | if (single_mode && conf->global) 147 | map_destroy(conf->global); 148 | 149 | return err; 150 | } 151 | 152 | int config_load(const char *path, config_cb_t *cb, void *data) 153 | { 154 | const char * const home = sys_getenv("HOME"); 155 | const char * const xdg_home = sys_getenv("XDG_CONFIG_HOME"); 156 | const char * const xdg_dirs = sys_getenv("XDG_CONFIG_DIRS"); 157 | struct config conf = { 158 | .data = data, 159 | .cb = cb, 160 | }; 161 | char buf[PATH_MAX]; 162 | int err; 163 | 164 | 165 | /* command line config file? */ 166 | if (path) 167 | return config_open(&conf, path, true); 168 | 169 | /* user config file? */ 170 | if (home) { 171 | if (xdg_home) 172 | snprintf(buf, sizeof(buf), "%s/i3xrocks/config", xdg_home); 173 | else 174 | snprintf(buf, sizeof(buf), "%s/.config/i3xrocks/config", home); 175 | err = config_open(&conf, buf, true); 176 | if (err != -ENOENT) 177 | return err; 178 | 179 | snprintf(buf, sizeof(buf), "%s/.i3xrocks.conf", home); 180 | err = config_open(&conf, buf, true); 181 | if (err != -ENOENT) 182 | return err; 183 | } 184 | 185 | /* system config file? */ 186 | if (xdg_dirs) 187 | snprintf(buf, sizeof(buf), "%s/i3xrocks/config", xdg_dirs); 188 | else 189 | snprintf(buf, sizeof(buf), "%s/xdg/i3xrocks/config", SYSCONFDIR); 190 | err = config_open(&conf, buf, true); 191 | if (err != -ENOENT) 192 | return err; 193 | 194 | snprintf(buf, sizeof(buf), "%s/i3xrocks.conf", SYSCONFDIR); 195 | return config_open(&conf, buf, true); 196 | } 197 | 198 | int config_dir_load(const char *path, config_cb_t *cb, void *data, const bool quiet) 199 | { 200 | struct config conf = { 201 | .data = data, 202 | .cb = cb, 203 | }; 204 | 205 | char buf[PATH_MAX]; 206 | struct dirent **namelist; 207 | int n, err, retval = 0; 208 | 209 | n = scandir(path, &namelist, NULL, alphasort); 210 | if (n < 0) { 211 | if (!quiet) perror(path); 212 | retval = -1; 213 | } else { 214 | for (int i=0; i < n; i++) { 215 | char *conf_file = namelist[i]->d_name; 216 | if (strcmp(conf_file, ".") != 0 && strcmp(conf_file, "..") != 0) { 217 | snprintf(buf, sizeof(buf), "%s/%s", path, conf_file); 218 | debug("Reading config file %s\n", buf); 219 | err = config_open(&conf, buf, false); 220 | 221 | if (err) { 222 | error("failed to load config file %s", conf_file); 223 | return err; 224 | } 225 | } 226 | free(namelist[i]); 227 | } 228 | free(namelist); 229 | } 230 | 231 | if (conf.global) 232 | map_destroy(conf.global); 233 | 234 | return retval; 235 | } 236 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h - configuration file header 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef CONFIG_H 20 | #define CONFIG_H 21 | 22 | #include "map.h" 23 | 24 | typedef int config_cb_t(struct map *map, void *data); 25 | int config_load(const char *path, config_cb_t *cb, void *data); 26 | int config_dir_load(const char *path, config_cb_t *cb, void *data, const bool quiet); 27 | 28 | #endif /* CONFIG_H */ 29 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([i3xrocks], 1.3.3) 2 | AC_CONFIG_AUX_DIR([build-aux]) 3 | AM_INIT_AUTOMAKE(foreign) 4 | AC_PROG_CC 5 | AC_CONFIG_HEADERS([i3xrocks-config.h]) 6 | AC_SEARCH_LIBS([xcb]) 7 | AC_CHECK_HEADERS([xcb/xcb.h xcb/xcb_xrm.h]) 8 | AC_CONFIG_FILES([ 9 | Makefile 10 | ]) 11 | AC_OUTPUT 12 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | i3xrocks (1.3.6-1) bionic; urgency=medium 2 | 3 | [ Khosrow Moossavi ] 4 | * feat: enable github action to test pull requests 5 | * feat: enable github action to publish to unstable 6 | * feat: enable github action to publish to testing 7 | * feat: enable github action to prepare release 8 | * fix: create release tag from correct git sha 9 | 10 | -- Regolith Linux Tue, 04 Feb 2025 02:55:20 +0000 11 | 12 | i3xrocks (1.3.5-1) bionic; urgency=medium 13 | 14 | [ Ken Gilmer ] 15 | * Do not print system file error if user files do not exist. Addresses https://github.com/regolith-linux/regolith-i3xrocks-config/issues/114. 16 | 17 | -- Regolith Linux Sat, 23 Jan 2021 12:28:24 -0800 18 | 19 | i3xrocks (1.3.4-1) bionic; urgency=medium 20 | 21 | [ Ken Gilmer ] 22 | * Version bump to match changelog. Cleanup. 23 | 24 | -- Regolith Linux Wed, 17 Jun 2020 21:07:27 -0700 25 | 26 | i3xrocks (1.3.3-1) bionic; urgency=medium 27 | 28 | [ Will Winder ] 29 | * Add optional default resource value. 30 | * Minor cleanup. 31 | * Free resource allocated by xcb_xrm_resource_get_string 32 | * Fix possible truncated resource value. 33 | 34 | [ Ken Gilmer ] 35 | * Add gbp config file 36 | 37 | -- Regolith Linux Wed, 17 Jun 2020 20:48:42 -0700 38 | 39 | i3xrocks (1.3.2) eoan; urgency=medium 40 | 41 | * Sync package and binary version strings. 42 | 43 | -- Regolith Linux Thu, 02 Apr 2020 14:50:42 -0700 44 | 45 | i3xrocks (1.3.1) eoan; urgency=medium 46 | 47 | * AND -> OR for multiple conf directory load logic. 48 | 49 | -- Regolith Linux Tue, 31 Mar 2020 07:29:31 -0700 50 | 51 | i3xrocks (1.3.0) eoan; urgency=medium 52 | 53 | * Add ability to read multiple config files from directory. 54 | * Add ability to read from user and fall back to other conf dir. 55 | 56 | -- Regolith Linux Sat, 28 Mar 2020 19:15:18 -0700 57 | 58 | i3xrocks (1.2.0-1ubuntu1~ppa1) eoan; urgency=medium 59 | 60 | * Merge from upstream version 1.5. 61 | 62 | -- Ken Gilmer Sat, 30 Nov 2019 09:44:18 -0800 63 | 64 | i3xrocks (1.1-1ubuntu1ppa2) disco; urgency=medium 65 | 66 | * Fix package metadata. 67 | 68 | -- Ken Gilmer Sat, 28 Sep 2019 23:22:30 -0700 69 | 70 | i3xrocks (1.1-1ubuntu1ppa1) bionic; urgency=medium 71 | 72 | * Initial release 73 | 74 | -- Ken Gilmer Sun, 21 Jul 2019 19:44:06 -0700 75 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 11 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: i3xrocks 2 | Section: x11 3 | Priority: optional 4 | Maintainer: Ken Gilmer 5 | Build-Depends: 6 | debhelper (>= 11), 7 | libxcb1-dev, 8 | libxcb-xrm-dev 9 | Standards-Version: 4.1.3 10 | Homepage: https://github.com/regolith-linux/i3xrocks 11 | 12 | Package: i3xrocks 13 | Architecture: any 14 | Depends: ${shlibs:Depends}, ${misc:Depends} 15 | Description: i3blocks with Xresources and conf.d configuration 16 | i3xrocks is a tracking fork of i3blocks that adds a few features 17 | for the Regolith Desktop Environment. 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: i3xrocks 3 | Source: https://github.com/regolith-linux/i3xrocks 4 | 5 | Files: * 6 | Copyright: Copyright © 2014 Vivien Didelot 7 | Copyright © 2019 Ken Gilmer 8 | License: GPL-3+ 9 | 10 | Files: debian/* 11 | Copyright: Copyright © 2015 Jason Pleau 12 | Copyright © 2019 Ken Gilmer 13 | License: GPL-3+ 14 | 15 | License: GPL-3+ 16 | This program is free software; you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License version 3 as 18 | published by the Free Software Foundation 19 | . 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | . 25 | You should have received a copy of the GNU General Public License 26 | along with this program. If not, see 27 | . 28 | On Debian systems, the complete text of the GNU General 29 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 30 | -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | upstream-tree=master 3 | debian-branch=master -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export DH_VERBOSE = 1 3 | 4 | %: 5 | dh $@ 6 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /docs/README.adoc: -------------------------------------------------------------------------------- 1 | :progname: i3xrocks 2 | :toc: 3 | 4 | = {progname} 5 | 6 | A feed generator for text based status bars 7 | 8 | 1. The name of the program is i3xrocks instead of i3blocks. 9 | 2. The config file can resolve Xresource dependencies if prefixed with `xresource:`. For example, if you have 10 | a Xresource named "blockColor" then you can load that into i3xrocks via: 11 | 12 | --- 13 | color=xresource:blockColor 14 | 15 | --- 16 | 17 | Otherwise this aims to be identical to i3blocks. Please refer to the upstream project for documentation. 18 | 19 | [source,ini] 20 | ---- 21 | [click] 22 | full_text=Click me! 23 | command=echo "Got clicked with button $button" 24 | color=#F79494 25 | 26 | # Guess the weather hourly 27 | [weather] 28 | command=curl -Ss 'https://wttr.in?0&T&Q' | cut -c 16- | head -2 | xargs echo 29 | interval=3600 30 | color=#A4C2F4 31 | 32 | # Query my default IP address only on startup 33 | [ip] 34 | command=hostname -i | awk '{ print "IP:" $1 }' 35 | interval=once 36 | color=#91E78B 37 | 38 | # Update time every 5 seconds 39 | [time] 40 | command=date +%T 41 | interval=5 42 | ---- 43 | 44 | == Installation 45 | 46 | {progname} is already packaged for: 47 | 48 | * Archlinux: link:https://www.archlinux.org/packages/community/x86_64/i3blocks[i3blocks] in the community repository and link:https://aur.archlinux.org/packages/i3blocks-git[i3blocks-git] in the AUR 49 | * Gentoo: link:https://packages.gentoo.org/packages/x11-misc/i3blocks[x11-misc/i3blocks] 50 | * Debian: link:https://packages.debian.org/i3blocks[i3blocks] 51 | * Ubuntu: link:http://packages.ubuntu.com/i3blocks[i3blocks] 52 | * Fedora (COPR): link:https://copr.fedorainfracloud.org/coprs/wyvie/i3blocks[i3blocks] 53 | * Void Linux: link:https://github.com/void-linux/void-packages/tree/master/srcpkgs/i3blocks[i3blocks] 54 | 55 | Or can be installed from source with: 56 | 57 | [source] 58 | ---- 59 | git clone https://github.com/vivien/i3blocks 60 | cd i3blocks 61 | ./autogen.sh 62 | ./configure 63 | make 64 | make install 65 | ---- 66 | 67 | == Getting started 68 | 69 | In your i3 configuration file, define {progname} as the link:https://i3wm.org/docs/userguide.html#status_command[status line command] of a new bar block: 70 | 71 | [source] 72 | ---- 73 | bar { 74 | status_command i3blocks 75 | } 76 | ---- 77 | 78 | IMPORTANT: The project's repository does not include default scripts anymore. 79 | 80 | For the lazy, you can start from link:https://github.com/vivien/i3blocks-contrib[our collection of scripts]: 81 | 82 | [source] 83 | ---- 84 | git clone https://github.com/vivien/i3blocks-contrib ~/.config/i3blocks 85 | cd !$ 86 | cp config.example config 87 | ---- 88 | 89 | For the picky, you can start a configuration file in one of the following preferred paths: 90 | 91 | * `$XDG_CONFIG_HOME/i3blocks/config` (or `~/.config/i3blocks/config`); 92 | * `~/.i3blocks.conf`; 93 | * `$XDG_CONFIG_DIRS/i3blocks/config` (or `/etc/xdg/i3blocks/config`); 94 | * `/etc/i3blocks.conf`; 95 | * or any other path that you will specify using the `-c` option. 96 | 97 | NOTE: By default `/etc` is prefixed by `/usr/local` when you installed from source. 98 | 99 | Use the example above or dig in the configuration details below. 100 | 101 | Now restart i3 with `i3-msg restart` to apply your changes. 102 | 103 | == Blocks 104 | 105 | The configuration file uses a simplified INI file format: 106 | 107 | [source,ini] 108 | ---- 109 | # Properties not preceded by a section are considered global 110 | # and merged into every section declarations. 111 | foo=bar 112 | 113 | [block1] 114 | baz=qux 115 | 116 | # This is a comment 117 | [block2] 118 | quux= quuz 119 | ---- 120 | 121 | In this example, _block2_ contains a _foo_ property equal to _"bar"_ and a _quux_ property equal to _" quuz"_ (including the leading space). 122 | Everything after the equal sign will be part of the value, thus inline comments won't be stripped out. 123 | 124 | At runtime, these properties are simply variables, that are passed along to the status bar program when printing is necessary. 125 | However on startup, {progname} checks some optional properties to eventually setup the scheduling of a command. 126 | 127 | If a block specifies a command, then all of its properties are passed as environment variables at execution, which means that the _foo=bar_ property will be available from a shell script with `$foo`. 128 | The output of the command is used to update the values of these variables. 129 | The values are reset to default (as defined in the configuration file) before the update, so that blocks get a consistent behavior at each execution. 130 | 131 | == i3bar properties 132 | 133 | In order to use {progname} with i3, its status bar command _i3bar_ expects specific keys. 134 | To know how to customize the blocks of your status line, you must refer to the link:https://i3wm.org/docs/i3bar-protocol.html#_blocks_in_detail[i3bar protocol]. 135 | 136 | NOTE: _full_text_ is the only mandatory key, the block will be skipped if this key is absent or empty. 137 | 138 | Unless overriden, the section name of the block defines the _name_ key. 139 | 140 | Below are examples of static blocks interacting with _i3bar_. 141 | 142 | [source,ini] 143 | ---- 144 | [simple] 145 | full_text=This is a looong white on red text 146 | short_text=Short white on red text 147 | background=#FF0000 148 | color=#FFFFFF 149 | 150 | # Block with a fixed width 151 | [aligned] 152 | full_text=Here. 153 | min_width=100 154 | align=center 155 | 156 | # Fancy text with multiple colors and shapes 157 | [funky] 158 | full_text=Roses and violets! 159 | markup=pango 160 | ---- 161 | 162 | == {progname} properties 163 | 164 | These are some special properties checked by {progname} on startup. 165 | These will be considered as simple variables at runtime. 166 | 167 | === command 168 | 169 | The optional _command_ property specifies a command line to be executed with `sh -c`. 170 | The command can be relative to the configuration file where it is defined. 171 | If the command outputs some text, it is used to update the block. 172 | 173 | An exit code of 0 means success. 174 | A special exit code of _33_ will set the _urgent_ i3bar key to true. 175 | Any other exit code will raise an error. 176 | 177 | [source,ini] 178 | ---- 179 | [pacman] 180 | full_text=c · 181 | command=echo "· ${full_text~~}" 182 | color=#FFFF00 183 | ---- 184 | 185 | === interval 186 | 187 | The optional _interval_ property specifies when the command must be scheduled. 188 | 189 | A positive value represents the number of seconds to wait between exectutions. 190 | 191 | [source,ini] 192 | ---- 193 | # Print seconds since 1970-01-01 194 | [epoch] 195 | command=date +%s 196 | interval=1 197 | ---- 198 | 199 | A value of _0_ (or undefined) means the command is not timed whatsoever and will not be executed on startup. 200 | This is useful to trigger the command only on user input (e.g. signal or click), not before. 201 | 202 | [source,ini] 203 | ---- 204 | # Restart i3 on click 205 | [restart] 206 | full_text=Restart 207 | command=i3-msg -q restart 208 | #interval=0 209 | ---- 210 | 211 | The interval value _once_ (or _-1_) will schedule the command only on startup. 212 | This tells {progname} not to schedule the command again on a time basis. 213 | But events such as signals and clicks will execute the command again of course. 214 | 215 | [source,ini] 216 | ---- 217 | # Fetch the public IP address only on startup 218 | [public-ip] 219 | command=wget -qO - icanhazip.com 220 | interval=once 221 | ---- 222 | 223 | The interval value _repeat_ (or _-2_) will respawn the command as soon as it terminates. 224 | This is convenient for blocking programs which exit as soon as the awaited event arises. 225 | 226 | NOTE: clicks are not supported with this value, since such commands are unlikely to expect data on their standard input. 227 | 228 | [source,ini] 229 | ---- 230 | # Print the last command entered in Bash 231 | [history] 232 | command=inotifywait -qq -e close_write ~/.bash_history; tail -1 ~/.bash_history 233 | interval=repeat 234 | ---- 235 | 236 | The interval value _persist_ (or _-3_) expects the command to be an infinite loop. 237 | Each line of the output will trigger an update of the block. 238 | 239 | [source,ini] 240 | ---- 241 | [window] 242 | command=xtitle -s 243 | interval=persist 244 | ---- 245 | 246 | === signal 247 | 248 | Blocks can be scheduled upon reception of a real-time signal (think prioritized and queueable). 249 | The range of available signal numbers is _1_ to _N_, where _SIGRTMIN+N = SIGRTMAX_. 250 | (Note: there are 31 real-time signals in Linux.) 251 | 252 | [source,ini] 253 | ---- 254 | [caps-lock] 255 | command=xset -q | grep Caps | awk '{ print $2, $3, $4 }' 256 | interval=once 257 | signal=10 258 | ---- 259 | 260 | This example block above will be scheduled once {progname} handles the _SIGRTMIN+10_ signal. 261 | This can be sent directly from an i3 binding on Caps Lock release with the following configuration: 262 | 263 | [source] 264 | ---- 265 | bindsym --release Caps_Lock exec pkill -SIGRTMIN+10 i3blocks 266 | ---- 267 | 268 | === format 269 | 270 | There are several formats supported to specify which variables {progname} must update. 271 | Some favor simplicity over flexibility but thus can be limited. 272 | 273 | When undefined, a raw format is assumed. 274 | Each line of the output corresponds to an i3bar key, in the order of definition found in the link:https://i3wm.org/docs/i3bar-protocol.html#_blocks_in_detail[i3bar protocol]: 275 | 276 | * the 1st line updates the _full_text_; 277 | * the 2nd line updates the _short_text_; 278 | * the 3rd line updates the _color_; 279 | * the 4th line updates the _background_. 280 | 281 | Excess lines are considered an error. 282 | Below is an example of a simple battery script. 283 | 284 | .battery.sh 285 | [source,sh] 286 | ---- 287 | #!/bin/bash 288 | 289 | BAT=$(acpi -b | grep -E -o '[0-9][0-9]?%') 290 | 291 | # Full and short texts 292 | echo "Battery: $BAT" 293 | echo "BAT: $BAT" 294 | 295 | # Set urgent flag below 5% or use orange below 20% 296 | [ ${BAT%?} -le 5 ] && exit 33 297 | [ ${BAT%?} -le 20 ] && echo "#FF8000" 298 | 299 | exit 0 300 | ---- 301 | 302 | [source,ini] 303 | ---- 304 | [battery] 305 | command=battery.sh 306 | interval=10 307 | ---- 308 | 309 | The _json_ format can update any variable. 310 | 311 | [source,ini] 312 | ---- 313 | [counter] 314 | _count=0 315 | command=printf '{"full_text":"Counter: %s", "_count":%d}\n' $_count $((_count + 1)) 316 | format=json 317 | interval=1 318 | ---- 319 | 320 | == Click 321 | 322 | When you click on a block, data such as the button number and coordinates are merged into the block variables. 323 | 324 | NOTE: _name_ and _instance_ are the two keys used by i3bar to identify a block. 325 | 326 | The data sent on click is detailed in the link:https://i3wm.org/docs/i3bar-protocol.html#_click_events[i3bar protocol]. 327 | 328 | If the block command isn't already spawned, it is executed again. 329 | 330 | [source,ini] 331 | ---- 332 | # Print click data 333 | [clickme] 334 | align=center 335 | full_text=Click me! 336 | min_width=Button=? x=? y=? 337 | command=echo "Button=$button x=$x y=$y" 338 | ---- 339 | 340 | If the value of the block's interval is _persist_, then the data is written on the command standard input, one line per click. 341 | What gets written depends on the block's format. 342 | The raw format only gets the click button. 343 | The JSON format gets all block variables. 344 | 345 | [source,ini] 346 | ---- 347 | [click-loop] 348 | full_text=Click me! 349 | command=while read button; do echo "Got click $button"; done 350 | interval=persist 351 | 352 | [click-loop-json] 353 | full_text=Click me! 354 | command=ruby -r json -n -e '$_ = JSON.parse($_)' -e '$_["full_text"] = "Click %s at (%d,%d)" % $_.slice("button", "x", "y").values' -e 'puts JSON.dump($_)' -e 'STDOUT.flush' 355 | interval=persist 356 | format=json 357 | ---- 358 | 359 | == FAQ 360 | 361 | Frequently Asked Questions and Troubleshooting. 362 | 363 | [qanda] 364 | What is a blocklet?:: 365 | A blocklet is the configuration of a single block, part of the status line. 366 | There are plenty listed in the link:https://vivien.github.io/i3blocks/blocklets[blocklets page]. 367 | 368 | Can I use my own variables?:: 369 | Yes, any variable defined in the block is exported as is to the environment of its command. 370 | The `foo=bar` property can be accessed with `$foo` from a shell script, `ENV["foo"]` from Ruby, and so on. 371 | + 372 | The IEEE and The Open Group state that link:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html["The name space of environment variable names containing lowercase letters is reserved for applications."]. 373 | i3bar suggests to prefix your own keys with an underscore (`_`), but it might be more intuitive to use uppercase environment variables, so it is your call to define your own naming convention. 374 | 375 | Why `$foo` doesn't work from the configuration file?:: 376 | {progname} does not do string interpolation of any sort. 377 | The definitions found in the configuration file are just raw strings, this means that `bar=$baz` defines a _bar_ variable equal to literally `$baz` (a dollar sign followed by "baz"). 378 | + 379 | String interpolation does work in the _command_ property though, since it is interpreted by a shell which has access to the environment variables. 380 | 381 | How can I simulate a button?:: 382 | This is pretty straightforward actually. 383 | Just make sure not to override the _full_text_, for example: 384 | + 385 | [source,ini] 386 | ---- 387 | [calc-button] 388 | full_text=Calculator 389 | command=gnome-calculator >/dev/null 390 | ---- 391 | 392 | Can a block start a GUI application?:: 393 | Sure. 394 | And if you do not wish your command to block until the application is closed, ask i3 to start it for you with `i3-msg -q exec myapp`. 395 | 396 | Why Pango isn't working?:: 397 | The Pango markup requires a Pango font. 398 | Make sure you configured link:https://i3wm.org/docs/userguide.html#_font[i3bar] to use a Pango font. 399 | For example: 400 | + 401 | [source] 402 | ---- 403 | font pango:Inconsolata, Icons 12 404 | ---- 405 | 406 | Why is the output from my persistent block not displayed?:: 407 | Make sure to flush stdout, for example: 408 | + 409 | [source,ini] 410 | ---- 411 | [ruby-loop] 412 | full_text=Click me 413 | command=ruby -p -e '$_.prepend("Got button ")' -e 'STDOUT.flush' 414 | interval=persist 415 | ---- 416 | 417 | Can I use a time interval below 1 second?:: 418 | No, the time unit for interval is the second. 419 | + 420 | But even though I wouldn't recommend it, you can still update faster than that with loops: 421 | + 422 | [source,ini] 423 | ---- 424 | [nano1] 425 | command=sleep .5; date +%N 426 | interval=repeat 427 | 428 | [nano2] 429 | command=while sleep .5; do date +%N; done 430 | interval=persist 431 | ---- 432 | 433 | Can I change the block separator?:: 434 | Not with {progname} itself, separators are drawn by i3bar. 435 | You can change the _separator_symbol_ in the link:https://i3wm.org/docs/userguide.html#_custom_separator_symbol[i3bar configuration]. 436 | + 437 | Alternatively, you can define static blocks as custom separators in your {progname} configuration. 438 | In the example below, we use the _"\xe3\x80\x89"_ UTF-8 character: 439 | + 440 | [source,ini] 441 | ---- 442 | # Define the custom separator in global properties for boilerplate 443 | full_text=〉 444 | align=center 445 | color=#666666 446 | separator=false 447 | separator_block_width=7 448 | 449 | [time] 450 | instance=la 451 | TZ=America/Los_Angeles 452 | command=date +%T 453 | interval=5 454 | 455 | [separator] 456 | 457 | [time] 458 | instance=nc 459 | TZ=Pacific/Noumea 460 | command=date +%T 461 | interval=5 462 | 463 | [separator] 464 | 465 | [time] 466 | instance=mtl 467 | TZ=America/Montreal 468 | command=date +%T 469 | interval=5 470 | ---- 471 | 472 | == Debugging 473 | 474 | The log level can be increased with the `-v` option. 475 | 476 | If your window manager (and thus this program) is run via systemd, you can inspect the program outputs with `journalctl -t -f`. 477 | You may also use this in conjonction with running the program manually with `systemd-cat -t ./i3blocks`. 478 | 479 | Alternatively you can redirect the standard output and error streams from the program invokation with: 480 | 481 | [source] 482 | ---- 483 | bar { 484 | status_command 2>/tmp/i3blocks.err /path/to/i3blocks -vvv -c /path/to/config | tee /tmp/i3blocks.out 485 | } 486 | ---- 487 | 488 | And inspect the log with `tail -f /tmp/i3blocks.err`. 489 | 490 | See the link:{progname}.1{outfilesuffix}[manpage] for details about the command line options and {progname} usage. 491 | 492 | == License 493 | 494 | {progname} is Copyright (C) Vivien Didelot 495 | 496 | See the file COPYING for information of licensing and distribution. 497 | -------------------------------------------------------------------------------- /docs/Rakefile: -------------------------------------------------------------------------------- 1 | require 'asciidoctor' 2 | 3 | task :default do 4 | Asciidoctor.convert_file 'README.adoc', safe: :unsafe, to_file: 'index.html', attributes: { 'reproducible' => true, 'toc' => 'left' } 5 | Asciidoctor.convert_file 'blocklets.adoc', safe: :unsafe, attributes: { 'reproducible' => true } 6 | Asciidoctor.convert_file 'i3xrocks.1.adoc', safe: :unsafe, attributes: { 'reproducible' => true, 'toc' => 'left' } 7 | Asciidoctor.convert_file 'i3xrocks.1.adoc', safe: :unsafe, backend: :manpage 8 | end 9 | -------------------------------------------------------------------------------- /docs/blocklets-docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/blocklets.adoc: -------------------------------------------------------------------------------- 1 | = Blocklets 2 | :docinfo: 3 | 4 | This page lists blocklets found in community projects. 5 | 6 | TIP: Have your blocklet(s) shown in that list! + 7 | Your repository will be included in the page automatically if it has both the **i3xrocks** and **blocklet** topics. + 8 | Data is fetch from link:https://github.com/search?q=topic:i3xrocks+topic:blocklet[the official Github search page]. 9 | 10 | IMPORTANT: This page needs some front-end love 💔 11 | 12 | [pass] 13 |
14 | -------------------------------------------------------------------------------- /docs/blocklets.css: -------------------------------------------------------------------------------- 1 | .card-container { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | grid-gap: 30px; 5 | } 6 | 7 | .card { 8 | display: grid; 9 | grid-template-columns: 1fr 3fr 1fr; 10 | grid-template-rows: 1fr 1fr; 11 | grid-gap: 8px; 12 | grid-template-areas: 13 | "top-left top-center top-right" 14 | "bottom bottom bottom"; 15 | } 16 | 17 | .card-top-left { 18 | grid-area: top-left; 19 | } 20 | 21 | .card-top-center { 22 | grid-area: top-center; 23 | } 24 | 25 | .card-top-right { 26 | grid-area: top-right; 27 | } 28 | 29 | .card-bottom { 30 | grid-area: bottom; 31 | } 32 | 33 | .repo { 34 | width: 400px; 35 | height: 150px; 36 | border-radius: 5px; 37 | border: 1px solid #d3d3d3; 38 | padding: 8px; 39 | color: #555; 40 | } 41 | 42 | .repo p { 43 | margin: 0; 44 | } 45 | 46 | .repo-owner-avatar { 47 | height: 64px; 48 | width: 64px; 49 | } 50 | 51 | .repo-stars, 52 | .repo-language, 53 | .repo-last-update { 54 | font-size: 12px; 55 | } 56 | 57 | .repo-stars span { 58 | font-size: 24px; 59 | } 60 | -------------------------------------------------------------------------------- /docs/blocklets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Blocklets 9 | 10 | 438 | 439 | 440 | 441 | 442 | 443 | 446 |
447 |
448 |

This page lists blocklets found in community projects.

449 |
450 |
451 | 452 | 453 | 456 | 461 | 462 |
454 |
Tip
455 |
457 | Have your blocklet(s) shown in that list!
458 | Your repository will be included in the page automatically if it has both the i3xrocks and blocklet topics.
459 | Data is fetch from the official Github search page. 460 |
463 |
464 |
465 | 466 | 467 | 470 | 473 | 474 |
468 |
Important
469 |
471 | This page needs some front-end love 💔 472 |
475 |
476 |
477 |
478 | 482 | 483 | -------------------------------------------------------------------------------- /docs/blocklets.js: -------------------------------------------------------------------------------- 1 | document.onreadystatechange = function () { 2 | if (document.readyState === 'complete') { 3 | var repos = document.querySelector('#repos'); 4 | var request = new XMLHttpRequest(); 5 | request.open('GET', "https://api.github.com/search/repositories?q=topic:i3xrocks+topic:blocklet"); 6 | request.onload = function () { 7 | var data = JSON.parse(request.responseText); 8 | 9 | data.items.forEach(function (repo) { 10 | repos.innerHTML += `
11 |
12 | ${ repo.owner.login } 13 |
14 |
15 |

${ repo.name }

16 |

Include ${ repo.language } code

17 |

Updated ${ moment(repo.updated_at).fromNow() }

18 |
19 |
20 |

${ repo.stargazers_count }

21 |
22 |
23 |

${ repo.description }

24 |
25 |
`; 26 | }); 27 | } 28 | 29 | request.send(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/i3xrocks.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: i3xrocks 3 | .\" Author: [see the "AUTHOR(S)" section] 4 | .\" Generator: Asciidoctor 2.0.10 5 | .\" Date: 2019-08-12 6 | .\" Manual: \ \& 7 | .\" Source: \ \& 8 | .\" Language: English 9 | .\" 10 | .TH "i3xrocks" "1" "2018-09-25" "\ \&" "\ \&" 11 | .ie \n(.g .ds Aq \(aq 12 | .el .ds Aq ' 13 | .ss \n[.ss] 0 14 | .nh 15 | .ad l 16 | .de URL 17 | \fI\\$2\fP <\\$1>\\$3 18 | .. 19 | .als MTO URL 20 | .if \n[.g] \{\ 21 | . mso www.tmac 22 | . am URL 23 | . ad l 24 | . . 25 | . am MTO 26 | . ad l 27 | . . 28 | . LINKSTYLE blue R < > 29 | .\} 30 | .SH "NAME" 31 | i3xrocks \- A minimalist scheduler for your status line scripts 32 | .SH "SYNOPSIS" 33 | .sp 34 | \fBi3xrocks\fP [\fIOPTION\fP]... 35 | .SH "DESCRIPTION" 36 | .sp 37 | The i3xrocks(1) command generates a status line for i3bar(1). 38 | It schedules command lines described in a simple configuration file at specified time intervals, upon signal reception or on clicks. 39 | .SH "OPTIONS" 40 | .sp 41 | \fB\-h\fP 42 | .RS 4 43 | Show the help message and exit. 44 | .RE 45 | .sp 46 | \fB\-c\fP \fICONFIGFILE\fP 47 | .RS 4 48 | Specifies an alternate configuration file path. 49 | .RE 50 | .sp 51 | \fB\-v\fP 52 | .RS 4 53 | Increase log level. 54 | This option is a cumulative. 55 | By default only fatal errors are shown in the status bar. 56 | Passing this option once will show error messages as well. 57 | Using \fB\-vv\fP and more will show more debug output on standard error. 58 | .RE 59 | .SH "CONFIGURATION" 60 | .sp 61 | i3xrocks must be defined as the status line command of the \fBbar\fP block described in your i3(1) configuration file: 62 | .sp 63 | .if n .RS 4 64 | .nf 65 | bar { 66 | status_command i3xrocks 67 | } 68 | .fi 69 | .if n .RE 70 | .sp 71 | By default, i3xrocks looks for a configuration file in the following order (note that /etc may be prefixed with /usr/local depending on the compilation flags): 72 | .sp 73 | .RS 4 74 | .ie n \{\ 75 | \h'-04' 1.\h'+01'\c 76 | .\} 77 | .el \{\ 78 | . sp -1 79 | . IP " 1." 4.2 80 | .\} 81 | \fI$XDG_CONFIG_HOME/i3xrocks/config\fP (or \fI~/.config/i3xrocks/config\fP) 82 | .RE 83 | .sp 84 | .RS 4 85 | .ie n \{\ 86 | \h'-04' 2.\h'+01'\c 87 | .\} 88 | .el \{\ 89 | . sp -1 90 | . IP " 2." 4.2 91 | .\} 92 | \fI~/.i3xrocks.conf\fP 93 | .RE 94 | .sp 95 | .RS 4 96 | .ie n \{\ 97 | \h'-04' 3.\h'+01'\c 98 | .\} 99 | .el \{\ 100 | . sp -1 101 | . IP " 3." 4.2 102 | .\} 103 | \fI$XDG_CONFIG_DIRS/i3xrocks/config\fP (or \fI/etc/xdg/i3xrocks/config\fP) 104 | .RE 105 | .sp 106 | .RS 4 107 | .ie n \{\ 108 | \h'-04' 4.\h'+01'\c 109 | .\} 110 | .el \{\ 111 | . sp -1 112 | . IP " 4." 4.2 113 | .\} 114 | \fI/etc/i3xrocks.conf\fP 115 | .RE 116 | .sp 117 | The configuration file uses a simplified INI format. 118 | Each section describes a new block to be displayed in the status bar. 119 | A line beginning with a \fI#\fP sign is a comment, and empty lines are ignored. 120 | A property is one \fIkey=value\fP pair per line, without spaces surrounding the equal sign. 121 | Properties declared outside a block (i.e. at the beginning of the file) describe global settings and are merged into every block definitions. 122 | .sp 123 | You must refer to \c 124 | .URL "http://i3wm.org/docs/i3bar\-protocol.html" "" " " 125 | to know what properties are understood by i3bar(1). 126 | .sp 127 | The following properties are interpreted by \fBi3xrocks\fP to describe how the optional command must be scheduled. 128 | .SS "command" 129 | .sp 130 | The command line to be invoked with \f(CRsh \-c\fP. 131 | .sp 132 | It can be relative to the configuration file where it is defined. 133 | .sp 134 | Data read from the standard output of the command is used to update the block properties. 135 | Data is interpreted depending of the specified \fIformat\fP. 136 | .SS "interval" 137 | .sp 138 | If it is a positive integer, then the block is spawned on startup and the value is used as a time interval in seconds to schedule future updates. 139 | If undefined or 0, the block won\(cqt be executed on startup (which is useful to simulate buttons). 140 | .sp 141 | If \fIonce\fP (or \fI\-1\fP), the block will be scheduled only on startup (note that a click or signal will still trigger an update). 142 | .sp 143 | If \fIrepeat\fP (or \fI\-2\fP), the block will be scheduled on startup, and as soon as it terminates (useful to repeat blocking commands). 144 | .sp 145 | If \fIpersist\fP (or \fI\-3\fP), the block will be scheduled only on startup, and updated as soon as it outputs a line. 146 | Updates are thus limited to single lines. 147 | .SS "signal" 148 | .sp 149 | The signal number used to update the block. 150 | All the real\-time (think prioritized and queueable) signals are available to the user. 151 | The number is valid between \fI1\fP and \fIN\fP, where \fISIGRTMIN+N=SIGRTMAX\fP. 152 | (Note that there are 31 real\-time signals in Linux.) 153 | For instance, \fIsignal=10\fP means that this block will be updated when \fBi3xrocks\fP receives the \fISIGRTMIN+10\fP signal. 154 | .SS "format" 155 | .sp 156 | This property specifies the format of the output text. 157 | .sp 158 | When undefined, the raw format is assumed. 159 | In this format, each non\-empty line of the output is used to update an i3bar key, in the order of definition found in the i3bar protocol: 160 | .sp 161 | .RS 4 162 | .ie n \{\ 163 | \h'-04' 1.\h'+01'\c 164 | .\} 165 | .el \{\ 166 | . sp -1 167 | . IP " 1." 4.2 168 | .\} 169 | \fIfull_text\fP 170 | .RE 171 | .sp 172 | .RS 4 173 | .ie n \{\ 174 | \h'-04' 2.\h'+01'\c 175 | .\} 176 | .el \{\ 177 | . sp -1 178 | . IP " 2." 4.2 179 | .\} 180 | \fIshort_text\fP 181 | .RE 182 | .sp 183 | .RS 4 184 | .ie n \{\ 185 | \h'-04' 3.\h'+01'\c 186 | .\} 187 | .el \{\ 188 | . sp -1 189 | . IP " 3." 4.2 190 | .\} 191 | \fIcolor\fP 192 | .RE 193 | .sp 194 | .RS 4 195 | .ie n \{\ 196 | \h'-04' 4.\h'+01'\c 197 | .\} 198 | .el \{\ 199 | . sp -1 200 | . IP " 4." 4.2 201 | .\} 202 | \fIbackground\fP 203 | .RE 204 | .sp 205 | Excess lines are considered an error. 206 | .sp 207 | If \fIjson\fP (or \fI1\fP) is used, the block output is read as a JSON object and will be merged into the block properties. 208 | .SH "SEE ALSO" 209 | .sp 210 | i3(1), i3bar(1), i3status(1) 211 | .SH "AUTHORS" 212 | .sp 213 | i3xrocks is written by Vivien Didelot and other contributors. 214 | .SH "RESOURCES" 215 | .sp 216 | \fBProject web site:\fP \c 217 | .URL "https://vivien.github.io/i3xrocks" "" "" 218 | .sp 219 | \fBGit source repository and issue tracker:\fP \c 220 | .URL "https://github.com/vivien/i3xrocks" "" "" 221 | .sp 222 | \fBOfficial collection of scripts for i3xrocks:\fP \c 223 | .URL "https://github.com/vivien/i3xrocks\-contrib" "" "" 224 | .sp 225 | \fBi3bar protocol:\fP \c 226 | .URL "http://i3wm.org/docs/i3bar\-protocol.html" "" "" 227 | .SH "COPYING" 228 | .sp 229 | Copyright \(co Vivien Didelot. 230 | Free use of this software is granted under the terms of the \fIGPLv3+\fP License. -------------------------------------------------------------------------------- /docs/i3xrocks.1.adoc: -------------------------------------------------------------------------------- 1 | :progname: i3xrocks 2 | 3 | = {progname}(1) 4 | 5 | == NAME 6 | 7 | {progname} - A feed generator for text based status bars 8 | 9 | == SYNOPSIS 10 | 11 | *{progname}* [_OPTION_]... 12 | 13 | == DESCRIPTION 14 | 15 | The {progname}(1) command generates a status line for i3bar(1). 16 | It schedules command lines described in a simple configuration file at specified time intervals, upon signal reception or on clicks. 17 | 18 | == OPTIONS 19 | 20 | *-h*:: 21 | Show the help message and exit. 22 | 23 | *-c* _CONFIGFILE_:: 24 | Specifies an alternate configuration file path. 25 | 26 | *-d* _CONFIGDIR_:: 27 | Specifies a conf.d style configuration directory. 28 | 29 | *-u* _USERCONFIGDIR_:: 30 | Specifies a conf.d style user configuration directory. 31 | 32 | *-v*:: 33 | Increase log level. 34 | This option is a cumulative. 35 | By default only fatal errors are shown in the status bar. 36 | Passing this option once will show error messages as well. 37 | Using *-vv* and more will show more debug output on standard error. 38 | 39 | == CONFIGURATION 40 | 41 | {progname} must be defined as the status line command of the *bar* block described in your i3(1) configuration file: 42 | 43 | [source] 44 | ---- 45 | bar { 46 | status_command i3xrocks 47 | } 48 | ---- 49 | 50 | By default, {progname} looks for a configuration file in the following order (note that /etc may be prefixed with /usr/local depending on the compilation flags): 51 | 52 | . _$XDG_CONFIG_HOME/i3xrocks/config_ (or _~/.config/i3xrocks/config_) 53 | . _~/.i3xrocks.conf_ 54 | . _$XDG_CONFIG_DIRS/i3xrocks/config_ (or _/etc/xdg/i3xrocks/config_) 55 | . _/etc/i3xrocks.conf_ 56 | 57 | For the content of the configuration file, please refer to the i3blocks website: https://vivien.github.io/i3blocks 58 | 59 | == SEE ALSO 60 | 61 | i3(1), i3bar(1), i3status(1) 62 | 63 | == AUTHORS 64 | 65 | {progname} is written by Vivien Didelot and other contributors. Xresource and conf.d additions by Ken Gilmer. 66 | 67 | == RESOURCES 68 | 69 | *Project web site:* https://vivien.github.io/i3xrocks 70 | 71 | *Git source repository and issue tracker:* https://github.com/vivien/i3xrocks 72 | 73 | *Official collection of scripts for {progname}:* https://github.com/vivien/i3xrocks-contrib 74 | 75 | *i3bar protocol:* http://i3wm.org/docs/i3bar-protocol.html 76 | 77 | == COPYING 78 | 79 | Copyright (C) Vivien Didelot. 80 | Free use of this software is granted under the terms of the _GPLv3+_ License. 81 | -------------------------------------------------------------------------------- /docs/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | pushd docs >/dev/null 6 | 7 | if git diff --name-only --staged -- *.adoc | grep -q ^ 8 | then 9 | rake -v 10 | echo "Documentation was regenerated." 11 | 12 | if git diff --name-only -- *.adoc | grep -q ^ 13 | then 14 | echo "Some documentation source files are unstaged," 15 | echo "stage and commit hunks yourself with:" 16 | echo 17 | echo " git commit --no-verify --patch *.1 *.html" 18 | echo 19 | 20 | exit 1 21 | fi 22 | 23 | git add -v -u *.1 *.html 24 | fi 25 | 26 | popd >/dev/null 27 | -------------------------------------------------------------------------------- /i3bar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * i3bar.c - i3bar (plus i3-gaps and sway) protocol support 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "bar.h" 20 | #include "block.h" 21 | #include "json.h" 22 | #include "line.h" 23 | #include "log.h" 24 | #include "map.h" 25 | #include "term.h" 26 | 27 | /* See https://i3wm.org/docs/i3bar-protocol.html for details */ 28 | 29 | static struct { 30 | const char * const key; 31 | bool string; 32 | } i3bar_keys[] = { 33 | { "", false }, /* unknown key */ 34 | 35 | /* Standard keys */ 36 | { "full_text", true }, 37 | { "short_text", true }, 38 | { "color", true }, 39 | { "background", true }, 40 | { "border", true }, 41 | { "min_width", false }, /* can also be a number */ 42 | { "align", true }, 43 | { "name", true }, 44 | { "instance", true }, 45 | { "urgent", false }, 46 | { "separator", false }, 47 | { "separator_block_width", false }, 48 | { "markup", true }, 49 | 50 | /* i3-gaps features */ 51 | { "border_top", false }, 52 | { "border_bottom", false }, 53 | { "border_left", false }, 54 | { "border_right", false }, 55 | }; 56 | 57 | static unsigned int i3bar_indexof(const char *key) 58 | { 59 | unsigned int i; 60 | 61 | for (i = 0; i < sizeof(i3bar_keys) / sizeof(i3bar_keys[0]); i++) 62 | if (strcmp(i3bar_keys[i].key, key) == 0) 63 | return i; 64 | 65 | return 0; 66 | } 67 | 68 | static int i3bar_line_cb(char *line, size_t num, void *data) 69 | { 70 | unsigned int index = num + 1; 71 | struct map *map = data; 72 | const char *key; 73 | 74 | if (index >= sizeof(i3bar_keys) / sizeof(i3bar_keys[0])) { 75 | debug("ignoring excess line %d: %s", num, line); 76 | return 0; 77 | } 78 | 79 | key = i3bar_keys[index].key; 80 | 81 | return map_set(map, key, line); 82 | } 83 | 84 | int i3bar_read(int fd, size_t count, struct map *map) 85 | { 86 | return line_read(fd, count, i3bar_line_cb, map); 87 | } 88 | 89 | static void i3bar_print_term(const struct bar *bar) 90 | { 91 | struct block *block = bar->blocks; 92 | const char *full_text; 93 | 94 | term_restore_cursor(); 95 | 96 | while (block) { 97 | full_text = map_get(block->env, "full_text"); 98 | if (full_text) 99 | fprintf(stdout, "%s ", full_text); 100 | 101 | block = block->next; 102 | } 103 | 104 | fflush(stdout); 105 | } 106 | 107 | static int i3bar_print_pair(const char *key, const char *value, void *data) 108 | { 109 | unsigned int index = i3bar_indexof(key); 110 | bool string = i3bar_keys[index].string; 111 | unsigned int *pcount = data; 112 | char buf[BUFSIZ]; 113 | bool escape; 114 | int err; 115 | 116 | /* Skip unknown keys */ 117 | if (!index) 118 | return 0; 119 | 120 | if (!value) 121 | value = "null"; 122 | 123 | if (string) { 124 | if (json_is_string(value)) 125 | escape = false; /* Expected string already quoted */ 126 | else 127 | escape = true; /* Enforce the string type */ 128 | } else { 129 | if (json_is_valid(value)) 130 | escape = false; /* Already valid JSON */ 131 | else 132 | escape = true; /* Unquoted string */ 133 | } 134 | 135 | if (escape) { 136 | err = json_escape(value, buf, sizeof(buf)); 137 | if (err) 138 | return err; 139 | 140 | value = buf; 141 | } 142 | 143 | if ((*pcount)++) 144 | fprintf(stdout, ","); 145 | 146 | fprintf(stdout, "\"%s\":%s", key, value); 147 | 148 | return 0; 149 | } 150 | 151 | static int i3bar_print_block(struct block *block, void *data) 152 | { 153 | const char *full_text = map_get(block->env, "full_text"); 154 | unsigned int *mcount = data; 155 | unsigned int pcount = 0; 156 | int err; 157 | 158 | /* "full_text" is the only mandatory key */ 159 | if (!full_text) { 160 | block_debug(block, "no text to display, skipping"); 161 | return 0; 162 | } 163 | 164 | if ((*mcount)++) 165 | fprintf(stdout, ","); 166 | 167 | fprintf(stdout, "{"); 168 | err = map_for_each(block->env, i3bar_print_pair, &pcount); 169 | fprintf(stdout, "}"); 170 | 171 | return err; 172 | } 173 | 174 | int i3bar_print(const struct bar *bar) 175 | { 176 | struct block *block = bar->blocks; 177 | unsigned int mcount = 0; 178 | int err; 179 | 180 | if (bar->term) { 181 | i3bar_print_term(bar); 182 | return 0; 183 | } 184 | 185 | fprintf(stdout, ",["); 186 | while (block) { 187 | err = i3bar_print_block(block, &mcount); 188 | if (err) 189 | break; 190 | 191 | block = block->next; 192 | } 193 | fprintf(stdout, "]\n"); 194 | fflush(stdout); 195 | 196 | return err; 197 | } 198 | 199 | int i3bar_printf(struct block *block, int lvl, const char *msg) 200 | { 201 | const struct bar *bar = block->bar; 202 | struct map *map = block->env; 203 | int err; 204 | 205 | if (bar->term || lvl > LOG_ERROR) 206 | return 0; 207 | 208 | block->tainted = true; 209 | 210 | err = map_set(map, "full_text", msg); 211 | if (err) 212 | return err; 213 | 214 | if (lvl <= LOG_ERROR) { 215 | err = map_set(map, "urgent", "true"); 216 | if (err) 217 | return err; 218 | } 219 | 220 | return i3bar_print(bar); 221 | } 222 | 223 | int i3bar_start(struct bar *bar) 224 | { 225 | if (bar->term) { 226 | term_save_cursor(); 227 | term_restore_cursor(); 228 | } else { 229 | fprintf(stdout, "{\"version\":1,\"click_events\":true}\n[[]\n"); 230 | fflush(stdout); 231 | } 232 | 233 | return 0; 234 | } 235 | 236 | void i3bar_stop(struct bar *bar) 237 | { 238 | if (bar->term) { 239 | term_reset_cursor(); 240 | } else { 241 | fprintf(stdout, "]\n"); 242 | fflush(stdout); 243 | } 244 | } 245 | 246 | static struct block *i3bar_find(struct bar *bar, const struct map *map) 247 | { 248 | const char *block_name, *block_instance; 249 | const char *map_name, *map_instance; 250 | struct block *block = bar->blocks; 251 | 252 | /* "name" and "instance" are the only identifiers provided by i3bar */ 253 | map_name = map_get(map, "name") ? : ""; 254 | map_instance = map_get(map, "instance") ? : ""; 255 | 256 | while (block) { 257 | block_name = block_get(block, "name") ? : ""; 258 | block_instance = block_get(block, "instance") ? : ""; 259 | 260 | if (strcmp(block_name, map_name) == 0 && 261 | strcmp(block_instance, map_instance) == 0) 262 | return block; 263 | 264 | block = block->next; 265 | } 266 | 267 | return NULL; 268 | } 269 | 270 | int i3bar_click(struct bar *bar) 271 | { 272 | struct block *block; 273 | struct map *click; 274 | int err; 275 | 276 | click = map_create(); 277 | if (!click) 278 | return -ENOMEM; 279 | 280 | for (;;) { 281 | /* Each click is one JSON object per line */ 282 | err = json_read(STDIN_FILENO, 1, click); 283 | if (err) { 284 | if (err == -EAGAIN) 285 | err = 0; 286 | 287 | break; 288 | } 289 | 290 | /* Look for the corresponding block */ 291 | block = i3bar_find(bar, click); 292 | if (block) { 293 | if (block->tainted) { 294 | err = block_reset(block); 295 | if (err) 296 | break; 297 | 298 | block->tainted = false; 299 | 300 | err = i3bar_print(bar); 301 | if (err) 302 | break; 303 | } else { 304 | err = map_copy(block->env, click); 305 | if (err) 306 | break; 307 | 308 | err = block_click(block); 309 | if (err) 310 | break; 311 | } 312 | } 313 | 314 | map_clear(click); 315 | } 316 | 317 | map_destroy(click); 318 | 319 | return err; 320 | } 321 | 322 | int i3bar_setup(struct block *block) 323 | { 324 | const char *instance = map_get(block->config, "instance"); 325 | const char *name = map_get(block->config, "name"); 326 | char buf[BUFSIZ]; 327 | int err; 328 | 329 | /* A block needs a name to be clickable */ 330 | if (!name) { 331 | name = "foo"; 332 | err = map_set(block->config, "name", name); 333 | if (err) 334 | return err; 335 | } 336 | 337 | if (instance) 338 | snprintf(buf, sizeof(buf), "%s:%s", name, instance); 339 | else 340 | snprintf(buf, sizeof(buf), "%s", name); 341 | 342 | block->name = strdup(buf); 343 | if (!block->name) 344 | return -ENOMEM; 345 | 346 | return 0; 347 | } 348 | -------------------------------------------------------------------------------- /i3xrocks.conf: -------------------------------------------------------------------------------- 1 | # i3xrocks configuration file 2 | # 3 | # The i3xrocks man page describes the usage of the binary, 4 | # and its website describes the configuration: 5 | # 6 | # https://vivien.github.io/i3blocks 7 | 8 | 9 | # Global properties 10 | separator=true 11 | separator_block_width=15 12 | 13 | [documentation] 14 | full_text=Documentation 15 | website=https://vivien.github.io/i3blocks 16 | command=xdg-open "$website" 17 | color=#f12711 18 | 19 | [greetings] 20 | color=#f5af19 21 | command=echo "Hello, $USER!" 22 | interval=once 23 | 24 | [time] 25 | command=date '+%Y-%m-%d %H:%M:%S' 26 | interval=1 27 | -------------------------------------------------------------------------------- /ini.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ini.c - generic INI parser 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "ini.h" 26 | #include "line.h" 27 | #include "log.h" 28 | 29 | struct ini { 30 | ini_sec_cb_t *sec_cb; 31 | ini_prop_cb_t *prop_cb; 32 | void *data; 33 | xcb_xrm_database_t *database; 34 | }; 35 | 36 | static int ini_section(struct ini *ini, char *section) 37 | { 38 | if (!ini->sec_cb) 39 | return 0; 40 | 41 | return ini->sec_cb(section, ini->data); 42 | } 43 | 44 | static int ini_property(struct ini *ini, char *key, char *value) 45 | { 46 | if (!ini->prop_cb) 47 | return 0; 48 | 49 | // If the value begins with 'xresource:` then query Xresources for the actual value or load an optional default. 50 | if (strncmp(value, "xresource:", strlen("xresource:")) == 0) { 51 | if (!ini->database) { 52 | fatal("Unable to access Xresources."); 53 | return -EINVAL; 54 | } 55 | 56 | // Add null terminator so that xcb_xrm_resource_get_string knows where to stop. 57 | int buffer_size = strlen(value); 58 | char *default_value = strchr(value, ' '); 59 | if (default_value) { 60 | if (*(default_value + 1) != '\0') { 61 | default_value++; 62 | default_value[-1] = '\0'; 63 | } 64 | } 65 | 66 | char *resource; 67 | if (xcb_xrm_resource_get_string(ini->database, value + strlen("xresource:"), NULL, &resource) == 0) { 68 | int ret = ini->prop_cb(key, resource, ini->data); 69 | free(resource); 70 | return ret; 71 | } else if (strlen(default_value) > 0) { 72 | value = default_value; 73 | } else { 74 | fatal("invalid Xresource key: \"%s\"", value); 75 | return -EINVAL; 76 | } 77 | } 78 | 79 | return ini->prop_cb(key, value, ini->data); 80 | } 81 | 82 | static int ini_parse_line(char *line, size_t num, void *data) 83 | { 84 | /* comment or empty line? */ 85 | if (*line == '\0' || *line == '#') 86 | return 0; 87 | 88 | /* section? */ 89 | if (*line == '[') { 90 | char *closing, *section; 91 | 92 | closing = strchr(line, ']'); 93 | if (!closing) { 94 | error("malformated section \"%s\"", line); 95 | return -EINVAL; 96 | } 97 | 98 | if (*(closing + 1) != '\0') { 99 | error("trailing characters \"%s\"", closing); 100 | return -EINVAL; 101 | } 102 | 103 | section = line + 1; 104 | *closing = '\0'; 105 | 106 | return ini_section(data, section); 107 | } 108 | 109 | /* property? */ 110 | if (isalpha(*line) || *line == '_') { 111 | char *equals, *key, *value; 112 | 113 | equals = strchr(line, '='); 114 | if (!equals) { 115 | error("malformated property, should be a key=value pair"); 116 | return -EINVAL; 117 | } 118 | 119 | *equals = '\0'; 120 | key = line; 121 | value = equals + 1; 122 | 123 | return ini_property(data, key, value); 124 | } 125 | 126 | error("invalid INI syntax for line: \"%s\"", line); 127 | return -EINVAL; 128 | } 129 | 130 | int ini_read(int fd, size_t count, ini_sec_cb_t *sec_cb, ini_prop_cb_t *prop_cb, 131 | void *data) 132 | { 133 | int screen; 134 | xcb_connection_t *conn = xcb_connect(NULL, &screen); 135 | 136 | xcb_xrm_database_t *xdatabase = xcb_xrm_database_from_default(conn); 137 | 138 | struct ini ini = { 139 | .sec_cb = sec_cb, 140 | .prop_cb = prop_cb, 141 | .data = data, 142 | .database = xdatabase 143 | }; 144 | 145 | int val = line_read(fd, count, ini_parse_line, &ini); 146 | 147 | xcb_xrm_database_free(xdatabase); 148 | 149 | xcb_disconnect(conn); 150 | 151 | return val; 152 | } 153 | -------------------------------------------------------------------------------- /ini.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ini.h - generic INI format parsing header 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef INI_H 20 | #define INI_H 21 | 22 | typedef int ini_sec_cb_t(char *section, void *data); 23 | typedef int ini_prop_cb_t(char *key, char *value, void *data); 24 | int ini_read(int fd, size_t count, ini_sec_cb_t *sec_cb, ini_prop_cb_t *prop_cb, 25 | void *data); 26 | 27 | #endif /* INI_H */ 28 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * json.c - flat JSON parsing 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "json.h" 26 | #include "line.h" 27 | #include "log.h" 28 | #include "map.h" 29 | #include "sys.h" 30 | 31 | /* Return the number of UTF-8 bytes on success, 0 if it is invalid */ 32 | static size_t json_parse_codepoint(const char *str, char *buf, size_t size) 33 | { 34 | uint16_t codepoint = 0; 35 | char utf8[3]; 36 | size_t len; 37 | int hex; 38 | char c; 39 | int i; 40 | 41 | for (i = 0; i < 4; i++) { 42 | c = str[i]; 43 | 44 | if (!isxdigit(c)) 45 | return 0; 46 | 47 | if (c >= '0' && c <= '9') 48 | hex = c - '0'; 49 | else if (c >= 'a' && c <= 'f') 50 | hex = c - 'a' + 10; 51 | else 52 | hex = c - 'A' + 10; 53 | 54 | codepoint |= hex << (12 - i * 4); 55 | } 56 | 57 | /* Support Only a single surrogate at the moment */ 58 | if (codepoint <= 0x7f) { 59 | len = 1; 60 | utf8[0] = codepoint; 61 | } else if (codepoint <= 0x7ff) { 62 | len = 2; 63 | utf8[0] = 0xc0 | (codepoint >> 6); 64 | utf8[1] = 0x80 | (codepoint & 0x3f); 65 | } else { 66 | len = 3; 67 | utf8[0] = (0xe0 | (codepoint >> 12)); 68 | utf8[1] = (0x80 | ((codepoint >> 6) & 0x3f)); 69 | utf8[2] = (0x80 | (codepoint & 0x3f)); 70 | } 71 | 72 | if (buf) { 73 | if (size < len) 74 | return 0; 75 | 76 | memcpy(buf, utf8, len); 77 | } 78 | 79 | return len; 80 | } 81 | 82 | /* Return the length of the parsed string, 0 if it is invalid */ 83 | static size_t json_parse_string(const char *str, char *buf, size_t size) 84 | { 85 | const char *end = str; 86 | size_t len; 87 | char c; 88 | 89 | if (*end != '"') 90 | return 0; 91 | 92 | do { 93 | len = 0; 94 | 95 | switch (*++end) { 96 | case '\0': 97 | return 0; 98 | case '"': 99 | c = '\0'; 100 | break; 101 | case '\\': 102 | switch (*++end) { 103 | case '"': 104 | c = '"'; 105 | break; 106 | case '\\': 107 | c = '\\'; 108 | break; 109 | case '/': 110 | c = '/'; 111 | break; 112 | case 'b': 113 | c = '\b'; 114 | break; 115 | case 'f': 116 | c = '\f'; 117 | break; 118 | case 'n': 119 | c = '\n'; 120 | break; 121 | case 'r': 122 | c = '\r'; 123 | break; 124 | case 't': 125 | c = '\t'; 126 | break; 127 | case 'u': 128 | len = json_parse_codepoint(++end, buf, size); 129 | if (!len) 130 | return 0; 131 | 132 | end += 3; /* jump to last hex digit */ 133 | break; 134 | default: 135 | return 0; 136 | } 137 | break; 138 | default: 139 | if (iscntrl(*end)) 140 | return 0; 141 | 142 | c = *end; 143 | break; 144 | } 145 | 146 | if (buf) { 147 | if (!len) { 148 | if (size < 1) 149 | return 0; 150 | 151 | *buf = c; 152 | len = 1; 153 | } 154 | 155 | buf += len; 156 | size -= len; 157 | } 158 | } while (c); 159 | 160 | return ++end - str; 161 | } 162 | 163 | /* Return the length of the parsed non scalar (with open/close delimiter), 0 if it is invalid */ 164 | static size_t json_parse_nested_struct(const char *str, char open, char close, 165 | char *buf, size_t size) 166 | { 167 | const char *end = str; 168 | size_t len; 169 | int nested; 170 | 171 | if (*str != open) 172 | return 0; 173 | 174 | nested = 1; 175 | while (nested) { 176 | ++end; 177 | 178 | /* control character or end-of-line? */ 179 | if (iscntrl(*end) || *end == '\0') 180 | return 0; 181 | 182 | if (*end == open) 183 | nested++; 184 | else if (*end == close) 185 | nested--; 186 | } 187 | 188 | len = ++end - str; 189 | if (!len) 190 | return 0; 191 | 192 | if (buf) { 193 | if (size <= len) 194 | return 0; 195 | 196 | strncpy(buf, str, len); 197 | buf[len] = '\0'; 198 | } 199 | 200 | return len; 201 | } 202 | 203 | /* Return the length of the parsed array, 0 if it is invalid */ 204 | static size_t json_parse_nested_array(const char *str, char *buf, size_t size) 205 | { 206 | return json_parse_nested_struct(str, '[', ']', buf, size); 207 | } 208 | 209 | /* Return the length of the parsed object, 0 if it is invalid */ 210 | static size_t json_parse_nested_object(const char *str, char *buf, size_t size) 211 | { 212 | return json_parse_nested_struct(str, '{', '}', buf, size); 213 | } 214 | 215 | /* Return the length of the parsed number, 0 if it is invalid */ 216 | static size_t json_parse_number(const char *str, char *buf, size_t size) 217 | { 218 | size_t len; 219 | char *end; 220 | 221 | strtoul(str, &end, 10); 222 | 223 | len = end - str; 224 | if (!len) 225 | return 0; 226 | 227 | if (buf) { 228 | if (size <= len) 229 | return 0; 230 | 231 | strncpy(buf, str, len); 232 | buf[len] = '\0'; 233 | } 234 | 235 | return len; 236 | } 237 | 238 | /* Return the length of the parsed literal, 0 if it is invalid */ 239 | static size_t json_parse_literal(const char *str, const char *literal, 240 | char *buf, size_t size) 241 | { 242 | const size_t len = strlen(literal); 243 | 244 | if (strncmp(str, literal, len) != 0) 245 | return 0; 246 | 247 | if (buf) { 248 | strncpy(buf, literal, size); 249 | if (buf[size - 1] != '\0') 250 | return 0; 251 | } 252 | 253 | return len; 254 | } 255 | 256 | /* A value can be a string, number, object, array, true, false, or null */ 257 | static size_t json_parse_value(const char *str, char *buf, size_t size) 258 | { 259 | size_t len; 260 | 261 | len = json_parse_string(str, buf, size); 262 | if (len) 263 | return len; 264 | 265 | len = json_parse_number(str, buf, size); 266 | if (len) 267 | return len; 268 | 269 | len = json_parse_nested_object(str, buf, size); 270 | if (len) 271 | return len; 272 | 273 | len = json_parse_nested_array(str, buf, size); 274 | if (len) 275 | return len; 276 | 277 | len = json_parse_literal(str, "true", buf, size); 278 | if (len) 279 | return len; 280 | 281 | len = json_parse_literal(str, "false", buf, size); 282 | if (len) 283 | return len; 284 | 285 | len = json_parse_literal(str, "null", buf, size); 286 | if (len) 287 | return len; 288 | 289 | return 0; 290 | } 291 | 292 | /* Return the length of a separator optionally enclosed by whitespaces, 0 otherwise */ 293 | static size_t json_parse_sep(const char *str, char sep) 294 | { 295 | size_t len = 0; 296 | 297 | while (isspace(*str)) 298 | str++, len++; 299 | 300 | if (*str != sep) 301 | return 0; 302 | 303 | str++; 304 | len++; 305 | 306 | while (isspace(*str)) 307 | str++, len++; 308 | 309 | return len; 310 | } 311 | 312 | /* Parse an inline ["name"][\s+:\s+][value] name-value pair */ 313 | static size_t json_parse_pair(const char *str, char *name, size_t namesiz, 314 | char *val, size_t valsiz) 315 | { 316 | size_t pair_len = 0; 317 | size_t len; 318 | 319 | len = json_parse_string(str, name, namesiz); 320 | if (!len) 321 | return 0; 322 | 323 | pair_len += len; 324 | str += len; 325 | 326 | len = json_parse_sep(str, ':'); 327 | if (!len) 328 | return 0; 329 | 330 | pair_len += len; 331 | str += len; 332 | 333 | len = json_parse_value(str, val, valsiz); 334 | if (!len) 335 | return 0; 336 | 337 | pair_len += len; 338 | 339 | return pair_len; 340 | } 341 | 342 | static int json_line_cb(char *line, size_t num, void *data) 343 | { 344 | struct map *map = data; 345 | char name[BUFSIZ]; 346 | char val[BUFSIZ]; 347 | size_t len; 348 | int err; 349 | 350 | for (;;) { 351 | /* Only support inline flattened structures at the moment */ 352 | while (*line == '[' || *line == ']' || *line == ',' || 353 | *line == '{' || *line == '}' || isspace(*line)) 354 | line++; 355 | 356 | if (*line == '\0') 357 | break; 358 | 359 | memset(name, 0, sizeof(name)); 360 | memset(val, 0, sizeof(val)); 361 | 362 | len = json_parse_pair(line, name, sizeof(name), val, 363 | sizeof(val)); 364 | if (!len) 365 | return -EINVAL; 366 | 367 | line += len; 368 | 369 | /* valid delimiters after a pair */ 370 | if (*line != ',' && *line != '}' && *line != '\0' && 371 | !isspace(*line)) 372 | return -EINVAL; 373 | 374 | if (*line != '\0') 375 | line++; 376 | 377 | if (map) { 378 | err = map_set(map, name, val); 379 | if (err) 380 | return err; 381 | } 382 | } 383 | 384 | return 0; 385 | } 386 | 387 | int json_read(int fd, size_t count, struct map *map) 388 | { 389 | return line_read(fd, count, json_line_cb, map); 390 | } 391 | 392 | bool json_is_string(const char *str) 393 | { 394 | size_t len; 395 | 396 | len = strlen(str); 397 | if (!len) 398 | return false; 399 | 400 | return json_parse_string(str, NULL, 0) == len; 401 | } 402 | 403 | bool json_is_valid(const char *str) 404 | { 405 | size_t len; 406 | 407 | len = strlen(str); 408 | if (!len) 409 | return false; 410 | 411 | return json_parse_value(str, NULL, 0) == len; 412 | } 413 | 414 | int json_escape(const char *str, char *buf, size_t size) 415 | { 416 | size_t null = strlen(str) + 1; 417 | char c = '\0'; 418 | int len; 419 | 420 | do { 421 | switch (c) { 422 | case '\0': 423 | len = snprintf(buf, size, "\""); 424 | break; 425 | case '\b': 426 | len = snprintf(buf, size, "\\b"); 427 | break; 428 | case '\f': 429 | len = snprintf(buf, size, "\\f"); 430 | break; 431 | case '\n': 432 | len = snprintf(buf, size, "\\n"); 433 | break; 434 | case '\r': 435 | len = snprintf(buf, size, "\\r"); 436 | break; 437 | case '\t': 438 | len = snprintf(buf, size, "\\t"); 439 | break; 440 | case '\\': 441 | len = snprintf(buf, size, "\\\\"); 442 | break; 443 | case '"': 444 | len = snprintf(buf, size, "\\\""); 445 | break; 446 | default: 447 | if (iscntrl(c)) 448 | len = snprintf(buf, size, "\\u%04x", c); 449 | else 450 | len = snprintf(buf, size, "%c", c); 451 | break; 452 | } 453 | 454 | /* Ensure the result was not truncated */ 455 | if (len < 0 || len >= size) 456 | return -ENOSPC; 457 | 458 | size -= len; 459 | buf += len; 460 | 461 | c = *str; 462 | if (c) 463 | str++; 464 | } while (null--); 465 | 466 | return 0; 467 | } 468 | -------------------------------------------------------------------------------- /json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * json.h - JSON manipulation header 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef JSON_H 20 | #define JSON_H 21 | 22 | #include 23 | 24 | struct map; 25 | 26 | int json_read(int fd, size_t count, struct map *map); 27 | 28 | bool json_is_string(const char *str); 29 | bool json_is_valid(const char *str); 30 | int json_escape(const char *str, char *buf, size_t size); 31 | 32 | #endif /* JSON_H */ 33 | 34 | -------------------------------------------------------------------------------- /line.c: -------------------------------------------------------------------------------- 1 | /* 2 | * line.c - generic line parser 3 | * Copyright (C) 2015-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "line.h" 20 | #include "log.h" 21 | #include "sys.h" 22 | 23 | /* Read a single character and return a negative error code if none was read */ 24 | static int line_getc(int fd, char *c) 25 | { 26 | return sys_read(fd, c, 1, NULL); 27 | } 28 | 29 | /* Read a line including the newline character and return its positive length */ 30 | static ssize_t line_gets(int fd, char *buf, size_t size) 31 | { 32 | size_t len = 0; 33 | int err; 34 | 35 | for (;;) { 36 | if (len == size) 37 | return -ENOSPC; 38 | 39 | err = line_getc(fd, buf + len); 40 | if (err) 41 | return err; 42 | 43 | if (buf[len++] == '\n') 44 | break; 45 | } 46 | 47 | /* at least 1 */ 48 | return len; 49 | } 50 | 51 | /* Read a line excluding the newline character */ 52 | static int line_parse(int fd, line_cb_t *cb, size_t num, void *data) 53 | { 54 | char buf[BUFSIZ]; 55 | ssize_t len; 56 | int err; 57 | 58 | len = line_gets(fd, buf, sizeof(buf)); 59 | if (len < 0) 60 | return len; 61 | 62 | /* replace newline with terminating null byte */ 63 | buf[len - 1] = '\0'; 64 | 65 | debug("&%d:%.3d: %s", fd, num, buf); 66 | 67 | if (cb) { 68 | err = cb(buf, num, data); 69 | if (err) 70 | return err; 71 | } 72 | 73 | return 0; 74 | } 75 | 76 | /* Read up to count lines excluding their newline character */ 77 | int line_read(int fd, size_t count, line_cb_t *cb, void *data) 78 | { 79 | size_t lines = 0; 80 | int err; 81 | 82 | while (count--) { 83 | err = line_parse(fd, cb, lines++, data); 84 | if (err) 85 | return err; 86 | } 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /line.h: -------------------------------------------------------------------------------- 1 | /* 2 | * line.h - generic line parsing header 3 | * Copyright (C) 2015-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef IO_H 20 | #define IO_H 21 | 22 | #include 23 | 24 | typedef int line_cb_t(char *line, size_t num, void *data); 25 | int line_read(int fd, size_t count, line_cb_t *cb, void *data); 26 | 27 | #endif /* IO_H */ 28 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * log.h - syslog friendly error and debug printing macros 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef LOG_H 20 | #define LOG_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define SYSLOG_CRIT "<2>" 28 | #define SYSLOG_ERR "<3>" 29 | #define SYSLOG_NOTICE "<5>" 30 | #define SYSLOG_DEBUG "<7>" 31 | 32 | extern enum { 33 | LOG_FATAL, 34 | LOG_ERROR, 35 | LOG_TRACE, 36 | LOG_DEBUG, 37 | } log_level; 38 | 39 | static inline void log_printf(int lvl, const char *fmt, ...) 40 | { 41 | va_list ap; 42 | 43 | if (lvl <= LOG_ERROR || lvl <= log_level) { 44 | va_start(ap, fmt); 45 | vfprintf(stderr, fmt, ap); 46 | va_end(ap); 47 | } 48 | } 49 | 50 | #define fatal(fmt, ...) \ 51 | log_printf(LOG_FATAL, SYSLOG_CRIT fmt "\n", ##__VA_ARGS__) 52 | 53 | #define error(fmt, ...) \ 54 | log_printf(LOG_ERROR, SYSLOG_ERR fmt "\n", ##__VA_ARGS__) 55 | 56 | #define trace(fmt, ...) \ 57 | log_printf(LOG_TRACE, SYSLOG_NOTICE fmt "\n", ##__VA_ARGS__) 58 | 59 | #define debug(fmt, ...) \ 60 | log_printf(LOG_DEBUG, SYSLOG_DEBUG "%s:%s:%d: " fmt "\n", \ 61 | __FILE__, __func__, __LINE__, ##__VA_ARGS__) 62 | 63 | #endif /* LOG_H */ 64 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * main.c - main entry point 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include "i3xrocks-config.h" 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "bar.h" 28 | #include "log.h" 29 | #include "sys.h" 30 | 31 | unsigned int log_level; 32 | 33 | int main(int argc, char *argv[]) 34 | { 35 | char *output = NULL; 36 | char *path = NULL; 37 | char *conf_dir = NULL; 38 | char *user_conf_dir = NULL; 39 | bool term; 40 | int c; 41 | 42 | while (c = getopt(argc, argv, "c:o:d:u:vhV"), c != -1) { 43 | switch (c) { 44 | case 'c': 45 | path = optarg; 46 | break; 47 | case 'd': 48 | conf_dir = optarg; 49 | break; 50 | case 'u': 51 | user_conf_dir = optarg; 52 | break; 53 | case 'o': 54 | output = optarg; 55 | break; 56 | case 'v': 57 | log_level++; 58 | break; 59 | case 'h': 60 | printf("Usage: %s [-c ] [-d ] [-u ] [-o ] [-v] [-h] [-V]\n", argv[0]); 61 | return EXIT_SUCCESS; 62 | case 'V': 63 | printf(PACKAGE_STRING " © 2020 - 2021 Ken Gilmer\n"); 64 | return EXIT_SUCCESS; 65 | default: 66 | error("Try '%s -h' for more information.", argv[0]); 67 | return EXIT_FAILURE; 68 | } 69 | } 70 | 71 | term = !sys_isatty(STDOUT_FILENO); 72 | if (output) 73 | term = !strcmp(output, "term"); 74 | 75 | if (bar_init(term, path, conf_dir, user_conf_dir)) 76 | return EXIT_FAILURE; 77 | 78 | return EXIT_SUCCESS; 79 | } 80 | -------------------------------------------------------------------------------- /map.c: -------------------------------------------------------------------------------- 1 | /* 2 | * map.c - implementation of an associative array 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "map.h" 24 | 25 | struct pair { 26 | char *key; 27 | char *value; 28 | 29 | struct pair *next; 30 | }; 31 | 32 | struct map { 33 | struct pair *head; 34 | }; 35 | 36 | static struct pair *map_head(const struct map *map) 37 | { 38 | return map->head; 39 | } 40 | 41 | /* Return previous pair if key is found, last pair otherwise */ 42 | static struct pair *map_prev(const struct map *map, const char *key) 43 | { 44 | struct pair *prev = map_head(map); 45 | struct pair *next = prev->next; 46 | 47 | while (next) { 48 | if (strcmp(next->key, key) == 0) 49 | break; 50 | 51 | prev = next; 52 | next = next->next; 53 | } 54 | 55 | return prev; 56 | } 57 | 58 | /* Update the value of a pair */ 59 | static int map_reassign(struct pair *pair, const char *value) 60 | { 61 | free(pair->value); 62 | pair->value = NULL; 63 | 64 | if (value) { 65 | pair->value = strdup(value); 66 | if (!pair->value) 67 | return -ENOMEM; 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | /* Create a new key-value pair */ 74 | static struct pair *map_pair(const char *key, const char *value) 75 | { 76 | struct pair *pair; 77 | int err; 78 | 79 | pair = calloc(1, sizeof(struct pair)); 80 | if (!pair) 81 | return NULL; 82 | 83 | pair->key = strdup(key); 84 | if (!pair->key) { 85 | free(pair); 86 | return NULL; 87 | } 88 | 89 | err = map_reassign(pair, value); 90 | if (err) { 91 | free(pair->key); 92 | free(pair); 93 | return NULL; 94 | } 95 | 96 | return pair; 97 | } 98 | 99 | /* Destroy a new key-value pair */ 100 | void map_unpair(struct pair *pair) 101 | { 102 | map_reassign(pair, NULL); 103 | free(pair->key); 104 | free(pair); 105 | } 106 | 107 | /* Insert a key-value pair after a given pair */ 108 | static int map_insert(struct pair *prev, const char *key, const char *value) 109 | { 110 | struct pair *pair; 111 | 112 | pair = map_pair(key, value); 113 | if (!pair) 114 | return -ENOMEM; 115 | 116 | pair->next = prev->next; 117 | prev->next = pair; 118 | 119 | return 0; 120 | } 121 | 122 | /* Delete the key-value pair after a given pair */ 123 | static void map_delete(struct pair *prev) 124 | { 125 | struct pair *pair = prev->next; 126 | 127 | prev->next = pair->next; 128 | map_unpair(pair); 129 | } 130 | 131 | const char *map_get(const struct map *map, const char *key) 132 | { 133 | struct pair *prev = map_prev(map, key); 134 | struct pair *next = prev->next; 135 | 136 | if (next) 137 | return next->value; 138 | else 139 | return NULL; 140 | } 141 | 142 | int map_set(struct map *map, const char *key, const char *value) 143 | { 144 | struct pair *prev = map_prev(map, key); 145 | struct pair *next = prev->next; 146 | 147 | if (next) 148 | return map_reassign(next, value); 149 | else 150 | return map_insert(prev, key, value); 151 | } 152 | 153 | int map_for_each(const struct map *map, map_func_t *func, void *data) 154 | { 155 | struct pair *pair = map_head(map); 156 | int err; 157 | 158 | while (pair->next) { 159 | pair = pair->next; 160 | err = func(pair->key, pair->value, data); 161 | if (err) 162 | return err; 163 | } 164 | 165 | return 0; 166 | } 167 | 168 | void map_clear(struct map *map) 169 | { 170 | struct pair *pair = map_head(map); 171 | 172 | while (pair->next) 173 | map_delete(pair); 174 | } 175 | 176 | static int map_dup(const char *key, const char *value, void *data) 177 | { 178 | struct map *map = data; 179 | 180 | return map_set(map, key, value); 181 | } 182 | 183 | int map_copy(struct map *map, const struct map *base) 184 | { 185 | return map_for_each(base, map_dup, map); 186 | } 187 | 188 | void map_destroy(struct map *map) 189 | { 190 | map_clear(map); 191 | free(map->head); 192 | free(map); 193 | } 194 | 195 | struct map *map_create(void) 196 | { 197 | struct map *map; 198 | 199 | map = calloc(1, sizeof(struct map)); 200 | if (!map) 201 | return NULL; 202 | 203 | map->head = calloc(1, sizeof(struct pair)); 204 | if (!map->head) { 205 | free(map); 206 | return NULL; 207 | } 208 | 209 | return map; 210 | } 211 | -------------------------------------------------------------------------------- /map.h: -------------------------------------------------------------------------------- 1 | /* 2 | * map.h - definition of an associative array 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef MAP_H 20 | #define MAP_H 21 | 22 | struct map; 23 | 24 | struct map *map_create(void); 25 | void map_destroy(struct map *map); 26 | 27 | int map_copy(struct map *map, const struct map *base); 28 | 29 | void map_clear(struct map *map); 30 | 31 | int map_set(struct map *map, const char *key, const char *value); 32 | const char *map_get(const struct map *map, const char *key); 33 | 34 | typedef int map_func_t(const char *key, const char *value, void *data); 35 | int map_for_each(const struct map *map, map_func_t *func, void *data); 36 | 37 | #endif /* MAP_H */ 38 | -------------------------------------------------------------------------------- /sys.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sys.c - system calls 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #define _GNU_SOURCE /* for F_SETSIG */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "log.h" 34 | 35 | #define sys_errno(msg, ...) \ 36 | trace(msg ": %s", ##__VA_ARGS__, strerror(errno)) 37 | 38 | int sys_chdir(const char *path) 39 | { 40 | int rc; 41 | 42 | rc = chdir(path); 43 | if (rc == -1) { 44 | sys_errno("chdir(%s)", path); 45 | rc = -errno; 46 | return rc; 47 | } 48 | 49 | return 0; 50 | } 51 | 52 | int sys_gettime(unsigned long *interval) 53 | { 54 | struct timespec ts; 55 | int rc; 56 | 57 | rc = clock_gettime(CLOCK_MONOTONIC, &ts); 58 | if (rc == -1) { 59 | sys_errno("clock_gettime(CLOCK_MONOTONIC)"); 60 | rc = -errno; 61 | return rc; 62 | } 63 | 64 | *interval = ts.tv_sec; 65 | 66 | return 0; 67 | } 68 | 69 | int sys_setitimer(unsigned long interval) 70 | { 71 | struct itimerval itv = { 72 | .it_value.tv_sec = interval, 73 | .it_interval.tv_sec = interval, 74 | }; 75 | int rc; 76 | 77 | rc = setitimer(ITIMER_REAL, &itv, NULL); 78 | if (rc == -1) { 79 | sys_errno("setitimer(ITIMER_REAL, %ld)", interval); 80 | rc = -errno; 81 | return rc; 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | int sys_waitid(pid_t *pid) 88 | { 89 | siginfo_t infop; 90 | int rc; 91 | 92 | /* Non-blocking check for dead child(ren) */ 93 | rc = waitid(P_ALL, 0, &infop, WEXITED | WNOHANG | WNOWAIT); 94 | if (rc == -1) { 95 | sys_errno("waitid()"); 96 | rc = -errno; 97 | return rc; 98 | } 99 | 100 | if (infop.si_pid == 0) 101 | return -ECHILD; 102 | 103 | *pid = infop.si_pid; 104 | 105 | return 0; 106 | } 107 | 108 | int sys_waitpid(pid_t pid, int *code) 109 | { 110 | int status; 111 | int rc; 112 | 113 | rc = waitpid(pid, &status, 0); 114 | if (rc == -1) { 115 | sys_errno("waitpid(%d)", pid); 116 | rc = -errno; 117 | return rc; 118 | } 119 | 120 | if (rc == 0) 121 | return -ECHILD; 122 | 123 | if (code) 124 | *code = WEXITSTATUS(status); 125 | 126 | return 0; 127 | } 128 | 129 | int sys_waitanychild(void) 130 | { 131 | int err; 132 | 133 | for (;;) { 134 | err = sys_waitpid(-1, NULL); 135 | if (err) { 136 | if (err == -ECHILD) 137 | break; 138 | return err; 139 | } 140 | } 141 | 142 | return 0; 143 | } 144 | 145 | int sys_setenv(const char *name, const char *value) 146 | { 147 | int rc; 148 | 149 | rc = setenv(name, value, 1); 150 | if (rc == -1) { 151 | sys_errno("setenv(%s=%s)", name, value); 152 | rc = -errno; 153 | return rc; 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | const char *sys_getenv(const char *name) 160 | { 161 | return getenv(name); 162 | } 163 | 164 | int sys_sigemptyset(sigset_t *set) 165 | { 166 | int rc; 167 | 168 | rc = sigemptyset(set); 169 | if (rc == -1) { 170 | sys_errno("sigemptyset()"); 171 | rc = -errno; 172 | return rc; 173 | } 174 | 175 | return 0; 176 | } 177 | 178 | int sys_sigfillset(sigset_t *set) 179 | { 180 | int rc; 181 | 182 | rc = sigfillset(set); 183 | if (rc == -1) { 184 | sys_errno("sigfillset()"); 185 | rc = -errno; 186 | return rc; 187 | } 188 | 189 | return 0; 190 | } 191 | 192 | int sys_sigaddset(sigset_t *set, int sig) 193 | { 194 | int rc; 195 | 196 | rc = sigaddset(set, sig); 197 | if (rc == -1) { 198 | sys_errno("sigaddset(%d (%s))", sig, strsignal(sig)); 199 | rc = -errno; 200 | return rc; 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | static int sys_sigprocmask(const sigset_t *set, int how) 207 | { 208 | int rc; 209 | 210 | rc = sigprocmask(how, set, NULL); 211 | if (rc == -1) { 212 | sys_errno("sigprocmask()"); 213 | rc = -errno; 214 | return rc; 215 | } 216 | 217 | return 0; 218 | } 219 | 220 | int sys_sigunblock(const sigset_t *set) 221 | { 222 | return sys_sigprocmask(set, SIG_UNBLOCK); 223 | } 224 | 225 | int sys_sigsetmask(const sigset_t *set) 226 | { 227 | return sys_sigprocmask(set, SIG_SETMASK); 228 | } 229 | 230 | int sys_sigwaitinfo(sigset_t *set, int *sig, int *fd) 231 | { 232 | siginfo_t siginfo; 233 | int rc; 234 | 235 | rc = sigwaitinfo(set, &siginfo); 236 | if (rc == -1) { 237 | sys_errno("sigwaitinfo()"); 238 | rc = -errno; 239 | return rc; 240 | } 241 | 242 | *sig = rc; 243 | *fd = siginfo.si_fd; 244 | 245 | return 0; 246 | } 247 | 248 | int sys_open(const char *path, int *fd) 249 | { 250 | int rc; 251 | 252 | rc = open(path, O_RDONLY | O_NONBLOCK); 253 | if (rc == -1) { 254 | sys_errno("open(%s)", path); 255 | rc = -errno; 256 | return rc; 257 | } 258 | 259 | *fd = rc; 260 | 261 | return 0; 262 | } 263 | 264 | int sys_close(int fd) 265 | { 266 | int rc; 267 | 268 | rc = close(fd); 269 | if (rc == -1) { 270 | sys_errno("close(%d)", fd); 271 | rc = -errno; 272 | return rc; 273 | } 274 | 275 | return 0; 276 | } 277 | 278 | /* Read up to size bytes and store the positive count on success */ 279 | int sys_read(int fd, void *buf, size_t size, size_t *count) 280 | { 281 | ssize_t rc; 282 | 283 | rc = read(fd, buf, size); 284 | if (rc == -1) { 285 | sys_errno("read(%d, %ld)", fd, size); 286 | rc = -errno; 287 | if (rc == -EWOULDBLOCK) 288 | rc = -EAGAIN; 289 | return rc; 290 | } 291 | 292 | /* End of file or pipe */ 293 | if (rc == 0) 294 | return -EAGAIN; 295 | 296 | if (count) 297 | *count = rc; 298 | 299 | return 0; 300 | } 301 | 302 | int sys_dup(int fd1, int fd2) 303 | { 304 | int rc; 305 | 306 | /* Defensive check */ 307 | if (fd1 == fd2) 308 | return 0; 309 | 310 | /* Close fd2, and reopen bound to fd1 */ 311 | rc = dup2(fd1, fd2); 312 | if (rc == -1) { 313 | sys_errno("dup2(%d, %d)", fd1, fd2); 314 | rc = -errno; 315 | return rc; 316 | } 317 | 318 | return 0; 319 | } 320 | 321 | static int sys_setsig(int fd, int sig) 322 | { 323 | int rc; 324 | 325 | rc = fcntl(fd, F_SETSIG, sig); 326 | if (rc == -1) { 327 | sys_errno("fcntl(%d, F_SETSIG, %d (%s))", fd, sig, 328 | strsignal(sig)); 329 | rc = -errno; 330 | return rc; 331 | } 332 | 333 | return 0; 334 | } 335 | 336 | static int sys_setown(int fd, pid_t pid) 337 | { 338 | int rc; 339 | 340 | rc = fcntl(fd, F_SETOWN, pid); 341 | if (rc == -1) { 342 | sys_errno("fcntl(%d, F_SETOWN, %d)", fd, pid); 343 | rc = -errno; 344 | return rc; 345 | } 346 | 347 | return 0; 348 | } 349 | 350 | static int sys_getfd(int fd, int *flags) 351 | { 352 | int rc; 353 | 354 | rc = fcntl(fd, F_GETFD); 355 | if (rc == -1) { 356 | sys_errno("fcntl(%d, F_GETFD)", fd); 357 | rc = -errno; 358 | return rc; 359 | } 360 | 361 | *flags = rc; 362 | 363 | return 0; 364 | } 365 | 366 | static int sys_setfd(int fd, int flags) 367 | { 368 | int rc; 369 | 370 | rc = fcntl(fd, F_SETFD, flags); 371 | if (rc == -1) { 372 | sys_errno("fcntl(%d, F_SETFD, %d)", fd, flags); 373 | rc = -errno; 374 | return rc; 375 | } 376 | 377 | return 0; 378 | } 379 | 380 | static int sys_getfl(int fd, int *flags) 381 | { 382 | int rc; 383 | 384 | rc = fcntl(fd, F_GETFL); 385 | if (rc == -1) { 386 | sys_errno("fcntl(%d, F_GETFL)", fd); 387 | rc = -errno; 388 | return rc; 389 | } 390 | 391 | *flags = rc; 392 | 393 | return 0; 394 | } 395 | 396 | static int sys_setfl(int fd, int flags) 397 | { 398 | int rc; 399 | 400 | rc = fcntl(fd, F_SETFL, flags); 401 | if (rc == -1) { 402 | sys_errno("fcntl(%d, F_SETFL, %d)", fd, flags); 403 | rc = -errno; 404 | return rc; 405 | } 406 | 407 | return 0; 408 | } 409 | 410 | int sys_cloexec(int fd) 411 | { 412 | int flags; 413 | int err; 414 | 415 | err = sys_getfd(fd, &flags); 416 | if (err) 417 | return err; 418 | 419 | return sys_setfd(fd, flags | FD_CLOEXEC); 420 | } 421 | 422 | /* Enable signal-driven I/O, formerly known as asynchronous I/O */ 423 | int sys_async(int fd, int sig) 424 | { 425 | pid_t pid; 426 | int flags; 427 | int err; 428 | 429 | err = sys_getfl(fd, &flags); 430 | if (err) 431 | return err; 432 | 433 | if (sig) { 434 | pid = getpid(); 435 | flags |= (O_ASYNC | O_NONBLOCK); 436 | } else { 437 | pid = 0; 438 | flags &= ~(O_ASYNC | O_NONBLOCK); 439 | } 440 | 441 | /* Establish a handler for the signal */ 442 | err = sys_setsig(fd, sig); 443 | if (err) 444 | return err; 445 | 446 | /* Set calling process as owner, that is to receive the signal */ 447 | err = sys_setown(fd, pid); 448 | if (err) 449 | return err; 450 | 451 | /* Enable/disable nonblocking I/O and signal-driven I/O */ 452 | return sys_setfl(fd, flags); 453 | } 454 | 455 | int sys_pipe(int *fds) 456 | { 457 | int rc; 458 | 459 | rc = pipe(fds); 460 | if (rc == -1) { 461 | sys_errno("pipe()"); 462 | rc = -errno; 463 | return rc; 464 | } 465 | 466 | return 0; 467 | } 468 | 469 | int sys_fork(pid_t *pid) 470 | { 471 | int rc; 472 | 473 | rc = fork(); 474 | if (rc == -1) { 475 | sys_errno("fork()"); 476 | rc = -errno; 477 | return rc; 478 | } 479 | 480 | *pid = rc; 481 | 482 | return 0; 483 | } 484 | 485 | void sys_exit(int status) 486 | { 487 | _exit(status); 488 | } 489 | 490 | int sys_execsh(const char *command) 491 | { 492 | int rc; 493 | 494 | static const char * const shell = "/bin/sh"; 495 | 496 | rc = execl(shell, shell, "-c", command, (char *) NULL); 497 | if (rc == -1) { 498 | sys_errno("execl(%s -c \"%s\")", shell, command); 499 | rc = -errno; 500 | return rc; 501 | } 502 | 503 | /* Unreachable */ 504 | return 0; 505 | } 506 | 507 | int sys_isatty(int fd) 508 | { 509 | int rc; 510 | 511 | rc = isatty(fd); 512 | if (rc == 0) { 513 | sys_errno("isatty(%d)", fd); 514 | rc = -errno; 515 | if (rc == -EINVAL) 516 | rc = -ENOTTY; 517 | return rc; 518 | } 519 | 520 | return 0; 521 | } 522 | -------------------------------------------------------------------------------- /sys.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sys.h - system calls 3 | * Copyright (C) 2017-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef SYS_H 20 | #define SYS_H 21 | 22 | #include /* for dirname(3) */ 23 | #include 24 | #include 25 | 26 | int sys_chdir(const char *path); 27 | 28 | int sys_gettime(unsigned long *interval); 29 | int sys_setitimer(unsigned long interval); 30 | 31 | int sys_waitid(pid_t *pid); 32 | int sys_waitpid(pid_t pid, int *code); 33 | int sys_waitanychild(void); 34 | 35 | int sys_setenv(const char *name, const char *value); 36 | const char *sys_getenv(const char *name); 37 | 38 | int sys_sigemptyset(sigset_t *set); 39 | int sys_sigfillset(sigset_t *set); 40 | int sys_sigaddset(sigset_t *set, int sig); 41 | int sys_sigunblock(const sigset_t *set); 42 | int sys_sigsetmask(const sigset_t *set); 43 | int sys_sigwaitinfo(sigset_t *set, int *sig, int *fd); 44 | 45 | int sys_open(const char *path, int *fd); 46 | int sys_close(int fd); 47 | int sys_read(int fd, void *buf, size_t size, size_t *count); 48 | int sys_dup(int fd1, int fd2); 49 | int sys_cloexec(int fd); 50 | int sys_async(int fd, int sig); 51 | 52 | int sys_pipe(int *fds); 53 | int sys_fork(pid_t *pid); 54 | void sys_exit(int status); 55 | int sys_execsh(const char *command); 56 | 57 | int sys_isatty(int fd); 58 | 59 | #endif /* SYS_H */ 60 | -------------------------------------------------------------------------------- /term.h: -------------------------------------------------------------------------------- 1 | /* 2 | * term.h - terminal output handling functions 3 | * Copyright (C) 2014-2019 Vivien Didelot 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TERM_H 20 | #define TERM_H 21 | 22 | #include 23 | 24 | static inline void term_save_cursor(void) 25 | { 26 | fprintf(stdout, "\033[s\033[?25l"); 27 | } 28 | 29 | static inline void term_restore_cursor(void) 30 | { 31 | fprintf(stdout, "\033[u\033[K"); 32 | } 33 | 34 | static inline void term_reset_cursor(void) 35 | { 36 | fprintf(stdout, "\033[?25h"); 37 | } 38 | 39 | #endif /* TERM_H */ 40 | --------------------------------------------------------------------------------