Cudatext Clipboard catcher (автоматическая вставка из буфера

main Alexey
Posts: 2245
Joined: 25.08.2021 18:15

Post by main Alexey »

>self - конкретный экземпляр класса?Правильно?
да.
>в начале модуля нового плагина подвешивает CudaText!
не знаю почему.
первый раз вижу что в плагине юзают gtk.
main Alexey
Posts: 2245
Joined: 25.08.2021 18:15

Post by main Alexey »

clipboard catcher видимо трудно сделать. из за ограничений АПИ - нет тредов, нет gtk/gdk.
mix-7
Posts: 741
Joined: 11.05.2018 11:02

Post by mix-7 »

main Alexey wrote:
>self - конкретный экземпляр класса?Правильно?
да.
>в начале модуля нового плагина подвешивает CudaText!
не знаю почему.
первый раз вижу что в плагине юзают gtk.
Ну не нашел иных решений по перехвату clipboard changed для Python 3.
В WikidPad он работал в фоне с возможностью редактирования страниц, но непонятно, как.
Могу привести код.

Может, есть возможность самому написать hook/interceptor (?)

Ночью как-то сидел часа два, пробовал разные варианты, записывал.

Code: Select all

import gi
from gi.repository import Gdk
Не виснет

Code: Select all

from gi.repository import Gtk
Виснет.
Может, пространство имен (Namespace) совпадает и оттого зависание?

Вот, кажется, перехватчик, не понял, что за owner.
Программа, которая передала текст (или картинку, или объект) в буфер обмена?

python - Gtk clipboard owner-change event: detecting whether the changer is me - Ask Ubuntu
https://askubuntu.com/questions/1365154 ... nger-is-me

Code: Select all

clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.set_text("It worked!, -1)

def clipboardChanged(clipboard, owner_change):
    print("New owner is", owner_change.owner.get_xid())

clipboard.connect('owner-change', clipboardChanged)
Но опять же здесь используется Gtk.

Вот этот код:

How to catch the clipboard event (onChangeClipboard equivalent) from any application in Python - Stack Overflow
https://stackoverflow.com/questions/259 ... ny-applica
I found in the web a solution using GTK:

Code: Select all

from gi.repository import Gtk, Gdk

def callBack(*args):
    print("Clipboard changed. New value = " + clip.wait_for_text())

clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clip.connect('owner-change', callBack)
Gtk.main()

Ставил после Gtk.main()
отладочный
print("after main")
Печати не было, т.е. код бесполезен (помимо невозможности использования Gtk) - дальнейшей работы не будет,
и из другой вкладки CudaText текст не скопировать.
mix-7
Posts: 741
Joined: 11.05.2018 11:02

Post by mix-7 »

main Alexey wrote:clipboard catcher видимо трудно сделать. из за ограничений АПИ - нет тредов, нет gtk/gdk.
Согласен!
На java, С видел действующие решения, на Python только то, что написал выше. Поищу еще.

Может, имеет результативный смысл написать в Lazarus issue, чтобы добавили событие "новый объект в буфере обмена"?

Потому что отслеживать изменения буфера обмена без тредов - добавлять лишние обработчики на совсем посторонние действия (смена фокуса, смена вкладки и т.п.)

Есть некрасивое обходное решение:
1. Использовать CopyQ, в ее истории буфера обмена можно выделить несколько элементов в произвольном порядке и скопировать разом а потом вставить.
2. Расширение Gnome пишет историю буфера обмена в файл
~/.cache/clipboard-indicator@tudmotu.com/registry.txt
историю в виде массива JSON-словарей.
Имено на изменение этого файла как признак изменения буфера обмена я хотел поставить обработчик.

Но для этого ведь его нужно открыть во вкладке, так?
Просто отслеживать inode (или как определяется событие изменения файла) без открытия в CudaText не получится?

Выход (полукрасивый) - в новом плагине Cudatext Clipboard catcher в обработчике on_dialog
вставить в буфер обмена уникальную величину, например, время Unix и запомнить его в глобальной переменной.
Запомнить так же положение каретки (курсора?).
А при следующем вызове этого обработчика через меню прочитать массив в файле до элемента с этой меткой.

Вот такой способ.
main Alexey
Posts: 2245
Joined: 25.08.2021 18:15

Post by main Alexey »

>Может, есть возможность самому написать hook/interceptor (?)

во 1вых я могу написать только под винду. а надо кросс-платформенно.
во 2ых я не хочу вставлять в редактор такие хуки, это чревато зависами Куд.
main Alexey
Posts: 2245
Joined: 25.08.2021 18:15

Post by main Alexey »

>Может, имеет результативный смысл написать в Lazarus issue, чтобы добавили событие "новый объект в буфере обмена"?

весьма трудно сделать это кросс-платформенно.
и потом там и так недостаток скорости разработки.
не надо.
никто не будет делать.


>Просто отслеживать inode (или как определяется событие изменения файла) без открытия в CudaText не получится?

не получится.
mix-7
Posts: 741
Joined: 11.05.2018 11:02

Post by mix-7 »

mix-7 wrote: Ну не нашел иных решений по перехвату clipboard changed для Python 3.
1. В WikidPad он работал в фоне с возможностью редактирования страниц, но непонятно, как.
Могу привести код.

2. Может, есть возможность самому написать hook/interceptor (?)

Для справки (не вопрос, просто оставлю здесь для информации):

1. WikidPad - wiki notebook for Windows/Linux/Mac OS
https://wikidpad.sourceforge.net/
Код
https://github.com/WikidPad/WikidPad/archive/WikidPad-2-4-alpha01.zip
и
https://github.com/WikidPad/WikidPad/archive/WikidPad-2-3-rc02.zip

Более новый:
GitHub - BjornFJohansson/WikidPad: WikidPad is a single user desktop wiki
https://github.com/BjornFJohansson/WikidPad#setuptools-pip-package-for-wikidpad-wikidpadmp


2. Код с тредами, но он действительно нагружает процессор, его автор задает вопрос, как снизить загрузку CPU:

performance - Clipboard detector in Python - Code Review Stack Exchange
https://codereview.stackexchange.com/questions/184570/clipboard-detector-in-python
The following contains my little app that detects changes to the clipboard and displays them in the GUI. I did my best using my limited knowledge of Python, but I have a feeling that I can definitely improve the program. It works, but Python's CPU usage shoots up to 20% whenever I run the program - which is due to my use of multiple threads and infinite loops I'm sure.

Code: Select all

#! python3

#GUI
import tkinter
#needed for the clipboard event detection
import time
import threading

#listener class that inherits from Thread 
class ClipListener(threading.Thread):
    #overriding Thread constructor
    def __init__(self, pause = .5):
        #from documentation: If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.
        super().__init__() #calls Thread class constructor first

        #initialize parameters 
        self.pause = pause
        self.stopping = False

        #initialize event to communicate with main thread 
        self.copyevent = threading.Event()

    #override run method
    def run(self):
        last_value =  tkinter.Tk().clipboard_get() #initialize last_value as 

        #continue until self.stopping = true
        while not self.stopping:
            #grab clip_board value 
            temp_value = tkinter.Tk().clipboard_get()
            #if last value is not equal to the temp_value, then (obviously) a change has occurred
            if temp_value != last_value:
                #set last value equal to current (temp) value and print
                last_value = temp_value
                print("set")
                #set the event if clipboard has changed 
                self.copyevent.set()
            time.sleep(self.pause) #sleep for indicated amount of time (.5 by default)

    #override stop method to work with our paramter 'stopping'
    def stop(self):
        self.stopping = True

#GUI extends Frame, serving as main container for a root window 
class GUI(tkinter.Frame):

    #constructor for GUI - intializes with a default height and width if none are given
    def __init__(self, master, ht=600, wt=800):

        #uses the parent class' constructor
        super().__init__(master, height=ht, width=wt)
        self.var = tkinter.StringVar()
        self.var.set("No copied text")
        self.pack_propagate(False) #window will use it's own width and height as parameters instead of child's dimensions
        self.pack()
        self.label = tkinter.Label(self, textvariable=self.var)
        self.label.pack()

    #method to update the label
    def update_label(self, newText):
        self.var.set(newText)
        self.label.pack()



def main():
    #good practice to have a variable to stop the loop
    running = True

    #GUI initialized
    root = tkinter.Tk()
    gui = GUI(root)

    #start thread containing Clipboard Listener 
    listener = ClipListener(.100)
    listener.start()


    #loop to keep updating the program without blocking the clipboard checks (since mainloop() is blocking)
    while running:
        #update the gui
        root.update();
        #wait .1 seconds for event to be set to true
        event_set = listener.copyevent.wait(.100)
        #if true then update the label and reset event
        if event_set:
            gui.update_label(root.clipboard_get())
            listener.copyevent.clear()


#only run this program if it is being used as the main program file
if __name__ == "__main__":
    main()
Ему советует "урезать марш" и проверять буфер обмена каждые 0.5 секунды:
Don't use threads
You don't need the complexities of threading for this problem. You can use the Tkinter method after to run a function periodically. Threading is necessary if the function you are running takes a long time, but that is not the case here.
You can use a class, but to keep this answer simple I'll only show the function.
Also, note that I use an existing window rather than tkinter.Tk() on each call. There's no need to create a new root window every time you do the check.
...
To start running, simply call run_listener once, and it will continue to run every 500 milliseconds:
...
Код на странице.
Он фрагментами, поэтому не цитирую.
mix-7
Posts: 741
Joined: 11.05.2018 11:02

Post by mix-7 »

Здравствуйте!

сделал прототип плагина Cudatext Clipboard catcher
Вот часть кода:

Code: Select all

...
import threading
import time
...

def clip_loop():
    print (app_proc(PROC_GET_CLIP, ""))
    return
    


class Command:
 
    def __init__(self):

        global option_int
        global option_bool
        option_int = int(ini_read(fn_config, 'op', 'option_int', str(option_int)))
        option_bool = str_to_bool(ini_read(fn_config, 'op', 'option_bool', bool_to_str(option_bool)))

    def dialog(self):
        global copy_clips

        thread = threading.Thread(target=clip_loop)
        thread.start()
Проблема: при работе появляется модальное окно небольшого размера с заголовком

Code: Select all

__init__.py - CudaText
И если так

Code: Select all

    t1=" "
    print (app_proc(PROC_GET_CLIP, t1))
тоже появляется модальное окно небольшого размера с заголовком "__init__.py - CudaText"

Если закомментарить

Code: Select all

#    print (app_proc(PROC_GET_CLIP, ""))
то окно не появляется.
Но нужно обрабатывать app_proc(PROC_GET_CLIP, "") в треде.

Code: Select all

print (app_proc(PROC_GET_CLIP, ""))
в функции def dialog(self): работает нормально, выводит текущее содержимое буфера обмена.

Не работает вызов CudaText API в треде?
Можно это как-то обойти.
С app_proc(PROC_GET_CLIP, "") плагин получается коротким и мультиплатформенным.

Спасибо!
mix-7
Posts: 741
Joined: 11.05.2018 11:02

Post by mix-7 »

А, наверное, в clip_loop() надо передать среду окружения, ED?
Как правильно это сделать?

P.S.
По совету отсюда, видимо, надо запомнить и передать ссылку на фиксированный tab.


Да, там же написано, что потоки не будут работать в простое программы.
Жаль.
Попробую, чтобы убедиться на своем опыте.
main Alexey
Posts: 2245
Joined: 25.08.2021 18:15

Post by main Alexey »

появляется модальное окно небольшого размера
не знаю почему. но плагина который бы читал clipboard в треде, еще не было.
некоторые плагины юзают треды, но видимо не так.
например Termilal / ExTerminal.
но плагины (вроде) не юзают Cud API в треде.
А, наверное, в clip_loop() надо передать среду окружения, ED?
то есть 'ed'? ed - всегда ссылка на текущий (с фокусом) редактор.
зачем ed передавать в тред?
он и из треда сработает.
может быть вы хотите прочитать фиксированный хендл
h = ed.get_prop(PROP_HANDLE_SELF, '')
и передать его в тред?
Post Reply