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:
Martin Gräßlin 2015-02-02 14:56:21 +01:00
parent 551b3a4c48
commit dfa89cc050
7 changed files with 211 additions and 107 deletions

View file

@ -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"

View file

@ -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) {

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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

View file

@ -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)