khurata’s blog

khurata’s blog

NONCLIENTMETRICS 構造体などの話

1.はじめに

 Windows 10 はウインドウの枠線の太さが細すぎて掴みにくいし、似たウインドウをたくさん開くと(たとえばメモ帳やコマンドプロンプトPowerShell など)、その境界が非常に分かりづらい。 しかも Windows 10 にはウインドウ枠の太さだけを変える設定 UI が用意されていない。

 ウインドウ枠の太さを変えるネット記事は検索でたくさん出てくるが、はっきり言って、2020年頃より前の記事の内容は、現在では全く役に立たない。

 かと言って Aero Lite に書き換えるワザは、確かにウインドウ枠が太くはなるが、タイトルバーやウインドウ右上の -□× 部分も大きくなってしまい、好みではない。

 そこでメトリクス値を取得して、必要なところだけを書き換える theme ファイルを作ってみようと思ったのだが、色々な事が有ったので記録の意味で書き残しておく。

 

2.MinGWgcc は使えなかった

 まずは、現在メトリクス取得のために、次のような C プログラムを書いてみた。

ShowNonclientMetrics.c

#include <stdio.h>
#include <winuser.h>
/* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa */
#define SPI_GETNONCLIENTMETRICS 0x0029

/* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics */
#define SM_CXBORDER 5
#define SM_CYBORDER 6

void getDecsFromMem( int *, size_t const, unsigned char const * );
void printDecs( int const * );
void filePrintDecs( int const * );
int copyDecs( int *, int, int const * );

int main( void ) {
    NONCLIENTMETRICSA nonclient_metrics = {};
    UINT  uiAction = SPI_GETNONCLIENTMETRICS;
    UINT  uiParam = sizeof( nonclient_metrics );
    PVOID pvParam = &nonclient_metrics;
    UINT  fWinIni = 0;
    int decs[ 1000 ] = {};
    int all_decs[ 1000 ] = {};
    int copy_start_pos = 0;

    int result;

    nonclient_metrics.cbSize = sizeof( nonclient_metrics );
    if ( SystemParametersInfoA( uiAction, uiParam, pvParam, fWinIni ) ) {
        printf( "SystemParametersInfoA() succeeded." );
        printf( "\n  .cbSize (UINT) =              %d /\t 0x%08x /\t", nonclient_metrics.cbSize, nonclient_metrics.cbSize );
        getDecsFromMem( decs, sizeof( UINT ), ( unsigned char * )&( nonclient_metrics.cbSize ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iBorderWidth (int) =         %d /\t 0x%08x /\t", nonclient_metrics.iBorderWidth, nonclient_metrics.iBorderWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iBorderWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iScrollWidth (int) =         %d /\t 0x%08x /\t", nonclient_metrics.iScrollWidth, nonclient_metrics.iScrollWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iScrollWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iScrollHeight (int) =        %d /\t 0x%08x /\t", nonclient_metrics.iScrollHeight, nonclient_metrics.iScrollHeight );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iScrollHeight ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iCaptionWidth (int) =        %d /\t 0x%08x /\t", nonclient_metrics.iCaptionWidth, nonclient_metrics.iCaptionWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iCaptionWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iCaptionHeight (int) =       %d /\t 0x%08x /\t", nonclient_metrics.iCaptionHeight, nonclient_metrics.iCaptionHeight );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iCaptionHeight ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .lfCaptionFont (LOGFONTA) =   %d /\t 0x%08x /\t", nonclient_metrics.lfCaptionFont, nonclient_metrics.lfCaptionFont );
        getDecsFromMem( decs, sizeof( LOGFONTA ), ( unsigned char * )&( nonclient_metrics.lfCaptionFont ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iSmCaptionWidth (int) =      %d /\t 0x%08x /\t", nonclient_metrics.iSmCaptionWidth, nonclient_metrics.iSmCaptionWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iSmCaptionWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iSmCaptionHeight (int) =     %d /\t 0x%08x /\t", nonclient_metrics.iSmCaptionHeight, nonclient_metrics.iSmCaptionHeight );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iSmCaptionHeight ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .lfSmCaptionFont (LOGFONTA) = %d /\t 0x%08x /\t", nonclient_metrics.lfSmCaptionFont, nonclient_metrics.lfSmCaptionFont );
        getDecsFromMem( decs, sizeof( LOGFONTA ), ( unsigned char * )&( nonclient_metrics.lfSmCaptionFont ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iMenuWidth (int) =           %d /\t 0x%08x /\t", nonclient_metrics.iMenuWidth, nonclient_metrics.iMenuWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iMenuWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iMenuHeight (int) =          %d /\t 0x%08x /\t", nonclient_metrics.iMenuHeight, nonclient_metrics.iMenuHeight );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iMenuHeight ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .lfMenuFont (LOGFONTA) =      %d /\t 0x%08x /\t", nonclient_metrics.lfMenuFont, nonclient_metrics.lfMenuFont );
        getDecsFromMem( decs, sizeof( LOGFONTA ), ( unsigned char * )&( nonclient_metrics.lfMenuFont ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .lfStatusFont (LOGFONTA) =    %d /\t 0x%08x /\t", nonclient_metrics.lfStatusFont, nonclient_metrics.lfStatusFont );
        getDecsFromMem( decs, sizeof( LOGFONTA ), ( unsigned char * )&( nonclient_metrics.lfStatusFont ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .lfMessageFont (LOGFONTA) =   %d /\t 0x%08x /\t", nonclient_metrics.lfMessageFont, nonclient_metrics.lfMessageFont );
        getDecsFromMem( decs, sizeof( LOGFONTA ), ( unsigned char * )&( nonclient_metrics.lfMessageFont ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\n  .iPaddedBorderWidth (int) =   %d /\t 0x%08x /\t", nonclient_metrics.iPaddedBorderWidth, nonclient_metrics.iPaddedBorderWidth );
        getDecsFromMem( decs, sizeof( int ), ( unsigned char * )&( nonclient_metrics.iPaddedBorderWidth ) );
        printDecs( decs );
        copy_start_pos = copyDecs( all_decs, copy_start_pos, decs );
        printf( "\nAll decimals =\n" );
        printDecs( all_decs );
        filePrintDecs( all_decs );
        printf( "\n" );
    }
    else {
        puts( "SystemParametersInfoA() failed." );
    }

    printf( "\n" );
    if ( result = GetSystemMetrics( SM_CXBORDER ) ) {
        printf( "GetSystemMetrics() succeeded." );
        printf( "\n  .SM_CXBORDER (int) =         %d /\t 0x%08x\n", result, result );
    }
    else {
        puts( "GetSystemMetrics() failed." );
    }

    printf( "\n" );
    if ( result = GetSystemMetrics( SM_CYBORDER ) ) {
        printf( "GetSystemMetrics() succeeded." );
        printf( "\n  .SM_CYBORDER (int) =         %d /\t 0x%08x\n", result, result );
    }
    else {
        puts( "GetSystemMetrics() failed." );
    }

    return 0;
}

void getDecsFromMem( int * o, size_t const sz, unsigned char const * s ) {
    size_t i;

    for ( i = 0; i < sz; i += 1 ) {
        *o = ( int )( *( s + i ) );
        o ++;
    }
    *o = -1; /* set sentinel */
}

void printDecs( int const * s ) {
    while ( *s >= 0 ) {  /* while sentinel */
        printf( "%d ", *s );
        s ++;
    }
}

void filePrintDecs( int const * s ) {
    FILE * f;

    if ( NULL != ( f = fopen( ".\\ShowNonclientMetrics.txt", "w" ) ) ) {
        while ( *s >= 0 ) {  /* while sentinel */
            fprintf( f, "%d ", *s );
            s ++;
        }
        fclose( f );
    }
}

int copyDecs( int * o, int pos, int const * s ) {
    while ( *s >= 0 ) {  /* while sentinel */
        o[ pos ] = *s;
        s ++;
        pos ++;
    }
    o[ pos ] = -1; /* set sentinel */
    return pos;
}

 ウチは WindowsC/C++ プログラムを書く際の第1選択が MinGWgcc なので、何の疑いも無くこれを書いたわけだが(厳しく見れば printf( ) の引数が型不一致だし、無駄な繰り返しもあるが、現在値を取得するという目的のためだけに書いたので大目に見てほしい)、iPaddedBorderWidth が無いというコンパイルエラーになってしまう。

 そう言われても、 https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nonclientmetricsa によれば iPaddedBorderWidth は有る。

 これはもしやウチの gcc が古いバージョンなのかと思い、MinGW Installer を起動してみたところ、はたして最新でない gcc コンポーネントがいくつかある。 さっそくアップデートして、再びコンパイルするも、やはり iPaddedBorderWidth が無いとエラーを吐かれる。

 仕方無く MinGW インストールフォルダー直下の include/winuser.h を直接見てみると……確かに無い、iPaddedBorderWidth が。

 つまりこの件に関して、MinGWgcc は使えない、という事である。

 

3.Visual Studio 2019 は使えた

 やむなく VS2019 を起動して空のプロジェクトを作り、先ほどのソースを取り込んでコンパイルエラーを取り除いた、次のような C プログラムをでっち上げた。

ShowNonclientMetrics.c (VS2019 版)

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <windows.h>
/* #include <winuser.h> */
/* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa */
#define SPI_GETNONCLIENTMETRICS 0x0029

/* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics */
#define SM_CXBORDER 5
#define SM_CYBORDER 6

void getDecsFromMem( int *, size_t const, unsigned char const * );
void printDecs( int const * );
void filePrintDecs( int const * );
int copyDecs( int *, int, int const * );

int main( void ) {
    NONCLIENTMETRICSA nonclient_metrics = { 0 };
    UINT  uiAction = SPI_GETNONCLIENTMETRICS;
    UINT  uiParam = sizeof( nonclient_metrics );
    PVOID pvParam = &nonclient_metrics;
    UINT  fWinIni = 0;
    int decs[ 1000 ] = { 0 };
    int all_decs[ 1000 ] = { 0 };
    int copy_start_pos = 0;

(以下同じ)

 これは警告が出るものの、期待通りに動作した。 やはり餅は餅屋で、Windows プログラムは Microsoft に限る、という事か。 VS2019 は何をするにもいちいち重くて、あまり好きではないのだが、今回ばかりは致し方ない。

 

4.NONCLIENTMETRICS の大きさは

 あとは上記プログラムが吐き出してくれた数字列を、theme ファイルの [Metrics] セクションの NonclientMetrics に書き込んで、ウインドウ枠の太さだけ書き換えれば良いであろう(参考 https://learn.microsoft.com/ja-jp/windows/win32/controls/themesfileformat-overview )。

 ……いや待て。 上記リンクで掲載されている NonclientMetrics には、数字が 340個しか書かれていないが、プログラムが吐き出した数字列は 344個あるではないか。

 もしかすると、MinGW と上記リンク記事は、双方とも iPaddedBorderWidth なんてものは無いという認識なのだろうか。

 

5.とにかく実行だ、Windows が壊れるわけではあるまい

 とにかく 344個のメトリクス値は得られたので、まずは先頭の 340個を theme ファイルに書き込んで実行してみる。 当然、iPaddedBorderWidth 部分が無いのだから、ウインドウ枠は変わらない。

 そこで次は 344個の数値を書き込んで試してみるが、なぜかウインドウ枠の太さは変わらない。 iPaddedBorderWidth 部分の、元の値は 4000 だったが、ヤケクソで 60000 とかに書き換えても変わってくれない。

 もしかすると iPaddedBorderWidth の読み取りを有効にするには、レジストリをいじらなければならないのか……などと思いつつ、値をいろいろ変えて試していると、なんとウインドウ枠の色が変わってしまった。 これじゃ話が違うではないか。

 

6.レジストリの書き換えはどうか

 ネット記事を見ていると、HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetricsPaddedBorderWidth を書き換えれば出来る、としているものが見られる。

 早速 regedit で見てみると、元々の値は -60 となっている。

 何かの記事で、これは -15 を1単位としていて、-60 は「4」を表す、というのを読んだ記憶があるので、0 にしたり -120 にしてみたり、いろいろ変えて再起動を繰り返してみたが……何も変わらない。

 

7.おわりに

 結局のところ、すべては徒労であった、という事か。

 それにつけても、デスクトップアイコンの「一覧」「詳細」を出来なくしたり、Microsoft の「昔ながらの UI デザインを制限しよう」という意図は、よく分からない……。