mirror of
https://github.com/Almamu/linux-wallpaperengine.git
synced 2025-07-14 05:12:25 +08:00
363 lines
12 KiB
C++
363 lines
12 KiB
C++
#include <iostream>
|
|
#include <getopt.h>
|
|
#include <unistd.h>
|
|
#include <SDL_mixer.h>
|
|
#include <SDL.h>
|
|
#include <FreeImage.h>
|
|
#include <sys/stat.h>
|
|
#include <GL/glew.h>
|
|
#include <GL/glx.h>
|
|
#include <filesystem>
|
|
#include <csignal>
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
|
|
#include "WallpaperEngine/Core/CProject.h"
|
|
#include "WallpaperEngine/Render/CWallpaper.h"
|
|
#include "WallpaperEngine/Render/CContext.h"
|
|
#include "WallpaperEngine/Render/CScene.h"
|
|
#include "WallpaperEngine/Render/CVideo.h"
|
|
|
|
#include "WallpaperEngine/Assets/CPackage.h"
|
|
#include "WallpaperEngine/Assets/CDirectory.h"
|
|
#include "WallpaperEngine/Assets/CCombinedContainer.h"
|
|
|
|
float g_Time;
|
|
|
|
using namespace WallpaperEngine::Core::Types;
|
|
|
|
void print_help (const char* route)
|
|
{
|
|
std::cout
|
|
<< "Usage:" << route << " [options] background_path" << std::endl
|
|
<< "options:" << std::endl
|
|
<< " --silent\t\tMutes all the sound the wallpaper might produce" << std::endl
|
|
<< " --screen-root <screen name>\tDisplay as screen's background" << std::endl
|
|
<< " --fps <maximum-fps>\tLimits the FPS to the given number, useful to keep battery consumption low" << std::endl;
|
|
}
|
|
|
|
std::string stringPathFixes(const std::string& s)
|
|
{
|
|
if (s.empty () == true)
|
|
return s;
|
|
|
|
std::string str (s);
|
|
|
|
// remove single-quotes from the arguments
|
|
if (str [0] == '\'' && str [str.size() - 1] == '\'')
|
|
str
|
|
.erase (str.size() - 1, 1)
|
|
.erase (0, 1);
|
|
|
|
// ensure there's a slash at the end of the path
|
|
if (str [str.size() - 1] != '/')
|
|
str += '/';
|
|
|
|
return std::move (str);
|
|
}
|
|
|
|
void free_display_wallpaper(int sig)
|
|
{
|
|
Display* display = XOpenDisplay (nullptr);
|
|
Window root = DefaultRootWindow(display);
|
|
// create a blank pm to reset compositors values, compositors will render as a blank X window.
|
|
Pixmap pm = XCreatePixmap(display, root, 1, 1, 1);
|
|
Atom prop_root = XInternAtom(display, "_XROOTPMAP_ID", False);
|
|
Atom prop_esetroot = XInternAtom(display, "ESETROOT_PMAP_ID", False);
|
|
XChangeProperty(display, root, prop_root, XA_PIXMAP, 32, PropModeReplace, (unsigned char *) &pm, 1);
|
|
XChangeProperty(display, root, prop_esetroot, XA_PIXMAP, 32, PropModeReplace, (unsigned char *) &pm, 1);
|
|
XFreePixmap(display, pm);
|
|
// set background to black. Only needed if no compositors are running
|
|
XSetWindowBackground(display, root, 0);
|
|
// sync changes before exiting
|
|
XClearWindow(display, root);
|
|
XFlush(display);
|
|
XCloseDisplay(display);
|
|
exit(sig);
|
|
}
|
|
|
|
int main (int argc, char* argv[])
|
|
{
|
|
std::vector <std::string> screens;
|
|
|
|
int maximumFPS = 30;
|
|
bool shouldEnableAudio = true;
|
|
std::string path;
|
|
|
|
static struct option long_options [] = {
|
|
{"screen-root", required_argument, 0, 'r'},
|
|
{"pkg", required_argument, 0, 'p'},
|
|
{"dir", required_argument, 0, 'd'},
|
|
{"silent", no_argument, 0, 's'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{"fps", required_argument, 0, 'f'},
|
|
{nullptr, 0, 0, 0}
|
|
};
|
|
|
|
while (true)
|
|
{
|
|
int c = getopt_long (argc, argv, "r:p:d:shf:", long_options, nullptr);
|
|
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c)
|
|
{
|
|
case 'r':
|
|
screens.emplace_back (optarg);
|
|
break;
|
|
|
|
case 'p':
|
|
case 'd':
|
|
std::cerr << "--dir/--pkg is deprecated and not used anymore" << std::endl;
|
|
path = stringPathFixes (optarg);
|
|
break;
|
|
|
|
case 's':
|
|
shouldEnableAudio = false;
|
|
break;
|
|
|
|
case 'h':
|
|
print_help (argv [0]);
|
|
break;
|
|
|
|
case 'f':
|
|
maximumFPS = atoi (optarg);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// increment the option index (useful for when no options were found)
|
|
// option_index ++;
|
|
|
|
if (path.empty () == true)
|
|
{
|
|
if (optind < argc && strlen (argv [optind]) > 0)
|
|
{
|
|
path = argv [optind];
|
|
}
|
|
else
|
|
{
|
|
print_help (argv [0]);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
char* finalPath = realpath(path.c_str (), nullptr);
|
|
|
|
if (finalPath == nullptr)
|
|
{
|
|
if (errno == ENAMETOOLONG)
|
|
throw std::runtime_error ("Cannot get wallpaper's folder, path is too long");
|
|
else
|
|
throw std::runtime_error ("Cannot find the specified folder");
|
|
}
|
|
|
|
// ensure the path points to a folder
|
|
struct stat pathinfo;
|
|
|
|
if (stat (finalPath, &pathinfo) != 0)
|
|
throw std::runtime_error ("Cannot find the specified wallpaper folder");
|
|
|
|
if (!S_ISDIR (pathinfo.st_mode))
|
|
throw std::runtime_error ("The specified path is not a folder");
|
|
|
|
// ensure the path has a trailing slash
|
|
|
|
// Attach signals for unexpected killing of program by user. We need to reset the
|
|
// screen otherwise the background will remain the last frame on sigterm or sigint.
|
|
if (!screens.empty())
|
|
{
|
|
std::signal(SIGINT, free_display_wallpaper);
|
|
std::signal(SIGTERM, free_display_wallpaper);
|
|
}
|
|
|
|
// first of all, initialize the window
|
|
if (glfwInit () == GLFW_FALSE)
|
|
{
|
|
fprintf (stderr, "Failed to initialize GLFW\n");
|
|
return 1;
|
|
}
|
|
|
|
// initialize freeimage
|
|
FreeImage_Initialise (TRUE);
|
|
|
|
// set some window hints (opengl version to be used)
|
|
glfwWindowHint (GLFW_SAMPLES, 4);
|
|
glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 2);
|
|
glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 1);
|
|
|
|
// will hide the window if we are drawing to X
|
|
if (!screens.empty())
|
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
|
|
|
auto containers = new WallpaperEngine::Assets::CCombinedContainer ();
|
|
|
|
// update the used path with the full one
|
|
path = finalPath;
|
|
path += "/";
|
|
|
|
// the background's path is required to load project.json regardless of the type of background we're using
|
|
containers->add (new WallpaperEngine::Assets::CDirectory (path));
|
|
// check if scene.pkg exists and add it to the list
|
|
try
|
|
{
|
|
std::string scene_path = path + "scene.pkg";
|
|
|
|
// add the package to the list
|
|
containers->add (new WallpaperEngine::Assets::CPackage (scene_path));
|
|
std::cout << "Detected scene.pkg file at " << scene_path << ". Adding to list of searchable paths" << std::endl;
|
|
}
|
|
catch(std::filesystem::filesystem_error ex)
|
|
{
|
|
// ignore this error, the package file was not found
|
|
std::cout << "No scene.pkg file found at " << path << ". Defaulting to normal folder storage" << std::endl;
|
|
}
|
|
catch (std::runtime_error ex)
|
|
{
|
|
// the package was found but there was an error loading it (wrong header or something)
|
|
fprintf (stderr, "Failed to load scene.pkg file: %s\n", ex.what());
|
|
return 4;
|
|
}
|
|
|
|
// add containers to the list
|
|
containers->add (new WallpaperEngine::Assets::CDirectory ("./assets/"));
|
|
|
|
// parse the project.json file
|
|
auto project = WallpaperEngine::Core::CProject::fromFile ("project.json", containers);
|
|
WallpaperEngine::Render::CWallpaper* wallpaper;
|
|
|
|
// auto projection = project->getWallpaper ()->as <WallpaperEngine::Core::CScene> ()->getOrthogonalProjection ();
|
|
// create the window!
|
|
// TODO: DO WE NEED TO PASS MONITOR HERE OR ANYTHING?
|
|
// TODO: FIGURE OUT HOW TO PUT THIS WINDOW IN THE BACKGROUND
|
|
GLFWwindow* window = glfwCreateWindow (1920, 1080, "WallpaperEngine", NULL, NULL);
|
|
|
|
if (window == nullptr)
|
|
{
|
|
fprintf (stderr, "Failed to open a GLFW window");
|
|
glfwTerminate ();
|
|
return 2;
|
|
}
|
|
|
|
// initialize inputs
|
|
CMouseInput* mouseInput = new CMouseInput (window);
|
|
|
|
glfwMakeContextCurrent (window);
|
|
|
|
// TODO: FIGURE THESE OUT BASED ON THE SCREEN
|
|
int windowWidth = 1920;
|
|
int windowHeight = 1080;
|
|
|
|
// get the real framebuffer size
|
|
glfwGetFramebufferSize (window, &windowWidth, &windowHeight);
|
|
|
|
// initialize glew
|
|
if (glewInit () != GLEW_OK)
|
|
{
|
|
fprintf (stderr, "Failed to initialize GLEW");
|
|
glfwTerminate ();
|
|
return 3;
|
|
}
|
|
|
|
// initialize custom context class
|
|
WallpaperEngine::Render::CContext* context = new WallpaperEngine::Render::CContext (screens);
|
|
// initialize mouse support
|
|
context->setMouse (mouseInput);
|
|
// set the default viewport
|
|
context->setDefaultViewport ({0, 0, windowWidth, windowHeight});
|
|
|
|
if (project->getType () == "scene")
|
|
{
|
|
WallpaperEngine::Core::CScene* scene = project->getWallpaper ()->as <WallpaperEngine::Core::CScene> ();
|
|
wallpaper = new WallpaperEngine::Render::CScene (scene, containers, context);
|
|
}
|
|
else if (project->getType () == "video")
|
|
{
|
|
// special steps, running a video needs a root directory change, files are not loaded from the container classes
|
|
// as they're streamed from disk
|
|
chdir (path.c_str ());
|
|
|
|
WallpaperEngine::Core::CVideo* video = project->getWallpaper ()->as <WallpaperEngine::Core::CVideo> ();
|
|
wallpaper = new WallpaperEngine::Render::CVideo (video, containers, context);
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error ("Unsupported wallpaper type");
|
|
}
|
|
|
|
// ensure the context knows what wallpaper to render
|
|
context->setWallpaper (wallpaper);
|
|
|
|
if (shouldEnableAudio == true)
|
|
{
|
|
int mixer_flags = MIX_INIT_MP3 | MIX_INIT_FLAC | MIX_INIT_OGG;
|
|
|
|
if (SDL_Init (SDL_INIT_AUDIO) < 0 || mixer_flags != Mix_Init (mixer_flags))
|
|
{
|
|
// Mix_GetError is an alias for SDL_GetError, so calling it directly will yield the correct result
|
|
// it doesn't matter if SDL_Init or Mix_Init failed, both report the errors through the same functions
|
|
fprintf (stderr, "Cannot initialize SDL audio system, SDL_GetError: %s", SDL_GetError ());
|
|
return 2;
|
|
}
|
|
|
|
// initialize audio engine
|
|
Mix_OpenAudio (22050, AUDIO_S16SYS, 2, 640);
|
|
}
|
|
|
|
// TODO: FIGURE OUT THE REQUIRED INPUT MODE, AS SOME WALLPAPERS USE THINGS LIKE MOUSE POSITION
|
|
// glfwSetInputMode (window, GLFW_STICKY_KEYS, GL_TRUE);
|
|
|
|
// enable depth text
|
|
glEnable (GL_DEPTH_TEST);
|
|
glDepthFunc (GL_LESS);
|
|
|
|
// cull anything that doesn't look at the camera (might be useful to disable in the future)
|
|
glDisable (GL_CULL_FACE);
|
|
|
|
double minimumTime = 1.0 / maximumFPS;
|
|
double startTime = 0.0;
|
|
double endTime = 0.0;
|
|
|
|
while (glfwWindowShouldClose (window) == 0)
|
|
{
|
|
// get the real framebuffer size
|
|
glfwGetFramebufferSize (window, &windowWidth, &windowHeight);
|
|
// set the default viewport
|
|
context->setDefaultViewport ({0, 0, windowWidth, windowHeight});
|
|
// calculate the current time value
|
|
g_Time = (float) glfwGetTime ();
|
|
// get the start time of the frame
|
|
startTime = glfwGetTime ();
|
|
// update our inputs first
|
|
mouseInput->update ();
|
|
// render the scene
|
|
context->render ();
|
|
// do buffer swapping
|
|
glfwSwapBuffers (window);
|
|
// poll for events (like closing the window)
|
|
glfwPollEvents ();
|
|
// get the end time of the frame
|
|
endTime = glfwGetTime ();
|
|
|
|
// ensure the frame time is correct to not overrun FPS
|
|
if ((endTime - startTime) < minimumTime)
|
|
usleep ((minimumTime - (endTime - startTime)) * CLOCKS_PER_SEC);
|
|
}
|
|
|
|
// terminate gl
|
|
glfwTerminate ();
|
|
// terminate SDL
|
|
SDL_Quit ();
|
|
// terminate free image
|
|
FreeImage_DeInitialise ();
|
|
// free paths
|
|
free(finalPath);
|
|
|
|
return 0;
|
|
} |