diff --git a/Base/res/apps/Screenshot.af b/Base/res/apps/Screenshot.af new file mode 100644 index 0000000000..13143a6ff9 --- /dev/null +++ b/Base/res/apps/Screenshot.af @@ -0,0 +1,4 @@ +[App] +Name=&Screenshot +Executable=/bin/Screenshot +Category=&Utilities diff --git a/Base/res/apps/SpaceAnalyzer.af b/Base/res/apps/SpaceAnalyzer.af index af36112e8c..eadf2ca4d3 100644 --- a/Base/res/apps/SpaceAnalyzer.af +++ b/Base/res/apps/SpaceAnalyzer.af @@ -1,4 +1,4 @@ [App] -Name=&Space Analyzer +Name=Space Ana&lyzer Executable=/bin/SpaceAnalyzer Category=&Utilities diff --git a/Base/res/icons/16x16/app-screenshot.png b/Base/res/icons/16x16/app-screenshot.png new file mode 100644 index 0000000000..9f193e69a9 Binary files /dev/null and b/Base/res/icons/16x16/app-screenshot.png differ diff --git a/Base/res/icons/32x32/app-screenshot.png b/Base/res/icons/32x32/app-screenshot.png new file mode 100644 index 0000000000..aca82dd84a Binary files /dev/null and b/Base/res/icons/32x32/app-screenshot.png differ diff --git a/Userland/Applications/CMakeLists.txt b/Userland/Applications/CMakeLists.txt index 41884484ce..01c1ad480f 100644 --- a/Userland/Applications/CMakeLists.txt +++ b/Userland/Applications/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory(Piano) add_subdirectory(PixelPaint) add_subdirectory(Presenter) add_subdirectory(Run) +add_subdirectory(Screenshot) add_subdirectory(Settings) add_subdirectory(SoundPlayer) add_subdirectory(SpaceAnalyzer) diff --git a/Userland/Applications/Screenshot/CMakeLists.txt b/Userland/Applications/Screenshot/CMakeLists.txt new file mode 100644 index 0000000000..bf066a9eb6 --- /dev/null +++ b/Userland/Applications/Screenshot/CMakeLists.txt @@ -0,0 +1,16 @@ +serenity_component( + Screenshot + RECOMMENDED + TARGETS Screenshot +) + +compile_gml(Screenshot.gml ScreenshotGML.cpp) + +set(SOURCES + ScreenshotGML.cpp + MainWindow.cpp + main.cpp +) + +serenity_app(Screenshot ICON app-screenshot) +target_link_libraries(Screenshot PRIVATE LibCore LibConfig LibGfx LibGUI LibMain) diff --git a/Userland/Applications/Screenshot/MainWidget.h b/Userland/Applications/Screenshot/MainWidget.h new file mode 100644 index 0000000000..4ec71e917f --- /dev/null +++ b/Userland/Applications/Screenshot/MainWidget.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, circl + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Screenshot { + +class MainWidget : public GUI::Widget { + C_OBJECT(MainWidget) +public: + MainWidget() = default; + static ErrorOr> try_create(); + + virtual ~MainWidget() override = default; +}; + +} diff --git a/Userland/Applications/Screenshot/MainWindow.cpp b/Userland/Applications/Screenshot/MainWindow.cpp new file mode 100644 index 0000000000..e11a43de39 --- /dev/null +++ b/Userland/Applications/Screenshot/MainWindow.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, circl + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MainWindow.h" +#include "MainWidget.h" +#include +#include +#include +#include +#include + +namespace Screenshot { + +MainWindow::MainWindow() +{ + auto app_icon = GUI::Icon::default_icon("app-screenshot"sv); + + set_title("Screenshot"); + set_icon(app_icon.bitmap_for_size(16)); + resize(300, 150); + set_resizable(false); + set_minimizable(false); + + auto main_widget = MUST(MainWidget::try_create()); + set_main_widget(main_widget); + + m_ok_button = *main_widget->find_descendant_of_type_named("ok_button"); + m_ok_button->set_default(true); + m_ok_button->on_click = [this](auto) { + take_screenshot(); + }; + + m_cancel_button = *main_widget->find_descendant_of_type_named("cancel_button"); + m_cancel_button->on_click = [this](auto) { + close(); + }; + + m_browse = *main_widget->find_descendant_of_type_named("browse"); + m_browse->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/open.png"sv).release_value_but_fixme_should_propagate_errors()); + m_browse->on_click = [this](auto) { + auto filepath = GUI::FilePicker::get_open_filepath(this, "Save screenshot to...", m_destination->text(), true); + + if (filepath.has_value()) { + Config::write_string("Screenshot"sv, "General"sv, "SavePath"sv, filepath.value()); + m_destination->set_text(filepath.value()); + m_destination->repaint(); + } + }; + + m_selected_area = *main_widget->find_descendant_of_type_named("selected_area"); + + m_edit_in_pixel_paint = *main_widget->find_descendant_of_type_named("edit_in_pixel_paint"); + m_edit_in_pixel_paint->on_checked = [this](bool is_checked) { + m_browse->set_enabled(!is_checked); + m_destination->set_enabled(!is_checked); + }; + + m_destination = *main_widget->find_descendant_of_type_named("destination"); + m_destination->set_text(Config::read_string("Screenshot"sv, "General"sv, "SavePath"sv, Core::StandardPaths::pictures_directory())); +} + +void MainWindow::take_screenshot() +{ + close(); + + Vector arguments; + + if (m_selected_area->is_checked()) + arguments.append("-r"sv); + + if (m_edit_in_pixel_paint->is_checked()) + arguments.append("-e"sv); + + // FIXME: Place common screenshot code into library and use that + MUST(Core::Process::spawn("/bin/shot"sv, arguments, m_destination->text(), Core::Process::KeepAsChild::No)); +} + +} diff --git a/Userland/Applications/Screenshot/MainWindow.h b/Userland/Applications/Screenshot/MainWindow.h new file mode 100644 index 0000000000..f869445063 --- /dev/null +++ b/Userland/Applications/Screenshot/MainWindow.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, circl + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Screenshot { + +class MainWindow final : public GUI::Window { + C_OBJECT(MainWindow) +public: + virtual ~MainWindow() override = default; + +private: + MainWindow(); + + void take_screenshot(); + + RefPtr m_ok_button; + RefPtr m_cancel_button; + RefPtr m_browse; + RefPtr m_selected_area; + RefPtr m_edit_in_pixel_paint; + RefPtr m_destination; +}; + +} diff --git a/Userland/Applications/Screenshot/Screenshot.gml b/Userland/Applications/Screenshot/Screenshot.gml new file mode 100644 index 0000000000..5ebf0b7ad2 --- /dev/null +++ b/Userland/Applications/Screenshot/Screenshot.gml @@ -0,0 +1,80 @@ +@Screenshot::MainWidget { + fill_with_background_color: true + layout: @GUI::HorizontalBoxLayout { + margins: [6, 4, 4, 4] + spacing: 6 + } + + @GUI::Widget { + max_width: 32 + layout: @GUI::VerticalBoxLayout {} + + @GUI::ImageWidget { + bitmap: "/res/icons/32x32/app-screenshot.png" + } + } + + @GUI::Widget { + layout: @GUI::VerticalBoxLayout {} + + @GUI::GroupBox { + title: "Take screenshot of:" + layout: @GUI::VerticalBoxLayout { + margins: [4] + spacing: 0 + } + + @GUI::RadioButton { + name: "whole_desktop" + text: "Whole desktop" + checked: true + } + + @GUI::RadioButton { + name: "selected_area" + text: "Selected area" + } + } + + @GUI::Widget { + layout: @GUI::VerticalBoxLayout { + margins: [0, 0, 0, 8] + } + + @GUI::CheckBox { + name: "edit_in_pixel_paint" + text: "Edit in Pixel Paint" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout {} + + @GUI::TextBox { + name: "destination" + mode: "DisplayOnly" + } + + @GUI::Button { + name: "browse" + max_width: 22 + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout {} + + @GUI::Layout::Spacer {} + + @GUI::DialogButton { + name: "ok_button" + text: "OK" + } + + @GUI::DialogButton { + name: "cancel_button" + text: "Cancel" + } + } + } +} diff --git a/Userland/Applications/Screenshot/main.cpp b/Userland/Applications/Screenshot/main.cpp new file mode 100644 index 0000000000..bc1e06bd24 --- /dev/null +++ b/Userland/Applications/Screenshot/main.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, circl + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MainWindow.h" +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments arguments) +{ + TRY(Core::System::pledge("stdio recvfd sendfd thread cpath rpath wpath unix proc exec")); + + auto app = TRY(GUI::Application::create(arguments)); + app->set_config_domain("Screenshot"_string); + + auto window = TRY(Screenshot::MainWindow::try_create()); + + window->show(); + + return app->exec(); +}