1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 08:14:58 +00:00
serenity/Userland/Utilities/shot.cpp
Tim Schumacher d5871f5717 AK: Rename Stream::{read,write} to Stream::{read_some,write_some}
Similar to POSIX read, the basic read and write functions of AK::Stream
do not have a lower limit of how much data they read or write (apart
from "none at all").

Rename the functions to "read some [data]" and "write some [data]" (with
"data" being omitted, since everything here is reading and writing data)
to make them sufficiently distinct from the functions that ensure to
use the entire buffer (which should be the go-to function for most
usages).

No functional changes, just a lot of new FIXMEs.
2023-03-13 15:16:20 +00:00

197 lines
5.9 KiB
C++

/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Aziz Berkay Yesilyurt <abyesilyurt@gmail.com>
* Copyright (c) 2022, Alex Major
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/Optional.h>
#include <AK/URL.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/DateTime.h>
#include <LibCore/DeprecatedFile.h>
#include <LibCore/Process.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/ConnectionToWindowServer.h>
#include <LibGUI/Painter.h>
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
#include <LibGfx/PNGWriter.h>
#include <LibGfx/Palette.h>
#include <LibMain/Main.h>
#include <unistd.h>
class SelectableLayover final : public GUI::Widget {
C_OBJECT(SelectableLayover)
public:
virtual ~SelectableLayover() override {};
Gfx::IntRect region() const
{
return m_region;
}
private:
SelectableLayover(GUI::Window* window)
: m_window(window)
, m_background_color(palette().threed_highlight().with_alpha(128))
{
set_override_cursor(Gfx::StandardCursor::Crosshair);
}
virtual void mousedown_event(GUI::MouseEvent& event) override
{
if (event.button() == GUI::MouseButton::Primary)
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::Primary)
m_window->close();
};
virtual void paint_event(GUI::PaintEvent&) override
{
GUI::Painter painter(*this);
painter.clear_rect(m_window->rect(), Gfx::Color::Transparent);
painter.fill_rect(m_window->rect(), m_background_color);
if (m_region.is_empty())
return;
painter.clear_rect(m_region, Gfx::Color::Transparent);
}
virtual void keydown_event(GUI::KeyEvent& event) override
{
if (event.key() == Key_Escape) {
m_region = Gfx::IntRect();
m_window->close();
}
}
Optional<Gfx::IntPoint> m_anchor_point;
Gfx::IntRect m_region;
GUI::Window* m_window = nullptr;
Gfx::Color const m_background_color;
};
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Core::ArgsParser args_parser;
DeprecatedString output_path;
bool output_to_clipboard = false;
unsigned delay = 0;
bool select_region = false;
bool edit_image = 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.add_option(edit_image, "Open in PixelPaint", "edit", 'e');
args_parser.parse(arguments);
if (output_path.is_empty()) {
output_path = Core::DateTime::now().to_deprecated_string("screenshot-%Y-%m-%d-%H-%M-%S.png"sv);
}
auto app = TRY(GUI::Application::try_create(arguments));
Optional<Gfx::IntRect> crop_region;
if (select_region) {
auto window = GUI::Window::construct();
auto container = TRY(window->set_main_widget<SelectableLayover>(window));
window->set_title("shot");
window->set_has_alpha_channel(true);
window->set_fullscreen(true);
window->show();
app->exec();
crop_region = container->region();
if (crop_region.value().is_empty()) {
dbgln("cancelled...");
return 0;
}
}
sleep(delay);
Optional<u32> screen_index;
if (screen >= 0)
screen_index = (u32)screen;
dbgln("getting screenshot...");
auto shared_bitmap = GUI::ConnectionToWindowServer::the().get_screen_bitmap(crop_region, screen_index);
dbgln("got screenshot");
RefPtr<Gfx::Bitmap> bitmap = shared_bitmap.bitmap();
if (!bitmap) {
warnln("Failed to grab screenshot");
return 1;
}
if (output_to_clipboard) {
GUI::Clipboard::the().set_bitmap(*bitmap);
return 0;
}
auto encoded_bitmap_or_error = Gfx::PNGWriter::encode(*bitmap);
if (encoded_bitmap_or_error.is_error()) {
warnln("Failed to encode PNG");
return 1;
}
auto encoded_bitmap = encoded_bitmap_or_error.release_value();
if (edit_image)
output_path = Core::DateTime::now().to_deprecated_string("/tmp/screenshot-%Y-%m-%d-%H-%M-%S.png"sv);
auto file_or_error = Core::File::open(output_path, Core::File::OpenMode::ReadWrite);
if (file_or_error.is_error()) {
warnln("Could not open '{}' for writing: {}", output_path, file_or_error.error());
return 1;
}
auto& file = *file_or_error.value();
// FIXME: This should write the entire span.
TRY(file.write_some(encoded_bitmap.bytes()));
if (edit_image)
TRY(Core::Process::spawn("/bin/PixelPaint"sv, Array { output_path }));
bool printed_hyperlink = false;
if (isatty(STDOUT_FILENO)) {
auto full_path = Core::DeprecatedFile::real_path_for(output_path);
if (!full_path.is_null()) {
char hostname[HOST_NAME_MAX];
VERIFY(gethostname(hostname, sizeof(hostname)) == 0);
auto url = URL::create_with_file_scheme(full_path, {}, hostname);
out("\033]8;;{}\033\\", url.serialize());
printed_hyperlink = true;
}
}
out("{}", output_path);
if (printed_hyperlink) {
out("\033]8;;\033\\");
}
outln("");
return 0;
}