You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
11 KiB
C++
302 lines
11 KiB
C++
// Copyright 2018 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
//
|
|
// Upstream: chromium/src/ash/display/cros_display_config.cc
|
|
// Revision: 7acd40ddefff49c9d36144999aafdbb674dbd07a
|
|
//
|
|
// Adapted for Cutefish: asynchronous D-Bus calls, service-owner watching and
|
|
// reconnecting after compositor restarts.
|
|
|
|
#include "displayclient.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <QDBusConnectionInterface>
|
|
#include <QDBusPendingCallWatcher>
|
|
#include <QDBusServiceWatcher>
|
|
|
|
#include "display_config_interface.h"
|
|
|
|
using namespace cutefish::display;
|
|
|
|
DisplayClient::DisplayClient(QObject *parent)
|
|
: QObject(parent)
|
|
, m_serviceName(qEnvironmentVariable("CUTEFISH_DISPLAY_CONFIG_SERVICE",
|
|
QStringLiteral("com.cutefish.DisplayConfig")))
|
|
{
|
|
ipc::registerTypes();
|
|
m_operationTimeout.setSingleShot(true);
|
|
m_operationTimeout.setInterval(15000);
|
|
connect(&m_operationTimeout, &QTimer::timeout, this, [this] {
|
|
finishOperation(m_operationToken,
|
|
{false, false, 1, tr("The display operation timed out."), {}});
|
|
refresh();
|
|
});
|
|
auto *watcher = new QDBusServiceWatcher(m_serviceName, QDBusConnection::sessionBus(),
|
|
QDBusServiceWatcher::WatchForOwnerChange, this);
|
|
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this,
|
|
[this](const QString &, const QString &oldOwner, const QString &newOwner) {
|
|
// A notification queued before this client looked the owner up is stale.
|
|
if (oldOwner != m_owner)
|
|
return;
|
|
m_owner = newOwner;
|
|
const bool wasAvailable = available();
|
|
connectInterface();
|
|
if (wasAvailable != available())
|
|
emit availabilityChanged();
|
|
refresh();
|
|
});
|
|
m_owner = QDBusConnection::sessionBus().interface()->serviceOwner(m_serviceName).value();
|
|
connectInterface();
|
|
refresh();
|
|
}
|
|
|
|
bool DisplayClient::available() const
|
|
{
|
|
return m_interface && m_interface->isValid() && !m_state.instanceId.isEmpty();
|
|
}
|
|
|
|
void DisplayClient::connectInterface()
|
|
{
|
|
++m_refreshSerial;
|
|
++m_serviceEpoch;
|
|
m_state = {};
|
|
m_loaded = false;
|
|
emit stateChanged();
|
|
if (operationPending())
|
|
finishOperation(m_operationToken,
|
|
{false, false, 1, tr("The display service restarted."), {}});
|
|
delete m_interface;
|
|
m_interface = new ComCutefishDisplayConfig1Interface(
|
|
m_serviceName, QStringLiteral("/com/cutefish/DisplayConfig1"),
|
|
QDBusConnection::sessionBus(), this);
|
|
connect(m_interface, &ComCutefishDisplayConfig1Interface::StateChanged, this, [this] {
|
|
refresh();
|
|
});
|
|
connect(m_interface, &ComCutefishDisplayConfig1Interface::PreviewChanged, this,
|
|
[this](const QString &, qulonglong, qulonglong, qulonglong) {
|
|
refresh();
|
|
});
|
|
connect(m_interface, &ComCutefishDisplayConfig1Interface::OperationFinished, this,
|
|
[this](const QString &token, bool succeeded, bool degraded, uint errorCode,
|
|
const QString &message, const QStringList &affected, qulonglong generation,
|
|
qulonglong revision) {
|
|
const OperationResult result {succeeded, degraded, errorCode, message,
|
|
affected, generation, revision};
|
|
if (const Completion done = m_requests.take(token))
|
|
done(succeeded, ipc::ErrorCode(errorCode));
|
|
else if (m_requestsInFlight)
|
|
m_earlyRequestResults.insert(token, result);
|
|
if (m_applyPending)
|
|
m_earlyResults.insert(token, result);
|
|
else if (token == m_operationToken)
|
|
queueCompletion(token, result);
|
|
refresh();
|
|
});
|
|
// Last, so a completion that asks again reaches the new owner.
|
|
failRequests();
|
|
}
|
|
|
|
void DisplayClient::refresh()
|
|
{
|
|
const auto serial = ++m_refreshSerial;
|
|
if (!m_interface || !m_interface->isValid()) {
|
|
m_state = {};
|
|
m_loaded = true;
|
|
emit stateChanged();
|
|
return;
|
|
}
|
|
auto pending = m_interface->GetState();
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, serial] {
|
|
QDBusPendingReply<QString, QString, qulonglong, qulonglong, QList<ipc::PhysicalDisplay>,
|
|
QList<ipc::LogicalDisplay>, QList<ipc::Placement>, QString, qulonglong>
|
|
reply = *watcher;
|
|
watcher->deleteLater();
|
|
if (serial != m_refreshSerial)
|
|
return;
|
|
m_loaded = true;
|
|
if (reply.isError()) {
|
|
m_state = {};
|
|
emit stateChanged();
|
|
emit availabilityChanged();
|
|
if (operationPending())
|
|
finishOperation(m_operationToken, {false, false, 1, reply.error().message(), {}});
|
|
return;
|
|
}
|
|
const bool wasAvailable = available();
|
|
m_state = {reply.argumentAt<0>(), reply.argumentAt<1>(), reply.argumentAt<2>(),
|
|
reply.argumentAt<3>(), reply.argumentAt<4>(), reply.argumentAt<5>(),
|
|
reply.argumentAt<6>(), reply.argumentAt<7>(), reply.argumentAt<8>()};
|
|
emit stateChanged();
|
|
if (wasAvailable != available())
|
|
emit availabilityChanged();
|
|
completeIfCurrent();
|
|
});
|
|
}
|
|
|
|
QString DisplayClient::apply(const QList<ipc::DisplayRequest> &displays, const QString &primary,
|
|
const QList<ipc::Placement> &placements,
|
|
const ipc::MirrorGroups &mirrorGroups, bool preview)
|
|
{
|
|
if (!available() || operationPending())
|
|
return {};
|
|
m_applyPending = true;
|
|
m_operationToken.clear();
|
|
m_earlyResults.clear();
|
|
m_operationTimeout.start();
|
|
const auto epoch = m_serviceEpoch;
|
|
const auto operation = ++m_operationSerial;
|
|
auto pending = preview
|
|
? m_interface->ApplyConfiguration(m_state.instanceId, m_state.topologyGeneration,
|
|
m_state.appliedRevision, displays, primary,
|
|
placements, mirrorGroups)
|
|
: m_interface->SetConfiguration(m_state.instanceId, m_state.topologyGeneration,
|
|
m_state.appliedRevision, displays, primary,
|
|
placements, mirrorGroups);
|
|
auto *watcher = new QDBusPendingCallWatcher(pending, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, epoch, operation] {
|
|
QDBusPendingReply<QString> reply = *watcher;
|
|
watcher->deleteLater();
|
|
if (epoch != m_serviceEpoch || operation != m_operationSerial || !m_applyPending)
|
|
return;
|
|
m_applyPending = false;
|
|
if (reply.isError()) {
|
|
queueCompletion({}, {false, false, 1, reply.error().message(), {}});
|
|
} else {
|
|
m_operationToken = reply.value();
|
|
const auto result = m_earlyResults.constFind(m_operationToken);
|
|
if (result != m_earlyResults.cend())
|
|
queueCompletion(m_operationToken, *result);
|
|
m_earlyResults.clear();
|
|
}
|
|
});
|
|
return QStringLiteral("pending");
|
|
}
|
|
|
|
void DisplayClient::finishOperation(const QString &token, const OperationResult &result)
|
|
{
|
|
const QString completedToken = token;
|
|
m_applyPending = false;
|
|
m_operationToken.clear();
|
|
m_earlyResults.clear();
|
|
m_completion.reset();
|
|
m_operationTimeout.stop();
|
|
emit operationFinished(completedToken, result.succeeded, result.degraded, result.errorCode,
|
|
result.message, result.affected);
|
|
}
|
|
|
|
bool DisplayClient::operationPending() const
|
|
{
|
|
return m_applyPending || !m_operationToken.isEmpty() || m_completion;
|
|
}
|
|
|
|
void DisplayClient::queueCompletion(const QString &token, const OperationResult &result)
|
|
{
|
|
m_operationToken = token;
|
|
m_completion = result;
|
|
refresh();
|
|
}
|
|
|
|
void DisplayClient::completeIfCurrent()
|
|
{
|
|
if (!m_completion)
|
|
return;
|
|
if (m_completion->generation
|
|
&& (m_state.topologyGeneration < m_completion->generation
|
|
|| (m_state.topologyGeneration == m_completion->generation
|
|
&& m_state.appliedRevision < m_completion->revision)))
|
|
return;
|
|
const auto result = *m_completion;
|
|
finishOperation(m_operationToken, result);
|
|
}
|
|
|
|
void DisplayClient::confirm(const QString &token)
|
|
{
|
|
if (available())
|
|
m_interface->ConfirmConfiguration(token);
|
|
}
|
|
|
|
void DisplayClient::revert(const QString &token)
|
|
{
|
|
if (available())
|
|
m_interface->RevertConfiguration(token);
|
|
}
|
|
|
|
bool DisplayClient::setDisplayPower(bool on, Completion done)
|
|
{
|
|
if (!m_interface || !m_interface->isValid())
|
|
return false;
|
|
// The compositor's power states: 0 is on, 1 is off.
|
|
return sendRequest(m_interface->SetDisplayPower({}, on ? 0 : 1), std::move(done));
|
|
}
|
|
|
|
bool DisplayClient::setDockedLidClosed(bool closed, Completion done)
|
|
{
|
|
if (!m_interface || !m_interface->isValid())
|
|
return false;
|
|
return sendRequest(m_interface->SetDockedLidClosed(closed), std::move(done));
|
|
}
|
|
|
|
bool DisplayClient::setNightLight(double strength, uint durationMs)
|
|
{
|
|
if (!m_interface || !m_interface->isValid())
|
|
return false;
|
|
m_interface->SetNightLight(strength, durationMs);
|
|
return true;
|
|
}
|
|
|
|
bool DisplayClient::nightLightAvailable() const
|
|
{
|
|
return std::any_of(m_state.physical.begin(), m_state.physical.end(),
|
|
[](const ipc::PhysicalDisplay &display) {
|
|
return display.capabilities & ipc::ColorTransformCapability;
|
|
});
|
|
}
|
|
|
|
bool DisplayClient::sendRequest(const QDBusPendingCall &call, Completion done)
|
|
{
|
|
++m_requestsInFlight;
|
|
const auto epoch = m_serviceEpoch;
|
|
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this, watcher, epoch, done = std::move(done)] {
|
|
QDBusPendingReply<QString> reply = *watcher;
|
|
watcher->deleteLater();
|
|
// Sent to a previous owner, whose OperationFinished never reaches this interface.
|
|
if (epoch != m_serviceEpoch) {
|
|
if (done)
|
|
done(false, ipc::ErrorCode::Unavailable);
|
|
return;
|
|
}
|
|
|
|
// A rejected request reports OperationFinished before its reply.
|
|
std::optional<OperationResult> early;
|
|
if (!reply.isError() && m_earlyRequestResults.contains(reply.value()))
|
|
early = m_earlyRequestResults.take(reply.value());
|
|
if (--m_requestsInFlight == 0)
|
|
m_earlyRequestResults.clear();
|
|
|
|
if (!done)
|
|
return;
|
|
if (reply.isError())
|
|
done(false, ipc::ErrorCode::Unavailable);
|
|
else if (early)
|
|
done(early->succeeded, ipc::ErrorCode(early->errorCode));
|
|
else
|
|
m_requests.insert(reply.value(), done);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
void DisplayClient::failRequests()
|
|
{
|
|
const auto requests = std::exchange(m_requests, {});
|
|
m_requestsInFlight = 0;
|
|
m_earlyRequestResults.clear();
|
|
for (const Completion &done : requests)
|
|
done(false, ipc::ErrorCode::Unavailable);
|
|
}
|