| // 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(); |
| } |