From 1c06d772628a0191289fe30edcc143e29ba2ca32 Mon Sep 17 00:00:00 2001 From: Aziz Berkay Yesilyurt Date: Fri, 9 Jul 2021 10:41:24 +0200 Subject: [PATCH] Userland: Add ability to screenshot rectangular region in `shot` (#8515) * LibGUI: Verify m_window_id is not-zero in set_maximized Window::set_maximized requires non-zero window id to be a valid call, i.e. calling Window::show beforehand. A verify statement before the server call can help developers by hinting correct usage. * LibGUI: Paint background when the fullscreen window is transparent The windows in the background are ignored when the window is fullscreen. However, we still would like to see the background if that window is transparent. * Userland: Add ability to capture rectangular region in shot A click and drag selectable, transparent, fullscreen window is displayed with the command line argument -r for screenshots. --- Userland/Libraries/LibGUI/Window.cpp | 1 + Userland/Services/WindowServer/Compositor.cpp | 9 +- Userland/Utilities/shot.cpp | 99 ++++++++++++++++++- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibGUI/Window.cpp b/Userland/Libraries/LibGUI/Window.cpp index 019737f006..c2b29d3e7e 100644 --- a/Userland/Libraries/LibGUI/Window.cpp +++ b/Userland/Libraries/LibGUI/Window.cpp @@ -948,6 +948,7 @@ bool Window::is_maximized() const void Window::set_maximized(bool maximized) { + VERIFY(m_window_id != 0); WindowServerConnection::the().async_set_maximized(m_window_id, maximized); } diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index f46c925976..c1ae95202d 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -504,7 +504,8 @@ void Compositor::compose() // Paint the window stack. if (m_invalidated_window) { - if (auto* fullscreen_window = wm.active_fullscreen_window()) { + auto* fullscreen_window = wm.active_fullscreen_window(); + if (fullscreen_window && fullscreen_window->is_opaque()) { compose_window(*fullscreen_window); fullscreen_window->clear_dirty_rects(); } else { @@ -1071,7 +1072,8 @@ void Compositor::recompute_occlusions() bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr; auto& main_screen = Screen::main(); - if (auto* fullscreen_window = wm.active_fullscreen_window()) { + auto* fullscreen_window = wm.active_fullscreen_window(); + if (fullscreen_window) { // TODO: support fullscreen windows on all screens auto screen_rect = main_screen.rect(); wm.for_each_visible_window_from_front_to_back([&](Window& w) { @@ -1099,7 +1101,8 @@ void Compositor::recompute_occlusions() }); m_opaque_wallpaper_rects.clear(); - } else { + } + if (!fullscreen_window || (fullscreen_window && !fullscreen_window->is_opaque())) { Gfx::DisjointRectSet visible_rects; visible_rects.add_many(Screen::rects()); bool have_transparent = false; diff --git a/Userland/Utilities/shot.cpp b/Userland/Utilities/shot.cpp index 2a4cffe76a..6ccd4e9433 100644 --- a/Userland/Utilities/shot.cpp +++ b/Userland/Utilities/shot.cpp @@ -1,19 +1,91 @@ /* * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021, Aziz Berkay Yesilyurt * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include #include #include #include #include #include +#include +#include +#include #include #include +#include #include +class SelectableLayover final : public GUI::Widget { + C_OBJECT(SelectableLayover) +public: + SelectableLayover(GUI::Window* window) + : m_window(window) + , m_background_color(palette().threed_highlight()) + { + set_override_cursor(Gfx::StandardCursor::Crosshair); + } + + virtual ~SelectableLayover() override {}; + + Gfx::IntRect region() const + { + return m_region; + } + +private: + virtual void mousedown_event(GUI::MouseEvent& event) override + { + if (event.button() == GUI::MouseButton::Left) + m_anchor_point = event.position(); + }; + + virtual void mousemove_event(GUI::MouseEvent& event) override + { + if (m_anchor_point.has_value()) { + m_region = Gfx::IntRect::from_two_points(*m_anchor_point, event.position()); + update(); + } + }; + + virtual void mouseup_event(GUI::MouseEvent& event) override + { + if (event.button() == GUI::MouseButton::Left) + m_window->close(); + }; + + virtual void paint_event(GUI::PaintEvent&) override + { + if (m_region.is_empty()) { + GUI::Painter painter(*this); + painter.fill_rect(m_window->rect(), m_background_color); + return; + } + + GUI::Painter painter(*this); + painter.fill_rect(m_region, Gfx::Color::Transparent); + for (auto rect : m_window->rect().shatter(m_region)) + painter.fill_rect(rect, m_background_color); + } + + virtual void keydown_event(GUI::KeyEvent& event) override + { + if (event.key() == Key_Escape) { + m_region = Gfx::IntRect(); + m_window->close(); + } + } + + Optional m_anchor_point; + Gfx::IntRect m_region; + GUI::Window* m_window = nullptr; + Gfx::Color const m_background_color; +}; + int main(int argc, char** argv) { Core::ArgsParser args_parser; @@ -21,12 +93,14 @@ int main(int argc, char** argv) String output_path; bool output_to_clipboard = false; unsigned delay = 0; + bool select_region = false; 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.add_option(select_region, "Select a region to capture", "region", 'r'); args_parser.parse(argc, argv); @@ -35,6 +109,25 @@ int main(int argc, char** argv) } auto app = GUI::Application::construct(argc, argv); + Gfx::IntRect crop_region; + if (select_region) { + auto window = GUI::Window::construct(); + auto& container = window->set_main_widget(window); + container.set_fill_with_background_color(true); + + window->set_title("shot"); + window->set_opacity(0.2); + window->set_fullscreen(true); + window->show(); + app->exec(); + + crop_region = container.region(); + if (crop_region.is_empty()) { + dbgln("cancelled..."); + return 0; + } + } + sleep(delay); Optional screen_index; if (screen >= 0) @@ -43,12 +136,16 @@ int main(int argc, char** argv) auto shared_bitmap = GUI::WindowServerConnection::the().get_screen_bitmap({}, screen_index); dbgln("got screenshot"); - auto* bitmap = shared_bitmap.bitmap(); + RefPtr bitmap = shared_bitmap.bitmap(); if (!bitmap) { warnln("Failed to grab screenshot"); return 1; } + if (select_region) { + bitmap = bitmap->cropped(crop_region); + } + if (output_to_clipboard) { GUI::Clipboard::the().set_bitmap(*bitmap); return 0;