#!/usr/bin/env python
# -*- coding: utf-8 -*-
# It script it publish under GNU GENERAL PUBLIC LICENSE
# http://www.gnu.org/licenses/gpl-3.0.en.html
# Author: the Galaxie Curses Team, all rights reserved
# Inspired by: http://code.activestate.com/recipes/576887-shared-clipboard/
# Inspired by: https://developer.gnome.org/gtk3/stable/gtk3-Clipboards.html
import os
import getpass
import codecs
try:
import pyperclip
except ImportError:
pass
import json
# try:
# from yaml import load, dump, FullLoader
# except ImportError:
# pass
import GLXCurses
[docs]class Clipboard(object):
def __init__(self):
# It's a GLXCurse Type
self.glxc_type = "GLXCurses.Clipboard"
# Unique ID it permit to individually identify a widget by example for get_focus get_default
self.id = GLXCurses.new_id()
self.name = "{0}{1}".format(self.__class__.__name__, self.id)
# pyperclip special attention
self._use_pyperclip = True
self._test_pyperclip()
# Public Property
self.can_store = True
self.__text = None
self.text = None
self.owner = getpass.getuser()
# Internal
self.directory = GLXCurses.get_os_temporary_dir()
self.filename = str("")
self.filename += str("GLXCurses-")
self.filename += str(getpass.getuser())
self.filename += str(".cb")
self.file = os.path.normpath(os.path.join(self.directory, self.filename))
@property
def text(self):
return self.__text
@text.setter
def text(self, value):
if value is None:
value = ""
if not isinstance(value, (str, int, float, bool)):
raise TypeError(
"only str, int, float, and bool values can be copied to the clipboard, not %s"
% (type(value))
)
if self.text != value:
self.__text = value
[docs] def get(self):
"""
Returns the clipboard object for the given selection.
:return: The appropriate clipboard object.
:rtype: GLXCurses.Clipboard
"""
return self
[docs] def set_text(self, clipboard=None, text=None, length=-1):
"""
Sets the contents of the GLXCurses.Clipboard to the given UTF-8 string.
GLXCurses will make a copy of the text and take responsibility for responding for requests for the text,
and for converting the text into the requested format.
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
:param text: a UTF-8 string.
:type text: str or None
:param length: length of text , in bytes, or -1, in which case the length will be determined with len().
:type length: int
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
if not isinstance(text, (str, int, float, bool)):
raise TypeError(
"only str, int, float, and bool values can be copied to the clipboard, not %s"
% (text.__class__.__name__)
)
# convert text as a str
text = str(text)
# make the job
if length < 0:
text = text
if length == 0:
text = ""
if length > 0:
length = GLXCurses.clamp(value=length, smallest=0, largest=len(text))
text = text[:length]
# in case value have change
if clipboard.text != text:
clipboard.text = text
[docs] def wait_for_text(self, clipboard=None):
"""
Requests the contents of the GLXCurses.Clipboard as text and converts the result to UTF-8 if necessary.
This function waits for the __area_data to be received using the main loop, so events, timeouts, etc,
may be dispatched during the wait.
:param clipboard: a GLXCurses.Clipboard
:type clipboard: GLXCurses.Clipboard
:return: a newly-allocated UTF-8 string or NULL if retrieving the selection __area_data failed. \
This could happen for various reasons, in particular if the clipboard was empty or if the contents of the \
clipboard could not be converted into text form.).
:rtype: str
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
# We make the job
# Check if pyperclip can be use
if clipboard._use_pyperclip:
# Get the content of the pyperclip clipboard
clipboard_contents = clipboard._pyperclip_paste(clipboard=clipboard)
# If it have content we inform the GLXCurses clipboard
if clipboard_contents:
# First we set the text in the GLXCurses clipboard
clipboard.set_text(clipboard=clipboard, text=clipboard_contents)
# YES !!! we save on the GLXCurses clipboard exchange file the content of pyperclip clipboard
clipboard.store(clipboard=clipboard)
# In any case pyperclip or not we read the GLXCurses clipboard exchange file
with codecs.open(clipboard.file, mode="r", encoding="utf-8-sig") as f:
clipboard_contents = json.load(f)
# Return GLXCurses clipboard exchange file content
return clipboard_contents[self.__class__.__name__]["__area_data"]
[docs] def set_can_store(self, clipboard=None, targets=None, n_targets=None):
"""
Hints that the clipboard __area_data should be stored somewhere when the application exits or when
store() is called.
This value is reset when the clipboard owner changes.
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
:param targets: array containing information about which forms should be stored or None to indicate that all \
forms should be stored.
:type targets: TYPE Constant or None
:param n_targets: number of elements in targets
:type n_targets: int or None
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
# Make the job
clipboard.can_store = True
[docs] def store(self, clipboard=None):
"""
Stores the current clipboard __area_data somewhere so that it will stay around after the application has quit.
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
# Make the job
if self.can_store:
data = {
self.__class__.__name__: {
"owner": clipboard.owner,
"atom": "CLIPBOARD",
"id": clipboard.id,
"type": GLXCurses.GLXC.TARGET_STRING,
"__area_data": clipboard.text,
}
}
with codecs.open(clipboard.file, mode="w", encoding="utf-8-sig") as fd:
json.dump(data, fd)
fd.close()
# value have change then in case inform pyperclip
if clipboard._use_pyperclip:
clipboard._pyperclip_copy(clipboard=clipboard)
def _test_pyperclip(self, clipboard=None):
"""
Determine if pyperclip can be use or not.
That estimation is done one time during the initialisation of the GLXCurses.Clipboard class.
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
:return: True if we use pyperclip, False if not
:rtype: bool
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
# We Make the job
try:
pyperclip.paste()
return True
except pyperclip.PyperclipException:
self.use_pyperclip = False
return False
def _pyperclip_copy(self, clipboard=None):
"""
Just in case inform pyperclip better it can .
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
pyperclip.copy(clipboard.text)
def _pyperclip_paste(self, clipboard=None):
"""
Just in case inform pyperclip better it can .
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
:return: The GLXCurses.Clipboard pyperclip content
:rtype: str
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
return str(pyperclip.paste())
def _get_file(self, clipboard=None):
"""
Get the file cross platform full path
:param clipboard: a GLXCurses.Clipboard object or None for self
:type clipboard: GLXCurses.Clipboard or None
:return: Normalized Cross platform path
:rtype: str
"""
# permit better unit test
if clipboard is None:
clipboard = self
# Try to exit as soon of possible
if not GLXCurses.glxc_type(clipboard):
raise TypeError("'clipboard' must be a GLXCurses type")
if not isinstance(clipboard, Clipboard):
raise TypeError("'clipboard' must be an instance of GLXCurses.Clipboard")
# We Make the job
return clipboard.file