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 9 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 src/Consolonia.Core/Infrastructure/ConsoleWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
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)
{

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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
using Key = Avalonia.Input.Key;
using Point = Avalonia.Point;
using static Vanara.PInvoke.Kernel32;
using Avalonia.Input.Platform;
using System.Text;
using System.Collections.Generic;

// ReSharper disable UnusedMember.Local
#pragma warning disable CS0649
Expand Down Expand Up @@ -66,6 +69,26 @@ private static readonly FlagTranslator<MOUSE_BUTTON_STATE, RawPointerEventType>
]);


private static readonly KEY_EVENT_RECORD[] CtrlVKeyEvents =
[
new()
{
bKeyDown = true, wVirtualKeyCode = 17, wVirtualScanCode = 29,
dwControlKeyState = CONTROL_KEY_STATE.LEFT_CTRL_PRESSED
},
new()
{
bKeyDown = true, wVirtualKeyCode = 86, wVirtualScanCode = 47, uChar = '\u0016',
dwControlKeyState = CONTROL_KEY_STATE.LEFT_CTRL_PRESSED
},
new()
{
bKeyDown = false, wVirtualKeyCode = 86, wVirtualScanCode = 47, uChar = '\u0016',
dwControlKeyState = CONTROL_KEY_STATE.LEFT_CTRL_PRESSED
},
new() { bKeyDown = false, wVirtualKeyCode = 17, wVirtualScanCode = 29, dwControlKeyState = 0 }
];

private readonly WindowsConsole _windowsConsole;

private MOUSE_BUTTON_STATE _mouseButtonsState = MOUSE_BUTTON_STATE.NONE;
Expand Down Expand Up @@ -111,37 +134,124 @@ public override void PauseIO(Task task)

private void StartEventLoop()
{
Task.Run(() =>
Task.Run(async () =>
{
while (!Disposed /*inject ThreadAbortException*/)
{
PauseTask?.Wait();
var readConsoleInput = _windowsConsole.ReadConsoleInput();
if (!readConsoleInput.Any())
throw new NotImplementedException();
foreach (INPUT_RECORD inputRecord in readConsoleInput)
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (inputRecord.EventType)
var inputRecords = _windowsConsole.ReadConsoleInput();
IClipboard clipboard = AvaloniaLocator.Current.GetService<IClipboard>();
if (clipboard != null && inputRecords.Where(evt => evt.EventType == EVENT_TYPE.KEY_EVENT).Skip(1).Any())
{
// when console is translating CTRL+V to sequence of key strokes it comes in as multiple key events.
await ProcessClipboardInput(clipboard, inputRecords);
}
else
{
foreach (INPUT_RECORD inputRecord in inputRecords)
HandleInputRecord(inputRecord);
}
}
});
}

/// <summary>
/// Process clipboard input and compare to clipboard text to determine if we should paste clipboard text.
/// </summary>
/// <param name="clipboard"></param>
/// <param name="inputRecords"></param>
/// <returns></returns>
private async Task ProcessClipboardInput(IClipboard clipboard, INPUT_RECORD[] inputRecords)
{
var clipboardText = (await clipboard?.GetTextAsync()) ?? String.Empty;
if (clipboardText.Trim().Length == 0)
{
// no text in clipboard, just process input records
foreach (var inputRecord in inputRecords)
HandleInputRecord(inputRecord);
return;
}

// KEY_EVENTS will emit \r instead of \n, so we need to remove \n from clipboard text
clipboardText = clipboardText.Replace("\n", String.Empty, StringComparison.Ordinal);
StringBuilder bufferText = new StringBuilder();
List<INPUT_RECORD> bufferedKeyEvents = new();

while (inputRecords.Any())
{
// process all input records
for (int i = 0; i < inputRecords.Length; i++)
{
var inputRecord = inputRecords[i];
if (inputRecord.EventType != EVENT_TYPE.KEY_EVENT)
{
// handle non-key board events
HandleInputRecord(inputRecord);
}
else
{
// capture the key event so we can play it back if we don't match clipboard text
bufferedKeyEvents.Add(inputRecord);

// for key down events for chars that are not 0 (control keys)
if (inputRecord.Event.KeyEvent.bKeyDown && inputRecord.Event.KeyEvent.uChar != 0)
{
case EVENT_TYPE.WINDOW_BUFFER_SIZE_EVENT:
WINDOW_BUFFER_SIZE_RECORD windowBufferSize = inputRecord.Event.WindowBufferSizeEvent;
Size = new PixelBufferSize((ushort)windowBufferSize.dwSize.X,
(ushort)windowBufferSize.dwSize.Y);
break;
case EVENT_TYPE.FOCUS_EVENT:
FOCUS_EVENT_RECORD focusEvent = inputRecord.Event.FocusEvent;
RaiseFocusEvent(focusEvent.bSetFocus != 0);
break;
case EVENT_TYPE.KEY_EVENT:
HandleKeyInput(inputRecord.Event.KeyEvent);
break;
case EVENT_TYPE.MOUSE_EVENT:
MOUSE_EVENT_RECORD mouseEvent = inputRecord.Event.MouseEvent;
HandleMouseInput(mouseEvent);
break;
// append the char to the buffer text
bufferText.Append(inputRecord.Event.KeyEvent.uChar);

var currentBufferText = bufferText.ToString();
if (clipboardText.Trim() == currentBufferText.Trim())
{
// buffered text matches clipboard, emit CTRL+V sequence and ignore buffered keyboard events
foreach (var ctrlVEvent in CtrlVKeyEvents)
HandleKeyInput(ctrlVEvent);

// process remaining input records
for (++i; i < inputRecords.Length; i++)
HandleInputRecord(inputRecords[i]);
return;
}
else if (!clipboardText.StartsWith(currentBufferText, StringComparison.Ordinal))
{
// buffered text doesn't match clipboard, emit buffered key events (we already played other events live)
foreach (var bufferedEvent in bufferedKeyEvents)
HandleInputRecord(bufferedEvent);

// process remaining input records
for (++i; i < inputRecords.Length; i++)
HandleInputRecord(inputRecords[i]);
return;
}
}
}
}
});

inputRecords = _windowsConsole.ReadConsoleInput();
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved
}

private void HandleInputRecord(INPUT_RECORD inputRecord)
{
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (inputRecord.EventType)
{
case EVENT_TYPE.WINDOW_BUFFER_SIZE_EVENT:
WINDOW_BUFFER_SIZE_RECORD windowBufferSize = inputRecord.Event.WindowBufferSizeEvent;
Size = new PixelBufferSize((ushort)windowBufferSize.dwSize.X,
(ushort)windowBufferSize.dwSize.Y);
break;
case EVENT_TYPE.FOCUS_EVENT:
FOCUS_EVENT_RECORD focusEvent = inputRecord.Event.FocusEvent;
RaiseFocusEvent(focusEvent.bSetFocus != 0);
break;
case EVENT_TYPE.KEY_EVENT:
HandleKeyInput(inputRecord.Event.KeyEvent);
break;
case EVENT_TYPE.MOUSE_EVENT:
MOUSE_EVENT_RECORD mouseEvent = inputRecord.Event.MouseEvent;
HandleMouseInput(mouseEvent);
break;
}
}

// ReSharper disable ExpressionIsAlwaysNull
Expand Down
Loading