├── .github └── workflows │ └── Public Release.yaml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── snippets.png ├── lib ├── flowlauncher-0.2.0.dist-info │ ├── INSTALLER │ ├── LICENSE │ ├── METADATA │ ├── RECORD │ ├── REQUESTED │ ├── WHEEL │ └── top_level.txt ├── flowlauncher │ ├── FlowLauncher.py │ ├── FlowLauncherAPI.py │ ├── __init__.py │ ├── __pycache__ │ │ ├── FlowLauncher.cpython-310.pyc │ │ ├── FlowLauncherAPI.cpython-310.pyc │ │ ├── __init__.cpython-310.pyc │ │ └── _version.cpython-310.pyc │ └── _version.py ├── pyperclip-1.8.2-py3.11.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── installed-files.txt │ └── top_level.txt └── pyperclip │ ├── __init__.py │ ├── __main__.py │ └── __pycache__ │ ├── __init__.cpython-311.pyc │ └── __main__.cpython-311.pyc ├── main.py ├── plugin.json ├── plugin └── snippets.py └── requirements.txt /.github/workflows/Public Release.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | paths-ignore: 8 | - .github/workflows/* 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.8] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: get version 23 | id: version 24 | uses: notiz-dev/github-action-json-property@release 25 | with: 26 | path: 'plugin.json' 27 | prop_path: 'Version' 28 | - run: echo ${{steps.version.outputs.prop}} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install -r ./requirements.txt -t ./lib 33 | zip -r Flow.Launcher.Plugin.Snippets.zip . -x '*.git*' 34 | - name: Add Tag 35 | uses: rickstaa/action-create-tag@v1 36 | with: 37 | tag: "v${{steps.version.outputs.prop}}" 38 | tag_exists_error: false 39 | message: "Latest release" 40 | - name: Publish 41 | uses: softprops/action-gh-release@v1 42 | with: 43 | files: 'Flow.Launcher.Plugin.Snippets.zip' 44 | tag_name: "v${{steps.version.outputs.prop}}" 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Raúl Losantos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flow.Launcher.Snippets 2 | 3 | This Snippets plugin let you save key/value snippets and copy to clipboard copy it from [Flow Launcher](https://www.flowlauncher.com/). 4 | 5 | ## How to use it 6 | 7 | 1. Install this plugin via `pm install Snippets` 8 | 2. The default action keyword is `sp` by default and it's recommended to replace it into `*` so that all the keys of snippets can be triggered directly. 9 | 2. Examples: 10 | - **Save `:` for Single Line**: open FlowLauncher to type `sp :`, click or `Enter` the shown option `Save Code Snippets` to save the `key=test-key` and `value=test-value` into the local sqlite3 db. 11 | - **Save From Clipbaord for Multiple Line**: Copy your selected snippets and then open FlowLauncher to type `sp `, As `key=clipboard-snippet-key` is not saved yet and there are snippets from clipboard, click or `Enter` the shown option `Save from clipboard` to save the `key=clipboard-snippet-key` and `value=clipboard-snippets` into the local sqlite3 db. 12 | - **Get Snippets By Key**: `sp `: Search for the `key=test-key` and click the item or `Enter` to copy the snippets value into clipboard. 13 | - **Delete Snippets By Key**: `sp `: Search for the `key=test-key`, it would show the save item, press `Shift + Enter` to see the context menu on selected item, click the item or `Enter` to delet the snippets 14 | 15 | > Disclaimer: The key/value would be stored on **local only ** via sqlite3 db file `./snippets.db` under `%FlowLauncherSettingsFolder%/Flow.Launcher.Snippets/` 16 | -------------------------------------------------------------------------------- /assets/snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/assets/snippets.png -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Flow-Launcher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: flowlauncher 3 | Version: 0.2.0 4 | Summary: Flow Launcher supports Python by JsonRPC. 5 | Home-page: https://github.com/Flow-Launcher/Flow.Launcher.JsonRPC.Python 6 | Download-URL: https://github.com/Flow-Launcher/Flow.Launcher.JsonRPC.Python/archive/master.zip 7 | Author: Flow-Launcher 8 | Author-email: Zeroto521@gmail.com 9 | Maintainer: Zero 10 | Maintainer-email: Zeroto521@gmail.com 11 | License: MIT 12 | Platform: Windows 13 | Classifier: Development Status :: 3 - Alpha 14 | Classifier: Intended Audience :: Developers 15 | Classifier: License :: OSI Approved :: MIT License 16 | Classifier: Natural Language :: English 17 | Classifier: Operating System :: Microsoft :: Windows 18 | Classifier: Programming Language :: Python 19 | Classifier: Programming Language :: Python :: 3 20 | Classifier: Programming Language :: Python :: 3 :: Only 21 | Classifier: Programming Language :: Python :: 3.7 22 | Classifier: Programming Language :: Python :: 3.8 23 | Classifier: Programming Language :: Python :: 3.9 24 | Classifier: Programming Language :: Python :: 3.10 25 | Classifier: Topic :: Software Development 26 | Classifier: Topic :: Software Development :: Libraries 27 | Classifier: Topic :: Software Development :: Libraries :: Application Frameworks 28 | Description-Content-Type: text/markdown 29 | License-File: LICENSE 30 | 31 | # Flow.Launcher.JsonRPC.Python 32 | 33 | [![](https://img.shields.io/pypi/v/flowlauncher.svg?style=for-the-badge)](https://pypi.org/project/flowlauncher/) 34 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/flowlauncher?style=for-the-badge)](https://pypi.org/project/flowlauncher/) 35 | 36 | Flow Launcher supports Python by JsonRPC. 37 | 38 | ## JSON-RPC 39 | 40 | > [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) is a remote procedure call protocol encoded in JSON. 41 | 42 | In Flow Launcher, we use JSON-RPC as a **local** procedure call protocol to bind Flow and other program languages. 43 | 44 | So we need to build a **common API** between Flow and Plugin. 45 | 46 | ![JsonRPC](./assets/jsonrpc.png) 47 | 48 | ### Example 49 | 50 | - `-->` denotes data sent to FLow. 51 | - `<--` denotes data coming from Flow. 52 | 53 | ```json 54 | --> {"method": "query", "parameters": [""]} 55 | <-- {"Title": "title", "SubTitle": "sub title", "IconPath": "favicon.ico"} 56 | ``` 57 | 58 | 59 | 60 | ## Installation 61 | 62 | ### Using `pip` 63 | 64 | ``` bash 65 | >>> pip install flowlauncher 66 | ``` 67 | 68 | ### Using `pip` + `git` 69 | 70 | ``` bash 71 | >>> pip install git+https://github.com/Flow-Launcher/Flow.Launcher.JsonRPC.Python.git 72 | ``` 73 | 74 | ### Using `git` 75 | 76 | ``` bash 77 | >>> git clone https://github.com/Flow-Launcher/Flow.Launcher.JsonRPC.Python.git 78 | >>> cd Flow.Launcher.JsonRPC.Python 79 | >>> python setup.py install 80 | ``` 81 | 82 | 83 | 84 | ### License 85 | 86 | This project is under the [MIT](./LICENSE) license. 87 | 88 | Some of the orignal codes from [JsonRPC/wox.py](https://github.com/Wox-launcher/Wox/blob/master/JsonRPC/wox.py) which is under the [MIT](https://github.com/Wox-launcher/Wox/blob/master/LICENSE) license. 89 | -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | flowlauncher-0.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | flowlauncher-0.2.0.dist-info/LICENSE,sha256=ghgUeUeZuF3zrJuPItKQfHORRmO9dfY_TFUUtkarxL4,1070 3 | flowlauncher-0.2.0.dist-info/METADATA,sha256=dm4FVJ6W7D4H_HB-M_0nLIuRcIRWeu6qTgxD0ozFiCs,2882 4 | flowlauncher-0.2.0.dist-info/RECORD,, 5 | flowlauncher-0.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 6 | flowlauncher-0.2.0.dist-info/WHEEL,sha256=ZL1lC_LiPDNRgDnOl2taCMc83aPEUZgHHv2h-LDgdiM,92 7 | flowlauncher-0.2.0.dist-info/top_level.txt,sha256=6w21mpB53RUVYxLbLn9bJusgNZKy0BBdP0HtBTDB6WM,13 8 | flowlauncher/FlowLauncher.py,sha256=_6uLoP7sJYKRvvJXTFSZ3QfIu-Xc_fCs2r0MkgEaOh4,1450 9 | flowlauncher/FlowLauncherAPI.py,sha256=Tq3AS_PyHGAgtS5avAocLKruPESoo70l3x8GO6-gxl4,2267 10 | flowlauncher/__init__.py,sha256=aIG4Da4cmfLfXwX99tc6BUX-yuYtropTV5XSEzCDwJo,307 11 | flowlauncher/__pycache__/FlowLauncher.cpython-310.pyc,, 12 | flowlauncher/__pycache__/FlowLauncherAPI.cpython-310.pyc,, 13 | flowlauncher/__pycache__/__init__.cpython-310.pyc,, 14 | flowlauncher/__pycache__/_version.cpython-310.pyc,, 15 | flowlauncher/_version.py,sha256=N1B1W-NeZ6MiRBIgcAjQ6l7RMePfWtsFi7Qs8ENzGSo,497 16 | -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/REQUESTED: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/flowlauncher-0.2.0.dist-info/REQUESTED -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.38.2) 3 | Root-Is-Purelib: true 4 | Tag: py3-none-any 5 | 6 | -------------------------------------------------------------------------------- /lib/flowlauncher-0.2.0.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | flowlauncher 2 | -------------------------------------------------------------------------------- /lib/flowlauncher/FlowLauncher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import inspect 5 | import sys 6 | from json import loads, dumps 7 | 8 | 9 | class FlowLauncher: 10 | """ 11 | Flow.Launcher python plugin base 12 | """ 13 | 14 | def __init__(self): 15 | 16 | # defalut jsonrpc 17 | self.rpc_request = {'method': 'query', 'parameters': ['']} 18 | self.debugMessage = "" 19 | 20 | if len(sys.argv) > 1: 21 | 22 | # Gets JSON-RPC from Flow Launcher process. 23 | self.rpc_request = loads(sys.argv[1]) 24 | 25 | # proxy is not working now 26 | # self.proxy = self.rpc_request.get("proxy", {}) 27 | 28 | request_method_name = self.rpc_request.get("method", "query") 29 | request_parameters = self.rpc_request.get("parameters", []) 30 | 31 | methods = inspect.getmembers(self, predicate=inspect.ismethod) 32 | request_method = dict(methods)[request_method_name] 33 | results = request_method(*request_parameters) 34 | 35 | if request_method_name in ("query", "context_menu"): 36 | print(dumps({ 37 | "result": results, 38 | "debugMessage": self.debugMessage 39 | })) 40 | 41 | def query(self, param: str = '') -> list: 42 | """ 43 | sub class need to override this method 44 | """ 45 | return [] 46 | 47 | def context_menu(self, data) -> list: 48 | """ 49 | optional context menu entries for a result 50 | """ 51 | return [] 52 | 53 | def debug(self, msg: str): 54 | """ 55 | alert msg 56 | """ 57 | self.debugMessage = msg 58 | -------------------------------------------------------------------------------- /lib/flowlauncher/FlowLauncherAPI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from json import dumps 4 | 5 | class FlowLauncherAPI: 6 | 7 | @classmethod 8 | def change_query(cls, query, requery: bool = False): 9 | """ 10 | change flow launcher query 11 | """ 12 | print(dumps({ 13 | "method": "Flow.Launcher.ChangeQuery", 14 | "parameters": [query, requery]})) 15 | 16 | @classmethod 17 | def shell_run(cls, cmd): 18 | """ 19 | run shell commands 20 | """ 21 | print(dumps({ 22 | "method": "Flow.Launcher.ShellRun", 23 | "parameters": [cmd]})) 24 | 25 | @classmethod 26 | def close_app(cls): 27 | """ 28 | close flow launcher 29 | """ 30 | print(dumps({ 31 | "method": "Flow.Launcher.CloseApp", 32 | "parameters": []})) 33 | 34 | @classmethod 35 | def hide_app(cls): 36 | """ 37 | hide flow launcher 38 | """ 39 | print(dumps({ 40 | "method": "Flow.Launcher.HideApp", 41 | "parameters": []})) 42 | 43 | @classmethod 44 | def show_app(cls): 45 | """ 46 | show flow launcher 47 | """ 48 | print(dumps({ 49 | "method": "Flow.Launcher.ShowApp", 50 | "parameters": []})) 51 | 52 | @classmethod 53 | def show_msg(cls, title: str, sub_title: str, ico_path: str = ""): 54 | """ 55 | show messagebox 56 | """ 57 | print(dumps({ 58 | "method": "Flow.Launcher.ShowMsg", 59 | "parameters": [title, sub_title, ico_path]})) 60 | 61 | @classmethod 62 | def open_setting_dialog(cls): 63 | """ 64 | open setting dialog 65 | """ 66 | print(dumps({ 67 | "method": "Flow.Launcher.OpenSettingDialog", 68 | "parameters": []})) 69 | 70 | @classmethod 71 | def start_loadingbar(cls): 72 | """ 73 | start loading animation in flow launcher 74 | """ 75 | print(dumps({ 76 | "method": "Flow.Launcher.StartLoadingBar", 77 | "parameters": []})) 78 | 79 | @classmethod 80 | def stop_loadingbar(cls): 81 | """ 82 | stop loading animation in flow launcher 83 | """ 84 | print(dumps({ 85 | "method": "Flow.Launcher.StopLoadingBar", 86 | "parameters": []})) 87 | 88 | @classmethod 89 | def reload_plugins(cls): 90 | """ 91 | reload all flow launcher plugins 92 | """ 93 | print(dumps({ 94 | "method": "Flow.Launcher.ReloadPlugins", 95 | "parameters": []})) 96 | -------------------------------------------------------------------------------- /lib/flowlauncher/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from ._version import get_versions 4 | from .FlowLauncher import FlowLauncher # noqa 5 | from .FlowLauncherAPI import FlowLauncherAPI # noqa 6 | 7 | __version__ = get_versions()["version"] 8 | del get_versions 9 | 10 | __license__ = 'MIT' 11 | __short_description__ = 'Flow Launcher supports Python by JsonRPC.' 12 | -------------------------------------------------------------------------------- /lib/flowlauncher/__pycache__/FlowLauncher.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/flowlauncher/__pycache__/FlowLauncher.cpython-310.pyc -------------------------------------------------------------------------------- /lib/flowlauncher/__pycache__/FlowLauncherAPI.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/flowlauncher/__pycache__/FlowLauncherAPI.cpython-310.pyc -------------------------------------------------------------------------------- /lib/flowlauncher/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/flowlauncher/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /lib/flowlauncher/__pycache__/_version.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/flowlauncher/__pycache__/_version.cpython-310.pyc -------------------------------------------------------------------------------- /lib/flowlauncher/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file was generated by 'versioneer.py' (0.27) from 3 | # revision-control system data, or from the parent directory name of an 4 | # unpacked source archive. Distribution tarballs contain a pre-generated copy 5 | # of this file. 6 | 7 | import json 8 | 9 | version_json = ''' 10 | { 11 | "date": "2022-11-07T10:04:30+0800", 12 | "dirty": false, 13 | "error": null, 14 | "full-revisionid": "c4f12e915842b03ad720f8480f2c901b5fd91cae", 15 | "version": "0.2.0" 16 | } 17 | ''' # END VERSION_JSON 18 | 19 | 20 | def get_versions(): 21 | return json.loads(version_json) 22 | -------------------------------------------------------------------------------- /lib/pyperclip-1.8.2-py3.11.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: pyperclip 3 | Version: 1.8.2 4 | Summary: A cross-platform clipboard module for Python. (Only handles plain text for now.) 5 | Home-page: https://github.com/asweigart/pyperclip 6 | Author: Al Sweigart 7 | Author-email: al@inventwithpython.com 8 | License: BSD 9 | Keywords: clipboard copy paste clip xsel xclip 10 | Classifier: Development Status :: 5 - Production/Stable 11 | Classifier: Environment :: Win32 (MS Windows) 12 | Classifier: Environment :: X11 Applications 13 | Classifier: Environment :: MacOS X 14 | Classifier: Intended Audience :: Developers 15 | Classifier: License :: OSI Approved :: BSD License 16 | Classifier: Operating System :: OS Independent 17 | Classifier: Programming Language :: Python 18 | Classifier: Programming Language :: Python :: 2 19 | Classifier: Programming Language :: Python :: 2.6 20 | Classifier: Programming Language :: Python :: 2.7 21 | Classifier: Programming Language :: Python :: 3 22 | Classifier: Programming Language :: Python :: 3.1 23 | Classifier: Programming Language :: Python :: 3.2 24 | Classifier: Programming Language :: Python :: 3.3 25 | Classifier: Programming Language :: Python :: 3.4 26 | Classifier: Programming Language :: Python :: 3.5 27 | Classifier: Programming Language :: Python :: 3.6 28 | Classifier: Programming Language :: Python :: 3.7 29 | Classifier: Programming Language :: Python :: 3.8 30 | Classifier: Programming Language :: Python :: 3.9 31 | License-File: LICENSE.txt 32 | License-File: AUTHORS.txt 33 | 34 | Pyperclip is a cross-platform Python module for copy and paste clipboard functions. It works with Python 2 and 3. 35 | 36 | Install on Windows: `pip install pyperclip` 37 | 38 | Install on Linux/macOS: `pip3 install pyperclip` 39 | 40 | Al Sweigart al@inventwithpython.com 41 | BSD License 42 | 43 | Example Usage 44 | ============= 45 | 46 | >>> import pyperclip 47 | >>> pyperclip.copy('The text to be copied to the clipboard.') 48 | >>> pyperclip.paste() 49 | 'The text to be copied to the clipboard.' 50 | 51 | 52 | Currently only handles plaintext. 53 | 54 | On Windows, no additional modules are needed. 55 | 56 | On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os. 57 | 58 | On Linux, this module makes use of the xclip or xsel commands, which should come with the os. Otherwise run "sudo apt-get install xclip" or "sudo apt-get install xsel" (Note: xsel does not always seem to work.) 59 | 60 | Otherwise on Linux, you will need the gtk or PyQt4 modules installed. 61 | -------------------------------------------------------------------------------- /lib/pyperclip-1.8.2-py3.11.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | AUTHORS.txt 2 | CHANGES.txt 3 | LICENSE.txt 4 | MANIFEST.in 5 | README.md 6 | setup.cfg 7 | setup.py 8 | docs/Makefile 9 | docs/conf.py 10 | docs/index.rst 11 | docs/make.bat 12 | src/pyperclip/__init__.py 13 | src/pyperclip/__main__.py 14 | src/pyperclip.egg-info/PKG-INFO 15 | src/pyperclip.egg-info/SOURCES.txt 16 | src/pyperclip.egg-info/dependency_links.txt 17 | src/pyperclip.egg-info/top_level.txt 18 | tests/test_pyperclip.py -------------------------------------------------------------------------------- /lib/pyperclip-1.8.2-py3.11.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/pyperclip-1.8.2-py3.11.egg-info/installed-files.txt: -------------------------------------------------------------------------------- 1 | ..\pyperclip\__init__.py 2 | ..\pyperclip\__main__.py 3 | ..\pyperclip\__pycache__\__init__.cpython-311.pyc 4 | ..\pyperclip\__pycache__\__main__.cpython-311.pyc 5 | PKG-INFO 6 | SOURCES.txt 7 | dependency_links.txt 8 | top_level.txt 9 | -------------------------------------------------------------------------------- /lib/pyperclip-1.8.2-py3.11.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pyperclip 2 | -------------------------------------------------------------------------------- /lib/pyperclip/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pyperclip 3 | 4 | A cross-platform clipboard module for Python, with copy & paste functions for plain text. 5 | By Al Sweigart al@inventwithpython.com 6 | BSD License 7 | 8 | Usage: 9 | import pyperclip 10 | pyperclip.copy('The text to be copied to the clipboard.') 11 | spam = pyperclip.paste() 12 | 13 | if not pyperclip.is_available(): 14 | print("Copy functionality unavailable!") 15 | 16 | On Windows, no additional modules are needed. 17 | On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli 18 | commands. (These commands should come with OS X.). 19 | On Linux, install xclip, xsel, or wl-clipboard (for "wayland" sessions) via package manager. 20 | For example, in Debian: 21 | sudo apt-get install xclip 22 | sudo apt-get install xsel 23 | sudo apt-get install wl-clipboard 24 | 25 | Otherwise on Linux, you will need the gtk or PyQt5/PyQt4 modules installed. 26 | 27 | gtk and PyQt4 modules are not available for Python 3, 28 | and this module does not work with PyGObject yet. 29 | 30 | Note: There seems to be a way to get gtk on Python 3, according to: 31 | https://askubuntu.com/questions/697397/python3-is-not-supporting-gtk-module 32 | 33 | Cygwin is currently not supported. 34 | 35 | Security Note: This module runs programs with these names: 36 | - which 37 | - where 38 | - pbcopy 39 | - pbpaste 40 | - xclip 41 | - xsel 42 | - wl-copy/wl-paste 43 | - klipper 44 | - qdbus 45 | A malicious user could rename or add programs with these names, tricking 46 | Pyperclip into running them with whatever permissions the Python process has. 47 | 48 | """ 49 | __version__ = '1.8.2' 50 | 51 | import contextlib 52 | import ctypes 53 | import os 54 | import platform 55 | import subprocess 56 | import sys 57 | import time 58 | import warnings 59 | 60 | from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar 61 | 62 | 63 | # `import PyQt4` sys.exit()s if DISPLAY is not in the environment. 64 | # Thus, we need to detect the presence of $DISPLAY manually 65 | # and not load PyQt4 if it is absent. 66 | HAS_DISPLAY = os.getenv("DISPLAY", False) 67 | 68 | EXCEPT_MSG = """ 69 | Pyperclip could not find a copy/paste mechanism for your system. 70 | For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error """ 71 | 72 | PY2 = sys.version_info[0] == 2 73 | 74 | STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode. 75 | 76 | ENCODING = 'utf-8' 77 | 78 | try: 79 | from shutil import which as _executable_exists 80 | except ImportError: 81 | # The "which" unix command finds where a command is. 82 | if platform.system() == 'Windows': 83 | WHICH_CMD = 'where' 84 | else: 85 | WHICH_CMD = 'which' 86 | 87 | def _executable_exists(name): 88 | return subprocess.call([WHICH_CMD, name], 89 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 90 | 91 | 92 | 93 | # Exceptions 94 | class PyperclipException(RuntimeError): 95 | pass 96 | 97 | class PyperclipWindowsException(PyperclipException): 98 | def __init__(self, message): 99 | message += " (%s)" % ctypes.WinError() 100 | super(PyperclipWindowsException, self).__init__(message) 101 | 102 | class PyperclipTimeoutException(PyperclipException): 103 | pass 104 | 105 | def _stringifyText(text): 106 | if PY2: 107 | acceptedTypes = (unicode, str, int, float, bool) 108 | else: 109 | acceptedTypes = (str, int, float, bool) 110 | if not isinstance(text, acceptedTypes): 111 | raise PyperclipException('only str, int, float, and bool values can be copied to the clipboard, not %s' % (text.__class__.__name__)) 112 | return STR_OR_UNICODE(text) 113 | 114 | 115 | def init_osx_pbcopy_clipboard(): 116 | 117 | def copy_osx_pbcopy(text): 118 | text = _stringifyText(text) # Converts non-str values to str. 119 | p = subprocess.Popen(['pbcopy', 'w'], 120 | stdin=subprocess.PIPE, close_fds=True) 121 | p.communicate(input=text.encode(ENCODING)) 122 | 123 | def paste_osx_pbcopy(): 124 | p = subprocess.Popen(['pbpaste', 'r'], 125 | stdout=subprocess.PIPE, close_fds=True) 126 | stdout, stderr = p.communicate() 127 | return stdout.decode(ENCODING) 128 | 129 | return copy_osx_pbcopy, paste_osx_pbcopy 130 | 131 | 132 | def init_osx_pyobjc_clipboard(): 133 | def copy_osx_pyobjc(text): 134 | '''Copy string argument to clipboard''' 135 | text = _stringifyText(text) # Converts non-str values to str. 136 | newStr = Foundation.NSString.stringWithString_(text).nsstring() 137 | newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding) 138 | board = AppKit.NSPasteboard.generalPasteboard() 139 | board.declareTypes_owner_([AppKit.NSStringPboardType], None) 140 | board.setData_forType_(newData, AppKit.NSStringPboardType) 141 | 142 | def paste_osx_pyobjc(): 143 | "Returns contents of clipboard" 144 | board = AppKit.NSPasteboard.generalPasteboard() 145 | content = board.stringForType_(AppKit.NSStringPboardType) 146 | return content 147 | 148 | return copy_osx_pyobjc, paste_osx_pyobjc 149 | 150 | 151 | def init_gtk_clipboard(): 152 | global gtk 153 | import gtk 154 | 155 | def copy_gtk(text): 156 | global cb 157 | text = _stringifyText(text) # Converts non-str values to str. 158 | cb = gtk.Clipboard() 159 | cb.set_text(text) 160 | cb.store() 161 | 162 | def paste_gtk(): 163 | clipboardContents = gtk.Clipboard().wait_for_text() 164 | # for python 2, returns None if the clipboard is blank. 165 | if clipboardContents is None: 166 | return '' 167 | else: 168 | return clipboardContents 169 | 170 | return copy_gtk, paste_gtk 171 | 172 | 173 | def init_qt_clipboard(): 174 | global QApplication 175 | # $DISPLAY should exist 176 | 177 | # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 178 | try: 179 | from qtpy.QtWidgets import QApplication 180 | except: 181 | try: 182 | from PyQt5.QtWidgets import QApplication 183 | except: 184 | from PyQt4.QtGui import QApplication 185 | 186 | app = QApplication.instance() 187 | if app is None: 188 | app = QApplication([]) 189 | 190 | def copy_qt(text): 191 | text = _stringifyText(text) # Converts non-str values to str. 192 | cb = app.clipboard() 193 | cb.setText(text) 194 | 195 | def paste_qt(): 196 | cb = app.clipboard() 197 | return STR_OR_UNICODE(cb.text()) 198 | 199 | return copy_qt, paste_qt 200 | 201 | 202 | def init_xclip_clipboard(): 203 | DEFAULT_SELECTION='c' 204 | PRIMARY_SELECTION='p' 205 | 206 | def copy_xclip(text, primary=False): 207 | text = _stringifyText(text) # Converts non-str values to str. 208 | selection=DEFAULT_SELECTION 209 | if primary: 210 | selection=PRIMARY_SELECTION 211 | p = subprocess.Popen(['xclip', '-selection', selection], 212 | stdin=subprocess.PIPE, close_fds=True) 213 | p.communicate(input=text.encode(ENCODING)) 214 | 215 | def paste_xclip(primary=False): 216 | selection=DEFAULT_SELECTION 217 | if primary: 218 | selection=PRIMARY_SELECTION 219 | p = subprocess.Popen(['xclip', '-selection', selection, '-o'], 220 | stdout=subprocess.PIPE, 221 | stderr=subprocess.PIPE, 222 | close_fds=True) 223 | stdout, stderr = p.communicate() 224 | # Intentionally ignore extraneous output on stderr when clipboard is empty 225 | return stdout.decode(ENCODING) 226 | 227 | return copy_xclip, paste_xclip 228 | 229 | 230 | def init_xsel_clipboard(): 231 | DEFAULT_SELECTION='-b' 232 | PRIMARY_SELECTION='-p' 233 | 234 | def copy_xsel(text, primary=False): 235 | text = _stringifyText(text) # Converts non-str values to str. 236 | selection_flag = DEFAULT_SELECTION 237 | if primary: 238 | selection_flag = PRIMARY_SELECTION 239 | p = subprocess.Popen(['xsel', selection_flag, '-i'], 240 | stdin=subprocess.PIPE, close_fds=True) 241 | p.communicate(input=text.encode(ENCODING)) 242 | 243 | def paste_xsel(primary=False): 244 | selection_flag = DEFAULT_SELECTION 245 | if primary: 246 | selection_flag = PRIMARY_SELECTION 247 | p = subprocess.Popen(['xsel', selection_flag, '-o'], 248 | stdout=subprocess.PIPE, close_fds=True) 249 | stdout, stderr = p.communicate() 250 | return stdout.decode(ENCODING) 251 | 252 | return copy_xsel, paste_xsel 253 | 254 | 255 | def init_wl_clipboard(): 256 | PRIMARY_SELECTION = "-p" 257 | 258 | def copy_wl(text, primary=False): 259 | text = _stringifyText(text) # Converts non-str values to str. 260 | args = ["wl-copy"] 261 | if primary: 262 | args.append(PRIMARY_SELECTION) 263 | if not text: 264 | args.append('--clear') 265 | subprocess.check_call(args, close_fds=True) 266 | else: 267 | pass 268 | p = subprocess.Popen(args, stdin=subprocess.PIPE, close_fds=True) 269 | p.communicate(input=text.encode(ENCODING)) 270 | 271 | def paste_wl(primary=False): 272 | args = ["wl-paste", "-n"] 273 | if primary: 274 | args.append(PRIMARY_SELECTION) 275 | p = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True) 276 | stdout, _stderr = p.communicate() 277 | return stdout.decode(ENCODING) 278 | 279 | return copy_wl, paste_wl 280 | 281 | 282 | def init_klipper_clipboard(): 283 | def copy_klipper(text): 284 | text = _stringifyText(text) # Converts non-str values to str. 285 | p = subprocess.Popen( 286 | ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents', 287 | text.encode(ENCODING)], 288 | stdin=subprocess.PIPE, close_fds=True) 289 | p.communicate(input=None) 290 | 291 | def paste_klipper(): 292 | p = subprocess.Popen( 293 | ['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'], 294 | stdout=subprocess.PIPE, close_fds=True) 295 | stdout, stderr = p.communicate() 296 | 297 | # Workaround for https://bugs.kde.org/show_bug.cgi?id=342874 298 | # TODO: https://github.com/asweigart/pyperclip/issues/43 299 | clipboardContents = stdout.decode(ENCODING) 300 | # even if blank, Klipper will append a newline at the end 301 | assert len(clipboardContents) > 0 302 | # make sure that newline is there 303 | assert clipboardContents.endswith('\n') 304 | if clipboardContents.endswith('\n'): 305 | clipboardContents = clipboardContents[:-1] 306 | return clipboardContents 307 | 308 | return copy_klipper, paste_klipper 309 | 310 | 311 | def init_dev_clipboard_clipboard(): 312 | def copy_dev_clipboard(text): 313 | text = _stringifyText(text) # Converts non-str values to str. 314 | if text == '': 315 | warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.') 316 | if '\r' in text: 317 | warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.') 318 | 319 | fo = open('/dev/clipboard', 'wt') 320 | fo.write(text) 321 | fo.close() 322 | 323 | def paste_dev_clipboard(): 324 | fo = open('/dev/clipboard', 'rt') 325 | content = fo.read() 326 | fo.close() 327 | return content 328 | 329 | return copy_dev_clipboard, paste_dev_clipboard 330 | 331 | 332 | def init_no_clipboard(): 333 | class ClipboardUnavailable(object): 334 | 335 | def __call__(self, *args, **kwargs): 336 | raise PyperclipException(EXCEPT_MSG) 337 | 338 | if PY2: 339 | def __nonzero__(self): 340 | return False 341 | else: 342 | def __bool__(self): 343 | return False 344 | 345 | return ClipboardUnavailable(), ClipboardUnavailable() 346 | 347 | 348 | 349 | 350 | # Windows-related clipboard functions: 351 | class CheckedCall(object): 352 | def __init__(self, f): 353 | super(CheckedCall, self).__setattr__("f", f) 354 | 355 | def __call__(self, *args): 356 | ret = self.f(*args) 357 | if not ret and get_errno(): 358 | raise PyperclipWindowsException("Error calling " + self.f.__name__) 359 | return ret 360 | 361 | def __setattr__(self, key, value): 362 | setattr(self.f, key, value) 363 | 364 | 365 | def init_windows_clipboard(): 366 | global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE 367 | from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, 368 | HINSTANCE, HMENU, BOOL, UINT, HANDLE) 369 | 370 | windll = ctypes.windll 371 | msvcrt = ctypes.CDLL('msvcrt') 372 | 373 | safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA) 374 | safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT, 375 | INT, INT, HWND, HMENU, HINSTANCE, LPVOID] 376 | safeCreateWindowExA.restype = HWND 377 | 378 | safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow) 379 | safeDestroyWindow.argtypes = [HWND] 380 | safeDestroyWindow.restype = BOOL 381 | 382 | OpenClipboard = windll.user32.OpenClipboard 383 | OpenClipboard.argtypes = [HWND] 384 | OpenClipboard.restype = BOOL 385 | 386 | safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard) 387 | safeCloseClipboard.argtypes = [] 388 | safeCloseClipboard.restype = BOOL 389 | 390 | safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard) 391 | safeEmptyClipboard.argtypes = [] 392 | safeEmptyClipboard.restype = BOOL 393 | 394 | safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData) 395 | safeGetClipboardData.argtypes = [UINT] 396 | safeGetClipboardData.restype = HANDLE 397 | 398 | safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData) 399 | safeSetClipboardData.argtypes = [UINT, HANDLE] 400 | safeSetClipboardData.restype = HANDLE 401 | 402 | safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc) 403 | safeGlobalAlloc.argtypes = [UINT, c_size_t] 404 | safeGlobalAlloc.restype = HGLOBAL 405 | 406 | safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock) 407 | safeGlobalLock.argtypes = [HGLOBAL] 408 | safeGlobalLock.restype = LPVOID 409 | 410 | safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock) 411 | safeGlobalUnlock.argtypes = [HGLOBAL] 412 | safeGlobalUnlock.restype = BOOL 413 | 414 | wcslen = CheckedCall(msvcrt.wcslen) 415 | wcslen.argtypes = [c_wchar_p] 416 | wcslen.restype = UINT 417 | 418 | GMEM_MOVEABLE = 0x0002 419 | CF_UNICODETEXT = 13 420 | 421 | @contextlib.contextmanager 422 | def window(): 423 | """ 424 | Context that provides a valid Windows hwnd. 425 | """ 426 | # we really just need the hwnd, so setting "STATIC" 427 | # as predefined lpClass is just fine. 428 | hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0, 429 | None, None, None, None) 430 | try: 431 | yield hwnd 432 | finally: 433 | safeDestroyWindow(hwnd) 434 | 435 | @contextlib.contextmanager 436 | def clipboard(hwnd): 437 | """ 438 | Context manager that opens the clipboard and prevents 439 | other applications from modifying the clipboard content. 440 | """ 441 | # We may not get the clipboard handle immediately because 442 | # some other application is accessing it (?) 443 | # We try for at least 500ms to get the clipboard. 444 | t = time.time() + 0.5 445 | success = False 446 | while time.time() < t: 447 | success = OpenClipboard(hwnd) 448 | if success: 449 | break 450 | time.sleep(0.01) 451 | if not success: 452 | raise PyperclipWindowsException("Error calling OpenClipboard") 453 | 454 | try: 455 | yield 456 | finally: 457 | safeCloseClipboard() 458 | 459 | def copy_windows(text): 460 | # This function is heavily based on 461 | # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard 462 | 463 | text = _stringifyText(text) # Converts non-str values to str. 464 | 465 | with window() as hwnd: 466 | # http://msdn.com/ms649048 467 | # If an application calls OpenClipboard with hwnd set to NULL, 468 | # EmptyClipboard sets the clipboard owner to NULL; 469 | # this causes SetClipboardData to fail. 470 | # => We need a valid hwnd to copy something. 471 | with clipboard(hwnd): 472 | safeEmptyClipboard() 473 | 474 | if text: 475 | # http://msdn.com/ms649051 476 | # If the hMem parameter identifies a memory object, 477 | # the object must have been allocated using the 478 | # function with the GMEM_MOVEABLE flag. 479 | count = wcslen(text) + 1 480 | handle = safeGlobalAlloc(GMEM_MOVEABLE, 481 | count * sizeof(c_wchar)) 482 | locked_handle = safeGlobalLock(handle) 483 | 484 | ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar)) 485 | 486 | safeGlobalUnlock(handle) 487 | safeSetClipboardData(CF_UNICODETEXT, handle) 488 | 489 | def paste_windows(): 490 | with clipboard(None): 491 | handle = safeGetClipboardData(CF_UNICODETEXT) 492 | if not handle: 493 | # GetClipboardData may return NULL with errno == NO_ERROR 494 | # if the clipboard is empty. 495 | # (Also, it may return a handle to an empty buffer, 496 | # but technically that's not empty) 497 | return "" 498 | return c_wchar_p(handle).value 499 | 500 | return copy_windows, paste_windows 501 | 502 | 503 | def init_wsl_clipboard(): 504 | def copy_wsl(text): 505 | text = _stringifyText(text) # Converts non-str values to str. 506 | p = subprocess.Popen(['clip.exe'], 507 | stdin=subprocess.PIPE, close_fds=True) 508 | p.communicate(input=text.encode(ENCODING)) 509 | 510 | def paste_wsl(): 511 | p = subprocess.Popen(['powershell.exe', '-command', 'Get-Clipboard'], 512 | stdout=subprocess.PIPE, 513 | stderr=subprocess.PIPE, 514 | close_fds=True) 515 | stdout, stderr = p.communicate() 516 | # WSL appends "\r\n" to the contents. 517 | return stdout[:-2].decode(ENCODING) 518 | 519 | return copy_wsl, paste_wsl 520 | 521 | 522 | # Automatic detection of clipboard mechanisms and importing is done in deteremine_clipboard(): 523 | def determine_clipboard(): 524 | ''' 525 | Determine the OS/platform and set the copy() and paste() functions 526 | accordingly. 527 | ''' 528 | 529 | global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5 530 | 531 | # Setup for the CYGWIN platform: 532 | if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1' 533 | # FIXME: pyperclip currently does not support Cygwin, 534 | # see https://github.com/asweigart/pyperclip/issues/55 535 | if os.path.exists('/dev/clipboard'): 536 | warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55') 537 | return init_dev_clipboard_clipboard() 538 | 539 | # Setup for the WINDOWS platform: 540 | elif os.name == 'nt' or platform.system() == 'Windows': 541 | return init_windows_clipboard() 542 | 543 | if platform.system() == 'Linux' and os.path.isfile('/proc/version'): 544 | with open('/proc/version', 'r') as f: 545 | if "microsoft" in f.read().lower(): 546 | return init_wsl_clipboard() 547 | 548 | # Setup for the MAC OS X platform: 549 | if os.name == 'mac' or platform.system() == 'Darwin': 550 | try: 551 | import Foundation # check if pyobjc is installed 552 | import AppKit 553 | except ImportError: 554 | return init_osx_pbcopy_clipboard() 555 | else: 556 | return init_osx_pyobjc_clipboard() 557 | 558 | # Setup for the LINUX platform: 559 | if HAS_DISPLAY: 560 | try: 561 | import gtk # check if gtk is installed 562 | except ImportError: 563 | pass # We want to fail fast for all non-ImportError exceptions. 564 | else: 565 | return init_gtk_clipboard() 566 | 567 | if ( 568 | os.environ.get("WAYLAND_DISPLAY") and 569 | _executable_exists("wl-copy") 570 | ): 571 | return init_wl_clipboard() 572 | if _executable_exists("xsel"): 573 | return init_xsel_clipboard() 574 | if _executable_exists("xclip"): 575 | return init_xclip_clipboard() 576 | if _executable_exists("klipper") and _executable_exists("qdbus"): 577 | return init_klipper_clipboard() 578 | 579 | try: 580 | # qtpy is a small abstraction layer that lets you write applications using a single api call to either PyQt or PySide. 581 | # https://pypi.python.org/pypi/QtPy 582 | import qtpy # check if qtpy is installed 583 | except ImportError: 584 | # If qtpy isn't installed, fall back on importing PyQt4. 585 | try: 586 | import PyQt5 # check if PyQt5 is installed 587 | except ImportError: 588 | try: 589 | import PyQt4 # check if PyQt4 is installed 590 | except ImportError: 591 | pass # We want to fail fast for all non-ImportError exceptions. 592 | else: 593 | return init_qt_clipboard() 594 | else: 595 | return init_qt_clipboard() 596 | else: 597 | return init_qt_clipboard() 598 | 599 | 600 | return init_no_clipboard() 601 | 602 | 603 | def set_clipboard(clipboard): 604 | ''' 605 | Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how 606 | the copy() and paste() functions interact with the operating system to 607 | implement the copy/paste feature. The clipboard parameter must be one of: 608 | - pbcopy 609 | - pbobjc (default on Mac OS X) 610 | - gtk 611 | - qt 612 | - xclip 613 | - xsel 614 | - klipper 615 | - windows (default on Windows) 616 | - no (this is what is set when no clipboard mechanism can be found) 617 | ''' 618 | global copy, paste 619 | 620 | clipboard_types = { 621 | "pbcopy": init_osx_pbcopy_clipboard, 622 | "pyobjc": init_osx_pyobjc_clipboard, 623 | "gtk": init_gtk_clipboard, 624 | "qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' 625 | "xclip": init_xclip_clipboard, 626 | "xsel": init_xsel_clipboard, 627 | "wl-clipboard": init_wl_clipboard, 628 | "klipper": init_klipper_clipboard, 629 | "windows": init_windows_clipboard, 630 | "no": init_no_clipboard, 631 | } 632 | 633 | if clipboard not in clipboard_types: 634 | raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()]))) 635 | 636 | # Sets pyperclip's copy() and paste() functions: 637 | copy, paste = clipboard_types[clipboard]() 638 | 639 | 640 | def lazy_load_stub_copy(text): 641 | ''' 642 | A stub function for copy(), which will load the real copy() function when 643 | called so that the real copy() function is used for later calls. 644 | 645 | This allows users to import pyperclip without having determine_clipboard() 646 | automatically run, which will automatically select a clipboard mechanism. 647 | This could be a problem if it selects, say, the memory-heavy PyQt4 module 648 | but the user was just going to immediately call set_clipboard() to use a 649 | different clipboard mechanism. 650 | 651 | The lazy loading this stub function implements gives the user a chance to 652 | call set_clipboard() to pick another clipboard mechanism. Or, if the user 653 | simply calls copy() or paste() without calling set_clipboard() first, 654 | will fall back on whatever clipboard mechanism that determine_clipboard() 655 | automatically chooses. 656 | ''' 657 | global copy, paste 658 | copy, paste = determine_clipboard() 659 | return copy(text) 660 | 661 | 662 | def lazy_load_stub_paste(): 663 | ''' 664 | A stub function for paste(), which will load the real paste() function when 665 | called so that the real paste() function is used for later calls. 666 | 667 | This allows users to import pyperclip without having determine_clipboard() 668 | automatically run, which will automatically select a clipboard mechanism. 669 | This could be a problem if it selects, say, the memory-heavy PyQt4 module 670 | but the user was just going to immediately call set_clipboard() to use a 671 | different clipboard mechanism. 672 | 673 | The lazy loading this stub function implements gives the user a chance to 674 | call set_clipboard() to pick another clipboard mechanism. Or, if the user 675 | simply calls copy() or paste() without calling set_clipboard() first, 676 | will fall back on whatever clipboard mechanism that determine_clipboard() 677 | automatically chooses. 678 | ''' 679 | global copy, paste 680 | copy, paste = determine_clipboard() 681 | return paste() 682 | 683 | 684 | def is_available(): 685 | return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste 686 | 687 | 688 | # Initially, copy() and paste() are set to lazy loading wrappers which will 689 | # set `copy` and `paste` to real functions the first time they're used, unless 690 | # set_clipboard() or determine_clipboard() is called first. 691 | copy, paste = lazy_load_stub_copy, lazy_load_stub_paste 692 | 693 | 694 | 695 | def waitForPaste(timeout=None): 696 | """This function call blocks until a non-empty text string exists on the 697 | clipboard. It returns this text. 698 | 699 | This function raises PyperclipTimeoutException if timeout was set to 700 | a number of seconds that has elapsed without non-empty text being put on 701 | the clipboard.""" 702 | startTime = time.time() 703 | while True: 704 | clipboardText = paste() 705 | if clipboardText != '': 706 | return clipboardText 707 | time.sleep(0.01) 708 | 709 | if timeout is not None and time.time() > startTime + timeout: 710 | raise PyperclipTimeoutException('waitForPaste() timed out after ' + str(timeout) + ' seconds.') 711 | 712 | 713 | def waitForNewPaste(timeout=None): 714 | """This function call blocks until a new text string exists on the 715 | clipboard that is different from the text that was there when the function 716 | was first called. It returns this text. 717 | 718 | This function raises PyperclipTimeoutException if timeout was set to 719 | a number of seconds that has elapsed without non-empty text being put on 720 | the clipboard.""" 721 | startTime = time.time() 722 | originalText = paste() 723 | while True: 724 | currentText = paste() 725 | if currentText != originalText: 726 | return currentText 727 | time.sleep(0.01) 728 | 729 | if timeout is not None and time.time() > startTime + timeout: 730 | raise PyperclipTimeoutException('waitForNewPaste() timed out after ' + str(timeout) + ' seconds.') 731 | 732 | 733 | __all__ = ['copy', 'paste', 'waitForPaste', 'waitForNewPaste', 'set_clipboard', 'determine_clipboard'] 734 | 735 | 736 | -------------------------------------------------------------------------------- /lib/pyperclip/__main__.py: -------------------------------------------------------------------------------- 1 | import pyperclip 2 | import sys 3 | 4 | if len(sys.argv) > 1 and sys.argv[1] in ('-c', '--copy'): 5 | if len(sys.argv) > 2: 6 | pyperclip.copy(sys.argv[2]) 7 | else: 8 | pyperclip.copy(sys.stdin.read()) 9 | elif len(sys.argv) > 1 and sys.argv[1] in ('-p', '--paste'): 10 | sys.stdout.write(pyperclip.paste()) 11 | else: 12 | print('Usage: python -m pyperclip [-c | --copy] [text_to_copy] | [-p | --paste]') 13 | print() 14 | print('If a text_to_copy argument is provided, it is copied to the') 15 | print('clipboard. Otherwise, the stdin stream is copied to the') 16 | print('clipboard. (If reading this in from the keyboard, press') 17 | print('CTRL-Z on Windows or CTRL-D on Linux/macOS to stop.') 18 | print('When pasting, the clipboard will be written to stdout.') -------------------------------------------------------------------------------- /lib/pyperclip/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/pyperclip/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /lib/pyperclip/__pycache__/__main__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fermiz/Flow.Launcher.Snippets/ffbb92271c62197f47336cc99709a5cda72bbbd4/lib/pyperclip/__pycache__/__main__.cpython-311.pyc -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Snippets in Flowlauncher. 4 | 5 | Simple plugin to save key/value snippets and copy to clipboard. 6 | """ 7 | 8 | import os 9 | import sys 10 | import sqlite3 11 | 12 | # add plugin to local PATH 13 | parentFolderPath = os.path.abspath(os.path.dirname(__file__)) 14 | sys.path.append(parentFolderPath) 15 | sys.path.append(os.path.join(parentFolderPath, 'lib')) 16 | sys.path.append(os.path.join(parentFolderPath, 'plugin')) 17 | 18 | from plugin.snippets import Snippets 19 | 20 | if __name__ == "__main__": 21 | pluginPath = os.path.abspath(os.path.dirname(parentFolderPath)) 22 | mainPath = os.path.abspath(os.path.dirname(pluginPath)) 23 | snippetSettingsPath = mainPath + "/Settings/Plugins/Flow.Launcher.Snippets" 24 | 25 | dbName = snippetSettingsPath + "/snippets.db" 26 | 27 | if not (os.path.exists(snippetSettingsPath)): 28 | os.makedirs(snippetSettingsPath) 29 | dbName = snippetSettingsPath + "/snippets.db" 30 | conn = sqlite3.connect(dbName) 31 | cursor = conn.cursor() 32 | cursor.execute('''CREATE TABLE IF NOT EXISTS snippets (key TEXT PRIMARY KEY, value TEXT)''') 33 | conn.commit() 34 | conn.close() 35 | 36 | Snippets(dbName=dbName) 37 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID":"51dd36d2-9824-4977-8e15-294dfbd7961e", 3 | "ActionKeyword":"sp", 4 | "Name":"Snippets", 5 | "Description":"Simple plugin to save key/value snippets and copy to clipboard", 6 | "Author":"Fermi Shuangqi Li", 7 | "Version":"1.1.1", 8 | "Language":"python", 9 | "Website":"https://github.com/Fermiz/Flow.Launcher.Snippets", 10 | "IcoPath":"assets\\snippets.png", 11 | "ExecuteFileName":"main.py" 12 | } 13 | -------------------------------------------------------------------------------- /plugin/snippets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Snippets in Flowlauncher. 4 | 5 | Simple plugin to save key/value snippets and copy to clipboard. 6 | """ 7 | 8 | from flowlauncher import FlowLauncher, FlowLauncherAPI 9 | import sys 10 | import ctypes 11 | import sqlite3 12 | import pyperclip 13 | 14 | def getValue(dbName, key): 15 | value = {} 16 | conn = sqlite3.connect(dbName) 17 | cursor = conn.cursor() 18 | 19 | cursor.execute("SELECT value FROM snippets WHERE key=?", (key,)) 20 | result = cursor.fetchone() 21 | if result: 22 | value = result[0] 23 | else: 24 | value = "" 25 | conn.close() 26 | return value 27 | 28 | def saveValue(dbName, key, value): 29 | conn = sqlite3.connect(dbName) 30 | cursor = conn.cursor() 31 | cursor.execute("INSERT OR REPLACE INTO snippets (key, value) VALUES (?, ?)", (key, value)) 32 | conn.commit() 33 | conn.close() 34 | copy2clip(dbName, value) 35 | 36 | def deleteValue(dbName, key): 37 | conn = sqlite3.connect(dbName) 38 | cursor = conn.cursor() 39 | cursor.execute("DELETE FROM snippets WHERE key=?", (key,)) 40 | conn.commit() 41 | conn.close() 42 | 43 | def copy2clip(dbName, value): 44 | """Put snippets into clipboard.""" 45 | pyperclip.copy(value) 46 | 47 | class Snippets(FlowLauncher): 48 | 49 | def __init__(self, dbName): 50 | self.dbName = dbName 51 | super().__init__() 52 | 53 | def query(self, query): 54 | results = [] 55 | try: 56 | if len(query.strip()) != 0: 57 | if ':' in query.strip(): 58 | key, value = query.strip().split(':', 1) 59 | results.append({ 60 | "Title": "Save Code Snippet", 61 | "SubTitle": "Key=" + key + ", Value=" + value, 62 | "IcoPath": "assets/snippets.png", 63 | "ContextData": [key, value], 64 | "JsonRPCAction": {"method": "save", "parameters": [key.strip(), value.strip()], }}) 65 | else: 66 | value = getValue(self.dbName, query.strip()) 67 | if len(value) != 0: 68 | results.append({ 69 | "Title": "⭐ " + query.strip(), 70 | "SubTitle": "[Snippet] Copy to clipboard", 71 | "IcoPath": "assets/snippets.png", 72 | "ContextData": [query.strip(), value], 73 | "JsonRPCAction": {"method": "copy", "parameters": [value], }}) 74 | else: 75 | clipboardValue = pyperclip.paste() 76 | displayValue = (clipboardValue[:16] + "...") if len(clipboardValue) > 16 else clipboardValue 77 | if len(clipboardValue) != 0: 78 | results.append({ 79 | "Title": "Save from clipboard", 80 | "SubTitle": "Key=" + query.strip() + ", Value=" + displayValue, 81 | "IcoPath": "assets/snippets.png", 82 | "ContextData": [query.strip(), clipboardValue], 83 | "JsonRPCAction": {"method": "save", "parameters": [query.strip(), clipboardValue], }}) 84 | 85 | except: 86 | value = sys.exc_info() 87 | print('Error opening %s: %s' % (value.filename, value.strerror)) 88 | results.append({ 89 | "Title": "Code Snippets Error", 90 | "SubTitle": "Please, Verify and try again", 91 | "IcoPath": "assets/snippets.png", "ContextData": "ctxData"}) 92 | 93 | return results 94 | 95 | def context_menu(self, data): 96 | results = [] 97 | results.append({ 98 | "Title": "Delete Code Snippet", 99 | "SubTitle": "Key=" + data[0] + ", Value=" + data[1], 100 | "IcoPath": "assets/snippets.png", 101 | "JsonRPCAction": {"method": "delete", "parameters": [data[0]], }}) 102 | results.append({ 103 | "Title": "Save/Update Code Snippet", 104 | "SubTitle": "Key=" + data[0] + ", Value=" + data[1], 105 | "IcoPath": "assets/snippets.png", 106 | "JsonRPCAction": {"method": "save", "parameters": [data[0], data[1]], }}) 107 | return results 108 | 109 | def copy(self, value): 110 | """Copy Snippets to clipboard.""" 111 | copy2clip(self.dbName, value) 112 | 113 | def save(self, key, value): 114 | """Save Snippets into sqlite""" 115 | saveValue(self.dbName, key.strip(), value.strip()) 116 | 117 | def delete(self, key): 118 | """Delete Snippets from sqlite""" 119 | deleteValue(self.dbName, key.strip()) 120 | 121 | if __name__ == "__main__": 122 | Snippets() 123 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flowlauncher --------------------------------------------------------------------------------