Skip to content

Commit

Permalink
WindowServer: Enable screen capture to span multiple screens
Browse files Browse the repository at this point in the history
This enables the shot utility to capture all screens or just one, and
enables the Magnifier application to track the mouse cursor across
multiple screens.
  • Loading branch information
tomuta authored and awesomekling committed Jun 20, 2021
1 parent 61af9d8 commit f232cb8
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 14 deletions.
92 changes: 81 additions & 11 deletions Userland/Services/WindowServer/ClientConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -921,30 +921,100 @@ void ClientConnection::did_become_responsive()
set_unresponsive(false);
}

Messages::WindowServer::GetScreenBitmapResponse ClientConnection::get_screen_bitmap(Optional<Gfx::IntRect> const& rect)
Messages::WindowServer::GetScreenBitmapResponse ClientConnection::get_screen_bitmap(Optional<Gfx::IntRect> const& rect, Optional<u32> const& screen_index)
{
auto& screen = Screen::main(); // TODO: implement screenshots from other screens or areas spanning multiple screens
if (rect.has_value()) {
auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(rect.value());
if (screen_index.has_value()) {
auto* screen = Screen::find_by_index(screen_index.value());
if (!screen) {
dbgln("get_screen_bitmap: Screen {} does not exist!", screen_index.value());
return { {} };
}
if (rect.has_value()) {
auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, *screen).cropped(rect.value());
return bitmap->to_shareable_bitmap();
}
auto& bitmap = Compositor::the().front_bitmap_for_screenshot({}, *screen);
return bitmap.to_shareable_bitmap();
}
// TODO: Mixed scale setups at what scale? Lowest? Highest? Configurable?
if (auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, Screen::bounding_rect().size(), 1)) {
Gfx::Painter painter(*bitmap);
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
if (rect.has_value() && !rect.value().intersects(screen_rect))
return IterationDecision::Continue;
auto src_rect = rect.has_value() ? rect.value().intersected(screen_rect) : screen_rect;
VERIFY(Screen::bounding_rect().contains(src_rect));
auto& screen_bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen);
// TODO: painter does *not* support down-sampling!!!
painter.blit(screen_rect.location(), screen_bitmap, src_rect.translated(-screen_rect.location()), 1.0f, false);
return IterationDecision::Continue;
});
return bitmap->to_shareable_bitmap();
}
auto& bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen);
return bitmap.to_shareable_bitmap();
return { {} };
}

Messages::WindowServer::GetScreenBitmapAroundCursorResponse ClientConnection::get_screen_bitmap_around_cursor(Gfx::IntSize const& size)
{
auto& screen = Screen::main(); // TODO: implement getting screen bitmaps from other screens or areas spanning multiple screens
auto scale_factor = screen.scale_factor();
// TODO: Mixed scale setups at what scale? Lowest? Highest? Configurable?
auto cursor_location = ScreenInput::the().cursor_location();
Gfx::Rect rect { (cursor_location.x() * scale_factor) - (size.width() / 2), (cursor_location.y() * scale_factor) - (size.height() / 2), size.width(), size.height() };
Gfx::Rect rect { cursor_location.x() - (size.width() / 2), cursor_location.y() - (size.height() / 2), size.width(), size.height() };

// Recompose the screen to make sure the cursor is painted in the location we think it is.
// FIXME: This is rather wasteful. We can probably think of a way to avoid this.
Compositor::the().compose();

auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(rect);
return bitmap->to_shareable_bitmap();
// Check if we need to compose from multiple screens. If not we can take a fast path
size_t intersecting_with_screens = 0;
Screen::for_each([&](auto& screen) {
if (rect.intersects(screen.rect()))
intersecting_with_screens++;
return IterationDecision::Continue;
});

if (intersecting_with_screens == 1) {
auto& screen = Screen::closest_to_rect(rect);
auto bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(rect.translated(-screen.rect().location()));
return bitmap->to_shareable_bitmap();
}

if (auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, rect.size(), 1)) {
auto bounding_screen_src_rect = Screen::bounding_rect().intersected(rect);
Gfx::Painter painter(*bitmap);
Gfx::IntRect last_cursor_rect;
auto& screen_with_cursor = ScreenInput::the().cursor_location_screen();
auto cursor_rect = Compositor::the().current_cursor_rect();
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
auto src_rect = screen_rect.intersected(bounding_screen_src_rect);
if (src_rect.is_empty())
return IterationDecision ::Continue;
auto& screen_bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen);
auto from_rect = src_rect.translated(-screen_rect.location());
auto target_location = rect.intersected(screen_rect).location().translated(-rect.location());
// TODO: painter does *not* support down-sampling!!!
painter.blit(target_location, screen_bitmap, from_rect, 1.0f, false);
// Check if we are a screen that doesn't have the cursor but the cursor would
// have normally been cut off (we don't draw portions of the cursor on a screen
// that doesn't actually have the cursor). In that case we need to render the remaining
// portion of the cursor on that screen's capture manually
if (&screen != &screen_with_cursor) {
auto screen_cursor_rect = cursor_rect.intersected(screen_rect);
if (!screen_cursor_rect.is_empty()) {
if (auto const* cursor_bitmap = Compositor::the().cursor_bitmap_for_screenshot({}, screen)) {
auto src_rect = screen_cursor_rect.translated(-cursor_rect.location());
auto cursor_target = cursor_rect.intersected(screen_rect).location().translated(-rect.location());
// TODO: painter does *not* support down-sampling!!!
painter.blit(cursor_target, *cursor_bitmap, src_rect);
}
}
}
return IterationDecision::Continue;
});
return bitmap->to_shareable_bitmap();
}
return { {} };
}

Messages::WindowServer::IsWindowModifiedResponse ClientConnection::is_window_modified(i32 window_id)
Expand Down
2 changes: 1 addition & 1 deletion Userland/Services/WindowServer/ClientConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class ClientConnection final
virtual Messages::WindowServer::GetMouseAccelerationResponse get_mouse_acceleration() override;
virtual void set_scroll_step_size(u32) override;
virtual Messages::WindowServer::GetScrollStepSizeResponse get_scroll_step_size() override;
virtual Messages::WindowServer::GetScreenBitmapResponse get_screen_bitmap(Optional<Gfx::IntRect> const&) override;
virtual Messages::WindowServer::GetScreenBitmapResponse get_screen_bitmap(Optional<Gfx::IntRect> const&, Optional<u32> const&) override;
virtual Messages::WindowServer::GetScreenBitmapAroundCursorResponse get_screen_bitmap_around_cursor(Gfx::IntSize const&) override;
virtual void set_double_click_speed(i32) override;
virtual Messages::WindowServer::GetDoubleClickSpeedResponse get_double_click_speed() override;
Expand Down
7 changes: 7 additions & 0 deletions Userland/Services/WindowServer/Compositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ Compositor::Compositor()
init_bitmaps();
}

const Gfx::Bitmap* Compositor::cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
{
if (!m_current_cursor)
return nullptr;
return &m_current_cursor->bitmap(screen.scale_factor());
}

const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
{
return *m_screen_data[screen.index()].m_front_bitmap;
Expand Down
1 change: 1 addition & 0 deletions Userland/Services/WindowServer/Compositor.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Compositor final : public Core::Object {

void did_construct_window_manager(Badge<WindowManager>);

const Gfx::Bitmap* cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;
const Gfx::Bitmap& front_bitmap_for_screenshot(Badge<ClientConnection>, Screen&) const;

private:
Expand Down
2 changes: 1 addition & 1 deletion Userland/Services/WindowServer/WindowServer.ipc
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ endpoint WindowServer
set_scroll_step_size(u32 step_size) => ()
get_scroll_step_size() => (u32 step_size)

get_screen_bitmap(Optional<Gfx::IntRect> rect) => (Gfx::ShareableBitmap bitmap)
get_screen_bitmap(Optional<Gfx::IntRect> rect, Optional<u32> screen_index) => (Gfx::ShareableBitmap bitmap)
get_screen_bitmap_around_cursor(Gfx::IntSize size) => (Gfx::ShareableBitmap bitmap)

pong() =|
Expand Down
9 changes: 8 additions & 1 deletion Userland/Utilities/shot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ int main(int argc, char** argv)
String output_path;
bool output_to_clipboard = false;
int delay = 0;
int screen = -1;

args_parser.add_positional_argument(output_path, "Output filename", "output", Core::ArgsParser::Required::No);
args_parser.add_option(output_to_clipboard, "Output to clipboard", "clipboard", 'c');
args_parser.add_option(delay, "Seconds to wait before taking a screenshot", "delay", 'd', "seconds");
args_parser.add_option(screen, "The index of the screen (default: -1 for all screens)", "screen", 's', "index");

args_parser.parse(argc, argv);

Expand All @@ -34,7 +36,12 @@ int main(int argc, char** argv)

auto app = GUI::Application::construct(argc, argv);
sleep(delay);
auto shared_bitmap = GUI::WindowServerConnection::the().get_screen_bitmap({});
Optional<u32> screen_index;
if (screen >= 0)
screen_index = (u32)screen;
dbgln("getting screenshot...");
auto shared_bitmap = GUI::WindowServerConnection::the().get_screen_bitmap({}, screen_index);
dbgln("got screenshot");

auto* bitmap = shared_bitmap.bitmap();
if (!bitmap) {
Expand Down

0 comments on commit f232cb8

Please sign in to comment.