Serenity Operating System
at master 196 lines 6.0 kB view raw
1/* 2 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Aziz Berkay Yesilyurt <abyesilyurt@gmail.com> 4 * Copyright (c) 2022, Alex Major 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/Format.h> 10#include <AK/Optional.h> 11#include <AK/URL.h> 12#include <LibCore/ArgsParser.h> 13#include <LibCore/DateTime.h> 14#include <LibCore/DeprecatedFile.h> 15#include <LibCore/Process.h> 16#include <LibGUI/Application.h> 17#include <LibGUI/Clipboard.h> 18#include <LibGUI/ConnectionToWindowServer.h> 19#include <LibGUI/Painter.h> 20#include <LibGUI/Widget.h> 21#include <LibGUI/Window.h> 22#include <LibGfx/PNGWriter.h> 23#include <LibGfx/Palette.h> 24#include <LibMain/Main.h> 25#include <unistd.h> 26 27class SelectableLayover final : public GUI::Widget { 28 C_OBJECT(SelectableLayover) 29public: 30 virtual ~SelectableLayover() override {}; 31 32 Gfx::IntRect region() const 33 { 34 return m_region; 35 } 36 37private: 38 SelectableLayover(GUI::Window* window) 39 : m_window(window) 40 , m_background_color(palette().threed_highlight().with_alpha(128)) 41 { 42 set_override_cursor(Gfx::StandardCursor::Crosshair); 43 } 44 45 virtual void mousedown_event(GUI::MouseEvent& event) override 46 { 47 if (event.button() == GUI::MouseButton::Primary) 48 m_anchor_point = event.position(); 49 }; 50 51 virtual void mousemove_event(GUI::MouseEvent& event) override 52 { 53 if (m_anchor_point.has_value()) { 54 m_region = Gfx::IntRect::from_two_points(*m_anchor_point, event.position()); 55 update(); 56 } 57 }; 58 59 virtual void mouseup_event(GUI::MouseEvent& event) override 60 { 61 if (event.button() == GUI::MouseButton::Primary) 62 m_window->close(); 63 }; 64 65 virtual void paint_event(GUI::PaintEvent&) override 66 { 67 GUI::Painter painter(*this); 68 painter.clear_rect(m_window->rect(), Gfx::Color::Transparent); 69 painter.fill_rect(m_window->rect(), m_background_color); 70 71 if (m_region.is_empty()) 72 return; 73 74 painter.clear_rect(m_region, Gfx::Color::Transparent); 75 } 76 77 virtual void keydown_event(GUI::KeyEvent& event) override 78 { 79 if (event.key() == Key_Escape) { 80 m_region = Gfx::IntRect(); 81 m_window->close(); 82 } 83 } 84 85 Optional<Gfx::IntPoint> m_anchor_point; 86 Gfx::IntRect m_region; 87 GUI::Window* m_window = nullptr; 88 Gfx::Color const m_background_color; 89}; 90 91ErrorOr<int> serenity_main(Main::Arguments arguments) 92{ 93 Core::ArgsParser args_parser; 94 95 DeprecatedString output_path; 96 bool output_to_clipboard = false; 97 unsigned delay = 0; 98 bool select_region = false; 99 bool edit_image = false; 100 int screen = -1; 101 102 args_parser.add_positional_argument(output_path, "Output filename", "output", Core::ArgsParser::Required::No); 103 args_parser.add_option(output_to_clipboard, "Output to clipboard", "clipboard", 'c'); 104 args_parser.add_option(delay, "Seconds to wait before taking a screenshot", "delay", 'd', "seconds"); 105 args_parser.add_option(screen, "The index of the screen (default: -1 for all screens)", "screen", 's', "index"); 106 args_parser.add_option(select_region, "Select a region to capture", "region", 'r'); 107 args_parser.add_option(edit_image, "Open in PixelPaint", "edit", 'e'); 108 109 args_parser.parse(arguments); 110 111 if (output_path.is_empty()) { 112 output_path = Core::DateTime::now().to_deprecated_string("screenshot-%Y-%m-%d-%H-%M-%S.png"sv); 113 } 114 115 auto app = TRY(GUI::Application::try_create(arguments)); 116 Optional<Gfx::IntRect> crop_region; 117 if (select_region) { 118 auto window = GUI::Window::construct(); 119 auto container = TRY(window->set_main_widget<SelectableLayover>(window)); 120 121 window->set_title("shot"); 122 window->set_has_alpha_channel(true); 123 window->set_fullscreen(true); 124 window->show(); 125 app->exec(); 126 127 crop_region = container->region(); 128 if (crop_region.value().is_empty()) { 129 dbgln("cancelled..."); 130 return 0; 131 } 132 } 133 134 sleep(delay); 135 Optional<u32> screen_index; 136 if (screen >= 0) 137 screen_index = (u32)screen; 138 dbgln("getting screenshot..."); 139 auto shared_bitmap = GUI::ConnectionToWindowServer::the().get_screen_bitmap(crop_region, screen_index); 140 dbgln("got screenshot"); 141 142 RefPtr<Gfx::Bitmap> bitmap = shared_bitmap.bitmap(); 143 if (!bitmap) { 144 warnln("Failed to grab screenshot"); 145 return 1; 146 } 147 148 if (output_to_clipboard) { 149 GUI::Clipboard::the().set_bitmap(*bitmap); 150 return 0; 151 } 152 153 auto encoded_bitmap_or_error = Gfx::PNGWriter::encode(*bitmap); 154 if (encoded_bitmap_or_error.is_error()) { 155 warnln("Failed to encode PNG"); 156 return 1; 157 } 158 auto encoded_bitmap = encoded_bitmap_or_error.release_value(); 159 160 if (edit_image) 161 output_path = Core::DateTime::now().to_deprecated_string("/tmp/screenshot-%Y-%m-%d-%H-%M-%S.png"sv); 162 163 auto file_or_error = Core::File::open(output_path, Core::File::OpenMode::ReadWrite); 164 if (file_or_error.is_error()) { 165 warnln("Could not open '{}' for writing: {}", output_path, file_or_error.error()); 166 return 1; 167 } 168 169 auto& file = *file_or_error.value(); 170 TRY(file.write_until_depleted(encoded_bitmap.bytes())); 171 172 if (edit_image) 173 TRY(Core::Process::spawn("/bin/PixelPaint"sv, Array { output_path })); 174 175 bool printed_hyperlink = false; 176 if (isatty(STDOUT_FILENO)) { 177 auto full_path = Core::DeprecatedFile::real_path_for(output_path); 178 if (!full_path.is_null()) { 179 char hostname[HOST_NAME_MAX]; 180 VERIFY(gethostname(hostname, sizeof(hostname)) == 0); 181 182 auto url = URL::create_with_file_scheme(full_path, {}, hostname); 183 out("\033]8;;{}\033\\", url.serialize()); 184 printed_hyperlink = true; 185 } 186 } 187 188 out("{}", output_path); 189 190 if (printed_hyperlink) { 191 out("\033]8;;\033\\"); 192 } 193 194 outln(""); 195 return 0; 196}