pencil 於 wsl2 中自動對應 Windows 11 的縮放設定

最近開始學習如何使用 Pencil 與 AI 互動進行畫面的設計,但我開發環境喜歡在 WSL2,不過我真的很不喜歡在 VS CODE 中使用 Pencil,所以我是用自行安裝 appImage 的方式,直接跑起 WSL2 的 GUI。

有個比較大的問題是,WSL2 的下的 pencil 不會跟著 Windows 11 的縮放比例進行調整,經查詢 pencil 是基於 electron 技術開發的,而 electron 又是 chromium ,所以調整甚麼環境變數沒有用,經過跟 AI 查詢後,electron 的應用可以透過 --force-device-scale-factor=1.25 這樣的方式調整縮放比為 125%,經過實驗確定是可以的。

可是還有一個問題,我其實是使用筆電,外出時是使用筆電的螢幕,縮放比是 150%,而在家則是外接螢幕,縮放比是 125%,但 WSL2 預設會直接用實際解析度,字都超小那怎麼辦呢 ?

在 Github 這則討論 Set dpi of wsl2 windows independently of screen resolution 有一位 A-Ribeiro 有撰寫了一個 sh , 可以抓取 Windows 11 的縮放比的方式自動設定環境變數,但我先前說過環境變數設定無效,但抓縮放比的方式卻能拿來用,於是又請了 AI 大神直接改造,做到執行 pencil 時,自動抓取 Windows 11 的設定,達成外出在家都得到一致性的縮放比例。

以下是原始碼

#!/bin/bash

# ──────────────────────────────────────────────
# 透過 PowerShell 讀取 Windows 主螢幕 DPI scale
# ──────────────────────────────────────────────
powershell_script='
Add-Type @"
using System.Runtime.InteropServices;
using System;
using System.Globalization;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DEVMODEA
{
    private const int CCHDEVICENAME = 32;
    private const int CCHFORMNAME = 32;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
    public string dmDeviceName;
    public short dmSpecVersion;
    public short dmDriverVersion;
    public short dmSize;
    public short dmDriverExtra;
    public int dmFields;
    public int dmPositionX;
    public int dmPositionY;
    public int dmDisplayOrientation;
    public int dmDisplayFixedOutput;
    public short dmColor;
    public short dmDuplex;
    public short dmYResolution;
    public short dmTTOption;
    public short dmCollate;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
    public string dmFormName;
    public short dmLogPixels;
    public int dmBitsPerPel;
    public int dmPelsWidth;
    public int dmPelsHeight;
    public int dmDisplayFlags;
    public int dmDisplayFrequency;
    public int dmICMMethod;
    public int dmICMIntent;
    public int dmMediaType;
    public int dmDitherType;
    public int dmReserved1;
    public int dmReserved2;
    public int dmPanningWidth;
    public int dmPanningHeight;
}

public class DCOps {
    [DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hwnd);
    [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
    [DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
    public const int LOGPIXELSX = 88;
    public const int LOGPIXELSY = 90;

    [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
    [DllImport("shcore.dll")] static extern int GetProcessDpiAwareness(IntPtr hprocess, out int lpdpiAwareness);
    [DllImport("shcore.dll")] static extern int SetProcessDpiAwareness(int value);
    public const int PROCESS_DPI_UNAWARE = 0;
    public const int PROCESS_SYSTEM_DPI_AWARE = 1;
    public const int PROCESS_PER_MONITOR_DPI_AWARE = 2;

    [DllImport("Shcore.dll")]
    static extern int GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY);
    public const uint MDT_EFFECTIVE_DPI = 0;

    [DllImport("user32.dll")]
    static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
    public const uint MONITOR_DEFAULTTOPRIMARY = 1;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct RECT { public int left, top, right, bottom; }

    [DllImport("user32.dll")] static extern IntPtr GetDesktopWindow();

    public static void run() {
        SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
        var hwnd = GetDesktopWindow();
        var hdc = GetDC(hwnd);
        if (hdc == IntPtr.Zero) { Console.WriteLine("1.25"); return; }

        var hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
        if (hmonitor == IntPtr.Zero) { ReleaseDC(hwnd, hdc); Console.WriteLine("1.25"); return; }

        double scaleFactor = 1.0;

        int processDpiAwareness;
        if (GetProcessDpiAwareness(GetCurrentProcess(), out processDpiAwareness) >= 0) {
            if (processDpiAwareness == PROCESS_PER_MONITOR_DPI_AWARE) {
                uint dpiX, dpiY;
                if (GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, out dpiX, out dpiY) >= 0) {
                    scaleFactor = Math.Round(((double)(dpiX + dpiY) * 0.5) / 96.0, 2);
                }
            } else {
                int dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
                int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
                scaleFactor = Math.Round(((double)(dpiX + dpiY) * 0.5) / 96.0, 2);
            }
        }

        ReleaseDC(hwnd, hdc);
        Console.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0}", scaleFactor));
    }
}
"@
[DCOps]::run()
'

# ──────────────────────────────────────────────
# 取得 Windows DPI scale factor
# ──────────────────────────────────────────────
SCALE=$("/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" \
    -NoProfile -NonInteractive -Command "$powershell_script" 2>/dev/null \
    | tr -d '\r\n')

# 若偵測失敗,fallback 到 1.0
if [[ -z "$SCALE" || "$SCALE" == "0" ]]; then
    SCALE="1.0"
    echo "[pencil] DPI 偵測失敗,使用預設值 ${SCALE}"
else
    echo "[pencil] 偵測到 Windows DPI scale: ${SCALE}"
fi

# ──────────────────────────────────────────────
# 啟動 Pencil(Electron app 使用 Chromium flag)
# ──────────────────────────────────────────────
exec ~/Applications/Pencil-linux-x86_64.AppImage \
    --force-device-scale-factor="${SCALE}" \
    "$@"

這段原始碼是符合我的環境如下:

  1. 我將 pencil 放置於 ~/Applications/Pencil-linux-x86_64.AppImage , 權限必須有 +x
  2. 我將 bash 存放於 ~/.local/bin/pencil , 權限必須有 +x

使用時就是直接執行 pencil 就可以了,以下對照圖左邊是 Windows 11 , 右邊是 WSL2,文字與 UI 大小完全一樣。

發佈留言