Plugin for sublime text editor that automatically creates templates, packages, and modules
リビジョン | e090a13165e2bc9314f7556428af125317111825 (tree) |
---|---|
日時 | 2021-04-21 02:15:52 |
作者 | Vladimir Markin <v.o.markin221@gmai...> |
コミッター | Vladimir Markin |
initialize
@@ -0,0 +1,26 @@ | ||
1 | +[ | |
2 | + { | |
3 | + "caption": "FCF", | |
4 | + "id": "fcf", | |
5 | + "children": | |
6 | + [ | |
7 | + { "command": "fcf_new_template", "caption": "New template", "args":{ "template": "template", "files": [], "dirs": []} }, | |
8 | + { "command": "fcf_new_template", "caption": "New template file", "args":{ "template": "template-file", "files": [], "dirs": []} }, | |
9 | + { "command": "fcf_new_template", "caption": "New wrapper file", "args":{ "template": "wrapper", "files": [], "dirs": []} }, | |
10 | + { "command": "fcf_new_template", "caption": "New hooks file", "args":{ "template": "hooks", "files": [], "dirs": []} }, | |
11 | + { "command": "fcf_new_template", "caption": "New module file", "args":{ "template": "module", "files": [], "dirs": []} }, | |
12 | + { "command": "fcf_new_template", "caption": "New receive file", "args":{ "template": "receive", "files": [], "dirs": []} }, | |
13 | + { "command": "fcf_new_template", "caption": "New projection file", "args":{ "template": "projection", "files": [], "dirs": []} }, | |
14 | + { "command": "fcf_new_template", "caption": "New package", "args":{ "template": "package", "files": [], "dirs": []} }, | |
15 | + | |
16 | + | |
17 | + ] | |
18 | + }, | |
19 | + { | |
20 | + "caption": "FCF: Copy file path", | |
21 | + "id": "fcfgetpath", | |
22 | + "caption": "Copy file path", | |
23 | + "command": "fcf_get_path", | |
24 | + "args": { "files": [], "dirs": []} | |
25 | + } | |
26 | +] | |
\ No newline at end of file |
@@ -0,0 +1,67 @@ | ||
1 | +import sublime | |
2 | +import sublime_plugin | |
3 | +import os | |
4 | +import sys | |
5 | +import subprocess | |
6 | +__dir__ = os.path.dirname(os.path.realpath(__file__)); | |
7 | +sys.path.insert(1, __dir__) | |
8 | +import pyperclip | |
9 | + | |
10 | + | |
11 | +class FcfGetPathCommand(sublime_plugin.TextCommand): | |
12 | + def run(self, edit, **args): | |
13 | + path = None | |
14 | + | |
15 | + try: | |
16 | + if len(args["dirs"]) != 0: | |
17 | + path = args["dirs"][0] | |
18 | + elif len(args["files"]) != 0: | |
19 | + path = args["files"][0] | |
20 | + except: | |
21 | + return | |
22 | + | |
23 | + if path == None: | |
24 | + return | |
25 | + pyperclip.copy(path) | |
26 | + | |
27 | + | |
28 | + | |
29 | + | |
30 | +class FcfNewTemplateCommand(sublime_plugin.TextCommand): | |
31 | + directory = None | |
32 | + args = None | |
33 | + template = None | |
34 | + | |
35 | + def run(self, edit, **args): | |
36 | + try: | |
37 | + self.directory = "" | |
38 | + self.args = {"file": ""} | |
39 | + self.template = args["template"] | |
40 | + if len(args["dirs"]) != 0: | |
41 | + self.directory = args["dirs"][0] | |
42 | + elif len(args["files"]) != 0: | |
43 | + self.directory = os.path.dirname(args["files"][0]) | |
44 | + except: | |
45 | + return; | |
46 | + | |
47 | + if self.directory == None: | |
48 | + return | |
49 | + | |
50 | + | |
51 | + self.view.window().show_input_panel("Enter file name:", "", | |
52 | + self.onDoneInput, None, None) | |
53 | + | |
54 | + def onDoneInput(self, inputString): | |
55 | + self.args["file"] = inputString.strip(); | |
56 | + self.action(); | |
57 | + | |
58 | + | |
59 | + def action(self): | |
60 | + try: | |
61 | + if len(self.directory) == 0: | |
62 | + return | |
63 | + if len(self.args["file"]) == 0: | |
64 | + return | |
65 | + subprocess.Popen(["fcfmngr", "c", self.template, self.args["file"]], cwd=self.directory) | |
66 | + except e: | |
67 | + print(e) |
@@ -0,0 +1,54 @@ | ||
1 | +Metadata-Version: 1.1 | |
2 | +Name: pyperclip | |
3 | +Version: 1.8.0 | |
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 | +Description: Pyperclip is a cross-platform Python module for copy and paste clipboard functions. It works with Python 2 and 3. | |
10 | + | |
11 | + `pip install pyperclip` | |
12 | + | |
13 | + Al Sweigart al@inventwithpython.com | |
14 | + BSD License | |
15 | + | |
16 | + Example Usage | |
17 | + ============= | |
18 | + | |
19 | + >>> import pyperclip | |
20 | + >>> pyperclip.copy('The text to be copied to the clipboard.') | |
21 | + >>> pyperclip.paste() | |
22 | + 'The text to be copied to the clipboard.' | |
23 | + | |
24 | + | |
25 | + Currently only handles plaintext. | |
26 | + | |
27 | + On Windows, no additional modules are needed. | |
28 | + | |
29 | + On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os. | |
30 | + | |
31 | + 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.) | |
32 | + | |
33 | + Otherwise on Linux, you will need the gtk or PyQt4 modules installed. | |
34 | + | |
35 | +Keywords: clipboard copy paste clip xsel xclip | |
36 | +Platform: UNKNOWN | |
37 | +Classifier: Development Status :: 5 - Production/Stable | |
38 | +Classifier: Environment :: Win32 (MS Windows) | |
39 | +Classifier: Environment :: X11 Applications | |
40 | +Classifier: Environment :: MacOS X | |
41 | +Classifier: Intended Audience :: Developers | |
42 | +Classifier: License :: OSI Approved :: BSD License | |
43 | +Classifier: Operating System :: OS Independent | |
44 | +Classifier: Programming Language :: Python | |
45 | +Classifier: Programming Language :: Python :: 2 | |
46 | +Classifier: Programming Language :: Python :: 2.6 | |
47 | +Classifier: Programming Language :: Python :: 2.7 | |
48 | +Classifier: Programming Language :: Python :: 3 | |
49 | +Classifier: Programming Language :: Python :: 3.1 | |
50 | +Classifier: Programming Language :: Python :: 3.2 | |
51 | +Classifier: Programming Language :: Python :: 3.3 | |
52 | +Classifier: Programming Language :: Python :: 3.4 | |
53 | +Classifier: Programming Language :: Python :: 3.5 | |
54 | +Classifier: Programming Language :: Python :: 3.6 |
@@ -0,0 +1,15 @@ | ||
1 | +MANIFEST.in | |
2 | +README.md | |
3 | +setup.cfg | |
4 | +setup.py | |
5 | +docs/Makefile | |
6 | +docs/conf.py | |
7 | +docs/index.rst | |
8 | +docs/make.bat | |
9 | +src/pyperclip/__init__.py | |
10 | +src/pyperclip/__main__.py | |
11 | +src/pyperclip.egg-info/PKG-INFO | |
12 | +src/pyperclip.egg-info/SOURCES.txt | |
13 | +src/pyperclip.egg-info/dependency_links.txt | |
14 | +src/pyperclip.egg-info/top_level.txt | |
15 | +tests/test_pyperclip.py | |
\ No newline at end of file |
@@ -0,0 +1,1 @@ | ||
1 | + |
@@ -0,0 +1,8 @@ | ||
1 | +../pyperclip/__init__.py | |
2 | +../pyperclip/__main__.py | |
3 | +../pyperclip/__pycache__/__init__.cpython-38.pyc | |
4 | +../pyperclip/__pycache__/__main__.cpython-38.pyc | |
5 | +PKG-INFO | |
6 | +SOURCES.txt | |
7 | +dependency_links.txt | |
8 | +top_level.txt |
@@ -0,0 +1,1 @@ | ||
1 | +pyperclip |
@@ -0,0 +1,694 @@ | ||
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 or xsel via package manager. For example, in Debian: | |
20 | + sudo apt-get install xclip | |
21 | + sudo apt-get install xsel | |
22 | + | |
23 | +Otherwise on Linux, you will need the gtk or PyQt5/PyQt4 modules installed. | |
24 | + | |
25 | +gtk and PyQt4 modules are not available for Python 3, | |
26 | +and this module does not work with PyGObject yet. | |
27 | + | |
28 | +Note: There seems to be a way to get gtk on Python 3, according to: | |
29 | + https://askubuntu.com/questions/697397/python3-is-not-supporting-gtk-module | |
30 | + | |
31 | +Cygwin is currently not supported. | |
32 | + | |
33 | +Security Note: This module runs programs with these names: | |
34 | + - which | |
35 | + - where | |
36 | + - pbcopy | |
37 | + - pbpaste | |
38 | + - xclip | |
39 | + - xsel | |
40 | + - klipper | |
41 | + - qdbus | |
42 | +A malicious user could rename or add programs with these names, tricking | |
43 | +Pyperclip into running them with whatever permissions the Python process has. | |
44 | + | |
45 | +""" | |
46 | +__version__ = '1.8.0' | |
47 | + | |
48 | +import contextlib | |
49 | +import ctypes | |
50 | +import os | |
51 | +import platform | |
52 | +import subprocess | |
53 | +import sys | |
54 | +import time | |
55 | +import warnings | |
56 | + | |
57 | +from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar | |
58 | + | |
59 | + | |
60 | +# `import PyQt4` sys.exit()s if DISPLAY is not in the environment. | |
61 | +# Thus, we need to detect the presence of $DISPLAY manually | |
62 | +# and not load PyQt4 if it is absent. | |
63 | +HAS_DISPLAY = os.getenv("DISPLAY", False) | |
64 | + | |
65 | +EXCEPT_MSG = """ | |
66 | + Pyperclip could not find a copy/paste mechanism for your system. | |
67 | + For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error """ | |
68 | + | |
69 | +PY2 = sys.version_info[0] == 2 | |
70 | + | |
71 | +STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode. | |
72 | + | |
73 | +ENCODING = 'utf-8' | |
74 | + | |
75 | +# The "which" unix command finds where a command is. | |
76 | +if platform.system() == 'Windows': | |
77 | + WHICH_CMD = 'where' | |
78 | +else: | |
79 | + WHICH_CMD = 'which' | |
80 | + | |
81 | +def _executable_exists(name): | |
82 | + return subprocess.call([WHICH_CMD, name], | |
83 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 | |
84 | + | |
85 | + | |
86 | + | |
87 | +# Exceptions | |
88 | +class PyperclipException(RuntimeError): | |
89 | + pass | |
90 | + | |
91 | +class PyperclipWindowsException(PyperclipException): | |
92 | + def __init__(self, message): | |
93 | + message += " (%s)" % ctypes.WinError() | |
94 | + super(PyperclipWindowsException, self).__init__(message) | |
95 | + | |
96 | +class PyperclipTimeoutException(PyperclipException): | |
97 | + pass | |
98 | + | |
99 | +def _stringifyText(text): | |
100 | + if PY2: | |
101 | + acceptedTypes = (unicode, str, int, float, bool) | |
102 | + else: | |
103 | + acceptedTypes = (str, int, float, bool) | |
104 | + if not isinstance(text, acceptedTypes): | |
105 | + raise PyperclipException('only str, int, float, and bool values can be copied to the clipboard, not %s' % (text.__class__.__name__)) | |
106 | + return STR_OR_UNICODE(text) | |
107 | + | |
108 | + | |
109 | +def init_osx_pbcopy_clipboard(): | |
110 | + | |
111 | + def copy_osx_pbcopy(text): | |
112 | + text = _stringifyText(text) # Converts non-str values to str. | |
113 | + p = subprocess.Popen(['pbcopy', 'w'], | |
114 | + stdin=subprocess.PIPE, close_fds=True) | |
115 | + p.communicate(input=text.encode(ENCODING)) | |
116 | + | |
117 | + def paste_osx_pbcopy(): | |
118 | + p = subprocess.Popen(['pbpaste', 'r'], | |
119 | + stdout=subprocess.PIPE, close_fds=True) | |
120 | + stdout, stderr = p.communicate() | |
121 | + return stdout.decode(ENCODING) | |
122 | + | |
123 | + return copy_osx_pbcopy, paste_osx_pbcopy | |
124 | + | |
125 | + | |
126 | +def init_osx_pyobjc_clipboard(): | |
127 | + def copy_osx_pyobjc(text): | |
128 | + '''Copy string argument to clipboard''' | |
129 | + text = _stringifyText(text) # Converts non-str values to str. | |
130 | + newStr = Foundation.NSString.stringWithString_(text).nsstring() | |
131 | + newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding) | |
132 | + board = AppKit.NSPasteboard.generalPasteboard() | |
133 | + board.declareTypes_owner_([AppKit.NSStringPboardType], None) | |
134 | + board.setData_forType_(newData, AppKit.NSStringPboardType) | |
135 | + | |
136 | + def paste_osx_pyobjc(): | |
137 | + "Returns contents of clipboard" | |
138 | + board = AppKit.NSPasteboard.generalPasteboard() | |
139 | + content = board.stringForType_(AppKit.NSStringPboardType) | |
140 | + return content | |
141 | + | |
142 | + return copy_osx_pyobjc, paste_osx_pyobjc | |
143 | + | |
144 | + | |
145 | +def init_gtk_clipboard(): | |
146 | + global gtk | |
147 | + import gtk | |
148 | + | |
149 | + def copy_gtk(text): | |
150 | + global cb | |
151 | + text = _stringifyText(text) # Converts non-str values to str. | |
152 | + cb = gtk.Clipboard() | |
153 | + cb.set_text(text) | |
154 | + cb.store() | |
155 | + | |
156 | + def paste_gtk(): | |
157 | + clipboardContents = gtk.Clipboard().wait_for_text() | |
158 | + # for python 2, returns None if the clipboard is blank. | |
159 | + if clipboardContents is None: | |
160 | + return '' | |
161 | + else: | |
162 | + return clipboardContents | |
163 | + | |
164 | + return copy_gtk, paste_gtk | |
165 | + | |
166 | + | |
167 | +def init_qt_clipboard(): | |
168 | + global QApplication | |
169 | + # $DISPLAY should exist | |
170 | + | |
171 | + # Try to import from qtpy, but if that fails try PyQt5 then PyQt4 | |
172 | + try: | |
173 | + from qtpy.QtWidgets import QApplication | |
174 | + except: | |
175 | + try: | |
176 | + from PyQt5.QtWidgets import QApplication | |
177 | + except: | |
178 | + from PyQt4.QtGui import QApplication | |
179 | + | |
180 | + app = QApplication.instance() | |
181 | + if app is None: | |
182 | + app = QApplication([]) | |
183 | + | |
184 | + def copy_qt(text): | |
185 | + text = _stringifyText(text) # Converts non-str values to str. | |
186 | + cb = app.clipboard() | |
187 | + cb.setText(text) | |
188 | + | |
189 | + def paste_qt(): | |
190 | + cb = app.clipboard() | |
191 | + return STR_OR_UNICODE(cb.text()) | |
192 | + | |
193 | + return copy_qt, paste_qt | |
194 | + | |
195 | + | |
196 | +def init_xclip_clipboard(): | |
197 | + DEFAULT_SELECTION='c' | |
198 | + PRIMARY_SELECTION='p' | |
199 | + | |
200 | + def copy_xclip(text, primary=False): | |
201 | + text = _stringifyText(text) # Converts non-str values to str. | |
202 | + selection=DEFAULT_SELECTION | |
203 | + if primary: | |
204 | + selection=PRIMARY_SELECTION | |
205 | + p = subprocess.Popen(['xclip', '-selection', selection], | |
206 | + stdin=subprocess.PIPE, close_fds=True) | |
207 | + p.communicate(input=text.encode(ENCODING)) | |
208 | + | |
209 | + def paste_xclip(primary=False): | |
210 | + selection=DEFAULT_SELECTION | |
211 | + if primary: | |
212 | + selection=PRIMARY_SELECTION | |
213 | + p = subprocess.Popen(['xclip', '-selection', selection, '-o'], | |
214 | + stdout=subprocess.PIPE, | |
215 | + stderr=subprocess.PIPE, | |
216 | + close_fds=True) | |
217 | + stdout, stderr = p.communicate() | |
218 | + # Intentionally ignore extraneous output on stderr when clipboard is empty | |
219 | + return stdout.decode(ENCODING) | |
220 | + | |
221 | + return copy_xclip, paste_xclip | |
222 | + | |
223 | + | |
224 | +def init_xsel_clipboard(): | |
225 | + DEFAULT_SELECTION='-b' | |
226 | + PRIMARY_SELECTION='-p' | |
227 | + | |
228 | + def copy_xsel(text, primary=False): | |
229 | + text = _stringifyText(text) # Converts non-str values to str. | |
230 | + selection_flag = DEFAULT_SELECTION | |
231 | + if primary: | |
232 | + selection_flag = PRIMARY_SELECTION | |
233 | + p = subprocess.Popen(['xsel', selection_flag, '-i'], | |
234 | + stdin=subprocess.PIPE, close_fds=True) | |
235 | + p.communicate(input=text.encode(ENCODING)) | |
236 | + | |
237 | + def paste_xsel(primary=False): | |
238 | + selection_flag = DEFAULT_SELECTION | |
239 | + if primary: | |
240 | + selection_flag = PRIMARY_SELECTION | |
241 | + p = subprocess.Popen(['xsel', selection_flag, '-o'], | |
242 | + stdout=subprocess.PIPE, close_fds=True) | |
243 | + stdout, stderr = p.communicate() | |
244 | + return stdout.decode(ENCODING) | |
245 | + | |
246 | + return copy_xsel, paste_xsel | |
247 | + | |
248 | + | |
249 | +def init_klipper_clipboard(): | |
250 | + def copy_klipper(text): | |
251 | + text = _stringifyText(text) # Converts non-str values to str. | |
252 | + p = subprocess.Popen( | |
253 | + ['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents', | |
254 | + text.encode(ENCODING)], | |
255 | + stdin=subprocess.PIPE, close_fds=True) | |
256 | + p.communicate(input=None) | |
257 | + | |
258 | + def paste_klipper(): | |
259 | + p = subprocess.Popen( | |
260 | + ['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'], | |
261 | + stdout=subprocess.PIPE, close_fds=True) | |
262 | + stdout, stderr = p.communicate() | |
263 | + | |
264 | + # Workaround for https://bugs.kde.org/show_bug.cgi?id=342874 | |
265 | + # TODO: https://github.com/asweigart/pyperclip/issues/43 | |
266 | + clipboardContents = stdout.decode(ENCODING) | |
267 | + # even if blank, Klipper will append a newline at the end | |
268 | + assert len(clipboardContents) > 0 | |
269 | + # make sure that newline is there | |
270 | + assert clipboardContents.endswith('\n') | |
271 | + if clipboardContents.endswith('\n'): | |
272 | + clipboardContents = clipboardContents[:-1] | |
273 | + return clipboardContents | |
274 | + | |
275 | + return copy_klipper, paste_klipper | |
276 | + | |
277 | + | |
278 | +def init_dev_clipboard_clipboard(): | |
279 | + def copy_dev_clipboard(text): | |
280 | + text = _stringifyText(text) # Converts non-str values to str. | |
281 | + if text == '': | |
282 | + warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.') | |
283 | + if '\r' in text: | |
284 | + warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.') | |
285 | + | |
286 | + fo = open('/dev/clipboard', 'wt') | |
287 | + fo.write(text) | |
288 | + fo.close() | |
289 | + | |
290 | + def paste_dev_clipboard(): | |
291 | + fo = open('/dev/clipboard', 'rt') | |
292 | + content = fo.read() | |
293 | + fo.close() | |
294 | + return content | |
295 | + | |
296 | + return copy_dev_clipboard, paste_dev_clipboard | |
297 | + | |
298 | + | |
299 | +def init_no_clipboard(): | |
300 | + class ClipboardUnavailable(object): | |
301 | + | |
302 | + def __call__(self, *args, **kwargs): | |
303 | + raise PyperclipException(EXCEPT_MSG) | |
304 | + | |
305 | + if PY2: | |
306 | + def __nonzero__(self): | |
307 | + return False | |
308 | + else: | |
309 | + def __bool__(self): | |
310 | + return False | |
311 | + | |
312 | + return ClipboardUnavailable(), ClipboardUnavailable() | |
313 | + | |
314 | + | |
315 | + | |
316 | + | |
317 | +# Windows-related clipboard functions: | |
318 | +class CheckedCall(object): | |
319 | + def __init__(self, f): | |
320 | + super(CheckedCall, self).__setattr__("f", f) | |
321 | + | |
322 | + def __call__(self, *args): | |
323 | + ret = self.f(*args) | |
324 | + if not ret and get_errno(): | |
325 | + raise PyperclipWindowsException("Error calling " + self.f.__name__) | |
326 | + return ret | |
327 | + | |
328 | + def __setattr__(self, key, value): | |
329 | + setattr(self.f, key, value) | |
330 | + | |
331 | + | |
332 | +def init_windows_clipboard(): | |
333 | + global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE | |
334 | + from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, | |
335 | + HINSTANCE, HMENU, BOOL, UINT, HANDLE) | |
336 | + | |
337 | + windll = ctypes.windll | |
338 | + msvcrt = ctypes.CDLL('msvcrt') | |
339 | + | |
340 | + safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA) | |
341 | + safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT, | |
342 | + INT, INT, HWND, HMENU, HINSTANCE, LPVOID] | |
343 | + safeCreateWindowExA.restype = HWND | |
344 | + | |
345 | + safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow) | |
346 | + safeDestroyWindow.argtypes = [HWND] | |
347 | + safeDestroyWindow.restype = BOOL | |
348 | + | |
349 | + OpenClipboard = windll.user32.OpenClipboard | |
350 | + OpenClipboard.argtypes = [HWND] | |
351 | + OpenClipboard.restype = BOOL | |
352 | + | |
353 | + safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard) | |
354 | + safeCloseClipboard.argtypes = [] | |
355 | + safeCloseClipboard.restype = BOOL | |
356 | + | |
357 | + safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard) | |
358 | + safeEmptyClipboard.argtypes = [] | |
359 | + safeEmptyClipboard.restype = BOOL | |
360 | + | |
361 | + safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData) | |
362 | + safeGetClipboardData.argtypes = [UINT] | |
363 | + safeGetClipboardData.restype = HANDLE | |
364 | + | |
365 | + safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData) | |
366 | + safeSetClipboardData.argtypes = [UINT, HANDLE] | |
367 | + safeSetClipboardData.restype = HANDLE | |
368 | + | |
369 | + safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc) | |
370 | + safeGlobalAlloc.argtypes = [UINT, c_size_t] | |
371 | + safeGlobalAlloc.restype = HGLOBAL | |
372 | + | |
373 | + safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock) | |
374 | + safeGlobalLock.argtypes = [HGLOBAL] | |
375 | + safeGlobalLock.restype = LPVOID | |
376 | + | |
377 | + safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock) | |
378 | + safeGlobalUnlock.argtypes = [HGLOBAL] | |
379 | + safeGlobalUnlock.restype = BOOL | |
380 | + | |
381 | + wcslen = CheckedCall(msvcrt.wcslen) | |
382 | + wcslen.argtypes = [c_wchar_p] | |
383 | + wcslen.restype = UINT | |
384 | + | |
385 | + GMEM_MOVEABLE = 0x0002 | |
386 | + CF_UNICODETEXT = 13 | |
387 | + | |
388 | + @contextlib.contextmanager | |
389 | + def window(): | |
390 | + """ | |
391 | + Context that provides a valid Windows hwnd. | |
392 | + """ | |
393 | + # we really just need the hwnd, so setting "STATIC" | |
394 | + # as predefined lpClass is just fine. | |
395 | + hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0, | |
396 | + None, None, None, None) | |
397 | + try: | |
398 | + yield hwnd | |
399 | + finally: | |
400 | + safeDestroyWindow(hwnd) | |
401 | + | |
402 | + @contextlib.contextmanager | |
403 | + def clipboard(hwnd): | |
404 | + """ | |
405 | + Context manager that opens the clipboard and prevents | |
406 | + other applications from modifying the clipboard content. | |
407 | + """ | |
408 | + # We may not get the clipboard handle immediately because | |
409 | + # some other application is accessing it (?) | |
410 | + # We try for at least 500ms to get the clipboard. | |
411 | + t = time.time() + 0.5 | |
412 | + success = False | |
413 | + while time.time() < t: | |
414 | + success = OpenClipboard(hwnd) | |
415 | + if success: | |
416 | + break | |
417 | + time.sleep(0.01) | |
418 | + if not success: | |
419 | + raise PyperclipWindowsException("Error calling OpenClipboard") | |
420 | + | |
421 | + try: | |
422 | + yield | |
423 | + finally: | |
424 | + safeCloseClipboard() | |
425 | + | |
426 | + def copy_windows(text): | |
427 | + # This function is heavily based on | |
428 | + # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard | |
429 | + | |
430 | + text = _stringifyText(text) # Converts non-str values to str. | |
431 | + | |
432 | + with window() as hwnd: | |
433 | + # http://msdn.com/ms649048 | |
434 | + # If an application calls OpenClipboard with hwnd set to NULL, | |
435 | + # EmptyClipboard sets the clipboard owner to NULL; | |
436 | + # this causes SetClipboardData to fail. | |
437 | + # => We need a valid hwnd to copy something. | |
438 | + with clipboard(hwnd): | |
439 | + safeEmptyClipboard() | |
440 | + | |
441 | + if text: | |
442 | + # http://msdn.com/ms649051 | |
443 | + # If the hMem parameter identifies a memory object, | |
444 | + # the object must have been allocated using the | |
445 | + # function with the GMEM_MOVEABLE flag. | |
446 | + count = wcslen(text) + 1 | |
447 | + handle = safeGlobalAlloc(GMEM_MOVEABLE, | |
448 | + count * sizeof(c_wchar)) | |
449 | + locked_handle = safeGlobalLock(handle) | |
450 | + | |
451 | + ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar)) | |
452 | + | |
453 | + safeGlobalUnlock(handle) | |
454 | + safeSetClipboardData(CF_UNICODETEXT, handle) | |
455 | + | |
456 | + def paste_windows(): | |
457 | + with clipboard(None): | |
458 | + handle = safeGetClipboardData(CF_UNICODETEXT) | |
459 | + if not handle: | |
460 | + # GetClipboardData may return NULL with errno == NO_ERROR | |
461 | + # if the clipboard is empty. | |
462 | + # (Also, it may return a handle to an empty buffer, | |
463 | + # but technically that's not empty) | |
464 | + return "" | |
465 | + return c_wchar_p(handle).value | |
466 | + | |
467 | + return copy_windows, paste_windows | |
468 | + | |
469 | + | |
470 | +def init_wsl_clipboard(): | |
471 | + def copy_wsl(text): | |
472 | + text = _stringifyText(text) # Converts non-str values to str. | |
473 | + p = subprocess.Popen(['clip.exe'], | |
474 | + stdin=subprocess.PIPE, close_fds=True) | |
475 | + p.communicate(input=text.encode(ENCODING)) | |
476 | + | |
477 | + def paste_wsl(): | |
478 | + p = subprocess.Popen(['powershell.exe', '-command', 'Get-Clipboard'], | |
479 | + stdout=subprocess.PIPE, | |
480 | + stderr=subprocess.PIPE, | |
481 | + close_fds=True) | |
482 | + stdout, stderr = p.communicate() | |
483 | + # WSL appends "\r\n" to the contents. | |
484 | + return stdout[:-2].decode(ENCODING) | |
485 | + | |
486 | + return copy_wsl, paste_wsl | |
487 | + | |
488 | + | |
489 | +# Automatic detection of clipboard mechanisms and importing is done in deteremine_clipboard(): | |
490 | +def determine_clipboard(): | |
491 | + ''' | |
492 | + Determine the OS/platform and set the copy() and paste() functions | |
493 | + accordingly. | |
494 | + ''' | |
495 | + | |
496 | + global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5 | |
497 | + | |
498 | + # Setup for the CYGWIN platform: | |
499 | + if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1' | |
500 | + # FIXME: pyperclip currently does not support Cygwin, | |
501 | + # see https://github.com/asweigart/pyperclip/issues/55 | |
502 | + if os.path.exists('/dev/clipboard'): | |
503 | + warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55') | |
504 | + return init_dev_clipboard_clipboard() | |
505 | + | |
506 | + # Setup for the WINDOWS platform: | |
507 | + elif os.name == 'nt' or platform.system() == 'Windows': | |
508 | + return init_windows_clipboard() | |
509 | + | |
510 | + if platform.system() == 'Linux': | |
511 | + with open('/proc/version', 'r') as f: | |
512 | + if "Microsoft" in f.read(): | |
513 | + return init_wsl_clipboard() | |
514 | + | |
515 | + # Setup for the MAC OS X platform: | |
516 | + if os.name == 'mac' or platform.system() == 'Darwin': | |
517 | + try: | |
518 | + import Foundation # check if pyobjc is installed | |
519 | + import AppKit | |
520 | + except ImportError: | |
521 | + return init_osx_pbcopy_clipboard() | |
522 | + else: | |
523 | + return init_osx_pyobjc_clipboard() | |
524 | + | |
525 | + # Setup for the LINUX platform: | |
526 | + if HAS_DISPLAY: | |
527 | + try: | |
528 | + import gtk # check if gtk is installed | |
529 | + except ImportError: | |
530 | + pass # We want to fail fast for all non-ImportError exceptions. | |
531 | + else: | |
532 | + return init_gtk_clipboard() | |
533 | + | |
534 | + if _executable_exists("xsel"): | |
535 | + return init_xsel_clipboard() | |
536 | + if _executable_exists("xclip"): | |
537 | + return init_xclip_clipboard() | |
538 | + if _executable_exists("klipper") and _executable_exists("qdbus"): | |
539 | + return init_klipper_clipboard() | |
540 | + | |
541 | + try: | |
542 | + # qtpy is a small abstraction layer that lets you write applications using a single api call to either PyQt or PySide. | |
543 | + # https://pypi.python.org/pypi/QtPy | |
544 | + import qtpy # check if qtpy is installed | |
545 | + except ImportError: | |
546 | + # If qtpy isn't installed, fall back on importing PyQt4. | |
547 | + try: | |
548 | + import PyQt5 # check if PyQt5 is installed | |
549 | + except ImportError: | |
550 | + try: | |
551 | + import PyQt4 # check if PyQt4 is installed | |
552 | + except ImportError: | |
553 | + pass # We want to fail fast for all non-ImportError exceptions. | |
554 | + else: | |
555 | + return init_qt_clipboard() | |
556 | + else: | |
557 | + return init_qt_clipboard() | |
558 | + else: | |
559 | + return init_qt_clipboard() | |
560 | + | |
561 | + | |
562 | + return init_no_clipboard() | |
563 | + | |
564 | + | |
565 | +def set_clipboard(clipboard): | |
566 | + ''' | |
567 | + Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how | |
568 | + the copy() and paste() functions interact with the operating system to | |
569 | + implement the copy/paste feature. The clipboard parameter must be one of: | |
570 | + - pbcopy | |
571 | + - pbobjc (default on Mac OS X) | |
572 | + - gtk | |
573 | + - qt | |
574 | + - xclip | |
575 | + - xsel | |
576 | + - klipper | |
577 | + - windows (default on Windows) | |
578 | + - no (this is what is set when no clipboard mechanism can be found) | |
579 | + ''' | |
580 | + global copy, paste | |
581 | + | |
582 | + clipboard_types = {'pbcopy': init_osx_pbcopy_clipboard, | |
583 | + 'pyobjc': init_osx_pyobjc_clipboard, | |
584 | + 'gtk': init_gtk_clipboard, | |
585 | + 'qt': init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5' | |
586 | + 'xclip': init_xclip_clipboard, | |
587 | + 'xsel': init_xsel_clipboard, | |
588 | + 'klipper': init_klipper_clipboard, | |
589 | + 'windows': init_windows_clipboard, | |
590 | + 'no': init_no_clipboard} | |
591 | + | |
592 | + if clipboard not in clipboard_types: | |
593 | + raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()]))) | |
594 | + | |
595 | + # Sets pyperclip's copy() and paste() functions: | |
596 | + copy, paste = clipboard_types[clipboard]() | |
597 | + | |
598 | + | |
599 | +def lazy_load_stub_copy(text): | |
600 | + ''' | |
601 | + A stub function for copy(), which will load the real copy() function when | |
602 | + called so that the real copy() function is used for later calls. | |
603 | + | |
604 | + This allows users to import pyperclip without having determine_clipboard() | |
605 | + automatically run, which will automatically select a clipboard mechanism. | |
606 | + This could be a problem if it selects, say, the memory-heavy PyQt4 module | |
607 | + but the user was just going to immediately call set_clipboard() to use a | |
608 | + different clipboard mechanism. | |
609 | + | |
610 | + The lazy loading this stub function implements gives the user a chance to | |
611 | + call set_clipboard() to pick another clipboard mechanism. Or, if the user | |
612 | + simply calls copy() or paste() without calling set_clipboard() first, | |
613 | + will fall back on whatever clipboard mechanism that determine_clipboard() | |
614 | + automatically chooses. | |
615 | + ''' | |
616 | + global copy, paste | |
617 | + copy, paste = determine_clipboard() | |
618 | + return copy(text) | |
619 | + | |
620 | + | |
621 | +def lazy_load_stub_paste(): | |
622 | + ''' | |
623 | + A stub function for paste(), which will load the real paste() function when | |
624 | + called so that the real paste() function is used for later calls. | |
625 | + | |
626 | + This allows users to import pyperclip without having determine_clipboard() | |
627 | + automatically run, which will automatically select a clipboard mechanism. | |
628 | + This could be a problem if it selects, say, the memory-heavy PyQt4 module | |
629 | + but the user was just going to immediately call set_clipboard() to use a | |
630 | + different clipboard mechanism. | |
631 | + | |
632 | + The lazy loading this stub function implements gives the user a chance to | |
633 | + call set_clipboard() to pick another clipboard mechanism. Or, if the user | |
634 | + simply calls copy() or paste() without calling set_clipboard() first, | |
635 | + will fall back on whatever clipboard mechanism that determine_clipboard() | |
636 | + automatically chooses. | |
637 | + ''' | |
638 | + global copy, paste | |
639 | + copy, paste = determine_clipboard() | |
640 | + return paste() | |
641 | + | |
642 | + | |
643 | +def is_available(): | |
644 | + return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste | |
645 | + | |
646 | + | |
647 | +# Initially, copy() and paste() are set to lazy loading wrappers which will | |
648 | +# set `copy` and `paste` to real functions the first time they're used, unless | |
649 | +# set_clipboard() or determine_clipboard() is called first. | |
650 | +copy, paste = lazy_load_stub_copy, lazy_load_stub_paste | |
651 | + | |
652 | + | |
653 | + | |
654 | +def waitForPaste(timeout=None): | |
655 | + """This function call blocks until a non-empty text string exists on the | |
656 | + clipboard. It returns this text. | |
657 | + | |
658 | + This function raises PyperclipTimeoutException if timeout was set to | |
659 | + a number of seconds that has elapsed without non-empty text being put on | |
660 | + the clipboard.""" | |
661 | + startTime = time.time() | |
662 | + while True: | |
663 | + clipboardText = paste() | |
664 | + if clipboardText != '': | |
665 | + return clipboardText | |
666 | + time.sleep(0.01) | |
667 | + | |
668 | + if timeout is not None and time.time() > startTime + timeout: | |
669 | + raise PyperclipTimeoutException('waitForPaste() timed out after ' + str(timeout) + ' seconds.') | |
670 | + | |
671 | + | |
672 | +def waitForNewPaste(timeout=None): | |
673 | + """This function call blocks until a new text string exists on the | |
674 | + clipboard that is different from the text that was there when the function | |
675 | + was first called. It returns this text. | |
676 | + | |
677 | + This function raises PyperclipTimeoutException if timeout was set to | |
678 | + a number of seconds that has elapsed without non-empty text being put on | |
679 | + the clipboard.""" | |
680 | + startTime = time.time() | |
681 | + originalText = paste() | |
682 | + while True: | |
683 | + currentText = paste() | |
684 | + if currentText != originalText: | |
685 | + return currentText | |
686 | + time.sleep(0.01) | |
687 | + | |
688 | + if timeout is not None and time.time() > startTime + timeout: | |
689 | + raise PyperclipTimeoutException('waitForNewPaste() timed out after ' + str(timeout) + ' seconds.') | |
690 | + | |
691 | + | |
692 | +__all__ = ['copy', 'paste', 'waitForPaste', 'waitForNewPaste' 'set_clipboard', 'determine_clipboard'] | |
693 | + | |
694 | + |
@@ -0,0 +1,12 @@ | ||
1 | +import pyperclip | |
2 | +import sys | |
3 | + | |
4 | +if len(sys.argv) > 1 and sys.argv[1] in ('-c', '--copy'): | |
5 | + pyperclip.copy(sys.stdin.read()) | |
6 | +elif len(sys.argv) > 1 and sys.argv[1] in ('-p', '--paste'): | |
7 | + sys.stdout.write(pyperclip.paste()) | |
8 | +else: | |
9 | + print('Usage: python -m pyperclip [-c | --copy] | [-p | --paste]') | |
10 | + print() | |
11 | + print('When copying, stdin will be placed on the clipboard.') | |
12 | + print('When pasting, the clipboard will be written to stdout.') | |
\ No newline at end of file |