Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions demos/clipboard/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"copyright": "Copyright 2025 Garrett D'Amore",
"description": "Clipboard Demo in Dcell",
"name": "clipboard",
"targetType": "executable",
"targetName": "clipboard",
"targetPath": "bin",
"mainSourceFile": "source/clipboard.d",
"dependencies": {
"dcell": { "path": "../.." }
}
}
150 changes: 150 additions & 0 deletions demos/clipboard/source/clipboard.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Clipboard demo for Dcell.
*
* Copyright: Copyright 2025 Garrett D'Amore
* Authors: Garrett D'Amore
* License:
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE or https://www.boost.org/LICENSE_1_0.txt)
* SPDX-License-Identifier: BSL-1.0
*/
module clipboard;

import std.stdio;
import std.string;
import std.conv;
import std.utf;

import dcell;

class Demo
{
string content;
Screen s;
bool done = false;

this(Screen scr) @safe
{
s = scr;
}

void centerStr(int y, Style style, string str) @safe
{
s.style = style;
s.position = Coord((s.size.x - cast(int)(str.length)) / 2, y);
s.write(str);
}

void display() @safe
{
auto size = s.size();
Style def;
def.bg = Color.black;
def.fg = Color.white;
s.clear();
Style style = {fg: Color.cadetBlue, bg: Color.black};
s.style = style;
centerStr(size.y / 2 - 1, style, "Press 1 to set the clipboard");
centerStr(size.y / 2 + 1, style, "Press 2 to get the clipboard");

auto msg = "No clipboard data";
if (!content.empty)
{
auto len = content.length;
if (content.length >= 40)
{
msg = format("Clipboard (length %d): %s ...", len, content[0 .. 36]);
}
else
{
msg = format("Clipboard (length %d): %s", len, content);
}
}
s.style = def;
centerStr(size.y / 2 + 3, def, msg);
centerStr(size.y / 2 + 5, def, "Press ESC to Exit.");

s.show();
}

void handleEvent(Event ev)
{
switch (ev.type)
{
case EventType.key:
if (ev.key.key == Key.esc || ev.key.key == Key.f1)
{
done = true;
}
else if (ev.key.key == Key.graph && ev.key.mod == Modifiers.none)
{
switch (ev.key.ch)
{
case '1':
s.setClipboard("Enjoy your new clipboard content!".representation);
break;
case '2':
s.getClipboard();
break;
default:
}
}

break;
case EventType.paste:
if (ev.paste.content.length > 0)
{
content = ev.paste.content;
}
else if (ev.paste.binary.length > 0)
{
try
{
auto chars = cast(char[])(ev.paste.binary);
validate(chars);
content = cast(string)(chars.idup);
}
catch (UTFException)
{
content = "Invalid UTF-8";
}
}
break;
case EventType.resize:
s.resize();
display();
s.sync();
break;
default:
break;
}
}

void run()
{
s.start();
scope (exit)
{
s.stop();
}

display();
s.enablePaste(true);
while (!done)
{
s.waitForEvent();
foreach (ev; s.events())
{
handleEvent(ev);
}
display();
}

}
}

void main()
{
auto app = new Demo(newScreen());
app.run();
}
5 changes: 3 additions & 2 deletions source/dcell/event.d
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ struct ResizeEvent
}

/**
* Paste start or stop.
* Paste start or stop. Only one of the content or binary fields will have data.
*/
struct PasteEvent
{
dstring content;
string content; /// string content for normal paste
ubyte[] binary; /// binary data via OSC 52 or similar
}

/// Focus event.
Expand Down
35 changes: 25 additions & 10 deletions source/dcell/parser.d
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module dcell.parser;
import core.time;
import std.algorithm : max;
import std.ascii;
import std.base64;
import std.conv : to;
import std.process : environment;
import std.string;
Expand Down Expand Up @@ -661,14 +662,18 @@ private:

void handleOsc()
{
// TODO: OSC 52 is for clipboard
// if content, ok := strings.CutPrefix(str, "52;c;"); ok {
// decoded := make([]byte, base64.StdEncoding.DecodedLen(len(content)))
// if count, err := base64.StdEncoding.Decode(decoded, []byte(content)); err == nil {
// ip.post(NewEventClipboard(decoded[:count]))
// return
// }
// }
if (scratch.startsWith("52;c;"))
{
scratch = scratch["52;c;".length .. $];
try
{
auto bin = Base64.decode(scratch);
evs ~= newPasteEvent(bin);
}
catch (Base64Exception) // just discard the data if it was malformed
{
}
}

// string is located in scratch.
parseState = ParseState.ini;
Expand Down Expand Up @@ -819,7 +824,7 @@ private:
{
if (pasting)
{
evs ~= newPasteEvent(pasteBuf);
evs ~= newPasteEvent(pasteBuf.to!string);
pasting = false;
pasteBuf = null;
}
Expand Down Expand Up @@ -1207,7 +1212,7 @@ private:
return ev;
}

Event newPasteEvent(dstring buffer) nothrow @safe
Event newPasteEvent(string buffer) nothrow @safe
{
Event ev = {
type: EventType.paste, when: MonoTime.currTime(), paste: {
Expand All @@ -1217,6 +1222,16 @@ private:
return ev;
}

Event newPasteEvent(ubyte[] buffer) nothrow @safe
{
Event ev = {
type: EventType.paste, when: MonoTime.currTime(), paste: {
binary: buffer
}
};
return ev;
}

unittest
{
import core.thread;
Expand Down
24 changes: 24 additions & 0 deletions source/dcell/screen.d
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,31 @@ interface Screen
@property Coord position() const @safe;
@property Coord position(const(Coord)) @safe;

/**
* Write content to the screen. The content will be displayed using the
* position and the style currently set. It will wrap at the edge of the
* display if it is too long.
*/
void write(string) @safe;
void write(wstring) @safe;
void write(dstring) @safe;

/**
* Post arbitrary data to the system clipboard.
* It's up to the recipient to decode the data meaningfully.
* Terminals may prevent this for security reasons.
*/
void setClipboard(const(ubyte[])) @safe;

/**
* Request the clipboard contents. It may be ignored.
*
* If the terminal is willing, it will post the clipboard contents using a
* `PasteEvent` with the clipboard content as the `binary`. (This may or may
* not be valid UTF-8. In most terminals it seems that only string data is posted,
* such as names of files rather than binary file content.)
*
* Terminals may prevent this for security reasons.
*/
void getClipboard() @safe;
}
29 changes: 28 additions & 1 deletion source/dcell/vt.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ package:
import core.atomic;
import core.time;
import std.algorithm : canFind;
import std.format;
import std.base64;
import std.datetime;
import std.exception;
import std.format;
import std.outbuffer;
import std.process;
import std.range;
Expand Down Expand Up @@ -119,6 +120,13 @@ class VtScreen : Screen
// - win32-input-mode (uses CSI _)
string enableCsiU = "\x1b[>4;2m" ~ "\x1b[>1u" ~ "\x1b[?9001h";
string disableCsiU = "\x1b[?9001l" ~ "\x1b[<u" ~ "\x1b[>4;0m";

// OSC 52 is for saving to the clipboard.
// This string takes a base64 string and sends it to the clipboard.
// It will also be able to retrieve the clipboard using "?" as the
// sent string, when we support that.
string setClipboard = "\x1b]52;c;%s\x1b\\";

// number of colors - again this can be overridden.
// Typical values are 0 (monochrome), 8, 16, 256, and 1<<24.
// There are some oddballs like xterm-88color. The first
Expand Down Expand Up @@ -246,6 +254,7 @@ class VtScreen : Screen
vt.saveTitle = null;
vt.enableCsiU = null;
vt.disableCsiU = null;
vt.setClipboard = null;
}
}

Expand Down Expand Up @@ -547,6 +556,24 @@ class VtScreen : Screen
cells.write(s);
}

void setClipboard(const(ubyte[]) b) @safe
{
if (!vt.setClipboard.empty)
{
puts(format(vt.setClipboard, Base64.encode(b)));
flush();
}
}

void getClipboard() @safe
{
if (!vt.setClipboard.empty)
{
puts(format(vt.setClipboard, "?"));
flush();
}
}

private:
struct KeyCode
{
Expand Down