Skip to content

Clipboard: Cross-application copy-paste support - #4499

Open
VimYoung wants to merge 47 commits into
GraphiteEditor:masterfrom
VimYoung:clipboard-svg-fix
Open

VimYoung wants to merge 47 commits into
GraphiteEditor:masterfrom
VimYoung:clipboard-svg-fix

Conversation

@VimYoung

@VimYoung VimYoung commented Sep 3, 2026

Copy link
Copy Markdown

Description

This PR aims to resolve #2373. Solution's approach has been thoroughly discussed in discord's development channel. The PR will fix this issue by introducing copy of both text and svg+xml mime types when copying a selection to the clipboard. Essentially making it possible for other apps to take the svg representation while graphite picks up the internal json representation for copy pasting.

Notes

  1. As mentioned by hypercube, image/svg+xml is not supported by firefox and safari Stable so that is handled.
  2. The intersecting by desktop still doesn't copy the svg and simply ignores it.

Following example showcase the same copy paste not working across apps (krita and inkscape shown as the other apps) in current build vs it working in this branch while still being compatible across graphite tabs.

Before(Only across graphite tabs, not across apps):

editor_before.mp4

After(Across tabs and other apps):

editor_after.mp4

@VimYoung
VimYoung marked this pull request as ready for review September 6, 2026 10:12

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Confidence score: 3/5

  • In editor/src/node_graph_executor/runtime.rs, selections containing an artboard currently discard the List<Artboard> monitor output and emit an empty SVG, so rendered previews or exports can lose the artboard’s content, position, dimensions, and clipping; render artboard outputs as SVG while preserving those properties.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="editor/src/node_graph_executor/runtime.rs">

<violation number="1" location="editor/src/node_graph_executor/runtime.rs:351">
P1: When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

return texture;
}
GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => {
let mut combined_graphics = List::<Graphic>::new();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When the selection contains an artboard, this branch ignores its List<Artboard> monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 351:

<comment>When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</comment>

<file context>
@@ -340,6 +347,59 @@ impl NodeRuntime {
 					return texture;
 				}
+				GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => {
+					let mut combined_graphics = List::<Graphic>::new();
+
+					for monitor_node_path in &self.monitor_nodes {
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think one can copy the artboard as is, even if they copy every content in the artboard, the copying type would still be graphic (of all selected nodes/layers) ig.

Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread editor/src/messages/clipboard/clipboard_message_handler.rs Outdated
Comment thread frontend/src/managers/clipboard.ts Outdated
Comment thread editor/src/node_graph_executor.rs

@0HyperCube 0HyperCube left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works well; thanks for your work so far.

Comment thread desktop/wrapper/src/intercept_frontend_message.rs
Comment thread editor/src/messages/clipboard/clipboard_message_handler.rs Outdated
Comment thread editor/src/messages/frontend/frontend_message.rs Outdated
Comment thread editor/src/node_graph_executor.rs Outdated
Comment thread editor/src/messages/frontend/frontend_message.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread frontend/src/managers/clipboard.ts Outdated
@timon-schelling

Copy link
Copy Markdown
Member

I would like to wait with this until winit update is merged. And we might want to make this API lazy.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files (changes from recent commits).

Confidence score: 3/5

  • In editor/src/node_graph_executor/runtime.rs, coalescing a copy request with graph evaluation can inspect monitor nodes before execution refreshes them, producing stale or empty SVG in the clipboard; ensure the pending execution completes before monitor introspection.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="editor/src/node_graph_executor/runtime.rs">

<violation number="1" location="editor/src/node_graph_executor/runtime.rs:203">
P2: When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread editor/src/node_graph_executor.rs Outdated
}

let requests = [preferences, graph, eyedropper, execution].into_iter().flatten();
let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 203:

<comment>When a copy request and a graph evaluation are coalesced in one runtime pass, this line introspects monitor nodes before the pending execution refreshes them, so the clipboard can contain stale or empty SVG. Ensure the copy path reads output from the relevant completed execution, and avoid relying only on request reordering because the execution branch currently returns before later requests.</comment>

<file context>
@@ -200,7 +200,7 @@ impl NodeRuntime {
 		}
 
-		let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten();
+		let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten();
 
 		for request in requests {
</file context>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically true however it probably isn't very relevant since the user would be copying an SVG that is not yet rendered into the viewport.

You could fix this by having a result variable instead of an early return:

let mut result = None;
for request in requests {
	match request {
		// ...
		GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {
			// ...
			result = Some(texture);
		}
	}
}
result

Comment thread editor/src/node_graph_executor/runtime.rs
@VimYoung

VimYoung commented Sep 7, 2026

Copy link
Copy Markdown
Author

Hey @timon-schelling , we can wait for the API to be merged and then do the changes, what do you mean for the API to be lazy here?
Also can you assign the issue to me as this PR is opened and in review?

@timon-schelling

Copy link
Copy Markdown
Member

Hey @timon-schelling , we can wait for the API to be merged and then do the changes, what do you mean for the API to be lazy here?

See the top level API definitions in rust-windowing/winit#4658

Both the DnD and Clipboard APIs are lazy in winit, meaning data only needs to be constructed once the other application accepts (based on data type).

I think we should model it in a similar way.
I'm not completely decided on how to model this with messages though.

Also can you assign the issue to me as this PR is opened and in review?

We usually do that when something is merged.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread editor/src/messages/clipboard/clipboard_message_handler.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread editor/src/node_graph_executor/runtime.rs
Comment thread editor/src/messages/clipboard/clipboard_message_handler.rs
Comment thread node-graph/libraries/core-types/src/transform.rs
Comment thread node-graph/libraries/core-types/src/transform.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs
Comment thread editor/src/messages/clipboard/clipboard_message_handler.rs
Comment thread editor/src/node_graph_executor/runtime.rs Outdated
Comment thread editor/src/node_graph_executor/runtime.rs
}

let requests = [preferences, graph, eyedropper, execution].into_iter().flatten();
let requests = [preferences, graph, eyedropper, svg_clipboard, execution].into_iter().flatten();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically true however it probably isn't very relevant since the user would be copying an SVG that is not yet rendered into the viewport.

You could fix this by having a result variable instead of an early return:

let mut result = None;
for request in requests {
	match request {
		// ...
		GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {
			// ...
			result = Some(texture);
		}
	}
}
result

VimYoung and others added 2 commits September 17, 2026 17:05
Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com>
…ditor#4518)

* Switch Color struct back to storing unassociated alpha

* Address review feedback

* Update the Invert node and legacy image migration for straight alpha and add round-trip tests

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Keavon and others added 23 commits September 17, 2026 17:08
….rs (GraphiteEditor#4524)

Move Gradient Map into adjustments.rs and position it by the classic 0.3/0.59/0.11 luma
…hiteEditor#4528)

Remove the 'Threshold' node's luminance calculation dropdown so it always compares the Rec. 601 luma
…election of desaturation methods (GraphiteEditor#4529)

* Remove the 'Threshold' node's luminance calculation dropdown so it always compares the Rec. 601 luma

* Find the HSL lightness extremes before encoding and drop a stale luminance TODO
…e properties (GraphiteEditor#4531)

* Add a range slider mode to the spectrum widget and slider rows to node properties

* Reset the weighted strength slider to the node's default of zero and skip markers off the track when picking up the nearest one
…es panel (GraphiteEditor#4532)

* Show a range slider beside bounded numbers in range mode

* Fall back to a plain number widget when a range's bounds are reversed or equal
…or supports having a highlighted stop (GraphiteEditor#4533)

* Add a selection mode to the spectrum widget so only the gradient editor keeps a highlighted stop

* Record the drag mode for every drag and require selection for reordering so a non-selecting widget cannot drag the wrong stop
… a gamma value (GraphiteEditor#4535)

* Add per-channel records and a gamma midtones value to the 'Levels' node, with a channel selector in its Properties panel

* Migrate the old 'Levels' midtones through its output range and bound a lone midtone marker by the track edges
… fields (GraphiteEditor#4226)

Use cargo workspace to remove dublicated version, license and author fields
* Desktop: Upgrade winit and port DnD to the new data transfer API

* Desktop: Read and write the clipboard via the winit data transfer API
…eEditor#4536)

* Add colorize and six hue ranges to the 'Hue/Saturation' node

* Hide the 'Hue/Saturation' range rows while colorize is on, note the +100 saturation rule, and tidy two lints and a tooltip
… work in linear light (GraphiteEditor#4537)

* Add a saturation input to the 'Vibrance' node and rework both axes to work in linear light

* Refine the sliders

* Clarify some comments
… the Luminosity blend's clipping (GraphiteEditor#4540)

* Add a 'Use Tint' toggle to the 'Black & White' node and give its tint the Luminosity blend's clipping

* Fix the Black & White migration for a wired tint and gray out the tint when unused
Rename the 'SpectrumInput' widget to 'SliderInput'
…erInput (GraphiteEditor#4546)

Make the ColorInput's gradient rendering strategy to use that of SliderInput, fixing a flicker in Firefox when editing gradients

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread editor/src/node_graph_executor.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 119 files (changes from recent commits).

Not reviewed (too large): node-graph/nodes/raster/src/adjustments.rs (~2,305 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread editor/src/node_graph_executor/runtime.rs
@VimYoung

Copy link
Copy Markdown
Author

Hey @0HyperCube, I have done the changes that you proposed after the second review, please take a look if this PR is still relevant (as there were some ongoing discussions on scrapping it it favor of something that would follow winit's new API changes).

@0HyperCube

Copy link
Copy Markdown
Contributor

Hi @VimYoung, thanks for your continued work on this. In my view this is the most viable approach for the moment.

The winit clipboard support with other types was added in #4519 (graphite now uses a winit fork). It should now be possible to implement the desktop support to this PR. See the clipboard example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Copy/pasting vector and raster content between other graphics apps

5 participants