Port reading Motif hints to XCB
A wrapper class for MotifHints is added to xcbutils. This class manages the information about the read Motif hints, so that Client doesn't need to have a copy of the read states. The class is designed in a way that during Client::manage we get rid of another roundtrip. REVIEW: 122378
This commit is contained in:
parent
551b3a4c48
commit
dfa89cc050
7 changed files with 211 additions and 107 deletions
|
@ -51,6 +51,9 @@ private Q_SLOTS:
|
|||
void testPropertyByteArray();
|
||||
void testPropertyBool();
|
||||
void testAtom();
|
||||
void testMotifEmpty();
|
||||
void testMotif_data();
|
||||
void testMotif();
|
||||
private:
|
||||
void testEmpty(WindowGeometry &geometry);
|
||||
void testGeometry(WindowGeometry &geometry, const QRect &rect);
|
||||
|
@ -421,5 +424,98 @@ void TestXcbWrapper::testAtom()
|
|||
QCOMPARE(atom3.name(), QByteArrayLiteral("WM_CLIENT_MACHINE"));
|
||||
}
|
||||
|
||||
void TestXcbWrapper::testMotifEmpty()
|
||||
{
|
||||
Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS"));
|
||||
MotifHints hints(atom);
|
||||
// pre init
|
||||
QCOMPARE(hints.hasDecoration(), false);
|
||||
QCOMPARE(hints.noBorder(), false);
|
||||
QCOMPARE(hints.resize(), true);
|
||||
QCOMPARE(hints.move(), true);
|
||||
QCOMPARE(hints.minimize(), true);
|
||||
QCOMPARE(hints.maximize(), true);
|
||||
QCOMPARE(hints.close(), true);
|
||||
// post init, pre read
|
||||
hints.init(m_testWindow);
|
||||
QCOMPARE(hints.hasDecoration(), false);
|
||||
QCOMPARE(hints.noBorder(), false);
|
||||
QCOMPARE(hints.resize(), true);
|
||||
QCOMPARE(hints.move(), true);
|
||||
QCOMPARE(hints.minimize(), true);
|
||||
QCOMPARE(hints.maximize(), true);
|
||||
QCOMPARE(hints.close(), true);
|
||||
// post read
|
||||
hints.read();
|
||||
QCOMPARE(hints.hasDecoration(), false);
|
||||
QCOMPARE(hints.noBorder(), false);
|
||||
QCOMPARE(hints.resize(), true);
|
||||
QCOMPARE(hints.move(), true);
|
||||
QCOMPARE(hints.minimize(), true);
|
||||
QCOMPARE(hints.maximize(), true);
|
||||
QCOMPARE(hints.close(), true);
|
||||
}
|
||||
|
||||
void TestXcbWrapper::testMotif_data()
|
||||
{
|
||||
QTest::addColumn<quint32>("flags");
|
||||
QTest::addColumn<quint32>("funtions");
|
||||
QTest::addColumn<quint32>("decorations");
|
||||
|
||||
QTest::addColumn<bool>("expectedHasDecoration");
|
||||
QTest::addColumn<bool>("expectedNoBorder");
|
||||
QTest::addColumn<bool>("expectedResize");
|
||||
QTest::addColumn<bool>("expectedMove");
|
||||
QTest::addColumn<bool>("expectedMinimize");
|
||||
QTest::addColumn<bool>("expectedMaximize");
|
||||
QTest::addColumn<bool>("expectedClose");
|
||||
|
||||
QTest::newRow("none") << 0u << 0u << 0u << false << false << true << true << true << true << true;
|
||||
QTest::newRow("noborder") << 2u << 5u << 0u << true << true << true << true << true << true << true;
|
||||
QTest::newRow("border") << 2u << 5u << 1u << true << false << true << true << true << true << true;
|
||||
QTest::newRow("resize") << 1u << 2u << 1u << false << false << true << false << false << false << false;
|
||||
QTest::newRow("move") << 1u << 4u << 1u << false << false << false << true << false << false << false;
|
||||
QTest::newRow("minimize") << 1u << 8u << 1u << false << false << false << false << true << false << false;
|
||||
QTest::newRow("maximize") << 1u << 16u << 1u << false << false << false << false << false << true << false;
|
||||
QTest::newRow("close") << 1u << 32u << 1u << false << false << false << false << false << false << true;
|
||||
|
||||
QTest::newRow("resize/all") << 1u << 3u << 1u << false << false << false << true << true << true << true;
|
||||
QTest::newRow("move/all") << 1u << 5u << 1u << false << false << true << false << true << true << true;
|
||||
QTest::newRow("minimize/all") << 1u << 9u << 1u << false << false << true << true << false << true << true;
|
||||
QTest::newRow("maximize/all") << 1u << 17u << 1u << false << false << true << true << true << false << true;
|
||||
QTest::newRow("close/all") << 1u << 33u << 1u << false << false << true << true << true << true << false;
|
||||
|
||||
QTest::newRow("all") << 1u << 62u << 1u << false << false << true << true << true << true << true;
|
||||
QTest::newRow("all/all") << 1u << 63u << 1u << false << false << false << false << false << false << false;
|
||||
QTest::newRow("all/all/deco") << 3u << 63u << 1u << true << false << false << false << false << false << false;
|
||||
}
|
||||
|
||||
void TestXcbWrapper::testMotif()
|
||||
{
|
||||
Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS"));
|
||||
QFETCH(quint32, flags);
|
||||
QFETCH(quint32, funtions);
|
||||
QFETCH(quint32, decorations);
|
||||
quint32 data[] = {
|
||||
flags,
|
||||
funtions,
|
||||
decorations,
|
||||
0,
|
||||
0
|
||||
};
|
||||
xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, m_testWindow, atom, atom, 32, 5, data);
|
||||
xcb_flush(QX11Info::connection());
|
||||
MotifHints hints(atom);
|
||||
hints.init(m_testWindow);
|
||||
hints.read();
|
||||
QTEST(hints.hasDecoration(), "expectedHasDecoration");
|
||||
QTEST(hints.noBorder(), "expectedNoBorder");
|
||||
QTEST(hints.resize(), "expectedResize");
|
||||
QTEST(hints.move(), "expectedMove");
|
||||
QTEST(hints.minimize(), "expectedMinimize");
|
||||
QTEST(hints.maximize(), "expectedMaximize");
|
||||
QTEST(hints.close(), "expectedClose");
|
||||
}
|
||||
|
||||
KWIN_TEST_MAIN(TestXcbWrapper)
|
||||
#include "test_xcb_wrapper.moc"
|
||||
|
|
107
client.cpp
107
client.cpp
|
@ -83,85 +83,6 @@ const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE
|
|||
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
|
||||
|
||||
//************************************
|
||||
// Motif
|
||||
//************************************
|
||||
class Motif
|
||||
{
|
||||
public:
|
||||
// Read a window's current settings from its _MOTIF_WM_HINTS
|
||||
// property. If it explicitly requests that decorations be shown
|
||||
// or hidden, 'got_noborder' is set to true and 'noborder' is set
|
||||
// appropriately.
|
||||
static void readFlags(xcb_window_t w, bool& got_noborder, bool& noborder,
|
||||
bool& resize, bool& move, bool& minimize, bool& maximize,
|
||||
bool& close);
|
||||
struct MwmHints {
|
||||
ulong flags;
|
||||
ulong functions;
|
||||
ulong decorations;
|
||||
long input_mode;
|
||||
ulong status;
|
||||
};
|
||||
enum {
|
||||
MWM_HINTS_FUNCTIONS = (1L << 0),
|
||||
MWM_HINTS_DECORATIONS = (1L << 1),
|
||||
|
||||
MWM_FUNC_ALL = (1L << 0),
|
||||
MWM_FUNC_RESIZE = (1L << 1),
|
||||
MWM_FUNC_MOVE = (1L << 2),
|
||||
MWM_FUNC_MINIMIZE = (1L << 3),
|
||||
MWM_FUNC_MAXIMIZE = (1L << 4),
|
||||
MWM_FUNC_CLOSE = (1L << 5)
|
||||
};
|
||||
};
|
||||
|
||||
void Motif::readFlags(xcb_window_t w, bool& got_noborder, bool& noborder,
|
||||
bool& resize, bool& move, bool& minimize, bool& maximize, bool& close)
|
||||
{
|
||||
Atom type;
|
||||
int format;
|
||||
unsigned long length, after;
|
||||
unsigned char* data;
|
||||
MwmHints* hints = 0;
|
||||
if (XGetWindowProperty(display(), w, atoms->motif_wm_hints, 0, 5,
|
||||
false, atoms->motif_wm_hints, &type, &format,
|
||||
&length, &after, &data) == Success) {
|
||||
if (data)
|
||||
hints = (MwmHints*) data;
|
||||
}
|
||||
got_noborder = false;
|
||||
noborder = false;
|
||||
resize = true;
|
||||
move = true;
|
||||
minimize = true;
|
||||
maximize = true;
|
||||
close = true;
|
||||
if (hints) {
|
||||
// To quote from Metacity 'We support those MWM hints deemed non-stupid'
|
||||
if (hints->flags & MWM_HINTS_FUNCTIONS) {
|
||||
// if MWM_FUNC_ALL is set, other flags say what to turn _off_
|
||||
bool set_value = ((hints->functions & MWM_FUNC_ALL) == 0);
|
||||
resize = move = minimize = maximize = close = !set_value;
|
||||
if (hints->functions & MWM_FUNC_RESIZE)
|
||||
resize = set_value;
|
||||
if (hints->functions & MWM_FUNC_MOVE)
|
||||
move = set_value;
|
||||
if (hints->functions & MWM_FUNC_MINIMIZE)
|
||||
minimize = set_value;
|
||||
if (hints->functions & MWM_FUNC_MAXIMIZE)
|
||||
maximize = set_value;
|
||||
if (hints->functions & MWM_FUNC_CLOSE)
|
||||
close = set_value;
|
||||
}
|
||||
if (hints->flags & MWM_HINTS_DECORATIONS) {
|
||||
got_noborder = true;
|
||||
noborder = !hints->decorations;
|
||||
}
|
||||
XFree(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a client:
|
||||
// - only by calling Workspace::createClient()
|
||||
// - it creates a new client and calls manage() for it
|
||||
|
@ -195,6 +116,7 @@ Client::Client()
|
|||
, m_originalTransientForId(XCB_WINDOW_NONE)
|
||||
, shade_below(NULL)
|
||||
, skip_switcher(false)
|
||||
, m_motif(atoms->motif_wm_hints)
|
||||
, blocks_compositing(false)
|
||||
, m_cursor(Qt::ArrowCursor)
|
||||
, autoRaiseTimer(NULL)
|
||||
|
@ -248,9 +170,6 @@ Client::Client()
|
|||
deleting = false;
|
||||
keep_above = false;
|
||||
keep_below = false;
|
||||
motif_may_move = true;
|
||||
motif_may_resize = true;
|
||||
motif_may_close = true;
|
||||
fullscreen_mode = FullScreenNone;
|
||||
skip_taskbar = false;
|
||||
original_skip_taskbar = false;
|
||||
|
@ -259,7 +178,6 @@ Client::Client()
|
|||
modal = false;
|
||||
noborder = false;
|
||||
app_noborder = false;
|
||||
motif_noborder = false;
|
||||
ignore_focus_stealing = false;
|
||||
demands_attention = false;
|
||||
check_active_modal = false;
|
||||
|
@ -742,7 +660,7 @@ void Client::updateShape()
|
|||
xcb_shape_mask(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, frameId(), 0, 0, XCB_PIXMAP_NONE);
|
||||
detectNoBorder();
|
||||
app_noborder = noborder;
|
||||
noborder = rules()->checkNoBorder(noborder || motif_noborder);
|
||||
noborder = rules()->checkNoBorder(noborder || m_motif.noBorder());
|
||||
updateDecoration(true);
|
||||
}
|
||||
|
||||
|
@ -1263,7 +1181,7 @@ void Client::sendClientMessage(xcb_window_t w, xcb_atom_t a, xcb_atom_t protocol
|
|||
*/
|
||||
bool Client::isCloseable() const
|
||||
{
|
||||
return rules()->checkCloseable(motif_may_close && !isSpecialWindow());
|
||||
return rules()->checkCloseable(m_motif.close() && !isSpecialWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1915,29 +1833,22 @@ void Client::setClientShown(bool shown)
|
|||
|
||||
void Client::getMotifHints()
|
||||
{
|
||||
bool mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose;
|
||||
Motif::readFlags(m_client, mgot_noborder, mnoborder, mresize, mmove, mminimize, mmaximize, mclose);
|
||||
if (mgot_noborder && motif_noborder != mnoborder) {
|
||||
motif_noborder = mnoborder;
|
||||
const bool wasClosable = m_motif.close();
|
||||
const bool wasNoBorder = m_motif.noBorder();
|
||||
m_motif.read();
|
||||
if (m_motif.hasDecoration() && m_motif.noBorder() != wasNoBorder) {
|
||||
// If we just got a hint telling us to hide decorations, we do so.
|
||||
if (motif_noborder)
|
||||
if (m_motif.noBorder())
|
||||
noborder = rules()->checkNoBorder(true);
|
||||
// If the Motif hint is now telling us to show decorations, we only do so if the app didn't
|
||||
// instruct us to hide decorations in some other way, though.
|
||||
else if (!app_noborder)
|
||||
noborder = rules()->checkNoBorder(false);
|
||||
}
|
||||
if (!hasNETSupport()) {
|
||||
// NETWM apps should set type and size constraints
|
||||
motif_may_resize = mresize; // This should be set using minsize==maxsize, but oh well
|
||||
motif_may_move = mmove;
|
||||
} else
|
||||
motif_may_resize = motif_may_move = true;
|
||||
|
||||
// mminimize; - Ignore, bogus - E.g. shading or sending to another desktop is "minimizing" too
|
||||
// mmaximize; - Ignore, bogus - Maximizing is basically just resizing
|
||||
const bool closabilityChanged = motif_may_close != mclose;
|
||||
motif_may_close = mclose; // Motif apps like to crash when they set this hint and WM closes them anyway
|
||||
const bool closabilityChanged = wasClosable != m_motif.close();
|
||||
if (isManaged())
|
||||
updateDecoration(true); // Check if noborder state has changed
|
||||
if (closabilityChanged) {
|
||||
|
|
5
client.h
5
client.h
|
@ -963,16 +963,13 @@ private:
|
|||
uint original_skip_taskbar : 1; ///< Unaffected by KWin
|
||||
uint skip_pager : 1;
|
||||
uint skip_switcher : 1;
|
||||
uint motif_may_resize : 1;
|
||||
uint motif_may_move : 1;
|
||||
uint motif_may_close : 1;
|
||||
Xcb::MotifHints m_motif;
|
||||
uint keep_below : 1; ///< NET::KeepBelow
|
||||
uint minimized : 1;
|
||||
uint hidden : 1; ///< Forcibly hidden by calling hide()
|
||||
uint modal : 1; ///< NET::Modal
|
||||
uint noborder : 1;
|
||||
uint app_noborder : 1; ///< App requested no border via window type, shape extension, etc.
|
||||
uint motif_noborder : 1; ///< App requested no border via Motif WM hints
|
||||
uint ignore_focus_stealing : 1; ///< Don't apply focus stealing prevention to this client
|
||||
uint demands_attention : 1;
|
||||
bool blocks_compositing;
|
||||
|
|
|
@ -902,9 +902,10 @@ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e)
|
|||
getIcons(); // because KWin::icon() uses WMHints as fallback
|
||||
break;
|
||||
default:
|
||||
if (e->atom == atoms->motif_wm_hints)
|
||||
if (e->atom == atoms->motif_wm_hints) {
|
||||
m_motif.fetch();
|
||||
getMotifHints();
|
||||
else if (e->atom == atoms->net_wm_sync_request_counter)
|
||||
} else if (e->atom == atoms->net_wm_sync_request_counter)
|
||||
getSyncCounter();
|
||||
else if (e->atom == atoms->activities)
|
||||
checkActivities();
|
||||
|
|
13
geometry.cpp
13
geometry.cpp
|
@ -1774,7 +1774,10 @@ void Client::NETMoveResizeWindow(int flags, int x, int y, int width, int height)
|
|||
*/
|
||||
bool Client::isMovable() const
|
||||
{
|
||||
if (!motif_may_move || isFullScreen())
|
||||
if (!hasNETSupport() && !m_motif.move()) {
|
||||
return false;
|
||||
}
|
||||
if (isFullScreen())
|
||||
return false;
|
||||
if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
|
||||
return false;
|
||||
|
@ -1788,8 +1791,9 @@ bool Client::isMovable() const
|
|||
*/
|
||||
bool Client::isMovableAcrossScreens() const
|
||||
{
|
||||
if (!motif_may_move)
|
||||
if (!hasNETSupport() && !m_motif.move()) {
|
||||
return false;
|
||||
}
|
||||
if (isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :)
|
||||
return false;
|
||||
if (rules()->checkPosition(invalidPoint) != invalidPoint) // forced position
|
||||
|
@ -1802,7 +1806,10 @@ bool Client::isMovableAcrossScreens() const
|
|||
*/
|
||||
bool Client::isResizable() const
|
||||
{
|
||||
if (!motif_may_resize || isFullScreen())
|
||||
if (!hasNETSupport() && !m_motif.resize()) {
|
||||
return false;
|
||||
}
|
||||
if (isFullScreen())
|
||||
return false;
|
||||
if (isSpecialWindow() || isSplash() || isToolbar())
|
||||
return false;
|
||||
|
|
|
@ -108,6 +108,7 @@ bool Client::manage(xcb_window_t w, bool isMapped)
|
|||
auto transientCookie = fetchTransient();
|
||||
auto activitiesCookie = fetchActivities();
|
||||
m_geometryHints.init(window());
|
||||
m_motif.init(window());
|
||||
info = new WinInfo(this, m_client, rootWindow(), properties, properties2);
|
||||
|
||||
// If it's already mapped, ignore hint
|
||||
|
|
91
xcbutils.h
91
xcbutils.h
|
@ -978,6 +978,97 @@ private:
|
|||
NormalHints::SizeHints *m_sizeHints = nullptr;
|
||||
};
|
||||
|
||||
class MotifHints
|
||||
{
|
||||
public:
|
||||
MotifHints(xcb_atom_t atom) : m_atom(atom) {}
|
||||
void init(xcb_window_t window) {
|
||||
Q_ASSERT(window);
|
||||
if (m_window) {
|
||||
// already initialized
|
||||
return;
|
||||
}
|
||||
m_window = window;
|
||||
fetch();
|
||||
}
|
||||
void fetch() {
|
||||
if (!m_window) {
|
||||
return;
|
||||
}
|
||||
m_hints = nullptr;
|
||||
m_prop = Property(0, m_window, m_atom, m_atom, 0, 5);
|
||||
}
|
||||
void read() {
|
||||
m_hints = m_prop.value<MwmHints*>(32, m_atom, nullptr);
|
||||
}
|
||||
bool hasDecoration() const {
|
||||
if (!m_window || !m_hints) {
|
||||
return false;
|
||||
}
|
||||
return m_hints->flags & uint32_t(Hints::Decorations);
|
||||
}
|
||||
bool noBorder() const {
|
||||
if (!hasDecoration()) {
|
||||
return false;
|
||||
}
|
||||
return !m_hints->decorations;
|
||||
}
|
||||
bool resize() const {
|
||||
return testFunction(Functions::Resize);
|
||||
}
|
||||
bool move() const {
|
||||
return testFunction(Functions::Move);
|
||||
}
|
||||
bool minimize() const {
|
||||
return testFunction(Functions::Minimize);
|
||||
}
|
||||
bool maximize() const {
|
||||
return testFunction(Functions::Maximize);
|
||||
}
|
||||
bool close() const {
|
||||
return testFunction(Functions::Close);
|
||||
}
|
||||
|
||||
private:
|
||||
struct MwmHints {
|
||||
uint32_t flags;
|
||||
uint32_t functions;
|
||||
uint32_t decorations;
|
||||
int32_t input_mode;
|
||||
uint32_t status;
|
||||
};
|
||||
enum class Hints {
|
||||
Functions = (1L << 0),
|
||||
Decorations = (1L << 1)
|
||||
};
|
||||
enum class Functions {
|
||||
All = (1L << 0),
|
||||
Resize = (1L << 1),
|
||||
Move = (1L << 2),
|
||||
Minimize = (1L << 3),
|
||||
Maximize = (1L << 4),
|
||||
Close = (1L << 5)
|
||||
};
|
||||
bool testFunction(Functions flag) const {
|
||||
if (!m_window || !m_hints) {
|
||||
return true;
|
||||
}
|
||||
if (!(m_hints->flags & uint32_t(Hints::Functions))) {
|
||||
return true;
|
||||
}
|
||||
// if MWM_FUNC_ALL is set, other flags say what to turn _off_
|
||||
const bool set_value = ((m_hints->functions & uint32_t(Functions::All)) == 0);
|
||||
if (m_hints->functions & uint32_t(flag)) {
|
||||
return set_value;
|
||||
}
|
||||
return !set_value;
|
||||
}
|
||||
xcb_window_t m_window = XCB_WINDOW_NONE;
|
||||
Property m_prop;
|
||||
xcb_atom_t m_atom;
|
||||
MwmHints *m_hints = nullptr;
|
||||
};
|
||||
|
||||
namespace RandR
|
||||
{
|
||||
XCB_WRAPPER(ScreenInfo, xcb_randr_get_screen_info, xcb_window_t)
|
||||
|
|
Loading…
Reference in a new issue