├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── awesomebot.yml │ ├── codeql-analysis.yml │ └── megalinter.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Changelog.md ├── LICENSE ├── Makefile ├── README.md ├── lima-plugin ├── poetry.lock ├── pyproject.toml └── screenshots ├── 101713774523_.pic.jpg ├── 111713774589_.pic.jpg ├── 121713774642_.pic.jpg └── 91713774470_.pic.jpg /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Describe the bug](#describe-the-bug) 6 | - [Expected behavior](#expected-behavior) 7 | - [Desktop (please complete the following information):](#desktop-please-complete-the-following-information) 8 | - [Additional context](#additional-context) 9 | 10 | 11 | 12 | --- 13 | name: Bug report 14 | about: Create a report to help us improve 15 | title: "[BUG]" 16 | labels: bug 17 | assignees: '' 18 | 19 | --- 20 | 21 | ## Describe the bug 22 | A clear and concise description of what the bug is. 23 | 24 | **To Reproduce** 25 | Steps to reproduce the behavior: 26 | 1. Go to '...' 27 | 2. Click on '....' 28 | 3. Scroll down to '....' 29 | 4. See error 30 | 31 | ## Expected behavior 32 | A clear and concise description of what you expected to happen. 33 | 34 | **Screenshots** 35 | If applicable, add screenshots to help explain your problem. 36 | 37 | ## Desktop (please complete the following information): 38 | - OS: [e.g. iOS] 39 | - Version [e.g. 22] 40 | 41 | ## Additional context 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Is your feature request related to a problem? Please describe.](#is-your-feature-request-related-to-a-problem-please-describe) 6 | - [Describe the solution you'd like](#describe-the-solution-youd-like) 7 | - [Describe alternatives you've considered](#describe-alternatives-youve-considered) 8 | - [Additional context](#additional-context) 9 | 10 | 11 | 12 | --- 13 | name: Feature request 14 | about: Suggest an idea for this project 15 | title: "[FEATURE]" 16 | labels: enhancement 17 | assignees: '' 18 | 19 | --- 20 | 21 | ## Is your feature request related to a problem? Please describe. 22 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 23 | 24 | ## Describe the solution you'd like 25 | A clear and concise description of what you want to happen. 26 | 27 | ## Describe alternatives you've considered 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | ## Additional context 31 | Add any other context or screenshots about the feature request here. 32 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Description](#description) 6 | - [Type of changes](#type-of-changes) 7 | - [Checklist](#checklist) 8 | - [License Acceptance](#license-acceptance) 9 | 10 | 11 | 12 | 13 | 14 | # Description 15 | 16 | 17 | 18 | # Type of changes 19 | 20 | # Checklist 21 | 22 | 23 | 24 | 25 | - [ ] All new and existing tests pass. 26 | - [ ] Any added/updated scripts added use `#!/usr/bin/env interpreter` instead of potentially platform-specific direct paths (`#!/bin/sh` and `#!/bin/bash` are allowed exceptions) 27 | - [ ] Added/updated scripts are marked executable 28 | - [ ] Scripts _do not_ have a language file extension unless they are meant to be sourced and not run standalone. No end-user should have to know if a script was written in `bash`, `python`, `ruby` or whatever. Not including file extensions makes it easier to rewrite the script in another language later without having to change every reference to the previous version. 29 | - [ ] I have confirmed that the link(s) in my PR are valid. 30 | 31 | # License Acceptance 32 | 33 | - [ ] This repository is Apache version 2.0 licensed (some scripts may have alternate licensing inline in their code) and by making this PR, I am contributing my changes to the repository under the terms of the Apache 2 license. 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Use `allow` to specify which dependencies to maintain 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/awesomebot.yml: -------------------------------------------------------------------------------- 1 | name: Check links in README.md 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ '*' ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: docker://dkhamsing/awesome_bot:latest 17 | with: 18 | args: /github/workspace/README.md --allow-dupe --allow-redirect --request-delay 1 --white-list https://github,https://img.shields.io 19 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '40 15 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v3 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3 72 | -------------------------------------------------------------------------------- /.github/workflows/megalinter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ########################### 3 | ########################### 4 | ## Linter GitHub Actions ## 5 | ########################### 6 | ########################### 7 | name: Lint Code Base 8 | 9 | # 10 | # Documentation: 11 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 12 | # 13 | 14 | ############################# 15 | # Start the job on all push # 16 | ############################# 17 | on: 18 | push: 19 | branches-ignore: [main] 20 | # Remove the line above to run when pushing to main 21 | pull_request: 22 | branches: [main] 23 | 24 | ############### 25 | # Set the Job # 26 | ############### 27 | jobs: 28 | build: 29 | # Name the Job 30 | name: Megalint Code Base 31 | # Set the agent to run on 32 | runs-on: ubuntu-latest 33 | 34 | ################## 35 | # Load all steps # 36 | ################## 37 | steps: 38 | ########################## 39 | # Checkout the code base # 40 | ########################## 41 | - name: Checkout Code 42 | uses: actions/checkout@v4 43 | with: 44 | # Full git history is needed to get a proper list of changed files within `super-linter` 45 | fetch-depth: 0 46 | 47 | ################################ 48 | # Run Linter against code base # 49 | ################################ 50 | - name: Lint Code Base 51 | uses: nvuillam/mega-linter@v8 52 | env: 53 | VALIDATE_ALL_CODEBASE: false 54 | DEFAULT_BRANCH: main 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | DISABLE_LINTERS: SPELL_CSPELL 57 | # SPELL_MISSPELL_FILTER_REGEX_EXCLUDE: '(\.yml)' 58 | ACTION_ACTIONLINT_DISABLE_ERRORS: true 59 | REPOSITORY_CHECKOV_DISABLE_ERRORS: true 60 | REPOSITORY_KICS_DISABLE_ERRORS: true 61 | REPOSITORY_TRIVY_DISABLE_ERRORS: true 62 | YAML_YAMLLINT_DISABLE_ERRORS: true 63 | # Upload Mega-Linter artifacts. They will be available on Github action page "Artifacts" section 64 | - name: Archive production artifacts 65 | if: ${{ success() }} || ${{ failure() }} 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: Mega-Linter reports 69 | path: | 70 | report 71 | mega-linter.log 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ETC 2 | requirements.txt 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See https://pre-commit.com for more information 3 | # See https://pre-commit.com/hooks.html for more hooks 4 | ci: 5 | skip: 6 | - poetry-lock 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v5.0.0 10 | hooks: 11 | - id: check-added-large-files 12 | - id: check-executables-have-shebangs 13 | - id: check-merge-conflict 14 | - id: check-shebang-scripts-are-executable 15 | - id: check-symlinks 16 | - id: debug-statements 17 | - id: end-of-file-fixer 18 | - id: trailing-whitespace 19 | - repo: https://github.com/astral-sh/ruff-pre-commit 20 | # Ruff version. 21 | rev: v0.11.12 22 | hooks: 23 | # Run the linter. 24 | - id: ruff 25 | args: 26 | - "--fix" 27 | # Run the formatter. 28 | - id: ruff-format 29 | - repo: https://github.com/thlorenz/doctoc 30 | rev: v2.2.0 31 | hooks: 32 | - id: doctoc 33 | - repo: https://github.com/python-poetry/poetry 34 | rev: 2.1.3 35 | hooks: 36 | - id: poetry-check 37 | - id: poetry-lock 38 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Lima Xbar/Swiftbar changelog](#lima-xbarswiftbar-changelog) 6 | - [0.0.1 - 1.0.0](#001---100) 7 | - [1.1.1](#111) 8 | - [1.2.0](#120) 9 | - [1.3.0](#130) 10 | - [1.3.1](#131) 11 | - [1.3.2](#132) 12 | - [1.3.3](#133) 13 | 14 | 15 | 16 | # Lima Xbar/Swiftbar changelog 17 | 18 | ## 0.0.1 - 1.0.0 19 | 20 | - `bash` proof of concept 21 | 22 | ## 1.1.1 23 | 24 | - Rewrite in Python for speed and maintainability. 25 | - Now have submenus for container and image operations instead of just start/stop the VM 26 | - We send notifications to the Notification Manager 27 | 28 | ## 1.2.0 29 | 30 | - Add option to pull new image into one of your Lima VMs. 31 | 32 | ## 1.3.0 33 | 34 | - Add option to run an arbitrary `lima` command in a VM. 35 | 36 | ## 1.3.1 37 | 38 | - Add `/opt/homebrew/bin` to the plugin's `$PATH` when it exists and is a directory. 39 | 40 | ## 1.3.2 41 | 42 | - Swiftbar started capturing `stderr` in addition to `stdout`, causing log messages to spam the menu bar. Default to log level critical so we don't spam log messages (that no one was seeing anyway) except when we're testing. 43 | 44 | ## 1.3.3 45 | 46 | dd more candidate directories to be added to $PATH 47 | 48 | - Check for `~/homebrew/bin` and `~/homebrew/sbin` to cope when homebrew 49 | is installed in a user's home directory. Closes https://github.com/unixorn/lima-xbar-plugin/issues/28 50 | - While we're in there, check for `/usr/local/sbin`, 51 | `/opt/homebrew/sbin`, `/opt/local/sbin`, `~/bin` and `~/sbin` 52 | directories too. 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2021] Joe Block 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | .DEFAULT_GOAL := help 4 | 5 | GREEN := $(shell tput -Txterm setaf 2) 6 | YELLOW := $(shell tput -Txterm setaf 3) 7 | WHITE := $(shell tput -Txterm setaf 7) 8 | CYAN := $(shell tput -Txterm setaf 6) 9 | RESET := $(shell tput -Txterm sgr0) 10 | 11 | .PHONY: all 12 | all: i install l lint r requirements f format t test ## run all targets 13 | 14 | i: format install 15 | install: format ## install the plugin 16 | @if logname >/dev/null 2>&1; then \ 17 | logged_in_user=$$(logname); \ 18 | else \ 19 | logged_in_user=$$(whoami); \ 20 | fi; \ 21 | @if [ -d "$${logged_in_user}/Library/Application Support/xbar/plugins" ]; then \ 22 | mkdir -p "$${logged_in_user}/Library/Application Support/xbar/plugins"; \ 23 | fi; \ 24 | cp lima-plugin "$${logged_in_user}/Library/Application Support/xbar/plugins/lima-plugin.10s" 25 | 26 | # TODO: replace shellcheck with ruff or another linter (shellcheck doesn't work with python scripts) 27 | # * cf. commit: e6b3896 28 | l: lint 29 | lint: ## lint the plugin 30 | shellcheck lima-plugin 31 | 32 | r: requirements 33 | requirements: ## export the requirements.txt file 34 | poetry export -f requirements.txt --output requirements.txt 35 | 36 | f: format 37 | format: ## format the plugin 38 | poetry run black lima-plugin 39 | 40 | t: test 41 | test: format ## test the plugin 42 | poetry run ./lima-plugin 43 | 44 | help: ## show this help 45 | @echo '' 46 | @echo 'Usage:' 47 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' 48 | @echo '' 49 | @echo 'Targets:' 50 | @awk 'BEGIN {FS = ":.*?## "} { \ 51 | if (/^[a-zA-Z_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \ 52 | else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \ 53 | }' $(MAKEFILE_LIST) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lima-xbar-plugin 2 | 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | ![Awesomebot](https://github.com/unixorn/lima-xbar-plugin/actions/workflows/awesomebot.yml/badge.svg) 5 | ![Megalinter](https://github.com/unixorn/lima-xbar-plugin/actions/workflows/megalinter.yml/badge.svg) 6 | ![codeql](https://github.com/unixorn/lima-xbar-plugin/actions/workflows/codeql-analysis.yml/badge.svg) 7 | [![GitHub stars](https://img.shields.io/github/stars/unixorn/lima-xbar-plugin.svg)](https://github.com/unixorn/lima-xbar-plugin/stargazers) 8 | 9 | 10 | 11 | 12 | ## Table of Contents 13 | 14 | - [Description](#description) 15 | - [Screen shots](#screen-shots) 16 | - [Theme](#theme) 17 | - [Installation](#installation) 18 | - [Dependencies](#dependencies) 19 | 20 | 21 | 22 | 23 | [Lima](https://github.com/lima-vm/lima) is an alternative to using Docker Desktop on your Mac. 24 | 25 | ## Description 26 | 27 | This plugin is compatible with [xbar](https://xbarapp.com/) and [SwiftBar](https://github.com/swiftbar/SwiftBar), and provides a menubar app that creates a Lima menubar option with submenus for each Lima VM on your machine. For each VM, you can: 28 | 29 | - start/stop the VM 30 | - stop, start or remove stopped containers 31 | - pull or remove images from the VM 32 | - Run an arbitrary command inside the VM with `lima` 33 | 34 | ### Screen shots 35 | 36 | ![Screen shot of xbar menu with container submenu for a running vm](https://raw.githubusercontent.com/unixorn/unixorn.github.io/master/images/lima-xbar/containers-submenu.png) 37 | 38 | ![Screen shot of xbar menu with image submenu for a running vm](https://raw.githubusercontent.com/unixorn/unixorn.github.io/master/images/lima-xbar/images-submenu.png) 39 | 40 | ### Theme 41 | 42 | Rename the plugin file to lima-plugin.[theme].[time_ext] to switch to the specified theme 43 | 44 | e.g. 45 | - lima-plugin.10s -> default 46 | - lima-plugin.default.10s -> default 47 | - lima-plugin.text.10s -> text 48 | - lima-plugin.sf_simple.10s -> sf_simple 49 | - lima-plugin.sf_simple.10s.py -> sf_simple 50 | 51 | Theme default: 52 | ![Theme default](https://raw.githubusercontent.com/exculibar/lima-xbar-plugin/custom_icons/screenshots/91713774470_.pic.jpg) 53 | 54 | Theme text: 55 | ![Theme text](https://raw.githubusercontent.com/exculibar/lima-xbar-plugin/custom_icons/screenshots/101713774523_.pic.jpg) 56 | 57 | Theme sf_simple: 58 | ![Theme sf_simple 1](https://raw.githubusercontent.com/exculibar/lima-xbar-plugin/custom_icons/screenshots/111713774589_.pic.jpg) 59 | ![Theme sf_simple 2](https://raw.githubusercontent.com/exculibar/lima-xbar-plugin/custom_icons/screenshots/121713774642_.pic.jpg) 60 | 61 | ## Installation 62 | 63 | Copy `lima-plugin` to `~/Library/Application\ Support/xbar/plugins/lima-plugin.30s`, or run `make install` 64 | ### Dependencies 65 | 66 | - [xbar](https://xbarapp.com/) or [SwiftBar](https://github.com/swiftbar/SwiftBar) - Both allow you to make custom menubar apps with simple scripts. 67 | -------------------------------------------------------------------------------- /lima-plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Lima Swiftbar/Xbar plugin 4 | # 5 | # Copyright 2021-2024, Joe Block 6 | # 7 | # Lima Control 8 | # v1.3.2 9 | # Joe Block 10 | # unixorn 11 | # Control Lima VM 12 | # jq,lima 13 | # https://raw.githubusercontent.com/unixorn/lima-xbar-plugin/main/pix/limactl-screen-shot.png 14 | # https://github.com/unixorn/lima-xbar-plugin/ 15 | # false 16 | # 17 | # Dependencies: 18 | # lima - https://github.com/lima-vm/lima 19 | # jq - https://stedolan.github.io/jq/ 20 | 21 | import argparse 22 | import json 23 | import logging 24 | import logging.handlers 25 | import os 26 | import subprocess 27 | import sys 28 | 29 | # import syslog 30 | 31 | # Running VM color (default green) 32 | RUNNING_VM_COLOR = "#29cc00" 33 | 34 | # Stopped VM color (default red) 35 | STOPPED_VM_COLOR = "#ff0033" 36 | 37 | VERSION = "1.3.3a" 38 | 39 | # Is plugin run in Swiftbar or not 40 | IN_SWIFTBAR = "SWIFTBAR" in os.environ 41 | 42 | """ 43 | Theme configuration dict 44 | - default 45 | - sf_simple 46 | - text 47 | 48 | Rename the plugin file to lima-plugin.[theme].[time_ext] to switch to the specified theme 49 | e.g. 50 | lima-plugin.10s -> default 51 | lima-plugin.default.10s -> default 52 | lima-plugin.text.10s -> text 53 | lima-plugin.sf_simple.10s -> sf_simple 54 | lima-plugin.sf_simple.10s.py -> sf_simple 55 | """ 56 | MENU_TITLE_ICON_CONF = { 57 | "default": { 58 | "swiftbar": { 59 | "main": { 60 | "stopped": (f"🐋❗ | color={STOPPED_VM_COLOR}", ""), 61 | "running": (f"🐋 🏃 | color={RUNNING_VM_COLOR}", ""), 62 | }, 63 | "vm": { 64 | "start": (lambda vm: f"▶️ Start {vm} VM", ""), 65 | "stop": (lambda vm: f"⭕️ Stop {vm} VM", ""), 66 | "edit": (lambda vm: f"⚙️ Edit {vm} VM", ""), 67 | "delete": (lambda vm: f"❌ Delete {vm} VM", ""), 68 | "run": ("🐚 Run lima command", ""), 69 | }, 70 | "image": { 71 | "pull_new": ("pull new image", ""), 72 | "image": (lambda img: img, ""), 73 | "pull": ("pull", ""), 74 | "rm": ("rm", ""), 75 | }, 76 | "container": { 77 | "container": (lambda container: container, ""), 78 | "Running": ("Running", ""), 79 | "stop": ("stop", ""), 80 | "kill": ("kill", ""), 81 | "pause": ("pause", ""), 82 | "Stopped": ("Stopped", ""), 83 | "rm": ("rm", ""), 84 | "start": ("start", ""), 85 | "unpause": ("unpause", ""), 86 | }, 87 | }, 88 | "xbar-light": { 89 | "main": { 90 | "stopped": (f"🐋❗ | color={STOPPED_VM_COLOR}", ""), 91 | "running": (f"🐋 🏃 | color={RUNNING_VM_COLOR}", ""), 92 | }, 93 | "vm": { 94 | "start": (lambda vm: f"▶️ Start {vm} VM", ""), 95 | "stop": (lambda vm: f"⭕️ Stop {vm} VM", ""), 96 | "edit": (lambda vm: f"⚙️ Edit {vm} VM", ""), 97 | "delete": (lambda vm: f"❌ Delete {vm} VM", ""), 98 | "run": ("🐚 Run lima command", ""), 99 | }, 100 | "image": { 101 | "pull_new": ("pull new image", ""), 102 | "image": (lambda img: img, ""), 103 | "pull": ("pull", ""), 104 | "rm": ("rm", ""), 105 | }, 106 | "container": { 107 | "container": (lambda container: container, ""), 108 | "Running": ("Running", ""), 109 | "stop": ("stop", ""), 110 | "kill": ("kill", ""), 111 | "pause": ("pause", ""), 112 | "Stopped": ("Stopped", ""), 113 | "rm": ("rm", ""), 114 | "start": ("start", ""), 115 | "unpause": ("unpause", ""), 116 | }, 117 | }, 118 | "xbar-dark": { 119 | "main": { 120 | "stopped": (f"🐋❗ | color={STOPPED_VM_COLOR}", ""), 121 | "running": (f"🐋 🏃 | color={RUNNING_VM_COLOR}", ""), 122 | }, 123 | "vm": { 124 | "start": (lambda vm: f"▶️ Start {vm} VM", ""), 125 | "stop": (lambda vm: f"⭕️ Stop {vm} VM", ""), 126 | "edit": (lambda vm: f"🔨 Edit {vm} VM", ""), 127 | "delete": (lambda vm: f"❌ Delete {vm} VM", ""), 128 | "run": ("🐚 Run lima command", ""), 129 | }, 130 | "image": { 131 | "pull_new": ("pull new image", ""), 132 | "image": (lambda img: img, ""), 133 | "pull": ("pull", ""), 134 | "rm": ("rm", ""), 135 | }, 136 | "container": { 137 | "container": (lambda container: container, ""), 138 | "Running": ("Running", ""), 139 | "stop": ("stop", ""), 140 | "kill": ("kill", ""), 141 | "pause": ("pause", ""), 142 | "Stopped": ("Stopped", ""), 143 | "rm": ("rm", ""), 144 | "start": ("start", ""), 145 | "unpause": ("unpause", ""), 146 | }, 147 | }, 148 | }, 149 | "sf_simple": { 150 | "swiftbar": { 151 | "main": { 152 | "stopped": ( 153 | "", 154 | "sfimage=externaldrive.badge.exclamationmark sfconfig=eyJyZW5kZXJpbmdNb2RlIjoiUGFsZXR0ZSIsImNvbG9ycyI6WyJyZWQiXSwic2NhbGUiOiJsYXJnZSIsIndlaWdodCI6ImJvbGQifQ==", 155 | ), 156 | "running": ("", "sfimage=externaldrive.fill.badge.checkmark"), 157 | }, 158 | "vm": { 159 | "start": (lambda vm: f"Start {vm} VM", "sfimage=play.circle.fill"), 160 | "stop": (lambda vm: f"Stop {vm} VM", "sfimage=stop.circle.fill"), 161 | "edit": (lambda vm: f"Edit {vm} VM", "sfimage=gear"), 162 | "delete": (lambda vm: f"Delete {vm} VM", "sfimage=trash.circle.fill"), 163 | "run": ("Run lima command", "sfimage=command.circle.fill"), 164 | }, 165 | "image": { 166 | "pull_new": ("pull new image", ""), 167 | "image": (lambda img: img, "sfimage=hockey.puck.fill"), 168 | "pull": ("pull", ""), 169 | "rm": ("rm", ""), 170 | }, 171 | "container": { 172 | "container": (lambda container: container, "sfimage=shippingbox.fill"), 173 | "Running": ("Running", ""), 174 | "stop": ("stop", ""), 175 | "kill": ("kill", ""), 176 | "pause": ("pause", ""), 177 | "Stopped": ("Stopped", ""), 178 | "rm": ("rm", ""), 179 | "start": ("start", ""), 180 | "unpause": ("unpause", ""), 181 | }, 182 | }, 183 | "xbar-light": { 184 | "main": { 185 | "stopped": ( 186 | "", 187 | "image=iVBORw0KGgoAAAANSUhEUgAAADoAAAAoCAYAAACvpem4AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAeGVYSWZNTQAqAAAACAAEARIAAwAAAAEAAQAAARoABQAAAAEAAAA+ARsABQAAAAEAAABGh2kABAAAAAEAAABOAAAAAAAAAJAAAAABAAAAkAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAOqADAAQAAAABAAAAKAAAAAC3F003AAAACXBIWXMAABYlAAAWJQFJUiTwAAAClmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj4xNDQ8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjE0NDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjI2MzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTgwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CkPhMDcAAAnzSURBVGgF7Vl7bF11Hf9+zz33tmtX3JiVidMtc3OEsfaWMkp7u3kNf5jF+GK5PkKcZE6iAWMkkWTTxMZkElwMiUaNMT7+GCFyZcZomBkKdVvbDSx9bDUI3VYNQbCAZayj93HOz8/3+zvn9nZre19tJMZfcs/vnN/j+379fpfo/+1/SwK81OwYIodSqdrgptM+AADU0rXaCCqiw+zaVKefR8eztRJpUqkITU5G6c03PR4czBWh+e+9mh5yTDLpLhcFZuvW2FLArkmjInlOpz0hxHTFt+O5m5g/gM8mMqY62MwejPY1YvMMOc5hPjE0qRqu0ZyrI0YYgy9is69MJlp/TMR3UyTikBvBq4zW0HxA9yA/z38VAvsa948cVt8HyBBnpdCrIglIGRvR4ZFo/TVFo7tBFNhWvvswPE4Gmqm0MTg01AjobRRxthS2e/l93Df6s1o0Wx2j8Enu7c2brrYvUyzyI8qDJ9+8QOzvAUGnCgTW8GK64/eA6e+B4ToI8RJMuZ1Pjjwv8UBwVwq6YkZDbWoAyk09B3N9P/neNDSY5L6hv2j0XdlWMSEFwicnOWTEJOIH4KcH1Uny3kPcN3yfrAtpKOxZjpcwCprOeMrsvNmYne3GdLc+ogQgQoIIp1a8pr09qvB2tDWD2b+b5C0G/YTpanmXjleBo3KibrzR+p5D+9RTJWgYRxml5ulCgKqJ2cFBxSERF+r7PdyCYMLrwd/HFW5P5dArYlS0KenEdLa2AdXtiIiCcYyi73hcUTdvX5LkDn/yQ63CPtKIAVlikMr0ecHDPTbaK84yHxUxSu/LWJ92nL3wzSCP8KMamJLJ+jCnlol78WVNTSpFevfwCTD4tAqVOWESLTtko0bgxSHMmS2bUZEwHx3PmFtvXQM0d2hI8LzLZHIPK0SUa3Mg1/ihwlMLIoH7mDKKNA3z3aegEbQqQVE2o9TUBA2iublPIQper6nb0DHuP3tOQ/5y1qScO4Ic/YqEWwj4k3Cd96ggUmRpkuESrawaNchdM9ZvvK9QBNskQDj+zxV+JhOFKVlTK4Gwounz542kKz469g9E3T+CyTvJdZvgs3sA5wG6tAmEjJdlSSUZBfVMYASA89Tgb4YhnaNc/lp8v0zRa48K4Tww8Jb0y9A8OTSQYGH+JYR7p01eZg/oelBdCeKW4FUK94J2Lvqi5IYYIZJeGWRMS0sjrYyu5f5BNVvKTF5D68be4DR5qvUwkJTCXs78ihcjFBz9UG4eRxDcof7q+7u5b+QIrK0eZjxTCtS8GlViNw76nJ4AgAnSImElr6ZYnUNZd5pPn74IwOcs8AmXIqs+Sy/Fv2QS9Doq8Qzl3oDvLCDk0MAXErHMy5xdZyjbSJSIO8aYM6i+jHUZhBZj9mLVEWpuzmFpofa2NF39vAqd5sqxsaws1VBunM8AcTs+r8OIpBQww8+Bj9/ywJCtiDo6riE3e5DqovcqCsmvV0HWmeoelmmirPBEyKlcR8afISfSwSeeHbV+PJ5ZDPgcckImTWILzpP1PwTAz+mxS8Db4sDKTnbJScU3x1Hn3ssDZ84IEgSM/Vj3Vey7hC+smgNelqCFVM83pwswoZIKFui7WN51FHXr9ABhTBZ0xfB+CPXv/SHdunuBRwFbuNhs37oWAI9RLLqtABQFGIi3oRw2BFhSAcUQARGi8hfB9B08MPonDRyP37CaGhqZLkcsR57H8KuQu7lkFM/JuzRZG443YMyT6yPHpaxpJtf/CFZ8E7Q04CfCvkB+7hYeGHs9pF9hzPNQ4EH6yEtP2ak/U8ztQmQVU2AAjEHABv1dCDXjFOHHMLoW2hTzNpCsSFluBLrkGDUPjiUdgjttAeNP4lSDXI7meXtxMP9FKUYdiJrh0FbiuX/vD5jMgQVJKREN3hKRTg4d5oHhfozLFYegEA3DfPIzsIA1sNRDMihNghkACuzqfnI5FjRzW3yD2XnTexWunEX7Rv+G97vUuiWxMH9C5hhxZbGyECkkqfc+egQyuA6xtwQ+2Fdu1KWE6WS75E40s0LHRJsiJGJXTZzoY6az7YO6xE6IzVX8EwRhOoPPfx3ivECeew53Ul/UakhY6xt+AoyeDAS+zXS0rFO8589bmkMiinqnoE2K7CTXWUd55VS0OdsYjGZzIZArb+VcIM1AqyDB36WbBgfzkIK6xSyQst90n+ncdgNY+i52QegcBbSHtPSzAsawORtAXAP3WVsKOg5BaZvw2LSphFgDTchUuD+KQFAXfIR9OIcePmz12yqDoNRQKrCIolUVvbqupYFxPhMTleYX3Swank3UZdw4CgghUahr1l6N374VPR3y6yw2hKei8eBVaFJO3ylBwQ6mrl5W3ojSg0P3X5ErvwVyPETeDPr9fHr0RRMW8kybA0qmYIWvlgJdrLn573k0m9AqcjyrSUO4zrCymQVeEK4HV7AfOEZhVcU/galpCj1KvG/jC1bityKy/kALAykzO25qwfSH9ObRmBf46dELso82biwQot9FDyFEtOrD2e9HxH2QsnlUHoims01SC0OiL4G/aazeHChduA20axB5o/VISY8iUHx6duvSvxmpwiIzT8Evb1Z5e/7d3D/801LpxUXUdai3F5LgUxo9LZOiXalGipjhUehHLpQ3gvEI+nAOvX5jNfcJa6qR3q0NuAqVNdW1l7GtKePQipUOTc+gYKlfjaR5O3FGbgbXazzJ5Z8SJhWB3GWNjS2Iyy1E3f6hk9Qd74ekusBwDjuEUV+vTDz/SVxlakQ13W3/wkXVfZTDMdDqMwukUjRMImv+QTE9Ed+L/hDC2hT6YvfQ6bIeTbJqBdFbwONISPckuqLGhewkj+fyZ2Fdaj2qzXRa6/OFYLt62SVXFpJwPfMAtPY7i4GkMrJpxkgxHzQDs7a6FDZFIA4Ylwrl+/Cp5/Ve9yJ9g+pjqxAkVoXGHW6vuhcG5ef5efwepkb/Hj42Oi3FCf5xW5RJwak6ERlRDz564KuJ+EHkxAOoeGQeF9NUD8lFUEhAAPwKxr6An+y7jF8dauIIcuxv6PrNKRVaZ+uHwfghEDWDvfDvgFV4uX5hU1lN1ocN1S++XgOVIxDeET41+qxMhaVruGyx3hqfbCr+ZywR/w7MY7+aiDLMOBJxPXxTNAfpgXHXsVrO+7+iqewetQiplWkCvw15dYkKL7AWJBQxBJgLETU83EultOCeKyYKjMp4sYRMd8tHEVwO4D71NviGxGW7VWM0cHo+ak6DvwlGfqJ7rQmJKS9LA3amXZs0G8gVSqVI5jAqm7UwDv6L1NNMfqobTHZAo+shUxc+/E+gfIZy9ceDmwYt4sN/pk0PDKw3WV0AWoz6JLQK11psScVz4rNqHiV2iiAWOzGU2P72mRYmlBkbfJQwMaH5xt8+VM9PyX8AV1dcSS8gTOEAAAAASUVORK5CYII=", 188 | ), 189 | "running": ( 190 | "", 191 | "image=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", 192 | ), 193 | }, 194 | "vm": { 195 | "start": ( 196 | lambda vm: f"Start {vm} VM", 197 | "image=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", 198 | ), 199 | "stop": ( 200 | lambda vm: f"Stop {vm} VM", 201 | "image=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", 202 | ), 203 | "edit": ( 204 | lambda vm: f"Edit {vm} VM", 205 | "image=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", 206 | ), 207 | "run": ( 208 | "Run lima command", 209 | "image=iVBORw0KGgoAAAANSUhEUgAAACkAAAAoCAYAAABjPNNTAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAJAAAAABAAAAkAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKaADAAQAAAABAAAAKAAAAADveEx6AAAACXBIWXMAABYlAAAWJQFJUiTwAAACMmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTU5PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE2MTwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4K9ogctQAABjRJREFUWAmtl1moVlUUx6+VjRbNqTlQaRNSQWUTJmg0Wj0UEvVWQRHUQ0RUkAahvTQQBVEKBRVUSuRLFCpoSCAFWRSpaRkVhfZQ2aDNv9/5zv9r33PPd+93tQX/b6299pr22sO5d8xAfzQGs33Bn7X5/vCLwBwwE8wAE4B2od8QtoINYB1YDbaA0H4IiRfdHnODhaYgLAZfgH9GCQuy0Pkg5ML3yWBPuF1JgYciPwZ2gRT3F/LvwOR/F/rMy7X5o0ap/xjdtSCUPBn3xV1dtm4e8rcgSSzM5Bn3y12IC7Lo+CxHPhxIYzusv9+y/YtwScDdyL06Fpt+eVmsDTinLq2vQtM9fV4CJjWg6LeA0di5K7G/HFkatlALTJEp8P/sXopp8nL751ZlDlNoDm+22AKbAR3bVTuQS9NmU+pyebS3oLYjk50y5ylA8uYPohToJUkhzWAma7swJmjapsiyS9ElfjlWjq03P/ei2lmrVTCJz8xKMA7oFEPEaj43fi3jZWA98PxMAsbQpwoKl4xp/G/AK+Bt8B2YBvRzwWUOZbs9ARwEVgGbZ5zuW/gosok0lAfp3k50V4Am3Ywitulotm8Jc36dSprK4H2gT+ya/o5PA1J3IZMZ+BlzMonimCKvYq5J6dydTGhv0iR+s2lcjH0btwN9Ej/50qSXa/scxYGHa4cYxCEJ360dZEeCF8CT4EAgGWgb0C9n60Lk0K0Ib4HzooAvBKV9cqZJxjkx9p6bz2qH5qqS8OkYw5+qbQ16W6FfUev1+R6Mr+dOrvXabwXmk64B6tKIFClPsx5Arvb7fPg04Aq6+49c0sHFwAWFpkSAx8au5lPn9HH+1LQJbiOk7IJFNSl1uJBqm2bXFm1FxnguNnbABHZyC5gO1oDQIwivATvpe/cDkDaC28Eu8AYwprnmgV6UvGdgMFmj14GrydYql4jev4LayMuTC9Sc76W/EkNzWKwo80XOMbiM+YFPaiO7FIMmz9yL2JwKfMd8WnK+EKuxeuFWpkC5tgcA38B7QeKFN/M5TnN8OaptUdlrRQlQBnS7d4A7QOg5BHXbwAfgKCCdBbYBfX4GbfGiK3kuz2IP+TggZeWd0dBfz4lbYPdOqqdTiEO7dHSNI+Dpsl2dCkKJkXMXfZOnnnEjGTYdHcdZufRPUeqVYxeuXmqOO9phfu3kT8CV2+peAbIN2ntLPwWHgK9AyOfFN/EX4M32TEk7wQbgcZkMjgWSx6tcZKUsfswpWd/AR0BFeeZSVHjOq1+ZiaAtuAsswXAQOecfMbeAX8FIOXNxqnPv26ZDlCks3DOk/BBooxTWa65NPwtl4qcBGYenaZd4dqaCS0Fb+9Vp4828DkiObwTzgR35Gkg+zleDc8GZwCPhwr1QdwH1W4E+X4LjwdnAYpo7Y6HqfA0WgMpZZduK0t2lGtZ0HzyrXRwlfFWht/vj67mLC/3aWie7vtZrm3jh0b2jodW+B3zQ3TYLbSPfrJDPTGhzBLgdkkzwYyV1fuy03ZKOAemaDehFqWNFaXA/A1dhMVmNPCtyISHf1cfBAuBtl/yaWIw+6f5s5JBd8+/DGVHAF4HSXllYoNxFTwJdcoU+G6VRnHKAb+haDxWySBeVha0ZatbVTETqlS+NWlJbV41INxaitLDdNU+RWZnON4Em3YOiaZtClzHnG1ySf9lsBPrErulvrhOAVB0Pz6I0FmwBbc7ppnM+zEvB82AzaCbIOAXYseXgGbCysM987OVp0IPIUho4aDCLkcZ2Lx1MEMdtgdU1beOT85lxeFucbPN64oXSwIy7Vd+NxmA6tSW3qyYXZYdTQJMbI/a9FpTF2PVJQPI9bqVMPMGsyXRuK7RZyN6M00EX4IMvDdrmjuq/X9ubFvuXuMnTib0ppJdvzqDPzQVA8m6MSGWhfs6SwBX3s72xH46XR2UTcafXVfVVYG1bdTNbPxOlX6Qktdi2g5/5Xjw7Uvo+Syz/tZBGVWDHpfMbR7vre7gdlEX0uhAWZNctSJuyMP39Js8CoTQk41HzMsBhePuPkZ/Jsth+ZG/uq2AOCHlBcgeiG8JHNKg9tLNYOxLyOzwbeBxOB1OAXxcTexHs+ufgQ7Cuxg645FfEmHZ7RPoXNldZJPT5POgAAAAASUVORK5CYII=", 210 | ), 211 | "delete": (lambda vm: f"Delete {vm} VM", ""), 212 | }, 213 | "image": { 214 | "pull_new": ("pull new image", ""), 215 | "image": ( 216 | lambda img: img, 217 | "image=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", 218 | ), 219 | "pull": ("pull", ""), 220 | "rm": ("rm", ""), 221 | }, 222 | "container": { 223 | "container": ( 224 | lambda container: container, 225 | "image=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", 226 | ), 227 | "Running": ("Running", ""), 228 | "stop": ("stop", ""), 229 | "kill": ("kill", ""), 230 | "pause": ("pause", ""), 231 | "Stopped": ("Stopped", ""), 232 | "rm": ("rm", ""), 233 | "start": ("start", ""), 234 | "unpause": ("unpause", ""), 235 | }, 236 | }, 237 | "xbar-dark": { 238 | "main": { 239 | "stopped": ( 240 | "", 241 | "image=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", 242 | ), 243 | "running": ( 244 | "", 245 | "image=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", 246 | ), 247 | }, 248 | "vm": { 249 | "start": ( 250 | lambda vm: f"Start {vm} VM", 251 | "image=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", 252 | ), 253 | "stop": ( 254 | lambda vm: f"Stop {vm} VM", 255 | "image=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", 256 | ), 257 | "edit": ( 258 | lambda vm: f"Edit {vm} VM", 259 | "image=iVBORw0KGgoAAAANSUhEUgAAACkAAAAoCAYAAABjPNNTAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAJAAAAABAAAAkAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKaADAAQAAAABAAAAKAAAAADveEx6AAAACXBIWXMAABYlAAAWJQFJUiTwAAACMmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MTU5PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE2MTwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4K9ogctQAABjRJREFUWAmtl1moVlUUx6+VjRbNqTlQaRNSQWUTJmg0Wj0UEvVWQRHUQ0RUkAahvTQQBVEKBRVUSuRLFCpoSCAFWRSpaRkVhfZQ2aDNv9/5zv9r33PPd+93tQX/b6299pr22sO5d8xAfzQGs33Bn7X5/vCLwBwwE8wAE4B2od8QtoINYB1YDbaA0H4IiRfdHnODhaYgLAZfgH9GCQuy0Pkg5ML3yWBPuF1JgYciPwZ2gRT3F/LvwOR/F/rMy7X5o0ap/xjdtSCUPBn3xV1dtm4e8rcgSSzM5Bn3y12IC7Lo+CxHPhxIYzusv9+y/YtwScDdyL06Fpt+eVmsDTinLq2vQtM9fV4CJjWg6LeA0di5K7G/HFkatlALTJEp8P/sXopp8nL751ZlDlNoDm+22AKbAR3bVTuQS9NmU+pyebS3oLYjk50y5ylA8uYPohToJUkhzWAma7swJmjapsiyS9ElfjlWjq03P/ei2lmrVTCJz8xKMA7oFEPEaj43fi3jZWA98PxMAsbQpwoKl4xp/G/AK+Bt8B2YBvRzwWUOZbs9ARwEVgGbZ5zuW/gosok0lAfp3k50V4Am3Ywitulotm8Jc36dSprK4H2gT+ya/o5PA1J3IZMZ+BlzMonimCKvYq5J6dydTGhv0iR+s2lcjH0btwN9Ej/50qSXa/scxYGHa4cYxCEJ360dZEeCF8CT4EAgGWgb0C9n60Lk0K0Ib4HzooAvBKV9cqZJxjkx9p6bz2qH5qqS8OkYw5+qbQ16W6FfUev1+R6Mr+dOrvXabwXmk64B6tKIFClPsx5Arvb7fPg04Aq6+49c0sHFwAWFpkSAx8au5lPn9HH+1LQJbiOk7IJFNSl1uJBqm2bXFm1FxnguNnbABHZyC5gO1oDQIwivATvpe/cDkDaC28Eu8AYwprnmgV6UvGdgMFmj14GrydYql4jev4LayMuTC9Sc76W/EkNzWKwo80XOMbiM+YFPaiO7FIMmz9yL2JwKfMd8WnK+EKuxeuFWpkC5tgcA38B7QeKFN/M5TnN8OaptUdlrRQlQBnS7d4A7QOg5BHXbwAfgKCCdBbYBfX4GbfGiK3kuz2IP+TggZeWd0dBfz4lbYPdOqqdTiEO7dHSNI+Dpsl2dCkKJkXMXfZOnnnEjGTYdHcdZufRPUeqVYxeuXmqOO9phfu3kT8CV2+peAbIN2ntLPwWHgK9AyOfFN/EX4M32TEk7wQbgcZkMjgWSx6tcZKUsfswpWd/AR0BFeeZSVHjOq1+ZiaAtuAsswXAQOecfMbeAX8FIOXNxqnPv26ZDlCks3DOk/BBooxTWa65NPwtl4qcBGYenaZd4dqaCS0Fb+9Vp4828DkiObwTzgR35Gkg+zleDc8GZwCPhwr1QdwH1W4E+X4LjwdnAYpo7Y6HqfA0WgMpZZduK0t2lGtZ0HzyrXRwlfFWht/vj67mLC/3aWie7vtZrm3jh0b2jodW+B3zQ3TYLbSPfrJDPTGhzBLgdkkzwYyV1fuy03ZKOAemaDehFqWNFaXA/A1dhMVmNPCtyISHf1cfBAuBtl/yaWIw+6f5s5JBd8+/DGVHAF4HSXllYoNxFTwJdcoU+G6VRnHKAb+haDxWySBeVha0ZatbVTETqlS+NWlJbV41INxaitLDdNU+RWZnON4Em3YOiaZtClzHnG1ySf9lsBPrErulvrhOAVB0Pz6I0FmwBbc7ppnM+zEvB82AzaCbIOAXYseXgGbCysM987OVp0IPIUho4aDCLkcZ2Lx1MEMdtgdU1beOT85lxeFucbPN64oXSwIy7Vd+NxmA6tSW3qyYXZYdTQJMbI/a9FpTF2PVJQPI9bqVMPMGsyXRuK7RZyN6M00EX4IMvDdrmjuq/X9ubFvuXuMnTib0ppJdvzqDPzQVA8m6MSGWhfs6SwBX3s72xH46XR2UTcafXVfVVYG1bdTNbPxOlX6Qktdi2g5/5Xjw7Uvo+Syz/tZBGVWDHpfMbR7vre7gdlEX0uhAWZNctSJuyMP39Js8CoTQk41HzMsBhePuPkZ/Jsth+ZG/uq2AOCHlBcgeiG8JHNKg9tLNYOxLyOzwbeBxOB1OAXxcTexHs+ufgQ7Cuxg645FfEmHZ7RPoXNldZJPT5POgAAAAASUVORK5CYII=", 260 | ), 261 | "run": ( 262 | "Run lima command", 263 | "image=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", 264 | ), 265 | "delete": (lambda vm: f"Delete {vm} VM", ""), 266 | }, 267 | "image": { 268 | "pull_new": ("pull new image", ""), 269 | "image": ( 270 | lambda img: img, 271 | "image=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", 272 | ), 273 | "pull": ("pull", ""), 274 | "rm": ("rm", ""), 275 | }, 276 | "container": { 277 | "container": ( 278 | lambda container: container, 279 | "image=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", 280 | ), 281 | "Running": ("Running", ""), 282 | "stop": ("stop", ""), 283 | "kill": ("kill", ""), 284 | "pause": ("pause", ""), 285 | "Stopped": ("Stopped", ""), 286 | "rm": ("rm", ""), 287 | "start": ("start", ""), 288 | "unpause": ("unpause", ""), 289 | }, 290 | }, 291 | }, 292 | "text": { 293 | "swiftbar": { 294 | "main": { 295 | "stopped": (f"Lima | color={STOPPED_VM_COLOR}", ""), 296 | "running": (f"Lima | color={RUNNING_VM_COLOR}", ""), 297 | }, 298 | "vm": { 299 | "start": (lambda vm: f"Start {vm} VM", ""), 300 | "stop": (lambda vm: f"Stop {vm} VM", ""), 301 | "edit": (lambda vm: f"Edit {vm} VM", ""), 302 | "run": ("Run lima command", ""), 303 | "delete": (lambda vm: f"Delete {vm} VM", ""), 304 | }, 305 | "image": { 306 | "pull_new": ("pull new image", ""), 307 | "image": (lambda img: img, ""), 308 | "pull": ("pull", ""), 309 | "rm": ("rm", ""), 310 | }, 311 | "container": { 312 | "container": (lambda container: container, ""), 313 | "Running": ("Running", ""), 314 | "stop": ("stop", ""), 315 | "kill": ("kill", ""), 316 | "pause": ("pause", ""), 317 | "Stopped": ("Stopped", ""), 318 | "rm": ("rm", ""), 319 | "start": ("start", ""), 320 | "unpause": ("unpause", ""), 321 | }, 322 | }, 323 | "xbar-light": { 324 | "main": { 325 | "stopped": (f"Lima | color={STOPPED_VM_COLOR}", ""), 326 | "running": (f"Lima | color={RUNNING_VM_COLOR}", ""), 327 | }, 328 | "vm": { 329 | "start": (lambda vm: f"Start {vm} VM", ""), 330 | "stop": (lambda vm: f"Stop {vm} VM", ""), 331 | "edit": (lambda vm: f"Edit {vm} VM", ""), 332 | "run": ("Run lima command", ""), 333 | "delete": (lambda vm: f"Delete {vm} VM", ""), 334 | }, 335 | "image": { 336 | "pull_new": ("pull new image", ""), 337 | "image": (lambda img: img, ""), 338 | "pull": ("pull", ""), 339 | "rm": ("rm", ""), 340 | }, 341 | "container": { 342 | "container": (lambda container: container, ""), 343 | "Running": ("Running", ""), 344 | "stop": ("stop", ""), 345 | "kill": ("kill", ""), 346 | "pause": ("pause", ""), 347 | "Stopped": ("Stopped", ""), 348 | "rm": ("rm", ""), 349 | "start": ("start", ""), 350 | "unpause": ("unpause", ""), 351 | }, 352 | }, 353 | "xbar-dark": { 354 | "main": { 355 | "stopped": (f"Lima | color={STOPPED_VM_COLOR}", ""), 356 | "running": (f"Lima | color={RUNNING_VM_COLOR}", ""), 357 | }, 358 | "vm": { 359 | "start": (lambda vm: f"Start {vm} VM", ""), 360 | "stop": (lambda vm: f"Stop {vm} VM", ""), 361 | "edit": (lambda vm: f"Edit {vm} VM", ""), 362 | "run": ("Run lima command", ""), 363 | "delete": (lambda vm: f"Delete {vm} VM", ""), 364 | }, 365 | "image": { 366 | "pull_new": ("pull new image", ""), 367 | "image": (lambda img: img, ""), 368 | "pull": ("pull", ""), 369 | "rm": ("rm", ""), 370 | }, 371 | "container": { 372 | "container": (lambda container: container, ""), 373 | "Running": ("Running", ""), 374 | "stop": ("stop", ""), 375 | "kill": ("kill", ""), 376 | "pause": ("pause", ""), 377 | "Stopped": ("Stopped", ""), 378 | "rm": ("rm", ""), 379 | "start": ("start", ""), 380 | "unpause": ("unpause", ""), 381 | }, 382 | }, 383 | }, 384 | } 385 | 386 | 387 | def buildMenuItem(title: "tuple[str, str]", *params) -> str: 388 | """ 389 | Build a menu item string 390 | """ 391 | label, icon = title 392 | sp = "" if "|" in label else " | " 393 | return f"{label}{sp}{icon} {' '.join(params)}" 394 | 395 | 396 | def getMenuTitle( 397 | type: "tuple[str, tuple]", action: str, theme: "str | None" = None 398 | ) -> "tuple[str, str]": 399 | """ 400 | Get a menu title tuple from MENU_TITLE_ICON_CONF dict 401 | If the key not exists, return a tuple: ('!Undefined!', '') 402 | """ 403 | if IN_SWIFTBAR: 404 | plugin_type = "swiftbar" 405 | else: 406 | plugin_type = ( 407 | "xbar-dark" if os.environ.get("XBARDarkMode") == "true" else "xbar-light" 408 | ) 409 | 410 | if theme is None: 411 | theme = getCurrentTheme() 412 | 413 | label, icon = ( 414 | MENU_TITLE_ICON_CONF.get(theme, {}) 415 | .get(plugin_type, {}) 416 | .get(type[0], {}) 417 | .get(action, ("!Undefined!", "")) 418 | ) 419 | if callable(label): 420 | return label(*type[1]), icon 421 | else: 422 | return label, icon 423 | 424 | 425 | def getCurrentTheme() -> str: 426 | """ 427 | Detect the theme from current plugin filename 428 | e.g. 429 | lima-plugin.10s -> default 430 | lima-plugin.default.10s -> default 431 | lima-plugin.text.10s -> text 432 | lima-plugin.sf_simple.10s -> sf_simple 433 | lima-plugin.sf_simple.10s.py -> sf_simple 434 | """ 435 | filename = __file__.split("/")[-1] 436 | ps = filename.split(".") 437 | if len(ps) >= 3 and ps[1] in MENU_TITLE_ICON_CONF.keys(): 438 | return ps[1] 439 | else: 440 | return "default" 441 | 442 | 443 | def logSetup(level: str = "INFO"): 444 | maclog = logging.handlers.SysLogHandler( 445 | address="/var/run/syslog", facility="local1" 446 | ) 447 | maclog.ident = "lima-xbar" 448 | 449 | loglevel = getattr(logging, level.upper(), None) 450 | logFormat = " [%(asctime)s][%(levelname)8s][%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s" 451 | logging.basicConfig(level=loglevel, format=logFormat) 452 | 453 | maclog.setLevel(loglevel) 454 | 455 | # set a format which is simpler for console use 456 | formatter = logging.Formatter(" %(name)-12s: %(levelname)-8s %(message)s") 457 | 458 | # tell the handler to use this format 459 | maclog.setFormatter(formatter) 460 | 461 | # add the handler to the root logger 462 | logging.getLogger("").addHandler(maclog) 463 | logging.debug("Set log level to %s", level.upper()) 464 | 465 | 466 | def parseCLI(): 467 | """ 468 | Parse the command line options 469 | """ 470 | parser = argparse.ArgumentParser(description="Lima Swiftbar/Xbar plugin") 471 | parser.add_argument("-d", "--debug", help="Debug setting", action="store_true") 472 | parser.add_argument( 473 | "-l", 474 | "--log-level", 475 | type=str.upper, 476 | help="set log level", 477 | choices=["DEBUG", "INFO", "ERROR", "WARNING", "CRITICAL"], 478 | default="CRITICAL", 479 | ) 480 | parser.add_argument( 481 | "--vm", "--virtual-machine", type=str, help="Which vm to use", default="default" 482 | ) 483 | parser.add_argument("--target", type=str, help="Which image/vm/container to target") 484 | 485 | parser.add_argument( 486 | "--container-action", 487 | choices=["start", "stop", "rm", "pause", "unpause"], 488 | help="Action to perform on a container", 489 | ) 490 | parser.add_argument( 491 | "--image-action", choices=["pull", "rm"], help="Action to perform on image" 492 | ) 493 | parser.add_argument("--pull-new-image", action="store_true") 494 | parser.add_argument( 495 | "--vm-action", 496 | choices=["create", "edit", "start", "stop", "lima", "delete"], 497 | help="Action to perform on vm", 498 | ) 499 | cliArgs = parser.parse_args() 500 | return cliArgs 501 | 502 | 503 | # fun with osascript 504 | 505 | 506 | def displayAlert(title: str, message: str): 507 | """ 508 | Display an alert using osascript. Blocking. 509 | 510 | :param str title: 511 | :param str message: 512 | """ 513 | alertCommand = f'display alert "{title}" message "{message}"' 514 | runCommand(command=["osascript", "-e", alertCommand]) 515 | 516 | 517 | def displayNotification(title: str, message: str): 518 | """ 519 | Publish a notification to the notification manager. 520 | 521 | :param str title: 522 | :param str message: 523 | """ 524 | alertCommand = f'display notification "{message}" with title "{title}" ' 525 | runCommand(command=["osascript", "-e", alertCommand]) 526 | 527 | 528 | def inputDialog(user_prompt: str, icon: str = "note"): 529 | """ 530 | Uses osascript to present a dialog with a prompt and returns the user's answer. 531 | 532 | :param str prompt: 533 | :param str icon: note, 534 | 535 | :return str: 536 | """ 537 | valid_icons = ["caution", "note", "stop"] 538 | if icon.lower() not in valid_icons: 539 | icon = "note" 540 | 541 | applescript = f"""set dialogText to text returned of (display dialog "{user_prompt}" default answer "") 542 | return dialogText 543 | """ 544 | 545 | answer = runCommand(command=["osascript", "-e", applescript]).strip() 546 | logging.debug(f"Asked {user_prompt} , got answer: {answer}") 547 | return answer 548 | 549 | 550 | def runCommand(command: list, env=dict(os.environ)): 551 | """ 552 | Run a command and return the decoded output 553 | 554 | :param list command: 555 | 556 | :return str: 557 | """ 558 | return subprocess.run(command, env=env, stdout=subprocess.PIPE).stdout.decode( 559 | "utf-8" 560 | ) 561 | 562 | 563 | def jsonCommand(command: list, env=dict(os.environ)): 564 | """ 565 | Run a command and decode the json output 566 | 567 | :param list command: 568 | :return dict: 569 | """ 570 | json_output = runCommand(command=command, env=env) 571 | 572 | data = [] 573 | for line in json_output.splitlines(): 574 | try: 575 | details = json.loads(line) 576 | data.append(details) 577 | except json.decoder.JSONDecodeError: 578 | logging.error("Bad JSON returned: %s", line) 579 | return data 580 | 581 | 582 | def listContainers(vm: str = "default"): 583 | """ 584 | List all containers in a VM 585 | 586 | :param vm: 587 | 588 | :return dict: 589 | """ 590 | containers = {} 591 | env = prep_environment_for_lima(vm=vm) 592 | 593 | command = [ 594 | "lima", 595 | "nerdctl", 596 | "container", 597 | "ls", 598 | "-a", 599 | "--format", 600 | "{{json .}}", 601 | ] 602 | raw = jsonCommand(command=command, env=env) 603 | for container in raw: 604 | try: 605 | if container["Names"] != "": 606 | key = container["Names"] 607 | else: 608 | key = container["ID"] 609 | containers[key] = container 610 | except KeyError: 611 | logging.error("Bad container record: %s", container) 612 | return containers 613 | 614 | 615 | def listImages(vm: str = "default"): 616 | """ 617 | List all images in a VM 618 | 619 | :param vm: 620 | 621 | :return dict: 622 | """ 623 | images = {} 624 | env = prep_environment_for_lima(vm=vm) 625 | 626 | command = ["lima", "nerdctl", "images", "--format", "{{json .}}"] 627 | raw = jsonCommand(command=command, env=env) 628 | logging.debug("Processing command output...") 629 | for image in raw: 630 | try: 631 | repo = "ERROR" 632 | tag = "ERROR" 633 | if "Repository" in image: 634 | repo = image["Repository"] 635 | else: 636 | logging.error("Repository key missing") 637 | if "Tag" in image: 638 | tag = image["Tag"] 639 | else: 640 | logging.error("Tag key missing") 641 | images["%s:%s" % (repo, tag)] = image 642 | except KeyError: 643 | logging.error("Bad image record: %s", image) 644 | logging.error("Keys: %s", image.keys()) 645 | logging.error(" ") 646 | return images 647 | 648 | 649 | def listVMs(): 650 | """ 651 | List all VMs 652 | 653 | :return dict: 654 | """ 655 | vmList = {} 656 | 657 | env = prep_environment_for_lima() 658 | 659 | vmRaw = subprocess.run( 660 | ["limactl", "list", "--json"], env=env, stdout=subprocess.PIPE 661 | ).stdout.decode("utf-8") 662 | 663 | for vm in vmRaw.splitlines(): 664 | details = json.loads(vm) 665 | vmList[details["name"]] = details 666 | return vmList 667 | 668 | 669 | # Submenu processing 670 | 671 | 672 | def prep_environment_for_lima(vm: str = "default", env: dict = dict(os.environ)): 673 | """ 674 | Set up an environment dictionary we can use to run a lima command. 675 | 676 | Also adds /usr/local/{s}bin, /opt/homebrew/{s}bin, /opt/local/{s}bin, 677 | ~/homebrew/{s}bin and ~/{s}bin to $PATH if they exist and are directories. 678 | 679 | :param str vm: VM to work in 680 | :param dict env: Environment variables to base returned environment on 681 | 682 | :return dict: Environment dictionary, with extra bindirs added to $PATH 683 | """ 684 | extrapaths = [ 685 | "/opt/homebrew/bin", 686 | "/opt/homebrew/sbin", 687 | "/opt/local/bin", 688 | "/opt/local/sbin", 689 | "/usr/local/bin", 690 | "/usr/local/sbin", 691 | f"{os.environ.get('HOME')}/bin", 692 | f"{os.environ.get('HOME')}/homebrew/bin", 693 | f"{os.environ.get('HOME')}/homebrew/sbin", 694 | f"{os.environ.get('HOME')}/sbin", 695 | ] 696 | for p in extrapaths: 697 | if os.path.isdir(p): 698 | logging.info("Adding %s to $PATH", p) 699 | newpath = "%s:%s" % (env["PATH"], p) 700 | env["PATH"] = newpath 701 | logging.info("New path: %s", env["PATH"]) 702 | 703 | if vm != "default": 704 | logging.info("Setting LIMA_INSTANCE to %s", vm) 705 | env["LIMA_INSTANCE"] = vm 706 | return env 707 | 708 | 709 | def containerOps(action: str, container: str, vm: str = "default"): 710 | """ 711 | Handle container operations 712 | 713 | :param str action: What container op to do 714 | :param str container: What container to do the action on 715 | :param str vm: Which VM is the container in? 716 | """ 717 | logging.warning("containerOps") 718 | logging.debug("action: %s" % action) 719 | logging.debug("container: %s" % container) 720 | logging.debug("vm: %s" % vm) 721 | 722 | env = prep_environment_for_lima(vm=vm) 723 | 724 | command = ["lima", "nerdctl", "container", action, container] 725 | logging.warning("containerops command: %s", command) 726 | displayNotification(title="Lima VM", message=" ".join(command)) 727 | 728 | output = runCommand(command=command, env=env) 729 | logging.warning(output) 730 | logging.warning("%s complete", action) 731 | displayNotification(title="Task complete", message=" ".join(command)) 732 | 733 | 734 | def imageOps(action: str, image: str, vm: str = "default"): 735 | """ 736 | Handle VM operations 737 | 738 | :param str action: What image op to do 739 | :param str image: What image to do the action on 740 | :param str vm: Which VM is the image in? 741 | """ 742 | logging.info("imageOps") 743 | logging.info("action: %s" % action) 744 | logging.info("image: %s" % image) 745 | logging.info("vm: %s" % vm) 746 | 747 | env = prep_environment_for_lima(vm=vm) 748 | 749 | command = ["lima", "nerdctl", "image", action, image] 750 | logging.warning("command: %s", command) 751 | logging.warning("PATH: %s", env["PATH"]) 752 | displayNotification(title="Lima VM", message=" ".join(command)) 753 | output = runCommand(command=command, env=env) 754 | logging.debug(output) 755 | logging.warning("%s complete", action) 756 | displayNotification(title="Task complete", message=" ".join(command)) 757 | 758 | 759 | def limaCommand(vm: str): 760 | """ 761 | Run an arbitrary command with lima 762 | 763 | :param str vm: Which vm to run the command in 764 | """ 765 | env = prep_environment_for_lima(vm=vm) 766 | user_command = inputDialog( 767 | user_prompt=f"What lima command do you want to run in the {vm} VM?" 768 | ) 769 | 770 | if user_command != "": 771 | lima_command = ["lima"] + user_command.split() 772 | displayNotification( 773 | title=f"Running '{user_command}'", message=" ".join(lima_command) 774 | ) 775 | runCommand(command=lima_command, env=env) 776 | displayNotification(title=f"Ran '{user_command}' in {vm}", message="Completed") 777 | else: 778 | displayAlert(title="Error!", message="No nerdctl command specified") 779 | 780 | 781 | def vmOps(action: str, vm: str = "default"): 782 | """ 783 | Handle VM operations 784 | 785 | :param str action: What action to run - should be start or stop 786 | :param str vm: Name of VM to act on 787 | """ 788 | logging.info("vmOps") 789 | logging.debug("action: %s" % action) 790 | logging.debug("vm: %s" % vm) 791 | 792 | env = prep_environment_for_lima(vm=vm) 793 | 794 | if action == "lima": 795 | limaCommand(vm=vm) 796 | 797 | if action in ["create", "edit", "start", "stop", "delete"]: 798 | if action in ["create", "edit"]: 799 | rosetta = None 800 | if action == "create": 801 | create_vm = inputDialog( 802 | user_prompt="Profile name for VM? (default=default)" 803 | ) 804 | if create_vm: 805 | vm = create_vm 806 | rosetta = inputDialog( 807 | user_prompt="(Can not be changed once created) Use rosetta2 or not(y/n)? Requirement Lima >= 0.14, macOS >= 13.0, ARM, and rosetta2 installed.(default=n)" 808 | ) 809 | command = ["limactl", "start", vm] 810 | cpu = inputDialog( 811 | user_prompt=f"Number of CPUs for {vm} VM? default=min(4, host CPU cores)" 812 | ) 813 | memory = inputDialog( 814 | user_prompt=f"Memory in GiB for {vm} VM? default=min(4, half of host memory)" 815 | ) 816 | disk = inputDialog( 817 | user_prompt=f"Disk size in GiB for {vm} VM? (default=100)" 818 | ) 819 | if rosetta == "y": 820 | command.extend(["--vm-type=vz", "--rosetta"]) 821 | if cpu: 822 | command.extend(["--cpus", cpu]) 823 | if memory: 824 | command.extend(["--memory", memory]) 825 | 826 | if action == "create": 827 | if disk: 828 | command.extend(["--disk", disk]) 829 | if action == "edit": 830 | command_stop = ["limactl", "stop", vm] 831 | runCommand(command=command_stop, env=env) 832 | if disk: 833 | settings = f'.disk = "{disk}GiB"' 834 | command_edit_disk = ["limactl", "edit", vm, f"--set={settings}"] 835 | runCommand(command=command_edit_disk, env=env) 836 | else: 837 | command = ["limactl", action, vm] 838 | logging.info("command: %s", command) 839 | 840 | displayNotification(title="Lima VM", message=" ".join(command)) 841 | output = runCommand(command=command, env=env) 842 | logging.debug(output) 843 | 844 | logging.info("%s complete", action) 845 | displayNotification(title="Task completed", message=" ".join(command)) 846 | 847 | 848 | def pullNewImage(vm: str = "default"): 849 | """ 850 | Pulls a new image. 851 | 852 | Args: 853 | vm (str, optional): Which VM to pull the new image into. Defaults to 'default'. 854 | """ 855 | env = prep_environment_for_lima(vm=vm) 856 | image = inputDialog(user_prompt=f"What image should we pull into VM {vm}?") 857 | if image != "": 858 | pull_command = ["lima", "nerdctl", "image", "pull", image] 859 | displayNotification( 860 | title=f"Pulling image {image}", message=" ".join(pull_command) 861 | ) 862 | runCommand(command=pull_command, env=env) 863 | displayNotification(title=f"Pulling image {image}", message="Completed") 864 | else: 865 | displayAlert(title="Error!", message="No image specified") 866 | 867 | 868 | # Actual Xbar-compatible output 869 | 870 | 871 | def xbar_icon(vms: dict = {}): 872 | """ 873 | Determine icon to display in menubar. 874 | 875 | We display a running menubar icon if at least one VM is running. 876 | 877 | :param dict vms: Data about Lima VMs 878 | """ 879 | menuBarIcon = f"{buildMenuItem(getMenuTitle(('main', ()), 'stopped'))}" 880 | for vm in vms: 881 | logging.debug("vm: %s", vm) 882 | if vms[vm]["status"] == "Running": 883 | menuBarIcon = f"{buildMenuItem(getMenuTitle(('main', ()), 'running'))}" 884 | break 885 | print(menuBarIcon) 886 | print("---") 887 | 888 | 889 | def aboutMenu(): 890 | """ 891 | Print details about plugin 892 | """ 893 | env = prep_environment_for_lima() 894 | limaVersion = subprocess.run( 895 | ["limactl", "--version"], stdout=subprocess.PIPE, env=env 896 | ).stdout.decode("utf-8") 897 | 898 | print("About…") 899 | print("-- Lima version: %s" % limaVersion.strip()) 900 | print("-- Lima home | href=https://github.com/lima-vm/lima") 901 | print("-- lima-xbar version: %s" % VERSION) 902 | print("-- force rescan | bash=limactl param1=list terminal=false refresh=true") 903 | 904 | 905 | def vmContainerSubMenu(vm: str = "default"): 906 | """ 907 | Generate a container submenu for a VM 908 | 909 | :param str vm: 910 | """ 911 | plugin_f = __file__ 912 | containers = listContainers(vm=vm) 913 | 914 | logging.debug("containers: %s", containers) 915 | 916 | print("-- Containers") 917 | for container in containers: 918 | if containers[container]["Status"] == "Up": 919 | container_params = f"color={RUNNING_VM_COLOR}" 920 | print( 921 | f"---- {buildMenuItem(getMenuTitle(('container', (container,)), 'container'), container_params)}" 922 | ) 923 | 924 | print(f"------ {buildMenuItem(getMenuTitle(('container', ()), 'Running'))}") 925 | 926 | stop_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=stop param5="--target" param6={container} terminal=false refresh=true' 927 | print( 928 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'stop'), stop_params)}" 929 | ) 930 | 931 | kill_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=kill param5="--target" param6={container} terminal=false refresh=true' 932 | print( 933 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'kill'), kill_params)}" 934 | ) 935 | 936 | pause_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=pause param5="--target" param6={container} terminal=false refresh=true' 937 | print( 938 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'pause'), pause_params)}" 939 | ) 940 | else: 941 | container_params = f"color={STOPPED_VM_COLOR}" 942 | print( 943 | f"---- {buildMenuItem(getMenuTitle(('container', (container,)), 'container'), container_params)}" 944 | ) 945 | 946 | print(f"------ {buildMenuItem(getMenuTitle(('container', ()), 'Stopped'))}") 947 | rm_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=rm param5="--target" param6={container} terminal=false refresh=true' 948 | print( 949 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'rm'), rm_params)}" 950 | ) 951 | 952 | start_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=start param5="--target" param6={container} terminal=false refresh=true' 953 | print( 954 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'start'), start_params)}" 955 | ) 956 | 957 | unpause_params = f'shell="{plugin_f}" param1="--vm" param2={vm} param3="--container-action" param4=unpause param5="--target" param6={container} terminal=false refresh=true' 958 | print( 959 | f"------ {buildMenuItem(getMenuTitle(('container', ()), 'unpause'), unpause_params)}" 960 | ) 961 | 962 | 963 | def vmImageSubMenu(vm: str = "default"): 964 | """ 965 | Generate an image submenu for a VM 966 | 967 | :param str vm: 968 | """ 969 | plugin_f = __file__ 970 | images = listImages(vm=vm) 971 | 972 | logging.debug("images: %s", images) 973 | 974 | print("-- Images") 975 | 976 | pull_new_params = f'bash="{plugin_f}" param1=--vm param2={vm} param3=--pull-new-image terminal=false refresh=true' 977 | print( 978 | f"---- {buildMenuItem(getMenuTitle(('image', ()), 'pull_new'), pull_new_params)}" 979 | ) 980 | 981 | for image in images: 982 | print(f"---- {buildMenuItem(getMenuTitle(('image', (image,)), 'image'))}") 983 | 984 | pull_params = f'bash="{plugin_f}" param1=--vm param2={vm} param3=--image-action=pull param4=--target={image} terminal=false refresh=true' 985 | print( 986 | f"------ {buildMenuItem(getMenuTitle(('image', ()), 'pull'), pull_params)}" 987 | ) 988 | 989 | rm_params = f'bash="{plugin_f}" param1=--vm param2={vm} param3=--image-action=rm param4=--target={image} terminal=false refresh=true' 990 | print(f"------ {buildMenuItem(getMenuTitle(('image', ()), 'rm'), rm_params)}") 991 | 992 | 993 | def vmMenu(vmData: dict = {}): 994 | """ 995 | Generate submenus for all the VMs, running or not 996 | """ 997 | plugin_f = __file__ 998 | logging.debug("vmMenu") 999 | logging.debug("vmData: %s", vmData) 1000 | 1001 | if len(vmData) > 0: 1002 | for vm in vmData: 1003 | logging.debug("status %s", vmData[vm]["status"]) 1004 | edit_params = f"""shell=\"{plugin_f}\" param1='--vm={vm}' param2='--vm-action=edit' terminal=false refresh=true""" 1005 | info = f"CPUS={vmData[vm]['cpus']}, MEMORY={vmData[vm]['memory'] // 1073741824}GiB, DISK={vmData[vm]['disk'] // 1073741824}GiB" 1006 | if vmData[vm]["status"] != "Running": 1007 | print("%s VM is stopped | color=%s" % (vm, STOPPED_VM_COLOR)) 1008 | print(f"-- {info}") 1009 | 1010 | delete_params = f"""shell=\"{plugin_f}\" param1='--vm={vm}' param2='--vm-action=delete' terminal=false refresh=true""" 1011 | print( 1012 | f"---- {buildMenuItem(getMenuTitle(('vm', (vm,)), 'delete'), delete_params)}" 1013 | ) 1014 | 1015 | start_params = f"""shell=\"{plugin_f}\" param1='--vm={vm}' param2='--vm-action=start' terminal=false refresh=true""" 1016 | print( 1017 | f"-- {buildMenuItem(getMenuTitle(('vm', (vm,)), 'start'), start_params)}" 1018 | ) 1019 | print( 1020 | f"-- {buildMenuItem(getMenuTitle(('vm', (vm,)), 'edit'), edit_params)}" 1021 | ) 1022 | else: 1023 | print(f"{vm} VM (running) | color={RUNNING_VM_COLOR}") 1024 | print(f"-- {info}") 1025 | 1026 | stop_params = f"""color={STOPPED_VM_COLOR} shell=\"{plugin_f}\" param1='--vm={vm}' param2='--vm-action=stop' terminal=false refresh=true""" 1027 | print( 1028 | f"-- {buildMenuItem(getMenuTitle(('vm', (vm,)), 'stop'), stop_params)}" 1029 | ) 1030 | 1031 | run_params = f"""shell=\"{plugin_f}\" param1='--vm={vm}' param2='--vm-action=lima' terminal=false refresh=true""" 1032 | print( 1033 | f"-- {buildMenuItem(getMenuTitle(('vm', (vm,)), 'run'), run_params)}" 1034 | ) 1035 | 1036 | vmContainerSubMenu(vm=vm) 1037 | vmImageSubMenu(vm=vm) 1038 | print( 1039 | f"""Create new VM | shell=\"{plugin_f}\" param1='--vm-action=create' terminal=false refresh=true""" 1040 | ) 1041 | 1042 | 1043 | def xbarMenu(): 1044 | """ 1045 | Generate Xbar Menu 1046 | """ 1047 | vms = listVMs() 1048 | 1049 | xbar_icon(vms) 1050 | aboutMenu() 1051 | vmMenu(vmData=vms) 1052 | 1053 | 1054 | def main(): 1055 | """ 1056 | Main program driver 1057 | """ 1058 | 1059 | cli = parseCLI() 1060 | logSetup(level=cli.log_level) 1061 | 1062 | logging.debug("plugin path: %s" % __file__) 1063 | logging.debug("VERSON: %s", VERSION) 1064 | 1065 | logging.debug("cli: %s" % cli) 1066 | 1067 | logging.info("argv[0] %s" % sys.argv[0]) 1068 | 1069 | xbarMenu() 1070 | 1071 | if cli.container_action: 1072 | logging.info("container action: %s", cli.container_action) 1073 | containerOps(vm=cli.vm, action=cli.container_action, container=cli.target) 1074 | sys.exit() 1075 | 1076 | if cli.image_action: 1077 | logging.info("image action: %s", cli.image_action) 1078 | imageOps(action=cli.image_action, image=cli.target, vm=cli.vm) 1079 | sys.exit() 1080 | 1081 | if cli.vm_action: 1082 | logging.info("vm action: %s", cli.vm_action) 1083 | vmOps(action=cli.vm_action, vm=cli.vm) 1084 | sys.exit() 1085 | 1086 | if cli.pull_new_image: 1087 | pullNewImage(vm=cli.vm) 1088 | 1089 | 1090 | if __name__ == "__main__": 1091 | main() 1092 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "black" 5 | version = "24.4.2" 6 | description = "The uncompromising code formatter." 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, 11 | {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, 12 | {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, 13 | {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, 14 | {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, 15 | {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, 16 | {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, 17 | {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, 18 | {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, 19 | {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, 20 | {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, 21 | {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, 22 | {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, 23 | {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, 24 | {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, 25 | {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, 26 | {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, 27 | {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, 28 | {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, 29 | {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, 30 | {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, 31 | {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, 32 | ] 33 | 34 | [package.dependencies] 35 | click = ">=8.0.0" 36 | mypy-extensions = ">=0.4.3" 37 | packaging = ">=22.0" 38 | pathspec = ">=0.9.0" 39 | platformdirs = ">=2" 40 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 41 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 42 | 43 | [package.extras] 44 | colorama = ["colorama (>=0.4.3)"] 45 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 46 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 47 | uvloop = ["uvloop (>=0.15.2)"] 48 | 49 | [[package]] 50 | name = "click" 51 | version = "8.1.7" 52 | description = "Composable command line interface toolkit" 53 | optional = false 54 | python-versions = ">=3.7" 55 | files = [ 56 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 57 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 58 | ] 59 | 60 | [package.dependencies] 61 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 62 | 63 | [[package]] 64 | name = "colorama" 65 | version = "0.4.6" 66 | description = "Cross-platform colored terminal text." 67 | optional = false 68 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 69 | files = [ 70 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 71 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 72 | ] 73 | 74 | [[package]] 75 | name = "coverage" 76 | version = "5.5" 77 | description = "Code coverage measurement for Python" 78 | optional = false 79 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 80 | files = [ 81 | {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, 82 | {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, 83 | {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, 84 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, 85 | {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, 86 | {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, 87 | {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, 88 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, 89 | {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, 90 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, 91 | {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, 92 | {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, 93 | {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, 94 | {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, 95 | {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, 96 | {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, 97 | {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, 98 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, 99 | {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, 100 | {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, 101 | {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, 102 | {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, 103 | {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, 104 | {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, 105 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, 106 | {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, 107 | {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, 108 | {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, 109 | {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, 110 | {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, 111 | {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, 112 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, 113 | {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, 114 | {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, 115 | {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, 116 | {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, 117 | {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, 118 | {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, 119 | {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, 120 | {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, 121 | {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, 122 | {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, 123 | {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, 124 | {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, 125 | {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, 126 | {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, 127 | {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, 128 | {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, 129 | {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, 130 | {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, 131 | {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, 132 | {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, 133 | ] 134 | 135 | [package.extras] 136 | toml = ["toml"] 137 | 138 | [[package]] 139 | name = "mypy-extensions" 140 | version = "1.0.0" 141 | description = "Type system extensions for programs checked with the mypy type checker." 142 | optional = false 143 | python-versions = ">=3.5" 144 | files = [ 145 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 146 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 147 | ] 148 | 149 | [[package]] 150 | name = "packaging" 151 | version = "24.0" 152 | description = "Core utilities for Python packages" 153 | optional = false 154 | python-versions = ">=3.7" 155 | files = [ 156 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, 157 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, 158 | ] 159 | 160 | [[package]] 161 | name = "pathspec" 162 | version = "0.12.1" 163 | description = "Utility library for gitignore style pattern matching of file paths." 164 | optional = false 165 | python-versions = ">=3.8" 166 | files = [ 167 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 168 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 169 | ] 170 | 171 | [[package]] 172 | name = "platformdirs" 173 | version = "4.2.2" 174 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 175 | optional = false 176 | python-versions = ">=3.8" 177 | files = [ 178 | {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, 179 | {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, 180 | ] 181 | 182 | [package.extras] 183 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 184 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 185 | type = ["mypy (>=1.8)"] 186 | 187 | [[package]] 188 | name = "tomli" 189 | version = "2.0.1" 190 | description = "A lil' TOML parser" 191 | optional = false 192 | python-versions = ">=3.7" 193 | files = [ 194 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 195 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 196 | ] 197 | 198 | [[package]] 199 | name = "typing-extensions" 200 | version = "4.11.0" 201 | description = "Backported and Experimental Type Hints for Python 3.8+" 202 | optional = false 203 | python-versions = ">=3.8" 204 | files = [ 205 | {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, 206 | {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, 207 | ] 208 | 209 | [metadata] 210 | lock-version = "2.0" 211 | python-versions = "^3.9" 212 | content-hash = "4705ad94b1ded55d4251e29f215948ae468bf17b7610e7d710f93e13332bf18a" 213 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "lima-plugin" 3 | version = "1.5.0" 4 | description = "Xbar and Swiftbar plugin to control lima" 5 | authors = ["Joe Block "] 6 | license = "Apache 2.0" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | coverage = "^5.5" 11 | 12 | [tool.poetry.dev-dependencies] 13 | black = "*" 14 | 15 | [build-system] 16 | requires = ["poetry-core>=1.0.0"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /screenshots/101713774523_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixorn/lima-xbar-plugin/2069abcb45579275edb70942ed27eb859081a50e/screenshots/101713774523_.pic.jpg -------------------------------------------------------------------------------- /screenshots/111713774589_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixorn/lima-xbar-plugin/2069abcb45579275edb70942ed27eb859081a50e/screenshots/111713774589_.pic.jpg -------------------------------------------------------------------------------- /screenshots/121713774642_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixorn/lima-xbar-plugin/2069abcb45579275edb70942ed27eb859081a50e/screenshots/121713774642_.pic.jpg -------------------------------------------------------------------------------- /screenshots/91713774470_.pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixorn/lima-xbar-plugin/2069abcb45579275edb70942ed27eb859081a50e/screenshots/91713774470_.pic.jpg --------------------------------------------------------------------------------