first commit
This commit is contained in:
commit
0d840f0d50
9 changed files with 519 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
.cache
|
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal 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
43
README.md
Normal 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
225
src/CliArguments.h
Normal 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
57
src/SwaybgProcess.cpp
Normal 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
33
src/SwaybgProcess.h
Normal 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
27
src/Wallpapers.cpp
Normal 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
25
src/Wallpapers.h
Normal 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
91
src/main.cpp
Normal 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;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue