aardio 文档

aardio 范例: 通过 adb 实现安卓手机投屏

//aardio 通过 adb 实现安卓手机投屏
import fonts.fontAwesome;
import win.ui.simpleWindow;
import process.adb;
import wsock.udp.mdnsClient;
import win.ui;
/*DSG{{*/
var winform = win.form(text="安卓投屏助手";right=920;bottom=620;bgcolor=0xFFFFFF;border="none")
winform.add(
bkActionsTitle={cls="bkplus";text='\uF0AD  快捷操作';left=15;top=250;right=265;bottom=275;align="left";color=0x333333;dl=1;dt=1;font=LOGFONT(h=-14;name='FontAwesome';weight=600);z=15};
bkCodePanel={cls="bkplus";left=280;top=46;right=921;bottom=621;bgcolor=0xFFFFFF;db=1;dl=1;dr=1;dt=1;z=26};
bkCodeTitle={cls="bkplus";text='\uF121  自动化代码';left=295;top=60;right=500;bottom=85;align="left";color=0x333333;dl=1;dr=1;dt=1;font=LOGFONT(h=-14;name='FontAwesome';weight=600);z=27};
bkDevicePanel={cls="bkplus";left=0;top=46;right=280;bottom=621;bgcolor=0xF5F5F5;db=1;dl=1;dt=1;z=4};
bkDeviceTitle={cls="bkplus";text='\uF0C9  设备信息';left=15;top=60;right=265;bottom=85;align="left";color=0x333333;dl=1;dt=1;font=LOGFONT(h=-14;name='FontAwesome';weight=600);z=5};
bkHeader={cls="bkplus";left=0;top=0;right=921;bottom=46;bgcolor=0x0078D4;dl=1;dr=1;dt=1;z=1};
bkIpLabel={cls="bkplus";text="IP 地址:";left=15;top=210;right=90;bottom=230;align="left";color=0x666666;dl=1;dt=1;z=13};
bkModelLabel={cls="bkplus";text="设备型号:";left=15;top=135;right=90;bottom=155;align="left";color=0x666666;dl=1;dt=1;z=7};
bkRecordTitle={cls="bkplus";text='\uF111  操作录制';left=15;top=425;right=265;bottom=450;align="left";color=0x333333;dl=1;dt=1;font=LOGFONT(h=-14;name='FontAwesome';weight=600);z=23};
bkScreenLabel={cls="bkplus";text="屏幕分辨率:";left=15;top=185;right=90;bottom=205;align="left";color=0x666666;dl=1;dt=1;z=11};
bkTitle={cls="bkplus";text="安卓投屏助手";left=45;top=10;right=300;bottom=36;align="left";color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-18;weight=600);z=2};
bkTitleIcon={cls="bkplus";text='\uF10B';left=12;top=8;right=42;bottom=40;color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-24;name='FontAwesome');z=3};
bkVersionLabel={cls="bkplus";text="安卓版本:";left=15;top=160;right=90;bottom=180;align="left";color=0x666666;dl=1;dt=1;z=9};
editCode={cls="edit";left=295;top=95;right=900;bottom=600;db=1;dl=1;dr=1;dt=1;edge=1;font=LOGFONT(h=-13;name='Consolas');hscroll=1;multiline=1;vscroll=1;z=30};
lbIpValue={cls="static";text="--";left=95;top=210;right=265;bottom=230;color=0x333333;dl=1;dt=1;z=14};
lbModelValue={cls="static";text="--";left=95;top=135;right=265;bottom=155;color=0x333333;dl=1;dt=1;z=8};
lbScreenValue={cls="static";text="--";left=95;top=185;right=265;bottom=205;color=0x333333;dl=1;dt=1;z=12};
lbVersionValue={cls="static";text="--";left=95;top=160;right=265;bottom=180;color=0x333333;dl=1;dt=1;z=10};
plusBack={cls="plus";text="返回";left=100;top=375;right=175;bottom=405;bgcolor=0xE0E0E0;color=0x333333;dl=1;dt=1;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF060';notify=1;textPadding={left=24};z=21};
plusClear={cls="plus";text="清空";left=735;top=58;right=795;bottom=83;color=0x666666;dr=1;dt=1;iconStyle={align="left";font=LOGFONT(name='FontAwesome');padding={left=5}};iconText='\uF1F8';notify=1;textPadding={left=20};z=28};
plusConnect={cls="plus";text="连接设备";left=15;top=285;right=135;bottom=320;bgcolor=0x0078D4;color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF0C1';notify=1;textPadding={left=32};z=16};
plusDisconnect={cls="plus";text="断开连接";left=145;top=285;right=265;bottom=320;bgcolor=0xE0E0E0;color=0x333333;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF127';notify=1;textPadding={left=32};z=17};
plusHome={cls="plus";text="主页";left=15;top=375;right=90;bottom=405;bgcolor=0xE0E0E0;color=0x333333;dl=1;dt=1;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF015';notify=1;textPadding={left=24};z=20};
plusMenu={cls="plus";text="菜单";left=185;top=375;right=265;bottom=405;bgcolor=0xE0E0E0;color=0x333333;dl=1;dt=1;iconStyle={align="left";font=LOGFONT(h=-13;name='FontAwesome');padding={left=8}};iconText='\uF0C9';notify=1;textPadding={left=24};z=22};
plusPower={cls="plus";text='\uF011';left=15;top=540;right=55;bottom=570;bgcolor=0xE0E0E0;color=0x333333;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=34};
plusRecent={cls="plus";text='\uF24D';left=60;top=540;right=100;bottom=570;bgcolor=0xE0E0E0;color=0x333333;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=35};
plusRotate={cls="plus";text='\uF021';left=105;top=540;right=145;bottom=570;bgcolor=0xE0E0E0;color=0x333333;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=36};
plusRunCode={cls="plus";text="运行";left=805;top=58;right=900;bottom=83;bgcolor=0x28A745;color=0xFFFFFF;dr=1;dt=1;iconStyle={align="left";font=LOGFONT(name='FontAwesome');padding={left=8}};iconText='\uF04B';notify=1;textPadding={left=24};z=29};
plusSave={cls="plus";text="保存";left=665;top=58;right=725;bottom=83;color=0x666666;dr=1;dt=1;iconStyle={align="left";font=LOGFONT(name='FontAwesome');padding={left=5}};iconText='\uF0C7';notify=1;textPadding={left=20};z=31};
plusScreen={cls="plus";text="投屏";left=15;top=330;right=135;bottom=365;bgcolor=0x28A745;color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF108';notify=1;textPadding={left=32};z=18};
plusScreenshot={cls="plus";text="截图";left=145;top=330;right=265;bottom=365;bgcolor=0xFFC107;color=0x333333;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF030';notify=1;textPadding={left=32};z=19};
plusStartRecord={cls="plus";text="开始录制";left=15;top=460;right=135;bottom=495;bgcolor=0xDC3545;color=0xFFFFFF;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF111';notify=1;textPadding={left=32};z=24};
plusStatus={cls="plus";text="未连接";left=15;top=95;right=265;bottom=125;align="left";color=0xFF6B6B;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-16;name='FontAwesome');padding={left=8}};iconText='\uF057';z=6};
plusStopRecord={cls="plus";text="停止录制";left=145;top=460;right=265;bottom=495;bgcolor=0xE0E0E0;color=0x333333;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={align="left";font=LOGFONT(h=-14;name='FontAwesome');padding={left=12}};iconText='\uF04D';notify=1;textPadding={left=32};z=25};
plusVolDown={cls="plus";text='\uF027';left=185;top=540;right=225;bottom=570;bgcolor=0xE0E0E0;color=0x333333;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=32};
plusVolUp={cls="plus";text='\uF028';left=230;top=540;right=270;bottom=570;bgcolor=0xE0E0E0;color=0x333333;db=1;dl=1;font=LOGFONT(h=-16;name='FontAwesome');notify=1;z=33}
)
/*}}*/

// 应用按钮样式
var applyButtonStyles = function(){
    winform.plusConnect.skin({
        background={default=0xFF0078D4;hover=0xFF106EBE;active=0xFF005A9E;disabled=0xFFCCCCCC};
        color={default=0xFFFFFFFF}
    });

    winform.plusDisconnect.skin({
        background={default=0xFFE0E0E0;hover=0xFFD0D0D0;active=0xFFC0C0C0;disabled=0xFFE8E8E8};
        color={default=0xFF333333;disabled=0xFFAAAAAA}
    });

    winform.plusScreen.skin({
        background={default=0xFF28A745;hover=0xFF218838;active=0xFF1E7E34;disabled=0xFFCCCCCC};
        color={default=0xFFFFFFFF}
    });

    winform.plusScreenshot.skin({
        background={default=0xFFFFC107;hover=0xFFE0A800;active=0xFFD39E00;disabled=0xFFCCCCCC};
        color={default=0xFF333333}
    });

    var grayBtnSkin = {
        background={default=0xFFE0E0E0;hover=0xFFD0D0D0;active=0xFFC0C0C0;disabled=0xFFE8E8E8};
        color={default=0xFF333333;disabled=0xFFAAAAAA}
    };

    winform.plusHome.skin(grayBtnSkin);
    winform.plusBack.skin(grayBtnSkin);
    winform.plusMenu.skin(grayBtnSkin);
    winform.plusVolUp.skin(grayBtnSkin);
    winform.plusVolDown.skin(grayBtnSkin);
    winform.plusPower.skin(grayBtnSkin);
    winform.plusRecent.skin(grayBtnSkin);
    winform.plusRotate.skin(grayBtnSkin);
    winform.plusStopRecord.skin(grayBtnSkin);

    winform.plusStartRecord.skin({
        background={default=0xFFDC3545;hover=0xFFC82333;active=0xFFBD2130;disabled=0xFFCCCCCC};
        color={default=0xFFFFFFFF}
    });

    winform.plusClear.skin({
        color={default=0xFF666666;hover=0xFFDC3545;active=0xFFC82333}
    });

    winform.plusSave.skin({
        color={default=0xFF666666;hover=0xFF0078D4;active=0xFF005A9E}
    });

    winform.plusRunCode.skin({
        background={default=0xFF28A745;hover=0xFF218838;active=0xFF1E7E34};
        color={default=0xFFFFFFFF}
    });
};
applyButtonStyles();

// 添加无边框窗口支持
win.ui.simpleWindow(winform);

// 状态变量
var isConnected = false;
var recordProcess = null;
var maxPosX, maxPosY;
var cxScreen, cyScreen;

// 自定义键盘映射
var KEYMAP = {
    down = { 
        ["ESC"] = "keyBack";
        ["HOME"] = "keyHome";
    }
};

// 更新连接状态
var updateStatus = function(connected){
    isConnected = connected;

    var btns = {
        winform.plusDisconnect;
        winform.plusScreen;
        winform.plusScreenshot;
        winform.plusHome;
        winform.plusBack;
        winform.plusMenu;
        winform.plusVolUp;
        winform.plusVolDown;
        winform.plusPower;
        winform.plusRecent;
        winform.plusRotate;
        winform.plusStartRecord;
    };

    if(connected){
        winform.plusStatus.iconText = '\uF058';
        winform.plusStatus.iconColor = 0xFF28A745;
        winform.plusStatus.argbColor = 0xFF28A745;
        winform.plusStatus.text = "已连接";

        for i, btn in btns {
            btn.disabled = false;
        }
    } else {
        winform.plusStatus.iconText = '\uF057';
        winform.plusStatus.iconColor = 0xFFFF6B6B;
        winform.plusStatus.argbColor = 0xFFFF6B6B;
        winform.plusStatus.text = "未连接";

        winform.lbModelValue.text = "--";
        winform.lbVersionValue.text = "--";
        winform.lbScreenValue.text = "--";
        winform.lbIpValue.text = "--";

        for i, btn in btns {
            btn.disabled = true;
        }
    }
};

// 获取设备信息
var refreshDeviceInfo = function(){
    if(!process.adb.isConnected()) return;

    var model = process.adb.getProp("ro.product.model");
    if(model) winform.lbModelValue.text = model;

    var version = process.adb.version(true);
    if(version) winform.lbVersionValue.text = "Android " + version;

    cxScreen, cyScreen = process.adb.getScreen();
    if(cxScreen && cyScreen){
        winform.lbScreenValue.text = cxScreen + " × " + cyScreen;
    }

    var ip = process.adb.ip();
    if(ip) winform.lbIpValue.text = ip;

    maxPosX, maxPosY = process.adb.getMaxPos();
    return #model || #version || #ip;
};

// 连接设备
winform.plusConnect.oncommand = function(){
    owner.disabledText = {'\uF110'};

    import process.adb.connecter;
    if(process.adb.connecter(winform)){
        updateStatus(true);
        refreshDeviceInfo();
        winform.editCode.print("// 设备连接成功: " + (winform.lbModelValue.text : "未知设备"));
    } else {
        updateStatus(false);
    }

    owner.disabledText = null;
};

// 断开连接
winform.plusDisconnect.oncommand = function(){
    process.adb.disconnect();
    updateStatus(false);
    winform.editCode.print("// 已断开设备连接");
};

// 投屏
winform.plusScreen.oncommand = function(){
    if(!isConnected){
        winform.msgboxErr("请先连接设备");
        return;
    }

    owner.disabledText = {'\uF110'};

    import process.scrcpy;
    var scrcpy = process.scrcpy();

    scrcpy.arguments = {
        powerOffOnClose = true;
        "--always-on-top";
        "--stay-awake";
        "--window-title": "投屏 - " + (winform.lbModelValue.text : "设备");
    };

    var prcs, err = scrcpy.open();
    if(prcs){
        winform.editCode.print("// 投屏已启动");

        // 启动键盘钩子
        import key.hook;
        if(!winform.keyHook){
            winform.keyHook = key.hook();
            winform.keyHook.mapKey(KEYMAP, process.adb, scrcpy.hwnd);
        }
    } else {
        winform.editCode.print("// 投屏启动失败: " + (err : "未知错误"));
    }

    owner.disabledText = null;
};

// 截图
winform.plusScreenshot.oncommand = function(){
    if(!isConnected) return;

    owner.disabledText = {'\uF110'};

    import fsys.dlg;
    var path = fsys.dlg.save("*.png|*.png", "screenshot" + tostring(time(), "_%Y%m%d_%H%M%S"));
    if(path){
        var ok, err = process.adb.screenCap(path);
        if(ok){
            winform.editCode.print('// 截图已保存: ' + path);
            raw.execute(path);
        } else {
            winform.editCode.print('// 截图失败: ' + (err : "未知错误"));
        }
    }

    owner.disabledText = null;
};

// 快捷按键
winform.plusHome.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyHome();
    winform.editCode.print('process.adb.keyHome()');
};

winform.plusBack.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyBack();
    winform.editCode.print('process.adb.keyBack()');
};

winform.plusMenu.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyMenu();
    winform.editCode.print('process.adb.keyMenu()');
};

winform.plusVolUp.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyVolumeUp();
    winform.editCode.print('process.adb.keyVolumeUp()');
};

winform.plusVolDown.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyVolumeDown();
    winform.editCode.print('process.adb.keyVolumeDown()');
};

winform.plusPower.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyPower();
    winform.editCode.print('process.adb.keyPower()');
};

winform.plusRecent.oncommand = function(){
    if(!isConnected) return;
    process.adb.keyAppSwitch();
    winform.editCode.print('process.adb.keyAppSwitch()');
};

winform.plusRotate.oncommand = function(){
    if(!isConnected) return;
    // 切换屏幕方向
    var orientation = process.adb.shellGet("settings get system accelerometer_rotation");
    if(string.trim(orientation) == "1"){
        process.adb.shellGet("settings put system accelerometer_rotation 0");
        winform.editCode.print('// 已锁定屏幕方向');
    } else {
        process.adb.shellGet("settings put system accelerometer_rotation 1");
        winform.editCode.print('// 已开启自动旋转');
    }
};



// 开始录制
var recordingThreadHandle;
winform.plusStartRecord.oncommand = function(){
    if(!isConnected){
        winform.msgboxErr("请先连接设备");
        return;
    }

    if(recordingThreadHandle) return;
    if(!maxPosX || !maxPosY){
        winform.msgboxErr("无法获取设备触摸坐标信息");
        return;
    }

    owner.disabled = true;
    winform.plusStopRecord.disabled = false;

    winform.editCode.print("");
    winform.editCode.print("// ========== 开始录制操作 ==========");
    winform.editCode.print("import process.adb;");
    winform.editCode.print("");

    recordingThreadHandle = thread.create(
        function(winform, maxPosX, maxPosY, cxScreen, cyScreen){
            var xPos, yPos;
            var lastTapTime = 0;

            import process.adb;
            recordProcess = process.adb.getEvent("-l");

            for device, eventType, eventCode, value in recordProcess.lines("([^:]+)\:\s+(\S+)\s+(\S+)\s+(\S+)", true){
                if(eventType == "EV_SYN"){
                    if(eventCode == "SYN_REPORT" && !tonumber(value, 16)){
                        if(xPos !== null && yPos !== null){
                            var now = time.tick();
                            if(now - lastTapTime > 100){
                                winform.editCode.printf("process.adb.tap(%d, %d)", xPos, yPos);
                                lastTapTime = now;
                            }
                            xPos = null;
                            yPos = null;
                        }
                    }
                }
                elseif(eventType == "EV_ABS"){
                    if(eventCode == "ABS_MT_POSITION_X"){
                        xPos = math.round((tonumber(value, 16) / maxPosX) * cxScreen);
                    }
                    elseif(eventCode == "ABS_MT_POSITION_Y"){
                        yPos = math.round((tonumber(value, 16) / maxPosY) * cyScreen);
                    }
                }
                elseif(eventType == "EV_KEY"){
                    if(..string.startsWith(eventCode, "KEY_", true)){
                        if(value === "DOWN"){
                            winform.editCode.printf('process.adb.key("%s")', eventCode);
                        }
                    }
                }
            }
        }, winform, maxPosX, maxPosY, cxScreen, cyScreen
    );
};

// 停止录制
winform.plusStopRecord.oncommand = function(){
    if(!recordingThreadHandle) return;

    if(recordingThreadHandle){
        //有时候做事件真不必那么完美,这样多省事
        thread.terminate(recordingThreadHandle,0)
        recordingThreadHandle = null;
    }

    winform.plusStartRecord.disabled = false;
    owner.disabled = true;

    winform.editCode.print("");
    winform.editCode.print("// ========== 录制结束 ==========");
};

// 清空代码
winform.plusClear.oncommand = function(){
    winform.editCode.text = "";
};

// 保存代码
winform.plusSave.oncommand = function(){
    import fsys.dlg;
    var path = fsys.dlg.save("*.aardio|*.aardio", "automation" + tostring(time(), "_%Y%m%d_%H%M%S"));
    if(path){
        string.save(path, winform.editCode.text);
        winform.msgbox("代码已保存到:\n" + path, "保存成功");
    }
};

// 运行代码
winform.plusRunCode.oncommand = function(){
    if(!isConnected){
        winform.msgboxErr("请先连接设备");
        return;
    }

    var code = winform.editCode.text;
    if(!#string.trim(code)){
        winform.msgboxErr("代码为空");
        return;
    }

    owner.disabledText = {'\uF110'};

    var func, err = loadcode(code);
    if(func){
        var ok, err = call(func);
        if(!ok){
            winform.editCode.print("// 运行出错: " + err);
        } else {
            winform.editCode.print("// 运行完成");
        }
    } else {
        winform.editCode.print("// 代码错误: " + err);
    }

    owner.disabledText = null;
};

// 初始状态
updateStatus(false);
winform.plusStopRecord.disabled = true;

// 检查是否已连接
if(process.adb.isConnected()){
    updateStatus(true);
    refreshDeviceInfo();
    winform.editCode.print("// 检测到已连接的设备");
    winform.editCode.print("// 设备: " + (winform.lbModelValue.text : "未知"));
}

var mdns = wsock.udp.mdnsClient(); 
mdns.onServiceResolved = function(service) {

    if( service.serviceType == "_adb-tls-connect._tcp.local"){
        if(!isConnected  && !process.adb.isConnected()){
            var result, err = ..process.adb.connect(service.ip + ":" + service.port);
            if(result){
                updateStatus(true);
                refreshDeviceInfo();
                winform.editCode.print("// 设备连接成功: " + (winform.lbModelValue.text : "未知设备"));
            } 
        }
    }
}

mdns.discover("_adb-tls-connect._tcp"); 

// 定时检测设备连接状态
winform.setInterval(
    function(){
        if(isConnected){
            // 轻量级检测:执行一个简单命令
            if(!process.adb.isConnected()){
                updateStatus(false);
                winform.editCode.print("// ⚠ 设备已断开连接");

                // 重新启动 mDNS 发现,等待自动重连
                mdns.discover("_adb-tls-connect._tcp"); 
            }
        }
    }, 3000  // 每3秒检查一次,可根据需要调整
);

// 窗口关闭时清理
winform.onDestroy = function(){ 

    mdns.close();

    if(winform.keyHook){
        winform.keyHook.close();
    }
};

winform.show();
win.loopMessage();
Markdown 格式