[Share Experiences] 【V23】微信 / 钉钉 Linux 版本快捷键显隐窗口 (2025-08-18 更新) Resolved
Tofloor
poster avatar
NoahLiu
deepin
2024-11-11 20:27
Author

更新脚本,支持窗口关闭之后,通过 dbus 唤出隐藏的微信窗口

let { exec } = require('child_process');
const { promisify } = require("util");
exec = promisify(exec);

async function run (cmdstr) {
  let { stdout } = await exec(cmdstr);
  let results = stdout.split('\n').filter(item => !!item);
  if (results.length === 0) {
    return null;
  } else if (results.length === 1) {
    return results[0];
  } else {
    return results;
  }
}

async function getWechatPID () {
  return await run(`pgrep -x "wechat" || pgrep -f "/usr/bin/wechat"`);
}

async function searchWindow () {
  return await run(`xdotool search -name "微信"`);
}

async function getWechatWindow (pid) {
  return await run(`xdotool search -pid ${pid}`);
}

async function activateWindow (pid) {
  await run(`dbus-send --session --type=method_call \
    --dest="org.kde.StatusNotifierItem-${pid}-1" \
    /StatusNotifierItem \
    org.kde.StatusNotifierItem.Activate int32:0 int32:0`);
}
async function deactivateWindow (id) {
  await run(`xdotool windowminimize ${id}`);
}

async function getActivateWindow () {
  return await run(`xdotool getactivewindow`);
}
async function main () {
  let pid = await getWechatPID();
  let activeId = await getActivateWindow();
  let ids = await searchWindow();
  let wechatIds = await getWechatWindow(pid);
  let hasActive = false;
  if (!Array.isArray(wechatIds)) {
    wechatIds = [wechatIds];
  }
  if (!Array.isArray(ids)) {
    ids = [ids];
  }

  ids.filter(id => {
    return wechatIds.includes(id);
  }).forEach(async (id) => {
    if (activeId === id) {
      hasActive = true;
      await deactivateWindow(id);
    }
  });

  if (!hasActive) {
    await activateWindow(pid);
  }
}

main();

补充一个钉钉的

let { exec } = require('child_process');
const { promisify } = require("util");
const { writeFileSync, readFileSync, existsSync } = require('fs');
const { join } = require('path');
const { error, log } = require('console');

exec = promisify(exec);

const tmpPath = join("/tmp", 'dingtalkpid');

async function run (cmdstr) {
  let { stdout } = await exec(cmdstr);
  let results = stdout.split('\n').filter(item => !!item);
  if (results.length === 0) {
    return null;
  } else {
    return results;
  }
}

async function getpidAndService () {
  if (existsSync(tmpPath)) {
    let pid = readFileSync(tmpPath, 'utf8');
    if (pid) {
      let hasPid = false;
      try {
        process.kill(pid, 0); // 发送信号 0,仅用于检查
        hasPid = true;
      } catch (err) {
        error(err);
      }
      if (hasPid) {
        return pid;
      }
    }
  }
  let pids = await run(`pgrep -f com.alibabainc.dingtalk`);
  let dbus = await run(`dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames`);
  dbus = dbus.filter(item => item.indexOf('org.kde.StatusNotifierItem-') > -1).map(item => {
    item = item.replace('      string ', '').replace('"', '').replace('"', '');
    return item;
  });
  let pid = pids.find(item => {
    return dbus.includes(`org.kde.StatusNotifierItem-${item}-1`);
  });
  if (!pid) {
    return null;
  }
  writeFileSync(tmpPath, JSON.stringify(pid));
  return pid;
}

async function searchWindow () {
  return await run(`xdotool search -name "钉钉"`);
}

async function getDingtalkWindow (pid) {
  return await run(`xdotool search -pid ${pid}`);
}

async function activateWindow (pid) {
  await run(`dbus-send --session --type=method_call \
    --dest="org.kde.StatusNotifierItem-${pid}-1" \
    /StatusNotifierItem \
    org.kde.StatusNotifierItem.Activate int32:0 int32:0`);

  await run(`dbus-send --session --type=method_call \
    --dest="org.kde.StatusNotifierItem-${pid}-1" \
    /StatusNotifierItem \
    org.kde.StatusNotifierItem.Activate int32:0 int32:0`);
}
async function deactivateWindow (id) {
  await run(`xdotool windowminimize ${id}`);
}

async function getActivateWindow () {
  let id = await run(`xdotool getactivewindow`);
  if (Array.isArray(id) && id.length === 1) {
    return id[0];
  } else {
    id;
  }
}
async function main () {
  let pid = await getpidAndService();
  if (pid !== null) {
    let activeId = await getActivateWindow();
    let ids = await searchWindow();
    let dingtalkIds = await getDingtalkWindow(pid);
    let hasActive = false;
    ids.filter(id => {
      return dingtalkIds.includes(id);
    }).forEach(async (id) => {
      if (activeId === id) {
        hasActive = true;
        await deactivateWindow(id);
      }
    });
    if (!hasActive) {
      await activateWindow(pid);
    }
  }
}

main();

假设你的 node 有一个 全局的快捷连接 在 /usr/bin/node 这个位置,上面的js 文件在 /home/user/window.js

/usr/bin/node ~/wechat.js

现阶段,QQ 、微信、钉钉都有原生linux版本了,只不过他们对快捷键的支持堪忧,所以用下面的方法,配合 deepin 窗口管理器最小化窗口的快捷键提升使用体验。

经验主体

本条经验基于 xdotool ,原理是使用 xdotool 根据窗口名称查找到对应 id 并根据 id 激活窗口。

xdotool 很强大,我刚刚接触,还有很多不懂,有兴趣的小伙伴可以自行学习探索

sudo apt install xdotool

根据窗口管理器上的窗口名称来搜索窗口ID

image.png

进入控制中心,点击下面的 + 添加新的快捷键
image.png

其中的 命令如下 字符串里面的名称来自于窗口管理器预览图上的名称,最新的 4.0 测试版 名称是 '微信(测试版)'

xdotool windowactivate $(xdotool search -name '微信(测试版)')

不足之处

因为是基于活动窗口查找的,当窗口被关闭 但是软件打开缩回托盘区的时候无解~

原理(更新)

基于进程查找窗口,跟激活的窗口对比,如果是微信窗口激活,就隐藏(最小化),如果激活窗口不是微信的窗口就通过 Dbus 通知微信激活窗口。

Reply Favorite View the author
All Replies
2 / 2
To page
NoahLiu
deepin
2025-03-14 13:34
#21
Oaklight
~$ xdotool windowactivate $(xdotool search -name '微信(测试版)')
XGetWindowProperty[_NET_WM_DESKTOP] failed (code=1)
~$ xdotool search --name '微信(测试版)' windowactivate
XGetWindowProperty[_NET_WM_DESKTOP] failed (code=1)

实际的窗口名称请鼠标悬浮窗口查看哈

Reply View the author
176******28
deepin
2025-03-14 22:40
#22
Reply View the author
2 / 2
To page