Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
428 lines (345 sloc) 11.2 KB
/*
* 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, &region);
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;
}