diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 338b9cce9..df5c3871f 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -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);
+ }
}
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
index add2342f6..e3d91b6d9 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
@@ -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);
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
index aba1b0ecd..56f0c4cf2 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
@@ -12,10 +12,13 @@ using System.Text;
using System.Threading.Tasks;
namespace Terminal.Gui {
+
+#pragma warning disable RCS1138 // Add summary to documentation comment.
///
///
///
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.
+
+ ///
+ /// Specifies the initial console width.
+ ///
+ public const int WIDTH = 80;
+
+ ///
+ /// Specifies the initial console height.
+ ///
+ public const int HEIGHT = 25;
+
///
///
///
- 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 {
///
///
///
- 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 {
///
///
///
- 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 {
///
///
///
- 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 {
///
public static void Clear ()
{
- _buffer = new char [WindowWidth, WindowHeight];
+ _buffer = new char [BufferWidth, BufferHeight];
SetCursorPosition (0, 0);
}
@@ -905,7 +920,8 @@ namespace Terminal.Gui {
///
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);
}
//
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index 5d69fc11f..246f5af5d 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -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 {
///
@@ -16,11 +19,14 @@ namespace Terminal.Gui {
///
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)
{
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index cc1117f16..67fc40dd8 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -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
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index 3d23c796d..bddf89f14 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -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);
+ }
}
diff --git a/Terminal.Gui/Core/Clipboard.cs b/Terminal.Gui/Core/Clipboard.cs
new file mode 100644
index 000000000..5e029fe29
--- /dev/null
+++ b/Terminal.Gui/Core/Clipboard.cs
@@ -0,0 +1,32 @@
+using NStack;
+using System;
+
+namespace Terminal.Gui {
+ ///
+ /// Provides cut, copy, and paste support for the clipboard with OS interaction.
+ ///
+ public static class Clipboard {
+ static ustring contents;
+
+ ///
+ /// Get or sets the operation system clipboard, otherwise the contents field.
+ ///
+ 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;
+ }
+ }
+ }
+ }
+}
diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs
index fc6451646..9f7594443 100644
--- a/Terminal.Gui/Core/ConsoleDriver.cs
+++ b/Terminal.Gui/Core/ConsoleDriver.cs
@@ -613,15 +613,27 @@ namespace Terminal.Gui {
/// The current number of columns in the terminal.
///
public abstract int Cols { get; }
+
///
/// The current number of rows in the terminal.
///
public abstract int Rows { get; }
+
+ ///
+ /// The current left in the terminal.
+ ///
+ public abstract int Left { get; }
+
///
/// The current top in the terminal.
///
public abstract int Top { get; }
+ ///
+ /// Get the operation system clipboard.
+ ///
+ public abstract IClipboard Clipboard { get; }
+
///
/// 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 {
/// The current attribute.
public abstract Attribute GetAttribute ();
}
+
+ ///
+ /// Definition to interact with the OS clipboard.
+ ///
+ public interface IClipboard {
+ ///
+ /// Sets the operation system clipboard.
+ ///
+ ///
+ void SetClipboardData (string text);
+ ///
+ /// Get the operation system clipboard.
+ ///
+ ///
+ string GetClipboardData ();
+ }
}
diff --git a/Terminal.Gui/Views/Clipboard.cs b/Terminal.Gui/Views/Clipboard.cs
deleted file mode 100644
index e24e79587..000000000
--- a/Terminal.Gui/Views/Clipboard.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using NStack;
-
-namespace Terminal.Gui {
- ///
- /// Provides cut, copy, and paste support for the clipboard.
- /// NOTE: Currently not implemented.
- ///
- public static class Clipboard {
- ///
- ///
- ///
- public static ustring Contents { get; set; }
- }
-}
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 62d583c42..d533e24d1 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -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);
diff --git a/UnitTests/ClipboardTests.cs b/UnitTests/ClipboardTests.cs
new file mode 100644
index 000000000..3ee4fc3c9
--- /dev/null
+++ b/UnitTests/ClipboardTests.cs
@@ -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 ();
+ }
+ }
+}
diff --git a/UnitTests/ConsoleDriverTests.cs b/UnitTests/ConsoleDriverTests.cs
index 78d8d6731..f2429e11a 100644
--- a/UnitTests/ConsoleDriverTests.cs
+++ b/UnitTests/ConsoleDriverTests.cs
@@ -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 ();
+ }
}
}
diff --git a/UnitTests/TextFieldTests.cs b/UnitTests/TextFieldTests.cs
index e0f179eee..bedcaf4fb 100644
--- a/UnitTests/TextFieldTests.cs
+++ b/UnitTests/TextFieldTests.cs
@@ -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.");
}