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

Crash after window resize #28

Closed
eiredrake opened this issue Dec 3, 2019 · 14 comments
Closed

Crash after window resize #28

eiredrake opened this issue Dec 3, 2019 · 14 comments

Comments

@eiredrake
Copy link

eiredrake commented Dec 3, 2019

Been testing the Goblinfactory console classes and so far I'm pretty happy with them. I have a console app that needs progress bars and a section where a log will be displayed. I was able to do all that fairly easily. As long as I don't mess with the window size everything runs just fine.

But I'm having an issue wherein the console window gets resized and causes an exception. At first I did this accidentally when I grabbed the wrong console but it occurs most of the time when I resize the window whenI do so manually not by using minimize/maximize.

Edit: Also seems to be when I make the window smaller. Making the window larger just makes a mess of the screen.

I'm getting an argument out of range exception. This occurs when the log sink I am using attempts to update the log IConsole. I'm using dependency injection, though I don't know if that really matters from your prospective. The IConsoleWrapper is just a class I use to hold on to the individual parts of the 'window'

I instantiate the IConsoles like this:

var main_window_height = Console.WindowHeight;
var main_window_width = Console.WindowWidth;

var mainWindow = new Window(0, 0, main_window_width, main_window_height);

var progress_window_height = 4;
var command_window_height  = 3;
var window_padding         = 2;

var progress_console       = default(IConsole);
var logging_console        = default(IConsole);
var command_console        = default(IConsole);              

progress_console = Window.Open(0, 0, main_window_width, progress_window_height, "operation progress", Konsole.Drawing.LineThickNess.Single);
command_console = Window.Open(0, main_window_height - command_window_height, main_window_width, command_window_height, "system command", Konsole.Drawing.LineThickNess.Single);
logging_console = Window.Open(0, progress_window_height, main_window_width, main_window_height - progress_console.WindowHeight - command_console.WindowHeight - (window_padding * 2), "operation logging", Konsole.Drawing.LineThickNess.Single);

var console_wrapper = new ConsoleWrapper(progress_console, logging_console, command_console, main_window_height - window_padding);

serviceCollection.AddSingleton<IConsoleWrapper>(console_wrapper);

and the log update happens here....it's triggered by a Serilog log write.

public void Emit(LogEvent logEvent)
{
	Dawn.Guard.Argument(() => logEvent).NotNull();

	var message = logEvent.RenderMessage(_formatProvider);

	this.consoleWrapper.Logging_Console.WriteLine(message);
}

The exception details are the following:

 | $exception | {"The value must be greater than or equal to zero and less than the console's buffer size in that dimension. (Parameter 'sourceWidth')\r\nActual value was 104."} | System.ArgumentOutOfRangeException


   at System.ConsolePal.MoveBufferArea(Int32 sourceLeft, Int32 sourceTop, Int32 sourceWidth, Int32 sourceHeight, Int32 targetLeft, Int32 targetTop, Char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
   at Konsole.Window.ScrollUp()
   at Konsole.Window.WriteLine(String format, Object[] args)
   at Konsole.ConcurrentWriter.WriteLine(String format, Object[] args)
   at Progress_Reporter_tests_cli.Implementations.Serilog_Sink.StealthConsoleSink.Emit(LogEvent logEvent) in C:\Users\ekramer\Documents\Visual Studio 2019\Projects\Progress_Reporter_tests_cli\Progress_Reporter_tests_cli\Implementations\Serilog_Sink\StealthConsoleSink.cs:line 34
   at Serilog.Core.Sinks.SafeAggregateSink.Emit(LogEvent logEvent)
@goblinfactory
Copy link
Owner

goblinfactory commented Dec 3, 2019

sounds like it should be easy enough to reproduce. (simple to reproduce but quite a big update to fix).
I've put it in my diary to look at this tomorrow morning. I'm thinking some type of virtual void Refresh() that will be called on screen resize, so that you can redraw your UI. Not sure how that would work because Konsole is designed to live in realtime side by side with any ongoing writes to the console. Essentially tracking bespoke regions of a screen and when the console scrolls tracking the Y position so that we can continue to render in the correct location on scrolled portions of console window. When you resize a window, what's on line X that's been wrapped no longer has any meaning.
Ah, doh, ..ok, I rekon you're probably ok with that and just don't want it to crash. So I should be able to handle that. Keeping it all looking pretty, not so much.
I'll aim at getting a patch for stopping it from throwing an exception, and then think about how it possibly should work. Though I think I may need the scroll fix (as well as the speed fix) to Konsole to really do that properly.
BTW, I'm looking for volunteers to help me update Konsole.
see here:
https://www.linkedin.com/posts/goblinfactory_callforhelp-konsole-progressbar-activity-6602883874693406720-qqvb

@eiredrake
Copy link
Author

was actually looking at it myself to try to see if there was a reasonable way to be alerted to the change in console size to maybe take some sort of corrective action. But didn't find any. Most of the googling had people saying you might as well implement a UI.

I don't know anything about porting to mac or linux so it might be an interesting thing to watch as it evolves. I can't say that I'd have anything useful to add in that respect but I'll poke my head in when I can.

@goblinfactory
Copy link
Owner

looking at all these now in the following order
https://github.com/goblinfactory/konsole/blob/master/backlog.md

goblinfactory added a commit that referenced this issue Dec 3, 2019
@goblinfactory
Copy link
Owner

This has been fixed in package 3.4.1.
I have released a patch, please test and let me know if this solves your resizing issue.
https://www.nuget.org/packages/Goblinfactory.Konsole/3.4.1

@goblinfactory
Copy link
Owner

I will close this issue if I don't hear back from you in a little while, or if you tell me this is working.

@goblinfactory
Copy link
Owner

I have not tested this, but if you want to stop the user from resizing a console window, this may work

class Program
    {
        private const int MF_BYCOMMAND = 0x00000000;
        public const int SC_CLOSE = 0xF060;
        public const int SC_MINIMIZE = 0xF020;
        public const int SC_MAXIMIZE = 0xF030;
        public const int SC_SIZE = 0xF000;

        [DllImport("user32.dll")]
        public static extern int DeleteMenu(IntPtr hMenu, int nPosition, int wFlags);

        [DllImport("user32.dll")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

        [DllImport("kernel32.dll", ExactSpelling = true)]
        private static extern IntPtr GetConsoleWindow();

        static void Main(string[] args)
        {
            IntPtr handle = GetConsoleWindow();
            IntPtr sysMenu = GetSystemMenu(handle, false);

            if (handle != IntPtr.Zero)
            {
                DeleteMenu(sysMenu, SC_CLOSE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_MINIMIZE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_MAXIMIZE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_SIZE, MF_BYCOMMAND);
            }
            Console.Read();
        }

    }

code is from here : https://social.msdn.microsoft.com/Forums/vstudio/en-US/1aa43c6c-71b9-42d4-aa00-60058a85f0eb/c-console-window-disable-resize?forum=csharpgeneral

@goblinfactory
Copy link
Owner

By the way, I ran your sample code and I noticed that the windows are rendered incorrectly when the height of a window is 3 lines high, and they render correctly if 4 or more lines.
see here : #31

I also saw the code needed to generate the layout you wanted (a common design) is not very elegant. Or more accurately, Konsole is not fluid enough to make creating the layout more elegantly, so I've got a draft of a new version committed (needs more tests), and will release that soon. You will be able to do the following;

        private static void QuickTest()
        {
            Console.CursorVisible = false;
            var c = new Window();
            var consoles = c.SplitRows(
                    new Split(4, "headline", LineThickNess.Single, ConsoleColor.Yellow),
                    new Split(0, "content", LineThickNess.Single),
                    new Split(4, "status", LineThickNess.Single, ConsoleColor.Red)
            );

            var headline = consoles[0];
            var content = consoles[1];
            var status = consoles[2];

            headline.Write("my headline");
            content.WriteLine("content goes here");
            status.Write("System offline!");
            Console.ReadLine();
        }

this produces the following output

no-bug

@eiredrake
Copy link
Author

I'll give it a try shortly. Thank you for the fast response. Also I'll look at the way I'm creating the windows and rework.

@goblinfactory
Copy link
Owner

I'm busy testing the final changes for columns, so you can now create any shape UI using rows and columns, passing a width or height of 0 to indicate it takes up the rest of the space.

Console.CursorVisible = false;
            var c = new Window();
            var consoles = c.SplitRows(
                    new Split(4, "heading", LineThickNess.Single),
                    new Split(0),
                    new Split(4, "status", LineThickNess.Single)
            ); ; ;

            var headline = consoles[0];
            var status = consoles[2];

            var contents = consoles[1].SplitColumns(
                    new Split(20),
                    new Split(0, "content") { Foreground = ConsoleColor.White, Background = ConsoleColor.Cyan },
                    new Split(20)
            );
            var menu = contents[0];
            var content = contents[1];
            var sidebar = contents[2];

            headline.Write("my headline");
            content.WriteLine("content goes here");

            menu.WriteLine("Options A");
            menu.WriteLine("Options B");

            sidebar.WriteLine("20% off all items between 11am and midnight tomorrow!");

            status.Write("System offline!");

gives you the following UI. This will be available in version 4.0.1 of Konsole. Should push that later tonight.

Capture

@goblinfactory
Copy link
Owner

goblinfactory commented Dec 4, 2019

done, fix should be available shortly. Package is being verified
https://www.nuget.org/packages/Goblinfactory.Konsole/4.0.1
Package is live, please test. Going to close this issue now.

@eiredrake
Copy link
Author

That worked pretty well actually.

 if (Environment.UserInteractive)
            {
                Console.CursorVisible = false;

                var mainWindow = new Window();
                var consoles = mainWindow.SplitRows(
                        new Split(4, "operation progress", LineThickNess.Single, ConsoleColor.Yellow),
                        new Split(0, "operation logging", LineThickNess.Single),
                        new Split(3, "system command", LineThickNess.Single, ConsoleColor.Red)
                );


                var progress_console = consoles.Take(1).FirstOrDefault();
                var logging_console  = consoles.Skip(1).Take(1).FirstOrDefault();
                var command_console  = consoles.LastOrDefault();

                
                var console_wrapper = new ConsoleWrapper(progress_console, logging_console, command_console, command_console.AbsoluteY);

                serviceCollection.AddSingleton<IConsoleWrapper>(console_wrapper);
            }

            return serviceCollection;

IConsoles don't seem to retain their name, is that true?

@eiredrake
Copy link
Author

Bugger... nope it happened again when I resized the window to a smaller size

System.ArgumentOutOfRangeException: 'The value must be greater than or equal to zero and less than the console's buffer size in that dimension. (Parameter 'sourceWidth')
Actual value was 106.'
at System.ConsolePal.MoveBufferArea(Int32 sourceLeft, Int32 sourceTop, Int32 sourceWidth, Int32 sourceHeight, Int32 targetLeft, Int32 targetTop, Char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
 at Konsole.Window.ScrollUp()
 at Konsole.Window.WriteLine(String format, Object[] args)
 at Konsole.ConcurrentWriter.WriteLine(String format, Object[] args)
 at Progress_Reporter_tests_cli.Implementations.Serilog_Sink.StealthConsoleSink.Emit(LogEvent logEvent) in C:\Users\ekramer\Documents\Visual Studio 2019\Projects\Progress_Reporter_tests_cli\Progress_Reporter_tests_cli\Implementations\Serilog_Sink\StealthConsoleSink.cs:line 33
 at Serilog.Core.Sinks.SafeAggregateSink.Emit(LogEvent logEvent)

@eiredrake
Copy link
Author

I have not tested this, but if you want to stop the user from resizing a console window, this may work

class Program
    {
        private const int MF_BYCOMMAND = 0x00000000;
        public const int SC_CLOSE = 0xF060;
        public const int SC_MINIMIZE = 0xF020;
        public const int SC_MAXIMIZE = 0xF030;
        public const int SC_SIZE = 0xF000;

        [DllImport("user32.dll")]
        public static extern int DeleteMenu(IntPtr hMenu, int nPosition, int wFlags);

        [DllImport("user32.dll")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

        [DllImport("kernel32.dll", ExactSpelling = true)]
        private static extern IntPtr GetConsoleWindow();

        static void Main(string[] args)
        {
            IntPtr handle = GetConsoleWindow();
            IntPtr sysMenu = GetSystemMenu(handle, false);

            if (handle != IntPtr.Zero)
            {
                DeleteMenu(sysMenu, SC_CLOSE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_MINIMIZE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_MAXIMIZE, MF_BYCOMMAND);
                DeleteMenu(sysMenu, SC_SIZE, MF_BYCOMMAND);
            }
            Console.Read();
        }

    }

code is from here : https://social.msdn.microsoft.com/Forums/vstudio/en-US/1aa43c6c-71b9-42d4-aa00-60058a85f0eb/c-console-window-disable-resize?forum=csharpgeneral

Can confirm this works. I added it to my ConsoleWrapper interface and implementation.

@goblinfactory
Copy link
Owner

goblinfactory commented Dec 4, 2019

@eiredrake I think I have a really nice solution to your resizing problem
there's a checkbox, on the console default properties, "wrap text on resize", untick this and everything works like it was made in heaven.
see #33 for screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants