Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom clipboard formats #6223

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c32b348
Alter clipboard to support multiple formats
minetoblend Mar 23, 2024
95116ea
Add custom format support to `HeadlessClipboard`
minetoblend Mar 23, 2024
cdd0c0c
Add in-memory fallback for custom clipboard format to `MacOSClipboard`
minetoblend Mar 23, 2024
16b00d9
Add in-memory fallback for custom clipboard format to `SDL2Clipboard`
minetoblend Mar 23, 2024
d149485
Add support for custom clipboard formats & web custom formats to `Win…
minetoblend Mar 23, 2024
99179b3
Add in-memory fallback for custom clipboard format to `AndroidClipboard`
minetoblend Mar 23, 2024
ef661c9
Add comment linking to web custom format explainer
minetoblend Mar 23, 2024
1b7ff7c
Add doc comments to clipboard entry classes
minetoblend Mar 23, 2024
38b11cf
Fix comments in `Clipboard`
minetoblend Mar 23, 2024
5f7d94d
Undo change to how pointers are freed in `WindowsClipboard.setClipboard`
minetoblend Mar 24, 2024
744d72d
Simplify the data structures used to represent multiple clipboard values
minetoblend Mar 24, 2024
beb1d98
Always free pointer after registering clipboard format
Mar 26, 2024
27efe4c
Refactor logic to create web custom format clipboard entries
Mar 26, 2024
ae106b3
Add documentation explaining the clipboard entries created to support…
Mar 26, 2024
def378e
Use consistent ordering for method parameters
Mar 26, 2024
51b5fbc
Use UTF8 instead of ansi for web custom format clipboard entries
Mar 26, 2024
825b952
Rename variable
Mar 26, 2024
74a35fa
Clean up `ClipboardData` and `Clipboard`
Mar 26, 2024
7e660f0
Rename variables to be consistent between methods
Mar 26, 2024
3398cad
Add doc comment explaining the web custom format fallback when readin…
Mar 26, 2024
48115c3
Add documentation for `SetData` return value
Mar 26, 2024
6fa516d
Fix formatting for `ClipboardData` values in `Clipboard`
Mar 26, 2024
ac78808
Handle cases where registering clipboard format fails
Mar 26, 2024
c758183
Add logging for cases where calls to win32 clipboard api fail
Mar 26, 2024
629ddff
Add logging for cases where calls to win32 clipboard api fail when re…
Mar 26, 2024
7e4231a
Add `SetLastError` flag to dll imports
minetoblend Mar 28, 2024
8081b3c
Fix gibberish in doc comment
minetoblend Mar 28, 2024
e0fd3fb
Rename `customFormats` to `registeredFormatIdentifiers`
minetoblend Mar 28, 2024
87f243b
Use `TryGetValue` to retrieve values from web custom format map
Mar 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 30 additions & 6 deletions osu.Framework.Android/AndroidClipboard.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using Android.Content;
using NuGet.Packaging;
using osu.Framework.Platform;
using SixLabors.ImageSharp;

Expand All @@ -10,6 +12,7 @@ namespace osu.Framework.Android
public class AndroidClipboard : Clipboard
{
private readonly ClipboardManager? clipboardManager;
private readonly Dictionary<string, string> customFormatValues = new Dictionary<string, string>();

public AndroidClipboard(AndroidGameView view)
{
Expand All @@ -18,14 +21,35 @@ public AndroidClipboard(AndroidGameView view)

public override string? GetText() => clipboardManager?.PrimaryClip?.GetItemAt(0)?.Text;

public override void SetText(string text)
public override Image<TPixel>? GetImage<TPixel>() => null;

public override bool SetData(ClipboardData data)
{
if (clipboardManager != null)
clipboardManager.PrimaryClip = ClipData.NewPlainText(null, text);
}
if (clipboardManager == null)
return false;

public override Image<TPixel>? GetImage<TPixel>() => null;
customFormatValues.Clear();
clipboardManager.PrimaryClip = null;

if (data.IsEmpty())
return false;

bool success = true;

if (data.Text != null)
clipboardManager.PrimaryClip = ClipData.NewPlainText(null, data.Text);

public override bool SetImage(Image image) => false;
if (data.Image != null)
success = false;

customFormatValues.AddRange(data.CustomFormatValues);

return success;
}

public override string? GetCustom(string format)
{
return customFormatValues[format];
}
}
}
38 changes: 36 additions & 2 deletions osu.Framework/Platform/Clipboard.cs
Expand Up @@ -20,7 +20,10 @@ public abstract class Clipboard
/// Copy text to the clipboard.
/// </summary>
/// <param name="text">Text to copy to the clipboard</param>
public abstract void SetText(string text);
public void SetText(string text)
{
SetData(new ClipboardData { Text = text });
}

/// <summary>
/// Retrieve an image from the clipboard.
Expand All @@ -33,6 +36,37 @@ public abstract class Clipboard
/// </summary>
/// <param name="image">The image to copy to the clipboard</param>
/// <returns>Whether the image was successfully copied or not</returns>
public abstract bool SetImage(Image image);
public bool SetImage(Image image)
{
return SetData(new ClipboardData { Image = image });
}

/// <summary>
/// Retrieve content with custom mime type from the clipboard.
/// </summary>
/// <returns>Mime type of the clipboard item to retrieve</returns>
public abstract string? GetCustom(string mimeType);

/// <summary>
/// Copy item with custom mime type to the clipboard
/// </summary>
/// <param name="mimeType">Mime type of the clipboard item</param>
/// <param name="text">Text to copy to the clipboard</param>
public void SetCustom(string mimeType, string text)
{
var data = new ClipboardData
{
CustomFormatValues = { [mimeType] = text }
};

SetData(data);
}

/// <summary>
/// Copy multiple values to the clipboard
/// </summary>
/// <param name="data">Data to copy the clipboard</param>
/// <returns>Whether the data was successfully copied or not</returns>
public abstract bool SetData(ClipboardData data);
bdach marked this conversation as resolved.
Show resolved Hide resolved
}
}
44 changes: 44 additions & 0 deletions osu.Framework/Platform/ClipboardData.cs
@@ -0,0 +1,44 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using SixLabors.ImageSharp;

namespace osu.Framework.Platform
{
/// <summary>
/// Holds multiple concurrent representations of some data to be copied to the clipboard.
/// </summary>
public struct ClipboardData
{
/// <summary>
/// Text to be stored in the default plaintext format in the clipboard.
/// </summary>
public string? Text;

/// <summary>
/// Image to be stored in the default image format in the clipboard.
/// </summary>
public Image? Image;

/// <summary>
/// Contains values to be stored with custom mime type in the clipboard.
/// Keyed by mime type.
/// </summary>
public readonly Dictionary<string, string> CustomFormatValues = new Dictionary<string, string>();
bdach marked this conversation as resolved.
Show resolved Hide resolved

public ClipboardData()
{
Text = null;
Image = null;
}

/// <summary>
/// Returns true if no data is present
/// </summary>
public bool IsEmpty()
{
return Text == null && Image == null && CustomFormatValues.Count == 0;
}
}
}
22 changes: 14 additions & 8 deletions osu.Framework/Platform/HeadlessClipboard.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using NuGet.Packaging;
using SixLabors.ImageSharp;

namespace osu.Framework.Platform
Expand All @@ -15,21 +17,25 @@ public class HeadlessClipboard : Clipboard
{
private string? clipboardText;
private Image? clipboardImage;
private readonly Dictionary<string, string> customValues = new Dictionary<string, string>();

public override string? GetText() => clipboardText;

public override void SetText(string text)
public override Image<TPixel>? GetImage<TPixel>() => clipboardImage?.CloneAs<TPixel>();

public override string? GetCustom(string mimeType)
{
clipboardImage = null;
clipboardText = text;
return customValues[mimeType];
}

public override Image<TPixel>? GetImage<TPixel>() => clipboardImage?.CloneAs<TPixel>();

public override bool SetImage(Image image)
public override bool SetData(ClipboardData data)
{
clipboardText = null;
clipboardImage = image;
clipboardText = data.Text;
clipboardImage = data.Image;
customValues.Clear();

customValues.AddRange(data.CustomFormatValues);

return true;
}
}
Expand Down
30 changes: 26 additions & 4 deletions osu.Framework/Platform/MacOS/MacOSClipboard.cs
Expand Up @@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using NuGet.Packaging;
using osu.Framework.Platform.MacOS.Native;
using SixLabors.ImageSharp;

Expand All @@ -11,13 +13,16 @@ public class MacOSClipboard : Clipboard
{
private readonly NSPasteboard generalPasteboard = NSPasteboard.GeneralPasteboard();

private readonly Dictionary<string, string> customFormatValues = new Dictionary<string, string>();

public override string? GetText() => Cocoa.FromNSString(getFromPasteboard(Class.Get("NSString")));

public override Image<TPixel>? GetImage<TPixel>() => Cocoa.FromNSImage<TPixel>(getFromPasteboard(Class.Get("NSImage")));

public override void SetText(string text) => setToPasteboard(Cocoa.ToNSString(text));

public override bool SetImage(Image image) => setToPasteboard(Cocoa.ToNSImage(image));
public override string? GetCustom(string mimeType)
{
return customFormatValues[mimeType];
}

private IntPtr getFromPasteboard(IntPtr @class)
{
Expand All @@ -32,12 +37,29 @@ private IntPtr getFromPasteboard(IntPtr @class)
return objects?.Length > 0 ? objects[0] : IntPtr.Zero;
}

public override bool SetData(ClipboardData data)
{
generalPasteboard.ClearContents();
customFormatValues.Clear();

bool success = true;

if (data.Text != null)
success &= setToPasteboard(Cocoa.ToNSString(data.Text));

if (data.Image != null)
success &= setToPasteboard(Cocoa.ToNSImage(data.Image));

customFormatValues.AddRange(data.CustomFormatValues);

return success;
}

private bool setToPasteboard(IntPtr handle)
{
if (handle == IntPtr.Zero)
return false;

generalPasteboard.ClearContents();
generalPasteboard.WriteObjects(NSArray.ArrayWithObject(handle));
return true;
}
Expand Down
28 changes: 24 additions & 4 deletions osu.Framework/Platform/SDL2/SDL2Clipboard.cs
@@ -1,28 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using NuGet.Packaging;
using SDL2;
using SixLabors.ImageSharp;

namespace osu.Framework.Platform.SDL2
{
public class SDL2Clipboard : Clipboard
{
private readonly Dictionary<string, string> customFormatValues = new Dictionary<string, string>();

// SDL cannot differentiate between string.Empty and no text (eg. empty clipboard or an image)
// doesn't matter as text editors don't really allow copying empty strings.
// assume that empty text means no text.
public override string? GetText() => SDL.SDL_HasClipboardText() == SDL.SDL_bool.SDL_TRUE ? SDL.SDL_GetClipboardText() : null;

public override void SetText(string text) => SDL.SDL_SetClipboardText(text);

public override Image<TPixel>? GetImage<TPixel>()
{
return null;
}

public override bool SetImage(Image image)
public override string? GetCustom(string mimeType)
{
return customFormatValues[mimeType];
}

public override bool SetData(ClipboardData data)
{
return false;
customFormatValues.Clear();

if (data.IsEmpty())
return false;

if (data.Image != null)
return false;

if (data.Text != null)
SDL.SDL_SetClipboardText(data.Text);

customFormatValues.AddRange(data.CustomFormatValues);

return true;
}
}
}