The new console mode disaster

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
misol101
Posts: 475
Joined: 02 May 2016 18:20

The new console mode disaster

#1 Post by misol101 » 07 Aug 2017 07:14

It was reported by penpen that "setfont" operation of my program "cmdwiz" did not work on Windows 10 machines.

Since I am a dinosaur I don't have a Win10 machine but I recently got ahold of one and tried it. As expected, my program did not work. I then found a setting in the cmd window properties called "Use legacy console (requires relaunch)". When I clicked this and restarted the cmd window, lo and behold, "cmdwiz setfont" worked just fine to change fonts.

Ok, so apparently Microsoft introduced "new console features" that break the old API's. Great. It's documented here: https://technet.microsoft.com/en-us/library/mt427362(v=ws.11).aspx

Then I tried carlos "bg" program, which reportedly works in Windows10. I tried it, and found that while it seemed to "kind of" work, it was acting strange. For example, there was no difference whatsoever between font 1 and font 2, and no difference between font 5 and font 6. Also, I had indication that the fonts were not the right sizes! Which turned out to be true.

It's actually easy to see this. Set e.g. font 0 or font 1 using carlos tool: "bg font 0". Type "start" to get a new window with a font of the same size. Now change the console mode to legacy mode in the properties, and type "start" again. The font is now much smaller. (It is, actually, the size in pixels it is supposed to be according to the properties settings!)

For my purposes, this is highly annoying. It means that even though we have set a font that is said to be 4x6 pixels (that is what it says in the setting properties for "raster font 0"), it is not! (when the new console mode is on).

Anyway, can I not just set this legacy mode then? Well, we can enable or disable the legacy console using this line (0 for legacy console, 1 for new console mode):

Code: Select all

reg add HKCU\Console /v ForceV2 /t reg_dword /d 0x0 /f

But...
1. The change is not immediate. Only when you open a new cmd window, the change will be visible
2. I think the current user would have to have admin privileges to set this (?)
3. If used in a script, it's tricky to restore the previous setting at the end of the script. What if the user just closes the window?

Anybody with more insights?

(my specific problem related to this is that for my program cmdgfx_gdi, I need to set an exact width and height for the window. To get e.g a 400x600 window, I would set the font to raster font 0 (it's 4x6 pixels), then do "mode 100,100". But obviously, if the size of the font cannot be trusted, then this method cannot be trusted (and all my windows end up being too big on Win10 if console legacy mode is not enabled! ... Oh my... I should have listened to aGerman, GUI stuff in the cmd window is just silly :mrgreen: )
Last edited by misol101 on 07 Aug 2017 14:15, edited 1 time in total.

misol101
Posts: 475
Joined: 02 May 2016 18:20

Re: The new console mode disaster

#2 Post by misol101 » 07 Aug 2017 08:10

Well, at least I can see that "cmdwiz setfont" works in Win10 with saved fonts, no matter if console legacy mode is on or not. With raster fonts though, it will behave the same way as "bg" if console legacy is disabled (i.e. it will set the wrong font from my perspective).

What I mean is that if you manually set the font to e.g. "Ms Gothic" size 18 and then do "cmdwiz savefont testfile", then you can write "cmdwiz setfont testfile" at any time to get back MsGothic-18.

But if you manually set the font to raster font 0 and save that with "cmdwiz savefont testfile", then "cmdwiz setfont testfile" will behave exactly the same as writing "bg font 0"

misol101
Posts: 475
Joined: 02 May 2016 18:20

Re: The new console mode disaster

#3 Post by misol101 » 07 Aug 2017 08:46

On closer inspection, it seems that raster fonts 2,3,4,6,7,8,9 have the same pixel sizes regardless of whether console legacy mode is set or not.

In new console mode, font 0 is the same size as font 1 in legacy mode (6x8 instead of 4x6), font 1 is the same as font 2 (8x8 instead of 6x8), and font 5 is the same as font 6 (8x12 instead of 7x12).

I realize this isn't a big deal to most people, but it is to me :mrgreen:

aGerman
Expert
Posts: 4678
Joined: 22 Jan 2010 18:01
Location: Germany

Re: The new console mode disaster

#4 Post by aGerman » 07 Aug 2017 14:11

I didn't deal much with setting fonts and stuff using WinAPI. In May penpen and me discussed about it via PM. He also fiddled with a C# porting (but my own C# skills are rather rudimentary).
Anyway. Turning legacy console on via registry settings is actually only a makeshift. Of course you can turn it on, start a new cmd process, and immediately reset it.

Proof of concept

Code: Select all

@echo off &setlocal
if "%~1" neq "~legacy~" for /f "tokens=2 delims=[" %%i in ('ver') do for /f "tokens=2 delims=. " %%j in ("%%i") do if %%j geq 10 (
  set "ForceV2=1"
  for /f "tokens=3" %%k in ('2^>nul reg query "HKCU\Console" /v "ForceV2"') do set /a "ForceV2=%%k"
  >nul reg add "HKCU\Console" /v "ForceV2" /t REG_DWORD /d 0 /f
  start cmd /c %~fs0 ~legacy~
  >nul pathping 127.0.0.1 -n -q 1 -p 500
  setlocal EnableDelayedExpansion
  >nul reg add "HKCU\Console" /v "ForceV2" /t REG_DWORD /d !ForceV2! /f
  exit /b
)

echo Hello from legacy console.
pause

Steffen
Last edited by aGerman on 07 Aug 2017 14:32, edited 1 time in total.
Reason: removed echo command that I added for testing purposes

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: The new console mode disaster

#5 Post by penpen » 08 Aug 2017 06:13

I didn't do much more than get a bugfix (created by aGerman :) using also some code of carlos) of my "CmdFont.cs.bat":

Code: Select all

// // >nul 2> nul & @goto :main
/*
 * Author: penpen
 * Bugfix: aGerman
 * Coords: carlos
 * Free for non profit use only.
 * For profit use contact me at www.dostips.com via private message (PM).
 * CmdFont.cs.bat
 */

/*
:main
   @echo off
   setlocal
   cls

   set "csc="

   pushd "%SystemRoot%\Microsoft.NET\Framework"
   for /f "tokens=* delims=" %%i in ('dir /b /o:n "v*"') do (
      dir /a-d /b "%%~fi\csc.exe" >nul 2>&1 && set "csc="%%~fi\csc.exe""
   )
   popd

   if defined csc (
      echo most recent C#.NET compiler located in:
      echo %csc%.
   ) else (
      echo C#.NET compiler not found.
      goto :eof
   )

   for %%a in ("%~dpn0") do for %%b in ("%%~dpna") do (
rem      %csc% /?
      %csc% /nologo /optimize /warnaserror /nowin32manifest /unsafe /debug- /target:exe /out:"%%~b.exe" "%~f0"
   )
   exit /B
*/

using System;
using System.Runtime.InteropServices;

using DWORD = System.Int32;
using HANDLE = System.IntPtr;
using ULONG = System.Int32;
using UINT = System.Int32;


public class CmdFont {
   public const DWORD STD_OUTPUT_HANDLE = (DWORD) (-11);
   internal const int LF_FACESIZE = 32;
   internal const string fontName = "Terminal";

   [StructLayout (LayoutKind.Sequential)]
   internal struct COORD {
      internal short X;
      internal short Y;
   }

   internal static readonly COORD[] terminal_font = {
      new COORD {X = 4, Y = 6},
      new COORD {X = 6, Y = 8},
      new COORD {X = 8, Y = 8},
      new COORD {X = 16, Y = 8},
      new COORD {X = 5, Y = 12},
      new COORD {X = 7, Y = 12},
      new COORD {X = 8, Y = 12},
      new COORD {X = 16, Y = 12},
      new COORD {X = 12, Y = 16},
      new COORD {X = 10, Y = 18}
   };

   [StructLayout (LayoutKind.Sequential)]
   internal unsafe struct CONSOLE_FONT_INFO {
      internal DWORD nFont;
      internal COORD dwFontSize;
   }

   [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)]
   internal unsafe struct CONSOLE_FONT_INFOEX {
      internal ULONG cbSize;
      internal DWORD nFont;
      internal COORD dwFontSize;
      internal UINT FontFamily;
      internal UINT FontWeight;
      internal fixed char FaceName[LF_FACESIZE];
   }

   [DllImport("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern bool SetCurrentConsoleFontEx (
      IntPtr consoleOutput,
      bool maximumWindow,
      ref CONSOLE_FONT_INFOEX consoleCurrentFontEx
   );

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern int SetConsoleFont (HANDLE hOut, DWORD nFont);

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern HANDLE GetStdHandle (DWORD nStdHandle);

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern bool GetCurrentConsoleFont (
      HANDLE         consoleOutput,
      bool         maximumWindow,
      ref CONSOLE_FONT_INFO   lpConsoleCurrentFont
   );

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern bool GetConsoleFontInfo (
      bool         bMaximumWindow,
      DWORD         nFontCount,
      ref CONSOLE_FONT_INFO   lpConsoleFontInfo
   );

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern COORD GetConsoleFontSize (
      HANDLE   hConsoleOutput,
      DWORD   nFont
   );

   [DllImport ("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true, SetLastError=true)]
   private static extern DWORD GetNumberOfConsoleFonts ();


   public const int HELP = 0;
   public const int GET = 1;
   public const int SET = 2;


   public static unsafe void Main (string[] args) {
      int option = HELP;
      int nFont = 0;

      switch (args.Length) {
         case 0:
            option = GET;
            break;

         case 1:
            option = (String.Compare ("GET", args [0], true) == 0) ? GET : HELP;
            break;

         case 2:
            try {
               nFont = int.Parse (args [1]);
               option = (String.Compare ("SET", args [0], true) == 0) ? SET : HELP;
            } catch {
            }
            break;

         default:
            option = HELP;
            break;
      }

      switch (option) {
         case SET:
            CONSOLE_FONT_INFOEX infoex = new CONSOLE_FONT_INFOEX ();
            infoex.cbSize = (ULONG)Marshal.SizeOf(infoex);
            infoex.nFont = nFont;
            infoex.dwFontSize.X = terminal_font[nFont].X;
            infoex.dwFontSize.Y = terminal_font[nFont].Y;
            infoex.FontFamily = 48;
            infoex.FontWeight = 400;
            IntPtr ptr = new IntPtr(infoex.FaceName);
            Marshal.Copy(fontName.ToCharArray(), 0, ptr, fontName.Length);
            SetCurrentConsoleFontEx (GetStdHandle (STD_OUTPUT_HANDLE), false, ref infoex);
           
            SetConsoleFont (GetStdHandle (STD_OUTPUT_HANDLE), nFont);
            break;

         case GET:
            CONSOLE_FONT_INFO info = new CONSOLE_FONT_INFO ();
            GetCurrentConsoleFont (GetStdHandle (STD_OUTPUT_HANDLE), false, ref info);
            COORD fontSize = GetConsoleFontSize (GetStdHandle (STD_OUTPUT_HANDLE), info.nFont);
            Console.WriteLine ("Font number {0}/{1} ({2}, {3})", info.nFont, GetNumberOfConsoleFonts (), fontSize.X, fontSize.Y);
            Console.WriteLine ("Screen size {0}, {1}", info.dwFontSize.X, info.dwFontSize.Y);
            Console.Out.Flush ();
            break;

         default:
            Console.WriteLine ("Usage CmdFont[.exe] [GET|SET nFont]");
            Console.WriteLine ("  [GET]      Displays a message with");
            Console.WriteLine ("              - the actual font number,");
            Console.WriteLine ("              - the actual font size, and");
            Console.WriteLine ("              - the actual console size.");
            Console.WriteLine ("  SET nFont  Sets the actual font number (nFont)");
            Console.Out.Flush ();
            break;
      }
   }
}

I then tried reading out the values stored in the table, because some settings might vary depending on the win installation (c++):

Code: Select all

#include <stdio.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <set>

typedef struct _SFontInfo {
   LONG         width;
   LONG         height;
   std::wstring name;

   bool operator<(const _SFontInfo& sFontInfo) const {
      return (height != sFontInfo.height) ? (height < sFontInfo.height) :
         (width != sFontInfo.width) ? (width < sFontInfo.width) :
         name < sFontInfo.name;
   }

} SFontInfo;


int CALLBACK EnumFontFamExProc(
   const LOGFONTW      *lpelfe,
   const TEXTMETRICW   *lpntme,
   DWORD                FontType,
   std::set<SFontInfo> *pdata
)
{
   pdata->insert({ lpelfe->lfWidth, lpelfe->lfHeight, lpelfe->lfFaceName });
   return 1;
}

int main(void)
{
   HWND hWnd = GetConsoleWindow();
   HDC hDc = GetDC(hWnd);
   CBDATA cbdata = {};
   LOGFONTW logfont = {};
   std::set<SFontInfo> setFontInfo = {};

   logfont.lfCharSet = OEM_CHARSET;
   wcscpy_s(logfont.lfFaceName, L"Terminal");

   EnumFontFamiliesExW(hDc, &logfont, (FONTENUMPROCW)EnumFontFamExProc, (LPARAM)&setFontInfo, (DWORD)0);

   int i = 0;
   for (std::set<SFontInfo>::iterator it = setFontInfo.begin(); it != setFontInfo.end(); ++it, ++i) {
      std::wcout << i << L": " << (*it).name.c_str() << L" " << (*it).width << L"x" << (*it).height << std::endl;
   }
   
   return 0;
}

I should have a more actual (c++) implementation with setting and retrieving the fonts, but actually i can't find it:
When i worked on that issue, i got toothache and a new PC, so i backed up all data and paused working on that issue.
But i will search and post you the actual state, if needed - but it's actually C++ only, the C# port is still missing.


penpen

batnoob
Posts: 56
Joined: 19 Apr 2017 12:23

Re: The new console mode disaster

#6 Post by batnoob » 09 Aug 2017 18:59

thank you misol, I have been looking for an answer to this myself.

thank you,
BatNoob

Post Reply