main.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Mohsan Ali <mohsan0073@gmail.com>
  4. * Copyright (c) 2023, Caoimhe Byrne <caoimhebyrne06@gmail.com>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include "MainWidget.h"
  9. #include "ViewWidget.h"
  10. #include <AK/URL.h>
  11. #include <LibConfig/Client.h>
  12. #include <LibCore/ArgsParser.h>
  13. #include <LibCore/System.h>
  14. #include <LibDesktop/Launcher.h>
  15. #include <LibFileSystemAccessClient/Client.h>
  16. #include <LibGUI/Action.h>
  17. #include <LibGUI/ActionGroup.h>
  18. #include <LibGUI/Application.h>
  19. #include <LibGUI/BoxLayout.h>
  20. #include <LibGUI/Clipboard.h>
  21. #include <LibGUI/Desktop.h>
  22. #include <LibGUI/FilePicker.h>
  23. #include <LibGUI/Label.h>
  24. #include <LibGUI/Menu.h>
  25. #include <LibGUI/Menubar.h>
  26. #include <LibGUI/MessageBox.h>
  27. #include <LibGUI/Toolbar.h>
  28. #include <LibGUI/ToolbarContainer.h>
  29. #include <LibGUI/Window.h>
  30. #include <LibGfx/Bitmap.h>
  31. #include <LibGfx/Palette.h>
  32. #include <LibGfx/Rect.h>
  33. #include <LibMain/Main.h>
  34. #include <serenity.h>
  35. #include <string.h>
  36. using namespace ImageViewer;
  37. ErrorOr<int> serenity_main(Main::Arguments arguments)
  38. {
  39. TRY(Core::System::pledge("stdio recvfd sendfd rpath wpath cpath unix thread"));
  40. auto app = TRY(GUI::Application::create(arguments));
  41. Config::pledge_domains({ "ImageViewer", "WindowManager" });
  42. app->set_config_domain("ImageViewer"_string);
  43. TRY(Desktop::Launcher::add_allowed_handler_with_any_url("/bin/ImageViewer"));
  44. TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_scheme("/usr/share/man/man1/Applications/ImageViewer.md") }));
  45. TRY(Desktop::Launcher::seal_allowlist());
  46. // FIXME: Use unveil when we solve the issue with ViewWidget::load_files_from_directory, an explanation is given in ViewWidget.cpp
  47. // TRY(Core::System::unveil("/tmp/session/%sid/portal/filesystemaccess", "rw"));
  48. // TRY(Core::System::unveil("/tmp/session/%sid/portal/image", "rw"));
  49. // TRY(Core::System::unveil("/res", "r"));
  50. // TRY(Core::System::unveil(nullptr, nullptr));
  51. auto app_icon = GUI::Icon::default_icon("filetype-image"sv);
  52. StringView path;
  53. Core::ArgsParser args_parser;
  54. args_parser.add_positional_argument(path, "The image file to be displayed.", "file", Core::ArgsParser::Required::No);
  55. args_parser.parse(arguments);
  56. auto window = TRY(GUI::Window::try_create());
  57. window->set_double_buffering_enabled(true);
  58. window->resize(300, 200);
  59. window->set_icon(app_icon.bitmap_for_size(16));
  60. window->set_title("Image Viewer");
  61. auto root_widget = TRY(window->set_main_widget<MainWidget>());
  62. auto toolbar_container = TRY(root_widget->try_add<GUI::ToolbarContainer>());
  63. auto main_toolbar = TRY(toolbar_container->try_add<GUI::Toolbar>());
  64. auto widget = TRY(root_widget->try_add<ViewWidget>());
  65. widget->on_scale_change = [&](float scale) {
  66. if (!widget->image()) {
  67. window->set_title("Image Viewer");
  68. return;
  69. }
  70. window->set_title(DeprecatedString::formatted("{} {} {}% - Image Viewer", widget->path(), widget->image()->size().to_deprecated_string(), (int)(scale * 100)));
  71. if (!widget->scaled_for_first_image()) {
  72. widget->set_scaled_for_first_image(true);
  73. widget->resize_window();
  74. }
  75. };
  76. widget->on_drop = [&](auto& event) {
  77. if (!event.mime_data().has_urls())
  78. return;
  79. auto urls = event.mime_data().urls();
  80. if (urls.is_empty())
  81. return;
  82. window->move_to_front();
  83. auto path = urls.first().serialize_path();
  84. auto result = FileSystemAccessClient::Client::the().request_file_read_only_approved(window, path);
  85. if (result.is_error())
  86. return;
  87. auto value = result.release_value();
  88. widget->open_file(value.filename(), value.stream());
  89. for (size_t i = 1; i < urls.size(); ++i) {
  90. Desktop::Launcher::open(URL::create_with_file_scheme(urls[i].serialize_path().characters()), "/bin/ImageViewer");
  91. }
  92. };
  93. widget->on_doubleclick = [&] {
  94. window->set_fullscreen(!window->is_fullscreen());
  95. toolbar_container->set_visible(!window->is_fullscreen());
  96. widget->set_frame_style(window->is_fullscreen() ? Gfx::FrameStyle::NoFrame : Gfx::FrameStyle::SunkenContainer);
  97. };
  98. // Actions
  99. auto open_action = GUI::CommonActions::make_open_action(
  100. [&](auto&) {
  101. FileSystemAccessClient::OpenFileOptions options {
  102. .window_title = "Open Image"sv,
  103. .allowed_file_types = Vector { GUI::FileTypeFilter::image_files(), GUI::FileTypeFilter::all_files() },
  104. };
  105. auto result = FileSystemAccessClient::Client::the().open_file(window, options);
  106. if (result.is_error())
  107. return;
  108. auto value = result.release_value();
  109. widget->open_file(value.filename(), value.stream());
  110. });
  111. auto delete_action = GUI::CommonActions::make_delete_action(
  112. [&](auto&) {
  113. auto path = widget->path();
  114. if (path.is_empty())
  115. return;
  116. auto msgbox_result = GUI::MessageBox::show(window,
  117. DeprecatedString::formatted("Are you sure you want to delete {}?", path),
  118. "Confirm Deletion"sv,
  119. GUI::MessageBox::Type::Warning,
  120. GUI::MessageBox::InputType::OKCancel);
  121. if (msgbox_result == GUI::MessageBox::ExecResult::Cancel)
  122. return;
  123. auto unlinked_or_error = Core::System::unlink(widget->path());
  124. if (unlinked_or_error.is_error()) {
  125. GUI::MessageBox::show(window,
  126. DeprecatedString::formatted("unlink({}) failed: {}", path, unlinked_or_error.error()),
  127. "Delete Failed"sv,
  128. GUI::MessageBox::Type::Error);
  129. return;
  130. }
  131. widget->clear();
  132. });
  133. auto quit_action = GUI::CommonActions::make_quit_action(
  134. [&](auto&) {
  135. app->quit();
  136. });
  137. auto rotate_counterclockwise_action = GUI::CommonActions::make_rotate_counterclockwise_action([&](auto&) {
  138. widget->rotate(Gfx::RotationDirection::CounterClockwise);
  139. });
  140. auto rotate_clockwise_action = GUI::CommonActions::make_rotate_clockwise_action([&](auto&) {
  141. widget->rotate(Gfx::RotationDirection::Clockwise);
  142. });
  143. auto vertical_flip_action = GUI::Action::create("Flip &Vertically", { Mod_None, Key_V }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-flip-vertical.png"sv)),
  144. [&](auto&) {
  145. widget->flip(Gfx::Orientation::Vertical);
  146. });
  147. auto horizontal_flip_action = GUI::Action::create("Flip &Horizontally", { Mod_None, Key_H }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/edit-flip-horizontal.png"sv)),
  148. [&](auto&) {
  149. widget->flip(Gfx::Orientation::Horizontal);
  150. });
  151. auto desktop_wallpaper_action = GUI::Action::create("Set as Desktop &Wallpaper", TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-display-settings.png"sv)),
  152. [&](auto&) {
  153. if (!GUI::Desktop::the().set_wallpaper(widget->image()->bitmap(GUI::Desktop::the().rect().size()).release_value_but_fixme_should_propagate_errors(), widget->path())) {
  154. GUI::MessageBox::show(window,
  155. DeprecatedString::formatted("set_wallpaper({}) failed", widget->path()),
  156. "Could not set wallpaper"sv,
  157. GUI::MessageBox::Type::Error);
  158. }
  159. });
  160. auto go_first_action = GUI::Action::create("&Go to First", { Mod_None, Key_Home }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-first.png"sv)),
  161. [&](auto&) {
  162. widget->navigate(ViewWidget::Directions::First);
  163. });
  164. auto go_back_action = GUI::Action::create("Go to &Previous", { Mod_None, Key_Left }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv)),
  165. [&](auto&) {
  166. widget->navigate(ViewWidget::Directions::Back);
  167. });
  168. auto go_forward_action = GUI::Action::create("Go to &Next", { Mod_None, Key_Right }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv)),
  169. [&](auto&) {
  170. widget->navigate(ViewWidget::Directions::Forward);
  171. });
  172. auto go_last_action = GUI::Action::create("Go to &Last", { Mod_None, Key_End }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-last.png"sv)),
  173. [&](auto&) {
  174. widget->navigate(ViewWidget::Directions::Last);
  175. });
  176. auto full_screen_action = GUI::CommonActions::make_fullscreen_action(
  177. [&](auto&) {
  178. widget->on_doubleclick();
  179. });
  180. auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
  181. [&](auto&) {
  182. widget->set_scale(widget->scale() * 1.44f);
  183. },
  184. window);
  185. auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
  186. [&](auto&) {
  187. widget->set_scale(1.f);
  188. },
  189. window);
  190. auto fit_image_to_view_action = GUI::Action::create(
  191. "Fit Image To &View", TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/fit-image-to-view.png"sv)), [&](auto&) {
  192. widget->fit_content_to_view();
  193. });
  194. auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
  195. [&](auto&) {
  196. widget->set_scale(widget->scale() / 1.44f);
  197. },
  198. window);
  199. auto hide_show_toolbar_action = GUI::Action::create_checkable("&Toolbar", { Mod_Ctrl, Key_T },
  200. [&](auto& action) {
  201. toolbar_container->set_visible(action.is_checked());
  202. });
  203. hide_show_toolbar_action->set_checked(true);
  204. auto copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  205. if (widget->image())
  206. GUI::Clipboard::the().set_bitmap(*widget->image()->bitmap({}).release_value_but_fixme_should_propagate_errors());
  207. });
  208. auto nearest_neighbor_action = GUI::Action::create_checkable("&Nearest Neighbor", [&](auto&) {
  209. widget->set_scaling_mode(Gfx::Painter::ScalingMode::NearestNeighbor);
  210. });
  211. nearest_neighbor_action->set_checked(true);
  212. auto smooth_pixels_action = GUI::Action::create_checkable("&Smooth Pixels", [&](auto&) {
  213. widget->set_scaling_mode(Gfx::Painter::ScalingMode::SmoothPixels);
  214. });
  215. auto bilinear_action = GUI::Action::create_checkable("&Bilinear", [&](auto&) {
  216. widget->set_scaling_mode(Gfx::Painter::ScalingMode::BilinearBlend);
  217. });
  218. auto box_sampling_action = GUI::Action::create_checkable("B&ox Sampling", [&](auto&) {
  219. widget->set_scaling_mode(Gfx::Painter::ScalingMode::BoxSampling);
  220. });
  221. widget->on_image_change = [&](Image const* image) {
  222. bool should_enable_image_actions = (image != nullptr);
  223. bool should_enable_forward_actions = (widget->is_next_available() && should_enable_image_actions);
  224. bool should_enable_backward_actions = (widget->is_previous_available() && should_enable_image_actions);
  225. delete_action->set_enabled(should_enable_image_actions);
  226. rotate_counterclockwise_action->set_enabled(should_enable_image_actions);
  227. rotate_clockwise_action->set_enabled(should_enable_image_actions);
  228. vertical_flip_action->set_enabled(should_enable_image_actions);
  229. horizontal_flip_action->set_enabled(should_enable_image_actions);
  230. desktop_wallpaper_action->set_enabled(should_enable_image_actions);
  231. go_first_action->set_enabled(should_enable_backward_actions);
  232. go_back_action->set_enabled(should_enable_backward_actions);
  233. go_forward_action->set_enabled(should_enable_forward_actions);
  234. go_last_action->set_enabled(should_enable_forward_actions);
  235. zoom_in_action->set_enabled(should_enable_image_actions);
  236. reset_zoom_action->set_enabled(should_enable_image_actions);
  237. zoom_out_action->set_enabled(should_enable_image_actions);
  238. if (!should_enable_image_actions) {
  239. window->set_title("Image Viewer");
  240. }
  241. };
  242. main_toolbar->add_action(open_action);
  243. main_toolbar->add_action(delete_action);
  244. main_toolbar->add_separator();
  245. main_toolbar->add_action(go_first_action);
  246. main_toolbar->add_action(go_back_action);
  247. main_toolbar->add_action(go_forward_action);
  248. main_toolbar->add_action(go_last_action);
  249. main_toolbar->add_separator();
  250. main_toolbar->add_action(zoom_in_action);
  251. main_toolbar->add_action(reset_zoom_action);
  252. main_toolbar->add_action(zoom_out_action);
  253. auto file_menu = window->add_menu("&File"_string);
  254. file_menu->add_action(open_action);
  255. file_menu->add_action(delete_action);
  256. file_menu->add_separator();
  257. file_menu->add_recent_files_list([&](auto& action) {
  258. auto path = action.text();
  259. auto result = FileSystemAccessClient::Client::the().request_file_read_only_approved(window, path);
  260. if (result.is_error())
  261. return;
  262. auto value = result.release_value();
  263. widget->open_file(value.filename(), value.stream());
  264. });
  265. file_menu->add_action(quit_action);
  266. auto image_menu = window->add_menu("&Image"_string);
  267. image_menu->add_action(rotate_counterclockwise_action);
  268. image_menu->add_action(rotate_clockwise_action);
  269. image_menu->add_action(vertical_flip_action);
  270. image_menu->add_action(horizontal_flip_action);
  271. image_menu->add_separator();
  272. image_menu->add_action(desktop_wallpaper_action);
  273. auto navigate_menu = window->add_menu("&Navigate"_string);
  274. navigate_menu->add_action(go_first_action);
  275. navigate_menu->add_action(go_back_action);
  276. navigate_menu->add_action(go_forward_action);
  277. navigate_menu->add_action(go_last_action);
  278. auto view_menu = window->add_menu("&View"_string);
  279. view_menu->add_action(full_screen_action);
  280. view_menu->add_separator();
  281. view_menu->add_action(zoom_in_action);
  282. view_menu->add_action(reset_zoom_action);
  283. view_menu->add_action(fit_image_to_view_action);
  284. view_menu->add_action(zoom_out_action);
  285. view_menu->add_separator();
  286. auto scaling_mode_menu = view_menu->add_submenu("&Scaling Mode"_string);
  287. scaling_mode_menu->set_icon(TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/scale.png"sv)));
  288. auto scaling_mode_group = make<GUI::ActionGroup>();
  289. scaling_mode_group->set_exclusive(true);
  290. scaling_mode_group->add_action(*nearest_neighbor_action);
  291. scaling_mode_group->add_action(*smooth_pixels_action);
  292. scaling_mode_group->add_action(*bilinear_action);
  293. scaling_mode_group->add_action(*box_sampling_action);
  294. scaling_mode_menu->add_action(nearest_neighbor_action);
  295. scaling_mode_menu->add_action(smooth_pixels_action);
  296. scaling_mode_menu->add_action(bilinear_action);
  297. scaling_mode_menu->add_action(box_sampling_action);
  298. view_menu->add_separator();
  299. view_menu->add_action(hide_show_toolbar_action);
  300. auto help_menu = window->add_menu("&Help"_string);
  301. help_menu->add_action(GUI::CommonActions::make_command_palette_action(window));
  302. help_menu->add_action(GUI::CommonActions::make_help_action([](auto&) {
  303. Desktop::Launcher::open(URL::create_with_file_scheme("/usr/share/man/man1/Applications/ImageViewer.md"), "/bin/Help");
  304. }));
  305. help_menu->add_action(GUI::CommonActions::make_about_action("Image Viewer"_string, app_icon, window));
  306. window->show();
  307. // We must do this here and not any earlier, as we need a visible window to call FileSystemAccessClient::Client::request_file_read_only_approved();
  308. if (path != nullptr) {
  309. auto result = FileSystemAccessClient::Client::the().request_file_read_only_approved(window, path);
  310. if (result.is_error())
  311. return 1;
  312. auto value = result.release_value();
  313. widget->open_file(value.filename(), value.stream());
  314. } else {
  315. widget->clear();
  316. }
  317. return app->exec();
  318. }