/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/desktop_capture/linux/x11/window_capturer_x11.h" #include #include #include #include #include #include #include #include "api/scoped_refptr.h" #include "modules/desktop_capture/desktop_capture_types.h" #include "modules/desktop_capture/desktop_frame.h" #include "modules/desktop_capture/desktop_region.h" #include "modules/desktop_capture/linux/x11/shared_x_display.h" #include "modules/desktop_capture/linux/x11/window_finder_x11.h" #include "modules/desktop_capture/linux/x11/window_list_utils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/trace_event.h" namespace webrtc { WindowCapturerX11::WindowCapturerX11(const DesktopCaptureOptions& options) : x_display_(options.x_display()), atom_cache_(display()), window_finder_(&atom_cache_) { int event_base, error_base, major_version, minor_version; if (XCompositeQueryExtension(display(), &event_base, &error_base) && XCompositeQueryVersion(display(), &major_version, &minor_version) && // XCompositeNameWindowPixmap() requires version 0.2 (major_version > 0 || minor_version >= 2)) { has_composite_extension_ = true; } else { RTC_LOG(LS_INFO) << "Xcomposite extension not available or too old."; } x_display_->AddEventHandler(ConfigureNotify, this); } WindowCapturerX11::~WindowCapturerX11() { x_display_->RemoveEventHandler(ConfigureNotify, this); } bool WindowCapturerX11::GetSourceList(SourceList* sources) { return GetWindowList(&atom_cache_, [this, sources](::Window window) { Source w; w.id = window; w.pid = (pid_t)GetWindowProcessID(window); if (this->GetWindowTitle(window, &w.title)) { sources->push_back(w); } return true; }); } bool WindowCapturerX11::SelectSource(SourceId id) { if (!x_server_pixel_buffer_.Init(&atom_cache_, id)) return false; // Tell the X server to send us window resizing events. XSelectInput(display(), id, StructureNotifyMask); selected_window_ = id; // In addition to needing X11 server-side support for Xcomposite, it actually // needs to be turned on for the window. If the user has modern // hardware/drivers but isn't using a compositing window manager, that won't // be the case. Here we automatically turn it on. // Redirect drawing to an offscreen buffer (ie, turn on compositing). X11 // remembers who has requested this and will turn it off for us when we exit. XCompositeRedirectWindow(display(), id, CompositeRedirectAutomatic); return true; } bool WindowCapturerX11::FocusOnSelectedSource() { if (!selected_window_) return false; unsigned int num_children; ::Window* children; ::Window parent; ::Window root; // Find the root window to pass event to. int status = XQueryTree(display(), selected_window_, &root, &parent, &children, &num_children); if (status == 0) { RTC_LOG(LS_ERROR) << "Failed to query for the root window."; return false; } if (children) XFree(children); XRaiseWindow(display(), selected_window_); // Some window managers (e.g., metacity in GNOME) consider it illegal to // raise a window without also giving it input focus with // _NET_ACTIVE_WINDOW, so XRaiseWindow() on its own isn't enough. Atom atom = XInternAtom(display(), "_NET_ACTIVE_WINDOW", True); if (atom != None) { XEvent xev; xev.xclient.type = ClientMessage; xev.xclient.serial = 0; xev.xclient.send_event = True; xev.xclient.window = selected_window_; xev.xclient.message_type = atom; // The format member is set to 8, 16, or 32 and specifies whether the // data should be viewed as a list of bytes, shorts, or longs. xev.xclient.format = 32; memset(xev.xclient.data.l, 0, sizeof(xev.xclient.data.l)); XSendEvent(display(), root, False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } XFlush(display()); return true; } void WindowCapturerX11::Start(Callback* callback) { RTC_DCHECK(!callback_); RTC_DCHECK(callback); callback_ = callback; } void WindowCapturerX11::CaptureFrame() { TRACE_EVENT0("webrtc", "WindowCapturerX11::CaptureFrame"); if (!x_server_pixel_buffer_.IsWindowValid()) { RTC_LOG(LS_ERROR) << "The window is no longer valid."; callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); return; } x_display_->ProcessPendingXEvents(); if (!has_composite_extension_) { // Without the Xcomposite extension we capture when the whole window is // visible on screen and not covered by any other window. This is not // something we want so instead, just bail out. RTC_LOG(LS_ERROR) << "No Xcomposite extension detected."; callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); return; } if (GetWindowState(&atom_cache_, selected_window_) == IconicState) { // Window is in minimized. Return a 1x1 frame as same as OSX/Win does. std::unique_ptr frame( new BasicDesktopFrame(DesktopSize(1, 1))); callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); return; } std::unique_ptr frame( new BasicDesktopFrame(x_server_pixel_buffer_.window_size())); x_server_pixel_buffer_.Synchronize(); if (!x_server_pixel_buffer_.CaptureRect(DesktopRect::MakeSize(frame->size()), frame.get())) { RTC_LOG(LS_WARNING) << "Temporarily failed to capture winodw."; callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); return; } frame->mutable_updated_region()->SetRect( DesktopRect::MakeSize(frame->size())); frame->set_top_left(x_server_pixel_buffer_.window_rect().top_left()); frame->set_capturer_id(DesktopCapturerId::kX11CapturerLinux); callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); } bool WindowCapturerX11::IsOccluded(const DesktopVector& pos) { return window_finder_.GetWindowUnderPoint(pos) != static_cast(selected_window_); } bool WindowCapturerX11::HandleXEvent(const XEvent& event) { if (event.type == ConfigureNotify) { XConfigureEvent xce = event.xconfigure; if (xce.window == selected_window_) { if (!DesktopRectFromXAttributes(xce).equals( x_server_pixel_buffer_.window_rect())) { if (!x_server_pixel_buffer_.Init(&atom_cache_, selected_window_)) { RTC_LOG(LS_ERROR) << "Failed to initialize pixel buffer after resizing."; } } } } // Always returns false, so other observers can still receive the events. return false; } bool WindowCapturerX11::GetWindowTitle(::Window window, std::string* title) { int status; bool result = false; XTextProperty window_name; window_name.value = nullptr; if (window) { status = XGetWMName(display(), window, &window_name); if (status && window_name.value && window_name.nitems) { int cnt; char** list = nullptr; status = Xutf8TextPropertyToTextList(display(), &window_name, &list, &cnt); if (status >= Success && cnt && *list) { if (cnt > 1) { RTC_LOG(LS_INFO) << "Window has " << cnt << " text properties, only using the first one."; } *title = *list; result = true; } if (list) XFreeStringList(list); } if (window_name.value) XFree(window_name.value); } return result; } int WindowCapturerX11::GetWindowProcessID(::Window window) { // Get _NET_WM_PID property of the window. Atom process_atom = XInternAtom(display(), "_NET_WM_PID", True); XWindowProperty process_id(display(), window, process_atom); return process_id.is_valid() ? *process_id.data() : 0; } // static std::unique_ptr WindowCapturerX11::CreateRawWindowCapturer( const DesktopCaptureOptions& options) { if (!options.x_display()) return nullptr; return std::unique_ptr(new WindowCapturerX11(options)); } } // namespace webrtc