More front end and refactoring

This commit is contained in:
Paul 2018-03-01 00:08:19 +01:00
parent bc13750209
commit 5419b34061
9 changed files with 288 additions and 144 deletions

View File

@ -9,31 +9,56 @@ m_builder(nullptr)
GError *error = NULL;
m_builder = gtk_builder_new();
const char ui_file[] = "glade/main_window.glade";
// Load the builder
gtk_builder_add_from_file (m_builder, ui_file, &error);
if(error != NULL) {
std::cerr << "An error occurred opening the main window.. Make sure " << ui_file << " exists and is a valid file." << std::endl;
exit(EXIT_FAILURE);
}
gtk_builder_connect_signals(m_builder, NULL);
// Load the required widgets through the builder
m_game_list = GTK_LIST_BOX(gtk_builder_get_object(m_builder, "game_list"));
m_main_window = GTK_WIDGET(gtk_builder_get_object(m_builder, "main_window"));
GtkWidget* game_placeholder = GTK_WIDGET(gtk_builder_get_object(m_builder, "game_placeholder"));
// Show the placeholder widget right away, which is the loading widget
gtk_list_box_set_placeholder(m_game_list, game_placeholder);
gtk_widget_show(game_placeholder);
}
// => Constructor
/**
* See https://stackoverflow.com/questions/9192223/remove-gtk-container-children-repopulate-it-then-refresh
* used here and other methods, tells you how to iterate through widgets with a relationship.
*
* This method will remove every game entry, only leaving the loading widget.
*/
void
MainPickerWindow::reset_game_list() {
//TODO
//for each m_rows
// gtk_widget_destroy
// See https://stackoverflow.com/questions/9192223/remove-gtk-container-children-repopulate-it-then-refresh
void MainPickerWindow::reset_game_list() {
gtk_container_foreach(GTK_CONTAINER(m_game_list), (GtkCallback)gtk_widget_destroy, NULL);
//TODO refresh the view but I dont know how to
}
// => reset_game_list
void MainPickerWindow::add_to_game_list(const Game_t& app) {
/**
* Add a game to the list. Ignores warnings for the obsolete GtkArrow.
* Such a classy widget, I don't get why I should bother creating a shitty
* gtkImage instead when it does just what I want out of the box.
* The entry created is pushed on m_rows, to be easily acccessed later.
* The new entry is not shown yet, call confirm_game_list for that.
*/
void
MainPickerWindow::add_to_game_list(const Game_t& app) {
// Because you can't clone widgets with GTK, I'm going to recreate
// GTK_LIST_BOX_ROW(gtk_builder_get_object(m_builder, "game_entry"));
// By hand. Which is dead stupid.
@ -52,7 +77,6 @@ void MainPickerWindow::add_to_game_list(const Game_t& app) {
#pragma GCC diagnostic pop
gtk_widget_set_size_request(wrapper, -1, 80);
gtk_buildable_set_name(GTK_BUILDABLE(game_logo), std::to_string(app.app_id).c_str());
gtk_box_pack_start(GTK_BOX(layout), GTK_WIDGET(game_logo), FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(layout), GTK_WIDGET(label), TRUE, TRUE, 0);
@ -61,35 +85,46 @@ void MainPickerWindow::add_to_game_list(const Game_t& app) {
gtk_container_add(GTK_CONTAINER(wrapper), GTK_WIDGET(layout));
gtk_list_box_insert(m_game_list, GTK_WIDGET(wrapper), -1);
// Do not show widgets yet
//Save the created row somewhere for EZ access
m_rows.insert(std::pair<unsigned long, GtkWidget*>(app.app_id, wrapper));
}
// => add_to_game_list
void MainPickerWindow::confirm_game_list() {
/**
* Draws all the games that have not been shown yet
*/
void
MainPickerWindow::confirm_game_list() {
gtk_widget_show_all(GTK_WIDGET(m_game_list));
// Show all widgetss
}
// => confirm_game_list
/**
* Refreshes the icon for the specified app ID
*/
void
MainPickerWindow::refresh_app_icon(const unsigned long app_id) {
//TODO make sure app_id is index of m_rows
GList *children;
GtkImage *img;
std::string path(g_cache_folder);
path += "/";
path += std::to_string(app_id);
path += "/banner";
std::cerr << "Gotta renew " << app_id << "'s icon" << std::endl;
children = gtk_container_get_children(GTK_CONTAINER(m_rows[app_id])); //children = the layout
children = gtk_container_get_children(GTK_CONTAINER(children->data)); //children = first element of layout
//children = g_list_next(children); //children = second element of layout...
GList *children, *iter;
char id[256];
char req_id[256];
children = gtk_container_get_children(GTK_CONTAINER(m_game_list));
strncpy(req_id, std::to_string(app_id).c_str(), 256);
std::cerr << "length: " << g_list_length(children); //0 TO DEBUG
for(iter = children; iter != NULL; iter = g_list_next(iter)) {
strncpy(id, gtk_buildable_get_name(GTK_BUILDABLE(iter->data)), 256);
if(strcmp(id, req_id) == 0) {
//TODO
}
img = GTK_IMAGE(children->data);
if( !GTK_IS_IMAGE(img) ) {
std::cerr << "It looks like the GUI has been modified, or something went wrong." << std::endl;
std::cerr << "Inform the developer or look at MainPickerWindow::refresh_app_icon." << std::endl;
exit(EXIT_FAILURE);
}
g_list_free(children);
gtk_image_set_from_file(img, path.c_str());
}
// => refresh_app_icon

View File

@ -2,20 +2,57 @@
#include <gtk/gtk.h>
#include <iostream>
#include <map>
#include "globals.h"
#include "Game.h"
/**
* The main GUI class to display both the games ans the achievements to the user
*/
class MainPickerWindow {
public:
//Todo: destructor and tidy
//Todo: destructor
/**
* The constructor loads the gtk builder, the main elements,
* and shows the main window.
*/
MainPickerWindow();
/**
* Empty the game list, leaving only the placeholder widget,
* which means the loading widget.
*/
void reset_game_list();
/**
* Adds a game to the game list. The new item will be added and saved,
* but not drawn.
*/
void add_to_game_list(const Game_t& app);
/**
* Commits all the changes made since the last commit
* Shows all widget that has been added to the list, removes all
* the deleted entries from the GUI list.
*/
void confirm_game_list();
/**
* When a game is added to the list, the "missing icon" is used by default
* Once the correct icon has been retrieved, this method will be called
* to set the right image retrieved from Steam
*/
void refresh_app_icon(const unsigned long app_id);
/**
* Getter for the main window
*/
GtkWidget* get_main_window() { return m_main_window; };
private:
GtkWidget *m_main_window;
GtkListBox *m_game_list;
GtkBuilder *m_builder;
std::map<unsigned long, GtkWidget*> m_rows;
};

View File

@ -1,28 +1,30 @@
/**
* Steam is a class that serves the purpose of being a "launcher" for thesteam games and apps.
* It will refuse to do anything unless a real Steam client can be accessed, with a user logged in.
*/
#define MAX_PATH 1000
#include "MySteam.h"
#define MAX_PATH 1000
MySteam::MySteam() : m_child_pid(-1) {
MySteam::MySteam()
:
m_child_pid(-1) {
}
// => Constructor
MySteam::~MySteam() {
}
MySteam* MySteam::get_instance() {
/**
* Gets the unique instance. See "Singleton design pattern" for help
*/
MySteam*
MySteam::get_instance() {
static MySteam instance;
return &instance;
}
// => get_instance
/**
* Fakes a new game being launched. Keeps running in the background until quit_game is called.
*/
bool MySteam::launch_game(std::string appID) {
bool
MySteam::launch_game(std::string appID) {
// Print an error if a game is already launched, maybe allow multiple games at the same time in the future?
if(m_child_pid > 0) {
std::cout << "Sorry, a game is already running. Please try again later." << std::endl;
@ -53,8 +55,14 @@ bool MySteam::launch_game(std::string appID) {
}
return false;
}
// => launch_game
bool MySteam::quit_game() {
/**
* If a fake game is running, stops it and returns true, else false.
*/
bool
MySteam::quit_game() {
if(m_child_pid > 0) {
kill(m_child_pid, SIGTERM);
}
@ -66,6 +74,8 @@ bool MySteam::quit_game() {
m_child_pid = -1;
return true;
}
// => quit_game
/**
* This does NOT retrieves all owned games.
@ -73,7 +83,8 @@ bool MySteam::quit_game() {
* Stores the owned games in m_all_subscribed_apps
* We assume the user didn't put any garbage in his steam folder as well.
*/
void MySteam::refresh_owned_apps() {
void
MySteam::refresh_owned_apps() {
const std::string path_to_cache_dir(MySteam::get_steam_install_path() + "/appcache/stats/");
DIR* dirp = opendir(path_to_cache_dir.c_str());
struct dirent * dp;
@ -94,7 +105,6 @@ void MySteam::refresh_owned_apps() {
if(sscanf(dp->d_name, input_scheme_c.c_str(), &app_id) == 1) {
game.app_id = app_id;
game.app_name = appDAO->get_app_name(app_id);
appDAO->download_app_icon(app_id);
m_all_subscribed_apps.push_back(game);
}
@ -103,6 +113,8 @@ void MySteam::refresh_owned_apps() {
closedir(dirp);
}
// => refresh_owned_apps
/**
* Could parse /home/user/.local/share/Steam/config/loginusers.vdf, but wrong id type
@ -110,7 +122,8 @@ void MySteam::refresh_owned_apps() {
* Return the most recently logged in user id
* Returns empty string on error
*/
std::string MySteam::get_user_steamId3() {
std::string
MySteam::get_user_steamId3() {
static const std::string file_path(MySteam::get_steam_install_path() + "/logs/parental_log.txt");
std::ifstream input(file_path, std::ios::in);
std::string word;
@ -142,8 +155,15 @@ std::string MySteam::get_user_steamId3() {
return latest_id;
}
// => get_user_steamId3
std::string MySteam::get_steam_install_path() {
/**
* Tries to locate the steam folder in multiple locations,
* which is not a failsafe implementation.
*/
std::string
MySteam::get_steam_install_path() {
static const std::string home_path(getenv("HOME"));
if(file_exists(home_path + "/.local/share/Steam/appcache/appinfo.vdf")) {
return std::string(home_path + "/.local/share/Steam");
@ -156,11 +176,34 @@ std::string MySteam::get_steam_install_path() {
exit(EXIT_FAILURE);
}
}
// => get_steam_install_path
void MySteam::print_all_owned_games() const {
/**
* Mostly used for debug purposes
*/
void
MySteam::print_all_owned_games() const {
std::cerr << "Summary of owned apps with stats or achievements" << std::endl << "========================" << std::endl;
for(Game_t i : m_all_subscribed_apps) {
std::cerr << i.app_id << " -> " << i.app_name << std::endl;
}
}
}
// => print_all_owned_games
/**
* Reminder that download_app_icon does check if the file is
* already there before attempting to download from the web.
* It also has a "callback" that will refresh the view.
*/
void
MySteam::refresh_icons() {
SteamAppDAO *appDAO = SteamAppDAO::get_instance();
for(Game_t i : m_all_subscribed_apps) {
appDAO->download_app_icon(i.app_id);
}
}
// => refresh_icons

View File

@ -4,35 +4,83 @@
#include <fstream>
#include <string>
#include <vector>
#include <chrono>
#include <thread>
#include <csignal>
#include <dirent.h>
#include "Game.h"
#include "SteamAppDAO.h"
#include "../steam/steam_api.h"
#include "../common/c_processes.h"
/**
* MySteam is a class that aims to retrieve as much data as
* possible regarding the latest logged in user on the machine.
* This data includes owned games with app and achievements,
* and identifiers.
*/
class MySteam {
public:
/**
* Returns the unique instance of this object.
* See "Singleto design pattern" for further help
*/
static MySteam* get_instance();
/**
* Returns the steamId3 of the last user who logged in on the
* machine. Make sure all logs are enabled, or this may result
* in an error.
*/
static std::string get_user_steamId3();
/**
* Returns the absolute path to the steam installation folder.
* This is not failsafe and may require some tweaking to add
* support for your distribution
*/
static std::string get_steam_install_path();
/**
* Starts a process that will emulate a steam game with the
* given appId. Returns false if this process failed to launch.
* The process may start successfully but fail during execution.
*/
bool launch_game(std::string appId);
/**
* Stops the process started with the above method launch_game.
* Returns true if a process was successfully stopped.
*/
bool quit_game();
/**
* Mostly used for debug purposes, prints all apps owned
* (with stats or achievements) in the console.
*/
void print_all_owned_games() const;
/**
* Makes a list of all owned games with stats or achievements.
*/
void refresh_owned_apps();
// Make sure to call refresh_owned_apps at least once to get correct results
/**
* Fetches all the app icons either online or on the disk.
* Once fetched, the view is automatically updated with
* the fetched image. (Observer pattern)
*/
void refresh_icons();
/**
* Returns all the already loaded retrieved apps by the latest logged
* in user. Make sure to call refresh_owned_apps at least once to get
* correct results
*/
std::vector<Game_t> get_all_games_with_stats() { return m_all_subscribed_apps; };
MySteam(MySteam const&) = delete;
void operator=(MySteam const&) = delete;
private:
MySteam();
~MySteam();
pid_t m_child_pid;
std::vector<Game_t> m_all_subscribed_apps;

View File

@ -3,17 +3,16 @@
// Wtf am I doing? Anyway thanks StackOverflow
//TODO: Find a more elegant way to fix this shit.
std::map<unsigned long, std::string> SteamAppDAO::m_app_names = std::map<unsigned long, std::string>();
const char* SteamAppDAO::CACHE_FOLDER = concat(getenv("HOME"), "/.SamRewritten");
void
SteamAppDAO::update_name_database() {
bool need_to_redownload = false;
struct stat file_info;
static const char* local_file_name = concat(SteamAppDAO::CACHE_FOLDER, "/app_names");
static const char* local_file_name = concat(g_cache_folder, "/app_names");
const std::time_t current_time(std::time(0));
// Make sure the cache folder is there
const int mkdir_error = mkdir(SteamAppDAO::CACHE_FOLDER, S_IRWXU | S_IRWXG | S_IROTH);
const int mkdir_error = mkdir(g_cache_folder, S_IRWXU | S_IRWXG | S_IROTH);
if(mkdir_error != 0 && errno != EEXIST) {
std::cerr << "Unable to create the cache folder ( ~/.SamRewritten/, errno " << errno << ")." << std::endl;
std::cerr << "Don't tell me you're running this as root.." << std::endl;
@ -62,7 +61,7 @@ SteamAppDAO::get_app_name(const unsigned long& app_id) {
void
SteamAppDAO::download_app_icon(const unsigned long& app_id) {
const std::string local_folder(std::string(SteamAppDAO::CACHE_FOLDER) + "/" + std::to_string(app_id));
const std::string local_folder(std::string(g_cache_folder) + "/" + std::to_string(app_id));
const std::string local_path(local_folder + "/banner");
const std::string url("http://cdn.akamai.steamstatic.com/steam/apps/" + std::to_string(app_id) + "/header_292x136.jpg");
@ -79,7 +78,7 @@ void
SteamAppDAO::parse_app_names() {
m_app_names.clear();
static const std::string file_path(std::string(SteamAppDAO::CACHE_FOLDER) + "/app_names");
static const std::string file_path(std::string(g_cache_folder) + "/app_names");
std::ifstream input(file_path, std::ios::in);
std::string word;
std::string app_name;

View File

@ -10,6 +10,7 @@
#include "../common/c_processes.h"
#include "../common/Downloader.h"
#include "globals.h"
#include "MainPickerWindow.h"
class SteamAppDAO : public Observer<unsigned long> {
public:
@ -34,17 +35,14 @@ public:
/**
* Download the app's banner ASYNCHRONOUSLY.
* If it fails, nothing is written.
* If it fails, nothing is written on the disk.
*/
void download_app_icon(const unsigned long& app_id);
/**
* Path name to the root of the cache folder. By now it is
* ~/.SamRewritten
* Observer inherited method. The update will refresh
* the image for app id "i" on the view.
*/
static const char* CACHE_FOLDER;
//TODO tidy
void update(unsigned long i);
SteamAppDAO(SteamAppDAO const&) = delete;

View File

@ -1,9 +1,29 @@
#pragma once
class MySteam;
#include "MainPickerWindow.h"
//TODO tidy & comment
//Globals
//Reason for the globals is that it's easier to access them in GTK callbacks
extern MySteam *g_steam; // The Model
extern MainPickerWindow *g_main_gui; // The view
class MainPickerWindow;
// Reason for the globals is that it's easier to access them in GTK callbacks
// All those variables are initialised in main.cpp
/**
* g_steam is the backend of all of this, the M of the MVC. It will analyse the
* steam folder and give data about it: games owned, icons, etc.
*/
extern MySteam *g_steam;
/**
* g_main_gui is the main application window. If you can read this, it is very
* glitchy, freezes instead of showing the loading widget, but still looks
* half-decent. It's the V of the MVC.
*/
extern MainPickerWindow *g_main_gui;
/**
* The absolute path to the cache folder. It's global, because everyone will
* need to write or read into it at some point.
* As of right now the default path is:
*
* ~/.SamRewritten
*/
extern char *g_cache_folder;

View File

@ -2,23 +2,29 @@
* Please read the license before modifying or distributing any of the code from
* this project. Thank you.
*/
#include <iostream>
#include <unistd.h>
#include <gtk/gtk.h>
#include <gmodule.h>
#include "MySteam.h"
#include "MainPickerWindow.h"
#include "globals.h"
int launcher_main();
/**************************************
* Declare global variables imported from globals.h
**************************************/
MySteam* g_steam = nullptr;
MainPickerWindow* g_main_gui = nullptr;
char* g_cache_folder = nullptr;
/**************************************
* Main entry point
**************************************/
int
main(int argc, char *argv[])
{
// Test if glib2 is installed, gtk will not work without it.
if(!g_module_supported()) {
std::cerr << "Sorry, but gmodules are not supported on your platform :(. Try installing as many gnome libs as you can maybe.." << std::endl;
exit(EXIT_FAILURE);
@ -26,60 +32,67 @@ main(int argc, char *argv[])
gtk_init(&argc, &argv);
launcher_main();
return 0;
}
int
launcher_main() {
g_cache_folder = concat(getenv("HOME"), "/.SamRewritten");
g_steam = MySteam::get_instance();
g_main_gui = new MainPickerWindow();
gtk_widget_show(g_main_gui->get_main_window());
gtk_main();
//steam->launch_game("368230");
//steam->quit_game();
return 0;
}
//Gtk Callbacks
//Controller
/**************************************
* Gtk Callbacks
**************************************/
extern "C"
{
// When you click on the close button if steam is not running
/**
* When you click on the close button if steam is not running
* Quit the GTK process, and destroy as much as possible: the program
* is about to be exitted completely.
*/
void
on_close_button_clicked() {
gtk_main_quit();
std::cerr << "Gtk did main quit" << std::endl;
gtk_widget_destroy(g_main_gui->get_main_window());
}
/**
* When the user wants to refresh the game list.
* This is also called when the main window just got spawned.
* - Clear the game list (will show the loading widget)
* - Do the backend work (get owned apps..)
* - Add all the retrieved data to the view.
* - Do the backend work again for icons: view must be initialized first
* to display the app logos.
* - Draw the result.
*/
void
on_ask_game_refresh() {
// Draw the loading screen on the view
g_main_gui->reset_game_list();
std::cerr << "Game refresh started." << std::endl;
// Do the backend work only when the view is already showing
g_steam = MySteam::get_instance();
g_steam->refresh_owned_apps();
// Add everything to the view
for(Game_t app : g_steam->get_all_games_with_stats()) {
g_main_gui->add_to_game_list(app);
}
// And draw it
g_steam->refresh_icons();
g_main_gui->confirm_game_list();
}
/**
* When the main window... started showing / will be showing??
* TODO: Make sure the loading widget is showing while starting to
* do the backend work
*/
void
on_main_window_show() {
on_ask_game_refresh();
on_ask_game_refresh(); //Run this async?
}
}

View File

@ -2,55 +2,6 @@
<!-- Generated with glade 3.20.3 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkListBoxRow" id="game_entry">
<property name="width_request">100</property>
<property name="height_request">80</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage" id="game_entry_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="game_entry_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">label</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkArrow">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkBox" id="game_placeholder">
<property name="visible">True</property>
<property name="can_focus">False</property>