first commit

This commit is contained in:
Vyn 2024-06-02 20:03:02 +02:00
commit 0d840f0d50
9 changed files with 519 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build
.cache

16
CMakeLists.txt Normal file
View file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.29.3)
project(
SwayWallpaper
VERSION 1.0
LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
add_executable(sway-wallpaper
src/main.cpp
src/Wallpapers.cpp
src/SwaybgProcess.cpp
)

43
README.md Normal file
View file

@ -0,0 +1,43 @@
# Sway Wallpaper
Display wallpapers on Sway, this is essentially a wrapper for `swaybg`.
## Features
- Specify a directory containing all your wallpapers
- Specify for which display
- Set interval to automatically change wallpaper after some time
- Wallpapers can be chosen randomly
- Wallpapers can be chosen in alphabetical order
- No "grey" flash when switching wallpapers
## Usage
```sh
usage: sway-wallpaper [options] <path> <output>
required:
output The outputs as shown with the command `swaymsg -t get_outputs`
path The path to the directory containing the wallpapers
options:
--always-random if set, always randomize the next wallpaper
-h, --help Show help
-i, --interval Specify the interval between
--never-random if set, wallpapers order is based on the names of the files
```
## Installation
First, clone this repository `git clone https://codeberg.org/vyn/sway-wallpaper.git` (or download it the way you prefer).
You only need **gcc** and **cmake**, run these commands in the project directory:
```
mkdir build
cd build
cmake ..
make
```
The executable should be located in `build/sway-wallpaper`

225
src/CliArguments.h Normal file
View file

@ -0,0 +1,225 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <exception>
#include <iomanip>
#include <iostream>
#include <map>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace CliArguments {
enum Type {
String,
Int,
Bool
};
struct Argument {
std::vector<std::string> aliases;
std::string description;
Type type = String;
short order = 0;
bool required = false;
bool direct = false;
};
class CliArguments {
public:
CliArguments(int argc, char** argv, std::map<std::string, Argument> arguments)
: argc_(argc), argv_(argv), arguments_(arguments) {
registerArgs(argc, argv, arguments);
}
std::string getString(const std::string& argName) const {
auto value = argumentsValue_.find(argName);
if (value == argumentsValue_.end()) {
throw std::logic_error("trying to access non existant argument '" + argName + "'");
}
return value->second;
}
int getInt(const std::string& argName) const {
auto valueStr = getString(argName);
try {
return std::stoi(valueStr);
} catch (std::exception& e) {
throw std::logic_error("trying to access argument '" + argName + "' (value: '" + valueStr + "')" + " as integer");
}
}
int isInt(const std::string& argName) const {
auto valueStr = getString(argName);
for (const auto& ch : valueStr) {
if (!isdigit(ch)) {
return false;
}
}
return true;
}
bool getBool(const std::string& argName) const {
auto valueStr = getString(argName);
if (valueStr != "true" && valueStr != "false") {
throw std::logic_error("trying to access argument '" + argName + "' (value: '" + valueStr + "')" + " as bool");
}
return valueStr == "true";
}
int isBool(const std::string& argName) const {
auto valueStr = getString(argName);
return valueStr == "true" || valueStr == "false";
}
bool exists(const std::string& argName) const {
auto value = argumentsValue_.find(argName);
return value != argumentsValue_.end();
}
void printDebug() const {
for (const auto& [key, arg] : arguments_) {
std::string value = "-";
if (argumentsValue_.find(key) != argumentsValue_.end()) {
value = argumentsValue_.find(key)->second;
}
std::cout << key << ": " << value << std::endl;
}
}
void printHelp() const {
int longestArgLength = 0;
for (const auto& [key, arg] : arguments_) {
if (key.length() > longestArgLength) {
longestArgLength = key.length();
}
}
std::cout << "usage: sway-wallpaper [options]" << std::endl;
for (const auto& arg : directArguments_) {
std::cout << " <" << arg << ">";
}
std::cout << std::endl;
std::cout << "required:" << std::endl;
for (const auto& [key, arg] : arguments_) {
if (!arg.direct) {
continue;
}
std::cout << std::left << std::setw(longestArgLength + 10) << (" " + key) << arg.description << std::endl;
}
std::cout << std::endl;
std::cout << "options:" << std::endl;
for (const auto& [key, arg] : arguments_) {
if (arg.direct) {
continue;
}
std::string argWithAliases = " ";
for (const auto& alias : arg.aliases) {
argWithAliases += "-" + alias + ", ";
}
argWithAliases += "--" + key;
std::cout << std::left << std::setw(longestArgLength + 10) << argWithAliases;
std::cout << arg.description << std::endl;
}
}
bool isValid() const {
return error_ == "";
}
const std::string& error() const {
return error_;
}
private:
void registerArgs(int argc, char** argv, std::map<std::string, Argument> arguments) {
for (auto& [key, arg] : arguments) {
if (arg.direct) {
directArguments_.push_back(key);
}
for (auto& alias : arg.aliases) {
aliases_[alias] = key;
}
}
std::ranges::sort(directArguments_, [&](const std::string& s1, const std::string& s2) {
return arguments_[s1].order < arguments_[s2].order;
});
int currentDirectFieldIndex = 0;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg.starts_with("-")) {
auto argName = extractArgName(arg);
if (!argName.has_value()) {
setError("'" + arg + "' is not a valid argument");
return;
}
if (arguments_[argName.value()].type == Bool) {
argumentsValue_[argName.value()] = "true";
return;
}
++i;
if (i >= argc) {
setError("Missing value for argument '" + arg + "'");
return;
}
std::string argValue = std::string(argv[i]);
argumentsValue_[argName.value()] = argValue;
} else if (currentDirectFieldIndex >= directArguments_.size()) {
setError("Too many argument, unknown '" + arg + "'");
return;
} else {
std::string& argName = directArguments_[currentDirectFieldIndex];
argumentsValue_[argName] = arg;
++currentDirectFieldIndex;
}
}
if (currentDirectFieldIndex != directArguments_.size()) {
setError("Missing required arguments");
}
}
std::optional<std::string> extractArgName(const std::string& arg) const {
if (arg.length() > 2 && arg.starts_with("--")) {
std::string argName = arg.substr(2);
if (arguments_.find(argName) == arguments_.end()) {
return std::nullopt;
}
return argName;
} else if (arg.length() == 2 && arg.starts_with("-")) {
std::string argAlias = arg.substr(1);
const auto& alias = aliases_.find(argAlias);
if (alias == aliases_.end()) {
return std::nullopt;
}
std::string argName = alias->second;
return argName;
}
return std::nullopt;
}
void setError(std::string error) {
error_ = error;
}
std::string error_;
int argc_;
char** argv_;
std::vector<std::string> directArguments_;
std::map<std::string, Argument> arguments_;
std::map<std::string, std::string> argumentsValue_;
std::map<std::string, std::string> aliases_;
};
}

57
src/SwaybgProcess.cpp Normal file
View file

@ -0,0 +1,57 @@
#include "SwaybgProcess.h"
#include <csignal>
#include <vector>
std::vector<SwaybgProcess*> processes;
void signalHandler(int sig) {
std::cout << "received signal: " << sig << std::endl;
for (const auto& process : processes) {
process->kill();
}
exit(1);
}
SwaybgProcess::SwaybgProcess(const std::string& wallpaperPath, const std::string& output) : wallpaperPath(wallpaperPath), output(output) {
static bool signalHandlerCreated = false;
if (!signalHandlerCreated) {
signalHandlerCreated = true;
signal(SIGTERM, signalHandler);
}
processes.push_back(this);
}
SwaybgProcess::~SwaybgProcess() { processes.erase(std::ranges::find(processes, this)); }
void SwaybgProcess::exec() {
if (pipe(fds) == -1) {
throw std::runtime_error("Can't create pipe");
}
pid = fork();
if (pid == 0) {
dup2(fds[1], STDOUT_FILENO);
close(fds[0]);
close(fds[1]);
execl("/bin/swaybg", "/bin/swaybg" , "-o", output.c_str(), "-i", wallpaperPath.c_str(), "-m", "fill", (char*)0) ;
} else if (pid > 0) {
killed = false;
close(fds[1]);
//int nbytes = read(fds[0], buf, sizeof(buf));
//printf("Output: (%.*s)\n", nbytes, buf);
} else {
throw std::runtime_error("Can't fork");
}
}
void SwaybgProcess::kill() {
if (killed) {
return;
}
killed = true;
::kill(pid, SIGKILL);
int status;
::waitpid(pid, &status, 0);
}

33
src/SwaybgProcess.h Normal file
View file

@ -0,0 +1,33 @@
#pragma once
#include <iostream>
#include <sched.h>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#define SWAYBGPROCESS_BUFFER_SIZE 4094
class SwaybgProcess {
public:
SwaybgProcess(const std::string& wallpaperPath, const std::string& output);
~SwaybgProcess();
void exec();
void kill();
private:
std::string wallpaperPath;
std::string output;
pid_t pid;
bool killed = true;
int fds[2]; // file descriptors for pipe
char buf[SWAYBGPROCESS_BUFFER_SIZE];
ssize_t nbytes;
};

27
src/Wallpapers.cpp Normal file
View file

@ -0,0 +1,27 @@
#include "Wallpapers.h"
#include <algorithm>
#include <filesystem>
#include <iostream>
Wallpapers::Wallpapers(const std::string& directoryPath) : directoryPath(directoryPath) {
for (const auto& entry : std::filesystem::directory_iterator(directoryPath)) {
wallpapersPath.push_back(entry.path());
}
for (const std::string& wallpaperPath : wallpapersPath) {
std::cout << wallpaperPath << std::endl;
}
e1 = std::default_random_engine(r());
uniformDist = std::uniform_int_distribution<int>(0, wallpapersPath.size());
}
void Wallpapers::shuffle() {
wallpapersPathQueue = wallpapersPath;
std::ranges::shuffle(wallpapersPathQueue, e1);
}
const std::string& Wallpapers::next() {
const std::string& wallpaperPath = wallpapersPathQueue[currentWallpaperIndex];
currentWallpaperIndex = (currentWallpaperIndex + 1) % wallpapersPathQueue.size();
return wallpaperPath;
}

25
src/Wallpapers.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include <random>
#include <string>
#include <vector>
class Wallpapers {
public:
Wallpapers(const std::string& directoryPath);
void shuffle();
const std::string& next();
private:
std::string directoryPath;
std::vector<std::string> wallpapersPath;
std::vector<std::string> wallpapersPathQueue;
std::random_device r;
std::default_random_engine e1;
std::uniform_int_distribution<int> uniformDist;
int currentWallpaperIndex = 0;
};

91
src/main.cpp Normal file
View file

@ -0,0 +1,91 @@
#include "CliArguments.h"
#include "Wallpapers.h"
#include "SwaybgProcess.h"
#include <iostream>
#include <ostream>
#include <string>
int main(int argc, char** argv, char** envp) {
CliArguments::CliArguments arguments{
argc, argv,
{
{"path", {
.description = "The path to the directory containing the wallpapers",
.type = CliArguments::String,
.order = 0,
.direct = true
}},
{"output", {
.description = "The outputs as shown with the command `swaymsg -t get_outputs`",
.type = CliArguments::String,
.order = 1,
.direct = true
}},
{"interval", {
.aliases = {"i"},
.description = "Specify the interval between",
.type = CliArguments::Int,
}},
{"never-random", {
.description = "if set, wallpapers order is based on the names of the files",
.type = CliArguments::Bool,
}},
{"always-random", {
.description = "if set, always randomize the next wallpaper",
.type = CliArguments::Bool,
}},
{"help", {
.aliases = {"h"},
.description = "Show help",
.type = CliArguments::Bool,
}},
}
};
if (!arguments.isValid()) {
std::cout << arguments.error() << std::endl;
arguments.printHelp();
return 1;
}
if (arguments.exists("help")) {
arguments.printHelp();
return 0;
}
const std::string path = arguments.getString("path");
const std::string output = arguments.getString("output");
Wallpapers wallpapers(path);
if (!arguments.exists("never-random")) {
wallpapers.shuffle();
}
SwaybgProcess swayBgProcess(wallpapers.next(), output);
swayBgProcess.exec();
if (arguments.exists("interval")) {
int interval = arguments.getInt("interval");
while (true) {
sleep(interval);
if (arguments.exists("always-random")) {
wallpapers.shuffle();
}
SwaybgProcess newSwayBgProcess(wallpapers.next(), output);
newSwayBgProcess.exec();
sleep(1);
swayBgProcess.kill();
swayBgProcess = newSwayBgProcess;
}
} else {
char ch;
while (ch != 'q') {
ch = std::cin.get();
}
}
swayBgProcess.kill();
return 0;
}