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

fix paste performance issue on windows and linux #249

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
532a5af
rename file to match class name
tomlm Jan 7, 2025
037a737
Fix perf issue with windows and paste
tomlm Jan 7, 2025
714e610
Merge branch 'main' into tomlm/fixPaste
tomlm Jan 7, 2025
2195755
1 000 000 characters test
jinek Jan 8, 2025
86160a4
Automated JetBrains cleanup
github-actions[bot] Jan 8, 2025
3061ac5
add scrollbar to gallery view pane
tomlm Jan 8, 2025
0e92466
Automated JetBrains cleanup
github-actions[bot] Jan 8, 2025
b355734
add paste clipboard comparison for emitting CTRL+V
tomlm Jan 8, 2025
b18075f
Merge branch 'tomlm/fixPaste' of https://github.com/jinek/Consolonia …
tomlm Jan 8, 2025
3784019
Automated JetBrains cleanup
github-actions[bot] Jan 8, 2025
d1dc7b9
use simplified clipboard impl instead of avalonia platform.
tomlm Jan 8, 2025
e403559
Merge branch 'tomlm/fixPaste' of https://github.com/jinek/Consolonia …
tomlm Jan 8, 2025
da7b7a9
remove extra files
tomlm Jan 8, 2025
a85aef0
cleanup
tomlm Jan 8, 2025
988380b
fix extensions naming error. (Why is this happening now?)
tomlm Jan 8, 2025
2090e28
Automated JetBrains cleanup
github-actions[bot] Jan 8, 2025
bb01b75
add textinput event
tomlm Jan 9, 2025
690143f
Merge branch 'tomlm/fixPaste' of https://github.com/jinek/Consolonia …
tomlm Jan 9, 2025
3e7f3ef
add new event to unit test
tomlm Jan 9, 2025
a3c37eb
Automated JetBrains cleanup
github-actions[bot] Jan 9, 2025
a0de64a
switch to naive clipboard
tomlm Jan 9, 2025
56c8c1c
Merge branch 'tomlm/fixPaste' of https://github.com/jinek/Consolonia …
tomlm Jan 9, 2025
2d23c1e
implement cross platform clipboards
tomlm Jan 9, 2025
407dc9d
update for wsl
tomlm Jan 9, 2025
b6237cd
use X11CLipboard on linux non-wsl
tomlm Jan 9, 2025
ce09026
cleanup
tomlm Jan 9, 2025
b0fe142
cleanup
tomlm Jan 9, 2025
e2d8827
fix typo in method name
tomlm Jan 9, 2025
55e5c7d
add scrollbars to gallery pages
tomlm Jan 9, 2025
b1e1ff2
lint
tomlm Jan 9, 2025
a157136
null checks
tomlm Jan 9, 2025
40cc7ec
rename IsWsl
tomlm Jan 9, 2025
8aecf8e
lint
tomlm Jan 9, 2025
6188132
lint lint lint lint lint lint
tomlm Jan 9, 2025
f953816
Automated JetBrains cleanup
github-actions[bot] Jan 9, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,4 @@ fabric.properties
*.sln.iml

/src/Tools/Consolonia.PreviewHost/Properties/launchSettings.json
src/.vscode/launch.json
2 changes: 1 addition & 1 deletion src/Consolonia.Core/Consolonia.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.116.1" />
Copy link

Choose a reason for hiding this comment

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

What do we use from skia?

<PackageReference Include="Unicode.net" Version="2.0.0" />
<PackageReference Include="Wcwidth" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace Consolonia.Core.Helpers
{
public static class Extensions
public static class UtilityExtensions
{
public static IDisposable SubscribeAction<TValue>(
this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
Expand Down
9 changes: 9 additions & 0 deletions src/Consolonia.Core/Infrastructure/ConsoleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Avalonia.Media;
using Consolonia.Core.Drawing;
using Consolonia.Core.Drawing.PixelBufferImplementation;
using Consolonia.Core.Text;

namespace Consolonia.Core.Infrastructure
{
Expand Down Expand Up @@ -68,6 +69,7 @@ protected void StartSizeCheckTimerAsync(uint slowInterval = 1500)
public event Action<Key, char, RawInputModifiers, bool, ulong> KeyEvent;
public event Action<RawPointerEventType, Point, Vector?, RawInputModifiers> MouseEvent;
public event Action<bool> FocusEvent;
public event Action<string, ulong> TextInputEvent;

protected void RaiseMouseEvent(RawPointerEventType eventType, Point point, Vector? wheelDelta,
RawInputModifiers modifiers)
Expand All @@ -80,6 +82,11 @@ protected void RaiseKeyPress(Key key, char character, RawInputModifiers modifier
KeyEvent?.Invoke(key, character, modifiers, down, timeStamp);
}

protected void RaiseTextInput(string text, ulong timestamp)
{
TextInputEvent?.Invoke(text, timestamp);
}

protected void RaiseFocusEvent(bool focused)
{
FocusEvent?.Invoke(focused);
Expand Down Expand Up @@ -123,6 +130,7 @@ public virtual void HideCaret()

public virtual void PrepareConsole()
{
WriteText(Esc.EnableBracketedPasteMode);
_consoleOutput.PrepareConsole();
}

Expand All @@ -135,6 +143,7 @@ public virtual void Print(PixelBufferCoordinate bufferPoint, Color background, C
public virtual void RestoreConsole()
{
_consoleOutput.RestoreConsole();
WriteText(Esc.DisableBracketedPasteMode);
}

public virtual void SetCaretPosition(PixelBufferCoordinate bufferPoint)
Expand Down
13 changes: 13 additions & 0 deletions src/Consolonia.Core/Infrastructure/ConsoleWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public ConsoleWindow()
Console = AvaloniaLocator.Current.GetService<IConsole>() ?? throw new NotImplementedException();
Console.Resized += OnConsoleOnResized;
Console.KeyEvent += ConsoleOnKeyEvent;
Console.TextInputEvent += ConsoleOnTextInputEvent;
Console.MouseEvent += ConsoleOnMouseEvent;
Console.FocusEvent += ConsoleOnFocusEvent;
Handle = null!;
Expand Down Expand Up @@ -394,6 +395,18 @@ private void OnConsoleOnResized()
});
}

private void ConsoleOnTextInputEvent(string text, ulong timeStamp)
{
Dispatcher.UIThread.Post(() =>
{
#pragma warning disable CS0618 // Type or member is obsolete // todo: change to correct constructor, CFA20A9A-3A24-4187-9CA3-9DF0081124EE
var rawInputEventArgs = new RawTextInputEventArgs(_myKeyboardDevice, timeStamp, _inputRoot, text);
#pragma warning restore CS0618 // Type or member is obsolete
Input!(rawInputEventArgs);
}, DispatcherPriority.Input);
}


private async void ConsoleOnKeyEvent(Key key, char keyChar, RawInputModifiers rawInputModifiers, bool down,
ulong timeStamp)
{
Expand Down
23 changes: 1 addition & 22 deletions src/Consolonia.Core/Infrastructure/ConsoloniaPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,13 @@ public void Initialize()
.Bind<IPlatformIconLoader>().ToConstant(new DummyIconLoader())
.Bind<IPlatformSettings>().ToSingleton<ConsoloniaPlatformSettings>()
.Bind<IStorageProvider>().ToSingleton<ConsoloniaStorageProvider>()
//.Bind<IClipboard>().ToConstant(null)
/*.Bind<IKeyboardNavigationHandler>().ToTransient<ArrowsAndKeyboardNavigationHandler>() todo: implement this navigation*/
//.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
//.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
//.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog())
/*.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())*/;

if (OperatingSystem.IsWindows())
{
AvaloniaLocator.CurrentMutable.Bind<IClipboard>()
.ToFunc(() =>
{
Assembly assembly = Assembly.Load("Avalonia.Win32");
ArgumentNullException.ThrowIfNull(assembly, "Avalonia.Win32");
Type type = assembly.GetType(assembly.GetName().Name + ".ClipboardImpl");
ArgumentNullException.ThrowIfNull(type, "ClipboardImpl");
var clipboard = Activator.CreateInstance(type) as IClipboard;
ArgumentNullException.ThrowIfNull(clipboard, nameof(clipboard));
return clipboard;
});
}
else if (OperatingSystem.IsMacOS())
{
// TODO: Implement or reuse MacOS clipboard
}
else if (OperatingSystem.IsLinux())
{
// TODO: Implement or reuse X11 Clipboard
}
}

[DebuggerStepThrough]
Expand Down
1 change: 1 addition & 0 deletions src/Consolonia.Core/Infrastructure/IConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface IConsole : IConsoleOutput
public event Action Resized;

event Action<Key, char, RawInputModifiers, bool, ulong> KeyEvent;
event Action<string, ulong> TextInputEvent;
event Action<RawPointerEventType, Point, Vector?, RawInputModifiers> MouseEvent;

event Action<bool> FocusEvent;
Expand Down
49 changes: 49 additions & 0 deletions src/Consolonia.Core/Infrastructure/NaiveClipboard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;

namespace Consolonia.Core.Infrastructure
{
/// <summary>
/// This clipboard only stores memory in the same process, so it is not useful for sharing data between processes.
/// </summary>
public class NaiveClipboard : IClipboard
Copy link

Choose a reason for hiding this comment

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

typo?

{
private string _text = String.Empty;

#pragma warning disable CA1822 // Mark members as static
public async Task ClearAsync()
{
await Task.CompletedTask;
Copy link

Choose a reason for hiding this comment

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

Can be replaced by await Task.Yeild() ?
Coz I would question why do we need to await nothing?
Why can't we just empty _text with out async code at all, and return Task.CompletedTask?

_text = String.Empty;
}

public Task<object> GetDataAsync(string format)
{
throw new NotImplementedException();
}

public Task<string[]> GetFormatsAsync()
{
throw new NotImplementedException();
}

public async Task<string> GetTextAsync()
{
await Task.CompletedTask;
return _text;
}

public Task SetDataObjectAsync(IDataObject data)
{
throw new NotImplementedException();
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

public async Task SetTextAsync(string text)
{
await Task.CompletedTask;
_text = text;
}
}
}
2 changes: 1 addition & 1 deletion src/Consolonia.Core/Text/Esc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal static class Esc

// bracketed
public const string EnableBracketedPasteMode = "\u001b[?2004h";
public const string DisableBracktedPasteMode = "\u001b[?2004l";
public const string DisableBracketedPasteMode = "\u001b[?2004l";

// mouse tracking
public const string EnableMouseTracking = "\u001b[?1000h";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
x:Class="Consolonia.Gallery.Gallery.GalleryViews.GalleryTextBox">
<!--Copied from https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Default/TextBox.xaml-->
<StackPanel Orientation="Vertical"
HorizontalAlignment="Stretch"
Spacing="1"
Margin="1,0">
<StackPanel.Styles>
<!--<StackPanel.Styles>
<Style Selector="TextBox">
<Setter Property="HorizontalAlignment"
Value="Left" />
<Setter Property="Width"
Value="35" />
</Style>
</StackPanel.Styles>
</StackPanel.Styles>-->
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit.">
<TextBox.ContextFlyout>
<Flyout>
Expand Down Expand Up @@ -64,10 +65,10 @@

<TextBox AcceptsReturn="True"
TextWrapping="Wrap"
Height="4"
Height="10"
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True"
Height="5"
Height="10"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />

</StackPanel>
Expand Down
9 changes: 5 additions & 4 deletions src/Consolonia.Gallery/View/ControlsListView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@

</Grid>
<Grid RowDefinitions="* Auto">
<Border
Child="{Binding ElementName=GalleryGrid,Path=SelectedItem, Converter={StaticResource GalleryItemConverter}}"
BorderThickness="1"
BorderBrush="{DynamicResource ThemeBorderBrush}" />
<Border BorderThickness="1"
BorderBrush="{DynamicResource ThemeBorderBrush}">
<ScrollViewer
Content="{Binding ElementName=GalleryGrid,Path=SelectedItem, Converter={StaticResource GalleryItemConverter}}" />
</Border>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Row="1">
Expand Down
21 changes: 12 additions & 9 deletions src/Consolonia.GuiCS/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,21 @@ public CONSOLE_INPUT_MODE ConsoleMode
}
}

const int bufferSize = 0xffff;
private static INPUT_RECORD[] s_inputBuffer = new INPUT_RECORD[bufferSize];

public INPUT_RECORD[] ReadConsoleInput()
{
const int bufferSize = 1;
var records = new INPUT_RECORD[bufferSize];

if (!Kernel32.ReadConsoleInput(InputHandle, records, bufferSize,
out var numberEventsRead))
throw GetLastError().GetException();
lock (s_inputBuffer)
Copy link

Choose a reason for hiding this comment

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

It looks like we can assume ReadConsoleInput can be not thread-safe

{

return numberEventsRead == 0
? Array.Empty<INPUT_RECORD>()
: records;
if (!Kernel32.ReadConsoleInput(InputHandle, s_inputBuffer, bufferSize,
out var numberEventsRead))
throw GetLastError().GetException();
INPUT_RECORD[] recordsToReturn = new INPUT_RECORD[numberEventsRead];
Array.Copy(s_inputBuffer, recordsToReturn, numberEventsRead);
return recordsToReturn;
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
1 change: 1 addition & 0 deletions src/Consolonia.NUnit/UnitTestConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ public void SetupLifetime(ClassicDesktopStyleApplicationLifetime lifetime)
public event Action<Key, char, RawInputModifiers, bool, ulong> KeyEvent;
public event Action<RawPointerEventType, Point, Vector?, RawInputModifiers> MouseEvent;
public event Action<bool> FocusEvent;
public event Action<string, ulong> TextInputEvent;
#pragma warning restore CS0067
}
}
97 changes: 97 additions & 0 deletions src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Consolonia.PlatformSupport.Clipboard
{
/// <summary>
/// Helper class for console drivers to invoke shell commands to interact with the clipboard.
/// </summary>
internal static class ClipboardProcessRunner
{
public static (int exitCode, string result) Bash(
string commandLine,
string inputText = "",
bool waitForOutput = false
)
{
var arguments = $"-c \"{commandLine}\"";
(int exitCode, string result) = Process("bash", arguments, inputText, waitForOutput);

return (exitCode, result.TrimEnd());
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

public static bool DoubleWaitForExit(this Process process)
{
bool result = process.WaitForExit(500);

if (result)
{
process.WaitForExit();
}

return result;
}

public static bool FileExists(this string value) { return !string.IsNullOrEmpty(value) && !value.Contains("not found", StringComparison.Ordinal); }
tomlm marked this conversation as resolved.
Show resolved Hide resolved

public static (int exitCode, string result) Process(
string cmd,
string arguments,
string input = null,
bool waitForOutput = true
)
{
var output = string.Empty;

using (var process = new Process
{
StartInfo = new()
{
FileName = cmd,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true
}

Check warning on line 61 in src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs

View workflow job for this annotation

GitHub Actions / build

"[UnusedVariable] Local variable 'eventHandled' is never used" on /home/runner/work/Consolonia/Consolonia/src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs(61,44)
})
{
TaskCompletionSource<bool> eventHandled = new();
process.Start();

if (!string.IsNullOrEmpty(input))
{
process.StandardInput.Write(input);
process.StandardInput.Close();
}

if (!process.WaitForExit(5000))
{
var timeoutError =
$@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";

throw new TimeoutException(timeoutError);
}

if (waitForOutput && process.StandardOutput.Peek() != -1)
{
output = process.StandardOutput.ReadToEnd();
}

if (process.ExitCode > 0)
{
output = $@"Process failed to run. Command line: {cmd} {arguments}.
Output: {output}
Error: {process.StandardError.ReadToEnd()}";
}

return (process.ExitCode, output);
}
}
}
}
Loading
Loading