- PVSM.RU - https://www.pvsm.ru -
Буфер обмена — один из основных элементов GUI, но он сложен в реализации. Подводных камней настолько много, что вы больше никогда не сможете произнести слово «копипаст» с презрительным выражением. Есть тысячи приложений и форматов данных. Невозможно обеспечить полную конвертацию всего во всё.
В некоторых случаях данные вообще нигде не сохраняются по нажатию Ctrl+C. И по нажатию Ctrl+V будет возвращён NULL
. Shit happens, как говорится…
В одних программах возможен копипаст только текста, в других можно выделить графику, где-то поддерживается копирование/вставка файлов и т. д. Нюансов очень много. Попробуем выделить основные моменты для Windows и Linux.
Если идти от простого к сложному и в примерно хронологическом порядке, то лучше начать с X11.
Графический стек Linux состоит из множества разных элементов [2], которые объединены в монолитную архитектуру и каким-то чудом работают вместе без постоянных глюков.
Так или иначе, клипборд в X11 работает не так, как в других ОС [3]. Здесь создаётся впечатление двух независимых буферов обмена. Один по горячим клавишам типа Ctrl+C/V (или Ctrl/Shift+Ins), а второй — по нажатию мыши. Если скопировать данные в буфер горячей клавишей с клавиатуры, то и вставить можно только с клавиатуры. А если скопировал мышью — то вставить средним колёсиком. Но иногда работают оба способа, то есть можно скопировать текст в буфер обмена мышью, а вставить — сочетанием клавиш.
Документация подтверждает, что в X11 действительно несколько независимых буферов обмена (здесь они называются selections). Их даже не два, а может быть сколько угодно много. По схеме эти selections представляют способ коммуникации между клиентами. Сами клиенты должны договориться между собой, какой буфер они будут использовать. Например, если вы пишете какой-то клиент под Linux, то как владелец буфера (SelectionOwner) можете ввести свой буфер обмена и надеяться, что остальные его поймут.
То есть работа с буфером обмена в общем виде выглядит так [4]:
Всё кажется просто и логично, при минимальном взаимодействии с системным ядром. Но в реальности тут возможны нестыковки и заминки. Самое главное, что нет абсолютно никакой гарантии, что клиент 1 когда-нибудь отправит запрошенные данные, а уж тем более в указанном формате.
Как же получается, что для пользователя вся эта система выглядит словно два изолированных буфера, один для горячих клавиш, а второй для мыши? Ну, здесь дело в идентификации буферов. Внутренне это произвольные номера, а внешне (для клиентов) три варианта [5]:
PRIMARY
: буфер «средней кнопки мыши» (условно говоря);SECONDARY
: практически не используется в наше время;CLIPBOARD
: буфер Ctrl+C (условно говоря).Часто работающие программы в X11 создают невидимые окна исключительно с целью владения (захвата) того или иного системного буфера. Теоретически, это можно сделать и с помощью видимого окна, но с помощью невидимого проще.
В реальности содержимое буфера передаётся через окна, а именно через запись в свойства окна (туда помещается ограниченный объём данных). В этом смысле работа буферов аналогична работе окон — в X11 нет требований, чтобы эти окна работали на одном хосте или по одному протоколу. Например, теоретически копипаст должен работать в разных программах, даже если клиенты находятся на разных компьютерах и подключены к одному X-серверу через интернет.
Вот так всё работает в X11: это просто брокер между клиентами, который принимает минимальное участие в передаче данных из буфера обмена. В X11 даже нет встроенного менеджера для таких данных.
Как известно, на замену X11 разработан протокол Wayland [6], где всё организовано несколько иначе [7].
Работа буфера обмена в спецификациях Wayland описана с применением той же специфической терминологии (selection — буфер обмена, clipboard — один из буферов обмена, уникальный идентификатор atom и т. д.).
Но если разобраться, всё организовано довольно просто и логично, как в X11, даже с некоторыми усовершенствованиями.
Здесь примерно так же организовано владение буфером определённого типа. Когда вы нажимаете Ctrl+C, программа просто захватывает буфер обмена определённого типа и заявляет о наличии данных в определённом формате (например, image/png
, text/x-moz-url1
или text/plain
). На этом этапе никакие данные никуда не передаются и нигде не сохраняются. Так что в отсутствие специального менеджера для буфера обмена данные будут потеряны при закрытии окна или при их удалении в окне.
Захват буфера разрешён только основному окну. Когда новое окно захватывает буфер, оно получает сообщение от предыдущего владельца буфера о наличии данных и их формате. Затем наступает этап вставки данных «из буфера» (в кавычках, потому что мы уже поняли, что буфер — абстрактное понятие), то есть помеченных для копирования данных. Так вот, второй клиент делает запрос (создаёт дескриптор файла) с указанием формата, в котором хочет получить данные. Этот формат может не полностью совпадать с указанными выше, и тогда он получит лишь часть данных или вовсе ничего.
Несмотря на долгую эволюцию, API для буфера обмена в Wayland далеко не совершенен и этой подсистеме по-прежнему нужен внешний менеджер (библиотека) для нормальной работы.
В Windows применяется та же концепция «владельца буфера» [8]. Владельцем становится любое приложение, которое отправило информацию в буфер стандартным способом. Поддерживаются следующие системные вызовы:
OpenClipboard(hwnd)
отправляет информацию об окне, которое должно стать новым владельцем буфера.EmptyClipboard()
стирает из буфера предыдущее содержимое.SetClipboardData()
для каждого фрагмента данных, которые вы хотите поместить в буфер (по историческим причинам этот вызов могут использовать даже программы, не владеющие буфером).CloseClipboard()
сообщает об окончании работы с данными.
При попытке стороннего окна получить доступ к буферу его владелец получает несколько сообщений. Среди них могут быть WM_RENDERFORMAT
(отложенный рендеринг данных для буфера до того момента, когда они будут непосредственно запрошены — о нём ниже), WM_RENDERALLFORMATS
(часть последовательности уничтожения окна, если он ещё является владельцем буфера обмена на момент его уничтожения) или WM_DESTROYCLIPBOARD
(опустошение содержимого буфера).
Предполагаемая схема использования для чтения данных из буфера обмена следующая:
OpenClipboard(hwnd)
отправляет информацию об окне, которое читает из буфера.GetClipboardData()
для получения данных из буфера.CloseClipboard()
для индикации окончания чтения данных.По словам [8] сотрудника Microsoft с 25-летним стажем Реймонда Чена, если бы все использовали рекомендуемую схему, то Windows бы работала как положено. К сожалению, сторонние программы постоянно норовят нарушить конвенцию, а API допускает запись в чужой буфер. Из-за этого возникают проблемы.
Примерно так же работает буфер в macOS (собственно, изобретателями самой концепции буфера обмена считаются [9] создатели компьютера Mac/Lisa, под Mac есть отличные менеджеры с поиском по многомесячной истории типа ClipBuddy, Alfred и Raycast).
Кроме того, в Windows есть Linux-подобный способ работы с буфером, когда данные нигде не сохраняются, а просто помечаются как доступные [10] (для экономии памяти). Это упомянутый выше отложенный рендеринг WM_RENDERFORMAT
. Поэтому если скопировать в буфер большой диапазон ячеек Excel, а потом попробовать их вставить в другой программе в текстовом формате, то операция может завершиться неудачно. В Windows действует ограничение 30 секунд на отложенный рендеринг (на июнь 2022 года). Если в течение этого времени данные не удалось переконвертировать в новый формат для вставки, то буфер вернёт NULL
.
Таким образом, буфер обмена — это высокоуровневая абстракция, которая описывает человеку механизм передачи данных между программами (клиентами, окнами) на понятном для него языке. При этом в Linux вообще никакого буфера нет, ничего туда не копируется, данные только помечаются для передачи. Это очень грамотный подход — расход памяти сводится к нулю.
Недостаток тоже понятен — при закрытии исходного окна информация «из буфера» тоже будет потеряна и уже никуда не вставится (хотя эту проблему решают специальные менеджеры буфера).
В других операционных системах решили пожертвовать производительностью (памятью) ради удобства пользователей, поскольку копипаст — одна из самых востребованных функций у обычных людей, и потеря данных недопустима.
Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх 🕹️ [11]
Автор: Анатолий Ализар
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/x11/383606
Ссылки в тексте:
[1] CopyQ: https://github.com/hluk/CopyQ
[2] множества разных элементов: https://habr.com/ru/post/148954/
[3] работает не так, как в других ОС: https://tronche.com/gui/x/icccm/sec-2.html#s-2
[4] в общем виде выглядит так: https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html
[5] три варианта: https://tronche.com/gui/x/icccm/sec-2.html#s-2.6.1
[6] протокол Wayland: https://habr.com/ru/post/322580/
[7] несколько иначе: https://whynothugo.nl/journal/2022/10/21/how-the-clipboard-works/
[8] применяется та же концепция «владельца буфера»: https://devblogs.microsoft.com/oldnewthing/20210526-00/?p=105252
[9] считаются: https://lisalist2.com/lisalist1/1796.html
[10] когда данные нигде не сохраняются, а просто помечаются как доступные: https://devblogs.microsoft.com/oldnewthing/20220608-00/?p=106727
[11] Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх 🕹️: https://t.me/ruvds_community
[12] Источник: https://habr.com/ru/post/723812/?utm_source=habrahabr&utm_medium=rss&utm_campaign=723812
Нажмите здесь для печати.