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