aardio 文档

aardio 范例: Windows 11 · 右键菜单风格

//RUNAS//Windows 11 右键菜单风格
//新版 Feature Flags 写入需要管理员权限

import win.ui;
import win.versionEx;
import win.reg;
import process;

if(!win.versionEx.isWin11Later){
    win.msgboxErr("此范例仅用于 Windows 11。");
    return;
}

// 按完整版本号判断,不能只比较 buildNumber,修订号也很重要。
var useFeatureFlags = win.versionEx.isGreaterOrEqual("10.0.26200.9807");
var featureFlags;
if(useFeatureFlags){
    import sys.featureFlags;
    featureFlags = sys.featureFlags;
}

// 这些 ID 控制 Windows 11 新版可自定义菜单,并非直接启用 Win10 经典菜单。
// 新系统切换经典菜单:先禁用新版路径,再设置原来的 CLSID 覆盖。
// Feature ID 属于 Windows 内部实现,后续系统更新可能改变其含义或效果。
var menuFeatures = [60813048,49453572,61934903];
var menuClsid = "{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}";
var classesPath = "HKCU\Software\Classes\CLSID";
var classicPath = classesPath ++ "\" ++ menuClsid;

// 只读查询,不创建注册表项。空字符串与“未设置默认值”不能混为一谈。
var readClassic = function(){
    var reg,err,code = win.regReaderWow64(classicPath ++ "\InprocServer32");
    if(!reg){
        if(code == 2 || code == 3) return false;
        return null,err;
    }
    var value = reg.queryValue("");
    reg.close();
    return value === "";
}

var writeClassic = function(enabled){
    if(enabled){
        var reg,err = win.regWow64(classicPath ++ "\InprocServer32");
        if(!reg) return null,err;
        var ok,err = reg.setSzValue("","");
        reg.close();
        return ok,err;
    }

    // 已不存在即为成功,避免恢复默认时无意创建空键。
    var existing,err,code = win.regWow64(classicPath,true);
    if(!existing){
        if(code == 2 || code == 3) return true;
        return null,err;
    }
    existing.close();
    var reg,err = win.regWow64(classesPath,true);
    if(!reg) return null,err;
    var ok,code = reg.delKeyTree(menuClsid);
    reg.close();
    if(!ok) return null,"删除经典菜单注册表项失败,错误码:" ++ tostring(code);
    return true;
}

// 这里只判断已保存的配置,不能据此断言正在运行的 Explorer 已经生效。
var classifyStyle = function(classic,features){
    if(!useFeatureFlags) return classic ? "classic" : "modern";
    var allDisabled,allEnabled,hasUserOverride = true,true,false;
    for(i=1;#menuFeatures){
        var info = features[i];
        if(info.error) return null,"Feature " ++ tostring(info.featureId) ++ ":" ++ info.error;
        if(info.enabledState != featureFlags.state.disabled) allDisabled = false;
        if(info.enabledState != featureFlags.state.enabled) allEnabled = false;
        if(info.priority >= featureFlags.priority.user) hasUserOverride = true;
    }
    if(classic && allDisabled) return "classic";
    if(!classic && (allEnabled || allDisabled)) return "modern";
    if(!classic && !hasUserOverride) return "system";
    return "mixed";
}

var readStyle = function(){
    var classic,err = readClassic();
    if(classic === null) return null,err;
    var features;
    if(useFeatureFlags) features = featureFlags.queryMany(menuFeatures);
    return classifyStyle(classic,features);
}

var applyStyle = function(mode){
    if(mode != "classic" && mode != "modern" && mode != "system")
        return null,"未知的菜单风格。";
    if(useFeatureFlags){
        // 查询失败不写入,避免在无法识别目标特性的系统上盲目修改。
        var features = featureFlags.queryMany(menuFeatures);
        for(i=1;#features){
            if(features[i].error) return null,features[i].error;
        }
        var ok,err;
        if(mode == "classic") ok,err = featureFlags.disable(menuFeatures);
        elseif(mode == "modern") ok,err = featureFlags.enable(menuFeatures);
        else ok,err = featureFlags.reset(menuFeatures); // 删除 User 覆盖,不等于强制启用
        if(!ok) return null,"Feature Flags 写入失败,请以管理员权限运行。" ++ '\r\n' ++ err;
    }

    var ok,err = writeClassic(mode == "classic");
    if(!ok){
        // Feature API 与注册表不是同一个事务:明确报告部分成功,不伪装成全部成功。
        if(useFeatureFlags) return null,"Feature Flags 已更新,但注册表操作失败。请重试或恢复系统默认。" ++ '\r\n' ++ err,true;
        return null,err;
    }
    ::Shell32.SHChangeNotify(0x8000000/*_SHCNE_ASSOCCHANGED*/,0,0,0);
    return true;
}
/*DSG{{*/
var winform = win.form(text="Windows 11 · 右键菜单风格";right=659;bottom=587;bgcolor=0xFAF7F4;border="dialog frame";font=LOGFONT(h=-13;name='Microsoft YaHei UI');max=false)
winform.add(
apply={cls="plus";text="应用所选风格";left=504;top=534;right=632;bottom=574;border={radius=8};notify=1;z=13};
classicCard={cls="plus";text="经典菜单";left=28;top=176;right=322;bottom=410;align="left";border={color=0xEBE2DA;radius=14;width=1};color=0x30251B;font=LOGFONT(h=-18;name='Microsoft YaHei UI';weight=600);notify=1;textPadding={left=20;top=18;right=48};valign="top";z=6};
current={cls="plus";text="正在读取配置…";left=28;top=427;right=632;bottom=453;align="left";color=0x30251B;font=LOGFONT(h=-14;name='Microsoft YaHei UI';weight=600);z=8};
intro={cls="plus";text="熟悉的经典菜单,或简洁的 Windows 11,由你选择。";left=28;top=97;right=632;bottom=123;align="left";color=0x857466;z=3};
modernCard={cls="plus";text="Windows 11";left=338;top=176;right=632;bottom=410;align="left";border={color=0xEBE2DA;radius=14;width=1};color=0x30251B;font=LOGFONT(h=-18;name='Microsoft YaHei UI';weight=600);notify=1;textPadding={left=20;top=18;right=48};valign="top";z=7};
refresh={cls="plus";text="刷新状态";left=550;top=130;right=632;bottom=160;border={radius=7};color=0xC06125;z=5};
reset={cls="plus";text="恢复系统默认";left=28;top=534;right=170;bottom=574;border={color=0xEBE2DA;radius=8;width=1};z=11};
restart={cls="plus";text="重启资源管理器";left=326;top=534;right=490;bottom=574;border={color=0xEBE2DA;radius=8;width=1};z=12};
section={cls="plus";text="WINDOWS 11  /  个性化";left=28;top=24;right=440;bottom=44;align="left";color=0xC06125;font=LOGFONT(name='Microsoft YaHei UI';weight=600);z=1};
separator={cls="bkplus";left=28;top=515;right=632;bottom=516;bgcolor=0xEBE2DA;z=10};
status={cls="plus";left=28;top=461;right=632;bottom=501;align="left";color=0x7B6959;font=LOGFONT(h=-13;name='Microsoft YaHei UI');valign="top";z=9};
systemInfo={cls="plus";left=28;top=132;right=542;bottom=157;align="left";color=0x7B6959;font=LOGFONT(h=-13;name='Microsoft YaHei UI');z=4};
title={cls="plus";text="右键菜单风格";left=26;top=51;right=632;bottom=91;align="left";color=0x30251B;font=LOGFONT(h=-28;name='Microsoft YaHei UI';weight=700);z=2}
)
/*}}*/

// 使用 plus 的状态皮肤;卡片预览是示意图,不读取或模拟实际系统菜单。
for i,card in [winform.classicCard,winform.modernCard]{
    card.skin({
        background={default=0xFFFFFFFF;hover=0xFFF8FAFD;active=0xFFF0F5FC};
        border={default={radius=14;width=1;color=0xFFDAE2EB};hover={radius=14;width=1;color=0xFF8AAED6}};
        checked={background={default=0xFFF2F7FF;hover=0xFFEDF4FF};border={default={radius=14;width=2;color=0xFF2563C0}}};
        cursor="hand"
    });
}

for i,button in [winform.reset,winform.restart,winform.refresh]{
    button.skin({background={default=0xFFFFFFFF;hover=0xFFEBF1FA;active=0xFFE1EAF6;disabled=0xFFF1F3F6};color={default=0xFF46566A;disabled=0xFF9BA7B6};cursor="hand"});
}

winform.apply.skin({background={default=0xFF2563C0;hover=0xFF3475D3;active=0xFF1D50A0;disabled=0xFFDFE6EF};color={default=0xFFFFFFFF;disabled=0xFF8C9CAF};cursor="hand"});

// 缓存绘图对象。自绘统一用逻辑坐标,避免高 DPI 下文字和菜单预览错位。
var drawing = {
    font=gdip.font("Microsoft YaHei UI",11.5);
    muted=gdip.solidBrush(0xFF59697B);
    ink=gdip.solidBrush(0xFF334155);
    stage=gdip.solidBrush(0xFFEAF0F7);
    paper=gdip.solidBrush(0xFFFFFFFF);
    highlight=gdip.solidBrush(0xFFE5EEFC);
    accent=gdip.solidBrush(0xFF2563C0);
    line=gdip.pen(0xFFD6DFEA,1);
    ring=gdip.pen(0xFF8A9BAF,1.3)
};

var drawPreview = function(graphics,rc){
    var modern = owner == winform.modernCard;
    var dpiX,dpiY = winform.dpiScale(1,1);
    var saved = graphics.save();

    graphics.scale(dpiX,dpiY);
    graphics.compositingQuality = 0;
    graphics.drawString(modern ? "简洁布局 · 新系统支持自定义" : "Windows 10 风格 · 完整命令直达",drawing.font,20,52,drawing.muted);
    graphics.drawCircle(drawing.ring,267,30,8);

    if(owner.checked) graphics.fillCircle(drawing.accent,267,30,5);
    graphics.fillRoundRect(drawing.stage,20,82,254,132,10);
    graphics.fillRoundRect(drawing.paper,43,92,208,112,modern ? 8 : 2);
    graphics.drawRoundRect(drawing.line,43,92,208,112,modern ? 8 : 2);

    if(modern){
        graphics.drawString("剪切      复制      删除",drawing.font,58,99,drawing.ink);
        graphics.drawLine(drawing.line,53,124,241,124);
        graphics.drawString("打开",drawing.font,59,131,drawing.ink);
        graphics.drawString("应用扩展                         ›",drawing.font,59,154,drawing.ink);
        graphics.drawString("自定义菜单",drawing.font,59,177,drawing.muted);
    }
    else{
        graphics.fillRect(drawing.highlight,47,95,200,20);
        for i,text in ["打开","剪切","复制","删除","属性"]{
            graphics.drawString(text,drawing.font,59,96+(i-1)*21,drawing.ink);
        }
    }

    graphics.restore(saved);
    return true;
}

winform.classicCard.onDrawForeground = drawPreview;
winform.modernCard.onDrawForeground = drawPreview;
winform.onDestroy = function(){
    for name,obj in drawing { obj.delete(); }
}

var selectedMode;
var names = {classic="经典菜单";modern="Windows 11";system="跟随 Windows 默认";mixed="混合配置,请重新选择"};
var selectStyle = function(mode){
    selectedMode = null;
    if(mode == "classic" || mode == "modern") selectedMode = mode;
    winform.classicCard.checked = mode == "classic";
    winform.modernCard.checked = mode == "modern";
    winform.apply.disabled = !selectedMode;
}

var setStatus = function(text,isError){
    winform.status.text = text;
    winform.status.color = isError ? 0xFFB34436 : 0xFF59697B;
}

var refreshState = function(){
    var mode,err = readStyle();
    selectStyle(mode);
    winform.current.text = "当前配置:" ++ (names[mode] : "读取失败");
    if(!mode) setStatus(err,true);
    else setStatus("选择卡片不会修改系统。新版开关影响整台电脑,注册表项仅影响当前账户。");
}

winform.systemInfo.text = win.versionEx.full() ++ (useFeatureFlags ? "  ·  Feature Flags + 注册表" : "  ·  注册表兼容模式");
winform.refresh.oncommand = refreshState;
winform.classicCard.oncommand = function(){
    selectStyle("classic");
    setStatus("已选择经典菜单。点击“应用所选风格”后保存,不会自动重启资源管理器。");
}
winform.modernCard.oncommand = function(){
    selectStyle("modern");
    setStatus("已选择 Windows 11。点击“应用所选风格”后保存,不会自动重启资源管理器。");
}

import win.ui.tooltip;
var balloonTipCtrl = win.ui.tooltip.tracking(winform,false);
var saveMode = function(mode){
    var message = "即将设置为:" ++ names[mode] ++ "。";
    if(useFeatureFlags) message ++= '\r\n新版 Feature 开关影响整台电脑;经典菜单注册表项仅影响当前账户。';
    message ++= '\r\n保存后需重启资源管理器;如果仍未生效,请注销或重启系统。继续?';
    if(!winform.msgboxTest(message,"保存菜单配置")) return;
    var ok,err,partial = applyStyle(mode);
    refreshState();
    if(ok) {
        setStatus("已保存。请先完成文件复制等操作,再点击“重启资源管理器”使配置生效。")

        var x,y,cx,cy = winform.restart.getPos(true); 
        balloonTipCtrl.setText('请先完成文件复制等操作。\n然后继续点击「重启资源管理器」。').trackPopup(true,x+120,y+cy );
    };
    else{
        setStatus(partial ? "部分设置已更新,请重试或恢复系统默认。" : "保存失败,请查看错误提示;当前配置已重新读取。",true);
        winform.msgboxErr(err,"未能完成设置");
    }
}

winform.apply.oncommand = function(){
    if(selectedMode) saveMode(selectedMode);
}
winform.reset.oncommand = function(){ saveMode("system"); }

// 只结束当前桌面的 Shell 进程,不按进程名杀掉其他登录会话的 Explorer。
// 桌面由 Windows 的 AutoRestartShell 以原用户身份重新启动,避免提权启动 Explorer。
var restartExplorer = function(){
    var shell = ::User32.GetShellWindow();
    if(!shell) return null,"未找到桌面进程,请在任务管理器中手动重启 Windows 资源管理器。";

    var pid = {INT value};
    ::User32.GetWindowThreadProcessId(shell,pid);

    var prcs,err = process(pid.value,0x100001/*_SYNCHRONIZE | _PROCESS_TERMINATE*/);
    if(!prcs) return null,err;

    var ok = ::Kernel32.TerminateProcess(prcs.handle,0);
    prcs.free();

    if(!ok) return null,"无法结束桌面进程,请在任务管理器中手动重启。";
    return pid.value;
}

winform.restart.oncommand = function(){
    if(!winform.msgboxTest("此操作会关闭当前桌面进程中的文件夹窗口,并短暂隐藏任务栏。" ++ '\r\n请先完成文件复制、移动等操作。现在重启?',"重启资源管理器")) return;
    winform.restart.disabledText = "正在重启…";
    var previousPid,err = restartExplorer();

    if(previousPid){
        var ready = winform.waitUntil(function(){
            var shell = ::User32.GetShellWindow();
            if(!shell) return false;
            var pid = {INT value};
            ::User32.GetWindowThreadProcessId(shell,pid);
            return pid.value != previousPid;
        },8000);

        if(ready) setStatus("资源管理器已重启。可在桌面或文件上单击右键查看实际效果。");
        else setStatus("桌面未自动恢复。请按 Ctrl+Shift+Esc,在任务管理器中运行 explorer.exe。",true);
    }
    else setStatus(err,true);
    winform.restart.disabledText = null;
}

refreshState();
winform.show();
win.loopMessage();

Markdown 格式