Python Programmatically Change Console font size

2020-04-12 00:29发布

问题:

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")

回答1:

I want to start by pointing out either:

  • [SO]: Python ctypes cdll.LoadLibrary, instantiate an object, execute its method, private variable address truncated (@CristiFati's answer)
  • [SO]: python ctypes issue on different OSes (@CristiFati's answer)

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

I changed your code a "bit".

code.py:

#!/usr/bin/env python

import sys
from ctypes import POINTER, WinDLL, Structure, sizeof, byref
from ctypes.wintypes import BOOL, SHORT, WCHAR, UINT, ULONG, DWORD, HANDLE


LF_FACESIZE = 32
STD_OUTPUT_HANDLE = -11


class COORD(Structure):
    _fields_ = [
        ("X", SHORT),
        ("Y", SHORT),
    ]


class CONSOLE_FONT_INFOEX(Structure):
    _fields_ = [
        ("cbSize", ULONG),
        ("nFont", DWORD),
        ("dwFontSize", COORD),
        ("FontFamily", UINT),
        ("FontWeight", UINT),
        ("FaceName", WCHAR * LF_FACESIZE)
    ]


kernel32_dll = WinDLL("kernel32.dll")

get_last_error_func = kernel32_dll.GetLastError
get_last_error_func.argtypes = []
get_last_error_func.restype = DWORD

get_std_handle_func = kernel32_dll.GetStdHandle
get_std_handle_func.argtypes = [DWORD]
get_std_handle_func.restype = HANDLE

get_current_console_font_ex_func = kernel32_dll.GetCurrentConsoleFontEx
get_current_console_font_ex_func.argtypes = [HANDLE, BOOL, POINTER(CONSOLE_FONT_INFOEX)]
get_current_console_font_ex_func.restype = BOOL

set_current_console_font_ex_func = kernel32_dll.SetCurrentConsoleFontEx
set_current_console_font_ex_func.argtypes = [HANDLE, BOOL, POINTER(CONSOLE_FONT_INFOEX)]
set_current_console_font_ex_func.restype = BOOL


def main():
    # Get stdout handle
    stdout = get_std_handle_func(STD_OUTPUT_HANDLE)
    if not stdout:
        print("{:s} error: {:d}".format(get_std_handle_func.__name__, get_last_error_func()))
        return
    # Get current font characteristics
    font = CONSOLE_FONT_INFOEX()
    font.cbSize = sizeof(CONSOLE_FONT_INFOEX)
    res = get_current_console_font_ex_func(stdout, False, byref(font))
    if not res:
        print("{:s} error: {:d}".format(get_current_console_font_ex_func.__name__, get_last_error_func()))
        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
        # Apply changes
        res = set_current_console_font_ex_func(stdout, False, byref(font))
        if not res:
            print("{:s} error: {:d}".format(set_current_console_font_ex_func.__name__, get_last_error_func()))
            return
        print("OMG! The window changed :)")
        # Get current font characteristics again and display font size
        res = get_current_console_font_ex_func(stdout, False, byref(font))
        if not res:
            print("{:s} error: {:d}".format(get_current_console_font_ex_func.__name__, get_last_error_func()))
            return
        print("\nNew sizes    X: {:d}, Y: {:d}".format(font.dwFontSize.X, font.dwFontSize.Y))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Notes:

  • ctypes allows low level access similar to C (only the syntax is Python)
  • Code uses [MS.Docs]: SetConsoleTextAttribute function
    • In order to avoid setting invalid values, it's used in conjunction with its counterpart: [MS.Docs]: GetCurrentConsoleFontEx function. Call that function in order to populate the CONSOLE_FONT_INFOEX structure, then modify only the desired values
    • There are "simpler" functions (e.g. [MS.Docs]: GetCurrentConsoleFont function or [MS.Docs]: GetConsoleFontSize function), but unfortunately there are no corresponding setters
  • 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 x64) the default value is 16. Yo 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 synch with the font size):



回答2:

It's not a pure python problem, but covers Windows API.

Look at the documentation of CONSOLE_FONT_INFOEX structure. There is a COORD member on it for width and height of each character.

To change console font size, you can give these attributes as a proper value:

font.dwFontSize.X = 11
font.dwFontSize.Y = 18

Reference: Change console font in Windows