Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * Copyright © 2016, 2017 Octopull Limited. | |
| * | |
| * This program is free software: you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 3, | |
| * as published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| * | |
| * Authored by: Alan Griffiths <alan@octopull.co.uk> | |
| */ | |
| #include "list_desktop_files.h" | |
| #include "printer.h" | |
| #include <mir/client/connection.h> | |
| #include <mir/client/display_config.h> | |
| #include <mir/client/surface.h> | |
| #include <mir/client/window.h> | |
| #include <mir/client/window_spec.h> | |
| #include <mir_toolkit/mir_buffer_stream.h> | |
| #include <linux/input.h> | |
| #include <boost/filesystem/fstream.hpp> | |
| #include <atomic> | |
| #include <condition_variable> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <exception> | |
| #include <iostream> | |
| #include <locale> | |
| #include <mutex> | |
| #include <string> | |
| #include <vector> | |
| #include <stdlib.h> | |
| namespace mc = mir::client; | |
| class Mircade | |
| { | |
| public: | |
| void run(); | |
| private: | |
| struct app_details | |
| { | |
| std::string name; | |
| std::string exec; | |
| std::string icon; | |
| }; | |
| static auto load_details() -> std::vector<app_details>; | |
| static void lifecycle_event_callback(MirConnection* connection, MirLifecycleState state, void* context); | |
| static void window_event_callback(MirWindow* window, MirEvent const* event, void* context); | |
| auto create_connection() -> mc::Connection; | |
| auto create_window(mc::Connection const& connection) -> mc::Window; | |
| void handle_input(MirInputEvent const* event); | |
| mc::Connection const connection{create_connection()}; | |
| mc::Surface const surface{mir_connection_create_render_surface_sync(connection, width, height)}; | |
| MirBufferStream* const buffer_stream{mir_render_surface_get_buffer_stream(surface, width, height, mir_pixel_format_xrgb_8888)}; | |
| mc::Window const window{create_window(connection)}; | |
| std::atomic<bool> running{true}; | |
| std::vector<app_details> const apps = load_details(); | |
| std::mutex mutable mutex; | |
| std::condition_variable mutable cv; | |
| std::vector<app_details>::const_iterator current_app{apps.begin()}; | |
| bool exec_currrent_app{false}; | |
| int width{0}; | |
| int height{0}; | |
| void handle_keyboard(MirKeyboardEvent const* event); | |
| void handle_pointer(MirPointerEvent const* event); | |
| void handle_touch(MirTouchEvent const* event); | |
| void prev_app(); | |
| void next_app(); | |
| void run_app(); | |
| }; | |
| int main() | |
| try | |
| { | |
| Mircade{}.run(); | |
| return 0; | |
| } | |
| catch (std::exception const& x) | |
| { | |
| std::cerr << "ERROR: " << x.what() << std::endl; | |
| return EXIT_FAILURE; | |
| } | |
| namespace | |
| { | |
| auto const* ignore_xmir = getenv("MIRCADE_IGNORE_XMIR"); | |
| auto works_with_mir(std::string const& exec) -> bool | |
| { | |
| if (ignore_xmir) | |
| return true; | |
| // "native" Mir apps and gtk3 apps depend on libmirclient | |
| // SDL2 apps depend on libSDL2 | |
| // SDL1.2 apps depend on libSDL-1.2 | |
| // Qt apps depend on libQt5Core | |
| // GTK3 apps depend on libgdk-3 | |
| auto const command = "exit $(ldd `which " + exec + "` | grep -e 'libmirclient\\|libSDL2\\libSDL-1.2\\|libQt5Core\\|libgdk-3' | wc -l)"; | |
| return system(command.c_str()); | |
| } | |
| auto xmir_installed() -> bool | |
| { | |
| if (ignore_xmir) | |
| return true; | |
| static bool installed = (0 == system("which Xmir")); | |
| return installed; | |
| } | |
| using namespace mircade; | |
| } | |
| void Mircade::run() | |
| { | |
| while (running) | |
| { | |
| std::unique_lock<decltype(mutex)> lock{mutex}; | |
| if (exec_currrent_app) | |
| { | |
| mir_window_set_state(window, mir_window_state_hidden); | |
| auto command = | |
| (works_with_mir(current_app->exec) ? | |
| "GDK_BACKEND=mir QT_QPA_PLATFORM=ubuntumirclient SDL_VIDEODRIVER=mir " : | |
| "miral-xrun ") + current_app->exec ; | |
| lock.unlock(); | |
| system(command.c_str()); | |
| mir_window_set_state(window, mir_window_state_fullscreen); | |
| lock.lock(); | |
| exec_currrent_app = false; | |
| } | |
| else | |
| { | |
| auto title = current_app->name; | |
| static uint8_t const pattern[4] = { 0x14, 0x48, 0xDD, 0xFF }; | |
| MirGraphicsRegion region; | |
| mir_buffer_stream_get_graphics_region(buffer_stream, ®ion); | |
| char* row = region.vaddr; | |
| for (int j = 0; j != region.height; ++j) | |
| { | |
| for (int i = 0; i < region.width; i++) | |
| memcpy(row+4*i, pattern, 4); | |
| row += region.stride; | |
| } | |
| // One day we'll use the icon file | |
| static Printer printer; | |
| printer.print(region, title); | |
| mir_buffer_stream_swap_buffers_sync(buffer_stream); | |
| height = region.height; | |
| } | |
| cv.wait(lock); | |
| } | |
| } | |
| auto Mircade::load_details() -> std::vector<app_details> | |
| { | |
| static std::string const categories_key{"Categories="}; | |
| static std::string const name_key{"Name="}; | |
| static std::string const exec_key{"Exec="}; | |
| static std::string const icon_key{"Icon="}; | |
| auto const desktop_listing = list_desktop_files(); | |
| std::vector<app_details> details; | |
| if (desktop_listing.size() == 0) | |
| details.push_back(app_details{"[No games found]", "", ""}); | |
| for (auto const& desktop : desktop_listing) | |
| { | |
| boost::filesystem::ifstream in(desktop); | |
| std::string line; | |
| std::string categories; | |
| std::string name; | |
| std::string exec; | |
| std::string icon; | |
| while (std::getline(in, line)) | |
| { | |
| if (line.find(categories_key) == 0) | |
| categories = line.substr(categories_key.length()) + ";"; | |
| else if (line.find(name_key) == 0) | |
| name = line.substr(name_key.length()); | |
| else if (line.find(exec_key) == 0) | |
| exec = line.substr(exec_key.length()); | |
| else if (line.find(icon_key) == 0) | |
| icon = line.substr(icon_key.length()); | |
| } | |
| if ((categories.find("Game;") != std::string::npos) && | |
| (xmir_installed() || works_with_mir(exec))) | |
| { | |
| details.push_back(app_details{name, exec, icon}); | |
| } | |
| } | |
| return details; | |
| } | |
| auto Mircade::create_window(mc::Connection const& connection) -> mc::Window | |
| { | |
| return mc::WindowSpec::for_normal_window(connection, width, height) | |
| .set_name("Mircade") | |
| .set_event_handler(&window_event_callback, this) | |
| .set_fullscreen_on_output(0) | |
| .add_surface(surface, width, height, 0, 0) | |
| .create_window(); | |
| } | |
| void Mircade::lifecycle_event_callback(MirConnection* /*connection*/, MirLifecycleState state, void* context) | |
| { | |
| switch (state) | |
| { | |
| case mir_lifecycle_state_will_suspend: | |
| case mir_lifecycle_state_resumed: | |
| return; | |
| case mir_lifecycle_connection_lost: | |
| auto self = (Mircade*)context; | |
| self->running = false; | |
| self->cv.notify_one(); | |
| break; | |
| } | |
| } | |
| void Mircade::window_event_callback(MirWindow* /*window*/, MirEvent const* event, void* context) | |
| { | |
| switch (mir_event_get_type(event)) | |
| { | |
| case mir_event_type_input: | |
| { | |
| auto const input_event = mir_event_get_input_event(event); | |
| auto self = (Mircade*)context; | |
| self->handle_input(input_event); | |
| break; | |
| } | |
| case mir_event_type_resize: | |
| { | |
| auto const self = (Mircade*)context; | |
| auto const resize = mir_event_get_resize_event(event); | |
| auto const new_width = mir_resize_event_get_width(resize); | |
| auto const new_height= mir_resize_event_get_height(resize); | |
| mir_render_surface_set_size(self->surface, new_width, new_height); | |
| mir_buffer_stream_set_size(self->buffer_stream, new_width, new_height); | |
| mc::WindowSpec::for_changes(self->connection) | |
| .add_surface(self->surface, new_width, new_height, 0, 0) | |
| .apply_to(self->window); | |
| } | |
| case mir_event_type_close_window: | |
| default: | |
| ; | |
| } | |
| } | |
| void Mircade::handle_input(MirInputEvent const* event) | |
| { | |
| switch (mir_input_event_get_type(event)) | |
| { | |
| case mir_input_event_type_key: | |
| handle_keyboard(mir_input_event_get_keyboard_event(event)); | |
| break; | |
| case mir_input_event_type_pointer: | |
| handle_pointer(mir_input_event_get_pointer_event(event)); | |
| break; | |
| case mir_input_event_type_touch: | |
| handle_touch(mir_input_event_get_touch_event(event)); | |
| break; | |
| default:; | |
| } | |
| } | |
| void Mircade::handle_keyboard(MirKeyboardEvent const* event) | |
| { | |
| if (mir_keyboard_event_action(event) == mir_keyboard_action_down) | |
| switch (mir_keyboard_event_scan_code(event)) | |
| { | |
| case KEY_RIGHT: | |
| case KEY_DOWN: | |
| { | |
| std::lock_guard<decltype(mutex)> lock{mutex}; | |
| next_app(); | |
| break; | |
| } | |
| case KEY_LEFT: | |
| case KEY_UP: | |
| { | |
| std::lock_guard<decltype(mutex)> lock{mutex}; | |
| prev_app(); | |
| break; | |
| } | |
| case KEY_ENTER: | |
| case KEY_SPACE: | |
| { | |
| std::lock_guard<decltype(mutex)> lock{mutex}; | |
| run_app(); | |
| break; | |
| } | |
| } | |
| } | |
| void Mircade::handle_pointer(MirPointerEvent const* event) | |
| { | |
| if (mir_pointer_event_action(event) == mir_pointer_action_button_up) | |
| { | |
| std::lock_guard<decltype(mutex)> lock{mutex}; | |
| auto const y = mir_pointer_event_axis_value(event, mir_pointer_axis_y); | |
| if (y < height/3) | |
| prev_app(); | |
| else if (y > (2*height)/3) | |
| next_app(); | |
| else | |
| run_app(); | |
| } | |
| } | |
| void Mircade::handle_touch(MirTouchEvent const* event) | |
| { | |
| auto const count = mir_touch_event_point_count(event); | |
| if (count == 1 && mir_touch_event_action(event, 0) == mir_touch_action_up) | |
| { | |
| auto const y = mir_touch_event_axis_value(event, 0, mir_touch_axis_y); | |
| std::lock_guard<decltype(mutex)> lock{mutex}; | |
| if (y < height/3) | |
| prev_app(); | |
| else if (y > (2*height)/3) | |
| next_app(); | |
| else | |
| run_app(); | |
| } | |
| } | |
| void Mircade::run_app() | |
| { | |
| exec_currrent_app = true; | |
| cv.notify_one(); | |
| } | |
| void Mircade::next_app() | |
| { | |
| if (++current_app == apps.end()) | |
| current_app = apps.begin(); | |
| cv.notify_one(); | |
| } | |
| void Mircade::prev_app() | |
| { | |
| if (current_app == apps.begin()) | |
| current_app = apps.end(); | |
| --current_app; | |
| cv.notify_one(); | |
| } | |
| auto Mircade::create_connection() -> mc::Connection | |
| { | |
| mc::Connection connection{mir_connect_sync(nullptr, "Mircade")}; | |
| if (!mir_connection_is_valid(connection)) | |
| throw std::runtime_error{mir_connection_get_error_message(connection)}; | |
| mir_connection_set_lifecycle_event_callback(connection, &lifecycle_event_callback, this); | |
| mc::DisplayConfig{connection}.for_each_output([this](MirOutput const* output) | |
| { | |
| if (!mir_output_is_enabled(output)) | |
| return; | |
| auto const mode = mir_output_get_current_mode(output); | |
| width = mir_output_mode_get_width(mode); | |
| height = mir_output_mode_get_height(mode); | |
| }); | |
| return connection; | |
| } |