Complete control of cmd windows

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#46 Post by einstein1969 » 08 Oct 2022 15:41

Is it possible to make a move_window starting from the latter?

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

Re: Complete control of cmd windows

#47 Post by aGerman » 08 Oct 2022 16:00

Well, on what base would you move your window then? You need the rectangle of the monitor's work space (not just the size because the origin of the monitor is not necessarily {0,0}, especially if the virtual screen spans over more than one monitor), and you need the window size. I guess this would need an extension of the ConsoleInfo macro upfront, right?

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#48 Post by einstein1969 » 08 Oct 2022 16:10

Okay, it gets complicated a lot, for the moment I tried to make a mix from what you did before, something like that came out:

Code: Select all

set move_window=for %%i in (1 2) do if %%i==2 (%\n%
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (%\n%
powershell -NoProfile -ExecutionPolicy Bypass -Command ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"kernel32.dll\")] static extern IntPtr GetConsoleWindow();^
%===% [DllImport(\"user32.dll\")] static extern void GetWindowRect(IntPtr hwnd,int[] rect);^
%===% [DllImport(\"user32.dll\")] static extern void GetMonitorInfoW(IntPtr hMonitor,int[] lpmi);^
%===% [DllImport(\"user32.dll\")] static extern IntPtr MonitorFromWindow(IntPtr hwnd,int dwFlags);^
%===% [DllImport(\"user32.dll\")] static extern void MoveWindow(IntPtr hwnd,int x,int y,int w,int h,int repaint);^
%===% public static void center() {^
%=====% var hwnd=GetConsoleWindow();^
%=====% var rect=new int[4];^
%=====% GetWindowRect(hwnd,rect);^
%=====% var moninf=new int[10];^
%=====% moninf[0]=40;^
%=====% GetMonitorInfoW(MonitorFromWindow(hwnd,2),moninf);^
%=====% MoveWindow(hwnd,^
%=======% %%~j,^
%=======% %%~k,^
%=======% rect[2]-rect[0],^
%=======% rect[3]-rect[1],^
%=======% 0);^
%===% }';^
%=% $w::center();^"%\n%
%=% )%\n%
%=% endlocal%\n%
) else setlocal EnableDelayedExpansion ^&set arg=
But the X always starts from -8 and I don't understand why.

What are you thinking about?

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

Re: Complete control of cmd windows

#49 Post by aGerman » 08 Oct 2022 17:42

The old move_window macro is quite okay. As I said, an update of the ConsoleInfo macro is far more sensible here. In the example below the values for centering are just calculated in the batch code.

Code: Select all

@echo off &setlocal enableDelayedExpansion
mode con lines=20 cols=50

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cfx=N,cfy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left pixel boundary of the monitor's workspace
::   cmt  top pixel boundary of the monitor's workspace
::   cmr  right pixel boundary of the monitor's workspace
::   cmb  bottom pixel boundary of the monitor's workspace
::   cwx  current width of the console window as number of pixels
::   cwy  current height of the console window as number of pixels
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   ccx  current width of the console window as number of character cells
::   ccy  current height of the console window as number of character cells
::   clx  largest width of the console window as number of character cells
::   cly  largest height of the console window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, 2^), $moninf^);^
 ^
%=   The $rect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $rect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $rect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
 ^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cfx={6},cfy={7},ccx={8},ccy={9},clx={10},cly={11}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($rect[2]-$rect[0]^), ($rect[3]-$rect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


for /f %%i in ('%ConsoleInfo%') do set /a "%%i"
echo console monitor boundaries: %cml%, %cmt%, %cmr%, %cmb%
echo console window size: %cwx%, %cwy%
echo console font size: %cfx%, %cfy%
echo console current size: %ccx%, %ccy%
echo console largest size: %clx%, %cly%
echo(

rem for macro definition/readability
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)

set move_window=for %%i in (1 2) do if %%i==2 (%\n%
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (%\n%
%=====% powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^"try{$c=Add-Type -Name WinAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\"^)] public static extern int SetWindowPos(IntPtr hWnd^, IntPtr hWndInsertAfter^, int X^, int Y^, int cx^, int cy^, uint uFlags^);^
%=====% [DllImport(\"kernel32.dll\"^)] public static extern IntPtr GetConsoleWindow(^);';^
%=====% $x=0; $y=0;^
%=====% if (([Int32]::TryParse(\"%%~j\"^, [ref]$x^) -eq $false^) -or ([Int32]::TryParse(\"%%~k\"^, [ref]$y^) -eq $false^)^){^
%=========% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%move_window%% X Y`r`n   X  left side of the window`r`n   Y  top of the window\"^);^
%=========% exit 1;^
%=====% }^
%=====% exit [int]($c::SetWindowPos($c::GetConsoleWindow(^)^, [IntPtr]::Zero^, $x^, $y^, 0^, 0^, 5^) -eq 0^);}catch{exit 1;}^"%\n%
%=% )%\n%
%=% endlocal%\n%
) else setlocal EnableDelayedExpansion ^&set arg=


:: center the window
set /a "newX=cml+(cmr-cml)/2-cwx/2"
set /a "newY=cmt+(cmb-cmt)/2-cwy/2"
%move_window% %newX% %newY%

pause

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#50 Post by einstein1969 » 09 Oct 2022 03:40

good work!

Tested only %ConsoleInfo% for now.

The test fail on "console window size"

tested with mode 50,50 - courier new 7x14 - zoom 100%

reported:

Code: Select all

console monitor boundaries: 0, 0, 1920, 1040
console window size: 366, 739
console font size: 7, 14
console current size: 50, 50
console largest size: 274, 72

calculate window size: 350 700
misurated 350x730 header 30px
Premere un tasto per continuare . . .
test code:

Code: Select all

@echo off & setlocal enableDelayedExpansion & if NOT "%1"=="" goto :subs

Start "Sphere" "%0" .

goto :eof

:subs

rem test with font 7x14 courier new
mode con lines=50 cols=50


call :init

echo console monitor boundaries: %cml%, %cmt%, %cmr%, %cmb%
echo console window size: %cwx%, %cwy%
echo console font size: %cfx%, %cfy%
echo console current size: %ccx%, %ccy%
echo console largest size: %clx%, %cly%
echo(

set /a cws.X=cfx*ccx, cws.Y=cfy*ccy

echo calculate window size: %cws.X% %cws.Y%
echo misurated 350x730 header 30px

pause

goto :eof

:: center the window
set /a "newX=cml+(cmr-cml)/2-cwx/2"
set /a "newY=cmt+(cmb-cmt)/2-cwy/2"
%move_window% %newX% %newY%

pause


rem choose console size
set /A img.x=50, img.y=50

(
rem setting request console size and remove buffers size on max
mode CON: COLS=!img.x! LINES=!img.y!

rem update Consoleinfo, I don't launch powershell again for the bug on win10 for chcp 65001
set /A ConsoleSize.X=img.x, ConsoleSize.Y=img.y
) 


rem center the windows
(
	rem is strange formula but work well
	set /A "center.x=(Screen.Width-img.x*FontSize.X)/2-8"

	rem to do : tuning.
	set /A "center.y=(Screen.height-img.y*FontSize.Y)/2-8-35"

	%move_window% !center.x! !center.y!
)

pause>nul
goto :eof

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init

rem clear environment for faster execution of SET command
(
rem set "Path=%SystemRoot%\system32"
for /F "Tokens=1 delims==" %%v in ('set') do if not %%v==ESC if not %%v==TMP if not %%v==Path if not %%v==SystemRoot set "%%v="
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cfx=N,cfy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left pixel boundary of the monitor's workspace
::   cmt  top pixel boundary of the monitor's workspace
::   cmr  right pixel boundary of the monitor's workspace
::   cmb  bottom pixel boundary of the monitor's workspace
::   cwx  current width of the console window as number of pixels
::   cwy  current height of the console window as number of pixels
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   ccx  current width of the console window as number of character cells
::   ccy  current height of the console window as number of character cells
::   clx  largest width of the console window as number of character cells
::   cly  largest height of the console window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, 2^), $moninf^);^
 ^
%=   The $rect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $rect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $rect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
 ^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cfx={6},cfy={7},ccx={8},ccy={9},clx={10},cly={11}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($rect[2]-$rect[0]^), ($rect[3]-$rect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

for /f %%i in ('%ConsoleInfo%') do set /a "%%i"

rem echo console monitor boundaries: %cml%, %cmt%, %cmr%, %cmb%
rem echo console window size: %cwx%, %cwy%
rem echo console font size: %cfx%, %cfy%
rem echo console current size: %ccx%, %ccy%
rem echo console largest size: %clx%, %cly%
rem echo(

rem font size in pixels
set /a "FontSize.X=%cfx%, FontSize.Y=%cfy%"

rem max/largest Size of cmd window/console window in chars
set /a "MaxConsoleSize.X=%clx%, MaxConsoleSize.Y=%cly%"

rem Current Size of cmd window/console window in chars
set /a "ConsoleSize.X=%ccx%, ConsoleSize.Y=%ccy%"

rem console window size in pixels
set /a "ConsoleWindowSize.X=%cwx%, ConsoleWindowSize.Y=%cwy%"

rem console monitor boundaries
set /A Monitor.left=%cml%, Monitor.top=%cmt% ,Monitor.right=%cmr% ,Monitor.bottom=%cmb%

rem get resolution of screen
for /f "usebackq tokens=1,2 delims= " %%x in (`mshta "javascript:new ActiveXObject('Scripting.FileSystemObject').GetStandardStream(1).Write(screen.width+' '+screen.height);close();noflash"`) do set /A Screen.Width=%%x, Screen.Height=%%y


rem for macro definition/readability
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)

set move_window=for %%i in (1 2) do if %%i==2 (%\n%
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (%\n%
%=====% powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^"try{$c=Add-Type -Name WinAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\"^)] public static extern int SetWindowPos(IntPtr hWnd^, IntPtr hWndInsertAfter^, int X^, int Y^, int cx^, int cy^, uint uFlags^);^
%=====% [DllImport(\"kernel32.dll\"^)] public static extern IntPtr GetConsoleWindow(^);';^
%=====% $x=0; $y=0;^
%=====% if (([Int32]::TryParse(\"%%~j\"^, [ref]$x^) -eq $false^) -or ([Int32]::TryParse(\"%%~k\"^, [ref]$y^) -eq $false^)^){^
%=========% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%move_window%% X Y`r`n   X  left side of the window`r`n   Y  top of the window\"^);^
%=========% exit 1;^
%=====% }^
%=====% exit [int]($c::SetWindowPos($c::GetConsoleWindow(^)^, [IntPtr]::Zero^, $x^, $y^, 0^, 0^, 5^) -eq 0^);}catch{exit 1;}^"%\n%
%=% )%\n%
%=% endlocal%\n%
) else setlocal EnableDelayedExpansion ^&set arg=


:: center the window
rem set /a "newX=cml+(cmr-cml)/2-cwx/2"
rem set /a "newY=cmt+(cmb-cmt)/2-cwy/2"
rem %rem move_window% %newX% %newY%

rem pause
:?

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#51 Post by einstein1969 » 09 Oct 2022 03:59

DPI awareness?

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

Re: Complete control of cmd windows

#52 Post by aGerman » 09 Oct 2022 04:07

DPI awareness doesn't actually matter as long as both the ConsoleInfo and the move_window macros are not DPI aware. Of course the absolute values you get will probably differ. Made them both DPI aware now:

Code: Select all

@echo off &setlocal enableDelayedExpansion
mode con lines=20 cols=50

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cvx=N,cvy=N,cfx=N,cfy=N,chx=N,chy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left dots boundary of the monitor's workspace
::   cmt  top dots boundary of the monitor's workspace
::   cmr  right dots boundary of the monitor's workspace
::   cmb  bottom dots boundary of the monitor's workspace
::   cwx  current width of the console window as number of dots
::   cwy  current height of the console window as number of dots
::   cvx  current width of the console viewport as number of dots
::   cvy  current height of the console viewport as number of dots
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   chx  approximated console charcell width as number of dots
::   chy  approximated console charcell height as number of dots
::   ccx  current width of the console client window as number of character cells
::   ccy  current height of the console client window as number of character cells
::   clx  largest width of the console client window as number of character cells
::   cly  largest height of the console client window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void SetProcessDPIAware(^);^
%===% [DllImport(\"shcore.dll\"^)]^
%=====% public static extern void SetProcessDpiAwareness(int value^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetClientRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%=% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $MONITOR_DEFAULTTONEAREST=2;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST^), $moninf^);^
 ^
%=   The $wrect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $wrect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $wrect^);^
 ^
%=   The $crect array is a replacement for the RECT structure. The elements at index          =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the client window.   =% ^
%=% $crect=[int[]]::new(4^);^
%=% $w::GetClientRect($hwnd, $crect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cvx={6},cvy={7},cfx={8},cfy={9},chx={10},chy={11},ccx={12},ccy={13},clx={14},cly={15}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($wrect[2]-$wrect[0]^), ($wrect[3]-$wrect[1]^),^
%===% ($crect[2]-$crect[0]^), ($crect[3]-$crect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% [math]::Round(($crect[2]-$crect[0]^)/$cur.Width^), [math]::Round(($crect[3]-$crect[1]^)/$cur.Height^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


for /f %%i in ('%ConsoleInfo%') do set /a "%%i"
echo console monitor boundaries: %cml%, %cmt%, %cmr%, %cmb%
echo console window size: %cwx%, %cwy%
echo console viewport size: %cvx%, %cvy%
echo console font size: %cfx%, %cfy%
echo console charcell size: %chx%, %chy%
echo console current size: %ccx%, %ccy%
echo console largest size: %clx%, %cly%
echo(

rem for macro definition/readability
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)

set move_window=for %%i in (1 2) do if %%i==2 (%\n%
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (%\n%
%=====% powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^"try{$c=Add-Type -Name WinAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\"^)] public static extern void SetProcessDPIAware(^);^
%=====% [DllImport(\"shcore.dll\"^)] public static extern void SetProcessDpiAwareness(int value^);^
%=====% [DllImport(\"user32.dll\"^)] public static extern int SetWindowPos(IntPtr hWnd^, IntPtr hWndInsertAfter^, int X^, int Y^, int cx^, int cy^, uint uFlags^);^
%=====% [DllImport(\"kernel32.dll\"^)] public static extern IntPtr GetConsoleWindow(^);';^
%=====% try{$c::SetProcessDpiAwareness(2^)}catch{$c::SetProcessDPIAware(^)}^
%=====% $x=0; $y=0;^
%=====% if (([Int32]::TryParse(\"%%~j\"^, [ref]$x^) -eq $false^) -or ([Int32]::TryParse(\"%%~k\"^, [ref]$y^) -eq $false^)^){^
%=========% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%move_window%% X Y`r`n   X  left side of the window`r`n   Y  top of the window\"^);^
%=========% exit 1;^
%=====% }^
%=====% exit [int]($c::SetWindowPos($c::GetConsoleWindow(^)^, [IntPtr]::Zero^, $x^, $y^, 0^, 0^, 5^) -eq 0^);}catch{exit 1;}^"%\n%
%=% )%\n%
%=% endlocal%\n%
) else setlocal EnableDelayedExpansion ^&set arg=


:: center the window
set /a "newX=cml+(cmr-cml)/2-cwx/2"
set /a "newY=cmt+(cmb-cmt)/2-cwy/2"
%move_window% %newX% %newY%

pause

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

Re: Complete control of cmd windows

#53 Post by aGerman » 09 Oct 2022 06:49

I updated the code above to get additional information:
- Size of the viewport in dots (the viewport is the "inner" client frame that displays the visible part of the console buffer).
- Size of a character cell. I suggest to use this instead of the font size for the calculation of your circle ratio!

Note: The size of a character cell is an approximation because the V2 console allows fractional charcells at the right and bottom edge. If you resize the window it doesn't snap in at full cells like the legacy console did.

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

Re: Complete control of cmd windows

#54 Post by aGerman » 09 Oct 2022 08:40

One of your previous codes updated to:
- draw spheres rounder
- draw faster
- support file names with special characters (like parentheses)
- close the window after a key is pressed

Code: Select all

@echo off
rem save this script in UTF-8

set "me=%~f0"
setlocal enableDelayedExpansion

if "%1"=="" (
  rem restart maximized
  Start "Sphere" /MAX cmd.exe /c ""!me!" max"
  goto :eof
)

if "%1"=="sphere" (
  call :sphere %2 %3 %4 %5 %6 %7
  goto :eof
)

rem "%1" is "max" if we reach out to this point

call :GetInfo

rem setting request console size and remove buffers size on max
mode CON: COLS=!ConsoleSize.X! LINES=!ConsoleSize.Y!

rem multithread 9 thread

set /A "deltaX=ConsoleSize.X/3, deltaY=ConsoleSize.Y/3, Radius=deltaY/2"

start "" /B cmd.exe /c ""!me!" sphere Red     1 1 !deltaX! !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Green   !deltaX! 1 !deltaX!*2 !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Blue    !deltaX!*2 1 !deltaX!*3 !deltaY! !Radius!"

start "" /B cmd.exe /c ""!me!" sphere Yellow  1 !deltaY!+1 !deltaX! !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Cyan    !deltaX! !deltaY!+1 !deltaX!*2 !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Magenta !deltaX!*2 !deltaY!+1 !deltaX!*3 !deltaY!*2 !Radius!"

start "" /B cmd.exe /c ""!me!" sphere White   1 !deltaY!*2+1 !deltaX! !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Orange  !deltaX! !deltaY!*2+1 !deltaX!*2 !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Pink    !deltaX!*2 !deltaY!*2+1 !deltaX!*3 !deltaY!*3 !Radius!"

pause>nul
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:sphere color Begin.X Begin.Y End.X End.Y Radius

rem title %1 %2 %3 %4 %5 %6
rem pause 

call :init

set /A Bimg.x=%2, Bimg.y=%3
set /A Eimg.x=%4, Eimg.y=%5
set /A Radius=%6

rem cell size in dots
set /A xc=CellSize.X, yc=CellSize.Y

rem draw a circle (x-x0)^2+(y-y0)^2=r^2 . Implicit equation for simpler math.

rem set radius and center in cmd windows, assume screen ratio greater than one ie 1280/720=1.777

set /A "R=Radius*yc, X0=((Eimg.x-Bimg.x)/2+Bimg.x)*xc, Y0=Bimg.y*yc+R"

rem setting step for better smoothing colors
set /A "mS=-(R*R), step=255*100000/-mS, stepL=128*100000/-mS"

rem (performance) we compare colors first instead of doing it for each painted cell
if "%1"=="Red" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Green" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 !C! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Blue" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 0 !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Yellow" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! !C! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Cyan" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 !C! !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Magenta" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="White" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! !C! !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Orange" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000, CL=-S*stepL/100000"
      if !S! leq 0 %plot% %%x %%y !C! !CL! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Pink" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000, CL=-S*stepL/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 !CL!
    )
  )
  %flush%
  goto :eof
)

%flush%
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:GetInfo

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cvx=N,cvy=N,cfx=N,cfy=N,chx=N,chy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left dots boundary of the monitor's workspace
::   cmt  top dots boundary of the monitor's workspace
::   cmr  right dots boundary of the monitor's workspace
::   cmb  bottom dots boundary of the monitor's workspace
::   cwx  current width of the console window as number of dots
::   cwy  current height of the console window as number of dots
::   cvx  current width of the console viewport as number of dots
::   cvy  current height of the console viewport as number of dots
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   chx  approximated console charcell width as number of dots
::   chy  approximated console charcell height as number of dots
::   ccx  current width of the console client window as number of character cells
::   ccy  current height of the console client window as number of character cells
::   clx  largest width of the console client window as number of character cells
::   cly  largest height of the console client window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void SetProcessDPIAware(^);^
%===% [DllImport(\"shcore.dll\"^)]^
%=====% public static extern void SetProcessDpiAwareness(int value^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetClientRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%=% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $MONITOR_DEFAULTTONEAREST=2;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST^), $moninf^);^
 ^
%=   The $wrect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $wrect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $wrect^);^
 ^
%=   The $crect array is a replacement for the RECT structure. The elements at index          =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the client window.   =% ^
%=% $crect=[int[]]::new(4^);^
%=% $w::GetClientRect($hwnd, $crect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cvx={6},cvy={7},cfx={8},cfy={9},chx={10},chy={11},ccx={12},ccy={13},clx={14},cly={15}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($wrect[2]-$wrect[0]^), ($wrect[3]-$wrect[1]^),^
%===% ($crect[2]-$crect[0]^), ($crect[3]-$crect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% [math]::Round(($crect[2]-$crect[0]^)/$cur.Width^), [math]::Round(($crect[3]-$crect[1]^)/$cur.Height^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

for /f %%i in ('%ConsoleInfo%') do set /a "%%i"

rem cell size in dots
set /a "CellSize.X=%chx%, CellSize.Y=%chy%"

rem Current Size of cmd window/console window
set /a "ConsoleSize.X=%ccx%, ConsoleSize.Y=%ccy%"

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init

rem this code must execute after ConsoleInfo macro, if not this change font family to TERMINAL/RASTER, in window 10
chcp 65001 >nul

rem clear environment for faster execution of SET command
(
  for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
  set "Path=%SystemRoot%\system32"
  set /a "CellSize.X=%CellSize.X%, CellSize.Y=%CellSize.Y%"
  set /a "ConsoleSize.X=%ConsoleSize.X%, ConsoleSize.Y=%ConsoleSize.Y%"
)

rem for ansi sequence
for /F %%a in ('echo prompt $E^| cmd.exe') do set "ESC=%%a"

:: Hide the cursor
<nul set /p "=!ESC![?25l"

rem ALT+219
set "Char=█"
rem set "Char=*"

rem for macro definition/readability
(set \n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: macro Plot=Screen.Setpixel X Y R G B

set "Buffer="

set Plot=for %%# in (1 2) do if %%#==2 (%\n%
	for /f "tokens=1-5 delims=, " %%1 in ("^!args^!") do ( %\n%
		if not defined Buffer (%\n%
			set "Buffer=^!ESC^![%%2;%%1H^!ESC^![38;2;%%3;%%4;%%5m^!Char^!" %\n%
		) else ( %\n%
			if "^!Buffer:~100,1^!"=="" ( %\n%
				set "Buffer=^!Buffer^!^!ESC^![%%2;%%1H^!ESC^![38;2;%%3;%%4;%%5m^!Char^!" %\n%
			) else ( %\n%
				^<nul set /p "=^!Buffer^!^!ESC^![%%2;%%1H^!ESC^![38;2;%%3;%%4;%%5m^!Char^!^!ESC^![0m" %\n%
				set "Buffer=" %\n%
			) %\n%
		) %\n%
)) else set args=

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Flush flush the variable buffer.

set Flush=^<nul set /p "=^!Buffer^!^!ESC^![0m" ^& set "Buffer="

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#55 Post by einstein1969 » 09 Oct 2022 14:30

great! :)

I'm testing everything

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

Re: Complete control of cmd windows

#56 Post by aGerman » 09 Oct 2022 15:29

Similarly using a centered window.

Code: Select all

@echo off
rem save this script in UTF-8

set "me=%~f0"
setlocal enableDelayedExpansion

if "%1"=="sphere" (
  call :sphere %2 %3 %4 %5 %6 %7
  goto :eof
)

title Spheres
mode CON: COLS=100 LINES=40

call :GetInfoAndCenter

rem multithread 9 thread

set /A "deltaX=ConsoleSize.X/3, deltaY=ConsoleSize.Y/3, Radius=deltaY/2"

start "" /B cmd.exe /c ""!me!" sphere Red     1 1 !deltaX! !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Green   !deltaX! 1 !deltaX!*2 !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Blue    !deltaX!*2 1 !deltaX!*3 !deltaY! !Radius!"

start "" /B cmd.exe /c ""!me!" sphere Yellow  1 !deltaY!+1 !deltaX! !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Cyan    !deltaX! !deltaY!+1 !deltaX!*2 !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Magenta !deltaX!*2 !deltaY!+1 !deltaX!*3 !deltaY!*2 !Radius!"

start "" /B cmd.exe /c ""!me!" sphere White   1 !deltaY!*2+1 !deltaX! !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Orange  !deltaX! !deltaY!*2+1 !deltaX!*2 !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Pink    !deltaX!*2 !deltaY!*2+1 !deltaX!*3 !deltaY!*3 !Radius!"

pause>nul
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:sphere color Begin.X Begin.Y End.X End.Y Radius

rem title %1 %2 %3 %4 %5 %6
rem pause 

call :init

set /A Bimg.x=%2, Bimg.y=%3
set /A Eimg.x=%4, Eimg.y=%5
set /A Radius=%6

rem cell size in dots
set /A xc=CellSize.X, yc=CellSize.Y

rem draw a circle (x-x0)^2+(y-y0)^2=r^2 . Implicit equation for simpler math.

rem set radius and center in cmd windows, assume screen ratio greater than one ie 1280/720=1.777

set /A "R=Radius*yc, X0=((Eimg.x-Bimg.x)/2+Bimg.x)*xc, Y0=Bimg.y*yc+R"

rem setting step for better smoothing colors
set /A "mS=-(R*R), step=255*100000/-mS, stepL=128*100000/-mS"

rem (performance) we compare colors first instead of doing it for each painted cell
if "%1"=="Red" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Green" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 !C! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Blue" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 0 !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Yellow" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! !C! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Cyan" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y 0 !C! !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Magenta" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="White" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y !C! !C! !C!
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Orange" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000, CL=-S*stepL/100000"
      if !S! leq 0 %plot% %%x %%y !C! !CL! 0
    )
  )
  %flush%
  goto :eof
)

if "%1"=="Pink" (
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000, CL=-S*stepL/100000"
      if !S! leq 0 %plot% %%x %%y !C! 0 !CL!
    )
  )
  %flush%
  goto :eof
)

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:GetInfoAndCenter

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cvx=N,cvy=N,cfx=N,cfy=N,chx=N,chy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left dots boundary of the monitor's workspace
::   cmt  top dots boundary of the monitor's workspace
::   cmr  right dots boundary of the monitor's workspace
::   cmb  bottom dots boundary of the monitor's workspace
::   cwx  current width of the console window as number of dots
::   cwy  current height of the console window as number of dots
::   cvx  current width of the console viewport as number of dots
::   cvy  current height of the console viewport as number of dots
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   chx  approximated console charcell width as number of dots
::   chy  approximated console charcell height as number of dots
::   ccx  current width of the console client window as number of character cells
::   ccy  current height of the console client window as number of character cells
::   clx  largest width of the console client window as number of character cells
::   cly  largest height of the console client window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void SetProcessDPIAware(^);^
%===% [DllImport(\"shcore.dll\"^)]^
%=====% public static extern void SetProcessDpiAwareness(int value^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetClientRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%=% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $MONITOR_DEFAULTTONEAREST=2;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST^), $moninf^);^
 ^
%=   The $wrect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $wrect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $wrect^);^
 ^
%=   The $crect array is a replacement for the RECT structure. The elements at index          =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the client window.   =% ^
%=% $crect=[int[]]::new(4^);^
%=% $w::GetClientRect($hwnd, $crect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cvx={6},cvy={7},cfx={8},cfy={9},chx={10},chy={11},ccx={12},ccy={13},clx={14},cly={15}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($wrect[2]-$wrect[0]^), ($wrect[3]-$wrect[1]^),^
%===% ($crect[2]-$crect[0]^), ($crect[3]-$crect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% [math]::Round(($crect[2]-$crect[0]^)/$cur.Width^), [math]::Round(($crect[3]-$crect[1]^)/$cur.Height^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: %MoveWindow% X Y
::   X  left side of the window
::   Y  top of the window
set MoveWindow=for %%i in (1 2) do if %%i==2 (^
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (powershell.exe -nop -ep Bypass -Command ^"^
%===% try {$w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\"^)]^
%=======% public static extern void SetProcessDPIAware(^);^
%=====% [DllImport(\"shcore.dll\"^)]^
%=======% public static extern void SetProcessDpiAwareness(int value^);^
%=====% [DllImport(\"user32.dll\"^)]^
%=======% public static extern int SetWindowPos(IntPtr hWnd^, IntPtr hWndInsertAfter^, int X^, int Y^, int cx^, int cy^, uint uFlags^);^
%=====% [DllImport(\"kernel32.dll\"^)]^
%=======% public static extern IntPtr GetConsoleWindow(^);^
%===% ';^
%===% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%===% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
%===% $x=0; $y=0;^
%===% if (([Int32]::TryParse(\"%%~j\"^, [ref]$x^) -eq $false^) -or ([Int32]::TryParse(\"%%~k\"^, [ref]$y^) -eq $false^)^){^
%=====% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%MoveWindow%% X Y`r`n   X  left side of the window`r`n   Y  top of the window\"^);^
%=====% exit 1;^
%===% }^
%===% exit [int]($w::SetWindowPos($w::GetConsoleWindow(^)^, [IntPtr]::Zero^, $x^, $y^, 0^, 0^, 5^) -eq 0^)} catch {exit 1}^"^
%=% )^
%=% ^&endlocal^
) else setlocal EnableDelayedExpansion ^&set arg=
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

::
for /f %%i in ('%ConsoleInfo%') do set /a "%%i"

:: center the window
set /a "newX=cml+(cmr-cml)/2-cwx/2, newY=cmt+(cmb-cmt)/2-cwy/2"
%MoveWindow% %newX% %newY%

rem cell size in dots
set /a "CellSize.X=%chx%, CellSize.Y=%chy%"

rem Current Size of cmd window/console window
set /a "ConsoleSize.X=%ccx%, ConsoleSize.Y=%ccy%"

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init

rem this code must execute after ConsoleInfo macro, if not this change font family to TERMINAL/RASTER, in window 10
chcp 65001 >nul

rem clear environment for faster execution of SET command
(
  for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
  set "Path=%SystemRoot%\system32"
  set /a "CellSize.X=%CellSize.X%, CellSize.Y=%CellSize.Y%"
  set /a "ConsoleSize.X=%ConsoleSize.X%, ConsoleSize.Y=%ConsoleSize.Y%"
)

rem for ansi sequence
for /F %%a in ('echo prompt $E^| cmd.exe') do set "ESC=%%a"

:: Hide the cursor
<nul set /p "=!ESC![?25l"

rem ALT+219
set "Char=█"
rem set "Char=*"

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: macro Plot=Screen.Setpixel X Y R G B

set "Buffer="
rem (performance) variables ESC and Char are already defined, we expand them
rem only once during macro definition rather than each time the macro is called;
rem execute "likely" code in the IF branch, and "unlikely" code in the ELSE branch
set Plot=for %%# in (1 2) do if %%#==2 (^
%=% for /f "tokens=1-5 delims=, " %%1 in ("^!args^!") do (^
%===% if defined Buffer (^
%=====% if "^!Buffer:~100^!"=="" (^
%=======% set "Buffer=^!Buffer^!%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%"^
%=====% ) else (^
%=======% ^<nul set /p "=^!Buffer^!%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%%ESC%[0m"^
%=======% ^& set "Buffer="^
%=====% )^
%===% ) else (^
%=====% set "Buffer=%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%"^
%===% )^
%=% )) else set args=

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Flush flush the variable buffer.

set Flush=^<nul set /p "=^!Buffer^!%ESC%[0m" ^& set "Buffer="

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
I've also been able to get rid of the \n in both the MoveWindow and Plot macros.

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#57 Post by einstein1969 » 09 Oct 2022 15:42

I have found an error on dimension reported.

The simple test:

zoom 125%
font size 7x14 consolas
size of console window in char 130x30
effettive font size (misurated with paint) 8x18
viewport.X=130x8=1040 - viewport.Y=30x18=540 -> 1040x540
console windows size (misurated) 1040x577, header (misurated)=37pix -> 1040x(540+37)

value returned by ConsoleInfo

Code: Select all

console monitor boundaries: 0, 0, 1920, 1030
console window size: 1058, 587
console viewport size: 1040, 540
console font size: 6, 14
console charcell size: 8, 18
console current size: 130, 30
console largest size: 240, 55

Code: Select all

console monitor boundaries is 	OK
console window size 		NOT OK
console viewport size is 	OK
console font size 		NOT OK
console charcell is 		OK
console current size is 	OK
console largest size is 	OK
the calcolate coords is incorrect because cwx and cwy are wrong

Code: Select all

	:: center the window
	set /a "newX=cml+(cmr-cml)/2-cwx/2"
	set /a "newY=cmt+(cmb-cmt)/2-cwy/2"

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#58 Post by einstein1969 » 09 Oct 2022 15:45

for reduce code you can use this part

Code: Select all

rem setting step for better smoothing colors
set /A "mS=-(R*R), step=255*100000/-mS, stepL=128*100000/-mS"

if "%1"=="Red" set "RGB=^!C^! 0 0"
if "%1"=="Green" set "RGB=0 ^!C^! 0"
if "%1"=="Blue" set "RGB=0 0 ^!C^!"
if "%1"=="Yellow" set "RGB=^!C^! ^!C^! 0"
if "%1"=="Cyan" set "RGB=0 ^!C^! ^!C^!"
if "%1"=="Magenta" set "RGB=^!C^! 0 ^!C^!"
if "%1"=="White" set "RGB=^!C^! ^!C^! ^!C^!"
if "%1"=="Orange" set "RGB=^!C^! ^!CL^! 0"
if "%1"=="Pink" set "RGB=^!C^! 0 ^!CL^!"

rem (performance) we compare colors first instead of doing it for each painted cell
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y %RGB%
    )
  )
  %flush%

goto :eof

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

Re: Complete control of cmd windows

#59 Post by aGerman » 09 Oct 2022 16:12

It's a mess if you try inferring dots from pixels. I actually trust the values I get from the API because the results are promising. Centering works well for me.
And in order to avoid working with the font size in pixels (reported by the Console API), I calculate the size of the character cells in dots from values reported by the GDI API. This is more reliable and makes spheres round with all fonts and font sizes I tried.
for reduce code you can use this part
Yeah, good idea :)

einstein1969
Expert
Posts: 960
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Complete control of cmd windows

#60 Post by einstein1969 » 09 Oct 2022 16:36

aGerman wrote:
09 Oct 2022 16:12
It's a mess if you try inferring dots from pixels. I actually trust the values I get from the API because the results are promising. Centering works well for me.
And in order to avoid working with the font size in pixels (reported by the Console API), I calculate the size of the character cells in dots from values reported by the GDI API. This is more relying and makes spheres round with all fonts and font sizes I tried.
for reduce code you can use this part
Yeah, good idea :)
Yes, the centering is still good although the data seem incorrect.

to understand better I did this test:
- uses 100% zoom and 8x8 raster fonts

Code: Select all

@echo off
rem save this script in UTF-8

set "me=%~f0"
setlocal enableDelayedExpansion

if "%1"=="sphere" (
  call :sphere %2 %3 %4 %5 %6 %7
  goto :eof
)

title Spheres
mode CON: COLS=100 LINES=50

call :GetInfo

set /A Newsize.X=clx-1, Newsize.Y=cly-2

mode CON: COLS=!Newsize.X! LINES=!Newsize.Y!

call :GetInfo

call :AndCenter

rem multithread 9 thread

set /A "deltaX=ConsoleSize.X/3, deltaY=ConsoleSize.Y/3, Radius=deltaY/2"

start "" /B cmd.exe /c ""!me!" sphere Red     1 1 !deltaX! !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Green   !deltaX! 1 !deltaX!*2 !deltaY! !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Blue    !deltaX!*2 1 !deltaX!*3 !deltaY! !Radius!"

start "" /B cmd.exe /c ""!me!" sphere Yellow  1 !deltaY!+1 !deltaX! !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Cyan    !deltaX! !deltaY!+1 !deltaX!*2 !deltaY!*2 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Magenta !deltaX!*2 !deltaY!+1 !deltaX!*3 !deltaY!*2 !Radius!"

start "" /B cmd.exe /c ""!me!" sphere White   1 !deltaY!*2+1 !deltaX! !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Orange  !deltaX! !deltaY!*2+1 !deltaX!*2 !deltaY!*3 !Radius!"
start "" /B cmd.exe /c ""!me!" sphere Pink    !deltaX!*2 !deltaY!*2+1 !deltaX!*3 !deltaY!*3 !Radius!"

pause>nul
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:sphere color Begin.X Begin.Y End.X End.Y Radius

rem title %1 %2 %3 %4 %5 %6
rem pause 

call :init

set /A Bimg.x=%2, Bimg.y=%3
set /A Eimg.x=%4, Eimg.y=%5
set /A Radius=%6

rem cell size in dots
set /A xc=CellSize.X, yc=CellSize.Y

rem draw a circle (x-x0)^2+(y-y0)^2=r^2 . Implicit equation for simpler math.

rem set radius and center in cmd windows, assume screen ratio greater than one ie 1280/720=1.777

set /A "R=Radius*yc, X0=((Eimg.x-Bimg.x)/2+Bimg.x)*xc, Y0=Bimg.y*yc+R"

rem setting step for better smoothing colors
set /A "mS=-(R*R), step=255*100000/-mS, stepL=128*100000/-mS"

if "%1"=="Red" set "RGB=^!C^! 0 0"
if "%1"=="Green" set "RGB=0 ^!C^! 0"
if "%1"=="Blue" set "RGB=0 0 ^!C^!"
if "%1"=="Yellow" set "RGB=^!C^! ^!C^! 0"
if "%1"=="Cyan" set "RGB=0 ^!C^! ^!C^!"
if "%1"=="Magenta" set "RGB=^!C^! 0 ^!C^!"
if "%1"=="White" set "RGB=^!C^! ^!C^! ^!C^!"
if "%1"=="Orange" set "RGB=^!C^! ^!CL^! 0"
if "%1"=="Pink" set "RGB=^!C^! 0 ^!CL^!"

rem (performance) we compare colors first instead of doing it for each painted cell
  For /L %%y in (!Bimg.y!,1,!Eimg.y!) do (
    For /L %%x in (!Bimg.x!,1,!Eimg.x!) do (
      set /A "px=%%x*xc, py=%%y*yc, x=px-x0, y=py-y0, S=y*y+x*x-R*R, C=-S*step/100000"
      if !S! leq 0 %plot% %%x %%y %RGB%
    )
  )
  %flush%
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:GetInfo

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
:: It prints a string in the following format to StdOut:
::   cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cvx=N,cvy=N,cfx=N,cfy=N,chx=N,chy=N,ccx=N,ccy=N,clx=N,cly=N
:: With:
::   N    different integral values of the below properties
::   cml  left dots boundary of the monitor's workspace
::   cmt  top dots boundary of the monitor's workspace
::   cmr  right dots boundary of the monitor's workspace
::   cmb  bottom dots boundary of the monitor's workspace
::   cwx  current width of the console window as number of dots
::   cwy  current height of the console window as number of dots
::   cvx  current width of the console viewport as number of dots
::   cvy  current height of the console viewport as number of dots
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   chx  approximated console charcell width as number of dots
::   chy  approximated console charcell height as number of dots
::   ccx  current width of the console client window as number of character cells
::   ccy  current height of the console client window as number of character cells
::   clx  largest width of the console client window as number of character cells
::   cly  largest height of the console client window as number of character cells
:: To define variables %cfx% .. %cly%, execute the macro in a FOR /F loop like that:
::   FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=^
powershell -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void SetProcessDPIAware(^);^
%===% [DllImport(\"shcore.dll\"^)]^
%=====% public static extern void SetProcessDpiAwareness(int value^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetClientRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%=% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
 ^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $MONITOR_DEFAULTTONEAREST=2;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST^), $moninf^);^
 ^
%=   The $wrect array is a replacement for the RECT structure. The elements at index    =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $wrect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $wrect^);^
 ^
%=   The $crect array is a replacement for the RECT structure. The elements at index          =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the client window.   =% ^
%=% $crect=[int[]]::new(4^);^
%=% $w::GetClientRect($hwnd, $crect^);^
 ^
%=% $NIL=[IntPtr]::Zero;^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $out=$w::CreateFile('CONOUT$', $GENERIC_READ -bor $GENERIC_WRITE, $FILE_SHARE_WRITE, $NIL, $OPEN_EXISTING, 0, $NIL^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure. The 16 low order bits of the   =% ^
%=   second element represent the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($out, 0, $fntinf^);^
%=% $w::CloseHandle($out^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cml={0},cmt={1},cmr={2},cmb={3},cwx={4},cwy={5},cvx={6},cvy={7},cfx={8},cfy={9},chx={10},chy={11},ccx={12},ccy={13},clx={14},cly={15}' -f^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($wrect[2]-$wrect[0]^), ($wrect[3]-$wrect[1]^),^
%===% ($crect[2]-$crect[0]^), ($crect[3]-$crect[1]^),^
%===% ($fntinf[1] -band 0xFFFF^), ($fntinf[1] -shr 16^),^
%===% [math]::Round(($crect[2]-$crect[0]^)/$cur.Width^), [math]::Round(($crect[3]-$crect[1]^)/$cur.Height^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height;^
 ^"
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: %MoveWindow% X Y
::   X  left side of the window
::   Y  top of the window
set MoveWindow=for %%i in (1 2) do if %%i==2 (^
%=% for /f "tokens=1*" %%j in ("^!arg^!") do (powershell.exe -nop -ep Bypass -Command ^"^
%===% try {$w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\"^)]^
%=======% public static extern void SetProcessDPIAware(^);^
%=====% [DllImport(\"shcore.dll\"^)]^
%=======% public static extern void SetProcessDpiAwareness(int value^);^
%=====% [DllImport(\"user32.dll\"^)]^
%=======% public static extern int SetWindowPos(IntPtr hWnd^, IntPtr hWndInsertAfter^, int X^, int Y^, int cx^, int cy^, uint uFlags^);^
%=====% [DllImport(\"kernel32.dll\"^)]^
%=======% public static extern IntPtr GetConsoleWindow(^);^
%===% ';^
%===% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%===% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
%===% $x=0; $y=0;^
%===% if (([Int32]::TryParse(\"%%~j\"^, [ref]$x^) -eq $false^) -or ([Int32]::TryParse(\"%%~k\"^, [ref]$y^) -eq $false^)^){^
%=====% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%MoveWindow%% X Y`r`n   X  left side of the window`r`n   Y  top of the window\"^);^
%=====% exit 1;^
%===% }^
%===% exit [int]($w::SetWindowPos($w::GetConsoleWindow(^)^, [IntPtr]::Zero^, $x^, $y^, 0^, 0^, 5^) -eq 0^)} catch {exit 1}^"^
%=% )^
%=% ^&endlocal^
) else setlocal EnableDelayedExpansion ^&set arg=
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

::
for /f %%i in ('%ConsoleInfo%') do set /a "%%i"

rem cell size in dots
set /a "CellSize.X=%chx%, CellSize.Y=%chy%"

rem Current Size of cmd window/console window
set /a "ConsoleSize.X=%ccx%, ConsoleSize.Y=%ccy%"

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:AndCenter

:: center the window
set /a "newX=cml+(cmr-cml)/2-cwx/2, newY=cmt+(cmb-cmt)/2-cwy/2"
%MoveWindow% %newX% %newY%

title Move to %newX%,%newY%

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init

rem this code must execute after ConsoleInfo macro, if not this change font family to TERMINAL/RASTER, in window 10
chcp 65001 >nul

rem clear environment for faster execution of SET command
(
  for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
  set "Path=%SystemRoot%\system32"
  set /a "CellSize.X=%CellSize.X%, CellSize.Y=%CellSize.Y%"
  set /a "ConsoleSize.X=%ConsoleSize.X%, ConsoleSize.Y=%ConsoleSize.Y%"
)

rem for ansi sequence
for /F %%a in ('echo prompt $E^| cmd.exe') do set "ESC=%%a"

:: Hide the cursor
<nul set /p "=!ESC![?25l"

rem ALT+219
set "Char=█"
rem set "Char=*"

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: macro Plot=Screen.Setpixel X Y R G B

set "Buffer="
rem (performance) variables ESC and Char are already defined, we expand them
rem only once during macro definition rather than each time the macro is called;
rem execute "likely" code in the IF branch, and "unlikely" code in the ELSE branch
set Plot=for %%# in (1 2) do if %%#==2 (^
%=% for /f "tokens=1-5 delims=, " %%1 in ("^!args^!") do (^
%===% if defined Buffer (^
%=====% if "^!Buffer:~100^!"=="" (^
%=======% set "Buffer=^!Buffer^!%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%"^
%=====% ) else (^
%=======% ^<nul set /p "=^!Buffer^!%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%%ESC%[0m"^
%=======% ^& set "Buffer="^
%=====% )^
%===% ) else (^
%=====% set "Buffer=%ESC%[%%2;%%1H%ESC%[38;2;%%3;%%4;%%5m%Char%"^
%===% )^
%=% )) else set args=

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Flush flush the variable buffer.

set Flush=^<nul set /p "=^!Buffer^!%ESC%[0m" ^& set "Buffer="

goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
as you can see the values are not completely wrong, it seems that they participate in making the centering formula work well, which in my case chooses the position (X=-4, Y=1).
The X position is negative!

Post Reply