mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-28 16:58:01 +01:00
Merge pull request #1315 from BDisp/clipboard-improvement
Fixes #983. Improving clipboard with interaction with the OS.
This commit is contained in:
@@ -19,11 +19,14 @@ namespace Terminal.Gui {
|
||||
internal class CursesDriver : ConsoleDriver {
|
||||
public override int Cols => Curses.Cols;
|
||||
public override int Rows => Curses.Lines;
|
||||
public override int Left => 0;
|
||||
public override int Top => 0;
|
||||
public override bool HeightAsBuffer { get; set; }
|
||||
public override IClipboard Clipboard { get => clipboard; }
|
||||
|
||||
CursorVisibility? initialCursorVisibility = null;
|
||||
CursorVisibility? currentCursorVisibility = null;
|
||||
IClipboard clipboard;
|
||||
|
||||
// Current row, and current col, tracked by Move/AddRune only
|
||||
int ccol, crow;
|
||||
@@ -102,8 +105,8 @@ namespace Terminal.Gui {
|
||||
//Console.Out.Flush ();
|
||||
|
||||
//Set cursor key to cursor.
|
||||
Console.Out.Write ("\x1b[?1l");
|
||||
Console.Out.Flush ();
|
||||
//Console.Out.Write ("\x1b[?1l");
|
||||
//Console.Out.Flush ();
|
||||
}
|
||||
|
||||
public override void UpdateScreen () => window.redrawwin ();
|
||||
@@ -714,8 +717,8 @@ namespace Terminal.Gui {
|
||||
|
||||
try {
|
||||
//Set cursor key to application.
|
||||
Console.Out.Write ("\x1b[?1h");
|
||||
Console.Out.Flush ();
|
||||
//Console.Out.Write ("\x1b[?1h");
|
||||
//Console.Out.Flush ();
|
||||
|
||||
window = Curses.initscr ();
|
||||
} catch (Exception e) {
|
||||
@@ -745,6 +748,12 @@ namespace Terminal.Gui {
|
||||
break;
|
||||
}
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
|
||||
clipboard = new MacOSXClipboard ();
|
||||
} else {
|
||||
clipboard = new CursesClipboard ();
|
||||
}
|
||||
|
||||
Curses.raw ();
|
||||
Curses.noecho ();
|
||||
|
||||
@@ -1046,4 +1055,139 @@ namespace Terminal.Gui {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CursesClipboard : IClipboard {
|
||||
public string GetClipboardData ()
|
||||
{
|
||||
var tempFileName = System.IO.Path.GetTempFileName ();
|
||||
try {
|
||||
// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
|
||||
BashRunner.Run ($"xclip -o > {tempFileName}");
|
||||
return System.IO.File.ReadAllText (tempFileName);
|
||||
} finally {
|
||||
System.IO.File.Delete (tempFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetClipboardData (string text)
|
||||
{
|
||||
var tempFileName = System.IO.Path.GetTempFileName ();
|
||||
System.IO.File.WriteAllText (tempFileName, text);
|
||||
try {
|
||||
// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
|
||||
BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
|
||||
} finally {
|
||||
System.IO.File.Delete (tempFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class BashRunner {
|
||||
public static string Run (string commandLine)
|
||||
{
|
||||
var errorBuilder = new System.Text.StringBuilder ();
|
||||
var outputBuilder = new System.Text.StringBuilder ();
|
||||
var arguments = $"-c \"{commandLine}\"";
|
||||
using (var process = new System.Diagnostics.Process {
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo {
|
||||
FileName = "bash",
|
||||
Arguments = arguments,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = false,
|
||||
}
|
||||
}) {
|
||||
process.Start ();
|
||||
process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
|
||||
process.BeginOutputReadLine ();
|
||||
process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
|
||||
process.BeginErrorReadLine ();
|
||||
if (!process.DoubleWaitForExit ()) {
|
||||
var timeoutError = $@"Process timed out. Command line: bash {arguments}.
|
||||
Output: {outputBuilder}
|
||||
Error: {errorBuilder}";
|
||||
throw new Exception (timeoutError);
|
||||
}
|
||||
if (process.ExitCode == 0) {
|
||||
return outputBuilder.ToString ();
|
||||
}
|
||||
|
||||
var error = $@"Could not execute process. Command line: bash {arguments}.
|
||||
Output: {outputBuilder}
|
||||
Error: {errorBuilder}";
|
||||
throw new Exception (error);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DoubleWaitForExit (this System.Diagnostics.Process process)
|
||||
{
|
||||
var result = process.WaitForExit (500);
|
||||
if (result) {
|
||||
process.WaitForExit ();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MacOSXClipboard : IClipboard {
|
||||
IntPtr nsString = objc_getClass ("NSString");
|
||||
IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
|
||||
IntPtr utfTextType;
|
||||
IntPtr generalPasteboard;
|
||||
IntPtr initWithUtf8Register = sel_registerName ("initWithUTF8String:");
|
||||
IntPtr allocRegister = sel_registerName ("alloc");
|
||||
IntPtr setStringRegister = sel_registerName ("setString:forType:");
|
||||
IntPtr stringForTypeRegister = sel_registerName ("stringForType:");
|
||||
IntPtr utf8Register = sel_registerName ("UTF8String");
|
||||
IntPtr nsStringPboardType;
|
||||
IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
|
||||
IntPtr clearContentsRegister = sel_registerName ("clearContents");
|
||||
|
||||
public MacOSXClipboard ()
|
||||
{
|
||||
utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
|
||||
nsStringPboardType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "NSStringPboardType");
|
||||
generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
|
||||
}
|
||||
|
||||
public string GetClipboardData ()
|
||||
{
|
||||
var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
|
||||
var charArray = objc_msgSend (ptr, utf8Register);
|
||||
return Marshal.PtrToStringAnsi (charArray);
|
||||
}
|
||||
|
||||
public void SetClipboardData (string text)
|
||||
{
|
||||
IntPtr str = default;
|
||||
try {
|
||||
str = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, text);
|
||||
objc_msgSend (generalPasteboard, clearContentsRegister);
|
||||
objc_msgSend (generalPasteboard, setStringRegister, str, utfTextType);
|
||||
} finally {
|
||||
if (str != default) {
|
||||
objc_msgSend (str, sel_registerName ("release"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr objc_getClass (string className);
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector);
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1);
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1);
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
|
||||
|
||||
[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
|
||||
static extern IntPtr sel_registerName (string selectorName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,13 @@ namespace Unix.Terminal {
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
public partial class Curses {
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
public struct winsize {
|
||||
public ushort ws_row;
|
||||
public ushort ws_col;
|
||||
public ushort ws_xpixel; /* unused */
|
||||
public ushort ws_ypixel; /* unused */
|
||||
};
|
||||
//[StructLayout (LayoutKind.Sequential)]
|
||||
//public struct winsize {
|
||||
// public ushort ws_row;
|
||||
// public ushort ws_col;
|
||||
// public ushort ws_xpixel; /* unused */
|
||||
// public ushort ws_ypixel; /* unused */
|
||||
//};
|
||||
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
public struct MouseEvent {
|
||||
@@ -77,8 +77,8 @@ namespace Unix.Terminal {
|
||||
[DllImport ("libc")]
|
||||
public extern static int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale);
|
||||
|
||||
[DllImport ("libc")]
|
||||
public extern static int ioctl (int fd, int cmd, out winsize argp);
|
||||
//[DllImport ("libc")]
|
||||
//public extern static int ioctl (int fd, int cmd, out winsize argp);
|
||||
|
||||
static void LoadMethods ()
|
||||
{
|
||||
@@ -142,11 +142,11 @@ namespace Unix.Terminal {
|
||||
if (l == 1 || l != lines || c != cols) {
|
||||
lines = l;
|
||||
cols = c;
|
||||
if (l <= 0 || c <= 0) {
|
||||
Console.Out.Write ($"\x1b[8;50;{c}t");
|
||||
Console.Out.Flush ();
|
||||
return false;
|
||||
}
|
||||
//if (l <= 0 || c <= 0) {
|
||||
// Console.Out.Write ($"\x1b[8;50;{c}t");
|
||||
// Console.Out.Flush ();
|
||||
// return false;
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -200,29 +200,29 @@ namespace Unix.Terminal {
|
||||
|
||||
internal static void console_sharp_get_dims (out int lines, out int cols)
|
||||
{
|
||||
//lines = Marshal.ReadInt32 (lines_ptr);
|
||||
//cols = Marshal.ReadInt32 (cols_ptr);
|
||||
lines = Marshal.ReadInt32 (lines_ptr);
|
||||
cols = Marshal.ReadInt32 (cols_ptr);
|
||||
|
||||
int cmd;
|
||||
if (UnmanagedLibrary.IsMacOSPlatform) {
|
||||
cmd = TIOCGWINSZ_MAC;
|
||||
} else {
|
||||
cmd = TIOCGWINSZ;
|
||||
}
|
||||
//int cmd;
|
||||
//if (UnmanagedLibrary.IsMacOSPlatform) {
|
||||
// cmd = TIOCGWINSZ_MAC;
|
||||
//} else {
|
||||
// cmd = TIOCGWINSZ;
|
||||
//}
|
||||
|
||||
if (ioctl (1, cmd, out winsize ws) == 0) {
|
||||
lines = ws.ws_row;
|
||||
cols = ws.ws_col;
|
||||
//if (ioctl (1, cmd, out winsize ws) == 0) {
|
||||
// lines = ws.ws_row;
|
||||
// cols = ws.ws_col;
|
||||
|
||||
if (lines == Lines && cols == Cols) {
|
||||
return;
|
||||
}
|
||||
// if (lines == Lines && cols == Cols) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
resizeterm (lines, cols);
|
||||
} else {
|
||||
lines = Lines;
|
||||
cols = Cols;
|
||||
}
|
||||
// resizeterm (lines, cols);
|
||||
//} else {
|
||||
// lines = Lines;
|
||||
// cols = Cols;
|
||||
//}
|
||||
}
|
||||
|
||||
public static Event mousemask (Event newmask, out Event oldmask)
|
||||
@@ -233,7 +233,6 @@ namespace Unix.Terminal {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// We encode ESC + char (what Alt-char generates) as 0x2000 + char
|
||||
public const int KeyAlt = 0x2000;
|
||||
|
||||
@@ -243,6 +242,7 @@ namespace Unix.Terminal {
|
||||
return key & ~KeyAlt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int StartColor () => methods.start_color ();
|
||||
public static bool HasColors => methods.has_colors ();
|
||||
public static int InitColorPair (short pair, short foreground, short background) => methods.init_pair (pair, foreground, background);
|
||||
|
||||
@@ -12,10 +12,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
|
||||
#pragma warning disable RCS1138 // Add summary to documentation comment.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class FakeConsole {
|
||||
#pragma warning restore RCS1138 // Add summary to documentation comment.
|
||||
|
||||
//
|
||||
// Summary:
|
||||
@@ -36,10 +39,22 @@ namespace Terminal.Gui {
|
||||
//
|
||||
// T:System.IO.IOException:
|
||||
// Error reading or writing information.
|
||||
#pragma warning disable RCS1138 // Add summary to documentation comment.
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the initial console width.
|
||||
/// </summary>
|
||||
public const int WIDTH = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the initial console height.
|
||||
/// </summary>
|
||||
public const int HEIGHT = 25;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static int WindowWidth { get; set; } = 80;
|
||||
public static int WindowWidth { get; set; } = WIDTH;
|
||||
//
|
||||
// Summary:
|
||||
// Gets a value that indicates whether output has been redirected from the standard
|
||||
@@ -198,7 +213,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static int BufferHeight { get; set; } = 25;
|
||||
public static int BufferHeight { get; set; } = HEIGHT;
|
||||
//
|
||||
// Summary:
|
||||
// Gets or sets the width of the buffer area.
|
||||
@@ -220,7 +235,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static int BufferWidth { get; set; } = 80;
|
||||
public static int BufferWidth { get; set; } = WIDTH;
|
||||
//
|
||||
// Summary:
|
||||
// Gets or sets the height of the console window area.
|
||||
@@ -243,7 +258,7 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static int WindowHeight { get; set; } = 25;
|
||||
public static int WindowHeight { get; set; } = HEIGHT;
|
||||
//
|
||||
// Summary:
|
||||
// Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control
|
||||
@@ -531,7 +546,7 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public static void Clear ()
|
||||
{
|
||||
_buffer = new char [WindowWidth, WindowHeight];
|
||||
_buffer = new char [BufferWidth, BufferHeight];
|
||||
SetCursorPosition (0, 0);
|
||||
}
|
||||
|
||||
@@ -905,7 +920,8 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public static void SetBufferSize (int width, int height)
|
||||
{
|
||||
throw new NotImplementedException ();
|
||||
BufferWidth = width;
|
||||
BufferHeight = height;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -939,6 +955,8 @@ namespace Terminal.Gui {
|
||||
{
|
||||
CursorLeft = left;
|
||||
CursorTop = top;
|
||||
WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0);
|
||||
WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using NStack;
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
using Console = Terminal.Gui.FakeConsole;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
@@ -16,11 +19,14 @@ namespace Terminal.Gui {
|
||||
/// </summary>
|
||||
public class FakeDriver : ConsoleDriver {
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
int cols, rows;
|
||||
int cols, rows, left, top;
|
||||
public override int Cols => cols;
|
||||
public override int Rows => rows;
|
||||
// Only handling left here because not all terminals has a horizontal scroll bar.
|
||||
public override int Left => 0;
|
||||
public override int Top => 0;
|
||||
public override bool HeightAsBuffer { get; set; }
|
||||
public override IClipboard Clipboard { get; }
|
||||
|
||||
// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
|
||||
int [,,] contents;
|
||||
@@ -53,9 +59,13 @@ namespace Terminal.Gui {
|
||||
|
||||
public FakeDriver ()
|
||||
{
|
||||
cols = FakeConsole.WindowWidth;
|
||||
rows = FakeConsole.WindowHeight; // - 1;
|
||||
UpdateOffscreen ();
|
||||
if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
|
||||
Clipboard = new WindowsClipboard ();
|
||||
} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
|
||||
Clipboard = new MacOSXClipboard ();
|
||||
} else {
|
||||
Clipboard = new CursesClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
bool needMove;
|
||||
@@ -127,6 +137,12 @@ namespace Terminal.Gui {
|
||||
|
||||
public override void Init (Action terminalResized)
|
||||
{
|
||||
TerminalResized = terminalResized;
|
||||
|
||||
cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
|
||||
rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
|
||||
UpdateOffscreen ();
|
||||
|
||||
Colors.TopLevel = new ColorScheme ();
|
||||
Colors.Base = new ColorScheme ();
|
||||
Colors.Dialog = new ColorScheme ();
|
||||
@@ -190,14 +206,16 @@ namespace Terminal.Gui {
|
||||
|
||||
public override void UpdateScreen ()
|
||||
{
|
||||
int rows = Rows;
|
||||
int top = Top;
|
||||
int left = Left;
|
||||
int rows = Math.Min (Console.WindowHeight + top, Rows);
|
||||
int cols = Cols;
|
||||
|
||||
FakeConsole.CursorTop = 0;
|
||||
FakeConsole.CursorLeft = 0;
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int row = top; row < rows; row++) {
|
||||
dirtyLine [row] = false;
|
||||
for (int col = 0; col < cols; col++) {
|
||||
for (int col = left; col < cols; col++) {
|
||||
contents [row, col, 2] = 0;
|
||||
var color = contents [row, col, 1];
|
||||
if (color != redrawColor)
|
||||
@@ -442,6 +460,94 @@ namespace Terminal.Gui {
|
||||
ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
|
||||
}
|
||||
|
||||
public void SetBufferSize (int width, int height)
|
||||
{
|
||||
cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = width;
|
||||
rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = height;
|
||||
ProcessResize ();
|
||||
}
|
||||
|
||||
public void SetWindowSize (int width, int height)
|
||||
{
|
||||
FakeConsole.WindowWidth = width;
|
||||
FakeConsole.WindowHeight = height;
|
||||
if (width > cols || !HeightAsBuffer) {
|
||||
cols = FakeConsole.BufferWidth = width;
|
||||
}
|
||||
if (height > rows || !HeightAsBuffer) {
|
||||
rows = FakeConsole.BufferHeight = height;
|
||||
}
|
||||
ProcessResize ();
|
||||
}
|
||||
|
||||
public void SetWindowPosition (int left, int top)
|
||||
{
|
||||
if (HeightAsBuffer) {
|
||||
this.left = FakeConsole.WindowLeft = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
|
||||
this.top = FakeConsole.WindowTop = Math.Max (Math.Min (top, Rows - Console.WindowHeight), 0);
|
||||
} else if (this.left > 0 || this.top > 0) {
|
||||
this.left = FakeConsole.WindowLeft = 0;
|
||||
this.top = FakeConsole.WindowTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessResize ()
|
||||
{
|
||||
ResizeScreen ();
|
||||
UpdateOffScreen ();
|
||||
TerminalResized?.Invoke ();
|
||||
}
|
||||
|
||||
void ResizeScreen ()
|
||||
{
|
||||
if (!HeightAsBuffer) {
|
||||
if (Console.WindowHeight > 0) {
|
||||
// Can raise an exception while is still resizing.
|
||||
try {
|
||||
#pragma warning disable CA1416
|
||||
Console.CursorTop = 0;
|
||||
Console.CursorLeft = 0;
|
||||
Console.WindowTop = 0;
|
||||
Console.WindowLeft = 0;
|
||||
#pragma warning restore CA1416
|
||||
} catch (System.IO.IOException) {
|
||||
return;
|
||||
} catch (ArgumentOutOfRangeException) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
#pragma warning disable CA1416
|
||||
Console.WindowLeft = Math.Max (Math.Min (left, Cols - Console.WindowWidth), 0);
|
||||
Console.WindowTop = Math.Max (Math.Min (top, Rows - Console.WindowHeight), 0);
|
||||
#pragma warning restore CA1416
|
||||
} catch (Exception) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Clip = new Rect (0, 0, Cols, Rows);
|
||||
|
||||
contents = new int [Rows, Cols, 3];
|
||||
dirtyLine = new bool [Rows];
|
||||
}
|
||||
|
||||
void UpdateOffScreen ()
|
||||
{
|
||||
// Can raise an exception while is still resizing.
|
||||
try {
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
contents [row, c, 0] = ' ';
|
||||
contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
|
||||
contents [row, c, 2] = 0;
|
||||
dirtyLine [row] = true;
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfRangeException) { }
|
||||
}
|
||||
|
||||
#region Unused
|
||||
public override void SetColors (ConsoleColor foreground, ConsoleColor background)
|
||||
{
|
||||
|
||||
@@ -1076,12 +1076,14 @@ namespace Terminal.Gui {
|
||||
int cols, rows, top;
|
||||
public override int Cols => cols;
|
||||
public override int Rows => rows;
|
||||
public override int Left => 0;
|
||||
public override int Top => top;
|
||||
public override bool HeightAsBuffer { get; set; }
|
||||
|
||||
public NetWinVTConsole NetWinConsole { get; }
|
||||
public bool IsWinPlatform { get; }
|
||||
public bool AlwaysSetPosition { get; set; }
|
||||
public override IClipboard Clipboard { get; }
|
||||
|
||||
int largestWindowHeight;
|
||||
|
||||
@@ -1093,6 +1095,13 @@ namespace Terminal.Gui {
|
||||
NetWinConsole = new NetWinVTConsole ();
|
||||
}
|
||||
largestWindowHeight = Math.Max (Console.BufferHeight, largestWindowHeight);
|
||||
if (IsWinPlatform) {
|
||||
Clipboard = new WindowsClipboard ();
|
||||
} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
|
||||
Clipboard = new MacOSXClipboard ();
|
||||
} else {
|
||||
Clipboard = new CursesClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
//
|
||||
using NStack;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -620,12 +621,16 @@ namespace Terminal.Gui {
|
||||
int cols, rows, top;
|
||||
WindowsConsole winConsole;
|
||||
WindowsConsole.SmallRect damageRegion;
|
||||
IClipboard clipboard;
|
||||
|
||||
public override int Cols => cols;
|
||||
public override int Rows => rows;
|
||||
public override int Left => 0;
|
||||
public override int Top => top;
|
||||
public override bool HeightAsBuffer { get; set; }
|
||||
|
||||
public override IClipboard Clipboard => clipboard;
|
||||
|
||||
public WindowsConsole WinConsole {
|
||||
get => winConsole;
|
||||
private set => winConsole = value;
|
||||
@@ -639,6 +644,7 @@ namespace Terminal.Gui {
|
||||
public WindowsDriver ()
|
||||
{
|
||||
winConsole = new WindowsConsole ();
|
||||
clipboard = new WindowsClipboard ();
|
||||
}
|
||||
|
||||
bool winChanging;
|
||||
@@ -1633,4 +1639,137 @@ namespace Terminal.Gui {
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsClipboard : IClipboard {
|
||||
public string GetClipboardData ()
|
||||
{
|
||||
if (!IsClipboardFormatAvailable (cfUnicodeText))
|
||||
return null;
|
||||
|
||||
try {
|
||||
if (!OpenClipboard (IntPtr.Zero))
|
||||
return null;
|
||||
|
||||
IntPtr handle = GetClipboardData (cfUnicodeText);
|
||||
if (handle == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
IntPtr pointer = IntPtr.Zero;
|
||||
|
||||
try {
|
||||
pointer = GlobalLock (handle);
|
||||
if (pointer == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
int size = GlobalSize (handle);
|
||||
byte [] buff = new byte [size];
|
||||
|
||||
Marshal.Copy (pointer, buff, 0, size);
|
||||
|
||||
return System.Text.Encoding.Unicode.GetString (buff)
|
||||
.TrimEnd ('\0')
|
||||
.Replace ("\r\n", "\n");
|
||||
} finally {
|
||||
if (pointer != IntPtr.Zero)
|
||||
GlobalUnlock (handle);
|
||||
}
|
||||
} finally {
|
||||
CloseClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetClipboardData (string text)
|
||||
{
|
||||
OpenClipboard ();
|
||||
|
||||
EmptyClipboard ();
|
||||
IntPtr hGlobal = default;
|
||||
try {
|
||||
var bytes = (text.Length + 1) * 2;
|
||||
hGlobal = Marshal.AllocHGlobal (bytes);
|
||||
|
||||
if (hGlobal == default) {
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
var target = GlobalLock (hGlobal);
|
||||
|
||||
if (target == default) {
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
try {
|
||||
Marshal.Copy (text.ToCharArray (), 0, target, text.Length);
|
||||
} finally {
|
||||
GlobalUnlock (target);
|
||||
}
|
||||
|
||||
if (SetClipboardData (cfUnicodeText, hGlobal) == default) {
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
hGlobal = default;
|
||||
} finally {
|
||||
if (hGlobal != default) {
|
||||
Marshal.FreeHGlobal (hGlobal);
|
||||
}
|
||||
|
||||
CloseClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenClipboard ()
|
||||
{
|
||||
var num = 10;
|
||||
while (true) {
|
||||
if (OpenClipboard (default)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (--num == 0) {
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
Thread.Sleep (100);
|
||||
}
|
||||
}
|
||||
|
||||
const uint cfUnicodeText = 13;
|
||||
|
||||
void ThrowWin32 ()
|
||||
{
|
||||
throw new Win32Exception (Marshal.GetLastWin32Error ());
|
||||
}
|
||||
|
||||
[DllImport ("User32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
static extern bool IsClipboardFormatAvailable (uint format);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
static extern int GlobalSize (IntPtr handle);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
static extern IntPtr GlobalLock (IntPtr hMem);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
static extern bool GlobalUnlock (IntPtr hMem);
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
static extern bool OpenClipboard (IntPtr hWndNewOwner);
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
static extern bool CloseClipboard ();
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
static extern IntPtr SetClipboardData (uint uFormat, IntPtr data);
|
||||
|
||||
[DllImport ("user32.dll")]
|
||||
static extern bool EmptyClipboard ();
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
static extern IntPtr GetClipboardData (uint uFormat);
|
||||
}
|
||||
}
|
||||
|
||||
32
Terminal.Gui/Core/Clipboard.cs
Normal file
32
Terminal.Gui/Core/Clipboard.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using NStack;
|
||||
using System;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Provides cut, copy, and paste support for the clipboard with OS interaction.
|
||||
/// </summary>
|
||||
public static class Clipboard {
|
||||
static ustring contents;
|
||||
|
||||
/// <summary>
|
||||
/// Get or sets the operation system clipboard, otherwise the contents field.
|
||||
/// </summary>
|
||||
public static ustring Contents {
|
||||
get {
|
||||
try {
|
||||
return Application.Driver.Clipboard.GetClipboardData ();
|
||||
} catch (Exception) {
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
set {
|
||||
try {
|
||||
Application.Driver.Clipboard.SetClipboardData (value.ToString ());
|
||||
contents = value;
|
||||
} catch (Exception) {
|
||||
contents = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,15 +613,27 @@ namespace Terminal.Gui {
|
||||
/// The current number of columns in the terminal.
|
||||
/// </summary>
|
||||
public abstract int Cols { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current number of rows in the terminal.
|
||||
/// </summary>
|
||||
public abstract int Rows { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current left in the terminal.
|
||||
/// </summary>
|
||||
public abstract int Left { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current top in the terminal.
|
||||
/// </summary>
|
||||
public abstract int Top { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the operation system clipboard.
|
||||
/// </summary>
|
||||
public abstract IClipboard Clipboard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If false height is measured by the window height and thus no scrolling.
|
||||
/// If true then height is measured by the buffer height, enabling scrolling.
|
||||
@@ -1166,4 +1178,20 @@ namespace Terminal.Gui {
|
||||
/// <returns>The current attribute.</returns>
|
||||
public abstract Attribute GetAttribute ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition to interact with the OS clipboard.
|
||||
/// </summary>
|
||||
public interface IClipboard {
|
||||
/// <summary>
|
||||
/// Sets the operation system clipboard.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
void SetClipboardData (string text);
|
||||
/// <summary>
|
||||
/// Get the operation system clipboard.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetClipboardData ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using NStack;
|
||||
|
||||
namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Provides cut, copy, and paste support for the clipboard.
|
||||
/// NOTE: Currently not implemented.
|
||||
/// </summary>
|
||||
public static class Clipboard {
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static ustring Contents { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1662,7 +1662,7 @@ namespace Terminal.Gui {
|
||||
int lineRuneCount = line.Count;
|
||||
var col = 0;
|
||||
|
||||
Move (0, idxRow);
|
||||
Move (0, row);
|
||||
for (int idxCol = leftColumn; idxCol < lineRuneCount; idxCol++) {
|
||||
var rune = idxCol >= lineRuneCount ? ' ' : line [idxCol];
|
||||
var cols = Rune.ColumnWidth (rune);
|
||||
|
||||
17
UnitTests/ClipboardTests.cs
Normal file
17
UnitTests/ClipboardTests.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Xunit;
|
||||
|
||||
namespace Terminal.Gui.Core {
|
||||
public class ClipboardTests {
|
||||
[Fact]
|
||||
public void Contents_Gets_Sets ()
|
||||
{
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
var clipText = "This is a clipboard unit test.";
|
||||
Clipboard.Contents = clipText;
|
||||
Assert.Equal (clipText, Clipboard.Contents);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,5 +245,173 @@ namespace Terminal.Gui.ConsoleDrivers {
|
||||
// Shutdown must be called to safely clean up Application if Init has been called
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TerminalResized_Simulation ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
var wasTerminalResized = false;
|
||||
Application.Resized = (e) => {
|
||||
wasTerminalResized = true;
|
||||
Assert.Equal (120, e.Cols);
|
||||
Assert.Equal (40, e.Rows);
|
||||
};
|
||||
|
||||
Assert.Equal (80, Console.BufferWidth);
|
||||
Assert.Equal (25, Console.BufferHeight);
|
||||
|
||||
// MockDriver is by default 80x25
|
||||
Assert.Equal (Console.BufferWidth, driver.Cols);
|
||||
Assert.Equal (Console.BufferHeight, driver.Rows);
|
||||
Assert.False (wasTerminalResized);
|
||||
|
||||
// MockDriver will now be sets to 120x40
|
||||
driver.SetBufferSize (120, 40);
|
||||
Assert.Equal (120, Application.Driver.Cols);
|
||||
Assert.Equal (40, Application.Driver.Rows);
|
||||
Assert.True (wasTerminalResized);
|
||||
|
||||
// MockDriver will still be 120x40
|
||||
wasTerminalResized = false;
|
||||
Application.HeightAsBuffer = true;
|
||||
driver.SetWindowSize (40, 20);
|
||||
Assert.Equal (120, Application.Driver.Cols);
|
||||
Assert.Equal (40, Application.Driver.Rows);
|
||||
Assert.Equal (120, Console.BufferWidth);
|
||||
Assert.Equal (40, Console.BufferHeight);
|
||||
Assert.Equal (40, Console.WindowWidth);
|
||||
Assert.Equal (20, Console.WindowHeight);
|
||||
Assert.True (wasTerminalResized);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HeightAsBuffer_Is_False_Left_And_Top_Is_Always_Zero ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
Assert.False (Application.HeightAsBuffer);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
driver.SetWindowPosition (5, 5);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
Application.HeightAsBuffer = true;
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
|
||||
driver.SetWindowPosition (81, 25);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HeightAsBuffer_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
Application.HeightAsBuffer = true;
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
|
||||
driver.SetWindowPosition (81, 25);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
// MockDriver will now be sets to 120x25
|
||||
driver.SetBufferSize (120, 25);
|
||||
Assert.Equal (120, Application.Driver.Cols);
|
||||
Assert.Equal (25, Application.Driver.Rows);
|
||||
Assert.Equal (120, Console.BufferWidth);
|
||||
Assert.Equal (25, Console.BufferHeight);
|
||||
Assert.Equal (120, Console.WindowWidth);
|
||||
Assert.Equal (25, Console.WindowHeight);
|
||||
driver.SetWindowPosition (121, 25);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
driver.SetWindowSize (90, 25);
|
||||
Assert.Equal (120, Application.Driver.Cols);
|
||||
Assert.Equal (25, Application.Driver.Rows);
|
||||
Assert.Equal (120, Console.BufferWidth);
|
||||
Assert.Equal (25, Console.BufferHeight);
|
||||
Assert.Equal (90, Console.WindowWidth);
|
||||
Assert.Equal (25, Console.WindowHeight);
|
||||
driver.SetWindowPosition (121, 25);
|
||||
Assert.Equal (30, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
Application.HeightAsBuffer = true;
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
|
||||
driver.SetWindowPosition (80, 26);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HeightAsBuffer_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
Application.HeightAsBuffer = true;
|
||||
Assert.True (Application.HeightAsBuffer);
|
||||
|
||||
driver.SetWindowPosition (80, 26);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
// MockDriver will now be sets to 120x25
|
||||
driver.SetBufferSize (80, 40);
|
||||
Assert.Equal (80, Application.Driver.Cols);
|
||||
Assert.Equal (40, Application.Driver.Rows);
|
||||
Assert.Equal (80, Console.BufferWidth);
|
||||
Assert.Equal (40, Console.BufferHeight);
|
||||
Assert.Equal (80, Console.WindowWidth);
|
||||
Assert.Equal (40, Console.WindowHeight);
|
||||
driver.SetWindowPosition (80, 40);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (0, Console.WindowTop);
|
||||
|
||||
driver.SetWindowSize (80, 20);
|
||||
Assert.Equal (80, Application.Driver.Cols);
|
||||
Assert.Equal (40, Application.Driver.Rows);
|
||||
Assert.Equal (80, Console.BufferWidth);
|
||||
Assert.Equal (40, Console.BufferHeight);
|
||||
Assert.Equal (80, Console.WindowWidth);
|
||||
Assert.Equal (20, Console.WindowHeight);
|
||||
driver.SetWindowPosition (80, 41);
|
||||
Assert.Equal (0, Console.WindowLeft);
|
||||
Assert.Equal (20, Console.WindowTop);
|
||||
|
||||
Application.Shutdown ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using Xunit;
|
||||
namespace Terminal.Gui.Views {
|
||||
public class TextFieldTests {
|
||||
|
||||
// This class enables test functions annoated with the [InitShutdown] attribute
|
||||
// This class enables test functions annotated with the [InitShutdown] attribute
|
||||
// to have a function called before the test function is called and after.
|
||||
//
|
||||
// This is necessary because a) Application is a singleton and Init/Shutdown must be called
|
||||
@@ -17,8 +17,8 @@ namespace Terminal.Gui.Views {
|
||||
{
|
||||
Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
|
||||
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901=32 (Length)
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901=32 (Length)
|
||||
TextFieldTests._textField = new TextField ("TAB to jump between text fields.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user