前言:
之前曾翻译过 X Desktop Group,今天分享一下如何自定义状态;
此篇讲述如何通过kwindowsystem新增状态,以及设置状态的过程;
窗口有不同的属性、状态、允许Action等等信息,以使窗口管理器来确定窗口的装饰、堆叠位置和其他行为,而我们可以通过xprop命令(之前曾讲过xprop)进行查看窗口允许的操作、属性状态等等。
正文:
一个客户端窗口除了自己本身进行UI设置和功能设置外,还需要设置不同的属性来供其他组件进行管理,或者联动,尤其是操作系统本身的组件,肯定需要通过设置property、Actions和status等信息,方便窗口管理器来确定窗口的装饰、堆叠位置和其他行为,更好的管理窗口。今天以_NET_WM_STATE为例,讲述如何增加自定义状态,当然举一反三自定义其他的类型属性,都是差不多的原理,不过还是建议使用已经定义的属性,因为比较规范,后期会分享一下,什么时候定义属性什么时候定义接口。
- 整体流程
- 代码部分
- KwindowSystem
如果我们想为窗口增加新的属性,可以通过在KwindowSystem的代码中,增加新的属性,进行处理,以下是增加属性的步骤:
1. atoms_p.h
//此文件保存了所有窗口属性用到的枚举变量,当我们需要增加新的自定义属性的时候,需要在此文件中找到自己想要新增属性对应类型的位置,此次我们新增的是应用窗口状态,所以在应用窗口状态下,新增如下代码:
ENUM(_NET_WM_STATE_NEW_TEST)
//注意全部大写,虽然也可以小写,但最好符合规范;
//NEW_TEST是我自己起的名字,不是固定的可以更换,一般前不同的类型半部分都有对应的固定部分
2. netwm_def.h
//此文件中定义了NETRootInfo 和NETWinInfo的基类NET,用于检索和修改窗口的属性。保持命名空间相对干净,所有枚举都在这里定义。
//我们需要在这里声明好我们先前新增的属性对应的枚举值,这样外部调用的时候,可以直接NET::***,设置窗口状态;
//我们依然找到自己新增的属性对应类型的位置,我新增的是状态,故增加在state中,代码如下:
enum State {
TestState = 1u << 0 //这里名字没有固定部分,根据自己需要进行起名即可
};
以上准备工作都已经完成,下面是真正进行处理窗口设置了该属性后,KwindowSystem需要做的工作。
3.netwm.cpp
//此文件中定义了NETRootInfo类,主要客户端和窗口管理器提供了一个通用API设置/读取/更改由NET窗口定义的根窗口的属性;
//在这文件中,重载了以下五个函数,为方便起见而提供,它们之间的区别仅在于它接受的参数,第一个参数代表接受的类型,第二个参数on,如果为true则设置相应属性,如果为false,则删除属性:
void setSupported(NET::Property property, bool on = true);
void setSupported(NET::Property2 property, bool on = true);
void setSupported(NET::WindowTypeMask property, bool on = true);
void setSupported(NET::State property, bool on = true);
void setSupported(NET::Action property, bool on = true);
//以上五个函数内部没有复杂的处理逻辑,所有的property、state、Action和mask都是在下面这个函数进行设置的,故我们只需要将新增属性的过滤写在下面的函数中:
void NETRootInfo::setSupported()
{
//这个函数仅是设置支持什么属性,此函数中处理了所有类型,所以只需要找到自己对应的部分,增加设置即可,我的是state,故代码如下:
if (p->properties & WMState) {
atoms[pnum++] = p->atom(_NET_WM_STATE);
//这里进行判断是否是新增的属性,如果是,则加入atoms中,因为一个窗口会有很多属性状态,所以需要存储在一起在函数尾部xcb_change_property()中设置;
if (p->states & TestState) {
atoms[pnum++] = p>atom(_NET_WM_STATE_NEW_TEST);
}
}
****************************************************************
//上面只是设置,因为属性有更新的情况,故我们还需要在以下函数中进行处理:
void NETRootInfo::updateSupportedProperties(xcb_atom_t atom)
{
//此函数的方式和上一个设置没有区别,只不过判断条件变化了
if (atom == p->atom(_NET_WM_STATE_NEW_TEST)) {
p->states |= TestState;
}
****************************************************************
//上面设置完成后,还需要在新增属性对应的处理函数中,增加处理,因为KwindowSystem中会有对外设置状态的接口,如我的是设置状态,故在下面的函数中设置:
void NETWinInfo::setState(NET::States state, NET::States mask)
{
//此函数设置应用程序窗口的状态
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.sequence = 0;
event.window = p->window;
event.type = p->atom(_NET_WM_STATE);
event.data.data32[3] = 0;
event.data.data32[4] = 0;
//以上是x下设置窗口状态的流程,指明窗口,设置状态
//注意:以下代码的外层还有一个if,所以我们在if中增加此部分代码,在else中也要进行判断,此处就不列出了;
if ((mask & TestState) && ((p->state & TestState) != (state & TestState))) {
event.data.data32[0] = (state & TestState) ? 1 : 0;
event.data.data32[1] = p->atom(_NET_WM_STATE_NEW_TEST);
event.data.data32[2] = 0l;
xcb_send_event(p->conn, false, p->root, netwm_sendevent_mask, (const char *) &event);
}
}
****************************************************************
//还需要在以下函数中进行处理:
void NETWinInfo::event(xcb_generic_event_t *event, NET::Properties *properties, NET::Properties2 *properties2)
{
//此函数接受传入的xcb_generic_event_t,并在传入的参数中返回更新的属性。
if ((xcb_atom_t) message->data.data32[i] == p->atom(_NET_WM_STATE_NEW_TEST)) {
mask |= TestState;
}
}
void NETWinInfo::update(NET::Properties dirtyProperties, NET::Properties2 dirtyProperties2)
{
if (_NET_WM_STATE_NEW_TEST) {
p->state |= TestState;
}
}
以上便是KwindowSystem中需要做的所有工作;
- 设置属性demo
- pro文件
QT += core gui x11extras //x11extras也一定要添加
QT += KWindowSystem //这个一定要添加
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
PKGCONFIG += x11 xext
CONFIG += c++14
CONFIG += qt warn_on
CONFIG += release
CONFIG += link_pkgconfig
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
utils.cpp \
widget.cpp
HEADERS += \
utils.h \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
- utils.h
#include <QObject>
//这两个头文件注意
#include <netwm.h>
#include <netwm_def.h>
class Utils : public QObject
{
public:
static void setProperty(int wid);
};
utils.cpp
#include "utils.h"
#include <QDebug>
#include <QtX11Extras/QX11Info>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <iostream>
#include <kwindowsystem.h>
void Utils::setProperty(int wid)
{
//这里直接调用setState接口,即上面提到的接口,传入窗口id和设置的state即可
KWindowSystem::setState( wid, NET::TestState);
}
- widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
class QPushButton;
QT_END_NAMESPACE
namespace Ui { class Widget; }
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);;
~Widget();
private:
Ui::Widget *ui;
QPushButton *m_test;
};
#endif // WIDGET_H
-
- widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "utils.h"
#include <QDebug>
#include <iostream>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QDebug>
#include <QtX11Extras/QX11Info>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <iostream>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(500, 700);
m_test = new QPushButton(this);
m_test->setText("setProperty");
QVBoxLayout *m_layout = new QVBoxLayout;
m_layout->addStretch();
m_layout->addWidget(m_test, 0 , Qt::AlignHCenter);
m_layout->addStretch();
this->setLayout(m_layout);
connect(m_test, &QPushButton::clicked, this, [=] {
qDebug() << "this->winId()" <<this->winId();
Utils::setProperty(this->winId());
});
}
Widget::~Widget()
{
delete ui;
}
- main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
- 获取属性demo
- pro文件
QT += core gui x11extras dbus
QT += KWindowSystem
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
PKGCONFIG += x11 xext
CONFIG += c++14
CONFIG += qt warn_on
CONFIG += release
CONFIG += link_pkgconfig
TARGET = getProperty
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp \
utils.cpp
HEADERS += \
widget.h \
utils.h
FORMS += \
widget.ui
-
- utils.h
#include <QObject>
#include <netwm.h>
#include <netwm_def.h>
class Utils : public QObject
{
public:
static char * getProperty();
};
-
- utils.cpp
#include "utils.h"
#include <QDebug>
#include <QtX11Extras/QX11Info>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <iostream>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusConnection>
#include <kwindowsystem.h>
char * Utils::getProperty()
{
// get window id 根据自己的需要获取相应的id
auto wid = 0;
const auto screen = QX11Info::appScreen();
Window rootWindow = QX11Info::appRootWindow(screen);
const NET::Properties properties =
NET::WMDesktop |
NET::WMState |
NET::WMWindowType |
NET::WMStrut |
NET::WMName |
NET::WMIconGeometry |
NET::WMIcon |
NET::WMPid |
NET::WMIconName;
const NET::Properties2 properties2 =
NET::WM2BlockCompositing |
NET::WM2WindowClass |
NET::WM2WindowRole |
NET::WM2UserTime |
NET::WM2StartupId |
NET::WM2ExtendedStrut |
NET::WM2Opacity |
NET::WM2FullscreenMonitors |
NET::WM2FrameOverlap |
NET::WM2GroupLeader |
NET::WM2Urgency |
NET::WM2Input |
NET::WM2Protocols |
NET::WM2InitialMappingState |
NET::WM2IconPixmap |
NET::WM2OpaqueRegion |
NET::WM2DesktopFileName |
NET::WM2GTKFrameExtents;
xcb_connection_t *c = QX11Info::connection();
NETWinInfo* info = new NETWinInfo(c, wid, rootWindow,properties, properties2, NET::Role::Client);
if(info->state() & NET::TestState)
{
qDebug() << "set is success";
}
else
{
qDebug() << "set is fail";
}
return nullptr;
}
-
- widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
class QPushButton;
QT_END_NAMESPACE
namespace Ui { class Widget; }
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);;
~Widget();
private:
Ui::Widget *ui;
QPushButton *m_getProperty;
};
#endif // WIDGET_H
-
- widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "utils.h"
#include <QDebug>
#include <iostream>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QDebug>
#include <QtX11Extras/QX11Info>
#include <X11/extensions/shape.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <iostream>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->resize(500, 700);
qDebug() << "this id is " << this->winId();
m_getProperty = new QPushButton(this);
m_getProperty->setText("m_getProperty");
QVBoxLayout *m_layout = new QVBoxLayout;
m_layout->addStretch();
m_layout->addWidget(m_getProperty, 0 , Qt::AlignHCenter);
m_layout->addStretch();
this->setLayout(m_layout);
connect(m_getProperty, &QPushButton::clicked, this, [=] {
qDebug() << "get value";
Utils::getProperty();
});
}
Widget::~Widget()
{
delete ui;
}
-
- main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
4. 谁获取谁设置
只是执行上面三步,会发现设置新增的属性后,使用xprop获取时看不见,还需要在调用放进行设置一次窗口属性,既可以看见了
以上代码都可以直接运行,可直接验证,当然不要忘记设置自己的属性哦~