mirror of
https://github.com/linuxdeepin/dde-dock.git
synced 2025-05-30 22:01:41 +00:00

1. taskmanager used to identify which entry should map to window in x11 environmrnt, listen to xevent in anohter thread, and handle those event when window create, destory, changed. use some way to identify which entry(desktopfile) should mapped to changed window. in wayland, connected plsamawindow signal(window created destoried. 2. use taskmanager instead of dbus in old dock code log: as title
471 lines
11 KiB
C++
471 lines
11 KiB
C++
// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
|
||
//
|
||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
||
#include "windowinfox.h"
|
||
#include "appinfo.h"
|
||
#include "xcbutils.h"
|
||
#include "common.h"
|
||
#include "processinfo.h"
|
||
|
||
#include <QDebug>
|
||
#include <QCryptographicHash>
|
||
#include <QTimer>
|
||
#include <QImage>
|
||
#include <QIcon>
|
||
#include <QBuffer>
|
||
|
||
#include <X11/Xlib.h>
|
||
#include <algorithm>
|
||
#include <qobject.h>
|
||
#include <string>
|
||
|
||
#define XCB XCBUtils::instance()
|
||
|
||
WindowInfoX::WindowInfoX(XWindow _xid, QObject *parent)
|
||
: WindowInfoBase (parent)
|
||
, m_x(0)
|
||
, m_y(0)
|
||
, m_width(0)
|
||
, m_height(0)
|
||
, m_hasWMTransientFor(false)
|
||
, m_hasXEmbedInfo(false)
|
||
, m_updateCalled(false)
|
||
{
|
||
xid = _xid;
|
||
m_createdTime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); // 获取当前时间,精确到纳秒
|
||
}
|
||
|
||
WindowInfoX::~WindowInfoX()
|
||
{
|
||
|
||
}
|
||
|
||
bool WindowInfoX::shouldSkip()
|
||
{
|
||
qInfo() << "window " << xid << " shouldSkip?";
|
||
if (!m_updateCalled) {
|
||
update();
|
||
m_updateCalled = true;
|
||
}
|
||
|
||
if (hasWmStateSkipTaskBar() || isValidModal() || shouldSkipWithWMClass())
|
||
return true;
|
||
|
||
for (auto atom : m_wmWindowType) {
|
||
if (atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_DIALOG") && !isActionMinimizeAllowed())
|
||
return true;
|
||
|
||
if (atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_UTILITY")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_COMBO")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_DESKTOP") // 桌面属性窗口
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_DND")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_DOCK") // 任务栏属性窗口
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_MENU")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_SPLASH")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_TOOLBAR")
|
||
|| atom == XCB->getAtom("_NET_WM_WINDOW_TYPE_TOOLTIP"))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
QString WindowInfoX::getIcon()
|
||
{
|
||
if (icon.isEmpty())
|
||
icon = getIconFromWindow();
|
||
|
||
return icon;
|
||
}
|
||
|
||
void WindowInfoX::activate()
|
||
{
|
||
XCB->changeActiveWindow(xid);
|
||
QTimer::singleShot(50, [&] {
|
||
XCB->restackWindow(xid);
|
||
});
|
||
}
|
||
|
||
void WindowInfoX::minimize()
|
||
{
|
||
XCB->minimizeWindow(xid);
|
||
}
|
||
|
||
bool WindowInfoX::isMinimized()
|
||
{
|
||
return containAtom(m_wmState, XCB->getAtom("_NET_WM_STATE_HIDDEN"));
|
||
}
|
||
|
||
int64_t WindowInfoX::getCreatedTime()
|
||
{
|
||
return m_createdTime;
|
||
}
|
||
|
||
QString WindowInfoX::getWindowType()
|
||
{
|
||
return "X11";
|
||
}
|
||
|
||
bool WindowInfoX::allowClose()
|
||
{
|
||
// 允许关闭的条件:
|
||
// 1. 不设置 functions 字段,即MotifHintFunctions 标志位;
|
||
// 2. 或者设置了 functions 字段并且 设置了 MotifFunctionAll 标志位;
|
||
// 3. 或者设置了 functions 字段并且 设置了 MotifFunctionClose 标志位。
|
||
// 相关定义在 motif-2.3.8/lib/Xm/MwmUtil.h 。
|
||
if ((m_motifWmHints.flags & MotifHintFunctions) == 0
|
||
|| (m_motifWmHints.functions & MotifFunctionAll) != 0
|
||
|| (m_motifWmHints.functions & MotifFunctionClose) != 0)
|
||
return true;
|
||
|
||
for (auto action : m_wmAllowedActions) {
|
||
if (action == XCB->getAtom("_NET_WM_ACTION_CLOSE")) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
QString WindowInfoX::getDisplayName()
|
||
{
|
||
XWindow winId = xid;
|
||
//QString role = wmRole;
|
||
QString className(m_wmClass.className.c_str());
|
||
QString instance;
|
||
if (m_wmClass.instanceName.size() > 0) {
|
||
int pos = QString(m_wmClass.instanceName.c_str()).lastIndexOf('/');
|
||
if (pos != -1)
|
||
instance.remove(0, pos + 1);
|
||
}
|
||
qInfo() << "getDisplayName class:" << className << " ,instance:" << instance;
|
||
|
||
//if (!role.isEmpty() && !className.isEmpty())
|
||
// return className + " " + role;
|
||
|
||
if (!className.isEmpty())
|
||
return className;
|
||
|
||
if (!instance.isEmpty())
|
||
return instance;
|
||
|
||
|
||
QString _wmName = m_wmName;
|
||
if (!_wmName.isEmpty()) {
|
||
int pos = _wmName.lastIndexOf('-');
|
||
if (pos != -1 && !_wmName.startsWith("-")) {
|
||
_wmName.truncate(pos);
|
||
return _wmName;
|
||
}
|
||
}
|
||
|
||
if (m_processInfo) {
|
||
QString exe {m_processInfo->getEnv("exe")};
|
||
if (!exe.isEmpty())
|
||
return exe;
|
||
}
|
||
|
||
return QString("window:%1").arg(winId);
|
||
}
|
||
|
||
void WindowInfoX::killClient()
|
||
{
|
||
XCB->killClientChecked(xid);
|
||
}
|
||
|
||
QString WindowInfoX::uuid()
|
||
{
|
||
return QString();
|
||
}
|
||
|
||
QString WindowInfoX::getGtkAppId()
|
||
{
|
||
return m_gtkAppId;
|
||
}
|
||
|
||
QString WindowInfoX::getFlatpakAppId()
|
||
{
|
||
return m_flatpakAppId;
|
||
}
|
||
|
||
QString WindowInfoX::getWmRole()
|
||
{
|
||
return m_wmRole;
|
||
}
|
||
|
||
WMClass WindowInfoX::getWMClass()
|
||
{
|
||
return m_wmClass;
|
||
}
|
||
|
||
QString WindowInfoX::getWMName()
|
||
{
|
||
return m_wmName;
|
||
}
|
||
|
||
ConfigureEvent *WindowInfoX::getLastConfigureEvent()
|
||
{
|
||
return m_lastConfigureNotifyEvent;
|
||
}
|
||
|
||
void WindowInfoX::setLastConfigureEvent(ConfigureEvent *event)
|
||
{
|
||
m_lastConfigureNotifyEvent = event;
|
||
}
|
||
|
||
bool WindowInfoX::isGeometryChanged(int _x, int _y, int _width, int _height)
|
||
{
|
||
return !(_x == m_x && _y == m_y && _width == m_width && _height == m_height);
|
||
}
|
||
|
||
void WindowInfoX::setGtkAppId(QString _gtkAppId)
|
||
{
|
||
m_gtkAppId = _gtkAppId;
|
||
}
|
||
|
||
void WindowInfoX::updateMotifWmHints()
|
||
{
|
||
// get from XCB
|
||
m_motifWmHints = XCB->getWindowMotifWMHints(xid);
|
||
}
|
||
|
||
// XEmbed info
|
||
// 一般 tray icon 会带有 _XEMBED_INFO 属性
|
||
void WindowInfoX::updateHasXEmbedInfo()
|
||
{
|
||
m_hasXEmbedInfo = XCB->hasXEmbedInfo(xid);
|
||
}
|
||
|
||
/**
|
||
* @brief WindowInfoX::genInnerId 生成innerId
|
||
* @param winInfo
|
||
* @return
|
||
*/
|
||
QString WindowInfoX::genInnerId(WindowInfoX *winInfo)
|
||
{
|
||
XWindow winId = winInfo->getXid();
|
||
QString wmClassName, wmInstance;
|
||
WMClass wmClass = winInfo->getWMClass();
|
||
if (wmClass.className.size() > 0)
|
||
wmClassName = wmClass.className.c_str();
|
||
|
||
if (wmClass.instanceName.size() > 0) {
|
||
QString instanceName(wmClass.instanceName.c_str());
|
||
instanceName.remove(0, instanceName.lastIndexOf('/') + 1);
|
||
wmInstance = instanceName;
|
||
}
|
||
|
||
QString exe, args;
|
||
if (winInfo->getProcess()) {
|
||
exe = winInfo->getProcess()->getExe();
|
||
for (auto arg : winInfo->getProcess()->getArgs()) {
|
||
if (arg.contains("/") || arg == "." || arg == "..") {
|
||
args += "%F ";
|
||
} else {
|
||
args += arg + " ";
|
||
}
|
||
}
|
||
|
||
if (args.size() > 0)
|
||
args.remove(args.size() - 2, 1);
|
||
}
|
||
|
||
bool hasPid = winInfo->getPid() != 0;
|
||
QString str;
|
||
// NOTE: 不要使用 wmRole,有些程序总会改变这个值比如 GVim
|
||
if (wmInstance.isEmpty() && wmClassName.isEmpty() && exe.isEmpty() && winInfo->getGtkAppId().isEmpty()) {
|
||
if (!winInfo->getWMName().isEmpty())
|
||
str = QString("wmName:%1").arg(winInfo->getWMName());
|
||
else
|
||
str = QString("windowId:%1").arg(winInfo->getXid());
|
||
} else {
|
||
str = QString("wmInstance:%1,wmClass:%2,exe:%3,args:%4,hasPid:%5,gtkAppId:%6").arg(wmInstance).arg(wmClassName).arg(exe).arg(args).arg(hasPid).arg(winInfo->getGtkAppId());
|
||
}
|
||
|
||
QByteArray encryText = QCryptographicHash::hash(str.toLatin1(), QCryptographicHash::Md5);
|
||
QString innerId = windowHashPrefix + encryText.toHex();
|
||
qInfo() << "genInnerId window " << winId << " innerId :" << innerId;
|
||
return innerId;
|
||
}
|
||
|
||
// 更新窗口类型
|
||
void WindowInfoX::updateWmWindowType()
|
||
{
|
||
m_wmWindowType.clear();
|
||
for (auto ty : XCB->getWMWindoType(xid)) {
|
||
m_wmWindowType.push_back(ty);
|
||
}
|
||
}
|
||
|
||
// 更新窗口许可动作
|
||
void WindowInfoX::updateWmAllowedActions()
|
||
{
|
||
m_wmAllowedActions.clear();
|
||
for (auto action : XCB->getWMAllowedActions(xid)) {
|
||
m_wmAllowedActions.push_back(action);
|
||
}
|
||
}
|
||
|
||
void WindowInfoX::updateWmState()
|
||
{
|
||
m_wmState.clear();
|
||
for (auto a : XCB->getWMState(xid)) {
|
||
m_wmState.push_back(a);
|
||
}
|
||
}
|
||
|
||
void WindowInfoX::updateWmClass()
|
||
{
|
||
m_wmClass = XCB->getWMClass(xid);
|
||
}
|
||
|
||
void WindowInfoX::updateWmName()
|
||
{
|
||
auto name = XCB->getWMName(xid);
|
||
if (!name.empty())
|
||
m_wmName = name.c_str();
|
||
|
||
title = getTitle();
|
||
}
|
||
|
||
void WindowInfoX::updateIcon()
|
||
{
|
||
icon = getIconFromWindow();
|
||
}
|
||
|
||
void WindowInfoX::updateHasWmTransientFor()
|
||
{
|
||
if (XCB->getWMTransientFor(xid) == 1)
|
||
m_hasWMTransientFor = true;
|
||
}
|
||
|
||
/**
|
||
* @brief WindowInfoX::update 更新窗口信息(在识别窗口时执行一次)
|
||
*/
|
||
void WindowInfoX::update()
|
||
{
|
||
updateWmClass();
|
||
updateWmState();
|
||
updateWmWindowType();
|
||
updateWmAllowedActions();
|
||
updateHasWmTransientFor();
|
||
updateProcessInfo();
|
||
updateWmName();
|
||
innerId = genInnerId(this);
|
||
}
|
||
|
||
QString WindowInfoX::getIconFromWindow()
|
||
{
|
||
WMIcon icon = XCB->getWMIcon(xid);
|
||
|
||
// invalid icon
|
||
if (icon.width == 0) {
|
||
return QString();
|
||
}
|
||
|
||
QImage img = QImage((uchar *)icon.data.data(), icon.width, icon.width, QImage::Format_ARGB32);
|
||
QBuffer buffer;
|
||
buffer.open(QIODevice::WriteOnly);
|
||
img.scaled(48, 48, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
img.save(&buffer, "PNG");
|
||
|
||
// convert to base64
|
||
QString encode = buffer.data().toBase64();
|
||
QString iconPath = QString("%1,%2").arg("data:image/png:base64").arg(encode);
|
||
buffer.close();
|
||
|
||
return iconPath;
|
||
}
|
||
|
||
bool WindowInfoX::isActionMinimizeAllowed()
|
||
{
|
||
return containAtom(m_wmAllowedActions, XCB->getAtom("_NET_WM_ACTION_MINIMIZE"));
|
||
}
|
||
|
||
bool WindowInfoX::hasWmStateDemandsAttention()
|
||
{
|
||
return containAtom(m_wmState, XCB->getAtom("_NET_WM_STATE_DEMANDS_ATTENTION"));
|
||
}
|
||
|
||
bool WindowInfoX::hasWmStateSkipTaskBar()
|
||
{
|
||
return containAtom(m_wmState, XCB->getAtom("_NET_WM_STATE_SKIP_TASKBAR"));
|
||
}
|
||
|
||
bool WindowInfoX::hasWmStateModal()
|
||
{
|
||
return containAtom(m_wmState, XCB->getAtom("_NET_WM_STATE_MODAL"));
|
||
}
|
||
|
||
bool WindowInfoX::isValidModal()
|
||
{
|
||
return hasWmStateModal() && hasWmStateModal();
|
||
}
|
||
|
||
// 通过WMClass判断是否需要隐藏此窗口
|
||
bool WindowInfoX::shouldSkipWithWMClass()
|
||
{
|
||
bool ret = false;
|
||
if (m_wmClass.instanceName == "explorer.exe" && m_wmClass.className == "Wine")
|
||
ret = true;
|
||
else if (m_wmClass.className == "dde-launcher" ||
|
||
m_wmClass.className == "dde-dock" ||
|
||
m_wmClass.className == "dde-lock") {
|
||
ret = true;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
void WindowInfoX::updateProcessInfo()
|
||
{
|
||
XWindow winId = xid;
|
||
pid = XCB->getWMPid(winId);
|
||
qInfo() << "updateProcessInfo: pid=" << pid;
|
||
m_processInfo.reset(new ProcessInfo(pid));
|
||
if (!m_processInfo->isValid()) {
|
||
// try WM_COMMAND
|
||
auto wmComand = XCB->getWMCommand(winId);
|
||
if (wmComand.size() > 0) {
|
||
QStringList cmds;
|
||
std::transform(wmComand.begin(), wmComand.end(), std::back_inserter(cmds), [=] (std::string cmd){ return QString::fromStdString(cmd);});
|
||
m_processInfo.reset(new ProcessInfo(cmds));
|
||
}
|
||
}
|
||
|
||
qInfo() << "updateProcessInfo: pid is " << pid;
|
||
}
|
||
|
||
bool WindowInfoX::getUpdateCalled()
|
||
{
|
||
return m_updateCalled;
|
||
}
|
||
|
||
void WindowInfoX::setInnerId(QString _innerId)
|
||
{
|
||
innerId = _innerId;
|
||
}
|
||
|
||
QString WindowInfoX::getTitle()
|
||
{
|
||
QString name = m_wmName;
|
||
if (name.isEmpty())
|
||
name = getDisplayName();
|
||
|
||
return name;
|
||
}
|
||
|
||
bool WindowInfoX::isDemandingAttention()
|
||
{
|
||
return hasWmStateDemandsAttention();
|
||
}
|
||
|
||
void WindowInfoX::close(uint32_t timestamp)
|
||
{
|
||
XCB->requestCloseWindow(xid, timestamp);
|
||
}
|