Search code examples
pythoncmdfontsctypes

Python Programmatically Change Console font size


I found the code below which is supposed to programmatically change the console font size. I'm on Windows 10.

However, whatever values I tweak, I can't seem to get any control over the font size, and also for some reason the console which gets opened when I run this script is very wide.

I have no idea how ctypes works - all I want is to modify the size of the console font from inside Python.

Any actual working solutions?

import ctypes

LF_FACESIZE = 32
STD_OUTPUT_HANDLE = -11

class COORD(ctypes.Structure):
    _fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)]

class CONSOLE_FONT_INFOEX(ctypes.Structure):
    _fields_ = [("cbSize", ctypes.c_ulong),
                ("nFont", ctypes.c_ulong),
                ("dwFontSize", COORD),
                ("FontFamily", ctypes.c_uint),
                ("FontWeight", ctypes.c_uint),
                ("FaceName", ctypes.c_wchar * LF_FACESIZE)]

font = CONSOLE_FONT_INFOEX()
font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX)
font.nFont = 12
font.dwFontSize.X = 11
font.dwFontSize.Y = 18
font.FontFamily = 54
font.FontWeight = 400
font.FaceName = "Lucida Console"

handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
ctypes.windll.kernel32.SetCurrentConsoleFontEx(
        handle, ctypes.c_long(False), ctypes.pointer(font))


print("Foo")

Solution

  • Before everything, check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions).

    The CTypes home page (also listed in the above URL): [Python.Docs]: ctypes - A foreign function library for Python

    I changed your code "a bit".

    code00.py:

    #!/usr/bin/env python
    
    import ctypes as cts
    import ctypes.wintypes as wts
    import sys
    
    
    LF_FACESIZE = 32
    STD_OUTPUT_HANDLE = -11
    
    
    class CONSOLE_FONT_INFOEX(cts.Structure):
        _fields_ = (
            ("cbSize", wts.ULONG),
            ("nFont", wts.DWORD),
            ("dwFontSize", wts._COORD),
            ("FontFamily", wts.UINT),
            ("FontWeight", wts.UINT),
            ("FaceName", wts.WCHAR * LF_FACESIZE)
        )
    
    
    def main(*argv):
        kernel32 = cts.WinDLL("Kernel32.dll")
    
        GetLastError = kernel32.GetLastError
        GetLastError.argtypes = ()
        GetLastError.restype = wts.DWORD
    
        GetStdHandle = kernel32.GetStdHandle
        GetStdHandle.argtypes = (wts.DWORD,)
        GetStdHandle.restype = wts.HANDLE
    
        GetCurrentConsoleFontEx = kernel32.GetCurrentConsoleFontEx
        GetCurrentConsoleFontEx.argtypes = (wts.HANDLE, wts.BOOL, cts.POINTER(CONSOLE_FONT_INFOEX))
        GetCurrentConsoleFontEx.restype = wts.BOOL
    
        SetCurrentConsoleFontEx = kernel32.SetCurrentConsoleFontEx
        SetCurrentConsoleFontEx.argtypes = (wts.HANDLE, wts.BOOL, cts.POINTER(CONSOLE_FONT_INFOEX))
        SetCurrentConsoleFontEx.restype = wts.BOOL
    
        # Get stdout handle
        stdout = GetStdHandle(STD_OUTPUT_HANDLE)
        if not stdout:
            print("{:s} error: {:d}".format(GetStdHandle.__name__, GetLastError()))
            return
        # Get current font characteristics
        font = CONSOLE_FONT_INFOEX()
        font.cbSize = cts.sizeof(CONSOLE_FONT_INFOEX)
        res = GetCurrentConsoleFontEx(stdout, False, cts.byref(font))
        if not res:
            print("{:s} error: {:d}".format(GetCurrentConsoleFontEx.__name__, GetLastError()))
            return
        # Display font information
        print("Console information for {:}".format(font))
        for field_name, _ in font._fields_:
            field_data = getattr(font, field_name)
            if field_name == "dwFontSize":
                print("    {:s}: {{X: {:d}, Y: {:d}}}".format(field_name, field_data.X, field_data.Y))
            else:
                print("    {:s}: {:}".format(field_name, field_data))
        while 1:
            try:
                height = int(input("\nEnter font height (invalid to exit): "))
            except:
                break
            # Alter font height
            font.dwFontSize.X = 10  # Changing X has no effect (at least on my machine)
            font.dwFontSize.Y = height
            # Display font information
            res = SetCurrentConsoleFontEx(stdout, False, cts.byref(font))
            if not res:
                print("{:s} error: {:d}".format(SetCurrentConsoleFontEx.__name__, GetLastError()))
                return
            print("OMG! The window changed :)")
            # Get current font characteristics again
            res = GetCurrentConsoleFontEx(stdout, False, cts.byref(font))
            if not res:
                print("{:s} error: {:d}".format(GetCurrentConsoleFontEx.__name__, GetLastError()))
                return
            print("\nNew sizes    X: {:d}, Y: {:d}".format(font.dwFontSize.X, font.dwFontSize.Y))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

    Notes:

    • CTypes allows low level access similar to C (only the syntax is Python)

    • Code uses [MS.Learn]: SetConsoleTextAttribute function

    • The ctypes.wintypes constants (which reference the standard CTypes types) are used (to give the code a Win like flavor)

    • It is very (almost painfully) long (also because I've added proper error handling)

    • An alternative, as suggested in one of the answers of [SO]: Change console font in Windows (where you copied the code from), would be to install a 3rd-party module (e.g. [GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions which is a Python wrapper over WINAPIs) which would require less code to write because the bridging between Python and C would be already implemented, and probably the above functionality could be accomplished in just a few lines

    • As I commented in the code, setting COORD.X seems to be ignored. But it's automatically set when setting COORD.Y (to a value close to COORD.Y // 2 - probably to preserve the aspect ratio). On my machine (Win 10 pc064) with the currently selected font, the default value is 16. You might want to set it back at the end, to avoid leaving the console in a "challenged" state (apparently, Win adjusts Cmd window size, to be (sort of) in sync with the font size):

    Img0