diff --git a/Base/etc/shellrc b/Base/etc/shellrc index 506af77c77..72876731e5 100644 --- a/Base/etc/shellrc +++ b/Base/etc/shellrc @@ -27,5 +27,6 @@ alias ws=WebServer alias sl=Solitaire alias wv=WebView alias ue=UserspaceEmulator +alias welcome=Serendipity alias ll='ls -l' diff --git a/Base/home/anon/Documents/tips.txt b/Base/home/anon/Documents/tips.txt new file mode 100644 index 0000000000..7c5447bdcb --- /dev/null +++ b/Base/home/anon/Documents/tips.txt @@ -0,0 +1,10 @@ +# SerenityOS Tips +# Did you know... +Pressing Ctrl+Alt+Space at an insertion caret pops up the emoji picker. +Holding Super while scrolling the mouse wheel over a window adjusts its transparency. +Browser has built-in ad blocking. Filter content by adding new domains to ~/.config/BrowserContentFiltering.txt +Default file and protocol associations can be changed in ~/.config/LaunchServer.ini +Text Editor has multiple viewing modes; edit and preview HTML and Markdown in real time. +It can help to get a second pair of $ Eyes on a problem. Or fifty: $ Eyes -n 100 +Highlighted text in Terminal can be launched or right-clicked for more context. +Focus can be cycled between windows by pressing and holding Super+Tab. Shift reverses the order. diff --git a/Base/res/graphics/welcome-serendipity.png b/Base/res/graphics/welcome-serendipity.png new file mode 100644 index 0000000000..f86ba0cce7 Binary files /dev/null and b/Base/res/graphics/welcome-serendipity.png differ diff --git a/Base/res/icons/16x16/app-serendipity.png b/Base/res/icons/16x16/app-serendipity.png new file mode 100644 index 0000000000..ad71e4cde6 Binary files /dev/null and b/Base/res/icons/16x16/app-serendipity.png differ diff --git a/Base/res/icons/32x32/app-serendipity.png b/Base/res/icons/32x32/app-serendipity.png new file mode 100644 index 0000000000..4edc2736c9 Binary files /dev/null and b/Base/res/icons/32x32/app-serendipity.png differ diff --git a/Userland/Applications/CMakeLists.txt b/Userland/Applications/CMakeLists.txt index 3713e43d80..56b7e0ba85 100644 --- a/Userland/Applications/CMakeLists.txt +++ b/Userland/Applications/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(Piano) add_subdirectory(PixelPaint) add_subdirectory(QuickShow) add_subdirectory(Run) +add_subdirectory(Serendipity) add_subdirectory(SoundPlayer) add_subdirectory(SpaceAnalyzer) add_subdirectory(Spreadsheet) diff --git a/Userland/Applications/Serendipity/CMakeLists.txt b/Userland/Applications/Serendipity/CMakeLists.txt new file mode 100644 index 0000000000..62df8900a0 --- /dev/null +++ b/Userland/Applications/Serendipity/CMakeLists.txt @@ -0,0 +1,11 @@ +compile_gml(SerendipityWindow.gml SerendipityWindowGML.h serendipity_window_gml) + +set(SOURCES + SerendipityWindowGML.h + SerendipityWidget.cpp + SerendipityWidget.h + main.cpp +) + +serenity_app(Serendipity ICON app-serendipity) +target_link_libraries(Serendipity LibGUI LibWeb) diff --git a/Userland/Applications/Serendipity/SerendipityWidget.cpp b/Userland/Applications/Serendipity/SerendipityWidget.cpp new file mode 100644 index 0000000000..2f731a8bfe --- /dev/null +++ b/Userland/Applications/Serendipity/SerendipityWidget.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2021, the SerenityOS Developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "SerendipityWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SerendipityWidget::SerendipityWidget() +{ + load_from_gml(serendipity_window_gml); + + auto& banner_label = *find_descendant_of_type_named("banner_label"); + banner_label.set_icon(Gfx::Bitmap::load_from_file("/res/graphics/welcome-serendipity.png")); + + auto& navigation_column = *find_descendant_of_type_named("navigation_column"); + navigation_column.layout()->add_spacer(); + + auto& nav_separator = navigation_column.add(Gfx::Orientation::Horizontal); + nav_separator.set_max_height(2); + + auto& tip_frame = *find_descendant_of_type_named("tip_frame"); + auto palette = tip_frame.palette(); + palette.set_color(Gfx::ColorRole::Base, Color::from_rgb(0xffffe1)); + tip_frame.set_palette(palette); + tip_frame.set_background_role(Gfx::ColorRole::Base); + tip_frame.set_fill_with_background_color(true); + + auto& light_bulb_label = *find_descendant_of_type_named("light_bulb_label"); + light_bulb_label.set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-serendipity.png")); + + auto& did_you_know_label = *find_descendant_of_type_named("did_you_know_label"); + did_you_know_label.set_font(Gfx::BitmapFont::load_from_file("/res/fonts/KaticaBold12.font")); + + m_web_view = *find_descendant_of_type_named("web_view"); + + m_tip_label = *find_descendant_of_type_named("tip_label"); + m_tip_label->set_font(Gfx::BitmapFont::load_from_file("/res/fonts/KaticaRegular12.font")); + + m_next_button = *find_descendant_of_type_named("next_button"); + m_next_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png")); + m_next_button->on_click = [&]() { + if (!tip_frame.is_visible()) { + m_web_view->set_visible(false); + tip_frame.set_visible(true); + } + if (m_tips.is_empty()) + return; + m_initial_tip_index++; + if (m_initial_tip_index >= m_tips.size()) + m_initial_tip_index = 0; + m_tip_label->set_text(m_tips[m_initial_tip_index]); + }; + + m_help_button = *find_descendant_of_type_named("help_button"); + m_help_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/book-open.png")); + m_help_button->on_click = []() { + pid_t pid; + const char* argv[] = { "Help", nullptr }; + if ((errno = posix_spawn(&pid, "/bin/Help", nullptr, nullptr, const_cast(argv), environ))) { + perror("posix_spawn"); + } else { + if (disown(pid) < 0) + perror("disown"); + } + }; + + m_new_button = *find_descendant_of_type_named("new_button"); + m_new_button->on_click = [&]() { + m_web_view->set_visible(!m_web_view->is_visible()); + tip_frame.set_visible(!tip_frame.is_visible()); + }; + + m_close_button = *find_descendant_of_type_named("close_button"); + m_close_button->on_click = []() { + GUI::Application::the()->quit(); + }; + + open_and_parse_readme_file(); + open_and_parse_tips_file(); + srand(time(nullptr)); + set_random_tip(); +} + +SerendipityWidget::~SerendipityWidget() +{ +} + +void SerendipityWidget::open_and_parse_tips_file() +{ + auto file = Core::File::construct("/home/anon/Documents/tips.txt"); + if (!file->open(Core::IODevice::ReadOnly)) { + m_tip_label->set_text("~/Documents/tips.txt has gone missing!"); + return; + } + + while (file->can_read_line()) { + auto line = file->read_line(); + auto* ch = line.characters(); + switch (*ch) { + case '\n': + case '\r': + case '\0': + case '#': + continue; + } + m_tips.append(line); + } +} + +void SerendipityWidget::open_and_parse_readme_file() +{ + auto file = Core::File::construct("/home/anon/ReadMe.md"); + if (!file->open(Core::IODevice::ReadOnly)) + return; + + auto document = Markdown::Document::parse(file->read_all()); + if (document) { + auto html = document->render_to_html(); + m_web_view->load_html(html, URL::create_with_file_protocol("/home/anon/ReadMe.md")); + } +} + +void SerendipityWidget::set_random_tip() +{ + if (m_tips.is_empty()) + return; + + size_t n; + do + n = rand(); + while (n >= m_tips.size()); + m_initial_tip_index = n; + m_tip_label->set_text(m_tips[n]); +} diff --git a/Userland/Applications/Serendipity/SerendipityWidget.h b/Userland/Applications/Serendipity/SerendipityWidget.h new file mode 100644 index 0000000000..c4e5b1c781 --- /dev/null +++ b/Userland/Applications/Serendipity/SerendipityWidget.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021, the SerenityOS Developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +class SerendipityWidget final : public GUI::Widget { + C_OBJECT(SerendipityWidget) +public: + virtual ~SerendipityWidget() override; + +private: + SerendipityWidget(); + + void set_random_tip(); + void open_and_parse_tips_file(); + void open_and_parse_readme_file(); + + RefPtr m_close_button; + RefPtr m_next_button; + RefPtr m_help_button; + RefPtr m_new_button; + RefPtr m_tip_label; + RefPtr m_web_view; + + size_t m_initial_tip_index { 0 }; + Vector m_tips; +}; diff --git a/Userland/Applications/Serendipity/SerendipityWindow.gml b/Userland/Applications/Serendipity/SerendipityWindow.gml new file mode 100644 index 0000000000..9393d7b888 --- /dev/null +++ b/Userland/Applications/Serendipity/SerendipityWindow.gml @@ -0,0 +1,133 @@ +@GUI::Widget { + fill_with_background_color: true + layout: @GUI::VerticalBoxLayout { + margins: [8, 8, 8, 8] + } + + @GUI::Widget { + fixed_height: 30 + layout: @GUI::HorizontalBoxLayout { + margins: [4, 0, 0, 0] + } + + @GUI::Label { + name: "banner_label" + fixed_width: 251 + } + + @GUI::Widget { + } + } + + @GUI::Widget { + fixed_height: 160 + layout: @GUI::HorizontalBoxLayout { + } + + @GUI::Frame { + name: "tip_frame" + fixed_width: 340 + fixed_height: 160 + layout: @GUI::HorizontalBoxLayout { + margins: [0, 0, 16, 0] + } + + @GUI::Widget { + fixed_width: 60 + layout: @GUI::VerticalBoxLayout { + } + + @GUI::Label { + name: "light_bulb_label" + fixed_height: 60 + } + + @GUI::Widget { + } + } + + @GUI::Widget { + layout: @GUI::VerticalBoxLayout { + } + + @GUI::Label { + fixed_height: 60 + name: "did_you_know_label" + text: "Did you know..." + text_alignment: "CenterLeft" + } + + @GUI::Label { + name: "tip_label" + text_alignment: "TopLeft" + word_wrap: true + } + } + } + + @Web::OutOfProcessWebView { + name: "web_view" + fixed_width: 340 + fixed_height: 160 + visible: false + } + + @GUI::Widget { + } + + @GUI::Widget { + name: "navigation_column" + fixed_width: 120 + fixed_height: 160 + layout: @GUI::VerticalBoxLayout { + } + + @GUI::Button { + name: "new_button" + text: "What's New" + fixed_width: 115 + fixed_height: 24 + } + + @GUI::Button { + name: "help_button" + text: "Help Contents" + fixed_width: 115 + fixed_height: 24 + } + + @GUI::Button { + name: "next_button" + text: "Next Tip" + fixed_width: 115 + fixed_height: 24 + } + + } + } + + @GUI::Widget { + } + + @GUI::Widget { + fixed_height: 24 + layout: @GUI::HorizontalBoxLayout { + } + + @GUI::CheckBox{ + name: "startup_checkbox" + text: "Show this Welcome Screen next time SerenityOS starts" + fixed_width: 315 + } + + @GUI::Widget { + } + + @GUI::Button { + name: "close_button" + text: "Close" + fixed_width: 115 + fixed_height: 24 + } + } +} diff --git a/Userland/Applications/Serendipity/main.cpp b/Userland/Applications/Serendipity/main.cpp new file mode 100644 index 0000000000..ec567f6618 --- /dev/null +++ b/Userland/Applications/Serendipity/main.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021, the SerenityOS Developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "SerendipityWidget.h" +#include +#include + +int main(int argc, char** argv) +{ + auto app = GUI::Application::construct(argc, argv); + + if (pledge("stdio recvfd sendfd rpath unix proc accept exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/res", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/home", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/tmp/portal/webcontent", "rw") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/bin/Help", "x") < 0) { + perror("unveil"); + return 1; + } + + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + auto window = GUI::Window::construct(); + window->resize(480, 250); + window->center_on_screen(); + + window->set_title("Welcome"); + window->set_resizable(false); + window->set_window_type(GUI::WindowType::ToolWindow); + window->set_main_widget(); + + window->show(); + + return app->exec(); +}