blob: 858ed0f833365d18a94b9775b3cd5e00c0aa6c54 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sys/time.h>
#include <time.h>
#include <csignal>
#include <cstdint>
#include <base/check.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <rfb/rfb.h>
#include "screen-capture-utils/capture.h"
#include "screen-capture-utils/crtc.h"
#include "screen-capture-utils/egl_capture.h"
#include "screen-capture-utils/kmsvnc_utils.h"
#include "screen-capture-utils/uinput.h"
namespace screenshot {
namespace {
constexpr const char kInternalSwitch[] = "internal";
constexpr const char kExternalSwitch[] = "external";
constexpr const char kCrtcIdSwitch[] = "crtc-id";
constexpr const char kRotateSwitch[] = "rotate";
constexpr const char kPasswordSwitch[] = "password";
constexpr const int kFindCrtcMaxRetries = 5;
const timespec kFindCrtcRetryInterval{0, 100000000}; // 100ms
class ScopedPowerLock {
public:
ScopedPowerLock() {
PCHECK(system("set_power_policy --screen_wake_lock=1") != -1)
<< "Invoking set_power_policy to avoid screen off";
}
ScopedPowerLock(const ScopedPowerLock&) = delete;
ScopedPowerLock& operator=(const ScopedPowerLock&) = delete;
~ScopedPowerLock() {
PCHECK(system("set_power_policy --screen_wake_lock=-1") != -1)
<< "Invoking set_power_policy to restore wake lock";
}
};
class FpsTimer {
public:
FpsTimer() { gettimeofday(&start_time_, NULL); }
FpsTimer(const FpsTimer&) = delete;
FpsTimer& operator=(const FpsTimer&) = delete;
~FpsTimer() = default;
void Frame() { frames_++; }
void ModifiedFrame() { modified_frames_++; }
// Print FPS stats once a second.
void MaybePrint() {
if (Elapsed() < 1.0) {
return;
}
VLOG(1) << "fps: " << Get(frames_)
<< " (modified frames: " << Get(modified_frames_) << ")";
modified_frames_ = 0;
frames_ = 0;
PCHECK(gettimeofday(&start_time_, NULL) != -1);
}
private:
struct timeval start_time_;
size_t frames_{0};
size_t modified_frames_{0};
double Elapsed() const {
struct timeval end_time;
PCHECK(gettimeofday(&end_time, NULL) != -1);
double seconds =
static_cast<double>(end_time.tv_sec - start_time_.tv_sec) +
static_cast<double>(end_time.tv_usec - start_time_.tv_usec) / 1000.0 /
1000.0;
return seconds;
}
double Get(size_t frames) const {
return static_cast<double>(frames) / Elapsed();
}
};
class ScopedSigaction {
public:
ScopedSigaction(int signum, void (*handler)(int)) : signum_(signum) {
struct sigaction new_action;
new_action.sa_handler = handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
sigaction(signum, &new_action, &old_action_);
}
ScopedSigaction(const ScopedSigaction&) = delete;
ScopedSigaction& operator=(const ScopedSigaction&) = delete;
~ScopedSigaction() { sigaction(signum_, &old_action_, nullptr); }
private:
const int signum_;
struct sigaction old_action_;
};
// Signal number received if shutdown requested.
volatile int g_shutdown_requested{0};
void SignalHandler(int signum) {
g_shutdown_requested = signum;
}
int VncMain() {
ScopedPowerLock power_lock;
auto* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->GetArgs().size() != 0) {
LOG(ERROR) << "Wrong number of parameters";
return 1;
}
int crtc_specs = (cmdline->HasSwitch(kInternalSwitch) ? 1 : 0) +
(cmdline->HasSwitch(kExternalSwitch) ? 1 : 0) +
(cmdline->HasSwitch(kCrtcIdSwitch) ? 1 : 0);
if (crtc_specs > 1) {
LOG(ERROR) << "--internal, --external and --crtc-id are exclusive";
return 1;
}
const bool rotate = cmdline->HasSwitch(kRotateSwitch);
CrtcFinder finder;
if (cmdline->HasSwitch(kInternalSwitch)) {
finder.SetSpec(CrtcFinder::Spec::kInternalDisplay);
} else if (cmdline->HasSwitch(kExternalSwitch)) {
finder.SetSpec(CrtcFinder::Spec::kExternalDisplay);
} else if (cmdline->HasSwitch(kCrtcIdSwitch)) {
uint32_t crtc_id;
if (!base::StringToUint(cmdline->GetSwitchValueASCII(kCrtcIdSwitch),
&crtc_id)) {
LOG(ERROR) << "Invalid --crtc-id specification";
return 1;
}
finder.SetSpec(CrtcFinder::Spec::kById);
finder.SetCrtcId(crtc_id);
}
auto crtc = finder.Find();
for (int retries = 0; !crtc && retries < kFindCrtcMaxRetries; retries++) {
LOG(WARNING) << "CRTC not found, retrying";
::nanosleep(&kFindCrtcRetryInterval, nullptr);
crtc = finder.Find();
}
if (!crtc) {
LOG(ERROR) << "CRTC not found. Is the screen on?";
return 1;
}
uint32_t crtc_width = crtc->width();
uint32_t crtc_height = crtc->height();
// vncViewer requires a width (but not height) with multiple of 4
// Pad the width. Swap width and height if rotatation is requested.
uint32_t vnc_width = GetVncWidth(rotate ? crtc_height : crtc_width);
uint32_t vnc_height = rotate ? crtc_width : crtc_height;
LOG(INFO) << "Starting with CRTC size of: " << crtc_width << " "
<< crtc_height;
LOG(INFO) << "with VNC view-port size of: " << vnc_width << " " << vnc_height;
if (vnc_width % 4 > 0) {
LOG(INFO) << "Vnc viewport width has been right-padded to be "
<< "vnc lib compatible multiple of 4.";
}
const rfbScreenInfoPtr server =
rfbGetScreen(0 /*argc*/, nullptr /*argv*/, vnc_width, vnc_height,
8 /*bitsPerSample*/, 3 /*samplesPerPixel*/, kBytesPerPixel);
CHECK(server);
// To enable password authentication, `server->authPasswdData` should contain
// a null-terminated C-style string list, and the pointers in the list must
// outlive the server.
std::string password;
const char* password_list[2] = {};
if (cmdline->HasSwitch(kPasswordSwitch)) {
password = cmdline->GetSwitchValueASCII(kPasswordSwitch);
password_list[0] = password.c_str();
server->authPasswdData = password_list;
server->passwordCheck = rfbCheckPasswordByList;
}
// Without setting this flag, rfbProcessEvents() consumes only one event per
// call.
server->handleEventsEagerly = true;
std::unique_ptr<screenshot::DisplayBuffer> display_buffer;
display_buffer.reset(new screenshot::EglDisplayBuffer(
crtc.get(), 0, 0, crtc_width, crtc_height, false));
std::vector<uint32_t> buffer(vnc_width * vnc_height);
// This is ARGB buffer.
{
auto capture_result = display_buffer->Capture(rotate);
ConvertBuffer(capture_result, buffer.data(), vnc_width);
server->frameBuffer = reinterpret_cast<char*>(buffer.data());
}
// http://libvncserver.sourceforge.net/doc/html/rfbproto_8h_source.html#l00150
server->serverFormat.redMax = 255;
server->serverFormat.greenMax = 255;
server->serverFormat.blueMax = 255;
server->serverFormat.redShift = 16;
server->serverFormat.greenShift = 8;
server->serverFormat.blueShift = 0;
// Create uinput devices and hook up input events.
const std::unique_ptr<Uinput> uinput = Uinput::Create(server, rotate);
rfbInitServer(server);
std::vector<uint32_t> prev(vnc_width * vnc_height);
ScopedSigaction sa1(SIGINT, SignalHandler);
ScopedSigaction sa2(SIGTERM, SignalHandler);
FpsTimer timer;
while (rfbIsActive(server)) {
timer.Frame();
timer.MaybePrint();
auto capture_result = display_buffer->Capture(rotate);
// Keep the previous framebuffer around for comparison.
prev.swap(buffer);
// Copy the current data to the buffer.
ConvertBuffer(capture_result, buffer.data(), vnc_width);
// Update VNC server's view to the swapped current buffer.
server->frameBuffer = reinterpret_cast<char*>(buffer.data());
// Find rectangle of modification.
int min_x = vnc_width;
int min_y = vnc_height;
int max_x = 0;
int max_y = 0;
for (int y = 0; y < vnc_height; y++) {
for (int x = 0; x < vnc_width; x++) {
if (buffer[x + y * vnc_width] == prev[x + y * vnc_width]) {
continue;
}
max_x = std::max(x, max_x);
max_y = std::max(y, max_y);
min_x = std::min(x, min_x);
min_y = std::min(y, min_y);
}
}
if ((min_x > max_x) || (min_y > max_y)) {
// Skipping unchanged frame.
} else {
timer.ModifiedFrame();
rfbMarkRectAsModified(server, min_x, min_y, max_x, max_y);
}
// deferUpdateTime (select timeout waiting for sockets) 60fps is 16ms if
// everything else happened in an instant.
rfbProcessEvents(server, 16000 /*deferUpdateTime*/);
if (g_shutdown_requested) {
LOG(INFO) << "Caught signal, shutting down";
rfbShutdownServer(server, true /*disconnectClients*/);
}
}
return 0;
}
} // namespace
} // namespace screenshot
int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
logging::LoggingSettings settings;
settings.logging_dest = logging::LOG_TO_STDERR;
logging::InitLogging(settings);
return screenshot::VncMain();
}